bigcache

news2024/9/24 4:17:35

bigcache

介绍

借用下图片,实际上,这张图还不太全,queueItem 中,entrydata的最前端 8 字节是时间戳,用来计算过期时间的。
请添加图片描述

bigcache 的思想主要有以下几点:

  • 大并发下,尽量减少同步带来的时间损耗
  • 大缓存下,避免 GC 带来的扫描时延

除了这两点,bigcache 支持传统的一下两点缓存特点:

  • 数据过期
  • 最大内存上限

除此之外,也有一些缺点的。

  • 内存只会有增长,不会有下降,除非使用 reset 函数,这相当于重新使用一套 cache了,不算是内存管理上的下降
  • 覆盖写同名 key 有很大的内存管理问题,覆盖写只是用 map 里面删去了访问的索引,以及重置了数据段为0 值,没有修改len 段以及时间戳段

如何增加并发性能

常规内存库都是使用 map,没有 map 是不行的,因为访问不够快。但不论是普通的 map加锁, 还是sync.Map,都有并发方面的性能损耗,为了规避这个问题。

bigcache 直接用多个 map 来解决。至于落到哪个 map,就看 hash 函数怎么决定了。

如何减少 GC 压力

GC 压力是什么?

对于Go语言中的map, 垃圾回收器在 mark和scan阶段检查map中的每一个元素, 如果缓存中包含数百万的缓存对象,垃圾回收器对这些对象的无意义的检查导致不必要的时间开销。

那么如何解决这个问题呢?

