压缩Redis里的字符串大对象操作


Posted in Redis onJune 23, 2021

背景

Redis缓存的字符串过大时会有问题。不超过10KB最好,最大不能超过1MB。

有几个配置缓存,上千个flink任务调用,每个任务5分钟命中一次,大小在5KB到6MB不等,因此需要压缩。

第一种,使用gzip

/**
 * 使用gzip压缩字符串
 */
public static String compress(String str) {
    if (str == null || str.length() == 0) {
        return str;
    }
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    GZIPOutputStream gzip = null;
    try {
        gzip = new GZIPOutputStream(out);
        gzip.write(str.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (gzip != null) {
            try {
                gzip.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return new sun.misc.BASE64Encoder().encode(out.toByteArray());
}
 
/**
 * 使用gzip解压缩
 */
public static String uncompress(String compressedStr) {
    if (compressedStr == null || compressedStr.length() == 0) {
        return compressedStr;
    }
 
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ByteArrayInputStream in = null;
    GZIPInputStream ginzip = null;
    byte[] compressed = null;
    String decompressed = null;
    try {
        compressed = new sun.misc.BASE64Decoder().decodeBuffer(compressedStr);
        in = new ByteArrayInputStream(compressed);
        ginzip = new GZIPInputStream(in);
        byte[] buffer = new byte[1024];
        int offset = -1;
        while ((offset = ginzip.read(buffer)) != -1) {
            out.write(buffer, 0, offset);
        }
        decompressed = out.toString();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (ginzip != null) {
            try {
                ginzip.close();
            } catch (IOException e) {
            }
        }
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
            }
        }
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
            }
        }
    }
    return decompressed;
}

第二种,使用Zstd

<!-- https://mvnrepository.com/artifact/com.github.luben/zstd-jni -->
        <dependency>
            <groupId>com.github.luben</groupId>
            <artifactId>zstd-jni</artifactId>
            <version>1.4.5-6</version>
        </dependency>
public class ConfigCacheUtil {
    private static ZstdDictCompress compressDict;
    private static ZstdDictDecompress decompressDict;
    private static final Integer LEVEL = 5;
    public static void train() throws IOException {
        // 初始化词典对象
        String dictContent = FileUtils.readFileToString(new File("/Users/yangguang/vscode/text/cache.json"),
            StandardCharsets.UTF_8);
        byte[] dictBytes = dictContent.getBytes(StandardCharsets.UTF_8);
        compressDict = new ZstdDictCompress(dictBytes, LEVEL);
        decompressDict = new ZstdDictDecompress(dictBytes);
    }
    public static void main(String[] args) throws IOException {
        String read = FileUtils.readFileToString(new File("/Users/yangguang/vscode/text/cache.json"));
        ConfigCacheUtil.testGzip(read);
        System.out.println("");
        ConfigCacheUtil.test(read.getBytes());
        System.out.println("");
        ConfigCacheUtil.testByTrain(read.getBytes());
    }
    public static void testGzip(String str) {
        logger.info("初始数据: {}", str.length());
        // 压缩数据
        long compressBeginTime = System.currentTimeMillis();
        String compressed = ConfigCacheUtil.compress(str);
        long compressEndTime = System.currentTimeMillis();
        logger.info("压缩耗时: {}", compressEndTime - compressBeginTime);
        logger.info("数据大小: {}", compressed.length());
        // 解压数据
        long decompressBeginTime = System.currentTimeMillis();
        // 第 3 个参数不能小于解压后的字节数组的大小
        String decompressed = ConfigCacheUtil.uncompress(compressed);
        long decompressEndTime = System.currentTimeMillis();
        logger.info("解压耗时: {}", decompressEndTime - decompressBeginTime);
        logger.info("数据大小: {}", decompressed.length());
    }
    
    public static void test(byte[] bytes) {
        logger.info("初始数据: {}", bytes.length);
        // 压缩数据
        long compressBeginTime = System.currentTimeMillis();
        byte[] compressed = Zstd.compress(bytes);
        long compressEndTime = System.currentTimeMillis();
        logger.info("压缩耗时: {}", compressEndTime - compressBeginTime);
        logger.info("数据大小: {}", compressed.length);
        // 解压数据
        long decompressBeginTime = System.currentTimeMillis();
        // 第 3 个参数不能小于解压后的字节数组的大小
        byte[] decompressed = Zstd.decompress(compressed, 20 * 1024 * 1024 * 8);
        long decompressEndTime = System.currentTimeMillis();
        logger.info("解压耗时: {}", decompressEndTime - decompressBeginTime);
        logger.info("数据大小: {}", decompressed.length);
    }
    public static void testByTrain(byte[] bytes) throws IOException {
        ConfigCacheUtil.train();
        logger.info("初始数据: {}", bytes.length);
        // 压缩数据
        long compressBeginTime = System.currentTimeMillis();
        byte[] compressed = Zstd.compress(bytes, compressDict);
        long compressEndTime = System.currentTimeMillis();
        logger.info("压缩耗时: {}", compressEndTime - compressBeginTime);
        logger.info("数据大小: {}", compressed.length);
        // 解压数据
        long decompressBeginTime = System.currentTimeMillis();
        // 第 3 个参数不能小于解压后的字节数组的大小
        byte[] decompressed = Zstd.decompress(compressed, decompressDict, 20 * 1024 * 1024 * 8);
        long decompressEndTime = System.currentTimeMillis();
        logger.info("解压耗时: {}", decompressEndTime - decompressBeginTime);
        logger.info("数据大小: {}", decompressed.length);
        compressDict.toString();
    }
}

