一、前言
在互联网利用中,缓存成为高并发架构的要害组件。这篇博客次要介绍缓存应用的典型场景、实操案例剖析、Redis 应用标准及惯例 Redis 监控。
二、常见缓存比照
常见的缓存计划,有本地缓存,包含 HashMap/ConcurrentHashMap、Ehcache、Memcache、Guava Cache 等,缓存中间件包含 Redis、Tair 等。
三、Redis 应用场景
1. 计数
Redis 实现疾速计数及缓存性能。
例如:视频或直播在线观看人数,用户每播放一次,就会自增 1。
2. Session 集中管理
Session 能够存储在应用服务是 JVM 中,但这一种计划会有一致性的问题,还有高并发下,会引发 JVM 内存溢出。Redis 将用户的 Session 集中管理,这种状况下只有保障 Redis 的高可用和扩展性,每次用户更新或查问登录都间接从 Redis 中信息获取。
3. 限速
例如:业务要求用户一分钟内,只能获取 5 次验证码。
4. 排行榜
关系型数据库在排行榜方面查问速度广泛偏慢,所以能够借助 redis 的 SortedSet 进行热点数据的排序。
比方在我的项目中,如果须要统计主播的吸金排行榜,能够以主播的 id 作为 member, 当天打赏的流动礼物对应的热度值作为 score, 通过 zrangebyscore 就能够获取主播流动日榜。
5. 分布式锁
在理论的多过程并发场景下,应用分布式锁来限度程序的并发执行。多用于避免高并发场景下,缓存被击穿的可能。
分布式锁的理论就是 ” 占坑 ”,当另一个过程来执行 setnx 时,发现标识位曾经为 1,只好放弃或者期待。
四、案例解析
1、【案例】过期设置 - set 命令会去掉过期工夫
Redis 所有的数据结构,都能够设置过期工夫。如果一个字符串曾经设置了过期工夫,而后从新设置它,会导致过期工夫隐没。所以在我的项目中须要正当评估 Redis 容量,防止因为频繁 set 导致没有过期策略,间接导致内存被占满。
如下是 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<Long>(connectionHandler, maxAttempts) {
@Override
public Long execute(Jedis connection) {return connection.pexpire(key, millisecondsTimestamp); // 此处 pexpire 应该是 pexpireAt
}
}.runBinary(key);
}
比照 pexpire 和 pexpireAt:
比方咱们以后应用的工夫是 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 办法的时候会先获取链接。
执行到 RedisConnectionUtils.java,会有一段获取链接的办法。
JedisConnectionFactory.java 会调用 JedisConnection 结构器,留神这边的 dbIndex 就是数据库编号,如:1
持续跟进 JedisConnection 代码,当抉择库大于 1 时,会有 select db 操作。如果始终应用 0 库是不须要额定执行切库命令的。晓得了第一个切库 select 1 的中央,那么 select 0 是哪来的呢?
其实客户端应用 Redis 也会是要开释链接的,只不过 RedisTemplate 曾经帮咱们主动开释了,让咱们再回到一开始 RedisTemplate 执行 execute(…)办法的中央。
上面还是 RedisConnectionUtils.java,执行链接敞开的代码。
按代码正文的意思,如果抉择库编号不为 0,spring-data-redis 框架每次都会执行重置 select 0!
笔者在 vivo 商城业务中,商品详情页接口通过下面的调优,性能进步了 3 倍多。
进一步验证数据库切换至多影响性能 3 倍左右(视具体业务而定)。
Rediscluster 集群数据库,默认 0 库,无奈抉择其余数据库,也就防止了这个问题。
5、【案例】当心工夫复杂度 o(n)Redis 命令
Redis 是单线程的,所以线程平安的。
Redis 应用非阻塞 IO,并且大部分命令的工夫复杂度 O(1)。
应用高耗时的命令是十分危险的,会占用惟一的一个线程的大量解决工夫,导致所有的申请都被拖慢。
例如:获取所有 set 汇合中的元素 smembers myset,返回指定 Hash 中所有的 member,工夫复杂度 O(N)。
缓存的 Value 汇合变大,当高并接口申请时,会从 Redis 读取相干数据,每个申请读取的工夫变长,一直的叠加,导致呈现热点 KEY 状况,Redis 某个分片处于阻塞,CPU 使用率达到 100%。
6、【案例】缓存热 key
在 Redis 中,拜访频率高的 key 称为热点 key,当某一热点 key 的申请到 Server 主机时,因为申请量特地大,导致主机资源有余,甚至宕机,影响失常的服务。
热 key 问题的产生,有如下 两种起因:
- 用户生产的数据远大于生产的数据,比方热卖商品或秒杀商品、热点新闻、热点评论等,这些典型的读多写少的场景会产生热点问题。
- 申请分片集中,超过单 Server 的性能极限,比方 固定名称 key,哈希落入一台 Server,访问量极大的状况,超过 Server 极限时,就会导致热点 Key 问题的产生。
那么在理论业务中,如何辨认到热点 key 呢?
- 凭借业务教训,进行预估哪些是热 key;
- 客户端统计收集,本地统计或者上报;
- 如果服务端有代理层,能够在代理层进行收集上报;
当咱们辨认到热 key,如何解决热 key 问题?
- Redis 集群扩容:减少分片正本,平衡读流量;
- 进一步对热 key 进行散列,比方将一个 key 备份为 key1,key2……keyN,同样的数据 N 个备份,N 个备份散布到不同分片,拜访时可随机拜访 N 个备份中的一个,进一步分担读流量。
- 应用二级缓存,即本地缓存。
当发现热 key 后,将热 key 对应数据首先加载到应用服务器本地缓存中,缩小对 Redis 的读申请。
五、Redis 标准
1、禁止应用非 database 0
阐明:
Redis-standalone 架构,禁止应用 Redis 中的其余 database。
原由:
- 为当前业务迁徙 Redis Cluster 放弃兼容性.
- 多个 database 用 select 切换时,更耗费 CPU 资源。
- 更易自动化运维治理,如 scan/dbsize 命令只用于当 database。
- 局部 Redis Clients 因线程平安问题,不反对单实例多 database。
2、Key 设计规范
按业务性能命名 key 前缀,避免 key 抵触笼罩,举荐 用冒号分隔,例如,业务名: 表名,如 live:rank:user:weekly:1:202003。
Key 的长度小于 30 个字符,Key 名字自身是 String 对象,Redis 硬编码限度最大长度 512MB。
在 Redis 缓存场景,举荐 Key 都设置 TTL 值,保障不应用的 Key 能被及时清理或淘汰。
Key 设计时禁止蕴含特殊字符,如空格、换行、单双引号以及其余转义字符。
3、Value 设计规范
单个 Value 大小必须管制 10KB 以内,单实例键个数过大,可能导致过期键的回收不及时。
set、hash、list 等简单数据类型,要尽量升高数据结构中的元素个数,倡议个数不要超过 1000。
4、关注命令工夫复杂度
举荐应用 O(1)命令,如 get scard 等。
O(N)命令关注 N 的数量,如下命令须要对 N 值在业务层面做管制。
- hgetall
- lrange
- smembers
- zrange
例如:smember 命令工夫复杂度为 O(n),当 n 继续减少时,会导致 Redis CPU 继续飙高,阻塞其余命令的执行;
5、Pipeline 应用
阐明:
Pipeline 是 Redis 批量提交的一种形式, 也就是把多个命令操作建设一次连贯发给 Redis 去执行, 会比循环的单次提交性能更优。
Redis 客户端执行一条命令分 4 个过程:发送命令 -> 命令排队 -> 命令执行 -> 返回后果。
罕用的 mget、mset 命令,无效节约 RTT(命令执行往返工夫),但 hgetall 并没有 mhgetall,是不反对批量操作的。此时,须要应用 Pipeline 命令
例如:直播中台我的项目中,须要同时查问主播日、周、月排行榜,应用 PIPELINE 一次提交多个命令,同时返回三个榜单数据。
6、线上禁用命令
- 禁止应用 Monitor
禁止生产环境应用 monitor 命令,monitor 命令在高并发条件下,会存在内存暴增和影响 Redis 性能的隐患
- 禁止应用 Keys
keys 操作是遍历所有的 key,如果 key 十分多的状况下导致慢查问,会阻塞其余命令。所以禁止应用 keys 及 keys pattern 命令。
倡议线上应用 scan 命令代替 keys 命令。
- 禁止应用 Flushall、Flushdb
删除 Redis 中所有数据库中的所有记录,并且该命令是原子性的,不会终止执行,一旦执行,将不会执行失败。
- 禁止应用 Save
阻塞以后 redis 服务器,直到长久化操作实现为止,对于内存较大的实例会造成长时间的阻塞。
- BGREWRITEAOF
手动 AOF,手动长久化对于内存较大的实例会造成长时间的阻塞。
- Config
Config 是客户端配置形式,不利于 Redis 运维。倡议在 Redis 配置文件中设置。
六、Redis 监控
1、慢查问
办法一:slowlog 获取慢查问日志
127.0.0.1:{port}> slowlog get 5
1) 1) (integer) 47
2) (integer) 1533810300
3) (integer) 175833
4) 1) “DEL”
2) “spring:session:expirations:1533810300000”
2) 1) (integer) 46
2) (integer) 1533810300
3) (integer) 117400
4) 1) “SMEMBERS”
办法二:更全面的慢查问能够通过 CacheCloud 工具监控。
门路:” 利用列表 ”- 点击相干利用名 - 点击 ” 慢查问 ”Tab 页。
点击 ” 慢查问 ”,重点关注慢查问个数及相干命令。
2、监控 Redis 实例绑定的 CPU 外围使用率
因为 Redis 是单线程,重点监控 Redis 实例绑定的 CPU 外围使用率。
个别 CPU 资源使用率为 10% 左右,如果使用率高于 20% 时,思考是否应用了 RDB 长久化。
3、Redis 分片负载平衡
以后 redis-cluster 架构模式,3 个 master 和 3 个 Slave 组成的集群,关注 Redis-cluster 每个分片 requests 流量平衡状况;
通过命令获取:redis-cli -p{port} -h{host} –stat
个别状况,超过 12W 须要告警。
4、关注大 key BigKey
通过 Redis 提供的工具,redis-cli 定时扫描相应 Redis 大 Key,进行优化。
具体命令如下:redis-cli -h 127.0.0.1 -p {port} –bigkeys 或 redis-memory-for-key -s {IP} -p {port} XXX_KEY
个别超过 10K 为大 key,须要重点关注,倡议从业务层面优化。
5、监控 Redis 占用内存大小
Info memory 命令查看,防止在高并发场景下,因为调配的 MaxMemory 被耗尽,带来的性能问题。
重点关注 used_memory_human 配置项对应的 value 值,增量过高时,须要重点评估。
七、总结
联合具体业务个性,正当评估 Redis 所需内存容量、抉择数据类型、设置单 key 大小,能力更好地服务于业务,为业务提供高性能的保障。
作者:Jessica Chen