Redis进阶

一、Redis事务

1、Redis事务定义

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

2、Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。

组队的过程中可以通过discard来放弃组队。

1) 组队成功,提交成功
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
2)组队阶段报错,提交报错
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set m1 v1
QUEUED
127.0.0.1:6379(TX)> set m2
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
3) 组队成功,提交有成功有失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set a1 v1
QUEUED
127.0.0.1:6379(TX)> incr a1
QUEUED
127.0.0.1:6379(TX)> set a2 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK

3、事务的错误处理

组队中某个命令出现了报错信息,执行时,整个队列都会被取消。

如果执行阶段某个命令报错了,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

4 、事务冲突问题

1) 案例

​ 一个请求想给金额减8000

​ 一个请求想给金额减5000

​ 一个请求想给金额减1000

2)悲观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

3)乐观锁

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

5、WATCH key [key ...]

在执行multi之前,先执行watch key [key ...],监视一个或多个key,如果在事务执行之前,key被其他命令所改动,那么事务会被打断。

  • 事务正常执行
127.0.0.1:6379(TX)> set ak 47
QUEUED
127.0.0.1:6379(TX)> incr ak 
QUEUED
127.0.0.1:6379(TX)> get ak
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (integer) 48
3) "48"
  • 事务提交之前,被监视的key被其他客户端改动
127.0.0.1:6379> set ak 47
OK
127.0.0.1:6379> get ak
"47"

# 打开另外一个客户端
127.0.0.1:6379> set ak 74
OK

# 切回,提交事务
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI

补充:unwatch命令会取消所有key的监视,如果在执行watch命令之后,exec命令或者discard命令先被执行了的话,那么就不需要再执行unwatch了。

6、事务的三特性

  • 单独的隔离操作

    事务中所有命令都会被序列化,按顺序执行,事务在执行的过程中,不会被其他客户端发来的命令请求所打断。

  • 没有隔离级别的概念

    队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。

  • 不保证原子性

    事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

二、持久化

1、RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

  • Fork

    Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程 。

    在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术” 。

    一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

1)RDB持久化流程

2)dump.rdb文件

在redis.conf中配置文件名称,默认为dump.rdb

rdb文件保存的路径默认是Redis启动时命令行所在的目录下,也可以指定路径。

3)如何触发RDB快照
  • 配置文件中默认的快照配置

    ################################ SNAPSHOTTING  ################################
    
    # Save the DB to disk.
    #
    # save <seconds> <changes>  # 
    #
    # Redis will save the DB if both the given number of seconds and the given
    # number of write operations against the DB occurred.
    #
    # Snapshotting can be completely disabled with a single empty string argument
    # as in following example:
    
    
    # save ""
    #
    # Unless specified otherwise, by default Redis will save the DB:
    #   * After 3600 seconds (an hour) if at least 1 key changed
    #   * After 300 seconds (5 minutes) if at least 100 keys changed
    #   * After 60 seconds if at least 10000 keys changed
    #
    # You can set these explicitly by uncommenting the three following lines.
    #
    # save 3600 1
    # save 300 100
    # save 60 10000
    
  • 命令save和bgsave

    save:只管保存,其它一律不管,全部阻塞。

    bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。

    可以通过lastsave命令获取最后一次成功执行快照的时间。

    127.0.0.1:6379> save
    OK
    127.0.0.1:6379> bgsave
    Background saving started
    127.0.0.1:6379> lastsave
    (integer) 1620637370
    
  • flushall

    执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

  • stop-writes-on-bgsave-error

    当Redis无法写入磁盘的话,直接关掉Redis的写操作,推荐yes

  • rdbcompression压缩文件

    对于存储到磁盘中的快照,可以设置是否进行压缩存储,如果是的话,Redis会采用LZF算法进行压缩,如果不想小号CPU来进行压缩的话,可以设置为关闭此功能,推荐yes。

  • rdbchecksum检查完整性

    存储快照后,可以让Redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能,可以关闭此功能。

4)rdb的恢复

通过config文件获取rdb文件的目录,将*.rdb文件拷贝到需要启动Redis的目录下,启动Redis,备份数据自动加载。

5)优势

适合大规模的数据恢复

对数据完整性和一致性要求不高更适合

节省磁盘空间,恢复速度快

6)劣势

Fork的时候,内存中的数据被克隆了一份,大致两倍的膨胀性需要考虑

虽然Redis在Fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。

在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

7)如何停止
# 动态停止RDB
redis-cli config set save "" # save后给空值,表示禁用保存策略

2、AOF

以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只允许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据恢复的工作。

