• Redis笔记(4)独立功能的实现


    1.前言

      本节记录一下redis的一些功能上的实现,包括发布订阅、事务、Lua脚本、排序、二进制位数组、慢查询日志和监视器。

    2.发布订阅

      上一章介绍sentinel的时候说到了sentinel会订阅主从服务器的hello频道,每个sentinel通过往这个频道中传递各自的状态,让其它sentinel发现自己并更新相关状态。

      Redis的发布与订阅功能是由PUBLISH、SUBSCRIBE、PSUBSCRIBE等命令组成。一个客户端订阅某个频道,一旦有消息推送到该频道,其就会知道相关消息。具体操作如下:

        SUBSCRIBE “xxx"    PUBLISH "xxx" "yyy" 订阅xxx频道的客户端就会接收到yyy消息。

      还可以使用匹配模式的方式订阅多个频道:

        PSUBSCRIBE ”new.[ie]t"   PUBLISH “new.it"  PUBLISH "new.et"

      实现原理:

        redisServer中有一个字段dict  *pubsub_channels保存了所有频道和订阅关系。键是频道,值是客户端构成的链表。

        有一个订阅者就会添加到这个字段中。

        退订频道时,从这里面删除。

      UNSUBSCRIBE

      模式的订阅与退订:

        redisServer中有一个字段list *pubsub_patterns保存了所有模式订阅关系.

        PSUBSCRIBE的时候就会添加到这里面。

        退订就是使用PUNSUBSCRIBE方法。

      发送消息:

        PUBLISH方法会将发送的消息,遍历pubsub_channels和pubsub_patterns找到对应的客户端发送给他们。

      查看订阅信息:

        PUBSUB命令是redis2.8新增加的命令之一,可以通过这个查看频道或者模式的相关信息。

        PUBSUB CHANNELS  或者PUBSUB CHANNELS “news.[is]*"

        PUBSUB NUMSUB查看输入的频道的订阅者数量

        PUBSUB NUMPAT 查看当前订阅模式有多少个

    3.事务

      redis通过MULTI、EXEC和WATCH等命令来实现事务功能。提供了一种将多个命令请求打包,然后一次性、按顺序执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令执行完毕。

      例如: MULTI

         SET "name" "xxx"

         GET “name"

         SET "author" "xx"

         EXEC

      事务的实现:

        MULTI意味着开始,客户端切换到事务状态,修改flags。

        EXEC、DISCARD、WATCH、MULTI四个命令会立刻执行。其他命令进入事务队列,返回QUEUED回复。事务队列在multiState mstate事务状态中。

        接收到EXEC命令的时候,服务器会遍历事务队列,执行所有的结果并返回。

      WATCH:

        watch命令是一个乐观锁,可以在执行EXEC命令之前监视任何数量的数据库键。并在执行EXEC命令时,检查被监视的键是否被修改,如果是就会拒绝执行事务。

          WATCH "name"

          MULTI

          SET "name" "peter"

          EXEC

        redisDb中有一个dict *watched_keys,键是具体的键,值是监视这个键的客户端链表。执行命令前先判断这个键有没有被监视,被监视了修改所有客户端的标志成REDIS_DIRTY_CAS。意味着事务安全性被破坏。EXEC时候会检查这个标志,就能判断是否可以提交。

      事务的ACID性质:

        原子性,redis的事务要不全部执行要不都不执行。

        一致性,redis入队时会检查命令是否正确,执行过程中错误的命令不会对数据库进行修改,停机状态没持久化就没了,有持久化能够恢复。

        隔离性,redis没有并发,所以有隔离性

        耐久性,redis没有提供额外的持久化,都是通过redis的持久化模式决定的。加个SAVE可以有,但是效率太低。

    4. Lua脚本

      略。

    5.排序

      SORT可以对列表键,集合键或者有序集合键的值进行排序,即list,set,zset。

      实现原理:

        创建了一个与排序元素数量相同的数组,结构是redisSortObject,每个都对应一个元素。将元素进行排序,从小到大。最后遍历数组,返回排序后的元素。

        默认只能进行数字类型比较大小,字符串用字符串的比较方式要命令添加ALPHA选项。

      SORT可以配合ASC选项和DESC选项来实现。影响的就是结果的排序倒序还是顺序,SORT name ASC

      BY选项的实现:

        by选项可以实现某些字符串键或者hash键的某些域作为元素的权重进行排序。

        比如:SADD fruits "apple" "banana" "cherry"

           字典里面有MSET apple-price 8 banana-price 5.5 cherry-price 7

          对fruits按照价格排序 SORT fruits by *-price

          就会返回 banana cherry apple

        By选项默认保存的是数字值,如果权重保存的是字符串,要同时使用ALPHA选项

          mset apple-id  "xxx" banana-id "yyy" cherry-id "zzz"

          SORT fruits by *-id ALPHA

      LIMIT选项:

        返回指定范围的元素 SORT name LIMIT skip size

        比如SORT fruits ALPHA 0 4

      GET选项:

        前面的都是返回排序的键的结果,如果我们想要返回关联的内容就要使用GET,比如对student进行排序,返回学生的全名。

        SADD students jack peter tom

        MSET peter-name "Peter White" jack-name "Jack Snow" tom-name "Tom Smith"

        SORT students ALPHA GET *-name

        就可以拿到学生排序后的相关学生姓名了。

      STORE选项:

        SORT只返回排序结果,通过STORE选项可以保存排序结果,以便下次使用。

        SORT students ALPHA STORE sorted_students

        下次取sorted_students键即可。

      多个选项的执行顺序:

        SORT <key> ALPHA DESC BY <by-pattern> LIMIT <offset> <count> GET <get-pattern> STORE <store_key>

        执行顺序是:排序,限制长度,获取外部键,保存排序结果,向客户端返回结果。

        除了GET选项,改变顺序不会影响执行顺序。

    6.二进制位数组

      redis提供了SETBIT、GETBIT、BITCOUNT、BITOP四个命令用于处理二进制位数组。

        SETBIT bit 0 1 # 0000 0001

        SETBIT bit 3 1 # 0000 0101

        GETBIT bit 3  得到的就是1

        BITCOUNT bit 得到的就是2

        BITOP可以进行与、或、异或运算,按位取反

        BITOP AND and-result x y z

        BITOP OR or-result x y z

        BITOP XOR xor-result x y z

        BITOP NOT not-value value

      redis用sds结构保存位数组。len表示保存了几个1字节长的位数组。

      GETBIT命令的实现:

        GETBIT <bitarray> <offset>

        计算下标 offset / 8 确定在哪个位数组上,取出这个1字节长的byte

        计算offset mod 8 + 1,计算偏移量,确定这个byte的第几个二进制位

        返回这个值

      SETBIT命令的实现:

        SETBIT <bitarray> <offset> <value>

        计算len offset / 8 + 1 ,确定需要多少个字节

        检查bitarray当前的len,不够扩容到计算出的长度,扩容部分全部填0

        byte = offset / 8 确定byte

        bit = offset mod 8 + 1 确定二进制位

        设置byte的bit位的值

        返回oldValue

      注意对于单个byte是使用逆序的方式保存内容的。

      BITCOUNT命令的实现:

        1.最直接的方法,遍历二进制位计数。假设数组长度为100MB,遍历要执行100 * 1024 * 1024 * 8,10来亿次计算,这个就可怕了。

        2.查表法:创建一个表记录每个值的1的个数,比如8位字节的查表,8位字节可以组成0~255范围,制作一张表0的时候对应的1的个数就是0,1的时候对应的个数就是1

            0000 0000      0

            0000 0001      1

            0000 0010    1

            ……

          这样就可以不需要检测各个位,直接获得相关的1的个数了。表越大,计算次数就越少。但是这个方法有两个问题,一是用空间换时间,表越大耗费的空间越多。二是CPU缓存命中降低,CPU缓存能保存的表比例越低,查表无法命中,缓存的换入换出频繁,影响实际效率。

        3.二进制统计算法:variable-precision SWAR算法

        计算汉明距离:

          i = (i & 0x55555555) + ((i >> 1) & 0x55555555);

          i = (i & 0x33333333) + ((i >> 1) & 0x33333333);

          i = (i & 0x0F0F0F0F) + ((i >> 1) & 0x0F0F0F0F);

          i = (i * (0x01010101) >> 24);

        第一步按两个二进制位进行一组,每组的十进制就是该组的汉明重量

        第二步按四个二进制位进行分组,每组的十进制就是该组的汉明重量

        第三步按八个二进制位进行分组,每组的十进制就是该组的汉明重量

        最后计算bitarray的汉明重量,并记录在二进制位的最高八位,右移24位得到汉明距离

        这个方法每次可以计算32个二进制位的汉明重量,比遍历快32倍,比键长8位的查表法快4倍。

        4.二进制统计算法:redis实现

          BITCOUNT命令使用了查表法和variable-precisionSWAR两种算法。

          查表法使用8位的表,记录0000 0000到1111 1111的汉明重量

          SWAR算法,每次循环载入128个二进制位,调用4次32位的SWAR算法计算这128个位的汉明重量。

        BITCOUNT会根据未处理的二进制位数量来决定使用哪种算法:

          未处理大于等于128位,使用SWAR算法。

          小于128位,使用查表法。

        100MB只需要执行625万次循环,比遍历8亿次快太多。

      BITOP命令的实现:

        创建一个空白数组位,然后对做操作的数组按字节进行与或操作,保存在这个空白数组中,最后返回。

    7.慢查询日志

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

        slowlog-log-slower-than 超过多少微妙保存记录

        slowlog-max-len    最多保存多少条慢查询日志,新的会让旧的被删除

      可以通过CONFIG SET命令设置这两个参数,如CONFIG SET slowlog-max-len 5

      SLOWLOG GET获取保存的慢查询日志:第一个是日志唯一标识,第二个是执行时的unix时间戳,第三个是执行耗时微妙,最后是命令及参数

      SLOWLOG RESET重置慢查询日志

    8.监视器

      执行MONITOR命令,客户端就会变成一个监视器。实时打印出服务器当前处理的命令请求的相关信息。

  • 相关阅读:
    Java集合类初始容量、加载因子、扩容增量
    并发之原子性、可见性、有序性
    多线程面试题
    MySQL引擎及选择
    SHA和MD5的Salt
    基于SSM的单点登陆05
    基于SSM的单点登陆04
    基于SSM的单点登陆03
    基于SSM的单点登陆02
    基于SSM的单点登陆01
  • 原文地址:https://www.cnblogs.com/lighten/p/9252777.html
Copyright © 2020-2023  润新知