开发者社区> 游客swjfd4h6nplio> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Redis知识点整理

简介: Redis基础
+关注继续查看

1、Redis的特性:

1)快:

    • 基于内存,操作不需要与磁盘交互
    • 命令执行单线程
    • 多路复用
    • k-v 底层也有很多数据结构支持 比如跳跃表(空间换时间)

2)高可用

    • 持久化
    • 主从、集群

3)支持很多种数据类型

4)支持多种语言客户端


2、Redis的五种数据结构:

1)string(底层结构:sds):

image.png

适用场景:

1.session过期机制、缓存

2.分布式锁:Redis单线程

3.分布式ID:incr

2)hash(底层结构:哈希表):

image.png

适用场景:

1、重入锁

2、购物车

3)list(底层结构:linkedlist或者ziplist(3.0以前);quicklist(3.2及以后)):

linkedlist:

image.png

ziplist:


适用场景:

1、消息队列(有丢失数据的风险)

2、浏览记录

4)set:

补充:如果元素都是整数,底层使用的是整数集合

使用场景:

1、抽奖

5)zset(底层结构:skiplist(跳表))

使用场景:

1、定时任务

2、排行榜

3、Redis面试题

1、有一个100M的数据,可以放到redis中吗?

可以放,但是不建议。数据量太大,查询会有io开销,因为Redis查询时单线程的,会阻塞到其它查询命令。


2、Redis的过期策略

主动删除:定时删除(内存友好、CPU不友好)、定期删除(折中方案)

主动删除:被动过期

1)被动过期(即拿的时候发现过期):对内存不友好,对CPU友好

2)定期删除:

1、只需要找到设置了过期时间的key

2、也不是一次性全部拿到设置过期时间的,根据hash桶的维度,拿到20(可配)个值为止:

假如第一个hash桶是30,拿30

假如第一个hash桶是18,第二个hash桶是100,拿118

3、删掉拿到的值里面过期的数据,如果删除比例超过10%,继续执行2,3步

4、最多只会拿16次

补充:多久执行一次?

1秒执行10次,也就是100ms执行一次


3、Redis的淘汰策略(正常的数据进行清理,内存满了)

wecom-temp-20fe8080d73ee760289b319caef3aea9.png

1)LRU(Least Recently Used):最近最少使用,操作时间离当前越远的约容易淘汰

1.volatile(设置了过期时间的)

2.allkeys(所有key)


1、键值对lru时钟值的初始化:

Redis结构都是key - value,其中value结构:

typedef struct redisObject {
    unsigned type:4; // 类型:string、list。。。
    unsigned encoding:4; // 底层结构:quicklist、ziplist。。。
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
                            // lru:代表这个redisObject被操作的
                            // 时间(秒单位)的最后24bit
    int refcount;
    void *ptr;  // 指向真正数据地址的指针
} robj;

创建RedisObject时,会给这个对象加上lru属性

参数lru解析:

maxmemory-policy:

为lru时:

被操作的时间(秒单位)的最后24bit

lfu:

前16 bits access time:被操作的时间(分单位)的最后16bit

后8 bits frequency:操作次数的8bit

当后续这个对象被访问时,lru属性就会被更新


2、lru算法何时执行?

每次redis-service执行操作命令时:不超时并且没有载入数据时执行


3、lru的具体执行:

1)判断当前内存使用情况

配置文件设置了maxmemory,判断内存使用是否大于maxmemory,当大于时会while循环进行淘汰策略


2)更新待淘汰的候选键值对集合

从哈希表中随机取出一定数量(默认是5个)的key,注意如果maxmemory_policy为allkeys_lru则取全局key,否则取设置过期时间的key


3)选择被淘汰的键值对并删除:找到符合淘汰的key进行删除,每次删除数组中最久未被操作过的key

#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
unsigned long long estimateObjectIdleTime(robj *o) {
    unsigned long long lruclock = LRU_CLOCK(); // 当前时间的最后24位bit
    /*
        如果当前时间大于等于redisObject.lru,返回当前时间减去lru的时间差值
        如果小于,返回当前时间加上24bit减去lru的时间差值
    */
    if (lruclock >= o->lru) {
        return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
    } else {
        return (lruclock + (LRU_CLOCK_MAX - o->lru)) *
                    LRU_CLOCK_RESOLUTION;
    }
}

