如何在 Spring Boot 中为缓存添加压缩?


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))
    }
}