第一章:《Redis深度历险:核心原理与应用实践》读书笔记

在这里插入图片描述

Redis应用场景:

1、点赞数,点击数,评论数(hash
2、帖子Id排序,快速现实帖子列表(zset
3、帖子标题、摘要、作者和封面信息,用户列表页展现(hash
4、点赞用户Id列表,评论Id列表,用来显示和去重计数(zset
5、缓存近期热帖内容(帖子的内容和空间占用比例大),减少数据库压力(hash
6、记录帖子相关文章Id,根据内容推荐相关帖子(list
7、如果帖子Id是整数自增的,可用Redis来分配帖子Id(计数器)
8、收藏集和帖子之间的关系(zset
9、记录热榜帖子Id列表,总热榜和分类热榜(zset
10、缓存用户行为历史,过滤恶意行为(zsethash

5种基础数据结构:

1、String字符串

打个比方:用户信息结构体使用Json序列化为字符串,是一个可修改的字符串,内部结构是实现类似于ArrayList,采用预分配冗余空间的形式来减少内存的频繁分配。当字符串长度小于1MB时,扩容为倍增。如果字符串长度超过1MB,扩容时一次只会扩1MB的空间。字符串最大长度为512MB。

2、list列表

Redis的列表相当于Java里的LinkedList,意味着list的插入和删除操作非常快,时间复杂度为O(1),索引位置就很慢,时间复杂度为O(0)。
Redis列表结构常用来做异步队列使用。

右边进,左边出,队列

rpush
lpop

右边进,右边出,栈

rpush
rpop

【快速列表】
quicklist = n*ziplist = ziplist+ziplist+…+ziplist
在列表元素较少的情况下,会使用一块连续的内存存储,这个结构是ziplist,即压缩列表。它将所有的元素彼此紧挨着一起存储,分配的是一块连续的存储空间。当数据量较多的时候才会改为quicklist,因为普通的链表需要的附加指针空间太大,浪费空间,会加重内存的碎片化。

3、hash(字典)

Redis字典相当于Java里的HashMap,无序字典,内部存储了很多键值对,但是Redis的hash字典只能是字符串。
当hash移除了最有一个元素后,该数据结构被自动删除,内存被回收。
hash缺点:hash结构的存储消耗要高于单个字符串。

4、set

Redis的set相当于Java里的HashSet,无序且唯一,有去重功能。
当set中最后一个元素被移除后,数据结构被自动删除,内存被回收。

5、zset【重点】

具有set属性,无序且唯一,zset:value,score,score表示value的排序权重
zset中最后一个value被移除后,数据结构被自动删除,内存被回收。
【跳跃列表】
zset内部的排序功能是通过“跳跃列表”数据结构实现的。因为zset要支持随机的插入和删除,不宜使用数组来表示。
zset根据score值进行排序,具体5.5节

分布式锁:【重点】

一、分布式锁的意义
功能:使用分布式锁来限制程序的并发执行。
目标:占坑
指令:setnx(set if not exists)指令,只允许被一个客户端占坑。先来先占,用结束后使用del释放坑。
会发生的问题:del指令没吊用,陷入死锁,所永远不能释放。所以,在拿到锁之后,再给锁加上一个过期时间。

setnx  name true
expire name 5
del name

但同样造成死锁情况:如果setnx之后服务器进程挂了,那么又成了死锁
所以要保证setnx和expire的原子性:

set name true ex 5 nx

set xxxxxxxxxxxxx nx
xxxxxxxxxxxxx = name true ex 5

二、超时问题
Redis的分布式锁不能解决超时问题:第一个线程持有的锁过期了,临界区的逻辑没有执行完,第二个线程提前持有了这把锁,导致临界区代码不能得到串行执行。为了避免这个问题,Redis分布式锁不能用较长时间的任务。
方案:(不完美)set指令的value参数设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除key,保证当前线程占有的锁不会被其他线程释放,除非这个锁是因为过期了而被服务器自动释放的。

延时队列

一、介绍
Redis消息队列不是专业的消息队里,消息不是十分可靠。

二、异步消息队列-list
list,右进左出,左进右出,rpush→lpop,lpush→rpop
三、列队空了怎么办
客户端会陷入pop死循环,不停pop,没有数据,继续pop……空轮询
空轮询:拉高客户端的CPU消耗,Redis的QPS也会被拉高,如果空轮询的客户端有几十个,Redis的慢查询会显著增多。
通常使用sleep来解决,让线程睡1s,就是1000毫秒,Thread.sleep(1000),CPU消耗会降低,Redis的QPS也会降低。
但是,sleep一秒会导致一个问题:阻塞读
四、阻塞读
睡眠会导致消息的延迟增大,如果有多个消费者,这个延迟会有所下降,因为每个消费之的睡眠时间是岔开的。阻塞读 blocking -》 blpop / brpop
阻塞读在队列没有数据的时候,会立即进入休眠状态【阻塞了】,一旦数据到了,就会醒过来。消息的延迟几乎为0。用blpop 和 brpop完美解决了上面的问题。
上面 的方法仍旧存在问题:空闲连接的问题
五、空闲连接问题–》造成异常
如果线程一直阻塞在那里,Redis的客户端连接就成了闲置连接,服务器很少主动断开连接,占用资源将减少。使用blpop和brpop会抛出异常。
六、客户端加锁失败问题–3种策略
1、抛出异常,稍后重试
2、sleep一会儿,再重试
3、请求转至延时队列,稍后重试

1–》本质是对当前请求的放弃,由用户决定是否发起新的请求
2–》会造成死锁
3–》适合异步消息处理,将当前冲突的请求扔到另一个对队列后处理以避开冲突

七、延时队列的实现
可通过Redis的zset(有序列表)来实现,将消息序列号为一个字符串作为zset的value,将消息的到期处理时间作为score,然后用多个线程轮询zset获取到期的任务进行处理
多线程是为了保证可用性,万一一个线程挂了,其他线程还可以继续处理。正因为有多个线程,所以需要考虑并发争抢任务,确保任务不会被多次执行。

此处应有代码敲打

位图

位图类似于数组,可用get/set直接获取设置整个位图的内容,也可使用getbit/setbit等将byte数组看成“位数组”来处理

  • 基本用法:零存整取、整存零取
  • 统计和查找
  • 指令bitfield:饱和阶段sat、失败不执行fail

HyperLogLog

1、定义:
提供不精确的去重计数方案,虽然不精确,标准误差0.81%,是redis的高级数据结构。
2、方法:
pfadd:增加计数
pfcount:获取计数
pfmerge:将多个pf计数累加在一起形成一个新的pf值

3、pfadd中的pf是什么?数据结构发明人Philippe Flajolet的首字母缩写!

4、注意事项
每个HyperLogLog都占12kb存储空间,当有上亿个用户,所占空间会很大。
但是redis对HyperLogLog进行了优化,在计数较小时,它的存储空间采用稀疏矩阵存储,空间所占很小,只有当计数慢慢变大超越了阈值时候,才会一次转变成稠密矩阵,占用12kb空间。

布隆过滤器

1、用途:解决去重问题
解决去重问题,空间上节省90%。
2、判断对象是否存在:
过滤器:数值存在
真实:数据可能不存在
过滤器:数值不存在
真实:数据不存在
3、基本用法
bf.add:添加元素
bf.exists:判断一个元素是否存在
bf.madd:添加多个元素
bf.mexists:判断多个元素是否存在

bf.add name1 value1
bf.exists name1 value1

bf.madd name1 value1 value2 value3
bf mexists name1 value1 value2 value3 value4

布隆过滤器对于已经见过的元素不会发生误判,只会误判没有见过的元素。
4、注意事项
布隆过滤器的initial_size设置的过大,会浪费存储空间,设置的过小,会影响准确率。
布隆过滤器的error_rate越小,需要的存储空间就越大。

Redis-Cell

限流使用漏斗限流算法。
指令cl.throttle
应用场景:用户回复行为频率每60秒30次,漏斗初始容量为15,然后收到漏水速度的影响。

cl.throttle userzz:reply 15 30 60 1

cl.throttle userzz:reply 15 30 60

指令会返回指令状态(0或1),漏斗容量,漏斗剩余空间,拒绝重试时间,漏斗空耗费时间。
可以直接返回数组的第四个值进行sleep,若不想阻塞线程,可以异步定时任务了重试。

GeoHash算法(粗略)地理位置Geo模块

其实就是zset结构
Geo指令–6个

  • geoadd 添加
  • geodist 距离
  • geopos 获取元素位置
  • geohash 获取元素hash
  • georadiusmumber 查询元素附近的元素
  • georadius 根据坐标值查询附近的元素

Keys&Scan(粗略)

1、keys的缺点:

keys算法是遍历算法,时间复杂度是O(n),如果实例中有千万级别以上的key,这个指令会导致Redis服务卡顿,所有读写Redis的其他指令都会被蔓延后甚至会超时报错,因为Redis是单线程程序,所有读写Redis的其他指令都必须等到keys执行完了才可以继续。

2、scan:
  • 复杂度也是O(n),但是通过游标分步进行的,不会阻塞线程
  • 提供limit参数,返回控制结果最大值
  • 提供模式匹配功能
  • 服务器不需要游标状态,scan的游标以整数给服务器
  • scan不提供去重,服务器端去重
  • 遍历过程中数据修改,不会被遍历到修改
  • 单词返回的结果为空不意味着遍历结束,要看游标值是否为0
3、scan的遍历:高位进位加法
4、渐进式rehash

为啥有rehash?
Java的HashMap在扩容时会一次性将旧数据下挂接的元素全部转移到新的数组下。如果HashMap中元素特别多,线程就会出现卡顿。
rehash的功能:
同时保留旧数组和新数组,在定时任务中以及后续hash的指令操作渐渐将就数据挂接的元素迁移到新数组中。【这意味着rehash的字典需要同时访问新旧两个数据结构。如果在旧数组下面找不到元素,还需要去新数组下面寻找】

zscan遍历zset集合元素
hscan遍历hash字典元素
sscan遍历set集合元素

业务开发中尽量避免key的使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值