Redis7新特性简介及十大数据类型

Redis是基于内存的K-V键值对内存数据库

浅谈Redis7新特性

  1. 主要是自身底层性能和资源利用率上的提高和优化。

  2. 多AOF文件支持

  3. config命令增强

  4. 限制客户端内存使用

  5. listpack紧凑列表调整

  6. 访问安全性增强

  7. Redis Functions(要抢Lua脚本的饭碗)

  8. RDB保存时间调整,保存规则发生变化。

  9. 命令新增和变动

Redis服务与客户端日常操作

redis.conf配置文件,改完后确保生效,记得重启

  1. 后台启动:默认daemonize no 改为 daemonize yes

  2. 关闭保护模式:默认protected-mode yes 改为 protected-mode no

  3. 注释掉bind 127.0.0.1 直接注释掉这行(默认bind 127.0.0.1只能本机访问)或改成本机IP地址,否则影响远程IP连接

  4. 添加redis密码:打开注释并把requirepass foobared 改为 requirepass + 你自己设置的密码

指定配置文件并启动服务:redis-server /myredis/redis7.conf

查看一下是否启动成功:ps -ef |grep redis|grep -v grep

连接客户端 redis-cli -a 111111 -p 6379

Linux中关闭redis服务

  • 单实例关闭

redis-cli -a 111111 shutdown

  • 多实例关闭指定端口

redis-cli -p 6379 shutdown

redis客户端中关闭redis服务

直接 shutdown

Redis十大数据类型

这里说的数据类型都是指value,key的数据类型都是字符串!

Redis键(key)的常用命令

  • keys * 查看当前库所有的key

  • exists key 判断某个key是否存在

  • type key 查看key是什么数据类型

  • del key 删除指定key的数据

  • unlink key 非阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续异步中操作

  • ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期

  • expire key 为给定的key设置过期时间[EXPIRE-秒;PEXPIRE-毫秒;EXPIREAT-秒(时间戳)]

  • move key dbindex[0-15] 将当前数据库的key移动到给定的数据库db中

  • select dbindex 切换数据库[0-15],默认为0

  • dbsize 查看当前数据库key的数量

  • flushdb 清空当前库

  • flushall 通杀全部库

各类型常用命令

  • Redis命令是不区分大小写的,而key是区分大小写的

  • 永远的帮助命令help @类型 help@string help@list .....

String

最常用

复制代码
GET key
复制代码
SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]

可选参数

  • NX:键不存在时设置

  • XX:键存在的时候设置

  • GET :返回指定键原本的值,若不存在时返回nil

  • EX:以秒为单位设置过期时间

  • PX:以毫秒为单位设置过期时间

  • EXAT:设置以秒为单位的UNIX时间戳所对应的时间为过期时间

  • PXAT:设置以毫秒为单位的UNIX时间戳所对应的时间为过期时间

  • KEEPTTL:保留设置前指定键的过期时间

    SET命令使用EX、PX、NX参数的效果等同于SETEX、PSETEX、SETNX命令(根据官方文档描述,未来版本中SETEX、PSETEX、SETNX命令可能会被淘汰)

返回值

OK:设置成功;

nil:未执行SET命令,如不满足NX、XX条件等。

若使用GET参数,则返回该键原来的值,或在键不存在时返回nil。

如何获得设置指定的Key过期的Unix时间,单位为秒

复制代码
System.out.println(Long.toString(System.currentTimeMillis()/1000L));

同时设置/获取多个键值

MSET key value [k1 v1 k2 v2 k3 v3 .....]

MGET key [k1 k2 k3 ......]

MSETNX key value [k1 v1 k2 v2 k3 v3 ......]

注意:msetnx中但凡有一个key是存在的,那么整个msetnx将不会执行(返回0)

设定/获取指定区间范围内的值

setrange/getrange

getrange:类似 between...and 的关系

0到-1表示全部

setrange:设置指定偏移位置的值

数值增减(一定要是数字才能加减)

INCR key 递增数值

INCRBY key increment 增加指定的整数

DECR key 递减数值

DECRBY key decrement 减少指定的整数

获得字符串长度和追加

STRLEN key 获取字符串长度

APPEND key value 追加字符串

分布式锁

复制代码
set key value [EX seconds][PX milliseconds] [NX|XX]

getset(先get再set)

getset 将给定key的值设为value,并返回 key 的旧value

list

一个双端链表的结构,容量是n的32次方减1,大概40多亿,主要功能有push/pop等,一般用在栈、队列、消息队列等场景。

left、right都可以插入添加;

如果键不存在,创建新的链表;

如果键已存在,新增内容;

