分析:为何缓冲区激增(Redis不可用的时间点11:22:02,之前都发生了什么)
知道了缓冲区挤爆Redis内存会导致Redis不工作之后,接下来就是分析为何当时出现了缓冲区激增并最终导致redis不可用。
实例信息
相关代码
案件还原
1.自然增长导致流出带宽不断变大直至96MB/s。
2.流出带宽超过96MB/s,输出缓冲区内存占用激增甚至溢出 (300setMaxTotal*10机器ip数量个客户端,之前推导过可以到9G)。
3.导致输出缓冲区爆了,redis客户端连接不得不关闭。
4.客户端连接关闭后,导致请求都走DB。
5.DB走完之后都会执行SET。
6.SET流量飙升,且因都是大KEY,导致流入带宽激增(别看写QPS只有50,但是如果每个写都是2MB,就可以做到瞬间占满流入带宽)。
7.Redis主线程模型,处理请求的速度过慢(大KEY),出现了间歇性阻塞,无法及时处理正常发送的请求,导致客户端发送的请求在输入缓冲区越积越多。
8.输入缓冲区内存随即激增。
9.最终,redis内存被缓冲区内存(输入、输出)完全侵占。
10.后续的SET GET命令甚至都进不了输入缓冲区,阻塞持续到客户端配置的SoTimeout时间;但是流入流出带宽依然占据并持续,总带宽到达了216MB/s > 实例最大带宽192MB/s。
11.造成最终的不可用(后续的命令想进场,要依赖当前输入缓冲区里的命令被执行给你腾出来位置,但是还是那句话Redis主线程处理消化的速度,实在是太慢了;从图中,可以看到Redis的QPS骤降。
11:35分之后,我把redis降级了,全使用db来抗流量。
开发运维规范
可以看到Redis的性能是有边界的,不能盲目相信所谓的高性能。
真正理解性能须使用benchmark。
它也是有问题能造成他的性能瓶颈的。
计算资源 使用通配符、Lua并发、1对多的PUBSUB、热点Key等会大量消耗计算资源,集群架构下还会导致访问倾斜,无法有效利用所有数据分片。
存储资源 Streaming慢消费、大Key等会占用大量存储资源,集群架构下还会导致数据倾斜,无法有效利用所有数据分片。
网络资源 扫描全库(KEYS命令)、大Value、大Key的范围查询(如HGETALL命令)等会消耗大量的网络资源,且极易引发线程阻塞。
重要
Redis的高并发能力不等同于高吞吐能力,例如将大Value存在Redis里以期望提升访问性能,此类场景往往不会有特别大的收益,反而会影响Redis整体的服务能力。
在上述的事故中,就占了【网络资源消耗高】【存储资源消耗高】两大问题
因此也到了本文的方法论环节:从业务部署、Key的设计、SDK、命令、运维管理等维度展示云数据库Redis开发运维规范:
● 业务部署规范:https://help.aliyun.com/zh/redis/use-cases/development-and-o-and-m-standards-for-apsaradb-for-redis
● Key设计规范:https://help.aliyun.com/zh/redis/use-cases/development-and-o-and-m-standards-for-apsaradb-for-redis
● SDK使用规范:https://help.aliyun.com/zh/redis/use-cases/development-and-o-and-m-standards-for-apsaradb-for-redis
● 命令使用规范:https://help.aliyun.com/zh/redis/use-cases/development-and-o-and-m-standards-for-apsaradb-for-redis
● 运维管理规范:https://help.aliyun.com/zh/redis/use-cases/development-and-o-and-m-standards-for-apsaradb-for-redis
业务部署规范
重要程度 规范 说明
★★★★★ 确定使用场景为高速缓存或内存数据库。 ● 高速缓存:建议关闭AOF以降低开销,同时,由于数据可能会被淘汰,业务设计上避免强依赖缓存中的数据。例如Redis被写满后,会触发数据淘汰策略以挪移出空间给新的数据写入,根据业务的写入量会相应地导致延迟升高。
重要
如需使用通过数据闪回按时间点恢复数据功能,AOF功能需保持开启状态。
● 内存数据库:应选购企业版(持久内存型),支持命令级持久化,同时应通过监控报警关注内存使用率。具体操作,请参见报警设置。
★★★★★ 就近部署业务,例如将业务部署在同一个专有网络VPC下的ECS实例中。 Redis具备极强的性能,如果部署位置过远(例如业务服务器与Redis实例通过公网连接),网络延迟将极大影响读写性能。
说明
针对多地部署应用的场景,您可以通过全球多活功能,借助其提供的跨域复制(Geo-replication)能力,快速实现数据异地灾备和多活,降低网络延迟和业务设计的复杂度。更多信息,请参见Redis全球多活简介。
★★★★☆ 为每个业务提供单独的Redis实例。 避免业务混用,尤其需要避免将同一Redis实例同时用作高速缓存和内存数据库业务。带来的影响例如针对某个业务淘汰策略设置、产生的慢请求或执行FLUSHDB命令影响将扩散至其他业务。
★★★★☆ 设置合理的过期淘汰策略。 云数据库Redis默认的默认逐出策略为 volatile-lru ,关于各逐出策略的说明,请参见Redis配置参数列表。
★★★☆☆ 合理控制压测的数据和压测时间。 云数据库Redis不会对您压测的数据执行自动删除操作,您需要自行控制压测数据的数据量和压测时间,避免对业务造成影响。
Key设计规范
重要程度 规范 说明
★★★★★ 设计合理的Key中Value的大小,推荐小于10 KB。 过大的Value会引发数据倾斜、热点Key、实例流量或CPU性能被占满等问题,应从设计源头上避免此类问题带来的影响。
★★★★★ 设计合理的Key名称与长度。 ● Key名称:
● 使用可读字符串作为Key名,如果使用Key名拼接库、表和字段名时,推荐使用英文冒号(:)分隔。例如project:user:001。
● 在能完整描述业务的前提下,尽量简化Key名的长度,例如username可简化为u。
● 由于大括号({})为Redis的hash tag语义,如果使用的是集群架构的实例,Key名称需要正确地使用大括号避免 引发数据倾斜 ,更多信息,请参见keys-hash-tags。
说明
集群架构下执行同时操作多个Key的命令时(例如RENAME命令),如果被操作的Key未使用hash tag让其处于相同的数据分片,则命令无法正常执行。
● 长度:推荐Key名的长度不超过128字节(越短越好)。
★★★★★ 对于支持子Key的复杂数据结构,应避免一个Key中包含过多的子Key(推荐低于1,000)。
说明
常见的复杂数据结构例如Hash、Set、Zset、Geo、Stream及Tair(Redis企业版)特有的exHash、Bloom、TairGIS等。 由于某些命令(例如HGETALL)的时间复杂度直接与Key中的子Key数量相关。如果频繁执行时间复杂度为O(N)及以上的命令,且Key中的子Key数量过多容易引发慢请求、数据倾斜或热点Key问题。
★★★★☆ 推荐使用串行化方法将Value转变为可读的结构。 由于编程语言的字节码随着版本可能会变化,如果存储裸对象(例如Java Object、C#对象)会导致整个软件栈升级困难,推荐使用串行化方法将Value变成可读的结构。
SDK使用规范
重要程度 规范 说明
★★★★★ 推荐使用JedisPool或者JedisCluster连接实例。
说明
企业版(内存型)实例推荐使用TairJedis客户端,支持新数据结构的封装类。使用方法,请参见通过客户端程序连接Redis。 如果使用单连接的方式,一旦遇到单次超时则无法自动恢复。关于JedisPool的连接方法,请参见通过客户端程序连接Redis、JedisPool资源池优化和JedisCluster。
★★★★☆ 程序客户端需要对超时和慢请求做容错处理。 由于Redis服务可能因网络波动或资源占满引发超时或慢请求,您需要在程序客户端上设计合理的容错机制。
★★★★☆ 程序客户端应设置相对宽松的超时重试时间。 如果超时重试时间设置的非常短(例如200毫秒以下),可能引发重试风暴,极易引发业务层雪崩。更多信息,请参见Redis客户端重连指南。