Redis源码阅读(一)总体概览

2022-01-28 15:13:26 浏览数 (1)

学习源码是一个由浅入深,再到逐渐简化的过程,即所谓“把书读厚,把书读薄”的过程。

”昨夜西风凋碧树。独上高楼,望尽天涯路”,我们先从Redis的特性、用途及数据类型这几个方面介绍下Redis,对其有个总体上的认知。

1. Redis特性及用途

定义(关键词):C语言、开源、key-value、NoSQL、基于内存

特点

  • 高性能,基于内存,读写高效,并发10W QPS
  • 单进程 单/多线程,线程安全,IO多路复用 这里的单线程是指Redis用来处理请求这一过程中使用了一个线程。 严格来说,从Redis4.0之后并不是单线程,除了主线程外,也有后台线程在处理一些较为缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等;Redis 6.0引入的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。
  • 丰富的数据类型(string/list/hash/set/zset/stream)
  • 数据持久化(RDB、AOF)
  • 高可用(主从复制、哨兵机制、集群)

用途

  • 数据库
  • 缓存
  • 分布式锁
  • 消息中间件

2. Redis的数据类型

(1) 六种基础数据类型

数据类型

底层数据结构

常用命令

应用场景

备注

String

int, embstr, raw

set, mset, get, append, setbit ...

【字符串】万物皆可String

字符串,二进制安全

Hash

ht, ziplist

hget, hset, hegetall, hkeys, hexists ...

【存储对象】操作用户属性

散列,键值对集合,map

List

linkedlist, ziplist

lpush/lpop, rpush/rpop, lrange ...

【增删快】最新消息排行;【按序】消息队列

列表,双向链表

Set

intset, ht

sadd, spop, sinter, sunion, sdiff ...

【支持交/并/差集】共同好友;【不重复】统计访问网站IP

集合,不重复

ZSet

ziplist, skipklist

zadd, zrange, zrem, zcard, zrank ...

【有序】排行榜;【score】带权重的消息队列、延时队列

有序集合,按score排序

Stream

listpack, rax

xadd, xrange, xrevrange, xgroup, xread ...

【消息队列】IRC / 实时聊天系统, IoT数据采集

类似于日志的数据结构,本质是一个消息队列

(2) 基于基础数据类型的三种扩展数据类型

数据类型

依赖的数据结构

常用命令

应用场景

备注

Bitmap

String

setbit, getbit, bitop, bitcount, bitpos

活跃用户数统计; 统计某一天的用户登陆数量

存储与对象ID关联的节省空间并且高性能的布尔信息

Hyperloglog

String

pfadd, pfcount, pfmerge

基数统计; 统计每日访问IP数/页面UV 数/在线用户数等

bitmap的升级版;概率算法,不直接存储数据集合本身,概率统计方法预估基数值

Geo

ZSet

geoadd, geohash, geopos, geodist, georadius

基于位置的服务(LBS)

Redis3.2以后版本

注意:

  • 一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃,hyperloglog计数
  • Geo数据建议使用单独的 Redis 实例部署(在项目开发中,有看到经纬度专用的Redis,与业务使用的Redis分离)
A. Bitmap

bitmap不是实际的数据类型是string类型上的一组面向bit操作的集合。

由于string是二进制安全的,并且它们的最大长度是512m,所以bitmap能最大设置2^32个不同的bit。

8 *1024*1024* 512 = 2^32 = 4294967296 = 40 亿

优点:

存储信息时可以节省大量的空间。例如在一个系统中,不同的用户被一个增长的用户ID表示。40亿(2^32=4*1024*1024*1024≈40亿)用户只需要512M内存就能记住某种信息,例如用户是否登录过。

操作:

  • 恒定时间的单个bit操作,例如把某个bit设置为0或者1。或者获取某bit的值。
    • SETBIT:设值
    • GETBIT:取值
  • 对一组bit的操作。例如给定范围内bit统计(例如人口统计)。
    • BITOP:执行两个不同string的位操作,包括AND,OR,XOR和NOT
    • BITCOUNT:统计位的值为1的数量
    • BITPOS:寻址第一个为0或者1的bit的位置

使用场景:

  • 各种实时分析
  • 存储与对象ID关联的节省空间并且高性能的布尔信息 例如: 统计访问网站的用户的最长连续时间 统计某一天的用户登陆数量(以当天的日志加固定的前缀作为key,建立一个bitmap,每一位二进制的位做为一个用户ID的标识)
B. Hyperloglog

hyperLogLog是bitmap的升级版。本质上是一种概率算法,不直接存储数据集合本身,而是通过一定的概率统计方法预估基数值。这种方法可以大大节省内存,同时保证误差控制在一定范围内。

被编码成Redis字符串。因此可以通过调用GET命令序列化一个Hyperloglog(HLL),也可以通过调用SET命令将其反序列化到redis服务器。HLL的API类似使用SETS数据结构做相同的任务,SETS结构中,通过SADD命令把每一个观察的元素添加到一个SET集合,用SCARD命令检查SET集合中元素的数量,集合里的元素都是唯一的,已经存在的元素不会被重复添加。

