Redis是单线程+多路IO复用技术
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)

1、Redis 中的数据类型有哪些

string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。

1.1、String

  • get
  • set

数据结构
简单动态字符串,可修改,类似于JAVA ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配
对于Value,当字符串长度小于1M时,扩容都是加倍现有空间,如果超过1M,扩容时一次只会多扩容1M的空间,字符串的最大长度为512M
应用:文章点赞数,阅读数,购物车数量

1.2、List

单键多值
数据结构,底层其实是一个双向快速链表,对两端的操作性能很高,通过索引下标操作中间的节点性能会比较差。
首先,在元素较少的情况下使用一块连续的内存存储,这个结构叫ziplist(压缩列表),数据量较多的时候会改成quicklist,将一个个的ziplist加上头尾指针做成quicklist。
应用:微信公众号订阅文章

1.3、Set

数据结构
dict字典,字典是用hash表实现的
应用:抽奖,朋友圈点赞,微博关系,qq可能认识的人

1.4、Hash

数据结构
对应javaMap>
ziplist压缩列表,hashtable哈希表
当field-value长度较短且个数较少的时候,是由ziplist,否则用hashtable
应用:购物车的功能
image.png

1.5、Zset有序集合

跟set类似,是一个没有重复元素的字符串集合
不用指出是有序集合的每个成员都关联了一个评分Score,集合的成员是唯一的,但是评分是可以重复的
数据结构

  • hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值

应用:销售排行榜,热搜

1.6、Bitmaps

Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:

  • Bitmaps本身不是一种数据类型,实际上它就是字符串(key-value),但是它可以对字符串的位进行操作。
  • Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量。

image.png

1.7、HyperLogLog

  • Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
  • 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
  • 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

    1.7.1、什么是基数

    比如数据集 {1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。基数估计就是在误差可接受的范围内,快速计算基数。

    1.8、Geospatial

    Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

    2、配置文件

    redis.conf

    2.1、Unit 单位

    配置大小单位,开头定义了一些基本的度量单位,支支持bytes,不支持bit,大小写不敏感

    2.2、INCLUDES包含

    类似jsp中的include,多实例的情况可以把公用的配置文件提取出来

    2.3、网络相关配置

    2.3.1、bind

    默认情况bind=127.0.0.1只能接受本机的访问请求
    不写的情况下,无限制接受任何ip地址的访问
    生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉
    如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
    保存配置,停止服务,重启启动查看进程,不再是本机访问了。

    2.3.2、protected-mode

    将本机访问保护模式设置no

    2.3.3、Port

    端口号,默认 6379

    2.3.4、tcp-backlog

    设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列+ 已经完成三次握手队列。
    在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
    注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果

    2.3.5、timeout

    一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。

    2.3.6、tcp-keepalive

    对访问客户端的一种心跳检测,每个n秒检测一次。
    单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60

    2.4. GENERAL通用

    2.4.1、daemonize

    是否为后台进程,设置为yes
    守护进程,后台启动

    2.4.2、pidfile

    存放pid文件的位置,每个实例会产生一个不同的pid文件

    2.4.3、loglevel

    指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
    四个级别根据使用阶段来选择,生产环境选择notice 或者warning

    2.4.4、logfile

    日志文件名称

    2.4.5、databases 16

    设定库的数量默认16,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id

    2.5、SECURITY安全

    2.5.1、设置密码

    访问密码的查看、设置和取消
    在命令中设置密码,只是临时的。重启redis服务器,密码就还原了。
    永久设置,需要再配置文件中进行设置。

    2.6、LIMITS限制

    2.6.1、maxclients

  • 设置redis同时可以与多少个客户端进行连接。

  • 默认情况下为10000个客户端。
  • 如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。

    2.6.2、maxmemory

  • 建议必须设置,否则,将内存占满,造成服务器宕机

  • 设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
  • 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
  • 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。

    2.6.3、maxmemory-policy(缓存过期淘汰策略)

  • volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)

  • allkeys-lru:在所有集合key中,使用LRU算法移除key
  • volatile-lfu:使用LFU算法移除key,只对设置了过期时间的键;(最近最少使用)
  • allkeys-lfu:在所有集合key中,使用LFU算法移除key
  • volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
  • allkeys-random:在所有集合key中,移除随机的key
  • volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
  • noeviction:不进行移除。针对写操作,只是返回错误信息(默认)

