文章目录
- 一、 操作 Redis的3种实现对比
- 不同点说明
- 1.1、Jedis
- 1.2、Lettuce
- 1.3、Redisson
- 1.4、spring再次封装redisTemplete源码
- 二、Lettuce概要
- 1.1、bug总结
- 1.1.1、OOM堆外内存溢出问题与方案
- 1.1.2、Connection断连问题与方案
- 1.1.3、Netty防止内存泄露常识
- 三、整合redis(多数据源)
- 1.1、工程结构
- 1.2、pom依赖
- 1.3、application.properties
- 1.4、redis的配置类
- 1.4.1、公共配置抽取
- 1.4.2、第一个redis数据源
- 1.4.2、第二个redis数据源
- 1.5、controller
- 1.6、注入说明与测试效果
- 1.6.1、注入方式说明
- 1.6.2、测试效果
一、 操作 Redis的3种实现对比
- Jedis
- Lettuce
- Redisson
共同点
:都提供了基于Redis操作的Java API,只是封装程度,具体实现稍有不同。
不同点说明
1.1、Jedis
- 是Redis的Java实现的客户端。
- 支持基本的数据类型5种:String、Hash、List、Set、Sorted Set。
特点:使用阻塞的I/O,方法调用同步,程序流需要等到socket处理完I/O才能执行,不支持异步操作。Jedis客户端实例不是线程安全的,需要通过连接池来使用Jedis。
1.2、Lettuce
- 用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
- 基于Netty框架的事件驱动的通信层,其方法调用是异步的。
- Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。
- 有2个缺陷就是高并发下没有及时回收导致OOM和间接性断连问题`
1.3、Redisson
优点:分布式锁,分布式集合,可通过Redis支持延迟队列。
可以整合其他实现如redis、springcache、等…
1.4、spring再次封装redisTemplete源码
- redisTemplete:lettuce、jedid操作redis的底层客户端。
- spring再次封装redisTemplete 在 (RedisAutoConfiguration)自动配置里面能看见
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
二、Lettuce概要
SpringBoot2.0以后默认使用 Lettuce作为操作redis的客户端
- springboot2.0以后默认使用lettuce作为操作redis的客户端,他使用
netty
进行网络通讯 - Lettuce新,使用netty,吞吐量大
1.1、bug总结
1.1.1、OOM堆外内存溢出问题与方案
lettuce 操作netty的时候,没有及时的进行内存释放,导致OutOfDirectMemoryError
2种解决办法:
第一种:
lettuce的高并发下没有及时回收内存的bug导致:
1、 netty堆外内存溢出
2、 netty如果没有指定堆外内存,他默认使用 -Xmx300m
3、 这个问题:可以通过netty的-Dio.netty.maxDirectMemory进行设置,但是治标不治本,加大-Xmx的配置,但是没有及时得到内存释放,一定会出现这个异常
第二种:
不采用Lettuce作为底层,切换成Jedis。等官方更新Lettuce客户端
1.1.2、Connection断连问题与方案
lettuce 的Connection长时间会断开,导致
RedisCommandTimeoutException
- 因为Socket连接断已经是事实,而且在分布式环境中,网络分区是必然的。
- 在网络环境,Redis 服务器主动断掉连接是很正常的,【lettuce 的作者也提及 lettuce 一天发生一两次重连是很正常的】
RedisCommandTimeoutException解决方案:
第一种:netty提供另一个参数的设置:TCP_USER_TIMEOUT,这个参数就是为了针对单独设置某个应用程序的超时重传的设置
第二种:lettuce提供了NettyCustomizer进行扩展,netty所提供的【心跳机制–IdleStateHandler】
【心跳机制】
1、 分析客户端自己做心跳检测,一旦发现Channel死了,主动关闭ctx.close(),那么ChannelInactived事件一定会被触发了。
2、 缺点:增加了客户端的压力
1.1.3、Netty防止内存泄露常识
- 在AbstractNioByteChannel.NioByteUnsafe.read() 处创建了ByteBuf并调用 pipeline.fireChannelRead(byteBuf) 送入Handler链。
- 根据上面的谁最后谁负责原则,每个Handler对消息可能有三种处理方式
- 对原消息不做处理,调用 ctx.fireChannelRead(msg)把原消息往下传,那不用做什么释放。
- 如果已经不再调用ctx.fireChannelRead(msg)传递任何消息,那更要把原消息release掉。
- 假设每一个Handler都把消息往下传,Handler并也不知道谁是启动Netty时所设定的Handler链的最后一员,所以Netty在Handler链的最末补了一个TailHandler,如果此时消息仍然是ReferenceCounted类型就会被release掉。
三、整合redis(多数据源)
1.1、工程结构
1.2、pom依赖
<properties>
<java.version>1.8</java.version>
<!--版本控制-->
<jackson.version>2.11.0</jackson.version>
</properties>
<!--spring-boot-starter-data-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- 排除lettuce。这个采用netty通讯目前有2bug。oom和断连-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- 这个包的作用是生成配置元数据官网推荐引入 @ConfigurationProperties(prefix = "spring.redis02")-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
</dependency>
1.3、application.properties
# 端口
server.port=8080
# 应用名称
spring.application.name=boot-redis
# ===============redis的配置-开始===============
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis服务器连接端口
spring.redis.port=6379
# Redis数据库索引(默认为0)【相当于库的意思】
spring.redis.database=0
# 连接超时时间(毫秒)
spring.redis.timeout=6000
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=1000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=5
#-----第二个redis数据源-----
spring.redis02.host=127.0.0.1
spring.redis02.password=
spring.redis02.port=6379
spring.redis02.database=5
spring.redis02.timeout=6000
# ===============redis的配置-结束===============
1.4、redis的配置类
1.4.1、公共配置抽取
package sqy.config.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
/**
* Redis配置类
* @EnableCaching注解是spring framework中的注解驱动的缓存管理功能。
* 自spring版本3.1起加入了该注解。如果你使用了这个注解,那么你就不需要在配置cache manager
* 当你在配置类(@Configuration)上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。
* 如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的bean执行处理。
* @author suqinyi
* @Date 2022/1/12
*/
@SuppressWarnings("all")//压制警告
//@EnableCaching
@Configuration
public class RedisConfig {
@Value("${spring.redis.jedis.pool.max-active}")
private int redisPoolMaxActive;
@Value("${spring.redis.jedis.pool.max-wait}")
private int redisPoolMaxWait;
@Value("${spring.redis.jedis.pool.max-idle}")
private int redisPoolMaxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int redisPoolMinIdle;
/**
* 创建redis连接工厂
*/
public JedisConnectionFactory createJedisConnectionFactory(int dbIndex, String host, int port, String password, int timeout) {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setDatabase(dbIndex);
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
jedisConnectionFactory.setPassword(password);
jedisConnectionFactory.setTimeout(timeout);
jedisConnectionFactory.setPoolConfig(setPoolConfig(redisPoolMaxIdle, redisPoolMinIdle, redisPoolMaxActive, redisPoolMaxWait, true));
return jedisConnectionFactory;
}
// /**
// * 配置CacheManager
// */
// @Bean
// public CacheManager cacheManager(RedisTemplate redisTemplate) {
// RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
// return redisCacheManager;
// }
/**
* 设置连接池属性
*/
public JedisPoolConfig setPoolConfig(int maxIdle, int minIdle, int maxActive, int maxWait, boolean testOnBorrow) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxTotal(maxActive);
poolConfig.setMaxWaitMillis(maxWait);
poolConfig.setTestOnBorrow(testOnBorrow);
return poolConfig;
}
/**
* 设置RedisTemplate的序列化方式
*/
public void setSerializer(RedisTemplate template) {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
}
}
1.4.2、第一个redis数据源
package sqy.config.redis;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @author suqinyi
* @Date 2022/1/12
*/
@Configuration
@EnableCaching
public class OneRedisConfig extends RedisConfig {
@Value("${spring.redis.database}")
private int dbIndex;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
/**
* 配置redis连接工厂
*/
@Bean
public RedisConnectionFactory defaultRedisConnectionFactory() {
return createJedisConnectionFactory(dbIndex, host, port, password, timeout);
}
/**
* 配置redisTemplate
* 注入方式使用@Resource(name="") 方式注入
*/
@Bean(name = "defaultRedisTemplate")
public RedisTemplate defaultRedisTemplate() {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(defaultRedisConnectionFactory());
setSerializer(template);
/**
* 非spring注入使用RedisTemplate,需先调用afterPropertiesSet()方法
* 不然会报:JedisConnectionFactory was not initialized through afterPropertiesSet()
*/
template.afterPropertiesSet();
return template;
}
}
1.4.2、第二个redis数据源
参数配置类
package sqy.config.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 第二个redis参数配置
*/
@ConfigurationProperties(prefix = "spring.redis02")
@Component
public class SecondProperties {
private String host;
private String password;
private int port;
private int database;
private int timeout;
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getDatabase() {
return database;
}
public void setDatabase(int database) {
this.database = database;
}
}
第二数据源配置
package sqy.config.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @author suqinyi
* @Date 2022/1/12
*/
@Configuration
@EnableCaching
public class SecondRedisConfig extends RedisConfig {
@Autowired
SecondProperties secondProperties;
/**
* 配置redis连接工厂
* @Primary 默认会注入@Primary配置的组件
*/
@Primary
@Bean
public RedisConnectionFactory cacheRedisConnectionFactory() {
int dbIndex= secondProperties.getDatabase();
int port= secondProperties.getPort();
String host= secondProperties.getHost();
String password= secondProperties.getPassword();
int timeout = secondProperties.getTimeout();
return createJedisConnectionFactory(dbIndex, host, port, password, timeout);
}
/**
* 配置redisTemplate
* 注入方式使用@Resource(name="") 名称注入
*/
@Bean(name = "redisTemplate2")
public RedisTemplate redisTemplate2() {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(cacheRedisConnectionFactory());
setSerializer(template);
/**
* 非spring注入使用RedisTemplate,需先调用afterPropertiesSet()方法
* 不然会报:JedisConnectionFactory was not initialized through afterPropertiesSet()
*/
template.afterPropertiesSet();
return template;
}
}
1.5、controller
package sqy.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import sqy.pojo.Student;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* * @author suqinyi
* * @Date 2022/1/12
* * 测试redis
*/
@RestController
public class RedisController {
/**
* 采用@Resource注入 。名称注入
*/
@Resource(name = "defaultRedisTemplate")
RedisTemplate redisTemplate;
@Resource(name = "redisTemplate2")
private RedisTemplate<String,String> redisTemplate2;
/**
* 使用【第一个redis数据源】 存入list类型
* localhost:8080/redis01Test
*/
@GetMapping("redis01Test")
public void redis01Test() throws JsonProcessingException {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("张三","123456"));
studentList.add(new Student("李四","789456"));
//jackson
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(studentList);
//存 5分钟
redisTemplate.opsForValue().set("studentList", json,5, TimeUnit.MINUTES);
//取
String list = (String)redisTemplate.opsForValue().get("studentList");
System.out.println(list);
}
/**
* 使用【第二个redis数据源】 存入string字符串
* localhost:8080/redis02Test
*/
@GetMapping("redis02Test")
public void redis02Test() {
String key="student:name";
//存 有效期5分钟
redisTemplate2.opsForValue().set(key+"qq", "存入字符串",5, TimeUnit.MINUTES);
//取
String s = redisTemplate2.opsForValue().get(key + "qq");
System.out.println(s);
}
}
1.6、注入说明与测试效果
1.6.1、注入方式说明
@Autowired 默认按照类型进行注入
- required属性,并且默认为true
- required = true 注入bean的时候该bean必须存在,不然就会注入失败!启动就报错
- required = false 注入bean的时候如果bean存在,就注入成功,如果没有就忽略跳过,启动不会报错。 但是不能直接使用,因为doRequiredTest为NULL!
@Resource 默认按照名称进行注入
有两个重要属性,分别是name和type
1.6.2、测试效果