瀏覽代碼

优化redis缓存 使用json方式。增加简单的监控

zhouhao 8 年之前
父節點
當前提交
5e15bebd63

+ 23 - 9
hsweb-web-concurrent/hsweb-web-concurrent-cache/src/main/java/org/hsweb/concureent/cache/RedisCacheManagerAutoConfig.java

@@ -1,18 +1,24 @@
 package org.hsweb.concureent.cache;
 
+import org.hsweb.concureent.cache.monitor.RedisMonitorCache;
+import org.hsweb.concureent.cache.redis.FastJsonRedisTemplate;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.cache.interceptor.KeyGenerator;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.DefaultRedisCachePrefix;
+import org.springframework.data.redis.cache.RedisCache;
 import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.connection.jedis.JedisConnection;
 import org.springframework.data.redis.core.RedisOperations;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
 import redis.clients.jedis.Jedis;
 
@@ -26,8 +32,22 @@ import redis.clients.jedis.Jedis;
 public class RedisCacheManagerAutoConfig extends CachingConfigurerSupport {
 
     @Bean
-    public CacheManager cacheManager(RedisTemplate redisTemplate) {
-        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
+    @ConditionalOnMissingBean(FastJsonRedisTemplate.class)
+    public FastJsonRedisTemplate redisTemplate(
+            RedisConnectionFactory redisConnectionFactory) {
+        FastJsonRedisTemplate template = new FastJsonRedisTemplate(redisConnectionFactory);
+        return template;
+    }
+
+    @Bean
+    public CacheManager cacheManager(FastJsonRedisTemplate redisTemplate) {
+        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate){
+            @Override
+            protected RedisCache createCache(String cacheName) {
+                long expiration = computeExpiration(cacheName);
+                return new RedisMonitorCache(cacheName, new DefaultRedisCachePrefix().prefix(cacheName), redisTemplate, expiration);
+            }
+        };
         redisCacheManager.setUsePrefix(true);
         return redisCacheManager;
     }
@@ -37,11 +57,5 @@ public class RedisCacheManagerAutoConfig extends CachingConfigurerSupport {
         return new SimpleKeyGenerator();
     }
 
-    @Bean
-    public RedisTemplate<String, String> redisTemplate(
-            RedisConnectionFactory redisConnectionFactory) {
-        StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory);
-        template.setValueSerializer(new JdkSerializationRedisSerializer());
-        return template;
-    }
+
 }

+ 169 - 0
hsweb-web-concurrent/hsweb-web-concurrent-cache/src/main/java/org/hsweb/concureent/cache/monitor/RedisMonitorCache.java