LRU和LFU是不同的!
LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面!
LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页!
image.png

2.6.4、maxmemory-samples

  • 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
  • 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。

    3、发布和订阅

    Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。
    image.pngimage.png

  • 打开一个客户端订阅:SUBSCRIBE channel1

  • 打开另一个客户端,给channel1发布消息hello:publish channel1 hello
  • 第一个客户端就可以接收到消息

注:发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息

4、Redis事务

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

4.1、Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。 image.png

4.2、事务的错误处理

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
image.png
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
image.png

4.3、悲观锁与乐观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

4.4、WATCH

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
unwatch,取消监视。如果在执行WATCH命令之后,EXEC命令或DISCARD命令先被执行了的话,那么就不需要再执行UNWATCH了。

4.5、Redis事务三特性

  • 单独的隔离操作
    • 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念
    • 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
  • 不保证原子性

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

      4.6、lua脚本

      Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
      很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。
      这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。
      https://www.w3cschool.cn/lua/

      4.6.1、LUA脚本在Redis中的优势

      将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
      LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
      但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。
      利用lua脚本淘汰用户,解决超卖问题。
      redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

      5、Redis持久化

      Redis 提供了2个不同形式的持久化方式。
  • RDB(Redis DataBase)

  • AOF(Append Of File)

    5.1、RDB

    在redis.conf中配置文件名称,默认为dump.rdb
    在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
    image.png

    5.1.1、RDB备份是如何执行的

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

    5.1.2、Fork

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

  • 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”
  • 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

    5.1.3、RDB的优劣

    优势

  • 适合大规模的数据恢复

  • 对数据完整性和一致性要求不高更适合使用
  • 节省磁盘空间
  • 恢复速度快

劣势

  • Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
  • 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

    5.1.4、rdbcompression 压缩文件

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

    5.2、AOF

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

    5.2.3、AOF持久化流程

  • 客户端的请求写命令会被append追加到AOF缓冲区内;

  • AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
  • AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
  • Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

image.png

5.2.4、AOF同步频率设置

  • appendfsync always
    • 始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
  • appendfsync everysec
    • 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
  • appendfsync no

    • redis不主动进行同步,把同步时机交给操作系统。

      5.2.5、AOF优劣

      优势
  • 备份机制更稳健,丢失数据概率更低。

  • 可读的日志文本,通过操作AOF稳健,可以处理误操作。

劣势

  • 比起RDB占用更多的磁盘空间。
  • 恢复备份速度要慢。
  • 每次读写都同步的话,有一定的性能压力。
  • 存在个别Bug,造成恢复不能。

    5.2.6、ReWrite压缩

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

    5.2.6.1、重写原理

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

    5.2.6.1、触发机制,何时重写

    Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
    重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。
    auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
    auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。

    5.2.6.3、重写过程

  • bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。

  • 主进程fork出子进程执行重写操作,保证主进程不会阻塞。
  • 子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。

    • 子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。
    • 主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
  • 使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

    6、主从复制

    主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

  • 读写分离,性能扩展

  • 容灾快速恢复

image.png
slave-priority 10

  • 设置从机的优先级,值越小,优先级越高,用于选举主机时使用。默认100

info replication:查看主机运行情况
slaveof 127.0.0.1 6379:成为某个实例的从服务器
用slaveof no one 将从机变为主机。

6.1、复制原理

  • Slave启动成功连接到master后会发送一个sync命令
  • Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步
  • 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
  • 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

image.png

6.2、哨兵模式

能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
image.png

  • 优先级在redis.conf中默认:slave-priority 100,值越小优先级越高
  • 偏移量是指获得原主机数据最全的
  • 每个redis实例启动后都会随机生成一个40位的runid

    7、Redis集群

    Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
    Redis 集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。

    7.1、Redis集群slots

    一个 Redis 集群包含16384 个插槽(hash slot),数据库中的每个键都属于这16384 个插槽的其中一个,
    集群使用公式CRC16(key) % 16384 来计算键key 属于哪个槽,其中CRC16(key) 语句用于计算键key 的CRC16 校验和。
    集群中的每个节点负责处理一部分插槽。举个例子,如果一个集群可以有主节点,其中:
    节点 A 负责处理0号至5460号插槽。
    节点 B 负责处理5461号至10922号插槽。
    节点 C 负责处理10923号至16383号插槽。
    在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。
    redis-cli客户端提供了–c 参数实现自动重定向。
    如redis-cli -c–p 6379登入后,再录入、查询键值对可以自动重定向。
    不在一个slot下的键值,是不能使用mget,mset等多键操作。
    可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。

    7.2、集群的优劣

    优势

  • 实现扩容

  • 分摊压力
  • 无中心配置相对简单