总结:每个RedisObjective-C(redis对象)会存储lru(保存最近操作的时间),redis进行淘汰时,会每次取5个,将操作时间越久的淘汰了。


2)LFU(least frequently used):最不频繁使用

1.volatile(设置了过期时间的)

2.allkeys(所有key)

1、键值对的初始化:image.pnglru变量存储:

前16 bits access time:被操作的时间(分单位)的最后16bit

后8 bits frequency:操作次数的8bit


2、key更新操作

第一步,根据距离上次访问的时长,衰减访问次数

/* Update LFU when an object is accessed.
 * Firstly, decrement the counter if the decrement time is reached.
 * Then logarithmically increment the counter, and update the access time. */
void updateLFU(robj *val) {
    // 首先,递减计数器
    unsigned long counter = LFUDecrAndReturn(val);
    // 然后以logN级别递增计数器,并更新访问次数。
    counter = LFULogIncr(counter);
    val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}

第二步,根据当前访问更新访问次数。

unsigned long LFUTimeElapsed(unsigned long ldt) {
    unsigned long now = LFUGetTimeInMinutes();
    if (now >= ldt) return now-ldt;
    return 65535-ldt+now;
}  // 返回多久没有访问过

unsigned long LFUDecrAndReturn(robj *o) {
    unsigned long ldt = o->lru >> 8;
    unsigned long counter = o->lru & 255;
    unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;
    if (num_periods)
        counter = (num_periods > counter) ? 0 : counter - num_periods;
    return counter;
} // 原本次数约大越不容易被更新


3、lfu淘汰流程:

1)第一步,调用 getMaxmemoryState 函数计算待释放的内存空间;


2)第二步,调用 evictionPoolPopulate 函数随机采样键值对,并插入到待淘汰集合 EvictionPoolLRU 中;

具体来说,在实现 LRU 算法时,待淘汰候选键值对集合 EvictionPoolLRU 中的每个元素,都使用成员变量 idle 来记录它距离上次访问的空闲时间。

而当实现 LFU 算法时,因为 LFU 算法会对访问次数进行衰减和按概率增加,所以,它是使用访问次数来近似表示访问频率的。相应的,LFU 算法其实是用 255 减去键值对的访问次数,这样来计算 EvictionPoolLRU 数组中每个元素的 idle 变量值的。而且,在计算 idle 变量值前,LFU 算法还会调用 LFUDecrAndReturn 函数,衰减一次键值对的访问次数,以便能更加准确地反映实际选择待淘汰数据时,数据的访问频率。

 if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
            idle = estimateObjectIdleTime(o);
 } else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
            idle = 255-LFUDecrAndReturn(o);
}


3)第三步,遍历待淘汰集合 EvictionPoolLRU,选择实际被淘汰数据,并删除。


3)random:随机

4)ttl:最接近过期

5)novication(默认的):不能写,能读


4、redis持久化

1)rdb(redis database):默认的持久化方式,快照(将某个时间点的redis信息保存到jump.db二进制文件中),保存数据库键值对

1、触发机制:

1)自动触发

save 900 1 // 900s内至少有1个key被修改、添加
save 300 10
save 60  10000

2)关闭redis时触发

3)flushall

4)手动

      • save 阻塞操作
      • bgsave 后台非阻塞操作


缺点:可靠性很低,会丢失很多数据

有点:恢复快


2)aof(Append Only File),保存Redis执行的写命令

默认是关闭的,开启之后,默认会用aof去备份,rdb还是会同时存在

redis提供了跟aof文件交互的配置

#define AOF_FSYNC_NO 0 //交给操作系统同步
#define AOF_FSYNC_ALWAYS 1 //每次都会交互
#define AOF_FSYNC_EVERYSEC 2 // 每秒同步一次,优先保证性能

优点:数据可靠性更强

缺点:性能没有rdb快


