一、技术全景
1.两大维度 & 三大主线
两大维度: 应用维度、底层原理维度;
三大主线:高性能、高可用、高扩展
- 高性能:包括线程模型、数据结构、持久化、网络框架;
- 高可靠:包括主从复制、哨兵机制;
- 高可扩:包括数据分片、负载均衡
2.应用场景
缓存
缓存机制几乎在所有的大型网站都有使用,合理地使用缓存不仅可以加快数据的访问速度,而且能够有效地降低后端数据源的压力。Redis提供了键值过期时间设置,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够为一个网站的稳定保驾护航。
一般MySQL数据库写的并发是600/s,读的2000/s,对于大型互联网项目的百万并发,根本扛不住,Redis的官方显示Redis能够单台达到10W+/s的并发。
排行榜系统
排行榜系统几乎存在于所有的网站,例如按照热度排名的排行榜,按照发布时间的排行榜,按照各种复杂维度计算出的排行榜,Redis提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统。
计数器应用
计数器在网站中的作用至关重要,例如视频网站有播放数、电商网站有浏览数,为了保证数据的实时性,每一次播放和浏览都要做加1的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择。
社交网络
赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适合保存这种类型的数据,Redis提供的数据结构可以相对比较容易地实现这些功能。
消息队列系统
消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务解耦、非实时业务削峰等特性。Redis提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足。这个是Redis的作者参考了Kafka做的拓展。
3.安装
版本号说明:版本号第二位为奇数,为非稳定版本,如2.7、2.9、3.1;版本号第二为偶数,为稳定版本如2.6、2.8、3.0;一般来说当前奇数版本是下一个稳定版本的开发版本,如2.9是3.0的开发版本
安装步骤及相关问题
wget https://download.redis.io/releases/redis-6.2.7.tar.gz
tar xzf redis-6.2.7.tar.gz
cd redis-6.2.7/
make
如果执行make命令报错:cc 未找到命令,原因是虚拟机系统中缺少gcc,执行下面命令安装gcc:
yum -y install gcc automake autoconf libtool make
如果执行make命令报错:致命错误:jemalloc/jemalloc.h: 没有那个文件或目录,则需要在make指定分配器为libc。执行下面命令即可正常编译:
make MALLOC=libc
redis启动默认配置、带参数启动、配置文件启动
在bin目录下直接输入 ./redis-server
./redis-server --port 6380
./redis-server ../conf/redis.conf
二、5种常用数据结构
字符串String
字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB
1.操作命令:
- set
set key value set test 111 --设置key=test,value=111 set test 111 ex 10 -- 设置该key过期时间为10s,可以通过 ttl key 来查看该key剩余过期时间 set test 111 px 10000 --设置key为test的数据过期时间为 10000毫秒 set test 111 nx --后面带nx,只有当test不存在的时候,才能设置成功(常用语分布式锁) set test 111 xx --后面带xx,只有当test存在的时候,才能设置成功,一般用于更新 setex key seconds value --设置key的过期时间为seconds,并且值为value setnx key value --只有当key不存在的时候才能设置成功,成功返回1,失败返回0 - get
get key --获取值,如果获取的键不存在,则返回nil - mset
mset a 1 b 2 c 3 --批量设置值,设置a=1,b=2,c=3 - mget
mget a b c d -- 批量获取键为a b c d 的vlue,不存在的返回nil - incr
incr key -- 返回结果分3种情况: 值不是整数,返回错误; 值是整数,返回自增后的结果; 键不存在,按照值为0自增,返回结果为1 除了incr命令,Redis提供了decr(自减)、 incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数). - append
append key value -- 在key的value后面追加内容 - strlen
strlen key -- 返回字符串长度,每个中文占3个字节 - getset
getset key -- 设置值并且返回原来的值 - setrange
setrange key offset value -- 设置第offest位置的值为value,下标从0开始计算 - getrange
getrange key start end -- 截取字符串中的一部分分,形成字串,截取的范围是个闭区间
2.使用场景
-
缓存功能
Redis 作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
-
计数
使用Redis 作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。
-
共享Session
一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。为了解决这个问题,可以使用Redis将用户的Session进行集中管理,,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。
-
限速
比如,很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。一些网站限制一个IP地址不能在一秒钟之内访问超过n次也可以采用类似的思路
哈希Hsah
Java里提供了HashMap,Redis中也有类似的数据结构,就是哈希类型。但是要注意,哈希类型中的映射关系叫作field-value,注意这里的value是指field对应的值,不是键对应的值。
1.操作命令:
- hset
hset key filed value hset user:1 name zhangsan -- 如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field。 - hget
hget user:1 name -- 如果键或field不存在,会返回nil。 - hdel
hdel user:1 name age -- hdel会删除一个或多个field,返回结果为成功删除field的个数。 - hlen
hlen user:1 -- 计算filed的个数 - hmset
hmset user:1 name zhangsan age 20 weight 50kg --批量设值 - hmget
hmget user:1 name age weight -- 批量取值 - hexists
HEXISTS user:1 age -- 判断filed是否存在,存在返回1,不存在返回0 - hkeys
hkeys user:1 --获取所有的filed - hvals
hvals user:1 -- 获取所有的values - hgetall
hgetall user:1 -- 获取所有的filed和value 在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型,hscan将在后面的章节介绍。 - hincrby
HINCRBY user:1 age 10 -- 将值+10 - hstrlen
hstrlen user:1 name -- 计算value字符串长度
2.使用场景
从前面的操作可以看出,String和Hash的操作非常类似,那为什么要弄一个hash出来存储。哈希类型比较适宜存放对象类型的数据,我们可以比较下,如果数据库中表记录user为:
| id | name | age |
|---|---|---|
| 1 | lijin | 18 |
| 2 | msb | 20 |
==========================================================
1.String类型:需要一条条去插入获取
set user:1:name lijin;
set user:1:age 18;
set user:2:name msb;
set user:2:age 20;
优点:简单直观,每个键对应一个值
缺点:键数过多,占用内存多,用户信息过于分散,不用于生产环境
===========================================================
2.将对象序列化存入redis
set user:1 serialize(userInfo);
优点:编程简单,若使用序列化合理内存使用率高
缺点:序列化与反序列化有一定开销,更新属性时需要把userInfo全取出来进行反序列化,更新后再序列化到redis
==========================================================
3.使用hash类型
hmset user:1 name lijin age 18
hmset user:2 name msb age 20
优点:简单直观,使用合理可减少内存空间消耗
缺点:要控制内部编码格式,不恰当的格式会消耗更多内存
列表List
列表( list)类型是用来存储多个有序的字符串,a、b、c、b四个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储(2^32-1)个元素(4294967295)。 在Redis 中,可以对列表两端插入( push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
列表类型有两个特点:
第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
第二、列表中的元素可以是重复的。
1.操作命令
- lrange
lrange key start end -- 从左到右获取指定范围内的元素列表(不会删除元素) 输入0 -1 就是获取全部元素 - rpush
rpush demolist a b c d -- 向右插入,最后变成 a -> b -> c -> d - lpush
lpush demolist d c b a -- 向左插入,最后变成 a -> b -> c -> d - linsert
linsert demolist4 before a x -- 在某个元素前或后插入新元素 - lpop
lpop demolist4 -- 从列表左侧弹出(会删除元素) - rpop
rpop demolist4 -- 从列表右侧弹出(会删除元素) - lrem
lrem key count element -- 最多删除count个值为element,如果count=0,表示全部删除,如果<0,取它的绝对值 - ltrim
ltrim demolist start end -- 保留demolist从start到end的数据,其余删除 - lset
lset demolist4 0 xxx -- 设置下标为0的内容为xxx - lindex
lindex demolist4 0 -- 获取列表索引下标为0的元素 - llen
llen demolist4 -- 获取列表长度 - blpop
blpop和brpop是lpop和rpop的阻塞版本,除此之外还支持多个列表类型,也支持设定阻塞时间,单位秒,如果阻塞时间为0,表示一直阻塞下去。 BLPOP key timeout -- 有就弹出,没有就阻塞timeout时间,如果timeout=0,则表示一直阻塞下去; 注意:brpop后面如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回。 - brpop
2.使用场景
-
消息队列
Redis 的 lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
-
文章列表
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
-
实现其他数据结构
lpush+lpop =Stack(栈)
lpush +rpop =Queue(队列)
lpsh+ ltrim =Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
集合Set
集合( set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储2的32次方-1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题.
1.操作命令
-
sadd
SADD demoset 1 2 3 4 5 6 -- 允许添加多个,返回结果为添加成功的元素个数 -
srem
SREM demoset 1 2 --允许删除多个,返回结果为成功删除元素个数 -
scard
SCARD demoset -- 计算元素个数 -
sismember
SISMEMBER demoset 3 -- 判断元素3是否在集合中,在返回1,不在返回0 -
srandmember
srandmember demoset 2 -- 随机从集合返回指定个数元素,指定个数如果不写默认为1 -
spop
SPOP demoset 2 -- 同样可以指定个数,如果不写默认为1,注意,既然是弹出,spop命令执行后,元素会从集合中删除,而srandmember不会。 -
smembers
SMEMBERS demoset -- members 获取所有元素(不会弹出元素),返回结果是无序的 -
sinter
sinter set1 set2 -- 求set1 set2 的交集 -
sunion
sunion set1 set2 -- 求set1 set2 的并集 -
sdiff
sdiff set1 set2 -- 求多个集合的差集 -
s__store
sinterstore destination key [key ...] suionstore destination key [key ...] sdiffstore destination key [key ...]
2.使用场景
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。
例如一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。
除此之外,集合还可以通过生成随机数进行比如抽奖活动,以及社交图谱等等。
有序集合Zset
有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数( score)作为排序的依据。
有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。
有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题。
1.操作命令
- zadd
ZADD zset 100 zhangsan 90 lisi 200 wangwu -- 批量添加成员,前面为分数,后面为值 - zcard
ZCARD zset -- 计算成员个数 - zscore
ZSCORE zset zhangsan -- 获取成员zhangsan的分数 - zrank
zrank zset lisi -- 计算成员排名,从低到高返回排名;zrevrank反之 - zrem
ZREM zset lisi -- 删除成员,允许一次删除多个成员.返回结果为成功删除的个数。 - zincrby
ZINCRBY zset 10 zhangsan -- 增加成员的分数 - zrange和zrevrange
zrange zset 0 -1 -- 有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。 zrange zset 0 -1 withscores -- 如果加上 withscores选项,同时会返回成员的分数 - zrangebyscore
zrangebyscore zset 888 999 withscores limit 0 1 --其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。例如下面操作从低到高返回200到221分的成员,withscores选项会同时返回每个成员的分数。同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大: - zcount
ZCOUNT zset 100 200 -- 返回指定分数范围成员个数 - zremrangebyscore
zremrangebyscore key min max -- 删除指定分数范围的成 - zremrangebyrank
zremrangebyrank key start end -- 按升序删除指定排名内的元素 - zinterstore
127.0.0.1:6379> zadd zset1 90 lijin 95 bobo 100 msb 127.0.0.1:6379> zadd zset2 99 lijin 96 bobo 90 msb 127.0.0.1:6379> ZINTERSTORE zsetavg 2 zset1 zset2 weights 0.2 0.8 127.0.0.1:6379> zrange zsetavg 0 -1 withscores 1) "msb" 2) "92" 3) "bobo" 4) "95.800000000000011" 5) "lijin" 6) "97.200000000000003" - zunionstore
127.0.0.1:6379> ZUNIONSTORE zset3 2 zset1 zset2 weights 0.5 0.5 127.0.0.1:6379> ZRANGE zset3 0 -1 withscores 1) "lijin" 2) "94.5" 3) "msb" 4) "95" 5) "bobo" 6) "95.5"
2.使用场景
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数
三、高级数据结构
Bitmaps
许多开发语言都提供了操作位的功能,合理地使用位能够有效地提高内存使用率和开发效率。Redis提供了Bitmaps这个“数据结构”可以实现对位的操作。
把数据结构加上引号主要因为:
Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作。
Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把 Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。
1.操作命令
- setbit
设置键的第offset位的值为1,值范围[0,1] 127.0.0.1:6379> SETBIT 20231109 0 1 (integer) 0 127.0.0.1:6379> SETBIT 20231109 2 1 (integer) 0 127.0.0.1:6379> SETBIT 20231109 4 1 (integer) 0 127.0.0.1:6379> SETBIT 20231109 6 1 (integer) 0 127.0.0.1:6379> SETBIT 20231109 8 1 (integer) 0 假设现在有5个用户,userid=0,2,4,6,8的用户对网站进行了访问,存储键名为日期。 - getbit
127.0.0.1:6379> GETBIT 20231109 8 (integer) 1 127.0.0.1:6379> GETBIT 20231109 7 (integer) 0 获取键的第 offset位的值(从0开始算),比如获取userid=8的用户是否在20231109这天访问过,返回0说明没有访问过,当然offset是不存在的,也会返回0。 - bitcount
bitcount [start] [end] BITCOUNT 20231109 0 8 -- 获取Bitmaps指定范围值为1的个数; [start]和[end]代表起始和结束字节数;此处为啥写8呢 - bitop
bitop op destkey key [key . ...] -- bitop是一个复合操作,它可以做多个Bitmaps 的 and(交集)or(并集)not(非)xor(异或)操作并将结果保存在destkey中。
2.应用场景
1.假设网站有1亿用户,每天独立访问的用户有5千万,如果每天用集合类型和 Bitmaps分别存储活跃用户,很明显,假如用户id是Long型,64位,则集合类型占据的空间为64bitx50 000 000= 8Byte*50M = 400 MB,而Bitmaps即使存1亿用户,仅需要1位×100 000 000= 1/8 Byte * 1亿 = 12.5MB,可见Bitmaps能节省很多的内存空间。
2.布隆过滤器:1970 年布隆提出了一种布隆过滤器的算法,用来判断一个元素是否在一个集合中。 这种算法由一个二进制数组和一个 Hash 算法组成。
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。实际上,布隆过滤器广泛应用于网页黑名单系统、垃圾邮件过滤系统、爬虫网址判重系统等,Google 著名的分布式数据库 Bigtable 使用了布隆过滤器来查找不存在的行或列,以减少磁盘查找的IO次数,Google Chrome浏览器使用了布隆过滤器加速安全浏览服务。
布隆过滤器的误判问题
Ø通过hash计算在数组上不一定在集合
Ø本质是hash冲突
Ø通过hash计算不在数组的一定不在集合(误判)
优化方案:增大数组(预估适合值);增加hash函数
HyperLogLog
HyperLogLog并不是一种新的数据结构(实际类型为字符串类型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等。
如果要统计网站的pv(pageView)也就是页面点击量或者浏,览量,可以给每个网页一个独立的redis计数器,这个key再拼接上当天日期,每来一个请求,incrby一次,最终可以统计出所有的PV数据.但是如果,如果要统计uv(uniqeVew)也就是独立访问量.简单的实现就是为每个页面用一个独立的set来存储当天访问过的用户id,最终通过scard取出集合大小,如果访问量很大,需要一个很大的set集合来统计,非常浪费空间.如果这样的页面很多,那所需要的存储空间是惊人的。为这样一个去重功能就耗费这样多的存储空间,值得么?其实需要的数据又不需要太精确,105w 和 106w 这两个数字对于老板们来说并没有多大区别.yperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,Redis官方给出标准误差是0.81%,这样的精确度已经可以满足上面的UV 统计需求了。
1.操作命令
- pfadd
pfadd用于向HyperLogLog 添加元素,如果添加成功返回1 pfadd key element [element …] 127.0.0.1:6379> PFADD u-11-18 u1 u2 u3 u4 u1 u2 u3 u4 - pfcount
pfcount用于计算一个或多个HyperLogLog的独立总数 127.0.0.1:6379> PFCOUNT u-11-18 - pfmerge
pfmerge destkey sourcekey [sourcekey ... ] pfmerge可以求出多个HyperLogLog的并集并赋值给destkey 127.0.0.1:6379> PFADD u-11-18 u1 u2 u3 u4 u1 u2 u3 u4 127.0.0.1:6379> PFADD u-11-19 u4 u5 u6 u7 u8 127.0.0.1:6379> PFMERGE u-11 u-11-18 u-11-19 127.0.0.1:6379> PFCOUNT u-11 (integer) 8 有了每日访问量,可以利用这个公式合并,计算每月访问量之类的
2.原理
HyperLogLog基于概率论中伯努利试验并结合了极大似然估算方法,并做了分桶优化。实际上目前还没有发现更好的在大数据场景中准确计算基数的高效算法,因此在不追求绝对准确的情况下,使用概率算法算是一个不错的解决方案。概率算法不直接存储数据集合本身,通过一定的概率统计方法预估值,这种方法可以大大节省内存,同时保证误差控制在一定范围内。
伯努利实验
大概意思就是:假如抛硬币,如果是第三次出现正面,即第3位为1,.那么我们可以认为一共做了2^3次方=8次.如果是第7次出现1,那么一共做了2^7次=128次实验
分桶优化
但是问题是,这种本身就是概率的问题,我也可以1次就做到第7次出现正面. 所以为了减少偏差,可以多做几次,那么就是所谓的分桶.多次试验之后,求其平均值.这里常规求平均,可能就是两者求/2,这里也有问题,比如我和首富的工资平均后,也很高,显然没有参考意义.所以引入了调和平均,计算方法就是: 2/(1/我工资 + 1/首富工资)
极大似然估算方法
(1)pfadd原理
pfadd u-11-18 hong
当我们执行这个操作时,hong这个字符串会被转化为64个bit的二进制比特串.
假如为101...10000 (这里假设为第14-15位置中间)000..01共64位,
那么以从右往左14位来确定具体分到redis的哪个桶(redis共有2^14次方个桶)里.假如这14位是000...01,从低到高,第一次出现1的的位置是第1位,那么这个数据将被放在1号桶.然后值的话就是从第15位-第64位首次出现1的位置:101...10000,那么就是从右往左的第4位,那么01号桶将会放进去一个4.如果分到桶的数据有重复了(这里比大小,大的替换小的):规则如下,比大小(比出现位置的大小),比如有个数据是最高位才出现1,那么这个位置算出来就是50,50比4大,则进行替换。1号桶的数据就变成了50(二进制是110010).
因为确定存值的位数一共也就50位,也就是桶最大存的数据就是50,换成二进制就是6位.所以对于任何一个key,比如u-11-18,它的所有的value只占据 16384(桶个数) * 6(每个桶最大存50,即6bit) / (8*1024) = 12kB
(2)pfcount原理
进行统计的时候,就是把16384桶,把每个桶的值拿出来,然后把每个桶的值做调和平均数,就可以算出一个算法值,比如取出是 n,那么访问次数就是2的n次方。同时,在具体的算法实现上,HLL还有一个分阶段偏差修正算法。我们就不做更深入的了解了.
GEO
Redis 3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。
地图元素的位置数据使用二维的经纬度表示,经度范围(-180, 180],纬度范围(-90, 90],纬度正负以赤道为界,北正南负,经度正负以本初子午线(英国格林尼治天文台) 为界,东正西负。
业界比较通用的地理位置距离排序算法是GeoHash 算法,Redis 也使用GeoHash 算法。GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。当我们想要计算「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。
在 Redis 里面,经纬度使用 52 位的整数进行编码,放进了 zset 里面,zset 的 value 是元素的 key,score 是 GeoHash 的 52 位整数值
1.操作命令
-
geoadd
geoadd key longitude latitude member [longitude latitude member ...J 添加地理位置信息 127.0.0.1:6379> GEOADD cities:locations 117.12 39.08 tianjin 114.29 38.03 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding (integer) 4 127.0.0.1:6379> -
geopos
geopos key member [member ...]下面操作会获取天津的经纬度127.0.0.1:6379> GEOPOS cities:locations tianjin 1) 1) "117.12000042200088501" 2) "39.0800000535766543" -
geodist
##### 获取两个地理位置的距离。 geodist key member1 member2 [unit] 127.0.0.1:6379> GEODIST cities:locations tianjin shijiazhuang "272435.7494" -
georadius和georadiusbymember
获取指定位置范围内的地理信息位置集合 georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist] [withhash][COUNT count] [ascldesc] [store key] [storedist key] georadiusbymember key member radius m|km|ft|mi [withcoord][withdist] [withhash] [COUNT count][ascldesc] [store key] [storedist key] 这两个命令有很多可选参数,如下所示: withcoord:返回结果中包含经纬度。 withdist:返回结果中包含离中心节点位置的距离。 withhash:返回结果中包含geohash,有关geohash后面介绍。 COUNT count:指定返回结果的数量。 asc l desc:返回结果按照离中心节点的距离做升序或者降序。 store key:将返回结果的地理位置信息保存到指定键。 storedist key:将返回结果离中心节点的距离保存到指定键。 下面操作计算五座城市中,距离北京150公里以内的城市: georadiusbymember cities:locations beijing 150 km -
geohash
geohash key member [member ...] 127.0.0.1:6379> GEOHASH cities:locations tianjin 1) "wwgq34k1tb0" 127.0.0.1:6379> GEOHASH cities:locations shijiazhuang 1) "wwc22epky10" 字符串越长,表示的位置更精确,geohash长度为9时,精度在2米左右,geohash长度为8时,精度在20米左右。两个字符串越相似,它们之间的距离越近,Redis 利用字符串前缀匹配算法实现相关的命令。geohash编码和经纬度是可以相互转换的。 -
zrem
zrem key member GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除。