Redis
持久化
持化双雄
RDB(Redis DataBase)
AOF(Append Only File)
RDB
(默认开启)
RDB持久性以指定的时间间隔执行数据集的时间点快照
实现类似照片记录效果的方式,就是把某一时刻的数据和状态以文件的形式写到磁盘上,也就是快照。 这样一来即使故障宕机,快照文件也不会丢失,数据的可靠性也得到了保证。 这个快照文件就称为RDB文件(dump.rdb), 其中,RDB就是Redis DataBase的缩写。
小技巧
物理恢复,一定要服务和备份分机隔离, 不可以把备份文件dump.rdb和生产redis服务器放在同一台机器,必须分开各自存储,以防生产物理机损坏后备份文件也挂了。
手动触发
Redis提供了两个命令来生成RDB文件,分别是save和bgsave
SAVE
在主程序中执行会阻塞当前redis服务器,直到持久化工作完成
执行save命令期间,Redis不能处理其他命令,线上禁止使用
BGSAVE
Redis会在后台异步进行快照操作,不阻塞快照同时还可以响应客户端请求,该触发方式会fork一个子进程由子进程复制持久化过程
LASTSAVE
可以通过lastsave命令获取最后依次成功执行快照的时间
除了缓存,redis的其它用法
数据共享, 分布式session
分布式锁
全局ID
计算器、点赞
位统计
轻量级消息队列
抽奖
点赞、签到、打卡
差集交集并集,用户关注、可能认识的人,推荐模型
热点新闻、热搜排行榜
计数器
def test_counter(self, c):
"""场景3: 计数器
https://redis.io/commands/incr/
在双十一或者其他大型节日中,业务服务器的资源有限,但是在高并发的场景下,过多的请求可能会导致服务器宕机等问题,
所以会对接口请求做一些限制,比如限制每秒请求总数为200次,超过200次就等待,等下一秒再次请求,这里用Redis作为计数器
的模式来实现。
"""
from datetime import datetime
# 1) 首先在接到请求之后设置一个键,然后自增1,如果不存在,则值会被初始化为0。
# 2) 当接口得到请求后,执行incryyyyMMddHHmmss:mykey, 如果返回结果小于200则进行处理,超过200将等待下一秒处理。
now = datetime.now().strftime("%Y%m%d%H%M%S")
key = f'{now}:mykey'
assert c.incr(key) == 1
assert c.incr(key) == 2
assert c.incr(key) == 3
assert c.incr(key) == 4
# 3) 因为每秒会生成一个键, 为了节省内存空间,可能需要一个定时任务,定时删除这些已经使用过的键。
c.delete(key)
全局ID
def test_unique_id(self, c):
"""场景4: 唯一自增的ID或者流水号
在有些业务中,为了提高数据库的读写能力,可能会将一个库根据业务拆分成多个小库、将一个大表拆分成几个小表,
比如user01表、user02表,但是又要保证两张表中的ID是全局统一自增的,所以数据库自带的自增属性是无法使用的。
这时可以利用Redis的String数据类型实现自增。
incrby: https://redis.io/commands/incrby/
"""
# Redis单机的性能是很不错的,但如果想拓展Redis的服务器,可以这样实现:
# 假设目前有5台Redis服务器, 第一台的生成方式
assert c.incrby("id", 1) == 1
assert c.incrby("id", 5) == 6
assert c.incrby("id", 5) == 11
assert c.incrby("id", 5) == 16
assert c.incrby("id", 5) == 21
c.delete("id")
# 第2台的生成方式
assert c.incrby("id", 2) == 2
assert c.incrby("id", 5) == 7
assert c.incrby("id", 5) == 12
assert c.incrby("id", 5) == 17
assert c.incrby("id", 5) == 22
c.delete("id")
# 第3台的生成方式
assert c.incrby("id", 3) == 3
assert c.incrby("id", 5) == 8
assert c.incrby("id", 5) == 13
assert c.incrby("id", 5) == 18
assert c.incrby("id", 5) == 23
c.delete("id")
# 第4台的生成方式
assert c.incrby("id", 4) == 4
assert c.incrby("id", 5) == 9
assert c.incrby("id", 5) == 14
assert c.incrby("id", 5) == 19
assert c.incrby("id", 5) == 24
c.delete("id")
# 第5台的生成方式
assert c.incrby("id", 5) == 5
assert c.incrby("id", 5) == 10
assert c.incrby("id", 5) == 15
assert c.incrby("id", 5) == 20
assert c.incrby("id", 5) == 25
# 更优秀的实现方式: 雪花算法
文章阅读数或者网页浏览数统计
def test_statistics(self, c):
"""场景5: 文章阅读数或者网页浏览数统计
用户的总点赞数、关注数、粉丝数、帖子的评论数、热度、文章的阅读数和收藏数等,
实现这些需求并想要减轻数据库压力,而且对数据的时效性和写文章的频率要求高时,可以使用Redis。
"""
# 文章第一次被阅读
c.incr("mypageurl")
# 查看文章的阅读数量
assert c.get("mypageurl") == b'1'
# 访问时每次都自增1
c.incr("mypageurl")
分布式锁
def test_distributed_lock(self, c):
"""场景6: 分布式锁
分布式系统可以借助于Redis中的setnx命令来实现分布式锁。
流程: 如果键没有值就能新增成功,就表示抢到锁了,否则失败。
等到操作成功时释放锁,也就是删除键。
"""
# 给键seckill001赋值,如果之前不存在, 则创建
assert c.setnx("seckill001", "true")
# 设置过期时间为20毫秒, 解决死锁问题
c.expire("seckill001", 20)
# 当其他客户端进来之后执行如下命令,返回False则表示锁被占用,只能后期继续尝试再次执行
assert c.setnx("seckill001", "true") is False
# 当第一个抢到锁的线程执行完业务之后,便可以删除键,让其他线程能够抢到锁
c.delete("seckill001")
# 其他线程执行如下命令时返回的结果是1,表示拿到了锁
assert c.setnx("seckill001", "true")
分布式锁
一个靠谱分布式锁需要具备的条件和刚需
独占性: OnlyOne, 任何时刻只能有且仅有一个线程持有
高可用: 1-若集群环境下, 不能因为某一个节点挂了而出现获取锁和释放锁失败的情况; 2-高并发请求下, 依旧性能OK好使
防死锁: 杜绝死锁, 必须有超时控制机制或者撤销操作,有个兜底终止跳出方案
不乱抢: 防止张冠李戴, 不能私下unlock别人的锁, 只能自己加锁自己释放。
重入性: 同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。
实现
中小厂代码
127.0.0.1:6379> setnx k1 true
127.0.0.1:6379> EXPIRE k1 60
大厂差评, setnx + expire不安全, 两条命令非原子性的。
可重入锁
是指在同一个县城在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提, 锁对象得是同一个对象),不会因为之前已经获取得过还没释放而阻塞。
核心是计数器,lock了几次就要unlock几次
用redis的hset字典计数
key=zzyyLock
field=uuid:threading_id
value=lock的次数