当前位置: 首页 > 科技观察

为了让你踩到Redis缓存的坑,这里有使用规范和监控方法

时间:2023-03-13 01:05:08 科技观察

1.前言在互联网应用中,缓存已经成为高并发架构的关键组成部分。本文主要介绍缓存使用的典型场景、实战案例分析、Redis使用规范和定期Redis监控。二、常用缓存对比常用缓存方案:本地缓存有HashMap/ConcurrentHashMap、Ehcache、Memcache、GuavaCache等,缓存中间件有Redis、Tair等三、Redis使用场景1、计数Redis实现快速计数缓存功能。例如:一个视频或直播,用户每播放一次,在线观看人数就会增加1。2.Session集中管理Session可以存储在应用服务的JVM中,但是这种方案会存在一致性问题,在高并发下会造成JVM内存溢出。Redis集中管理用户的Session。这样的话,只要保证Redis的高可用和可扩展性,每次用户更新或者查询登录都是直接从Redis获取。3.限速例如:对于高并发的秒杀活动,使用incrby命令实现原子递增。例如:业务要求用户在一分钟内只获取5个验证码。4.排行榜关系型数据库查询排行榜一般比较慢,可以使用redis的SortedSet对热点数据进行排序。比如在一个项目中,如果需要统计主播的吸金排名,可以将主播的id作为会员,当天打赏的活动礼物对应的人气值作为分值,就可以得到通过zrangebyscore的每日主播活动列表。5.分布式锁在实际的多进程并发场景中,分布式锁用于限制程序的并发执行。多用于防止高并发场景下缓存崩溃的可能性。分布式锁的现实是“占用”。当另一个进程执行setnx时,发现flag已经是1,只好放弃或者等待。四、案例分析1、过期设置——set命令会清除Redis所有有过期时间的数据结构,过期时间可以设置。如果一个字符串设置了过期时间,那么重置它会导致过期时间消失。因此,需要合理评估项目中的Redis容量,避免频繁设置过期策略缺失,间接导致内存爆满。以下为Redis源码截图:2.Jedis2.9.0及以下版本过期设置BUG发现Jedis在调用expireAt命令时出现bug,最后调用pexpire命令。这个bug会导致key过期时间过长,导致Redis内存溢出等问题。建议升级到Jedis2.9.1及以上版本。BinaryJedisCluster.java的源码如下:@OverridepublicLongpexpireAt(finalbyte[]key,finallongmillisecondsTimestamp){returnnewJedisClusterCommand(connectionHandler,maxAttempts){@OverridepublicLongexecute(Jedisconnection){returnconnection.pexpire(key,expires/Attempts应该在这里)}}.runBinary(key);}比较pexpire和pexpireAt:比如我们当前使用的时间是2018-06-1417:00:00,它的unix时间戳是1528966800000毫秒。当我们使用PEXPIREAT命令时,由于是过去,对应的key会立即过期。当我们误用PEXPIRE命令时,key并不会立即过期,而是会等到1528966800000毫秒后才过期。key的过期时间会比较长,大约是W天后,可能会导致Redis内存溢出,服务器崩溃等问题。3.缓存键有过期策略。如果此时有大量并发请求这个key,当这些请求发现缓存过期时,这些请求通常会从后端DB返回源数据,并重新设置到缓存中。大量并发请求可能会立即压垮后端数据库。业界常用的优化方案有两种:第一种:使用分布式锁保证高并发下只有一个线程可以返回源后端DB;第二种:保证高并发请求收到的Rediskey一直有效,使用非用户请求回源后端,改成主动回源。一般可以使用异步任务来主动刷新缓存。4、redis-standalone架构禁止使用非0库redis执行命令select0和select1切换,造成性能损失。RedisTemplate在执行execute方法时会先获取链接。执行到RedisConnectionUtils.java,会有获取链接的方法。JedisConnectionFactory.java将调用JedisConnection构造函数。注意这里的dbIndex是数据库编号,如:1.继续跟进JedisConnection的代码。当selectedlibrary大于1时,会有selectdb操作。如果一直在使用0库,则无需另外执行切库命令。知道了select1是第一个切库的地方,那select0从哪里来呢?其实客户端在使用Redis的时候也会释放链接,只是RedisTemplate已经自动帮我们释放了。让我们回到一开始RedisTemplate执行execute(...)方法的地方。下面依然是RedisConnectionUtils.java,执行链接关闭的代码。根据代码注释的意思,如果select库号不为0,spring-data-redis框架每次都会重新设置select0!在vivo商城业务中,经过上述优化,商品详情页界面性能提升了3倍以上。进一步验证数据库切换至少影响性能3倍(视具体业务而定)。Rediscluster集群数据库默认是0数据库,不能选择其他数据库,就避免了这个问题。5.当心时间复杂度o(n)Redis命令Redis是单线程的,所以是线程安全的。Redis使用非阻塞IO,大部分命令的时间复杂度为O(1)。使用耗时命令是非常危险的,会占用唯一线程大量的处理时间,导致所有请求变慢。例如:获取所有set集合中的元素smembersmyset,返回指定Hash中的所有成员,时间复杂度为O(N)。缓存的值集变大。当高层接口请求时,会从Redis中读取相关数据。每次请求的读取时间变长,不断叠加导致hotKEY的情况,Redis的某个shard被阻塞。CPU使用率达到100%。6、缓存热键在Redis中,访问频率高的键称为热键。向Server主机请求热键时,由于请求量大,导致主机资源不足,甚至宕机,影响正常服务。造成热键问题的原因有两个:用户消耗的数据远大于产生的数据,比如热销商品或闪购商品、热点新闻、热评等,这些典型的阅读多而少的场景写入会造成热点;请求分片的集中度超过了单台服务器的性能极限,比如一个固定名称的key,hash落在一台服务器上,流量极大。当超过服务器的限制时,会出现热键问题。那么在实际业务中,如何识别热键呢?根据业务经验,估计哪些是热键;客户统计收集、本地统计或报告;如果服务器有代理层,可以在代理层收集上报。当我们确定了一个热键后,如何解决热键问题呢?Redis集群扩容:增加分片副本,平衡读流量;进一步hashhotkey,比如备份一个key为key1,key2...keyN,相同数据的N个备份,N个备份分布到不同的分片,访问时可以随机访问N个备份中的一个来进一步共享读取流量;使用二级缓存,即本地缓存。当找到一个hotkey时,首先将该hotkey对应的数据加载到应用服务器的本地缓存中,以减少对Redis的读请求。五、Redis规范1.禁止使用非数据库0说明:Redis-单机架构,禁止使用Redis中的其他数据库。原因:为以后的业务迁移RedisCluster保持兼容性;多个数据库在使用select切换时消耗CPU资源较多;更容易自动化运维管理,比如scan/dbsize命令只作为数据库使用;一些Redis客户端是线程安全的,不支持单个实例中的多个数据库。2.按键设计规范根据业务功能命名按键前缀,防止按键冲突被覆盖。建议用冒号隔开。比如业务名:表名:id:,比如live:rank:user:weekly:1:202003。键的长度小于30个字符,键名本身是一个String对象。Redis硬编码的最大长度为512MB。在Redis缓存场景下,建议为所有key设置TTL值,保证不用的key能及时清除或淘汰。Key是为了禁止特殊字符,比如空格、换行、单引号和双引号等转义字符。3、Value设计规范单个Value的大小必须控制在10KB以内。如果单实例key数量过多,可能会导致过期key回收不及时。对于set、hash、list等复杂数据类型,尽量减少数据结构中的元素个数。建议数量不要超过1000。4.注意命令的时间复杂度。推荐使用O(1)命令,比如getscard。O(N)命令关注的是N的个数,下面的命令需要在业务层面控制N的取值。hgetalllrangesmemberszrange例如:smember命令的时间复杂度为O(n)。当n继续增加时,RedisCPU会持续飙升,阻塞其他命令的执行。5、管道使用说明:管道是Redis批量提交的一种方式,即为多个命令操作建立连接,发送到Redis执行,相比循环单次提交会有更好的性能。Redis客户端执行一个命令分四个过程:发送命令->命令排队->命令执行->返回结果。常用的mget和mset命令可以有效节省RTT(命令执行往返时间),但是hgetall没有mhgetall,不支持批量操作。这时候就需要用到Pipeline命令了。例如:在直播项目中,需要同时查询主播的日、周、月排名。使用PIPELINE一次提交多个命令,同时返回三个排名数据。6.禁用联机命令,禁止使用Monitor。禁止在生产环境中使用monitor命令。在高并发情况下,monitor命令会存在内存爆炸的隐患,影响Redis的性能。keys操作就是遍历所有的keys。如果key很多,查询慢的情况下,会阻塞其他命令。因此,键和键模式命令是被禁止的。在线推荐使用scan命令代替keys命令。禁止在Redis中使用Flushall和Flushdb删除所有数据库中的所有记录,该命令是原子的,不会终止执行。一旦执行,执行不会失败。禁止使用Save阻塞当前redis服务器,直到持久化操作完成。对于内存大的实例,会造成长期阻塞BGREWRITEAOF手动AOF,对于内存大的实例,手动持久化会造成长期阻塞ConfigConfig是客户端的配置方式,不利于Redis的运维。建议在Redis的配置文件中设置。六、redis监控1、慢查询方式一:slowlog获取慢查询日志127.0.0.1:{port}>slowlogget51)1)(integer)472)(integer)15338103003)(integer)1758334)1)"DEL"2)"spring:session:expirations:1533810300000"2)1)(integer)462)(integer)15338103003)(integer)1174004)1)"SMEMBERS"方法二:更全面的慢查询可以通过CacheCloud工具监控。路径:“应用列表”-点击相关应用名称-点击“慢查询”Tab页。点击“SlowQueries”,关注慢查询的数量和相关命令。2、监控Redis实例绑定的CPU核心使用率由于Redis是单线程的,所以重点监控Redis实例绑定的CPU核心使用率。一般CPU资源使用率在10%左右。如果使用率高于20%,考虑是否使用RDB持久化。3、redis分片负载均衡目前的redis-cluster架构模式,3主3从组成一个集群,注意redis-cluster的各个分片请求的流量均衡;通过命令获取:redis-cli-p{port}-h{host}--stat一般超过12W就需要告警。4.注意bigkeyBigKey使用Redis提供的工具,redis-cli定时扫描对应的Redisbigkey进行优化。具体命令如下:redis-cli-h127.0.0.1-p{port}--bigkeys或者redis-memory-for-key-s{IP}-p{port}XXX_KEY一般超过10K的作为bigkey,需要注意的地方,建议从业务层面进行优化。5、监控Redis占用的内存大小,使用Infomemory命令查看,避免高并发场景下分配的MaxMemory耗尽导致的性能问题。关注used_memory_human配置项对应的值。当增量太高时,需要重点评估。7.小结结合具体业务特点,合理评估Redis所需内存容量、数据类型选择、单个key大小设置,才能更好地服务于业务,为业务提供高性能保障。