- 为什么要做过期时间?
- redis失效时间是如何做的
- redis有那些过期策略,优缺点,实现原理?
- redis使用的什么方案
- redis 有那些内存淘汰策略?常用的是什么,为什么?
- noeviction: 不处理
- lru: 未使用时间最久的key
- lfu: 使用次数最少的key
- random: 随机key
- volatile与allkeys的区别
仓库地址: https://github.com/dengjiayue/my-redis-v2.0-RESP-.git
为什么要做过期时间?
: 因为redis是内存数据库,空间是有限并且昂贵的,如果我们只存数据很快就会资源耗尽.
所以我们需要清理数据腾出空间储存其他的数据
redis失效时间是如何做的
redis是使用失效时间字典去存放失效时间的
key为对应数据的指针,val为失效时间戳(毫秒级)
redis有那些过期策略,优缺点,实现原理?
-
立即清除,key到过期时间立即清除
优点:内存友好,能够快速释放出空间
缺点: cpu消耗大,不停执行清除,如果遇到请求压力大的时候会额外增加cpu的负担
实现方案: 使用时间轮调度器,每设置一个失效时间就加一个延时调度删除方法,到过期时间会调度删除方法删除该数据 -
惰性清除: 当使用key的时候检查是否过期,如果过期就清除该数据
优点: cpu友好,不需要时刻执行清除
缺点: 内存不友好,有一些不使用的过期数据会无法清除,浪费空间资源(redis空间很宝贵)
实现方案: 当调用查询的时候先检查是否过期,如果过期清除数据并返回空 -
定时清除: 定时扫描过期字典,将过期的数据清除
这是一个折中的方案,既不会向立即清除那样消耗太多的cpu,又不会让不使用的数据长期停留在数据库中
实现方案: 做一个定时任务,定时扫描过期字典并清除过期数据
redis使用的什么方案
redis使用的是定时以惰性删除结合的方案
package src
import (
"my_redis/src/timewheel"
"strconv"
"time"
)
//过期时间实现
//
// 扫描部分key并检查是否过期
func (s *Server) CheckExpire(num int) {
// 遍历所有key
for k, v := range s.Ex {
if v < time.Now().Unix() {
// 过期了
delete(s.Ex, k)
// 删除key
delete(s.M, k)
}
num--
if num == 0 {
break
}
}
}
// 定时过期
func (s *Server) TimingExpire() {
// 定时执行
ticker := time.NewTicker(time.Second)
for {
<-ticker.C
// 定时执行
s.CheckExpire(100)
}
}
// 惰性过期(输出是否过期 )
func (s *Server) LazyExpire(key string) bool {
// 检查key是否过期
if s.Ex[key] < time.Now().Unix() {
// 过期了
delete(s.Ex, key)
// 删除key
delete(s.M, key)
return true
}
return false
}
// 立即过期
// 使用时间轮实现立即过期
// exTime为毫秒过期时间
func (s *Server) ImmediatelyExpire(key string, exTime int64) {
late := time.Millisecond * time.Duration(exTime)
timewheel.Delay(late, key, func() {
// 删除数据
delete(s.Ex, key)
// 过期时间记录
delete(s.M, key)
})
}
// 设置过期时间: 向Ex写数据
// exTime为过期时间(毫秒)
func (s *Server) SetExTime(key string, exTime string) error {
//将exTime转换为数字
exTimeNum, err := strconv.Atoi(exTime)
if err != nil {
return err
}
// 将exTime转换为过期时间戳(毫秒级)
ex := time.Now().UnixMilli() + int64(exTimeNum)
s.Ex[key] = ex
return nil
}
参考:https://github.com/dawnzzz/simple-redis
redis 有那些内存淘汰策略?常用的是什么,为什么?
noeviction: 不处理
这种在内存资源耗尽的时候写会报错!
lru: 未使用时间最久的key
原理: 使用一个队列进行维护,如果一个key被使用那么就将这个key放在对头,如果队列满了(空间满了)再加入数据就会从队尾淘汰一个数据,再将新数据加到对头
这种是最常用的,因为一个key在一段时间内没有被使用那么它在后面被使用的概率就会比较低.
lfu: 使用次数最少的key
原理: 统计每一个key的使用次数,需要淘汰的时候,淘汰使用次数最少的key.
这个有一个问题就是新key加进来使用次数比较低,在内存淘汰的时候新可以容易被淘汰掉
random: 随机key
就是随机挑选一个key进行淘汰.
这个基本很少用
volatile与allkeys的区别
volatile就是淘汰设置了过期时间的key
allkeys就是淘汰所有的key
根据实际需求选择合适的内存淘汰机制,一般会使用volatile进行淘汰,因为没设置过期时间一般默认为需要长期缓存的数据.