Redis的基本原理
Redis是单线程程序,它快的原因是因为它是在内存中运行的,它的多路复用技术保证了保证了并发客户端的连接。
NIO
NIO又被称为Non_Blocking IO。对于普通的IO而言,在一个socket对象进行网络传输的读写时候,当读操作读取的byte数还没有达到读取的要求就会被阻塞住,直到新的数据来到或者等连接关闭。对于写操作,内核为写缓冲分配的内存满了,就会发生阻塞。
对于NIO而言,一次性能读多少就读多少,读完之后立即返回,写操作也是一样。但是这样如何保证数据读取和写入的完整性呢?这就用到了多路复用技术。
多路复用
其实从本质上而言就是,操作系统内核为程序分配了一个线程API叫做“select”,或者也linux中也有一种线程叫做“epoll”。它一只轮询着去看有没有新的时事件返回过来,有的话就赶紧通知redis的线程过来处理。
对多个客户端而言,多路复用中维护了一个指令队列,把客户端放进去,按顺序进行处理。
在轮询的同时,它们也会有timeout的一个属性,表示在一个事件等待的超时事件。这个时间的计算是动态的。是在定时任务中有个最小堆的数据结构。判断最先要执行的事件还需要多长时间,这个等待事件就是timeout时间。
持久化
Redis在持久化中,会使用操作系统的多进程COW(Copy On Write)来实现。
-
快照方式
快照持久化的方式是,Redis会调用glibc函数fork出一个子进程。所有的持久化工作完全由这个子进程完成,当子进程进行持久化的时候,仅仅是从内存中进行读操作,而客户端的写操作会复制出一份快照进行。这时候也就用到了操作系统的COW,进行页面分离,可以保证读写是分离的。
-
AOF方式
它记录了所有客户端操作的命令,当恢复的时候只需要执行这些命令即可。
AOF有两个特点,当Redis执行时间过长,会对AOF文件进行瘦身,会fork出一个子进程,然后遍历内存中的数据,然后形成新的AOF文件,同步到磁盘中,同时增量添加这段时间的添加指令。
AOF文件从内存放到磁盘上可以使用,Linux 的 glibc 提供了 fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。
Redis也可以使用混合持久化的方式,在实际运维中,持久化操作总是在从节点上进行的。但是Reids本来就不是强一致性的,所以无论采用什么方式,当机器突然宕机的时候总会丢失一些数据。
Redis的数据结构
String
string是动态字符串,小于1M的时候每次扩容都是翻倍的,大于1M每次扩容都是加1M。可以认为它是一个ArrayList结构,有个常见的用途是我们可以使用fastjson把用户结构体序列化成字符串形式,然后做缓存。
expire name 5 //可以用来设置过期时间5s
setnx name 1 /会先检测有没有name这个key,有则输入失败,没有则添加成功
list
redis中的list相当于Java中的LinkedList,它是链表结构。当使用的键值对过多的情况下, 会产生较多的内存碎片。所以list中有一种列表叫做快速列表,就是在内存足够的时候分配一串连续的内存,叫做ziplist。
hash
更hashmap的结构基本是类似的,只是rehash的方式是不一致的,Redis为了高性能,不阻塞服务使用的是渐进式rehash策略。当hash移除了最后一个元素,就会删除原来旧的数据结构。
set
set集合相当于Java中的HashSet,可以用来存储中奖用户,它的内部键值都是无序且唯一的。
- zset
可以实现key唯一且排序。它的结构是使用跳跃列表来实现的。因为排序就意味着要插入,通常的插入点都是使用二分法来实现的,但是二分法只能使用在数组上,所以redis提供了一种数据结构叫做跳跃列表,就类似于一个索引,从高层到底层,去查找。
Redis实现分布式锁
redis实现分布式锁最简单的方式是
setnx lock 1
expire lock 5
死锁问题
但是因为这两步操作并非原子性的,所以在Redis2.8之后提供了一种方式:
set lock:codehole true ex 5 nx
超时问题
给每个set操作的value值加一个随机数,当锁释放的时候先比较这个随机数是否一致,一致的话则进行del操作。
位图
位图的操作是setbit,getbit。可以指定一个string的ASCII码的位数。
HyperLogLog
使用pfadd和pfcount来提供不精确的统计方案,它可以用来统计日常的pv和uv。
布隆过滤器
在推荐算法中,过滤掉那些你看过的资讯。
bf.add test movie1
bf.exist test movie1
(integer) 1
bf.exist test movie2
(integer) 0
布隆过滤器的原理其实是一个大型的位数组和几个不一样的无偏hash函数。在每次添加key的时候, 利用hash函数算的位数组的索引,然后把这一位置为1。
简单的限流
限流的策略可以使用zset中的score数据结构,可以使用score的排序功能,划出一个时间窗口。简单的使用pipeline实现限流。
漏斗限流
可以使用开源插件redis-cell实现原子性操作
SCAN
1.通过游标分步进行的,不会阻塞线程
2.提供limit参数,可以控制每次返回结果的最大条数。
3.可以提供模式匹配功能
4.返回的结果可能会有重复,需要客户端去重
5.遍历的过程中,如果有数据的修改,改动过的数据有没有被遍历到是不知道的。
6.单次返回的结果为空并不意味着遍历的结束,而是要看返回的游标值是否为零。
网友评论