redis使用 :
redis是数据库,数据库的思想设计都是类似,像mysql es redis都是相似的。
使用最基础的是 使用它,add del set添加、修改、删除操作。
add数据的数据类型,像string hash list set zset,
它是单线程的,删除大量的、占据内存大的key的话,会阻塞,
影响查询速度。-->因此,设置时,尽量选择string占内存小的、
而且要避免大量删除(过期时间 雪崩)
setNx删除:加上过期时间,防止死锁、时间设置均匀一点、带上UUID、requestID 删除策略
数据持久化 AOF 内存逐出策略
集群:分担写的压力、选举机制、哨兵机制、数据主从复制、数据不一致
16个数据库,观察,结构。
数据类型(string hash list set zset)
单线程的,key大 阻塞操作;所以,set时 、del时 要注意;
分布式锁setnx 带上过期时间,requestId, 用redission
过期机制
【使用中的注意点】
学redis就看知乎它的几篇文章
Redis最佳实践:7个维度+43条使用规范,带你彻底玩转Redis | 附最佳实践清单 - 知乎 (zhihu.com)
Redis 在使用中会遇到哪些坑?如何规避? - 知乎 (zhihu.com)
为啥 redis 使用跳表(skiplist)而不是使用 red-black? - 知乎 (zhihu.com)
Redis为什么变慢了?一文讲透如何排查Redis性能问题 | 万字长文 - 知乎 (zhihu.com)
- 部署 Redis 时,如何做好资源规划?
- 如何降低 Redis 故障发生的频率?
日常运维 Redis 需要注意什么?
- Redis 监控重点要关注哪些指标?
- 我的 Redis 内存为什么增长这么快?
- 为什么我的 Redis 操作延迟变大了?
1. 对key的操作
key * 查看库里所有key
dbsize 库里key的数量
exists key key是否存在
type key 查看key的类型
del key 删除指定key的数据
ttl key 查看过期,-1永不过期,-2已过期
2. 常用数据类型
- 跳跃表是Redis特有的数据结构,就是在链表的基础上,增加多级索引提升查找效率。
- 跳跃表支持平均 O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。
跳表是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为 O(logN)。简单说来跳表也是链表的一种,只不过它在链表的基础上增加了跳跃功能,正是这个跳跃的功能,使得在查找元素时,跳表能够提供 O(logN) 的时间复杂度。
跳表为了避免每次插入或删除带来的额外操作,不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level)。而且新插入一个节点不会影响其它节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。
Zset 为何不使用红黑树等平衡树?
1)跳跃表范围查询比平衡树操作简单。 因为平衡树在查询到最小值的时还需要采用中序遍历去查询最大值。 而跳表只需要在找到最小值后,对第一层的链表遍历即可。
2)平衡树的删除和插入需要对子树进行相应的调整,而跳表只需要修改相邻的节点即可。
3)跳表和平衡树的查询操作都是O(logN)的时间复杂度。
4)从整体上来看,跳表算法实现的难度要低于平衡树。
- String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。
- List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码
- Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。
- Set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码。
- Zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码
有哪些应用场景?
Redis 在互联网产品中使用的场景实在是太多太多,这里分别对 Redis 几种数据类型做了整理:
1)String:个redis字符串value最多是512M ;
缓存、限流、分布式锁、计数器、分布式 Session 等。
2)Hash:用户信息、用户主页访问量、组合查询等。
3)List:底层是双向链表。
简单队列、关注列表时间轴。
4)Set:赞、踩、标签等。
5)ZSet:有序集合
排行榜、好友关系链表。
3.单线程
使用中:set key(大小)、del(阻塞)、读取时 数据不一致
DEL命令耗时久,阻塞主线程,影响查询速度;
管理好 Key的大小
Redis是单线程的,所以一些耗时的操作(删除key、set key)会导致Redis卡顿,比如当Redis数据量特别大的时候,使用keys * 命令列出所有的key
删除key会阻塞redis:
删除一个 key 的耗时,与这个 key 的类型有关。
1. key的类型不是string, list hash set zset元素太多
2.string占用太多内存
数据不一致:
redis低版本会 导致 主从库读取过期key 数据不一致(4.0以上的才修复bug)
加锁:
过期时间 (不加上的话,有可能造成死锁;合理评估时间,避免集中失效;但也不要时间不够)、
带上UUID;加锁解锁 也可以LUA脚本实现,3行。
主从复制 异步: 主机挂了, 还没来得及 把最新的数据 同步给从,就发起 总从切换,导致 分布式锁 或 数据丢失;
主从库 设置的时钟不一致 导致 数据不一致;
主从库 设置的最大内存不同,触发内存逐出策略 导致数据不一致
主机开启数据持久化,丢失数据不敏感的情况,不开AOF,会拖慢redis性能
定期+惰性 删除过期key,
第一轮 定期轮训 随机删 导致某些过期key还在内存;
exist api方法 触发第二轮 惰性删,删除过期key;
读是共享的,没必要给 读 加锁;只保证 读写、写写并发 时加锁;
redis是 执行命令时单线程+OS的多路IO复用(IO多线程)技术
虽然是单线程,但能有多线程的效果,它是怎么实现的?
什么是I/O多路复用?
多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epoll作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。
- I/O :网络 I/O
- 多路 :多个网络连接
- 复用:复用同一个线程。
- IO多路复用其实就是一种同步IO模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出cpu。
延申:NIO模型、epoll模型
为什么redis单线程,还那么快?
1.首先,是因为它做的事情简单,绝大多数命令都是 对单个key的操作,而且没有复杂事务;
2. redis存在内存里,比 去磁盘快;
3. 操作系统的多路复用 属于一种NIO(非阻塞IO),我写过NIO模型的代码,有三版的优化。
4. 单线程减少上下文切换---》延伸到计组的 cache
5. 高级数据结构(如 SDS、Hash以及跳表等)。--->按块(缓存行)读
第一个:阻塞的(BIO blocking)。
阻塞导致的弊端:
1.连接:建立服务器连接,此时没有连接的话,会阻塞;
没办法 同一时刻 处理多个连接( 没办法处理 并发)
2.读数据: read(),没有具体的时间发生,就会阻塞(debug打断点,可以看出);
那线程其实就一直在闲置 空等-->浪费线程开销,cpu
对并发、资源敏感的程序,不太友好
于是要优化,就引入了NIO
NIO的非阻塞 其实是由OS内部实现的,底层调用了linux内核的系统函数。
accept函数、epoll_create函数
对socketChannel设置非阻塞后,那么即使客户端没发送数据 也不会被阻塞,仍然可以读,只不过读出的结果是null。
目前的优势:
(ps: 非阻塞的IO多路复用 selector选择器的设计思路,特别像 在做国资委项目的高并发的优化。
不要for循环查库遍历10W,
而是先取出所有数据,过滤出有效的,放到一个set集合里。
)
1. 可以一次性接收很多连接了; 不会因为某个线程的阻塞,导致没办法处理 新的客户端连接;
目前的弊端:
1.大量的空循环、空执行 导致浪费了大量的程序执行时间 CPU
举例:假如10w个连接,但只有100个发送数据的,
那每次都需要遍历10w次,而且读了很多的null,浪费了CPU
于是优化,引入selector选择器,它底层就是epoll实现的
思想:其实就是把那100个有数据的放到另一个set集合里,每次只遍历这100个
selector只关注某个具体的时间(如:连接、读等)
把通道channel注册到selector选择器上,
监听
如果事件发生,就执行;没有,就阻塞;
native方法,是用c语言实现;linux源码使用c和汇编实现的。
#man操作手册
select epoll的区别
select 非阻塞,但没有过滤,
epoll是最好的,这是一个优化的过程
netty也是对NIO封装,并且进行了优化。
byteBuffer这个类不好用,flip方法很容易有bug
常见的缓存使用方式:
读请求来了,先查缓存,缓存有值命中,直接返回;
缓存没命中,就去查数据库,把数据库的值更新到缓存,再返回。
缓存穿透:查询不存在的数据 每次请求都要到数据库去查询,给数据库带来压力。
通俗点说,读请求访问时,缓存和数据库都没有某个值,这样就会导致每次对这个值的查询请求都会穿透到数据库,这就是缓存穿透。
布隆过滤器:判断该key存在
分布式锁:
缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
缓存雪奔:大量数据同时过期,引起数据库压力过大甚至down机。
- 缓存雪奔一般是由于大量数据同时过期造成的,均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。
- Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群。
一、在Redis中,假如我们设置了100w个key,这些key设置了只能存活2个小时,那么在2个小时后,redis是如何来删除过期的key?
答案:定期删除 + 惰性删除。
那什么是定期删除?什么的惰性删除?靠这两种策略就可以删除掉redis中过期的key吗?
定期删除:
周期性轮询 随机抽取 策略
Redis会把 所有设置了过期时间的key 放进一个字典里,然后每隔100ms 从字典中随机一些key检查过期时间 并删除 已过期的key。
redis 默认每秒10次 过期扫描 :随机抽取 一些key(注意:是一些,不是 所有),检查是否过期,过期则删除。
如果每隔100ms,就把redis的所有key(假设有1000w的key)都检查一遍,会给CPU很大的负载,redis就卡死了。
因此,弊端: 如果只采用定期删除策略,会导致很多过期的key到时间了 但还没被删除。占用了大量内存资源。
为了解决这个弊端,redis增加了惰性删除策略
惰性删除:对于那些过期的key,靠定期删除策略没有被删除掉,还保留在内存中,
这时候如果系统去主动查询这个key,redis判断已经过期了,才会把这个过期的key删除掉。
内存不够 逐出算法
当新数据进入redis时,如果内存不足怎么办?
Redis使用内存存储数据,在执行每一个命令前,
会调用freeMemoryIfNeeded()检测内存是否充足。
如果内存不满足新加入数据的最低存储要求,
redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。
注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。
从redis.conf配置文件里 看到了redis的淘汰机制 。
最近 最少使用的;
使用次数最少的;
Redis 内存淘汰策略
- volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
- allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。
- volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。
- allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰;
- volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据;。
- allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;
- noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
持久化
redis数据放到内存里,但为了防止宕机时数据丢失 快速恢复,选择把数据 持久化备份到磁盘里;
RDB (启动快,但是丢5分钟数据)和 AOF(存 追加的命令,丢一秒钟数据,就是启动慢)
持久化各有利弊,
RDB 文件 是经过压缩的二进制文件,是 某个时间点的完整数据,
恢复大数据集时的速度快
由fork创建 子线程,把数据写入到一个临时文件(需要内存是原来的2倍) 完成持久化;
缺点:Redis 出现宕机,最多可能丢失5分钟数据
AOF 是像mysql的binlog日志一样 写入命令,
有3种策略:
1.always:每次redis数据发生变化时,把命令追加到aof文件,产生大量的磁盘IO,虽然影响性能,但数据最安全;
2.每秒: 只在每秒 写命令追加到aof文件;宕机时 丢失一秒的数据;
缺点:文件 保存的是 所有的命令,它恢复时 时需要重新执行指令,所以启动速度很慢;
混合:
redis重启时,为了启动快,先加载 RDB的内容;
然后有时间了,就再重放增量AOF日志
关键词串联:
分布式锁使用:
1.为了效率,只在一台机器上执行
用分布式锁,带上过期时间(防止死锁,设置不同的时间,防止统一过期雪崩、单线程:删除耗时,影响查询速度),带上进程id或UUID(防止释放别人的锁)
2.已知 定期删除+惰性删除,所以 代码里用exist方法(触发 惰性删除),从内存中删除key
1. 项目是分布式集群,某个发邮件的功能代码,想只需要在一台机器上执行即可(为了效率,不重复 执行),
所以set值的时候 用分布式锁setNX,带上过期时间。
(不带过期时间的话,锁一直存在,防止死锁 导致别的进程无法使用(别的进程对这个key set值时 就无法set值了)。)
(可以调用api判断key是否存在: 定期删除策略 即使没有及时删除这个key,这时调用是否存在方法 就会激发 惰性删除策略 把它给从内存里清除掉)
2. 设置不同的缓存时间,避免大量缓存同一时间失效。
设置key时,尽量用string 并且 占内存小一点
关键词串联;
集群:
多主实例:红锁,为解决主从切换时,分布式锁丢失(主从切换时,主从尚未同步复制成功,(主从的锁还不一致时 就切换了,从变成主后 没有锁))
DBA: 哨兵模式、一主多从的集群、数据淘汰机制、读写分离、主从同步、主开启数据持久化到磁盘
为了主从数据一致性:
考虑:统一 主从的机器时钟、最大内存、版本、磁盘内存、
【故障】
主从复制 丢数据-----》MQ(消息队列)本质是为了 重试。当删除redis失败后,重试。
1.解决 功能性问题 而产生的: java jsp tomcat html linux jdbc svn
2.解决扩展性问题 (而产生的框架): struts springmvc mybatisplus
3.解决性能问题 而产生的: nosql(redis) java线程 hadoop nginx mq es(搜索)
nosql缓存,减少io操作,提高时间效率。
不遵循sql标准
不支持ACID1nn
查询性能高
(3)支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
存到内存、支持持久化备份恢复、k-v模式存储 list set hash zset
redis默认有16个数据库,一般都是用的0,密码都是相同的
3. 配置文件
4. 发布和订阅
redis发布订阅(pub/sub)是一种通信模式,发送者(pub)发送消息,订阅者(sub)接收消息。
5.事务与锁机制
redi事务的主要作用就是 串联多个命令 防止别的命令插队。按顺序执行。
悲观锁 乐观锁
6.备份
AOF 数据完整性好
读写分离:
一主多从:
从应用里 写数据到 主服务器里,主服务器把内容复制到 几个从服务器里,
以后应用想°数据的话,就去 从服务器里读。
哨兵模式:
哨兵监听主服务器是否挂了,挂了的话 去从服务器里选举出一个作为新的主服务器。
配置哨兵、启动哨兵
7.redis集群
容量不够,redis如何扩容?---通过集群,解决容量不够的问题
并发写操作,redis如何分摊?--用集群,解决 写的压力;
8.基于redis实现分布式锁(共享锁)
分布式锁:在这段代码上加个分布式锁,部署的3台机器上,只要让其中一个机器执行就可以了。
给集群的某台机器上锁了,那其他机器不知道,
是为了让所有机器都能共享,知道这个资源被上锁了。
SETNX 加锁 (最好 带上过期时间);
删除del key之后,才释放了。
只能等你释放锁之后,别人才能使用;否则就等待;
高并发意味着大流量,需要运用技术手段 抵抗流量的冲击
高性能:性能反映了用户体验,响应时间分别是100毫秒和1秒,给用户的感受是完全不同的。