java理论知识之Redis

一、什么是Redis

Redis 是一个使用 C 语言写成的,开源的高性能key-value非关系缓存数据库。
它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。
Redis的数据都基于缓存的,所以很快,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
Redis也可以实现数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。


二、为什么要用Redis/为什么要用缓存

(1)高性能:直接从内存中读取数据
(2)高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去。
(3)结合实际:传统关系型数据库关系复杂是读取很慢,频繁更新的数据放在关系型数据库性能损耗严重(状态,统计,心跳)
(4)使用redis的推送订阅
(5)用redis实现分布式锁


三、Redis为什么这么快

完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。树结构类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU
使用基于epoll 的多路复用,I/O 多路复用功能来监听多个 socket 连接客户端,这样就可以使用一个线程来处理多个情况,同时避免了 I/O 阻塞操作
主要的性能瓶颈是内存或者网络带宽,而并非 CPU


四、版本变化(升级多线程)

Redis 在 4.0 以及之后的版本中引入了惰性删除(也叫异步删除)这是由额外的线程执行的
好处就是不会使 Redis 的主线程卡顿,会把这些删除操作交给后台线程来执行

6.0引入多线程
Redis 会通过系统调用将数据从内核态拷贝到用户态,供Redis解析用,这个拷贝过程是阻塞的,术语称作“同步 IO”,这是 Redis 目前的瓶颈之一。Redis6.0 引入的“多线程”机制就是对于上诉瓶颈的优化,核心思路是,将主线程的 IO 读写任务拆分出来给一组独立的线程执行,使得多个 socket 的读写可以并行化。

Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑并发及线程安全问题。
IO线程要么同时在读 socket,要么同时在写,不会同时读或写

参考
https://ruby-china.org/topics/38957%EF%BC%89
https://zhuanlan.zhihu.com/p/76788470
https://www.cnblogs.com/mumage/p/12832766.html
https://www.cnblogs.com/traditional/p/13273089.html


五、Redis有哪些数据类型

Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求


六、Redis线程IO多路复用

多路指的是多个socket连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)。


七、redis分布式锁-java实现

为了保证多台服务器在执行某一段代码时保证只有一台服务器执行
要求:
①互斥性。在任何时刻,保证只有一个客户端持有锁,并且上锁和解锁都是同一个客户端
②具备可重入特性
③不能出现死锁。如果在一个客户端持有锁的期间,这个客户端崩溃了,也要保证后续的其他客户端可以上锁。
④高可用,高性能地获取锁与释放锁
⑤具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

解决方案
①上锁时设置过期时间(redis自带)
②自定义监视线程,超时释放上锁
③合理规划上锁粒度,让上锁执行率和申请锁失败率达到平衡状态


八、Redis锁分类

不可重入锁 :即若当前线程已经获取了该锁,那么尝试再次获取锁时,就会获取不到被阻塞。 同一个人拿一个锁 ,只能拿一次不能同时拿2次

可重入锁:也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层函数仍然可以获取到该锁。防止在同一线程中多次获取锁而导致死锁发生

常用可重入锁,如java自带的ReentrantLock,redis的Redisson,Redisson是Redis官方推荐的Java版的Redis客户端,简单例子如下;

SingleServerConfig serverConfig 
=config.useSingleServer().setAddress(node).setTimeout(3000).setConnectionPoolSize(10).setConnectionMinimumIdleSize(10);
RLock rLock = redissonClient.getLock("666");

通过redis的setnx方式(不存在则设置),往redis上设置一个带有过期时间的key,如果设置成功,则获得了分布式锁

setIfAbsent方法,就是当键不存在的时候,设置,并且该方法可以设置键的过期时间。可重入锁
Boolean success = redisTemplate.opsForValue().setIfAbsent("lockKey", "lockValue", millisecond, TimeUnit.MILLISECONDS);//设置锁
String lockValue = redisTemplate.opsForValue().get("lockKey")//获取锁的值
redisTemplate.delete("lockKey");//释放锁

【1】redis分布式锁-可重入锁 https://www.cnblogs.com/x-kq/p/14801527.html
【2】https://www.cnblogs.com/happy4java/p/11205993.html
【3】https://blog.csdn.net/hfaflanf/article/details/110930310


九、Redis持久化

Redis提供两种持久化RDB(默认)和AOF

(1)RDB

即Redis DataBase,是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb

优点:

①只有一个文件 dump.rdb,方便持久化
②性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化
③相对于数据集大时,比 AOF 的启动效率更高

缺点:

①数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。

(2)AOF

即Append-only file,是指所有的命令行记录通过write函数追加到文件中,保存为 aof 文件,通俗的理解就是日志记录。
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。

AOF重写:

①将整个内存中的数据库内容用命令的方式重写了一个新的aof文件
②创建重写子进程开始的那一刻起,把后面来的写入命令也copy一份写到这个重写缓冲区中(原来的旧文件的日志记录保持正常工作)
③子进程重写AOF文件结束之后,再把这个缓冲区中的命令写入到新的AOF文件中
④最后再重命名新的AOF文件,替换旧的AOF文件

