redis基础(6.0)数据结构、事务、常用组件等

news2025/1/20 11:01:29

1 概述

1.1 redis介绍

Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。Redis 以其超高的性能、完美的文档、 简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评。国内外很多大型互联网 公司都在使用 Redis,比如 Twitter、YouPorn、暴雪娱乐、Github、StackOverflow、腾讯、 阿里、京东、华为、新浪微博等等,很多中小型公司也都有应用。

Redis 是一个开源的 key-value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括 string (字符串)list (链表)set (集合)zset (sorted set –有序集合)hash(哈希类型)。这些数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。

在此基础上,Redis 支持各种不同方式的排序。与 memcached 一样,为了保证效率,数据都是缓存在内存中。区别的是 Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。并且在此基础上实现了 master-slave (主从) 同步。

1.2 redis应用场景

为什么使用?

  • 解决应用服务器的CPU和内存压力
  • 减少IO的读操作,减轻IO的压力
  • 关系型数据库的扩展性不强,难以改变表结构

1.2.1 应用:缓存

Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,使用Redis用在缓存的场合非常多。合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。

1.2.2 应用:计数器

诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能;

命令:INCRBY

1.2.3 应用:队列

redis设计用来做缓存的,但是由于它自身的某种特性使得它可以用来做消息队列,它有几个阻塞式的API可以使用,正是这些阻塞式的API让其有能力做消息队列;另外,做消息队列的其他特性例如FIFO(先入先出)也很容易实现,只需要一个list对象从头取数据,从尾部塞数据即可;redis能做消息队列还得益于其list对象blpop brpop接口以及Pub/Sub(发布/订阅)的某些接口,它们都是阻塞版的,所以可以用来做消息队列。

如果对于数据一致性要求高的话还是用RocketMQ等专业系统。

1.2.4 应用:分布式锁与单线程机制

Redis单线程的特性,用来做高性能的分布式锁,秒杀场景等

1.2.5 应用:自动过期能力

Redis针对数据都可以设置过期时间,这个特点也是大家应用比较多的,过期的数据清理无需使用方去关注,所以开发效率也比较高,当然,性能也比较高。最常见的就是:短信验证码、具有时间性的商品展示等。无需像数据库还要去查时间进行对比。

1.2.6 应用:位操作(大数据处理)

用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。

想想一下腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,你能怎么做?这里要用到位操作——使用setbit、getbit、bitcount命令。

redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示我们上面例子里面的用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,上面我说的几个场景也就能够实现

1.2.7 应用:排行榜

谁得分高谁排名往上。命令:ZADD(有续集,sorted set)

1.2.8 应用:用数据库来算附近的人

Redis 在 3.2 版本以后增加了地理位置 GEO 模块,意味着我们可以使用 Redis 来实现 摩拜单车「附近的 Mobike」、美团和饿了么「附近的餐馆」这样的功能了。

业界比较通用的地理位置距离排序算法是 GeoHash 算法,Redis 也使用 GeoHash 算 法。

2 redis安装

1、https://redis.io/download/ 下载对应版本的redis,解压缩tar -zxvf redis-6.2.7.tar.gz

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gSrA1bu-1681570135804)(assets/image-20221208160304260.png)]

2、检查环境

检查gcc环境 gcc -v,如果命令无效,安装gcc环境:sudo apt install gcc

检查make环境,如果无效:sudo apt install make

lizheng@lz-x:~/redis-6.2.7$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.3.0-10ubuntu2' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)

3、make & make install

cd /redis-6.2.7
make

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQeVbvPq-1681570135805)(assets/image-20221208161310148.png)]

make install

在这里插入图片描述

4、启动:redis-server
在这里插入图片描述

3 数据类型

3.1 字符串(String)

String 是 Redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。

String 类型是二进制安全的。意味着 Redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。

String 类型是 Redis 最基本的数据类型,一个 Redis 中字符串 value 最多可以是 512M

在这里插入图片描述

Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字 符串实际分配的空间 capacity 一般要高于实际字符串长度 len。

当字符串长度小于 1M 时, 扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是 字符串最大长度为 512M

