redis 判断存在性_了解redis的高级特性和原理

上一篇我们大致了解了redis的基本数据类型和数据结构,所以大致对于我们存储的数据在内存中有了一个比较模糊的样子了。并且对一些命令也了解了一些,因为针对不同的应用场景,这么命令可以起到不同的作用,就算平时我们不太会用,说简单点,万一面试官问到呢,所以呀,知道总比不知道好。然后呢,我们还要了解下redis的一些高级特性的,比如持久化、内存淘汰机制和过期机制等。好了,废话不要太多,直入主题。

发布订阅模式

我们知道list可以实现队列或者栈,但是用这个来实现队列的话是有局限性的,就是我们得再项目中不同的监听这个list队列的。这肯定说比较消耗内存的,但是如果设置sleep一会再去list队列中拿数据,这又会造成消息消费不及时问题,或者在产生大量消息时没有及时处理redis会消耗内存。并且不支持一对多,反正还是会有很多缺点滴。

所以就有发布订阅这种模式,发布者和订阅者没有直接关系,实现了解藕。这种模式说首先会有很多频道channel,然后生产者向channel发送消息,订阅者可以订阅多个channel接收消息。

订阅消息支持符号:

支持?和*占位符。?代表一个字符,*代表 0 个或者多个字符。

操作命令

1:PSUBSCRIBE pattern [pattern ...]

订阅一个或多个符合给定模式的频道。

2:PUBSUB subcommand [argument [argument ...]]

查看订阅与发布系统状态。

3:PUBLISH channel message

将信息发送到指定的频道。

4:PUNSUBSCRIBE [pattern [pattern ...]]

退订所有给定模式的频道。

5:SUBSCRIBE channel [channel ...]

订阅给定的一个或多个频道的信息。

6:UNSUBSCRIBE [channel [channel ...]]

指退订给定的频道。基于Redis实现消息队列典型方案_数据库_weixin_40663800的博客-CSDN博客​blog.csdn.net

redis事务和watch命令

为什么要用事务?

redis的单个命令执行说原子性的,这个各位大佬都是了解的很滴。但是在多个命令的时候,就不是啦,所以需要一个事务,来把多个命令作为一个不可分割的处理序列,说白了就是这几个命令按顺序依次执行,中间又不会被其他客户端分开。

redis的事务有两个特点:

1:按进入队列顺序执行

2:不会受到其他客户端的影响

Redis 的事务涉及到四个命令:multi(开启事务),exec(执行事务),discard (取消事务),watch(监视)

事务的用法

案例场景:a 和 b 各有 1000 元,a 需要向 b 转账 100 元。 a 的账户余额减少 100 元,b 的账户余额增加 100 元。

[root@instance-d26690z3 ~]# /opt/javaEvn/redisDir/bin/redis-cli

127.0.0.1:6379> auth 1

OK

127.0.0.1:6379> set a 1000

OK

127.0.0.1:6379> set b 1000

OK

127.0.0.1:6379> MULTI

OK

127.0.0.1:6379> DECRBY a 100

QUEUED

127.0.0.1:6379> INCRBY b 100

QUEUED

127.0.0.1:6379> EXEC

1) (integer) 900

2) (integer) 1100

127.0.0.1:6379> MGET a b

1) "900"

2) "1100"

通过multi命令开启事务,multi 命令不能嵌套使用,如果已经开启了事务的情况下,再执行 multi 命令,会提示如下错误:

(error) ERR MULTI calls can not be nested

multi 执行后,客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被 执行, 而是被放到一个队列中, 当 exec 命令被调用时, 所有队列中的命令才会被执 行。通过 exec 的命令执行事务。如果没有执行 exec,所有的命令都不会被执行。 如果中途不想执行事务了,怎么办? 可以调用 discard 可以清空事务队列,放弃执行。

watch命令

该命令可以用来实现乐观锁的一个行为。我们可以用watch监视一个或多个key,如果在开启事务后,有一个key在被监视,当执行事务exec之前该key值被其他的更改,那么整个事务都会被取消。

> watch k

OK

> multi

OK

> set k v2

QUEUED

> exec

(nil)

> get k

