Redis 实践

Timeline 时间线

Twitter 有两个 timeline,一个是已关注的人的 timeline(又叫 home timeline),一个是自己发的推的 timeline(又叫 user timeline)

现在需要处理的是 home timeline(推模式),即大 V 发推之后,需要把这些推推送到这个大 V 的粉丝 home timeline 中

使用 list

1
2
3
4
5
6
7
# fanout
lpush user:1:htimeline toString({"tid": "推 ID", "uid": "大 V ID", "meta": "元数据"})
lpush user:2:htimeline toString({"tid": "推 ID", "uid": "大 V ID", "meta": "元数据"})

# 用户刷新 home timeline
lrange user:1:htimeline 0 9
lrange user:2:htimeline 0 9

参考:

Leaderboard & ranking 排行榜

使用 sorted set

1
2
3
4
5
6
7
8
9
# 添加新用户
zadd lb:bname1 0 user1

# 更新分数(重新 add 或者 incr)
zadd lb:bname1 3 user1
zincrby lb:bname1 3 user1

# 查看 top 10
zrevrange lb:bname1 0 9 [withscores]

参考:

UV & DAU & MAU 独立访客,日/月活跃用户数

UV(Unique Visitor) & DAU(Daily Active Users) & MAU(Monthly Active Users)

需要考虑用户数量、用户 ID 特性、业务是否只是需要数目而不是具体的人

sorted set 使用条件:

  • unique id
  • 几乎没有什么条件,只是会浪费一些空间而已
  • 可以查询出都是哪些用户

bitmap 使用条件:

  • unique id

  • ID 需要自增。能映射到 bitmap 中

    如果不是,可以考虑注册时间(需唯一)。假设精确到秒,注册时间减平台上线时间,一个 key 可以保存 49710 个用户((2^32)/(246060)),再用多个 key 解决其他问题

  • 用户最多只有 40 亿(2^32 个 bit)。实际上可以使用多个 key 来突破这个限制

hyperloglog 使用条件:

  • unique id
  • 只在乎数目,不在乎具体是谁
  • 允许有误差。HLL 本来就有误差
1
2
3
4
5
6
7
8
# 用户登录
sadd dau:todayDate <uniqueID>

# 查看今天登录的所有用户
smembers dau:todayDate

# 查看今天登录的用户数
scard dau:todayDate

参考:

Rate Limiter 限流

非滑动窗口模式(滑动窗口需要记得每秒的访问次数),算是固定窗口模式

1
2
3
4
5
6
7
8
9
10
11
12
# 设置 10 s 内只允许 15 个请求(固定窗口)

# 每次访问都需要执行
set rate-limiter:127.0.0.1 15 NX EX 10
get rate-limiter:127.0.0.1 # 为 0 则阻止访问,否则
decr rate-limiter:127.0.0.1


# 滑动窗口的简单实现。缺:需要定期修剪 sorted set
zadd limit:127.0.0.1 currentSecondTime currentSecondTime
zrangebyscore limit:127.0.0.1 (currentSecontTime - 窗口时间) currentSecondTime # 查看 size 是否符合请求数
zremrangebyscore limit:127.0.0.1 -inf ((currentSecontTime - 窗口时间) # 第一个 ( 表示不取到当前值

参考:

最长连续登录天数

使用 bitmap 的 bitpos 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 登录的时候
setbit login:user1 (currentDay - websitePulishDay) 1

# 查找最长连续登录天数
# 1. 先找到第一个登录记录
bitpos login:user1 1
# 2. 找到第一个登录记录之后的第一个不登陆记录
bitpos login:user1 0 (上一条指令的返回值)
# 3. 计算连续
(2 返回结果) - (1 返回结果)
# 4. 继续寻找

# 测试数据 0111 1011 0000 1100(Redis 7.0 才有效,因为 bit 参数)
set login:user1 "\x7b\x0c"
bitpos login:user1 1
bitpos login:user1 0 1 -1 BIT
# 5 - 1 = 4
bitpos login:user1 1 5 -1 BIT

月签到

使用 bitmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 用户1月6号签到
SETBIT u:sign:1225:202101 5 1 # 偏移量是从0开始,所以要把6减1

# 检查1月6号是否签到
GETBIT u:sign:1225:202101 5 # 偏移量是从0开始,所以要把6减1

# 统计1月份的签到次数
BITCOUNT u:sign:1225:202101

# 获取1月份前31天的签到数据
BITFIELD u:sign:1225:202101 get u31 0

# 获取1月份首次签到的日期
BITPOS u:sign:1225:202101 1 # 返回的首次签到的偏移量,返回值位索引,加 1 即为当月的几号

参考:Redis实战篇(二)基于Bitmap实现用户签到功能

公司部门架构信息

架构图

1
2
3
4
5
6
7
8
9
# 添加部门架构
sadd dep:董事会 dep:总经办
sadd dep:总经办 dep:行政中心 dep:技术中心 dep:运营中心 dep:营销中心
# ...

# 添加部门人员
sadd dep:董事会:per 1 2 3
sadd dep:技术中心:per 4 5 6 7 8
#...

另一种使用 list 的 存法

1
2
3
4
5
6
7
8
# 这种结构在 Redis GUI 中看起来就像是树结构
rpush dep:董事会 1 2 3
rpush dep:董事会:总经办:技术中心 4 5 6 7 8
rpush dep:董事会:总经办:技术中心:研发部 4 5

# 但是查询必须带上完整的架构路径。不建议使用模糊 key,然后再查的方法
key dep:*研发部
lrange <准确 key> 0 -1

貌似需要考量很多东西来判断

  • 后端数据据是怎么存储的
  • 前端的需求是什么?是否需要查看比某个部门高级的所有部门(或者换成人,查看该人的所有领导(非直接))
  • 考虑使用 Redis 拓展?ioredis-tree

数据库保存树结构的方法(前四个强推):

文章 tag

  • 根据文章找到 tag
  • 根据 tag 找文章

使用 set

1
2
3
4
5
6
7
8
9
10
11
12
13
# 新文章(同时更新该文章有多少 tag,以及某个 tag 下有多少文章)
sadd post:1:tag 1 2 3 4 5
sadd tag:1:post 1
sadd tag:2:post 1
sadd tag:3:post 1
sadd tag:4:post 1
sadd tag:5:post 1

# 获取文章的 tag
smembers post:1:tag

# 获取 tag 对应的文章
smembers tag:3:post

其他