1) AOF持久化流程
  • 客户端的读写命令会被append追加到AOF缓冲区内;

  • AOF缓冲区根据AOF持久化策略【always,everysec,no】将操作sync同步到磁盘的AOF文件中;

  • AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;

  • Redis服务重启时,会重新load加载AOF文件中写操作达到数据恢复的目的;

2)AOF默认不开启

可以在redis.conf中配置文件名称,默认为appendonly.aof

AOF文件的保存路径,同RDB的路径一致。

3)AOF和RDB同时开启,Redis听谁的?

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

4)AOF启动、修复、恢复
  • AOF的备份机制和性能虽然和RDB不同,但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redsi工作目录下,启动系统即加载。
  • 正常恢复
    • 修改默认的appendonly no, 改为yes
    • 将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir)
    • 恢复:重启Redis重新加载
  • 异常恢复
    • 修改默认的appendonly no,改为yes
    • 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof-fix appendonly.aof 进行恢复
    • 备份被写坏的AOF文件
    • 恢复:重启Redis,然后重新加载
5)AOF同步频率设置
  • appendfsync always

    始终同步,每次Redis的写入都会立刻计入日志;性能较差但数据完整性比较好

  • appendfsync everysec

    每秒同步,每秒记入日志一次,如果宕机,本秒数据可能丢失

  • appendfsync no

    Redis不主动同步,把同步时机交给操作系统

6)Rewrite压缩

AOF采用文件追加方式,文件会越来越大,为了避免出现这种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof

  • 重写原理,如何实现重写?

    AOF文件持续增长而过大时,会fork出一个新进程来将文件重写(也是先写临时文件最后再rename),Redis4.0版本后的重写,事实上就是把rdb的快照,以二进制的形式附加在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。

7)优势
  • 备份机制更稳健,丢失数据概率更低
8)劣势
  • 比起RDB占用更多磁盘空间
  • 恢复备份速度要慢
  • 每次读写都同步的话,有一定的性能压力
  • 存在个别Bug,造成不能正常恢复

3、which one

官方推荐两个都用,如果对数据不敏感,可以选单独用RDB,不建议单独使用AOF,因为可能会出现BUG,如果只是做纯内存缓存,可以都不用。

  • 官方建议

    RDB持久化方式能够在指定的时间间隔对你的数据进行快照存储

    AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议住家保存每次写的操作到文件末尾。

    Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大

    只做缓存:如果你只希望你的数据在服务器运行的时候存在,可以不适用任何持久化方式

    同时开启两种持久化方式,在这种情况下,Redis重启的时候会有限载入AOF文件来恢复原始数据,因为通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整

    RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF? 建议不要,因为RDB更适用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。

    性能建议:因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟一次就够了,只保留save 9001这条规则。如果使用AOF,好处是在最恶劣的情况下也只丢失不超过两秒的数据,启动脚本比较简单,只load自己的AOF文件就可以了。代价是带来了持续ID,同时AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率。

三、Redis应用问题解决

1、缓存穿透

  • 问题描述

    key对应的数据在数据库中并不存在,每次针对此key的请求从缓存中获取不到,请求直接打在数据库上,从而可能导致数据库宕机。比如用一个不存在的用户ID获取用户信息,不论是缓存还是数据库都没有,若黑客利用此漏洞来攻击可能搞垮数据库。

  • 解决方案

    一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且处于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都会去存储层去查询,失去了缓存的意义。

    • 对空值缓存 :如果一个查询返回的数据为空,仍然把这个结果进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
    • 设置可访问的白名单:使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,如果访问id不存在,进行拦截。
    • 采用布隆过滤器:布隆过滤器可以用于检索一个元素是否在一个集合中,它的优点是空间效率和查询时间都远超过一般的算法,缺点是有一定的误判率。

2、缓存击穿

  • 问题描述

    key对应的数据存在,但在Redis中过期了,此时若有大量并发请求过来,这些请求全部落到数据库,造成DB宕机。

  • 解决方案

    • 热点数据用不过期,定时任务去刷新数据就可以
    • 使用互斥锁,第一个请求去查询数据库的时候,对它进行加锁,其余查询请求会被阻塞,直到锁释放,后面的线程进来发现已经有缓存了就直接走缓存,从而保护了数据库

3、缓存雪崩

  • 问题描述

    缓存中大量的或者全部的Key都过期了,请求全部落到数据库稻城DB宕机。

    缓存雪崩与缓存击穿的区别在于这里针对的是很多key的缓存,缓存击穿是某一个key的请求。

  • 解决方案

    • 事前:如果是缓存宕机造成的,将Redis集群部署,如果是大量缓存因为失效时间造成的,在批量往Redis缓存数据的时候,每个key的失效时间加上随机值
    • 事中:事前没有考虑到雪崩问题,可以采用限流或降级策略,避免MySQL被打死
    • 事后:如果是缓存服务宕机,开启Redis持久化机制。