"v"

从以上命令可以看出,如果 exec 返回的结果是 nil 时,表示 watch 监控的对象在事务执行的过程中被其他修改了。从 get k 的结果也可以看出,在事务中设置的值 set k v2 并未正常执行。如果是正常执行应该返回的是 OK。

有个问题要注意, watch 命令只能在客户端开启事务之前执行,在事务中执行 watch 命令会引发错误,但不会造成整个事务失败,如下代码所示:

> multi

OK

> set k v3

QUEUED

> watch k

(error) ERR WATCH inside MULTI is not allowed

> exec

1) OK

> get k

"v3"

事务可能遇到的问题

在这呢,可以把事务执行过程中遇到的问题,分为两种。在执行exec之前发生错误

比如:入队的命令存在语法错误,包括参数数量,参数名等等(编译器错误)。

127.0.0.1:6379> multi

OK

127.0.0.1:6379> set gupao 666

QUEUED

127.0.0.1:6379> hset qingshan 2673

(error) ERR wrong number of arguments for 'hset' command

127.0.0.1:6379> exec

(error) EXECABORT Transaction discarded because of previous errors.

在这种情况下事务会被拒绝执行,也就是队列中所有的命令都不会得到执行。在执行exec之后发生错误

比如,类型错误,比如对 String 使用了 Hash 的命令,这是一种运行时错误。

127.0.0.1:6379> flushall

OK

127.0.0.1:6379> multi

OK

127.0.0.1:6379> set k1 1

QUEUED

127.0.0.1:6379> hset k1 a b

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 k1

"1"

最后我们发现 set k1 1 的命令是成功的,也就是在这种发生了运行时异常的情况下, 只有错误的命令没有被执行,但是其他命令没有受到影响。 这个显然不符合我们对原子性的定义,也就是我们没办法用 Redis 的这种事务机制来实现原子性,保证数据的一致。Redis事务深入解析和WATCH使用_数据库_weixin_40663800的博客-CSDN博客​blog.csdn.net

Lua脚本

Lua脚本,是一种轻量级的脚本语言。设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua脚本的应用也很多,比如Nginx+Lua实现的OpenResty,Redis+Lua配合使用(Redisson中大量使用了Lua脚本)。

在redis中调用lua脚本

使用 eval /ɪ'væl/ 方法,语法格式:

redis> eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....]

eval 代表执行 Lua 语言的命令。

lua-script 代表 Lua 语言脚本内容。

key-num 表示参数中有多少个 key,需要注意的是 Redis 中 key 是从 1 开始的,如果没有 key 的参数,那么写 0。

[key1 key2 key3…]是 key 作为参数传递给 Lua 语言,也可以不填,但是需要和 key-num 的个数对应起来。

[value1 value2 value3 ….]这些参数传递给 Lua 语言,它们是可填可不填的。

例如:返回一个字符串,0个参数:

redis> eval "return 'Hello World'" 0

在lua中调用redis命令

使用 redis.call(command, key [param1, param2…])进行操作。语法格式:

redis> eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value

command 是命令,包括 set、get、del 等。

key 是被操作的键。

param1,param2…代表给 key 的参数。

注意跟 Java 不一样,定义只有形参,调用只有实参。

Lua 是在调用时用 key 表示形参,argv 表示参数值(实参)。

设置键值对

在 Redis 中调用 Lua 脚本执行 Redis 命令

redis> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 test test

redis> get test

以上命令等价于 set test test。

在 redis-cli 中直接写 Lua 脚本不够方便,也不能实现编辑和复用,通常我们会把脚

本放在文件里面,然后执行这个文件。

在redis中调用lua脚本

创建 Lua 脚本文件:

cd /usr/local/soft/redis5.0.5/src

vim test.lua

Lua 脚本内容,先设置,再取值:

redis.call('set','test','lua666')

return redis.call('get','test')

在 Redis 客户端中调用 Lua 脚本

cd /usr/local/soft/redis5.0.5/src

redis-cli --eval test.lua 0

得到返回值:

[root@localhost src]# redis-cli --eval test.lua 0

"lua666"

Lua脚本具有以下好处:

