Redis java客户端
Jedis
- Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
- Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。
- Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接(像BIO)
- 使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis仅支持基本的数据类型如:String、Hash、List、Set、Sorted Set。
为什么说Jedis是非线程安全的呢?
因为发送命令和获取返回值时使用全局变量RedisOutputStream和RedisInputStream(redis.clients.jedis.Connection类中)。
当不同的线程在set和get的时候,有可能会出现线程A的set()的响应流,被线程B的get()作为返回了,所以出现了线程安全问题。
通过JedisPool连接池去管理实例,在多线程情况下让每个线程有自己的独立的jedis实例,从而避免了线程安全问题。
Lettuce
- Lettuce 是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。支持Redis 的高级功能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型
- Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,因为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例.
- 高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
- 基于Netty框架的事件驱动的通信层(NIO),其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。
- springboot2.0之后,原来使用的Jedis 被替换为Lettuce
Redisson
- Redisson实现了分布式和可扩展的Java数据结构,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列。和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
- Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用。
- 基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。
- Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用,还提供了许多分布式服务,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)。
Lettuce实现步骤
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.properties 配置文件
spring.redis.host=xxx.xxx.xxx.xxx
spring.redis.port=6379
spring.redis.password=
#连接超时时间(毫秒)
spring.redis.timeout=10000ms
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池最大阻塞等待时间,单位毫秒(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=1000ms
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.shutdown-timeout=100ms
SpringbootRedisApplicationTests :
自动注入容器中的redisTemplate 实例(在redis的自动装配里面会给spring容器注入一个redisTemplate实例),而redisTemplate 是封装了对redis数据库的一系列操作
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Test
void contextLoads() {
System.out.println(redisTemplate);
redisTemplate.opsForValue().set("name","xt");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
成功连接上服务器上的redis完成基本操作
在不是应用基本数据结构的情况下,比如实体类的情况下,我们需要对实体类进行序列化
User 类
(使用lombok插件,减少手写setter/getter,constructor方法)
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String name;
private int age;
private String sex;
}
SpringbootRedisApplicationTests 新加一个test方法
@Test
public void test() throws JsonProcessingException {
User user = new User("xt",23,"男");
//将对象转化为json格式的字符串
String jsonuser = new ObjectMapper().writeValueAsString(user);
System.out.println(jsonuser);
redisTemplate.opsForValue().set("userXT",jsonuser);
System.out.println(redisTemplate.opsForValue().get("userXT"));
}
如果不传入序列化后的key,value,或者不能实现序列化的key,value,就会引发以下异常
要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis
使用springboot-data-redis,默认情况下是使用org.springframework.data.redis.serializer.JdkSerializationRedisSerializer这个类来做序列化
所以你也可以给实体类上实现Serializable接口
public class User implements Serializable
自定义工具类
我们可以自己封装一个工具类,以redisTemplate的API为基础,再度封装。先在工具类中注入spring容器中的redisTemplate
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
在使用的地方注入redisUtil 实例即可,@Component标注的RedisUtil, Spring 会帮我们生成redisUtil实例
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Autowired
private RedisUtil redisUtil;
@Test
void contextLoads() {
System.out.println(redisTemplate);
redisTemplate.opsForValue().set("name","xt");
System.out.println(redisTemplate.opsForValue().get("name"));
}
@Test
public void test() throws JsonProcessingException {
User user = new User("xt",23,"男");
//将对象转化为json格式的字符串
// String jsonuser = new ObjectMapper().writeValueAsString(user);
// System.out.println(jsonuser);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.opsForValue().set("userXT", user);
System.out.println(redisTemplate.opsForValue().get("userXT"));
}
@Test
public void testRedisUtils(){
System.out.println(redisUtil);
User user = new User("xt",23,"男");
redisUtil.set("userXT",user);
System.out.println(redisUtil.get("userXT"));
}
}
扩展阅读
META-INF/spring.factories文件的作用是什么
查看 spring-data-redis jar包的信息包文件夹META-INF下的 spring.factories,仅有一个 RepositoryFactorySupport 并无自动装配。
org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.redis.repository.support.RedisRepositoryFactory
在下载的jar包文件中,我们可以看到有个spring-boot-autoconfigure jar包
在 jar包的信息包文件夹META-INF中的spring.factories文件
里面有这样的一部分配置,key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,value是各个以逗号隔开的*AutoConfiguration,结合spring-factories的运行原理,我们就可以知道所有的自动配置是从这里开始加载的。
可以在这个文件中找到redis的自动配置类RedisAutoConfiguration
把鼠标放到自动配置类RedisAutoConfiguration上 按 Ctrl-B 进入,看见他会帮我们new一个RedisTemplate实例(redisTemplate)放到spring的IOC容器中
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
//当这个redisTemplate bean实例不存在,下面的这个方法就生效
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
@Configuration
用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
proxyBeanMethods参数设置为false时即为:Lite 轻量级模式。该模式下注入容器中的同一个组件无论被取出多少次都是不同的bean实例,即多实例对象,在该模式下SpringBoot每次启动会跳过检查容器中是否存在该组件
什么时候用Full全模式,什么时候用Lite轻量级模式?
当在你的同一个Configuration配置类中,注入到容器中的bean实例之间有依赖关系时,建议使用Full全模式;当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系时,建议使用Lite轻量级模式,以提高springboot的启动速度和性能
@ConditionalOnClass表示只有classpath中能找到RedisOperations.class时,RedisAutoConfiguration这个类才会被spring容器实例化,这其实也解释了为什么我们有时候只需要在pom.xml添加某些依赖包,某些功能就自动打开了
关与 @EnableConfigurationProperties 注解
@EnableConfigurationProperties引入了RedisProperties的配置,将使用了@ConfigurationProperties 注解的RedisProperties类生效,注入到spring的容器中
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
@EnableConfigurationProperties({RedisProperties.class})
Ctrl-B 进入RedisProperties类,我们可以看到他里面封装了很多属性,host和port有默认值
我们也可以发现application.properties中的前缀为spring.redis的配置是用在这里面的
注解@ConfigurationProperties
redis 配置由 springBoot 集成,在 spring-boot-autoconfigure 中配置,spring.factories 中 EnableAutoConfiguration 指定自动配置类 RedisAutoConfiguration 进行自动配置,spring-configuration-metadata.properties 指定了配置元数据 RedisAutoConfiguration ConditionalOnClass 配置条件 RedisOperations,当引入 spring-data-redis 时存在类路径 RedisOperations 进行 redis 的自动加载。redis 自动配置使用 Lettuce 作为 client 进行连接,配置了默认的 bean redisTemplate、StringRedisTemplate。
RedisTemplate
@ConditionalOnMissingBean表示在Spring容器中如果有一个Bean的name是redisTemplate那将不需要再执行被此注解修饰的代码块,也就是此方法。
如果我们自己写了一个redisTemplate bean,那么我们获取的就是自己写的那个bean 实例。
redisTemplate 里面通过set注入了一个redisConnectionFactory
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
//当这个redisTemplate bean实例不存在,下面的这个方法就生效
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
redisConnectionFactory是一个接口,里面提供了两个实现
分别是JedisConnectionFactory 和 LettuceConnectionFactory
进入JedisConnectionFactory类,点击右上角悬浮条下载源码
可以发现很多类会爆红,无法引入(springboot 2.0 已经抛弃 Jedis)
但是LettuceConnectionFactory不会出现这种情况
RedisTemplate 序列化
要缓存的JavaBean必须实现Serializable接口,因为Spring会将对象先序列化再存入 Redis
RedisTemplate 有如下字段需要序列化
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
默认的序列化方式是JDK序列化
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
当然你也可以自己修改redisTemplate指定的序列化实现类
public void setKeySerializer(RedisSerializer<?> serializer) {
this.keySerializer = serializer;
}
RedisSerializer是一个Redis序列化的接口,我们可以看到这个接口又有很多的实现类
References:
- https://blog.csdn/liujun03/article/details/82891784
- https://www.bilibili/video/BV1S54y1R7SB?p=25
- https://xie.infoq/article/59431718520a87b1a59b5ef9a
- https://cloud.tencent/developer/article/1500854
- https://wwwblogs/myitnews/p/13733882.html
- https://blog.csdn/song_java/article/details/86509971
更多推荐
spring boot集成redis以及redis的自动装配
发布评论