首頁 >資料庫 >Redis >Redis快取空間怎麼優化

Redis快取空間怎麼優化

PHPz
PHPz轉載
2023-05-27 23:44:061219瀏覽

場景設定

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,還可以進一步節省空間。可以使用Long型別來取代字串來表示checkTime,這樣序列化工具輸出的位元組數會更少。

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壓縮。

對於小於30個位元組的文件,ZIP壓縮可能增加檔案大小,不一定能減少檔案體積。在重複性內容較少的情況下,無法獲得明顯提升。且存在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%以上的屬性為空時,需要進行測試,是否仍然適用)* *

Redis快取空間怎麼優化

在上述案例中,我們採取了幾個非常簡單的措施,僅有幾行簡單的程式碼,可降低空間70%以上,在資料量較大以及性能要求較高的場景中,是非常值得推薦的。 :

• 使用陣列替代物件(如果大量欄位為空,需配合序列化工具對null進行壓縮)

• 使用更好的序列化工具

• 使用更小的資料類型

• 考慮使用ZIP壓縮

• 使用list取代hash結構(如果大量欄位為空,需要進行測試對比)

以上是Redis快取空間怎麼優化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除