Loading

[03] Redis 数据类型

0. Redis 基本指令

  • keys * 查询当前库的所有键
  • exists <key> 判断某个键是否存在
  • type <key> 查看键的类型
  • dbsize 查看当前数据库的 key 的数量
  • del <key> 删除某个键
  • flushdb 清空当前库
  • flushall 通杀所有库

1. string

1.1 简述

  • string 是 Redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value
  • string 类型是二进制安全的。意味着 Redis 的 string 可以包含任何数据。比如 jpg 或者序列化的对象
  • string 类型是 Redis 最基本的数据类型,一个 Redis 中字符串 value 最多可以是 512M

1.2 常用操作

  • 设置
    • set <key> <value> 添加键值对
    • setex <key> <过期时间> <value> 设置键值的同时,设置过期时间,单位秒
    • setnx <key> <value> 只有在 key 不存在时设置 key 的值
    • mset <key1> <value1> [<key2> <value2> ...] 同时设置 1 个或多个 key-value 对
    • msetnx <key1> <value1> [<key2> <value2> ...] 同时设置 1 个或多个 key-value 对,当且仅当所有给定 key 都不存在
  • 获取
    • get <key> 查询对应键值
    • mget <key1> [<key2> ...] 同时获取 1 个或多个 value
    • getset <key> <value> 以新换旧,设置了新值的同时获取旧值
    • strlen <key> 获取值的长度
    • getrange <key> <起始位置> <结束位置> 获取指定范围的值,类 Java 的 substring,但这里包前也包后
    • ttl <key> 查看还有多少秒过期,-1 代表永不过期,-2 表示已过期
  • 修改
    • expire <key> <seconds> 为键值设置过期时间,单位秒
    • append <key> <value> 将给定的 value 追加到原值的末尾(如果当前 key 不存在,该命令等价于 set)
    • incr <key> 将 key 中储存的数字值增 1,只能对数字值操作,如果为空,新增值为 1
    • decr <key> 将 key 中储存的数字值减 1,只能对数字值操作,如果为空,新增值为 -1
    • incrby / decrby <key> <步长> 将 key 中储存的数字值增减,自定义步长
    • setrange <key> <起始位置> <value> 用 value 覆写 key 所存储的字符串值,从起始位置开始

1.3 incr 操作的原子性

  • 所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)
    • 在单线程中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。
    • 在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。
  • Redis 单命令的原子性主要得益于 Redis 的单线程
  • Java 中的 i++ 是否是原子操作?不是

2. list

2.1 简述

  • 单键多值
  • Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
  • 底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差

2.2 常用操作

  • lpush/rpush <key> <value1> <value2> ... 从左/右边插入 1 个或多个值
  • lpop/rpop <key> 从左/右边吐出一个值(值在键在,值光键亡)
  • llen <key> 获得列表长度
  • lindex <key> <index> 按照索引下标获得元素(从左到右)
  • lrange <key> <start> <stop> 按照给定索引范围获得所有元素(从左到右)
  • lset <key> <index> <value> 更新指定索引位置的 value
  • linsert <key> before|after <value> <newValue> 在 value 的前|后面插入 newValue
  • ltrim <key> [start] [stop] 截取 [start, stop] 范围的列表,重新赋值给 key
  • rpoplpush <key1> <key2> 从 key1 列表右边吐出一个值插到 key2 列表左边
  • lrem <key> <n> <value> 删除 n 个 value
    • n > 0 从左往右删 n 个
    • n < 0 从右往左删 n 个
    • n = 0 删除全部

3. set

3.1 简述

  • Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。
  • Redis set 是 string 类型的无序集合。它的底层其实是一个 value 为 null 的 hash 表,所以添加 / 删除 / 查找的复杂度都是 O(1)。

