栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springboot整合redis(多数据源)与lettuce问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springboot整合redis(多数据源)与lettuce问题

文章目录

一、 操作 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

共同点:都提供了基于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依赖

  1.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}
 

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;


@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 默认按照名称进行注入

有两个重要属性,分别是name和type

1.6.2、测试效果

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/703974.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号