Golang实现Redis过期时间实例探究

 更新时间:2024年01月24日 10:09:44   作者:绍纳 nullbody笔记  
这篇文章主要介绍了Golang实现Redis过期时间实例探究,

引言

用11篇文章实现一个可用的Redis服务,姑且叫EasyRedis吧,希望通过文章将Redis掰开撕碎了呈现给大家,而不是仅仅停留在八股文的层面,并且有非常爽的感觉,欢迎持续关注学习。

  • [x] easyredis之TCP服务
  • [x] easyredis之网络请求序列化协议(RESP)
  • [x] easyredis之内存数据库
  • [x] easyredis之过期时间 (时间轮实现)
  • [x] easyredis之持久化 (AOF实现)
  • [ ] easyredis之发布订阅功能
  • [ ] easyredis之有序集合(跳表实现)
  • [ ] easyredis之 pipeline 客户端实现
  • [ ] easyredis之事务(原子性/回滚)
  • [ ] easyredis之连接池
  • [ ] easyredis之分布式集群存储

【第四篇】EasyRedis之过期时间

在使用Redis的时候经常会对缓存设定过期时间,例如set key value ex 3,设定过期时间3s,等到过期以后,我们再执行get key正常情况下是得不到数据的。不同的key会设定不同的过期时间1s 5s 2s等等。按照八股文我们知道key过期的时候,有两种删除策略:

  • 惰性删除:不主动删除过期key,当访问该key的时候,如果发现过期了再删除 好处:对CPU友好,不用频繁执行删除,但是对内存不友好,都过期了还占用内存
  • 定时删除:主动删除key,到了key的过期时间,立即执行删除 好处:对内存友好,可以缓解内存压力,对CPU不友好,需要频繁的执行删除

所以redis就把两种策略都实现了,我们看下代码如何使下?

惰性删除

本质就是访问的时候判断下key是否过期,过期就删除并返回空。 代码路径engine/database.go在获取key的值时候,我们会执行一次 db.IsExpire(key)判断key是否过期

func (db *DB) GetEntity(key string) (*payload.DataEntity, bool) {
	// key 不存在
	val, exist := db.dataDict.Get(key)
	if !exist {
		returnnil, false
	}
	// key是否过期(主动检测一次)
	if db.IsExpire(key) {
		returnnil, false
	}
	// 返回内存数据
	dataEntity, ok := val.(*payload.DataEntity)
	if !ok {
		returnnil, false
	}
	return dataEntity, true
}

就是从过期字典ttlDict中获取key的过期时间

  • 如果没有获取到,说明没有设定过期时间(do nothing)

  • 如果有过期时间,并且时间已经过期,主动删除之

// 判断key是否已过期
func (db *DB) IsExpire(key string) bool {
	val, result := db.ttlDict.Get(key)
	if !result {
		returnfalse
	}
	expireTime, _ := val.(time.Time)
	isExpire := time.Now().After(expireTime)
	if isExpire { // 如果过期,主动删除
		db.Remove(key)
	}
	return isExpire
}

定时删除

本质是对key设定一个过期时间,时间一到立即执行删除的任务。 正常的思路肯定是设定一个固定的定时器,例如3s检测一次,这种思路可以,但是存在一个问题,

  • 如果key的过期时间为1s,那你3s才检测是否太不够及时了?

  • 那就把检测间隔设定为1s吧,那如果key的过期时间都为3s,到执行时间检测一遍发现任务都没过期,那不就白白浪费CPU时间了吗?

这就要推出我们的时间轮算法了,时间轮算法就是在模拟现实世界钟表的原理

  • 我想里面增加2个3s的任务,那就将任务添加到距离当前位置pos + 3的位置

  • 同时再加1个5s的任务,那就将任务添加到距离当前位置pos + 5的位置

当钟表的指针指向pos + 3的位置,就执行任务链表的任务即可。 因为钟表是循环往复的运行,那如果我再添加11s的任务,可以发现该任务也是放置到 pos+3的位置,那任务就要区分下,到底是3s的任务还是11s的任务

所以里面又有了一个circle的标记,表示当前任务是第几圈的任务

代码路径tool/timewheel

代码中通过切片模型环,通过链表模拟任务链表

// 循环队列 + 链表
type TimeWheel struct {
	// 间隔
	interval time.Duration
	// 定时器
	ticker *time.Ticker
	// 游标
	curSlotPos int
	// 循环队列大小
	slotNum int
	// 底层存储
	slots []*list.List
	m     map[string]*taskPos
	// 任务通道
	addChannel   chan *task
	cacelChannel chanstring
	// 停止
	stopChannel chanstruct{}
}

当添加任务的时候,需要通过延迟时间计算当前任务的圈数circle