@@ -0,0 +1,169 @@
+/*
+ * Copyright 2011-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hsweb.concureent.cache.monitor;
+
+import static org.springframework.util.Assert.*;
+import static org.springframework.util.ObjectUtils.*;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
+
+import org.hsweb.commons.StringUtils;
+import org.hsweb.web.core.cache.monitor.MonitorCache;
+import org.hsweb.web.core.utils.ThreadLocalUtils;
+import org.springframework.cache.Cache;
+import org.springframework.cache.support.SimpleValueWrapper;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.RedisSystemException;
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.data.redis.cache.RedisCacheElement;
+import org.springframework.data.redis.cache.RedisCacheKey;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.ReturnType;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Cache implementation on top of Redis.
+ *
+ * @author Costin Leau
+ * @author Christoph Strobl
+ * @author Thomas Darimont
+ */
+@SuppressWarnings("unchecked")
+public class RedisMonitorCache extends RedisCache implements Cache, MonitorCache {
+
+    @SuppressWarnings("rawtypes")//
+    private final RedisOperations redisOperations;
+    private final byte[]          totalTimeKey;
+    private final byte[]          hitTimeKey;
+    private final byte[]          putTimeKey;
+    private final byte[]          keySetKey;
+    private long expiration = 0;
+
+    public RedisMonitorCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
+                             long expiration) {
+        super(name, prefix, redisOperations, expiration);
+        this.expiration = expiration;
+        this.redisOperations = redisOperations;
+        this.keySetKey = (name + "~keys").getBytes();
+        this.totalTimeKey = name.concat(":total-times").getBytes();
+        this.hitTimeKey = name.concat(":hit-times").getBytes();
+        this.putTimeKey = name.concat(":put-times").getBytes();
+    }
+
+    @Override
+    public <T> T get(Object key, Class<T> type) {
+        String localCacheKey = "cache-".concat(String.valueOf(key));
+        T localCache = ThreadLocalUtils.get(localCacheKey);
+        if (localCache != null) {
+            return localCache;
+        }
+        T v = super.get(key, type);
+        redisOperations.execute((RedisCallback) connection -> {
+            connection.incr(totalTimeKey);
+            if (v != null) {
+                connection.incr(hitTimeKey);
+            }
+            return null;
+        });
+        if (v != null) {
+            ThreadLocalUtils.put(localCacheKey, v);
+        }
+        return v;
+    }
+
+    @Override
+    public ValueWrapper get(Object key) {
+        String localCacheKey = "cache-".concat(String.valueOf(key));
+        ValueWrapper localCache = ThreadLocalUtils.get(localCacheKey);
+        if (localCache != null) {
+            return localCache;
+        }
+        ValueWrapper wrapper = super.get(key);
+        redisOperations.execute((RedisCallback) connection -> {
+            connection.incr(totalTimeKey);
+            if (wrapper != null) {
+                connection.incr(hitTimeKey);
+            }
+            return null;
+        });
+        if (wrapper != null) {
+            ThreadLocalUtils.put(localCacheKey, wrapper);
+        }
+        return wrapper;
+    }
+
+    @Override
+    public void put(Object key, Object value) {
+        super.put(key, value);
+        redisOperations.execute((RedisCallback) connection -> {
+            connection.multi();
+            connection.incr(putTimeKey);
+            connection.sAdd(keySetKey, ((String) key).getBytes());
+            if (expiration != 0) connection.expire(keySetKey, expiration);
+            connection.exec();
+            return null;
+        });
+    }
+
+    @Override
+    public void evict(Object key) {
+        super.evict(key);
+        redisOperations.execute((RedisCallback) connection -> {
+            connection.sRem(keySetKey, ((String) key).getBytes());
+            return null;
+        });
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+        redisOperations.delete(keySetKey);
+    }
+
+    @Override
+    public Set<Object> keySet() {
+        return (Set<Object>) redisOperations.execute((RedisCallback) connection -> connection.sMembers(keySetKey).stream().map(String::new).collect(Collectors.toSet()));
+    }
+
+    @Override
+    public int size() {
+        return redisOperations.opsForSet().size(new String(keySetKey)).intValue();
+    }
+
+    @Override
+    public long getTotalTimes() {
+        return StringUtils.toInt(redisOperations.opsForValue().get(new String(totalTimeKey)));
+    }
+
+    @Override
+    public long getHitTimes() {
+        return StringUtils.toInt(redisOperations.opsForValue().get(new String(hitTimeKey)));
+    }
+
+    @Override
+    public long getPutTimes() {
+        return StringUtils.toInt(redisOperations.opsForValue().get(new String(putTimeKey)));
+    }
+}

+ 24 - 0
hsweb-web-concurrent/hsweb-web-concurrent-cache/src/main/java/org/hsweb/concureent/cache/redis/FastJsonRedisSerializer.java

@@ -0,0 +1,24 @@
+package org.hsweb.concureent.cache.redis;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+/**
+ * @author zhouhao
+ * @TODO
+ */
+public class FastJsonRedisSerializer implements RedisSerializer<Object> {
+    @Override
+    public byte[] serialize(Object o) throws SerializationException {
+        if (o == null) return null;
+        return JSON.toJSONBytes(o, SerializerFeature.WriteClassName);
+    }
+
+    @Override
+    public Object deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null) return null;
+        return JSON.parse(bytes);
+    }
+}

+ 42 - 0
hsweb-web-concurrent/hsweb-web-concurrent-cache/src/main/java/org/hsweb/concureent/cache/redis/FastJsonRedisTemplate.java

@@ -0,0 +1,42 @@
+package org.hsweb.concureent.cache.redis;
+
+import org.springframework.data.redis.connection.DefaultStringRedisConnection;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * @author zhouhao
+ * @TODO
+ */
+public class FastJsonRedisTemplate extends RedisTemplate<String, Object> {
+    /**
+     * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
+     * and {@link #afterPropertiesSet()} still need to be called.
+     */
+    public FastJsonRedisTemplate() {
+        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
+        FastJsonRedisSerializer redisSerializer = new FastJsonRedisSerializer();
+        setKeySerializer(stringSerializer);
+        setValueSerializer(redisSerializer);
+        setHashKeySerializer(stringSerializer);
+        setHashValueSerializer(redisSerializer);
+    }
+
+    /**
+     * Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
+     *
+     * @param connectionFactory connection factory for creating new connections
+     */
+    public FastJsonRedisTemplate(RedisConnectionFactory connectionFactory) {
+        this();
+        setConnectionFactory(connectionFactory);
+        afterPropertiesSet();
+    }
+
+    protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
+        return new DefaultStringRedisConnection(connection);
+    }
+}