• 高级架构师_Redis_第4章Redis 发布与订阅+事务+lua脚本+ 慢查询日志+监视器


    高级架构师_Redis_第4章Redis 发布与订阅+事务+lua脚本+ 慢查询日志+监视器

    第二部分 Redis 扩展功能

    • 掌握Redis发布与订阅的命令
    • 理解Redis发布与订阅的机制
    • 知道Redis发布与订阅的使用场景
    • 掌握Redis事务的命令
    • 理解Redis事务的机制
    • 理解Redis事务的弱事务性
    • 理解lua的概念
    • 掌握Redis和lua的整合使用
    • 了解慢查询日志的概念
    • 掌握慢查询的定位和处理
    • 了解监视器的概念
    • 掌握监视器的使用

    第一节 发布与订阅

    Redis 提供了发布订阅功能,可以用于消息的传输

    Redis 的发布订阅机制包括三个部分,publisher,subscriber 和 Channel

    image.png

    发布者和订阅者都是 Redis 客户端,Channel 则为 Redis 服务器端。

    发布者将消息发送到某个的频道,订阅了这个频道的订阅者就能接收到这条消息。

    1.1 频道/模式的订阅与退订

    subscribe:订阅 subscribe channel1 channel2 …

    Redis 客户端 1 订阅频道 1 和频道 2

    127.0.0.1:6379> subscribe channel1 channel2
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "channel1"
    3) (integer) 1
    1) "subscribe"
    2) "channel2"
    3) (integer) 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    publish:发布消息 publish channel message

    Redis 客户端 2 将消息发布在频道 1 和频道 2 上

    127.0.0.1:6379> publish channel1 weather-cloudy
    (integer) 1
    127.0.0.1:6379> publish channel2 weather-sunny
    (integer) 1
    
    • 1
    • 2
    • 3
    • 4

    Redis 客户端 1 接收到频道 1 和频道 2 的消息

    1) "message"
    2) "channel1"
    3) "weather-cloudy"
    1) "message"
    2) "channel2"
    3) "weather-sunny"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    unsubscribe:退订 channel

    Redis 客户端 1 退订频道 1

    127.0.0.1:6379> unsubscribe channel1
    1) "unsubscribe"
    2) "channel1"
    3) (integer) 0
    
    • 1
    • 2
    • 3
    • 4

    psubscribe :模式匹配 psubscribe + 模式

    Redis 客户端 1 订阅所有以 ch 开头的频道

    127.0.0.1:6379> psubscribe ch*
    Reading messages... (press Ctrl-C to quit)
    1) "psubscribe"
    2) "ch*"
    3) (integer) 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Redis 客户端 2 发布信息在频道 5 上

    127.0.0.1:6379> publish ch3 sky-blue
    (integer) 1
    
    • 1
    • 2

    Redis 客户端 1 收到频道 5 的信息

    1) "pmessage"
    2) "ch*"
    3) "ch3"
    4) "sky-blue"
    
    • 1
    • 2
    • 3
    • 4

    punsubscribe 退订模式

    127.0.0.1:6379> punsubscribe ch*
    1) "punsubscribe"
    2) "ch*"
    3) (integer) 0
    
    • 1
    • 2
    • 3
    • 4
    1.2 发布订阅的机制

    订阅某个频道或模式:

    客户端(client):

    • 属性为 pubsub_channels,该属性表明了该客户端订阅的所有频道

    • 属性为 pubsub_patterns,该属性表示该客户端订阅的所有模式

    服务器端(RedisServer):

    • 属性为 pubsub_channels,该服务器端中的所有频道以及订阅了这个频道的客户端

    • 属性为 pubsub_patterns,该服务器端中的所有模式和订阅了这些模式的客户端

    typedef struct redisClient { ...
    
    dict  *pubsub_channels; //该client订阅的channels,以channel为key用dict的方式组织
    list  *pubsub_patterns;  //该client订阅的pattern,以list的方式组织
    
    
    } redisClient;
    
    struct redisServer { 
    ...
    
    dict *pubsub_channels;    //redis server进程中维护的channel dict,它以channel
    ist *pubsub_patterns;  //redis server进程中维护的pattern list
    int notify_keyspace_events; 
    ...
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    当客户端向某个频道发送消息时,Redis 首先在 redisServer 中的 pubsub_channels 中找出键为该频道的 结点,遍历该结点的值,即遍历订阅了该频道的所有客户端,将消息发送给这些客户端。 然后,遍历结构体 redisServer 中的 pubsub_patterns,找出包含该频道的模式的结点,将消息发送给订 阅了该模式的客户端。

    1.3 使用场景:哨兵模式,Redisson 框架使用

    在 Redis 哨兵模式中,哨兵通过发布与订阅的方式与 Redis 主服务器和 Redis 从服务器进行通信。这个我 们将在后面的章节中详细讲解。

    Redisson 是一个分布式锁框架,在 Redisson 分布式锁释放的时候,是使用发布与订阅的方式通知的, 这个我们将在后面的章节中详细讲解。

    第二节 Redis 中的事务

    所谓事务(Transaction) ,是指作为单个逻辑工作单元执行的一系列操作

    2.1 ACID 回顾
    • Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不 执行。

    • Redis:一个队列中的命令 执行或不执行 (非语法错误,将导致非原子性)

    • Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者是一致的。

    • Redis: 集群中不能保证时时的一致性,只能是最终一致性

    • Isolation(隔离性):事务之间不会相互影响。

    • Redis: 命令是顺序执行的,在一个事务中,有可能被执行其他客户端的命令的(watch 对象被其他客户端改变,将会清空 command queue)

    • Durability(持久性):事务执行成功后必须全部写入磁盘。

    Redis 有持久化但不保证 数据的完整性

    2.2 Redis 事务
    • Redis 的事务是通过 multi、exec、discard 和 watch 这四个命令来完成的。

    • Redis 的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。

    • Redis 将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行

    • Redis 不支持回滚操作

    事务命令

    • multi:用于标记事务块的开始 Redis 会将后续的命令逐个放入队列中,然后使用 exec 原子化地执行这个 命令队列

    • exec:执行命令队列

    • discard:清除命令队列

    • watch:监视 key

    • unwatch:清除监视 key

    image.png

    127.0.0.1:6379> multi 
    OK
    127.0.0.1:6379> set s1 222 
    QUEUED
    127.0.0.1:6379> hset set1 name zhangfei 
    QUEUED
    127.0.0.1:6379> exec
    1) OK 
    2) (integer) 1
    127.0.0.1:6379> multi 
    OK
    127.0.0.1:6379> set s2 333 
    QUEUED
    127.0.0.1:6379> hset set2 age 23 
    QUEUED
    127.0.0.1:6379> discard OK
    127.0.0.1:6379> exec
    (error) ERR EXEC without MULTI
    127.0.0.1:6379> watch s1 
    OK
    127.0.0.1:6379> multi 
    OK
    127.0.0.1:6379> set s1 555 
    QUEUED
    127.0.0.1:6379> exec 
    (nil)
    127.0.0.1:6379> get s1 
    222
    127.0.0.1:6379> unwatch 
    OK
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    2.3 事务机制

    事务的执行

    1. 事务开始
      在 RedisClient 中,有属性 flags,用来表示是否在事务中 flags=REDIS_MULTI
    2. 命令入队
      RedisClient 将命令存放在事务队列中 (EXEC,DISCARD,WATCH,MULTI 除外)
    3. 事务队列
      multiCmd *commands 用于存放命令
    4. 执行事务
      RedisClient 向服务器端发送 exec 命令,RedisServer 会遍历事务队列,执行队列中的命令,最后将执 行的结果一次性返回给客户端。

    如果某条命令在入队过程中发生错误,redisClient 将 flags 置为 REDIS_DIRTY_EXEC,EXEC 命令将会失败 返回。

    image.png

    typedef struct redisClient{
    // flags
    int flags //状态
    // 事务状态
    multiState mstate;
    // .....
    }redisClient;
    
    // 事务状态
    typedef struct multiState{
    // 事务队列,FIFO顺序
    // 是一个数组,先入队的命令在前,后入队在后
    multiCmd *commands;
    // 已入队命令数
    int count;
    }multiState;
    
    // 事务队列
    typedef struct multiCmd{
    // 参数
    robj **argv;
    // 参数数量
    int argc;
    // 命令指针
    struct redisCommand *cmd;
    }multiCmd;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    Watch 的执行

    类似与 cas 的版本号,旧的版本则舍弃命令队列

    WATCH 命令监视数据库键

    redisDb 有一个 watched_keys 字典,key 是某个被监视的数据的 key,值是一个链表.记录了所有监视这个数

    据的客户端。

    监视机制的触发 当修改数据后,监视这个数据的客户端的 flags 置为 REDIS_DIRTY_CAS

    事务执行

    RedisClient 向服务器端发送 exec 命令,服务器判断 RedisClient 的 flags,如果为 REDIS_DIRTY_CAS,则 清空事务队列。

    image.png

    Redis 的弱事务性

    Redis 语法错误

    则整个事务的命令在队列里都清除

    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> sets m1 44
    (error) ERR unknown command `sets`, with args beginning with: `m1`, `44`, 127.0.0.1:6379> set m2 55
    QUEUED 
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors. 
    127.0.0.1:6379>get m1
     "22"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Redis 运行错误

    在队列里正确的命令可以执行

    (弱事务性) 弱事务性 :

    1、在队列里正确的命令可以执行 (非原子操作)

    2、不支持回滚

    127.0.0.1:6379> multi OK
    127.0.0.1:6379> set m1 55 QUEUED
    127.0.0.1:6379> lpush m1 1 2 3 #不能是语法错误
    QUEUED 127.0.0.1:6379> exec
    1)	OK
    2)	(error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> get m1
    "55"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Redis 不支持事务回滚(为什么呢)

    • 1、大多数事务失败是因为语法错误或者类型错误,这两种错误,在开发阶段都是可以预见的
    • 2、Redis 为了性能方面而忽略了事务回滚。 (回滚记录历史版本)
    2.4 Lua 脚本

    lua 是一种轻量小巧的脚本语言,用标准 C 语言编写并以源代码形式开放, 其设计目的是为了嵌入应用 程序中,从而为应用程序提供灵活的扩展和定制功能。

    Lua 应用场景:游戏开发、独立应用脚本、Web 应用脚本、扩展和数据库插件。

    nginx 上使用 lua 实现高并发

    OpenRestry:一个可伸缩的基于 Nginx 的 Web 平台,是在 nginx 之上集成了 lua 模块的第三方服务器

    OpenRestry 是一个通过 Lua 扩展 Nginx 实现的可伸缩的 Web 平台,内部集成了大量精良的 Lua 库、第三 方模块以及大多数的依赖项。 用于方便地搭建能够处理超高并发(日活千万级别)、扩展性极高的动态 Web 应用、Web 服务和动态网 关。

    功能和 nginx 类似,就是由于支持 lua 动态脚本,所以更加灵活。 OpenRestry 通过 Lua 脚本扩展 nginx 功能,可提供负载均衡、请求路由、安全认证、服务鉴权、流量控 制与日志监控等服务。

    类似的还有 Kong(Api Gateway)、tengine(阿里)

    2.4.1 创建并修改 lua 环境

    下载

    地址:http://www.lua.org/download.html

    可以本地下载上传到 linux,也可以使用 curl 命令在 linux 系统中进行在线下载

    curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
    
    • 1

    安装

    yum -y install readline-devel ncurses-devel 
    tar -zxvf lua-5.3.5.tar.gz 
    #在src目录下 
    make linux 
    #或
    make install
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果报错,说找不到 readline/readline.h, 可以通过 yum 命令安装

    yum -y install readline-devel ncurses-devel
    
    • 1

    安装完以后再

    make linux
    #或者
    make install
    
    • 1
    • 2
    • 3

    最后,直接输入 lua 命令即可进入 lua 的控制台

    2.4.2 Lua 环境协作组件
    • 从 Redis2.6.0 版本开始,通过内置的 lua 编译/解释器,可以使用 EVAL 命令对 lua 脚本进行求值

    • 脚本的命令是原子的,RedisServer 在执行脚本命令中,不允许插入新的命令

    • 脚本的命令可以复制,RedisServer 在获得脚本后不执行,生成标识返回,Client 根据标识就可以随时执 行

    EVAL 命令实现

    通过执行 redis 的 eval 命令,可以运行一段 lua 脚本。

    EVAL script numkeys key [key ...] arg [arg ...]
    
    • 1

    命令说明:

    • script 参数:是一段 Lua 脚本程序,它会被运行在 Redis 服务器上下文中,这段脚本不必(也不应该) 定义为一个 Lua 函数。
    • numkeys 参数:用于指定键名参数的个数。
    • key [key …]参数: 从 EVAL 的第三个参数开始算起,使用了 numkeys 个键(key),表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形 式访问( KEYS[1] , KEYS[2] ,以此类推)。必须大写
    • arg [arg …]参数:可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似(ARGV[1] 、 ARGV[2] ,诸如此类)。
    eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
    
    • 1

    lua 脚本中调用 Redis 命令

    redis.call():

    • 返回值就是 redis 命令执行的返回值
    • 如果出错,则返回错误信息,不继续执行

    redis.pcall():

    • 返回值就是 redis 命令执行的返回值
    • 如果出错,则记录错误信息,继续执行

    注意事项

    • 在脚本中,使用 return 语句将返回值返回给客户端,如果没有 return,则返回 nil
    eval "return redis.call('set',KEYS[1],ARGV[1])" 1 n1 zhaoyun
    
    • 1

    EVALSHA

    EVAL 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。

    Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来 传送脚本主体并不是最佳选择。

    为了减少带宽的消耗, Redis 实现了 EVALSHA 命令,它的作用和 EVAL 一样,都用于对脚本求值,但 它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)

    SCRIPT 命令

    • SCRIPT FLUSH :清除所有脚本缓存

    • SCRIPT EXISTS :根据给定的脚本校验和,检查指定的脚本是否存在于脚本缓存

    • SCRIPT LOAD :将一个脚本装入脚本缓存,返回 SHA1 摘要,但并不立即运行它

    • SCRIPT KILL :杀死当前正在运行的脚本

    192.168.24.131:6380> script load "return redis.call('set',KEYS[1],ARGV[1])" "c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
    192.168.24.131:6380> evalsha c686f316aaf1eb01d5a4de1b0b63cd233010e63d 1 n2 zhangfei
    OK
    192.168.24.131:6380> get n2
    
    • 1
    • 2
    • 3
    • 4
    2.4.3 脚本管理命令实现

    使用 redis-cli 直接执行 lua 脚本。

    test.lua

    return redis.call('set',KEYS[1],ARGV[1])
    
    ./redis-cli -h 127.0.0.1 -p 6379 --eval test.lua name:6 , 'caocao' #,两边有空格
    
    • 1
    • 2
    • 3

    list.lua

    local key=KEYS[1]
    local list=redis.call("lrange",key,0,-1);
    return list;
    ./redis-cli --eval list.lua list
    
    • 1
    • 2
    • 3
    • 4

    利用 Redis 整合 Lua,主要是为了性能以及事务的原子性。因为 redis 帮我们提供的事务功能太差。

    脚本复制

    Redis 传播 Lua 脚本,在使用主从模式和开启 AOF 持久化的前提下:

    当执行 lua 脚本时,Redis 服务器有两种模式:脚本传播模式和命令传播模式。

    脚本传播模式

    脚本传播模式是 Redis 复制脚本时默认使用的模式

    Redis 会将被执行的脚本及其参数复制到 AOF 文件以及从服务器里面。 执行以下命令:

    eval "redis.call('set',KEYS[1],ARGV[1]);redis.call('set',KEYS[2],ARGV[2])" 2 n1 n2 
    zhaoyun1 zhaoyun2
    
    • 1
    • 2

    那么主服务器将向从服务器发送完全相同的 eval 命令:

    eval "redis.call('set',KEYS[1],ARGV[1]);redis.call('set',KEYS[2],ARGV[2])" 2 n1 n2 
    zhaoyun1 zhaoyun2 
    
    • 1
    • 2

    注意:在这一模式下执行的脚本不能有时间、内部状态、随机函数(spop)等。执行相同的脚本以及参数 必须产生相同的效果。在 Redis5,也是处于同一个事务中。

    命令传播模式

    处于命令传播模式的主服务器会将执行脚本产生的所有写命令用事务包裹起来,然后将事务复制到 AOF 文件以及从服务器里面。

    因为命令传播模式复制的是写命令而不是脚本本身,所以即使脚本本身包含时间、内部状态、随机函数 等,主服务器给所有从服务器复制的写命令仍然是相同的。

    为了开启命令传播模式,用户在使用脚本执行任何写操作之前,需要先在脚本里面调用以下函数:

    redis.replicate_commands()
    
    • 1

    redis.replicate_commands() 只对调用该函数的脚本有效:

    在使用命令传播模式执行完当前脚本之后, 服务器将自动切换回默认的脚本传播模式。

    如果我们在主服务器执行以下命令:

    eval "redis.replicate_commands();redis.call('set',KEYS[1],ARGV[1]);redis.call('set',K EYS[2],ARGV[2])" 
    2 n1 n2 zhaoyun11 zhaoyun22
    
    • 1
    • 2

    那么主服务器将向从服务器复制以下命令:

    EXEC
    *1
    $5
    MULTI
    *3
    $3
    set
    $2
    n1
    $9
    zhaoyun11
    *3
    $3
    set
    $2
    n2
    $9
    zhaoyun22
    *1
    $4
    EXEC
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    管道(pipeline),事务和脚本(lua)三者的区别

    三者都可以批量执行命令

    管道无原子性,命令都是独立的,属于无状态的操作

    事务和脚本是有原子性的,其区别在于脚本可借助 Lua 语言可在服务器端存储的便利性定制和简化操作

    脚本的原子性要强于事务,脚本执行期间,另外的客户端 其它任何脚本或者命令都无法执行,脚本的执 行时间应该尽量短,不能太耗时的脚本

    2.5 慢查询日志

    我们都知道 MySQL 有慢查询日志 Redis 也有慢查询日志,可用于监视和优化查询

    慢查询设置

    在 redis.conf 中可以配置和慢查询日志相关的选项:

    #执行时间超过多少微秒的命令请求会被记录到日志上  0 :全记录 <0 不记录
    slowlog-log-slower-than 10000 
    #slowlog-max-len 存储慢查询日志条数 
    slowlog-max-len 128
    
    • 1
    • 2
    • 3
    • 4

    Redis 使用列表存储慢查询日志,采用队列方式(FIFO)

    config set 的方式可以临时设置,

    redis 重启后就无效

    config set slowlog-log-slower-than 微秒

    config set slowlog-max-len 条数

    查看日志:slowlog get [n]

    操作演示

    127.0.0.1:6379> config set slowlog-log-slower-than 0
    OK
    127.0.0.1:6379> config set slowlog-max-len 2
    OK
    127.0.0.1:6379> set name:001 zhaoyun
    OK
    127.0.0.1:6379> set name:002 zhangfei
    OK
    127.0.0.1:6379> get name:002
    "zhangfei"
    127.0.0.1:6379> slowlog get     #set和get都记录,第一条被移除了。
    1) 1) (integer) 4		#日志的唯一标识符(uid)
       2) (integer) 1627616880	#命令执行时的UNIX时间戳
       3) (integer) 4		#命令执行的时长(微秒)
       4) 1) "get"			#执行命令及参数
          2) "name:002"
       5) "127.0.0.1:58834"
       6) ""
    2) 1) (integer) 3
       2) (integer) 1627616874
       3) (integer) 5
       4) 1) "set"
          2) "name:002"
          3) "zhangfei"
       5) "127.0.0.1:58834"
       6) ""
    127.0.0.1:6379> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    慢查询定位&处理

    使用 slowlog get 可以获得执行较慢的 redis 命令,针对该命令可以进行优化:

    1、尽量使用短的 key,对于 value 有些也可精简,能使用 int 就 int。

    2、避免使用 keys *、hgetall 等全量操作。

    3、减少大 key 的存取,打散为小 key 100K 以上

    4、将 rdb 改为 aof 模式

    rdb fork 子进程 数据量过大 主进程阻塞 redis 性能大幅下降 关闭持久化 , (适合于数据量较小,有固定数据源)

    5、想要一次添加多条数据的时候可以使用管道

    6、尽可能地使用哈希存储

    7、尽量限制下 redis 使用的内存大小,这样可以避免 redis 使用 swap 分区或者出现 OOM 错误 内存与硬盘的 swap

    慢查询记录的保存

    在 redisServer 中保存和慢查询日志相关的信息

    struct redisServer {
    
    
    // ...
    
    // 下一条慢查询日志的 ID
    long long slowlog_entry_id;
    
    // 保存了所有慢查询日志的链表 FIFO list *slowlog;
    
    // 服务器配置 slowlog-log-slower-than 选项的值
    long long slowlog_log_slower_than;
    
    // 服务器配置 slowlog-max-len 选项的值
    unsigned long slowlog_max_len;
    // ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    慢查询日志的阅览&删除

    初始化日志列表

    void slowlogInit(void) { 
    server.slowlog = listCreate(); /* 创建一个list列表 */ 
    server.slowlog_entry_id = 0; /* 日志ID从0开始 */ 
    listSetFreeMethod(server.slowlog,slowlogFreeEntry); /* 指定慢查询日志list空间 的释放方法 */ 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    获得慢查询日志记录

    slowlog get [n]

    def SLOWLOG_GET(number=None):
        # 用户没有给定 number 参数
        # 那么打印服务器包含的全部慢查询日志
        if number is None:
            number = SLOWLOG_LEN()
        # 遍历服务器中的慢查询日志
        for log in redisServer.slowlog:
            if number <= 0:
            # 打印的日志数量已经足够,跳出循环
                break
            else:
                # 继续打印,将计数器的值减一
                number -= 1
            # 打印日志
            printLog(log)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    查看日志数量的 slowlog len

    def SLOWLOG_LEN():
    # slowlog 链表的长度就是慢查询日志的条目数量 
    return len(redisServer.slowlog)
    
    • 1
    • 2
    • 3

    清除日志 slowlog reset

    def SLOWLOG_RESET():
    # 遍历服务器中的所有慢查询日志 
    for log in redisServer.slowlog:
    # 删除日志 
    deleteLog(log)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    添加日志实现

    在每次执行命令的之前和之后, 程序都会记录微秒格式的当前 UNIX 时间戳, 这两个时间戳之间的差 就是服务器执行命令所耗费的时长, 服务器会将这个时长作为参数之一传给 slowlogPushEntryIfNeeded 函数, 而 slowlogPushEntryIfNeeded 函数则负责检查是否需要为这 次执行的命令创建慢查询日志

    // 记录执行命令前的时间
    before = unixtime_now_in_us()
    //执行命令
    execute_command(argv, argc, client)
    //记录执行命令后的时间
    after = unixtime_now_in_us()
    // 检查是否需要创建新的慢查询日志
    slowlogPushEntryIfNeeded(argv, argc, before-after)
        
    void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration) {
        if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */ /* 负
    数表示禁用 */
        if (duration >= server.slowlog_log_slower_than) /* 如果执行时间 > 指定阈值*/
            listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration)); 
    /* 创建一个slowlogEntry对象,添加到列表首部*/
    while (listLength(server.slowlog) > server.slowlog_max_len) /* 如果列表长度 > 
    指定长度 */
            listDelNode(server.slowlog,listLast(server.slowlog));  /* 移除列表尾部元素
    */
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    slowlogPushEntryIfNeeded 函数的作用有两个:

    1. 检查命令的执行时长是否超过 slowlog-log-slower-than 选项所设置的时间, 如果是的话, 就 为命令创建一个新的日志, 并将新日志添加到 slowlog 链表的表头。
    2. . 检查慢查询日志的长度是否超过 slowlog-max-len 选项所设置的长度, 如果是的话, 那么将多 出来的日志从 slowlog 链表中删除掉。

    慢查询定位&处理

    使用slowlog get 可以获得执行较慢的redis命令,针对该命令可以进行优化:

    • 1、尽量使用短的key,对于value有些也可精简,能使用int就int。

    • 2、避免使用keys *、hgetall等全量操作。

    • 3、减少大key的存取,打散为小key

    • 4、将rdb改为aof模式

      rdb fork 子进程 主进程阻塞 redis大幅下降关闭持久化 , (适合于数据量较小)

      改aof 命令式

    • 5、想要一次添加多条数据的时候可以使用管道

    • 6、尽可能地使用哈希存储

    • 7、尽量限制下redis使用的内存大小,这样可以避免redis使用swap分区或者出现OOM错误 内存与硬盘的swap

    2.6 监视器

    Redis 客户端通过执行 MONITOR 命令可以将自己变为一个监视器,实时地接受并打印出服务器当前处理 的命令请求的相关信息。

    此时,当其他客户端向服务器发送一条命令请求时,服务器除了会处理这条命令请求之外,还会将这条 命令请求的信息发送给所有监视器。

    image.png

    Redis 客户端 1

    ➜  bin ./redis-cli
    127.0.0.1:6379> monitor
    OK
    1627617887.578979 [0 127.0.0.1:58834] "lpush" "domain" "lane.show" "galaxylib.com"
    1627617933.485665 [0 127.0.0.1:58834] "lrange" "domain" "0" "-1"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Redis 客户端 2

    127.0.0.1:6379> lpush domain lane.show galaxylib.com
    (integer) 2
    127.0.0.1:6379> lrange domain 0 -1
    1) "galaxylib.com"
    2) "lane.show"
    127.0.0.1:6379> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实现监视器

    redisServer 维护一个monitors 的链表,记录自己的监视器,每次收到MONITOR 命令之后,将客户端追加到链表尾。

    void monitorCommand(redisClient *c) {
    /* ignore MONITOR if already slave or in monitor mode */ if (c->flags & REDIS_SLAVE) return;
    c->flags |= (REDIS_SLAVE|REDIS_MONITOR);
    listAddNodeTail(server.monitors,c); addReply(c,shared.ok); //回复OK
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    向监视器发送命令信息

    利用call函数实现向监视器发送命令

    // call() 函数是执行命令的核心函数,这里只看监视器部分
    /*src/redis.c/call*/
    /* Call() is the core of Redis execution of a command */ void call(redisClient *c, int flags) {
    long long dirty, start = ustime(), duration; int client_old_flags = c->flags;
    /* Sent the command to clients in MONITOR mode, only if the commands are
    * not generated from reading an AOF. */ if (listLength(server.monitors) &&
    !server.loading &&
    !(c->cmd->flags & REDIS_CMD_SKIP_MONITOR))
    {
    replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
    }
    ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    call 主要调用了 replicationFeedMonitors ,这个函数的作用就是将命令打包为协议,发送给监视器。

    Redis 监控平台

    grafana、prometheus 以及 redis_exporter。

    Grafana 是一个开箱即用的可视化工具,具有功能齐全的度量仪表盘和图形编辑器,有灵活丰富的图形 化选项,可以混合多种风格,支持多个数据源特点。

    Prometheus 是一个开源的服务监控系统,它通过 HTTP 协议从远程的机器收集数据并存储在本地的时序 数据库上。 redis_exporter 为 Prometheus 提供了 redis 指标的导出,配合 Prometheus 以及 grafana 进行可视化及监 控。

    image.png

  • 相关阅读:
    golang开发 gorilla websocket的使用
    面向对象的选项卡切换
    Matlab---figure图窗属性与设置与交互设置
    asp.net网上书店管理系统VS开发sqlserver数据库web结构c#编程计算机网页源码项目
    基于社交网络优化的BP神经网络(分类应用) - 附代码
    Dockerfile
    简单工厂模式(一)在源码中的应用 | Calendar 日历 | 源码浅析 | 使用总结 | 建造者模式
    马斯克:让我成功的其实是“第一性原理”(PM必读)
    基于 Apache Hudi 和 Apache Spark Sql 的近实时数仓架构分享
    dfs+剪枝,LeetCode 39. 组合总和
  • 原文地址:https://blog.csdn.net/guan1843036360/article/details/127708713