Redis ********************************************** :个人仓库: https://gitee.com/luzhenxiong/bilibili-database/tree/master/redis 持久化 ======================================= 持化双雄 * RDB(Redis DataBase) * AOF(Append Only File) RDB --------------------------------------- (默认开启) RDB持久性以指定的时间间隔执行数据集的时间点快照 实现类似照片记录效果的方式,就是把某一时刻的数据和状态以文件的形式写到磁盘上,也就是快照。 这样一来即使故障宕机,快照文件也不会丢失,数据的可靠性也得到了保证。 这个快照文件就称为RDB文件(dump.rdb), 其中,RDB就是Redis DataBase的缩写。 .. tip:: 物理恢复,一定要服务和备份分机隔离, 不可以把备份文件dump.rdb和生产redis服务器放在同一台机器,必须分开各自存储,以防生产物理机损坏后备份文件也挂了。 手动触发 ------------------------------- Redis提供了两个命令来生成RDB文件,分别是save和bgsave SAVE ~~~~~~~~~~~~~~~~~~~~~~~~ 在主程序中执行会阻塞当前redis服务器,直到持久化工作完成 执行save命令期间,Redis不能处理其他命令,线上禁止使用 BGSAVE ~~~~~~~~~~~~~~~~~~~~~~~~~ Redis会在后台异步进行快照操作,不阻塞快照同时还可以响应客户端请求,该触发方式会fork一个子进程由子进程复制持久化过程 LASTSAVE ~~~~~~~~~~~~~~~~~~~~~~~~~~ 可以通过lastsave命令获取最后依次成功执行快照的时间 除了缓存,redis的其它用法 ======================================== 1. 数据共享, 分布式session 2. 分布式锁 3. 全局ID 4. 计算器、点赞 5. 位统计 6. 轻量级消息队列 7. 抽奖 8. 点赞、签到、打卡 9. 差集交集并集,用户关注、可能认识的人,推荐模型 10. 热点新闻、热搜排行榜 计数器 ---------------------------- .. code-block:: python 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 ------------------------------------ .. code-block:: python 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 # 更优秀的实现方式: 雪花算法 文章阅读数或者网页浏览数统计 ----------------------------------------------------- .. code-block:: python def test_statistics(self, c): """场景5: 文章阅读数或者网页浏览数统计 用户的总点赞数、关注数、粉丝数、帖子的评论数、热度、文章的阅读数和收藏数等, 实现这些需求并想要减轻数据库压力,而且对数据的时效性和写文章的频率要求高时,可以使用Redis。 """ # 文章第一次被阅读 c.incr("mypageurl") # 查看文章的阅读数量 assert c.get("mypageurl") == b'1' # 访问时每次都自增1 c.incr("mypageurl") 分布式锁 --------------------------------------- .. code-block:: python 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别人的锁, 只能自己加锁自己释放。 * 重入性: 同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。 实现 -------------------------- 中小厂代码 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: console 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的次数 后端项目踩坑 ============================ * `go-zero的redis只能用db0 `_