# 键值对
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
# 批量操作
127.0.0.1:6379> mset id 1 name zhangsan age 12
OK
127.0.0.1:6379> mget id name age
1) "1"
2) "zhangsan"
3) "12"
# 过期和 set 命令扩展
127.0.0.1:6379> expire name 5 #过期5秒
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> setex name 5 lisi #过期5秒
OK
127.0.0.1:6379> get name
"lisi"
127.0.0.1:6379> get name
(nil)
# 计数
127.0.0.1:6379> set age 10
OK
127.0.0.1:6379> incr age
(integer) 11
127.0.0.1:6379> incrby age 2
(integer) 13
127.0.0.1:6379> get age
"13"
127.0.0.1:6379>

3.2 列表(List)

Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)

实际上 Redis 底层存储的还不是一个简单的 linkedlist,而是称之为 快速链表 quicklist 的一个结构。

在这里插入图片描述

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是 压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的 时候才会改成 quicklist

因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且 会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的 指针 prevnext 。将多个 ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

右边进左边出:队列

127.0.0.1:6379> rpush books java golang python js
(integer) 4
127.0.0.1:6379> llen books
(integer) 4
127.0.0.1:6379> lpop books
"java"
127.0.0.1:6379> lpop books
"golang"
127.0.0.1:6379> lpop books
"python"
127.0.0.1:6379> lpop books
"js"
127.0.0.1:6379> lpop books
(nil)
127.0.0.1:6379> llen books
(integer) 0
127.0.0.1:6379>

右边进右边出:栈

127.0.0.1:6379> rpush books java golang python js
(integer) 4
127.0.0.1:6379> rpop books
"js"
127.0.0.1:6379> rpop books
"python"
127.0.0.1:6379> rpop books 3
1) "golang"
2) "java"
127.0.0.1:6379> llen books
(integer) 0
127.0.0.1:6379>

Lindex 慢操作

Lindex 命令用于通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

127.0.0.1:6379> rpush books java golang python js
(integer) 4
127.0.0.1:6379> lindex books -1
"js"
127.0.0.1:6379> lindex books -2
"python"
127.0.0.1:6379> lindex books 2
"python"
127.0.0.1:6379> lindex books 0
"java"
127.0.0.1:6379> lindex books 100
(nil)
127.0.0.1:6379>

3.3 哈希字典(hash)

Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
在这里插入图片描述

127.0.0.1:6379> hset hkey name "zhangsan" age 12 doub 11.22
(integer) 3
127.0.0.1:6379> hmget hkey
(error) ERR wrong number of arguments for 'hmget' command
127.0.0.1:6379> hmget hkey name age doub
1) "zhangsan"
2) "12"
3) "11.22"
127.0.0.1:6379> hgetall hkey
1) "name"
2) "zhangsan"
3) "age"
4) "12"
5) "doub"
6) "11.22"

同字符串一样,hash 结构中的单个子 key 也可以进行计数,它对应的指令是 hincrby, 和 incr 使用基本一样。

3.4 集合(set)

Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的 内部实现相当于一个特殊的hash字典,字典中所有的 value 都是一个值 NULL

当需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。

127.0.0.1:6379> sadd setkey java
(integer) 1
127.0.0.1:6379> sadd setkey java
(integer) 0
127.0.0.1:6379> sadd setkey redis
(integer) 1
127.0.0.1:6379> sadd setkey python go
(integer) 2
127.0.0.1:6379> smembers setkey
1) "redis"
2) "java"
3) "go"
4) "python"
127.0.0.1:6379> sismember setkey java
(integer) 1
127.0.0.1:6379> sismember setkey java1
(integer) 0

3.5 有序集合(zset)

类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着跳跃列表的数据结构。

zset 可以用来存 粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间 进行排序。 zset 还可以用来存储学生的成绩,value 值是学生的 ID,score 是他的考试成绩。我们 可以对成绩按分数进行排序就可以得到他的名次

127.0.0.1:6379> zadd books 9.0 java
(integer) 1
127.0.0.1:6379> zadd books 7.0 go
(integer) 1
127.0.0.1:6379> zadd books 8.8 js
127.0.0.1:6379> zrange books 1 -1 #排序输出
1) "go"
2) "js"
3) "java"
4) "mysql"
127.0.0.1:6379> zrevrange books 0 -1 #逆排序输出
1) "mysql"
2) "java"
3) "js"
4) "go"
127.0.0.1:6379> zrangebyscore books 8 9 #根据分值筛选
1) "js"
2) "java"

