Sprintboot redisはgzipとSnappy compressを採用してデータを圧縮する

18276 ワード

1はじめに
Sprintboot+redisの使い方と組み合わせは、私の前の文章を参照してください.https://blog.csdn.net/zzhongcy/article/details/102584028
ここでは主に,生産環境において単一のredisデータが大きい場合に,圧縮データを考慮してredisに格納する可能性について述べる.
圧縮データのメリットとデメリット:
  • の利点1:圧縮はredisストレージデータ量を減少させ、redisのスループット
  • を増加させる.
  • の利点2:圧縮が少ないネットワーク帯域幅
  • の欠点は、CPU消費量
  • が増加することである.
    2 Sprintboot redis構成
    次の2つの構成方法があります.
    2.1方式1:RedisTemplate構成
    @Configuration
    public class RedisConfig {
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    
            jackson2JsonRedisSerializer.setObjectMapper(om);
    
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key  String      
            template.setKeySerializer(stringRedisSerializer);
    
            // hash key   String      
            template.setHashKeySerializer(stringRedisSerializer);
    
            // value       jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
    
            // hash value       jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
    
            template.afterPropertiesSet();
            return template;
        }
    }

    格納方式コードは次のとおりです.
    //valueシーケンス化方式jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);
    2.2方式2:RedisCacheConfiguration構成
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration (long defaultExpiration ) {
    
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    
        //      
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
                        LaissezFaireSubTypeValidator.instance,
                        ObjectMapper.DefaultTyping.NON_FINAL,
                        JsonTypeInfo.As.WRAPPER_ARRAY);
        
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    
        //  config
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        return config.entryTtl( Duration.ofSeconds( defaultExpiration ) )
             .serializeKeysWith( SerializationPair.fromSerializer( new StringRedisSerializer() ) )
             .serializeValuesWith( SerializationPair.fromSerializer( jackson2JsonRedisSerializer) );
    }

    velue圧縮設定は次のとおりです.
          .serializeValuesWith( SerializationPair.fromSerializer( jackson2JsonRedisSerializer)
    ここではカスタムJackson 2 JsonRedisSerializerクラスを使用し、
    jsonデータの内部にはjavaが含まれている.util.ArrayList、com.server.model.classなどのタイプの文字列.
    もちろんデフォルトの汎用フォーマットGenericJackson 2 JsonRedisSerializerも使用できます.以下のようになります.
          .serializeValuesWith( SerializationPair.fromSerializer( new GenericJackson2JsonRedisSerializer())
    jsonデータの内部にはjavaが含まれている.util.ArrayList、com.server.model.class、@classなどのタイプの文字列.
     
    3データ圧縮
    3.1 gzip圧縮
    public class RedisSerializerGzip extends JdkSerializationRedisSerializer {
    
        @Override
        public Object deserialize(byte[] bytes) {
            return super.deserialize(decompress(bytes));
        }
    
        @Override
        public byte[] serialize(Object object) {
            return compress(super.serialize(object));
        }
    
    
        private byte[] compress(byte[] content) {
            byte[] ret = null;
            ByteArrayOutputStream byteArrayOutputStream = null;
            try {
                byteArrayOutputStream = new ByteArrayOutputStream();
                GZIPOutputStream gzipOutputStream= new GZIPOutputStream(byteArrayOutputStream);
                gzipOutputStream.write(content);
                //gzipOutputStream.flush();     //   flush    ,          close  finish    
                stream.close();   //    finish
    
                ret = byteArrayOutputStream.toByteArray();
                byteArrayOutputStream.flush();
                byteArrayOutputStream.close();
            } catch (IOException e) {
                throw new SerializationException("Unable to compress data", e);
            }
            return ret;
        }
    
        private byte[] decompress(byte[] contentBytes) {
            byte[] ret = null;
            ByteArrayOutputStream out = null;
            try {
                out = new ByteArrayOutputStream();
                GZIPInputStream stream = new GZIPInputStream(new ByteArrayInputStream(contentBytes));
                IOUtils.copy(stream, out);
                stream.close();
    
                ret = out.toByteArray();
                out.flush();
                out.close();
            } catch (IOException e) {
                throw new SerializationException("Unable to decompress data", e);
            }
            return ret;
        }
    
    }

    注意:
    //gzipOutputStream.flush();//flushを呼び出すだけではリフレッシュされません.圧縮タイプのストリームはcloseまたはfinishを実行する必要があります.gzipOutputStreamが完了します.close();//内部コールfinish
    圧縮データが完了しないと、解凍がエラーになります.
    java.io.EOFException : Unexpected end of ZLIB input stream
    at java.util.zip.InflaterInputStream.fill( InflaterInputStream.java:240)
    at java.util.zip.InflaterInputStream.read( InflaterInputStream.java:158)
    at java.util.zip.GZIPInputStream.read( GZIPInputStream.java:117)
     
    3.1.1自定バッファ解凍方式:
    /**
         * GZIP   
         * 
         * @param bytes
         * @return
         */
        public static byte[] uncompress(byte[] bytes) {
            if (bytes == null || bytes.length == 0) {
                return null;
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            try {
                GZIPInputStream ungzip = new GZIPInputStream(in);
                byte[] buffer = new byte[256];
                int n;
                while ((n = ungzip.read(buffer)) >= 0) {
                    out.write(buffer, 0, n);
                }
            } catch (IOException e) {
                ApiLogger.error("gzip uncompress error.", e);
            }
     
            return out.toByteArray();
        }
     
    
        /**
         * 
         * @param bytes
         * @param encoding
         * @return
         */
        public static String uncompressToString(byte[] bytes, String encoding) {
            if (bytes == null || bytes.length == 0) {
                return null;
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            try {
                GZIPInputStream ungzip = new GZIPInputStream(in);
                byte[] buffer = new byte[256];
                int n;
                while ((n = ungzip.read(buffer)) >= 0) {
                    out.write(buffer, 0, n);
                }
                return out.toString(encoding);
            } catch (IOException e) {
                ApiLogger.error("gzip uncompress to string error.", e);
            }
            return null;
        }

    3.1.2 IOUtils.copyソース解読
    実はcopy関数の内部もwhile((n=ungzip.read)>=0)を呼び出して実現される.
    public static int copy(InputStream input, OutputStream output) throws IOException {
            long count = copyLarge(input, output);
            return count > 2147483647L ? -1 : (int)count;
        }
    
        public static long copyLarge(InputStream input, OutputStream output) throws IOException {
            byte[] buffer = new byte[4096];
            long count = 0L;
    
            int n;
            for(boolean var5 = false; -1 != (n = input.read(buffer)); count += (long)n) {
                output.write(buffer, 0, n);
            }
    
            return count;
        }

    ただIOUtilscopy内部バッファサイズは4 kで、大きい点を設定すると読み取り速度が向上する可能性があります.
     
    3.2 Snappy圧縮
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    import org.springframework.util.SerializationUtils;
    import org.xerial.snappy.Snappy;
    import java.io.Serializable;
    
    public class RedisSerializerSnappy extends JdkSerializationRedisSerializer {
    
        private RedisSerializer innerSerializer;
    
        public RedisSerializerSnappy(RedisSerializer innerSerializer) {
            this.innerSerializer = innerSerializer;
        }
    
        @Override
        public Object deserialize(byte[] bytes) {
            return super.deserialize(decompress(bytes));
        }
    
        @Override
        public byte[] serialize(Object object) {
            return compress(super.serialize(object));
        }
    
        private byte[] compress(byte[] content) {
            try {
                byte[] bytes = innerSerializer != null ? innerSerializer.serialize(content)
                        : SerializationUtils.serialize((Serializable) content);
                return Snappy.compress(bytes);
    
            } catch (Exception e) {
                throw new SerializationException(e.getMessage(), e);
            }
        }
    
        private byte[] decompress(byte[] contentBytes) {
            try {
                byte[] bos = Snappy.uncompress(contentBytes);
                return (byte[]) (innerSerializer != null ? innerSerializer.deserialize(bos) : SerializationUtils.deserialize(bos));
            } catch (Exception e) {
                throw new SerializationException(e.getMessage(), e);
            }
        }
    }

    3.3圧縮設定
    ここではRedisTemplateの構成について簡単に説明しますが、RedisCacheConfigurationの構成は上記の説明を参照してください.
    @Bean
        public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory) {
            RedisTemplate template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);
    
            // Set a custom serializer that will compress/decompress data to/from redis
            RedisSerializerGzip serializerGzip = new RedisSerializerGzip();
            template.setValueSerializer(serializerGzip);
            template.setHashValueSerializer(serializerGzip);
    
    
            //RedisSerializerSnappy serializerSnappy = new RedisSerializerSnappy(null);
            //redisTemplate.setValueSerializer(serializerSnappy);
            //redisTemplate.setHashValueSerializer(serializerSnappy);
    
            return template;
        }

    これにより、データ圧縮が実現されます.
    4エンド
    4.1圧縮率効果
    ここでは圧縮効率を簡単にテストしました(注意:圧縮効率は圧縮内容と密接な関係があり、ここでは文字列の例を簡単に採用し、テストを行うだけです):
  • 1、Jackson 2 JsonRedisSerializer、テストデータ長:13.70 Kb
  • 2、RedisSerializerGzip圧縮、テストデータ長:9.19 Kb
  • 3、RedisSerializerSnappy圧縮、テストデータ長:13.37 Kb
  • RedisSerializerGzipは圧縮率ではなく圧縮速度を追求するため、RedisSerializerSnappyは圧縮率が高く、RedisSerializerSnappyは圧縮率が低いことがわかる.どのような圧縮を採用するかは、皆さんのプロジェクトの状況に応じて自分で決めましょう.
    4.2性能比較:
    以前の記事を参考にしてください.
    圧縮フォーマットgzip/snappy/lzo/bzip 2の比較とまとめ:https://blog.csdn.net/zzhongcy/article/details/89375346
    圧縮フォーマット
    あっしゅくひ
    あっしゅくそくど
    解凍速度
    gzip
    13.4%
    21 MB/s
    118 MB/s
    lzo
    20.5%
    135 MB/s
    410 MB/s
    snappy
    22.2%
    172 MB/s
    409 MB/s
    bzip2
    13.2%
    2.4MB/s
    9.5MB/s
    データを圧縮するかどうか、およびどのような圧縮フォーマットを使用するかは、パフォーマンスに重要な影響を及ぼします.
     
    5その他のシーケンス
    5.1 Fstシーケンス化
    import org.nustaq.serialization.FSTConfiguration;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import javax.xml.crypto.Data;
    import java.time.LocalDateTime;
    import java.util.Date;
    
    /**
     * Description: Fst    
     */
    public class FstSerializer implements RedisSerializer {
    
        private static FSTConfiguration configuration = FSTConfiguration.createStructConfiguration();
        private Class clazz;
    
        public FstSerializer(Class clazz) {
            super();
            this.clazz = clazz;
            configuration.registerClass(clazz);
        }
    
        @Override
        public byte[] serialize(T t) throws SerializationException {
            return configuration.asByteArray(t);
        }
    
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            return (T) configuration.asObject(bytes);
        }
    }

    5.2 Kryoシーケンス化
    import com.esotericsoftware.kryo.Kryo;
    import com.esotericsoftware.kryo.io.ByteBufferInput;
    import com.esotericsoftware.kryo.io.Input;
    import com.esotericsoftware.kryo.io.Output;
    import com.esotericsoftware.kryo.serializers.DefaultSerializers;
    import com.esotericsoftware.kryo.serializers.JavaSerializer;
    import com.esotericsoftware.kryo.util.Pool;
    import de.javakaffee.kryoserializers.ArraysAsListSerializer;
    import de.javakaffee.kryoserializers.BitSetSerializer;
    import de.javakaffee.kryoserializers.GregorianCalendarSerializer;
    import de.javakaffee.kryoserializers.JdkProxySerializer;
    import de.javakaffee.kryoserializers.RegexSerializer;
    import de.javakaffee.kryoserializers.SynchronizedCollectionsSerializer;
    import de.javakaffee.kryoserializers.URISerializer;
    import de.javakaffee.kryoserializers.UUIDSerializer;
    import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import java.awt.print.Book;
    import java.io.ByteArrayInputStream;
    import java.lang.reflect.InvocationHandler;
    import java.math.BigDecimal;
    import java.math.BigInteger;
    import java.net.URI;
    import java.text.SimpleDateFormat;
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.regex.Pattern;
    
    /**
     * Description: Kryo    .
    */ public class KryoSerializer implements RedisSerializer { private static final int BUFFER_SIZE = 2048; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static Pool kryoPool = new Pool(true, false, 8) { @Override protected Kryo create() { Kryo kryo = new Kryo(); kryo.setRegistrationRequired(false); kryo.register(Book.class); kryo.addDefaultSerializer(Throwable.class, new JavaSerializer()); kryo.register(Arrays.asList("").getClass(), new ArraysAsListSerializer()); kryo.register(GregorianCalendar.class, new GregorianCalendarSerializer()); kryo.register(InvocationHandler.class, new JdkProxySerializer()); kryo.register(BigDecimal.class, new DefaultSerializers.BigDecimalSerializer()); kryo.register(BigInteger.class, new DefaultSerializers.BigIntegerSerializer()); kryo.register(Pattern.class, new RegexSerializer()); kryo.register(BitSet.class, new BitSetSerializer()); kryo.register(URI.class, new URISerializer()); kryo.register(UUID.class, new UUIDSerializer()); UnmodifiableCollectionsSerializer.registerSerializers(kryo); SynchronizedCollectionsSerializer.registerSerializers(kryo); kryo.register(HashMap.class); kryo.register(ArrayList.class); kryo.register(LinkedList.class); kryo.register(HashSet.class); kryo.register(TreeSet.class); kryo.register(Hashtable.class); kryo.register(Date.class); kryo.register(Calendar.class); kryo.register(ConcurrentHashMap.class); kryo.register(SimpleDateFormat.class); kryo.register(GregorianCalendar.class); kryo.register(Vector.class); kryo.register(BitSet.class); kryo.register(StringBuffer.class); kryo.register(StringBuilder.class); kryo.register(Object.class); kryo.register(Object[].class); kryo.register(String[].class); kryo.register(byte[].class); kryo.register(char[].class); kryo.register(int[].class); kryo.register(float[].class); kryo.register(double[].class); return kryo; } }; private static Pool outputPool = new Pool(true, false, 16) { @Override protected Output create() { return new Output(BUFFER_SIZE, -1); } }; private static Pool inputPool = new Pool(true, false, 16) { @Override protected Input create() { return new ByteBufferInput(BUFFER_SIZE); } }; private Class clazz; public KryoSerializer(Class clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (null == t) { return EMPTY_BYTE_ARRAY; } Kryo kryo = null; Output output = null; byte[] bytes; try { output = outputPool.obtain(); kryo = kryoPool.obtain(); kryo.writeClassAndObject(output, t); output.flush(); return output.toBytes(); } finally { if (output != null) { outputPool.free(output); } if (kryo != null) { kryoPool.free(kryo); } } } @Override public T deserialize(byte[] bytes) throws SerializationException { if (null == bytes || bytes.length <= 0) { return null; } Kryo kryo = null; Input input = null; try { input = inputPool.obtain(); input.setInputStream(new ByteArrayInputStream(bytes)); kryo = kryoPool.obtain(); return (T) kryo.readClassAndObject(input); } finally { if (input != null) { inputPool.free(input); } if (kryo != null) { kryoPool.free(kryo); } } } }

     
    6参照
    https://stackoverflow.com/questions/58829724/adding-compression-to-spring-data-redis-with-lettuceconnectionfactory
    https://github.com/cboursinos/java-spring-redis-compression-snappy-kryo
    https://ld246.com/article/1532328272348
    https://gitee.com/SoftMeng/spring-boot-skill/tree/master/redis-serializer-line