3)重写机制

  • 4.0版本之前:比较指令
lpush test a
lpush test b c d
lpop

----------重写机制
lpush test a b c

缺点:效率低


  • 4.0以后:RDB和AOF混合模式:重写的时候,会将aof文件压缩成rdb(二进制)格式的文件,后面又是命令的形式


  • 触发重写机制
createIntConfig(
    "auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 
    0, INT_MAX, server.aof_rewrite_perc, 
    100, INTEGER_CONFIG, NULL, NULL
) //下一次触发的条件是:aof文件的大小跟上次压缩过之后的大小进行比较,
    如果超过了一倍了,就会进行重写
createOffTConfig(
    "auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 
    0, LLONG_MAX, server.aof_rewrite_min_size, 
    64*1024*1024, MEMORY_CONFIG, NULL, NULL
)//aof文件必须打到64MB才能重写


5、主从复制

1)主从复制的过程

    1. 建立连接:主从互相保存对方的IP、端口、偏移量等信息
    2. 全量同步:从库发送指令到主库,主收到后,通过BGSAVE备份RDB文件发送给从库,从库删除自己的数据,将RDB数据同步过来
    3. 增量同步:全量数据同步过程中,主的增量数据会先放到缓存中,等从库同步完全量数据后,主库会把缓存和后面的增量命令同步给从库


2)主从数据丢失的场景?

1、自动切换:哨兵、cluster

      1. 主库每次的命令都会给到从库,假如没给到从库就挂了,这部分数据只在主库有
      2. 这个时候会重新去从节点找一个从库顶替主库,产生了新的主节点
      3. 如果挂了的旧主节点重启了,旧主节点就会变成新的从节点
      4. 数据同步,旧主节点的数据被删除,这部分数据丢失

2、脑裂:哨兵和主节点失去连接,这个时候可能会出现双节点,也会出现数据丢失


6、cluster集群

最低配置:3主3从

分片思想:保证高可用

分片的一般实现:

1、取模,缺点:每次改动要处理所有数据

2、一致性hash:会导致数据不均匀问题

wecom-temp-2e6f36bb488a2efee4ff405485d91ace.png

redis的解决方法:使用虚拟槽(0-16383个虚拟槽),解决数据不均匀问题。

虚拟节点跟真实节点有对应关系,例如:

节点1: 0-5460的虚拟槽

节点2: 5461-10922的虚拟槽

节点3: 10923-16383的虚拟槽

操作key时,根据key取模,决定放在哪个节点上


7、redission分布式锁

lua + hash(可重入):看门狗

特点:

1、防止死锁

2、防止业务没有执行完,锁释放。你去设置时间的话,就没有看门狗了,也就是等于-1的时候才有看门狗


8、场景分析

缓存雪崩、缓存穿透、缓存击穿

缓存雪崩:redis大量的热点数据同时过期,所有请求打到DB,解决:过期时间不一致

缓存穿透:一条数据过期,但是过期的时候发生大量的并发,并发请求打到DB,解决:加锁

缓存穿透:redis没有,DB没有,但是前端大量的请求过来,会打到DB。解决:布隆过滤器。布隆过滤器:bitmap+hash 没法判断一定存在,但是能判断一定不存在。存在一定的误判率;减少误判率:1、多次hash;2、扩容值:会带来性能消耗


9、Redis跟DB的数据一致性问题

(1)造成原因:在多并发的场景下,操作不是原子性的。例如:

线程A请求数据,没有缓存,访问DB,更新缓存

线程A没有同步完,线程B将DB改成2,删除缓存

线程A继续同步,缓存的数据是1,数据不一致

(2)解决:延迟双删(并不是最好的办法),最好的办法是别管,业务场景下的话就舍弃一个,Redis和DB只能一个为主

10、面试八股文

https://zhuanlan.zhihu.com/p/419224864

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
redis
redis分布式缓存
15 0
Redis
本文整理了Redis相关的知识,方便以后查阅。
61 0
Redis常见知识点汇总 也许你能用到
Redis常见知识点汇总 也许你能用到
1679 0
redis
test
595 0
w@
redis
1200 0
6
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载