如果值全移除,对应的键也就消失了。

它的底层实际上是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

lpush、rpush、lrange()

lpush:先进后出

rpush:先进先出

lrange:遍历

lpop、rpop

lpop:弹出第一个元素

rpop:弹出最后一个元素

lindex

按照下标获得元素

llen

获取list中元素个数

Irem key count element

删除count个element元素

ltrim key start stop

截取指定范围的值后再赋给key

rpoplpush 源列表 目的列表

把源列表中的最后一个元素放到目的列表的第一个元素位置

lset key index value

给list中的指定索引设置一个新的value

linsert key before/after 已有值 新的值

在已有值前面/后面插入一个新的值

应用场景:

命令 场景
lpush likearticle:userid 微信关注的公众号只要发布了新文章,就会放进我的list中
lrange likearticle:userid 0 9 查看我订阅的全部文章,类似分页,下面0-9就是一次显示10条

hash

类似于Map<String,Map<Object,Object>>,String是key;Map<Object,Object>是value

HSET / HGET

设置/获取

HMSET / HMGET

设置/批量获取字段(目前版本hset与hmset作用完全相同

HGETALL

获取指定hash的所有字段

HDEL

删除指定hash的指定字段

HLEN

获取某个key内的全部数量

HEXISTS key field

key中是否存在field字段

HKEY /HVALS key

得到key中所有的键/值

HINCRBY key field increment(整数)

key中的field字段增加一个整数

HINCRBYFLOAT key field increment(小数)

key中的field字段增加一个小数

HSETNX

添加字段,若未存在添加成功;已存在,添加失败。

应用场景:购物车

set

无序、不可重复的集合

SADD key member[member...]

添加元素

SMEMBERS key

遍历集合中所有元素

SISMEMBER key member[member...]

判断元素是否在集合中

SREM key member [member....]

删除元素

SCARD key

获取集合里的元素个数

srandmember key

SRANDMEMBER key [count]

从集合中随机展现出count个元素(元素不删除)

SPOP key [count]

从集合中随机弹出count个元素,弹一个删一个

SMOVE key1 key2 member

把key1中的member元素移动到key2

集合运算

SDIFF key1 key2

差集 ,属于key1集合但不属于key2集合的元素构成的集合(key1-key2)

SUNION key1 key2

并集,属于key1集合或者属于key2集合的元素合并后的集合(A ∪ B)

SINTER key1 key2

交集,属于key1集合同时也属于key2集合的共同拥有的元素构成的集合

SINTERCARD numkeys key [key...] [LIMIT limit]

返回结果集的基数。返回由所有给定集合的交集产生的集合的基数

应用场景:

微信抽奖小程序

命令 场景
SADD key userid 用户ID,立即参与按钮
SCARD key 显示已经有多少人参与了,上图23208人参加
SRANDMEMBER key2 随机抽奖2个人,元素不删除
SPOP key 3 随机抽奖3个人,元素会删除

微信朋友圈点赞查看同赞朋友

命令 场景
sadd pub:msgID 点赞用户ID1 点赞用户ID2 新增点赞
srem pub:msgID 点赞用户ID 取消点赞
SMEMBERS pub:msgID 展现所有点赞过的用户
scard pub:msgID 点赞用户数统计,就是常见的点赞红色数字
SISMEMBER pub:msgID 用户ID 判断某个朋友是否对楼主点赞过

QQ推送可能认识的人

SDIFF user1 user2

zset

在set基础上,每个val值前加一个score分数值;

set是 key v1 v2 v3...

zset是 key score1 v1 score2 v2 score3 v3

ZADD key score member[score member]

添加元素

ZRANGE key start stop [WITHSCORES]

按照元素分数从小到大的顺序,返回索引从start到stop之间的所有元素(0 ,-1 代表全部)

加上WITHSCORES 表示带上分数

ZREVRANGE

按照元素分数从大到小 的顺序,返回索引从start到stop之间的所有元素(0 ,-1 代表全部)

加上WITHSCORES 表示带上分数

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

获取指定分数范围的元素;

加上(:不包含

LIMIT作用是限制返回 limit 开始下标步 多少步

ZSCORE key member

获取元素的分数

ZCARD KEY

获取集合中元素的数量

ZREM key member [member...]

删除元素

ZINCRBY key increment member

增加某个元素的分数

ZCOUNT key min max

获得指定分数范围内的元素个数

ZMPOP numkeys key [key...] MIN/MAX [COUNT count]

从键名列表中的第一个非空排序集中弹出一个或多个元素,他们是成员分数对

ZRANK key member

获得指定元素的下标值

ZREVRANK key member

逆序 获得下标值

应用场景:

思路:定义商品销售排行榜(soreted set集合),key 为 goods:sellsort,分数为商品销售数量

商品编号1001的销量是9,商品编号1002的销量是15, zadd goods:sellsort 9 1001 15 1002

有一个客户又买了2件商品1001,商品编号1001销量加2 zindrby goods:sellsort 2 1001

求商品销量前10名 zrange goods:sellsort 0 9 withscores

bitmap(位图)

说明:用String类型作为底层数据结构实现的一种统计二值状态的数据类型

位图本质是数组,它是基于String 数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们称之为一个索引)。

Bitmap支持的最大位数 是23位,它可以极大的节约存储空间,使用512M内存就可以存储多达42.9亿的字节信息(232 = 4294967296)

概述:由0和1状态表现的二进制位的bit数组

通过需求理解其作用:

  • 用户是否登录过,比如京东每日签到领京豆

  • 电影、广告是否被点击过

  • 钉钉打卡上下班、签到统计

  • .......

SETBIT key offset value

setbit 键 值(只能0/1)

bitmap的偏移量是从0开始算的

GETBIT key offset

获取位图的值

STRLEN key

获取位图占用的字节数,不是根据字符串长度计算的!凡是超过8位(0-7)后都是按照8位一组(1 byte)再扩容的!

BITCOUNT key

当前位图中含有1的有多少

BITOP operation destkey key [key...]

operation:AND / OR / NOT / XOR

案例一:

某个网站的用户有1000万,做个用户id和位置的映射

0号位对应用户id:uid:055d-ddf

1号位对应用户id:uid:dfd5-05d

......

签到了代表1,没签到代表0

1号签到人数:4

2号签到人数:2

连续两天都完成签到的用户有2个

案例二:

sign:u1:202401 表示u1用户2024年1月签到信息

查看该用户3号和31号是否签到:

查看该用户2024年1月总计签到的天数:

一年365天,全年天天签到占用多少字节?46

按年去存储一个用户的签到情况,365 天只需要 365 / 8 ≈ 46 Byte,1000W 用户量一年也只需要 44 MB 就足够了。

假如是亿级的系统,

每天使用1个1亿位的Bitmap约占12MB的内存(108/8/1024/1024),10天的Bitmap的内存开销约为120MB,内存压力不算太高。

此外,在实际使用时,最好对Bitmap设置过期时间 ,让Redis自动删除不再需要的签到记录以节省内存开销。

HyperLogLog(基数统计)

看需求:

统计某个网站、文章的的UV(Unique Visitor)、一般理解为客户端IP,需要去重的。

用户搜索网站关键词的数量;统计用户每天搜索不同词条的个数

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

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

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

案例Case:

全集 I = {2,4 ,6,8 ,11,22,44,4 ,8 ,10} 去掉重复的内容后: 基数 = {2,4,6,8,11,22,44,10} = 8

基数统计:用于统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算

总结:去重脱水后的真实数据

PFADD key element [element...]

添加指定元素到 HyperLogLog 中

PFCOUNT key [key...]

返回给定HyperLogLog 的基数估算值。

PFMERGE destkey sourcekey [sourcekey...]

将多个HyperLogLog合并为一个HyperLogLog

GEO(地理空间)

移动互联网时代LBS(Location Based Service)应用越来越多,交友软件中附近的人、外卖软件中附近的美食店铺、高德地图附近的核酸检查点等等,那这种附近各种形形色色的XXX地址位置选择是如何实现的?

地球上的地理位置是使用二维的经纬度表示,经度范围 (-180, 180],纬度范围 (-90, 90],只要我们确定一个点的经纬度就可以名取得他在地球的位置。

例如滴滴打车,最直观的操作就是实时记录更新各个车的位置,然后当我们要找车时,在数据库中查找距离我们(坐标x0,y0)附近r公里范围内部的车辆

使用如下伪SQL即可:

select taxi from position where x0-r < x < x0+r and y0-r < y < y0+r

但是这样会有什么问题呢?

1.查询性能问题,如果并发高,数据量大这种查询是要搞垮数据库的

2.这个查询的是一个矩形访问,而不是以我为中心r公里为半径的圆形访问。

3.精准度的问题,我们知道地球不是平面坐标系,而是一个圆球,这种矩形计算在长距离计算时会有很大误差

核心思想就是将球体转换为平面,将平面中每一个区块转换为一点

主要分为三步:

  1. 将三维的地球变为二维的坐标

  2. 再将二维的坐标转换为一维的点块

  3. 最后将一维的点块转换为二进制再通过base32 编码

GEOADD key longitude latitude member [longitude latitude member ...]

geoadd用于存储指定的地理空间位置,可以将一个或多个经纬度和该经纬度的位置名称添加到指定key中

longitude :经度

latitude:纬度

member:位置名称

GEOPOS key member [member ...]

用于从给定的key里返回所有指定名称(member)的经纬度,不存在的返回null

解决中文乱码

--raw

GEOHASH key member [member ...]

返回坐标的geohash表示

GEODIST key member1 member2 [M|KM|FT|MI]

两个位置之间的距离;m 米 km 千米 ft 英尺 mi 英里

GEORADIUS key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]

