【前言】
看到这里就晓得了,之前那一一篇文章go优雅读取zip压缩包,依旧还是有些问题,接下来,我就开始描述下本文章讲述的内容:
- 面对需要多次读取多个zip压缩包里的指定文件内容,如何提升读取的速度;
- 在提升速度的过程中,如何一步步找到内存占用和读取速度的平衡点;
!!!注意:
本次文章需要结合上一篇食用,本章不会直接贴全部实现代码,只提供核心实现代码和实现逻辑
结合上一次提到的通过读取zip的内存地址,来读取指定文件内容,后续发现一个问题。
Ⅰ 全局map实现快速读取指定文件
【问题】每次都需要去读取zip的内存地址,这个过程十分消耗时间,经测试后,发现读取速度居然要100ms左右
【解决办法】采用全局map,来存储下每一个zip包的内存地址,下次使用的时候,直接调用这个全局map,根据key-value,读取指定zip包的内存地址,后续就可以做到速率大幅度提升
【效果】 读取速度初次为50ms~100ms,后续的读取速度稳定到10ms左右
【相关代码如下】
var (
tarFiles = make(map[string]*zip.Reader) //创建一个全局的map来缓存已经打开的tar文件内存地址
FSys fs.FS
)
func main() {
//读取压缩包
ctx := context.Background()
fsys, ok := tarFiles[archivePath]
if !ok {
var err error
// 打开zip文件
FSys, err = archiver.FileSystem(ctx, archivePath)
if err != nil {
fmt.Println("----从缓存中获取tar文件Error:", err)
}
if fsinfo, ok := FSys.(*zip.Reader); ok {
tarFiles[archivePath] = fsinfo
fsys = fsinfo
} else {
fmt.Println("----压缩包初始化失败:", err)
}
// 释放内存
FSys = nil
}
// 其余代码
}
Ⅱ LRU找到内存占用和读取速度的平衡点
【现遇到问题】采用全局map存储zip内存地址,当处理大量的zip文件时,这将导致内存占用偏高,而且这个内存不会下降。
【解决办法】
采用缓存淘汰策略LRU算法(最近最少使用,根据数据的历史访问记录来进行淘汰数据)。这样,你可以在内存中保持一定数量的压缩包,当超过这个数量时,最不常用的压缩包将被从内存中删除,控制下存储的量 设置为25个,这样可以在速度和内存消耗之间找到一个平衡。
实操演练
// 第三种:采用LRU内存淘汰策略
fsys, err := zipFiles.Get(archivePath)
if err != nil {
FSys, err = archiver.FileSystem(ctx, archivePath)
if err != nil {
fmt.Println("----从缓存中获取tar文件Error:", err)
return nil, err
}
if fsinfo, ok := FSys.(*zip.Reader); ok {
zipFiles.Add(archivePath, fsinfo)
fsys = fsinfo
} else {
fmt.Println("----压缩包初始化失败:", err)
return nil, err
}
FSys = nil
}
LRU算法实现
package core
import (
zip "bt-tamper_proof_go/public/archiver/zip"
"container/list"
"errors"
"sync"
)
// entry
// @author: 小小吴同学<2024-03-30>
// @Description: LRU 使用缓存淘汰策略算法
type entry struct {
key string
value *zip.Reader
}
type LRUCache struct {
maxEntries int
ll *list.List
cache map[string]*list.Element
lock sync.Mutex
}
func NewLRUCache(maxEntries int) *LRUCache {
return &LRUCache{
maxEntries: maxEntries,
ll: list.New(),
cache: make(map[string]*list.Element),
}
}
/*
* @author: 小小吴同学<2024-03-30 12:03:48>
* @Description: 判断是否内存地址存在,不存在则创建,存在则删除再建
* @param key
* @param value
*/
func (c *LRUCache) Add(key string, value *zip.Reader) {
c.lock.Lock()
defer c.lock.Unlock()
if ee, ok := c.cache[key]; ok {
c.ll.MoveToFront(ee)
ee.Value.(*entry).value = value
return
}
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
if c.maxEntries != 0 && c.ll.Len() > c.maxEntries {
c.removeOldest()
}
}
func (c *LRUCache) Get(key string) (*zip.Reader, error) {
c.lock.Lock()
defer c.lock.Unlock()
if ele, hit := c.cache[key]; hit {
c.ll.MoveToFront(ele)
return ele.Value.(*entry).value, nil
}
return nil, errors.New("Key not found")
}
func (c *LRUCache) removeOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
}
}
【效果】
- 压测108个网站 新增文件1000次,读取文件稳定;
- 内存占用均60M,文件防护速度保持在10ms内
【现有不足】访问模式的变化,需要一段时间才能逐渐将这部分新的数据加载到缓存中,替换掉原本的数据,这时候的缓存命中率下降,从原始的数据源拿去数据,防护速度会降低(后续会想下其余办法解决下这个问题)