redis-基础命令和持久化 网络 bitmap

redis单线程模型

I/O多路复用(select,poll,epoll,epoll的两种工作模式)
2
2

io多路复用

  • i/o : 网络I/O
  • 多路: 多个客户端连接,(连接就是套接字描述符,socket或者channel),指多条TCP连接
  • 复用:用一个线程来处理多条连接,使用单线程就能实现同时处理多个客户端连接。

这里的复用指的就是对进程的复用

IO multiplexing就是我们说的select,poll,epoll,有些技术书籍也称这种IO方式为event driven IO事件驱动IO。就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。可以基于一个阻塞对象并同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程,每次new一个线程),这样可以大大节省系统资源。所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select,poll,epoll等函数就可以返回。

2
阶段1等待数据 阻塞
阶段2 内核数据拷贝也是阻塞。

引出问题

假设有一个服务端,接收客户端socket 的请求。调用recvfrom 尝试从客户端socket中读取数据,如果有很多的socket请求都来了,但是单线程情况下,只能处理处理一个socket请求,多个socket请求排队。如果正在处理的socket请求恰好未就绪(数据不可读,或者不可写)等待数据阶段,服务器线程就会被阻塞。所有其他客户端socket都必须等待, 性能很差。

  • 方案:数据就绪了,用户应用就去读取数据。
    用户进程如何知道内核中数据是否有数据呢?

文件描述符 file descriptor fd
从0开始递增的无符号整数,在linux 中一切皆文件,包括socket。

  • io多路复用
    利用单个线程同时监听多个FD,并在某个FD 可读、可写时得到通知,从而避免无效的等待,充分利用cpu资源。

不过监听fd的方式、通知的方式有多种实现:

  • select
  • poll
  • epoll

一些指令 位图

布隆过滤器

3

bitmap

bitcount

BITCOUNT key [start end]
计算给定键key对应的位图中,被设置为1的位的数量,通过指定的start和end参数,可以让计数只在特定的字节(注意start和end指的是字节不是位,演示中会进行说明)上进行。

不存在的键key会被当成是空位图来处理,因此对一个不存在的键key进行BITCOUNT操作时,结果会为 0 。
1
2

3
由上图可知,start和end参数指的是字节,不是位。

bitfield

hello 等价于 01101000 01100101 01101100 01101100 01101111
104 101 108 108 111
位移修改,溢出控制。

BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]

bitfield key get type offset

BITFIELD k1 get u8 0

从第8位开始,读无符号8位,结果101
1

bitfield key set type offset value

3
3
bitfield k1 set u8 #1 120
使用 # 前缀可以让我们免去手动计算被设置比特位所在位置的麻烦。
SET对应的偏移量为:8*1=8

溢出控制

wrap : 使用回绕
2
·sat:饱和计算方法处理溢出,下溢出计算结果位最小的整数值,而上溢计算的结果为最大的整数值。
下溢出 (-128) 之后还减小,还是-128,
上溢出 127 之后再增加 还是127
2

fail:拒绝执行 返回nil
2

BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]

type : i有符号 u无符号 指定长度
offset 偏移量
bitfield bm1 get u2 0
从bm1key中 第0号位置开始 获取无符号2位
2
3

比特值和位置偏移量

我们有两种方式来设置偏移量:如果数字前没有前缀,那么就是基于0的比特位偏移量;如果数字有带有 ‘#’ 前缀,offset就等于提供的整数宽度乘以’#‘后面的偏移量。例如:

BITFIELD mystring SET i8 #0 100 SET i8 #1 200
01100100 11001000

复制代码
第一个SET对应的偏移量为:80=0
第二个SET对应的偏移量为:8
1=8
使用 # 前缀可以让我们免去手动计算被设置比特位所在位置的麻烦。

3.redis 持久化 aof rdb

2

3.1 rdb

3.1.1 rdb 配置

#	RDB的保存策略
# 如果 900秒 内有 1 条Key信息发生变化,则进行快照;
save 3600 1
# 如果 300 秒内有 10 条Key信息发生变化,则进行快照;
save 300 100
# 如果 60 秒内有 10000 条 Key 信息发生变化,则进行快照。
save 60 10000
# save 5 1