1、减少网络开销:Lua脚本在执行的时候,是先发送到Redis服务器的,然后在服务器上执行脚本。

多个命令和业务逻辑都封装到脚本里,一次性提交到服务器。

2、原子性操作:我们都知道redis在执行命令时是单线程的,但是每个命令之间就存在并发的情况,

就存在先查询再操作时,两个命令没办法保证线程安全。但使用Lua脚本时,redis把这个脚本操作当成是一个命令,那么这个脚本中的多条操作也就保证了原子性。(注意:只保证原子性,不是事务)

虽然Lua脚本有这么多优点,但是也不能乱用,使用的时候要注意:

1、Lua脚本可以在redis单机模式、主从模式、Sentinel集群模式下正常使用,

但是无法在分片集群模式下使用。(脚本操作的key可能不在同一个分片)。

(其实集群模式不支持问题也是可以解决的,在使用spring的RedisTemplate执行lua脚本时,

报错EvalSha is not supported in cluster environment,不支持cluster。

但是redis是支持lua脚本的,只要拿到原redis的connection对象,通过connection去执行即可,

在后面会说下这个问题)

2、Lua脚本中尽量避免使用循环操作(可能引发死循环问题),尽量避免长时间运行。

3、redis在执行lua脚本时,默认最长运行时间时5秒,当脚本运行时间超过这一限制后,

Redis将开始接受其他命令但不会执行(以确保脚本的原子性,因为此时脚本并没有被终止),

而是会返回“BUSY”错误。Redis实现库存扣减操作_数据库_weixin_40663800的博客-CSDN博客​blog.csdn.net

redis为什么这么快?

1.redis到底有多快?

我们也想知道redis到底有多快呢?其实redis自己是有提供工具可以测试一下滴。可以看下redis给的官方文档:https://redis.io/topics/benchmarks​redis.io

Redis包含的redis-benchmark实用程序可模拟N个客户端同时发送M个总查询的运行命令根据官方的数据,Redis 的 QPS 可以达到 10 万左右(每秒请求数)。

执行命令: redis-benchmark -t set,lpush -n 100000 -q

结果(本地虚拟机):

SET: 49529.47 requests per second —— 每秒钟处理 5 万多次 set 请求

LPUSH: 49333.99 requests per second —— 每秒钟处理 5 万多次 lpush 请求

2.为什么这么快?

内存:kv结构的内存数据库,时间复杂度O(1)

单线程:没有多线程带来的上下文切换、竞争等问题

异步非阻塞:异步非阻塞 I/O,多路复用处理并发连接。

3.为什么塞单线程的?

因为单线程够用了,官方也是这么说的,CPU并不是使用Redis的瓶颈,

因为通常Redis要么受内存限制,要么受网络限制。

例如,使用在一般Linux系统上运行的流水线Redis每秒可以发送一百万个请求,

因此,如果您的应用程序主要使用O(N)或O(log(N))命令,则几乎不会使用过多的CPU 。https://redis.io/topics/faq#redis-is-single-threaded-how-can-i-exploit-multiple-cpu--cores​redis.io

4.单线程为什么这么快?

1:因为单线程,避免了多线程的线程切换,就是上下文切换问题,其实这是耗资源的一个操作

2:I/O 多路复用,I/O 指的是网络 I/O。 多路指的是多个 TCP 连接(Socket 或 Channel)。

复用指的是复用一个或多个线程。

内存淘汰和过期策略

过期策略

Redis 中同时使用了惰性过期和定期过期两种过期策略。

1:定时过期(主动):

每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。

该策略可以立即清除过期的数据,对内存很友好;

但是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

2:惰性过期(被动):

只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。

该策略可以最大化地节省 CPU 资源,却对内存非常不友好。

极端情况可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存。

例如 String,在 getCommand 里面

会调用 expireIfNeeded server.c expireIfNeeded(redisDb *db, robj *key)

第二种情况,每次写入 key 时,发现内存不够,

调用 activeExpireCycle 释放一部分内存。expire.c activeExpireCycle(int type)

3:定期过期:

每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key。

该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,

可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。

内存淘汰机制

