springboot使用redis的两种方式与redis数据淘汰策略

springboot使用redis的两种方式与redis数据淘汰策略

redis的使用真的就是使用模板存取吗? 幼稚,那要增减呢! 所以今天聊聊redis的springbooot的两种使用方法。

1、springboot使用redis的方式

1.1、采用redisTemplate

它不是简单的引入模板就可以的,需要封装。因为模板仅仅可以操作字符串,配置redistemplate的序列化方式之后就可以顺利的执行increment、decrement。

①导包
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
②配置(需要配置密码的自己配置)
spring:
	  redis:
	    host: localhost
	    port: 6379
③封装类
@Component
@Slf4j
public class RedisService {
    @Autowired
    protected RedisTemplate redisTemplate;
    /**
     * 配置redistemplate的序列化方式之后就可以顺利的执行increment、decrement
     * @param redisTemplate
     */
    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        //序列化为String
        RedisSerializer stringSerializer = new StringRedisSerializer();
        //序列化为Json
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer(Object.class);
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        this.redisTemplate = redisTemplate;
    }

    /**
     * 对字符串的操作,存入key
     * @param key
     * @param value
     * @return
     */
    public boolean setString(String key,String value){
        boolean state = true;
        try{
            redisTemplate.opsForValue().set(key,value);
        }catch (Exception e){
            state = false;
            log.info("redis string type,set error,key:"+key+",value:"+value+",msg:"+e.getMessage());
        }
        return state;
    }

    /**
     * 对字符串的操作,获取key
     * @param key
     * @return
     */
    public String getString(String key){
        try{
            Object data = redisTemplate.opsForValue().get(key);
            return String.valueOf(data);
        }catch (Exception e){
            log.info("redis string type,get error,key:"+key+",msg:"+e.getMessage());
        }
        return null;
    }

    /**
     * 给key指定过期时间
     * @param key
     * @param expire
     * @return
     */
    public boolean expire(final String key, long expire) {
        boolean state = true;
        try{
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }catch (Exception e){
            state = false;
            log.info("redis string type,set error,key:"+key+",value:"+expire+",msg:"+e.getMessage());
        }
        return state;
    }

    /**
     * 给key赋值为Map类型的value
     * @param key
     * @param value
     * @return
     */
    public boolean setMap(String key, Map value) {
        boolean state = true;
        try {
            redisTemplate.opsForHash().putAll(key, value);
        } catch (Exception e) {
            state = false;
            log.info("redis string type,set error,key:" + key + ",value:" + value + ",msg:" + e.getMessage());
        }
        return state;
    }

    /**
     * 获取key对应的值是一个Map
     * @param key
     * @return
     */
    public Map getMap(String key) {
        try {
            return redisTemplate.opsForHash().entries(key);
        } catch (Exception e) {
            log.info("redis string type,get error,key:" + key + ",msg:" + e.getMessage());
        }
        return null;
    }

    /**
     * 给指定的key赋值value,并指定过期时间
     * @author liaochao
     * @Date 2020.04.09
     * @param key
     * @return
     */
    public <T> boolean setCacheValueForTimes(String key, T value,long time,TimeUnit tu){
        boolean state = true;
        try{
            redisTemplate.opsForValue().set(key, value, time, tu);
        }catch (Exception e){
            e.printStackTrace();
            state = false;
            log.info("redis string type,set error,key:"+key+",value:"+value+",msg:"+e.getMessage());
        }
        return state;
    }

    /**
     * 对指定的key进行自增1
     * @author liaochao
     * @Date 2020.04.09
     * @param key
     * @return
     */
    public long testInckey(String key){
        long result = 0;
        try{
            result =  redisTemplate.boundValueOps(key).increment(1);
        }catch (Exception e){
            e.printStackTrace();
            log.info("redis string type,set error,key:temporary,value:"+result+",msg:"+e.getMessage());
            result = -1;
        }
        return result;
    }
    /**
     * 对指定的key进行自减1
     * @author liaochao
     * @Date 2020.04.09
     * @param key
     * @return
     */
    public long testDeckey(String key){
        long result = 0;
        try{
            result =  redisTemplate.boundValueOps(key).decrement(1);

        }catch (Exception e){
            e.printStackTrace();
            log.info("redis string type,set error,key:temporary,value:"+result+",msg:"+e.getMessage());
            result = -1;
        }
        return result;
    }
    /**
     * 获取key对应的value值,传入value的类型,获取出来也是转换了类型的value值
     * @author liaochao
     * @Date 2020.04.09
     * @param key
     * @return
     */
    public <T> T getValue(String key,Class<T> clazz){
        try{
            String s = ""+ redisTemplate.opsForValue().get(key);
            T value = JSON.parseObject(s,clazz);
            return value;
        }catch (Exception e){
            e.printStackTrace();
            log.info("redis string type,get error,key:"+key+",msg:"+e.getMessage());
        }
        return null;
    }

    /**
     * 删除key
     * @param key
     * @return
     */
    public boolean delete(String key){
        boolean result = false;
        try{
            result = redisTemplate.delete(key);
        }catch (Exception e){
            log.info("redis string type,set error,key:"+key+",msg:"+e.getMessage());
        }
        return result;
    }
}

这样在其他地方使用,引入这个类就可以了。这里没有写操作集合的,读者自己完成

1.2 、采用Jedis
①导包:
 <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.7.3</version>
        </dependency>
②配置yml
spring:
	  redis:
	    host: localhost
	    password: redis
	    port: 6379
	    # Redis数据库索引(默认为0)
	    database: 0
	    # 连接超时时间(毫秒)
	    timeout: 0
	    jedis:
	      pool:
	        # 最大连接池数
	        max-active: 100
	        # 连接池最大阻塞等待时间(使用负值表示没有限制)
	        max-wait: 20
	        # 连接池中的最小空闲连接
	        min-idle: 0
	        # 连接池中的最大空闲连接
	        max-idle: 100
