总结思路来源:《我们一起进大厂》系列- Redis基础
redis优势是什么?
传统关系型数据库瓶颈在哪?
关系模型是在1970年由IBM的研究员E.F.Codd博士首先提出的,在之后的几十年中,关系模型的概念得到了充分的发展并逐渐成为主流数据库结构的主流模型。简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。
在关系型数据库中,导致性能欠佳的最主要原因是多表的关联查询,以及复杂的数据分析类型的复杂SQL报表查询。为了保证数据库的ACID特性,我们必须尽量按照其要求的范式进行设计,关系型数据库中的表都是存储一个格式化的数据结构。每个元组字段的组成都是一样,即使不是每个元组都需要所有的字段,但数据库会为每个元组分配所有的字段,这样的结构可以便于标语表之间进行链接等操作,但从另一个角度来说它也是关系型数据库性能瓶颈的一个因素。
- 高并发读写需求,硬盘I/O是一个很大的瓶颈
- 海量数据的高效率读写,在存储庞大数据单表中查询效率非常低
- 高扩展性和可用性,对数据库系统进行升级和扩展困难
redis属于非关系型数据库(key-value),具备的优势
- 速度快,C编写,所有操作内存中完成,读写可达10w/20w
- 丰富的数据类型,String,Lists,Hashes,Sets,Ordered Sets
- 持久化,RDB(全量快照)和AOF(增量文件)。RDB和AOF,COW
- 自动操作,自动完成不同数据类型操作
- 快速主从复制(Redis主从复制原理总结)
- Sharding技术,横向扩展Redis示例
与Memcached比较(分布式缓存Redis之与Memcached的比较)
- 性能:Redis单核,Memcached多核;100kb以上数据,后者更优
- 内存效率:简单key-value,Memcached略胜;若redis采用hash存储更优
- 数据类型:Redis支持多种数据类型,Memcached只支持key-value;所以相应地Redis支持更复杂且高效的数据操作,而Memcached需要get后进行复杂操作后再set,增加了网络IO操作
- 备份容错:Memcached挂了数据无法恢复;Redis通过RDB快照备份,且可结合AOF规避数据丢失问题
- 内存管理:Memcached默认使用Slab Allocation机制管理内存,内存块大小固定;Redis封装mallc/free函数动态分配内存
- 集群&分布式存储:Memcached不支持,需客户端采用类似一致性Hash算法来实现;Redis采用cluster集群在服务端构建
redis数据结构
redis中所有键值都用对象表示(redisObject),其组成如下:(Redis 几种数据类型及应用场景)
typedef struct redisObject {
unsigned type:4; //type字段表示对象的类型,包括5种基本类型
unsigned encoding:4; //表示值对象的内部编码
unsigned lru:REDIS_LRU_BITS; //记录对象被命令访问的最后一次时间,和内存回收有很大关系
int refcount; //记录被引用的次数
void *ptr; //指向真正内容的指针
}
| 对象 | 编码 | 数据结构 |
|---|---|---|
| string(字符串对象) | int | 可存储long类型整数 |
| embstr | embstr编码的简单动态字符串 | |
| raw | 简单动态字符串(sds) | |
| list(列表对象) | ziplist | 压缩列表 |
| linkedlist | 双端链表 | |
| hash(哈希对象) | ziplist | 压缩列表 |
| hashtable(HT) | 字典 | |
| set(集合对象) | intset | 整数集合 |
| hashtable(HT) | 字典 | |
| zset(有序集合对象) | ziplist | 压缩列表 |
| skiplist | 字典+跳跃表 |
除了基本数据类型外,还包括
- HyperLogLog: 基数估算算法。估算在一批数据中,不重复元素的个数有多少。(计算网页每日被不同用户访问数)Redis 中 HyperLogLog 的使用场景
- Geo: 存储地理位置信息。(寻找三公里内出租车)Redis GEO
- Pub/Sub: 发布/订阅
- BloomFilter: 布隆过滤器,检索一个元素是否在一个集合中。Redis-避免缓存穿透的利器
- RedisSearch: 搜索引擎。搜索引擎 Redisearch 入门实战
- Redis-ML: 机器学习那快的
缓存穿透,缓存雪崩,缓存击穿
缓存穿透
访问缓存不存在的数据,导致每次请求都打到数据库从而导致压垮数据库。若有大量类似请求,考虑是否恶意攻击。
- 将不存在的数据缓存为key-value。注意设定合理超时时间,否则新增大量键值对会耗光内存。
- 使用布隆过滤器(BloomFilter):创建bit数组(初始化全为0),将某个元素通过n个散列函数映射到bit数组里并置为1,下次检索时如果映射的位置存在0,即可判断该元素一定不存在;若全为1,则可能存在(有可能哈希冲突导致误判)。布隆过滤器几个关键词:数据量,误判率,hash函数选取,bit数组大小。一般来说,存入的数据量越大,要求的误判率越低,所需的hash函数数量越多,bit数组越大。
缓存雪崩
某一批key同时失效(过期),导致所有查询直接请求数据库,从而不堪重负系统崩溃。
- 数据库查询加锁。在并发量不大的情况下,所有失效的缓存key,都需要排队从数据库中获取并更新。影响系统吞吐量,可能导致超时。
//伪代码
String key = "key";
String lockKey = "lockKey"
var value = getFromRedis(key);
if(value != null){
return value;
}
lock(lockKey){
//获取锁期间,可能缓存已更新,所以再判断一次
value = getFromRedis(key);
if(value != null){
return value;
}
//数据库中获取
value = getFromDB(key);
//更新缓存
setRedisCache(key, value)
}
return value;
- 增加缓存标记。给每一个缓存数据加上标记时间,超过该时间后,加入队列中更新缓存,在队列未执行期间,直接返回旧数据即可,保证系统吞吐量。
//伪代码
String key = "key";
//设置标记时间为1小时
Long signTime = getCurrentTimestamp() + 1000 * 3600;
var value = getFromRedis(key);
if(value != null){
Long st = getSignTime(value);
//当前时间已超过标记时间,更新缓存
if(getCurrentTimestamp() > st){
updateCacheQueue.addTask(redisKey -> {
var dbValue = getFromDB(key);
//将值设为类似:value_1624026929
dbValue = setSignTime(dbValue, signTime);
//更新缓存时,将过期时间设置大于标识时间,这段时间差用于更新缓存
setRedisCache(key, dbValue, signTime*2);
});
}
return value;
}
- 缓存过期时间设为随机值。每个缓存key的过期时间设定为某一区间内的随机值,避免了同一时刻缓存批量失效。
缓存击透
缓存击透我理解是缓存雪崩的一种特殊情况。相比缓存雪崩是批量缓存失效导致,缓存击透是单个访问量相当大的key失效(热点数据),期间所有请求直接打到数据库导致瘫痪。
- 互斥锁更新缓存。
- 增加缓存标识。与上面缓存雪崩处理一样,在缓存过期前异步更新缓存。
- 热点数据设置永不过期,说白了就是不设置redis过期时间。
Redis分布式锁
分布式系统中,类似java自带的Synchronized失效,无法保证某一时刻只有一个节点进行操作。需要借助分布式锁。常见的分布式锁实现:基于数据库,基于redis,基于zk等
Redis分布式锁单机版实现:基于redis的分布式锁二种应用场景
Redis分布式锁集群版实现:
Redis集群场景下比较复杂,若从Master获取锁之后,Master挂了,salve升级为Master,此时另一个节点是可以获取锁的。可采用RedLock算法思想进行加锁,java中redisson包对这个算法做了封装。集群环境下Redis分布式锁的正确姿势
批量查找key
keys order* 列出所有以order开头的key
当键的数量非常庞大时,由于redis单线程机制,keys命令会发生阻塞(时间复杂度O(n)),影响生产环境。可以使用scan来替代:Redis Scan迭代器遍历操作原理
Redis异步队列
- 基于
list结构。rpush生产消息,lpop消费消息,客户端休眠轮询获取消息。或者使用blpop阻塞式获取。 - 基于
pub/sub主题订阅模式实现生产一次消费多次。功能等同于kafka,RocketMQ等,但改方法消息不能持久化,消费者下线后,消息会丢失。 - 基于
sortedset实现延时队列。key存储消息内容,score存储时间戳。zadd生产消息,zrangebyscore对score排序,获取N秒前的消息。
Pipeline技术
属于客户端层面的优化,开启pipeline之前,N次Redis请求会发生N次网络开销(N次Request和N次Response)。pipeline的思想就是将没有因果关系的多次请求合并,每次请求先存入缓冲区,等缓冲区满了之后再一并发起请求。
Redis持久化技术RDB与AOF
RDB
类似于全量快照技术,定期持久化内存全量数据。有save和bsave两种执行命令:
save占用redis主进程进行复制,可能导致阻塞bsave主进程fork()一个子进程进行复制操作,涉及写时复制(COW)技术
AOF
类似binlog日志,实时增量备份数据,通过不同的配置实现每条写操作即时同步,每秒同步一次,同步取决操作系统三种同步方式。两种机制全部开启的时候,Redis在重启的时候会默认使用AOF去重新构建数据,因为AOF的数据是比RDB更完整的。
RDB VS AOF
RDB优点
- 紧凑的快照文件,体积较小。保存某个时间点的全量数据,适合容灾备份处理
bsave命令创建子进程执行,不影响正常服务- 大量数据恢复效率高于AOF
RDB缺点
- 安全性差于AOF,备份时间较长,若执行中服务器宕机,将会丢失几分钟的数据
fork()子进程操作对系统性能有一定影响
AOF优点
- 数据即时(取决同步策略)同步,安全性较高,数据丢失量较少
- AOF日志文件是可读的,某些情况下可直接通过阅读AOF日志文件进行恢复
AOF缺点
- AOF日志文件体积较大,且可能冗余大量无效命令(可通过
bgrewriteaof命令执行重写) - 同步效率可能低于RDB,取决于同步策略
在集群同步中的应用
在一主多从级联结构下,从节点初始化需要向主节点发起复制请求,主节点接受到从节点复制请求后执行bsave生成RDB文件同步给从节点,从节点执行复制过程中,主节点还会将这期间的写操作记录至缓冲区,从节点复制RDB结束后,再读取缓冲区数据完成数据复制同步。
三种集群模式
博主“编程迷思”讲解Redis集群系列文章很详细:深入学习Redis
主从模式
一个Master节点,负责写操作;多个Slave节点,负责读操作。数据由Master单向复制给从节点(RDB复制)。主从模式是Redis高可用的基础。
哨兵模式
主从模式故障转移需要人工干预(手动修改配置文件,使从节点变为主节点),不具备自动容错和恢复功能,哨兵模式主要增加了自动化故障转移功能。
哨兵节点是Redis的一种特殊节点,其主要功能是监听主从节点的状态。当某个哨兵节点发现Master节点ping不通的时候,将此Master标记为“主观下线”,然后遍历其他哨兵节点,如果发现超过一定数量的哨兵节点也ping不通Master节点,则将Master节点置为“客观下线”,然后哨兵节点中选举其中一个执行故障转移,并把最新的主从状态通过客户端。
集群模式
主从模式和哨兵模式都无法实现对写操作的负载均衡,单机压力比较大。集群模式将数据分区(分片)存储,每个分区都有对应的主从节点对外提供读写能力,大大提高了集群响应能力。
数据分区是集群模式的核心,有顺序分区和哈希分区两种,其中哈希分区又分为哈希取余分区、一致性哈希分区、带虚拟节点的一致性哈希分区等。
集群模式的故障转移与哨兵模式类似,从节点自动“上位”。
Redis内存管理
过期策略
Redis同时使用了惰性删除和定期删除两种策略
- 定时策略:给每个设置过期时间key创建定时器,到期立即清除。内存能及时释放,但CPU压力大。
- 惰性删除:被动删除,key被访问时才判断是否过期删除。节省CPU资源,但内存压力大,且会出现部分key没被访问导致内存泄露
- 定期删除:每隔一段时间抽取一定数据过期key进行删除,前两种方法的折中。
内存淘汰策略
惰性删除+定期删除策略下,还是有部分key“免于一死”,在内存不足的情况下需要依赖内存淘汰策略主动清理。
- volatile-lru:在那些设置了expire过期时间的缓存中,清除最少用的旧缓存,然后保存新的缓存
- allkeys-lru:清除最少用的旧缓存,然后保存新的缓存
- volatile-lfu:在那些设置了expire过期时间的缓存中,清除最长时间未用的旧缓存,然后保存新的缓存
- allkeys-lfu:清除最长时间未用的旧缓存,然后保存新的缓存
- volatile-random:在那些设置了expire过期时间的缓存中,随机删除缓存
- allkeys-random:在所有的缓存中随机删除(不推荐)
- volatile-ttl:在那些设置了expire过期时间的缓存中,删除即将过期的
- noeviction:旧缓存永不过期,新缓存设置不了,返回错误
Redis线程模型
超详细讲解:Redis 多线程网络模型全面揭秘

被折叠的 条评论
为什么被折叠?



