文章目录
管道
一个请求/相应服务可以实现如下,即使客户端没有读取到旧请求的响应,服务端依旧可以处理新请求。通过这种方式,可以完全无需等待服务端应答地发送多条指令给服务端,并最终一次性读取所有应答。管道技术最显著的优势是提高了redis服务的性能。
yum install nc
nc localhost 6379
keys *
echo -e "set k2 99\nincr k2\n get k2" | nc localhost 6379
注:
需要注意的是,命令与命令之间是需要\n换行符隔开的。echo打印set k2 99 \n incr k2 \n get k2字符串,-e参数会识别字符串的\n为换行,就相当于发送了三条命令set k2 99,incr k2,get k2,然后把打印的内容交给nc localhost 6379也就是redis。
总结:管道的作用就是让通信的成本变低了一些,仅此而已。
Pub/Sub
订阅,取消订阅和发布实现了发布/订阅消息范式(引自wikipedia),发送者(发布者)不是计划发送消息给特定的接收者(订阅者)。而是发布的消息分到不同的频道,不需要知道什么样的订阅者订阅。订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道什么样的发布者发布的。这种发布者和订阅者的解耦合可以带来更大的扩展性和更加动态的网络拓扑。
Redis可以执行发布/订阅模式(publish/subscribe), 该模式可以解耦消息的发送者和接收者,使程序具有更好的扩展性.从宏观上来讲,Redis的发布/订阅模式具有如下特点:
- 客户端执行订阅以后,除了可以继续订阅(SUBSCRIBE或者PSUBSCRIBE),取消订阅(UNSUBSCRIBE或者PUNSUBSCRIBE), PING命令和结束连接(QUIT)外, 不能执行其他操作,客户端将阻塞直到订阅通道上发布消息的到来.
- 发布的消息在Redis系统中不存储.因此,必须先执行订阅,再等待消息发布. 但是,相反的顺序则不支持.
- 订阅的通道名称支持glob模式匹配.如果客户端同时订阅了glob模式的通道和非glob模式的通道,并且名称存在交集,则对于一个发布的消息,该执行订阅的客户端接收到两个消息.
pub/sub API
Redis的发布/订阅设计模式相关的命令有六个:
- SUBSCRIBE命令执行订阅.客户端可以多次执行该命令, 也可以一次订阅多个通道. 多个客户端可以订阅相同的通道.该命令的响应包括三部分, 依次是:命令名称(字符串subscribe),订阅的通道名称,总共订阅的通道数(包含glob通道).
- PSUBSCRIBE命令执行glob模式订阅.客户端可以多次执行该命令, 也可以一次订阅多个glob通道. 多个客户端可以订阅相同的glob通道.该命令的响应包括三部分, 依次是:命令名称(字符串psubscribe),订阅的glob通道名称,总共订阅的通道数(包含非glob通道).
- UNSUBSCRIBE命令取消订阅指定的通道.可以指定一个或者多个取消的订阅通道名称,也可以不带任何参数,此时将取消所有的订阅的通道(不包括glob通道).该命令的响应包括三部分, 依次是:命令名称(字符串unsubscribe),取消的订阅通道名称,总共订阅的通道数(包含glob通道).
- PUNSUBSCRIBE命令取消订阅指定的glob模式通道.可以指定一个或者多个取消的glob模式的订阅通道名称,也可以不带任何参数,此时将取消所有的glob模式订阅的通道(不包括非glob通道).该命令的响应包括三部分, 依次是:命令名称(字符串punsubscribe),取消的glob模式的订阅通道名称,总共订阅的通道数(包含非glob通道).
- PUBLISH命令在指定的通道上发布消息.只能在一个通道上发布消息,不能在多个通道上同时发布消息.该命令的响应包括通知的接收者个数,需要注意的是,这里的接收者数目大于等于订阅该通道的客户端数目(因为一个客户端的glob通道和非glob通道同时匹配发布通道的话,则视为两个接收者).而在接收端,收到的响应包括三部分,依次是 :message或者pmessage字符串(取决于是否为glob匹配),匹配的通道名称,发布的消息内容.
- PUBSUB命令执行状态查询.支持若干子命令.需要注意的是,该命令不能在客户端进入订阅后执行.
127.0.0.1:6379> help @pubsub
PSUBSCRIBE pattern [pattern ...]
summary: Listen for messages published to channels matching the given patterns
since: 2.0.0
PUBLISH channel message
summary: Post a message to a channel
since: 2.0.0
PUBSUB subcommand [argument [argument ...]]
summary: Inspect the state of the Pub/Sub subsystem
since: 2.8.0
PUNSUBSCRIBE [pattern [pattern ...]]
summary: Stop listening for messages posted to channels matching the given patterns
since: 2.0.0
SUBSCRIBE channel [channel ...]
summary: Listen for messages published to the given channels
since: 2.0.0
UNSUBSCRIBE [channel [channel ...]]
summary: Stop listening for messages posted to the given channels
since: 2.0.0
实战
[root@z8524210 ~]# redis-cli
127.0.0.1:6379> clear
127.0.0.1:6379> SUBSCRIBE channe1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channe1"
3) (integer) 1
1) "message"
2) "channe1"
3) "hello"
1) "message" //收到消息
2) "channe1"
3) "how do you do!" //接收到消息后依然是监听状态
127.0.0.1:6379> PUBLISH channe1 hello //publish消息
(integer) 2
127.0.0.1:6379> PUBLISH channe1 "how do you do!" //publish消息
(integer) 2
127.0.0.1:6379>
应用
面对聊天消息,redis如何做缓存?
- read方面
- 对于 实时性 的聊天记录,我们就可以使用redis提供的Pub/Sub就能够解决。
- 对于 更老的 聊天记录,因为这些数据访问量会很低,所以一定是从数据库里面获取。
- 对于近期三天内的记录,我们可以把聊天记录存入redis的sorted set类型,把时间抽象成分数,让其排序,并且可以使用zremrangebyscore命令删除最近三天之外的聊天记录。同时还可以使用zremrangebyrank命令删除指定条数之外的记录。(sorted set物理内存左小右大,距离当前最近的时间数值一定大于三天前时间的数值,根据索引,删除条目之外的数据)
- write 方面
- 准备两个Redis,第一个Redis负责Pub/Sub,第二个redis负责sorted set相关功能。
- Client1给Pub/Sub redis发送消息时,Client2可以从Pub/Sub Redis服务器取走消息,同时sorted set Redis器从Pub/Sub Redis订阅并且存入sorted set类型,自己写的service也从Pub/Sub Redis订阅消息并交给kafka存入数据库。
总结:
- 订阅客户端上,才能取消订阅. 一个客户端连接不能为另一个客户端连接取消订阅. 这是显而易见的.
- 在发布/订阅模式下,通道名称是全局的,和客户端连接的Redis数据库没有关系.
缓存的数据(热数据)
那么redis是如何做到数据随着访问变化只保留热数据呢?
- 业务逻辑: key是可以设置有效期的,具体key的有效期 需要根据 用户 的关注时间窗而设定的。过了这个时间窗这个数据就不应该出现在redis里,因为这个数据被访问的次数会很低,不能称之为热数据。
- 业务运转: 内存是有限的,当redis内存满了,应该采取相应的策略剔除掉冷数据。
redis内存上限多少呢?
可以通过6379.config配置文件来设定:maxmemory 单位为byte .
redis内存满了之后又如何处理?
在6379.config配置文件来设定maxmemory-policy noeviction。
LRU 的关注点是 该元素多久没有被使用。
LFU 的关注点是 该元素被使用了多少次。
redis提供了6种回收策略
noeviction: 直接返回错误,不允许Client继续使用了。(更适合redis作为数据库,能保持数据的完整性)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。(常用,结合业务场景,非期限key多)
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在有期限元素集合内,使得新添加的数据有空间存放。(常用,结合业务场景,期限key多)
allkeys-random: 回收随机的键使得新添加的数据有空间存放。(比较随意)
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在有期限元素集合内。(比较随机)
volatile-ttl: 回收在过期集合的键,并且优先回即将过期的键,使得新添加的数据有空间存放。(复杂度高)
key的有效期
- 设置key的过期时间,超过时间后,将会自动删除该key。在Redis的术语中一个key的相关超时是不确定的
- 超时后只有对key执行DEL命令或者SET命令或者GETSET时才会清除。这意味着,从概念上讲所有改变key的值的操作都会使他清除。 例如,INCR递增key的值,执行LPUSH操作,或者用HSET改变hash的field所有这些操作都会触发删除动作
- 使用PERSIST命令可以清除超时,使其变成一个永久的key
- 如果key被RENAME命令修改,相关的超时时间会转移到新key上面
- 如果key被RENAME命令修改,比如原来就存在Key_A,然后调用RENAME Key_B Key_A命令,这时不管原来Key_A是永久的还是设置为超时的,都会由Key_B的有效期状态覆盖
- 对已经有过期时间的key执行EXPIRE操作,将会更新它的过期时间。有很多应用有这种业务场景,例如记录会话的session
Keys的过期时间
- 通常Redis keys创建时没有设置相关过期时间。他们会一直存在,除非使用显示的命令移除,例如,使用DEL命令
- EXPIRE一类命令能关联到一个有额外内存开销的key。当key执行过期操作时,Redis会确保按照规定时间删除他们
- key的过期时间和永久有效性可以通过EXPIRE和PERSIST命令(或者其他相关命令)来进行更新或者删除过期时间
过期精度
- 在 Redis 2.4 及以前版本,过期期时间可能不是十分准确,有0-1秒的误差
- 从 Redis 2.6 起,过期时间误差缩小到0-1毫秒
过期和持久
- Keys的过期时间使用Unix时间戳存储(从Redis 2.6开始以毫秒为单位)。这意味着即使Redis实例不可用,时间也是一直在流逝的
- 要想过期的工作处理好,计算机必须采用稳定的时间。 如果你将RDB文件在两台时钟不同步的电脑间同步,有趣的事会发生(所有的 keys装载时就会过期)
- 即使正在运行的实例也会检查计算机的时钟,例如如果你设置了一个key的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后
如何淘汰过期的keys
Redis keys过期有两种方式:被动和主动方式
- 当一些客户端尝试访问它时,key会被发现并主动的过期
当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除
- Redis每秒10次做的事情:
- 测试随机的20个keys进行相关过期检测。
- 删除所有已经过期的keys。
- 如果有多于25%的keys过期,重复步奏1.
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys
在复制AOF文件时如何处理过期
- 为了获得正确的行为而不牺牲一致性,当一个key过期,DEL命令将会随着AOF文件一起合成到所有附加的slaves。在master实例中,这种方法是集中的,并且不存在一致性错误的机会。
- 然而,当slaves连接到master时,不会独立过期keys(会等到master执行DEL命令),他们任然会在数据集里面存在,所以当slave当选为master时淘汰keys会独立执行,然后成为master
事务
redis的事务为什么不支持回滚?
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速
redis概念
-
redis是单进程的,所以事物回滚存在一些与关系型数据库的差别
-
multi,exec,discard,watch(乐观锁的效果)
mutli命令 和 exec命令:
首先redis是单进程的,单进程就像一个管道,当我们先执行mutli命令之后,且在redis接收到exec命令之前(夹在mutli和exec中间的命令)是不会得到执行的。 -
如果有多个Client同时发送mutli命令进入redis的单线程进行排队,在单线程串行的理论上,关注点不是在于哪个Client的mutli命令先到达,而是哪个Client的exec命令先到到达redis被处理,那么exec命令先到达的Client的命令先被执行(从一个Client的mutil命令到达开始,这个Client的命令会被存放在一个单独的缓冲区,哪个缓冲区的exec命令先到就哪个缓冲区的命令先执行)。
watch命令:
- watch命令是一个乐观锁CAS的操作,就是在事务还没有执行的时候,通过一个watch命令 监控 将要被操作的元素,如果在这个元素在mutli开启之后没有等exec命令到达之前发生了改变,那么后续的事务会被直接撤销。 撤销之后如何处理,需要客户端自己捕获进行处理(重新提交等操作)。
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
1、事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2、事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
EXEC 命令负责触发并执行事务中的所有命令:
- 如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
- 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。
当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。
然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。
如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。
使用redis-check-aof程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。
从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作
使用事务时可能会遇上以下两种错误:
- 事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
- 命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。