3.2 常用操作

  • sadd <key> <value1> [<value2> ...] 将 1 个或多个 value 加入到集合 key 当中,已经存在于集合的 value 元素将被忽略
  • srem <key> <value1> [<value2> ...] 删除集合 key 中指定的 value
  • spop <key> 随机从该集合中移除一个值
  • srandmember <key> <n> 随机从该集合中获取 n 个值(不会从集合中删除)
  • scard <key> 返回集合 key 的元素个数
  • smembers <key> 取出该集合的所有值
  • sismember <key> <value> 判断集合 key 中是否含有指定 value 值;有则返回 1,没有返回 0
  • smove <key1> <key2> <value> 将 key1 中的 value 移动到 key2
  • sinter <key1> <key2> 返回两个集合的交集元素
  • sunion <key1> <key2> 返回两个集合的并集元素
  • sdiff <key1> <key2> 返回两个集合的差集元素

3.3 应用场景

抽奖 相关命令
立即参与按钮 SADD <key> 用户ID
显示已经有多少人参与 SCARD <key>
抽奖(任意选取 N 个中奖人) SRANDMEMBER <key> N
点赞 相关命令
新增点赞 SADD <pub:msgID> 用户ID1 用户ID2
取消点赞 SREM <pub:msgID> 用户ID
展现所有点赞过的用户 SMEMBERS <pub:msgID>
点赞用户数统计 SCARD <pub:msgID>
判断某个朋友是否对楼主点赞过 SISMEMBER <pub:msgID> 用户ID
社交关系 相关命令
共同关注的人 SINTER <用户ID1> <用户ID2>
可能认识的人 SDIFF <用户ID1> <用户ID2>

4. hash

4.1 简述

  • Redis hash 是一个键值对集合。K-V 模式不变,但 value 是一个键值对集合,类比 Map<String, String>
  • string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

4.2 存储 JavaBean

1. 用户 ID 为 key,value 为 JavaBean 序列化后的字符串。

缺点:每次修改用户的某个属性需要,先反序列化改好后再序列化回去。开销较大


2. {用户ID + 属性名} 作为 key,属性值作为 value。

缺点:用户 ID 数据冗余


3. 通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。

电商项目中,可以用来存储用户购物车~

4.3 常用操作

  • hset <key> <field> <value> 给集合 key 中的键 field 赋值 value
  • hsetnx <key> <field> <value> 将集合 key 的 field 值设为 value,当且仅当 field 不存在
  • hmset <key> <field1> <value1> [<field2> <value2> ...] 批量设置集合 key 的键值对
  • hdel <key> <field> 从集合 key 中删除指定 field(& value)
  • hget <key> <field> 从集合 key 中取出键 field 对应的 value
  • hlen <key> 获取集合 key 的 field 数目
  • hgetall <key> 查看集合 key 中所有元素(field、value)
  • hkeys <key> 列出集合 key 的所有 field
  • hvals <key> 列出集合 key 的所有 value
  • hexists <key> <field> 查看集合 key 中,给定 field 是否存在
  • hincrby <key> <field> <increment> 为集合 key 中给定 field 的 value 加上增量
  • hdecrby <key> <field> <decrement> 为集合 key 中给定 field 的 value 减去增量

5. zset(sorted set)

5.1 简述

  • Redis 有序集合 zset 与普通集合 set 非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(score) ,这个评分(score) 被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。// 以 value 为键,score 为值的 map
  • 因为元素是有序的,所以你也可以很快的根据评分(score) 或者次序(position) 来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