劣势

  • 多键操作是不被支持的
  • 多键的Redis事务是不被支持的。lua脚本不被支持

    8、Redis应用问题

    8.1、缓存穿透

    key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
    image.png
    一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
    解决方案:

  • 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟

  • 设置可访问的名单(白名单):

使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。

  • 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

  • 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

8.2、缓存击穿

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
image.png
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
解决问题:

  • 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
  • 实时调整:现场监控哪些数据热门,实时调整key的过期时长
  • 使用锁:
    • 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
    • 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
    • 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
    • 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。

image.png

8.3、缓存雪崩

很多key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
image.png
解决方案:

  • 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)
  • 使用锁或队列:

用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况

  • 设置过期标志更新缓存:

记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。

  • 将缓存失效时间分散开:

比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

8.4、Redis实现分布式锁

setnx设置锁

  • 优化之设置锁的过期时间
    • 通过expire设置过期时间(缺乏原子性:如果在setnx和expire之间出现异常,锁也无法释放)
    • 在set时指定过期时间(推荐)
  • 优化之UUID防误删
  • 优化之LUA脚本保证删除的原子性

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  • 加锁和解锁必须具有原子性。

    9、权限控制

    Redis ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接
    在Redis 5版本之前,Redis 安全规则只有密码控制还有通过rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。Redis 6 则提供ACL的功能对用户进行更细粒度的权限控制:
    (1)接入权限:用户名和密码
    (2)可以执行的命令
    (3)可以操作的 KEY
    使用acl list命令展现用户权限列表
    使用acl whoami命令查看当前用户
    使用aclsetuser命令创建和编辑用户ACL

    10、过期键删除策略

    如果一个键是过期的,那它到了过期时间之后是不是马上就从内存中被被删除呢??如果不是,那过期后到底什么时候被删除呢??
    其实有三种不同的删除策略:
  1. 立即删除。在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。
  2. 惰性删除。键过期了就过期了,不管。每次从dict字典中按key取值时,先检查此key是否已经过期,如果过期了就删除它,并返回nil,如果没过期,就返回键值。
  3. 定时删除。每隔一段时间,对expires字典进行检查,删除里面的过期键。

可以看到,第二种为被动删除,第一种和第三种为主动删除,且第一种实时性更高。下面对这三种删除策略进行具体分析。

10.1、立即删除

立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。但是立即删除对cpu是最不友好的。因为删除操作会占用cpu的时间,如果刚好碰上了cpu很忙的时候,比如正在做交集或排序等计算的时候,就会给cpu造成额外的压力。
而且目前redis事件处理器对时间事件的处理方式—无序链表,查找一个key的时间复杂度为O(n),所以并不适合用来处理大量的时间事件。

10.2、惰性删除

惰性删除是指,某个键值过期后,此键值不会马上被删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。所以惰性删除的缺点很明显:浪费内存。dict字典和expires字典都要保存这个键值的信息。
举个例子,对于一些按时间点来更新的数据,比如log日志,过期后在很长的一段时间内可能都得不到访问,这样在这段时间内就要拜拜浪费这么多内存来存log。这对于性能非常依赖于内存大小的redis来说,是比较致命的。可能会导致内存泄露

10.3、定时删除

从上面分析来看,立即删除会短时间内占用大量cpu,惰性删除会在一段时间内浪费内存,所以定时删除是一个折中的办法。
定时删除是:每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对cpu的影响。另一方面定时删除也有效的减少了因惰性删除带来的内存浪费。
redis使用的策略
redis使用的过期键值删除策略是:惰性删除加上定期删除,两者配合使用。内存接近阈值了,采用淘汰策略进行删除