func (tw *TimeWheel) posAndCircle(d time.Duration) (pos, circle int) {
	// 延迟(秒)
	delaySecond := int(d.Seconds())
	// 间隔(秒)
	intervalSecond := int(tw.interval.Seconds())
	// delaySecond/intervalSecond 表示从curSlotPos位置偏移
	pos = (tw.curSlotPos + delaySecond/intervalSecond) % tw.slotNum
	circle = (delaySecond / intervalSecond) / tw.slotNum
	return
}
func (tw *TimeWheel) addTask(t *task) {
	// 定位任务应该保存在循环队列的位置 & 圈数
	pos, circle := tw.posAndCircle(t.delay)
	t.circle = circle
	// 将任务保存到循环队列pos位置
	ele := tw.slots[pos].PushBack(t)
	// 在map中记录 key -> { pos, ele } 的映射
	if t.key != "" {
		// 已经存在重复的key
		if _, ok := tw.m[t.key]; ok {
			tw.cancelTask(t.key)
		}
		tw.m[t.key] = &taskPos{pos: pos, ele: ele}
	}
}

代码中注释的很清晰,也就100多行建议看代码结合上图体会下(很简单)

额外补充

我们在执行set key value ex 3的时候,先设定过期时间为3s,但是在1s的时候,我们又执行了set key value,请问key还会过期吗??

答案:不会过期了。相当于对key去掉了过期时间。所以在代码处理中,我们需要考虑这种情况,重复设定的问题

代码细节位于engine/string.go set命令处理函数func cmdSet(db *DB, args [][]byte) protocal.Reply 的尾部位置

项目代码地址: https://github.com/gofish2020/easyredis 

以上就是Golang实现Redis过期时间实例探究的详细内容,更多关于Golang Redis过期时间的资料请关注脚本之家其它相关文章!

相关文章

  • go解析YAML文件(多文档解析)

    go解析YAML文件(多文档解析)

    本文介绍了如何使用GO语言和client-go库处理YAML文件,特别是在Kubernetes环境下,分析了YAML的特点,如简洁性、易读性、可嵌套性等,并展示了相关代码实现,包括单文档和多文档的处理方法,感兴趣的可以了解一下
    2024-10-10
  • 一文搞懂Golang 值传递还是引用传递

    一文搞懂Golang 值传递还是引用传递

    最多人犯迷糊的就是 slice、map、chan 等类型,都会认为是 “引用传递”,从而认为 Go 语言的 xxx 就是引用传递。正因为它们还引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据,这篇文章主要介绍了Golang 值传递还是引用传递,需要的朋友可以参考下
    2023-01-01
  • 重学Go语言之数组的具体使用详解

    重学Go语言之数组的具体使用详解

    Go的数组是一种复合数据类型,在平时开发中并不常用,更常用的是切片(slice),可以把切片看作是能动态扩容的数组,切片的底层数据结构就是数组,所以数组虽不常用,但仍然有必要掌握
    2023-02-02
  • Go语言对前端领域的入侵WebAssembly运行原理

    Go语言对前端领域的入侵WebAssembly运行原理

    这篇文章主要为大家介绍了不安分的Go语言对Web 前端领域的入侵WebAssembly运行原理实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Go语言常见错误之滥用getters/setters误区实例探究

    Go语言常见错误之滥用getters/setters误区实例探究

    在Go语言编程中,恰如其分地使用getters和setters是至关重要的,过度和不适当地使用它们可能导致代码冗余、可读性差和封装不当,在本文中,我们将深入探讨如何识别滥用getter和setter的情况,以及如何采取最佳实践来避免这些常见的Go错误
    2024-01-01
  • 分布式架构在Go语言网站的应用

    分布式架构在Go语言网站的应用

    分布式架构是目前应对高流量、高并发的重要解决方案,分布式架构的核心思想是分而治之,将单台服务器的资源划分为多台服务器进行协同完成,分布式架构应用于Go语言网站中既能提升服务速度,又能降低了服务器宕机的风险
    2024-01-01
  • golang如何使用gomobile进行Android开发

    golang如何使用gomobile进行Android开发

    golang可以开发android,使用golang开发android需要下载安装gomobile,下面这篇文章主要给大家介绍了关于golang如何使用gomobile进行Android开发的相关资料,需要的朋友可以参考下
    2023-01-01
  • Go语言指针使用分析与讲解

    Go语言指针使用分析与讲解

    这篇文章主要介绍了Go语言指针使用分析与讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • 在Mac中搭建go语言开发环境的操作步骤

    在Mac中搭建go语言开发环境的操作步骤

    go语言在开发效率和运行效率中的优势让很多人青睐,所以有倾向打算转向go语言的开发。下面介绍在Mac中golang的开发环境配置。有需要的可以参考借鉴。
    2016-08-08
  • Go语言中的延迟函数defer示例详解

    Go语言中的延迟函数defer示例详解

    众所周知golang的defer优雅又简洁, 是golang的亮点之一。所以下面这篇文章主要给大家介绍了关于Go语言中延迟函数defer的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-10-10

最新评论