5.2 常用操作

  • zadd <key> <score1> <value1> [<score2> <value2> ...] 将 1 个或多个 value 及其 score 值加入到有序集 key 中
  • zrem <key> <value> 删除该集合下,指定值的元素
  • zincrby <key> <increment> <value> 为 value 的 score 加上指定增量
  • zrank <key> <value> 返回 value 在集合中的排名,从 0 开始
  • zcard <key> 获取该集合所有的 field
  • zcount <key> <min> <max> 统计该集合中指定分数区间内的元素个数
  • zrange/zrevrange <key> <start> <stop> [withscores]
    • 返回有序集 key 中,下标在 <start><stop> 之间的元素(升序/降序)
    • withscores 的,可以让 score 和 value 一起返回到结果集
  • zrangebyscore key [(]min [(]max [withscores] [limit offset count]
    • 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max)的 value
    • 有序集 value 按 score 递增次序排列
    • ( 表示开区间;min/max 除取具体数值外,还可以取值 -inf | +inf
  • zrevrangebyscore key [(]max [(]min [withscores] [limit offset count] 同上,改为降序排列

5.3 应用场景

如何利用 zset 实现一个文章访问量的排行榜?

根据商品销售对商品进行排序显示。思路:定义商品销售排行榜,key 为 goods:sellsort,分数为商品销售数量。

操作 命令
商品编号 1101 的销量是 9,商品编号 1002 的销量是 15 zadd goods:sellsort 9 1101 15 1002
客户又买了 2 件编号为 1101 的商品 zincrby goods:sellsort 2 1101
商品销量前 10 名 ZRANGE goods:sellsort 0 10 withscores

6. Bitmap

6.1 常用操作

位存储!由 0 和 1 状态表现的二进制位的 bit 数组。

Bitmap 的偏移量是从 0 开始算的。

SETbit <key> <offset> <value>              # 指定第offset位赋值为value
GETbit <key> <offset>                      # 查询第offset位置的value
bitCOUNT <key> [start end]                 # 统计start到end字节中二进制值为1的个数
    - bitCOUNT test 0 0 只查找第 1 个字节
    - bitCOUNT test 1 1 只查找第 2 个字节
    - bitCOUNT test 0 1 查找第1~2个字节
bitOP <op> <destKey> <key1> [<key2> ...]   # 对不同的二进制数据位运算(AND、OR、NOT、XOR)

Redis 中 bit 映射被限制在 512MB 之内,所以最大是 2^32 位。建议每个 key 的位数都控制下,因为读取时候时间复杂度 O(n),越大的串读的时间花销越多。

6.2 简单说明

实质是二进制的 ascii 编码对应,使用 type 命令可以看到实际数据类型是 string。

strlen 返回的不是字符串长度而是占据几个字节,超过 8 位后按照 8 位一组一字节再扩容。

6.3 应用场景

主要用于状态统计。

  • 日活统计
  • 连续 N 天签到打卡
  • 最近一周的活跃用户
  • 统计指定用户一年之中的登陆天数
  • 某用户按照一年 365 天,哪几天登陆过?哪几天没有登陆?全年中登录的天数共计多少?

按「年」去存储一个用户的签到情况,365 天只需要 365 / 8 ≈ 46 Byte,1000W 用户量一年也只需要 44 MB 就足够了。假如是亿级的系统,每天使用 1 个 1 亿位的 Bitmap 约占 12MB 的内存(10^8/8/1024/1024),10 天的 Bitmap 的内存开销约为 120MB,内存压力不算太高。在实际使用时,最好对 Bitmap 设置过期时间,让 Redis 自动删除不再需要的签到记录以节省内存开销。

7. HyperLogLog

7.1 前置说明

(1)数据显较大亿级的去重复统计不能用 bitmap

bitmap 是通过用 bit 数组来表示各元素是否出现,每个元素对应一位,所需的总内存为 N 个 bit。基数计数则将每一个元素对应到 bit 数组中的其中一位(对应关系存到表里),比如 bit 数组 010010101(按照从零开始下标,有的就是1、4、6、8)。

新进入的元素只需要将已经有的 bit 数组和新加入的元素进行按位或计算就行。这个方式能大大减少内存占用且位操作迅速。但是,假设一个样本案例就是一亿个基数位值数据,一个样本就是一亿。

如果要统计 1 亿个数据的基数位值,大约需要内存 100000000/8/1024/1024 约等于 12M,内存减少占用的效果显著。这样得到统计一个对象样本的基数值需要 12M。

如果统计 10000 个对象样本(1w 个亿级),就需要 117.1875G 将近 120G,可见使用 bitmap 还是不适用大数据量下(亿级)的基数计数场景,但是 bitmap 方法是精确计算的。

(2)概率算法

通过牺牲准确率来换取空间,对于不要求绝对准确率的场景下可以使用,因为概率算法不直接存储数据本身,通过一定的概率统计方法预估基数值,同时保证误差在一定范围内,由于又不储存数据故此可以大大节约内存。

HyperLogLog 就是一种概率算法的实现。

(3)HyperLogLog

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

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

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

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

基于上述所说,已经知道它需要 12KB 的内存来做基数统计,原因就是 HyperLogLog 中“槽=16834 & 桶=6”,因此使用空间为 16834*6/8/1024 = 12KB

每个桶为何取 6 位?因为 6 位可以存储的最大值为 64,现在计算机都是 64/32 位操作系统,因此 6 位最节省内存,又能满足需求。

要注意的是它有 0.81% 的错误率。但如果误差可接受的范围内,推荐使用 HyperLogLog,它能快速计算基数!

补充https://blog.csdn.net/u010887744/article/details/108041280

(4)为什么 Redis 集群的最大槽数是 16384个?

Redis 集群并没有使用〈一致性 hash〉而是引入了「哈希槽」的概念。Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。但为什么哈希槽的数量是 16384(2^14)个呢?

CRC16 算法产生的 hash 值有 16 bit,该算法可以产生 2^16=65536 个值。换句话说值是分布在 0~65535 之间。那作者在做取模运算的时候,为什么不 mod65536,而选择 mod16384?

  1. 正常的心跳数据包带有该节点的完整配置,可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。这意味着它们包含原始节点的插槽配置,所以,如果采用 16384 个插槽,占发送心跳信息的消息头占用空间为 2KB (16384/8),而当 65536 个插槽时,发送心跳信息的消息头占空间 8KB (65536/8)。8KB 的心跳包看似不大,但这是心跳包,每秒都要将本节点的信息同步给集群其他节点的。比起 16384 个插槽,头大小增加了 4 倍,ping 消息的消息头太大了,浪费带宽;
  2. Redis Cluster 不太可能扩展到超过 1000 个主节点,太多可能导致网络拥堵;
  3. Redis 主节点的哈希槽配置信息是通过 bitmap 来保存的,传输过程中,会对 bitmap 进行压缩,bitmap 的填充率越低,压缩率越高(bitmap 填充率 = slots / 节点数)。所以,插槽数偏低的话, 填充率会降低,压缩率会升高。

综合下来,从心跳包的大小、网络带宽、心跳并发、压缩率等维度考虑,16384 个插槽更有优势且能满足业务需求。


再来看下 master 节点间心跳数据包格式】消息由消息头和消息体组成。消息头包含发送节点自身状态数据,接收节点根据消息头就可以获取到发送节点的相关数据。结构体定义如下:

其中,消息头有一个名为 myslots 的 char 类型数组 unsigned char myslots[CLUSTER_SLOTS/8],该数组长度为 16384/8 = 2048。底层存储其实是一个 bitmap,存储发送方提供服务的 slot 映射表(如果为从,则为该从所对应的主提供服务的 slot 映射表),每一个位代表一个槽,如果该位为 1 则表示这个槽是属于这个节点。

7.2 使用场景

PV、UV、DAU、MAU

  • PV(Page View,点击量):即页面浏览量或点击量,用户每次刷新即被计算一次;
  • UV(Unique Visitor,独立访客即用户数量):访问您网站的一台电脑客户端为一个访客,在一段时间内;
  • DAU(Daily Active User,日活跃用户数量):常用于反映网站、互联网应用或网络游戏的运营情况。DAU 通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户),这与流量统计工具里的访客(UV)概念相似。
  • MAU(Monthly Active Users,月活跃用户人数):是在线游戏的一个用户数量统计名词,数量越大意味着玩这款游戏的人越多。

博客文章访问量(同一个人访问一篇文章多次,但还是只能算 1 次)。用术语说这实际是一个实时数据流统计分析问题。要实现这个统计需求。需要做到如下 3 点:

  1. 对独立访客做标识
  2. 在访客点击链接时记录下链接编号及访客标记
  3. 对每一个要统计的链接维护一个数据结构和一个当前 UV 值,当某个链接发生一次点击时,能迅速定位此用户在今天是否已经点过此链接,如果没有则此链接的 UV 增加 1。

[3] → 如果将每个链接被点击的日志中访客标识字段组成一个集合,那么此链接当前的 UV 也就是这个集合的基数,因此 UV 计算本质上就是一个基数计数问题。

7.3 常用操作

PFadd 命令将所有元素参数添加到 HyperLogLog 数据结构中。返回值为整型,如果至少有个元素被添加返回 1, 否则返回 0。

PFADD <key> <value1> [<value2> ...]

PFcount 命令返回给定 HyperLogLog 的基数(重复的数不算,求总数)估算值。如果多个 HyperLogLog 则返回基数估值之和。

PFCOUNT <key1> [<key2> ...]

PFmerge 命令将多个 HyperLogLog 合并为一个 HyperLogLog ,合并后的 HyperLogLog 的基数估算值是通过对所有给定 HyperLogLog 进行“并集”计算得出的。

PFMERGE <destkey> <sourcekey1> [<sourcekey2> ...]

8. Geospatial

Geo 是 Redis 用来处理位置信息的。在 Redis3.2 中正式使用。主要是利用了 Z 阶曲线、Base32 编码和 Geohash 算法。

(1)Z 阶曲线

在 x 轴和 y 轴上将十进制数转化为二进制数,采用 x 轴和 y 轴对应的二进制数依次交叉后得到一个六位数编码。把数字从小到大依次连起来的曲线称为 Z 阶曲线,Z 阶曲线是把多维转换成一维的一种方法。

(2)Base32 编码

Base32 这种数据编码机制,主要用来把二进制数据编码成可见的字符串,其编码规则是:任意给定一个二进制数据,以 5 个 bit 为一组进行切分(base64 以 6 个 bit 为一组),对切分而成的每个组进行编码得到 1 个可见字符。Base32 编码表字符集中的字符总数为 32 个(0-9、b-z 去掉 a、i、l、o),这也是 Base32 名字的由来。

(3)Geohash 算法

Gustavo 在 2008 年 2 月上线了 geohash.org 网站。Geohash 是一种地理位置信息编码方法。 经过 Geohash 映射后,地球上任意位置的经纬度坐标可以表示成一个较短的字符串。可以方便的存储在数据库中,附在邮件上,以及方便的使用在其他服务中。以北京的坐标举例,[39.928167, 116.389550] 可以转换成 wx4g0s8q3jf9 。

8.1 应用场景

如「附近的人」、「打车距离计算」...

如果用数据库做,当我们查找距离我们(x0,y0) 附近 r 公里范围内部的车辆,使用如下 SQL 即可:

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

这样会有什么问题呢?

  1. 查询性能问题,如果并发高,数据量大这种查询是要搞垮数据库的;
  2. 这个查询的是一个矩形访问,而不是以我为中心 r 公里为半径的圆形访问;
  3. 精准度的问题,我们知道地球不是平面坐标系,而是一个圆球,这种矩形计算在长距离计算时会有很大误差。

8.2 常用操作

Redis 的 Geo 在 Redis3.2 版本就推出了,这个功能可以推算地理位置的信息,两地之间的距离,方圆几里地人等。

(1)将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中。这些数据将会存储到 sorted set,这样的目的是为了方便使用 GEORADIUS 或者 GEORADIUSBYMEMBER 命令对数据进行半径查询等操作。

GEOadd <key> <纬度> <经度> <名称>

sorted set 使用一种称为 Geohash 的技术进行填充。经度和纬度的位是交错的,以形成一个独特的 52 位整数。已知一个 sorted set 的 double score 可以代表一个 52 位的整数,而不会失去精度。

有效的经度从 -180 度到 180 度,有效的纬度从 -85.05112878 度到 85.05112878 度。当坐标位置超出上述指定范围时,该命令将会返回一个错误。

(2)从 key 里返回所有给定位置元素的位置(经度和纬度)。具体是返回一个数组,数组中的每个项都由两个元素组成:第 1 个元素为给定位置元素的经度,而第 2 个元素则为给定位置元素的纬度。当给定的位置元素不存在时,对应的数组项为空值。

GEOpos <key> <名称>

(3)计算出的距离会以双精度浮点数的形式被返回。如果给定的位置元素不存在,那么命令返回空值。

GEOdist <key> <名称1> <名称2> [单位]

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米(默认)
  • km 表示单位为千米
  • mi 表示单位为英里
  • ft 表示单位为英尺

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

GEOradius <key> <纬度> <经度> <半径> [单位] <携带值|升降序|返回数目>

在给定以下可选项时,命令会返回额外的信息:

  • 【withDIST】在返回位置元素的同时,将〈位置元素与中心之间的距离〉也一并返回(距离的单位和用户给定的范围单位保持一致);
  • 【withCOORD】将〈位置元素的经度和维度〉也一并返回;
  • 【withHASH】以 52 位有符号整数的形式,返回〈位置元素经过原始 Geohash 编码的有序集合分值〉。这个选项主要用于底层应用或者调试,实际中的作用并不大。

命令默认返回未排序的位置元素。通过以下两个参数,用户可以指定被返回位置元素的排序方式:

  • 【ASC】根据中心的位置,按照从近到远的方式返回位置元素;
  • 【DESC】根据中心的位置,按照从远到近的方式返回位置元素。

在默认情况下,GEORADIUS 命令会返回所有匹配的位置元素。虽然用户可以使用 <count> 选项去获取前 N 个匹配元素,但是因为命令在内部可能会需要对所有被匹配的元素进行处理,所以在对一个非常大的区域进行搜索时,即使只使用 COUNT 选项去获取少量元素,命令的执行速度也可能会非常慢。但是从另一方面来说,使用 COUNT 选项去减少需要返回的元素数量,对于减少带宽来说仍然是非常有用的。

在没有给定任何 WITH 选项的情况下,命令只会返回一个像 ["New York", "Milan", "Paris"] 这样的线性列表。但在指定了 WITHCOORD 、 WITHDIST 、 WITHHASH 等选项的情况下,命令返回一个二层嵌套数组,内层的每个子数组就表示一个元素。

(5)该命令将返回 11 个字符的 Geohash 字符串。

GEOhash <key> <名称1> [<名称2> ...]

一个数组,数组的每个项都是一个 Geohash。命令返回的 Geohash 的位置与用户给定的位置元素的位置一一对应。

(6)这个命令和 GEORADIUS 命令一样,都可以找出位于指定范围内的元素,但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的,而不是像 GEORADIUS 那样,使用输入的经度和纬度来决定中心点。

GEOrediusbymember <key> <名称> <半径> [单位]

8.3 Java 代码

@RestController
@RequestMapping("/geo")
public class GeoController {
  public static final String CITY = "city";

  @Autowired
  private RedisTemplate redisTemplate;

  @RequestMapping("/add")
  public String geoAdd() {
    Map<String, Point> map = new HashMap<>(16);
    map.put("天安门", new Point(116.403963, 39.915119));
    map.put("故宫", new Point(116.403414, 39.924091));
    map.put("长城", new Point(116.024067, 40.362639));
    redisTemplate.opsForGeo().add(CITY, map);
    return map.toString();
  }

  @ApiOperation("获取经纬度坐标")
  @GetMapping(value = "/position")
  public Point position(String member) {
    List<Point> list = this.redisTemplate.opsForGeo().position(CITY, member);
    return list.get(0);
  }

  @ApiOperation("geoHash算法生成的base32编码值")
  @GetMapping(value = "/hash")
  public String hash(String member) {
    List<String> list = this.redisTemplate.opsForGeo().hash(CITY, member);
    return list.get(0);
  }

  @GetMapping(value = "/distance")
  public Distance distance(String member1, String member2) {
    Distance distance = this.redisTemplate.opsForGeo().distance(
                    CITY, member1, member2, RedisGeoCommands.DistanceUnit.KILOMETERS);
    return distance;
  }

  @ApiOperation("通过经纬度查找附近50公里的建筑")
  @GetMapping(value = "/radius50ByPos")
  public GeoResults radiusByPos(Double centerX, Double centerY) {
    Circle circle = new Circle(centerX, centerY, Metrics.KILOMETERS.getMultiplier());
    RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
                                                    .newGeoRadiusArgs().includeDistance()
                                                    .includeCoordinates().sortAscending()
                                                    .limit(50);
    GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = this.redisTemplate
                                                    .opsForGeo().radius(CITY, circle, args);
    return geoResults;
  }

  @ApiOperation("通过地标名称查找半径10公里内建筑(50条)")
  @GetMapping(value = "/radius10ByMember")
  public GeoResults radiusByMember(String member) {
    RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
                                                    .newGeoRadiusArgs().includeDistance()
                                                    .includeCoordinates().sortAscending()
                                                    .limit(50);
    Distance distance = new Distance(10, Metrics.KILOMETERS);
    GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = this.redisTemplate.opsForGeo().radius(CITY, member, distance, args);
    return geoResults;
  }

}

9. 四种统计

【实际场景】

  • 手机 App 中的每天的用户登录信息:1 天对应一系列用户 ID 或移动设备 ID;
  • 电商网站上商品的用户评论列表:1 个商品对应了一系列的评论;
  • 用户在手机 App 上的签到打卡信息:1 天对应一系列用户的签到记录;
  • 应用网站上的网页访问信息:1 个网页对应一系列的访问点击。
  • 在移动应用中,需要统计每天的新增用户数和第 2 天的留存用户数;
  • 在电商网站的商品评论中,需要统计评论列表中的最新评论;
  • 在签到打卡中,需要统计一个月内连续打卡的用户数;
  • 在网页访问记录中,需要统计独立访客(UniqueVisitor,UV)量;
  • 类似今日头条、抖音、淘宝这样的额用户访问级别都是亿级的,请问如何处理?

【痛点】

亿级数据的收集 + 统计

一句话就是得做到:存的进 + 取得快 + 多维统计

9.1 聚合统计

统计多个集合元素的聚合结果,就是交差并等集合统计。

9.2 排序统计

抖音视频最新评论留言的场景,请你设计一个展现列表。

在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议使用 zSet(右侧)。

如果用 List(左侧),则每个商品评价对应一个 List 集合,这个 List 包含了对这个商品的所有评论,而且会按照评论时间保存这些评论,每来一个新评论就用 LPUSH 命令把它插入 List 的队头。但是,如果在演示第 2 页前,又产生了一个新评论,第 2 页的评论不一样了。

原因:List 是通过元素在 List 中的位置来排序的,当有一个新元素插入时,原先的元素在 List 中的位置都后移了一位,原来在第 1 位的元素现在排在了第 2 位,当 LRANGE 读取时,就会读到旧元素。

9.3 二值统计

集合元素的取值就只有 0 和 1 两种。如签到打卡的场景中,我们只用记录有签到(1)或没签到(0)。

9.4 基数统计

指统计⼀个集合中不重复的元素个数。

posted @ 2020-09-04 12:41  tree6x7  阅读(199)  评论(0编辑  收藏  举报