Redis最佳实现,以及使用规范

背景:由于项目某个对性能要求较高的实时数据处理模块频繁出现问题,程序处理数据缓慢、Redis大量数据堆积、程序崩溃等问题。经过分析发现由于功能中大量使用了Redis,通过打印日志的方式确认是由于某些Redis命令操作耗时严重导致程序的处理线程卡住。

通过分析发现是由于不合理的使用Redis的命令了以及Redis设计规范。大部分原因是错误的使用了Redis,其中包括:客户端工具的选型、Redis的使用定位、Redis命令的错误使用。例如:把Redis当成数据库使用、把大量的数据存入Redis、把Redis当做消息中间件使用、使用了HGETALL、LRANGE 0 -1,等全量操作命令。通过对Redis命令使用的改造,功能设计调整。已经初步解决线上的程序卡住、崩溃问题。下面是总结一些Redis使用的最佳实践(借鉴与某云产品的最佳实践),级别为“强烈建议”的建议认真阅读,希望能对大家有些帮助,能在使用Redis的过程中少走弯路,少出事故。

业务使用规范

原则原则说明级别备注
就近部署业务,避免时延过大如果部署位置过远(非同一个region)或者时延较大(例如业务服务器与Redis实例通过公网连接),网络延迟将极大影响读写性能。强烈建议
冷热数据区分建议将热数据加载到 Redis 中。低频数据可存储在 Mysql或者ElasticSearch中。建议一方面避免业务相互影响,另一方面避免单实例膨胀,并能在故障时降低影响面,快速恢复。
业务数据分离避免多个业务共用一个Redis。强烈建议一方面避免业务相互影响,另一方面避免单实例膨胀,并能在故障时降低影响面,快速恢复。
业务数据分离禁止使用select功能在单Redis实例做多db区分。建议Redis单实例内多DB隔离性较差,Redis开源社区已经不再发展多DB特性,后续不建议依赖该特性。
以缓存方式使用RedisRedis事务功能较弱,不建议过多使用。建议事务执行完后,不可回滚。
以缓存方式使用Redis数据异常的情况下,支持清空缓存进行数据恢复。强烈建议Redis本身没有保障数据强一致的机制和协议,业务不能强依赖Redis数据的准确性。
以缓存方式使用Redis以缓存方式使用Redis时,所有的key需设置过期时间,不可把Redis作为数据库使用。强烈建议失效时间并非越长越好,需要根据业务性质进行设置。
防止缓存击穿推荐搭配本地缓存使用Redis,对于热点数据建立本地缓存。本地缓存数据使用异步方式进行刷新。强烈建议可减少与Redis的交互。减少因Redis网络延迟、操作阻塞带来的性能问题。
防止缓存穿透非关键路径透传数据库,建议对访问数据库进行限流。建议
防止缓存穿透从Redis获取数据未命中时,访问只读数据库实例。可通过域名等方式对接多个只读实例建议核心是未命中的缓存数据不会打到主库上。
用域名对接多个只读数据库实例,一旦出现问题,可以增加只读实例应急。
不用作消息队列发布订阅场景下,不建议作为消息队列使用。强烈建议如没有非常特殊的需求,不建议将 Redis 当作消息队列使用。
Redis 当作消息队列使用,会有容量、网络、效率、功能方面的多种问题。
如需要消息队列,可使用高吞吐的Kafka或者高可靠的RocketMQ。
合理选择规格如果业务增长会带来Redis请求增长,请选择集群实例(Proxy集群和Cluster集群)强烈建议单机和主备扩容只能实现内存、带宽的扩容,无法实现计算性能扩容。
合理选择规格生产实例需要选择主备或者集群实例,不能选用单机实例强烈建议
合理选择规格主备实例,不建议使用过大的规格。建议Redis在执行RewriteAOF和BGSAVE的时候,会fork一个进程,过大的内存会导致卡顿
具备降级或容灾措施缓存访问失败时,具备降级措施,从DB获取数据;或者具备容灾措施,自动切换到另一个Redis使用。建议

数据设计规范