输出

5KB

2020-09-08 22:42:48 INFO ConfigCacheUtil:157 - 初始数据: 5541
2020-09-08 22:42:48 INFO ConfigCacheUtil:163 - 压缩耗时: 2
2020-09-08 22:42:48 INFO ConfigCacheUtil:164 - 数据大小: 1236
2020-09-08 22:42:48 INFO ConfigCacheUtil:171 - 解压耗时: 2
2020-09-08 22:42:48 INFO ConfigCacheUtil:172 - 数据大小: 5541

2020-09-08 22:42:48 INFO ConfigCacheUtil:176 - 初始数据: 5541
2020-09-08 22:42:48 INFO ConfigCacheUtil:182 - 压缩耗时: 523
2020-09-08 22:42:48 INFO ConfigCacheUtil:183 - 数据大小: 972
2020-09-08 22:42:48 INFO ConfigCacheUtil:190 - 解压耗时: 85
2020-09-08 22:42:48 INFO ConfigCacheUtil:191 - 数据大小: 5541

2020-09-08 22:42:48 INFO ConfigCacheUtil:196 - 初始数据: 5541
2020-09-08 22:42:48 INFO ConfigCacheUtil:202 - 压缩耗时: 1
2020-09-08 22:42:48 INFO ConfigCacheUtil:203 - 数据大小: 919
2020-09-08 22:42:48 INFO ConfigCacheUtil:210 - 解压耗时: 22
2020-09-08 22:42:48 INFO ConfigCacheUtil:211 - 数据大小: 5541

6MB

2020-09-08 22:44:06 INFO ConfigCacheUtil:158 - 初始数据: 5719269
2020-09-08 22:44:06 INFO ConfigCacheUtil:164 - 压缩耗时: 129
2020-09-08 22:44:06 INFO ConfigCacheUtil:165 - 数据大小: 330090
2020-09-08 22:44:06 INFO ConfigCacheUtil:172 - 解压耗时: 69
2020-09-08 22:44:06 INFO ConfigCacheUtil:173 - 数据大小: 5719269

2020-09-08 22:44:06 INFO ConfigCacheUtil:177 - 初始数据: 5874139
2020-09-08 22:44:06 INFO ConfigCacheUtil:183 - 压缩耗时: 265
2020-09-08 22:44:06 INFO ConfigCacheUtil:184 - 数据大小: 201722
2020-09-08 22:44:06 INFO ConfigCacheUtil:191 - 解压耗时: 81
2020-09-08 22:44:06 INFO ConfigCacheUtil:192 - 数据大小: 5874139

2020-09-08 22:44:06 INFO ConfigCacheUtil:197 - 初始数据: 5874139
2020-09-08 22:44:06 INFO ConfigCacheUtil:203 - 压缩耗时: 42
2020-09-08 22:44:06 INFO ConfigCacheUtil:204 - 数据大小: 115423
2020-09-08 22:44:07 INFO ConfigCacheUtil:211 - 解压耗时: 49
2020-09-08 22:44:07 INFO ConfigCacheUtil:212 - 数据大小: 5874139

Redis 压缩列表

压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,Redis就会使用压缩列表来做列表键的底层实现。

下面看一下压缩列表实现的列表键:

压缩Redis里的字符串大对象操作

列表键里面包含的都是1、3、5、10086这样的小整数值,以及''hello''、''world''这样的短字符串。

再看一下压缩列表实现的哈希键:

压缩Redis里的字符串大对象操作

压缩列表是Redis为了节约内存而开发的,是一系列特殊编码的连续内存块组成的顺序型数据结构。

一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。

压缩Redis里的字符串大对象操作

看一下压缩列表的示例:

压缩Redis里的字符串大对象操作

看一下包含五个节点的压缩列表:

压缩Redis里的字符串大对象操作

