当前位置: 首页 > article >正文

Redis梳理

1、Redis是什么?
Redis是开源的(BSD许可开源),内存数据结构存储,被用作数据库,缓存,消息队列。
它支持的数据结构像Strings,hashes,lists,sets,sorted set等范围查询数据结构,以及bitmaps,hyperloglogs,geospatial indexes等半径查询和流。Redis有内置的复制,lua脚本,LRU驱逐,事务和不同级别的磁盘持久化,同时提供高可用集群:Redis sentinel和自动分区的Redis cluster。
Redis中单独操作某个数据类型支持原子性操作。Redis也支持主从异步复制,非阻塞第一次同步非常快的,自动重连接与在网络分割时部分重同步。
Redis是C语言写的。支持大多数POXSI操作系统像Linux, *BSD, OS X,没有其他的外部依赖。
2、事务
MUTIL、EXEC、DISCARD和WATCH是redis事务的基础。它们允许在一个步骤中执行一组命令,其中有两个重要保证:
事务中的所有命令都被序列化并按顺序执行。在执行Redis事务的过程中,决不会出现另一个客户端发出的请求被服务的情况。这可以保证命令作为单独的操作执行。

要么所有的命令都被处理,要么一个都没有,所以Redis事务也是原子的。EXEC命令触发事务中所有命令的执行,因此,如果客户机在调用EXEC命令之前在事务上下文中断开与服务器的连接,则不会执行任何操作,相反,如果调用EXEC命令,则会执行所有操作。当使用仅附加文件时,Redis确保使用一个write(2)syscall将事务写入磁盘。但是,如果Redis服务器崩溃或被系统管理员以某种方式终止,则可能只注册了部分操作。Redis将在重新启动时检测到这种情况,并将以错误退出。使用redis check aof工具,可以修复将删除部分事务的append-only文件,以便服务器可以重新启动。

从2.2版开始,Redis允许对以上两种情况提供额外的保证,以乐观锁定的形式,其方式非常类似于check and set(CAS)操作。本页稍后将对此进行说明。
2.1 事务使用
一个redis事务使用MULTI命令表示开启事务,这个命令总是会响应OK,在它后面可以输入多个命令,但是这些命令不会立即执行,redis会先将它们排列起来,然后通过使用EXEC命令来统一执行这个事务。相反,如果使用DISCARD命令,则会刷新Redis事务队列,并且推出事务。
相关命令:

DISCARD
EXEC
MULTI
UNWATCH
WATCH

例1:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
1) "whf"
2) OK
3) "18"

例2:

127.0.0.1:6379> multi 
OK
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> discard
OK

例3:
像一些操作数据类型的事务中,一个命令错误时,redis也会排列所有命令,并且执行完所有的命令。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> lpush name 1
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) "18"

例4:
但是比如是一些语法性的错误,会直接报错,但是事务会继续排列命令,最终执行exec命令时会直接报错。

127.0.0.1:6379> multi 
OK
127.0.0.1:6379> incr name age
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6379> 

例5:
watch命令使用cas(check-and-set)作为乐观锁,在监视一个key的并且还未执行exec命令的过程中,如果另外一个客户端修改了这个key的值,那么事务就会中止,并且空响应来通知事务失败

127.0.0.1:6379> get age
"18"
127.0.0.1:6379> watch age
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec   # 在执行之前其他客户端incr age
(nil)

watch命令解释:
watch命令可以看作是exec命令执行的一个前置条件,当watch一个key的时候,不允许其他客户端对key进行修改,否则exec会空响应表示失败。在执行exec命令执行可以使用watch监听多个key,假如我们不需要监听的时候,可以使用unwatch命令解锁对所有watch key的监听。

redis脚本和事务
根据定义,Redis脚本是事务性的,因此您可以使用Redis事务执行的所有操作,也可以使用脚本来完成,通常脚本将更加简单和快速。
这种重复是由于Redis 2.6中引入了脚本,而事务早已存在。然而,我们不太可能在短期内取消对事务的支持,因为从语义上讲,即使不使用Redis脚本,仍然可以避免竞争条件,特别是由于Redis事务的实现复杂性很小。

