一、 操作 Redis的3种实现对比
不同点说明
1.1、Jedis1.2、Lettuce1.3、Redisson1.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.properties1.4、redis的配置类
1.4.1、公共配置抽取1.4.2、第一个redis数据源1.4.2、第二个redis数据源 1.5、controller1.6、注入说明与测试效果
1.6.1、注入方式说明1.6.2、测试效果
一、 操作 Redis的3种实现对比
- JedisLettuceRedisson
不同点说明 1.1、Jedis共同点:都提供了基于Redis操作的Java API,只是封装程度,具体实现稍有不同。
- 是Redis的Java实现的客户端。支持基本的数据类型5种:String、Hash、List、Set、Sorted Set。
特点:使用阻塞的I/O,方法调用同步,程序流需要等到socket处理完I/O才能执行,不支持异步操作。Jedis客户端实例不是线程安全的,需要通过连接池来使用Jedis。
用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
基于Netty框架的事件驱动的通信层,其方法调用是异步的。
Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。
有2个缺陷就是高并发下没有及时回收导致OOM和间接性断连问题`
优点:分布式锁,分布式集合,可通过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,吞吐量大
lettuce 操作netty的时候,没有及时的进行内存释放,导致OutOfDirectMemoryError
2种解决办法:
第一种:
lettuce的高并发下没有及时回收内存的bug导致:
1、 netty堆外内存溢出
2、 netty如果没有指定堆外内存,他默认使用 -Xmx300m
3、 这个问题:可以通过netty的-Dio.netty.maxDirectMemory进行设置,但是治标不治本,加大-Xmx的配置,但是没有及时得到内存释放,一定会出现这个异常
第二种:
不采用Lettuce作为底层,切换成Jedis。等官方更新Lettuce客户端
lettuce 的Connection长时间会断开,导致 RedisCommandTimeoutException
- 因为Socket连接断已经是事实,而且在分布式环境中,网络分区是必然的。在网络环境,Redis 服务器主动断掉连接是很正常的,【lettuce 的作者也提及 lettuce 一天发生一两次重连是很正常的】
RedisCommandTimeoutException解决方案:
第一种:netty提供另一个参数的设置:TCP_USER_TIMEOUT,这个参数就是为了针对单独设置某个应用程序的超时重传的设置
第二种:lettuce提供了NettyCustomizer进行扩展,netty所提供的【心跳机制–IdleStateHandler】
【心跳机制】
1、 分析客户端自己做心跳检测,一旦发现Channel死了,主动关闭ctx.close(),那么ChannelInactived事件一定会被触发了。
2、 缺点:增加了客户端的压力
- 在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依赖
1.3、application.properties1.8 2.11.0 org.springframework.boot spring-boot-starter-data-redis io.lettuce lettuce-core redis.clients jedis org.springframework.boot spring-boot-configuration-processor true com.fasterxml.jackson.core jackson-core ${jackson.version} com.fasterxml.jackson.core jackson-annotations ${jackson.version} com.fasterxml.jackson.core jackson-databind ${jackson.version} com.fasterxml.jackson.dataformat jackson-dataformat-xml ${jackson.version}
# 端口 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;
@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;
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;
}
//
// @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;
}
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;
@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;
@Bean
public RedisConnectionFactory defaultRedisConnectionFactory() {
return createJedisConnectionFactory(dbIndex, host, port, password, timeout);
}
@Bean(name = "defaultRedisTemplate")
public RedisTemplate defaultRedisTemplate() {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(defaultRedisConnectionFactory());
setSerializer(template);
template.afterPropertiesSet();
return template;
}
}
1.4.2、第二个redis数据源
参数配置类
package sqy.config.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@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;
@Configuration
@EnableCaching
public class SecondRedisConfig extends RedisConfig {
@Autowired
SecondProperties secondProperties;
@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);
}
@Bean(name = "redisTemplate2")
public RedisTemplate redisTemplate2() {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(cacheRedisConnectionFactory());
setSerializer(template);
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;
@RestController
public class RedisController {
@Resource(name = "defaultRedisTemplate")
RedisTemplate redisTemplate;
@Resource(name = "redisTemplate2")
private RedisTemplate redisTemplate2;
@GetMapping("redis01Test")
public void redis01Test() throws JsonProcessingException {
List 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);
}
@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属性,并且默认为truerequired = true 注入bean的时候该bean必须存在,不然就会注入失败!启动就报错required = false 注入bean的时候如果bean存在,就注入成功,如果没有就忽略跳过,启动不会报错。 但是不能直接使用,因为doRequiredTest为NULL!
@Resource 默认按照名称进行注入
1.6.2、测试效果有两个重要属性,分别是name和type