# 文件名称
dbfilename dump.rdb

# 文件保存路径
dir /home/work/app/redis/data/

# 如果持久化出错,主进程是否停止写入
stop-writes-onbgsave-error yes

# 是否压缩
rdbcompression yes
#该属性将在字符串类型的数据被快照到磁盘文件时,启用 LZF 压缩算法


# 导入时是否检查
rdbchecksum yes
#在存储快照后,还可以让Redis使用CRC64算法来进行数据校验
#,但是这样做会增加大约10%的性能消耗,
#如果希望获取到最大的性能提升,可以关闭此功能

3.1.2 写时复制

写时复制技术:
· 如果主线程收到的客户端的写请求,需要修改某块数据那么这块数据就会被复制一份到内存,生成该数据的副本主进程在该副本上进行修改操作。所以即使对某个数据进行了修改,Redis持久化到RDB中的数据也是未修改的数据这也是把RDB文件称为"快照"文件的原因,子进程所看到的数据在它被创建的一瞬间就固定下来了,父进程修改的某个数据只是该数据的复制品。

  • 这里再深入一点,Redis内存中的全量数据由一个个的"数据段页面"组成,每个数据段页面的大小为4K,客户端要修改的数据在哪个页面中,就会复制一份这个页面到内存中,这个复制的过程称为"页面分离",在持久化过程中,随着分离出的页面越来越多,内存就会持续增长,但是不会超过原内存的2倍,因为在一次持久化的过程中,几乎不会出现所有的页面都会分离的情况,读写请求针对的只是原数据中的小部分,大部分Redis数据还是"冷数据"。
  • 正因为修改的部分数据会被额外的复制一份,所以会占用额外的内存,当在进行RDB持久化操作的过程中,与此同时如果持续往redis中写入的数据量越多,就会导致占用的额外内存消耗越大
  • 2

那么在此期间写入的数据最终去哪了呢?

写入的数据还是存在了内存当中,并没有写入当前的持久化文件中,等到下次进行RDB持久化时才会把 ” 写入的数据 ” 落盘到RDB文件中。
2

3.1.3 RDB 优缺点

  • 优点
    • RDB 文件是某个时间节点的快照,默认使用 LZF 算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;(体积小)
    • Redis 加载 RDB 文件恢复数据要远远快于 AOF 方式;(速度快)
  • 缺点
    • RDB 方式实时性不够,无法做到秒级的持久化;(实时性不够)
    • 每次调用 bgsave 都需要 fork 子进程,fork 子进程属于重量级操作,频繁执行成本较高;(fork 成本高)
    • RDB 文件是二进制的,没有可读性,AOF 文件在了解其结构的情况下可以手动修改或者补全;(可读性不高)
    • 版本兼容 RDB 文件问题;(不兼容)

3.1.4 rdb文件修复

2

3.1.5 哪些情况会触发快照

  • redis.conf 中配置 save m n,即在 m 秒内有 n 次修改时,自动触发 bgsave 生成 rdb 文件;
  • 主从复制时,从节点要从主节点进行 全量复制时 也会触发 bgsave 操作,生成当时的快照发送到从节点;
  • 执行 debug reload 命令重新加载 redis 时也会触发 bgsave 操作;
  • 默认情况下执行 shutdown 命令时,如果没有开启 aof 持久化,那么也会触发 bgsave 操作;
  • 执行save/bgsave 命令,flush/flushall

3.1.6 rdb 优化配置项

save m n
dbfilename
dir

  • stop-writes-onbgsave-error
    2
    rdbcompression
    压缩存储lzf 压缩
    rdbchecksum
    默认yes
    rdb-del-sync-files
    默认no

3.2 aof

aof 采用写后技术,先写内存,再写日志。

3.2.1 aof 配置

# appendonly参数开启AOF持久化
appendonly no

# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"

# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./

# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no

# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 配置no-appendfsync-on-rewrite = yes,
# 禁止在rewrite期间做aof,避免因AOF引起的阻塞

# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加载aof出错如何处理
aof-load-truncated yes