然而,在不久的将来,我们将看到整个用户群都在使用脚本,这并非不可能。如果发生这种情况,可能会否决并最终删除事务。

2.2 redis事务为什么不支持回滚
1)redis命令只会在语法错误或者对一个key操作一个错误的数据类型的情况下提示报错,因为这种错误应该在开发时就应该被发现而不是在生产使用过程中再报错。
2)因为不需要回滚,所以redis内部处理事务会更加简单和快速。
3)除了语法性的错误,编程上的逻辑错误不可避免,比如一个查询将一个key加2而不是加1,这个是无法避免的,这种情况下即使回滚也无法帮助程序员避免错误。所以选择了无支持事务回滚的更简单,更快的方式。

3、发布和订阅
SUBSCRIBE、UNSUBSCRIBE和PUBLISH实现了PUBLISH/SUBSCRIBE消息传递范式,其中发送者(发布者)没有被编程为向特定的接收者(订阅者)发送消息。更确切地说,发布的消息被描述成频道,而不知道可能有什么(如果有的话)订阅者。订阅者表示对一个或多个频道感兴趣,只接收感兴趣的消息,而不知道有什么(如果有的话)发布者。发布者和订阅者的这种分离可以提供更大的可伸缩性和更动态的网络拓展。
其他客户端发送到这些通道的消息将由Redis推送到所有订阅的客户端。
订阅了一个或多个频道的客户端不应发出命令,尽管它可以订阅和取消订阅其他频道。对订阅和取消订阅操作的响应以消息的形式发送,这样客户机就可以读取一致的消息流,其中第一个元素表示消息的类型。订阅客户机上下文中允许的命令有SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE、PING和QUIT。
相关命令:

PSUBSCRIBE
PUBLISH
PUBSUB
PUNSUBSCRIBE
SUBSCRIBE
UNSUBSCRIBE

例1:
订阅单个通道
监听者:

127.0.0.1:6379> subscribe test
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "test"
3) (integer) 1
1) "message"
2) "test"
3) "hello"
1) "message"
2) "test"
3) "how are you"

发布者:

127.0.0.1:6379> publish test hello
(integer) 1
127.0.0.1:6379> publish test how are you
(error) ERR wrong number of arguments for 'publish' command
127.0.0.1:6379> publish test "how are you"
(integer) 1
127.0.0.1:6379> 

例2:
订阅多个通道
监听者:

127.0.0.1:6379> subscribe test second
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "test"
3) (integer) 1
1) "subscribe"
2) "second"
3) (integer) 2
1) "message"
2) "test"
3) "whf"
1) "message"
2) "second"
3) "lvq"

发布者:

127.0.0.1:6379> publish test whf
(integer) 1
127.0.0.1:6379> publish second lvq
(integer) 1
127.0.0.1:6379> 

例3:
批量订阅批量取消订阅

psubscribe new.*
punsubscribe new.*

4、Lua脚本
1)自2.6.0可用。
2)时间复杂度:取决于所执行的脚本。
脚本执行会被缓存
Eval 命令使用 Lua 解释器执行脚本。

EVAL script numkeys key [key ...] arg [arg ...] 
script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
numkeys: 用于指定键名参数的个数。
key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)

5、过期时间key
一个key设置了超时时间,在时间过期的时候,这个key就会被删除(实际上并不是所有的key过期时间立马删除)。
一个key的超时设置只会被删除或覆盖操作清楚掉,包括del,set,getset以及所有的赋值存储命令。
这意味着,在概念上更改存储在键处的值而不使用新值替换它的所有操作将保持超时不变。例如,使用INCR递增键值,使用LPUSH将新值推入列表,或使用HSET更改散列的字段值,这些操作都不会影响超时。还可以使用PERSIST命令清除超时,将key恢复为持久key。
如果密钥被重命名为RENAME,则关联的生存时间将转移到新密钥名。
expire命令
在Redis 2.4中,过期时间可能不是精确的,可能在0到1秒之间。
由于Redis 2.6,过期错误从0到1毫秒。
为一个key设置超时时间,expire命令返回值:
1:代表key存在并且设置超时成功
0:代表key不存在

