redis pipeline
redis pipeline 是一个redis-cli提供的机制,而不是redis-server提供的;
目的:节约网络传输时间;
Redis通过网络传输请求通常由同步和异步两种方式:
同步:发一条请求,等待回复,再发一条请求,等待回复……
异步:一次性发送N条请求,然后等待回复。
前者能保证发送和回复的顺序性,后者则能保证高效率地发送。
redis pipeline属于异步网络传输。
Redis的发布-订阅模式
举例:当有三个服务器连接了一个redis,当有一条新的message进入了redis,这三个服务器都想能马上了解怎么办呢?
根据redis的监听-发布者订阅模式,需要这么操作:
- 建立一个channel。
- 三个服务器都去监听这个channel。
- 如果有一条新的message想迅速地被三个服务器知道,那么它往这这个channel发布message。
- 当然,message也能是其中一个server发布的,通过监听-发布者模式也能让三个server都迅速获message。
相关代码:
# 订阅频道 subscribe 频道 # 取消订阅频道 unsubscribe 频道 # 发布具体频道或模式频道的内容 publish 频道 内容 # 客户端收到具体频道内容 message 具体频道 内容
(右上角是发布者,其余是监听者。)
# 订阅模式频道 psubscribe 频道 # 取消订阅模式频道 punsubscribe 频道 # 发布具体频道或模式频道的内容 publish 频道 内容 # 客户端收到模式频道内容 pmessage 模式频道 具体频道 内容
一个redis-cli可以与数据库建立多条连接。
发布订阅功能一般要给同一个redis-cli开两个连接,一个负责监听,一个负责日常发命令;因为命令连接严格遵循请求回应模式;而 pubsub 能收到 redis 主动推送的内容。
所以实际项目中如果支持 pubsub 的话,需要另开一条连接用于处理发布订阅。
注意:发布订阅的生产者传递过来一个消息,redis 会直接找到相应的消费者并传递过去;假如没有消费者,消息直接丢弃;假如开始有2个消费者,一个消费者突然挂掉了,另外一个消费者依然能收到消息,但是如果刚挂掉的消费者重新连上后,在断开连接期间的消息对于该消费者来说彻底丢失了;另外,redis 停机重启,public和 subscribe 的消息是不会持久化的,所有的消息被直接丢弃;
所以我们只能在要求不确保消息到达的时候使用pub/sub模式。如果想确保消息一定能到达,需要使用其他模式,比如kafka,stream。
Redis事务
如果redis-server几乎同时收到了来在redis-cliA的3条操作指令,收到了redis-cliB的5条操作指令。因为redis-server处理命令的时候是单线程的,一次只能执行一条命令(具备原子性),有可能把A的3条命令和B的5条命令穿插着处理。如果有要求A和B的命令不能分割处理,要么先处理完A的再处理B的,要么先处理完B的再处理A的该怎么办呢?
这时候需要把A的命令和B的命令再发送前打包成【事务】。
事务:用户定义一系列数据库操作,这些操作视为一个完整的 逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。如果只有一个redis-cli连接redis-server,那么不用考虑事务。
Rredis事务的操作命令(了解即可,实务几乎不用):
MULTI:开启事务
EXEC:提交事务
DISCARD:取消事务
WATCH:检测 key
逻辑图:
如果MULTI之前先WATCH了某个key,这个key在EXEC之前发生了值变化,那么EXEC会清空事件队列。如果MULTI和EXEC中间pop了DISCARD命令,那么EXEC会清空事件队列。
然而,我们一般使用lua脚本来执行redis事务。
Redis-cli发送一串lua脚本给redis-server,然后redis-server用内置的lua虚拟机执行接收到的lua脚本。
# 测试使用 EVAL script numkeys key [key ...] arg [arg ...] # 线上使用 EVALSHA sha1 numkeys key [key ...] arg [arg ...]
key [key ...]是一个数字,表示script里用到的redis key的数量,arg [arg ...]则表示redis key的名字。
Sha怎么产生?用SCRIPT命令把命令注册到redis-server中
该方法有两个优点:1、用短短的字符串代替复杂脚本。2、只需要编译注册一次,能节省资源。
redis事务的ACID特性分析
redis满足A与I,不满足C与D。
A原子性;redis事务是一个不可分割的工作单位,事务中的操作要么全部执行,要么事前检测出事务中有错误的、不可执行的部分全部不执行;redis事务的原子性体现在此处。但是redis 不支持回滚;如果redis事务事前没检测出错误,事务正常执行,然而事务队列中的某个命令在执行期间出现了错误(比如有可能是因为内存不足的原因),整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止,这时候就会没有严格的原子性了,会出现一部分命令成功,一部分命令执行失败的情况,需要我们人工回滚。
I 隔离性;各个事务之间互不影响;redis 是单线程执行,天然具备隔离性;
C 一致性;redis 不能确保事务执行前后的数据的完整约束,也就是说数据状态(数据的取值或内容)会有事务内部操作只成功了部分,导致事务结束之后,数值的变化和事前预期不一致的情况。而且并不满足业务功能上的一致性(一致性的满足要么数据全部成功,要么出现失败然后全部回滚);比如转账功能,一个扣钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了钱;
D持久性;redis 只有在 aof 持久化策略的时候,并且需要在 redis.conf 中appendfsync=always 才具备持久性;实际项 目中几乎不会使用 aof 持久化策略;
Redis异步连接
同步连接:等待redis返回了命令执行后的报文再继续发送下一条命令。
异步连接:不等待redis返回命令执行后的报文,照样继续发送后续的报文。
异步连接的速度远高于同步连接的速度,甚至达到了数十倍,缺点是可能会造成业务逻辑的割裂。
如何实现异步连接了?
1、另起线程。
2、使用reactor模型。可以使用开源框架,hiredis(hi,redis)制作客户端。
3、使用proactor模型。
本文介绍一下使用基于hiredis建立能与redis通信reactor服务器的逻辑。
技术拆解:
1、与redis建立连接
a、创建socket,设置fd为非阻塞。
b、connet。
c、fd注册到epoll当中,绑定写事件。
d、如果连接建立成功,写事件会进行响应,然后注销写事件,注册读事件。
2、向redis发送数据
a、int n = write(fd,buf,size),根据redis协议加密数据包;如果n<size,说明对应的发送缓冲区已满。
b、注册写事件,如果写事件触发,继续发送数据,如果全部发送,注销写事件。通常来说客户端不需要通过epoll发现写事件,就能直接发送数据。但是异步发送数据速度非常快,可能会因为缓冲区的限制造成无法发送数据,这时候需要用epoll检测写事件的触发然后再行发送。
c、注册读事件。
3、读取redis 的返回
a、读事件触发
b、根据redis协议分割数据包
其中最关键的是,我们大多是时候不希望自己解析redis协议,那么就需要需要做的是把hiredis的适配器的关键接口实现。
若需要相关的demo可私信联系。