String、hash、list、set、sorted_set/zset
hash 底层是hash表实现的数据存储。
list 能存储多个数据,按照插入顺序排序,底层使用双向链表实现。
set 和hash存储结构完全相同,仅存储键值,不存储value。且键值不允许重复。存储大量数据,且快速查询。
zset 是字典(dict) + 跳表(skiplist),数据比较少的时候用ziplist编码结构存储。是有序集合,不允许有重复的数据结构,每一个元素关联double类型分数。通过分数对集合中元素从小到大排序。成员唯一的,分数不唯一的。
string 微博上大v的访问量存储。
hash 商品的抢购可以用,店铺的id为key,商品id为filed,数量为value。
list 对有顺序的信息管理。
set 用于黑白名单。
zset底层就使用了跳跃表,是由多层多链表实现。
在进行数据插入时候,先插入底层,然后利用算法(类似抛硬币算法),如果正面插入l2层,然后继续利用算法计算是否还继续向上层插入,当判断不需要插入,元素就插入结束。
删除元素,从上到下找到元素,找到删除,一直到最底层。
跳跃表在查询时候速度很快,因为在跳跃表中每个列表都是有顺序的。从上层往下查找,每一层查询到查询结束,如果比某一个节点大向右边,比一个节点大向左边。然后一层层查询。
是集合(set)的底层实现之一,当一个集合(set)只包含整数值元素,并且这个 集合的元素不多时,Redis就会使用整数集合(intset)作为该集合的底层实现。
压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表只包含少量列表项时,并且每个列 表项时小整数值或短字符串,那么Redis会使用压缩列表来做该列表的底层实现。 压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数 据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整 数值。
好处:获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。
Redis中是不支持回滚的,可以使用lua脚本执行redis条命令。
是一种用于保存键值对的抽象数据结构。字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改。redis地址底层是hash表实现。
可以做队列,也可以做分布式锁
点赞和关注使用String数据类型。热搜榜单排行榜,使用zset数据类型。
两种方式持久化:RDB,记录二进制数据,即快照方式。AOF,记录读写操作过程,即日志持久化。
RDB进行持久化,发送指令,调用fock函数创建一个子线程,进行持久化,生成一个二进制文件。返回信息。
AOF进行持久化,发送指令,调用fock函数创建一个子线程,从写aof文件,响应信息。
即持久化就是创建一个子线程来进行。
RDB(快照),可以手动调用持久化,也可以配置自动化持久化。根据一个时间段内变化的频率来进行持久化。持久化速率快。
AOF(快照),持久化方式三种,每次、每秒、系统控制。持久化相对慢点,但是有效防止数据丢失。
如果数据十分敏感,采用AOF持久化,如果想要快速数据恢复,采用RDB。
会执行AOF的重写,无效指令直接忽略,有效指令但是已经过期了不写入AOF中,对同条数据操作指令合并一条。
过期数据是一块独立内存,filed是value数据内存地址,value是过期时间。进行过期处理时候,对时间进行检测,找出过期的对应的地址,然后进行操作。
删除策略有:定时删除(过期删除)、惰性删除(用到时删除)、定期删除(随机选若干个,对过期数据删除。如果过期数据比例大于阙值循环操作)。
在redis执行命令时候都会检查内存是否充足,如果不充足将要删除一些数据叫内存淘汰。淘汰策略为:
检查易失数据、检查全库数据、放弃数据驱逐。
分为三个阶段:建立连接、数据同步、命令传播。
建立连接
建立slave到master连接,使master能识别slave,并保存slave端口。
数据同步
复制master全部数据到slave,请求同步数据,创建RDB同步数据,在slave恢复RDB数据,即全量复制。master发送在RDB同步过程的数据到slave,同步数据完成,复制缓冲区数据,部分复制。
命令传播
master接受到数据变更指令,发送给slave。slave执行接受指令。
命令传播过程中的部分复制
slave向master发送心跳,携带runid和复制偏移量。master对主节点的runid和携带过来runid进行比对,如果不对进行全量复制。如果匹对成功比较偏移量,如果偏移量不一样,进行部分复制。
哨兵是实现redis高可用的,监控集群的主从变化,进行故障转移。
哨兵节点监控
三个定时任务:如下
第一个定时任务,每隔十秒钟,每个sentinel节点向redis节点发送信息。向主节点发送info,获取最新的salve信息。当有新的节点能立刻感知到。完成故障转移,通过info命令完成新的拓扑结构。
第二个定时任务,每隔两秒,每个sentinel节点向sentinel:hello频道发送当前节点信息和对主节点的判断。所有节点都订阅这个频道,了解其他节点信息和其他节点对主节点的判断。发现新的sentinel节点,将新的sentinel节点信息保存并建立连接。sentinel交换主节点判断的状态,做为后面客观下线和主节点选举的依据。
第三个定时任务,每隔1秒,每个sentinel会向其他sentinel节点和redis节点发送ping指令,判断当前节点是否可达,检查每隔个节点的健康状态。
故障转移过程
当主服务器故障不能工作时候(哨兵对当前主节点做了主观下线),sentinel会自动做一次故障转移,从主节点的从节点选择做为主节点(哨兵选举新leader),然后从节点和原来主节点从新复制新节点数据。客户端连接老的主节点时候,集群向客户端返回新的主节点。
主观下线:一个哨兵判断当前主节点宕机。
客观下线:多个哨兵判断一个主节点宕机了,并且多个哨兵交流后对主节点做出宕机判断。
使用Canal监听数据库指定表的增量变化,在Java程序中消费Canal监听到的增量变化,并在Java程序中实现对Redis和Nginx缓存更新。
同一个时间多个调用方加锁竞争,只有一个调用方加锁成功。
在加锁的时候,放入唯一标识(一般为线程id)。在解锁时候判断传入的值和加锁时候唯一标识是否一样。如果是一样可以解锁,不一样不能解锁。
因为释放锁会有很多人同事操作,但是我们在释放锁时候,先查询比对,是同把锁进行释放。但是这是两步操作,redis中我们无法保证原子性,因此采用lua脚本完成查询删除。
因为业务执行时间和很多因素有关,无法判断业务执行时间。如果业务还没执行时候,锁过期了。会有其他线程获取锁,执行业务,无法保证原子性。解决方案,在添加锁时,添加一个守护线程,开启一个定时任务每隔一段时间对于未释放锁增加过期时间。任务执行完,释放锁,关闭守护线程。解决锁续期。
getLock(lockKey)获得锁对象。
lock(10, TimeUnit.SECONDS),设置锁失效时间。
tryLock(),使用看门狗策略。对于未设置过期时间的锁,起一个线程在锁快要过期,对锁不停添加过期时间(默认30秒)。保证任务结束之前锁有效。
unlock(),释放锁。
获得锁(tryLock):
检查key是否被占用,没有设置key和唯一标识初始值为1,并设置过期时间。
被占用,检查下value是否也匹配,如果匹配表示当前线程持有锁,重用次数+1,设置过期时间。
返回key失效时间。
释放锁
判断key和线程id标识是否匹配,如果步匹配,锁已经占用,直接返回。
如果当前线程持有锁,value-1,进入重入操作。大于0,设置过期时间。小于0直接删除key,并发布锁释放消息,通知其他线程申请锁。
看门狗实现原理
根据过期时间、时间单位、线程id获取锁,如果设置了锁的过期时间,直接创建锁。如果没有设置锁的过期时间,把永不过期设置为默认30秒过期,并且创建异步任务,如果没有获取到锁,则什么都不做。如果获取到了锁,则创建一个线程对当前线程id的锁进行延时。
Redisson是分布式锁的第三方类库,对各种锁都有实现,我们只需要调用api就行。解决了锁续期问题和单机问题。
Redisson中的红锁解决了上述问题,如下
获取所有节点redis中的锁(获取锁时间远小于锁释放时间,避免客户端等一个已经关闭客户端),在获取之前先看看获取锁时间是否大于失效时间(获取当前锁是否已过期),没有过期,检查是否有半数以上的redis中获取锁。是获取锁成功。不是,获取锁失败。然后redis节点释放获取失败锁。获取失败后,可以在一定时间尝试获取锁,设置尝试次数。
要开启AOF持久化方案
防止所有节点挂掉,锁信息丢失,导致多线程同时执行。设置持久化时间为1秒。在一秒重启仍然会有数据丢失。
支持应用程序将一个或者多个fd交给内核,内核检测fd上的状态变化(连接读写等)。因为应用程序对磁盘读、网络数据读写都是通过内核获取访问权限的。
epoll网络通信模型
select通信模型(IO多路复用)
应用进程自己维护fd set集合,然后调用select将fd集合传递给内核,内核检测就绪的fd,返回给应用进程,应用进程进行事件信息处理。应用程序循环调用select系统函数。
划分了主线程和多个IO线程,Io线程负责网络连接,命令执行还是主线程来执行。多线程是默认关闭的。如果要使用,修改配置打开多线程,配置线程数,建议线程数小于内核数。
用户查询数据,先查询缓冲没有查询到数据,然后再查询数据库也没有查询到数据。用户反复刷新查询。
第一种解决方案:查询缓冲没有查询到数据,然后查询数据库也没查询到数据。将null作为value存储到缓存中。当下次查询直接将null返回。
第二种解决方案:在查询数据时候,先查询布隆过滤器查询。如果查询到再去缓存种查询,如果没有查询到直接返回。
布隆过滤器介绍:大规模数据下不需要精准过滤场景。布隆过滤器含有一个数组两个hash函数,一个数经过两个hash函数计算后产生两个数,放在数组中。当有一个新的数m1,经过两个hash函数计算出两个数有一个在集合中证明m1在布隆过滤器中。
缓存过期,但是此时有大量的请求访问key。导致大量请求查询数据库。
方案一:设置一个定时器,主动更新缓存。
方案二:多级缓存,在redis存储数据设置永不过期,用户查询数据时候,先去nginx去查询数据,如果有直接返回。没有查询redis数据,并将redis中数据放到nginx中,并设置过期时间。
方案三:Nginx缓存队列术,多个客户端请求一个缓存中不存在的文件。只允许第一个请求发送到服务端,其他请求在缓存中取到取到信息。
大量的缓存失效,导致大量请求查询数据库。
解决方案:多级缓存、缓存预热。