127.0.0.1:6379> set mykey hello
OK
127.0.0.1:6379> expire mykey 10
(integer) 1
127.0.0.1:6379> ttl mykey
(integer) 5
127.0.0.1:6379> ttl mykey
(integer) -2
127.0.0.1:6379> expire myname 10
(integer) 0

persist命令
将一个超时key转换为持久化key

127.0.0.1:6379> set mykey hello ex 100
OK
127.0.0.1:6379> ttl mykey
(integer) 97
127.0.0.1:6379> persist mykey
(integer) 1
127.0.0.1:6379> ttl mykey
(integer) -1
127.0.0.1:6379> 

6、redis如何处理过期key
redis有两种删除key的方式:
1、惰性删除:当客户端访问某个key并且发现该key已经过期的时候删除
2、定时随机删除:每秒执行十次扫描删除操作,具体步骤如下
1)从设置了过期时间的key中随机抽取20个key
2)判断这20个key中是否过期,过期的则进行删除
3)如果过期的比例超过25%(第2步删除了超过25%的key,也就是删除了5个key时候),则继续进行第1步,重新扫描。
这意味着任一时刻,过期key的最大数量不超过每秒写入操作最大数量的25%
当对key的操作记录追加到AOF文件时,刚好key过期了,为了保证数据的一致性,此时会追加DEL命令到AOF文件中。
另外,从节点中的过期key在过期时间后不会直接过期,而是保持了一个过期key的完整形态,在从节点选举为主节点的时候来删除key,也就是说,只有主节点才会独立地使过期key过期。

7、内存淘汰策略和LRU算法
maxmemory配置
用于将Redis配置为使用多大的内存存储数据。可以启动前在redis.conf文件配置,或在运行时使用CONFIG SET命令配置。如果maxmemory是0则代表没有内存限制。

redis 127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "3221225472"  # 这里是b为单位
redis 127.0.0.1:6379> config set maxmemory 4294967296 
OK
redis 127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "4294967296"

内存淘汰策略
当内存容量达到maxmemory时,redis可以根据以下几种内存淘汰策略淘汰掉一些旧数据。

1)noeviction:当达到内存限制并且客户端试图执行可能导致使用更多内存的命令时返回错误(大多数write命令,但DEL和一些其他异常)。
2)allkeys-lru:从所有key中删除最近最少使用的(lru)key
3)volatile-lru:从设置了过期时间的key中删除最近最少使用的key
4)allkeys-random:从所有的key中随机删除key
5)volatile-random:从设置了过期时间的key中随机删除key
6)volatile-ttl:删除设置了过期时间的key,并且尝试先删除那些即将过期的key(可存活时间较短的)

一般经验法则:
1)当您期望在您的请求受欢迎程度中出现幂律分布时,请使用allkeys lru策略,也就是说,您期望元素的子集将比其他元素更频繁地被访问。如果你不确定,这是个不错的选择。
2)如果您有一个循环访问,其中所有密钥都被连续扫描,或者当您希望分布是均匀的(所有元素都可能以相同的概率访问),请使用allkeys random。
3)如果您想在创建缓存对象时使用不同的ttl值来向Redis提供提示,说明哪些是最适合过期的候选对象,请使用volatile ttl。
4)volatile lru和volatile random策略主要在您希望使用单个实例进行缓存并拥有一组持久密钥时非常有用。但是,通常最好运行两个Redis实例来解决这个问题。
还值得注意的是,将expire设置为key会消耗内存,因此使用allkeys lru这样的策略可以更有效地节省内存,因为在内存压力下,不需要为键设置expire。

redis的lru算法是近似lru算法,并不是完全对等的,它通过对少量key进行采样,并且将采样的key中上次访问时间最长的key进行删除。可以通过配置来调整lru算法的精度,默认为5,如果想要更加接近真实的lru算法,可以将maxmemory-samples 设为10,但是可能会增加CPU消耗以及缓存的命中率。

config set maxmemory-samples 10