以给定的经纬度为中心,返回键包含的位置元素当中,与中心的距离不超过给定最大距离的所有位置元素

当前位置:116.418017 39.914402

WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。

WITHCOORD: 将位置元素的经度和维度也一并返回。

WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大

COUNT 限定返回的记录数。

GEORADIUSBYMEMBER key member

根据位置名称找出位于指定范围内的元素,与GEORADIUS 类似

Stream

Redis5之前的痛点:

Redis消息队列的2种方案:

  1. List实现消息队列

按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

所以常用来做异步队列 使用,将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

List实现方式其实就是点对点 的方式,它对于一对多 的情况力不从心。

LPUSH、RPOP 左进右出 RPUSH、LPOP 右进左出

  1. 发布订阅(Pub/Sub)

Redis发布订阅有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。而且也没有 Ack 机制来保证数据的可靠性,假设一个消费者都没有,那消息就直接被丢弃了。

Redis 5.0版本新增了一个更强大的数据结构-----Stream -> 一句话:Redis版的MQ消息中间件+阻塞队列

Stream 能干嘛

实现消息队列,它支持消息持久化、支持自动生成全局唯一ID、支持ack确认消息的模式、支持消费组模式等,让消息队列更加稳定和可靠

Stream的结构

一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的ID和对应的内容。