四、Redis6.0新功能

1、ACL

Redis ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。

在Redis 5版本之前,Redis安全规则只有密码控制,还有通过rename来调整高危命令,比如flushdb, keys * ,shutdown等。Redis 6则提供ACL的功能对用户进行更细粒度的权限控制:

  • 接入权限:用户名和密码
  • 可以执行的命令
  • 可以操作的key
1) 命令
  • acl list
# 使用acl list命令展现用户权限列表
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"

default:用户名
on:是否启动(on/off)
nopass:密码(没有密码)
~*:可操作的key
all:课执行的命令
  • acl cat命令
# 查看添加权限指令类别
127.0.0.1:6379> acl cat
 1) "keyspace"
 2) "read"
 3) "write"
 4) "set"
 5) "sortedset"
 6) "list"
 7) "hash"
 8) "string"
 9) "bitmap"
10) "hyperloglog"
11) "geo"
12) "stream"
13) "pubsub"
14) "admin"
15) "fast"
16) "slow"
17) "blocking"
18) "dangerous"
19) "connection"
20) "transaction"
21) "scripting"

# 加参数类型名可以查看类型下具体命令
127.0.0.1:6379> acl cat string
 1) "set"
 2) "setnx"
 3) "mget"
 4) "decrby"
 5) "substr"
 6) "get"
 7) "incrbyfloat"
 8) "getrange"
 9) "setrange"
10) "strlen"
11) "setex"
12) "msetnx"
13) "decr"
14) "psetex"
15) "getex"
16) "incr"
17) "append"
18) "incrby"
19) "getset"
20) "getdel"
21) "stralgo"
22) "mset"
  • acl whoami
# 使用acl whoami命令查看当前用户
127.0.0.1:6379> acl whoami
"default"
  • acl setuser

    • ACL规则
    类型 参数 说明
    启动和禁用用户 on 激活某用户账号
    off 急用某用户账号。注意:已验证的连接仍然可以工作,如果默认用户被标记为off,则新连接将在未进行身份验证的情况下启动,并要求用户使用AUTH选项发送AUTH或HELLO,以便以某种方式进行身份验证
    权限的添加和删除 + 将指令添加到用户可以调用的指令列表中
    - 从用户可执行指令列表移除指令
    +@ 添加该类别中用户要调用的所有指令,有效类别为@admin、@set、@sortedset...等,通过调用acl cat命令查看完整列表,特殊类别@all表示所有命令,包括当前存在于服务器中的命令,以及将来将通过模块加载的命令
    -@ 从用户可调用指令中移除类别
    allcommands +@all的别名
    nocommand -@all的别名
    可操作键的添加或删除 - 添加可作为用户可操作性的键的模式,例如-*允许所有键
    • acl setuser user1
    # 通过命令创建新用户默认权限
    127.0.0.1:6379> acl setuser user1
    OK
    127.0.0.1:6379> acl list
    1) "user default on nopass ~* &* +@all"
    2) "user user1 off &* -@all"
    
    # 上面的示例中,根本没有指定任何规则,如果用户不存在,这将使用just created的默认属性来创建用户,如果用户已经存在,则上面的命令将不执行任何操作。
    
    • acl setuser user2 on >password ~cached:* +get
    # 设置用户名、密码、ACL权限,并启动的用户
    127.0.0.1:6379> acl setuser user2 on >password ~cached:* +get
    OK
    
    # 切换用户,验证权限
    127.0.0.1:6379> auth user2 password
    OK
    127.0.0.1:6379> acl whoami
    (error) NOPERM this user has no permissions to run the 'acl' command or its subcommand
    127.0.0.1:6379> get cached:1121
    (nil)
    127.0.0.1:6379> set cached:1121 1121
    (error) NOPERM this user has no permissions to run the 'set' command or its subcommand
    

2、IO多线程

Redis 6终于支撑多线程了,告别单线程了吗?

IO 多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程,Redis执行命令依然是单线程。

Redis 6加入多线程,但跟Memcached这种从IO处理到数据访问多线程实现模式有些诧异,Redis的多线程部分知识用来处理网络数据的读写和协议解析,执行命令仍然是单线程,之所以这么设计是不想因为多线程而变得复杂,需要去控制key、lua、事务、lpush/lpop等等的并发问题。整体的设计大体如下:

posted @ 2021-07-28 15:34  李大鹅  阅读(141)  评论(0编辑  收藏  举报