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

看完这篇文章,别再说不会 Redis 的高级特性了

时间:2023-03-17 16:00:31 科技观察

看完这篇文章,别再说你不知道Redis的高级特性了。转载本文请联系Java极客技术公众号。Redis是后端工程师的必备技能。阿芬每次接受采访都会被问到。阿粉特意将公众号发布的Redis系列文章整理成一篇,自己学习,帮助大家一起学习。文章较长,建议先收藏一下再看。Redis的数据类型有哪些?Redis有五种数据类型,每种数据类型都有相关的命令。类型如下:String(字符串)List(列表)Hash(字典)Set(集合)SortedSet(带Ordered集合)Redis有五种常见的数据类型,每一种都有自己的使用场景。一般的字符串类型使用最广泛,常见的Key/Value都是这种类型;列表类型常用于粉丝列表和关注列表的场景;字典类型是哈希表结构,在各种系统中也被广泛使用,可以用来存储用户或设备信息,类似于HashMap的结构;Redisset提供的功能类似于列表类型,也是列表功能。区别在于Set是去重的;有序集函数与Set相同,但顺序不同。Redis内存回收与Key过期策略Redis内存过期策略过期策略配置Redis随着使用的时间越来越长,会占用越来越多的内存,所以当Redis内存不够用的时候,我们需要知道Redis是根据什么策略来的淘汰数据,我们在配置文件中使用maxmemory-policy配置策略,如下图,我们可以看到策略的值如下:volatile-lru:对所有有过期时间的key使用LRU算法进行淘汰数据;alkeys-lru:使用最近最少使用的LRU算法,淘汰所有key中的数据,保证新增数据正常;volatile-random:随机剔除所有有过期时间的key中的数据;allkeys-random:随机剔除所有key中的数据;volatile-ttl:剔除所有有过期时间的key中最早过期的数据;noeviction:不回收,当达到最大内存时,添加新数据时返回错误,不清除旧数据。这是Redis的默认策略;volatile-lru、volatile-random、volatile-ttl等同于Redis中没有过期Key时的noeviction策略。淘汰策略可动态调整,调整时无需重启。原文是这么说的,我们可以根据自己的Redis模式动态调整策略。“根据应用程序的访问模式选择正确的驱逐策略很重要,但是您可以在应用程序运行时在运行时重新配置策略,并使用RedisINFO输出监控缓存未命中和命中的次数,以便调整yoursetup。ApproximateLRUalgorithmRedis中的LRU算法不是精确的LRU算法,而是采样的LRU,我们可以通过设置maxmemory-samples5来设置采样的大小配置文件,默认值为5,我们可以自己设置调整,官方采用的对比如下,我们可以看到当采用数设置为10时,就是already非常接近真正的LRU算法。在Redis3.x以上版本进行了优化。目前的近似LRU算法在效率上有了很大的提升。Redis之所以没有对实际的LRU算法进行采样,是因为它非常消耗内存。原文是这样说的Redis之所以没有使用真正的LRU实现,是因为比较耗内存。Key的过期策略设置key带过期时间前面介绍了Redis的内存回收策略,我们来看Key的过期策略,提到了Key的过期policy,我们说的是有过期时间的key,当然是通过redis>setnameziyouuex100命令,我们在Redis中设置一个key作为name,value就是ziyouu的数据,从上面的截图我们可以看到右下角有一个TTL,每刷新一次就递减,说明我们已经成功给key设置了过期时间。Redis如何清除有过期时间的key对于如何清除过期的key,我们很自然地可以想到可以给每个key加一个定时器,这样当时间到了过期时间,key就会被自动删除。这种策略我们称之为时机策略。这种方式对内存友好,因为过期的可以及时清理,但是由于每个有过期时间的key都需要定时器,所以这种方式对CPU不友好,会占用大量CPU。另外,这种方法是一种主动行为。有主动的和被动的。代替定时器,我们可以在每次访问key的时候判断key是否到了过期时间,过期就删除。我们称这种方法为惰性策略。这种方式对CPU是友好的,但是也有一个相应的问题,就是如果我们永远不访问这些过期的key,它们永远不会被删除。在实际实现Redis服务器的时候,会用到上面两种方式,这样可以得到一个折中的方法。另外,在计时策略中,我们可以从官网看到如下说明具体是Redis每秒做10次:从关联过期的key集合中随机测试20个key。删除所有发现过期的密钥。如果超过25%的key过期,则从步骤1重新开始。也就是说Redis会从设置过期时间的key中随机选择20个key,删除已经过期的key,如果超过25则重新执行操作%。每秒执行10个这样的操作。你知道Redis的发布订阅功能吗?发布订阅系统在我们的日常工作中经常用到。在大多数情况下,我们使用消息队列。常用的消息队列有Kafka、RocketMQ、RabbitMQ,每个消息队列都有自己的特点。其实很多时候,我们可能不需要独立部署相应的消息队列,只是简单的使用,数据量不会太大。这种情况下,我们可以考虑使用Redis的Pub/Sub模型。如何使用发布和订阅Redis的发布和订阅功能主要由PUBLISH、SUBSCRIBE和PSUBSCRIBE命令组成。一个或多个客户端订阅一个或多个频道。当其他客户端向该频道发送消息时,他们订阅了该频道。客户端会收到相应的消息。上图中,有四个客户端,Client02、Client03、Client04订阅了同一个Sport频道(Channel)。此时,当Client01向SportChannel发送消息“篮球”时,三个Client02-04两端同时收到消息。整个过程的执行命令如下:首先打开四个Redis客户端,然后在Client02、Client03、Client04分别输入subscribesport命令,表示订阅体育频道,然后进入publishsportbasketball在Client01客户端中将消息“basketball”发送到体育频道。这个时候我们在看Client02-04的clients,可以看到消息已经收到了,每个订阅这个频道的client都是一样的。这里Client02-Client04订阅Sport频道,我们称为订阅者,Client01发布消息,我们称为发布者,发送的消息为消息。模式订阅我们之前看到的是客户端订阅一个频道。事实上,单个客户端也可以同时订阅多个Channel。使用模式匹配,客户端可以同时订阅多个频道。如上图,Client05通过命令subscriberun订阅了run频道,Client06通过命令psubscriberun*订阅了频道匹配run*。当客户端07向运行通道发送消息666时,客户端05和06都收到消息;当Client07向run1和run_sport这两个通道发送消息时,Client06仍然可以收到消息,而Client05则收不到消息。客户端05订阅run通道并接收消息:客户端06订阅run*通道并接收消息:image-20191222141458065客户端07向多个通道发送消息:image-20191222141514914通过上面的案例,我们了解到一个客户端可以订阅到单个或多个通道,分别通过subscribe和psubscribe命令,客户端可以通过publish发送相应的消息。在命令行中,我们可以使用Ctrl+C来取消相关的订阅,对应的命令是unsubscribechannelName。Pub/Sub底层存储结构订阅Channel在Redis底层结构中,通过字典加链表的结构保存客户端与Channel的订阅关系,形式如下:Redis底层结构中,Redis服务器结构定义了一个pubsub_channels字典structredisServer{//用来保存所有频道的订阅关系dict*pubsub_channels;}在这个字典中,key代表频道名,value是一个链表,里面存储了所有的频道订阅此频道的客户。因此,当客户端执行订阅频道的动作时,服务器会将客户端与pubsub_channels字典中订阅的频道相关联。此时有两种情况:频道是第一次订阅:频道是第一次订阅,说明字典中没有该频道的信息,那么程序首先要创建一个对应的key并赋值一个空链表,然后将相应的客户端添加到链表中。此时链表只有一个元素。该频道已经被其他客户端订阅:此时只需在链表末尾添加相应的客户端信息即可。比如新客户端Client08要订阅run频道,那么上图就会变成如果Client08要订阅新频道new_sport,那么就变成image-20191222161558999整个订阅流程就可以用了如下代码实现Map>pubsub_channels=newHashMap<>();publicvoidsubscribe(String[]subscribeList,Objectclient){//遍历所有订阅的频道,检查是否在pubsub_channels中,如果不在,新建key和空列表for(inti=0;i());}pubsub_channels.get(subscribeList[i]).add(client);}}取消订阅上面描述了单个频道的订阅。相反,如果一个client想要取消订阅相关的Channel,无非就是找到对应Channel的链表,从中删除对应的client。如果client是最后一个,相应的Channel也会被删除。publicvoidunSubscribe(String[]subscribeList,Objectclient){//遍历所有订阅的频道,依次删除for(inti=0;ipubsub_patterns=newArrayList<>();publicvoidpsubscribe(String[]subscribeList,Objectclient){//遍历所有订阅的频道,为(inti=0;i