diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a5af5193..ac4a5a703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,25 @@ Redisson Releases History ================================ ### Please Note: trunk is current development branch. -__[Redisson PRO](https://redisson.pro)__ now costs only __$975__ per year and supports unlimited Redisson instances +Try __[Redisson PRO](https://redisson.pro)__ version. + +### 28-Jul-2017 - versions 2.10.0 and 3.5.0 released + +Feature - __Local Cache support for Hibernate Cache__ Please read [documentation](https://github.com/redisson/redisson/wiki/14.-Integration-with-frameworks/#1431-hibernate-cache-local-cache) for more details +Feature - __Local Cache support for Spring Cache__ Please read [documentation](https://github.com/redisson/redisson/wiki/14.-Integration-with-frameworks/#1421-spring-cache-local-cache) for more details +Feature - __`RedissonLocalCachedMapCache` object added__ Please read [documentation](https://github.com/redisson/redisson/wiki/7.-distributed-collections/#713-map-local-cache-for-expiring-entries) for more details +Feature - __`BlockingFairDeque` object added__ Please read [documentation](https://github.com/redisson/redisson/wiki/7.-distributed-collections#714-blocking-fair-deque) for more details +Feature - __`RLockReactive` object added__ +Feature - __`RReadWriteLockReactive` object added__ +Feature - __`RSemaphoreReactive` object added__ +Feature - `unlink`, `flushdbParallel`, `flushallParallel` methods added +Fixed - ContextClassLoader should be used by Redisson Codec for Tomcat session's object serialization +Fixed - Spring Cache `NullValue` does not implement Serializable +Fixed - `RLocalCachedMap` doesn't work with non-json and non-binary codecs +Fixed - Tomcat RedissonSessionManager doesn't remove session on invalidation/expiration +Fixed - `RedissonBatch` shouldn't require `reactor.fn.Supplier` dependency +Fixed - Spring Session 1.3.x compatibility (thanks to Vcgoyo) +Fixed - priority queues should acquire lock before polling the element ### 12-Jul-2017 - versions 2.9.4 and 3.4.4 released @@ -16,6 +34,7 @@ Feature - ability to submit few tasks atomically (in batch) through `RExecutorSe Feature - [Config.keepPubSubOrder](https://github.com/redisson/redisson/wiki/2.-Configuration#keeppubsuborder) setting added Improvement - make `RMapReactive` and `RMapCacheReactive` interfaces match with `RMap` and `RMapCache` Improvement - `RLexSortedSet` should extend `RSortedSet` +Fixed - connection listener is not invoked in some cases Fixed - `RMapCache` `remove`, `put`, `putIfAbsent` and `replace` methods aren't respect entry expiration Fixed - `SCAN` command should be used in `RKeys.deleteByPattern` method Fixed - `RBinaryStream` doesn't work in Redis cluster environment diff --git a/README.md b/README.md index 94bcfeaa6..da38e9ce7 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ Based on high-performance async and lock-free Java Redis client and [Netty](http | Stable Release Version | JDK Version compatibility | Release Date | | ------------- | ------------- | ------------| -| 3.4.4 | 1.8+ | 12.07.2017 | -| 2.9.4 | 1.6, 1.7, 1.8 and Android | 12.07.2017 | +| 3.5.0 | 1.8+ | 28.07.2017 | +| 2.10.0 | 1.6, 1.7, 1.8 and Android | 28.07.2017 | __NOTE__: Both version lines have same features except `CompletionStage` interface added in 3.x.x @@ -82,23 +82,23 @@ Quick start org.redisson redisson - 3.4.4 + 3.5.0 org.redisson redisson - 2.9.4 + 2.10.0 #### Gradle // JDK 1.8+ compatible - compile 'org.redisson:redisson:3.4.4' + compile 'org.redisson:redisson:3.5.0' // JDK 1.6+ compatible - compile 'org.redisson:redisson:2.9.4' + compile 'org.redisson:redisson:2.10.0' #### Java @@ -123,11 +123,11 @@ RExecutorService executor = redisson.getExecutorService("myExecutorService"); Downloads =============================== -[Redisson 3.4.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=3.4.4&e=jar), -[Redisson node 3.4.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.4.4&e=jar) +[Redisson 3.5.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=3.5.0&e=jar), +[Redisson node 3.5.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.5.0&e=jar) -[Redisson 2.9.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.9.4&e=jar), -[Redisson node 2.9.4](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.9.4&e=jar) +[Redisson 2.10.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.10.0&e=jar), +[Redisson node 2.10.0](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.10.0&e=jar) ### Supported by diff --git a/pom.xml b/pom.xml index 327cadf7c..54b568cac 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson-parent - 3.4.5-SNAPSHOT + 3.5.2-SNAPSHOT pom Redisson diff --git a/redisson-all/pom.xml b/redisson-all/pom.xml index 52e42670c..fc7be2ab9 100644 --- a/redisson-all/pom.xml +++ b/redisson-all/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-parent - 3.4.5-SNAPSHOT + 3.5.2-SNAPSHOT ../ diff --git a/redisson-tomcat/README.md b/redisson-tomcat/README.md index f2d90344c..c3bc10c6d 100644 --- a/redisson-tomcat/README.md +++ b/redisson-tomcat/README.md @@ -26,22 +26,22 @@ Usage **2** Copy two jars into `TOMCAT_BASE/lib` directory: 1. __For JDK 1.8+__ - [redisson-all-3.4.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.4.4&e=jar) + [redisson-all-3.5.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.5.0&e=jar) for Tomcat 6.x - [redisson-tomcat-6-3.4.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-6&v=3.4.4&e=jar) + [redisson-tomcat-6-3.5.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-6&v=3.5.0&e=jar) for Tomcat 7.x - [redisson-tomcat-7-3.4.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-7&v=3.4.4&e=jar) + [redisson-tomcat-7-3.5.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-7&v=3.5.0&e=jar) for Tomcat 8.x - [redisson-tomcat-8-3.4.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=3.4.4&e=jar) + [redisson-tomcat-8-3.5.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=3.5.0&e=jar) 2. __For JDK 1.6+__ - [redisson-all-2.9.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.9.4&e=jar) + [redisson-all-2.10.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.10.0&e=jar) for Tomcat 6.x - [redisson-tomcat-6-2.9.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-6&v=2.9.4&e=jar) + [redisson-tomcat-6-2.10.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-6&v=2.10.0&e=jar) for Tomcat 7.x - [redisson-tomcat-7-2.9.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-7&v=2.9.4&e=jar) + [redisson-tomcat-7-2.10.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-7&v=2.10.0&e=jar) for Tomcat 8.x - [redisson-tomcat-8-2.9.4.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=2.9.4&e=jar) + [redisson-tomcat-8-2.10.0.jar](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=2.10.0&e=jar) diff --git a/redisson-tomcat/pom.xml b/redisson-tomcat/pom.xml index 242121a82..613968db7 100644 --- a/redisson-tomcat/pom.xml +++ b/redisson-tomcat/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-parent - 3.4.5-SNAPSHOT + 3.5.2-SNAPSHOT ../ diff --git a/redisson-tomcat/redisson-tomcat-6/pom.xml b/redisson-tomcat/redisson-tomcat-6/pom.xml index 9ec0b7d31..766b64f4c 100644 --- a/redisson-tomcat/redisson-tomcat-6/pom.xml +++ b/redisson-tomcat/redisson-tomcat-6/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-tomcat - 3.4.5-SNAPSHOT + 3.5.2-SNAPSHOT ../ diff --git a/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSession.java b/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSession.java index a8981b95b..e59905564 100644 --- a/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSession.java +++ b/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSession.java @@ -19,11 +19,11 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.catalina.session.StandardSession; import org.redisson.api.RMap; +import org.redisson.tomcat.RedissonSessionManager.ReadMode; /** * Redisson Session object for Apache Tomcat @@ -36,10 +36,12 @@ public class RedissonSession extends StandardSession { private final RedissonSessionManager redissonManager; private final Map attrs; private RMap map; + private final RedissonSessionManager.ReadMode readMode; - public RedissonSession(RedissonSessionManager manager) { + public RedissonSession(RedissonSessionManager manager, RedissonSessionManager.ReadMode readMode) { super(manager); this.redissonManager = manager; + this.readMode = readMode; try { Field attr = StandardSession.class.getDeclaredField("attributes"); @@ -51,6 +53,15 @@ public class RedissonSession extends StandardSession { private static final long serialVersionUID = -2518607181636076487L; + @Override + public Object getAttribute(String name) { + if (readMode == ReadMode.REDIS) { + return map.get(name); + } + + return super.getAttribute(name); + } + @Override public void setId(String id, boolean notify) { super.setId(id, notify); @@ -163,24 +174,30 @@ public class RedissonSession extends StandardSession { } } - public void load() { - Set> entrySet = map.readAllEntrySet(); - for (Entry entry : entrySet) { - if ("session:creationTime".equals(entry.getKey())) { - creationTime = (Long) entry.getValue(); - } else if ("session:lastAccessedTime".equals(entry.getKey())) { - lastAccessedTime = (Long) entry.getValue(); - } else if ("session:thisAccessedTime".equals(entry.getKey())) { - thisAccessedTime = (Long) entry.getValue(); - } else if ("session:maxInactiveInterval".equals(entry.getKey())) { - maxInactiveInterval = (Integer) entry.getValue(); - } else if ("session:isValid".equals(entry.getKey())) { - isValid = (Boolean) entry.getValue(); - } else if ("session:isNew".equals(entry.getKey())) { - isNew = (Boolean) entry.getValue(); - } else { - setAttribute(entry.getKey(), entry.getValue(), false); - } + public void load(Map attrs) { + Long creationTime = (Long) attrs.remove("session:creationTime"); + if (creationTime != null) { + this.creationTime = creationTime; + } + Long lastAccessedTime = (Long) attrs.remove("session:lastAccessedTime"); + if (lastAccessedTime != null) { + this.lastAccessedTime = lastAccessedTime; + } + Long thisAccessedTime = (Long) attrs.remove("session:thisAccessedTime"); + if (thisAccessedTime != null) { + this.thisAccessedTime = thisAccessedTime; + } + Boolean isValid = (Boolean) attrs.remove("session:isValid"); + if (isValid != null) { + this.isValid = isValid; + } + Boolean isNew = (Boolean) attrs.remove("session:isNew"); + if (isNew != null) { + this.isNew = isNew; + } + + for (Entry entry : attrs.entrySet()) { + setAttribute(entry.getKey(), entry.getValue(), false); } } diff --git a/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSessionManager.java b/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSessionManager.java index 693f0350b..07cc4f5ea 100644 --- a/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSessionManager.java +++ b/redisson-tomcat/redisson-tomcat-6/src/main/java/org/redisson/tomcat/RedissonSessionManager.java @@ -17,6 +17,7 @@ package org.redisson.tomcat; import java.io.File; import java.io.IOException; +import java.util.Map; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -40,13 +41,24 @@ import org.redisson.config.Config; */ public class RedissonSessionManager extends ManagerBase implements Lifecycle { + public enum ReadMode {REDIS, MEMORY} + private final Log log = LogFactory.getLog(RedissonSessionManager.class); protected LifecycleSupport lifecycle = new LifecycleSupport(this); private RedissonClient redisson; private String configPath; + private ReadMode readMode = ReadMode.MEMORY; + public String getReadMode() { + return readMode.toString(); + } + + public void setReadMode(String readMode) { + this.readMode = ReadMode.valueOf(readMode); + } + public void setConfigPath(String configPath) { this.configPath = configPath; } @@ -114,9 +126,18 @@ public class RedissonSessionManager extends ManagerBase implements Lifecycle { public Session findSession(String id) throws IOException { Session result = super.findSession(id); if (result == null && id != null) { + Map attrs = getMap(id).readAllMap(); + if (attrs.isEmpty()) { + log.info("Session " + id + " can't be found"); + return null; + } + RedissonSession session = (RedissonSession) createEmptySession(); session.setId(id); - session.load(); + session.load(attrs); + + session.access(); + session.endAccess(); return session; } @@ -125,7 +146,7 @@ public class RedissonSessionManager extends ManagerBase implements Lifecycle { @Override public Session createEmptySession() { - return new RedissonSession(this); + return new RedissonSession(this, readMode); } @Override diff --git a/redisson-tomcat/redisson-tomcat-7/pom.xml b/redisson-tomcat/redisson-tomcat-7/pom.xml index 82bfe16e7..f00cb0adb 100644 --- a/redisson-tomcat/redisson-tomcat-7/pom.xml +++ b/redisson-tomcat/redisson-tomcat-7/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-tomcat - 3.4.5-SNAPSHOT + 3.5.2-SNAPSHOT ../ diff --git a/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSession.java b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSession.java index 5c90fd02e..b82004555 100644 --- a/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSession.java +++ b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSession.java @@ -19,11 +19,11 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.catalina.session.StandardSession; import org.redisson.api.RMap; +import org.redisson.tomcat.RedissonSessionManager.ReadMode; /** * Redisson Session object for Apache Tomcat @@ -36,10 +36,12 @@ public class RedissonSession extends StandardSession { private final RedissonSessionManager redissonManager; private final Map attrs; private RMap map; + private final RedissonSessionManager.ReadMode readMode; - public RedissonSession(RedissonSessionManager manager) { + public RedissonSession(RedissonSessionManager manager, RedissonSessionManager.ReadMode readMode) { super(manager); this.redissonManager = manager; + this.readMode = readMode; try { Field attr = StandardSession.class.getDeclaredField("attributes"); attrs = (Map) attr.get(this); @@ -50,6 +52,15 @@ public class RedissonSession extends StandardSession { private static final long serialVersionUID = -2518607181636076487L; + @Override + public Object getAttribute(String name) { + if (readMode == ReadMode.REDIS) { + return map.get(name); + } + + return super.getAttribute(name); + } + @Override public void setId(String id, boolean notify) { super.setId(id, notify); @@ -167,24 +178,30 @@ public class RedissonSession extends StandardSession { } } - public void load() { - Set> entrySet = map.readAllEntrySet(); - for (Entry entry : entrySet) { - if ("session:creationTime".equals(entry.getKey())) { - creationTime = (Long) entry.getValue(); - } else if ("session:lastAccessedTime".equals(entry.getKey())) { - lastAccessedTime = (Long) entry.getValue(); - } else if ("session:thisAccessedTime".equals(entry.getKey())) { - thisAccessedTime = (Long) entry.getValue(); - } else if ("session:maxInactiveInterval".equals(entry.getKey())) { - maxInactiveInterval = (Integer) entry.getValue(); - } else if ("session:isValid".equals(entry.getKey())) { - isValid = (Boolean) entry.getValue(); - } else if ("session:isNew".equals(entry.getKey())) { - isNew = (Boolean) entry.getValue(); - } else { - setAttribute(entry.getKey(), entry.getValue(), false); - } + public void load(Map attrs) { + Long creationTime = (Long) attrs.remove("session:creationTime"); + if (creationTime != null) { + this.creationTime = creationTime; + } + Long lastAccessedTime = (Long) attrs.remove("session:lastAccessedTime"); + if (lastAccessedTime != null) { + this.lastAccessedTime = lastAccessedTime; + } + Long thisAccessedTime = (Long) attrs.remove("session:thisAccessedTime"); + if (thisAccessedTime != null) { + this.thisAccessedTime = thisAccessedTime; + } + Boolean isValid = (Boolean) attrs.remove("session:isValid"); + if (isValid != null) { + this.isValid = isValid; + } + Boolean isNew = (Boolean) attrs.remove("session:isNew"); + if (isNew != null) { + this.isNew = isNew; + } + + for (Entry entry : attrs.entrySet()) { + setAttribute(entry.getKey(), entry.getValue(), false); } } diff --git a/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSessionManager.java b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSessionManager.java index 21c7cf3b3..2a53bb402 100644 --- a/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSessionManager.java +++ b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSessionManager.java @@ -17,6 +17,7 @@ package org.redisson.tomcat; import java.io.File; import java.io.IOException; +import java.util.Map; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; @@ -28,6 +29,7 @@ import org.apache.juli.logging.LogFactory; import org.redisson.Redisson; import org.redisson.api.RMap; import org.redisson.api.RedissonClient; +import org.redisson.client.codec.Codec; import org.redisson.config.Config; /** @@ -38,11 +40,22 @@ import org.redisson.config.Config; */ public class RedissonSessionManager extends ManagerBase { + public enum ReadMode {REDIS, MEMORY} + private final Log log = LogFactory.getLog(RedissonSessionManager.class); private RedissonClient redisson; private String configPath; + private ReadMode readMode = ReadMode.MEMORY; + public String getReadMode() { + return readMode.toString(); + } + + public void setReadMode(String readMode) { + this.readMode = ReadMode.valueOf(readMode); + } + public void setConfigPath(String configPath) { this.configPath = configPath; } @@ -91,9 +104,18 @@ public class RedissonSessionManager extends ManagerBase { public Session findSession(String id) throws IOException { Session result = super.findSession(id); if (result == null && id != null) { + Map attrs = getMap(id).readAllMap(); + if (attrs.isEmpty()) { + log.info("Session " + id + " can't be found"); + return null; + } + RedissonSession session = (RedissonSession) createEmptySession(); session.setId(id); - session.load(); + session.load(attrs); + + session.access(); + session.endAccess(); return session; } @@ -102,7 +124,7 @@ public class RedissonSessionManager extends ManagerBase { @Override public Session createEmptySession() { - return new RedissonSession(this); + return new RedissonSession(this, readMode); } @Override @@ -135,6 +157,15 @@ public class RedissonSessionManager extends ManagerBase { } try { + try { + Config c = new Config(config); + Codec codec = c.getCodec().getClass().getConstructor(ClassLoader.class) + .newInstance(Thread.currentThread().getContextClassLoader()); + config.setCodec(codec); + } catch (Exception e) { + throw new IllegalStateException("Unable to initialize codec with ClassLoader parameter", e); + } + redisson = Redisson.create(config); } catch (Exception e) { throw new LifecycleException(e); diff --git a/redisson-tomcat/redisson-tomcat-8/pom.xml b/redisson-tomcat/redisson-tomcat-8/pom.xml index 495f4dd7c..1da26b580 100644 --- a/redisson-tomcat/redisson-tomcat-8/pom.xml +++ b/redisson-tomcat/redisson-tomcat-8/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-tomcat - 3.4.5-SNAPSHOT + 3.5.2-SNAPSHOT ../ diff --git a/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSession.java b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSession.java index 5c90fd02e..b0132f570 100644 --- a/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSession.java +++ b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSession.java @@ -19,11 +19,11 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.catalina.session.StandardSession; import org.redisson.api.RMap; +import org.redisson.tomcat.RedissonSessionManager.ReadMode; /** * Redisson Session object for Apache Tomcat @@ -36,10 +36,13 @@ public class RedissonSession extends StandardSession { private final RedissonSessionManager redissonManager; private final Map attrs; private RMap map; + private final RedissonSessionManager.ReadMode readMode; - public RedissonSession(RedissonSessionManager manager) { + public RedissonSession(RedissonSessionManager manager, RedissonSessionManager.ReadMode readMode) { super(manager); this.redissonManager = manager; + this.readMode = readMode; + try { Field attr = StandardSession.class.getDeclaredField("attributes"); attrs = (Map) attr.get(this); @@ -50,6 +53,15 @@ public class RedissonSession extends StandardSession { private static final long serialVersionUID = -2518607181636076487L; + @Override + public Object getAttribute(String name) { + if (readMode == ReadMode.REDIS) { + return map.get(name); + } + + return super.getAttribute(name); + } + @Override public void setId(String id, boolean notify) { super.setId(id, notify); @@ -167,24 +179,30 @@ public class RedissonSession extends StandardSession { } } - public void load() { - Set> entrySet = map.readAllEntrySet(); - for (Entry entry : entrySet) { - if ("session:creationTime".equals(entry.getKey())) { - creationTime = (Long) entry.getValue(); - } else if ("session:lastAccessedTime".equals(entry.getKey())) { - lastAccessedTime = (Long) entry.getValue(); - } else if ("session:thisAccessedTime".equals(entry.getKey())) { - thisAccessedTime = (Long) entry.getValue(); - } else if ("session:maxInactiveInterval".equals(entry.getKey())) { - maxInactiveInterval = (Integer) entry.getValue(); - } else if ("session:isValid".equals(entry.getKey())) { - isValid = (Boolean) entry.getValue(); - } else if ("session:isNew".equals(entry.getKey())) { - isNew = (Boolean) entry.getValue(); - } else { - setAttribute(entry.getKey(), entry.getValue(), false); - } + public void load(Map attrs) { + Long creationTime = (Long) attrs.remove("session:creationTime"); + if (creationTime != null) { + this.creationTime = creationTime; + } + Long lastAccessedTime = (Long) attrs.remove("session:lastAccessedTime"); + if (lastAccessedTime != null) { + this.lastAccessedTime = lastAccessedTime; + } + Long thisAccessedTime = (Long) attrs.remove("session:thisAccessedTime"); + if (thisAccessedTime != null) { + this.thisAccessedTime = thisAccessedTime; + } + Boolean isValid = (Boolean) attrs.remove("session:isValid"); + if (isValid != null) { + this.isValid = isValid; + } + Boolean isNew = (Boolean) attrs.remove("session:isNew"); + if (isNew != null) { + this.isNew = isNew; + } + + for (Entry entry : attrs.entrySet()) { + setAttribute(entry.getKey(), entry.getValue(), false); } } diff --git a/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSessionManager.java b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSessionManager.java index df588284b..b16d6c075 100644 --- a/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSessionManager.java +++ b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSessionManager.java @@ -17,6 +17,7 @@ package org.redisson.tomcat; import java.io.File; import java.io.IOException; +import java.util.Map; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; @@ -27,6 +28,7 @@ import org.apache.juli.logging.LogFactory; import org.redisson.Redisson; import org.redisson.api.RMap; import org.redisson.api.RedissonClient; +import org.redisson.client.codec.Codec; import org.redisson.config.Config; /** @@ -37,11 +39,23 @@ import org.redisson.config.Config; */ public class RedissonSessionManager extends ManagerBase { + public enum ReadMode {REDIS, MEMORY} + private final Log log = LogFactory.getLog(RedissonSessionManager.class); private RedissonClient redisson; private String configPath; + private ReadMode readMode = ReadMode.MEMORY; + + public String getReadMode() { + return readMode.toString(); + } + + public void setReadMode(String readMode) { + this.readMode = ReadMode.valueOf(readMode); + } + public void setConfigPath(String configPath) { this.configPath = configPath; } @@ -90,9 +104,18 @@ public class RedissonSessionManager extends ManagerBase { public Session findSession(String id) throws IOException { Session result = super.findSession(id); if (result == null && id != null) { + Map attrs = getMap(id).readAllMap(); + if (attrs.isEmpty()) { + log.info("Session " + id + " can't be found"); + return null; + } + RedissonSession session = (RedissonSession) createEmptySession(); session.setId(id); - session.load(); + session.load(attrs); + + session.access(); + session.endAccess(); return session; } @@ -101,7 +124,7 @@ public class RedissonSessionManager extends ManagerBase { @Override public Session createEmptySession() { - return new RedissonSession(this); + return new RedissonSession(this, readMode); } @Override @@ -134,6 +157,11 @@ public class RedissonSessionManager extends ManagerBase { } try { + Config c = new Config(config); + Codec codec = c.getCodec().getClass().getConstructor(ClassLoader.class) + .newInstance(Thread.currentThread().getContextClassLoader()); + config.setCodec(codec); + redisson = Redisson.create(config); } catch (Exception e) { throw new LifecycleException(e); diff --git a/redisson/pom.xml b/redisson/pom.xml index 117df03b8..c2afce4d9 100644 --- a/redisson/pom.xml +++ b/redisson/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-parent - 3.4.5-SNAPSHOT + 3.5.2-SNAPSHOT ../ diff --git a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java index 2ca4e9a86..88f645330 100644 --- a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java +++ b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java @@ -15,6 +15,7 @@ */ package org.redisson; +import java.io.IOException; import java.io.Serializable; import java.math.BigDecimal; import java.util.AbstractCollection; @@ -60,13 +61,17 @@ import org.redisson.client.protocol.convertor.NumberConvertor; import org.redisson.client.protocol.decoder.ObjectMapEntryReplayDecoder; import org.redisson.client.protocol.decoder.ObjectMapReplayDecoder; import org.redisson.client.protocol.decoder.ObjectSetReplayDecoder; +import org.redisson.codec.JsonJacksonCodec; import org.redisson.command.CommandAsyncExecutor; import org.redisson.eviction.EvictionScheduler; import org.redisson.misc.Hash; import org.redisson.misc.RPromise; +import org.redisson.misc.RedissonObjectFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.internal.ThreadLocalRandom; @@ -178,6 +183,7 @@ public class RedissonLocalCachedMap extends RedissonMap implements R private int invalidationListenerId; private int invalidationStatusListenerId; private volatile long lastInvalidate; + private Codec topicCodec; protected RedissonLocalCachedMap(CommandAsyncExecutor commandExecutor, String name, LocalCachedMapOptions options, EvictionScheduler evictionScheduler, RedissonClient redisson) { super(commandExecutor, name, redisson, options); @@ -207,7 +213,28 @@ public class RedissonLocalCachedMap extends RedissonMap implements R } private void addListeners(String name, final LocalCachedMapOptions options, final RedissonClient redisson) { - invalidationTopic = new RedissonTopic(commandExecutor, suffixName(name, "topic")); + topicCodec = codec; + + LocalCachedMapInvalidate msg = new LocalCachedMapInvalidate(new byte[] {1, 2, 3}, new byte[] {4, 5, 6}); + ByteBuf buf = null; + try { + byte[] s = topicCodec.getValueEncoder().encode(msg); + buf = Unpooled.wrappedBuffer(s); + msg = (LocalCachedMapInvalidate) topicCodec.getValueDecoder().decode(buf, null); + if (!Arrays.equals(msg.getExcludedId(), new byte[] {1, 2, 3}) + || !Arrays.equals(msg.getKeyHashes()[0], new byte[] {4, 5, 6})) { + throw new IllegalArgumentException(); + } + } catch (Exception e) { + log.warn("Defined {} codec doesn't encode service messages properly. Default JsonJacksonCodec used to encode messages!", topicCodec.getClass()); + topicCodec = new JsonJacksonCodec(); + } finally { + if (buf != null) { + buf.release(); + } + } + + invalidationTopic = new RedissonTopic(topicCodec, commandExecutor, suffixName(name, "topic")); if (options.getInvalidationPolicy() == InvalidationPolicy.NONE) { return; @@ -1345,4 +1372,19 @@ public class RedissonLocalCachedMap extends RedissonMap implements R return future; } + protected byte[] encode(Object value) { + if (commandExecutor.isRedissonReferenceSupportEnabled()) { + RedissonReference reference = RedissonObjectFactory.toReference(commandExecutor.getConnectionManager().getCfg(), value); + if (reference != null) { + value = reference; + } + } + + try { + return topicCodec.getValueEncoder().encode(value); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + } diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index 626f91637..6f9b9910f 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -1576,18 +1576,21 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_VOID, - "for i, value in ipairs(ARGV) do " - + "if i % 2 == 0 then " + "for i=1, #ARGV, 5000 do " + + "redis.call('hmset', KEYS[1], unpack(ARGV, i, math.min(i+4999, table.getn(ARGV)))) " + + "end; " + + + "for i, value in ipairs(ARGV) do " + + "if i % 2 == 0 then " + "local val = struct.pack('dLc0', 0, string.len(value), value); " + "ARGV[i] = val; " + "local key = ARGV[i-1];" + "local msg = struct.pack('Lc0Lc0', string.len(key), key, string.len(value), value); " + "redis.call('publish', KEYS[2], msg); " - + "end;" + "end;" - + "return redis.call('hmset', KEYS[1], unpack(ARGV)); ", - Arrays.asList(getName(), getCreatedChannelName()), params.toArray()); + + "end;", + Arrays.asList(getName(), getCreatedChannelName()), params.toArray()); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonMultiLock.java b/redisson/src/main/java/org/redisson/RedissonMultiLock.java index c5007e988..32b980ab2 100644 --- a/redisson/src/main/java/org/redisson/RedissonMultiLock.java +++ b/redisson/src/main/java/org/redisson/RedissonMultiLock.java @@ -20,16 +20,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.ListIterator; -import java.util.Queue; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import org.redisson.api.RFuture; import org.redisson.api.RLock; -import io.netty.util.concurrent.Future; import io.netty.util.internal.ThreadLocalRandom; /** @@ -226,12 +223,4 @@ public class RedissonMultiLock implements Lock { throw new UnsupportedOperationException(); } - protected boolean isLockFailed(Future future) { - return !future.isSuccess(); - } - - protected boolean isAllLocksAcquired(AtomicReference lockedLockHolder, AtomicReference failed, Queue lockedLocks) { - return lockedLockHolder.get() == null && failed.get() == null; - } - } diff --git a/redisson/src/main/java/org/redisson/RedissonReactive.java b/redisson/src/main/java/org/redisson/RedissonReactive.java index 8f777a8a8..19596e3f9 100644 --- a/redisson/src/main/java/org/redisson/RedissonReactive.java +++ b/redisson/src/main/java/org/redisson/RedissonReactive.java @@ -43,6 +43,7 @@ import org.redisson.api.RQueueReactive; import org.redisson.api.RReadWriteLockReactive; import org.redisson.api.RScoredSortedSetReactive; import org.redisson.api.RScriptReactive; +import org.redisson.api.RSemaphoreReactive; import org.redisson.api.RSetCacheReactive; import org.redisson.api.RSetReactive; import org.redisson.api.RTopicReactive; @@ -55,6 +56,7 @@ import org.redisson.config.Config; import org.redisson.config.ConfigSupport; import org.redisson.connection.ConnectionManager; import org.redisson.eviction.EvictionScheduler; +import org.redisson.pubsub.SemaphorePubSub; import org.redisson.reactive.RedissonAtomicLongReactive; import org.redisson.reactive.RedissonBatchReactive; import org.redisson.reactive.RedissonBitSetReactive; @@ -73,6 +75,7 @@ import org.redisson.reactive.RedissonQueueReactive; import org.redisson.reactive.RedissonReadWriteLockReactive; import org.redisson.reactive.RedissonScoredSortedSetReactive; import org.redisson.reactive.RedissonScriptReactive; +import org.redisson.reactive.RedissonSemaphoreReactive; import org.redisson.reactive.RedissonSetCacheReactive; import org.redisson.reactive.RedissonSetReactive; import org.redisson.reactive.RedissonTopicReactive; @@ -91,7 +94,9 @@ public class RedissonReactive implements RedissonReactiveClient { protected final ConnectionManager connectionManager; protected final Config config; protected final CodecProvider codecProvider; + protected final UUID id = UUID.randomUUID(); + protected final SemaphorePubSub semaphorePubSub = new SemaphorePubSub(); protected RedissonReactive(Config config) { this.config = config; @@ -103,6 +108,11 @@ public class RedissonReactive implements RedissonReactiveClient { codecProvider = config.getCodecProvider(); } + @Override + public RSemaphoreReactive getSemaphore(String name) { + return new RedissonSemaphoreReactive(commandExecutor, name, semaphorePubSub); + } + @Override public RReadWriteLockReactive getReadWriteLock(String name) { return new RedissonReadWriteLockReactive(commandExecutor, name, id); diff --git a/redisson/src/main/java/org/redisson/RedissonRedLock.java b/redisson/src/main/java/org/redisson/RedissonRedLock.java index 86fd7feaa..4b6aa4689 100644 --- a/redisson/src/main/java/org/redisson/RedissonRedLock.java +++ b/redisson/src/main/java/org/redisson/RedissonRedLock.java @@ -16,13 +16,9 @@ package org.redisson; import java.util.List; -import java.util.Queue; -import java.util.concurrent.atomic.AtomicReference; import org.redisson.api.RLock; -import io.netty.util.concurrent.Future; - /** * RedLock locking algorithm implementation for multiple locks. * It manages all locks as one. @@ -58,14 +54,4 @@ public class RedissonRedLock extends RedissonMultiLock { unlockInner(locks); } - @Override - protected boolean isLockFailed(Future future) { - return false; - } - - @Override - protected boolean isAllLocksAcquired(AtomicReference lockedLockHolder, AtomicReference failed, Queue lockedLocks) { - return (lockedLockHolder.get() == null && failed.get() == null) || lockedLocks.size() >= minLocksAmount(locks); - } - } diff --git a/redisson/src/main/java/org/redisson/RedissonSemaphore.java b/redisson/src/main/java/org/redisson/RedissonSemaphore.java index 14cbd956c..bea274380 100644 --- a/redisson/src/main/java/org/redisson/RedissonSemaphore.java +++ b/redisson/src/main/java/org/redisson/RedissonSemaphore.java @@ -27,7 +27,7 @@ import org.redisson.api.RSemaphore; import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.RedisCommands; -import org.redisson.command.CommandExecutor; +import org.redisson.command.CommandAsyncExecutor; import org.redisson.misc.RPromise; import org.redisson.pubsub.SemaphorePubSub; @@ -48,9 +48,9 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore { private final SemaphorePubSub semaphorePubSub; - final CommandExecutor commandExecutor; + final CommandAsyncExecutor commandExecutor; - protected RedissonSemaphore(CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub) { + public RedissonSemaphore(CommandAsyncExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub) { super(commandExecutor, name); this.commandExecutor = commandExecutor; this.semaphorePubSub = semaphorePubSub; @@ -478,7 +478,7 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore { @Override public int drainPermits() { - Long res = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG, + RFuture future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG, "local value = redis.call('get', KEYS[1]); " + "if (value == false or value == 0) then " + "return 0; " + @@ -486,12 +486,14 @@ public class RedissonSemaphore extends RedissonExpirable implements RSemaphore { "redis.call('set', KEYS[1], 0); " + "return value;", Collections.singletonList(getName())); + Long res = get(future); return res.intValue(); } @Override public int availablePermits() { - Long res = commandExecutor.write(getName(), LongCodec.INSTANCE, RedisCommands.GET_LONG, getName()); + RFuture future = commandExecutor.writeAsync(getName(), LongCodec.INSTANCE, RedisCommands.GET_LONG, getName()); + Long res = get(future); return res.intValue(); } diff --git a/redisson/src/main/java/org/redisson/api/RKeys.java b/redisson/src/main/java/org/redisson/api/RKeys.java index f3445184b..56bd641b6 100644 --- a/redisson/src/main/java/org/redisson/api/RKeys.java +++ b/redisson/src/main/java/org/redisson/api/RKeys.java @@ -236,7 +236,7 @@ public interface RKeys extends RKeysAsync { *