分类原则原则说明级别备注
Key相关规范使用统一的命名规范。一般使用业务名(或数据库名)为前缀,用冒号分隔。Key的名称保证语义清晰。建议例如,业务名:子业务名:id
Key相关规范控制Key名称的长度。在保证语义清晰的情况下,尽量减少Key的长度。有些常用单词可使用缩写,例如,user缩写为u,messages缩写为msg。建议建议不要超过128字节(越短越好)。
Key相关规范禁止包含特殊字符(大括号“{}”除外)。禁止包含特殊字符,如空格、换行、单双引号以及其他转义字符。强烈建议由于大括号“{}”为Redis的hash tag语义,如果使用的是集群实例,Key名称需要正确地使用大括号避免分片不均的情况。
Value相关规范设计合理的Value大小。设计合理的Key中Value的大小,推荐小于10 KB。建议过大的Value会引发分片不均、热点Key、实例流量或CPU使用率冲高等问题,还可能导致变更规格和迁移失败。应从设计源头上避免此类问题带来的影响。
Value相关规范设计合理的Key中元素的数量。对于集合和列表类的数据结构(例如Hash,Set,List等),避免其中包含过多元素,建议单Key中的元素不要超过5000个。强烈建议由于某些命令(例如HGETALL)的时间复杂度直接与Key中的元素数量相关。如果频繁执行时间复杂度为O(N)及以上的命令,且Key中的子Key数量过多容易引发慢请求、分片流量不均或热点Key问题。
Value相关规范选择合适的数据类型。合理地选择数据结构能够节省内存和带宽。建议例如存储用户的信息,可用使用多个key,使用set u:1:name “X”、set u:1:age 20存储,也可以使用hash数据结构,存储成1个key,设置用户属性时使用hmset一次设置多个,同时这样存储也能节省内存。
设置合理的过期时间。设置合理的过期时间合理设置Key的过期时间,将过期时间打散,避免大量Key在同一时间点过期。建议设置过期时间时,可以在基础值上增减一个随机偏移值,避免在同一个时间点大量Key过期。大量Key过期会导致CPU使用率冲高。

命令使用规范

原则原则说明级别备注
谨慎使用O(N)复杂度的命令时间复杂度为O(N)的命令,需要特别注意N的值。避免N过大,造成Redis阻塞以及CPU使用率冲高。强烈建议例如:hgetall、lrange、smembers、zrange、sinter这些命令都是做全集操作,如果元素很多,会消耗大量CPU资源。可使用hscan、sscan、zscan这些分批扫描的命令替代。
禁用高危命令禁止使用flushall、keys、hgetall等命令,或对命令进行重命名限制使用。强烈建议
使用批量操作提高效率如果有批量操作,可使用mget、mset或pipeline,提高效率,但要注意控制一次批量操作的元素个数建议mget、mset和pipeline的区别如下:mget和mset是原子操作,pipeline是非原子操作。pipeline可以打包不同的命令,mget和mset做不到。使用pipeline,需要客户端和服务端同时支持。
避免在lua脚本中使用耗时代码lua脚本的执行超时时间为5秒钟,建议不要在lua脚本中使用比较耗时的代码。强烈建议比如长时间的sleep、大的循环等语句。
避免在lua脚本中使用随机函数调用lua脚本时,建议不要使用随机函数去指定key,否则在主备节点上执行结果不一致,从而导致主备节点数据不一致。强烈建议
遵循集群实例使用lua的限制遵循集群实例使用lua的限制。强烈建议使用EVAL和EVALSHA命令时,命令参数中必须带有至少1个key,否则客户端会提示“ERR eval/evalsha numkeys must be bigger than zero in redis cluster mode”的错误。使用EVAL和EVALSHA命令时,Redis集群实例使用第一个key来计算slot,用户代码需要保证操作的key是在同一个slot。
对mget,hmget等批量命令做并行和异步IO优化某些客户端对于MGET,HMGET这些命令没有做特殊处理,串行执行再合并返回,效率较低,建议做并行优化。建议例如Jedis对于MGET命令在集群中执行的场景就没有特殊优化,串行执行,比起lettuce中并行pipeline,异步IO的实现,性能差距可达到数十倍,该场景建议使用Jedis的客户端自行实现slot分组和pipeline的功能。
禁止使用del命令直接删除大Key使用del命令直接删除大Key(主要是集合类型)会导致节点阻塞,影响后续请求强烈建议Redis 4.0后的版本可以通过UNLINK命令安全地删除大Key,该命令是异步非阻塞的。对于Redis 4.0之前的版本:
如果是Hash类型的大Key,推荐使用hscan + hdel。
如果是List类型的大Key,推荐使用ltrim。
如果是Set类型的大Key,推荐使用sscan + srem。
如果是SortedSet类型的大Key,推荐使用zscan + zrem

SDK使用规范

原则原则说明级别备注
使用连接池和长连接短连接性能差,推荐使用带有连接池的客户端。建议连接的频繁创建和销毁,会浪费大量的系统资源,极限情况会造成宿主机宕机。请确保使用了正确的Redis客户端连接池配置。
客户端需要对可能的故障和慢请求做容错处理由于Redis服务可能因网络波动或基础设置故障的影响,引发主备倒换,命令超时或慢请求等现象,需要在客户端内设计合理的容错重试机制建议
合理设置重试时间和次数合理设置容错处理的重试时间,根据业务要求设置,避免过短或者过长。强烈建议如果超时重试时间设置的非常短(例如200毫秒以下),可能引发重试风暴,极易引发业务层雪崩。
如果重试时间设置较长或者重试次数设置得较大,则可能导致在主备倒换情况下业务恢复较慢。
避免使用Lettuce客户端Lettuce客户端在默认配置下有一定性能优势,并且是spring的默认客户端,但是Jedis客户端在面对连接异常,网络抖动等场景下的异常处理和检测能力明显强于Lettuce,可靠性更强,建议使用Jedis。建议Lettuce存在几个方面的问题:
Lettuce默认未配置集群拓补刷新的配置,会导致Cluster集群在发生拓补信息变化(主备倒换,扩容缩容)时,无法识别新的节点信息,导致业务失败。可参考使用Lettuce连接Cluster集群实例时的扩容异常处理。
Lettuce没有连接池校验的功能,无法检测连接池中的连接是否仍然有效,获取失效连接之后会导致业务失败。