③写一个JedisPool配置类
@Configuration // 配置类
@EnableCaching // 开启缓存(可不写这个注解)
public class RedisConfig {

    @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;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWaitMillis;

    @Bean
    public JedisPool getJedisPool() {
        log.info("JedisPool注入成功!!");
        log.info("redis地址:" + host + ":" + port);
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis * 1000);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
        return jedisPool;
    }
}
④封装redis的操作方法的类
@Service
public class RedisService {
    @Autowire
    private JedisPool jedisPool;

    private Jedis getResource() {
        return jedisPool.getResource();
    }
    private void returnResource(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
    // 获取
    public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = getResource();
            String str = jedis.get(prefix.getPrefix() + key);
            return JSON.parseObject(str, clazz);
        } finally {
            returnResource(jedis);
        }
    }
    // 存储
    public <T> boolean set(KeyPrefix prefix, String key, T value) {
        if (value == null || key == null) {
            return false;
        }
        Jedis jedis = null;
        try {
            jedis = getResource();
            if (prefix.expireSeconds() <= 0) {
                jedis.set(prefix.getPrefix() + key, JSON.toJSONString(value));
            } else {
                // 设置有效期
                jedis.setex(prefix.getPrefix() + key, prefix.expireSeconds(), JSON.toJSONString(value));
            }
            log.info("存入redis,key={}", (prefix.getPrefix() + key));
            return true;
        } finally {
            returnResource(jedis);
        }
    }
    // 判断key是否存在
    public boolean exists(KeyPrefix prefix, String key) {
        if (key == null) {
            return false;
        }
        Jedis jedis = null;
        try {
            jedis = getResource();
            return jedis.exists(prefix.getPrefix() + key);
        } finally {
            returnResource(jedis);
        }
    }
    // 数字值增加一
    public Long incr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = getResource();
            return jedis.incr(prefix.getPrefix() + key);
        } finally {
            returnResource(jedis);
        }
    }
    // 数字值减少一
    public Long decr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = getResource();
            return jedis.decr(prefix.getPrefix() + key);
        } finally {
            returnResource(jedis);
        }
    }
    // 删除
    public boolean delete(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = getResource();
            return jedis.del(prefix.getPrefix() + key) > 0;
        } finally {
            returnResource(jedis);
        }
    }

这样在其他地方使用时,引入这个RedisService 就可以了。

题外话:今天遇到double类型保留两位小数的方法
例如:a是一个Double类型,有很多位小数,要把它保留两位。 BigDecimal b = new BigDecimal(a);
Double s = b.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
这样得到的s就是a保留两位小数的结果了!

spring boot 2.0后集成的redis,在高并发的情况下报 异常(对外内存溢出异常): Redis exception; nested exception is io.lettuce.core.RedisException: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate37748736 byte(s) of direct
原因有如下:
1、spring boot 2.0以后默认使用的是lettuce作为操作redis的客户端。它使用netty进行网络通信;
2、lettuce的bug导致netty堆外内存溢出,netty如果没有指定堆外内存,默认使用 -Xmx100m(堆的大小);
netty可以通过
-Dio.netty.maxDirectMemory进行设置;
解决方案:不要使用-Dio.netty.maxDirectMemory进行调大堆外内存;
a、升级lettuce 客户端;
b、切换到jedis;

例如:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

这样引包就可以排除lettuce的bug,而使用jedis作底层连接。那就可以放心使用springboot2.0
去集成redisTemplate不会出现高并发下的对外内存溢出

2、 缓存失效问题

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将>去查询数据库,但是数 据库也无此记录,我们没有将这次查询的 null写入缓存,这将导致这个不存在的数据每次 请求都要到存储层去查询,失去了缓存的意义
解决办法:缓存空结果、并且设置短的过期时间

缓存击穿对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问, 是一种非常“热点”的数据。
这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所 有对这个 key 的数据查询都落到
db,我们称为缓存击穿
解决办法:加锁(单体部署就加本地锁,分布式的话就要用分布式锁)

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失 效,请求全部转发到 DB,DB 瞬时压力过重雪崩
解决办法:原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的 重复率就会降低,就很难引发集体失效的事件

3、redis数据淘汰策略

一共8种

  1. volatile-lru,针对设置了过期时间的key,使用lru算法进行淘汰。
  2. allkeys-lru,针对所有key使用lru算法进行淘汰。
  3. volatile-lfu,针对设置了过期时间的key,使用lfu算法进行淘汰。
  4. allkeys-lfu,针对所有key使用lfu算法进行淘汰。
  5. volatile-random,从所有设置了过期时间的key中使用随机淘汰的方式进行淘汰。
  6. allkeys-random,针对所有的key使用随机淘汰机制进行淘汰。
  7. volatile-ttl,删除生存时间最近的一个键。
  8. noeviction(默认),不删除键,值返回错误。 主要是4种算法,针对不同的key,形成的策略。 算法:

4种算法

lru LRU是Least Recently Used的缩写,也就是表示最近很少使用,也可以理解成最久没有使用
lfu LFU(Least Frequently Used),表示最近最少使用,它和key的使用次数有关,其思想是:根据key最近被访问的频率进行淘汰,比较少访问的key优先淘汰,反之则保留。
random 随机淘汰 ttl 快要过期的先淘汰 key :
volatile 有过期的时间的那些key allkeys 所有的key

工作原理
客户端执行一条新命令,导致数据库需要增加数据(比如set key value) Redis会检查内存使用,如果内存使用超过
maxmemory,就会按照置换策略删除一些 key 新的命令执行成功

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神雕大侠mu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值