如何在 Spring Boot 中為快取新增壓縮?

banq發表於2021-06-10

RAM 是雲提供商提供的最昂貴的資源之一。因此,將所有快取資料儲存在記憶體快取中是有代價的。這就是為什麼必須實施旨在不浪費它的技術。
此外,當您的 Spring Boot 應用程式和快取伺服器共存於同一臺機器上,共享底層資源時,這幾乎是不可避免的。事實上,快取從應用程式中竊取的 RAM 越少越好。此外,眾所周知,序列化的 Java 物件會佔用大量空間。因此,透過快取它們,您的 RAM 可能很容易耗盡空間。
這就是壓縮發揮作用的地方!
讓我們看看如何在 Kotlin 和 Java中為Spring Boot中的快取系統新增壓縮。
  

壓縮和快取
通常,在 Spring Boot 中處理快取時,資料會被序列化,然後儲存在快取中。需要時,資料被搜尋、反序列化,最後以原始格式返回給應用程式。
增加一個壓縮層,就是將資料序列化後進行壓縮,反序列化前先解壓。
壓縮資料可減少快取的大小併為您提供兩種選擇:

  1. 減少快取伺服器所需的 RAM,為您節省資金。
  2. 保持快取大小相同,但允許您儲存更多資料。

這兩個選項都很棒,但壓縮也會帶來開銷。特別是,壓縮和解壓縮會帶來時間成本,這可能會顯著降低快取的效能優勢。這代表了 RAM 和 CPU 之間的權衡,由您來確定這種方法是否適合您的特定情況。
 

實現壓縮邏輯
請記住,上面介紹的方法可以用於Spring Boot 支援的任何快取提供程式。讓我們看看如何在使用Redis快取時實現它。這可以透過註冊一個自定義的類JdkSerializationRedisSerializer作為預設的 Redis 序列化程式來輕鬆實現。
首先,您需要定義一個有效的 Redis 序列化程式,以實現如上圖所述的壓縮和解壓縮邏輯。您將看到如何使用GZIP,它在 Java 中本機實現,但其他壓縮方法也是可能的。此外,Commons IO庫將用於保持解壓邏輯簡單。
如果您是 Maven 使用者,請將以下依賴項新增到您專案的構建 POM 中:

<dependency> 
    <groupId>commons-io</groupId> 
    <artifactId>commons-io</artifactId> 
    <version>2.9.0</version> 
</dependency>

現在,您擁有了定義自定義 Redis 序列化程式所需的一切。
 

Java程式碼

class RedisCacheGZIPSerializer extends JdkSerializationRedisSerializer {
    @Override
    public Object deserialize(
            byte[] bytes
    ) {
        return super.deserialize(decompress(bytes));
    }

    @Override
    public byte[] serialize(
            Object o
    ) {
        return compress(super.serialize(o));
    }

    private byte[] compress(
            byte[] data
    ) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        
        // compressing the input data using GZIP
        try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
            gzipOutputStream.write(data);
        } catch (IOException e) {
            throw new SerializationException(e.getMessage());
        }

        return byteArrayOutputStream.toByteArray();
    }

    private byte[] decompress(
            byte[] data
    ) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {
            // decompressing the input data using GZIP
            IOUtils.copy(new GZIPInputStream(new ByteArrayInputStream(data)), out);
        } catch (IOException e) {
            throw new SerializationException(e.getMessage());
        }

        return out.toByteArray();
    }
}

 

Kotlin程式碼

class RedisCacheGZIPSerializer : JdkSerializationRedisSerializer() {
    override fun deserialize(
        bytes: ByteArray?
    ) : Any {
        return super.deserialize(decompress(bytes))
    }

    override fun serialize(
        o: Any?
    ): ByteArray {
        return compress(super.serialize(o))
    }

    private fun compress(
        data: ByteArray
    ) : ByteArray {
        val byteArrayOutputStream = ByteArrayOutputStream()

        try {
            // compressing the input data using GZIP
            GZIPOutputStream(byteArrayOutputStream).use { gzipOutputStream -> gzipOutputStream.write(data) }
        } catch (e: IOException) {
            throw SerializationException(Constants.COMPRESSION_ERROR_MESSAGE, e)
        }

        return byteArrayOutputStream.toByteArray()
    }

    private fun decompress(
        data: ByteArray?
    ) : ByteArray {
        val out = ByteArrayOutputStream()

        try {
            // decompressing the input data using GZIP
            IOUtils.copy(GZIPInputStream(ByteArrayInputStream(data)), out)
        } catch (e: IOException) {
            throw SerializationException(Constants.DECOMPRESSION_ERROR_MESSAGE, e)
        }

        return out.toByteArray()
    }
}


其次,需要將剛剛定義的類宣告為預設的Redis值序列化程式。這可以透過註冊RedisCacheConfiguration主bean來實現:在實現CachingConfigurerSupport自定義類中使用@Configuration註釋,如下所示:

@Configuration
class CacheConfig extends CachingConfigurerSupport {
    @Bean
    @Primary
    public RedisCacheConfiguration defaultCacheConfig() {
        RedisCacheGzipSerializer serializerGzip = new RedisCacheGzipSerializer();

        return RedisCacheConfiguration
                .defaultCacheConfig()
                .serializeValuesWith(SerializationPair.fromSerializer(serializerGzip));
    }
}


kotlin:

@Configuration
class CacheConfig : CachingConfigurerSupport() {
    @Bean
    @Primary
    fun defaultCacheConfig(
    ) : RedisCacheConfiguration? {
      // registering the custom Redis serializer
        val serializerGzip = RedisCacheGzipSerializer()

        return RedisCacheConfiguration
            .defaultCacheConfig()
            .serializeValuesWith(SerializationPair.fromSerializer(serializerGzip))
    }
}

 

相關文章