69.使用Go标准库compress/gzip压缩数据存入Redis避免BigKey
文章目录
- 一:简介
- 二:Go标准库compress/gzip包介绍
- Constants
- Variables
- type Header
- type Reader
- 三:代码实践
- 1、压缩与解压工具包
- 2、单元测试
- 3、为何压缩后还要用base64编码
代码地址: https://gitee.com/lymgoforIT/golang-trick/tree/master/41-go-gzip
一:简介
在工作中,我们有时候需要用到Redis来缓存数据,减轻DB的压力,并提升访问性能。但是当要缓存的key数据过大了会带来BigKey问题:30.Go处理Redis BigKey
在介绍BigKey问题的影响和解决办法时,我们其实说过,最好的办法是预防BigKey的出现,而不是出现后去解决。
但有些场景要缓存的一条信息内容可能就是比较大,且无法拆分,这时候怎么办呢?那就压缩后再存入Redis吧。
二:Go标准库compress/gzip包介绍
compress/gzip是Go标准库中的一个压缩工具包,中文文档地址:https://studygolang.com/static/pkgdoc/pkg/compress_gzip.htm
gzip包实现了gzip格式压缩文件的读写,核心内容如下。
-
Constants:常量定义
-
Variables:变量定义
-
type Header:数据头结构
-
type Reader:解压
- func NewReader(r io.Reader) (*Reader, error)
- func (z *Reader) Reset(r io.Reader) error
- func (z *Reader) Read(p []byte) (n int, err error)
- func (z *Reader) Close() error
-
type Writer:压缩
- func NewWriter(w io.Writer) *Writer
- func NewWriterLevel(w io.Writer, level int) (*Writer, error)
- func (z *Writer) Reset(w io.Writer)
- func (z *Writer) Write(p []byte) (int, error)
- func (z *Writer) Flush() error
- func (z *Writer) Close() error
Constants
const (NoCompression = flate.NoCompression // 不压缩BestSpeed = flate.BestSpeed // 最快速度BestCompression = flate.BestCompression // 最佳压缩比DefaultCompression = flate.DefaultCompression // 默认压缩比
)
这些常量都是拷贝自flate包,因此导入"compress/gzip"后,就不必再导入"compress/flate"了。
Variables
var (// 当读取的gzip数据的校验和错误时,会返回ErrChecksumErrChecksum = errors.New("gzip: invalid checksum")// 当读取的gzip数据的头域错误时,会返回ErrHeaderErrHeader = errors.New("gzip: invalid header")
)
type Header
//数据头结构
type Header struct {Comment string // 文件注释Extra []byte // 附加数据ModTime time.Time // 文件修改时间Name string // 文件名OS byte // 操作系统类型
}
gzip文件保存一个头域,提供关于被压缩的文件的一些元数据。该头域作为Writer和Reader类型的一个可导出字段,可以提供给调用者访问。
type Reader
type Reader struct {Header// 内含隐藏或非导出字段
}
Reader类型满足io.Reader接口,可以从gzip格式压缩文件读取并解压数据。
一般情况下,一个gzip文件可以是多个gzip文件的串联,每一个都有自己的头域。从Reader读取数据会返回串联的每个文件的解压数据,但只有第一个文件的头域被记录在Reader的Header字段里。
gzip文件会保存未压缩前数据的长度与校验和。当读取到未压缩数据的结尾时,如果数据的长度或者校验和不正确,Reader会返回ErrCheckSum,如果没有读取完毕后,长度和校验和与压缩前数据的一致,说明解压成功。因此,调用者应该将Read方法返回的数据视为暂定的,直到他们在数据结尾获得了一个io.EOF。
func NewReader
func NewReader(r io.Reader) (*Reader, error)
NewReader返回一个从r读取并解压数据的Reader。其实现会缓冲输入流的数据,并可能从r中读取比需要的更多的数据(如长度和校验和)。调用者有责任在读取完毕后调用返回值的Close方法。
注意入参是io.Reader,返回值是*gzip.Reader,但*gzip.Reader(Struct)也是实现了io.Reader接口的
func (*Reader) Reset
func (z *Reader) Reset(r io.Reader) error
Reset将z重置,丢弃当前的读取状态,并将下层读取目标设为r。效果上等价于将z设为使用r重新调用NewReader返回的Reader。这让我们可以重用z而不是再申请一个新的。(因此效率更高)
func (*Reader) Read
func (z *Reader) Read(p []byte) (n int, err error)
func (*Reader) Close
func (z *Reader) Close() error
调用Close会关闭z,但不会关闭下层io.Reader接口。
type Writer
type Writer struct {Header// 内含隐藏或非导出字段
}
Writer满足io.WriteCloser接口。它会将提供给它的数据压缩后写入下层io.Writer接口。
func NewWriter
func NewWriter(w io.Writer) *Writer
NewWriter创建并返回一个Writer。写入返回值gzip.Writer的数据都会在压缩后写入入参指定的w中。调用者有责任在结束写入后调用返回值gzip.Writer的Close方法。因为写入的数据可能保存在缓冲中没有刷新入下层。
如要设定Writer.Header字段,调用者必须在第一次调用Write方法或者Close方法之前设置。Header字段的Comment和Name字段是go的utf-8字符串,但下层格式要求为NUL中止的ISO 8859-1 (Latin-1)序列。如果这两个字段的字符串包含NUL或非Latin-1字符,将导致Write方法返回错误。
func NewWriterLevel
func NewWriterLevel(w io.Writer, level int) (*Writer, error)
NewWriterLevel类似NewWriter但指定了压缩水平而不是采用默认的DefaultCompression。
参数level可以是DefaultCompression、NoCompression或BestSpeed与BestCompression之间的任何整数。如果level合法,返回的错误值为nil。
func (*Writer) Reset
func (z *Writer) Reset(dst io.Writer)
Reset将z重置,丢弃当前的写入状态,并将下层输出目标设为dst。效果上等价于将w设为使用dst和w的压缩水平重新调用NewWriterLevel返回的*Writer。这让我们可以重用z而不是再申请一个新的。(因此效率更高)
func (*Writer) Write
func (z *Writer) Write(p []byte) (int, error)
Write将p压缩后写入下层io.Writer接口。压缩后的数据不一定会立刻刷新,除非Writer被关闭或者显式的刷新,即调用Close方法或者Flush方法。
func (*Writer) Flush
func (z *Writer) Flush() error
Flush将缓冲中的压缩数据刷新到下层io.Writer接口中。
本方法主要用在传输压缩数据的网络连接中,以保证远端的接收者可以获得足够的数据来重构数据报。Flush会阻塞直到所有缓冲中的数据都写入下层io.Writer接口后才返回。如果下层的io.Writetr接口返回一个错误,Flush也会返回该错误。在zlib包的术语中,Flush方法等价于Z_SYNC_FLUSH。
func (*Writer) Close
func (z *Writer) Close() error
调用Close会关闭z,但不会关闭下层io.Writer接口。
三:代码实践
1、压缩与解压工具包
package utilimport ("bytes""compress/gzip""context""encoding/base64""fmt"
)// GzipEncode 采用gzip算法压缩字符串,输出base64编码的字符串
func GzipEncode(ctx context.Context, input string) (string, error) {if len(input) == 0 {return input, nil}var b bytes.Buffer // 实现了io.Writergz := gzip.NewWriter(&b)defer func() {if err := gz.Close(); err != nil {fmt.Println(fmt.Sprintf("gz.Close() err:%v", err))}}()if _, err := gz.Write([]byte(input)); err != nil {fmt.Println(fmt.Sprintf("[GzipEncode] gz write err:%v", err))return "", err}// 将gzip.Writer缓冲中的数据刷到底层io.Writer中if err := gz.Flush(); err != nil {fmt.Println(fmt.Sprintf("[GzipEncode] gz flush err:%v", err))return "", err}// 在读取数据之前必须close,否则读取的数据会有问题,在这里作用同Flush一样// 即将压缩后的数据立即写入底层io.Writer中,在这里是b(bytes.Buffer)if err := gz.Close(); err != nil {fmt.Println(fmt.Sprintf("[GzipEncode] gz close err:%v", err))return "", err}newStr := base64.StdEncoding.EncodeToString(b.Bytes())return newStr, nil
}// GzipDecode 采用gzip算法解压字符串
func GzipDecode(ctx context.Context, input string) (string, error) {newInput, err := base64.StdEncoding.DecodeString(input)if err != nil {fmt.Println(fmt.Sprintf("[GzipDecode] base decode err:%v", err))return "", err}bReader := bytes.NewReader(newInput)gReader, err := gzip.NewReader(bReader)if err != nil {fmt.Println(fmt.Sprintf("[GzipDecode] new reader err,%v", err))return "", err}if err = gReader.Close(); err != nil {fmt.Println(fmt.Sprintf("[GzipDecode] reader close err:%v", err))return "", err}buf := new(bytes.Buffer)if _, err = buf.ReadFrom(gReader); err != nil {fmt.Println(fmt.Sprintf("[GzipDecode] read from greader err:%v", err))return "", err}return buf.String(), err
}
2、单元测试
package utilimport ("context""fmt""testing"
)func TestGzipEncode(t *testing.T) {encode, err := GzipEncode(context.Background(), "hello world")if err != nil {fmt.Println(fmt.Sprintf("GzipEncode err:%v", err))return}fmt.Println(fmt.Sprintf("res:%v", encode))
}func TestGzipDecode(t *testing.T) {decode, err := GzipDecode(context.Background(), "H4sIAAAAAAAA/8pIzcnJVyjPL8pJAQAAAP//AQAA//+FEUoNCwAAAA==")if err != nil {fmt.Println(fmt.Sprintf("GzipEncode err:%v", err))return}fmt.Println(fmt.Sprintf("res:%v", decode))
}
当执行TestGzipEncode后,我们可以看到输出为