连接池选择及Jedis连接池参数配置建议

Jedis连接池优势

Lettuce客户端及Jedis客户端比较如下:

  • Lettuce:
    • Lettuce客户端没有连接保活探测,错误连接存在连接池中会造成请求超时报错。
    • Lettuce客户端未实现testOnBorrow等连接池检测方法,无法在使用连接之前进行连接校验。
  • Jedis:
    • Jedis客户端实现了testOnBorrow、testWhileIdle、testOnReturn等连接池校验配置。
      开启testOnBorrow在每次借用连接前都会进行连接校验,可靠性最高,但是会影响性能(每次Redis请求前会进行探测)。
    • testWhileIdle可以在连接空闲时进行连接检测,合理配置阈值可以及时剔除连接池中的异常连接,防止使用异常连接造成业务报错。
      在空闲连接检测之前,连接出现问题,可能会造成使用该连接的业务报错,此处可以通过参数控制检测间隔(timeBetweenEvictionRunsMillis)。
      因此,Jedis客户端在面对连接异常,网络抖动等场景下的异常处理和检测能力明显强于Lettuce,可靠性更强。

Jedis连接池参数配置建议

参数配置介绍配置建议
maxTotal最大连接,单位:个根据Web容器的Http线程数来进行配置,估算单个Http请求中可能会并行进行的Redis调用次数,例如:Tomcat中的Connector内的maxConnections配置为150,每个Http请求可能会并行执行2个Redis请求,在此之上进行部分预留,则建议配置至少为:150 x 2 + 100= 400
限制条件:单个Redis实例的最大连接数。maxTotal和客户端节点数(CCE容器或业务VM数量)数值的乘积要小于单个Redis实例的最大连接数。
例如:Redis主备实例配置maxClients为10000,单个客户端maxTotal配置为500,则最大客户端节点数量为20个。
maxIdle最大空闲连接,单位:个建议配置为maxTotal一致。
minIdle最小空闲连接,单位:个一般来说建议配置为maxTotal的X分之一,例如此处常规配置建议为:100。
对于性能敏感的场景,防止经常连接数量抖动造成影响,也可以配置为与maxIdle一致,例如:400。
maxWaitMillis最大获取连接等待时间,单位:毫秒获取连接时最大的连接池等待时间,根据单次业务最长容忍的失败时间减去执行命令的超时时间得到建议值。例如:Http最大容忍超时时间为15s,Redis请求的timeout设置为10s,则此处可以配置为5s。
timeout命令执行超时时间,单位:毫秒单次执行Redis命令最大可容忍的超时时间,根据业务程序的逻辑进行选择,一般来说处于对网络容错等考虑至少建议配置为210ms以上。特殊的探测逻辑或者环境异常检测等,可以适当调整达到秒级。
minEvictableIdleTimeMillis空闲连接逐出时间,大于该值的空闲连接一直未被使用则会被释放,单位:毫秒如果希望系统不会经常对连接进行断链重建,此处可以配置一个较大值(xx分钟),或者此处配置为-1并且搭配空闲连接检测进行定期检测。
timeBetweenEvictionRunsMillis空闲连接探测时间间隔,单位:毫秒根据系统的空闲连接数量进行估算,例如系统的空闲连接探测时间配置为30s,则代表每隔30s会对连接进行探测,如果30s内发生异常的连接,经过探测后会进行连接排除。根据连接数的多少进行配置,如果连接数太大,配置时间太短,会造成请求资源浪费。对于几百级别的连接,常规来说建议配置为30s,可以根据系统需要进行动态调整。
testOnBorrow向资源池借用连接时是否做连接有效性检测(ping),检测到的无效连接将会被移除。对于业务连接极端敏感的,并且性能可以接受的情况下,可以配置为True,一般来说建议配置为False,启用连接空闲检测。
testWhileIdle是否在空闲资源监测时通过ping命令监测连接有效性,无效连接将被销毁。True
testOnReturn向资源池归还连接时是否做连接有效性检测(ping),检测到无效连接将会被移除False
maxAttempts在JedisCluster模式下,您可以配置maxAttempts参数来定义失败时的重试次数。建议配置3-5之间,默认配置为5。
根据业务接口最大超时时间和单次请求的timeout综合配置,最大配置不建议超过10,否则会造成单次请求处理时间过长,接口请求阻塞。
  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值