在Redis官网中提供了各种语言的客户端。
地址:https://redis.io/clients
前三名客户端是jedis、lettuce、redisson。
jedis:以Redis命令作为方法名称,学习成本低,简单实用。但是jedis实例是线程不安全的,所以每个线程都要建立单独的jedis势力,所以多线程环境下需要基于连接池来使用。
lettuce:是基于netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。
redisson:是一个基于Redis实现的分布式、可伸缩的Java数据结构集合。包含了诸如Map、Queue、Lock、Semaphore、AtomicLong等强大功能。
使用Spring Data Redis就等于包含了jedis和lettuce。不过不少公司依然用jedis。
jedisjedis官网:https://github.com/redis/jedis
jedis使用的基本步骤1,引入依赖
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.7.0version>
dependency>
2,建立链接
private Jedis jedis;
@BeforeEach
void setUp() {
// 建立链接
jedis = new Jedis("IP地址",端口);
// 设置密码
jedis.auth("123123");
// 选择库
jedis.select(0);
}
3,测试
@Test
void testString() {
// 插入数据,方法名称就是Redis命令名称,非常简单
String result = jedis.set("name","asd");
print(result);// ok
// 获取数据
String name = jedis.get("name");
print(name);
}
@Test
void testHash () {
// 插入hash数据
jedis.hset("user:1","name","asd");
jedis.hset("user:1","age","22");
// 获取
Map<String,String> map = jedis.hgetAll("user:1");
print(map);
}
// 其他的都一样,所以省略
4,释放资源
@AfterEach
void tearDown() {
// 释放资源
if(jedis != null) jedis.close();
}
jedis连接池
jedis本身是线程不安全的,并且频繁的创建和销毁链接会有性能损耗,因此推荐使用jedis连接池来替代jedis直连方式。因为多线程时,由于jedis线程不安全,所以每个线程需要各自的jedis实例,因此会有频繁的创建和销毁的情况。
1,定义工具类
public class JedisConnectionFactory {
private static final JedisPool jedisPool;// jedis连接池
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 做配置的
// 最大链接
jedisPoolConfig.setMaxTotal(8);
// 最大空闲链接
jedisPoolConfig.setMaxIdle(8);
// 最小空闲链接
jedisPoolConfig.setMinIdle(8);
// 设置最长等待时间,ms毫秒。连接池里没链接是,等多长时间。
jedisPoolConfig.setMaxWaitMillis(1000);
// 1000是超时时间?
jedisPool = new JedisPool(jedisPoolConfig,"Redis的IP地址",端口,1000,"123123密码");
}
// 获取jedis对象
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
然后把上面的jedis = new Jedis(“IP地址”,端口); 改成如下:
jedis = JedisConnectionFactory.getJedis();
至于释放资源的jedis.close();里面的源码中可以看到有连接池的话直接归还到连接池了,所以有连接池时close方法调用后就不会销毁,而是把该链接干入到连接池。
SpringDataRedisspringdata是spring中数据 *** 作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做springdataRedis。
官网是:https://spring.io/projects/spring-data-redis
springdataredis中提供了Redistemplate工具类,其中封装了各种对Redis的 *** 作。并且将不同数据类型的 *** 作API封装到了不同的类型中:
API-----返回值类型-----说明
redisTemplate.opsForValue()-----ValueOperations----- *** 作string类型数据
redisTemplate.opsForHash()-----HashOperations----- *** 作hash类型数据
redisTemplate.opsForList()-----ListOperations----- *** 作list类型数据
redisTemplate.opsForSet()-----SetOperations ----- *** 作set类型数据
redisTemplate.opsForZSet()-----ZSetOperations----- *** 作sortedset类型数据
redisTemplate-------------------------------------------------通用的命令
springboot提供了对springdataRedis的支持。
1,引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
2,配置文件
spring:
redis:
host: Redis的IP地址
port: 端口
password: 密码
lettuce:
pool:
max-active: 8 #最大链接
max-idle: 8 #最大空闲链接
min-idle: 0 #最小空闲链接
max-wate: 100 #链接等待时间
3,注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
4,编写测试
@SpringBootTest
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString() {
// 插入一条string类型数据
redisTemplate.opsForValue().set("name","张三");
// 读取一条string类型数据
Object name = redisTemplate.opsForValue().get("name");
print(name);
}
}
执行之前,假如在终端用命令设置name为李四。执行程序后,设置了张三,取出来也是张三。但此时到了终端再取出时依然是李四。在终端执行keys *之后,的确能看见有个key是“name”,但还能看见“\xac\xed\x00\x05t\x00\x04name”这个。在终端执行get "\xac\xed\x00\x05t\x00\x04name"时能看到这个才是张三。这就要说到序列化了,在代码中redisTemplate.opsForValue().set(“name”,“张三”);这里接受的参数类型并不是string,都是Object,这就是springdataRedis的特殊功能,她可以接收任何类型的对象,然后帮我们把他转成Redis可以处理的字节,所以我们存进去的"name"和"张三"都被当成了Java对象,而Redistemplate底层默认对这些对象的处理方式就是利用JDK的序列化工具ObjectOutputStream,这个流的作用就是把Java对象转成字节,然后把这个字节写入Redis之后,就是刚才那个结果了。
springdataRedis的序列化方式redisTemplate可接受任意object作为值写入Redis,只不过写入前会把object序列化为字节形式,默认是采用JDK序列化,得到的结果就是刚才那样。(key也被序列化,所以刚才明明改了name,却不是改了name。内存占用也大,明明是name,但整出为\xac\xed\x00\x05t\x00\x04name,明明只是张三,却整出为一堆向name那种东西。)
缺点:可读性差、内存占用较大
所以为了实现写什么就是写什么,所以必须去改变Redistemplate的默认序列化模式。默认用的是RedisSerializer接口的JdkSerializationRedisSerializer实现类。所以可以使用其他的实现类。比如专门处理string的是StringRedisSerializer这个实现类,在key为字符串情况下,可以用这个。
如果是value,可能不只是字符串,有可能是对象,建议使用GenericJackson2JsonRedisSerializer这个实现类。所以key一般用StringRedisSerializer,value一般用GenericJackson2JsonRedisSerializer。
@Configuration
public class RedisConfig {
@Bean
public Redistemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throw UnknownHostException {
// 创建template
Redistemplate<String,Object> redisTemplate = new Redistemplate<>();
// 设置链接工厂(Redistemplate需要链接工厂,springboot自动创建的使用即可)
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// key和hashkey采用string序列化(RedisSerializer.string()返回StringRedisSerializer.UTF_8)
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
// value和hashvalue采用JSON序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
return redisTemplate;
}
}
然后如下改:
@Autowired
private RedisTemplate<String,Object> redisTemplate;
还有在pom.xml里添加jackson依赖,因为用到了JSON序列化。(因为这个例子没引用springMVC依赖,所以要手动引入Jackson依赖,如果依赖了springMVC,则不需要,因为springMVC自带她)
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
这样的话,上面的问题就解决了。
还可以设置对象,比如有User.java。
@Test
void test() {
redisTemplate.opsForValue().set("user:1",new User("asd",12));
Object user = redisTemplate.opsForValue().get("user:1");//也可以强转成User
print(user);
}
在图形界面客户端查的话,可以发现Redis里存入的值是json:
{
"@class": "com.asd.User"
"name": "asd"
"age": 12
}
这说明他为我们自动化转为JSON写入到了Redis,然后取的时候又自动的反序列化成User了。
能自动做反序列化是因为有"@class": "com.asd.User"这个属性,所以知道要把结果转成哪个对象,没有这个就不行,虽然序列化好说,但反序列化就难了,所以这个玩意是自动反序列化必不可少的。
StringRedistemplate尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,比如"@class": "com.asd.User"这玩意也被包含在值上。为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入到JSON结果中,存入Redis,会带来额外的内存开销(一个还行,要是成千上万个,就不得了)。
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用string序列化器(也就是RedisSerializer.string()),要求只能存储string类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
不用去修改刚才自定义的RedisTemplate的设置了,因为spring提供了StringRedistemplate类,她的key和value序列化方式默认都是string方式,省去了自定义RedisTemplate的过程。
@Autowired
private StringRedisTemplate stringRedisTemplate;
// json工具(这是spring默认提供的,也可以使用其他的)
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testStringTemplate() throws JsonProcessingException {
// 准备对象
User user = new User("asd",19);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入一条数据到Redis
stringRedisTemplate.opsForValue().set("user:1",json);
// 读取数据
String val = stringRedisTemplate.opsForValue().get("user:1");
// 反序列化
User user1 = mapper.readValue(val,User.class);
print(user1);
}
@Test
void testString() {
// 插入一条string类型数据
stringRedisTemplate.opsForValue().set("name","张三");
// 读取一条string类型数据
Object name = stringRedisTemplate.opsForValue().get("name");
print(name);
}
@Test
void testHash() {
stringRedisTemplate.opsForHash().put("user:1","name","张三");
stringRedisTemplate.opsForHash().put("user:1","age","21");
Map<Object,Object> map = stringRedisTemplate.opsForHash().entries("user:1");
print(map);
// stringRedisTemplate.opsForHash()中的其他方法这里省略。
}
总结:RedisTemplate的两种序列化实践方案
方案1:
1,自定义RedisTemplate
2,修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
1,使用StringRedisTemplate
2,写入Redis时,手动把对象序列化为JSON
3,读取Redis时,手动把读取到的JSON反序列化为对象
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)