名词 解释
Message Content 消息内容
Consumer group 消费组,通过XGROUP CREATE 命令创建,同一个消费组可以有多个消费者
Last_delivered_id 游标,每个消费组会有个游标 last_delivered_id,任意一个消费者读取了消息都会使游标 last_delivered_id 往前移动。
Consumer 消费者,消费组中的消费者
Pending_ids 消费者会有一个状态变量,用于记录被当前消费已读取但未ack的消息Id,如果客户端没有ack,这个变量里面的消息ID会越来越多,一旦某个消息被ack它就开始减少。这个pending_ids变量在Redis官方被称之为 PEL(Pending Entries List),记录了当前已经被客户端读取的消息,但是还没有 ack (Acknowledge character:确认字符),它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理

特殊符号:

符号 解释
- + 最小和最大可能出现的id
$ $表示只消费新的消息,当前流中最大的id,可用于将要到来的消息
> 用于XREADGROUP命令,表示迄今为止还没有发送给组中使用者的消息,会更新消费者组的最后ID
* 用于XADD命令中,让系统自动生成id

队列相关指令:

XADD

XADD 用于向Stream 队列中添加消息,如果指定的Stream 队列不存在,则该命令执行时会新建一个Stream 队列

* 号表示服务器自动生成 MessageID(类似mysql里面主键auto_increment),后面顺序跟着一堆 业务key/value

  1. 信息条目指的是序列号,在相同的毫秒下序列号从0开始递增,序列号是64位长度,理论上在同一毫秒内生成的数据量无法到达这个级别,因此不用担心序列号会不够用。millisecondsTime指的是Redis节点服务器的本地时间,如果存在当前的毫秒时间戳比以前已经存在的数据的时间戳小的话(本地时间钟后跳),那么系统将会采用以前相同的毫秒创建新的ID,也即redis 在增加信息条目时会检查当前 id 与上一条目的 id, 自动纠正错误的情况,一定要保证后面的 id 比前面大,一个流中信息条目的ID必须是单调增的,这是流的基础。

  2. 客户端显示传入规则:Redis对于ID有强制要求,格式必须是时间戳-自增Id这样的方式,且后续ID不能小于前一个ID

  3. Stream的消息内容,也就是图中的Message Content它的结构类似Hash结构,以key-value的形式存在。

XRANGE

遍历

XREVRANGE

反转遍历

XDEL

删除

XLEN

获取Stream队列的消息的长度

XTRIM key MAXLEN| MINID [LIMIT count]

用于对Stream的长度进行截取

MAXLEN:允许的最大长度,对流进行修剪限制长度

MINID:允许的最小id,从某个id值开始比该id值小的将会被抛弃

MAXLEN

截取后:

MINID

XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key...] id [id...]

