上篇我们说完了 Redis 的五大基本数据类型,下面接着说它的特殊类型
目录
Redis 的三种特殊数据类型
Geospatial(地理位置)
官方文档:https://www.redis.net.cn/order/3685.html
geospatial 是 Reids 在 3.2 版本中推出的功能,这个功能可以获取某一城市的经纬度,两地之间的距离,限定范围内的城市等等。
它所用之处在我们生活中很常见,比如:朋友的定位,附近的人,打车距离计算,geospatial 都可以实现。
它的命令非常简单,只有 6 个命令
首先,我们需要将指定的地理空间位置(纬度、经度、名称)添加到指定的key中,注意:
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
- 当坐标位置超出上述指定范围时,该命令将会返回一个错误。
127.0.0.1:6379> geoadd china:city 116.40 39.93 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1
有了城市经纬度数据后,我们来写测试代码
1、根据城市名获取它的经纬度
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.39999896287918"
2) "39.930001051312857"
127.0.0.1:6379> GEOPOS china:city beijing chongqing
1) 1) "116.39999896287918"
2) "39.930001051312857"
2) 1) "106.49999767541885"
2) "29.529999579006592"
2、获取两地之间的直线距离
,可以规定单位:m、km
127.0.0.1:6379> GEODIST china:city beijing shanghai
"1070354.2012"
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1070.3542"
127.0.0.1:6379> GEODIST china:city shenzhen shanghai
"1215922.3698"
127.0.0.1:6379> GEODIST china:city shenzhen shanghai km
"1215.9224"
3、自定义经纬度,获取这个经纬度某一范围内的城市,这个功能可以用来查找自己附近的人
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
1) "chongqing"
2) "xi`an"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
# 获取出的城市,带上到我们自定义经纬度的距离
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
3) 1) "shenzhen"
2) "924.6408"
4) 1) "hangzhou"
2) "977.5143"
# 获取出的城市,带上获取城市的经纬度
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord
1) 1) "chongqing"
2) 1) "106.49999767541885"
2) "29.529999579006592"
2) 1) "xian"
2) 1) "108.96000176668167"
2) "34.2599996441893"
3) 1) "shenzhen"
2) 1) "114.04999762773514"
2) "22.520000087950386"
4) 1) "hangzhou"
2) 1) "120.16000002622604"
2) "30.240000322949022"
# 获取出的城市,带上获取城市的经纬度和到我们自定义经纬度的距离
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord withdist
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885"
2) "29.529999579006592"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167"
2) "34.2599996441893"
3) 1) "shenzhen"
2) "924.6408"
3) 1) "114.04999762773514"
2) "22.520000087950386"
4) 1) "hangzhou"
2) "977.5143"
3) 1) "120.16000002622604"
2) "30.240000322949022"
# 获取出的城市,带上获取城市的经纬度和到我们自定义经纬度的距离并限定查询出的城市个数
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord withdist count 1
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885"
2) "29.529999579006592"
以某个城市为中心,获取该城市某一范围内的城市
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
geohash,将某一城市的经纬度转换成一个 11 位的字符串,字符串越接近表示两个城市越近
127.0.0.1:6379> GEOHASH china:city beijing
1) "wx4g0tpche0"
127.0.0.1:6379> GEOHASH china:city beijing shanghai
1) "wx4g0tpche0"
2) "wtw3sj5zbj0"
geo 的底层实现原理是 Zset,我们可以使用 Zset 操作 geo
127.0.0.1:6379> ZRANGE china:city 0 -1 # 查看地图中的所有元素
1) "chongqing"
2) "xi`an"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city hangzhou # 移除地图中的某一元素
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "shanghai"
5) "beijing"
Hyperloglog(基数统计)
hyperloglog 是用来做基数统计的,什么是基数?
给定两个集合,A{1,2,3,4,5,1},B{4,5,6,7},基数就是没有重复的元素的个数,A 集合的基数为 5,B 集合的基数为 4
hyperloglog 优点:占用固定的内存且很小,2^64 不同的元素的基数,只需要占 12KB内存,
作用:记录网页被访问的次数。一个人访问一个网站多次,但是还是算作一个人!我们的目的如果仅仅是为了计数,就可以采用 hyperloglog,但是 hyperloglog 有 0.81% 错误率,如果允许容错,那么 hyperloglog 用在这里是非常便利的
127.0.0.1:6379> pfadd mykey a b c d e f g h i j # 创建一个集合
(integer) 1
127.0.0.1:6379> pfadd mykey2 i j k l m n o p # 创建另一个集合
(integer) 1
127.0.0.1:6379> pfcount mykey # 获取第一个集合的基基数
(integer) 10
127.0.0.1:6379> pfcount mykey2 # 获取第二个集合的基基数
(integer) 8
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 # 合并连个集合,做基数统计
OK
127.0.0.1:6379> pfcount mykey3 # 统计出来的个数
(integer) 16
Bitmap(位存储)
Bitmap 是位存储,只有 0 ,1两位。可以用来表示状态。
例如,用 Bitmap 来存储一周上班的打卡情况,0 表示未打卡,1 表示已打卡
周一(0):1(已打卡),周二(1):1(已打卡),周三(2):0(未打卡)
周四(3):1(已打卡),周五(4):1(已打卡),周六(5):0(未打卡)
周日(6):0(未打卡)
127.0.0.1:6379> SETBIT sign 0 1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 1
(integer) 0
127.0.0.1:6379> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379> SETBIT sign 6 0
(integer) 0
查看某天是否打卡
127.0.0.1:6379> GETBIT sign 0
(integer) 1 # 周一已打卡
127.0.0.1:6379> GETBIT sign 2
(integer) 0 # 周三未打卡
统计打卡天数,判断是否全勤
127.0.0.1:6379> BITCOUNT sign
(integer) 4
Redis 事务
Redis 事务本质:是一组命令的集合, 一个事务中的所有命令都会被序列化,并没有直接被执行,只有发起执行命令的时候才会执行。
----------- 开启事务(multi)
命令1
命令2
----------- 执行事务(exec)
Redis 的事务没有隔离级别的概念
Redis 的单条命令是保证原子性的,但是事务不保证原子性
正常执行事务
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> DISCARD # 放弃事务
OK
127.0.0.1:6379> get k1 # 放弃事务后,命令没有执行,获取不出 k1 的值
(nil)
事务的编译期异常
事务的编译期异常,在代码编写时出现异常,导致事务总体不成功
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k2
(error) ERR wrong number of arguments for 'getset' command # 命令错误,报出异常
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors. # 事务执行异常
127.0.0.1:6379> get k3
(nil)
事务的运行期异常
事务的运行期异常,编写的代码符合逻辑,但是运行时出错,比如:给字符型数据 +1,这时,只有错误的命令执行失败,其余的命令依旧成功执行
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1 # 会执行失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range # 第一条命令执行失败
2) OK
127.0.0.1:6379> get k2 # 不影响第二条命令,第二条命令执行成功
"v2"
Redis实现乐观锁
Redis 监控的字段是 watch,可以用来做乐观锁。先解释一下乐观锁和悲观锁
悲观锁:认为什么时候都会出现问题,不论什么时候都加锁。
乐观锁:认为什么时候都不会出问题,所以不上锁,在更新数据的时候才去判断一下,在前一段时间是否有人修改了数据。
成功案例
127.0.0.1:6379> set money 100 # 现有的钱
OK
127.0.0.1:6379> set out 0 # 花出去的钱
OK
127.0.0.1:6379> watch money # 监视钱
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> decrby money 20 # 命令1
QUEUED
127.0.0.1:6379> INCRBY out 20 # 命令2
QUEUED
127.0.0.1:6379> exec # 执行事务
1) (integer) 80
2) (integer) 20
失败案例
乐观锁的失败案例,当我们在执行事务前,开启另一个客户端,修改数据,然后再执行事务,事务会执行失败
第一个客户端
127.0.0.1:6379> set money 100 # 现有的钱
OK
127.0.0.1:6379> set out 0 # 花出去的钱
OK
127.0.0.1:6379> watch money # 监视钱
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec # 在这先不执行事务,开启另一个客户端,改变money的值
(nil) # 修改值后,执行事务,发现执行失败
另一个客户端
127.0.0.1:6379> set money 1000 # 另一个客户端,改变 money 的值
OK
解决方法
既然执行失败了,我们怎么办,解除监视,重新执行我们的事务即可,因为只要是能打断我们执行事务,并成功修改数据的,一般都是被允许的客户的操作
127.0.0.1:6379> unwatch # 解除监视
OK
127.0.0.1:6379> watch money # 重新监视
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> decrby money 30 # 命令
QUEUED
127.0.0.1:6379> incrby out 30
QUEUED
127.0.0.1:6379> exec # 执行事务
1) (integer) 970
2) (integer) 30
我们下篇再见…(∩_∩)~