一、 操作 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
spring.redis.host=127.0.0.1
spring.redis.password=
spring.redis.port=6379
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
spring.redis02.host=127.0.0.1
spring.redis02.password=
spring.redis02.port=6379
spring.redis02.database=5
spring.redis02.timeout=6000
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")
@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;
}
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();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
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<String,String> redisTemplate2;
@GetMapping("redis01Test")
public void redis01Test() throws JsonProcessingException {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("张三","123456"));
studentList.add(new Student("李四","789456"));
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(studentList);
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";
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、测试效果
|