压缩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三种高可用方式部署的实现
May 11 Redis
5分钟教你docker安装启动redis全教程(全新方式)
May 29 Redis
SpringBoot 集成Redis 过程
Jun 02 Redis
redis实现的四种常见限流策略
Jun 18 Redis
使用Redis实现实时排行榜功能
Jul 02 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
Redis中缓存穿透/击穿/雪崩问题和解决方法
Dec 04 Redis
关于使用Redisson订阅数问题
Jan 18 Redis
redis数据一致性的实现示例
Mar 18 Redis
关于Redis的主从复制及哨兵问题
Jun 16 Redis
你真的了解redis为什么要提供pipeline功能
Redis缓存-序列化对象存储乱码问题的解决
比较几种Redis集群方案
解析Redis Cluster原理
解析高可用Redis服务架构分析与搭建方案
Redis基于Bitmap实现用户签到功能
redis实现的四种常见限流策略
You might like
php网页后退不再出现过期
2007/03/08 PHP
PHP文件注释标记及规范小结
2012/04/01 PHP
深入extjs与php参数交互的详解
2013/06/25 PHP
php代码审计比较有意思的例子
2014/05/07 PHP
Windows下编译PHP5.4和xdebug全记录
2015/04/03 PHP
php函数重载的替代方法--伪重载详解
2015/05/08 PHP
PHP在线书签系统分享
2016/01/04 PHP
php快速排序原理与实现方法分析
2016/05/26 PHP
html超链接打开窗口大小的方法
2013/03/05 Javascript
JQuery动画animate的stop方法使用详解
2014/05/09 Javascript
JQuery中层次选择器用法实例详解
2015/05/18 Javascript
jQuery如何封装输入框插件
2016/08/19 Javascript
ReactNative 之FlatList使用及踩坑封装总结
2017/11/29 Javascript
利用vue + element实现表格分页和前端搜索的方法
2017/12/25 Javascript
Angular 封装并发布组件的方法示例
2018/04/19 Javascript
layui 优化button按钮和弹出框的方法
2018/08/15 Javascript
Vux+Axios拦截器增加loading的问题及实现方法
2018/11/08 Javascript
VUE脚手架具体使用方法
2019/05/20 Javascript
浅谈layui 表单元素的选中问题
2019/10/25 Javascript
vue封装自定义指令之动态显示title操作(溢出显示,不溢出不显示)
2020/11/12 Javascript
Python随机数random模块使用指南
2016/09/09 Python
Python中常见的异常总结
2018/02/20 Python
Python使用xlwt模块操作Excel的方法详解
2018/03/27 Python
python在TXT文件中按照某一字符串取出该字符串所在的行方法
2018/12/10 Python
Django 开发调试工具 Django-debug-toolbar使用详解
2019/07/23 Python
python获取指定日期范围内的每一天,每个月,每季度的方法
2019/08/08 Python
如何利用Python识别图片中的文字
2020/05/31 Python
keras导入weights方式
2020/06/12 Python
在终端启动Python时报错的解决方案
2020/11/20 Python
CSS3实现歌词进度文字颜色填充变化动态效果的思路详解
2020/06/02 HTML / CSS
几个MySql的面试题
2013/04/22 面试题
物理系毕业生自荐书
2014/06/13 职场文书
学生病假条范文
2015/08/17 职场文书
2016年记者节感言
2015/12/08 职场文书
2019年年中职场激励人心语录30条
2019/08/07 职场文书
如何使用SQL Server语句创建表
2022/04/12 SQL Server