Go 1.5中一个修复有关(#9477), 这个issue还是描述了包含大量对象的map的垃圾回收时的耗时问题,Go的开发者优化了垃圾回收时对于map的处理,如果map对象中的key和value不包含指针,那么垃圾回收器就会对它们进行优化:

所以如果我们的对象不包含指针,虽然也是分配在堆上,但是垃圾回收可以无视它们。

如果我们把map定义成map[int]int,就会发现gc的耗时就会将下来了。

遗憾的是,我们没办法要求用户的缓存对象只能包含int、bool这样的基本数据类型。

解决办法就是使用哈希值作为map[int]int的key。 把缓存对象序列化后放到一个预先分配的大的字节数组中,然后将它在数组中的offset作为map[int]int的value。

bigchache 怎么定时清理过期数据

		go func() {
			ticker := time.NewTicker(config.CleanWindow)
			defer ticker.Stop()
			for {
				select {
				case <-ctx.Done():
					fmt.Println("ctx done, shutting down bigcache cleanup routine")
					return
				case t := <-ticker.C:
					cache.cleanUp(uint64(t.Unix()))
				case <-cache.close:
					return
				}
			}
		}()
  • 每个 shard 都会检查
  • 每个 shard 内部的entries会检查到没过期的为止
  • 清理只是移动BytesQueue中的首尾指针,并不是整理内存碎片,也不会gc,也不会归还内存
  • 会删除 map 中的 key,所以就访问不到这部分内存

覆盖写

	if previousIndex := s.hashmap[hashedKey]; previousIndex != 0 {
		if previousEntry, err := s.entries.Get(int(previousIndex)); err == nil {
			resetKeyFromEntry(previousEntry)
			//remove hashkey
			delete(s.hashmap, hashedKey)
		}
	}
...
	for {
		if index, err := s.entries.Push(w); err == nil {
			s.hashmap[hashedKey] = uint32(index)
			s.lock.Unlock()
			return nil
		}
		if s.removeOldestEntry(NoSpace) != nil {
			s.lock.Unlock()
			return fmt.Errorf("entry is bigger than max shard size")
		}
	}
  • 覆盖写发生在 set 时
  • 覆盖写不会移动任何BytesQueue中的指针,因此会在BytesQueue形成空洞,这个空洞只是无法访问,在过期之前,它都占用内存,也无法被重复利用
  • 写入操作会只看 ByteQueue指针之外的空间是否足够,如果不够,强制移除最旧的元素,不管过不过期

如何减少覆盖写带来的内存浪费?

被覆盖写key 浪费的内存只有被过期重复利用,以及在内存不够时强制覆盖。

bigcache 内存扩展

func (q *BytesQueue) Push(data []byte) (int, error) {
	neededSize := getNeededSize(len(data))

	if !q.canInsertAfterTail(neededSize) {
		if q.canInsertBeforeHead(neededSize) {
			q.tail = leftMarginIndex
		} else if q.capacity+neededSize >= q.maxCapacity && q.maxCapacity > 0 {
			return -1, &queueError{"Full queue. Maximum size limit reached."}
		} else {
			q.allocateAdditionalMemory(neededSize)
		}
	}

	index := q.tail

	q.push(data, neededSize)

	return index, nil
}

什么时候会发生内存的扩张?

当内存队列的尾巴,头部都没有空余位置,并且没有超过最大容量时扩展

扩展多大?

简单来说会扩展到原来的两倍,但如果申请的比原来的还打

使用bigcache时遇到一个问题。反复覆盖多个 key(key 的数量不是很多个位数,value 内容也不多,kb 级别),但是内存初始值非常高,而且后续随着覆盖写同名 key,内存有逐步上升。

首先贴出我的启动配置:

config := bigcache.Config{
		Shards:             1,
		LifeWindow:         1 * time.Second,
		CleanWindow:        2 * time.Second,
		MaxEntriesInWindow: 1,
		MaxEntrySize:       10,
}
BigCache, initErr = bigcache.New(context.Background(), config)

for {
		buf := make([]byte, 1024*1024)
		err := BigCache.Set(rangeKey(), buf)
		if err != nil {
			fmt.Println(err)
		}
		time.Sleep(time.Millisecond * 100)
	}

这里每次写入1MB 数据,2 秒会扫描一次清理过期数据,超过 1 秒的数据被标记为死亡。

观察到常驻内存在 60MB。

这里调整过很多参数,下面逐步说明参数意义。

Shards

shards 的目的是为了分成多个 map 管理,这样锁的粒度小一些,并发更高。但要合理设置 shards,尽量不要有多余的。因为初始化内存大小为

shards * MaxEntrySize * MaxEntriesInWindow

实际上设置为 1 一样的用,只是锁的冲突会高一点。

MaxEntriesInWindow和MaxEntrySize

只是决定初始化内存大小的参数,设置比较小的话初始化会消耗一些时间,但是再低也不会影响实际需要的内存大小,尽量开低点比较好。

CleanWindow和LifeWindow

LifeWindow是生命周期,按实际需要填写,不仅仅是有效数据遵循 lifewindow,连被覆盖的无效数据也有!!!所以这个参数一定不能为一个很大的值。

func (s *cacheShard) isExpired(oldestEntry []byte, currentTimestamp uint64) bool {
	oldestTimestamp := readTimestampFromEntry(oldestEntry)
	if currentTimestamp <= oldestTimestamp { // if currentTimestamp < oldestTimestamp, the result will out of uint64 limits;
		return false
	}
	return currentTimestamp-oldestTimestamp > s.lifeWindow
}

CleanWindow是清理的窗口时间,填写的话,就会定时清理,如果不设置,那么就会在每次 set 的时候去检查清理一下,仅清理一条。

func (s *cacheShard) set(key string, hashedKey uint64, entry []byte) error {
	if !s.cleanEnabled {
		if oldestEntry, err := s.entries.Peek(); err == nil {
			s.onEvict(oldestEntry, currentTimestamp, s.removeOldestEntry)
		}
	}
}

按照这个代码来看,不存在不过期的数据,因此要设置较长时间才可以。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1012845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3 分钟,带你了解低代码开发

一、低代码平台存在的意义 传统软件开发交付链中&#xff0c;需求经过3次传递&#xff0c;用户→业务→架构师→开发&#xff0c;每一层传递都可能使需求失真&#xff0c;导致最终交付的功能返工。 业务的变化促使软件开发过程不断更新、迭代和演进&#xff0c;而低代码开发即是…

2023!6招玩转 Appium 自动化测试

Appium是个什么鬼 Appium是一个移动端的自动化框架&#xff0c;可用于测试原生应用&#xff0c;移动网页应用和混合型应用&#xff0c;且是跨平台的。可用于IOS和Android以及firefox的操作系统。原生的应用是指用android或ios的sdk编写的应用&#xff0c;移动网页应用是指网页…

合约谈崩,3大汽车厂工人集体罢工 | 百能云芯

周五&#xff0c;美国联合汽车工会&#xff08;UAW&#xff09;在底特律三大汽车制造商通用汽车、福特汽车和克莱斯勒母公司Stellantis旗下的各一家工厂同步举行了罢工&#xff0c;可能因工资和就业保障问题引发一场代价高昂且长时间的对峙。 协商签订新劳资协议的最后期限已过…

【多线程】死锁 详解

死锁 一. 死锁是什么二. 死锁的场景1. 一个线程一把锁2. 两个线程两把锁3. N 个线程 M 把锁 三. 死锁产生的四个必要条件四. 如何避免死锁 一. 死锁是什么 死锁是这样一种情形&#xff1a; 多个线程同时被阻塞&#xff0c;因为每个进程都在等其他线程释放某些资源&#xff0c;…

在波卡区块链学院学习 Web3 是种什么体验?

成立于 2022 年的 Polkadot Blockchain Academy&#xff08;波卡区块链学院&#xff0c;以下简称 PBA&#xff09;是由波卡生态成立的一个深入的、开创性的区块链课程&#xff0c;致力于帮助 Web3 创新者和未来的顶级编程人员实现他们的想法和抱负。 波卡区块链学院由波卡创始…

LeetCode 1222. 可以攻击国王的皇后【数组,模拟】1391

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

Yakit学习

Yakit下载 下载地址&#xff1a; yaklang/yakit: Cyber Security ALL-IN-ONE Platform (github.com) MITM交互式劫持 这个模块相当于burpsuite的proxy模块&#xff0c;MITM 操作台可百分百替代 BurpSuite执行所有操作(下载并安装证书、劫持请求、响应、编辑劫持到的数据包等)。…

Datax 数据同步-使用总结(二)

一、前言 这部分主要记录 datax 实现增量同步的方案。 二、核心思路 结合datax 提供的preSql、 postSql以及占位符&#xff0c;外加另外一张表同步日志表来记录相关同步信息。 三、版本迭代 3.1 初版本 where tbq.opera_date > cast(date_format(DATE_SUB(NOW(), inte…

图论第四天|127. 单词接龙、841. 钥匙和房间、463. 岛屿的周长

127. 单词接龙 ★ 文档讲解 &#xff1a;代码随想录 - 127. 单词接龙 状态&#xff1a;开始学习。&#xff08;★&#xff1a;需要多次回顾并重点回顾&#xff09; 思路&#xff1a; 本题需要解决两个问题&#xff1a; 图中的线是如何连在一起的 题目中并没有给出点与点之间的…

【Robotframework+python】实现http接口自动化测试

前言 下周即将展开一个http接口测试的需求&#xff0c;刚刚完成的java类接口测试工作中&#xff0c;由于之前犯懒&#xff0c;没有提前搭建好自动化回归测试框架&#xff0c;以至于后期rd每修改一个bug&#xff0c;经常导致之前没有问题的case又产生了bug&#xff0c;所以需要…

长城汽车,能打“持久战”吗?

文丨智能相对论 作者丨沈浪 百年汽车工业史正在进入一个全新的发展阶段&#xff1a;油改电的变革仍在激化&#xff0c;智能化的探索才刚刚起步&#xff0c;汽车产品将以什么样的面貌展现在世人面前&#xff0c;市场格局又将迎来怎样的变化&#xff1f;无人可知。 然而&#…

【Linix-Day12-线程同步和线程安全】

线程同步 和 线程安全 线程同步 除了信号量和互斥锁&#xff08;互斥锁和条件变量上次介绍过&#xff09;&#xff0c;还有两种方式同步 1.读写锁 当同时对一块内存读写时&#xff0c;会出现下列问题&#xff0c;故而引入读写锁 接口介绍&#xff1a; 1.int pthread_rwloc…

PostgreSQL 事务并发锁

文章目录 PostgreSQL 事务大家都知道的 ACID事务的基本使用保存点 PostgreSQL 并发并发问题MVCC PostgreSQL 锁机制表锁行锁 总结 PostgreSQL 事务 大家都知道的 ACID 在日常操作中&#xff0c;对于一组相关操作&#xff0c;通常要求要么都成功&#xff0c;要么都失败。在关系…

Windows PHP 将 WORD转PDF,执行完成后 释放进程

Windows PHP 将 WORD转PDF,执行完成后 释放进程 word转PDF清理任务进程 【附赠彩蛋】每次PHP执行完word转pdf之后,在任务进程中都会生成并残留WINWORD.EXE进程,时间久了,服务器就会越来原卡,本文完整的讲述怎么转PDF和转换之后的操作。 word转PDF /**$doc 传入完整的doc路…

flutter run长时间卡在Running Gradle task “assembleDebug“问题解决

1.下载离线gradle, 在android>>gradle>>wrapper 中找到gradle-wrappper.properties 可以看到要下载的gradle的版本 下载官方链接,更改url的版本号就好 Gradle | Thank you for downloading Gradle! 在android>>gradle>>wrapper 中找到gradle-wra…

【C++从0到王者】第三十二站:异常

文章目录 一、C语言传统的处理错误的方式二、C异常概念三、异常的使用四、异常的抛出与捕获1.异常的抛出原则2.在函数调用链中异常栈展开匹配原则 五、实际应用中的异常使用六、C标准库的异常体系七、异常规范八、异常安全九、异常的优缺点总结 一、C语言传统的处理错误的方式 …

计网第五章(运输层)(四)(TCP的流量控制)

一、基本概念 流量控制就是指让发送方的发送速率不要太快&#xff0c;使得接收方来得及接收。可以使用滑动窗口机制在TCP连接上实现对发送方的流量控制。 注意&#xff1a;之前在讨论可靠传输时&#xff0c;讨论过选择重传协议和回退N帧协议都是基于滑动窗口的机制上进行实现…

学生在线查询系统

在教育管理中&#xff0c;学生查询系统是一个必不可少的工具&#xff0c;它能够方便学生、家长和教师快速获取学生的各项信息。而易查分作为一个功能强大的在线查询工具&#xff0c;能够帮助教育机构快速搭建一个高效便捷的学生查询系统。通过注册易查分账号&#xff0c;创建查…

Java毕业设计 SSM SpringBoot 水果蔬菜商城

Java毕业设计 SSM SpringBoot 水果蔬菜商城 SSM 水果蔬菜商城 功能介绍 首页 图片轮播 关键字搜索商品 分类菜单 折扣大促销商品 热门商品 商品详情 商品评价 收藏 加入购物车 公告 留言 登录 注册 我的购物车 结算 个人中心 我的订单 商品收藏 修改密码 后台管理 登录 商品…