# 文件重写策略
aof-rewrite-incremental-fsync yes

解析

  • Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
  • Everysec,每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
    - No,操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

3.2.2 AOF文件故障恢复

如遇到AOF文件损坏,可通过
redis-check-aof --fix appendonly.aof 进行恢复

3.2.3 Rewrite 重写机制

Redis如何实现重写

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

  • AOF重写会阻塞吗?

AOF重写过程是由后台·进程bgrewriteaof来完成的。主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。所以aof在重写时,在fork进程时是会阻塞住主线程的

  • AOF日志何时会重写?

系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

auto-aof-rewrite-min-size:表示运行AOF重写时文件的最小大小,默认为64MB。
auto-aof-rewrite-percentage:这个值的计算方式是,当前aof文件大小和上一次重写后aof文件大小的差值,再除以上一次重写后aof文件大小。也就是当前aof文件比上一次重写后aof文件的增量大小,和上一次重写后aof文件大小的比值。

  • 重写日志时,有新数据写入咋整?

所以如果这个阶段修改的是一个 bigkey,也就是数据量比较大的 key-value 的时候,这时复制的物理内存数据的过程就会比较耗时,有阻塞主进程的风险。

还有个问题,重写 AOF 日志过程中,如果主进程修改了已经存在 key-value,此时这个 key-value 数据在子进程的内存数据就跟主进程的内存数据不一致了,这时要怎么办呢?

为了解决这种数据不一致问题,Redis 设置了·一个 AOF 重写缓冲区,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。

在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」
2
也就是说,在 bgrewriteaof 子进程执行 AOF 重写期间,主进程需要执行以下三个工作:

  • 执行客户端发来的命令;

  • 将执行后的写命令追加到 「AOF 缓冲区」;

  • 将执行后的写命令追加到 「AOF 重写缓冲区」;

当子进程完成 AOF 重写工作(扫描数据库中所有数据,逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志)后,会向主进程发送一条信号,信号是进程间通讯的一种方式,且是异步的。

主进程收到该信号后,会调用一个信号处理函数,该函数主要做以下工作:

  • AOF 重写缓冲区·中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致;

  • 新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。

信号函数执行完后,主进程就可以继续像往常一样处理命令了。

在整个 AOF 后台重写过程中,除了发生写时复制会对主进程造成阻塞,还有信号处理函数执行时也会对主进程造成阻塞,在其他时候,AOF 后台重写都不会阻塞主进程。

重写过程总结为:·“一个拷贝,两处日志”。在fork出子进程时的拷贝,以及在重写时,如果有新数据写入主线程就会将命令记录到两个aof日志内存缓冲区中。如果AOF写回策略配置的是always,则直接将命令写回旧的日志文件,并且保存一份命令至AOF重写缓冲区,这些操作对新的日志文件是不存在影响的。(旧的日志文件:主线程使用的日志文件,新的日志文件:bgrewriteaof进程使用的日志文件)

而在bgrewriteaof子进程完成会日志文件的重写操作后,会提示主线程已经完成重写操作,主线程会将AOF重写缓冲中的命令追加到新的日志文件后面。这时候在高并发的情况下,AOF重写缓冲区积累可能会很大,这样就会造成阻塞,Redis后来通过Linux管道技术让aof重写期间就能同时进行回放,这样aof重写结束后只需回放少量剩余的数据即可(这个过程会阻塞·)。

最后通过修改文件名的方式,保证文件切换的原子性。

在AOF重写日志期间发生宕机的话,因为日志文件还没切换,所以恢复数据时,用的还是旧的日志文件。

3.2.4 重写总结

总结操作:

  • 主线程fork出子进程重写aof日志
  • 子进程重写日志完成后,主线程追加aof日志缓冲
  • 替换日志文件

2

3.2.5 aof优缺点

aof 文件 大

3.3 aof rdb 混合模式

设置aof-use-rdb-preamble的值为 yes yes表示开启,设置为no表示禁用

2

4 redis 事务

Redis事务的主要作用就是串联多个命令防止别的命令插队
不保证原子性
cli1 set k1 v1
get k1

cl2 set k1 v11

watch multi discard exec

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值