跳跃列表

zset 内部的排序功能是通过「跳跃列表」数据结构来实现的,它的结构非常特殊,也比较复杂。 因为 zset 要支持随机的插入和删除,所以它不好使用数组来表示。我们先看一个普通的 链表结构
在这里插入图片描述

我们需要这个链表按照 score 值进行排序。这意味着当有新元素需要插入时,要定位到特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表地址不连续效率低,于是引入了跳跃列表

跳跃列表是一种数据结构。它允许快速查询一个有序连续元素的数据链表,而其快速查询是通过维护一个多层次的链表,且每一层链表中的元素是前一层链表元素的子集。

在这里插入图片描述

我们把一些节点从有序表中提取出来,缓存一级索引,就组成了下面这样的结构:

在这里插入图片描述

同样地,一级索引也可以往上再提取一层,组成二级索引,如下:

在这里插入图片描述

如果我们再查找17这个元素呢?只需要经过6、15、17这几个元素就可以找到17了。

这基本上就是跳表的核心思想了,其实这也是一个“空间换时间”的算法,通过向上提取索引增加了查找的效率。

跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。 首先 0 层肯定是 100% 了,1 层只有 50% 的概率,2 层只有 25% 的概率,3 层只有 12.5% 的概率,以此类推,绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概率就会越大。

3.6 位图(Bitmaps)

Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)。由于bit是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。位图(bitmap)同样属于 string 数据类型。Redis 中一个字符串类型的值最多能存储 512 MB 的内容,每个字符串由多个字节组成,每个字节又由 8 个 Bit 位组成。位图结构正是使用“位”来实现存储的,它通过将比特位设置为 0 或 1来达到数据存取的目的,这大大增加了 value 存储数量,它存储上限为2^32

比如记录用户一年内签到的次数,签了是 1,没签是 0。如果使用 key-value 来存储,那么每个用户都要记录 365 次,当用户成百上亿时,需要的存储空间将非常巨大。Redis 提供位图结构,可以很好的解决。

位图本质上就是一个普通的字节串,也就是 bytes 数组。您可以使用getbit/setbit命令来处理这个位数组,位图的结构如下所示:

在这里插入图片描述

127.0.0.1:6379> setbit bitkey 0 1
(integer) 0
127.0.0.1:6379> setbit bitkey 1 0
(integer) 0
127.0.0.1:6379> setbit bitkey 2 1
(integer) 0
127.0.0.1:6379> setbit bitkey 3 1
(integer) 0
127.0.0.1:6379> setbit bitkey 4 1
(integer) 0
127.0.0.1:6379> setbit bitkey 5 0
(integer) 0
127.0.0.1:6379> getbit bitkey 4
(integer) 1
127.0.0.1:6379> get bitkey
"\xb8"
127.0.0.1:6379> bitcount bitkey
(integer) 4

3.7 基数统计(HyperLogLog)

基数统计(Cardinality Counting) 通常是用来统计一个集合中不重复的元素个数。

思考这样的一个场景: 如果你负责开发维护一个大型的网站,有一天老板找产品经理要网站上每个网页的 UV(独立访客,每个用户每天只记录一次),然后让你来开发这个统计模块,你会如何实现?

解决基数问题有很多种方案:

  • 数据存储在 MySQL 表中,使用 distinct count 计算不重复个数。

  • 使用 Redis 提供的 hash、set、bitmaps 等数据结构来处理。

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。能否能够降低一定的精度来平衡存储空间?Redis 推出了 HyperLogLog

  1. Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

  2. 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

  3. 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?

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

HyperLogLog 是大数据基数统计中的常见方法,无论是 Redis,Spark 还是 Flink 都提供了这个功能,其目的就是在一定的误差范围内,用最小的空间复杂度来估算一个数据流的基数

HyperLogLog 算法简要思路是通过一个 hash 函数把数据流映射到 {0,1}∞ ,也就是说用二进制来表示数据流中的元素。每一个数据流中的元素x都对应这一个0,1序列