当执行TestGzipDecode后,可以看到输出为

测试结果符合预期。
此时可能会有一个疑问,一个hello world压缩并用base64编码后,字符串长度更长了,这不是与压缩的初衷背道而驰了嘛?是的,这里只是为了演示,所以用了个hello world,实际场景下,既然选择了压缩,那肯定是已知要压缩的内容是比较大的。
比如我们稍微将字符串变长一些,看看效果就知道压缩对于减少内存占用确实是有用的

3、为何压缩后还要用base64编码
我们可以将util中的源代码去掉base64编码后,单元测试一下看看原始gzip压缩后,字符串会是什么形式呢?

可以看到压缩后字符串确实很小了,但是美观,且复制后对其解压会报错GzipEncode err:unexpected EOF,所以还是把base64编码用上吧!!
相关文章:
69.使用Go标准库compress/gzip压缩数据存入Redis避免BigKey
文章目录 一:简介二:Go标准库compress/gzip包介绍ConstantsVariablestype Headertype Reader 三:代码实践1、压缩与解压工具包2、单元测试3、为何压缩后还要用base64编码 代码地址: https://gitee.com/lymgoforIT/golang-trick/t…...
JavaScript实现的一些小案例
小案例 灯开关案例 <body><img id"light" src"img/off.jpg"><script>var light document.getElementById("light");var flag false;if(flag){light.src "img/on.jpg";flag false;}else{light.src "img/…...
MVC模式
Model-View-Controller : 模型-视图-控制器模式,用于应用程序的分层开发。 Model(模型):代表一个存取数据的对象。也可以带有逻辑,在数据变化时更新控制器。 View(视图):代表模型包含的数据的可视化。 Controller(控制器)…...
Java中的代理模式(一)
大家好👋,我是极客涛😎,今天我们聊一聊java中的代理模式,话不多说,还是老思路,什么是代理模式,为什么要有代理模式,如何实现代理模式 代理模式 在说java中的代理模式之前…...
跳跃游戏-算法
题目 给定一个数组nums {1,2,3,4,5},每个元素nums[i]表示从i这个位置最多可以向前跳跃nums[i]个台阶,求最小需要跳几次就可以调到末尾 思路 反向查找 从末尾开始逐个向前判断最远的起跳位置,接着再以该位置递归的判断 public int jumpT…...
ERP系统哪个好用?用友,金蝶,ORACLE,SAP综合测评
ERP系统哪个好用?用友,金蝶,ORACLE,SAP综合测评 ERP领域SAP、ORACLE相对于国内厂商如用友、金蝶优势在哪? SAP,ORACLE操作习惯一般国人用不惯;相对于国产软件,界面也很难看&#x…...
外汇天眼:美国证券交易委员会(SEC)采纳了一系列规定,以加强与特殊目的收购公司(SPACs)相关的投资者保护
美国证券交易委员会(SEC)今天通过了一系列新规和修订,以增强特殊目的收购公司(SPACs)的首次公开募股(IPOs)中的披露,并在SPACs与目标公司之间的后续业务合并交易(de-SPAC…...
kotlin map 与 flatmap
kotlin map 与 flatmap 是2个不同的概念的 map 是一种数据结构,flatmap 是一个高阶函数,处理集合用的 Map Map 是一种数据结构,它由一系列的键值对组成,每个键都是唯一的,并且与一个特定的值相关联。你可以通过键来…...
nginx-rtmp-module 支持 Enhancing RTMP HEVC(H.265)
Enhancing RTMP, FLV 2023年7月31号正式发布,主要支持了HEVC(H.265)、VP9、AV1视频编码,发布差不多半年了,很多开源项目已支持,最近打算播放和推送端也支持下,想找个支持的rtmp server方便测试用,但没找到合…...
2024最新JDK1.8+JDK17+JDK21安装包下载+文档
2024年更新,JDK8的64位和32位安装包都有,Java8最新文档也有,JDK17和JDK21的最新安装包也有 因为网上的安装包都不是最新的,所以自己去Oracle官网登录下载保存了一份,需要的朋友下面网盘链接下载 JDK8—64位安装程序&…...
如何利用chatgpt提升工作效率
chatgpt全领域小助手 项目管理:制定项目计划、跟踪进度、分配任务和记录里程碑。客户服务:回答常见问题、提供产品支持和处理客户投诉,提升客户满意度。销售支持:提供销售培训、销售脚本和客户资料,辅助销售团队进行销…...
WinSCP下载安装并实现远程SSH本地服务器上传文件
文章目录 1. 简介2. 软件下载安装:3. SSH链接服务器4. WinSCP使用公网TCP地址链接本地服务器5. WinSCP使用固定公网TCP地址访问服务器 1. 简介 Winscp是一个支持SSH(Secure SHell)的可视化SCP(Secure Copy)文件传输软件,它的主要功能是在本地与远程计…...
QEMU搭建arm虚拟机开发环境
获取QEMU代码 git clone https://gitlab.com/qemu-project/qemu.git 切换对应的工程分支 使用git指令切换到对应的分支上,我这里使用的是stable-4.0的分支 git checkout -b stable-4.0 remotes/origin/stable-4.0 配置&编译 在工程的根目录下执行 ./conf…...
web 应用常见的安全问题
一xss攻击 人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。 跨站脚本攻击ÿ…...
502. IPO
502. IPO 题目链接:502. IPO 代码如下: //堆的使用 class Solution { public:int findMaximizedCapital(int k, int w, vector<int>& profits, vector<int>& capital) {vector<pair<int,int>> mp;//优先队列默认的是大…...
如何安装MeterSphere并实现无公网ip远程访问服务管理界面
文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能学习网站, 通…...
做FP独立站怎么引流?这个引流法宝收好了!
近年来,由于卖家数量飙升导致平台竞争持续升级,卖家之间的恶性循环竞争以及平台政策的不断调整等,造成了众多亚马逊等跨境卖家纷纷从平台转向独立站。可是,转型做独立站前要先考虑清楚独立站与平台二者之间的区别。 如果在第三方平…...
幻兽帕鲁PalWorld服务器搭建教程,1分钟开服,纯小白教程,无需基础
雨云面板服快速开幻兽帕鲁PalWorld服务器的教程,配置文件修改方法和配置项中文注释。 最近这游戏挺火,很多人想跟朋友联机,如果有专用服务器,就不需要房主一直开着电脑,稳定性也好得多。 幻兽帕鲁简介 《幻兽帕鲁》…...
算法小抄01
1. 计数排序是一种基于 统计 的排序算法 2. 基于比较的排序算法有:(1)直接插入排序;(2)冒泡排序;(3)简单选择排序;(4)希尔排序&#…...
Spring Boot 集成 API 文档 - Swagger、Knife4J、Smart-Doc
文章目录 1.OpenAPI 规范2.Swagger: 接口管理的利器3.Swagger 与 SpringFox:理念与实现4.Swagger 与 Knife4J:增强与创新5.案例:Spring Boot 整合 Swagger35.1 引入 Swagger3 依赖包5.2 优化路径匹配策略兼容 SpringFox5.3 配置 Swagger5.4 S…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
