From 5095620e94a1563d457e008d0bde9190db09a780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=A2=E8=96=AF?= Date: Tue, 3 Jul 2018 16:41:27 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 98302c2..abc59bf 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,7 @@ channel.close(); * https://gitee.com/tywo45/t-io * https://gitee.com/nutz/nutzboot * https://gitee.com/qingfengtaizi/wxmp +* https://gitee.com/xchao/j-im 更多项目收集中,如果你的项目用了,请告诉我 -- Gitee From 2e93c1366be6db3d83b9a8258055fb434f19f62a Mon Sep 17 00:00:00 2001 From: zhangxiaoyu <937425288@qq.com> Date: Wed, 4 Jul 2018 16:39:58 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E7=9A=84=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/spring-boot-starter/readme.md | 4 +- .../J2CacheAutoConfiguration.java | 11 +- .../j2cache/autoconfigure/J2CacheConfig.java | 18 ++- .../J2CacheSpringRedisAutoConfiguration.java | 117 +++++++++++++-- .../ConfigureNotifyKeyspaceEventsAction.java | 49 +++++++ .../SpringRedisActiveMessageListener.java | 45 ++++++ .../cache/support/redis/SpringRedisCache.java | 32 ++-- .../redis/SpringRedisGenericCache.java | 137 ++++++++++++++++++ .../support/redis/SpringRedisProvider.java | 15 +- .../redis/SpringRedisPubSubPolicy.java | 50 ++++++- 10 files changed, 434 insertions(+), 44 deletions(-) create mode 100644 modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java create mode 100644 modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java create mode 100644 modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java diff --git a/modules/spring-boot-starter/readme.md b/modules/spring-boot-starter/readme.md index 74cc268..6f9ed40 100644 --- a/modules/spring-boot-starter/readme.md +++ b/modules/spring-boot-starter/readme.md @@ -12,7 +12,7 @@ j2cache.config-location=/j2cache-${spring.profiles.active}.properties j2cache.open-spring-cache=true ``` ``` -spring.cache.type=none +spring.cache.type=GENERIC ``` 在j2cache.properties中配置,可以使用springRedis进行广播通知缓失效 ``` @@ -21,5 +21,7 @@ j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPol 在j2cache.properties中配置,使用springRedis替换二级缓存 ``` j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider +j2cache.L2.config_section = redis ``` + diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java b/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java index a65b013..a2ffd85 100644 --- a/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java @@ -29,11 +29,16 @@ public class J2CacheAutoConfiguration { } @Bean - @DependsOn("springUtil") - public CacheChannel cacheChannel() throws IOException { + public net.oschina.j2cache.J2CacheConfig j2CacheConfig() throws IOException{ net.oschina.j2cache.J2CacheConfig cacheConfig = new net.oschina.j2cache.J2CacheConfig(); cacheConfig = net.oschina.j2cache.J2CacheConfig.initFromConfig(j2CacheConfig.getConfigLocation()); - J2CacheBuilder builder = J2CacheBuilder.init(cacheConfig); + return cacheConfig; + } + + @Bean + @DependsOn({"springUtil","j2CacheConfig"}) + public CacheChannel cacheChannel(net.oschina.j2cache.J2CacheConfig j2CacheConfig) throws IOException { + J2CacheBuilder builder = J2CacheBuilder.init(j2CacheConfig); return builder.getChannel(); } diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java b/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java index 3c77068..a3a9d14 100644 --- a/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java @@ -11,9 +11,17 @@ public class J2CacheConfig { private String configLocation = "/j2cache.properties"; /** - * 是否开启spring cache缓存,注意:开启后需要添加spring.cache.type=none,将缓存类型设置为none + * 是否开启spring cache缓存,注意:开启后需要添加spring.cache.type=GENERIC,将缓存类型设置为GENERIC */ private Boolean openSpringCache = false; + + /** + * 缓存清除模式, + * active:主动清除,二级缓存过期主动通知各节点清除,优点在于所有节点可以同时收到缓存清除 + * passive:被动清除,一级缓存过期进行通知各节点清除一二级缓存, + * blend:两种模式一起运作,对于各个节点缓存准确以及及时性要求高的可以使用,正常用前两种模式中一个就可 + */ + private String cacheCleanMode = "passive"; public String getConfigLocation() { return configLocation; @@ -30,4 +38,12 @@ public class J2CacheConfig { public void setOpenSpringCache(Boolean openSpringCache) { this.openSpringCache = openSpringCache; } + + public String getCacheCleanMode() { + return cacheCleanMode; + } + + public void setCacheCleanMode(String cacheCleanMode) { + this.cacheCleanMode = cacheCleanMode; + } } diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java b/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java index 22fec12..99c5356 100644 --- a/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java @@ -1,45 +1,138 @@ package net.oschina.j2cache.autoconfigure; import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.serializer.StringRedisSerializer; import net.oschina.j2cache.cache.support.util.J2CacheSerializer; +import net.oschina.j2cache.redis.RedisUtils; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.JedisShardInfo; +import redis.clients.jedis.exceptions.JedisConnectionException; /** * 对spring redis支持的配置入口 + * * @author zhangsaizz * */ @Configuration -@AutoConfigureAfter({RedisAutoConfiguration.class}) -@AutoConfigureBefore({J2CacheAutoConfiguration.class}) +@AutoConfigureAfter({ RedisAutoConfiguration.class }) +@AutoConfigureBefore({ J2CacheAutoConfiguration.class }) public class J2CacheSpringRedisAutoConfiguration { + private final static int MAX_ATTEMPTS = 3; + + private final static int CONNECT_TIMEOUT = 5000; + + private static final Logger log = LoggerFactory.getLogger(J2CacheSpringRedisAutoConfiguration.class); + + @Bean("j2CahceRedisConnectionFactory") + @ConditionalOnMissingBean(name = "j2CahceRedisConnectionFactory") + public JedisConnectionFactory j2CahceRedisConnectionFactory(net.oschina.j2cache.J2CacheConfig j2CacheConfig) { + Properties l2CacheProperties = j2CacheConfig.getL2CacheProperties(); + String hosts = l2CacheProperties.getProperty("hosts"); + String mode = l2CacheProperties.getProperty("mode"); + String clusterName = l2CacheProperties.getProperty("cluster_name"); + String password = l2CacheProperties.getProperty("password"); + int database = Integer.parseInt(l2CacheProperties.getProperty("database")); + JedisConnectionFactory connectionFactory = null; + JedisPoolConfig config = RedisUtils.newPoolConfig(l2CacheProperties, null); + List nodes = new ArrayList<>(); + for (String node : hosts.split(",")) { + String[] s = node.split(":"); + String host = s[0]; + int port = (s.length > 1) ? Integer.parseInt(s[1]) : 6379; + RedisNode n = new RedisNode(host, port); + nodes.add(n); + } + + switch (mode) { + case "sentinel": + RedisSentinelConfiguration sentinel = new RedisSentinelConfiguration(); + sentinel.setMaster(clusterName); + sentinel.setSentinels(nodes); + connectionFactory = new JedisConnectionFactory(sentinel, config); + connectionFactory.setPassword(password); + connectionFactory.setDatabase(database); + break; + case "cluster": + RedisClusterConfiguration cluster = new RedisClusterConfiguration(); + cluster.setClusterNodes(nodes); + cluster.setMaxRedirects(MAX_ATTEMPTS); + connectionFactory = new JedisConnectionFactory(cluster, config); + connectionFactory.setPassword(password); + connectionFactory.setDatabase(database); + break; + case "sharded": + try { + for (String node : hosts.split(",")) { + connectionFactory = new JedisConnectionFactory(new JedisShardInfo(new URI(node))); + connectionFactory.setDatabase(database); + connectionFactory.setPoolConfig(config); + break; + } + } catch (URISyntaxException e) { + throw new JedisConnectionException(e); + } + break; + default: + for (RedisNode node : nodes) { + String host = node.getHost(); + int port = node.getPort(); + connectionFactory = new JedisConnectionFactory(config); + connectionFactory.setPassword(password); + connectionFactory.setHostName(host); + connectionFactory.setPort(port); + connectionFactory.setDatabase(database); + break; + } + if (!"single".equalsIgnoreCase(mode)) + log.warn("Redis mode [" + mode + "] not defined. Using 'single'."); + break; + } + return connectionFactory; + + } + @Bean("j2CacheRedisTemplate") - @ConditionalOnBean(RedisConnectionFactory.class) - public RedisTemplate j2CacheRedisTemplate(RedisConnectionFactory connectionFactory) { + @ConditionalOnBean(name = "j2CahceRedisConnectionFactory") + public RedisTemplate j2CacheRedisTemplate( + JedisConnectionFactory j2CahceRedisConnectionFactory) { RedisTemplate template = new RedisTemplate(); template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setDefaultSerializer(new J2CacheSerializer()); - template.setConnectionFactory(connectionFactory); + template.setConnectionFactory(j2CahceRedisConnectionFactory); return template; } - @Bean("j2CacheRedisMessageListenerContainer") - RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory){ - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - return container; - } + @Bean("j2CacheRedisMessageListenerContainer") + @ConditionalOnBean(name = "j2CahceRedisConnectionFactory") + RedisMessageListenerContainer container(JedisConnectionFactory j2CahceRedisConnectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(j2CahceRedisConnectionFactory); + return container; + } + } diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java new file mode 100644 index 0000000..bfb7b80 --- /dev/null +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java @@ -0,0 +1,49 @@ +package net.oschina.j2cache.cache.support.redis; + +import java.util.List; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.redis.connection.RedisConnection; + +/** + * 设置redis键值回调 + * @param connection + */ +public class ConfigureNotifyKeyspaceEventsAction { + + static final String CONFIG_NOTIFY_KEYSPACE_EVENTS = "notify-keyspace-events"; + + + public void config(RedisConnection connection) { + String notifyOptions = getNotifyOptions(connection); + String customizedNotifyOptions = notifyOptions; + if (!customizedNotifyOptions.contains("E")) { + customizedNotifyOptions += "E"; + } + boolean A = customizedNotifyOptions.contains("A"); + if (!(A || customizedNotifyOptions.contains("g"))) { + customizedNotifyOptions += "g"; + } + if (!(A || customizedNotifyOptions.contains("x"))) { + customizedNotifyOptions += "x"; + } + if (!notifyOptions.equals(customizedNotifyOptions)) { + connection.setConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS, customizedNotifyOptions); + } + } + + private String getNotifyOptions(RedisConnection connection) { + try { + List config = connection.getConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS); + if (config.size() < 2) { + return ""; + } + return config.get(1); + } + catch (InvalidDataAccessApiUsageException e) { + throw new IllegalStateException( + "Unable to configure Redis to keyspace notifications. See http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent", + e); + } + } +} diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java new file mode 100644 index 0000000..1faf99d --- /dev/null +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java @@ -0,0 +1,45 @@ +package net.oschina.j2cache.cache.support.redis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; + +import net.oschina.j2cache.ClusterPolicy; + +/** + * 监听二缓key失效,主动清除本地缓存 + * + * @author zhangsaizz + * + */ +public class SpringRedisActiveMessageListener implements MessageListener { + + private static Logger logger = LoggerFactory.getLogger(SpringRedisActiveMessageListener.class); + + private ClusterPolicy clusterPolicy; + + private String namespace; + + SpringRedisActiveMessageListener(ClusterPolicy clusterPolicy, String namespace) { + this.clusterPolicy = clusterPolicy; + this.namespace = namespace; + } + + @Override + public void onMessage(Message message, byte[] pattern) { + String key = message.toString(); + if (key == null) { + return; + } + if (key.startsWith(namespace + ":")) { + String[] k = key.replaceFirst(namespace + ":", "").split(":", 2); + if(k.length != 2) { + return; + } + clusterPolicy.evict(k[0], k[1]); + } + + } + +} diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java index 415b121..b91d217 100644 --- a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java @@ -9,7 +9,6 @@ import java.util.Set; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.util.StringUtils; import net.oschina.j2cache.Level2Cache; @@ -21,7 +20,7 @@ import net.oschina.j2cache.Level2Cache; */ public class SpringRedisCache implements Level2Cache { - private String namespace = "j2cache~key"; + private String namespace; private String region; @@ -31,9 +30,7 @@ public class SpringRedisCache implements Level2Cache { if (region == null || region.isEmpty()) { region = "_"; // 缺省region } - if(!StringUtils.isEmpty(namespace)) { - this.namespace = namespace; - } + this.namespace = namespace; this.redisTemplate = redisTemplate; this.region = getRegionName(region); } @@ -49,11 +46,6 @@ public class SpringRedisCache implements Level2Cache { redisTemplate.opsForHash().delete(region); } - @Override - public Object get(String key) { - return redisTemplate.boundHashOps(region).get(key); - } - @Override public boolean exists(String key) { return redisTemplate.opsForHash().hasKey(region, key); @@ -63,7 +55,7 @@ public class SpringRedisCache implements Level2Cache { public void evict(String... keys) { for (String k : keys) { if (!k.equals("null")) { - redisTemplate.opsForHash().delete(region, k); + redisTemplate.opsForHash().delete(region, k); } else { redisTemplate.delete(region); } @@ -82,7 +74,7 @@ public class SpringRedisCache implements Level2Cache { @Override public byte[] getBytes(String key) { - return redisTemplate.opsForHash().getOperations().execute((RedisCallback) redis -> redis.hGet(region.getBytes(), key.getBytes())); + return redisTemplate.opsForHash().getOperations().execute((RedisCallback) redis -> redis.hGet(region.getBytes(), key.getBytes())); } @Override @@ -111,14 +103,22 @@ public class SpringRedisCache implements Level2Cache { @Override public void setBytes(String key, byte[] bytes) { - // TODO Auto-generated method stub - + redisTemplate.opsForHash().getOperations().execute((RedisCallback>) redis -> { + redis.set(_key(key).getBytes(), bytes); + redis.hSet(region.getBytes(), key.getBytes(), bytes); + return null; + }); } @Override public void setBytes(Map bytes) { - // TODO Auto-generated method stub - + bytes.forEach((k, v) -> { + setBytes(k, v); + }); + } + + private String _key(String key) { + return this.region + ":" + key; } } diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java new file mode 100644 index 0000000..043a62a --- /dev/null +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java @@ -0,0 +1,137 @@ +package net.oschina.j2cache.cache.support.redis; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; + +import net.oschina.j2cache.Level2Cache; + +public class SpringRedisGenericCache implements Level2Cache { + + private final static Logger log = LoggerFactory.getLogger(SpringRedisGenericCache.class); + + private String namespace; + + private String region; + + private RedisTemplate redisTemplate; + + public SpringRedisGenericCache(String namespace, String region, RedisTemplate redisTemplate) { + if (region == null || region.isEmpty()) { + region = "_"; // 缺省region + } + this.namespace = namespace; + this.redisTemplate = redisTemplate; + this.region = getRegionName(region); + } + + private String getRegionName(String region) { + if (namespace != null && !namespace.isEmpty()) + region = namespace + ":" + region; + return region; + } + + @Override + public void clear() { + Collection keys = keys(); + keys.stream().forEach(k -> { + redisTemplate.delete(k); + }); + } + + @Override + public boolean exists(String key) { + return redisTemplate.hasKey(_key(key)); + } + + @Override + public void evict(String... keys) { + for (String k : keys) { + redisTemplate.delete(_key(k)); + } + } + + @Override + public Collection keys() { + Set list = redisTemplate.keys(this.region + ":*"); + List keys = new ArrayList<>(list.size()); + for (String s : list) { + keys.add(s); + } + return keys; + } + + @Override + public byte[] getBytes(String key) { + return redisTemplate.opsForValue().getOperations().execute((RedisCallback) redis -> { + try { + return redis.get(_key(key).getBytes("utf-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return redis.get(_key(key).getBytes()); + } + }); + } + + @Override + public List getBytes(Collection keys) { + return redisTemplate.opsForValue().getOperations().execute((RedisCallback>) redis -> { + byte[][] bytes = keys.stream().map(k -> _key(k)).toArray(byte[][]::new); + return redis.mGet(bytes); + }); + } + + @Override + public void setBytes(String key, byte[] bytes, long timeToLiveInSeconds) { + if (timeToLiveInSeconds <= 0) { + log.debug(String.format("Invalid timeToLiveInSeconds value : %d , skipped it.", timeToLiveInSeconds)); + setBytes(key, bytes); + } else { + redisTemplate.opsForValue().getOperations().execute((RedisCallback>) redis -> { + try { + redis.setEx(_key(key).getBytes("utf-8"), (int) timeToLiveInSeconds, bytes); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + redis.setEx(_key(key).getBytes(), (int) timeToLiveInSeconds, bytes); + } + return null; + }); + } + } + + @Override + public void setBytes(Map bytes, long timeToLiveInSeconds) { + bytes.forEach((k, v) -> setBytes(k, v, timeToLiveInSeconds)); + } + + @Override + public void setBytes(String key, byte[] bytes) { + redisTemplate.opsForValue().getOperations().execute((RedisCallback) redis -> { + try { + redis.set(_key(key).getBytes("utf-8"), bytes); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + redis.set(_key(key).getBytes(), bytes); + } + return null; + }); + } + + @Override + public void setBytes(Map bytes) { + bytes.forEach((k,v) -> setBytes(k, v)); + } + + private String _key(String key) { + return this.region + ":" + key; + } +} diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java index 6acecc2..3a6f9ec 100644 --- a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java @@ -24,9 +24,11 @@ public class SpringRedisProvider implements CacheProvider { private RedisTemplate redisTemplate; - private String namespace = "j2cache"; + private String namespace; - protected ConcurrentHashMap caches = new ConcurrentHashMap<>(); + private String storage; + + protected ConcurrentHashMap caches = new ConcurrentHashMap<>(); @Override public String name() { @@ -45,12 +47,16 @@ public class SpringRedisProvider implements CacheProvider { @Override public Cache buildCache(String region, CacheExpiredListener listener) { - SpringRedisCache cache = caches.get(region); + Cache cache = caches.get(region); if (cache == null) { synchronized (SpringRedisProvider.class) { cache = caches.get(region); if (cache == null) { - cache = new SpringRedisCache(this.namespace, region, redisTemplate); + if("hash".equalsIgnoreCase(this.storage)) + cache = new SpringRedisCache(this.namespace, region, redisTemplate); + else { + cache = new SpringRedisGenericCache(this.namespace, region, redisTemplate); + } caches.put(region, cache); } } @@ -67,6 +73,7 @@ public class SpringRedisProvider implements CacheProvider { @Override public void start(Properties props) { this.namespace = props.getProperty("namespace"); + this.storage = props.getProperty("storage"); this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); } diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java index 6d1c53d..f13c16c 100644 --- a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java @@ -1,6 +1,8 @@ package net.oschina.j2cache.cache.support.redis; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import org.springframework.data.redis.core.RedisTemplate; @@ -9,6 +11,7 @@ import org.springframework.data.redis.listener.RedisMessageListenerContainer; import net.oschina.j2cache.ClusterPolicy; import net.oschina.j2cache.Command; +import net.oschina.j2cache.J2CacheConfig; import net.oschina.j2cache.cache.support.util.SpringUtil; /** @@ -20,30 +23,63 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ private RedisTemplate redisTemplate; + private net.oschina.j2cache.autoconfigure.J2CacheConfig config; + + /** + * 是否是主动模式 + */ + private static boolean isActive = false; + private String channel = "j2cache_channel"; - + @SuppressWarnings("unchecked") @Override public void connect(Properties props) { - String channel_name = props.getProperty("jgroups.channel.name"); + J2CacheConfig j2config = SpringUtil.getBean(J2CacheConfig.class); + this.config = SpringUtil.getBean(net.oschina.j2cache.autoconfigure.J2CacheConfig.class); + this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); + if("active".equals(config.getCacheCleanMode()) || "blend".equals(config.getCacheCleanMode())) { + isActive = true; + } + String channel_name = j2config.getL2CacheProperties().getProperty("channel"); if(channel_name != null && !channel_name.isEmpty()) { this.channel = channel_name; } - this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); RedisMessageListenerContainer listenerContainer = SpringUtil.getBean("j2CacheRedisMessageListenerContainer", RedisMessageListenerContainer.class); + listenerContainer.addMessageListener(new SpringRedisMessageListener(this, this.channel), new PatternTopic(this.channel)); + if(isActive) { + //设置键值回调 + ConfigureNotifyKeyspaceEventsAction action = new ConfigureNotifyKeyspaceEventsAction(); + action.config(listenerContainer.getConnectionFactory().getConnection()); + + String namespace = j2config.getL2CacheProperties().getProperty("namespace"); + String database = j2config.getL2CacheProperties().getProperty("database"); + String expired = "__keyevent@" + (database == null || "".equals(database) ? "0" : database) + "__:expired"; + String del = "__keyevent@" + (database == null || "".equals(database) ? "0" : database) + "__:del"; + List topics = new ArrayList<>(); + topics.add(new PatternTopic(expired)); + topics.add(new PatternTopic(del)); + listenerContainer.addMessageListener(new SpringRedisActiveMessageListener(this, namespace), topics); + } + } @Override public void sendEvictCmd(String region, String... keys) { - String com = new Command(Command.OPT_EVICT_KEY, region, keys).json(); - redisTemplate.convertAndSend(this.channel, com); + if(!isActive) { + String com = new Command(Command.OPT_EVICT_KEY, region, keys).json(); + redisTemplate.convertAndSend(this.channel, com); + } + } @Override public void sendClearCmd(String region) { - String com = new Command(Command.OPT_CLEAR_KEY, region, "").json(); - redisTemplate.convertAndSend(this.channel, com); + if(!isActive) { + String com = new Command(Command.OPT_CLEAR_KEY, region, "").json(); + redisTemplate.convertAndSend(this.channel, com); + } } @Override -- Gitee From eae8aded9fa1b229f6f33c70a15c9c69a25cfd1c Mon Sep 17 00:00:00 2001 From: zhangxiaoyu <937425288@qq.com> Date: Wed, 4 Jul 2018 16:44:51 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E6=A8=A1=E5=BC=8F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/spring-boot-starter/readme.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/spring-boot-starter/readme.md b/modules/spring-boot-starter/readme.md index 6f9ed40..9087dd8 100644 --- a/modules/spring-boot-starter/readme.md +++ b/modules/spring-boot-starter/readme.md @@ -10,9 +10,15 @@ j2cache.config-location=/j2cache-${spring.profiles.active}.properties 如下两项配置在application.properties,可以开启对spring cahce的支持 ``` j2cache.open-spring-cache=true +spring.cache.type=GENERIC ``` +如下两项配置在application.properties,可以选择缓存清除的模式, +* 缓存清除模式, +* active:主动清除,二级缓存过期主动通知各节点清除,优点在于所有节点可以同时收到缓存清除 +* passive:被动清除,一级缓存过期进行通知各节点清除一二级缓存 +* blend:两种模式一起运作,对于各个节点缓存准确性以及及时性要求高的可以使用,(推荐使用前面两种模式中一种) ``` -spring.cache.type=GENERIC +j2cache.cache-clean-mode=passive ``` 在j2cache.properties中配置,可以使用springRedis进行广播通知缓失效 ``` -- Gitee From 5e191d3a855622ec749757506f9492aff6208fb0 Mon Sep 17 00:00:00 2001 From: zhangxiaoyu <937425288@qq.com> Date: Wed, 4 Jul 2018 16:48:18 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/spring-boot-starter/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/spring-boot-starter/readme.md b/modules/spring-boot-starter/readme.md index 9087dd8..1dbe598 100644 --- a/modules/spring-boot-starter/readme.md +++ b/modules/spring-boot-starter/readme.md @@ -12,11 +12,11 @@ j2cache.config-location=/j2cache-${spring.profiles.active}.properties j2cache.open-spring-cache=true spring.cache.type=GENERIC ``` -如下两项配置在application.properties,可以选择缓存清除的模式, -* 缓存清除模式, +如下配置在application.properties,可以选择缓存清除的模式 +* 缓存清除模式 * active:主动清除,二级缓存过期主动通知各节点清除,优点在于所有节点可以同时收到缓存清除 * passive:被动清除,一级缓存过期进行通知各节点清除一二级缓存 -* blend:两种模式一起运作,对于各个节点缓存准确性以及及时性要求高的可以使用,(推荐使用前面两种模式中一种) +* blend:两种模式一起运作,对于各个节点缓存准确性以及及时性要求高的可以使用(推荐使用前面两种模式中一种) ``` j2cache.cache-clean-mode=passive ``` -- Gitee From 4555e68978570f956e1eb8256d48126c366e98cc Mon Sep 17 00:00:00 2001 From: zhangxiaoyu <937425288@qq.com> Date: Wed, 4 Jul 2018 16:48:50 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=AF=B9spring=20boot2=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E7=9A=84=E5=A2=9E=E5=8A=A0=E7=BC=93=E5=AD=98=E6=B8=85=E9=99=A4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/spring-boot2-starter/pom.xml | 6 +- modules/spring-boot2-starter/readme.md | 12 +- .../J2CacheAutoConfiguration.java | 11 +- .../j2cache/autoconfigure/J2CacheConfig.java | 18 ++- .../J2CacheSpringRedisAutoConfiguration.java | 126 ++++++++++++++-- .../ConfigureNotifyKeyspaceEventsAction.java | 49 +++++++ .../SpringRedisActiveMessageListener.java | 45 ++++++ .../cache/support/redis/SpringRedisCache.java | 47 +++--- .../redis/SpringRedisGenericCache.java | 137 ++++++++++++++++++ .../support/redis/SpringRedisProvider.java | 15 +- .../redis/SpringRedisPubSubPolicy.java | 50 ++++++- 11 files changed, 457 insertions(+), 59 deletions(-) create mode 100644 modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java create mode 100644 modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java create mode 100644 modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java diff --git a/modules/spring-boot2-starter/pom.xml b/modules/spring-boot2-starter/pom.xml index c4c7bdd..ed93161 100644 --- a/modules/spring-boot2-starter/pom.xml +++ b/modules/spring-boot2-starter/pom.xml @@ -35,9 +35,9 @@ compile - org.springframework.data - spring-data-redis - compile + org.springframework.boot + spring-boot-starter-data-redis + compile diff --git a/modules/spring-boot2-starter/readme.md b/modules/spring-boot2-starter/readme.md index af2b4e3..2d68534 100644 --- a/modules/spring-boot2-starter/readme.md +++ b/modules/spring-boot2-starter/readme.md @@ -11,9 +11,15 @@ j2cache.config-location=/j2cache-${spring.profiles.active}.properties 如下两项配置在application.properties,可以开启对spring cahce的支持 ``` j2cache.open-spring-cache=true +spring.cache.type=GENERIC ``` +如下配置在application.properties,可以选择缓存清除的模式 +* 缓存清除模式 +* active:主动清除,二级缓存过期主动通知各节点清除,优点在于所有节点可以同时收到缓存清除 +* passive:被动清除,一级缓存过期进行通知各节点清除一二级缓存 +* blend:两种模式一起运作,对于各个节点缓存准确性以及及时性要求高的可以使用(推荐使用前面两种模式中一种) ``` -spring.cache.type=none +j2cache.cache-clean-mode=passive ``` 在j2cache.properties中配置,可以使用springRedis进行广播通知缓失效 ``` @@ -22,5 +28,9 @@ j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPol 在j2cache.properties中配置,使用springRedis替换二级缓存 ``` j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider +j2cache.L2.config_section = redis ``` + + + diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java index a65b013..a2ffd85 100644 --- a/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheAutoConfiguration.java @@ -29,11 +29,16 @@ public class J2CacheAutoConfiguration { } @Bean - @DependsOn("springUtil") - public CacheChannel cacheChannel() throws IOException { + public net.oschina.j2cache.J2CacheConfig j2CacheConfig() throws IOException{ net.oschina.j2cache.J2CacheConfig cacheConfig = new net.oschina.j2cache.J2CacheConfig(); cacheConfig = net.oschina.j2cache.J2CacheConfig.initFromConfig(j2CacheConfig.getConfigLocation()); - J2CacheBuilder builder = J2CacheBuilder.init(cacheConfig); + return cacheConfig; + } + + @Bean + @DependsOn({"springUtil","j2CacheConfig"}) + public CacheChannel cacheChannel(net.oschina.j2cache.J2CacheConfig j2CacheConfig) throws IOException { + J2CacheBuilder builder = J2CacheBuilder.init(j2CacheConfig); return builder.getChannel(); } diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java index 3c77068..a3a9d14 100644 --- a/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheConfig.java @@ -11,9 +11,17 @@ public class J2CacheConfig { private String configLocation = "/j2cache.properties"; /** - * 是否开启spring cache缓存,注意:开启后需要添加spring.cache.type=none,将缓存类型设置为none + * 是否开启spring cache缓存,注意:开启后需要添加spring.cache.type=GENERIC,将缓存类型设置为GENERIC */ private Boolean openSpringCache = false; + + /** + * 缓存清除模式, + * active:主动清除,二级缓存过期主动通知各节点清除,优点在于所有节点可以同时收到缓存清除 + * passive:被动清除,一级缓存过期进行通知各节点清除一二级缓存, + * blend:两种模式一起运作,对于各个节点缓存准确以及及时性要求高的可以使用,正常用前两种模式中一个就可 + */ + private String cacheCleanMode = "passive"; public String getConfigLocation() { return configLocation; @@ -30,4 +38,12 @@ public class J2CacheConfig { public void setOpenSpringCache(Boolean openSpringCache) { this.openSpringCache = openSpringCache; } + + public String getCacheCleanMode() { + return cacheCleanMode; + } + + public void setCacheCleanMode(String cacheCleanMode) { + this.cacheCleanMode = cacheCleanMode; + } } diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java index 22fec12..9b8a316 100644 --- a/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/autoconfigure/J2CacheSpringRedisAutoConfiguration.java @@ -1,45 +1,147 @@ package net.oschina.j2cache.autoconfigure; import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.RedisPassword; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.util.StringUtils; import net.oschina.j2cache.cache.support.util.J2CacheSerializer; +import net.oschina.j2cache.redis.RedisUtils; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.JedisShardInfo; +import redis.clients.jedis.exceptions.JedisConnectionException; /** * 对spring redis支持的配置入口 + * * @author zhangsaizz * */ @Configuration -@AutoConfigureAfter({RedisAutoConfiguration.class}) -@AutoConfigureBefore({J2CacheAutoConfiguration.class}) +@AutoConfigureAfter({ RedisAutoConfiguration.class }) +@AutoConfigureBefore({ J2CacheAutoConfiguration.class }) public class J2CacheSpringRedisAutoConfiguration { + private final static int MAX_ATTEMPTS = 3; + + private final static int CONNECT_TIMEOUT = 5000; + + private static final Logger log = LoggerFactory.getLogger(J2CacheSpringRedisAutoConfiguration.class); + + @SuppressWarnings("deprecation") + @Bean("j2CahceRedisConnectionFactory") + @ConditionalOnMissingBean(name = "j2CahceRedisConnectionFactory") + public JedisConnectionFactory j2CahceRedisConnectionFactory(net.oschina.j2cache.J2CacheConfig j2CacheConfig) { + Properties l2CacheProperties = j2CacheConfig.getL2CacheProperties(); + String hosts = l2CacheProperties.getProperty("hosts"); + String mode = l2CacheProperties.getProperty("mode"); + String clusterName = l2CacheProperties.getProperty("cluster_name"); + String password = l2CacheProperties.getProperty("password"); + int database = Integer.parseInt(l2CacheProperties.getProperty("database")); + JedisConnectionFactory connectionFactory = null; + JedisPoolConfig config = RedisUtils.newPoolConfig(l2CacheProperties, null); + List nodes = new ArrayList<>(); + for (String node : hosts.split(",")) { + String[] s = node.split(":"); + String host = s[0]; + int port = (s.length > 1) ? Integer.parseInt(s[1]) : 6379; + RedisNode n = new RedisNode(host, port); + nodes.add(n); + } + RedisPassword paw = RedisPassword.none(); + if (!StringUtils.isEmpty(password)) { + paw = RedisPassword.of(password); + } + + switch (mode) { + case "sentinel": + RedisSentinelConfiguration sentinel = new RedisSentinelConfiguration(); + sentinel.setDatabase(database); + sentinel.setPassword(paw); + sentinel.setMaster(clusterName); + sentinel.setSentinels(nodes); + connectionFactory = new JedisConnectionFactory(sentinel, config); + break; + case "cluster": + RedisClusterConfiguration cluster = new RedisClusterConfiguration(); + cluster.setClusterNodes(nodes); + cluster.setMaxRedirects(MAX_ATTEMPTS); + cluster.setPassword(paw); + connectionFactory = new JedisConnectionFactory(cluster, config); + break; + case "sharded": + try { + for (String node : hosts.split(",")) { + connectionFactory = new JedisConnectionFactory(new JedisShardInfo(new URI(node))); + break; + } + } catch (URISyntaxException e) { + throw new JedisConnectionException(e); + } + break; + default: + for (RedisNode node : nodes) { + String host = node.getHost(); + int port = node.getPort(); + RedisStandaloneConfiguration single = new RedisStandaloneConfiguration(host, port); + single.setDatabase(database); + JedisClientConfigurationBuilder clientConfiguration = JedisClientConfiguration.builder(); + clientConfiguration.usePooling().poolConfig(config); + clientConfiguration.connectTimeout(Duration.ofMillis(CONNECT_TIMEOUT)); + connectionFactory = new JedisConnectionFactory(single, clientConfiguration.build()); + break; + } + if (!"single".equalsIgnoreCase(mode)) + log.warn("Redis mode [" + mode + "] not defined. Using 'single'."); + break; + } + return connectionFactory; + + } + @Bean("j2CacheRedisTemplate") - @ConditionalOnBean(RedisConnectionFactory.class) - public RedisTemplate j2CacheRedisTemplate(RedisConnectionFactory connectionFactory) { + @ConditionalOnBean(name = "j2CahceRedisConnectionFactory") + public RedisTemplate j2CacheRedisTemplate( + JedisConnectionFactory j2CahceRedisConnectionFactory) { RedisTemplate template = new RedisTemplate(); template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setDefaultSerializer(new J2CacheSerializer()); - template.setConnectionFactory(connectionFactory); + template.setConnectionFactory(j2CahceRedisConnectionFactory); return template; } - @Bean("j2CacheRedisMessageListenerContainer") - RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory){ - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - return container; - } + @Bean("j2CacheRedisMessageListenerContainer") + @ConditionalOnBean(name = "j2CahceRedisConnectionFactory") + RedisMessageListenerContainer container(JedisConnectionFactory j2CahceRedisConnectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(j2CahceRedisConnectionFactory); + return container; + } + } diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java new file mode 100644 index 0000000..2d4134f --- /dev/null +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/ConfigureNotifyKeyspaceEventsAction.java @@ -0,0 +1,49 @@ +package net.oschina.j2cache.cache.support.redis; + +import java.util.Properties; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.redis.connection.RedisConnection; + +/** + * 设置redis键值回调 + * @param connection + */ +public class ConfigureNotifyKeyspaceEventsAction { + + static final String CONFIG_NOTIFY_KEYSPACE_EVENTS = "notify-keyspace-events"; + + + public void config(RedisConnection connection) { + String notifyOptions = getNotifyOptions(connection); + String customizedNotifyOptions = notifyOptions; + if (!customizedNotifyOptions.contains("E")) { + customizedNotifyOptions += "E"; + } + boolean A = customizedNotifyOptions.contains("A"); + if (!(A || customizedNotifyOptions.contains("g"))) { + customizedNotifyOptions += "g"; + } + if (!(A || customizedNotifyOptions.contains("x"))) { + customizedNotifyOptions += "x"; + } + if (!notifyOptions.equals(customizedNotifyOptions)) { + connection.setConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS, customizedNotifyOptions); + } + } + + private String getNotifyOptions(RedisConnection connection) { + try { + Properties config = connection.getConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS); + if (config.isEmpty()) { + return ""; + } + return config.getProperty(config.stringPropertyNames().iterator().next()); + } + catch (InvalidDataAccessApiUsageException e) { + throw new IllegalStateException( + "Unable to configure Redis to keyspace notifications. See http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent", + e); + } + } +} diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java new file mode 100644 index 0000000..1faf99d --- /dev/null +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisActiveMessageListener.java @@ -0,0 +1,45 @@ +package net.oschina.j2cache.cache.support.redis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; + +import net.oschina.j2cache.ClusterPolicy; + +/** + * 监听二缓key失效,主动清除本地缓存 + * + * @author zhangsaizz + * + */ +public class SpringRedisActiveMessageListener implements MessageListener { + + private static Logger logger = LoggerFactory.getLogger(SpringRedisActiveMessageListener.class); + + private ClusterPolicy clusterPolicy; + + private String namespace; + + SpringRedisActiveMessageListener(ClusterPolicy clusterPolicy, String namespace) { + this.clusterPolicy = clusterPolicy; + this.namespace = namespace; + } + + @Override + public void onMessage(Message message, byte[] pattern) { + String key = message.toString(); + if (key == null) { + return; + } + if (key.startsWith(namespace + ":")) { + String[] k = key.replaceFirst(namespace + ":", "").split(":", 2); + if(k.length != 2) { + return; + } + clusterPolicy.evict(k[0], k[1]); + } + + } + +} diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java index f26bb6b..b91d217 100644 --- a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisCache.java @@ -7,11 +7,8 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.springframework.dao.DataAccessException; -import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.util.StringUtils; import net.oschina.j2cache.Level2Cache; @@ -23,7 +20,7 @@ import net.oschina.j2cache.Level2Cache; */ public class SpringRedisCache implements Level2Cache { - private String namespace = "j2cache~key"; + private String namespace; private String region; @@ -33,9 +30,7 @@ public class SpringRedisCache implements Level2Cache { if (region == null || region.isEmpty()) { region = "_"; // 缺省region } - if(!StringUtils.isEmpty(namespace)) { - this.namespace = namespace; - } + this.namespace = namespace; this.redisTemplate = redisTemplate; this.region = getRegionName(region); } @@ -51,11 +46,6 @@ public class SpringRedisCache implements Level2Cache { redisTemplate.opsForHash().delete(region); } - @Override - public Object get(String key) { - return redisTemplate.boundHashOps(region).get(key); - } - @Override public boolean exists(String key) { return redisTemplate.opsForHash().hasKey(region, key); @@ -65,7 +55,7 @@ public class SpringRedisCache implements Level2Cache { public void evict(String... keys) { for (String k : keys) { if (!k.equals("null")) { - redisTemplate.opsForHash().delete(region, k); + redisTemplate.opsForHash().delete(region, k); } else { redisTemplate.delete(region); } @@ -84,21 +74,14 @@ public class SpringRedisCache implements Level2Cache { @Override public byte[] getBytes(String key) { - return redisTemplate.opsForHash().getOperations().execute(new RedisCallback() { - public byte[] doInRedis(RedisConnection redis) { - return redis.hGet(region.getBytes(), key.getBytes()); - } - }); + return redisTemplate.opsForHash().getOperations().execute((RedisCallback) redis -> redis.hGet(region.getBytes(), key.getBytes())); } @Override public List getBytes(Collection keys) { - return redisTemplate.opsForHash().getOperations().execute(new RedisCallback>() { - @Override - public List doInRedis(RedisConnection redis) throws DataAccessException { - byte[][] bytes = keys.stream().map(k -> k.getBytes()).toArray(byte[][]::new); - return redis.hMGet(region.getBytes(), bytes); - } + return redisTemplate.opsForHash().getOperations().execute((RedisCallback>) redis -> { + byte[][] bytes = keys.stream().map(k -> k.getBytes()).toArray(byte[][]::new); + return redis.hMGet(region.getBytes(), bytes); }); } @@ -120,14 +103,22 @@ public class SpringRedisCache implements Level2Cache { @Override public void setBytes(String key, byte[] bytes) { - // TODO Auto-generated method stub - + redisTemplate.opsForHash().getOperations().execute((RedisCallback>) redis -> { + redis.set(_key(key).getBytes(), bytes); + redis.hSet(region.getBytes(), key.getBytes(), bytes); + return null; + }); } @Override public void setBytes(Map bytes) { - // TODO Auto-generated method stub - + bytes.forEach((k, v) -> { + setBytes(k, v); + }); + } + + private String _key(String key) { + return this.region + ":" + key; } } diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java new file mode 100644 index 0000000..043a62a --- /dev/null +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisGenericCache.java @@ -0,0 +1,137 @@ +package net.oschina.j2cache.cache.support.redis; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; + +import net.oschina.j2cache.Level2Cache; + +public class SpringRedisGenericCache implements Level2Cache { + + private final static Logger log = LoggerFactory.getLogger(SpringRedisGenericCache.class); + + private String namespace; + + private String region; + + private RedisTemplate redisTemplate; + + public SpringRedisGenericCache(String namespace, String region, RedisTemplate redisTemplate) { + if (region == null || region.isEmpty()) { + region = "_"; // 缺省region + } + this.namespace = namespace; + this.redisTemplate = redisTemplate; + this.region = getRegionName(region); + } + + private String getRegionName(String region) { + if (namespace != null && !namespace.isEmpty()) + region = namespace + ":" + region; + return region; + } + + @Override + public void clear() { + Collection keys = keys(); + keys.stream().forEach(k -> { + redisTemplate.delete(k); + }); + } + + @Override + public boolean exists(String key) { + return redisTemplate.hasKey(_key(key)); + } + + @Override + public void evict(String... keys) { + for (String k : keys) { + redisTemplate.delete(_key(k)); + } + } + + @Override + public Collection keys() { + Set list = redisTemplate.keys(this.region + ":*"); + List keys = new ArrayList<>(list.size()); + for (String s : list) { + keys.add(s); + } + return keys; + } + + @Override + public byte[] getBytes(String key) { + return redisTemplate.opsForValue().getOperations().execute((RedisCallback) redis -> { + try { + return redis.get(_key(key).getBytes("utf-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return redis.get(_key(key).getBytes()); + } + }); + } + + @Override + public List getBytes(Collection keys) { + return redisTemplate.opsForValue().getOperations().execute((RedisCallback>) redis -> { + byte[][] bytes = keys.stream().map(k -> _key(k)).toArray(byte[][]::new); + return redis.mGet(bytes); + }); + } + + @Override + public void setBytes(String key, byte[] bytes, long timeToLiveInSeconds) { + if (timeToLiveInSeconds <= 0) { + log.debug(String.format("Invalid timeToLiveInSeconds value : %d , skipped it.", timeToLiveInSeconds)); + setBytes(key, bytes); + } else { + redisTemplate.opsForValue().getOperations().execute((RedisCallback>) redis -> { + try { + redis.setEx(_key(key).getBytes("utf-8"), (int) timeToLiveInSeconds, bytes); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + redis.setEx(_key(key).getBytes(), (int) timeToLiveInSeconds, bytes); + } + return null; + }); + } + } + + @Override + public void setBytes(Map bytes, long timeToLiveInSeconds) { + bytes.forEach((k, v) -> setBytes(k, v, timeToLiveInSeconds)); + } + + @Override + public void setBytes(String key, byte[] bytes) { + redisTemplate.opsForValue().getOperations().execute((RedisCallback) redis -> { + try { + redis.set(_key(key).getBytes("utf-8"), bytes); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + redis.set(_key(key).getBytes(), bytes); + } + return null; + }); + } + + @Override + public void setBytes(Map bytes) { + bytes.forEach((k,v) -> setBytes(k, v)); + } + + private String _key(String key) { + return this.region + ":" + key; + } +} diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java index 6acecc2..3a6f9ec 100644 --- a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisProvider.java @@ -24,9 +24,11 @@ public class SpringRedisProvider implements CacheProvider { private RedisTemplate redisTemplate; - private String namespace = "j2cache"; + private String namespace; - protected ConcurrentHashMap caches = new ConcurrentHashMap<>(); + private String storage; + + protected ConcurrentHashMap caches = new ConcurrentHashMap<>(); @Override public String name() { @@ -45,12 +47,16 @@ public class SpringRedisProvider implements CacheProvider { @Override public Cache buildCache(String region, CacheExpiredListener listener) { - SpringRedisCache cache = caches.get(region); + Cache cache = caches.get(region); if (cache == null) { synchronized (SpringRedisProvider.class) { cache = caches.get(region); if (cache == null) { - cache = new SpringRedisCache(this.namespace, region, redisTemplate); + if("hash".equalsIgnoreCase(this.storage)) + cache = new SpringRedisCache(this.namespace, region, redisTemplate); + else { + cache = new SpringRedisGenericCache(this.namespace, region, redisTemplate); + } caches.put(region, cache); } } @@ -67,6 +73,7 @@ public class SpringRedisProvider implements CacheProvider { @Override public void start(Properties props) { this.namespace = props.getProperty("namespace"); + this.storage = props.getProperty("storage"); this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); } diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java index 6d1c53d..f13c16c 100644 --- a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java @@ -1,6 +1,8 @@ package net.oschina.j2cache.cache.support.redis; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import org.springframework.data.redis.core.RedisTemplate; @@ -9,6 +11,7 @@ import org.springframework.data.redis.listener.RedisMessageListenerContainer; import net.oschina.j2cache.ClusterPolicy; import net.oschina.j2cache.Command; +import net.oschina.j2cache.J2CacheConfig; import net.oschina.j2cache.cache.support.util.SpringUtil; /** @@ -20,30 +23,63 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ private RedisTemplate redisTemplate; + private net.oschina.j2cache.autoconfigure.J2CacheConfig config; + + /** + * 是否是主动模式 + */ + private static boolean isActive = false; + private String channel = "j2cache_channel"; - + @SuppressWarnings("unchecked") @Override public void connect(Properties props) { - String channel_name = props.getProperty("jgroups.channel.name"); + J2CacheConfig j2config = SpringUtil.getBean(J2CacheConfig.class); + this.config = SpringUtil.getBean(net.oschina.j2cache.autoconfigure.J2CacheConfig.class); + this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); + if("active".equals(config.getCacheCleanMode()) || "blend".equals(config.getCacheCleanMode())) { + isActive = true; + } + String channel_name = j2config.getL2CacheProperties().getProperty("channel"); if(channel_name != null && !channel_name.isEmpty()) { this.channel = channel_name; } - this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); RedisMessageListenerContainer listenerContainer = SpringUtil.getBean("j2CacheRedisMessageListenerContainer", RedisMessageListenerContainer.class); + listenerContainer.addMessageListener(new SpringRedisMessageListener(this, this.channel), new PatternTopic(this.channel)); + if(isActive) { + //设置键值回调 + ConfigureNotifyKeyspaceEventsAction action = new ConfigureNotifyKeyspaceEventsAction(); + action.config(listenerContainer.getConnectionFactory().getConnection()); + + String namespace = j2config.getL2CacheProperties().getProperty("namespace"); + String database = j2config.getL2CacheProperties().getProperty("database"); + String expired = "__keyevent@" + (database == null || "".equals(database) ? "0" : database) + "__:expired"; + String del = "__keyevent@" + (database == null || "".equals(database) ? "0" : database) + "__:del"; + List topics = new ArrayList<>(); + topics.add(new PatternTopic(expired)); + topics.add(new PatternTopic(del)); + listenerContainer.addMessageListener(new SpringRedisActiveMessageListener(this, namespace), topics); + } + } @Override public void sendEvictCmd(String region, String... keys) { - String com = new Command(Command.OPT_EVICT_KEY, region, keys).json(); - redisTemplate.convertAndSend(this.channel, com); + if(!isActive) { + String com = new Command(Command.OPT_EVICT_KEY, region, keys).json(); + redisTemplate.convertAndSend(this.channel, com); + } + } @Override public void sendClearCmd(String region) { - String com = new Command(Command.OPT_CLEAR_KEY, region, "").json(); - redisTemplate.convertAndSend(this.channel, com); + if(!isActive) { + String com = new Command(Command.OPT_CLEAR_KEY, region, "").json(); + redisTemplate.convertAndSend(this.channel, com); + } } @Override -- Gitee From e8f95c0a726491d03901e1400f6e71de9db0efe0 Mon Sep 17 00:00:00 2001 From: zhangxiaoyu <937425288@qq.com> Date: Wed, 4 Jul 2018 17:08:35 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BC=80=E5=90=AF?= =?UTF-8?q?=E6=B7=B7=E5=90=88=E6=A8=A1=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cache/support/redis/SpringRedisPubSubPolicy.java | 6 +++--- .../cache/support/redis/SpringRedisPubSubPolicy.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java index f13c16c..254bca4 100644 --- a/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java +++ b/modules/spring-boot-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java @@ -38,7 +38,7 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ J2CacheConfig j2config = SpringUtil.getBean(J2CacheConfig.class); this.config = SpringUtil.getBean(net.oschina.j2cache.autoconfigure.J2CacheConfig.class); this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); - if("active".equals(config.getCacheCleanMode()) || "blend".equals(config.getCacheCleanMode())) { + if("active".equals(config.getCacheCleanMode())) { isActive = true; } String channel_name = j2config.getL2CacheProperties().getProperty("channel"); @@ -67,7 +67,7 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ @Override public void sendEvictCmd(String region, String... keys) { - if(!isActive) { + if(!isActive || "blend".equals(config.getCacheCleanMode())) { String com = new Command(Command.OPT_EVICT_KEY, region, keys).json(); redisTemplate.convertAndSend(this.channel, com); } @@ -76,7 +76,7 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ @Override public void sendClearCmd(String region) { - if(!isActive) { + if(!isActive || "blend".equals(config.getCacheCleanMode())) { String com = new Command(Command.OPT_CLEAR_KEY, region, "").json(); redisTemplate.convertAndSend(this.channel, com); } diff --git a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java index f13c16c..ff6b9c0 100644 --- a/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java +++ b/modules/spring-boot2-starter/src/net/oschina/j2cache/cache/support/redis/SpringRedisPubSubPolicy.java @@ -38,7 +38,7 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ J2CacheConfig j2config = SpringUtil.getBean(J2CacheConfig.class); this.config = SpringUtil.getBean(net.oschina.j2cache.autoconfigure.J2CacheConfig.class); this.redisTemplate = SpringUtil.getBean("j2CacheRedisTemplate", RedisTemplate.class); - if("active".equals(config.getCacheCleanMode()) || "blend".equals(config.getCacheCleanMode())) { + if("active".equals(config.getCacheCleanMode())) { isActive = true; } String channel_name = j2config.getL2CacheProperties().getProperty("channel"); @@ -48,7 +48,7 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ RedisMessageListenerContainer listenerContainer = SpringUtil.getBean("j2CacheRedisMessageListenerContainer", RedisMessageListenerContainer.class); listenerContainer.addMessageListener(new SpringRedisMessageListener(this, this.channel), new PatternTopic(this.channel)); - if(isActive) { + if(isActive || "blend".equals(config.getCacheCleanMode())) { //设置键值回调 ConfigureNotifyKeyspaceEventsAction action = new ConfigureNotifyKeyspaceEventsAction(); action.config(listenerContainer.getConnectionFactory().getConnection()); @@ -67,7 +67,7 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ @Override public void sendEvictCmd(String region, String... keys) { - if(!isActive) { + if(!isActive || "blend".equals(config.getCacheCleanMode())) { String com = new Command(Command.OPT_EVICT_KEY, region, keys).json(); redisTemplate.convertAndSend(this.channel, com); } @@ -76,7 +76,7 @@ public class SpringRedisPubSubPolicy implements ClusterPolicy{ @Override public void sendClearCmd(String region) { - if(!isActive) { + if(!isActive || "blend".equals(config.getCacheCleanMode())) { String com = new Command(Command.OPT_CLEAR_KEY, region, "").json(); redisTemplate.convertAndSend(this.channel, com); } -- Gitee From 55e1de1fdb234741ec86b9cdf3aff72faa623475 Mon Sep 17 00:00:00 2001 From: likui Date: Wed, 1 Jul 2020 17:43:31 +0800 Subject: [PATCH 7/7] update README.md. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index abc59bf..267cedd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ![J2Cache](docs/J2Cache.png) + # J2Cache —— 基于内存和 Redis 的两级 Java 缓存框架 专用QQ群: `379110351` -- Gitee