在介绍HyperLogLog之前考虑一个场景:在一个抛硬币的场景下,假设硬币的正面对应着 1 硬币的反面对应着 0,依次置出0,0,0,1的概率是多少?通过计算可以得到 1/2^4 = 1/16,那就相当于平均置16次,才会获得 0,0,0,1 这个序列,反之如果出现 0,0,0,1 这个序列说明起码置了16次硬币。

对于大批量随机0,1序列,可以根据第一出现1的位置,来估算这批0,1序列的个数,例如:

  • 出现序列1 X X X X意味着不可重复元素估计 2^1 = 2 个
  • 出现序列0 1 X X X意味着不可重复元素估计 2^2 = 4 个
  • 出现序列0 0 1 X X意味着不可重复元素估计 2^3 = 8 个
  • 出现序列0 0 0 1 X意味着不可重复元素估计 2^4 = 16 个
127.0.0.1:6379> pfadd hllkey redis java
(integer) 1
127.0.0.1:6379> pfadd hllkey redis
(integer) 0
127.0.0.1:6379> pfadd hllkey redis
(integer) 0
127.0.0.1:6379> pfadd hllkey go
(integer) 1
127.0.0.1:6379> pfadd hllkey go
(integer) 0
127.0.0.1:6379> pfadd hllkey js
(integer) 1
127.0.0.1:6379> pfcount hllkey
(integer) 4

3.8 位置(Geospatial)

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

127.0.0.1:6379> geoadd geokey 13.361389 38.115556 Palermo 15.087269 37.502669 Catania
(integer) 2
127.0.0.1:6379> geodist geokey Palermo Catania
"166274.1516"
127.0.0.1:6379> georadius geokey 15 37 100m
(error) ERR wrong number of arguments for 'georadius' command
127.0.0.1:6379> georadius geokey 15 37 100 m
(empty array)
127.0.0.1:6379> georadius geokey 15 37 100 km
1) "Catania"
127.0.0.1:6379> georadius geokey 15 37 200 km
1) "Palermo"
2) "Catania"

3.9 容器型数据结构的通用规则

list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:

1、create if not exists 如果容器不存在,那就创建一个,再进行操作。比如 rpush 操作刚开始是没有列表的, Redis 就会自动创建一个,然后再 rpush 进去新元素。

2、drop if no elements 如果容器里元素没有了,那么立即删除元素,释放内存。这意味着 lpop 操作到最后一 个元素,列表就消失了。

4 redis 的发布订阅

4.1 什么是发布订阅

Redis 发布/订阅是一种消息传递模式,其中发送者(在Redis术语中称为发布者)发送消息,而接收者(订阅者)接收消息。传递消息的通道称为channel

在Redis中,客户端可以订阅任意数量的频道,下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
在这里插入图片描述

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

在这里插入图片描述

4.2 测试

订阅一个channel

127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1

向channel1发布消息

127.0.0.1:6379> publish channel1 hello
(integer) 1

4.3 发布订阅缺点

PubSub 的生产者传递过来一个消息,Redis 会直接找到相应的消费者传递过去。如果一个消费者都没有,那么消息直接丢弃。如果开始有三个消费者,一个消费者突然挂掉了,生产者会继续发送消息,另外两个消费者可以持续收到消息。但是挂掉的消费者重新连上的时候,这断连期间生产者发送的消息,对于这个消费者来说就是彻底丢失了。

正是因为 PubSub 有这些缺点,它几乎找不到合适的应用场景,Redis5.0 新增了 Stream 数据结构,这个功能给 Redis 带来了持久化消息队列。

5 Redis Stream

5.1 Stream概述

Redis Stream 是 Redis 5.0 版本新增加的数据结构。主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。且无法记录历史消息。

而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

每个 Stream 都有唯一的名称,它就是 Redis 的 key,在我们首次使用 xadd 指令追加消息时自动创建。Redis Stream 的结构如下所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容:

在这里插入图片描述

上图解析:

  • Consumer Group :消费组,使用 XGROUP CREATE 命令创建,一个消费组有多个消费者(Consumer)。
  • last_delivered_id :游标,每个消费组会有个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。
  • pending_ids :消费者(Consumer)的状态变量,作用是维护消费者的未确认的 id。 pending_ids 记录了当前已经被客户端读取的消息,但是还没有 ack (Acknowledge character:确认字符)。

