【前言】
看到这里就晓得了,之前那一一篇文章go优雅读取zip压缩包,依旧还是有些问题,接下来,我就开始描述下本文章讲述的内容:

  1. 面对需要多次读取多个zip压缩包里的指定文件内容,如何提升读取的速度;
  2. 在提升速度的过程中,如何一步步找到内存占用和读取速度的平衡点;

!!!注意:
本次文章需要结合上一篇食用,本章不会直接贴全部实现代码,只提供核心实现代码和实现逻辑

结合上一次提到的通过读取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内

在这里插入图片描述

【现有不足】访问模式的变化,需要一段时间才能逐渐将这部分新的数据加载到缓存中,替换掉原本的数据,这时候的缓存命中率下降,从原始的数据源拿去数据,防护速度会降低(后续会想下其余办法解决下这个问题)

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