Redis学习笔记(三):发布与订阅、事务

独立功能的实现
 
1.发布与订阅
 
Redis的发布与订阅功能由 publish,subscribe,psubscribe等命令组成
 
客户端订阅一个或多个频道
> subcribe "news.it" "news.et"   //订阅两个频道
 
客户端向频道发送消息
> publish "news.it" "hello"      //向news.it频道发送消息hello
订阅了news.it频道的客户端都会收到hello消息
 
客户端订阅一个或多个模式
> psubscribe "news.*" "book.*"   //相当于订阅了匹配这两个模式中任意一个的所有频道
 
客户端退订频道
> unsubscribe "news.sport"
客户端退订模式
> punsubscribe "book.*"
 
2.频道订阅原理
当客户端执行subscribe命令订阅频道时,客户端与频道就建立了一种订阅关系。该关系存储在服务器状态的 pubsub_channels 字典里。
字典键为订阅的频道;字典值为一个链表,链表中记录了所有订阅该频道的客户端
 
订阅频道时,根据是否是该频道的第一个订阅者进行不同处理
    1、是第一个订阅者,创建一个频道对应的键(是字符串对象),并将键值设置为空链表结构,然后将客户端添加到链表中。
    2、不是第一个订阅者,则已存在键,那么直接添加到对应的值链表结构的末尾。
 
退订频道时,根据是否是最后一个订阅者进行不同处理
    1、是最后一个订阅者,则删除退订客户端后,继续删除频道在字典中对应的键
    2、不是最后一个订阅者,则直接根据键找到对应的值链表结构,然后遍历删除退订客户端即可
 
模式订阅原理
模式的订阅关系保存在服务器状态的 pubsub_patterns 链表中
pubsub_patterns链表的每个节点都包含一个pubsubPattern结构
pubsubPattern结构包含pattern属性和client属性(这两个属性都是简单字符串构成,或者说这两个一起就是一个字符串对象?)
 
订阅模式
    新建一个pubsubPattern结构,将其pattern属性设置为对应模式,client属性设置为订阅者,然后添加到当前pubsub_patterns链表的表尾。
 
退订模式
    查找pattern属性为退订模式,client属性为退订者的pubsubPattern结构即可。
 
发送消息流程与原理
> publish <channel> <message>   //channel为频道

1、将消息发送给所有订阅该频道的订阅者
        原理:在pubsub_channels字典中找到对应的键,然后按照对应的值链表结构中的客户端依次发送消息
 
2、将消息发送给予该频道相匹配的模式的订阅者
        原理:在pubsub_patterns链表中遍历,找到所有匹配该频道的模式(pattern属性),并将消息发送给他们对应的客户端(client属性)
 
 
查看订阅信息的pubsub的三个子命令
> pubsub channels [pattern]     //查询服务器当前被订阅的所有频道,[]内为可选参数,可用于匹配限制
 
> pubsub numsub [channel1 channel2 ...]   //返回这些频道的订阅者数量,即订阅者键对应的值链表结构的长度
 
> pubsub numpat                //返回服务器当前被订阅的模式的数量,即返回pubsub_patterns链表的长度
 
3.事务(transaction)
 
概念:将一种或多个命令打包,然后一次性、按顺序的统一执行。
 
相关命令
 
监听事务中的数据库键
> watch <key_name>    //key_name为要监听的数据库键,用于判断数据库键是否在事务过程中被其他客户端修改
 
开启事务
> multi
 
提交并执行事务
> exec
 
注意:事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为pipeline。 pipeline 可以一次性发送多条命令并在执行完后一次性将结果返回,可以减少客户端与服务器之间的网络通信次数从而提升性能,并且 pineline 基于队列,而队列的特点是先进先出,这样就保证数据的顺序性。
 
事务的实现
一般分为三个阶段
 
1.事务开始
    使用multi命令,执行该命令的客户端从非事务状态切换到事务状态
    客户端状态的flags属性会打开 REDIS_MULTI标识,以标志该客户端开启事务,处于事务状态
 
2.命令入队
    非事务状态下,Redis客户端发送的命令会被服务器直接执行
    事务状态下,Redis服务器会将客户端发送的命令放入一个事务队列,并返回QUEUE(exec,discard,watch,multi四个命令除外)
 
    每个客户端有自己的事务状态,该状态保存在客户端的mstate属性里(是一个multiState结构,内含一个FIFO事务队列和一个计数器count)
 
    事务队列是一个multiCmd数组,每个multiCmd元素都存储了一个入队命令的信息(函数指针,命令参数,参数数量)
 
    count计数器则用来统计事务队列的长度。
 
3.事务执行
    客户端发送 exec 命令后
    服务器遍历客户端的事务队列,单线程串行执行事务队列中的事务,并将每个命令的结果依次返回给客户端
 
 
Watch命令
 
    watch命令是一个乐观锁,在exec命令前执行,用来监视数据库键在事务建立过程中是否发生改变
    watch命令字exec命令执行时,会检查其监视的键是否发生了修改;如果发生修改,则服务器会拒绝执行事务。
 
    底层实现
        每个Redis服务器都保存了一个 watched_keys 字典,
        该字典的键为被监视的数据库键,值是一个链表结构,链表中记录了所有监视该数据库键的客户端
    
    原理实现
        一旦服务器执行了对数据库进行修改的命令(如SET,LPUSH,DEL,ZADD等),便会在执行后调用一个函数对 watched_keys字典进行检查,查看被监视的键是否发生了修改;有,则将监视该数据库键的所有客户端的REDIS_DIRTY_CAS标识打开,以表示客户端事务安全性被破坏。而执行exec命令时,会对客户端的REDIS_DIRTY_CAS标识进行判断,如果发现该标识已打开,则认为事务不安全,并拒绝执行该事务。
 
Redis事务的ACID
    原子性、一致性、隔离性、持久性
 
    Redis事务总具有原子性、一致性、隔离性;特定情况下也会具有持久性(AOF持久化模式下,且appendfsync设置为always)
 
    一致的概念:数据符合数据库的定义和要求,不包含非法或无效的错误数据
    
    Redis从三个方面维护一致性(错误检测和设计来维护)
        1.入队错误
            若命令不存在、格式不正确等,入队过程会报错,而服务器拒绝执行入队过程中出现错误的事务
 
        2.执行错误
            如命令正确但是操作键对象不符合等(SET操作列表等),出错命令被服务器识别,并进行相应错误处理,但错误命令不会对数据库进行任何修改
 
        3.服务器停机
            (1)RDB、AOF持久化维护
            (2)如果找不到对应文件,或处于无持久化模式下,则重启后数据库为空白,仍符合一致的要求
 
    Redis通过单线程、事务队列串行执行某个事务的命令,所以即使多个客户端事务并发,仍然不会互相影响
 
    要了解为什么大部分情况无法维护持久性(如RDB持久化,如appendfsync=everysec/no时的持久化为什么不能维护)
    
Redis事务和传统关系型数据库事务最大区别
    Redis不支持事务的回滚(roll back),执行事务命令期间,即使发现命令执行错误,整个事务也会继续执行下去,直到将该客户端的对应的事务队列中所有命令执行完毕。(问题是这样不就不满足原子性了吗?Redis作者认为这种情况是存在编译错误,实际生产中不会出现?)

猜你喜欢

转载自www.cnblogs.com/xiang9286/p/11003922.html