5.2 Stream 消息太多怎么办

要是消息积累太多,Stream 的链表岂不是很长,内容会不会爆掉? xdel 指令又不会删除消息,它只是给消息做了个标志位。 Redis 自然考虑到了这一点,所以它提供了一个定长 Stream 功能。在 xadd 的指令提供 一个定长长度 maxlen,就可以将老的消息干掉,确保最多不超过指定长度。

5.3 Stream 的高可用

Stream 的高可用是建立主从复制基础上的,它和其它数据结构的复制机制没有区别,也就是说在 SentinelCluster 集群环境下 Stream 是可以支持高可用的。不过鉴于 Redis 的指令复制是异步的,在 failover 发生时,Redis 可能会丢失极小部分数据,这点 Redis 的其 它数据结构也是一样的。

6 事务(Transcation)

6.1 redis事务概述

Redis 事务不是严格意义上的事务,只是用于帮助用户在一个步骤中执行多个命令。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

Redis 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。也可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

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

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  1. 批量操作在发送 EXEC 命令前被放入队列缓存。
  2. 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  3. 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

6.2 事务的使用

一个事务从开始到执行会经历以下三个阶段:

  1. 开始事务。
  2. 命令入队。
  3. 执行事务。

MULTIEXECDISCARDWATCH 这四个指令构成了 redis 事务处理的基础。

  1. MULTI 用来组装一个事务;
  2. EXEC 用来执行一个事务;
  3. DISCARD 用来取消一个事务;
  4. WATCH 本身的作用是监视 key 是否被改动过,而且支持同时监视多个 key,只要还没真正触发事务,WATCH 都会尽职尽责的监视,一旦发现某个 key 被修改了,在执行 EXEC 时就会返回 nil,表示事务无法触发。

在这里插入图片描述

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name sz
QUEUED
127.0.0.1:6379(TX)> set age 10
QUEUED
127.0.0.1:6379(TX)> set address = 1
QUEUED
127.0.0.1:6379(TX)> set sex 0
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) (error) ERR syntax error # 不影响下面执行
4) OK
127.0.0.1:6379> get sex
"0"

watch

127.0.0.1:6379> watch good_num
OK
127.0.0.1:6379> set good_num 0
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set good_num 0
QUEUED
127.0.0.1:6379(TX)> exec #不会被执行
(nil)

7 Lua脚本

7.1 简介

Redis的单个命令都是原子性的,有时候我们希望能够组合多个Redis命令,并让这个组合也能够原子性的执行,甚至可以重复使用,在软件热更新中也有一席之地。Redis开发者意识到这种场景还是很普遍的,就在2.6版本中引入了一个特性来解决这个问题,这就是Redis执行Lua脚本。允许开发者使用Lua语言编写脚本传到Redis中执行。

Lua广泛作为其它语言的嵌入脚本,尤其是C/C++,语法简单,小巧,源码一共才200多K,这可能也是Redis官方选择它的原因。

使用脚本的好处如下:

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
  • 原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
  • 复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

7.2 Redis中Lua的常用命令

7.2.1 EVAL命令

Redis中使用EVAL命令来直接执行指定的Lua脚本。

EVAL luascript numkeys key [key ...] arg [arg ...]
  • EVAL 命令的关键字。
  • luascript Lua 脚本。
  • numkeys 指定的Lua脚本需要处理键的数量,其实就是 key数组的长度,必传。
  • key 传递给Lua脚本零到多个键,空格隔开,在Lua 脚本中通过 KEYS[INDEX]来获取对应的值,其中1 <= INDEX <= numkeys
  • arg是传递给脚本的零到多个附加参数,空格隔开,在Lua脚本中通过ARGV[INDEX]来获取对应的值,其中1 <= INDEX <= numkeys
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> EVAL "return redis.call('GET',KEYS[1])" 1 hello
"world"
127.0.0.1:6379> EVAL "return redis.call('GET','hello')"
(error) ERR wrong number of arguments for 'eval' command
127.0.0.1:6379> EVAL "return redis.call('GET','hello')" 0
"world"

7.2.2 call函数和pcall函数