节点的encoding属性记录了节点的content属性所保存数据的类型以及长度。

节点的content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

压缩Redis里的字符串大对象操作

连锁更新:

每个节点的previous_entry_length属性都记录了前一个节点的长度,那么当前一个节点的长度从254以下变成254以上时,本节点的存储前一个节点的长度的previous_entry_length就需要从1字节变为5字节。

那么后面的节点的previous_entry_length属性也有可能更新。不过连锁更新的几率并不大。

总结:

压缩Redis里的字符串大对象操作

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Redis 相关文章推荐
Redis安装启动及常见数据类型
Apr 14 Redis
Redis Cluster 字段模糊匹配及删除
May 27 Redis
详解Redis集群搭建的三种方式
May 31 Redis
聊一聊Redis与MySQL双写一致性如何保证
Jun 26 Redis
redis不能访问本机真实ip地址的解决方案
Jul 07 Redis
使用redis生成唯一编号及原理示例详解
Sep 15 Redis
redis调用二维码时的不断刷新排查分析
Apr 01 Redis
解决 Redis 秒杀超卖场景的高并发
Apr 12 Redis
Redis官方可视化工具RedisInsight安装使用教程
Apr 19 Redis
详解Redis的三种常用的缓存读写策略步骤
May 06 Redis
利用Redis实现点赞功能的示例代码
Jun 28 Redis
Redis唯一ID生成器的实现
Jul 07 Redis
你真的了解redis为什么要提供pipeline功能
Redis缓存-序列化对象存储乱码问题的解决
比较几种Redis集群方案
解析Redis Cluster原理
解析高可用Redis服务架构分析与搭建方案
Redis基于Bitmap实现用户签到功能
redis实现的四种常见限流策略
You might like
浅析php插件 Simple HTML DOM 用DOM方式处理HTML
2013/07/01 PHP
ThinkPHP 3.2 版本升级了哪些内容
2015/03/05 PHP
php保存任意网络图片到服务器的方法
2015/04/14 PHP
PHP关联数组实现根据元素值删除元素的方法
2015/06/26 PHP
php file_get_contents取文件中数组元素的方法
2017/04/01 PHP
javascript静态页面传值的三种方法分享
2013/11/12 Javascript
jQuery form插件的使用之处理server返回的JSON, XML,HTML数据
2016/01/26 Javascript
javascript html5移动端轻松实现文件上传
2020/03/27 Javascript
js实现获取两个日期之间所有日期的方法
2016/06/17 Javascript
JS 动态加载js文件和css文件 同步/异步的两种简单方式
2016/09/23 Javascript
微信js-sdk预览图片接口及从拍照或手机相册中选图接口用法示例
2016/10/13 Javascript
利用Bootstrap实现表格复选框checkbox全选
2016/12/21 Javascript
jQuery插件DataTable使用方法详解(.Net平台)
2016/12/22 Javascript
Vue.js实战之Vuex的入门教程
2017/04/01 Javascript
JS+HTML5 FileReader对象用法示例
2017/04/07 Javascript
微信小程序 本地数据读取实例
2017/04/27 Javascript
BootStrap Select清除选中的状态恢复默认状态
2017/06/20 Javascript
原生JS实现DOM加载完成马上执行JS代码的方法
2018/09/07 Javascript
layui实现form表单同时提交数据和文件的代码
2019/10/25 Javascript
Vue实现简单计算器案例
2020/02/25 Javascript
vue组件开发之tab切换组件使用详解
2020/08/21 Javascript
[06:48]DOTA2-DPC中国联赛2月26日Recap集锦
2021/03/11 DOTA
python使用mailbox打印电子邮件的方法
2015/04/30 Python
部署Python的框架下的web app的详细教程
2015/04/30 Python
python实现数据预处理之填充缺失值的示例
2017/12/22 Python
Python连接Oracle之环境配置、实例代码及报错解决方法详解
2020/02/11 Python
用python按照图像灰度值统计并筛选图片的操作(PIL,shutil,os)
2020/06/04 Python
基于PyTorch的permute和reshape/view的区别介绍
2020/06/18 Python
Python爬虫爬取糗事百科段子实例分享
2020/07/31 Python
纯CSS3制作的简洁蓝白风格的登录模板(非IE效果更好)
2013/08/11 HTML / CSS
如何在网站上添加谷歌定位信息
2016/04/16 HTML / CSS
体育专业个人求职信范文
2013/12/27 职场文书
预备党员思想汇报
2014/01/08 职场文书
自愿解除劳动合同协议书
2014/09/11 职场文书
Python 实现Mac 屏幕截图详解
2021/10/05 Python
CSS SandBox应用场景及常见问题
2022/06/25 HTML / CSS