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

Redis常用用法进阶秘籍,速看!_0

时间:2023-03-17 19:30:22 科技观察

Redis被大家广泛使用,但是我们大多数人可能只关注业务本身,往往会忽略底层的细节。久而久之,对个人成长帮助不大。图片来自Pexels。本文总结了一份Redis常用用法进阶指南,希望能帮助大家加深对这项技术的理解。Redis基本数据结构①StringRedis中的字符串是动态字符串,会根据实际情况动态调整。类似于Go中的slice-slice,如果长度不够,会自动扩容。至于如何扩容,方法大致如下:当长度小于1M时,扩容规则将当前字符串加倍;如果长度大于1M,则每次只扩容1M,直到达到512M。②ListRedis中的List是一个链表。由于链表本身有插入和删除比较块,但查询效率比较低,所以常用作异步队列。Redis中的List设计的非常好。当数据量比较小的时候,数据结构是压缩链表,当数据量大的时候,就变成了快速链表。适用场景:业务中,异步队列使用rpush/lpush操作队列,使用lpop、rpop退出队列。具体结构如下图所示:③SetRedis中的set是一个无序的Map。由于Go中没有集合结构,这里只能类比Java中的HashSet概念。Redis集合底层也是一个Map结构,和Java不同的是:value是一个NULL。由于set的性质,它可以用于去重逻辑,这在Java中也经常被使用。适用场景:赛事抽奖去重。④HashRedis中的字典类型大家都很熟悉了。可能其他语言也有这种结构(python、Java、Go)。哈希扩展的rehash过程和Go中的设计很相似,就是维护两个哈希结构。如果需要扩容,将新的数据写入新的字典,然后在后端设置线程逐步迁移。一般来说,采用的是用空间换时间的思路。适用场景:记录业务中不同用户/不同产品/不同场景的信息:比如某个用户的名字,或者用户的历史行为。⑤ZsetRedis中的zset是一个比较特殊的数据结构(跳转列表),也就是我们所知道的跳转列表。底层由于集合的特性保证了值的唯一性,同时也给值打分。所谓顺序,其实就是根据这个分数来排序的。至于如何插入跳表,内部其实采用的是随机策略:L0:100%-L2:50%-L3:25%-....Ln:(n-1)value/2%。适用场景:榜单、普通榜单、热点榜单。Redis进阶使用①BloomfilterRedis在4.0之后支持Bloomfilter(准确的说是支持Bloomfilter的插件),为Redis提供了强大的去重功能。在业务中,我们可能需要查询数据库,判断历史数据是否存在。如果数据库的并发能力有限,此时我们可以使用Redis集去重。如果缓存的数据太大,这时候我们就需要遍历所有的缓存数据。另外,如果我们的历史数据缓存写不下来,最终还是要查询数据库。这时候,我们就可以使用布隆过滤器。当然,Bloomfilter的准确率并不是100%准确(如果数据准确率很高,这里不建议使用),因为对于已有的数据来说,该值不一定存在,当然,如果它不存在,那一定是100%不存在。命令用法:bf.add#添加元素bf.exists#判断元素是否存在bf.madd#批量添加bf.mexists#批量判断是否存在原理如下:Bloomfilter的组成可以看成是位array和几次计算的结果是一个比较统一的哈希函数。每增加一个key,都会对该key进行多次hash计算得到的position。如果当前位置不为0,则表示存在。可见,这个计算存在一定的误差,这就是其不准确的根源。②分布式锁大家可能对分布式锁并不陌生。现在市面上实现分布式锁的主流技术有ZK和Redis;下面简单介绍一下Redis是如何实现分布式锁的。命令如下:setnxlock:mutexture#addlockdellock:mutex#deletelock实现分布式锁的核心是:请求时设置key,其他请求设置失败则无法获取锁。但是有一个问题:如果业务出现panic或者忘记调用del,就会出现死锁。这时候大家很容易想到:我们可以过期一个过期时间,这样就可以保证请求不会一直独占锁,无法释放锁的逻辑。但是假设业务中有这样一种情况:A请求获取锁后处理逻辑。因为逻辑太长,这个时候锁过期了,被释放了。此时A刚处理完,B又改了数据。锁失效问题。解决这个问题,参考CAS方法。给锁设置一个随机数,可以理解为版本号。如果发布的时候版本号不一致,说明发布的时候已经改了版本号。深入原理①IO模型Redis是单线程模型(这里的单线程是指IO和key-value对的读写由一个线程完成),当然如果严谨的话还是可以的理解为多线程。但是这样的多线程无非就是在数据备份的时候fork一个子进程从磁盘读取数据组装RDB,然后同步到slave节点进行操作。当然,包括备份和持久化也是通过另外一个线程来完成的,所以我们可以把Redis看成一个单线程模型。那么问题来了,为什么单线程模型这么快呢?原因很简单,因为Redis本身是运行在内存中的,对于上游客户端的请求,它采用了多路复用的原理。Redis为每一个客户端socket关联了一个指令队列,客户端的指令队列被排队顺序处理。同时,Reids为每个客户端的suitekey关联了一个响应队列,Redis服务器通过响应队列将命令接口返回给客户端。RedisIO处理模型②通信协议Redis使用Gossip协议作为通信协议。八卦是一种传播新闻的方式,可以比作瘟疫或流感的传播方式。使用Gossip协议的有:RedisCluster、Consul、ApacheCassandra等。Gossip协议类似于病毒的传播方式,将信息传播到其他节点。这个协议非常有效。它只需要广播给附近的节点,然后被广播的节点继续做同样的操作。当然,这个协议也有一个缺点:会造成浪费,即使之前已经通知过一个节点,下次广播后还是会重复转发。③持久化RDB:是对当前Redis存储数据的快照(具体原理和怎么做,限于篇幅,这里不再赘述)。AOF:日志只记录Redis修改内存的指令记录。Redis提供了bgrewriteaif指令来压缩AOF。其原理是:开辟一个子进程遍历内存,转换成一系列对Redis的操作指令,序列化到一个新的AOF日志文件中。序列化完成后,将发送的增量AOF日志追加到这个新的AOF日志中,追加完成后用新的AOF日志替换旧的。混合持久化:由于纯RDB,可能存在数据丢失,频繁的AOF会影响性能。Redis4.0之后支持混合持久化。即每次启动时使用RDB+增量AOF文件进行回复。由于增量AOF只记录持久化开始到持久化结束之间发生的增量,所以日志不会太大,性能也比较高。④主从同步Redis的同步方式包括:主从同步和从从同步(由于都是master同步,会损失性能,所以slave之间会同步一些slave)。同步过程如下:建立连接,然后从库告诉主库:“我要同步,你给我准备好”,然后主库告诉从库:“收到”。从库中获取数据后,将数据保存到库中。这时候本地就会完成数据加载,使用RDB。主库将新数据AOF同步到从库。⑤SentinelRedis的主从切换由Sentinel解决。这里sentinel解决的主要问题是:当master挂掉后,如果短时间内重新选举出新的master。Sentinel集群是由3-5个(可以更多)节点组成的集群,用于监控整个Redis集群,如果发现master不可用,会关闭并断开连接master的所有旧链接。此时,Sentinel将完成选举和故障转移,新的请求将转移到新的master上。⑥Redis集群工作原理Redis集群通过槽分配机制来决定写命令应该分配到哪个节点。整个集群对应的slot由一个大小为16384的二进制数组组成,集群中的每个master节点分配一部分slot,每个写命令落到二进制数组中的某个位置。哪个节点分配到哪个位置,相应的命令就由该节点执行。slot分配对应的二进制数组如下图所示:从上图可以看出:节点1只负责执行0到4999的slot,而Node2负责执行5000到9999的slot,而节点3负责执行9999到16383的slot,写的时候:setkeyvalue命令通过CRC16(key)&16383=6789(假设结果),由于节点2负责slot5000-9999,所以6789命令的结果为最后由节点2执行。当然,如果节点2执行命令,假设通过CRC计算得到的值为567,则应该由节点1执行。此时,该命令将进行转向操作,该命令待执行的流程将被转移到节点1执行。集群节点同步:集群中的每个主节点都会周期性的向其他主节点发送信息进行同步。如果其他主节点在规定时间内响应发送消息的主节点,则发送消息的主节点认为响应消息的主节点正常;它的节点将其标记为“疑似离线”。当集群中超过一半的节点认为某个主节点被标记为“疑似下线”时,其中一个主节点会将疑似下线的节点标记为下线,并向集群广播下线消息。当下线节点对应的从节点收到消息后,从从节点中选出一个节点作为主节点,继续对外提供服务。为什么Redis变慢了?在业务场景中,不知道大家有没有遇到过Redis变慢的情况:执行SET和DEL命令需要很长时间,偶尔会卡顿,然后又恢复正常。在某一时刻,它突然开始改变。慢原因分析:查看慢查询,因为笔者自己的机器没有慢查询,所以这里是空的(尴尬,这里没有例子~~)因为Redis在IO操作和key-value上是单线程的pairs是的,所以直接在客户端Redis-cli执行Redis命令可能会导致运行延迟较大。使用复杂的命令会减慢Redis的处理速度,CPU占用过高,比如SORT、SUNION、ZUNIONSTORE聚合命令(时间责任O(N))。查询的数据量太大,导致数据协议的组装和网络传输的过程耗费了更多的时间。大key查询,比如大hash、zset等,这类对象给Redis集群数据迁移带来了很大的问题,因为在集群环境下,如果一个key过大,会导致数据迁移卡顿pause。另外在内存分配上,如果一个key太大,需要扩容的时候,会一次性申请更大的一块内存,也会造成卡顿。如果删除这个大key,内存会被一次性回收,又会出现卡顿现象。集中过期和较慢的时间是统一的,所以业务中的key过期时间尽量在统一的时间点加上一个随机数时间。内存使用率达到上限。当内存达到内存上限时,有些数据无法消除。这时候Redis的查询效率也可能很低。碎片整理,Redis在4.0版本后会自动进行碎片整理(因为内存回收过程中有大量碎片空间,不整理会造成Redis空间的小小浪费),碎片整理的过程会消耗CPU资源,从而影响要求的表现。网络带宽、Redis集群和业务混合,或者并发量过大每次返回的数据也很大。网卡带宽满的情况很容易导致网络拥塞。AOF的频率太高,因为AOF需要同步所有的写命令,如果同步间隔时间比较短,也会影响Redis的性能。Redis提供了flushdb和flushall指令来清空数据库,这也是导致Redis慢的操作。Redis安全默认会监听6379端口。最好在Redis配置文件中指定监听IP地址。进一步的,可以增加Redis的ACL访问控制,为客户指定分组,限制用户读写数据。访问Redis时,尽量使用公司代理。由于Redis本身不支持SSL链接,所以使用公司代理可以保证安全。客户端登录Redis必须设置Authsecretlogin。作者:何永康腾讯CSIG后台研发工程师编辑:陶家龙来源:转载自公众号云家社区(ID:QcloudCommunity)