在上面的例子中我们通过redis.call()来执行了一个GET命令,其实我们也可以替换为redis.pcall()。它们唯一的区别就在于处理错误的方式,前者执行命令错误时会向调用者直接返回一个错误;而后者则会将错误包装为一个Lua table表格:

7.3 Lua和Redis数据类型转换

当Lua脚本使用call()或pcall()调用Redis命令时,Redis返回值将转换为Lua数据类型。同样,在调用Redis命令和Lua脚本返回值时,Lua数据类型将转换为Redis协议类型,以便脚本可以控制EVAL返回给客户端的内容。

数据类型之间的转换原则是,如果将Redis类型转换为Lua类型,然后将结果转换回Redis类型,则结果与初始值相同。

换句话说,Lua和Redis类型之间存在一对一的转换。下表显示了所有转换规则:

Redis to Lua 转换对应表

Redis integer reply -> Lua number

Redis bulk reply -> Lua string

Redis multi bulk reply -> Lua table (may have other Redis data types nested)

Redis status reply -> Lua table with a single ok field containing the status

Redis error reply -> Lua table with a single err field containing the error

Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type

Lua to Redis 转换对应表

Lua number -> Redis integer reply (the number is converted into an integer)

Lua string -> Redis bulk reply

Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
Lua table with a single ok field -> Redis status reply

Lua table with a single err field -> Redis error reply

Lua boolean false -> Redis Nil bulk reply.
Lua boolean true -> Redis integer reply with value of 1
127.0.0.1:6379> eval "return 3.14" 0
(integer) 3 # 丢失精度
127.0.0.1:6379> eval "return tostring(3.14)" 0
"3.14"

7.4 脚本原子性

Redis使用相同的Lua解释器来运行所有命令。另外,Redis保证以原子方式执行脚本:执行脚本时不会执行其他脚本或Redis命令。与 MULTI/EXEC 事务的概念相似。从所有其他客户端的角度来看,脚本要不已经执行完成,要不根本不执行。

然而运行一个缓慢的脚本就是一个很愚蠢的主意。创建快速执行的脚本并不难,因为脚本开销非常低。但是,如果您要使用了执行缓慢的脚本,由于其的原子性,其他客户端的命令都是得不到执行的,这并不是我们想要的结果,大家要切记。

7.5 脚本管理

7.5.1 SCRIPT LOAD

加载脚本到缓存以达到重复使用,避免多次加载浪费带宽,每一个脚本都会通过SHA校验返回唯一字符串标识。需要配合EVALSHA命令来执行缓存后的脚本。

127.0.0.1:6379> SCRIPT LOAD "return 'hello'"
"1b936e3fe509bcbc9cd0664897bbe8fd0cac101b"
127.0.0.1:6379> EVALSHA 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b 0
"hello"

7.5.2 SCRIPT FLUSH

既然有缓存就有清除缓存,但是遗憾的是并没有根据SHA来删除脚本缓存,而是清除所有的脚本缓存,所以在生产中一般不会再生产过程中使用该命令。

127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b
1) (integer) 0

7.5.3 SCRIPT EXISTS

以SHA标识为参数检查一个或者多个缓存是否存在。

127.0.0.1:6379> script exists 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b
1) (integer) 0

7.5.4 SCRIPT KILL

终止正在执行的脚本。但是为了数据的完整性此命令并不能保证一定能终止成功。如果当一个脚本执行了一部分写的逻辑而需要被终止时,该命令是不凑效的。需要执行SHUTDOWN nosave在不对数据执行持久化的情况下终止服务器来完成终止脚本。

127.0.0.1:6379> SCRIPT LOAD “return ‘hello’”
“1b936e3fe509bcbc9cd0664897bbe8fd0cac101b”
127.0.0.1:6379> EVALSHA 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b 0
“hello”


### 7.5.2 SCRIPT FLUSH

既然有缓存就有`清除缓存`,但是遗憾的是并没有根据SHA来删除脚本缓存,而是`清除所有`的脚本缓存,所以在生产中一般不会再生产过程中使用该命令。

