缓存空间优化实践

news2024/11/15 17:17:04

导读

缓存 Redis,是我们最常用的服务,其适用场景广泛,被大量应用到各业务场景中。也正因如此,缓存成为了重要的硬件成本来源,我们有必要从空间上做一些优化,降低成本的同时也会提高性能。

下面以我们的案例说明,将缓存空间减少 70% 的做法。

场景设定

1、我们需要将 POJO 存储到缓存中,该类定义如下

public class TestPOJO implements Serializable {
    private String testStatus;
    private String userPin;
    private String investor;
    private Date testQueryTime;
    private Date createTime;
    private String bizInfo;
    private Date otherTime;
    private BigDecimal userAmount;
    private BigDecimal userRate;
    private BigDecimal applyAmount;
    private String type;
    private String checkTime;
    private String preTestStatus;
    
    public Object[] toValueArray(){
        Object[] array = {testStatus, userPin, investor, testQueryTime,
                createTime, bizInfo, otherTime, userAmount,
                userRate, applyAmount, type, checkTime, preTestStatus};
        return array;
    }
    
    public CreditRecord fromValueArray(Object[] valueArray){         
        //具体的数据类型会丢失,需要做处理
    }
}

2、用下面的实例作为测试数据

TestPOJO pojo = new TestPOJO();
pojo.setApplyAmount(new BigDecimal("200.11"));
pojo.setBizInfo("XX");
pojo.setUserAmount(new BigDecimal("1000.00"));
pojo.setTestStatus("SUCCESS");
pojo.setCheckTime("2023-02-02");
pojo.setInvestor("ABCD");
pojo.setUserRate(new BigDecimal("0.002"));
pojo.setTestQueryTime(new Date());
pojo.setOtherTime(new Date());
pojo.setPreTestStatus("PROCESSING");
pojo.setUserPin("ABCDEFGHIJ");
pojo.setType("Y");

常规做法

System.out.println(JSON.toJSONString(pojo).length());

使用 JSON 直接序列化、打印 length=284这种方式是最简单的方式,也是最常用的方式,具体数据如下:

{"applyAmount":200.11,"bizInfo":"XX","checkTime":"2023-02-02","investor":"ABCD","otherTime":"2023-04-10 17:45:17.717","preCheckStatus":"PROCESSING","testQueryTime":"2023-04-10 17:45:17.717","testStatus":"SUCCESS","type":"Y","userAmount":1000.00,"userPin":"ABCDEFGHIJ","userRate":0.002}

我们发现,以上包含了大量无用的数据,其中属性名是没有必要存储的。

改进 1 - 去掉属性名

System.out.println(JSON.toJSONString(pojo.toValueArray()).length());

通过选择数组结构代替对象结构,去掉了属性名,打印 length=144,将数据大小降低了 50%,具体数据如下:

["SUCCESS","ABCDEFGHIJ","ABCD","2023-04-10 17:45:17.717",null,"XX","2023-04-10 17:45:17.717",1000.00,0.002,200.11,"Y","2023-02-02","PROCESSING"]

我们发现,null 是没有必要存储的,时间的格式被序列化为字符串,不合理的序列化结果,导致了数据的膨胀,所以我们应该选用更好的序列化工具。

改进 2 - 使用更好的序列化工具

//我们仍然选取JSON格式,但使用了第三方序列化工具
System.out.println(new ObjectMapper(new MessagePackFactory()).writeValueAsBytes(pojo.toValueArray()).length);

选取更好的序列化工具,实现字段的压缩和合理的数据格式,打印 length=92,空间比上一步又降低了 40%。

这是一份二进制数据,需要以二进制操作 Redis,将二进制转为字符串后,打印如下:

��SUCCESS�ABCDEFGHIJ�ABCD��j�6���XX��j�6����?`bM����@i��Q�Y�2023-02-02�PROCESSING

顺着这个思路再深挖,我们发现,可以通过手动选择数据类型,实现更极致的优化效果,选择使用更小的数据类型,会获得进一步的提升。

改进 3 - 优化数据类型

在以上用例中,testStatus、preCheckStatus、investor 这 3 个字段,实际上是枚举字符串类型,如果能够使用更简单数据类型(比如 byte 或者 int 等)替代 string,还可以进一步节省空间。其中 checkTime 可以用 Long 类型替代字符串,会被序列化工具输出更少的字节。

public Object[] toValueArray(){
    Object[] array = {toInt(testStatus), userPin, toInt(investor), testQueryTime,
    createTime, bizInfo, otherTime, userAmount,
    userRate, applyAmount, type, toLong(checkTime), toInt(preTestStatus)};
    return array;
}

在手动调整后,使用了更小的数据类型替代了 String 类型,打印 length=69

改进 4 - 考虑 ZIP 压缩

除了以上的几点之外,还可以考虑使用 ZIP 压缩方式获取更小的体积,在内容较大或重复性较多的情况下,ZIP 压缩的效果明显,如果存储的内容是 TestPOJO 的数组,可能适合使用 ZIP 压缩。

但 ZIP 压缩并不一定会减少体积,在小于 30 个字节的情况下,也许还会增加体积。在重复性内容较少的情况下,无法获得明显提升。并且存在 CPU 开销。

在经过以上优化之后,ZIP 压缩不再是必选项,需要根据实际数据做测试才能分辨到 ZIP 的压缩效果。

最终落地

上面的几个改进步骤体现了优化的思路,但是反序列化的过程会导致类型的丢失,处理起来比较繁琐,所以我们还需要考虑反序列化的问题。

在缓存对象被预定义的情况下,我们完全可以手动处理每个字段,所以在实战中,推荐使用手动序列化达到上述目的,实现精细化的控制,达到最好的压缩效果和最小的性能开销。

可以参考以下 msgpack 的实现代码,以下为测试代码,请自行封装更好的 Packer 和 UnPacker 等工具:

<dependency>    
    <groupId>org.msgpack</groupId>    
    <artifactId>msgpack-core</artifactId>    
    <version>0.9.3</version>
</dependency>
    public byte[] toByteArray() throws Exception {
        MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();
        toByteArray(packer);
        packer.close();
        return packer.toByteArray();
    }

    public void toByteArray(MessageBufferPacker packer) throws Exception {
        if (testStatus == null) {
            packer.packNil();
        }else{
            packer.packString(testStatus);
        }

        if (userPin == null) {
            packer.packNil();
        }else{
            packer.packString(userPin);
        }

        if (investor == null) {
            packer.packNil();
        }else{
            packer.packString(investor);
        }

        if (testQueryTime == null) {
            packer.packNil();
        }else{
            packer.packLong(testQueryTime.getTime());
        }

        if (createTime == null) {
            packer.packNil();
        }else{
            packer.packLong(createTime.getTime());
        }

        if (bizInfo == null) {
            packer.packNil();
        }else{
            packer.packString(bizInfo);
        }

        if (otherTime == null) {
            packer.packNil();
        }else{
            packer.packLong(otherTime.getTime());
        }

        if (userAmount == null) {
            packer.packNil();
        }else{
            packer.packString(userAmount.toString());
        }

        if (userRate == null) {
            packer.packNil();
        }else{
            packer.packString(userRate.toString());
        }

        if (applyAmount == null) {
            packer.packNil();
        }else{
            packer.packString(applyAmount.toString());
        }

        if (type == null) {
            packer.packNil();
        }else{
            packer.packString(type);
        }

        if (checkTime == null) {
            packer.packNil();
        }else{
            packer.packString(checkTime);
        }

        if (preTestStatus == null) {
            packer.packNil();
        }else{
            packer.packString(preTestStatus);
        }
    }


    public void fromByteArray(byte[] byteArray) throws Exception {
        MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(byteArray);
        fromByteArray(unpacker);
        unpacker.close();
    }

    public void fromByteArray(MessageUnpacker unpacker) throws Exception {
        if (!unpacker.tryUnpackNil()){
            this.setTestStatus(unpacker.unpackString());
        }
        if (!unpacker.tryUnpackNil()){
            this.setUserPin(unpacker.unpackString());
        }
        if (!unpacker.tryUnpackNil()){
            this.setInvestor(unpacker.unpackString());
        }
        if (!unpacker.tryUnpackNil()){
            this.setTestQueryTime(new Date(unpacker.unpackLong()));
        }
        if (!unpacker.tryUnpackNil()){
            this.setCreateTime(new Date(unpacker.unpackLong()));
        }
        if (!unpacker.tryUnpackNil()){
            this.setBizInfo(unpacker.unpackString());
        }
        if (!unpacker.tryUnpackNil()){
            this.setOtherTime(new Date(unpacker.unpackLong()));
        }
        if (!unpacker.tryUnpackNil()){
            this.setUserAmount(new BigDecimal(unpacker.unpackString()));
        }
        if (!unpacker.tryUnpackNil()){
            this.setUserRate(new BigDecimal(unpacker.unpackString()));
        }
        if (!unpacker.tryUnpackNil()){
            this.setApplyAmount(new BigDecimal(unpacker.unpackString()));
        }
        if (!unpacker.tryUnpackNil()){
            this.setType(unpacker.unpackString());
        }
        if (!unpacker.tryUnpackNil()){
            this.setCheckTime(unpacker.unpackString());
        }
        if (!unpacker.tryUnpackNil()){
            this.setPreTestStatus(unpacker.unpackString());
        }
    }

场景延伸

假设,我们为 2 亿用户存储数据,每个用户包含 40 个字段,字段 key 的长度是 6 个字节,字段是分别管理的。

正常情况下,我们会想到 hash 结构,而 hash 结构存储了 key 的信息,会占用额外资源,字段 key 属于不必要数据,按照上述思路,可以使用 list 替代 hash 结构。

通过 Redis 官方工具测试,使用 list 结构需要 144G 的空间,而使用 hash 结构需要 245G 的空间(当 50% 以上的属性为空时,需要进行测试,是否仍然适用)

在以上案例中,我们采取了几个非常简单的措施,仅仅有几行简单的代码,可降低空间 70% 以上,在数据量较大以及性能要求较高的场景中,是非常值得推荐的。:

• 使用数组替代对象(如果大量字段为空,需配合序列化工具对 null 进行压缩)

• 使用更好的序列化工具

• 使用更小的数据类型

• 考虑使用 ZIP 压缩

• 使用 list 替代 hash 结构(如果大量字段为空,需要进行测试对比)

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/452686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】数据库完整性和安全性

目录 一、完整性 1.概念 2.sql语言支持的两种约束 2.1静态约束 撤销追加约束 断言 2.3动态约束 触发器 二、安全性 用DBMS对数据库实现的两个特性 一、完整性 1.概念 指dbms保证的db的一种特性&#xff0c;在任何情况下的正确性、有效性、一致性 原理图 广义完整性&…

深度学习第J7周:ResNeXt-50算法思考

目录 一、问题 二、思考分析 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&#xff08;版权归 *K同学啊* 所有&#xff09; &#x1f356; 作者&#xff1a;[K同学啊] 查看j6周代码&#xff0c;思考解决问题。 一、问题 &#x1f4cc;你需要解决的…

自然语言处理实战项目5-文本数据处理输入模型操作,以命名实体识别为例,打通NLP模型训练从0到1

大家好&#xff0c;我是微学AI&#xff0c;今天给大家带来自然语言处理实战项目5-文本数据处理输入模型操作&#xff0c;以命名实体识别为例。今天我给出的案例是命名实体识别&#xff0c;假设我们有一个命名实体识别任务&#xff0c;需要从文本中识别人名、地点和组织等实体。…

快速找出满足所需比值的一对电阻值 - Python 函数实现

常用的5% 和1% 精度电阻的阻值满足E24 数系&#xff0c;基数只能在这个数系里取&#xff0c;再乘上10 的n 次幂。E24 数系如下图&#xff1a; 之前我都是人肉一个一个试的&#xff0c;凭运气挑&#xff0c;终于忍不住想整个一劳永逸的小工具。 代码 对于给定的比值&#xff0…

【计算机是怎么跑起来的】基础:计算机三大原则

【计算机是怎么跑起来的】基础&#xff1a;计算机三大原则 计算机的三个根本性基础1.计算机是执行输入&#xff0c;运算&#xff0c;输出的机器输入&#xff0c;运算&#xff0c;输出 2. 软件是指令和数据的集合指令数据 3. 计算机的处理方式有时与人们的思维习惯不同对计算机来…

格式工厂将视频导出Maya需要的图像序列帧

目录 一、格式工厂影片转序列帧 1、格式工厂下载链接 2、打开需要转换的视频 3、输出配置 4、开始导出序列帧 二、Maya中添加影片序列帧和动画播放 1、打开Maya2016版本软件 2、导入图像 3、序列帧设置 4、播放速度设置 5、Maya中播放序列帧动画 一、格式工厂影片转序列帧…

展会邀请 | 虹科诚邀您4月26-28日前来参观成都国际工业博览会

HONGKE NEWS 2023 成都国际工业博览会精准聚焦中国智能制造&#xff0c;将通过展示自动化和工业机器人技术、新一代信息技术、金属加工、节能与工业配套、新材料等全行业最新技术和解决方案&#xff0c;完美呈现智能工业产业链中的创新技术及产品的有效融合。 2023年4月26日-…

无人机3d可视化系统的应用是怎样实现的?

随着科技的发展&#xff0c;以信息化为支撑的系统化操作将成为未来信息对抗的主要形式&#xff0c;通过人工智能技术赋能感知系统&#xff0c;可以抓住机会控制局面&#xff0c;带动后续环节高效运行&#xff0c;缩短循环求解时间&#xff0c;为信息对抗提供机会和关键支撑。无…

为什么医疗保健需要MFT来帮助保护EHR文件传输

毫无疑问&#xff0c;医疗保健行业需要EHR技术来处理患者&#xff0c;设施&#xff0c;提供者等之间的敏感患者信息。但是&#xff0c;如果没有安全的MFT解决方案&#xff0c;您将无法安全地传输患者文件&#xff0c;从而使您的运营面临遭受数据泄露&#xff0c;尴尬&#xff0…

leetcode 周赛 2386. 找出数组的第 K 大和-java实现

题目所属分类 华为校招 原题链接 给你一个整数数组 nums 和一个 正 整数 k 。你可以选择数组的任一 子序列 并且对其全部元素求和。 数组的 第 k 大和 定义为&#xff1a;可以获得的第 k 个 最大 子序列和&#xff08;子序列和允许出现重复&#xff09; 返回数组的 第 k 大…

【新时代圈友app】为什么要使用MongoDB数据库?— 查询缘分值最高的最佳好友并返回相关信息

目录 一、为什么要使用MongoDB数据库&#xff1f; 二、缘分值最佳好友 思路 一、为什么要使用MongoDB数据库&#xff1f; 本项目涉及到的圈子(动态)功能&#xff0c;用户会对朋友圈进行点赞、评论&#xff1b;那么随着用户的不断增多&#xff0c;评论点赞收藏等信息也会不断…

JS之Map的基本使用

一、Map的基本API 创建&#xff1a; const map new Map()插入&#xff1a;map.set("name", "郑建")读取&#xff1a;map.get("name")判断&#xff1a;map.has("name")删除&#xff1a;map.delete大小&#xff1a;map.size遍历&#…

webhub123 设计师好用的笔刷纹理网站收录​

整理了一些可以免费下载的好用的笔刷和纹理资源网站&#xff0c;收录到 webhub123 设计师好用的笔刷纹理网站收录​http://www.webhub123.com/#/home/detail?projectHashid31645930&ownerUserid21336964 收录效果如下&#xff0c;每个网站显示为一张图片&#xff0c;点击…

【Python基础入门学习】Python基础语法学习

基础认识 1. 注释2. 变量2.1 变量命名规则2.2 变量的类型2.3 不同类型之间的运算规则2.4 变量的输入和输出2.4.1 print 函数使用2.4.2 input 函数使用 2.5 变量的类型转换 3. 分支语句3.1 判断的定义3.2 if 判断语句基本语法3.3 else 处理条件不满足的情况3.4 逻辑运算3.5 if 的…

小红书数据分析:这个夏天,“围炉冰茶”继续刷屏

导语 去年秋冬开始爆火的“围炉煮茶”&#xff0c;果集千瓜数据显示&#xff1a;近90天来&#xff0c;笔记预估阅读总数达1,038.83万&#xff0c;同比下降80.67%&#xff0c;笔记互动总量61.78万&#xff0c;下降高达76.85%。 图 | 果集千瓜数据 受到时令的影响&#xff0c;围…

算法 - 随机 Coding 刷算法合集 [1]

目录 一.数组中重复的数字 [集合] 1.题目要求 2.题目思路 3.题目实现 二.二维数组中的查找 [数组] 1.题目要求 2.题目思路 3.题目实现 三.替换空格 [字符串] 1.题目要求 2.题目思路 3.题目实现 四.从尾到头打印链表 [链表] 1.题目要求 2.题目思路 3.题目实现 …

电脑技巧:分享浏览器5个小技巧,太实用了

大家在日常办公当中&#xff0c;浏览器可以说占用非常大的比重&#xff0c;比如搜个素材、图片、文档等等&#xff0c;今天就来给大家分享5个浏览器使用的小技巧&#xff0c;希望对大家能有所帮助&#xff01; 1、浏览器常用快捷键梳理 其实Web浏览器快捷键很多&#xff0c;但…

MySQL_第14章_视图

第14章_视图 1. 常见的数据库对象 对象描述表(TABLE) 表是存储数据的逻辑单元&#xff0c;以行和列的形式存在&#xff0c;列就是字段&#xff0c;行就是记录 数据字典 就是系统表&#xff0c;存放数据库相关信息的表。系统表的数据通常由数据库系统维护&#xff0c; 程序…

Java多线程基础-6:线程安全问题及解决措施,synchronized关键字与volatile关键字

线程安全问题是多线程编程中最典型的一类问题之一。如果多线程环境下代码运行的结果是符合我们预期的&#xff0c;即该结果正是在单线程环境中应该出现的结果&#xff0c;则说这个程序是线程安全的。 通俗来说&#xff0c;线程不安全指的就是某一代码在多线程环境下执行会出现b…

【边缘计算】登临(Goldwasser-UL64)BW-BR2边缘设备配置指南

目录 开箱配置激活SDK环境测试cuda兼容性 开箱配置 更改盒子root用户密码&#xff1a; sudo passwd root(密码同为root) 切换到root用户身份&#xff1a; su root查看ssh的状态&#xff0c;没有返回说明没有启动 sudo ps -e|grep ssh此时说明ssh服务已启动。 更改ssh配置文…