而使用HLL时并不是真正添加项到HLL中(这一点和SETS结构差异很大),因为HLL的数据结构只包含一个不包含实际元素的状态。

操作:

  • PFADD:用于添加一个新元素到统计中。
  • PFCOUNT:用于获取到目前为止通过PFADD命令添加的唯一元素个数近似值。
  • PFMERGE:执行多个HLL之间的联合操作。

使用场景:

  • 统计基数数量(大量)
    • 统计注册 IP 数
    • 统计每日访问 IP 数
    • 统计页面实时 UV 数
    • 统计在线用户数
    • 统计用户每天搜索不同词条的个数
  • 一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃,hyperloglog计数
C. Geo

底层数据类型:zset

Redis的GEO特性在 Redis3.2版本中推出,这个功能可以将用户给定的地理位置(经度和纬度)信息储存起来,并对这些信息进行操作。

GEO相关命令只有6个:

  • GEOADD:GEOADD key longitude latitude member [longitude latitude member …],将指定的地理空间位置(纬度、经度、名称)添加到指定的key中
  • GEOHASH:GEOHASH key member [member …],返回一个或多个位置元素的标准Geohash值,它可以在http://geohash.org/使用
  • GEOPOS:GEOPOS key member [member …],从key里返回所有给定位置元素的位置(经度和纬度)
  • GEODIST:GEODIST key member1 member2 [unit],返回两个给定位置之间的距离。GEODIST命令在计算距离时会假设地球为完美的球形。在极限情况下,这一假设最大会造成0.5%的误差。
  • GEORADIUS:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD][WITHDIST] [WITHHASH][COUNT count],以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。这个命令可以查询某城市的周边城市群。
  • GEORADIUSBYMEMBER:GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD][WITHDIST] [WITHHASH][COUNT count],这个命令和GEORADIUS命令一样,都可以找出位于指定范围内的元素,但是GEORADIUSBYMEMBER的中心点是由给定的位置元素决定的,而不是像 GEORADIUS那样,使用输入的经度和纬度来决定中心点。

使用场景:

  • 基于位置的服务(LBS)

GEO类型的底层数据结构是用Zset实现的。

例如,存储车辆/店铺的经纬度信息时,元素是车辆/店铺ID,元素的权重Sore是本应是经纬度信息,但Sore应该是float类型,因此,需要对一组经纬度进行编码(即GeoHash编码)。

简要步骤:

Step 1:将经度/纬度进行二分拆解,得到二叉树结构,并进行0/1编码,再通过N位bit进行存储(N越大,精度越高);

Step 2:将经度和纬度的N位bit进行交叉组合,得到GeoHash值。

GeoHash编码的基本原理是“二分区间,区间编码”,先对经度和纬度分别编码,再将经纬度各自的编码组合成一个最终编码。简单来说,GeoHash将一个空间分割成一个个小方块,我们可以通过查询给定经纬度所在方格周围的4个或者8个方格,以此进行“周边查找”。【GeoHash值相近,并不一定位置相近,故需计算邻居节点,以提高LBS精度】

GeoHash编码规则GeoHash编码规则

注意:

在项目开发中,会看到将一个Redis单独划分出来,用于经纬度的计算。如果数据量过亿,就需要对 Geo 数据进行拆分,按国家/省/市拆分,甚至按区拆分,以降低单个 zset 集合的大小。

【原因:在一个地图应用中,数据可能会有百万千万条,如果使用Geo,位置信息将全部放在一个 zset 集合中。在 Redis 的集群环境中,集合 可能会从一个节点迁移到另一个节点,如果单个 key 的数据过大,会导致集群的迁移出现卡顿等问题,影响线上服务的正常运行。所以,建议 Geo 的数据使用单独的 Redis 实例部署。】

实例:

代码语言:javascript复制
# 新增
127.0.0.1:6379> geoadd city 114.06667 22.61667 "shenzhen" 119.30000 26.08333 "fuzhou"
(integer) 2
​
# zset类型
127.0.0.1:6379> TYPE city
zset
​
# 标准Geohash值,可以在http://geohash.org/使用。
127.0.0.1:6379> geohash city shenzhen fuzhou
1) "ws10ethzdh0"
2) "wssu6srd7k0"
​
# 获取key的经纬度
127.0.0.1:6379> geopos city shenzhen fuzhou
1) 1) "114.06667023897171021"
   2) "22.61666928352524764"
2) 1) "119.29999798536300659"
   2) "26.08332883679719316"
​
# 计算距离
127.0.0.1:6379> geodist city shenzhen fuzhou km
"655.5342"
​
# 计算范围
127.0.0.1:6379> georadius city 116 15 1000 km
1) "shenzhen"
127.0.0.1:6379> georadius city 116 15 2000 km
1) "shenzhen"
2) "fuzhou"
127.0.0.1:6379> georadius city 116 15 2000 km asc
1) "shenzhen"
2) "fuzhou"

0 人点赞