开发者学堂课程【Redis 入门及实战:Redis 的开发规范和常见问题】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/777/detail/13651
Redis 的开发规范和常见问题
内容介绍:
一、 为什么要制定开发规则
二、 阿里内部系统的开发规约
三、 Redis 的常见问题处理
一、为什么要制定开发规则
这个课程的所有内容都是在解决 Redis 在设计时带来的问题,所以从 Redis 带来的问题进行讲解。
1、Redis- 从问题说起
Run-to-Completion in a solo thread – Redis 最大的问题
最大的问题:Redis 运行在一个单线程中。
如图:用户所有的来自不同 client 的请求,实际上在每个 event 到来后,交由后端单线程执行,等每个 event 处理完成后,才处理下一个。
单线程 run-to-completion in a solo thread-Redis 就是没有 dispatcher, 没有后端的 multl-worker。所以说,如果一旦有慢查询,比如:单机版的 keys、lrange、hgetall 等拖慢了一次查询,那么后面的请求可能会被拖慢。
使用 Sentine 判活的 trick:
ping 命令会受到慢查询影响,就会卡住,则 ping 失败。如果判活失败会导致主备频繁切换,一旦一个 db 的主备都当掉了,那么 Redis 就会 OOS。
如果一个进程不行,那就来一个风幕式及扩展成集群版会怎么样?
扩展成集群版确实能解决一部分问题,但常见的请求是可以分散到不同地域上去的。要注意的是,集群版还是解决不了单个 db 被卡主的问题。因为 Redis 的 key是规则,是按照外面的 Pkey 来做的。如果没有按照里面的子 key 或者是 field 来做,如果遇到了很大的 KKV 结构,同样会卡住。但一旦有其他请求,比如:mget、mset 之类的,或者卡住 db 上本来就有很多请求,仍然会 block 住及问题还是存在。
2、Protocol 问题-大量客户端与引擎 Full-Meshed 问题
扩展性比较差,基于 Question-Answer 模式,由于在 Question/Answer 里面没有对应的 Sequence 的存在,存储引擎端设法 match 请求和响应,只能采取 Run-To-Completion 来挂住链接。
当引擎挂住太多 active 链接的时候,性能下降太多,如果性能测试,当 db 等于10k active 链接时,性能下降约 30-35%,由于引擎挂住的链接不能被返回客户端,会大量报错。
看下面这张图:
“Could not get a resource from the pool”
由于 Redis 执行的采用 long to complition 特性,客户端只能采用连接池的方案,因为 Redis 协议不支持连接池收敛。
因为 Message 没有 Sequence ID,Request 和 Response 对不起来。
连接池具体运作方式是每次查询,都从连接池里面取出一个连接,当服务端返回结果之后,再放回连接池。
如果用户返回的及时,那么连接池一直保有的连续数并不高。
但是一旦返回不及时,又有新的请求,就只能再 checkout 一根连接。
当 Engine 层出现慢查询,就会让请求返回得慢。造成的后果是,很容易让用户把连接池用光。
当应用机器特别多时,按每个 client 连接池 50 个 max link 来算,很容易打到 10K连接的限制,导致 engine 回调速度慢。
当连接池被用完时,就会报没有连接的异常,“Could not get a resource from the pool”
,这是非常常见的错误。
特别的有恶性循环的在里面,比如:服务端返回得慢、连接池的连接会创建得很快,用户很容易达到一万条,这时候,创建连接越多,性能越差,返回越慢,服务容易雪崩掉。
3、Redis-不要触碰边界
出了这么多问题,是为了在开发业务时,不要触碰 Redis 的边界。
(1)Redis 的边界
这里从计算、存储、网络三个维度出发,总结出下图。
计算方面:Lua、Wildcard、PUBSUB、热点等会大量消耗计算资源。
存储方面:Streaming、Bigkey 等会造成高存储的消耗。
高网络消耗方面:Keys 全表扫描、大量的 Batch,mget,mset、大Value、Bigkey Range 查询,比如:hgetall、smembers 会造成比较大的网络消耗。
对于 Redis 边界来说:
高并发不等于高吞吐
因为存在大 Value 的问题,高速储存并不会有特别大的高吞吐收益,相反会很危险。QPS 如果高起来,容易把网卡打爆,导致限流。
数据倾斜和算力倾斜
bigKey 对的问题:break 掉存储的分配律,bigkey 大概率是热点问题,所以会导致 cpu 分配的不均衡。
大 Range 的问题:对 NoSQL 的慢查询和导致的危害没有足够的重视
储存边界
Lua 使用不当造成的存储成本直线上升
数据倾斜带来的成本飙升,无法有效使用
对于 Latency 的解决问题(RT高)
存储引擎的 Latency 都是 P99Latency,如:99.99% 在 1ms 以内,99.5% 在 3ms以内,等等。
偶发性时延高是必然的,这个根因在于储存引擎内部的复杂性和熵
所以客户端一定要足够认识到 Latency。
二、 阿里内部系统的开发规约
如何避免边界,需要制定规约。
1、Redis 的使用建议
推荐
确定场景,是缓存cache )还是存储型
Cache 的使用原则是:“无它也可 ,有它更强”
永远不要强依赖 Cache,它会丢,也会被海汰
优先设计合理的数据结构和逻辑
设计避免 bigKey,就避免了 80% 的问题
Keyspace 能分开,就多申请几个 Redis 实例
pubsub 不适合做消息分发
尽量避免用 lua 做事务
不建议
我的服务对 RT 很敏感→低 RT 能让我的服务运行的更好,一旦有个 RT 比较高,服务就会崩溃。存储引擎 Latency 都是 P99 计算,客户端一定要避免这种情况,增加一些容错处理。
我把存储都公用在一个 redis 里→区分 cache 和内存数据库用法,区分应用。会导致业务混用的问题,比如:cache 的用法、持久型的用法,这两个业务一旦在一起时,容易造成一些数据淘汰或者是Redis升级之后,会导致其他业务受到不可控的影响。
我有一个大排行榜/大集合/大链表/消息队列;我觉得服务能力足够了→尽量拆散,服务能力不够可通过分布式集群版可以打散,但是如果出现大 Key,分布式集群解决不了。
我有一个特别大的 Value,存在 redis 里,访问能好些 →redis 吞吐量有瓶颈。大Value 会拖慢引擎,网卡、或者限制,所以如果有大 Value,redis 吞吐量是有瓶颈的。
2、Bigkey- 洪水猛兽
据初步统计,80% 问题是由 bigkey 导致。
上图:集群中有 4 个分片,每个分片大约有 100 个 key,从 key 个数来看,是均匀分布;但是图中有 key301,hash301,下面有两百万个 field,说明是大 key,导致严重的数据倾斜。可能其他的用了 10% 或 20% 的内存,大 key 就用了百分之八九十,而且大 key 大概率是热点,所以上图里面的用法可能会导致一个分片内存满,返回有问题,其他分片就很少用。另一问题,这个分片的访问比较热,造成网卡或者打满,或者 cpu 的打满,导致一些限流发生,服务可能就夯住了。
3、Redis LUA JIT( LUA 脚本文启)
上示意图表示一次 LUA 脚本的执行过程,客户端调用 EVAL script 后,会产生script load 行为,Lua JIT 开始编译字节生成字节码,会产生 SHA 字符串,表示bytecode 缓存;loading bytecode 后,开始执行脚本,还需保证在副本上执行成功,最后是 unload Cleaning 整个过程结束。
示意图中小火图标表示耗费 cpu 的程度,脚本的 compile-load-run-unload 非常耗费 cpu。
整个 lua 相当于把复杂事务推送到 Redis 中执行,如果稍有不慎内存会爆掉,引擎算力耗光后挂住 redis。
优化办法:比如:script+EVAL、可以将脚本在 redis 中预编译或加载,使用EVALSHA 来执行,会比纯 EVAL 省 cpu,但是 Redis 重启、切换等时,脚本就会消失。需要重新 reload,业务上面需要做这项处理。但是仍然是缺陷方案,建议使用复杂数据结构,或者 module 来取代 lua。比如,tell 推出的 tell steam 或者 CNS 低命令,来实现风幕式锁。
对于 JIT 技术在存储引擎中而言,“EVAL is evil”(可以把 lua 看做魔鬼)尽量避免使用 lua 耗费内存和计算资源(省事省心),稍有不慎,就会出现大问题。
某些 SDK(如 Redisson)很多高级实现都内置使用 lua,开发者可能莫名走入 CPU 运算风暴中,须谨慎。
4、Pubsub/Transaction/Pipeline
Pubsub 的典型场景
悲观锁(通知更新)
简单信号(消息传递)
不适合:稳定的更新(会丢消息) ; 1 对 N 的消息转发通道(服务瓶颈) ;模糊通知(算力瓶颈)
Transaction、Pipeline 比较多时,会造成 cpu 打满,造成慢查、服务夯阻。
Transaction
伪事务,没有回滚条件
集群版需要所有 key 使用 hashtag 保证,打 hashtag 会导致算力和存储倾斜。
Lua 中封装了 multi-exec,但更耗费 CPU (编译,加载)。比如编译加载时,线上经常会出现这种问题。
Pipeline
看上图,实际是将多个请求封装在一个请求中,合并在一个请求里发送,服务端一次性返回,这样有效地提高 IO 提高执行效率,但是需要用户去聚合小的命令,避免在 pipeline 里做大 range。注意 Pipeline 中的批量任务不是原子执行的(从来不是)。所以要处理 Pipeline
其中几个命令失败的场景。
5、KESY 命令
结论:一定会出问题( sooner or later )
现在没有,到后面客户量上涨后,必然会引发问题。
出现后无能为力处理。
这种情况在一开始时可以预防,可以在控制台通过危险命令禁用,禁止剔除掉命令。
出现时,可以用一些手段优化。
KEYS 命令的模糊命令:
因为 redis 存储 key 是用无序的,必须全标扫描。
Key 数目一多必然卡住,所以一定要优化。
举个例子:可以修改为 hash 结构,可以从全表扫描变为点查或部分 range。虽然 hash 结构中 field 太多也会变慢,但比 key 性能提升一个到两个数量级。但是这个例子里,product1 前缀可以提取成 hash 的一个 key,然后,要取 product1 前缀的所有东西,可以下发一个 HGETALL,这样就表示优化。
6、除去 KEYS,下面命令依然危险。
hgetall , smembers , lrange,zrange,exhgetall
lrange,zrange,exhgetall 这些复杂难度较高,O(n),携带 value 比较大时,打爆网卡。
建议使用各自命令的 scan 来替代
bitop, bitset
设置过远的 bit 会直接导致 OOM
flushall,flushdb(表示清空数据库)
在阿里云上面有二次校验,在控制台里点击清除数据,会要求是使用者去二次校验,这是比较好的校验功能。在阿里云控制台上面可以单独清理过期数据,对其他正常访问的数据没有影响。
数据丢失
配置中和 ziplist 相关的参数
Redis 在存储相关数据时,数据量较小时,使用 ziplist 结构,达到一定的量级,比如 key 变多,field 变多,就会转换数据结构,比如,hash 就会从 ziplist 转换成ziplist table。当一个结构在 ziplist 结构体质下时,开销变大,比如查询时是 On 级别的,但是占用的内存变少,所以不建议用户随便更改 ziplist 参数。因为修改ziplist,让 hash 在非常大时继续使用 ziplist 结构,因为时间复杂度变成了 On 或者是 O(m*n),这时候是非常大的数量级的提升,hash 的普通操作也会比较慢。
7、规范总结:
1.选型:用户需要确定场景是 cache 还是内存数据库使用
Cache 选择单副本,关闭 AOF ;内存数据库选择双副本
如果 keyspace 能够分开,就申请不同的实例来隔离,减少混用的影响。
2.使用:避免触发高速存储的边界
set/hash/zsetlis/tairhash/bloom/gis 等大 key (内部叫做 godkey )不要超过 3000 ,会记 sillylog
避免使用 keys,hgetall,lrange0-1 等大 range (使用 scan 替代)
避免使用大 value ( 10k 以上就算大 value,50k 会记录)
3.SDK: 使用规范
严禁设置低读超时和紧密重试( 建议 200ms 以下 read timeout,或者是有比较大的概率引起雪崩的 )
需要接受 P99 时延,在客户端做好重试逻辑去增加程序的健壮性,做一些容错处理。
尽量使用扩展数据结构,避免使用 lua
尽量避免 pubsub 和 blocking 的 API
4.接受主动运维
主动运维在阿里云上,比如机器卡机,或者机器有风险,会做主动运维,做 EHA 切换。
三、 Redis 的常见问题处理
1、Redis 常见问题的处理
Redis 内存控制是精华部分,大部分问题都是和内存有关。所以先了解 Redis 内存模型。
总体内存=链路内存(动态)+数据内存+管理内存(静态)
链路内存包括:Input buff、Output buff、等,这两个 buff 跟每个客户端的连接是有关系的,正常情况下是比较小的,但是当 range 操作时,或者有大 key 手法比较慢时,这两个区内存就会增大,影响数据,甚至会造成 OM。还包括 lua JIT 相关的,比如,JIT over hat、lua link,包函了 code catch,lua 执行缓存等等。
第二部分是用户数据区,就是用户实际存储的 value。最后一部分是静态 buff,启动时较小,比较恒定,这个区域主要是管理 key 和 vakue 的一些原数据,overhead。当 key 非常多时,比如,几千万几个亿,就会占用非常大的内存。最后一部分是 Repl+buff,aof+buff 都是比较小的。
OOM 场景
大都是动态内存管理失控
in/out buff 堆积
限流的影响( plus timer mem )
无所畏惧的 Lua 脚本
Tair/Redis
Tair/Redis
Redis 被定义为“缓存"
粗糖的动态内存控制
"Footprint" 是内存数据库的核心技术
Tair 致力于 footprint control
售卖内存接近 User Dataset
2、缓存分析-内存分布统计、bigKey,key pattern
对于内存,阿里云有现成的功能能一键分析,入口在实际管理,CloudDBA 下面的缓存分析;如果是热 Key 分析无需主动触发。缓存分析可以支持历史的备份集和现有的备份集进行分析,所以准实时,或者对历史的备分析。支持所有线上的社区版,企业版,包括所有架构,包括标准版,读写分离版,集群版。
上图是阿里云控制台的试用截图,这个功能是比较常用的。
3、缓存分析-内存分析统计、bigKey、key pattern
上图是缓存分析的报告介绍,可以看到每个 db 每个分布内存统计,包括不同类型的数据结构内存统计,key 对应的元素数分级统计,可以统计总体有多少大 key。统计 key 过期时间分布,可以发现过时间设计是否合理,took 100 big key 具体有哪些大 key,业务可以参照这做优化。Took 100 key 前缀,是做了 key padent 统计,如果 key,是按照业务模块来制定的 key 前缀,这就可以统计到个个业务用了多少内存,大体上可以指导业务去优化的。
4、热 key 分析
阿里云提供了在线和离线两种在线方式。
在线实时分析热 key
使用入口:“实例管理”--> "CloudDBA" --> "缓存分析” -- >"HotKey"
使用须知: Tair 版,或 Redis 版本> =redis 4.0
精确统计(非采样),能抓出当前所有 Per Key QPS > 3000 的记录
参考文档: htp://help.aliyun.com/document detail/160585.html
离线分析热 key
方法1 :缓存分析也可以分析出相对较热的 key,通过工具实现
方法2 :最佳实践,imonitor 命令+ redis faina 分析出热点 Key
方法3 :使用审计日志查询历史热 Key, 参考文档
https://help.aliyun.com/document detail/181195.html
5、Tair/Redis 全链路诊断
链路很长,从 APP 测的 SDK 到网络,到 VIP 还有 Proxy 和 DB。每个部分都有可能出问题。问题排查包括前端排查和后端排查,前端排查先要确定是一台出问题还是全部出问题,如果是一台出问题,那大概率是客户自己的问题。排查方向包括ECS、load、内存等等,PPS 的限制。
客户端包括链接池满、RT(跨地域、gc 等)、建链路慢(K8s、DNS 等)、大查询,发快收慢。网络有可能会丢包、收敛,运营商网格抖动;后面后面主要是后端排查,后端主要是慢查和 CPU 排查,主要的主键是 VIP、Proxy、DB。VIP 上问题比较少,一般没有问题。主要是从 Proxy 和 DB 上去排查。Proxy 的问题有:分发慢查、流量高(扩容 proxy)、消息堆积、Backend 网格抖动。DB 方面:容量,CPU,流量(见前文),主机故障,HA 速度,慢查询。如果是慢查询,需要客户端去排查,是否是业务层面使用方式有问题,进而改造慢查询。
6、Tair/Redis 诊断报告
对于全链路诊断,推出了诊断报告功能。
可以对某个时间端发起一键诊断,这里主要是后端排查。目前都是 DB 相关的,可以看到有哪些异常情况发生,核心指标的曲线,可以看哪些时间点,哪些节点有峰值;慢请求部分展示了 TOP tend 节点的慢命令的统计,性能水位可以看到哪些指标,哪些节点超过了预设的水位或者是节点发生了倾斜,这对发现问题有很大的帮助。
诊断可以是准实时的,比如过去的半小时,最近半小时或者是对过去某一天或者是某几天的诊断。目前还没有完全对外开放,如果使用有兴趣的话,可以在阿里云上提供端单独开放访问。
7、Tair/Redis 慢日志
设置合理的 Proxy 和 DB 慢日志采集参数
slowlog-log- slower-than:
DB 分片上慢日志阈值,不可设置过低。因为
slowlog max-len :
DB 分片 slowlog 链表最大保持长度
rt. _threshold ms :
Proxy 上慢日志阈值,不可设置过低!
阈值设置过小,那么 DB 在采集慢日志会频繁地记录慢日志,可能会造成引擎的性能变慢,所以说大家尽量要使用默认的参数。
历史慢日志
使用入口:“实例管理”-->“日志管理” --> "慢日志”
使用须知: Tair 版,或 Redis 版本> =redis4.0,具体查看帮助文档
可获取近 72 小时内的慢日志
实时慢日志
使用入口:“实例管理" --> "CloudDBA" --“慢请求”
实时获取,能抓出当前所有分片 slowlog
8、资源的规划-自建 VS 云 Redis
从优缺看:有很多同学都会自建 Redis。下面说明自建 Redis 和云 Redis 优缺点和成本。
自建 Redis 的优点:便宜、拥有最高权限,完全自主可控。
可操控性强,自主可控就意味着需要做的东西太多。
比如:不能做到快速弹性的资源创建,业务突发高峰时,没办法快速处理、需要有增值的 DB 去盯着或者是处理,甚至是基础架构人员长期去维护,管控节点和其他配套的第三方工具或者生态功能需要额外开发,或者是购买资源,安装部署。Redis 的内核是非常难以优化的,没有专家服务兜底,出现紧急问题或者是重大故障时,没有办法快速止血。如果使用云 Redis 服务,开箱即用,可以随时随地弹性升降配,和产品类型的转换。内核方面也做了很多优化,简化集群使用,支持跨SLOT 多 key 操作的 proxy,安全加固,账号权限控制的功能。还有企业级管理增值功能的特性,比如:高可用无脑裂,账号鉴权体系,操作审计,RDB+AOF 备份,5 秒级监控粒度,全量大 key 分析,命令读写统计等。性能强需求可选 Tair 性能增强型:具备多线程和丰富实用的数据结构,同时拥有秒级数据恢复,比如:PITR ,全球多活等强大功能。
成本与大规模需求可选 Tair 持久内存型与容量存储型,多种形态针对不同性能容量要求可以进一步降低成本。最后有 7*24 小时专家服务支持,即使出现重大的问题,也有阿里云的技术专家在线支持,快速止血。我们使用过表格里的建议的规格配置,发现云 Redis 服务的成本是低于自建的,这里对比的是内存版。如果负载压力满足条件,客户可以进一步降低成本,比如:tall 持久内存型,可以降低内存30% 的成本;tall 容量存储型,可以降低 20% 左右。所以选择性比较大。