Skip to content

三种特殊数据结构、事务

更新: 2025/2/24 字数: 0 字 时长: 0 分钟

三种特殊数据结构

Redis除了前面五种常用的数据结构,还有三种特殊的数据结构,它们同样可以应用在生活的方方面面。

Geospatial位置

**Redis在3.2版本就推出了用来存储和推算地理位置信息的,两地之间的距离geospatial数据类型,简称Geo。**应用场景:例如朋友定位、附近的人、计算打车距离等。

添加位置

Redis中添加地理位置的命令如下:

# geoadd 键:值 经度 纬度 定位名称
# 有效的经度从-180度到180度,有效的维度从-85.05度到85.05度

# 添加北京的经度纬度
geoadd china:city 116.40 39.90 beijing
# 添加上海、重庆、深圳、杭州、西安的经度纬度
geoadd china:city 121.47 31.23 shanghai 106.50 29.53 chongqing 114.05 22.52 shengzheng 120.16 30.24 hangzhou 108.96 34.26 xian

!> 注意:使用该命令添加经度纬度超出有效值范围会报错。

QQ截图20220715160703

获取位置

Redis中获取地理位置的命令如下:

# geopos 键:值 定位名称

# 获取北京的地理位置信息
geopos china:city beijing
# 获取重庆、西安的地理位置信息
geopos china:city chongqing xian

QQ截图20220715161330

相对距离

利用两个经度纬度计算出两地之间的直线距离可以使用如下命令:

# geodist 键:值 定位名称1 定位名称2 距离单位
# 距离单位 m:单位米;km单位千米;mi单位英里;ft单位英尺

# 计算北京到上海的直线距离,单位千米
geodist china:city beijing shanghai km

QQ截图20220715162605

QQ截图20220715162952

附近的人

以某一个经度纬度为中心,筛选出半径范围的地理位置信息:

# 以经度纬度为中心
georadius 键:值 经度 纬度 半径 距离单位

# 筛选以经度110纬度30为中心,半径1000千米内的城市
georadius china:city 110 30 1000 km
# 筛选以经度110纬度30为中心,半径500千米内的城市以及直线距离
georadius china:city 110 30 500 km withdist
# 筛选以经度110纬度30为中心,半径500千米内的城市以及城市的经度纬度
georadius china:city 110 30 500 km withcoord
# 筛选以经度110纬度30为中心,半径500千米内的城市以及城市的经度纬度,结果显示1个
georadius china:city 110 30 500 km withcoord count 1

QQ截图20220715164008

以某一个定位名称为中心,筛选出半径范围的地理位置信息:

# 以经度纬度为中心
georadiusbymember 键:值 定位名称 半径 距离单位

# 以北京为中心,半径1000千米的城市
georadiusbymember china:city beijing 1000 km

QQ截图20220715164613

删除位置

Redis并没提供官方的删除位置的命令,但是Geo的底层原理就是Zset数据类型,我们可以使用Zset命令来操作geo!

# 查询所有城市
zrange china:city 0 -1

# 删除北京的地理信息
zrem china:city beijing

QQ截图20220715165321

Hyperloglog基数

在Reids中Hyperloglog数据结构主要用于基数统计的算法(去重统计),而且占内存是固定的,存储2^64不同元素的基数,只需要12KB内存。

# 添加元素
pfadd 键 元素1 元素2 元素3 ...

# 统计元素个数
pfcount 键

# 将键1、键2合并为键3
pfmerge 键3 键1 键2

QQ截图20220715171601

Bitmaps位图

**在Redis中有一种Bitmaps位图数据结构,专门用于统计两个状态的信息,底层是操作二进制位来记录,只有0和1两个状态。**例如:感染、未感染;登录、未登录;打卡、未打卡...

设置状态

Bitmaps设置状态:

setbit 键 位置 状态

# 记录一周的打卡,0代表未打卡,1代表打卡
# 周一打卡
setbit sign 0 1
# 周二打卡
setbit sign 1 1
# 周三打卡
setbit sign 2 1
# 周四打卡
setbit sign 3 1
# 周五打卡
setbit sign 4 1
# 周六未打卡
setbit sign 5 0
# 周日未打卡
setbit sign 6 0

QQ截图20220715173822

查看状态

Bitmaps查看状态:

# 查看周四是否打卡
getbit sign 3
# 查看周日是否打卡
getbit sign 6

QQ截图20220715174015

统计次数

Bitmaps统计次数:

# 统计一周的打卡次数
bitcount sign

QQ截图20220715174142

事务

基本操作

**简单地说,事务表示一组被序列化的命令,在事务执行过程中,会按照顺序执行。**因此具有三个特点:

  • 一次性:整个事务只会执行一次;
  • 顺序性:事务会按照顺序一条条的执行命令;
  • 排他性:执行事务过程中,不会执行事务外的命令。

执行事务

Redis提供了简单的事务功能,它分为三个部分:

  • 开始事务(multi
  • 命令队列(需要执行的命令)
  • 命令队列结束,开始执行事务(exec

可以看到下面 sadd 命令此时的返回结果是QUEUED,代表命令并没有真正执行,而是暂时保存在队列中,只有当 exec 执行后,两条命令才开始执行:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED
127.0.0.1:6379> exec 
1) (integer) 1
2) (integer) 1 
127.0.0.1:6379> sismember user:a:follow user:b 
(integer) 1

QQ截图20220718171736

放弃事务

如果要停止事务的执行,可以使用 discard 命令代替 exec 命令即可。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED
127.0.0.1:6379> discard 
OK
127.0.0.1:6379> sismember user:a:follow user:b 
(integer) 0

QQ截图20220718171843

异常事务

在Redis中单条命令是保证原子性的,但是事务不保证原子性。

  • 语法错误(例如执行不存在的命令),整个事务都会无法执行;
  • 语法正确,命令错误(例如给字符串加1),则只有错误命令则会抛出异常,则其他命令正常执行。

!> 注意:Redis不支持回滚功能。

监控

两种锁

在Redis中有两种类型的锁:乐观锁、悲观锁。

悲观锁:很悲观,担心任何时候都会出现问题,无论做什么都会加锁,而且很影响程序的性能。(很少使用)

乐观锁:很乐观,认为任何时候都不会出现问题,所以不会上锁,只在更新数据的时候去判断一下在此期间是否修改过这个数据,判断的依据是基于数据版本(version)的记录机制实现的。(常用)

实现乐观锁

在Redis中实现乐观锁很简单,只需一个watch命令就能实现:

watch 键
单线程事务

QQ截图20220718173110

多线程事务

第一个线程:设置money值,并进行监控,且设置好事务,但不执行。

QQ截图20220718173557

第二个线程:重新设置money值。

QQ截图20220718173909

返回第一个线程:现在再执行事务,发现返回一个空。

QQ截图20220718174058

出现上述现象的原因就是:在第一个线程事务建立但还未执行的过程当中,处于监控的money值被第二个线程重新设置了,当第一个线程再来执行该事务的时候发现money值被改变了,就拒绝了执行该事务。如果还想再次执行该事务,就需要对money值重新监控,因为 execdiscardunwatch 命令都会清除事务中的监控,然后再重新添加事务中的命令,再通过 exec 命令执行事务。