替你踩过Redis缓存的坑,奉上使用规范和监控方法

一、前言
在互联网应用中 , 缓存成为高并发架构的关键组件 。 本文主要介绍缓存使用的典型场景、实操案例分析、Redis使用规范及常规Redis监控 。
二、常见缓存对比
常见的缓存方案:本地缓存包括HashMap/ConcurrentHashMap、Ehcache、Memcache、Guava Cache等 , 缓存中间件包括Redis、Tair等 。
替你踩过Redis缓存的坑,奉上使用规范和监控方法文章插图
三、Redis使用场景
1、计数
Redis实现快速计数及缓存功能 。
例如:视频或直播在线观看人数 , 用户每播放一次 , 就会自增1 。
2、Session集中管理
Session可以存储在应用服务是JVM中 , 但这一种方案会有一致性的问题 , 还有高并发下 , 会引发JVM内存溢出 。 Redis将用户的Session集中管理 , 这种情况下只要保证Redis的高可用和扩展性 , 每次用户更新或查询登录都直接从Redis中信息获取 。
3、限速
例如:高并发的秒杀活动 , 使用incrby命令实现原子性递增 。
例如:业务要求用户一分钟内 , 只能获取5次验证码 。
4、排行榜
关系型数据库在排行榜方面查询速度普遍偏慢 , 所以可以借助redis的SortedSet进行热点数据的排序 。
比如在项目中 , 如果需要统计主播的吸金排行榜 , 可以以主播的id作为member, 当天打赏的活动礼物对应的热度值作为 score, 通过zrangebyscore就可以获取主播活动日榜 。
5、分布式锁
在实际的多进程并发场景下 , 使用分布式锁来限制程序的并发执行 。 多用于防止高并发场景下 , 缓存被击穿的可能 。
分布式锁的实际就是"占坑" , 当另一个进程来执行setnx时 , 发现标识位已经为1 , 只好放弃或者等待 。
四、案例解析
1、过期设置- set命令会去掉过期时间
Redis所有的数据结构 , 都可以设置过期时间 。 如果一个字符串已经设置了过期时间 , 然后重新设置它 , 会导致过期时间消失 。 所以在项目中需要合理评估Redis容量 , 避免因为频繁set导致没有过期策略 , 间接导致内存被占满 。
如下是 Redis 源码截图:
替你踩过Redis缓存的坑,奉上使用规范和监控方法文章插图
2、Jedis 2.9.0及以下版本过期设置BUG
发现Jedis在进行expiredAt命令调用时有bug , 最终调用的是pexpire命令 , 这个bug会导致key过期时间很长 , 导致Redis内存溢出等问题 。 建议升级到Jedis 2.9.1及以上版本 。
BinaryJedisCluster.java源码如下:
@Override
public Long pexpireAt(final byte[] key, final long millisecondsTimestamp) {
return new JedisClusterCommand(connectionHandler, maxAttempts) {
@Override
public Long execute(Jedis connection) {
return connection.pexpire(key, millisecondsTimestamp); //此处pexpire应该是pexpireAt
}
}.runBinary(key);
}
对比pexpire和pexpireAt:
替你踩过Redis缓存的坑,奉上使用规范和监控方法文章插图
比如我们当前使用的时间是2018-06-14 17:00:00 , 它的unix时间戳为1528966800000毫秒 , 当我们使用PEXPIREAT命令时 , 由于是过去的时间 , 相应的key会立即过期 。
而我们误用了PEXPIRE命令时 , key不会立即过期 , 而是等到1528966800000毫秒后才过期 , key过期时间会相当长 , 约几W天后 , 从而可能导致Redis内存溢出、服务器崩溃等问题 。
3、缓存被击穿
缓存的key有过期策略 , 如果恰好在这个时间点对这个Key有大量的并发请求 , 这些请求发现缓存过期一般都会从后端DB回源数据并回设到缓存 , 这个时候大并发的请求可能会瞬间把后端DB压挂 。
业界常用优化方案有两种:

  • 第一种:使用分布式锁 , 保证高并发下 , 仅有一个线程能回源后端DB;
  • 第二种:保证高并发的请求到的Redis key始终是有效的 , 使用非用户请求回源后端 , 改成主动回源 。 一般可以使用异步任务进行缓存的主动刷新 。
4、Redis-standalone架构禁止使用非0库
Redis执行命令select 0和select 1切换 , 造成性能损耗 。
RedisTemplate在执行execute方法的时候会先获取链接 。
替你踩过Redis缓存的坑,奉上使用规范和监控方法文章插图
执行到RedisConnectionUtils.java , 会有一段获取链接的方法 。
替你踩过Redis缓存的坑,奉上使用规范和监控方法文章插图
JedisConnectionFactory.java 会调用
JedisConnection构造器 , 注意这边的dbIndex就是数据库编号 , 如:1
替你踩过Redis缓存的坑,奉上使用规范和监控方法文章插图