用于获取消息(阻塞/非阻塞),只会返回大于指定id的消息

COUNT 最多读取多少条消息

BLOCK 是否以阻塞的方式读取消息,默认不阻塞,如果 milliseconds设置为0,表示永远阻塞。

  • $代表特殊ID,表示以当前Stream已经存储的最大的ID作为最后一个ID,当前Stream中不存在大于当前最大ID的消息,因此此时返回nil

  • 0-0代表从最小的ID开始获取Stream中的消息,当不指定count,将会返回Stream中的所有消息,注意也可以使用0(00/000也都是可以的......)

非阻塞式:

阻塞式:

案例需要两个Redis客户端

第一个客户端输入阻塞获取消息的指令,此时客户端进入阻塞状态

直到第二个客户端执行完成添加消息指令,第一个客户端才可获取到消息从而退出阻塞状态

Stream的基础方法,使用xadd存入消息和xread循环阻塞读取消息的方式可以实现简易版的消息队列,交互流程如下

对比List结构

消费组相关指令:

XGROUP CREATE key groupname

创建消费组

$表示从Stream尾部开始消费

0表示从Stream头部开始消费

创建消费者组的时候必须指定 ID, ID 为 0 表示从头开始消费,为 $ 表示只消费新的消息,队尾新来

XREADGROUP GROUP

读取消息

">" 表示从第一条尚未被消费的消息开始读取

消费组groupA内的消费者

stream中的消息一旦被消费组里的一个消费者读取了,就不能再被同组的其他消费者读取了,即同一个消费组里的消费者不能消费同一条消息:

但是,不同消费组的消费者可以消费同一条消息:

but,please thinking why we need 消费组?

让组内的多个消费者共同分担读取消息,所以,我们通常会让每个消费者读取部分消息,从而实现消息读取负载在多个消费者间是均衡分布的

基于Stream实现的消息队列,如何保证消费者在发生故障或宕机再次重启后,仍然可以读取未处理完的消息?

  1. Streams 会自动使用内部队列(也称为 PENDING List )留存消费组里每个消费者读取的消息保底措施,直到消费者使用 XACK 命令通知 Streams"消息已经处理完成"。

  2. 消费确认增加了消息的可靠性,一般在业务处理完成之后,需要执行 XACK 命令确认消息已经被消费完成

XPENDING

查询每个消费组内所有消费者【已读取、但尚未确认 】的消息

一旦某条消息被某个消费者处理了该消费者就可以使用 XACK 命令通知 Streams,然后这条消息就会被删除

XACK key group id

向消息队列确认消息处理完成

XINFO STREAM key

用于打印Stream/Consumer/Group的详细信息

Stream 使用建议:

Stream还是不能100%等价于Kafka、RabbitMQ来使用的,生产案例少,慎用

仅代表本人愚见,非权威!

bitfield 位域(了解)

bitfield命令可以将一个Redis字符串看作是一个由二进制位组成的数组,并对这个数组中任意偏移进行访问。可以使用命令对一个有符号的5位整型数的第1234位设置指定值,也可以对一个31位无符号整型数的第4567位进行取值。类似地,本命令可以对指定的整数进行自增和自减操作,可配置的上溢和下溢处理操作。

bitfield命令可以在一次调用中同时对多个位范围进行操作:它接受一系列待执行的操作作为参数,并返回一个数组,数组中的每个元素就是对应操作的执行结果。

用途:

BITFIELD命令的作用在于它能够将很多小的整数储存到一个长度较大的位图中,又或者将一个非常庞大的键分割为多个较小的键来进行储存,从而非常高效地使用内存,使得Redis能够得到更多不同的应用------特别是在实时分析领域:BITFIELD能够以指定的方式对计算溢出进行控制的能力,使得它可以被应用于这一领域。

当需要一个整型时,有符号整型需在位数前加 i ,无符号在位数前加 u 。例如,u8是一个8位的无符号整型,i16是一个16位的有符号整型。

GET type offset

返回指定的位域

hello 等价于 0110100001100101011011000110110001101111;

SET type offset value

设置指定位域的值并返回它的原值

溢出控制:

  • WRAP(默认):使用回绕(wrap around)方法处理有符号整数和无符号整数的溢出情况

  • SAT:使用饱和计算(saturation arithmetic)方法处理溢出,下溢计算的结果为最小的整数值,而上溢计算的结果为最大的整数值。

  • FAIL:命令将拒绝执行那些会导致上溢或者下溢情况出现的计算,并向用户返回空值表示计算未被执行。

INCRBY type offset increment

WRAP

SAT

FAIL

十大数据类型结束啦!