压缩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 24 Redis
redis实现排行榜功能
May 24 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
Redis 哨兵集群的实现
Jun 18 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
linux下安装redis图文详细步骤
Dec 04 Redis
分布式Redis Cluster集群搭建与Redis基本用法
Feb 24 Redis
浅谈Redis跟MySQL的双写问题解决方案
Feb 24 Redis
面试分析分布式架构Redis热点key大Value解决方案
Mar 13 Redis
Redis安装使用RedisJSON模块的方法
Mar 23 Redis
Redis全局ID生成器的实现
Jun 05 Redis
你真的了解redis为什么要提供pipeline功能
Redis缓存-序列化对象存储乱码问题的解决
比较几种Redis集群方案
解析Redis Cluster原理
解析高可用Redis服务架构分析与搭建方案
Redis基于Bitmap实现用户签到功能
redis实现的四种常见限流策略
You might like
PHP MVC模式在网站架构中的实现分析
2010/03/04 PHP
PHP中extract()函数的妙用分析
2012/07/11 PHP
解析PHP实现多进程并行执行脚本
2013/06/18 PHP
Zend Framework实现留言本分页功能(附demo源码下载)
2016/03/22 PHP
PHP实现图片批量打包下载功能
2017/03/01 PHP
PHP数据源架构模式之表入口模式实例分析
2020/01/23 PHP
javascript getElementsByTagName
2011/01/31 Javascript
自己做的模拟模态对话框实现代码
2012/05/23 Javascript
js 浏览本地文件夹系统示例代码
2013/10/24 Javascript
JS实现获取剪贴板内容的方法
2016/06/21 Javascript
Angularjs 实现分页功能及示例代码
2016/09/14 Javascript
微信小程序之页面跳转和参数传递的实现
2017/09/29 Javascript
js 两个日期比较相差多少天的实例
2017/10/19 Javascript
Angular 4.x+Ionic3踩坑之Ionic 3.x界面传值详解
2018/03/13 Javascript
JS实现统计字符串中字符出现个数及最大个数功能示例
2018/06/04 Javascript
微信小程序实现星星评价效果
2018/11/02 Javascript
vue单文件组件lint error自动fix与styleLint报错自动fix详解
2019/01/08 Javascript
[01:00:25]2018DOTA2亚洲邀请赛3月30日 小组赛A组 VG VS Liquid
2018/03/31 DOTA
Python中动态获取对象的属性和方法的教程
2015/04/09 Python
Python实现把json格式转换成文本或sql文件
2015/07/10 Python
Python写的一个定时重跑获取数据库数据
2016/12/28 Python
Python部署web开发程序的几种方法
2017/05/05 Python
详解python OpenCV学习笔记之直方图均衡化
2018/02/08 Python
python写入已存在的excel数据实例
2018/05/03 Python
使用matplotlib画散点图的方法
2018/05/25 Python
pyqt5中QThread在使用时出现重复emit的实例
2019/06/21 Python
关于Keras模型可视化教程及关键问题的解决
2020/01/24 Python
Pymysql实现往表中插入数据过程解析
2020/06/02 Python
PyQt5.6+pycharm配置以及pyinstaller生成exe(小白教程)
2020/06/02 Python
python解释器安装教程的方法步骤
2020/07/02 Python
Python判断变量是否是None写法代码实例
2020/10/09 Python
在购买印度民族服饰:Soch
2020/09/15 全球购物
大学生的创业计划书就该这么写
2014/01/30 职场文书
群众路线教育实践活动民主生活会个人检查对照思想汇报
2014/10/04 职场文书
2015年元旦联欢晚会活动总结
2014/11/28 职场文书
九寨沟导游词
2015/02/02 职场文书