优点:

①数据相对安全,AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据
②通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题
③文件可读性强,写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松

缺点:

①AOF 文件比 RDB 文件大,且恢复速度慢,启动效率低
②AOF 出现过恢复跟之前不一样的bug,相比较 RDB 几乎是不可能出现这种 bug 的。

【1】https://blog.csdn.net/JavaTeachers/article/details/108998121
【2】https://blog.nowcoder.net/n/f201a802357b48fcaa5e2ef43718dbaa
【3】https://baijiahao.baidu.com/s?id=1654694618189745916&wfr=spider&for=pc


十、Redis的过期键的删除策略

过期策略通常有以下三种:

(1)立即过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

(2)惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

(3)定期过期

每隔一定的时间,会扫描 expires(保存所有设置了过期时间的key)字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果
Redis中同时使用了惰性过期和定期过期两种过期策略。


十一、Redis中的事务

Redis中的事务是可以视为一个队列,即我们可以通过MULTI开始一个事务,这相当于我们声明了一个命令队列。接下来,我们向Redis中提交的每条命令,都会被排入这个命令队列。当我们输入EXEC命令时,将触发当前事务,这相当于我们从命令队列中取出命令并执行,所以Redis中一个事务从开始到执行会经历开始事务、命令入队和执行事务三个阶段。

就算有命令失败,队列中的其他命令也会被执行
Redis命令在事务中可能会执行失败,但是Redis事务不会回滚,而是继续会执行余下的命令(只有当发生语法错误,都不执行,类似回滚)

分编译期异常和运行期异常,编译期异常,事务不执行,勉强算是原子性,运行期异常,继续执行,不具备原子性。只保证ACID中的一致性和隔离性

Jedis jedis = new Jedis("localhost");
Transaction transaction = jedis.multi();
transaction.lpush("key", "11");
transaction.lpush("key", "22");  
transaction.lpush("key", "33");
List<Object> list = transaction.exec();

十二、什么是缓存击穿、缓存穿透、缓存雪崩?

(1)缓存穿透

常见的缓存使用方式:读请求来了,先查缓存,缓存有值,命中返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回
当缓存和数据库都没有某个数据时,对这个数据的每次请求都要到数据库去查询,这样就会导致每次查询请求都会穿透到数据库,这就是缓存穿透

缓存穿透产生情况:
①失误操作导致:比如缓存和数据库的数据都被误删除了
②非法请求攻击:比如黑客故意捏造大量非法请求,以读取不存在的业务数据
③不合理的业务:比如用户某个业务数据没有授权,但是查询数据使用的缓存机制,每次查询都穿透到数据库,又因为没权限获取不到。

如何避免缓存穿透
①如果是非法请求,我们在API入口,对参数进行校验,过滤非法值
②如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。
③优化业务,将校验标志保存到redis,比如授权url,api等,提前判断是否有继续查询的必要

(2)缓存雪崩

指缓存中数据大批量到达过期时间,而此时到达的查询数量巨大,大量请求都直接访问数据库,瞬间引起数据库压力过大甚至宕机。

如何避免缓存雪崩
①缓存数据的过期时间设置随机,或者采用均匀设置过期时间方式,防止同一时间大量数据过期现象发生;
②如果缓存数据库是分布式部署,将热点数据均匀分布在不同缓存数据库中;
③设置热点数据永远不过期
④Redis故障宕机也可能引起缓存雪崩,需要构造Redis高可用集群

(3)缓存击穿

指某数据缓存中没有但数据库中有(一般是缓存时间到期导致),某时刻这个数据有大量的并发请求过来,全部都访问到数据库

如何避免缓存击穿
①设置热点数据永远不过期
②使用互斥锁方案,缓存失效时,不是立即去加载db数据,而是先获取锁再从数据库去取数据,没释放锁之前,其他并行进入的线程会等待一定时间,再重新去缓存取数据,这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现

【1】https://blog.csdn.net/millery22/article/details/123472415


十三、热Key问题

热点key,即访问频率高的key

如何解决热点key问题
①Redis集群扩容:增加分片副本,均衡读流量;
②将热key分散到不同的服务器中;
③使用二级缓存,即JVM本地缓存, 减少Redis的读请求。


十四、Redis集群

所谓的集群,就是通过添加服务器的数量,提供相同的服务,从而让服务器达到一个稳定、高效的状态。

(1)为什么需要?

①单个redis存在不稳定性。当redis服务宕机了,就没有可用的服务了。
②单个redis的读写能力是有限的。

(2)redis集群概念

①redis集群中,每一个redis称之为一个节点。
②redis集群中,有两种类型的节点:主节点(master)、从节点(slave)。
③redis集群,是基于redis主从复制实现。

(3)主从复制模式