```bash
127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b
1) (integer) 0

7.5.3 SCRIPT EXISTS

以SHA标识为参数检查一个或者多个缓存是否存在。

127.0.0.1:6379> script exists 1b936e3fe509bcbc9cd0664897bbe8fd0cac101b
1) (integer) 0

7.5.4 SCRIPT KILL

终止正在执行的脚本。但是为了数据的完整性此命令并不能保证一定能终止成功。如果当一个脚本执行了一部分写的逻辑而需要被终止时,该命令是不凑效的。需要执行SHUTDOWN nosave在不对数据执行持久化的情况下终止服务器来完成终止脚本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/414595.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为手表开发:WATCH 3 Pro(16)传感器订阅气压

华为手表开发&#xff1a;WATCH 3 Pro&#xff08;16&#xff09;传感器订阅气压初环境与设备气压传感器介绍与说明鸿蒙开发文件夹&#xff1a;文件新增展示的文本标记index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发 环境与设备 …

【目标检测】YOLOv5:修改自己的网络结构

前言 YOLOv5就像一座金矿&#xff0c;里面有无数可以学习的东西。之前的博文一直将YOLOv5当作一个黑盒使用&#xff0c;只考虑模型的输入和输出&#xff0c;以此来对模型进行二次开发。 本篇博文将更近一层&#xff0c;深入到“金矿”内部&#xff0c;来尝试对模型结构进行替换…

高并发浅析

什么是高并发 高并发指通过设计保证系统能够同时并行处理很多请求&#xff0c;是分布式系统非常重要的概念 评价分布式系统性能的指标有&#xff1a; 响应时间&#xff1a;系统对请求做出响应的时间。吞吐量&#xff1a;单位时间内处理的请求数量。QPS&#xff08;和吞吐量基…

C++标准库 -- 顺序容器 (Primer C++ 第五版 · 阅读笔记)

C标准库 -- 顺序容器(Primer C 第五版 阅读笔记&#xff09;第9章 顺序容器------(持续更新)9.1、顺序容器概述9.2、容器库概览9.2.1 、迭代器9.2.2 、容器类型成员9.2.3 、begin 和 end 成员9.2.4 、容器定义和初始化9.2.5 、赋值和 swap9.2.6 、容器大小操作9.2.7 、关系运算…

电脑0x0000001A蓝屏错误怎么U盘重装系统教学

电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug&#xff0c;只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢&#xff1f;来看看以下的详细操作方法教学吧。 准备工作&…

ThinkPad-L480电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板ThinkPad-L480 处理器Intel Core i7-8550U已驱动 内存16GB DDR4 2400Mhz已驱动 硬盘Intel 760p 512GB已驱动 显卡Intel UHD 620已驱动 声卡瑞昱…

【探花交友】day06—即时通信

目录 1、即时通信 1.1、什么是即时通信&#xff1f;​编辑 1.2、功能说明 1.3、技术方案 2、环信 2.1、开发简介 2.2、环信Console 2.3、接口说明 3、抽取环信组件 3.1、编写HuanXinTemplate 3.2、编写Properties对象 3.3、配置 3.4、测试 4、用户体系集成 4.1、…

力扣刷题第一天:剑指 Offer 18. 删除链表的节点、LC206.反转链表

目录 零、前言 剑指 Offer 18. 删除链表的节点 一、题目描述 二、解题思路 三、完整代码 LC206.反转链表 一、题目描述 二、解题思路 三、完整代码 零、前言 这篇文章主要讲解两道链表相关的题目&#xff0c;分别是剑指 Offer 18和LC206。链表作为数据结构中重要的一…

Vue学习——【第五弹】

前言 上一篇文章 Vue学习——【第四弹】 中学到了数据代理&#xff0c;这篇文章接着学习 Vue中的事件处理。 事件处理 我们在学习JavaScript时就经常接触事件处理&#xff0c;比如在进行表单、按钮、列表折叠等操作时&#xff0c;我们就经常用到 click&#xff08;点击&…

Redis源码之Hash表实现

通常我们如果要设计一个 Hash 表&#xff0c;那么我们需要考虑这几个问题&#xff1a; 有没有并发操作Hash冲突如何解决以什么样的方式扩容对 Redis 来说&#xff0c;首先它是单线程的工作模式&#xff0c;所以不需要考虑并发问题。 想实现一个性能优异的 Hash 表&#xff0c…

ubuntu快速安装VMware Tools(全屏用的)

VMware Tools实现主机和虚拟机的文件共享。 第一步 打开VMware Workstation,启动ubuntu系统。 点击主界面的&#xff08;虚拟机&#xff09;——点击&#xff08;安装VMware Tools&#xff09;。 弹出提示框点击是——等待自动下载完成。 第二步 将安装包复制到桌面&#x…

ESP32 web WiFi 管理器esp32-wifi-manager

拓 2023/04/09-2022/04/11 1. 简介 github仓库 https://github.com/tonyp7/esp32-wifi-manager 说明 esp32-wifi-manager是esp32的纯C esp-idf组件&#xff0c;可通过门户网站轻松管理wifi网络。 esp32-wifi-manager是一个集所有功能于一身的wifi扫描仪、http服务器和dns守…

2022年第十三届蓝桥杯题解(全)C/C++

A题就是一个简单的进制转化&#xff0c;代码实现如下&#xff1a; #include <bits/stdc.h>using namespace std;const int N 1e5 10;int main() {int x 2022;int a 1;int res 0;while(x) {res (x % 10) * a;a a * 9;x / 10;}cout << res;return 0; } B题有…

【论文阅读笔记】COFFEE: A Contrastive Oracle-Free Framework for Event Extraction

论文题目&#xff1a;COFFEE: A Contrastive Oracle-Free Framework for Event Extraction 论文来源&#xff1a; 论文链接&#xff1a;https://arxiv.org/pdf/2303.14452.pdf 代码链接&#xff1a; 0 摘要 事件抽取是一项复杂的信息抽取任务&#xff0c;它涉及到从非结构…

【AIGC】7、CLIP | OpenAI 出品使用 4 亿样本训练的图文匹配模型

文章目录一、背景二、方法2.1 使用自然语言来监督训练2.2 建立一个超大数据集2.3 选择预训练模型2.4 模型缩放和选择三、效果论文&#xff1a;Learning Transferable Visual Models From Natural Language Supervision 代码&#xff1a;https://github.com/OpenAI/CLIP 官网&…

DJ3-5 死锁概述

目录 3.5 死锁概述 3.5.2 计算机系统中的死锁 1. 竞争资源 2. 进程推进顺序不当 3.5.3 死锁的必要条件和处理方法 1. 死锁的必要条件 2. 处理死锁的方法 3.6 预防死锁 3.6.1 摒弃 “请求和保持” 条件 3.6.2 摒弃 “不剥夺” 条件 3.6.3 摒弃 “环路等待” 条…

企业在数字化建设中,BI 处于什么位置?

对市场异常敏感的商业世界自然不会放过获取数字经济的机会&#xff0c;在众多企业开始进行数字化转型&#xff0c;通过信息化建设&#xff0c;部署BI来完成转型工作。 很多人都听说过BI&#xff0c; 但是并不太清楚BI 在IT信息化中到底处于一个什么位置&#xff1f;有很多的疑…

APIs --- DOM基础事件

1. 事件 事件是编程时系统内发生的动作或者发生的事情&#xff0c;它是用来描述程序的行为或状态的&#xff0c;一旦行为或状态发生改变&#xff0c;便立即调用一个函数。 例如&#xff1a;用户使用【鼠标点击】网页中的一个按钮、用户使用【鼠标拖拽】网页中的一张图片 事件…

【MySQL】外键约束和外键策略

一、什么是外键约束&#xff1f; 外键约束&#xff08;FOREIGN KEY&#xff0c;缩写FK&#xff09;是用来实现数据库表的参照完整性的。外键约束可以使两张表紧密的结合起来&#xff0c;特别是针对修改或者删除的级联操作时&#xff0c;会保证数据的完整性。 外键是指表…

ElasticSearch——详细看看ES集群的启动流程

参考&#xff1a;一起看看ES集群的启动流程 本文主要从流程上介绍整个集群是如何启动的&#xff0c;集群状态如何从Red变成Green&#xff0c;然后分析其他模块的流程。 这里的集群启动过程指集群完全重启时的启动过程&#xff0c;期间要经历选举主节点、主分片、数据恢复等重…