Redis 简介
Redis 是完全开源免费的,遵守BSD协议,是性能极高的nosql数据库,Redis读的速度能达到110000次/s,写的速度能达到81000次/s 。
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis支持数据的备份,即master-slave模式的数据备份。
- 丰富的数据类型:有五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
- Redis的所有操作是原子性的,意思就是要么成功执行要么失败完全不执行。
- 还有丰富的特性:Redis还支持publish/subscribe, 通知, key 过期等等特性。
Redis与其他key-value存储有什么不同?
- Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
Redis内存数据如何保存到磁盘
Redis的所有数据都存在内存之中,防止数据丢失;
Redis持久化有两种方式,分别是RDB(又称快照)与AOF。
对于两种方式各有优缺点,具体如下:
RDB:将内存中所有数据以二进制数据方式存到硬盘中,同步慢,数据较小。
AOF:追加操作指令到原来的aof文件中,同步较快,数据量随时间增加文件而增大。
RDB原理
SAVE:该命令会阻塞当前Redis服务,导致Redis服务不可用,直至RDB持久化进程完毕,所以一般都不采取该方式进行RDB持久化。
BGSAVE:当使用该命令进行RDB持久化时,Redis会fork产生一个子进程,由子进程进行RDB持久化操作,父进程接受客户端的操作请求。子进程只会遍历读取内存中的数据,写入磁盘,并不会修改数据,若父进程在子进程读取数据过后进行了数据修改,子进程就会与父进程存在数据不一致的问题。
Copy on Write:众所周知Redis是单线程程序[1],文件IO操作不能进行多路复用,于是在BGSAVE进行RDB持久化时Redis采用操作系统的Copy on Write[2]机制进行RDB持久化。
单独使用RDB持久化,存在日志文件丢失风险
AOF原理
AOF日志存储的是,Redis执行的顺序写指令。当开启AOF持久化时,Redis会先执行数据的写指令,再将指令写入AOF日志中。
fsync:实际情况中,AOF机制会先将Redis写指令写入内存缓冲区中,再根据配置情况,定时调用fsync将缓冲区文件写入AOF日志中。我们可以根据实际情况设置fsync频率:
always:每次修改,立即提交磁盘。该方式数据完整性良好,但是IO开销大,影响效率.
everysec: 每秒同步一次到磁盘。该方式只会丢失一秒内的数据,
no: 从不同步到磁盘。
Redis4.0
从Redis4.0开始,Redis开始支持混合持久化。即将RDB与AOF结合使用,RDB负责定时全量持久化数据,AOF负责记录最后一次RDB后的增量日志。使得Redis的数据恢复在恢复效率与数据完整性之间取得一个完美的平衡点。
Redis线程模型、单线程快的原因
1.2.单线程快的原因
内存操作
核心是基于非阻塞的io多路复用机制
单线程避免多线程的上下文切换带来的性能问题;
1.使用场景
面试流程
1.redis是什么,基于内内存的;k/v类型的nosql数据库,工作线程是单线程的 worker,
iothreads
连接很多 ; epoll
5中类型;本地方法;计算数据移动,io优化
集群
分片
主从
持久化;
整体模型
串行/并行那个更优
线程模型:worker 单线程
秒杀;串行;
单个线程
多个线程:io是多线程的,工作线程是单线程的
工作线程是一个,满足redis单线程的
你的项目中如何运用的redis
1.并发
2.性能
3.分布式锁、
b/s:单一架构当访问量非常大的时候,tomcat访问数据库会达到瓶颈,引入redis-nosql数据库;(是为了解决什么问题?)提高性能,解决并发量;
介绍redis :redis基于内存,(redis特性,速度方面)
redis用来干什么;
使用redis做缓存层有没有遇到什么问题?
在引入redis层后遇到了:缓存雪崩、缓存击穿、缓存穿透
缓存雪崩原因:
后果:产生的雪崩导致服务卡顿,乃至服务宕机;
数据的key同时失效导致
解决办法:
1、给key设置随机时间,避免同时失效;
2、通过定时任务刷新key的过期时间;redis服务其宕机;
解决办法:
1、把热点数据放到不同redis集群节点中
缓存穿透:
非法请求,查询不存在的数据,直接穿过redis访问数据库;例如:每次请求id为uuid的请求,redis中不存在,直接查询数据库;
-
解决办法:
1、请求redis的数据不存在,查询数据库也不存在都会将数据存到redis中;
2、使用互斥分布式锁解决:去redis中请求,不存在,在请求数据库之前添加互斥锁,使第一个请求进入到数据库中查询,使其他请求进入到短暂的睡眠,当第一次请求完成后,将数据粗到redis中,其他请求就可以继续redis中继续查询;
jvm中的锁解决互斥锁:
分布锁,解决多个独立个体排队问题
3、如果是uuid的请求使用布隆过滤器来解决;
隆过滤器:
布隆过滤器过滤大部门非法请求;
过滤器会标识所有数据的id,但是布隆过滤器会存在错误率,请求进来经过布隆过滤器,判断id是否存在,存在可能查询不到数据;因为经过布隆算法,将id经过hash算法存到布隆过滤器中;但是当redis去布隆过滤器中查询,布隆过滤器中不存在,那就真的不存在,所以会存在错误率;
- 具体介绍:
-
底层实现
二进制数组;
可以设置错误率;错误率越小,hash函数越多,存到过滤器中的数据量越多,导致的计算时间越长;
误判率大,hash函数少,存到过滤器中的数据量越少,计算按时间越短;
缓存击穿:
web请求队一个热点数据进行大量访问,当key失效了,大量请求会进入到数据;导致服务卡顿,宕机;
- 解决办法:
你知道redis集群中hash一致性算法原理机器作用
hash一致性算法;
如何保证数据均匀的存在这两台redis服务器中
redis 是key:value数据库
对key进行hash取模算法:
hash(key) %2 == 0 ?redis1:redis2
- 通过hash一致性算法来解决当集群中机器发生变化,不会发生大量的数据移动问题;
比如有三台服务器分别计算服务其位置;
第一台服务器:hash(ip+服务器编号) % 2^32
第二台服务器:hash(ip+服务器编号) % 2^32
第三台服务器:hash(ip+服务器编号) % 2^32
在计算数据存储在那个位置
hash(key) % 2^32 = value
根据value数据库顺时针查找数据库;
-
当新增一台服务器,根据hash(ip+服务器编号) % 2^32计算出位置,
-
删除一台服务器
将1-3之间的数据移动到2就行
hash一致性算法思路是:解决小范围数据的移动,不至于全盘数据调整;对于集群的拓展,容错效率都非常高;
- 数据倾斜
hash算法缺点:带来数据的倾斜;
存储框架数据倾斜:少量数据存在大量数据,大量数据存在少量数据;
计算框架数据的倾斜:
解决数据倾斜问题;使用虚拟节点;
redis服务器虚拟成2个节点
- 通过经验:
10台redis服务器一般虚拟100-200个虚拟服务节点;
5台redis服务其一般虚拟200-400个虚拟服务器;
总和一般1000-2000个虚拟节点;这样数据才不会倾斜
-------------------------------------------------------------------------
缓存雪崩
缓存穿透
缓存穿透
redis过期键删除策略
- 定时过期
三种比较常用;定期过期比较好的这种方式;
Redis线程模型,单线程为什么那么快
???????
redis事务实现 保证ACID
与mysql都是相同;保证
原子性:
一致性:
持久性:
隔离性:
mysql有回滚机制,redis没有回滚机制
事务开始之前监控key,
redis集群方案
哨兵模式
redis Cluster
RedisSharding客户端分片
Redis主从复制的核心原理
-
全量复制
-
部分复制
Redis的过期键删除策略
- 定时过期
给每个key设置一个过期时间;
CAP理论
BASE理论
负载均衡算法
负载均衡类型
分布式架构下,Session共享有什么方案
不用session:JWT或者TOKEN
高并发场景下秒杀下超卖bug
通过redisson 获取锁对象,对产品的key进行加锁,加完锁,然后对锁进行30/3的时间进行续命,保证锁不失效;操作哇按后进行释放锁;
Redis6.0多线程工作模式
Redis核心数据结构
Redis缓存雪崩,穿透、
如何实现千万级用户日活统计
setbit实现
统计单日登录用户
统计两天都登陆的用户
redis应用场景
redis核心数据结构和底层原理
- redis数据结构 :分布式的数据结构;
jdk的数据结构不能跨集群的数据结构;
- redis的set和get的执行过程
存在全局hash表中,hash的这种算法结构查询/存储效率并比较高;
hash(key)% 全局hash表size;
redis底层自己实现的自己的hash算法;
- redis set时value的存储结构;
incr: 存储的时候对value转成int类型,转成功就可以自增,不能转就不能自增;
操作类型
String
在分库分表的情况下id怎么生成,使用redis的incr生成id;
批量生成id在edis中生成100个,程序中自增++,当程序中++到100了就调用redis的incryby生成100个;优化生成效率;
hash结构
对象缓存
应用:京东的购物车
List 数据结构
使用:
redis的list类型的:消息流应用
当消息来了往自己的消息列表中粗怒一条数据,不用每次都查询寻数据库;提高性能;
set数据结构
set应用:
集合操作:
zset有序集合
zset应用:比如:热搜
redis数据结构
zset数据存储结构:
- 压缩列表
- 跳表
介绍:
每两个元素往上建个索引层;
跳表元素越多,查询效率越高;类似于折半查找;
跳表以空间换时间;
跳表详解
压缩列表和跳表的切换阈值;阈值:128个元素切换:
这个地方说说集合特点
链表特点,源码;
集合特点,源码;
数组插入:
链表插入:
redis数据结构
JAVA缓存热点数据,使用redis缓存数据,保证热点数据的缓存用法与原理
通过redis本身的设置过期时间来实现缓存热点数据
1、缓存每命中一次,就重新给该数据设置过期时间
2、那么经常命中的缓存始终不会过期,不会被删除,而非热点数据过期时间一到那么就会被删除掉,保证了redis中始终存在的是热点数据。
【原理】
1、原理其实就是Java中延时阻塞队列DelayQueue的原理
2、当对redis中缓存数据设置过期时间,相当于将缓存数据放入redis中维护的延时阻塞队列DelayQueue。
3、DelayQueue会对放入的缓存数据根据过期时间进行排序,时间短的在前面,时间长的在队列后面。
4、会使用一个或者多个线程循环查询DelayQueue,一旦能从DelayQueue获取元素了就说明该缓存数据到期了,就可以取出来并且删除掉了。
5、当有多个线程都同时查询DelayQueue的时候,只有一个线程能够争取到头元素,其它线程将被阻塞。当头元素被取走以后,会唤醒所有阻塞线程,线程竞争头元素,竞争到头元素的线程会查询头元素的剩余delay时间,并且标记头元素已经被该线程占有,再根据delay时间wait自己,最后获取头元素后唤醒其它阻塞线程。
Redis I/O 多路复用模块
“I/O 多路复用模块”会监听多个 FD ,当这些FD产生,accept,read,write 或 close 的文件事件。会向“文件事件分发器(dispatcher)”传送事件。
文件事件分发器(dispatcher)在收到事件之后,会根据事件的类型将事件分发给对应的 handler。
我们顺着图,从上到下的逐一讲解 Redis 是怎么实现这个 Reactor 模型的。
I/O 多路复用模块
Redis 的 I/O 多路复用模块,其实是封装了操作系统提供的 select,epoll,avport 和 kqueue 这些基础函数。向上层提供了一个统一的接口,屏蔽了底层实现的细节。
一般而言 Redis 都是部署到 Linux 系统上,所以我们就看看使用 Redis 是怎么利用 linux 提供的 epoll 实现I/O 多路复用。
首先看看 epoll 提供的三个方法:
/*
* 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
*/
int epoll_create(int size);
/*
* 可以理解为,增删改 fd 需要监听的事件
* epfd 是 epoll_create() 创建的句柄。
* op 表示 增删改
* epoll_event 表示需要监听的事件,Redis 只用到了可读,可写,错误,挂断 四个状态
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*
* 可以理解为查询符合条件的事件
* epfd 是 epoll_create() 创建的句柄。
* epoll_event 用来存放从内核得到事件的集合
* maxevents 获取的最大事件数
* timeout 等待超时时间
*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
再看 Redis 对文件事件,封装epoll向上提供的接口:
/*
* 事件状态
*/
typedef struct aeApiState {
// epoll_event 实例描述符
int epfd;
// 事件槽
struct epoll_event *events;
} aeApiState;
/*
* 创建一个新的 epoll
*/
static int aeApiCreate(aeEventLoop *eventLoop)
/*
* 调整事件槽的大小
*/
static int aeApiResize(aeEventLoop *eventLoop, int setsize)
/*
* 释放 epoll 实例和事件槽
*/
static void aeApiFree(aeEventLoop *eventLoop)
/*
* 关联给定事件到 fd
*/
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
/*
* 从 fd 中删除给定事件
*/
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)
/*
* 获取可执行事件
*/
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)
https://www.jb51.net/article/137486.htm
缓存预热
系统冷启动
当系统上线时,缓存内还没有数据,如果直接提供给用户使用,每个请求都会穿过缓存去访问底层数据库,如果并发大的话,很有可能在上线当天就会宕机,这种情况就叫“系统冷启动”,因此我们需要在上线前先将数据库内的热点数据缓存至Redis内再提供出去使用,这种操作就成为"缓存预热"。
解决方案
提前给redis中嵌入部分数据,再提供服务。 这宛如一句feihua,但是却也是行之有效的好办法。
那如何实施?
肯定不可能将所有数据都写入redis,因为数据量太大了,第一耗费的时间太长了,第二redis根本就容纳不下所有的数据。
所以,就.需要更具当天的具体访问情况,统计出频率较高的热数据。
然后将访问频率较高的热数据写入到redis,如果说热数据也比较多,我们也得多个服务并行的读取数据去写,并行的分布式的缓存预热。
然后将嵌入的热数据的redis对外提供服务,这样就不至于冷启动,直接让数据库崩溃了。
缓存更新
缓存服务(Redis)和数据服务(底层数据库)是相互独立且异构的系统,在更新缓存或更新数据的时候无法做到原子性的同时更新两边的数据,因此在并发读写或第二步操作异常时会遇到各种数据不一致的问题。如何解决并发场景下更新操作的双写一致是缓存系统的一个重要知识点。
即数据一致性,在开头的那篇博客里已经讲得挺详尽了。
那就再提一嘴,延时双删,这里就不展开了,挺多的。