• 1824章


    18、发布与订阅

    • Redis 的发布与订阅功能由PUBLISH、SUBSCRIBE、PSUBSCRIBE等命令组成

    • 频道的订阅与退订

      • Redis将所有频道的订阅关系都保存在服务器状态的pubsub_channels字典里面,这个字典的键是某个被订阅的频道,而键的值则是一个链表,链表里面记录了所有订阅这个频道的客户端

      • UNSUBSCRIBE命令的行为和SUBSCRIBE命令的行为正好相反,当一个客户端退订某个或某些频道的时候,服务器将从pubsub_channels中解除客户端与被退订频道之间的关联

    • 模式的订阅与退订

      • 服务器也将所有模式的订阅关系都保存在服务器状态的pubsub_patterns中。pubsub_patterns属性是一个链表,链表中的每个节点都包含着一个pubsubPattern结构,这个结构的pattern属性记录了被订阅的模式,而client属性则记录了订阅模式的客户端

      • 订阅模式就会创建一个pubsubPattern,插入到链表的结尾。退订就会删除链表的最后一个节点

    • 将消息发送给频道订阅者

      • 遍历链表,把消息发送给链表上的所有订阅者

    • 将消息发送给模式订阅者

      • 遍历整个pubsub_patterns链表,查找那些与channel频道相匹配的模式,并将消息发送给订阅了这些模式的客户端。

    • 查看订阅信息

      • pubsub channels:返回服务器当前被订阅的频道。底层是遍历pubsub_channels所有的键

      • pubsub numsub [ channel1 channel2 ..]:返回频道的订阅者数量,底层是返回键所对应的链表长度

      • pubsub numpat:返回服务器当前被订阅模式的数量

    19、事务

    • 事务的实现

      • 事务开始:MULTI命令可以将执行该命令的客户端从非事务状态切换至事务状态,这一切换是通过在客户端状态的flags属性中打开REDIS_MULTI标识来完成的

      • 命令入队:

        • 每个Redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的mstate属性。事务状态包含一个事务队列,以及一个已入队命令的计数器。事务队列是一个multicmd类型的数组,数组中的每个multicmd结构都保存了一个已入队命令的相关信息,包括指向命令实现函数的指针、命令的参数,以及参数的数量,事务队列以FIFO的方式保存入队的指令

      • 事务执行:当一个处于事务状态的客户端向服务器发送EXEC命令时,这个EXEC命令将立即被服务器执行。服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果全部返回给客户端。

    • WATCH命令的实现

      • WATCH命令是一个乐观锁( optimistic locking ),它可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。

      • 每个Redis数据库都保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端。

      • 通过一个函数触发监视机制,会将监视被修改键的客户端的REDIS DIRTY CAS标识打开,表示该客户端的安全性事务被破坏

    • 事务的ACID性质

      • 原子性:Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制( rollback ),即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。

      • 一致性:Redis通过谨慎的错误检测和简单的设计来保证事务的一致性

        • 入队错误:命令不存在或者格式不正确

        • 执行错误:比如设置了一个字符串键,但是入队的指令是列表键等

        • 服务器停机:无论Redis服务器运行在哪种持久化模式下,事务执行中途发生的停机都不会影响数据库的一致性。

      • 隔离性:多个事务并发执行,事务与事务之间不影响

      • 持久性:当一个事务执行完毕时,执行这个事务所得的结果已经被保存到永久性存储介质(比如硬盘)里面了,即使服务器在事务执行完毕之后停机,执行事务所得的结果也不会丢失。根据持久化模式决定

    20、LUA脚本

    • 创建并修改LUA环境

      • 创建LUA环境:服务器首先调用Lua的CAPI函数lua_open,创建一个新的 Lua环境

      • 载入函数库:基础库、表格库、字符串、数学库、调试库、lua CJSON库、struct库、lua cmsgpack库

      • 创建redis全局表格:把这个表格设置为全局变量,表格中包含以下函数:redis.call、redis.pcall.....

      • 使用Redis自制的随机函数来替换Lua原有的随机函数:为了保证相同的脚本可以在不同的机器上产生相同的结果,Redis要求所有传人服务器的Lua脚本,以及Lua环境中的所有函数,都必须是无副作用( side effect)的纯函数(purefunction )。但是数学库中的random函数有副作用

      • 创建排序辅助函数:对于Lua脚本来说,另一个可能产生不一致数据的地方是那些带有不确定性质的命令。比如对于一个集合键来说,因为集合元素的排列是无序的,所以即使两个集合的元素完全相同,它们的输出结果也可能并不相同。为了消除命令的不确定性,会创建排序辅助函数

      • 创建redis.pcall函数的错误报告辅助函数

      • 保护LUA的全局变量:在这一步,服务器将对Lua环境中的全局环境进行保护,确保传入服务器的脚本不会因为忘记使用local关键字而将额外的全局变量添加到Lua环境里面。

      • 将Lua环境保存到服务器状态的lua属性里面

    • LUA环境协作组件

      • 伪客户端:因为执行Redis命令必须有相应的客户端状态,所以为了执行Lua脚本中包含的Redis命令,Redis服务器专门为Lua环境创建了一个伪客户端,并由这个伪客户端负责处理Lua脚本中包含的所有Redis命令。

      • lua_scripts字典:这个字典的键为某个Lua脚本的SHA1校验和( checksum ),而字典的值则是SHA1校验和对应的lua脚本。有以下两个功能:

        • 实现SCRIPT EXISTS命令

        • 另一个是实现脚本复制功能

    • EVAL命令的实现

      • 定义脚本函数:当客户端向服务器发送EVAL命令,要求执行某个Lua脚本的时候,服务器首先要做的就是在Lua环境中,为传入的脚本定义一个与这个脚本相对应的Lua函数,其中,Lua函数的名字由f_前缀加上脚本的SHA1校验和(四十个字符长)组成,而函数的体( body )则是脚本本身。

      • 将脚本保存到lua_scripts字典中:

      • 执行脚本函数:

    • EVALSHA命令的实现

      • 只要脚本对应的函数曾经在Lua环境里面定义过,那么即使不知道脚本的内容本身,客户端也可以根据脚本的SHA1校验和来调用脚本对应的函数,从而达到执行脚本的目的,这就是EVALSHA命令的实现原理。

    • 脚本管理命令的实现

      • script flush:用于清除服务器中所有和Lua脚本有关的信息,这个命令会释放并重建lua_scripts字典,关闭现有的Lua环境并重新创建一个新的Lua环境。

      • SCRIPT EXISTS:命令根据输入的SHA1校验和,检查校验和对应的脚本是否存在于服务器中。

      • SCRIPT LOAD:命令所做的事情和EVAL命令执行脚本时所做的前两步完全一样:命令首先在Lua环境中为脚本创建相对应的函数,然后再将脚本保存到lua_scripts字典里面。

      • script kill

    • 脚本复制

      • Redis复制EVAL、SCRIPT FLUSH、SCRIPT LOAD三个命令的方法和复制其他普通Redis命令的方法一样,当主服务器执行完以上三个命令的其中一个时,主服务器会直接将被执行的命令传播( propagate)给所有从服务器

      • 对于一个在主服务器被成功执行的EVALSHA命令来说,相同的EVALSHA命令在从服务器执行时却可能会出现脚本未找到( not found)错误。

        • 为了防止以上假设的情况出现,Redis要求主服务器在传播EVALSHA命令的时候,必须确保EVALSHA命令要执行的脚本已经被所有从服务器载入过,如果不能确保这一点的话,主服务器会将EVALSHA命令转换成一个等价的EVAL命令,然后通过传播EVAL命令来代替EVALSHA命令。

    21、排序

    • sort< key >实现原理

      • SORT命令为每个被排序的键都创建一个与键长度相同的数组,数组的每个项都是一个redissort0bject结构,根据数组项中的u.scores进行排序

    • alpha选项的实现

      • 通过使用ALPHA选项,SORT命令可以对包含字符串值的键进行排序:sort key alpha

    • asc和desc命令的实现

      • 都使用了快排

    • by选项的实现

      • 通过使用BY选项,SORT命令可以指定某些字符串键,或者某个哈希键所包含的某些域( field)来作为元素的权重,对一个键进行排序。redis> SORT fruits BY *-price1

      • 也是根据u.scores排序

    • limit实现

      • 通过LIMIT选项,我们可以让SORT命令只返回其中一部分已排序的元素

    • get的实现

      • SORT命令在对键进行排序之后,总是返回被排序键本身所包含的元素。但是,通过使用GET选项,我们可以让SORT命令在对键进行排序之后,根据被排序的元素,以及GET选项所指定的模式,查找并返回某些键的值。

    • store的实现

      • 默认情况下sort只向客户端返回排序的结果,他并不保存结果。但是,通过使用STORE 选项,我们可以将排序结果保存在指定的键里面,并在有需要时重用这个排序结果:

    • 多个选项的执行顺序

      • 排序:在这一步,命令会使用ALPHA 、ASC或DESC、BY这几个选项,对输入键进行排序,并得到一个排序结果集。

      • 限制排序结果集的长度:在这一步,命令会使用LIMIT选项,对排序结果集的长度进行限制,只有LIMIT选项指定的那部分元素会被保留在排序结果集中。

      • 获取外部键:在这一步,命令会使用GET选项,根据排序结果集中的元素,以及GET选项指定的模式,查找并获取指定键的值,并用这些值来作为新的排序结果集。

      • 保存排序结果集:在这一步,命令会使用STORE 选项,将排序结果集保存到指定的键上面去。

      • 向客户端返回排序结果集:在最后这一步,命令遍历排序结果集,并依次向客户端返回排序结果集中的元素。

    22、二进制位数组

    • 位数组的表示

      • 使用字符串对象来保存位数组,SDS。需要注意的是,buf数组保存位数组的顺序和我们平时书写位数组的顺序是完全相反的,因为可以简化SETBIT的实现

    • bitcount命令的实现

      • 遍历算法

      • 查表算法:创建一个表,表的键为数组的位置,值为单个数组中1的个数

      • variable-precision SWAR算法

      • redis的实现:如果未处理的二进制位的数量大于等于128位,那么程序使用variable-precisionSWAR算法来计算二进制位的汉明重量。如果未处理的二进制位的数量小于128位,那么程序使用查表算法来计算二进制位的汉明重量。

    • bitop命令的实现

    23、慢查询日志

    • Redis 的慢查询日志功能用于记录执行时间超过给定时长的命令请求,用户可以通过这个功能产生的日志来监视和优化查询速度。

    • Redis的慢查询日志功能用于记录执行时间超过指定时长的命令。

    • Redis服务器将所有的慢查询日志保存在服务器状态的slowlog链表中,每个链表节点都包含一个slowlogEntry结构,每个slowlogEntry结构代表一条慢查询日志。

    • 打印和删除慢查询日志可以通过遍历slowlog链表来完成。slowlog链表的长度就是服务器所保存慢查询日志的数量。

    • 新的慢查询日志会被添加到slowlog链表的表头,如果日志的数量超过slowlog-max-len选项的值,那么多出来的日志会被删除。

    24、监视器

    • 客户端可以通过执行MONITOR命令,将客户端转换成监视器,接收并打印服务器处理的每个命令请求的相关信息。

    • 当一个客户端从普通客户端变为监视器时,该客户端的REDIS_MONITOR标识会被打开。

    • 服务器将所有监视器都记录在monitors链表中。

    • 每次处理命令请求时,服务器都会遍历monitors链表,将相关信息发送给监视器。

  • 相关阅读:
    火狐浏览器kaptcha验证码点击无法刷新问题解决方法
    算法学习笔记——洗碗时遇到的汉诺塔问题
    JSP学习笔记
    springmvc中Tomcat跨服务器上传中文名文件报错解决方案
    MAVEN项目报错解决方法集锦(1)
    原生js模板语法之underscore.js
    HTML针式打印机打印模板
    elementui form表单验证
    vue+swiper背景图随swiper改变
    uni.navigateTo和uni.switchTab的区别
  • 原文地址:https://www.cnblogs.com/zyj23/p/16156140.html
Copyright © 2020-2023  润新知