压缩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 sentinel 频繁主备切换的问题
Apr 12 Redis
redis 查看所有的key方式
May 07 Redis
基于Redis位图实现用户签到功能
May 08 Redis
Django使用redis配置缓存的方法
Jun 01 Redis
基于Redis的List实现特价商品列表功能
Aug 30 Redis
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 Redis
关于SpringBoot 使用 Redis 分布式锁解决并发问题
Nov 17 Redis
Redis集群节点通信过程/原理流程分析
Mar 18 Redis
详解Redis的三种常用的缓存读写策略步骤
May 06 Redis
Redis入门基础常用操作命令整理
Jun 01 Redis
Redis实现主从复制方式(Master&Slave)
Jun 21 Redis
你真的了解redis为什么要提供pipeline功能
Redis缓存-序列化对象存储乱码问题的解决
比较几种Redis集群方案
解析Redis Cluster原理
解析高可用Redis服务架构分析与搭建方案
Redis基于Bitmap实现用户签到功能
redis实现的四种常见限流策略
You might like
php数组删除元素示例
2014/03/21 PHP
php防止恶意刷新与刷票的方法
2014/11/21 PHP
PHP的RSA加密解密方法以及开发接口使用
2018/02/11 PHP
js不是基础的基础
2006/12/24 Javascript
javascript编程起步(第七课)
2007/02/27 Javascript
jquery.Jwin.js 基于jquery的弹出层插件代码
2012/05/23 Javascript
js字符串转换成xml对象并使用技巧解读
2013/04/18 Javascript
常用DOM整理
2015/06/16 Javascript
javascript 将共享属性迁移到原型中去的实现方法
2016/08/31 Javascript
浅谈MVC+EF easyui dataGrid 动态加载分页表格
2016/11/10 Javascript
js中获取键盘按下键值event.keyCode、event.charCode和event.which的兼容性详解
2017/03/15 Javascript
浅谈箭头函数写法在ReactJs中的使用
2017/08/22 Javascript
vue跨域解决方法
2017/10/15 Javascript
jQuery EasyUI 折叠面板accordion的使用实例(分享)
2017/12/25 jQuery
浅谈一个webpack构建速度优化误区
2019/06/24 Javascript
微信小程序用户拒绝授权的处理方法详解
2019/09/20 Javascript
vue-admin-template配置快捷导航的代码(标签导航栏)
2020/09/04 Javascript
[01:02:05]LGD vs Mineski 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
Python操作mongodb数据库进行模糊查询操作示例
2018/06/09 Python
django使用LDAP验证的方法示例
2018/12/10 Python
python打包exe开机自动启动的实例(windows)
2019/06/28 Python
解决pycharm启动后总是不停的updating indices...indexing的问题
2019/11/27 Python
python实现逆滤波与维纳滤波示例
2020/02/26 Python
详解基于python的图像Gabor变换及特征提取
2020/10/26 Python
美体小铺奥地利官方网站:The Body Shop奥地利
2019/04/11 全球购物
小学教师自我鉴定范文
2014/03/20 职场文书
入股协议书
2014/04/14 职场文书
公司员工活动策划方案
2014/08/20 职场文书
面试感谢信范文
2015/01/22 职场文书
检察院起诉意见书
2015/05/20 职场文书
教师节祝酒词
2015/08/11 职场文书
2019新学期家长会工作计划
2019/08/21 职场文书
Mysql服务添加 iptables防火墙策略的方案
2021/04/29 MySQL
SpringBoot整合JWT的入门指南
2021/06/29 Java/Android
详解JavaScript中Arguments对象用途
2021/08/30 Javascript
Pygame游戏开发之太空射击实战敌人精灵篇
2022/08/05 Python