LFU最少使用频率(redis4.0)
LFU类似于LRU:它使用一个概率计数器,称为Morris计数器,以便仅使用每个对象的几个位来估计对象访问频率,再加上一个衰减周期,以便计数器随着时间的推移而减少:在某些情况下,我们不再希望将密钥视为频繁访问的,即使它们在过去,从而使算法能够适应访问模式的变化。

8、redis持久化方式
redis4之前的数据持久化有AOF和RDB两种,从Redis4之后新增了AOF+RDB混合持久化的方式.
1)RDB(Redis DataBase)
RDB持久化方式是将当前内存中的数据快照到磁盘中。RDB可以通过手动触发或者根据配置文件中配置时间定时触发。
手动触发可以通过save或bgsave命令进行快照,不同的是save命令会阻塞redis主进程进行快照,会导致服务不可用,bgsave是fork一个子进程来进行数据快照的,两个命令都是调用的rdbSave函数,持久化之后的文件名为dump.rdb

# redis.conf中的RDB配置
save 900 1      # 900秒内有1次修改则会触发
save 300 10     # 300秒内有10次修改则会触发
save 60 10000   # 60秒内有10000次修改则会触发

dbfilename "dump.rdb"        # RDB文件名
dir "/opt/app/redis6/data"   # RDB文件存储路径

另外在主从全量同步,debug reload和shutdown情况下都会触发rdb。
2)AOF(Append-Only File)
将redis中执行的操作追加到文件中,redis服务重启时会将aof中的命令执行恢复数据。

AOF持久化流程

appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

2.1)将写命令append到AOF缓冲区内
2.2)针对AOF配置(always,everysec,no)将缓冲区的命令同步到AOF文件中
2.3)当AOF文件大小触发配置中的重写配置时,会对AOF文件进行重写,压缩AOF文件容量

RDB+AOF混合模式(redis4)
以下配置,redis会在启动时先找rdb来恢复数据,然后再继续加载AOF文件

# redis.conf
aof-use-rdb-preamble yes

优缺点:
RDB持久化快,恢复数据快,但是有可能会丢失数据。
AOF使用everysec不会丢失数据,但是缺点是数据恢复时比较慢。
9、redis集群方式
脑裂、sentinel、cluster模式
原理,故障转移。。。
todo


http://www.kler.cn/a/448265.html

相关文章:

  • 【Java基础面试题016】JavaObject类中有什么主要方法,作用是什么?
  • 【day11】面向对象编程进阶(继承)
  • OpenResty、Lua介绍认识
  • 【NLP 16、实践 ③ 找出特定字符在字符串中的位置】
  • 冯诺依曼架构与哈佛架构的对比与应用
  • 知网研学 | 知网文献(CAJ+PDF)批量下载
  • 计算机视觉目标检测——DETR(End-to-End Object Detection with Transformers)
  • 自然语言处理学什么
  • 前端开放性技术面试—面试题
  • 虚拟世界中的社交互动:Facebook在元宇宙中的发展路径
  • CNN分类-卷积神经网络(Convolutional Neural Network)
  • 【C语言】倒序输出
  • uniapp input的触发事件
  • robots协议
  • 使用Python和OpenCV进行双目摄像头测距的详细教程及源代码
  • Hive SQL 查询所有函数
  • setTimeout 最小执行时间是多少
  • java.util.ConcurrentModificationException异常出现的原因及解决方法
  • 【python实现烟花】
  • 解锁 SSM 与 Vue 在新锐台球厅管理系统设计与实现中的融合密码
  • 【大语言模型】ACL2024论文-29 答案即所需:通过回答问题实现指令跟随的文本嵌入
  • 【多维DP】【准NOI难度】力扣3251. 单调数组对的数目 II
  • 爬虫代码中如何处理异常?
  • 【面试 - 遇到的问题】Vue 里 router-view 使用 key + 关闭页面后重新打开页面-获取的数据赋值到旧组件问题(钩子执行顺序)
  • oracle使用imp命令导入dmp文件
  • 方正畅享全媒体新闻采编系统 reportCenter.do Sql注入漏洞复现(附脚本)