* Requires Redis 4.0+ * - * @param keys + * @param keys of objects * @return number of removed keys */ long unlink(String ... keys); diff --git a/redisson/src/main/java/org/redisson/api/RSemaphoreReactive.java b/redisson/src/main/java/org/redisson/api/RSemaphoreReactive.java new file mode 100644 index 000000000..f9deb34e6 --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/RSemaphoreReactive.java @@ -0,0 +1,179 @@ +/** + * Copyright 2016 Nikita Koksharov + * + * 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.redisson.api; + +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Publisher; + +/** + * + * @author Nikita Koksharov + * + */ +public interface RSemaphoreReactive extends RExpirableReactive { + + /** + * Acquires a permit only if one is available at the + * time of invocation. + * + *

Acquires a permit, if one is available and returns immediately, + * with the value {@code true}, + * reducing the number of available permits by one. + * + *

If no permit is available then this method will return + * immediately with the value {@code false}. + * + * @return {@code true} if a permit was acquired and {@code false} + * otherwise + */ + Publisher tryAcquire(); + + /** + * Acquires the given number of permits only if all are available at the + * time of invocation. + * + *

Acquires a permits, if all are available and returns immediately, + * with the value {@code true}, + * reducing the number of available permits by given number of permits. + * + *

If no permits are available then this method will return + * immediately with the value {@code false}. + * + * @param permits the number of permits to acquire + * @return {@code true} if a permit was acquired and {@code false} + * otherwise + */ + Publisher tryAcquire(int permits); + + /** + * Acquires a permit from this semaphore. + * + *

Acquires a permit, if one is available and returns immediately, + * reducing the number of available permits by one. + * + * @return void + * + */ + Publisher acquire(); + + /** + * Acquires the given number of permits, if they are available, + * and returns immediately, reducing the number of available permits + * by the given amount. + * + * @param permits the number of permits to acquire + * @throws IllegalArgumentException if {@code permits} is negative + * @return void + */ + Publisher acquire(int permits); + + /** + * Releases a permit, returning it to the semaphore. + * + *

Releases a permit, increasing the number of available permits by + * one. If any threads of Redisson client are trying to acquire a permit, + * then one is selected and given the permit that was just released. + * + *

There is no requirement that a thread that releases a permit must + * have acquired that permit by calling {@link #acquire()}. + * Correct usage of a semaphore is established by programming convention + * in the application. + * + * @return void + */ + Publisher release(); + + /** + * Releases the given number of permits, returning them to the semaphore. + * + *

Releases the given number of permits, increasing the number of available permits by + * the given number of permits. If any threads of Redisson client are trying to + * acquire a permits, then next threads is selected and tries to acquire the permits that was just released. + * + *

There is no requirement that a thread that releases a permits must + * have acquired that permit by calling {@link #acquire()}. + * Correct usage of a semaphore is established by programming convention + * in the application. + * + * @param permits amount + * @return void + */ + Publisher release(int permits); + + /** + * Sets number of permits. + * + * @param permits - number of permits + * @return true if permits has been set successfully, otherwise false. + */ + Publisher trySetPermits(int permits); + + /** + *

Acquires a permit, if one is available and returns immediately, + * with the value {@code true}, + * reducing the number of available permits by one. + * + *

If a permit is acquired then the value {@code true} is returned. + * + *

If the specified waiting time elapses then the value {@code false} + * is returned. If the time is less than or equal to zero, the method + * will not wait at all. + * + * @param waitTime the maximum time to wait for a permit + * @param unit the time unit of the {@code timeout} argument + * @return {@code true} if a permit was acquired and {@code false} + * if the waiting time elapsed before a permit was acquired + */ + Publisher tryAcquire(long waitTime, TimeUnit unit); + + /** + * Acquires the given number of permits only if all are available + * within the given waiting time. + * + *

Acquires a permits, if all are available and returns immediately, + * with the value {@code true}, + * reducing the number of available permits by one. + * + *

If a permits is acquired then the value {@code true} is returned. + * + *

If the specified waiting time elapses then the value {@code false} + * is returned. If the time is less than or equal to zero, the method + * will not wait at all. + * + * @param permits amount + * @param waitTime the maximum time to wait for a available permits + * @param unit the time unit of the {@code timeout} argument + * @return {@code true} if a permit was acquired and {@code false} + * if the waiting time elapsed before a permit was acquired + */ + Publisher tryAcquire(int permits, long waitTime, TimeUnit unit); + + /** + * Shrinks the number of available permits by the indicated + * reduction. This method can be useful in subclasses that use + * semaphores to track resources that become unavailable. This + * method differs from {@link #acquire()} in that it does not block + * waiting for permits to become available. + * + * @param permits - reduction the number of permits to remove + * @return void + * @throws IllegalArgumentException if {@code reduction} is negative + */ + Publisher reducePermits(int permits); + + +} diff --git a/redisson/src/main/java/org/redisson/api/RedissonReactiveClient.java b/redisson/src/main/java/org/redisson/api/RedissonReactiveClient.java index 1105eb7a4..31689bf14 100644 --- a/redisson/src/main/java/org/redisson/api/RedissonReactiveClient.java +++ b/redisson/src/main/java/org/redisson/api/RedissonReactiveClient.java @@ -30,6 +30,14 @@ import org.redisson.config.Config; */ public interface RedissonReactiveClient { + /** + * Returns semaphore instance by name + * + * @param name - name of object + * @return Semaphore object + */ + RSemaphoreReactive getSemaphore(String name); + /** * Returns readWriteLock instance by name. * diff --git a/redisson/src/main/java/org/redisson/client/RedisClient.java b/redisson/src/main/java/org/redisson/client/RedisClient.java index e7e8a35ff..ce0e0b9d8 100644 --- a/redisson/src/main/java/org/redisson/client/RedisClient.java +++ b/redisson/src/main/java/org/redisson/client/RedisClient.java @@ -175,6 +175,9 @@ public class RedisClient { this.commandTimeout = config.getCommandTimeout(); } + public String getIpAddr() { + return addr.getAddress().getHostAddress() + ":" + addr.getPort(); + } public InetSocketAddress getAddr() { return addr; diff --git a/redisson/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java b/redisson/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java index 17dd7bbdb..652b75261 100644 --- a/redisson/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java +++ b/redisson/src/main/java/org/redisson/client/handler/ConnectionWatchdog.java @@ -39,6 +39,11 @@ import io.netty.util.TimerTask; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +/** + * + * @author Nikita Koksharov + * + */ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { private final Logger log = LoggerFactory.getLogger(getClass()); @@ -82,12 +87,16 @@ public class ConnectionWatchdog extends ChannelInboundHandlerAdapter { return; } - timer.newTimeout(new TimerTask() { - @Override - public void run(Timeout timeout) throws Exception { - tryReconnect(connection, Math.min(BACKOFF_CAP, attempts + 1)); - } - }, timeout, TimeUnit.MILLISECONDS); + try { + timer.newTimeout(new TimerTask() { + @Override + public void run(Timeout timeout) throws Exception { + tryReconnect(connection, Math.min(BACKOFF_CAP, attempts + 1)); + } + }, timeout, TimeUnit.MILLISECONDS); + } catch (IllegalStateException e) { + // skip + } } private void tryReconnect(final RedisConnection connection, final int nextAttempt) { diff --git a/redisson/src/main/java/org/redisson/cluster/ClusterConnectionManager.java b/redisson/src/main/java/org/redisson/cluster/ClusterConnectionManager.java index e18b37d04..bb3e7d352 100644 --- a/redisson/src/main/java/org/redisson/cluster/ClusterConnectionManager.java +++ b/redisson/src/main/java/org/redisson/cluster/ClusterConnectionManager.java @@ -76,13 +76,12 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { private ScheduledFuture monitorFuture; private volatile URI lastClusterNode; - + public ClusterConnectionManager(ClusterServersConfig cfg, Config config) { super(config); this.config = create(cfg); initTimer(this.config); - init(this.config); Throwable lastException = null; List failedMasters = new ArrayList(); @@ -92,13 +91,11 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { RedisConnection connection = connectionFuture.syncUninterruptibly().getNow(); List nodes = connection.sync(RedisCommands.CLUSTER_NODES); - if (log.isDebugEnabled()) { - StringBuilder nodesValue = new StringBuilder(); - for (ClusterNodeInfo clusterNodeInfo : nodes) { - nodesValue.append(clusterNodeInfo.getNodeInfo()).append("\n"); - } - log.debug("cluster nodes state from {}:\n{}", connection.getRedisClient().getAddr(), nodesValue); + StringBuilder nodesValue = new StringBuilder(); + for (ClusterNodeInfo clusterNodeInfo : nodes) { + nodesValue.append(clusterNodeInfo.getNodeInfo()).append("\n"); } + log.info("Redis cluster nodes configuration got from {}:\n{}", connection.getRedisClient().getAddr(), nodesValue); lastClusterNode = addr; @@ -185,23 +182,19 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { } RedisConnection connection = future.getNow(); - if (connection.isActive()) { - nodeConnections.put(addr, connection); - result.trySuccess(connection); - } else { - connection.closeAsync(); - result.tryFailure(new RedisException("Connection to " + connection.getRedisClient().getAddr() + " is not active!")); - } - } - }); + if (connection.isActive()) { + nodeConnections.put(addr, connection); + result.trySuccess(connection); + } else { + connection.closeAsync(); + result.tryFailure(new RedisException("Connection to " + connection.getRedisClient().getAddr() + " is not active!")); + } + } + }); return result; } - @Override - protected void initEntry(MasterSlaveServersConfig config) { - } - private RFuture>> addMasterEntry(final ClusterPartition partition, final ClusterServersConfig cfg) { if (partition.isMasterFail()) { RedisException e = new RedisException("Failed to add master: " + @@ -253,7 +246,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { final MasterSlaveEntry e; List> futures = new ArrayList>(); - if (config.getReadMode() == ReadMode.MASTER) { + if (config.checkSkipSlavesInit()) { e = new SingleEntry(partition.getSlotRanges(), ClusterConnectionManager.this, config); } else { config.setSlaveAddresses(partition.getSlaveAddresses()); @@ -426,7 +419,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { aliveSlaves.removeAll(newPart.getFailedSlaveAddresses()); for (URI uri : aliveSlaves) { currentPart.removeFailedSlaveAddress(uri); - if (entry.slaveUp(uri.getHost(), uri.getPort(), FreezeReason.MANAGER)) { + if (entry.slaveUp(uri, FreezeReason.MANAGER)) { log.info("slave: {} has up for slot ranges: {}", uri, currentPart.getSlotRanges()); } } @@ -435,7 +428,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { failedSlaves.removeAll(currentPart.getFailedSlaveAddresses()); for (URI uri : failedSlaves) { currentPart.addFailedSlaveAddress(uri); - if (entry.slaveDown(uri.getHost(), uri.getPort(), FreezeReason.MANAGER)) { + if (entry.slaveDown(uri, FreezeReason.MANAGER)) { log.warn("slave: {} has down for slot ranges: {}", uri, currentPart.getSlotRanges()); } } @@ -448,7 +441,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { for (URI uri : removedSlaves) { currentPart.removeSlaveAddress(uri); - if (entry.slaveDown(uri.getHost(), uri.getPort(), FreezeReason.MANAGER)) { + if (entry.slaveDown(uri, FreezeReason.MANAGER)) { log.info("slave {} removed for slot ranges: {}", uri, currentPart.getSlotRanges()); } } @@ -466,7 +459,7 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { } currentPart.addSlaveAddress(uri); - entry.slaveUp(uri.getHost(), uri.getPort(), FreezeReason.MANAGER); + entry.slaveUp(uri, FreezeReason.MANAGER); log.info("slave: {} added for slot ranges: {}", uri, currentPart.getSlotRanges()); } }); @@ -510,8 +503,6 @@ public class ClusterConnectionManager extends MasterSlaveConnectionManager { ClusterPartition newMasterPart = find(newPartitions, slot); // does partition has a new master? if (!newMasterPart.getMasterAddress().equals(currentPart.getMasterAddress())) { - log.info("changing master from {} to {} for {}", - currentPart.getMasterAddress(), newMasterPart.getMasterAddress(), slot); URI newUri = newMasterPart.getMasterAddress(); URI oldUri = currentPart.getMasterAddress(); diff --git a/redisson/src/main/java/org/redisson/codec/CompositeCodec.java b/redisson/src/main/java/org/redisson/codec/CompositeCodec.java new file mode 100644 index 000000000..51750c5b5 --- /dev/null +++ b/redisson/src/main/java/org/redisson/codec/CompositeCodec.java @@ -0,0 +1,74 @@ +/** + * Copyright 2016 Nikita Koksharov + * + * 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.redisson.codec; + +import org.redisson.client.codec.Codec; +import org.redisson.client.protocol.Decoder; +import org.redisson.client.protocol.Encoder; + +/** + * + * @author Nikita Koksharov + * + */ +public class CompositeCodec implements Codec { + + private final Codec mapKeyCodec; + private final Codec mapValueCodec; + private final Codec valueCodec; + + public CompositeCodec(Codec mapKeyCodec, Codec mapValueCodec) { + this(mapKeyCodec, mapValueCodec, null); + } + + public CompositeCodec(Codec mapKeyCodec, Codec mapValueCodec, Codec valueCodec) { + super(); + this.mapKeyCodec = mapKeyCodec; + this.mapValueCodec = mapValueCodec; + this.valueCodec = valueCodec; + } + + @Override + public Decoder getMapValueDecoder() { + return mapValueCodec.getMapKeyDecoder(); + } + + @Override + public Encoder getMapValueEncoder() { + return mapValueCodec.getMapValueEncoder(); + } + + @Override + public Decoder getMapKeyDecoder() { + return mapKeyCodec.getMapKeyDecoder(); + } + + @Override + public Encoder getMapKeyEncoder() { + return mapKeyCodec.getMapKeyEncoder(); + } + + @Override + public Decoder getValueDecoder() { + return valueCodec.getValueDecoder(); + } + + @Override + public Encoder getValueEncoder() { + return valueCodec.getValueEncoder(); + } + +} diff --git a/redisson/src/main/java/org/redisson/config/BaseMasterSlaveServersConfig.java b/redisson/src/main/java/org/redisson/config/BaseMasterSlaveServersConfig.java index bdc5d7312..de10668a5 100644 --- a/redisson/src/main/java/org/redisson/config/BaseMasterSlaveServersConfig.java +++ b/redisson/src/main/java/org/redisson/config/BaseMasterSlaveServersConfig.java @@ -65,6 +65,8 @@ public class BaseMasterSlaveServersConfig + * Applications must ensure the JVM DNS cache TTL is low enough to support this.

+ * Set -1 to disable. + *

+ * Default is 5000. + * + * @param dnsMonitoringInterval time + * @return config + */ + public T setDnsMonitoringInterval(long dnsMonitoringInterval) { + this.dnsMonitoringInterval = dnsMonitoringInterval; + return (T) this; + } + public long getDnsMonitoringInterval() { + return dnsMonitoringInterval; + } + } diff --git a/redisson/src/main/java/org/redisson/connection/ClientConnectionsEntry.java b/redisson/src/main/java/org/redisson/connection/ClientConnectionsEntry.java index c148c35e8..97b83d728 100644 --- a/redisson/src/main/java/org/redisson/connection/ClientConnectionsEntry.java +++ b/redisson/src/main/java/org/redisson/connection/ClientConnectionsEntry.java @@ -32,6 +32,11 @@ import org.slf4j.LoggerFactory; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +/** + * + * @author Nikita Koksharov + * + */ public class ClientConnectionsEntry { final Logger log = LoggerFactory.getLogger(getClass()); @@ -49,17 +54,17 @@ public class ClientConnectionsEntry { private FreezeReason freezeReason; final RedisClient client; - private final NodeType nodeType; + private NodeType nodeType; private ConnectionManager connectionManager; private final AtomicInteger failedAttempts = new AtomicInteger(); public ClientConnectionsEntry(RedisClient client, int poolMinSize, int poolMaxSize, int subscribePoolMinSize, int subscribePoolMaxSize, - ConnectionManager connectionManager, NodeType serverMode) { + ConnectionManager connectionManager, NodeType nodeType) { this.client = client; this.freeConnectionsCounter = new AsyncSemaphore(poolMaxSize); this.connectionManager = connectionManager; - this.nodeType = serverMode; + this.nodeType = nodeType; this.freeSubscribeConnectionsCounter = new AsyncSemaphore(subscribePoolMaxSize); if (subscribePoolMaxSize > 0) { @@ -67,7 +72,10 @@ public class ClientConnectionsEntry { } connectionManager.getConnectionWatcher().add(poolMinSize, poolMaxSize, freeConnections, freeConnectionsCounter); } - + + public void setNodeType(NodeType nodeType) { + this.nodeType = nodeType; + } public NodeType getNodeType() { return nodeType; } diff --git a/redisson/src/main/java/org/redisson/connection/DNSMonitor.java b/redisson/src/main/java/org/redisson/connection/DNSMonitor.java new file mode 100644 index 000000000..6e513a0ed --- /dev/null +++ b/redisson/src/main/java/org/redisson/connection/DNSMonitor.java @@ -0,0 +1,139 @@ +/** + * Copyright 2016 Nikita Koksharov + * + * 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.redisson.connection; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.RedisConnectionException; +import org.redisson.connection.ClientConnectionsEntry.FreezeReason; +import org.redisson.misc.URIBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.ScheduledFuture; + +/** + * DNS changes monitor. + * + * @author Nikita Koksharov + * + */ +public class DNSMonitor { + + private static final Logger log = LoggerFactory.getLogger(DNSMonitor.class); + + private ScheduledFuture dnsMonitorFuture; + + private ConnectionManager connectionManager; + + private final Map masters = new HashMap(); + private final Map slaves = new HashMap(); + + private long dnsMonitoringInterval; + + public DNSMonitor(ConnectionManager connectionManager, Set masterHosts, Set slaveHosts, long dnsMonitoringInterval) { + for (URI host : masterHosts) { + try { + masters.put(host, InetAddress.getByName(host.getHost())); + } catch (UnknownHostException e) { + throw new RedisConnectionException("Unknown host: " + host.getHost(), e); + } + } + for (URI host : slaveHosts) { + try { + slaves.put(host, InetAddress.getByName(host.getHost())); + } catch (UnknownHostException e) { + throw new RedisConnectionException("Unknown host: " + host.getHost(), e); + } + } + this.connectionManager = connectionManager; + this.dnsMonitoringInterval = dnsMonitoringInterval; + } + + public void start() { + monitorDnsChange(); + log.debug("DNS monitoring enabled; Current masters: {}, slaves: {}", masters, slaves); + } + + public void stop() { + if (dnsMonitorFuture != null) { + dnsMonitorFuture.cancel(true); + } + } + + private void monitorDnsChange() { + dnsMonitorFuture = GlobalEventExecutor.INSTANCE.schedule(new Runnable() { + @Override + public void run() { + // As InetAddress.getByName call is blocking. Method should be run in dedicated thread + connectionManager.getExecutor().execute(new Runnable() { + @Override + public void run() { + try { + for (Entry entry : masters.entrySet()) { + InetAddress master = entry.getValue(); + InetAddress now = InetAddress.getByName(entry.getKey().getHost()); + if (!now.getHostAddress().equals(master.getHostAddress())) { + log.info("Detected DNS change. {} has changed from {} to {}", entry.getKey().getHost(), master.getHostAddress(), now.getHostAddress()); + for (MasterSlaveEntry entrySet : connectionManager.getEntrySet()) { + if (entrySet.getClient().getAddr().getHostName().equals(entry.getKey().getHost()) + && entrySet.getClient().getAddr().getPort() == entry.getKey().getPort()) { + entrySet.changeMaster(entry.getKey()); + } + } + masters.put(entry.getKey(), now); + log.info("Master {} has been changed", entry.getKey().getHost()); + } + } + + for (Entry entry : slaves.entrySet()) { + InetAddress slave = entry.getValue(); + InetAddress updatedSlave = InetAddress.getByName(entry.getKey().getHost()); + if (!updatedSlave.getHostAddress().equals(slave.getHostAddress())) { + log.info("Detected DNS change. {} has changed from {} to {}", entry.getKey().getHost(), slave.getHostAddress(), updatedSlave.getHostAddress()); + for (MasterSlaveEntry masterSlaveEntry : connectionManager.getEntrySet()) { + URI uri = URIBuilder.create("redis://" + slave.getHostAddress() + ":" + entry.getKey().getPort()); + if (masterSlaveEntry.slaveDown(uri, FreezeReason.MANAGER)) { + masterSlaveEntry.slaveUp(entry.getKey(), FreezeReason.MANAGER); + } + } + slaves.put(entry.getKey(), updatedSlave); + log.info("Slave {} has been changed", entry.getKey().getHost()); + } + } + + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + monitorDnsChange(); + } + } + }); + } + + }, dnsMonitoringInterval, TimeUnit.MILLISECONDS); + } + + +} diff --git a/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index 145c56a1f..2db3203fc 100644 --- a/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -51,8 +51,6 @@ import org.redisson.command.CommandSyncService; import org.redisson.config.BaseMasterSlaveServersConfig; import org.redisson.config.Config; import org.redisson.config.MasterSlaveServersConfig; -import org.redisson.config.ReadMode; -import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.misc.InfinitySemaphoreLatch; import org.redisson.misc.RPromise; import org.redisson.misc.RedissonPromise; @@ -128,6 +126,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager { protected final Queue freePubSubConnections = new ConcurrentLinkedQueue(); + protected DNSMonitor dnsMonitor; + protected MasterSlaveServersConfig config; private final Map entries = PlatformDependent.newConcurrentHashMap(); @@ -161,7 +161,8 @@ public class MasterSlaveConnectionManager implements ConnectionManager { public MasterSlaveConnectionManager(MasterSlaveServersConfig cfg, Config config) { this(config); initTimer(cfg); - init(cfg); + this.config = cfg; + initSingleEntry(); } public MasterSlaveConnectionManager(Config cfg) { @@ -237,19 +238,6 @@ public class MasterSlaveConnectionManager implements ConnectionManager { return new HashSet(entries.values()); } - protected void init(MasterSlaveServersConfig config) { - this.config = config; - - connectionWatcher = new IdleConnectionWatcher(this, config); - - try { - initEntry(config); - } catch (RuntimeException e) { - stopThreads(); - throw e; - } - } - protected void initTimer(MasterSlaveServersConfig config) { int[] timeouts = new int[]{config.getRetryInterval(), config.getTimeout(), config.getReconnectionTimeout()}; Arrays.sort(timeouts); @@ -273,26 +261,38 @@ public class MasterSlaveConnectionManager implements ConnectionManager { throw new IllegalStateException(e); } + connectionWatcher = new IdleConnectionWatcher(this, config); } - protected void initEntry(MasterSlaveServersConfig config) { - HashSet slots = new HashSet(); - slots.add(singleSlotRange); - - MasterSlaveEntry entry; - if (config.getReadMode() == ReadMode.MASTER) { - entry = new SingleEntry(slots, this, config); - RFuture f = entry.setupMasterEntry(config.getMasterAddress()); - f.syncUninterruptibly(); - } else { - entry = createMasterSlaveEntry(config, slots); - } - - for (int slot = singleSlotRange.getStartSlot(); slot < singleSlotRange.getEndSlot() + 1; slot++) { - addEntry(slot, entry); + protected void initSingleEntry() { + try { + HashSet slots = new HashSet(); + slots.add(singleSlotRange); + + MasterSlaveEntry entry; + if (config.checkSkipSlavesInit()) { + entry = new SingleEntry(slots, this, config); + RFuture f = entry.setupMasterEntry(config.getMasterAddress()); + f.syncUninterruptibly(); + } else { + entry = createMasterSlaveEntry(config, slots); + } + + for (int slot = singleSlotRange.getStartSlot(); slot < singleSlotRange.getEndSlot() + 1; slot++) { + addEntry(slot, entry); + } + + if (config.getDnsMonitoringInterval() != -1) { + dnsMonitor = new DNSMonitor(this, Collections.singleton(config.getMasterAddress()), + config.getSlaveAddresses(), config.getDnsMonitoringInterval()); + dnsMonitor.start(); + } + } catch (RuntimeException e) { + stopThreads(); + throw e; } } - + protected MasterSlaveEntry createMasterSlaveEntry(MasterSlaveServersConfig config, HashSet slots) { MasterSlaveEntry entry = new MasterSlaveEntry(slots, this, config); @@ -349,7 +349,9 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdownAsync(RedisClient client) { - clientEntries.remove(client); + if (clientEntries.remove(client) == null) { + log.error("Can't find client {}", client); + } client.shutdownAsync(); } @@ -683,10 +685,6 @@ public class MasterSlaveConnectionManager implements ConnectionManager { return entries.get(slot); } - protected void slaveDown(ClusterSlotRange slotRange, String host, int port, FreezeReason freezeReason) { - getEntry(slotRange.getStartSlot()).slaveDown(host, port, freezeReason); - } - protected void changeMaster(int slot, URI address) { getEntry(slot).changeMaster(address); } @@ -779,6 +777,10 @@ public class MasterSlaveConnectionManager implements ConnectionManager { @Override public void shutdown(long quietPeriod, long timeout, TimeUnit unit) { + if (dnsMonitor != null) { + dnsMonitor.stop(); + } + shutdownLatch.close(); shutdownPromise.trySuccess(true); shutdownLatch.awaitUninterruptibly(); diff --git a/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java b/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java index c0d5b7444..1314c524c 100644 --- a/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java +++ b/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java @@ -42,6 +42,7 @@ import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.connection.balancer.LoadBalancerManager; import org.redisson.connection.pool.MasterConnectionPool; import org.redisson.connection.pool.MasterPubSubConnectionPool; +import org.redisson.misc.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,10 +86,14 @@ public class MasterSlaveEntry { writeConnectionHolder = new MasterConnectionPool(config, connectionManager, this); pubSubConnectionHolder = new MasterPubSubConnectionPool(config, connectionManager, this); } + + public MasterSlaveServersConfig getConfig() { + return config; + } public List> initSlaveBalancer(Collection disconnectedNodes) { boolean freezeMasterAsSlave = !config.getSlaveAddresses().isEmpty() - && config.getReadMode() == ReadMode.SLAVE + && !config.checkSkipSlavesInit() && disconnectedNodes.size() < config.getSlaveAddresses().size(); List> result = new LinkedList>(); @@ -120,17 +125,8 @@ public class MasterSlaveEntry { return writeConnectionHolder.add(masterEntry); } - private boolean slaveDown(ClientConnectionsEntry entry, FreezeReason freezeReason) { - ClientConnectionsEntry e = slaveBalancer.freeze(entry, freezeReason); - if (e == null) { - return false; - } - - return slaveDown(e, freezeReason == FreezeReason.SYSTEM); - } - - public boolean slaveDown(String host, int port, FreezeReason freezeReason) { - ClientConnectionsEntry entry = slaveBalancer.freeze(host, port, freezeReason); + public boolean slaveDown(URI address, FreezeReason freezeReason) { + ClientConnectionsEntry entry = slaveBalancer.freeze(address, freezeReason); if (entry == null) { return false; } @@ -140,10 +136,10 @@ public class MasterSlaveEntry { private boolean slaveDown(ClientConnectionsEntry entry, boolean temporaryDown) { // add master as slave if no more slaves available - if (config.getReadMode() == ReadMode.SLAVE && slaveBalancer.getAvailableClients() == 0) { - InetSocketAddress addr = masterEntry.getClient().getAddr(); - if (slaveUp(addr.getHostName(), addr.getPort(), FreezeReason.SYSTEM)) { - log.info("master {}:{} used as slave", addr.getHostName(), addr.getPort()); + if (!config.checkSkipSlavesInit() && slaveBalancer.getAvailableClients() == 0) { + URI addr = masterEntry.getClient().getConfig().getAddress(); + if (slaveUp(addr, FreezeReason.SYSTEM)) { + log.info("master {} used as slave", addr); } } @@ -153,7 +149,7 @@ public class MasterSlaveEntry { if (connection == null) { break; } - + connection.closeAsync().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { @@ -309,18 +305,22 @@ public class MasterSlaveEntry { public boolean hasSlave(InetSocketAddress addr) { return slaveBalancer.contains(addr); } - + + public boolean hasSlave(String addr) { + return slaveBalancer.contains(addr); + } + public RFuture addSlave(URI address) { return addSlave(address, true, NodeType.SLAVE); } - private RFuture addSlave(URI address, boolean freezed, NodeType mode) { + private RFuture addSlave(URI address, boolean freezed, NodeType nodeType) { RedisClient client = connectionManager.createClient(NodeType.SLAVE, address); ClientConnectionsEntry entry = new ClientConnectionsEntry(client, this.config.getSlaveConnectionMinimumIdleSize(), this.config.getSlaveConnectionPoolSize(), this.config.getSubscriptionConnectionMinimumIdleSize(), - this.config.getSubscriptionConnectionPoolSize(), connectionManager, mode); + this.config.getSubscriptionConnectionPoolSize(), connectionManager, nodeType); if (freezed) { synchronized (entry) { entry.setFreezed(freezed); @@ -334,17 +334,18 @@ public class MasterSlaveEntry { return masterEntry.getClient(); } - public boolean slaveUp(String host, int port, FreezeReason freezeReason) { - if (!slaveBalancer.unfreeze(host, port, freezeReason)) { + public boolean slaveUp(URI address, FreezeReason freezeReason) { + if (!slaveBalancer.unfreeze(address, freezeReason)) { return false; } + InetSocketAddress naddress = new InetSocketAddress(address.getHost(), address.getPort()); InetSocketAddress addr = masterEntry.getClient().getAddr(); // exclude master from slaves - if (config.getReadMode() == ReadMode.SLAVE - && (!addr.getHostName().equals(host) || port != addr.getPort())) { - slaveDown(addr.getHostName(), addr.getPort(), FreezeReason.SYSTEM); - log.info("master {}:{} excluded from slaves", addr.getHostName(), addr.getPort()); + if (!config.checkSkipSlavesInit() + && (!addr.getAddress().getHostAddress().equals(naddress.getAddress().getHostAddress()) || naddress.getPort() != addr.getPort())) { + slaveDown(address, FreezeReason.SYSTEM); + log.info("master {} excluded from slaves", addr); } return true; } @@ -364,14 +365,21 @@ public class MasterSlaveEntry { public void operationComplete(Future future) throws Exception { writeConnectionHolder.remove(oldMaster); pubSubConnectionHolder.remove(oldMaster); - slaveDown(oldMaster, FreezeReason.MANAGER); + + oldMaster.freezeMaster(FreezeReason.MANAGER); + slaveDown(oldMaster, false); + slaveDown(URIBuilder.create("redis://" + oldMaster.getClient().getIpAddr()), FreezeReason.MANAGER); + + slaveBalancer.changeType(oldMaster.getClient().getAddr(), NodeType.SLAVE); + slaveBalancer.changeType(address, NodeType.MASTER); // more than one slave available, so master can be removed from slaves - if (config.getReadMode() == ReadMode.SLAVE + if (!config.checkSkipSlavesInit() && slaveBalancer.getAvailableClients() > 1) { - slaveDown(address.getHost(), address.getPort(), FreezeReason.SYSTEM); + slaveDown(address, FreezeReason.SYSTEM); } connectionManager.shutdownAsync(oldMaster.getClient()); + log.info("master {} has changed to {}", oldMaster.getClient().getAddr(), address); } }); } @@ -410,10 +418,16 @@ public class MasterSlaveEntry { } public RFuture connectionReadOp(RedisCommand command) { + if (config.getReadMode() == ReadMode.MASTER) { + return connectionWriteOp(command); + } return slaveBalancer.nextConnection(command); } public RFuture connectionReadOp(RedisCommand command, InetSocketAddress addr) { + if (config.getReadMode() == ReadMode.MASTER) { + return connectionWriteOp(command); + } return slaveBalancer.getConnection(command, addr); } @@ -438,6 +452,10 @@ public class MasterSlaveEntry { } public void releaseRead(RedisConnection connection) { + if (config.getReadMode() == ReadMode.MASTER) { + releaseWrite(connection); + return; + } slaveBalancer.returnConnection(connection); } diff --git a/redisson/src/main/java/org/redisson/connection/ReplicatedConnectionManager.java b/redisson/src/main/java/org/redisson/connection/ReplicatedConnectionManager.java index 329ff31fa..8882f1df6 100644 --- a/redisson/src/main/java/org/redisson/connection/ReplicatedConnectionManager.java +++ b/redisson/src/main/java/org/redisson/connection/ReplicatedConnectionManager.java @@ -101,7 +101,7 @@ public class ReplicatedConnectionManager extends MasterSlaveConnectionManager { throw new RedisConnectionException("Can't connect to servers!"); } - init(this.config); + initSingleEntry(); scheduleMasterChangeCheck(cfg); } @@ -192,7 +192,6 @@ public class ReplicatedConnectionManager extends MasterSlaveConnectionManager { if (master.equals(addr)) { log.debug("Current master {} unchanged", master); } else if (currentMaster.compareAndSet(master, addr)) { - log.info("Master has changed from {} to {}", master, addr); changeMaster(singleSlotRange.getStartSlot(), addr); } } diff --git a/redisson/src/main/java/org/redisson/connection/SentinelConnectionManager.java b/redisson/src/main/java/org/redisson/connection/SentinelConnectionManager.java index 4e9c3588c..7b0fe1dac 100755 --- a/redisson/src/main/java/org/redisson/connection/SentinelConnectionManager.java +++ b/redisson/src/main/java/org/redisson/connection/SentinelConnectionManager.java @@ -39,16 +39,15 @@ import org.redisson.cluster.ClusterSlotRange; import org.redisson.config.BaseMasterSlaveServersConfig; import org.redisson.config.Config; import org.redisson.config.MasterSlaveServersConfig; -import org.redisson.config.ReadMode; import org.redisson.config.SentinelServersConfig; import org.redisson.connection.ClientConnectionsEntry.FreezeReason; +import org.redisson.misc.URIBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.internal.PlatformDependent; -import org.redisson.misc.URIBuilder; /** * @@ -67,13 +66,13 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { public SentinelConnectionManager(SentinelServersConfig cfg, Config config) { super(config); - - this.config = create(cfg); - initTimer(this.config); - + if (cfg.getMasterName() == null) { throw new IllegalArgumentException("masterName parameter is not defined!"); } + + this.config = create(cfg); + initTimer(this.config); for (URI addr : cfg.getSentinelAddresses()) { RedisClient client = createClient(NodeType.SENTINEL, addr, this.config.getConnectTimeout(), this.config.getRetryInterval() * this.config.getRetryAttempts()); @@ -126,8 +125,9 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { stopThreads(); throw new RedisConnectionException("Can't connect to servers!"); } - init(this.config); - + + initSingleEntry(); + List> connectionFutures = new ArrayList>(cfg.getSentinelAddresses().size()); for (URI addr : cfg.getSentinelAddresses()) { RFuture future = registerSentinel(cfg, addr, this.config); @@ -171,7 +171,7 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { - log.warn("Can't connect to sentinel: {}:{}", addr.getHost(), addr.getPort()); + log.warn("Can't connect to sentinel: {}", addr); return; } @@ -220,8 +220,7 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { String ip = parts[2]; String port = parts[3]; - String addr = createAddress(ip, port); - URI uri = URIBuilder.create(addr); + URI uri = convert(ip, port); registerSentinel(cfg, uri, c); } } @@ -241,7 +240,7 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { } // to avoid addition twice - if (slaves.putIfAbsent(slaveAddr, true) == null) { + if (slaves.putIfAbsent(slaveAddr, true) == null && !config.checkSkipSlavesInit()) { final MasterSlaveEntry entry = getEntry(singleSlotRange.getStartSlot()); RFuture future = entry.addSlave(URIBuilder.create(slaveAddr)); future.addListener(new FutureListener() { @@ -253,11 +252,13 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { return; } - if (entry.slaveUp(ip, Integer.valueOf(port), FreezeReason.MANAGER)) { + URI uri = convert(ip, port); + if (entry.slaveUp(uri, FreezeReason.MANAGER)) { String slaveAddr = ip + ":" + port; log.info("slave: {} added", slaveAddr); } } + }); } else { slaveUp(ip, port); @@ -267,6 +268,12 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { } } + protected URI convert(String ip, String port) { + String addr = createAddress(ip, port); + URI uri = URIBuilder.create(addr); + return uri; + } + private void onNodeDown(URI sentinelAddr, String msg) { String[] parts = msg.split(" "); @@ -305,11 +312,12 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { } private void slaveDown(String ip, String port) { - if (config.getReadMode() == ReadMode.MASTER) { + if (config.checkSkipSlavesInit()) { log.warn("slave: {}:{} has down", ip, port); } else { MasterSlaveEntry entry = getEntry(singleSlotRange.getStartSlot()); - if (entry.slaveDown(ip, Integer.valueOf(port), FreezeReason.MANAGER)) { + URI uri = convert(ip, port); + if (entry.slaveDown(uri, FreezeReason.MANAGER)) { log.warn("slave: {}:{} has down", ip, port); } } @@ -361,13 +369,14 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { } private void slaveUp(String ip, String port) { - if (config.getReadMode() == ReadMode.MASTER) { + if (config.checkSkipSlavesInit()) { String slaveAddr = ip + ":" + port; log.info("slave: {} has up", slaveAddr); return; } - if (getEntry(singleSlotRange.getStartSlot()).slaveUp(ip, Integer.valueOf(port), FreezeReason.MANAGER)) { + URI uri = convert(ip, port); + if (getEntry(singleSlotRange.getStartSlot()).slaveUp(uri, FreezeReason.MANAGER)) { String slaveAddr = ip + ":" + port; log.info("slave: {} has up", slaveAddr); } @@ -386,7 +395,6 @@ public class SentinelConnectionManager extends MasterSlaveConnectionManager { if (!newMaster.equals(current) && currentMaster.compareAndSet(current, newMaster)) { changeMaster(singleSlotRange.getStartSlot(), URIBuilder.create(newMaster)); - log.info("master {} changed to {}", current, newMaster); } } } else { diff --git a/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java b/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java index 83d7c1e5c..bc42ef691 100644 --- a/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java +++ b/redisson/src/main/java/org/redisson/connection/SingleConnectionManager.java @@ -15,22 +15,11 @@ */ package org.redisson.connection; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import org.redisson.client.RedisConnectionException; import org.redisson.config.Config; import org.redisson.config.MasterSlaveServersConfig; import org.redisson.config.ReadMode; import org.redisson.config.SingleServerConfig; import org.redisson.config.SubscriptionMode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.util.concurrent.GlobalEventExecutor; -import io.netty.util.concurrent.ScheduledFuture; /** * @@ -39,24 +28,8 @@ import io.netty.util.concurrent.ScheduledFuture; */ public class SingleConnectionManager extends MasterSlaveConnectionManager { - private final Logger log = LoggerFactory.getLogger(getClass()); - - private final AtomicReference currentMaster = new AtomicReference(); - - private ScheduledFuture monitorFuture; - public SingleConnectionManager(SingleServerConfig cfg, Config config) { super(create(cfg), config); - - if (cfg.isDnsMonitoring()) { - try { - this.currentMaster.set(InetAddress.getByName(cfg.getAddress().getHost())); - } catch (UnknownHostException e) { - throw new RedisConnectionException("Unknown host: " + cfg.getAddress().getHost(), e); - } - log.debug("DNS monitoring enabled; Current master set to {}", currentMaster.get()); - monitorDnsChange(cfg); - } } private static MasterSlaveServersConfig create(SingleServerConfig cfg) { @@ -84,6 +57,11 @@ public class SingleConnectionManager extends MasterSlaveConnectionManager { newconfig.setIdleConnectionTimeout(cfg.getIdleConnectionTimeout()); newconfig.setFailedAttempts(cfg.getFailedAttempts()); newconfig.setReconnectionTimeout(cfg.getReconnectionTimeout()); + if (cfg.isDnsMonitoring()) { + newconfig.setDnsMonitoringInterval(cfg.getDnsMonitoringInterval()); + } else { + newconfig.setDnsMonitoringInterval(-1); + } newconfig.setMasterConnectionMinimumIdleSize(cfg.getConnectionMinimumIdleSize()); newconfig.setSubscriptionConnectionMinimumIdleSize(cfg.getSubscriptionConnectionMinimumIdleSize()); @@ -92,41 +70,4 @@ public class SingleConnectionManager extends MasterSlaveConnectionManager { return newconfig; } - private void monitorDnsChange(final SingleServerConfig cfg) { - monitorFuture = GlobalEventExecutor.INSTANCE.schedule(new Runnable() { - @Override - public void run() { - // As InetAddress.getByName call is blocking. Method should be run in dedicated thread - getExecutor().execute(new Runnable() { - @Override - public void run() { - try { - InetAddress master = currentMaster.get(); - InetAddress now = InetAddress.getByName(cfg.getAddress().getHost()); - if (!now.getHostAddress().equals(master.getHostAddress())) { - log.info("Detected DNS change. {} has changed from {} to {}", cfg.getAddress().getHost(), master.getHostAddress(), now.getHostAddress()); - if (currentMaster.compareAndSet(master, now)) { - changeMaster(singleSlotRange.getStartSlot(), cfg.getAddress()); - log.info("Master has been changed"); - } - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } finally { - monitorDnsChange(cfg); - } - } - }); - } - - }, cfg.getDnsMonitoringInterval(), TimeUnit.MILLISECONDS); - } - - @Override - public void shutdown() { - if (monitorFuture != null) { - monitorFuture.cancel(true); - } - super.shutdown(); - } } diff --git a/redisson/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java b/redisson/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java index 40e46a5c8..5ed44e8c6 100644 --- a/redisson/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java +++ b/redisson/src/main/java/org/redisson/connection/balancer/LoadBalancerManager.java @@ -16,15 +16,18 @@ package org.redisson.connection.balancer; import java.net.InetSocketAddress; +import java.net.URI; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.redisson.api.NodeType; import org.redisson.api.RFuture; import org.redisson.client.RedisConnection; import org.redisson.client.RedisConnectionException; import org.redisson.client.RedisPubSubConnection; import org.redisson.client.protocol.RedisCommand; import org.redisson.config.MasterSlaveServersConfig; +import org.redisson.config.ReadMode; import org.redisson.connection.ClientConnectionsEntry; import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.connection.ConnectionManager; @@ -39,14 +42,20 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.internal.PlatformDependent; +/** + * + * @author Nikita Koksharov + * + */ public class LoadBalancerManager { private final Logger log = LoggerFactory.getLogger(getClass()); private final ConnectionManager connectionManager; - private final Map addr2Entry = PlatformDependent.newConcurrentHashMap(); private final PubSubConnectionPool pubSubConnectionPool; private final SlaveConnectionPool slaveConnectionPool; + + private final Map ip2Entry = PlatformDependent.newConcurrentHashMap(); public LoadBalancerManager(MasterSlaveServersConfig config, ConnectionManager connectionManager, MasterSlaveEntry entry) { this.connectionManager = connectionManager; @@ -54,6 +63,25 @@ public class LoadBalancerManager { pubSubConnectionPool = new PubSubConnectionPool(config, connectionManager, entry); } + public void changeType(InetSocketAddress addr, NodeType nodeType) { + ClientConnectionsEntry entry = ip2Entry.get(addr.getAddress().getHostAddress() + ":" + addr.getPort()); + changeType(addr, nodeType, entry); + } + + protected void changeType(Object addr, NodeType nodeType, ClientConnectionsEntry entry) { + if (entry != null) { + if (connectionManager.isClusterMode()) { + entry.getClient().getConfig().setReadOnly(nodeType == NodeType.SLAVE && connectionManager.getConfig().getReadMode() != ReadMode.MASTER); + } + entry.setNodeType(nodeType); + } + } + + public void changeType(URI address, NodeType nodeType) { + ClientConnectionsEntry entry = getEntry(address); + changeType(address, nodeType, entry); + } + public RFuture add(final ClientConnectionsEntry entry) { final RPromise result = connectionManager.newPromise(); FutureListener listener = new FutureListener() { @@ -65,7 +93,8 @@ public class LoadBalancerManager { return; } if (counter.decrementAndGet() == 0) { - addr2Entry.put(entry.getClient().getAddr(), entry); + String addr = entry.getClient().getIpAddr(); + ip2Entry.put(addr, entry); result.trySuccess(null); } } @@ -80,7 +109,7 @@ public class LoadBalancerManager { public int getAvailableClients() { int count = 0; - for (ClientConnectionsEntry connectionEntry : addr2Entry.values()) { + for (ClientConnectionsEntry connectionEntry : ip2Entry.values()) { if (!connectionEntry.isFreezed()) { count++; } @@ -88,11 +117,10 @@ public class LoadBalancerManager { return count; } - public boolean unfreeze(String host, int port, FreezeReason freezeReason) { - InetSocketAddress addr = new InetSocketAddress(host, port); - ClientConnectionsEntry entry = addr2Entry.get(addr); + public boolean unfreeze(URI address, FreezeReason freezeReason) { + ClientConnectionsEntry entry = getEntry(address); if (entry == null) { - throw new IllegalStateException("Can't find " + addr + " in slaves!"); + throw new IllegalStateException("Can't find " + address + " in slaves!"); } synchronized (entry) { @@ -111,12 +139,21 @@ public class LoadBalancerManager { return false; } - public ClientConnectionsEntry freeze(String host, int port, FreezeReason freezeReason) { - InetSocketAddress addr = new InetSocketAddress(host, port); - ClientConnectionsEntry connectionEntry = addr2Entry.get(addr); + private String convert(URI address) { + InetSocketAddress addr = new InetSocketAddress(address.getHost(), address.getPort()); + return addr.getAddress().getHostAddress() + ":" + addr.getPort(); + } + + public ClientConnectionsEntry freeze(URI address, FreezeReason freezeReason) { + ClientConnectionsEntry connectionEntry = getEntry(address); return freeze(connectionEntry, freezeReason); } + private ClientConnectionsEntry getEntry(URI address) { + String addr = convert(address); + return ip2Entry.get(addr); + } + public ClientConnectionsEntry freeze(ClientConnectionsEntry connectionEntry, FreezeReason freezeReason) { if (connectionEntry == null) { return null; @@ -143,11 +180,15 @@ public class LoadBalancerManager { } public boolean contains(InetSocketAddress addr) { - return addr2Entry.containsKey(addr); + return ip2Entry.containsKey(addr.getAddress().getHostAddress() + ":" + addr.getPort()); + } + + public boolean contains(String addr) { + return ip2Entry.containsKey(addr); } public RFuture getConnection(RedisCommand command, InetSocketAddress addr) { - ClientConnectionsEntry entry = addr2Entry.get(addr); + ClientConnectionsEntry entry = ip2Entry.get(addr.getAddress().getHostAddress()); if (entry != null) { return slaveConnectionPool.get(command, entry); } @@ -160,23 +201,23 @@ public class LoadBalancerManager { } public void returnPubSubConnection(RedisPubSubConnection connection) { - ClientConnectionsEntry entry = addr2Entry.get(connection.getRedisClient().getAddr()); + ClientConnectionsEntry entry = ip2Entry.get(connection.getRedisClient().getAddr().getAddress().getHostAddress()); pubSubConnectionPool.returnConnection(entry, connection); } public void returnConnection(RedisConnection connection) { - ClientConnectionsEntry entry = addr2Entry.get(connection.getRedisClient().getAddr()); + ClientConnectionsEntry entry = ip2Entry.get(connection.getRedisClient().getAddr().getAddress().getHostAddress()); slaveConnectionPool.returnConnection(entry, connection); } public void shutdown() { - for (ClientConnectionsEntry entry : addr2Entry.values()) { + for (ClientConnectionsEntry entry : ip2Entry.values()) { entry.getClient().shutdown(); } } public void shutdownAsync() { - for (ClientConnectionsEntry entry : addr2Entry.values()) { + for (ClientConnectionsEntry entry : ip2Entry.values()) { connectionManager.shutdownAsync(entry.getClient()); } } diff --git a/redisson/src/main/java/org/redisson/connection/pool/ConnectionPool.java b/redisson/src/main/java/org/redisson/connection/pool/ConnectionPool.java index ce8aa3ee9..c0ba05035 100644 --- a/redisson/src/main/java/org/redisson/connection/pool/ConnectionPool.java +++ b/redisson/src/main/java/org/redisson/connection/pool/ConnectionPool.java @@ -204,7 +204,7 @@ abstract class ConnectionPool { } RedisConnectionException exception = new RedisConnectionException( - "Can't aquire connection to " + entry.getClient().getAddr()); + "Can't aquire connection to " + entry); return connectionManager.newFailedFuture(exception); } @@ -296,7 +296,7 @@ abstract class ConnectionPool { private void promiseFailure(ClientConnectionsEntry entry, RPromise promise, Throwable cause) { if (entry.incFailedAttempts() == config.getFailedAttempts()) { - checkForReconnect(entry); + checkForReconnect(entry, cause); } releaseConnection(entry); @@ -308,7 +308,7 @@ abstract class ConnectionPool { int attempts = entry.incFailedAttempts(); if (attempts == config.getFailedAttempts()) { conn.closeAsync(); - checkForReconnect(entry); + checkForReconnect(entry, null); } else if (attempts < config.getFailedAttempts()) { releaseConnection(entry, conn); } else { @@ -321,15 +321,14 @@ abstract class ConnectionPool { promise.tryFailure(cause); } - private void checkForReconnect(ClientConnectionsEntry entry) { + private void checkForReconnect(ClientConnectionsEntry entry, Throwable cause) { if (entry.getNodeType() == NodeType.SLAVE) { - masterSlaveEntry.slaveDown(entry.getClient().getAddr().getHostName(), - entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT); - log.warn("slave {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts()); + masterSlaveEntry.slaveDown(entry.getClient().getConfig().getAddress(), FreezeReason.RECONNECT); + log.error("slave " + entry.getClient().getAddr() + " disconnected due to failedAttempts=" + config.getFailedAttempts() + " limit reached", cause); scheduleCheck(entry); } else { if (entry.freezeMaster(FreezeReason.RECONNECT)) { - log.warn("host {} disconnected due to failedAttempts={} limit reached", entry.getClient().getAddr(), config.getFailedAttempts()); + log.error("host " + entry.getClient().getAddr() + " disconnected due to failedAttempts=" + config.getFailedAttempts() + " limit reached", cause); scheduleCheck(entry); } } @@ -342,19 +341,23 @@ abstract class ConnectionPool { connectionManager.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { - if (entry.getFreezeReason() != FreezeReason.RECONNECT - || !entry.isFreezed() + synchronized (entry) { + if (entry.getFreezeReason() != FreezeReason.RECONNECT + || !entry.isFreezed() || connectionManager.isShuttingDown()) { - return; + return; + } } RFuture connectionFuture = entry.getClient().connectAsync(); connectionFuture.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { - if (entry.getFreezeReason() != FreezeReason.RECONNECT - || !entry.isFreezed()) { - return; + synchronized (entry) { + if (entry.getFreezeReason() != FreezeReason.RECONNECT + || !entry.isFreezed()) { + return; + } } if (!future.isSuccess()) { @@ -372,9 +375,11 @@ abstract class ConnectionPool { @Override public void operationComplete(Future future) throws Exception { try { - if (entry.getFreezeReason() != FreezeReason.RECONNECT - || !entry.isFreezed()) { - return; + synchronized (entry) { + if (entry.getFreezeReason() != FreezeReason.RECONNECT + || !entry.isFreezed()) { + return; + } } if (future.isSuccess() && "PONG".equals(future.getNow())) { @@ -385,14 +390,14 @@ abstract class ConnectionPool { public void operationComplete(Future future) throws Exception { if (entry.getNodeType() == NodeType.SLAVE) { - masterSlaveEntry.slaveUp(entry.getClient().getAddr().getHostName(), entry.getClient().getAddr().getPort(), FreezeReason.RECONNECT); - log.info("slave {} successfully reconnected", entry.getClient().getAddr()); + masterSlaveEntry.slaveUp(entry.getClient().getConfig().getAddress(), FreezeReason.RECONNECT); + log.info("slave {} has been successfully reconnected", entry.getClient().getAddr()); } else { synchronized (entry) { if (entry.getFreezeReason() == FreezeReason.RECONNECT) { entry.setFreezed(false); entry.setFreezeReason(null); - log.info("host {} successfully reconnected", entry.getClient().getAddr()); + log.info("host {} has been successfully reconnected", entry.getClient().getAddr()); } } } diff --git a/redisson/src/main/java/org/redisson/executor/TasksRunnerService.java b/redisson/src/main/java/org/redisson/executor/TasksRunnerService.java index 71bc94cd6..d865ae515 100644 --- a/redisson/src/main/java/org/redisson/executor/TasksRunnerService.java +++ b/redisson/src/main/java/org/redisson/executor/TasksRunnerService.java @@ -67,7 +67,7 @@ public class TasksRunnerService implements RemoteExecutorService, RemoteParams { try { this.codec = codec.getClass().getConstructor(ClassLoader.class).newInstance(classLoader); } catch (Exception e) { - throw new IllegalStateException(e); + throw new IllegalStateException("Unable to initialize codec with ClassLoader parameter", e); } } diff --git a/redisson/src/main/java/org/redisson/reactive/RedissonSemaphoreReactive.java b/redisson/src/main/java/org/redisson/reactive/RedissonSemaphoreReactive.java new file mode 100644 index 000000000..de60f6861 --- /dev/null +++ b/redisson/src/main/java/org/redisson/reactive/RedissonSemaphoreReactive.java @@ -0,0 +1,152 @@ +/** + * Copyright 2016 Nikita Koksharov + * + * 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.redisson.reactive; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Publisher; +import org.redisson.RedissonLock; +import org.redisson.RedissonSemaphore; +import org.redisson.api.RFuture; +import org.redisson.api.RLockAsync; +import org.redisson.api.RSemaphoreAsync; +import org.redisson.api.RSemaphoreReactive; +import org.redisson.command.CommandAsyncExecutor; +import org.redisson.command.CommandReactiveExecutor; +import org.redisson.pubsub.SemaphorePubSub; + +import reactor.fn.Supplier; + +/** + * + * @author Nikita Koksharov + * + */ +public class RedissonSemaphoreReactive extends RedissonExpirableReactive implements RSemaphoreReactive { + + private final RSemaphoreAsync instance; + + public RedissonSemaphoreReactive(CommandReactiveExecutor connectionManager, String name, SemaphorePubSub semaphorePubSub) { + super(connectionManager, name); + instance = new RedissonSemaphore(commandExecutor, name, semaphorePubSub); + } + + protected RLockAsync createLock(CommandAsyncExecutor connectionManager, String name, UUID id) { + return new RedissonLock(commandExecutor, name, id); + } + + @Override + public Publisher tryAcquire() { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.tryAcquireAsync(); + } + }); + } + + @Override + public Publisher tryAcquire(final int permits) { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.tryAcquireAsync(permits); + } + }); + } + + @Override + public Publisher acquire() { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.acquireAsync(); + } + }); + } + + @Override + public Publisher acquire(final int permits) { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.acquireAsync(permits); + } + }); + } + + @Override + public Publisher release() { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.releaseAsync(); + } + }); + } + + @Override + public Publisher release(final int permits) { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.releaseAsync(permits); + } + }); + } + + @Override + public Publisher trySetPermits(final int permits) { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.trySetPermitsAsync(permits); + } + }); + } + + @Override + public Publisher tryAcquire(final long waitTime, final TimeUnit unit) { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.tryAcquireAsync(waitTime, unit); + } + }); + } + + @Override + public Publisher tryAcquire(final int permits, final long waitTime, final TimeUnit unit) { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.tryAcquireAsync(permits, waitTime, unit); + } + }); + } + + @Override + public Publisher reducePermits(final int permits) { + return reactive(new Supplier>() { + @Override + public RFuture get() { + return instance.reducePermitsAsync(permits); + } + }); + } + +} diff --git a/redisson/src/main/java/org/redisson/spring/cache/NullValue.java b/redisson/src/main/java/org/redisson/spring/cache/NullValue.java index 563c26066..193bcf2cf 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/NullValue.java +++ b/redisson/src/main/java/org/redisson/spring/cache/NullValue.java @@ -15,6 +15,8 @@ */ package org.redisson.spring.cache; +import java.io.Serializable; + import org.springframework.cache.Cache.ValueWrapper; /** @@ -22,8 +24,10 @@ import org.springframework.cache.Cache.ValueWrapper; * @author Nikita Koksharov * */ -public class NullValue implements ValueWrapper { +public class NullValue implements ValueWrapper, Serializable { + private static final long serialVersionUID = -8310337775544536701L; + public static final NullValue INSTANCE = new NullValue(); @Override diff --git a/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java b/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java index 696feb815..8d1ce777f 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java +++ b/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java @@ -55,7 +55,7 @@ public class RedissonSpringCacheManager implements CacheManager, ResourceLoaderA RedissonClient redisson; Map configMap = new ConcurrentHashMap(); - private ConcurrentMap instanceMap = new ConcurrentHashMap(); + ConcurrentMap instanceMap = new ConcurrentHashMap(); String configLocation; diff --git a/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java b/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java index b1d57e481..d4f011772 100644 --- a/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java +++ b/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java @@ -8,7 +8,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; import org.junit.Assert; import org.junit.Test; @@ -22,6 +21,7 @@ import org.redisson.api.LocalCachedMapOptions.InvalidationPolicy; import org.redisson.api.RLocalCachedMap; import org.redisson.api.RMap; import org.redisson.cache.Cache; +import org.redisson.client.codec.StringCodec; import mockit.Deencapsulation; @@ -148,6 +148,32 @@ public class RedissonLocalCachedMapTest extends BaseMapTest { } }.execute(); } + + @Test + public void testInvalidationOnUpdateNonBinaryCodec() throws InterruptedException { + LocalCachedMapOptions options = LocalCachedMapOptions.defaults().evictionPolicy(EvictionPolicy.LFU).cacheSize(5); + RLocalCachedMap map1 = redisson.getLocalCachedMap("test", new StringCodec(), options); + Cache cache1 = Deencapsulation.getField(map1, "cache"); + + RLocalCachedMap map2 = redisson.getLocalCachedMap("test", new StringCodec(), options); + Cache cache2 = Deencapsulation.getField(map2, "cache"); + + map1.put("1", "1"); + map1.put("2", "2"); + + assertThat(map2.get("1")).isEqualTo("1"); + assertThat(map2.get("2")).isEqualTo("2"); + + assertThat(cache1.size()).isEqualTo(2); + assertThat(cache2.size()).isEqualTo(2); + + map1.put("1", "3"); + map2.put("2", "4"); + Thread.sleep(50); + + assertThat(cache1.size()).isEqualTo(1); + assertThat(cache2.size()).isEqualTo(1); + } @Test public void testInvalidationOnUpdate() throws InterruptedException { diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java index af6fdc76a..ee019494c 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -457,6 +457,19 @@ public class RedissonMapCacheTest extends BaseMapTest { Assert.assertEquals(1, map.size()); } + @Test + public void testPutAllBig() { + Map joinMap = new HashMap(); + for (int i = 0; i < 100000; i++) { + joinMap.put(i, "" + i); + } + + Map map = redisson.getMapCache("simple"); + map.putAll(joinMap); + + assertThat(map.size()).isEqualTo(joinMap.size()); + } + @Test public void testPutAll() { Map map = redisson.getMapCache("simple"); diff --git a/redisson/src/test/java/org/redisson/RedissonTest.java b/redisson/src/test/java/org/redisson/RedissonTest.java index 653a69c73..3660ef171 100644 --- a/redisson/src/test/java/org/redisson/RedissonTest.java +++ b/redisson/src/test/java/org/redisson/RedissonTest.java @@ -32,7 +32,6 @@ import org.redisson.api.NodesGroup; import org.redisson.api.RMap; import org.redisson.api.RedissonClient; import org.redisson.client.RedisConnectionException; -import org.redisson.client.RedisException; import org.redisson.client.RedisOutOfMemoryException; import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.client.protocol.decoder.ScanObjectEntry; @@ -536,7 +535,7 @@ public class RedissonTest { @Test(expected = RedisConnectionException.class) public void testSentinelConnectionFail() throws InterruptedException { Config config = new Config(); - config.useSentinelServers().addSentinelAddress("redis://127.99.0.1:1111"); + config.useSentinelServers().addSentinelAddress("redis://127.99.0.1:1111").setMasterName("test"); Redisson.create(config); Thread.sleep(1500);