一、Redis数据类型介绍及常用指令及应用场景
1.1 String
1.2 hash
(实现与JAVA中的HashMap类似)
1.3 list
(可充当队列及栈使用,最常被用作队列)
在消息的可靠性要求不是很高的情况下可被用作消息队列来替换RabbitMQ
1.4 set
1.5 zset
(实现:跳跃表,介绍原理)
1.6 位图bitmap
(其实存储时也是按照字符串的存储格式,只是按位查找数据)
应用场景:统计用户登录的天数等等
1.7 HyperLogLog
(去重,统计网页有多少个用户浏览)
(1)算法原理
举一个常见的例子:每次抛硬币之后,出现正面和反面的概率分别为1/2,如果不停地抛硬币,直至出现正面为止,这就是一个伯努利过程。
这样,我们假设一共进行了n次伯努利过程,出现正面的次数分别为k1, k2, … kmax,那么有以下两个结论:
- n次伯努利过程的投掷次数都不大于kmax n次伯努利过程
- 至少有一次的投掷次数等于kmax
已知投掷k次才出现正面的概率为:1/2^k,那么:
第一种情况的概率为:
第二种情况的概率为:
如果n >> 2^k,则P(x >= kmax)为0;如果n << 2^k,则P(x <= kmax)为0。因此我们可以用2^k来作为n的近似估计结果。
接下来,详细的Hash、分桶及点数估计转移到此文章查看:
https://www.jianshu.com/p/55defda6dcd2
(2)算法大致流程
对于一个新的value:
- 通过hash函数计算输入值对应的比特串
- 比特串的低N位对应的数字用来找到连续零位的最大长度K
- 通过比特串高位计算该value所在的桶编号,K若大于此时的记录值,更新桶记录并通过计算所有桶的调和平均得到估计点的数目。
(3)HLL概率算法Demo
老外写的关于HLL算法及LLC算法运行过程的网页可视化程序,随机产生一个Value作为输入,并生成相应的Hash值,计算Hash对应的二进制后六位的十进制值作为该Value所在的桶编号,绿色标注的部分作为类伯努利过程结果,记录绿色部分倒序第一次出现1的位编号,若其大于Register Value表中其所在桶的记录最大值,更新该桶的记录,并重新估计所有出现点的个数。
http://content.research.neustar.biz/blog/hll.html
1.8 布隆过滤器(Bloom Filter)
redis缓存穿透:如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义。如果在大流量下数据库可能挂掉。这就是缓存击穿。
应用场景:在redis缓存查询之前加一层布隆过滤器,防止redis缓存穿透。首先通过Bloom Filter判断数据库中是否存在此数据,Bloom Filter判断为不存在,则数据必然不在数据库中,但Bloom Filter若判断存在,有一定几率(概率可设为很小)判断错误(即数据其实不在数据库中)。故通过Bloom Filter可滤除绝大多数无效数据。通过Bloom Filter的数据交由Redis及MySQL进一步查询。
介绍Bloom Filter原理
Bloom Filter参数设置计算器(输入预计元素的数量及错误率,输出最佳的Hash函数个数及所需内存空间大小)
https://krisives.github.io/bloom-calculator
二、分布式锁
https://baijiahao.baidu.com/s?id=1623086259657780069&wfr=spider&for=pc
redLock
三、Redis事务
Redis 提供的事务机制与传统的数据库事务有些不同,传统数据库事务必须维护以下特性:原子性(Atomicity), 一致性(Consistency), 隔离性(Isolation), 持久性(Durability),简称ACID。
而Redis事务不支持回滚,原子性支持不完整,所以一致性也无法保证。支持隔离性,持久性因为需要开启AOF持久化(每次操作都写),出于性能问题一般不会开启,故持久性基本上也不支持。
我们逐项考察下 Redis 在事务的 ACID 上做出的权衡与取舍:
原子性(Atomicity) 原子意味着操作的不可再分,要么执行要么不执行。Redis 本身提供的所有 API 都是原子操作,那么 Redis 事务其实是要保证批量操作的原子性。Redis 实现批量操作的原理是在一个事务上下文中(通过 MULTI命令开启),所有提交的操作请求都先被放入队列中缓存,在 EXEC 命令提交时一次性批量执行。这样保证了批量操作的一次性执行过程,但 Redis 在事务执行过程的错误情况做出了权衡取舍,那就是放弃了回滚。 Redis 官方文档对此给出的解释是:
- Redis 操作失败的原因只可能是语法错误或者错误的数据库类型操作,这些都是在开发层面能发现的问题不会进入到生产环境,因此不需要回滚。
- Redis 内部设计推崇简单和高性能,因此不需要回滚能力。
据实而说第一条说法感觉有点站不住脚,可以想象得到 Redis 操作失败的原因绝对不止语法层面的错误,特别是一些像依赖操作系统、文件系统的操作。第二条说法更实在,Redis 的应用场景明显不是为了数据存储的高可靠而设计的,而是为了数据访问的高性能而设计,设计者为了简单性和高性能而部分放弃了原子性。
出于以上考虑 Redis 的事务执行有以下特点:
- 批量操作在发送 EXEC 命令前被放入队列缓存
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中
关于Watch命令的一些说明:
Redis的基本事务(basic transaction)需要用到MULTI命令和EXEC命令,这种事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令。被NULTI命令和EXEC命令包围的所有命令会一个接一个地执行,直到所有命令都执行完毕为止。当一个事务执行完毕之后,Redis才会处理其他客户端的命令。
WATCH命令使得 EXEC 命令的执行必须满足一个条件:如果被WATCH的 keys 没有一个被更改(但它们可以在事务中被修改),则执行事务;不然,就不会执行这个事务。(注意,如果你 WATCH了一个有生命周期的key,并且这个key过期了, EXEC 依然会执行)。
在用户使用WATCH命令对键进行监视之后,直到用户执行EXEC命令的这段时间,如果有其他客户端抢先对任何被监视的键进行了替换、更新或删除等操作,那么当用户尝试执行EXEC命令的时候,事务将失败并返回一个错误(之后选择重试事务或者放弃事务)。
当Redis从一个客户端那里接收到MULTI命令时,Redis会将这个客户端之后发送的所有命令都放入到一个队列里面,直到这个客户端发送EXEC命令为止,然后Redis就会在不被打断的情况下,一个接一个地执行存储在队列里面的命令。为了减少Redis与客户端之间的通信往返次数,提升执行多个命令时的性能,Redis客户端会存储起事务包含的多个命令,然后在事务执行时一次性地将所有命令都发送给Redis。
由上分析我们可以知道Redis事务的数据被其他线程修改(并发修改)仅能出现在MULTI命令到EXEC命令入事务队列期间。因为所有的指令在exec之前是不执行的,而是依次缓存到服务器的一个事务队列中,服务器一旦收到exec才开始执行整个事务队列。所以在exec之前的指令入队之前,Redis完全可以执行其他客户端的命令,而其他客户端的命令有可能修改我们在本客户端事务中使用的数据。故我们在MULTI命令之前使用WATCH命令监视一些在事务中使用的数据,使得在命令入队的过程被其他客户端并发修改的数据能够在执行之前(EXEC指令要顺序执行缓存的事务队列时)被及时发现,此时EXEC指令就会返回NULL告知客户端事务执行失败,而这个时候客户端一般会选择重试。在exec命令之后,事务执行期间,其他客户端命令被严格禁止插入该事务,此时会保证隔离性。
下图展示了上面描述的一个实例:
在T4时间,客户端B修改了被监控的name,当客户端A在T5执行EXEC命令时,就拒绝执行整个事务返回nil。
四、Redis的线程IO模型
https://blog.csdn.net/programmer_acu/article/details/52447463
五、Redis通信协议
RESP(Redis序列化协议:Redis Serialization Protocol)