介绍
redis是mysql的带刀侍卫,为了减轻mysql的压力,因为大部分操作都是select,所以给mysql加一层缓存,redis应运而生。
安装
只用于学习。
docker run -d --name redis --restart always -p 6379:6379 redis:7.4.0
docker exec -it redis redis-cli
数据结构
先介绍ziplist和listpack,因为向list、hash、zset在redis7之前底层都有ziplist, 7之后都升级为listpack。
ziplist: 压缩列表,一段连续内存。
针对不同的数据进行不同的编码,节省内存,且每个节点记录了前一个节点的长度。缺点:1、元素多了查找就慢。2、增加节点可能会导致连锁更新:如增加一个长为5的节点在1和2之间,但节点2本来存的是节点1的长度:1,现在变成5,那节点2也要扩容,同理,节点3可能因为因为节点2也要扩容,导致连锁更新。
listpack:每个节点只记录当前节点的长度,就没有连锁更新了。
1、string: 可以存字符串、数字。数字只是以字符串形式存储,可以加。
底层: SDS,动态字符串
struct sdshdr {
char buf[]; // 字符数组,保存字符串
int len;//字符串长度
}
类似c的字符数组,但又有一些优点:
1、获取长度是O(1), c是O(n)
2、且不以\0为结尾,可存储任意的二进制数据
3、 自动扩容。
2、list: 双链表,首尾都能增删。
底层: quicklist, 每个节点都是一个listpack。
3、hash:哈希表。
底层:
redis6:若元素个数小于512,长度小于64(默认值),就用ziplist;否则就用哈希表
redis7: 把ziplist改为listpack。
4、**set: ** 无序集合,去重, 如key: {“123”, “213”}。若元素都是证书,就用整数集合存储,否则就用哈希表。
5、zset,有序集合,每个元素有分数,根据分数排序。底层是listpack+skiplist。
当元素个数大于128,或者长度大于64,就用跳表,否则就用listpack
redis为什么快
我们说redis是单线程指的是:redis的命令工作线程是单线程的。
1、基于内存。
2、高效的数据结构,如哈希表、跳表等。
3、IO多路复用,提高并发。
3、单线程避免线程切换的开销,且使用了IO多路复用提高并发处理。
4、新版redis对于网络io的处理改成了多线程。类似reactor,读写事件来了,主线程分配一个io线程去处理请求,然后由主线程去执行命令。
缓存淘汰策略
定时删除+惰性删除+内存淘汰。
1、首先缓存肯定要设置过期时间,不然无限增长了。我们定期去随机选取部分进行检查删除;且当被查询到的键过期了,立即删除(惰性删除)
2、有些键值总是能躲过随机选取,且又没有被查询,长次以往会占满内存。所以又设置内存淘汰策略,如lru: 淘汰最久未使用的, lfu: 淘汰使用次数最少的。
缓存雪崩:为了保证缓存中的数据和数据库数据的一致性,缓存的数据会设置过期时间,当数据过期,就要访问数据库重新生成缓存。若大量缓存数据同时过期,同时又来了大量请求,就会全访问数据库,导致数据库压力骤增甚至崩溃。可均匀设置过期时间。或者像数据库构建缓存时加锁,保证同一时间只有一个线程构建缓存。
缓存击穿:某个热点数据过期了,同时大量数据访问该热点数据,也会直接访问数据库。可以不给热点数据设置过期时间,我们后台更新缓存。
缓存穿透:用户访问的数据既不在缓存,也不在数据库,大量的这种请求也会导致数据库压力骤增。这一般是恶意访问,可以限制非法请求,或者没查到的数据缓存空值,后续也不用再查数据库了。
数据库和缓存如何保证一致性:读的时候先读缓存,缓存若没有就读数据库,并把数据放入缓存;写的时候先更新数据库再删除缓存。为啥是删除缓存不是更新缓存:因为更新的代价比较大,一个缓存可能设计多张表,而且这个缓存也不一定会被频繁访问到,这就是资源的浪费。这也是叫懒加载的思想,用到的时候再计算。
持久化
redis持久化:三种方式:
- AOF日志:类似binlog,每执行一条写命令后都会把它记录到AOF缓冲区,按写回策略写回磁盘。后续根据AOF日志进行数据恢复。写回策略:总是,每秒,由操作系统决定。A0F恢复是逐条恢复,比较慢,但是全。
- RDB快照:定期照一张快照,记录内存的所有数据,再写回磁盘,后续根据快照恢复。RDB很快因为直接是根据数据恢复,但不能太频繁,因为每次快照写回磁盘数据量比较大。所以RDB会有数据安全的问题。
- 混合持久化:AOF日志前半部分记录RDB的全量数据,后半部分记录AOF的增量,这样的化,重启redis后加载快,且数据丢失也较少。
高可用
1、主从复制:一主多从,从机备份主机数据,主机写,从机读,实现读写分离。
刚连接上来是全量复制RDB,后面增量复制aof。
redis主从保证数据一致性吗:他是AP,保证可用性和分区容错性。
2、哨兵:监控主机的运行情况,若主机挂了,他会选一个从机成为主机,具体过程:哨兵会定期发送ping判断主机是否正常运行,若ping失败则认为主机主观下线。多数哨兵都认为下线,则主机客观下线–>开始重选主机:所有从机按优先级、复制进度、ID号排序,选出新的主机,到此故障迁移完成。
高并发
redis的主从复制和哨兵只能保证高可用,而写操作还是只有主节点能写,高并发还需要集群来保证。
我们对数据进行分片,每个redis主节点只存一部分数据。如三个分布式系统都是1主三从,搞3个,就是3个1主3从。
数据映射方案:数据落到哪个redis,与负载均衡类似:
哈希取余:简单,但扩缩容麻烦。
一致性哈希:略
哈希槽分区:redis分了16384个槽位,每个分片都分一些槽位,数据映射到槽位,即放到了对应的一个分片。移动数据方便,只需要移槽->容易扩缩容。
分布式锁
SET lock_key unique_value NX PX 30000
lock_key就是键名
unique_value值
NX表示key不存在才插入
PX设置过期时间
大key:一个key对应的值数据量太大,比较耗内存,且容易造成阻塞。
解决:拆。把一个大string拆成多个小string;把一个大hash拆成多个小hash等等。