如果内存满了,那该怎么办呢?所以就有了内存淘汰机制

最大内存设置

首先redis可以设置最大最大内存设置,redis.conf 参数配置:

# maxmemory

如果不设置 maxmemory 或者设置为 0,64 位系统不限制内存,32 位系统最多使 用 3GB 内存。

也可命令行动态修改: redis> config set maxmemory 2GB

到达最大内存以后怎么办?提供内存淘汰机制,一共提供了八种内存淘汰机制

配置:redis.conf

# maxmemory-policy noeviction

(1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。

(2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。

(3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。

(4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。

(5)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰

(6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。

(7)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

(8) no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,

新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据

不被丢失。

这八种大体上可以分为4中,lru、lfu、random、ttl。

持久化机制

RDB

RDB方式,是将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。

触发:

1:手动触发,save, shutdown, slave 命令会触发这个操作。

粒度比较大,如果save, shutdown, slave 之前crash了,则中间的操作没办法恢复。

2:配置规则触发,在配置文件中redis.conf中

save 900 1 # 900 秒内至少有一个 key 被修改(包括添加)

save 300 10 # 400 秒内至少有 10 个 key 被修改

save 60 10000 # 60 秒内至少有 10000 个 key 被修改

# 以上满足任何一个都会触发

# 文件路径,

dir ./

# 文件名称

dbfilename dump.rdb

# 是否是 LZF 压缩 rdb 文件

rdbcompression yes

# 开启数据校验

rdbchecksum yes

过程:

fork一个进程,遍历hash table,利用copy on write,把整个db dump保存下来。

备份时:redis在进行数据持久化的过程中,创建子进程,会先将数据写入到一个临时文件中,待持久化过程都结束了,

才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总

是完整可用的。

优点:对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,

这样就确保了redis极高的性能。

恢复数据时:如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式

更加的高效。而且RDB,就是一份数据文件,恢复的时候,直接加载到内存中即可,速度很快

虽然RDB有不少优点,但它的缺点也是不容忽视的。如果你对数据的完整性非常敏感,那么RDB方式就不太适合你,

因为即使你每5分钟都持久化一次,当redis故障时,仍然会有近5分钟的数据丢失。

所以,redis还提供了另一种持久化方式,那就是AOF。

AOF

AOF,英文是Append Only File,即只允许追加不允许改写的文件。

如前面介绍的,AOF方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍,

就这么简单。我们通过配置redis.conf中的appendonly yes就可以打开AOF功能。

如果有写操作(如SET等),redis就会被追加到AOF文件的末尾。。

触发:自动触发

过程

默认的AOF持久化策略是每秒钟fsync一次(fsync是指把缓存中的写指令记录到磁盘中),

因为在这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。

日志修复:如果在追加日志时,恰好遇到磁盘空间满、inode满或断电等情况导致日志写入不完整,也没有关系,

redis提供了redis-check-aof工具,可以用来进行日志修复。

重写:因为采用了追加方式,如果不做任何处理的话,AOF文件会变得越来越大,

为此,redis提供了AOF文件重写(rewrite)机制,即当AOF文件的大小超过所设定的阈值时,

redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。

举个例子或许更形象,假如我们调用了100次INCR指令,在AOF文件中就要存储100条指令,

但这明显是很低效的,完全可以把这100条指令合并成一条SET指令,这就是重写机制的原理。

在进行AOF重写时,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影

响AOF文件的可用性,这点大家可以放心。

虽然优点多多,但AOF方式也同样存在缺陷,比如在同样数据规模的情况下,AOF文件要比RDB文件的体积大。

而且,AOF方式的恢复速度也要慢于RDB方式。因为RDB是备份的数据,AOF是备份的写指令,需要重写执行4-redis持久化:持久化机制RDB与AOF对于生产环境中的灾难恢复_数据库_weixin_40663800的博客-CSDN博客​blog.csdn.net5-redis持久化:RDB持久化配置以及数据恢复实验_数据库_weixin_40663800的博客-CSDN博客​blog.csdn.net6-redis持久化:AOF持久化深入讲解各种操作和相关实验_weixin_40663800的博客-CSDN博客​blog.csdn.net

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值