主从复制模型中,有多个redis节点。其中,有且仅有一个为主节点Master。从节点Slave可以有多个。
只要网络连接正常,Master会一直将自己的数据更新同步给Slaves,保持主从同步
在这里插入图片描述

(4)主从模式的缺陷

当主节点宕机了,整个集群就没有可写的节点了,因此引入哨兵Sentinel

监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。

提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会进行选举,将其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。

(5)多哨兵网络

当只有一个sentinel的时候,如果这个sentinel挂掉了,那么就无法实现自动故障切换了

投票(半数原则): 当任何一个Sentinel发现被监控的Master下线时,会通知其它的Sentinel开会,投票确定该Master是否下线(半数以上)。

选举: 当Sentinel确定Master下线后,会在所有的Slaves中,选举一个新的节点,升级成Master节点。其它Slaves节点,转为该节点的从节点

【1】redis集群简介 https://www.cnblogs.com/vieta/p/11192137.html


十五、Cluster集群模式

实现了Redis的分布式存储,对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题

Cluster集群节点的通讯
一个Redis集群由多个节点组成,各个节点之间是怎么通信的呢?通过Gossip协议!
常用的Gossip消息分为4种,分别是:ping、pong、meet、fail。通过集群总线(cluster bus) 与其他的节点进行通信的
meet消息:通知新节点加入
ping消息:用于检测节点是否在线和交换彼此状态信息
pong消息:当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。
fail消息:通知有节点下线,其他节点接收到fail消息之后把对应节点更新为下线状态。

插槽算法
分布式存储,Cluster集群使用的分布式算法是Hash Slot插槽算法。

集群redirect转向
由于Redis集群无中心节点,整个集群Cluster,被看做一个整体,请求会随机发给任意主节点
主节点只会处理自己负责槽位的命令请求,其它槽位的命令请求,该主节点会返回客户端一个转向错误;
客户端根据错误中包含的地址和端口重新向正确的负责的主节点发起命令请求。

Cluster集群主从复制

故障转移: Redis集群实现了高可用,当集群内节点出现故障时,通过故障转移,以保证集群正常对外提供服务。
下线: 某个节点认为另一个节点不可用,即标记为下线状态,此时主观下线,多个主观形成客观,形成客观下线
故障恢复:
资格检查: 检查从节点是否具备替换故障主节点的条件。
准备选举时间: 资格检查通过后,更新触发故障选举时间。
发起选举: 到了故障选举时间,进行选举。
选举投票: 只有持有槽的主节点才有票,从节点收集到足够的选票(大于一半),触发替换主节点操作

【2】https://www.cnblogs.com/cqming/p/11191079.html


十六、MySQL与Redis 如何保证双写一致性

(1)缓存延时双删

先删除缓存
再更新数据库
休眠一会(比如1秒),再次删除缓存

删除缓存重试机制
因为延时双删可能会存在第二步的删除缓存失败,导致的数据不一致问题
在这里插入图片描述

核心步骤是把删除失败的key放到消息队列,然后重试删除 (可以通过业务实现或者通过数据库的binlog来异步淘汰key)

(2)先更新DB再删除缓存

此方案为网上较多的一种比较好的方案,但是仍然存在数据不一致的情况,例如T1先读取数据D1, T2更新DB为D1->D2,T2删除缓存,T1更新刷新缓存塞D1,此时DB的数据为D2,缓存的数据为D1。
这种方案数据不一致的概率会低很多,因为一般读请求的耗时更短,只有在T1更新刷新缓存的耗时大于T2写入DB的耗时的情况下才会发生

【1】https://blog.csdn.net/micro_hz/article/details/102458016


十七、Redis的Hash 冲突

Redis 作为一个K-V的内存数据库,它使用用一张全局的哈希来保存所有的键值对。
这张哈希表,有多个哈希桶组成,哈希桶中的entry元素保存了key和value指针,其中*key指向了实际的键,*value指向了实际的值。
查询时,首先通过key计算哈希值,找到对应的哈希桶位置,然后定位到entry,在entry找到对应的数据。
在这里插入图片描述

什么是哈希冲突?
哈希冲突:通过不同的key,计算出一样的哈希值,导致落在同一个哈希桶中。
Redis为了解决哈希冲突,采用了链式哈希。链式哈希是指同一个哈希桶中,多个元素用一个链表来保存,它们之间依次用指针连接。
在这里插入图片描述

rehash操作
为了保持高效,Redis 会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。
为了rehash更高效,Redis还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表(ht[0]),一个用于扩容,称为备用哈希表(ht[1])
比如对ht[0]进行rehash时
先将ht[0]中的数据转移到ht[1]中,在转移的过程中,需要对哈希表节点的数据重新进行哈希值计算
最后将ht[0]释放,然后将ht[1]设置成ht[0],最后为ht[1]分配一个空白哈希表:
rehash 操作并不是一次性、集中式完成的,而是分多次、渐进式地完成的。

【1】redis底层数据结构 https://www.cnblogs.com/jaycekon/p/6227442.html

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cy谭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值