diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bdad30ee..caf082d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,29 @@ Redisson Releases History ================================ -####Please Note: trunk is current development branch. +### Please Note: trunk is current development branch. Try __ULTRA-FAST__ [Redisson PRO](https://redisson.pro) edition. -####04-Mar-2017 - versions 2.8.1 and 3.3.1 released +## Please take part in [Redisson survey](https://www.surveymonkey.com/r/QXQZH5D) + +### 21-Mar-2017 - versions 2.8.2 and 3.3.2 released + +Feature - Redisson's Spring custom namespace support (thanks to Rui Gu) +Feature - ability to set custom connection manager (thanks to Saikiran Daripelli) +Feature - autoconfigured Spring Boot CacheStatisticsProvider implementation (thanks to Craig Andrews) +Feature - `RKeys.touch` and `RObject.touch` methods added +Feature - `RedissonCompletionService` implementation added +Feature - `RMap.getReadWriteLock` method added +Fixed - NPE during `RLocalCachedMap.fastRemove` invocation +Fixed - `redisson-tomcat-8` module is not compatible with Tomcat 8.5 +Fixed - URLBuilder methods should be synchronized +Fixed - use PSETEX in `RBucket.set` method +Fixed - `DelayedQueue.remove()` and `DelayedQueue.removeAll()` +Fixed - unable to define Type and AvroSchema for AvroJacksonCodec +Fixed - ReadWriteLock leaseTimeout calculation +Fixed - `Config.fromJson(file)` method, throws StackOverflowError + +### 04-Mar-2017 - versions 2.8.1 and 3.3.1 released Feature - Cache with SoftReference support added for `RLocalCachedMap` Feature - `Config.subscriptionMode` setting added @@ -13,7 +32,7 @@ Fixed - StackOverflowException in URLBuilder Fixed - TomcatSessionManager can't be used in Tomcat if Redisson has been deployed in web application Fixed - skip cluster nodes with the "handshake" flag (thanks to @dcheckoway) -####19-Feb-2017 - versions 2.8.0 and 3.3.0 released +### 19-Feb-2017 - versions 2.8.0 and 3.3.0 released Feature - __`RClusteredLocalCachedMap` object added__ More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections#713-map-data-partitioning) Feature - __`RClusteredMapCache` object added__ More details [here](https://github.com/redisson/redisson/wiki/7.-distributed-collections#713-map-data-partitioning) @@ -28,7 +47,7 @@ Fixed - cascade slaves are not supported in cluster mode Fixed - shutdown checking during master change state check added Fixed - master isn't checked during new slave discovery in Sentinel mode -####02-Feb-2017 - versions 2.7.4 and 3.2.4 released +### 02-Feb-2017 - versions 2.7.4 and 3.2.4 released Feature - Allow to specify Redisson instance/config during JCache cache creation Fixed - `ByteBuf.release` method invocation is missed in `LZ4Codec` and `SnappyCodec` @@ -40,7 +59,7 @@ Fixed - `JCache` expiration listener doesn't work Fixed - `RLocalCachedMap` doesn't work with `SerializationCodec` Fixed - `Can't find entry` error during operation execution on slave nodes -####19-Jan-2017 - versions 2.7.3 and 3.2.3 released +### 19-Jan-2017 - versions 2.7.3 and 3.2.3 released Redisson Team is pleased to announce __ULTRA-FAST__ Redisson PRO edition. Performance measure results available in [Benchmark whitepaper](https://redisson.pro/Redisson%20PRO%20benchmark%20whitepaper.pdf) @@ -55,7 +74,7 @@ Fixed - `RSortedSet.removeAsync` and `RSortedSet.addAsync` Fixed - `RBloomFilter.tryInit` were not validated properly Fixed - CommandDecoder should print all replay body on error -####19-Dec-2016 - versions 2.7.2 and 3.2.2 released +### 19-Dec-2016 - versions 2.7.2 and 3.2.2 released Feature - `RList`, `RSet` and `RScoredSortedSet` implements `RSortable` interface with SORT command support Feature - `NodeAsync` interface @@ -63,7 +82,7 @@ Feature - `Node.info`, `Node.getNode` methods added Fixed - elements distribution of `RBlockingFairQueue` across consumers Fixed - `factory already defined` error during Redisson initialization under Apache Tomcat -####14-Dec-2016 - versions 2.7.1 and 3.2.1 released +### 14-Dec-2016 - versions 2.7.1 and 3.2.1 released Url format used in config files __has changed__. For example: @@ -77,7 +96,7 @@ Fixed - command timeout didn't respect during topic subscription Fixed - possible PublishSubscribe race-condition Fixed - blocking queue/deque poll method blocks infinitely if delay less than 1 second -####26-Nov-2016 - versions 2.7.0 and 3.2.0 released +### 26-Nov-2016 - versions 2.7.0 and 3.2.0 released Feature - __Spring Session implementation__. More details [here](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#145-spring-session) Feature - __Tomcat Session Manager implementation__. More details [here](https://github.com/redisson/redisson/wiki/14.-Integration%20with%20frameworks/#144-tomcat-redis-session-manager) @@ -89,31 +108,31 @@ Fixed - Wrong nodes parsing in result of cluster info command Fixed - NullPointerException in CommandDecoder.handleResult Fixed - Redisson shutdown status should be checked during async command invocation -####07-Nov-2016 - versions 2.6.0 and 3.1.0 released +### 07-Nov-2016 - versions 2.6.0 and 3.1.0 released Feature - __new object added__ `RBinaryStream`. More info about it [here](https://github.com/redisson/redisson/wiki/6.-distributed-objects/#62-binary-stream-holder) Improvement - limit Payload String on RedisTimeoutException Improvement - Elasticache master node change detection process optimization -####27-Oct-2016 - versions 2.5.1 and 3.0.1 released +### 27-Oct-2016 - versions 2.5.1 and 3.0.1 released Include all code changes from __2.2.27__ version Fixed - RMapCache.fastPutIfAbsentAsync doesn't take in account expiration Fixed - timer field of RedisClient hasn't been initialized properly in some cases -####27-Oct-2016 - version 2.2.27 released +### 27-Oct-2016 - version 2.2.27 released This version fixes old and annonying problem with `ConnectionPool exhusted` error. From this moment connection pool waits for free connection instead of throwing pool exhausted error. This leads to more effective Redis connection utilization. Improvement - remove `Connection pool exhausted` exception -####17-Oct-2016 - version 3.0.0 released +### 17-Oct-2016 - version 3.0.0 released Fully compatible with JDK 8. Includes all code changes from __2.5.0__ version Feature - `RFeature` extends `CompletionStage` -####17-Oct-2016 - version 2.5.0 released +### 17-Oct-2016 - version 2.5.0 released This version brings greatly improved version of `RLiveObjectService` and adds cascade handling, cyclic dependency resolving, simplified object creation. Read more in this [article](https://dzone.com/articles/java-distributed-in-memory-data-model-powered-by-r) Includes all code changes from __2.2.26__ version @@ -139,11 +158,11 @@ Fixed - `RLiveObjectService` can't detach content of List object Fixed - `RLiveObjectService` doesn't create objects mapped to Redisson objects in runtime during getter accesss Fixed - `RLiveObjectService` can't recognize id field of object without setter -####17-Oct-2016 - version 2.2.26 released +### 17-Oct-2016 - version 2.2.26 released Fixed - NPE in CommandDecoder Fixed - PubSub connection re-subscription doesn't work in case when there is only one slave available -####27-Sep-2016 - version 2.4.0 released +### 27-Sep-2016 - version 2.4.0 released Includes all code changes from __2.2.25__ version Feature - __new object added__ `RPermitExpirableSemaphore`. More info about it [here](https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers#87-permitexpirablesemaphore) @@ -162,7 +181,7 @@ Improvement - `RMultimap.get` should return `RSet` or `RList` interface instead Fixed - `RExecutorService` should reject non-static inner task class Fixed - wrong object encoding in `RScoredSortedSet.addScore` method -####27-Sep-2016 - version 2.2.25 released +### 27-Sep-2016 - version 2.2.25 released Improvement - log unexpected errors in netty handlers Improvement - `Not all slots are covered` error should be more informative Improvement - implement random wait time in `lock` method of `RedissonRedLock` and `RedissonMultiLock` objects @@ -176,7 +195,7 @@ Fixed - TRYAGAIN error handling in cluster mode Fixed - sync commands in connectionListener leads to connection timeout exception Fixed - can't find slave error in cluster mode if failed slave hasn't been added before -####24-Aug-2016 - version 2.3.0 released +### 24-Aug-2016 - version 2.3.0 released Starting from this version Redisson could be run as standalone node to execute distributed tasks. More features will be added to it in future. Read docs about it [here](https://github.com/mrniko/redisson/wiki/12.-Standalone-node) Feature - __new service added__ `RExecutorService`. More info about it [here](https://github.com/mrniko/redisson/wiki/9.-distributed-services/#93-distributed-executor-service) @@ -194,11 +213,11 @@ __Breaking api change__ - all classes moved from `org.redisson.core` to `org.red __Breaking api change__ - switched from `io.netty.util.concurrent.Future` to `org.redisson.api.RFuture` interface Fixed - division by zero in WeightedRoundRobinBalancer (thanks to Shailender R Bathula) -####08-Aug-2016 - version 2.2.24 released +### 08-Aug-2016 - version 2.2.24 released Fixed - PubSub connection in cluster mode should be connected to node according slot derived from channel name Fixed - `RLock.tryLock` could block forever under some conditions -####04-Aug-2016 - version 2.2.23 released +### 04-Aug-2016 - version 2.2.23 released Improvement - `Future.cancel` method handling for RemoteService async call Fixed - unable to redefine RedisClient command execution timeout Fixed - exception occured in CommandEncoder leads to reponse timeout exception @@ -206,23 +225,23 @@ Fixed - exception occured in CommandDecoder leads to reponse timeout exception Fixed - BLPOP timeout calculation fixed Fixed - object used in RemoteService to prevent race-condition during ack receiving should be created per request -####26-Jul-2016 - version 2.2.22 released +### 26-Jul-2016 - version 2.2.22 released Fixed - java.lang.UnsupportedOperationException during command batch usage with netty 4.0.38 and higher -####15-Jul-2016 - version 2.2.21 released +### 15-Jul-2016 - version 2.2.21 released Fixed - `RLock`, `RReadWriteLock`, `RSemaphore` and `RCountDownLatch` can blocks forever under some conditions -####14-Jul-2016 - version 2.2.20 released +### 14-Jul-2016 - version 2.2.20 released Fixed - NPE during pubsub re-subscription (regression since 2.2.18) Fixed - `RSortedSet` doesn't work in cluster mode (regression since 2.2.16) Fixed - PubSub connection pool initialization in cluster mode Fixed - NPE during pubsub usage in cluster mode (regression since 2.2.18) -####13-Jul-2016 - version 2.2.19 released +### 13-Jul-2016 - version 2.2.19 released Feature - `RSetReactive.readIntersection`, `RSetReactive.diff` and `RSetReactive.intersection` added Fixed - cluster commands handling regression (regression since 2.2.18) -####13-Jul-2016 - version 2.2.18 released +### 13-Jul-2016 - version 2.2.18 released Feature - `RSet.randomAsync` and `RSet.random` commands added (thanks to dcheckoway) Feature - commandTimeout param added to RedisClient Feature - `JsonJacksonMapValueCodec` basic typed map value codec added (thanks to andrejserafim) @@ -235,7 +254,7 @@ Fixed - response parsing of cluster nodes command Fixed - Connections weren't closing during `RedisClient` shutdown Fixed - `RedissonRedLock.unlock` -####30-Jun-2016 - version 2.2.17 released +### 30-Jun-2016 - version 2.2.17 released Feature - `RMultimap.keySize` method added Feature - `RKeys.getType` method added Feature - `RKeys.getKeysByPattern` method with count param added @@ -251,7 +270,7 @@ Fixed - RLock.lock can hang in some cases Fixed - PubSub subscription may stuck in some cases Fixed - return value of `RedissonMultimap.keySet.size` method -####12-Jun-2016 - version 2.2.16 released +### 12-Jun-2016 - version 2.2.16 released Feature - `RGeo`, `RMultimapCache` added to `RBatch` Feature - `fastRemove` and `fastRemoveAsync` methods were added to `RList` Improvement - added Spring 4.3.0 support to RedissonSpringCacheManager @@ -261,14 +280,14 @@ Improvement - ability to define `Codec` for `RRemoteService` Fixed - cluster state managing with redis masters only Fixed - dead lock during `RLock`, `RSemaphore`, `RReadWriteLock`, `RCountDownLatch` usage under heavy load -####08-Jun-2016 - version 2.2.15 released +### 08-Jun-2016 - version 2.2.15 released Improvement - Performance boost up to 30% for `RSortedSet.add` method Fixed - auth during reconnection (thanks to fransiskusx) Fixed - Infinity loop with iterator Fixed - NPE in `RSortedSet` Fixed - `RSortedSet.remove` and `iterator.remove` methods can break elements ordering -####27-May-2016 - version 2.2.14 released +### 27-May-2016 - version 2.2.14 released Redisson Team is pleased to announce [Redisson PRO](http://redisson.pro) edition. This version is based on open-source edition and has 24x7 support and some features. Feature - __data sharding for `RMap`, `RSet` structures in cluster mode__ available only in [Redisson PRO](http://redisson.pro) edition @@ -293,7 +312,7 @@ Fixed - FSTObjectOutput shouldn't be closed after write Fixed - possible race-condition during ack waiting in `RRemoteService` object Fixed - timeWait checking fixed in `RLock.tryLockAsync` -####30-Apr-2016 - version 2.2.13 released +### 30-Apr-2016 - version 2.2.13 released Feature - `RSet.diff` and `RSet.intersection` methods added Imporovement - `RScoredSortedSet`.`containsAll`, `removeAll` and `retainAll` methods speed optimization @@ -303,7 +322,7 @@ Fixed - possible infinity `RLock` expiration renewal process Fixed - error during `RSetCache.readAll` invocation. Fixed - expiration override wasn't work in `RSetCache.add` -####22-Apr-2016 - version 2.2.12 released +### 22-Apr-2016 - version 2.2.12 released Imporovement - Replaying phase handling in CommandDecoder Fixed - cluster state update manager can't try next node if current node has failed to response @@ -320,7 +339,7 @@ Fixed - array start index in LUA scripts Fixed - RMap iterator Fixed - probably thread blocking issues -####04-Apr-2016 - version 2.2.11 released +### 04-Apr-2016 - version 2.2.11 released Since this version Redisson has __perfomance boost up to 43%__ @@ -335,7 +354,7 @@ Fixed - RTopic listeners hangs during synchronous commands execution inside it Fixed - Redisson hangs during shutdown if `RBlockingQueue\Deque.take` or `RBlockingQueue\Deque.poll` methods were invoked -####23-Mar-2016 - version 2.2.10 released +### 23-Mar-2016 - version 2.2.10 released Feature - __new object added__ `RRemoteService` Feature - __new object added__ `RSetMultimapCache` @@ -350,7 +369,7 @@ Fixed - `RLock.tryLockAsync` NPE Fixed - possible NPE during Redisson version logging Fixed - Netty threads shutdown after connection error -####04-Mar-2016 - version 2.2.9 released +### 04-Mar-2016 - version 2.2.9 released Feature - __new object added__ `RSetMultimap` Feature - __new object added__ `RListMultimap` @@ -361,7 +380,7 @@ Improvement - Add DynamicImport-Package to OSGi headers Fixed - `RedissonSpringCacheManager` Sentinel compatibility Fixed - `RAtomicLong.compareAndSet` doesn't work when expected value is 0 and it wasn't initialized -####12-Feb-2016 - version 2.2.8 released +### 12-Feb-2016 - version 2.2.8 released Feature - `union`, `unionAsync`, `readUnion` and `readUnionAsync` methods were added to `RSet` object Feature - `readAll` and `readAllAsync` methods were added to `RSetCache` object @@ -370,7 +389,7 @@ Fixed - Script error during `RSetCache.toArray` and `RSetCache.readAll` methods Fixed - Sentinel doesn't support AUTH command Fixed - RMap iterator -####03-Feb-2016 - version 2.2.7 released +### 03-Feb-2016 - version 2.2.7 released Feature - `readAllKeySet`, `readAllValues`, `readAllEntry`, `readAllKeySetAsync`, `readAllValuesAsync`, `readAllEntryAsync` methods were added to `RMap` object Improvement - `RKeys.delete` optimization in Cluster mode @@ -385,7 +404,7 @@ Fixed - offline slaves handling during Redisson start in Sentinel mode Fixed - `SELECT` command can't be executed in Sentinel mode Fixed - `database` setting removed from cluster config -####28-Jan-2016 - version 2.2.6 released +### 28-Jan-2016 - version 2.2.6 released Feature - __new object added__ `RedissonMultiLock` Feature - `move` method added to `RSet`, `RSetReactive` objects (thanks to thrau) @@ -405,7 +424,7 @@ Fixed - can't connect with password to Sentinel and Elasticache servers Fixed - Cluster slave discovery (regression since 2.1.5) Fixed - Sentinel slave discovery (regression since 2.1.5) -####09-Jan-2015 - version 2.2.5 released +### 09-Jan-2015 - version 2.2.5 released Feature - __new object added__ `RBloomFilter` Feature - __new object added__ `RAtomicDouble` @@ -424,7 +443,7 @@ Fixed - `RMap.addAndGetAsync` key encoding Fixed - `RBatch` errors handling Fixed - `RBlockingQueue.pollLastAndOfferFirstToAsync` does not block properly -####25-Dec-2015 - version 2.2.4 released +### 25-Dec-2015 - version 2.2.4 released Please update to this version ASAP due to connection leak discovered in previous versions since Redisson 2.1.4. Feature - __new object added__ `RBlockingDeque` @@ -441,18 +460,18 @@ Fixed - `RReadWriteLock.forceUnlock` works only for current thread Fixed - MapKeyDecoder and MapValueDecoder are called in wrong order Fixed - `RReadWriteLock` doesn't work in cluster mode -####15-Dec-2015 - version 2.2.3 released +### 15-Dec-2015 - version 2.2.3 released Feature - ability to set connection listener via `Config.connectionListener` setting Fixed - `RLock` expiration bug fixed (regression bug since 2.2.2) Fixed - NPE in `RedissonSortedSet` constructor -####14-Dec-2015 - version 2.2.2 released +### 14-Dec-2015 - version 2.2.2 released Feature - `isShuttingDown` and `isShutdown` methods were added to RedissonClient and RedissonReactiveClient Feature - __new object added__ `RSetCacheReactive` Fixed - RLock expiration renewal task scheduling fixed (regression bug since 2.2.1) Fixed - RExpirable.expireAsync timeUnit precision fixed (regression bug since 2.2.1) -####11-Dec-2015 - version 2.2.1 released +### 11-Dec-2015 - version 2.2.1 released Feature - __new object added__ `RReadWriteLock` with reentrant read/write locking Feature - __new object added__ `RMapCache` map-based cache with TTL support for each entry Feature - __new object added__ `RSetCache` set-based cache with TTL support for each value @@ -461,7 +480,7 @@ Feature - `RMap.values()`, `RMap.keySet()`, `RMap.entrySet()` reimplemented with Feature - `RObjectReactive.isExists`, `RObject.isExists` and `RObject.isExistsAsync` added Fixed - `RLock.unlock` not thrown IllegalMonitorStateException -####04-Dec-2015 - version 2.2.0 released +### 04-Dec-2015 - version 2.2.0 released Since 2.2.0 version Redisson supports [Reactive Streams](http://www.reactive-streams.org). Use `Redisson.createReactive` method to access Reactive objects. Feature - [Reactive Streams](http://www.reactive-streams.org) support @@ -476,12 +495,12 @@ Fixed - `RLock.delete` didn't check lock existence Deprecated methods are dropped -####30-Nov-2015 - version 2.1.6 released +### 30-Nov-2015 - version 2.1.6 released Fixed - connection pool regression bug Fixed - connection init during `Node.ping` and `ClusterNode.info` invocation -####24-Nov-2015 - version 2.1.5 released +### 24-Nov-2015 - version 2.1.5 released Feature - new methods with `limit` option support were added to `RLexSortedSet`: `lexRange`, `lexRangeHead`, `lexRangeHeadAsync`, `lexRangeTail`, `lexRangeTailAsync`, `lexRangeAsync` (thanks to jackygurui) Feature - new methods with `limit` option support were added to `RScoredSortedSet`: `valueRange`, `valueRangeAsync`, `entryRange`, `entryRangeAsync`, `valueRange`, `valueRangeAsync` (thanks to jackygurui) Feature - `LOADING` Redis server response handling @@ -497,7 +516,7 @@ Fixed - `RSet.iterator` Fixed - `RBatch.execute` and `RBatch.executeAsync` errors handling -####11-Nov-2015 - version 2.1.4 released +### 11-Nov-2015 - version 2.1.4 released Cluster support improvements. New codecs. Stability improvements. Feature - [LZ4](https://github.com/jpountz/lz4-java) compression codec support @@ -522,7 +541,7 @@ Fixed - NPE during Publish/Subscribe event handling Fixed - Redisson shutdown handling Fixed - EOFException during RLock usage with SerializationCodec (thanks to Oleg Ternovoi) -####17-Sep-2015 - version 2.1.3 released +### 17-Sep-2015 - version 2.1.3 released Feature - Ability to define `Codec` for each object Feature - `refreshConnectionAfterFails` setting added Feature - [AWS Elasticache](https://aws.amazon.com/elasticache/) support via `Config.useElasticacheServers` method (thanks to Steve Ungerer) @@ -530,7 +549,7 @@ Feature - `RScoredSortedSet` and `RLexSortedSet` added. Both uses native Redis S Fixed - missed AUTH during channel reconnection Fixed - resubscribe to subscribed topics during channel reconnection -####05-Sep-2015 - version 2.1.2 released +### 05-Sep-2015 - version 2.1.2 released Fixed - possible NPE during channel reconnection Fixed - executeAsync freezes in cluster mode Fixed - use same node for SCAN/SSCAN/HSCAN during iteration @@ -540,7 +559,7 @@ Fixed - NPE with empty sentinel servers Fixed - unable to read `clientName` config param in Master\Slave and Sentinel modes Fixed - "Too many open files" error in cluster mode -####15-Aug-2015 - version 2.1.1 released +### 15-Aug-2015 - version 2.1.1 released Feature - all keys operations extracted to `RKeys` interface Feature - `RKeys.getKeys`, `RKeys.getKeysByPattern` and `RKeys.randomKey`methods added Feature - `RBlockingQueueAsync.drainToAsync` method added @@ -554,7 +573,7 @@ Fixed - skip disconnected sentinels during startup Fixed - slave node discovery in sentinel mode which has been disconnected since start __Deprecated__ - Redisson methods `deleteAsync`, `delete`, `deleteByPatternAsync`, `deleteByPattern`, `findKeysByPatternAsync`, `findKeysByPattern`. Use same methods with `RKeys` interface -####03-Aug-2015 - version 2.1.0 released +### 03-Aug-2015 - version 2.1.0 released Feature - `RTopic` subscribtion/unsubscription status listener added Feature - `RSet`: `removeRandom` and `removeRandomAsync` methods added Improvement - `RList`: `retainAll`,`containsAll`, `indexOf`, `lastIndexOf` optimization @@ -565,7 +584,7 @@ Fixed - timeout timer interval calculation Fixed - `RBatch` NPE's with very big commands list Fixed - `RBucket.set` with timeout -####26-Jul-2015 - version 2.0.0 released +### 26-Jul-2015 - version 2.0.0 released Starting from 2.0.0 version Redisson has a new own async and lock-free Redis client under the hood. Thanks to the new architecture pipline (command batches) support has been implemented and a lot of code has gone. Feature - new `RObject` methods: `move`, `moveAsync`, `migrate`, `migrateAsync` @@ -574,12 +593,12 @@ Feature - multiple commands batch (Redis pipelining) support via `Redisson.creat Feature - new methods `flushall`, `deleteAsync`, `delete`, `deleteByPatternAsync`, `deleteByPattern`, `findKeysByPatternAsync`, `findKeysByPattern` added to `RedissonClient` interface Improvement - closed channel detection speedup -####22-Jul-2015 - version 1.3.1 released +### 22-Jul-2015 - version 1.3.1 released Fixed - requests state sync during shutdown Fixed - netty-transport-native-epoll is now has a provided scope Fixed - NPE during `BlockingQueue.poll` invocation -####04-Jul-2015 - version 1.3.0 released +### 04-Jul-2015 - version 1.3.0 released Feature - `RQueue.pollLastAndOfferFirstTo` method added Feature - `RObject.rename`, `RObject.renameAsync`, `RObject.renamenx`, `RObject.renamenxAsync` methods added Feature - `RList.getAsync`, `RList.addAsync`, `RList.addAllAsync` methods added @@ -600,7 +619,7 @@ Fixed - `RedissonClient.getScript` method added Fixed - `BlockingQueue.poll` method Fixed - Incorrect map key encoding makes hmget return no fields when string keys are used (thanks to sammiq) -####02-Apr-2015 - version 1.2.1 released +### 02-Apr-2015 - version 1.2.1 released Feature - all redis-script commands via 'RScript' object Feature - implementation of `java.util.concurrent.BlockingQueue` (thanks to pdeschen) Feature - buckets load by pattern (thanks to mathieucarbou) @@ -614,13 +633,13 @@ Fixed - `RMap.replace` concurrency issue (thanks to AndrewKolpakov) Fixed - `RLock` subscription timeout units fixed (thanks to AndrewKolpakov) Fixed - Re-throw async exceptions (thanks to AndrewKolpakov) -####09-Jan-2015 - version 1.2.0 released +### 09-Jan-2015 - version 1.2.0 released Feature - cluster mode support Fixed - `RList` iterator race conditions Fixed - `RDeque.addFirst` `RDeque.addLast` methods Fixed - OSGi support -####16-Dec-2014 - version 1.1.7 released +### 16-Dec-2014 - version 1.1.7 released Improvement - `RAtomicLong` optimization Fixed - `RMap.fastRemove` and `RMap.getAll` methods Fixed - `RTopic` listeners re-subscribing in sentinel mode @@ -631,7 +650,7 @@ Fixed - `RAtomicLong` NPE Fixed - infinity loop during master/slave connection acquiring Fixed - `RedissonList.addAll` result -####18-Nov-2014 - version 1.1.6 released +### 18-Nov-2014 - version 1.1.6 released Feature - `RBucket.exists` and `RBucket.existsAsync` methods added Feature - `RMap.addAndGet` method added Feature - database index via `database` and operation timeout via `timeout` config params added @@ -644,40 +663,40 @@ Fixed - HashedWheelTimer shutdown Fixed - `RLock` race conditions (thanks to jsotuyod and AndrewKolpakov) Fixed - `RCountDownLatch` race conditions -####23-Jul-2014 - version 1.1.5 released +### 23-Jul-2014 - version 1.1.5 released Feature - operations auto-retry. `retryAttempts` and `retryInterval` params added for each connection type Feature - `RMap.filterEntries`, `RMap.getAll`, `RMap.filterKeys`, `RMap.filterValues` methods added Feature - `RMap.fastRemove`, `RMap.fastRemoveAsync`, `RMap.fastPut` & `RMap.fastPutAsync` methods added Fixed - async operations timeout handling Fixed - sorting algorithm used in `RSortedSet`. -####15-Jul-2014 - version 1.1.4 released +### 15-Jul-2014 - version 1.1.4 released Feature - new `RLock.lockInterruptibly`, `RLock.tryLock`, `RLock.lock` methods with TTL support Fixed - pub/sub connections reattach then slave/master down Fixed - turn off connection watchdog then slave/master down Fixed - sentinel master switch Fixed - slave down connection closing -####13-Jul-2014 - version 1.1.3 released +### 13-Jul-2014 - version 1.1.3 released Improvement - RedissonCountDownLatch optimization Improvement - RedissonLock optimization Fixed - RedissonLock thread-safety Fixed - master/slave auth using Sentinel servers Fixed - slave down handling using Sentinel servers -####03-Jul-2014 - version 1.1.2 released +### 03-Jul-2014 - version 1.1.2 released Improvement - RedissonSet.iterator implemented with sscan Improvement - RedissonSortedSet.iterator optimization Feature - `RSortedSet.removeAsync`, `RSortedSet.addAsync`, `RSet.removeAsync`, RSet.addAsync methods added Feature - slave up/down detection in Sentinel servers connection mode Feature - new-slave automatic discovery in Sentinel servers connection mode -####17-June-2014 - version 1.1.1 released +### 17-June-2014 - version 1.1.1 released Feature - sentinel servers support Fixed - connection leak in `RTopic` Fixed - setted password not used in single server connection -####07-June-2014 - version 1.1.0 released +### 07-June-2014 - version 1.1.0 released Feature - master/slave connection management Feature - simple set/get object support via `org.redisson.core.RBucket` Feature - hyperloglog support via `org.redisson.core.RHyperLogLog` @@ -691,24 +710,24 @@ Fixed - `RTopic.publish` now returns the number of clients that received the mes Fixed - reconnection handling (thanks to renzihui) Improvement - `org.redisson.core.RTopic` now use lazy apporach for subscribe/unsubscribe -####04-May-2014 - version 1.0.4 released +### 04-May-2014 - version 1.0.4 released Feature - distributed implementation of `java.util.Deque` Feature - some objects implements `org.redisson.core.RExpirable` Fixed - JsonJacksonCodec lazy init -####26-Mar-2014 - version 1.0.3 released +### 26-Mar-2014 - version 1.0.3 released Fixed - RedissonAtomicLong state format Fixed - Long serialization in JsonJacksonCodec -####05-Feb-2014 - version 1.0.2 released +### 05-Feb-2014 - version 1.0.2 released Feature - distributed implementation of `java.util.SortedSet` Fixed - OSGi compability -####17-Jan-2014 - version 1.0.1 released +### 17-Jan-2014 - version 1.0.1 released Improvement - forceUnlock, isLocked, isHeldByCurrentThread and getHoldCount methods added to RLock Feature - connection load balancer to use multiple Redis servers Feature - published in maven central repo -####11-Jan-2014 - version 1.0.0 released +### 11-Jan-2014 - version 1.0.0 released First stable release. diff --git a/README.md b/README.md index 21d1f0691..eb5f8909a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ Redisson: Redis based In-Memory Data Grid for Java. ==== -[Quick start](https://github.com/redisson/redisson#quick-start) | [Documentation](https://github.com/redisson/redisson/wiki) | [Javadocs](http://www.javadoc.io/doc/org.redisson/redisson/3.3.1) | [Changelog](https://github.com/redisson/redisson/blob/master/CHANGELOG.md) | [Code examples](https://github.com/redisson/redisson-examples) | [Support chat](https://gitter.im/mrniko/redisson) +[Quick start](https://github.com/redisson/redisson#quick-start) | [Documentation](https://github.com/redisson/redisson/wiki) | [Javadocs](http://www.javadoc.io/doc/org.redisson/redisson/3.3.2) | [Changelog](https://github.com/redisson/redisson/blob/master/CHANGELOG.md) | [Code examples](https://github.com/redisson/redisson-examples) | [Support chat](https://gitter.im/mrniko/redisson) | [Ultra-fast version](https://redisson.pro) Based on high-performance async and lock-free Java Redis client and [Netty](http://netty.io) framework. -Redis 2.8+ compatible. +## Please take part in [Redisson survey](https://www.surveymonkey.com/r/QXQZH5D) | Stable Release Version | JDK Version compatibility | Release Date | | ------------- | ------------- | ------------| -| 3.3.1 | 1.8+ | 03.03.2017 | -| 2.8.1 | 1.6, 1.7, 1.8 and Android | 03.03.2017 | +| 3.3.2 | 1.8+ | 21.03.2017 | +| 2.8.2 | 1.6, 1.7, 1.8 and Android | 21.03.2017 | __NOTE__: Both version lines have same features except `CompletionStage` interface added in 3.x.x @@ -55,7 +55,7 @@ Features Who uses Redisson ================================ -[Electronic Arts](http://ea.com), [Baidu](http://baidu.com), [New Relic Synthetics](https://newrelic.com/synthetics), [Brookhaven National Laboratory](http://bnl.gov/), [Singtel](http://singtel.com), [Infor](http://www.infor.com/), [Setronica](http://setronica.com/), [Monits](http://monits.com/), [Netflix Dyno client] (https://github.com/Netflix/dyno), [武林Q传](http://www.nbrpg.com/), [Ocous](http://www.ocous.com/), [Invaluable](http://www.invaluable.com/), [Clover](https://www.clover.com/) , [Apache Karaf Decanter](https://karaf.apache.org/projects.html#decanter), [Atmosphere Framework](http://async-io.org/), [BrandsEye](http://brandseye.com), [Datorama](http://datorama.com/), [BrightCloud](http://brightcloud.com/), [Azar](http://azarlive.com/), [Snapfish](http://snapfish.com), [Crimson Hexagon](http://www.crimsonhexagon.com), [Quby](http://quby.com/), [Base CRM](http://getbase.com) +[Electronic Arts](http://ea.com), [Baidu](http://baidu.com), [New Relic Synthetics](https://newrelic.com/synthetics), [Brookhaven National Laboratory](http://bnl.gov/), [Singtel](http://singtel.com), [Infor](http://www.infor.com/), [Netflix Dyno client](https://github.com/Netflix/dyno), [Ocous](http://www.ocous.com/), [Invaluable](http://www.invaluable.com/), [Clover](https://www.clover.com/) , [Apache Karaf Decanter](https://karaf.apache.org/projects.html#decanter), [Atmosphere Framework](http://async-io.org/), [BrandsEye](http://brandseye.com), [Datorama](http://datorama.com/), [BrightCloud](http://brightcloud.com/), [Azar](http://azarlive.com/), [Snapfish](http://snapfish.com), [Crimson Hexagon](http://www.crimsonhexagon.com), [Quby](http://quby.com/), [Base CRM](http://getbase.com) Articles ================================ @@ -82,23 +82,23 @@ Quick start org.redisson redisson - 3.3.1 + 3.3.2 org.redisson redisson - 2.8.1 + 2.8.2 #### Gradle // JDK 1.8+ compatible - compile 'org.redisson:redisson:3.3.1' + compile 'org.redisson:redisson:3.3.2' // JDK 1.6+ compatible - compile 'org.redisson:redisson:2.8.1' + compile 'org.redisson:redisson:2.8.2' #### Java @@ -123,11 +123,11 @@ RExecutorService executor = redisson.getExecutorService("myExecutorService"); Downloads =============================== -[Redisson 3.3.1](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=3.3.1&e=jar), -[Redisson node 3.3.1](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.3.1&e=jar) +[Redisson 3.3.2](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=3.3.2&e=jar), +[Redisson node 3.3.2](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.3.2&e=jar) -[Redisson 2.8.1](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.8.1&e=jar), -[Redisson node 2.8.1](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.8.1&e=jar) +[Redisson 2.8.2](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson&v=2.8.2&e=jar), +[Redisson node 2.8.2](https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=2.8.2&e=jar) ### Supported by diff --git a/pom.xml b/pom.xml index d819cfc88..716332c67 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.redisson redisson-parent - 2.8.2-SNAPSHOT + 2.8.3-SNAPSHOT pom Redisson diff --git a/redisson-all/pom.xml b/redisson-all/pom.xml index 9d40c05a9..f02132f22 100644 --- a/redisson-all/pom.xml +++ b/redisson-all/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-parent - 2.8.2-SNAPSHOT + 2.8.3-SNAPSHOT ../ @@ -156,7 +156,12 @@ [3.1,) provided - + + org.springframework.boot + spring-boot-actuator + [1.4,) + provided + diff --git a/redisson-tomcat/pom.xml b/redisson-tomcat/pom.xml index 32d8f3c10..c74233f23 100644 --- a/redisson-tomcat/pom.xml +++ b/redisson-tomcat/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-parent - 2.8.2-SNAPSHOT + 2.8.3-SNAPSHOT ../ diff --git a/redisson-tomcat/redisson-tomcat-6/pom.xml b/redisson-tomcat/redisson-tomcat-6/pom.xml index 3a77822e7..00c229ad3 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 - 2.8.2-SNAPSHOT + 2.8.3-SNAPSHOT ../ diff --git a/redisson-tomcat/redisson-tomcat-7/pom.xml b/redisson-tomcat/redisson-tomcat-7/pom.xml index e5bb47f02..c8b357e07 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 - 2.8.2-SNAPSHOT + 2.8.3-SNAPSHOT ../ diff --git a/redisson-tomcat/redisson-tomcat-8/pom.xml b/redisson-tomcat/redisson-tomcat-8/pom.xml index dd91cca2c..f592d9d91 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 - 2.8.2-SNAPSHOT + 2.8.3-SNAPSHOT ../ diff --git a/redisson/pom.xml b/redisson/pom.xml index 958504053..19564494b 100644 --- a/redisson/pom.xml +++ b/redisson/pom.xml @@ -4,7 +4,7 @@ org.redisson redisson-parent - 2.8.2-SNAPSHOT + 2.8.3-SNAPSHOT ../ @@ -186,36 +186,36 @@ com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.8.7 + 2.6.7 com.fasterxml.jackson.core jackson-core - 2.8.7 + 2.6.7 com.fasterxml.jackson.core jackson-databind - 2.8.7 + 2.6.7 com.fasterxml.jackson.dataformat jackson-dataformat-cbor - 2.8.7 + 2.6.7 provided true com.fasterxml.jackson.dataformat jackson-dataformat-smile - 2.8.7 + 2.6.7 provided true com.fasterxml.jackson.dataformat jackson-dataformat-avro - 2.8.7 + 2.6.7 provided true @@ -258,6 +258,13 @@ provided true + + org.springframework.boot + spring-boot-actuator + [1.4,) + provided + true + diff --git a/redisson/src/main/java/org/redisson/PubSubPatternStatusListener.java b/redisson/src/main/java/org/redisson/PubSubPatternStatusListener.java index 41b915e9f..c2d0f5562 100644 --- a/redisson/src/main/java/org/redisson/PubSubPatternStatusListener.java +++ b/redisson/src/main/java/org/redisson/PubSubPatternStatusListener.java @@ -75,12 +75,15 @@ public class PubSubPatternStatusListener implements RedisPubSubListener { @Override public boolean onStatus(PubSubType type, String channel) { - if (type == PubSubType.PSUBSCRIBE) { - listener.onPSubscribe(channel); - } else if (type == PubSubType.PUNSUBSCRIBE) { - listener.onPUnsubscribe(channel); + if (channel.equals(name)) { + if (type == PubSubType.PSUBSCRIBE) { + listener.onPSubscribe(channel); + } else if (type == PubSubType.PUNSUBSCRIBE) { + listener.onPUnsubscribe(channel); + } + return true; } - return true; + return false; } } diff --git a/redisson/src/main/java/org/redisson/PubSubStatusListener.java b/redisson/src/main/java/org/redisson/PubSubStatusListener.java index 43335f4c7..78b1abcab 100644 --- a/redisson/src/main/java/org/redisson/PubSubStatusListener.java +++ b/redisson/src/main/java/org/redisson/PubSubStatusListener.java @@ -75,12 +75,15 @@ public class PubSubStatusListener implements RedisPubSubListener { @Override public boolean onStatus(PubSubType type, String channel) { - if (type == PubSubType.SUBSCRIBE) { - listener.onSubscribe(channel); - } else if (type == PubSubType.UNSUBSCRIBE) { - listener.onUnsubscribe(channel); + if (channel.equals(name)) { + if (type == PubSubType.SUBSCRIBE) { + listener.onSubscribe(channel); + } else if (type == PubSubType.UNSUBSCRIBE) { + listener.onUnsubscribe(channel); + } + return true; } - return true; + return false; } } diff --git a/redisson/src/main/java/org/redisson/Redisson.java b/redisson/src/main/java/org/redisson/Redisson.java index 2fc4a4c2d..f3b225ed3 100755 --- a/redisson/src/main/java/org/redisson/Redisson.java +++ b/redisson/src/main/java/org/redisson/Redisson.java @@ -193,12 +193,12 @@ public class Redisson implements RedissonClient { @Override public RGeo getGeo(String name) { - return new RedissonGeo(connectionManager.getCommandExecutor(), name); + return new RedissonGeo(connectionManager.getCommandExecutor(), name, this); } @Override public RGeo getGeo(String name, Codec codec) { - return new RedissonGeo(codec, connectionManager.getCommandExecutor(), name); + return new RedissonGeo(codec, connectionManager.getCommandExecutor(), name, this); } @Override @@ -233,12 +233,12 @@ public class Redisson implements RedissonClient { @Override public RList getList(String name) { - return new RedissonList(connectionManager.getCommandExecutor(), name); + return new RedissonList(connectionManager.getCommandExecutor(), name, this); } @Override public RList getList(String name, Codec codec) { - return new RedissonList(codec, connectionManager.getCommandExecutor(), name); + return new RedissonList(codec, connectionManager.getCommandExecutor(), name, this); } @Override @@ -253,17 +253,17 @@ public class Redisson implements RedissonClient { @Override public RLocalCachedMap getLocalCachedMap(String name, LocalCachedMapOptions options) { - return new RedissonLocalCachedMap(id, connectionManager.getCommandExecutor(), name, options); + return new RedissonLocalCachedMap(id, connectionManager.getCommandExecutor(), name, options, evictionScheduler, this); } @Override public RLocalCachedMap getLocalCachedMap(String name, Codec codec, LocalCachedMapOptions options) { - return new RedissonLocalCachedMap(id, codec, connectionManager.getCommandExecutor(), name, options); + return new RedissonLocalCachedMap(id, codec, connectionManager.getCommandExecutor(), name, options, evictionScheduler, this); } @Override public RMap getMap(String name) { - return new RedissonMap(id, connectionManager.getCommandExecutor(), name); + return new RedissonMap(id, connectionManager.getCommandExecutor(), name, this); } @Override @@ -298,27 +298,27 @@ public class Redisson implements RedissonClient { @Override public RSetCache getSetCache(String name) { - return new RedissonSetCache(evictionScheduler, connectionManager.getCommandExecutor(), name); + return new RedissonSetCache(evictionScheduler, connectionManager.getCommandExecutor(), name, this); } @Override public RSetCache getSetCache(String name, Codec codec) { - return new RedissonSetCache(codec, evictionScheduler, connectionManager.getCommandExecutor(), name); + return new RedissonSetCache(codec, evictionScheduler, connectionManager.getCommandExecutor(), name, this); } @Override public RMapCache getMapCache(String name) { - return new RedissonMapCache(id, evictionScheduler, connectionManager.getCommandExecutor(), name); + return new RedissonMapCache(id, evictionScheduler, connectionManager.getCommandExecutor(), name, this); } @Override public RMapCache getMapCache(String name, Codec codec) { - return new RedissonMapCache(id, codec, evictionScheduler, connectionManager.getCommandExecutor(), name); + return new RedissonMapCache(id, codec, evictionScheduler, connectionManager.getCommandExecutor(), name, this); } @Override public RMap getMap(String name, Codec codec) { - return new RedissonMap(id, codec, connectionManager.getCommandExecutor(), name); + return new RedissonMap(id, codec, connectionManager.getCommandExecutor(), name, this); } @Override @@ -338,12 +338,12 @@ public class Redisson implements RedissonClient { @Override public RSet getSet(String name) { - return new RedissonSet(connectionManager.getCommandExecutor(), name); + return new RedissonSet(connectionManager.getCommandExecutor(), name, this); } @Override public RSet getSet(String name, Codec codec) { - return new RedissonSet(codec, connectionManager.getCommandExecutor(), name); + return new RedissonSet(codec, connectionManager.getCommandExecutor(), name, this); } @Override @@ -399,17 +399,17 @@ public class Redisson implements RedissonClient { @Override public RScoredSortedSet getScoredSortedSet(String name) { - return new RedissonScoredSortedSet(connectionManager.getCommandExecutor(), name); + return new RedissonScoredSortedSet(connectionManager.getCommandExecutor(), name, this); } @Override public RScoredSortedSet getScoredSortedSet(String name, Codec codec) { - return new RedissonScoredSortedSet(codec, connectionManager.getCommandExecutor(), name); + return new RedissonScoredSortedSet(codec, connectionManager.getCommandExecutor(), name, this); } @Override public RLexSortedSet getLexSortedSet(String name) { - return new RedissonLexSortedSet(connectionManager.getCommandExecutor(), name); + return new RedissonLexSortedSet(connectionManager.getCommandExecutor(), name, this); } @Override @@ -434,12 +434,12 @@ public class Redisson implements RedissonClient { @Override public RBlockingFairQueue getBlockingFairQueue(String name) { - return new RedissonBlockingFairQueue(connectionManager.getCommandExecutor(), name, semaphorePubSub, id); + return new RedissonBlockingFairQueue(connectionManager.getCommandExecutor(), name, semaphorePubSub, id, this); } @Override public RBlockingFairQueue getBlockingFairQueue(String name, Codec codec) { - return new RedissonBlockingFairQueue(codec, connectionManager.getCommandExecutor(), name, semaphorePubSub, id); + return new RedissonBlockingFairQueue(codec, connectionManager.getCommandExecutor(), name, semaphorePubSub, id, this); } @Override @@ -452,52 +452,52 @@ public class Redisson implements RedissonClient { @Override public RQueue getQueue(String name) { - return new RedissonQueue(connectionManager.getCommandExecutor(), name); + return new RedissonQueue(connectionManager.getCommandExecutor(), name, this); } @Override public RQueue getQueue(String name, Codec codec) { - return new RedissonQueue(codec, connectionManager.getCommandExecutor(), name); + return new RedissonQueue(codec, connectionManager.getCommandExecutor(), name, this); } @Override public RBlockingQueue getBlockingQueue(String name) { - return new RedissonBlockingQueue(connectionManager.getCommandExecutor(), name); + return new RedissonBlockingQueue(connectionManager.getCommandExecutor(), name, this); } @Override public RBlockingQueue getBlockingQueue(String name, Codec codec) { - return new RedissonBlockingQueue(codec, connectionManager.getCommandExecutor(), name); + return new RedissonBlockingQueue(codec, connectionManager.getCommandExecutor(), name, this); } @Override public RBoundedBlockingQueue getBoundedBlockingQueue(String name) { - return new RedissonBoundedBlockingQueue(semaphorePubSub, connectionManager.getCommandExecutor(), name); + return new RedissonBoundedBlockingQueue(semaphorePubSub, connectionManager.getCommandExecutor(), name, this); } @Override public RBoundedBlockingQueue getBoundedBlockingQueue(String name, Codec codec) { - return new RedissonBoundedBlockingQueue(semaphorePubSub, codec, connectionManager.getCommandExecutor(), name); + return new RedissonBoundedBlockingQueue(semaphorePubSub, codec, connectionManager.getCommandExecutor(), name, this); } @Override public RDeque getDeque(String name) { - return new RedissonDeque(connectionManager.getCommandExecutor(), name); + return new RedissonDeque(connectionManager.getCommandExecutor(), name, this); } @Override public RDeque getDeque(String name, Codec codec) { - return new RedissonDeque(codec, connectionManager.getCommandExecutor(), name); + return new RedissonDeque(codec, connectionManager.getCommandExecutor(), name, this); } @Override public RBlockingDeque getBlockingDeque(String name) { - return new RedissonBlockingDeque(connectionManager.getCommandExecutor(), name); + return new RedissonBlockingDeque(connectionManager.getCommandExecutor(), name, this); } @Override public RBlockingDeque getBlockingDeque(String name, Codec codec) { - return new RedissonBlockingDeque(codec, connectionManager.getCommandExecutor(), name); + return new RedissonBlockingDeque(codec, connectionManager.getCommandExecutor(), name, this); }; @Override diff --git a/redisson/src/main/java/org/redisson/RedissonBatch.java b/redisson/src/main/java/org/redisson/RedissonBatch.java index a75a028ab..b25b07f50 100644 --- a/redisson/src/main/java/org/redisson/RedissonBatch.java +++ b/redisson/src/main/java/org/redisson/RedissonBatch.java @@ -17,6 +17,7 @@ package org.redisson; import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; import org.redisson.api.RAtomicDoubleAsync; import org.redisson.api.RAtomicLongAsync; @@ -42,7 +43,6 @@ import org.redisson.api.RScriptAsync; import org.redisson.api.RSetAsync; import org.redisson.api.RSetCacheAsync; import org.redisson.api.RTopicAsync; -import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.command.CommandBatchService; import org.redisson.connection.ConnectionManager; @@ -60,6 +60,10 @@ public class RedissonBatch implements RBatch { private final CommandBatchService executorService; private final UUID id; + private long timeout; + private int retryAttempts; + private long retryInterval; + protected RedissonBatch(UUID id, EvictionScheduler evictionScheduler, ConnectionManager connectionManager) { this.executorService = new CommandBatchService(connectionManager); this.evictionScheduler = evictionScheduler; @@ -88,32 +92,32 @@ public class RedissonBatch implements RBatch { @Override public RListAsync getList(String name) { - return new RedissonList(executorService, name); + return new RedissonList(executorService, name, null); } @Override public RListAsync getList(String name, Codec codec) { - return new RedissonList(codec, executorService, name); + return new RedissonList(codec, executorService, name, null); } @Override public RMapAsync getMap(String name) { - return new RedissonMap(id, executorService, name); + return new RedissonMap(id, executorService, name, null); } @Override public RMapAsync getMap(String name, Codec codec) { - return new RedissonMap(id, codec, executorService, name); + return new RedissonMap(id, codec, executorService, name, null); } @Override public RSetAsync getSet(String name) { - return new RedissonSet(executorService, name); + return new RedissonSet(executorService, name, null); } @Override public RSetAsync getSet(String name, Codec codec) { - return new RedissonSet(codec, executorService, name); + return new RedissonSet(codec, executorService, name, null); } @Override @@ -128,42 +132,42 @@ public class RedissonBatch implements RBatch { @Override public RQueueAsync getQueue(String name) { - return new RedissonQueue(executorService, name); + return new RedissonQueue(executorService, name, null); } @Override public RQueueAsync getQueue(String name, Codec codec) { - return new RedissonQueue(codec, executorService, name); + return new RedissonQueue(codec, executorService, name, null); } @Override public RBlockingQueueAsync getBlockingQueue(String name) { - return new RedissonBlockingQueue(executorService, name); + return new RedissonBlockingQueue(executorService, name, null); } @Override public RBlockingQueueAsync getBlockingQueue(String name, Codec codec) { - return new RedissonBlockingQueue(codec, executorService, name); + return new RedissonBlockingQueue(codec, executorService, name, null); } @Override public RBlockingDequeAsync getBlockingDeque(String name) { - return new RedissonBlockingDeque(executorService, name); + return new RedissonBlockingDeque(executorService, name, null); } @Override public RBlockingDequeAsync getBlockingDeque(String name, Codec codec) { - return new RedissonBlockingDeque(codec, executorService, name); + return new RedissonBlockingDeque(codec, executorService, name, null); } @Override public RDequeAsync getDeque(String name) { - return new RedissonDeque(executorService, name); + return new RedissonDeque(executorService, name, null); } @Override public RDequeAsync getDeque(String name, Codec codec) { - return new RedissonDeque(codec, executorService, name); + return new RedissonDeque(codec, executorService, name, null); } @Override @@ -178,17 +182,17 @@ public class RedissonBatch implements RBatch { @Override public RScoredSortedSetAsync getScoredSortedSet(String name) { - return new RedissonScoredSortedSet(executorService, name); + return new RedissonScoredSortedSet(executorService, name, null); } @Override public RScoredSortedSetAsync getScoredSortedSet(String name, Codec codec) { - return new RedissonScoredSortedSet(codec, executorService, name); + return new RedissonScoredSortedSet(codec, executorService, name, null); } @Override public RLexSortedSetAsync getLexSortedSet(String name) { - return new RedissonLexSortedSet(executorService, name); + return new RedissonLexSortedSet(executorService, name, null); } @Override @@ -198,12 +202,12 @@ public class RedissonBatch implements RBatch { @Override public RMapCacheAsync getMapCache(String name, Codec codec) { - return new RedissonMapCache(id, codec, evictionScheduler, executorService, name); + return new RedissonMapCache(id, codec, evictionScheduler, executorService, name, null); } @Override public RMapCacheAsync getMapCache(String name) { - return new RedissonMapCache(id, evictionScheduler, executorService, name); + return new RedissonMapCache(id, evictionScheduler, executorService, name, null); } @Override @@ -218,32 +222,50 @@ public class RedissonBatch implements RBatch { @Override public RSetCacheAsync getSetCache(String name) { - return new RedissonSetCache(evictionScheduler, executorService, name); + return new RedissonSetCache(evictionScheduler, executorService, name, null); } @Override public RSetCacheAsync getSetCache(String name, Codec codec) { - return new RedissonSetCache(codec, evictionScheduler, executorService, name); + return new RedissonSetCache(codec, evictionScheduler, executorService, name, null); } + @Override + public RBatch retryAttempts(int retryAttempts) { + this.retryAttempts = retryAttempts; + return this; + } + + @Override + public RBatch retryInterval(long retryInterval, TimeUnit unit) { + this.retryInterval = unit.toMillis(retryInterval); + return this; + } + + @Override + public RBatch timeout(long timeout, TimeUnit unit) { + this.timeout = unit.toMillis(timeout); + return this; + } + @Override public List execute() { - return executorService.execute(); + return executorService.execute(timeout, retryAttempts, retryInterval); } @Override public void executeSkipResult() { - executorService.executeSkipResult(); + executorService.executeSkipResult(timeout, retryAttempts, retryInterval); } @Override public RFuture executeSkipResultAsync() { - return executorService.executeSkipResultAsync(); + return executorService.executeSkipResultAsync(timeout, retryAttempts, retryInterval); } @Override public RFuture> executeAsync() { - return executorService.executeAsync(); + return executorService.executeAsync(timeout, retryAttempts, retryInterval); } @Override @@ -268,12 +290,12 @@ public class RedissonBatch implements RBatch { @Override public RGeoAsync getGeo(String name) { - return new RedissonGeo(executorService, name); + return new RedissonGeo(executorService, name, null); } @Override public RGeoAsync getGeo(String name, Codec codec) { - return new RedissonGeo(codec, executorService, name); + return new RedissonGeo(codec, executorService, name, null); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonBlockingDeque.java b/redisson/src/main/java/org/redisson/RedissonBlockingDeque.java index 49a51da4f..5699208b0 100644 --- a/redisson/src/main/java/org/redisson/RedissonBlockingDeque.java +++ b/redisson/src/main/java/org/redisson/RedissonBlockingDeque.java @@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RBlockingDeque; import org.redisson.api.RFuture; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandAsyncExecutor; @@ -38,14 +39,14 @@ public class RedissonBlockingDeque extends RedissonDeque implements RBlock private final RedissonBlockingQueue blockingQueue; - protected RedissonBlockingDeque(CommandAsyncExecutor commandExecutor, String name) { - super(commandExecutor, name); - blockingQueue = new RedissonBlockingQueue(commandExecutor, name); + protected RedissonBlockingDeque(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(commandExecutor, name, redisson); + blockingQueue = new RedissonBlockingQueue(commandExecutor, name, redisson); } - protected RedissonBlockingDeque(Codec codec, CommandAsyncExecutor commandExecutor, String name) { - super(codec, commandExecutor, name); - blockingQueue = new RedissonBlockingQueue(codec, commandExecutor, name); + protected RedissonBlockingDeque(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(codec, commandExecutor, name, redisson); + blockingQueue = new RedissonBlockingQueue(codec, commandExecutor, name, redisson); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonBlockingFairQueue.java b/redisson/src/main/java/org/redisson/RedissonBlockingFairQueue.java index c82214cf8..2eb562609 100644 --- a/redisson/src/main/java/org/redisson/RedissonBlockingFairQueue.java +++ b/redisson/src/main/java/org/redisson/RedissonBlockingFairQueue.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.redisson.api.RBlockingFairQueue; import org.redisson.api.RFuture; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.StringCodec; @@ -51,15 +52,15 @@ public class RedissonBlockingFairQueue extends RedissonBlockingQueue imple private final AtomicInteger instances = new AtomicInteger(); private final SemaphorePubSub semaphorePubSub; - protected RedissonBlockingFairQueue(CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub, UUID id) { - super(commandExecutor, name); + protected RedissonBlockingFairQueue(CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub, UUID id, RedissonClient redisson) { + super(commandExecutor, name, redisson); this.semaphorePubSub = semaphorePubSub; this.id = id; instances.incrementAndGet(); } - protected RedissonBlockingFairQueue(Codec codec, CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub, UUID id) { - super(codec, commandExecutor, name); + protected RedissonBlockingFairQueue(Codec codec, CommandExecutor commandExecutor, String name, SemaphorePubSub semaphorePubSub, UUID id, RedissonClient redisson) { + super(codec, commandExecutor, name, redisson); this.semaphorePubSub = semaphorePubSub; this.id = id; instances.incrementAndGet(); diff --git a/redisson/src/main/java/org/redisson/RedissonBlockingQueue.java b/redisson/src/main/java/org/redisson/RedissonBlockingQueue.java index 3ecfc38ce..46d8b6e0f 100644 --- a/redisson/src/main/java/org/redisson/RedissonBlockingQueue.java +++ b/redisson/src/main/java/org/redisson/RedissonBlockingQueue.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RBlockingQueue; import org.redisson.api.RFuture; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommands; @@ -40,12 +41,12 @@ import org.redisson.connection.decoder.ListDrainToDecoder; */ public class RedissonBlockingQueue extends RedissonQueue implements RBlockingQueue { - protected RedissonBlockingQueue(CommandAsyncExecutor commandExecutor, String name) { - super(commandExecutor, name); + protected RedissonBlockingQueue(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(commandExecutor, name, redisson); } - protected RedissonBlockingQueue(Codec codec, CommandAsyncExecutor commandExecutor, String name) { - super(codec, commandExecutor, name); + protected RedissonBlockingQueue(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(codec, commandExecutor, name, redisson); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java b/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java index 4b1fc9747..f349774aa 100644 --- a/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java +++ b/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RBoundedBlockingQueue; import org.redisson.api.RFuture; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; import org.redisson.client.protocol.RedisCommand; @@ -47,14 +48,14 @@ public class RedissonBoundedBlockingQueue extends RedissonQueue implements private final CommandExecutor commandExecutor; private final SemaphorePubSub semaphorePubSub; - protected RedissonBoundedBlockingQueue(SemaphorePubSub semaphorePubSub, CommandExecutor commandExecutor, String name) { - super(commandExecutor, name); + protected RedissonBoundedBlockingQueue(SemaphorePubSub semaphorePubSub, CommandExecutor commandExecutor, String name, RedissonClient redisson) { + super(commandExecutor, name, redisson); this.semaphorePubSub = semaphorePubSub; this.commandExecutor = commandExecutor; } - protected RedissonBoundedBlockingQueue(SemaphorePubSub semaphorePubSub, Codec codec, CommandExecutor commandExecutor, String name) { - super(codec, commandExecutor, name); + protected RedissonBoundedBlockingQueue(SemaphorePubSub semaphorePubSub, Codec codec, CommandExecutor commandExecutor, String name, RedissonClient redisson) { + super(codec, commandExecutor, name, redisson); this.semaphorePubSub = semaphorePubSub; this.commandExecutor = commandExecutor; } diff --git a/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java b/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java index 595cdc7f2..cc1c8f684 100644 --- a/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java +++ b/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java @@ -492,11 +492,6 @@ public class RedissonDelayedQueue extends RedissonExpirable implements RDelay return get(pollLastAndOfferFirstToAsync(dequeName)); } - @Override - public V pollLastAndOfferFirstTo(RQueue deque) { - return get(pollLastAndOfferFirstToAsync(deque.getName())); - } - @Override public void destroy() { queueTransferService.remove(getQueueName()); diff --git a/redisson/src/main/java/org/redisson/RedissonDeque.java b/redisson/src/main/java/org/redisson/RedissonDeque.java index 0b4e05e16..4a8632b9f 100644 --- a/redisson/src/main/java/org/redisson/RedissonDeque.java +++ b/redisson/src/main/java/org/redisson/RedissonDeque.java @@ -20,6 +20,7 @@ import java.util.NoSuchElementException; import org.redisson.api.RDeque; import org.redisson.api.RFuture; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand.ValueType; @@ -41,12 +42,12 @@ public class RedissonDeque extends RedissonQueue implements RDeque { private static final RedisCommand LRANGE_SINGLE = new RedisCommand("LRANGE", new ListFirstObjectDecoder()); - protected RedissonDeque(CommandAsyncExecutor commandExecutor, String name) { - super(commandExecutor, name); + protected RedissonDeque(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(commandExecutor, name, redisson); } - public RedissonDeque(Codec codec, CommandAsyncExecutor commandExecutor, String name) { - super(codec, commandExecutor, name); + public RedissonDeque(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(codec, commandExecutor, name, redisson); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonExecutorService.java b/redisson/src/main/java/org/redisson/RedissonExecutorService.java index 94addf097..d7434f049 100644 --- a/redisson/src/main/java/org/redisson/RedissonExecutorService.java +++ b/redisson/src/main/java/org/redisson/RedissonExecutorService.java @@ -18,12 +18,10 @@ package org.redisson; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -35,20 +33,19 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.redisson.api.CronSchedule; +import org.redisson.api.RAtomicLong; import org.redisson.api.RFuture; +import org.redisson.api.RRemoteService; import org.redisson.api.RScheduledExecutorService; import org.redisson.api.RScheduledFuture; +import org.redisson.api.RSemaphore; import org.redisson.api.RTopic; -import org.redisson.api.RedissonClient; import org.redisson.api.RemoteInvocationOptions; -import org.redisson.api.annotation.RInject; -import org.redisson.api.listener.BaseStatusListener; import org.redisson.api.listener.MessageListener; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; @@ -62,14 +59,15 @@ import org.redisson.executor.RemoteExecutorServiceAsync; import org.redisson.executor.RemoteExecutorServiceImpl; import org.redisson.executor.RemotePromise; import org.redisson.executor.ScheduledExecutorRemoteService; +import org.redisson.misc.Injector; import org.redisson.misc.RPromise; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.netty.util.Timeout; -import io.netty.util.TimerTask; +import io.netty.buffer.ByteBufUtil; import io.netty.util.concurrent.FutureListener; import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.ThreadLocalRandom; /** * @@ -92,9 +90,16 @@ public class RedissonExecutorService implements RScheduledExecutorService { private final String schedulerQueueName; private final String schedulerChannelName; + private final String workersChannelName; + private final String workersSemaphoreName; + private final String workersCounterName; + private final String tasksCounterName; private final String statusName; private final RTopic terminationTopic; + private final RRemoteService remoteService; + private final RTopic workersTopic; + private int workersGroupListenerId; private final RemoteExecutorServiceAsync asyncScheduledService; private final RemoteExecutorServiceAsync asyncScheduledServiceAtFixed; @@ -125,6 +130,13 @@ public class RedissonExecutorService implements RScheduledExecutorService { schedulerQueueName = objectName + ":scheduler"; schedulerTasksName = objectName + ":scheduler-tasks"; + workersChannelName = objectName + ":workers-channel"; + workersSemaphoreName = objectName + ":workers-semaphore"; + workersCounterName = objectName + ":workers-counter"; + + remoteService = redisson.getRemoteService(name, codec); + workersTopic = redisson.getTopic(workersChannelName); + ExecutorRemoteService remoteService = new ExecutorRemoteService(codec, redisson, name, commandExecutor); remoteService.setTerminationTopicName(terminationTopic.getChannelNames().get(0)); remoteService.setTasksCounterName(tasksCounterName); @@ -143,13 +155,36 @@ public class RedissonExecutorService implements RScheduledExecutorService { asyncScheduledServiceAtFixed = scheduledRemoteService.get(RemoteExecutorServiceAsync.class, RemoteInvocationOptions.defaults().noAck().noResult()); } + protected String generateRequestId() { + byte[] id = new byte[16]; + // TODO JDK UPGRADE replace to native ThreadLocalRandom + ThreadLocalRandom.current().nextBytes(id); + return ByteBufUtil.hexDump(id); + } + + @Override + public int countActiveWorkers() { + String id = generateRequestId(); + int subscribers = (int) workersTopic.publish(id); + RSemaphore semaphore = redisson.getSemaphore(workersSemaphoreName + ":" + id); + try { + semaphore.tryAcquire(subscribers, 10, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + RAtomicLong atomicLong = redisson.getAtomicLong(workersCounterName + ":" + id); + long result = atomicLong.get(); + redisson.getKeys().delete(semaphore, atomicLong); + return (int) result; + } + @Override public void registerWorkers(int workers) { registerWorkers(workers, commandExecutor.getConnectionManager().getExecutor()); } @Override - public void registerWorkers(int workers, ExecutorService executor) { + public void registerWorkers(final int workers, ExecutorService executor) { QueueTransferTask scheduler = new QueueTransferTask(connectionManager) { @Override protected RTopic getTopic() { @@ -186,7 +221,14 @@ public class RedissonExecutorService implements RScheduledExecutorService { service.setSchedulerChannelName(schedulerChannelName); service.setSchedulerQueueName(schedulerQueueName); - redisson.getRemoteService(name, codec).register(RemoteExecutorService.class, service, workers, executor); + remoteService.register(RemoteExecutorService.class, service, workers, executor); + workersGroupListenerId = workersTopic.addListener(new MessageListener() { + @Override + public void onMessage(String channel, String id) { + redisson.getAtomicLong(workersCounterName + ":" + id).getAndAdd(workers); + redisson.getSemaphore(workersSemaphoreName + ":" + id).release(); + } + }); } @Override @@ -200,18 +242,7 @@ public class RedissonExecutorService implements RScheduledExecutorService { private byte[] encode(Object task) { // erase RedissonClient field to avoid its serialization - Field[] fields = task.getClass().getDeclaredFields(); - for (Field field : fields) { - if (RedissonClient.class.isAssignableFrom(field.getType()) - && field.isAnnotationPresent(RInject.class)) { - field.setAccessible(true); - try { - field.set(task, null); - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - } + Injector.inject(task, null); try { return codec.getValueEncoder().encode(task); @@ -243,6 +274,9 @@ public class RedissonExecutorService implements RScheduledExecutorService { @Override public void shutdown() { + remoteService.deregister(RemoteExecutorService.class); + workersTopic.removeListener(workersGroupListenerId); + commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID, "if redis.call('exists', KEYS[2]) == 0 then " + "if redis.call('get', KEYS[1]) == '0' or redis.call('exists', KEYS[1]) == 0 then " @@ -338,7 +372,7 @@ public class RedissonExecutorService implements RScheduledExecutorService { } @Override - public Future submit(Callable task) { + public RFuture submit(Callable task) { RemotePromise promise = (RemotePromise) submitAsync(task); execute(promise); return promise; @@ -411,7 +445,7 @@ public class RedissonExecutorService implements RScheduledExecutorService { } @Override - public Future submit(Runnable task) { + public RFuture submit(Runnable task) { RemotePromise promise = (RemotePromise) submitAsync(task); execute(promise); return promise; @@ -428,7 +462,7 @@ public class RedissonExecutorService implements RScheduledExecutorService { } @Override - public ScheduledFuture schedule(Runnable task, long delay, TimeUnit unit) { + public RScheduledFuture schedule(Runnable task, long delay, TimeUnit unit) { RedissonScheduledFuture future = (RedissonScheduledFuture) scheduleAsync(task, delay, unit); execute((RemotePromise)future.getInnerPromise()); return future; @@ -446,7 +480,7 @@ public class RedissonExecutorService implements RScheduledExecutorService { } @Override - public ScheduledFuture schedule(Callable task, long delay, TimeUnit unit) { + public RScheduledFuture schedule(Callable task, long delay, TimeUnit unit) { RedissonScheduledFuture future = (RedissonScheduledFuture) scheduleAsync(task, delay, unit); execute((RemotePromise)future.getInnerPromise()); return future; @@ -464,7 +498,7 @@ public class RedissonExecutorService implements RScheduledExecutorService { } @Override - public ScheduledFuture scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) { + public RScheduledFuture scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) { RedissonScheduledFuture future = (RedissonScheduledFuture) scheduleAtFixedRateAsync(task, initialDelay, period, unit); execute((RemotePromise)future.getInnerPromise()); return future; @@ -505,7 +539,7 @@ public class RedissonExecutorService implements RScheduledExecutorService { } @Override - public ScheduledFuture scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit) { + public RScheduledFuture scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit) { RedissonScheduledFuture future = (RedissonScheduledFuture) scheduleWithFixedDelayAsync(task, initialDelay, delay, unit); execute((RemotePromise)future.getInnerPromise()); return future; diff --git a/redisson/src/main/java/org/redisson/RedissonGeo.java b/redisson/src/main/java/org/redisson/RedissonGeo.java index 5e50b37d9..f2939180b 100644 --- a/redisson/src/main/java/org/redisson/RedissonGeo.java +++ b/redisson/src/main/java/org/redisson/RedissonGeo.java @@ -27,6 +27,7 @@ import org.redisson.api.GeoPosition; import org.redisson.api.GeoUnit; import org.redisson.api.RFuture; import org.redisson.api.RGeo; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.codec.GeoEntryCodec; import org.redisson.client.codec.ScoredCodec; @@ -55,14 +56,14 @@ public class RedissonGeo extends RedissonScoredSortedSet implements RGeo> postitionDecoder; MultiDecoder> distanceDecoder; - public RedissonGeo(CommandAsyncExecutor connectionManager, String name) { - super(connectionManager, name); + public RedissonGeo(CommandAsyncExecutor connectionManager, String name, RedissonClient redisson) { + super(connectionManager, name, redisson); postitionDecoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); distanceDecoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); } - public RedissonGeo(Codec codec, CommandAsyncExecutor connectionManager, String name) { - super(codec, connectionManager, name); + public RedissonGeo(Codec codec, CommandAsyncExecutor connectionManager, String name, RedissonClient redisson) { + super(codec, connectionManager, name, redisson); postitionDecoder = new NestedMultiDecoder(new GeoPositionDecoder(), new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); distanceDecoder = new FlatNestedMultiDecoder(new GeoDistanceDecoder(codec), new GeoMapReplayDecoder(), true); } diff --git a/redisson/src/main/java/org/redisson/RedissonKeys.java b/redisson/src/main/java/org/redisson/RedissonKeys.java index 449cbdec4..863a787d2 100644 --- a/redisson/src/main/java/org/redisson/RedissonKeys.java +++ b/redisson/src/main/java/org/redisson/RedissonKeys.java @@ -17,7 +17,6 @@ package org.redisson; import java.net.InetSocketAddress; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -26,11 +25,13 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.redisson.api.RFuture; import org.redisson.api.RKeys; +import org.redisson.api.RObject; import org.redisson.api.RType; import org.redisson.client.RedisException; import org.redisson.client.codec.ScanCodec; @@ -132,14 +133,45 @@ public class RedissonKeys implements RKeys { } @Override - public Long isExists(String... names) { - return commandExecutor.get(isExistsAsync(names)); + public long touch(String... names) { + return commandExecutor.get(touchAsync(names)); } @Override - public RFuture isExistsAsync(String... names) { - Object[] params = Arrays.copyOf(names, names.length, Object[].class); - return commandExecutor.readAsync((String)null, null, RedisCommands.EXISTS_LONG, params); + public RFuture touchAsync(String... names) { + return commandExecutor.writeAllAsync(RedisCommands.TOUCH_LONG, new SlotCallback() { + AtomicLong results = new AtomicLong(); + @Override + public void onSlotResult(Long result) { + results.addAndGet(result); + } + + @Override + public Long onFinish() { + return results.get(); + } + }, names); + } + + @Override + public long countExists(String... names) { + return commandExecutor.get(countExistsAsync(names)); + } + + @Override + public RFuture countExistsAsync(String... names) { + return commandExecutor.readAllAsync(RedisCommands.EXISTS_LONG, new SlotCallback() { + AtomicLong results = new AtomicLong(); + @Override + public void onSlotResult(Long result) { + results.addAndGet(result); + } + + @Override + public Long onFinish() { + return results.get(); + } + }, names); } @@ -227,7 +259,22 @@ public class RedissonKeys implements RKeys { public long delete(String ... keys) { return commandExecutor.get(deleteAsync(keys)); } + + @Override + public long delete(RObject ... objects) { + return commandExecutor.get(deleteAsync(objects)); + } + @Override + public RFuture deleteAsync(RObject ... objects) { + List keys = new ArrayList(); + for (RObject obj : objects) { + keys.add(obj.getName()); + } + + return deleteAsync(keys.toArray(new String[keys.size()])); + } + @Override public RFuture deleteAsync(String ... keys) { if (!commandExecutor.getConnectionManager().isClusterMode()) { @@ -338,4 +385,85 @@ public class RedissonKeys implements RKeys { } } + @Override + public long remainTimeToLive(String name) { + return commandExecutor.get(remainTimeToLiveAsync(name)); + } + + @Override + public RFuture remainTimeToLiveAsync(String name) { + return commandExecutor.readAsync(name, StringCodec.INSTANCE, RedisCommands.PTTL, name); + } + + @Override + public void rename(String currentName, String newName) { + commandExecutor.get(renameAsync(currentName, newName)); + } + + @Override + public RFuture renameAsync(String currentName, String newName) { + return commandExecutor.writeAsync(currentName, RedisCommands.RENAME, currentName, newName); + } + + @Override + public boolean renamenx(String oldName, String newName) { + return commandExecutor.get(renamenxAsync(oldName, newName)); + } + + @Override + public RFuture renamenxAsync(String oldName, String newName) { + return commandExecutor.writeAsync(oldName, RedisCommands.RENAMENX, oldName, newName); + } + + @Override + public boolean clearExpire(String name) { + return commandExecutor.get(clearExpireAsync(name)); + } + + @Override + public RFuture clearExpireAsync(String name) { + return commandExecutor.writeAsync(name, StringCodec.INSTANCE, RedisCommands.PERSIST, name); + } + + @Override + public boolean expireAt(String name, long timestamp) { + return commandExecutor.get(expireAtAsync(name, timestamp)); + } + + @Override + public RFuture expireAtAsync(String name, long timestamp) { + return commandExecutor.writeAsync(name, StringCodec.INSTANCE, RedisCommands.PEXPIREAT, name, timestamp); + } + + @Override + public boolean expire(String name, long timeToLive, TimeUnit timeUnit) { + return commandExecutor.get(expireAsync(name, timeToLive, timeUnit)); + } + + @Override + public RFuture expireAsync(String name, long timeToLive, TimeUnit timeUnit) { + return commandExecutor.writeAsync(name, StringCodec.INSTANCE, RedisCommands.PEXPIRE, name, timeUnit.toMillis(timeToLive)); + } + + @Override + public void migrate(String name, String host, int port, int database) { + commandExecutor.get(migrateAsync(name, host, port, database)); + } + + @Override + public RFuture migrateAsync(String name, String host, int port, int database) { + return commandExecutor.writeAsync(name, RedisCommands.MIGRATE, host, port, name, database); + } + + @Override + public boolean move(String name, int database) { + return commandExecutor.get(moveAsync(name, database)); + } + + @Override + public RFuture moveAsync(String name, int database) { + return commandExecutor.writeAsync(name, RedisCommands.MOVE, name, database); + } + + } diff --git a/redisson/src/main/java/org/redisson/RedissonLexSortedSet.java b/redisson/src/main/java/org/redisson/RedissonLexSortedSet.java index ba5fc011f..26faf938c 100644 --- a/redisson/src/main/java/org/redisson/RedissonLexSortedSet.java +++ b/redisson/src/main/java/org/redisson/RedissonLexSortedSet.java @@ -21,14 +21,21 @@ import java.util.List; import org.redisson.api.RFuture; import org.redisson.api.RLexSortedSet; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandAsyncExecutor; +/** + * Sorted set contained values of String type + * + * @author Nikita Koksharov + * + */ public class RedissonLexSortedSet extends RedissonScoredSortedSet implements RLexSortedSet { - public RedissonLexSortedSet(CommandAsyncExecutor commandExecutor, String name) { - super(StringCodec.INSTANCE, commandExecutor, name); + public RedissonLexSortedSet(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(StringCodec.INSTANCE, commandExecutor, name, redisson); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonList.java b/redisson/src/main/java/org/redisson/RedissonList.java index 73608583b..92cf8c495 100644 --- a/redisson/src/main/java/org/redisson/RedissonList.java +++ b/redisson/src/main/java/org/redisson/RedissonList.java @@ -34,7 +34,9 @@ import java.util.NoSuchElementException; import org.redisson.api.RFuture; import org.redisson.api.RList; +import org.redisson.api.RedissonClient; import org.redisson.api.SortOrder; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand.ValueType; @@ -44,6 +46,7 @@ import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.client.protocol.convertor.Convertor; import org.redisson.client.protocol.convertor.IntegerReplayConvertor; import org.redisson.command.CommandAsyncExecutor; +import org.redisson.mapreduce.RedissonCollectionMapReduce; /** * Distributed and concurrent implementation of {@link java.util.List} @@ -56,14 +59,23 @@ public class RedissonList extends RedissonExpirable implements RList { public static final RedisCommand EVAL_BOOLEAN_ARGS2 = new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.OBJECTS); - public RedissonList(CommandAsyncExecutor commandExecutor, String name) { + private RedissonClient redisson; + + public RedissonList(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(commandExecutor, name); + this.redisson = redisson; } - public RedissonList(Codec codec, CommandAsyncExecutor commandExecutor, String name) { + public RedissonList(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(codec, commandExecutor, name); + this.redisson = redisson; } + @Override + public RCollectionMapReduce mapReduce() { + return new RedissonCollectionMapReduce(this, redisson, commandExecutor.getConnectionManager()); + } + @Override public int size() { return get(sizeAsync()); diff --git a/redisson/src/main/java/org/redisson/RedissonListMultimap.java b/redisson/src/main/java/org/redisson/RedissonListMultimap.java index 3cf59986e..88fe1e448 100644 --- a/redisson/src/main/java/org/redisson/RedissonListMultimap.java +++ b/redisson/src/main/java/org/redisson/RedissonListMultimap.java @@ -185,7 +185,7 @@ public class RedissonListMultimap extends RedissonMultimap implement final String keyHash = hash(keyState); final String setName = getValuesName(keyHash); - return new RedissonList(codec, commandExecutor, setName) { + return new RedissonList(codec, commandExecutor, setName, null) { @Override public RFuture deleteAsync() { diff --git a/redisson/src/main/java/org/redisson/RedissonListMultimapIterator.java b/redisson/src/main/java/org/redisson/RedissonListMultimapIterator.java index 212b5368b..2107b30ec 100644 --- a/redisson/src/main/java/org/redisson/RedissonListMultimapIterator.java +++ b/redisson/src/main/java/org/redisson/RedissonListMultimapIterator.java @@ -28,7 +28,7 @@ public class RedissonListMultimapIterator extends RedissonMultiMapItera @Override protected Iterator getIterator(String name) { - RedissonList set = new RedissonList(codec, commandExecutor, map.getValuesName(name)); + RedissonList set = new RedissonList(codec, commandExecutor, map.getValuesName(name), null); return set.iterator(); } diff --git a/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java b/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java index ada36e130..cce087fc9 100644 --- a/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java +++ b/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java @@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RFuture; import org.redisson.api.RList; import org.redisson.api.SortOrder; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommand.ValueType; @@ -68,7 +69,12 @@ public class RedissonListMultimapValues extends RedissonExpirable implements super(codec, commandExecutor, name); this.timeoutSetName = timeoutSetName; this.key = key; - this.list = new RedissonList(codec, commandExecutor, name); + this.list = new RedissonList(codec, commandExecutor, name, null); + } + + @Override + public RCollectionMapReduce mapReduce() { + return null; } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java index a9c45fc07..0c332b689 100644 --- a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java +++ b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java @@ -31,18 +31,24 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeUnit; import org.redisson.api.LocalCachedMapOptions; import org.redisson.api.LocalCachedMapOptions.EvictionPolicy; +import org.redisson.api.LocalCachedMapOptions.InvalidationPolicy; import org.redisson.api.RFuture; import org.redisson.api.RLocalCachedMap; +import org.redisson.api.RScoredSortedSet; import org.redisson.api.RTopic; +import org.redisson.api.RedissonClient; +import org.redisson.api.listener.BaseStatusListener; import org.redisson.api.listener.MessageListener; import org.redisson.cache.Cache; import org.redisson.cache.LFUCacheMap; import org.redisson.cache.LRUCacheMap; import org.redisson.cache.NoneCacheMap; -import org.redisson.cache.SoftCacheMap; +import org.redisson.cache.ReferenceCacheMap; +import org.redisson.client.codec.ByteArrayCodec; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.StringCodec; @@ -51,10 +57,14 @@ import org.redisson.client.protocol.RedisCommand.ValueType; import org.redisson.client.protocol.RedisCommands; 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.command.CommandAsyncExecutor; +import org.redisson.eviction.EvictionScheduler; import org.redisson.misc.Hash; import org.redisson.misc.RPromise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; @@ -65,8 +75,11 @@ import io.netty.util.internal.ThreadLocalRandom; * @author Nikita Koksharov * */ +@SuppressWarnings("serial") public class RedissonLocalCachedMap extends RedissonMap implements RLocalCachedMap { + private static final Logger log = LoggerFactory.getLogger(RedissonLocalCachedMap.class); + public static class LocalCachedMapClear implements Serializable { } @@ -180,66 +193,147 @@ public class RedissonLocalCachedMap extends RedissonMap implements R } - private static final RedisCommand> ALL_KEYS = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), ValueType.MAP_KEY); + private static final RedisCommand> ALL_KEYS = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), ValueType.MAP_KEY); private static final RedisCommand>> ALL_ENTRIES = new RedisCommand>>("EVAL", new ObjectMapEntryReplayDecoder(), ValueType.MAP); + private static final RedisCommand> ALL_MAP = new RedisCommand>("EVAL", new ObjectMapReplayDecoder(), ValueType.MAP); private static final RedisCommand EVAL_PUT = new RedisCommand("EVAL", -1, ValueType.OBJECT, ValueType.MAP_VALUE); private static final RedisCommand EVAL_REMOVE = new RedisCommand("EVAL", -1, ValueType.OBJECT, ValueType.MAP_VALUE); + private long cacheUpdateLogTime = TimeUnit.MINUTES.toMillis(10); private byte[] instanceId; private RTopic invalidationTopic; private Cache cache; private int invalidateEntryOnChange; private int invalidationListenerId; + private int invalidationStatusListenerId; + private volatile long lastInvalidate; - protected RedissonLocalCachedMap(UUID id, CommandAsyncExecutor commandExecutor, String name, LocalCachedMapOptions options) { - super(id, commandExecutor, name); - init(id, name, options); + protected RedissonLocalCachedMap(UUID id, CommandAsyncExecutor commandExecutor, String name, LocalCachedMapOptions options, EvictionScheduler evictionScheduler, RedissonClient redisson) { + super(id, commandExecutor, name, redisson); + init(id, name, options, redisson, evictionScheduler); } - protected RedissonLocalCachedMap(UUID id, Codec codec, CommandAsyncExecutor connectionManager, String name, LocalCachedMapOptions options) { - super(id, codec, connectionManager, name); - init(id, name, options); + protected RedissonLocalCachedMap(UUID id, Codec codec, CommandAsyncExecutor connectionManager, String name, LocalCachedMapOptions options, EvictionScheduler evictionScheduler, RedissonClient redisson) { + super(id, codec, connectionManager, name, redisson); + init(id, name, options, redisson, evictionScheduler); } - private void init(UUID id, String name, LocalCachedMapOptions options) { + private void init(UUID id, String name, LocalCachedMapOptions options, RedissonClient redisson, EvictionScheduler evictionScheduler) { instanceId = generateId(); - if (options.isInvalidateEntryOnChange()) { + if (options.getInvalidationPolicy() == InvalidationPolicy.ON_CHANGE + || options.getInvalidationPolicy() == InvalidationPolicy.ON_CHANGE_WITH_CLEAR_ON_RECONNECT) { invalidateEntryOnChange = 1; } - if (options.getEvictionPolicy() == EvictionPolicy.NONE) { - cache = new NoneCacheMap(options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); - } - if (options.getEvictionPolicy() == EvictionPolicy.LRU) { - cache = new LRUCacheMap(options.getCacheSize(), options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); - } - if (options.getEvictionPolicy() == EvictionPolicy.LFU) { - cache = new LFUCacheMap(options.getCacheSize(), options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); - } - if (options.getEvictionPolicy() == EvictionPolicy.SOFT) { - cache = new SoftCacheMap(options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); + if (options.getInvalidationPolicy() == InvalidationPolicy.ON_CHANGE_WITH_LOAD_ON_RECONNECT) { + invalidateEntryOnChange = 2; + evictionScheduler.schedule(getUpdatesLogName(), cacheUpdateLogTime + TimeUnit.MINUTES.toMillis(1)); } + cache = createCache(options); + + addListeners(name, options, redisson); + } + + private void addListeners(String name, final LocalCachedMapOptions options, final RedissonClient redisson) { invalidationTopic = new RedissonTopic(commandExecutor, suffixName(name, "topic")); - if (options.isInvalidateEntryOnChange()) { - invalidationListenerId = invalidationTopic.addListener(new MessageListener() { + + if (options.getInvalidationPolicy() == InvalidationPolicy.NONE) { + return; + } + + if (options.getInvalidationPolicy() != InvalidationPolicy.ON_CHANGE) { + invalidationStatusListenerId = invalidationTopic.addListener(new BaseStatusListener() { @Override - public void onMessage(String channel, Object msg) { - if (msg instanceof LocalCachedMapClear) { + public void onSubscribe(String channel) { + if (options.getInvalidationPolicy() == InvalidationPolicy.ON_CHANGE_WITH_CLEAR_ON_RECONNECT) { cache.clear(); } - if (msg instanceof LocalCachedMapInvalidate) { - LocalCachedMapInvalidate invalidateMsg = (LocalCachedMapInvalidate)msg; - if (!Arrays.equals(invalidateMsg.getExcludedId(), instanceId)) { - for (byte[] keyHash : invalidateMsg.getKeyHashes()) { - CacheKey key = new CacheKey(keyHash); - cache.remove(key); - } + if (options.getInvalidationPolicy() == InvalidationPolicy.ON_CHANGE_WITH_LOAD_ON_RECONNECT + // check if instance has already been used + && lastInvalidate > 0) { + + if (System.currentTimeMillis() - lastInvalidate > cacheUpdateLogTime) { + cache.clear(); + return; } + + isExistsAsync().addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + return; + } + + if (!future.getNow()) { + cache.clear(); + return; + } + + RScoredSortedSet logs = redisson.getScoredSortedSet(getUpdatesLogName(), ByteArrayCodec.INSTANCE); + logs.valueRangeAsync(lastInvalidate, true, Double.POSITIVE_INFINITY, true) + .addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + log.error("Can't load update log", future.cause()); + return; + } + + for (byte[] entry : future.getNow()) { + byte[] keyHash = Arrays.copyOf(entry, 16); + CacheKey key = new CacheKey(keyHash); + cache.remove(key); + } + } + }); + } + }); + } } }); } + + invalidationListenerId = invalidationTopic.addListener(new MessageListener() { + @Override + public void onMessage(String channel, Object msg) { + if (msg instanceof LocalCachedMapClear) { + cache.clear(); + } + if (msg instanceof LocalCachedMapInvalidate) { + LocalCachedMapInvalidate invalidateMsg = (LocalCachedMapInvalidate)msg; + if (!Arrays.equals(invalidateMsg.getExcludedId(), instanceId)) { + for (byte[] keyHash : invalidateMsg.getKeyHashes()) { + CacheKey key = new CacheKey(keyHash); + cache.remove(key); + } + } + if (options.getInvalidationPolicy() == InvalidationPolicy.ON_CHANGE_WITH_LOAD_ON_RECONNECT) { + lastInvalidate = System.currentTimeMillis(); + } + } + } + }); + } + + protected Cache createCache(LocalCachedMapOptions options) { + if (options.getEvictionPolicy() == EvictionPolicy.NONE) { + return new NoneCacheMap(options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); + } + if (options.getEvictionPolicy() == EvictionPolicy.LRU) { + return new LRUCacheMap(options.getCacheSize(), options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); + } + if (options.getEvictionPolicy() == EvictionPolicy.LFU) { + return new LFUCacheMap(options.getCacheSize(), options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); + } + if (options.getEvictionPolicy() == EvictionPolicy.SOFT) { + return ReferenceCacheMap.soft(options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); + } + if (options.getEvictionPolicy() == EvictionPolicy.WEAK) { + return ReferenceCacheMap.weak(options.getTimeToLiveInMillis(), options.getMaxIdleInMillis()); + } + throw new IllegalArgumentException("Invalid eviction policy: " + options.getEvictionPolicy()); } private CacheKey toCacheKey(Object key) { @@ -298,13 +392,30 @@ public class RedissonLocalCachedMap extends RedissonMap implements R return future; } - protected byte[] generateId() { + String getUpdatesLogName() { + return prefixName("redisson__cache_updates_log", getName()); + } + + protected static byte[] generateId() { byte[] id = new byte[16]; // TODO JDK UPGRADE replace to native ThreadLocalRandom ThreadLocalRandom.current().nextBytes(id); return id; } + protected static byte[] generateLogEntryId(byte[] keyHash) { + byte[] result = new byte[keyHash.length + 1 + 8]; + result[16] = ':'; + byte[] id = new byte[8]; + // TODO JDK UPGRADE replace to native ThreadLocalRandom + ThreadLocalRandom.current().nextBytes(id); + + System.arraycopy(keyHash, 0, result, 0, keyHash.length); + System.arraycopy(id, 0, result, 17, id.length); + return result; + } + + @Override public RFuture putAsync(K key, V value) { if (key == null) { @@ -316,17 +427,24 @@ public class RedissonLocalCachedMap extends RedissonMap implements R byte[] mapKey = encodeMapKey(key); CacheKey cacheKey = toCacheKey(mapKey); + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); CacheValue cacheValue = new CacheValue(key, value); cache.put(cacheKey, cacheValue); return commandExecutor.evalWriteAsync(getName(), codec, EVAL_PUT, "local v = redis.call('hget', KEYS[1], ARGV[1]); " - + "if redis.call('hset', KEYS[1], ARGV[1], ARGV[2]) == 0 and ARGV[4] == '1' then " - + "redis.call('publish', KEYS[2], ARGV[3]); " + + "if redis.call('hset', KEYS[1], ARGV[1], ARGV[2]) == 0 then " + + "if ARGV[4] == '1' then " + + "redis.call('publish', KEYS[2], ARGV[3]); " + + "end;" + + "if ARGV[4] == '2' then " + + "redis.call('zadd', KEYS[3], ARGV[5], ARGV[6]);" + + "redis.call('publish', KEYS[2], ARGV[3]); " + + "end;" + "end; " + "return v; ", - Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), - mapKey, encodeMapValue(value), msg, invalidateEntryOnChange); + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + mapKey, encodeMapValue(value), msg, invalidateEntryOnChange, System.currentTimeMillis(), entryId); } @Override @@ -339,8 +457,9 @@ public class RedissonLocalCachedMap extends RedissonMap implements R } byte[] encodedKey = encodeMapKey(key); - byte[] encodedValue = encodeMapKey(value); + byte[] encodedValue = encodeMapValue(value); CacheKey cacheKey = toCacheKey(encodedKey); + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); CacheValue cacheValue = new CacheValue(key, value); cache.put(cacheKey, cacheValue); @@ -349,11 +468,15 @@ public class RedissonLocalCachedMap extends RedissonMap implements R + "if ARGV[4] == '1' then " + "redis.call('publish', KEYS[2], ARGV[3]); " + "end;" + + "if ARGV[4] == '2' then " + + "redis.call('zadd', KEYS[3], ARGV[5], ARGV[6]);" + + "redis.call('publish', KEYS[2], ARGV[3]); " + + "end;" + "return 0; " + "end; " + "return 1; ", - Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), - encodedKey, encodedValue, msg, invalidateEntryOnChange); + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + encodedKey, encodedValue, msg, invalidateEntryOnChange, System.currentTimeMillis(), entryId); } @Override @@ -361,6 +484,9 @@ public class RedissonLocalCachedMap extends RedissonMap implements R if (invalidationListenerId != 0) { invalidationTopic.removeListener(invalidationListenerId); } + if (invalidationStatusListenerId != 0) { + invalidationTopic.removeListener(invalidationStatusListenerId); + } } @Override @@ -371,16 +497,23 @@ public class RedissonLocalCachedMap extends RedissonMap implements R byte[] keyEncoded = encodeMapKey(key); CacheKey cacheKey = toCacheKey(keyEncoded); + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); cache.remove(cacheKey); return commandExecutor.evalWriteAsync(getName(), codec, EVAL_REMOVE, "local v = redis.call('hget', KEYS[1], ARGV[1]); " - + "if redis.call('hdel', KEYS[1], ARGV[1]) == 1 and ARGV[3] == '1' then " - + "redis.call('publish', KEYS[2], ARGV[2]); " + + "if redis.call('hdel', KEYS[1], ARGV[1]) == 1 then " + + "if ARGV[3] == '1' then " + + "redis.call('publish', KEYS[2], ARGV[2]); " + + "end; " + + "if ARGV[3] == '2' then " + + "redis.call('zadd', KEYS[3], ARGV[4], ARGV[5]);" + + "redis.call('publish', KEYS[2], ARGV[2]); " + + "end;" + "end; " + "return v", - Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), - keyEncoded, msgEncoded, invalidateEntryOnChange); + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + keyEncoded, msgEncoded, invalidateEntryOnChange, System.currentTimeMillis(), entryId); } @Override @@ -389,35 +522,72 @@ public class RedissonLocalCachedMap extends RedissonMap implements R throw new NullPointerException(); } - List params = new ArrayList(); - params.add(invalidateEntryOnChange); + if (invalidateEntryOnChange == 1) { + List params = new ArrayList(keys.length*2); + for (K k : keys) { + byte[] keyEncoded = encodeMapKey(k); + params.add(keyEncoded); + + CacheKey cacheKey = toCacheKey(keyEncoded); + cache.remove(cacheKey); + byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); + params.add(msgEncoded); + } + + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG, + "local counter = 0; " + + "for j = 1, #ARGV, 2 do " + + "if redis.call('hdel', KEYS[1], ARGV[j]) == 1 then " + + "redis.call('publish', KEYS[2], ARGV[j+1]); " + + "counter = counter + 1;" + + "end;" + + "end;" + + "return counter;", + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), + params.toArray()); + } + + if (invalidateEntryOnChange == 2) { + List params = new ArrayList(keys.length*3); + params.add(System.currentTimeMillis()); + for (K k : keys) { + byte[] keyEncoded = encodeMapKey(k); + params.add(keyEncoded); + + CacheKey cacheKey = toCacheKey(keyEncoded); + cache.remove(cacheKey); + byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); + params.add(msgEncoded); + + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); + params.add(entryId); + } + + return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG, + "local counter = 0; " + + "for j = 2, #ARGV, 3 do " + + "if redis.call('hdel', KEYS[1], ARGV[j]) == 1 then " + + "redis.call('zadd', KEYS[3], ARGV[1], ARGV[j+2]);" + + "redis.call('publish', KEYS[2], ARGV[j+1]); " + + "counter = counter + 1;" + + "end;" + + "end;" + + "return counter;", + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + params.toArray()); + } + + List params = new ArrayList(keys.length + 1); + params.add(getName()); for (K k : keys) { byte[] keyEncoded = encodeMapKey(k); params.add(keyEncoded); CacheKey cacheKey = toCacheKey(keyEncoded); cache.remove(cacheKey); - if (invalidateEntryOnChange == 1) { - byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); - params.add(msgEncoded); - } else { - params.add(null); - } } - - return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_LONG, - "local counter = 0; " + - "for j = 2, #ARGV, 2 do " - + "if redis.call('hdel', KEYS[1], ARGV[j]) == 1 then " - + "if ARGV[1] == '1' then " - + "redis.call('publish', KEYS[2], ARGV[j+1]); " - + "end; " - + "counter = counter + 1;" - + "end;" - + "end;" - + "return counter;", - Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), - params.toArray()); + + return commandExecutor.writeAsync(getName(), codec, RedisCommands.HDEL, params.toArray()); } @@ -426,12 +596,12 @@ public class RedissonLocalCachedMap extends RedissonMap implements R cache.clear(); byte[] msgEncoded = encode(new LocalCachedMapClear()); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, - "if redis.call('del', KEYS[1]) == 1 and ARGV[2] == '1' then " + "if redis.call('del', KEYS[1], KEYS[3]) > 0 and ARGV[2] ~= '0' then " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1;" + "end; " + "return 0;", - Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), msgEncoded, invalidateEntryOnChange); } @@ -742,18 +912,30 @@ public class RedissonLocalCachedMap extends RedissonMap implements R i++; } + if (invalidateEntryOnChange == 2) { + long time = System.currentTimeMillis(); + for (byte[] hash : hashes) { + byte[] entryId = generateLogEntryId(hash); + params.add(time); + params.add(entryId); + } + } + byte[] msgEncoded = encode(new LocalCachedMapInvalidate(instanceId, hashes)); params.add(msgEncoded); final RPromise result = newPromise(); RFuture future = commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_VOID, "redis.call('hmset', KEYS[1], unpack(ARGV, 3, tonumber(ARGV[2]) + 2));" - + "if ARGV[1] == '1' then " -// + "for i = tonumber(ARGV[2]) + 3, #ARGV, 1 do " - + "redis.call('publish', KEYS[2], ARGV[#ARGV]); " -// + "end; " - + "end;", - Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), params.toArray()); + + "if ARGV[1] == '1' then " + + "redis.call('publish', KEYS[2], ARGV[#ARGV]); " + + "end;" + + "if ARGV[1] == '2' then " + + "redis.call('zadd', KEYS[3], unpack(ARGV, tonumber(ARGV[2]) + 2 + 1, #ARGV - 1));" + + "redis.call('publish', KEYS[2], ARGV[#ARGV]); " + + "end;", + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + params.toArray()); future.addListener(new FutureListener() { @Override @@ -774,15 +956,19 @@ public class RedissonLocalCachedMap extends RedissonMap implements R final byte[] keyState = encodeMapKey(key); CacheKey cacheKey = toCacheKey(keyState); byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); - + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); RFuture future = commandExecutor.evalWriteAsync(getName(), StringCodec.INSTANCE, new RedisCommand("EVAL", new NumberConvertor(value.getClass())), "local result = redis.call('HINCRBYFLOAT', KEYS[1], ARGV[1], ARGV[2]); " + "if ARGV[3] == '1' then " - + "redis.call('publish', KEYS[2], ARGV[4]); " - + "end; " + + "redis.call('publish', KEYS[2], ARGV[4]); " + + "end;" + + "if ARGV[3] == '2' then " + + "redis.call('zadd', KEYS[3], ARGV[5], ARGV[6]);" + + "redis.call('publish', KEYS[2], ARGV[4]); " + + "end;" + "return result; ", - Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0)), - keyState, new BigDecimal(value.toString()).toPlainString(), invalidateEntryOnChange, msg); + Arrays.asList(getName(), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + keyState, new BigDecimal(value.toString()).toPlainString(), invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId); future.addListener(new FutureListener() { @Override @@ -867,6 +1053,37 @@ public class RedissonLocalCachedMap extends RedissonMap implements R return promise; } + @Override + public RFuture> readAllMapAsync() { + final Map result = new HashMap(); + List mapKeys = new ArrayList(); + for (CacheValue value : cache.values()) { + mapKeys.add(encodeMapKey(value.getKey())); + result.put((K)value.getKey(), (V)value.getValue()); + } + + final RPromise> promise = newPromise(); + RFuture> future = readAll(ALL_MAP, mapKeys, result); + + future.addListener(new FutureListener>() { + @Override + public void operationComplete(Future> future) throws Exception { + if (!future.isSuccess()) { + return; + } + + for (java.util.Map.Entry entry : future.getNow().entrySet()) { + CacheKey cacheKey = toCacheKey(entry.getKey()); + cache.put(cacheKey, new CacheValue(entry.getKey(), entry.getValue())); + } + result.putAll(future.getNow()); + promise.trySuccess(result); + } + }); + + return promise; + } + @Override public RFuture>> readAllEntrySetAsync() { final Set> result = new HashSet>(); @@ -875,9 +1092,31 @@ public class RedissonLocalCachedMap extends RedissonMap implements R mapKeys.add(encodeMapKey(value.getKey())); result.add(new AbstractMap.SimpleEntry((K)value.getKey(), (V)value.getValue())); } - + final RPromise>> promise = newPromise(); - RFuture>> future = commandExecutor.evalReadAsync(getName(), codec, ALL_ENTRIES, + RFuture>> future = readAll(ALL_ENTRIES, mapKeys, result); + + future.addListener(new FutureListener>>() { + @Override + public void operationComplete(Future>> future) throws Exception { + if (!future.isSuccess()) { + return; + } + + for (java.util.Map.Entry entry : future.getNow()) { + CacheKey cacheKey = toCacheKey(entry.getKey()); + cache.put(cacheKey, new CacheValue(entry.getKey(), entry.getValue())); + } + result.addAll(future.getNow()); + promise.trySuccess(result); + } + }); + + return promise; + } + + private RFuture readAll(RedisCommand evalCommandType, List mapKeys, R result) { + return commandExecutor.evalReadAsync(getName(), codec, evalCommandType, "local entries = redis.call('hgetall', KEYS[1]); " + "local result = {};" + "for j, v in ipairs(entries) do " @@ -897,24 +1136,6 @@ public class RedissonLocalCachedMap extends RedissonMap implements R + "return result; ", Arrays.asList(getName()), mapKeys.toArray()); - - future.addListener(new FutureListener>>() { - @Override - public void operationComplete(Future>> future) throws Exception { - if (!future.isSuccess()) { - return; - } - - for (java.util.Map.Entry entry : future.getNow()) { - CacheKey cacheKey = toCacheKey(entry.getKey()); - cache.put(cacheKey, new CacheValue(entry.getKey(), entry.getValue())); - } - result.addAll(future.getNow()); - promise.trySuccess(result); - } - }); - - return promise; } @Override @@ -922,21 +1143,28 @@ public class RedissonLocalCachedMap extends RedissonMap implements R final byte[] keyState = encodeMapKey(key); byte[] valueState = encodeMapValue(value); final CacheKey cacheKey = toCacheKey(keyState); + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); RFuture future = commandExecutor.evalWriteAsync(getName(key), codec, RedisCommands.EVAL_MAP_VALUE, "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " + "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); " + + "if ARGV[3] == '1' then " + "redis.call('publish', KEYS[2], ARGV[4]); " - + "end; " + + "end;" + + "if ARGV[3] == '2' then " + + "redis.call('zadd', KEYS[3], ARGV[5], ARGV[6]);" + + "redis.call('publish', KEYS[2], ARGV[4]); " + + "end;" + + "return v; " + "else " + "return nil; " + "end", - Arrays.asList(getName(key), invalidationTopic.getChannelNames().get(0)), - keyState, valueState, invalidateEntryOnChange, msg); + Arrays.asList(getName(key), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + keyState, valueState, invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId); future.addListener(new FutureListener() { @Override @@ -961,6 +1189,7 @@ public class RedissonLocalCachedMap extends RedissonMap implements R byte[] oldValueState = encodeMapValue(oldValue); byte[] newValueState = encodeMapValue(newValue); final CacheKey cacheKey = toCacheKey(keyState); + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); RFuture future = commandExecutor.evalWriteAsync(getName(key), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, @@ -968,13 +1197,17 @@ public class RedissonLocalCachedMap extends RedissonMap implements R + "redis.call('hset', KEYS[1], ARGV[1], ARGV[3]); " + "if ARGV[4] == '1' then " + "redis.call('publish', KEYS[2], ARGV[5]); " - + "end; " + + "end;" + + "if ARGV[4] == '2' then " + + "redis.call('zadd', KEYS[3], ARGV[6], ARGV[7]);" + + "redis.call('publish', KEYS[2], ARGV[5]); " + + "end;" + "return 1; " + "else " + "return 0; " + "end", - Arrays.asList(getName(key), invalidationTopic.getChannelNames().get(0)), - keyState, oldValueState, newValueState, invalidateEntryOnChange, msg); + Arrays.asList(getName(key), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + keyState, oldValueState, newValueState, invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId); future.addListener(new FutureListener() { @Override @@ -997,19 +1230,24 @@ public class RedissonLocalCachedMap extends RedissonMap implements R final byte[] keyState = encodeMapKey(key); byte[] valueState = encodeMapValue(value); final CacheKey cacheKey = toCacheKey(keyState); + byte[] entryId = generateLogEntryId(cacheKey.getKeyHash()); byte[] msg = encode(new LocalCachedMapInvalidate(instanceId, cacheKey.getKeyHash())); RFuture future = commandExecutor.evalWriteAsync(getName(key), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then " + "if ARGV[3] == '1' then " + "redis.call('publish', KEYS[2], ARGV[4]); " - + "end; " + + "end;" + + "if ARGV[3] == '2' then " + + "redis.call('zadd', KEYS[3], ARGV[5], ARGV[6]);" + + "redis.call('publish', KEYS[2], ARGV[4]); " + + "end;" + "return redis.call('hdel', KEYS[1], ARGV[1]) " + "else " + "return 0 " + "end", - Arrays.asList(getName(key), invalidationTopic.getChannelNames().get(0)), - keyState, valueState, invalidateEntryOnChange, msg); + Arrays.asList(getName(key), invalidationTopic.getChannelNames().get(0), getUpdatesLogName()), + keyState, valueState, invalidateEntryOnChange, msg, System.currentTimeMillis(), entryId); future.addListener(new FutureListener() { @Override diff --git a/redisson/src/main/java/org/redisson/RedissonMap.java b/redisson/src/main/java/org/redisson/RedissonMap.java index 14db82119..d48f6d418 100644 --- a/redisson/src/main/java/org/redisson/RedissonMap.java +++ b/redisson/src/main/java/org/redisson/RedissonMap.java @@ -33,6 +33,9 @@ import java.util.UUID; import org.redisson.api.RFuture; import org.redisson.api.RLock; import org.redisson.api.RMap; +import org.redisson.api.RReadWriteLock; +import org.redisson.api.RedissonClient; +import org.redisson.api.mapreduce.RMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.codec.MapScanCodec; import org.redisson.client.codec.StringCodec; @@ -46,6 +49,7 @@ import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.command.CommandAsyncExecutor; import org.redisson.command.CommandExecutor; import org.redisson.connection.decoder.MapGetAllDecoder; +import org.redisson.mapreduce.RedissonMapReduce; import org.redisson.misc.Hash; /** @@ -66,15 +70,23 @@ public class RedissonMap extends RedissonExpirable implements RMap { static final RedisCommand EVAL_PUT = EVAL_REPLACE; private final UUID id; + private final RedissonClient redisson; - protected RedissonMap(UUID id, CommandAsyncExecutor commandExecutor, String name) { + protected RedissonMap(UUID id, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(commandExecutor, name); this.id = id; + this.redisson = redisson; } - public RedissonMap(UUID id, Codec codec, CommandAsyncExecutor commandExecutor, String name) { + public RedissonMap(UUID id, Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(codec, commandExecutor, name); this.id = id; + this.redisson = redisson; + } + + @Override + public RMapReduce mapReduce() { + return new RedissonMapReduce(this, redisson, commandExecutor.getConnectionManager()); } @Override @@ -83,10 +95,16 @@ public class RedissonMap extends RedissonExpirable implements RMap { return new RedissonLock((CommandExecutor)commandExecutor, lockName, id); } + @Override + public RReadWriteLock getReadWriteLock(K key) { + String lockName = getLockName(key); + return new RedissonReadWriteLock((CommandExecutor)commandExecutor, lockName, id); + } + private String getLockName(Object key) { try { byte[] keyState = codec.getMapKeyEncoder().encode(key); - return "{" + getName() + "}:" + Hash.hashToBase64(keyState) + ":key"; + return suffixName(getName(), Hash.hashToBase64(keyState) + ":key"); } catch (IOException e) { throw new IllegalStateException(e); } @@ -260,6 +278,17 @@ public class RedissonMap extends RedissonExpirable implements RMap { return commandExecutor.readAsync(getName(), codec, RedisCommands.HGETALL_ENTRY, getName()); } + @Override + public Map readAllMap() { + return get(readAllMapAsync()); + } + + @Override + public RFuture> readAllMapAsync() { + return commandExecutor.readAsync(getName(), codec, RedisCommands.HGETALL, getName()); + } + + @Override public V putIfAbsent(K key, V value) { return get(putIfAbsentAsync(key, value)); @@ -385,10 +414,6 @@ public class RedissonMap extends RedissonExpirable implements RMap { return commandExecutor.readAsync(getName(key), codec, RedisCommands.HGET, getName(key), key); } - protected String getName(Object key) { - return getName(); - } - @Override public RFuture putAsync(K key, V value) { if (key == null) { diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index b0005bca3..049dff84c 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RFuture; import org.redisson.api.RMapCache; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.MapScanCodec; @@ -50,6 +51,10 @@ import org.redisson.eviction.EvictionScheduler; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import java.io.IOException; +import java.math.BigDecimal; +import org.redisson.client.codec.StringCodec; +import org.redisson.client.protocol.convertor.NumberConvertor; /** *

Map-based cache with ability to set TTL for each entry via @@ -80,27 +85,29 @@ public class RedissonMapCache extends RedissonMap implements RMapCac private static final RedisCommand EVAL_REMOVE = new RedisCommand("EVAL", 4, ValueType.MAP_KEY, ValueType.MAP_VALUE); private static final RedisCommand EVAL_REMOVE_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 5, ValueType.MAP); private static final RedisCommand EVAL_PUT_TTL = new RedisCommand("EVAL", 9, ValueType.MAP, ValueType.MAP_VALUE); + private static final RedisCommand EVAL_PUT_TTL_IF_ABSENT = new RedisCommand("EVAL", 10, ValueType.MAP, ValueType.MAP_VALUE); private static final RedisCommand EVAL_FAST_PUT_TTL = new RedisCommand("EVAL", new BooleanReplayConvertor(), 9, ValueType.MAP, ValueType.MAP_VALUE); + private static final RedisCommand EVAL_FAST_PUT_TTL_IF_ABSENT = new RedisCommand("EVAL", new BooleanReplayConvertor(), 10, ValueType.MAP, ValueType.MAP_VALUE); private static final RedisCommand EVAL_GET_TTL = new RedisCommand("EVAL", 7, ValueType.MAP_KEY, ValueType.MAP_VALUE); private static final RedisCommand EVAL_CONTAINS_KEY = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, ValueType.MAP_KEY); static final RedisCommand EVAL_CONTAINS_VALUE = new RedisCommand("EVAL", new BooleanReplayConvertor(), 7, ValueType.MAP_VALUE); static final RedisCommand EVAL_FAST_REMOVE = new RedisCommand("EVAL", 5, ValueType.MAP_KEY); - RedissonMapCache(UUID id, CommandAsyncExecutor commandExecutor, String name) { - super(id, commandExecutor, name); + RedissonMapCache(UUID id, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(id, commandExecutor, name, redisson); } - RedissonMapCache(UUID id, Codec codec, CommandAsyncExecutor commandExecutor, String name) { - super(id, codec, commandExecutor, name); + RedissonMapCache(UUID id, Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(id, codec, commandExecutor, name, redisson); } - public RedissonMapCache(UUID id, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { - super(id, commandExecutor, name); + public RedissonMapCache(UUID id, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(id, commandExecutor, name, redisson); evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName()); } - public RedissonMapCache(UUID id, Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { - super(id, codec, commandExecutor, name); + public RedissonMapCache(UUID id, Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(id, codec, commandExecutor, name, redisson); evictionScheduler.schedule(getName(), getTimeoutSetName(), getIdleSetName()); } @@ -267,26 +274,57 @@ public class RedissonMapCache extends RedissonMap implements RMapCac maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta; } - return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_TTL, - "if redis.call('hexists', KEYS[1], ARGV[4]) == 0 then " - + "if tonumber(ARGV[1]) > 0 then " - + "redis.call('zadd', KEYS[2], ARGV[1], ARGV[4]); " - + "end; " - + "if tonumber(ARGV[2]) > 0 then " - + "redis.call('zadd', KEYS[3], ARGV[2], ARGV[4]); " - + "end; " - + "local value = struct.pack('dLc0', ARGV[3], string.len(ARGV[5]), ARGV[5]); " - + "redis.call('hset', KEYS[1], ARGV[4], value); " - + "return nil; " - + "else " - + "local value = redis.call('hget', KEYS[1], ARGV[4]); " - + "if value == false then " - + "return nil; " - + "end;" - + "local t, val = struct.unpack('dLc0', value); " - + "return val; " - + "end", - Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value); + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT_TTL_IF_ABSENT, + "local insertable = false; " + + "local value = redis.call('hget', KEYS[1], ARGV[5]); " + + "if value == false then " + + "insertable = true; " + + "else " + + "if insertable == false then " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "insertable = true; " + + "end; " + + "end; " + + "end; " + + + "if insertable == true then " + // ttl + + "if tonumber(ARGV[2]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[5]); " + + "end; " + + // idle + + "if tonumber(ARGV[3]) > 0 then " + + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[3], ARGV[5]); " + + "end; " + + // value + + "local val = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('hset', KEYS[1], ARGV[5], val); " + + + "return nil;" + + "else " + + "local t, val = struct.unpack('dLc0', value); " + + "redis.call('zadd', KEYS[3], t + ARGV[1], ARGV[5]); " + + "return val;" + + "end; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value); } @Override @@ -362,7 +400,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT, "local value = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); " + "if redis.call('hsetnx', KEYS[1], ARGV[1], value) == 1 then " - + "return nil " + + "return nil;" + "else " + "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "if v == false then " @@ -374,6 +412,56 @@ public class RedissonMapCache extends RedissonMap implements RMapCac Collections.singletonList(getName(key)), key, value); } + @Override + public V addAndGet(K key, Number value) { + return get(addAndGetAsync(key, value)); + } + + @Override + public RFuture addAndGetAsync(K key, Number value) { + byte[] keyState = encodeMapKey(key); + byte[] valueState; + try { + valueState = StringCodec.INSTANCE + .getMapValueEncoder() + .encode(new BigDecimal(value.toString()).toPlainString()); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + return commandExecutor.evalWriteAsync(getName(key), StringCodec.INSTANCE, + new RedisCommand("EVAL", new NumberConvertor(value.getClass())), + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "local expireDate = 92233720368547758; " + + "local t = 0; " + + "local val = 0; " + + "if value ~= false then " + + "t, val = struct.unpack('dLc0', value); " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "end; " + + "local newValue = tonumber(ARGV[3]); " + + "if expireDate >= tonumber(ARGV[1]) then " + + "newValue = tonumber(val) + newValue; " + + "end; " + + "local newValuePack = struct.pack('dLc0', t + tonumber(ARGV[1]), string.len(newValue), newValue); " + + "redis.call('hset', KEYS[1], ARGV[2], newValuePack); " + + "return tostring(newValue); ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), keyState, valueState); + } + @Override public boolean fastPut(K key, V value, long ttl, TimeUnit ttlUnit) { return get(fastPutAsync(key, value, ttl, ttlUnit)); @@ -685,6 +773,99 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "return 1; ", Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), key, value); } + + @Override + public boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit) { + return fastPutIfAbsent(key, value, ttl, ttlUnit, 0, null); + } + + @Override + public boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { + return get(fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit)); + } + + @Override + public RFuture fastPutIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { + if (ttl < 0) { + throw new IllegalArgumentException("ttl can't be negative"); + } + if (maxIdleTime < 0) { + throw new IllegalArgumentException("maxIdleTime can't be negative"); + } + if (ttl == 0 && maxIdleTime == 0) { + return fastPutIfAbsentAsync(key, value); + } + + if (ttl > 0 && ttlUnit == null) { + throw new NullPointerException("ttlUnit param can't be null"); + } + + if (maxIdleTime > 0 && maxIdleUnit == null) { + throw new NullPointerException("maxIdleUnit param can't be null"); + } + + long ttlTimeout = 0; + if (ttl > 0) { + ttlTimeout = System.currentTimeMillis() + ttlUnit.toMillis(ttl); + } + + long maxIdleTimeout = 0; + long maxIdleDelta = 0; + if (maxIdleTime > 0) { + maxIdleDelta = maxIdleUnit.toMillis(maxIdleTime); + maxIdleTimeout = System.currentTimeMillis() + maxIdleDelta; + } + + return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_FAST_PUT_TTL_IF_ABSENT, + "local insertable = false; " + + "local value = redis.call('hget', KEYS[1], ARGV[5]); " + + "if value == false then " + + "insertable = true; " + + "else " + + "if insertable == false then " + + "local t, val = struct.unpack('dLc0', value); " + + "local expireDate = 92233720368547758; " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); " + + "if expireIdle ~= false then " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "if expireDate <= tonumber(ARGV[1]) then " + + "insertable = true; " + + "end; " + + "end; " + + "end; " + + + "if insertable == true then " + // ttl + + "if tonumber(ARGV[2]) > 0 then " + + "redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[2], ARGV[5]); " + + "end; " + + // idle + + "if tonumber(ARGV[3]) > 0 then " + + "redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); " + + "else " + + "redis.call('zrem', KEYS[3], ARGV[5]); " + + "end; " + + // value + + "local val = struct.pack('dLc0', ARGV[4], string.len(ARGV[6]), ARGV[6]); " + + "redis.call('hset', KEYS[1], ARGV[5], val); " + + + "return 1; " + + "else " + + "return 0; " + + "end; ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), ttlTimeout, maxIdleTimeout, maxIdleDelta, key, value); + } @Override public RFuture replaceAsync(K key, V oldValue, V newValue) { @@ -835,7 +1016,11 @@ public class RedissonMapCache extends RedissonMap implements RMapCac @Override public RFuture>> readAllEntrySetAsync() { - return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_MAP_ENTRY, + return readAll(RedisCommands.EVAL_MAP_ENTRY); + } + + private RFuture readAll(RedisCommand evalCommandType) { + return commandExecutor.evalWriteAsync(getName(), codec, evalCommandType, "local s = redis.call('hgetall', KEYS[1]); " + "local result = {}; " + "for i, v in ipairs(s) do " @@ -867,6 +1052,12 @@ public class RedissonMapCache extends RedissonMap implements RMapCac "return result;", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis()); } + + @Override + public RFuture> readAllMapAsync() { + return readAll(RedisCommands.EVAL_MAP); + } + @Override public RFuture> readAllValuesAsync() { @@ -901,5 +1092,4 @@ public class RedissonMapCache extends RedissonMap implements RMapCac "return result;", Arrays.asList(getName(), getTimeoutSetName(), getIdleSetName()), System.currentTimeMillis()); } - } diff --git a/redisson/src/main/java/org/redisson/RedissonMultimap.java b/redisson/src/main/java/org/redisson/RedissonMultimap.java index ace7843a1..e14002f71 100644 --- a/redisson/src/main/java/org/redisson/RedissonMultimap.java +++ b/redisson/src/main/java/org/redisson/RedissonMultimap.java @@ -33,6 +33,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RFuture; import org.redisson.api.RLock; import org.redisson.api.RMultimap; +import org.redisson.api.RReadWriteLock; import org.redisson.client.codec.Codec; import org.redisson.client.codec.LongCodec; import org.redisson.client.codec.MapScanCodec; @@ -71,6 +72,12 @@ public abstract class RedissonMultimap extends RedissonExpirable implement return new RedissonLock((CommandExecutor)commandExecutor, lockName, id); } + @Override + public RReadWriteLock getReadWriteLock(K key) { + String lockName = getLockName(key); + return new RedissonReadWriteLock((CommandExecutor)commandExecutor, lockName, id); + } + private String getLockName(Object key) { try { byte[] keyState = codec.getMapKeyEncoder().encode(key); @@ -168,6 +175,16 @@ public abstract class RedissonMultimap extends RedissonExpirable implement return new EntrySet(); } + @Override + public Set readAllKeySet() { + return get(readAllKeySetAsync()); + } + + @Override + public RFuture> readAllKeySetAsync() { + return commandExecutor.readAsync(getName(), codec, RedisCommands.HKEYS, getName()); + } + @Override public long fastRemove(K ... keys) { return get(fastRemoveAsync(keys)); diff --git a/redisson/src/main/java/org/redisson/RedissonNode.java b/redisson/src/main/java/org/redisson/RedissonNode.java index 52e30a141..e14db7bb5 100644 --- a/redisson/src/main/java/org/redisson/RedissonNode.java +++ b/redisson/src/main/java/org/redisson/RedissonNode.java @@ -20,10 +20,10 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.Map.Entry; +import org.redisson.api.RExecutorService; import org.redisson.api.RFuture; import org.redisson.api.RedissonClient; import org.redisson.client.RedisConnection; -import org.redisson.client.protocol.RedisCommands; import org.redisson.config.RedissonNodeConfig; import org.redisson.connection.ConnectionManager; import org.redisson.connection.MasterSlaveEntry; @@ -134,6 +134,12 @@ public class RedissonNode { config.getRedissonNodeInitializer().onStartup(this); } + int mapReduceWorkers = config.getMapReduceWorkers(); + if (mapReduceWorkers == 0) { + mapReduceWorkers = Runtime.getRuntime().availableProcessors(); + } + redisson.getExecutorService(RExecutorService.MAPREDUCE_NAME).registerWorkers(mapReduceWorkers); + for (Entry entry : config.getExecutorServiceWorkers().entrySet()) { String name = entry.getKey(); int workers = entry.getValue(); diff --git a/redisson/src/main/java/org/redisson/RedissonObject.java b/redisson/src/main/java/org/redisson/RedissonObject.java index 79915fedf..5c5a1aba8 100644 --- a/redisson/src/main/java/org/redisson/RedissonObject.java +++ b/redisson/src/main/java/org/redisson/RedissonObject.java @@ -136,6 +136,16 @@ public abstract class RedissonObject implements RObject { return commandExecutor.writeAsync(getName(), RedisCommands.DEL_BOOL, getName()); } + @Override + public boolean touch() { + return get(touchAsync()); + } + + @Override + public RFuture touchAsync() { + return commandExecutor.writeAsync(getName(), codec, RedisCommands.TOUCH, getName()); + } + @Override public boolean isExists() { return get(isExistsAsync()); diff --git a/redisson/src/main/java/org/redisson/RedissonPriorityQueue.java b/redisson/src/main/java/org/redisson/RedissonPriorityQueue.java index d5aa64722..8cb18e943 100644 --- a/redisson/src/main/java/org/redisson/RedissonPriorityQueue.java +++ b/redisson/src/main/java/org/redisson/RedissonPriorityQueue.java @@ -93,7 +93,7 @@ public class RedissonPriorityQueue extends RedissonList implements RPriori private RBucket comparatorHolder; protected RedissonPriorityQueue(CommandExecutor commandExecutor, String name, Redisson redisson) { - super(commandExecutor, name); + super(commandExecutor, name, redisson); this.commandExecutor = commandExecutor; comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE); @@ -103,7 +103,7 @@ public class RedissonPriorityQueue extends RedissonList implements RPriori } public RedissonPriorityQueue(Codec codec, CommandExecutor commandExecutor, String name, Redisson redisson) { - super(codec, commandExecutor, name); + super(codec, commandExecutor, name, redisson); this.commandExecutor = commandExecutor; comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE); diff --git a/redisson/src/main/java/org/redisson/RedissonQueue.java b/redisson/src/main/java/org/redisson/RedissonQueue.java index c1cbba746..7f387861b 100644 --- a/redisson/src/main/java/org/redisson/RedissonQueue.java +++ b/redisson/src/main/java/org/redisson/RedissonQueue.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RFuture; import org.redisson.api.RQueue; +import org.redisson.api.RedissonClient; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandAsyncExecutor; @@ -33,12 +34,12 @@ import org.redisson.command.CommandAsyncExecutor; */ public class RedissonQueue extends RedissonList implements RQueue { - protected RedissonQueue(CommandAsyncExecutor commandExecutor, String name) { - super(commandExecutor, name); + protected RedissonQueue(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(commandExecutor, name, redisson); } - protected RedissonQueue(Codec codec, CommandAsyncExecutor commandExecutor, String name) { - super(codec, commandExecutor, name); + protected RedissonQueue(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { + super(codec, commandExecutor, name, redisson); } @Override @@ -115,9 +116,4 @@ public class RedissonQueue extends RedissonList implements RQueue { return commandExecutor.writeAsync(getName(), codec, RedisCommands.RPOPLPUSH, getName(), queueName); } - @Override - public V pollLastAndOfferFirstTo(RQueue queue) { - return pollLastAndOfferFirstTo(queue.getName()); - } - } diff --git a/redisson/src/main/java/org/redisson/RedissonRemoteService.java b/redisson/src/main/java/org/redisson/RedissonRemoteService.java index a6eb65a2f..17cf21b90 100644 --- a/redisson/src/main/java/org/redisson/RedissonRemoteService.java +++ b/redisson/src/main/java/org/redisson/RedissonRemoteService.java @@ -17,8 +17,10 @@ package org.redisson; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -59,6 +61,7 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS private static final Logger log = LoggerFactory.getLogger(RedissonRemoteService.class); private final Map beans = PlatformDependent.newConcurrentHashMap(); + private final Map, Set>> futures = PlatformDependent.newConcurrentHashMap(); public RedissonRemoteService(RedissonClient redisson, CommandExecutor commandExecutor) { super(redisson, commandExecutor); @@ -81,6 +84,23 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS register(remoteInterface, object, 1); } + @Override + public void deregister(Class remoteInterface) { + for (Method method : remoteInterface.getMethods()) { + RemoteServiceKey key = new RemoteServiceKey(remoteInterface, method.getName(), getMethodSignatures(method)); + beans.remove(key); + } + + Set> removedFutures = futures.remove(remoteInterface); + if (removedFutures == null) { + return; + } + + for (RFuture future : removedFutures) { + future.cancel(false); + } + } + @Override public void register(Class remoteInterface, T object, int workers) { register(remoteInterface, object, workers, commandExecutor.getConnectionManager().getExecutor()); @@ -99,19 +119,33 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS } } + Set> values = Collections.newSetFromMap(PlatformDependent., Boolean>newConcurrentHashMap()); + futures.put(remoteInterface, values); + + String requestQueueName = getRequestQueueName(remoteInterface); + RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName, getCodec()); for (int i = 0; i < workers; i++) { - String requestQueueName = getRequestQueueName(remoteInterface); - RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName, getCodec()); subscribe(remoteInterface, requestQueue, executor); } } private void subscribe(final Class remoteInterface, final RBlockingQueue requestQueue, final ExecutorService executor) { - RFuture take = requestQueue.takeAsync(); + Set> futuresSet = futures.get(remoteInterface); + if (futuresSet == null) { + return; + } + final RFuture take = requestQueue.takeAsync(); + futuresSet.add(take); take.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { + Set> futuresSet = futures.get(remoteInterface); + if (futuresSet == null) { + return; + } + futuresSet.remove(take); + if (!future.isSuccess()) { if (future.cause() instanceof RedissonShutdownException) { return; @@ -157,10 +191,10 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS @Override public void operationComplete(Future future) throws Exception { if (!future.isSuccess()) { - log.error("Can't send ack for request: " + request, future.cause()); if (future.cause() instanceof RedissonShutdownException) { return; } + log.error("Can't send ack for request: " + request, future.cause()); // re-subscribe after a failed send (ack) subscribe(remoteInterface, requestQueue, executor); return; @@ -260,12 +294,17 @@ public class RedissonRemoteService extends BaseRemoteService implements RRemoteS clientsFuture.addListener(new FutureListener>() { @Override public void operationComplete(Future> future) throws Exception { + // interface has been deregistered + if (futures.get(remoteInterface) == null) { + return; + } + if (!future.isSuccess()) { - log.error("Can't send response: " + responseHolder.get() + " for request: " + request, - future.cause()); if (future.cause() instanceof RedissonShutdownException) { return; } + log.error("Can't send response: " + responseHolder.get() + " for request: " + request, + future.cause()); } // re-subscribe anyways (fail or success) after the send diff --git a/redisson/src/main/java/org/redisson/RedissonScoredSortedSet.java b/redisson/src/main/java/org/redisson/RedissonScoredSortedSet.java index a4d7cb95f..7bf4a1521 100644 --- a/redisson/src/main/java/org/redisson/RedissonScoredSortedSet.java +++ b/redisson/src/main/java/org/redisson/RedissonScoredSortedSet.java @@ -30,7 +30,9 @@ import java.util.Map.Entry; import org.redisson.api.RFuture; import org.redisson.api.RScoredSortedSet; +import org.redisson.api.RedissonClient; import org.redisson.api.SortOrder; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.codec.DoubleCodec; import org.redisson.client.codec.LongCodec; @@ -44,6 +46,7 @@ import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.command.CommandAsyncExecutor; +import org.redisson.mapreduce.RedissonCollectionMapReduce; /** * @@ -53,12 +56,21 @@ import org.redisson.command.CommandAsyncExecutor; */ public class RedissonScoredSortedSet extends RedissonExpirable implements RScoredSortedSet { - public RedissonScoredSortedSet(CommandAsyncExecutor commandExecutor, String name) { + private RedissonClient redisson; + + public RedissonScoredSortedSet(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(commandExecutor, name); + this.redisson = redisson; } - public RedissonScoredSortedSet(Codec codec, CommandAsyncExecutor commandExecutor, String name) { + public RedissonScoredSortedSet(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(codec, commandExecutor, name); + this.redisson = redisson; + } + + @Override + public RCollectionMapReduce mapReduce() { + return new RedissonCollectionMapReduce(this, redisson, commandExecutor.getConnectionManager()); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonSet.java b/redisson/src/main/java/org/redisson/RedissonSet.java index 590461ccf..ec4a64fdb 100644 --- a/redisson/src/main/java/org/redisson/RedissonSet.java +++ b/redisson/src/main/java/org/redisson/RedissonSet.java @@ -26,7 +26,9 @@ import java.util.Set; import org.redisson.api.RFuture; import org.redisson.api.RSet; +import org.redisson.api.RedissonClient; import org.redisson.api.SortOrder; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.codec.ScanCodec; import org.redisson.client.protocol.RedisCommand; @@ -36,6 +38,7 @@ import org.redisson.client.protocol.convertor.BooleanReplayConvertor; import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.command.CommandAsyncExecutor; +import org.redisson.mapreduce.RedissonCollectionMapReduce; /** * Distributed and concurrent implementation of {@link java.util.Set} @@ -46,12 +49,21 @@ import org.redisson.command.CommandAsyncExecutor; */ public class RedissonSet extends RedissonExpirable implements RSet, ScanIterator { - protected RedissonSet(CommandAsyncExecutor commandExecutor, String name) { + RedissonClient redisson; + + protected RedissonSet(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(commandExecutor, name); + this.redisson = redisson; } - public RedissonSet(Codec codec, CommandAsyncExecutor commandExecutor, String name) { + public RedissonSet(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(codec, commandExecutor, name); + this.redisson = redisson; + } + + @Override + public RCollectionMapReduce mapReduce() { + return new RedissonCollectionMapReduce(this, redisson, commandExecutor.getConnectionManager()); } @Override @@ -79,10 +91,6 @@ public class RedissonSet extends RedissonExpirable implements RSet, ScanIt return commandExecutor.readAsync(getName(o), codec, RedisCommands.SISMEMBER, getName(o), o); } - protected String getName(Object o) { - return getName(); - } - @Override public ListScanResult scanIterator(String name, InetSocketAddress client, long startPos) { RFuture> f = commandExecutor.readAsync(client, name, new ScanCodec(codec), RedisCommands.SSCAN, name, startPos); diff --git a/redisson/src/main/java/org/redisson/RedissonSetCache.java b/redisson/src/main/java/org/redisson/RedissonSetCache.java index 131b5ab96..cb0418d0e 100644 --- a/redisson/src/main/java/org/redisson/RedissonSetCache.java +++ b/redisson/src/main/java/org/redisson/RedissonSetCache.java @@ -27,6 +27,8 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RFuture; import org.redisson.api.RSetCache; +import org.redisson.api.RedissonClient; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.codec.ScanCodec; import org.redisson.client.protocol.RedisCommand; @@ -38,6 +40,7 @@ import org.redisson.client.protocol.decoder.ListScanResult; import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.command.CommandAsyncExecutor; import org.redisson.eviction.EvictionScheduler; +import org.redisson.mapreduce.RedissonCollectionMapReduce; /** *

Set-based cache with ability to set TTL for each entry via @@ -59,22 +62,27 @@ import org.redisson.eviction.EvictionScheduler; */ public class RedissonSetCache extends RedissonExpirable implements RSetCache, ScanIterator { - RedissonSetCache(CommandAsyncExecutor commandExecutor, String name) { + RedissonClient redisson; + + public RedissonSetCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(commandExecutor, name); + if (evictionScheduler != null) { + evictionScheduler.schedule(getName(), 0); + } + this.redisson = redisson; } - RedissonSetCache(Codec codec, CommandAsyncExecutor commandExecutor, String name) { + public RedissonSetCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) { super(codec, commandExecutor, name); + if (evictionScheduler != null) { + evictionScheduler.schedule(getName(), 0); + } + this.redisson = redisson; } - public RedissonSetCache(EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { - super(commandExecutor, name); - evictionScheduler.schedule(getName()); - } - - public RedissonSetCache(Codec codec, EvictionScheduler evictionScheduler, CommandAsyncExecutor commandExecutor, String name) { - super(codec, commandExecutor, name); - evictionScheduler.schedule(getName()); + @Override + public RCollectionMapReduce mapReduce() { + return new RedissonCollectionMapReduce(this, redisson, commandExecutor.getConnectionManager()); } @Override diff --git a/redisson/src/main/java/org/redisson/RedissonSetMultimap.java b/redisson/src/main/java/org/redisson/RedissonSetMultimap.java index bb2c40e34..5b8caffff 100644 --- a/redisson/src/main/java/org/redisson/RedissonSetMultimap.java +++ b/redisson/src/main/java/org/redisson/RedissonSetMultimap.java @@ -162,7 +162,7 @@ public class RedissonSetMultimap extends RedissonMultimap implements final String keyHash = hash(keyState); final String setName = getValuesName(keyHash); - return new RedissonSet(codec, commandExecutor, setName) { + return new RedissonSet(codec, commandExecutor, setName, null) { @Override public RFuture deleteAsync() { diff --git a/redisson/src/main/java/org/redisson/RedissonSetMultimapIterator.java b/redisson/src/main/java/org/redisson/RedissonSetMultimapIterator.java index 6045bf46f..ec067b840 100644 --- a/redisson/src/main/java/org/redisson/RedissonSetMultimapIterator.java +++ b/redisson/src/main/java/org/redisson/RedissonSetMultimapIterator.java @@ -28,7 +28,7 @@ public class RedissonSetMultimapIterator extends RedissonMultiMapIterat @Override protected Iterator getIterator(String name) { - RedissonSet set = new RedissonSet(codec, commandExecutor, map.getValuesName(name)); + RedissonSet set = new RedissonSet(codec, commandExecutor, map.getValuesName(name), null); return set.iterator(); } diff --git a/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java b/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java index 239a7d771..12ea3d8af 100644 --- a/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java +++ b/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java @@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit; import org.redisson.api.RFuture; import org.redisson.api.RSet; import org.redisson.api.SortOrder; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.codec.ScanCodec; import org.redisson.client.protocol.RedisCommand; @@ -66,7 +67,7 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R super(codec, commandExecutor, name); this.timeoutSetName = timeoutSetName; this.key = key; - this.set = new RedissonSet(codec, commandExecutor, name); + this.set = new RedissonSet(codec, commandExecutor, name, null); } @Override @@ -74,6 +75,11 @@ public class RedissonSetMultimapValues extends RedissonExpirable implements R return get(sizeAsync()); } + @Override + public RCollectionMapReduce mapReduce() { + return null; + } + @Override public RFuture clearExpireAsync() { throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set"); diff --git a/redisson/src/main/java/org/redisson/RedissonShutdownException.java b/redisson/src/main/java/org/redisson/RedissonShutdownException.java index f471aa3f3..b75c8277b 100644 --- a/redisson/src/main/java/org/redisson/RedissonShutdownException.java +++ b/redisson/src/main/java/org/redisson/RedissonShutdownException.java @@ -15,7 +15,9 @@ */ package org.redisson; -public class RedissonShutdownException extends RuntimeException { +import org.redisson.client.RedisException; + +public class RedissonShutdownException extends RedisException { private static final long serialVersionUID = -2694051226420789395L; diff --git a/redisson/src/main/java/org/redisson/RedissonSortedSet.java b/redisson/src/main/java/org/redisson/RedissonSortedSet.java index 5184a2789..dd0ee87d0 100644 --- a/redisson/src/main/java/org/redisson/RedissonSortedSet.java +++ b/redisson/src/main/java/org/redisson/RedissonSortedSet.java @@ -32,10 +32,13 @@ import org.redisson.api.RBucket; import org.redisson.api.RFuture; import org.redisson.api.RLock; import org.redisson.api.RSortedSet; +import org.redisson.api.RedissonClient; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.codec.Codec; import org.redisson.client.codec.StringCodec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandExecutor; +import org.redisson.mapreduce.RedissonCollectionMapReduce; import org.redisson.misc.RPromise; /** @@ -94,10 +97,12 @@ public class RedissonSortedSet extends RedissonObject implements RSortedSet list; private RBucket comparatorHolder; + private RedissonClient redisson; - protected RedissonSortedSet(CommandExecutor commandExecutor, String name, Redisson redisson) { + protected RedissonSortedSet(CommandExecutor commandExecutor, String name, RedissonClient redisson) { super(commandExecutor, name); this.commandExecutor = commandExecutor; + this.redisson = redisson; comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE); lock = redisson.getLock("redisson_sortedset_lock:{" + getName() + "}"); @@ -116,6 +121,11 @@ public class RedissonSortedSet extends RedissonObject implements RSortedSet RCollectionMapReduce mapReduce() { + return new RedissonCollectionMapReduce(this, redisson, commandExecutor.getConnectionManager()); + } private void loadComparator() { try { diff --git a/redisson/src/main/java/org/redisson/RedissonSubList.java b/redisson/src/main/java/org/redisson/RedissonSubList.java index 2246e1575..4896114a6 100644 --- a/redisson/src/main/java/org/redisson/RedissonSubList.java +++ b/redisson/src/main/java/org/redisson/RedissonSubList.java @@ -58,7 +58,7 @@ public class RedissonSubList extends RedissonList implements RList { int size = -1; protected RedissonSubList(Codec codec, CommandAsyncExecutor commandExecutor, String name, int fromIndex, int toIndex) { - super(codec, commandExecutor, name); + super(codec, commandExecutor, name, null); this.fromIndex = fromIndex; this.toIndex.set(toIndex); } diff --git a/redisson/src/main/java/org/redisson/api/LocalCachedMapOptions.java b/redisson/src/main/java/org/redisson/api/LocalCachedMapOptions.java index 523cc5ff5..fcecb42eb 100644 --- a/redisson/src/main/java/org/redisson/api/LocalCachedMapOptions.java +++ b/redisson/src/main/java/org/redisson/api/LocalCachedMapOptions.java @@ -25,9 +25,69 @@ import java.util.concurrent.TimeUnit; */ public class LocalCachedMapOptions { - public enum EvictionPolicy {NONE, LRU, LFU, SOFT}; + public enum InvalidationPolicy { + + /** + * No invalidation on map changes + */ + NONE, + + /** + * Invalidate cache entry across all LocalCachedMap instances on map entry change. + */ + ON_CHANGE, + + /** + * Invalidate cache entry across all LocalCachedMap instances on map entry change. + *

+ * Clear cache if LocalCachedMap instance has been disconnected for a while. + * It's applied to avoid stale objects in cache. + */ + ON_CHANGE_WITH_CLEAR_ON_RECONNECT, + + /** + * Invalidate cache entry across all LocalCachedMap instances on map entry change. + *

+ * Store invalidated entry hash in invalidation log for 10 minutes. + * Cache keys for stored invalidated entry hashes will be removed + * if LocalCachedMap instance has been disconnected less than 10 minutes + * or whole cache will be cleaned otherwise. + * It's applied to avoid stale objects in cache. + */ + ON_CHANGE_WITH_LOAD_ON_RECONNECT + } + + public enum EvictionPolicy { + + /** + * Cache without eviction. + */ + NONE, + + /** + * Least Recently Used cache. + */ + LRU, + + /** + * Least Frequently Used cache. + */ + LFU, + + /** + * Cache with Soft Reference used for values. + * All references will be collected by GC + */ + SOFT, + + /** + * Cache with Weak Reference used for values. + * All references will be collected by GC + */ + WEAK + }; - private boolean invalidateEntryOnChange; + private InvalidationPolicy invalidationPolicy; private EvictionPolicy evictionPolicy; private int cacheSize; private long timeToLiveInMillis; @@ -37,7 +97,7 @@ public class LocalCachedMapOptions { } protected LocalCachedMapOptions(LocalCachedMapOptions copy) { - this.invalidateEntryOnChange = copy.invalidateEntryOnChange; + this.invalidationPolicy = copy.invalidationPolicy; this.evictionPolicy = copy.evictionPolicy; this.cacheSize = copy.cacheSize; this.timeToLiveInMillis = copy.timeToLiveInMillis; @@ -62,13 +122,9 @@ public class LocalCachedMapOptions { return new LocalCachedMapOptions() .cacheSize(0).timeToLive(0).maxIdle(0) .evictionPolicy(EvictionPolicy.NONE) - .invalidateEntryOnChange(true); + .invalidationPolicy(InvalidationPolicy.ON_CHANGE); } - public boolean isInvalidateEntryOnChange() { - return invalidateEntryOnChange; - } - public EvictionPolicy getEvictionPolicy() { return evictionPolicy; } @@ -95,6 +151,27 @@ public class LocalCachedMapOptions { this.cacheSize = cacheSize; return this; } + + public InvalidationPolicy getInvalidationPolicy() { + return invalidationPolicy; + } + + /** + * Sets entry invalidation policy. + * + * @param invalidationPolicy + *

NONE - no invalidation applied. + *

ON_CHANGE - invalidation message which removes corresponding entry from cache + * will be sent to all other RLocalCachedMap instances on each entry update/remove operation. + *

ON_CHANGE_WITH_CLEAR_ON_RECONNECT - includes ON_CHANGE policy + * and clears local cache of current instance in case of reconnection to Redis. + * + * @return LocalCachedMapOptions instance + */ + public LocalCachedMapOptions invalidationPolicy(InvalidationPolicy invalidationPolicy) { + this.invalidationPolicy = invalidationPolicy; + return this; + } /** * Sets entry invalidation behavior. @@ -104,9 +181,12 @@ public class LocalCachedMapOptions { * if false then invalidation message won't be sent * @return LocalCachedMapOptions instance */ + @Deprecated public LocalCachedMapOptions invalidateEntryOnChange(boolean value) { - this.invalidateEntryOnChange = value; - return this; + if (value) { + return invalidationPolicy(InvalidationPolicy.ON_CHANGE); + } + return invalidationPolicy(InvalidationPolicy.NONE); } /** diff --git a/redisson/src/main/java/org/redisson/api/RBatch.java b/redisson/src/main/java/org/redisson/api/RBatch.java index 802b3ad4e..0521a575b 100644 --- a/redisson/src/main/java/org/redisson/api/RBatch.java +++ b/redisson/src/main/java/org/redisson/api/RBatch.java @@ -16,6 +16,7 @@ package org.redisson.api; import java.util.List; +import java.util.concurrent.TimeUnit; import org.redisson.client.RedisException; import org.redisson.client.codec.Codec; @@ -27,7 +28,7 @@ import org.redisson.client.codec.Codec; * from this interface are batched to separate queue and could be executed later * with execute() or executeAsync() methods. *

- * Please be ware, atomicity is not guaranteed. + * Please be aware, atomicity is not guaranteed. * * * @author Nikita Koksharov @@ -426,4 +427,46 @@ public interface RBatch { * */ RFuture executeSkipResultAsync(); + + /** + * Defines timeout for Redis response. + * Starts to countdown when Redis command has been successfully sent. + *

+ * 0 value means use Config.setTimeout value instead. + *

+ * Default is 0 + * + * @param timeout value + * @param unit value + * @return self instance + */ + RBatch timeout(long timeout, TimeUnit unit); + + /** + * Defines time interval for another one attempt send Redis commands batch + * if it hasn't been sent already. + *

+ * 0 value means use Config.setRetryInterval value instead. + *

+ * Default is 0 + * + * @param retryInterval value + * @param unit value + * @return self instance + */ + RBatch retryInterval(long retryInterval, TimeUnit unit); + + /** + * Defines attempts amount to re-send Redis commands batch + * if it hasn't been sent already. + *

+ * 0 value means use Config.setRetryAttempts value instead. + *

+ * Default is 0 + * + * @param retryAttempts value + * @return self instance + */ + RBatch retryAttempts(int retryAttempts); + } diff --git a/redisson/src/main/java/org/redisson/api/RExecutorService.java b/redisson/src/main/java/org/redisson/api/RExecutorService.java index 591365a86..8a3fd48d2 100644 --- a/redisson/src/main/java/org/redisson/api/RExecutorService.java +++ b/redisson/src/main/java/org/redisson/api/RExecutorService.java @@ -15,16 +15,59 @@ */ package org.redisson.api; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; /** - * Distributed implementation of {@link java.util.concurrent.ExecutorService} + * Redis based implementation of {@link java.util.concurrent.ExecutorService} * * @author Nikita Koksharov * */ public interface RExecutorService extends ExecutorService, RExecutorServiceAsync { + /** + * MapReduce's executor name + */ + String MAPREDUCE_NAME = "redisson_mapreduce"; + + /** + * Submits a value-returning task for execution and returns a + * Future representing the pending results of the task. The + * Future's {@code get} method will return the task's result upon + * successful completion. + * + * @param task the task to submit + * @param the type of the task's result + * @return a Future representing pending completion of the task + */ + @Override + RFuture submit(Callable task); + + /** + * Submits a Runnable task for execution and returns a Future + * representing that task. The Future's {@code get} method will + * return the given result upon successful completion. + * + * @param task the task to submit + * @param result the result to return + * @param the type of the result + * @return a Future representing pending completion of the task + */ + @Override + RFuture submit(Runnable task, T result);; + + /** + * Submits a Runnable task for execution and returns a Future + * representing that task. The Future's {@code get} method will + * return {@code null} upon successful completion. + * + * @param task the task to submit + * @return a Future representing pending completion of the task + */ + @Override + RFuture submit(Runnable task); + /** * Returns executor name * @@ -47,11 +90,18 @@ public interface RExecutorService extends ExecutorService, RExecutorServiceAsync void registerWorkers(int workers); /** - * Register workers with custom executor which executes each task + * Register workers with custom executor * * @param workers - workers amount * @param executor - executor instance */ void registerWorkers(int workers, ExecutorService executor); + /** + * Returns active worker groups + * + * @return active worker groups count + */ + int countActiveWorkers(); + } diff --git a/redisson/src/main/java/org/redisson/api/RExecutorServiceAsync.java b/redisson/src/main/java/org/redisson/api/RExecutorServiceAsync.java index e8e66c365..f439b4ee7 100644 --- a/redisson/src/main/java/org/redisson/api/RExecutorServiceAsync.java +++ b/redisson/src/main/java/org/redisson/api/RExecutorServiceAsync.java @@ -18,7 +18,7 @@ package org.redisson.api; import java.util.concurrent.Callable; /** - * Distributed implementation of {@link java.util.concurrent.ExecutorService} + * Redis based implementation of {@link java.util.concurrent.ExecutorService} * * @author Nikita Koksharov * @@ -33,7 +33,7 @@ public interface RExecutorServiceAsync { RFuture deleteAsync(); /** - * Submit task for execution in async mode with listeners support + * Use {@link RExecutorService#submit(Callable)} * * @param type of return value * @param task - task to execute @@ -42,7 +42,7 @@ public interface RExecutorServiceAsync { RFuture submitAsync(Callable task); /** - * Submit task for execution in async mode with listeners support + * Use {@link RExecutorService#submit(Runnable)} * * @param task - task to execute * @return Future object diff --git a/redisson/src/main/java/org/redisson/api/RKeys.java b/redisson/src/main/java/org/redisson/api/RKeys.java index 73ca62ffa..2357fb2f2 100644 --- a/redisson/src/main/java/org/redisson/api/RKeys.java +++ b/redisson/src/main/java/org/redisson/api/RKeys.java @@ -16,6 +16,7 @@ package org.redisson.api; import java.util.Collection; +import java.util.concurrent.TimeUnit; /** * @@ -24,13 +25,98 @@ import java.util.Collection; */ public interface RKeys extends RKeysAsync { + /** + * Move object to another database + * + * @param name of object + * @param database - Redis database number + * @return true if key was moved else false + */ + boolean move(String name, int database); + + /** + * Transfer an object from source Redis instance to destination Redis instance + * + * @param name of object + * @param host - destination host + * @param port - destination port + * @param database - destination database + */ + void migrate(String name, String host, int port, int database); + + /** + * Set a timeout for object. After the timeout has expired, + * the key will automatically be deleted. + * + * @param name of object + * @param timeToLive - timeout before object will be deleted + * @param timeUnit - timeout time unit + * @return true if the timeout was set and false if not + */ + boolean expire(String name, long timeToLive, TimeUnit timeUnit); + + /** + * Set an expire date for object. When expire date comes + * the key will automatically be deleted. + * + * @param name of object + * @param timestamp - expire date in milliseconds (Unix timestamp) + * @return true if the timeout was set and false if not + */ + boolean expireAt(String name, long timestamp); + + /** + * Clear an expire timeout or expire date for object. + * + * @param name of object + * @return true if timeout was removed + * false if object does not exist or does not have an associated timeout + */ + boolean clearExpire(String name); + + /** + * Rename object with oldName to newName + * only if new key is not exists + * + * @param oldName - old name of object + * @param newName - new name of object + * @return true if object has been renamed successfully and false otherwise + */ + boolean renamenx(String oldName, String newName); + + /** + * Rename current object key to newName + * + * @param currentName - current name of object + * @param newName - new name of object + */ + void rename(String currentName, String newName); + + /** + * Remaining time to live of Redisson object that has a timeout + * + * @param name of key + * @return time in milliseconds + * -2 if the key does not exist. + * -1 if the key exists but has no associated expire. + */ + long remainTimeToLive(String name); + + /** + * Update the last access time of an object. + * + * @param names of keys + * @return count of objects were touched + */ + long touch(String... names); + /** * Checks if provided keys exist * * @param names of keys * @return amount of existing keys */ - Long isExists(String... names); + long countExists(String... names); /** * Get Redis object type by key @@ -128,6 +214,14 @@ public interface RKeys extends RKeysAsync { */ long deleteByPattern(String pattern); + /** + * Delete multiple objects + * + * @param objects of Redisson + * @return number of removed keys + */ + long delete(RObject ... objects); + /** * Delete multiple objects by name * diff --git a/redisson/src/main/java/org/redisson/api/RKeysAsync.java b/redisson/src/main/java/org/redisson/api/RKeysAsync.java index 70c9772a6..acfebd599 100644 --- a/redisson/src/main/java/org/redisson/api/RKeysAsync.java +++ b/redisson/src/main/java/org/redisson/api/RKeysAsync.java @@ -16,6 +16,7 @@ package org.redisson.api; import java.util.Collection; +import java.util.concurrent.TimeUnit; /** * @@ -24,13 +25,98 @@ import java.util.Collection; */ public interface RKeysAsync { + /** + * Move object to another database + * + * @param name of object + * @param database - Redis database number + * @return true if key was moved else false + */ + RFuture moveAsync(String name, int database); + + /** + * Transfer an object from source Redis instance to destination Redis instance + * + * @param name of object + * @param host - destination host + * @param port - destination port + * @param database - destination database + */ + RFuture migrateAsync(String name, String host, int port, int database); + + /** + * Set a timeout for object. After the timeout has expired, + * the key will automatically be deleted. + * + * @param name of object + * @param timeToLive - timeout before object will be deleted + * @param timeUnit - timeout time unit + * @return true if the timeout was set and false if not + */ + RFuture expireAsync(String name, long timeToLive, TimeUnit timeUnit); + + /** + * Set an expire date for object. When expire date comes + * the key will automatically be deleted. + * + * @param name of object + * @param timestamp - expire date in milliseconds (Unix timestamp) + * @return true if the timeout was set and false if not + */ + RFuture expireAtAsync(String name, long timestamp); + + /** + * Clear an expire timeout or expire date for object. + * + * @param name of object + * @return true if timeout was removed + * false if object does not exist or does not have an associated timeout + */ + RFuture clearExpireAsync(String name); + + /** + * Rename object with oldName to newName + * only if new key is not exists + * + * @param oldName - old name of object + * @param newName - new name of object + * @return true if object has been renamed successfully and false otherwise + */ + RFuture renamenxAsync(String oldName, String newName); + + /** + * Rename current object key to newName + * + * @param currentName - current name of object + * @param newName - new name of object + */ + RFuture renameAsync(String currentName, String newName); + + /** + * Remaining time to live of Redisson object that has a timeout + * + * @param name of key + * @return time in milliseconds + * -2 if the key does not exist. + * -1 if the key exists but has no associated expire. + */ + RFuture remainTimeToLiveAsync(String name); + + /** + * Update the last access time of an object. + * + * @param names of keys + * @return count of objects were touched + */ + RFuture touchAsync(String... names); + /** * Checks if provided keys exist * * @param names of keys * @return amount of existing keys */ - RFuture isExistsAsync(String... names); + RFuture countExistsAsync(String... names); /** * Get Redis object type by key @@ -84,6 +170,14 @@ public interface RKeysAsync { */ RFuture deleteByPatternAsync(String pattern); + /** + * Delete multiple objects + * + * @param objects of Redisson + * @return number of removed keys + */ + RFuture deleteAsync(RObject ... objects); + /** * Delete multiple objects by name * diff --git a/redisson/src/main/java/org/redisson/api/RLexSortedSet.java b/redisson/src/main/java/org/redisson/api/RLexSortedSet.java index 081e2d582..65d1f9129 100644 --- a/redisson/src/main/java/org/redisson/api/RLexSortedSet.java +++ b/redisson/src/main/java/org/redisson/api/RLexSortedSet.java @@ -18,8 +18,25 @@ package org.redisson.api; import java.util.Collection; import java.util.Set; +import org.redisson.api.mapreduce.RCollectionMapReduce; + +/** + * Sorted set contained values of String type + * + * @author Nikita Koksharov + * + */ public interface RLexSortedSet extends RLexSortedSetAsync, Set, RExpirable { + /** + * Returns RMapReduce object associated with this object + * + * @param output key + * @param output value + * @return MapReduce instance + */ + RCollectionMapReduce mapReduce(); + String pollFirst(); String pollLast(); diff --git a/redisson/src/main/java/org/redisson/api/RLexSortedSetAsync.java b/redisson/src/main/java/org/redisson/api/RLexSortedSetAsync.java index 33a22eee3..16e2a5f20 100644 --- a/redisson/src/main/java/org/redisson/api/RLexSortedSetAsync.java +++ b/redisson/src/main/java/org/redisson/api/RLexSortedSetAsync.java @@ -17,6 +17,12 @@ package org.redisson.api; import java.util.Collection; +/** + * Sorted set contained values of String type + * + * @author Nikita Koksharov + * + */ public interface RLexSortedSetAsync extends RCollectionAsync { RFuture pollLastAsync(); diff --git a/redisson/src/main/java/org/redisson/api/RList.java b/redisson/src/main/java/org/redisson/api/RList.java index b6dc6a05a..2a295eff6 100644 --- a/redisson/src/main/java/org/redisson/api/RList.java +++ b/redisson/src/main/java/org/redisson/api/RList.java @@ -18,6 +18,8 @@ package org.redisson.api; import java.util.List; import java.util.RandomAccess; +import org.redisson.api.mapreduce.RCollectionMapReduce; + /** * Distributed and concurrent implementation of {@link java.util.List} * @@ -27,6 +29,15 @@ import java.util.RandomAccess; */ public interface RList extends List, RExpirable, RListAsync, RSortable>, RandomAccess { + /** + * Returns RMapReduce object associated with this map + * + * @param output key + * @param output value + * @return MapReduce instance + */ + RCollectionMapReduce mapReduce(); + /** * Add element after elementToFind * diff --git a/redisson/src/main/java/org/redisson/api/RMap.java b/redisson/src/main/java/org/redisson/api/RMap.java index cae8c6738..8b47a470d 100644 --- a/redisson/src/main/java/org/redisson/api/RMap.java +++ b/redisson/src/main/java/org/redisson/api/RMap.java @@ -20,6 +20,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; +import org.redisson.api.mapreduce.RMapReduce; + /** * Distributed and concurrent implementation of {@link java.util.concurrent.ConcurrentMap} * and {@link java.util.Map} @@ -33,6 +35,23 @@ import java.util.concurrent.ConcurrentMap; */ public interface RMap extends ConcurrentMap, RExpirable, RMapAsync { + /** + * Returns RMapReduce object associated with this map + * + * @param output key + * @param output value + * @return MapReduce instance + */ + RMapReduce mapReduce(); + + /** + * Returns RReadWriteLock instance associated with key + * + * @param key - map key + * @return readWriteLock + */ + RReadWriteLock getReadWriteLock(K key); + /** * Returns RLock instance associated with key * @@ -120,6 +139,13 @@ public interface RMap extends ConcurrentMap, RExpirable, RMapAsync> readAllEntrySet(); + /** + * Read all map as local instance at once + * + * @return map + */ + Map readAllMap(); + /** * Returns key set. * This method DOESN'T fetch all of them as {@link #readAllKeySet()} does. diff --git a/redisson/src/main/java/org/redisson/api/RMapAsync.java b/redisson/src/main/java/org/redisson/api/RMapAsync.java index e8c1cf60b..8d576e3d4 100644 --- a/redisson/src/main/java/org/redisson/api/RMapAsync.java +++ b/redisson/src/main/java/org/redisson/api/RMapAsync.java @@ -98,6 +98,13 @@ public interface RMapAsync extends RExpirableAsync { */ RFuture>> readAllEntrySetAsync(); + /** + * Read all map as local instance at once + * + * @return map + */ + RFuture> readAllMapAsync(); + RFuture getAsync(K key); RFuture putAsync(K key, V value); diff --git a/redisson/src/main/java/org/redisson/api/RMapCache.java b/redisson/src/main/java/org/redisson/api/RMapCache.java index 5a181d9c4..c59be9beb 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCache.java +++ b/redisson/src/main/java/org/redisson/api/RMapCache.java @@ -161,6 +161,53 @@ public interface RMapCache extends RMap, RMapCacheAsync { * false if key already exists in the hash and the value was updated. */ boolean fastPut(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); + + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + *

+ * Stores value mapped by key with specified time to live. + * Entry expires after specified time to live. + *

+ * Works faster than usual {@link #putIfAbsent(Object, Object, long, TimeUnit)} + * as it not returns previous value. + * + * @param key - map key + * @param value - map value + * @param ttl - time to live for key\value entry. + * If 0 then stores infinitely. + * @param ttlUnit - time unit + * @return true if key is a new key in the hash and value was set. + * false if key already exists in the hash + */ + boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit); + + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + *

+ * Stores value mapped by key with specified time to live and max idle time. + * Entry expires when specified time to live or max idle time has expired. + *

+ * Works faster than usual {@link #putIfAbsent(Object, Object, long, TimeUnit, long, TimeUnit)} + * as it not returns previous value. + * + * @param key - map key + * @param value - map value + * @param ttl - time to live for key\value entry. + * If 0 then time to live doesn't affect entry expiration. + * @param ttlUnit - time unit + * @param maxIdleTime - max idle time for key\value entry. + * If 0 then max idle time doesn't affect entry expiration. + * @param maxIdleUnit - time unit + *

+ * if maxIdleTime and ttl params are equal to 0 + * then entry stores infinitely. + * + * @return true if key is a new key in the hash and value was set. + * false if key already exists in the hash. + */ + boolean fastPutIfAbsent(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); /** * Returns the number of entries in cache. diff --git a/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java b/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java index 979590ebb..8eb0a8698 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java +++ b/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java @@ -163,6 +163,32 @@ public interface RMapCacheAsync extends RMapAsync { * @return true if value has been set successfully */ RFuture fastPutAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); + + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + *

+ * Stores value mapped by key with specified time to live and max idle time. + * Entry expires when specified time to live or max idle time has expired. + *

+ * Works faster than usual {@link #putIfAbsentAsync(Object, Object, long, TimeUnit, long, TimeUnit)} + * as it not returns previous value. + * + * @param key - map key + * @param value - map value + * @param ttl - time to live for key\value entry. + * If 0 then time to live doesn't affect entry expiration. + * @param ttlUnit - time unit + * @param maxIdleTime - max idle time for key\value entry. + * If 0 then max idle time doesn't affect entry expiration. + * @param maxIdleUnit - time unit + *

+ * if maxIdleTime and ttl params are equal to 0 + * then entry stores infinitely. + * + * @return previous associated value + */ + RFuture fastPutIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); /** * Returns the number of entries in cache. diff --git a/redisson/src/main/java/org/redisson/api/RMultimap.java b/redisson/src/main/java/org/redisson/api/RMultimap.java index 31212d450..a09d52520 100644 --- a/redisson/src/main/java/org/redisson/api/RMultimap.java +++ b/redisson/src/main/java/org/redisson/api/RMultimap.java @@ -29,6 +29,14 @@ import java.util.Set; */ public interface RMultimap extends RExpirable, RMultimapAsync { + /** + * Returns RReadWriteLock instance associated with key + * + * @param key - map key + * @return readWriteLock + */ + RReadWriteLock getReadWriteLock(K key); + /** * Returns RLock instance associated with key * @@ -236,5 +244,11 @@ public interface RMultimap extends RExpirable, RMultimapAsync { */ long fastRemove(K ... keys); + /** + * Read all keys at once + * + * @return keys + */ + Set readAllKeySet(); } diff --git a/redisson/src/main/java/org/redisson/api/RMultimapAsync.java b/redisson/src/main/java/org/redisson/api/RMultimapAsync.java index 6830ae6ba..d32b843f8 100644 --- a/redisson/src/main/java/org/redisson/api/RMultimapAsync.java +++ b/redisson/src/main/java/org/redisson/api/RMultimapAsync.java @@ -16,6 +16,7 @@ package org.redisson.api; import java.util.Collection; +import java.util.Set; /** * Base asynchronous MultiMap interface. A collection that maps multiple values per one key. * @@ -158,5 +159,11 @@ public interface RMultimapAsync extends RExpirableAsync { */ RFuture fastRemoveAsync(K ... keys); + /** + * Read all keys at once + * + * @return keys + */ + RFuture> readAllKeySetAsync(); } diff --git a/redisson/src/main/java/org/redisson/api/RObject.java b/redisson/src/main/java/org/redisson/api/RObject.java index ca93b1b13..cc0b021ba 100644 --- a/redisson/src/main/java/org/redisson/api/RObject.java +++ b/redisson/src/main/java/org/redisson/api/RObject.java @@ -25,6 +25,13 @@ import org.redisson.client.codec.Codec; */ public interface RObject extends RObjectAsync { + /** + * Update the last access time of an object. + * + * @return true if object was touched else false + */ + boolean touch(); + /** * Transfer an object from source Redis instance to destination Redis instance * diff --git a/redisson/src/main/java/org/redisson/api/RObjectAsync.java b/redisson/src/main/java/org/redisson/api/RObjectAsync.java index 2747eb892..829034e5f 100644 --- a/redisson/src/main/java/org/redisson/api/RObjectAsync.java +++ b/redisson/src/main/java/org/redisson/api/RObjectAsync.java @@ -23,6 +23,13 @@ package org.redisson.api; */ public interface RObjectAsync { + /** + * Update the last access time of an object in async mode. + * + * @return true if object was touched else false + */ + RFuture touchAsync(); + /** * Transfer an object from source Redis instance to destination Redis instance * in async mode diff --git a/redisson/src/main/java/org/redisson/api/RQueue.java b/redisson/src/main/java/org/redisson/api/RQueue.java index 380038023..1d32f8322 100644 --- a/redisson/src/main/java/org/redisson/api/RQueue.java +++ b/redisson/src/main/java/org/redisson/api/RQueue.java @@ -29,9 +29,6 @@ public interface RQueue extends Queue, RExpirable, RQueueAsync { V pollLastAndOfferFirstTo(String dequeName); - @Deprecated - V pollLastAndOfferFirstTo(RQueue deque); - List readAll(); } diff --git a/redisson/src/main/java/org/redisson/api/RRemoteService.java b/redisson/src/main/java/org/redisson/api/RRemoteService.java index 48c2c82cb..ea6db3219 100644 --- a/redisson/src/main/java/org/redisson/api/RRemoteService.java +++ b/redisson/src/main/java/org/redisson/api/RRemoteService.java @@ -88,6 +88,14 @@ public interface RRemoteService { */ void register(Class remoteInterface, T object, int workers, ExecutorService executor); + /** + * Deregister all workers for remote service + * + * @param type of remote service + * @param remoteInterface - remote service interface + */ + void deregister(Class remoteInterface); + /** * Get remote service object for remote invocations. *

diff --git a/redisson/src/main/java/org/redisson/api/RScheduledExecutorService.java b/redisson/src/main/java/org/redisson/api/RScheduledExecutorService.java index 35bb5a845..48f2d2697 100644 --- a/redisson/src/main/java/org/redisson/api/RScheduledExecutorService.java +++ b/redisson/src/main/java/org/redisson/api/RScheduledExecutorService.java @@ -15,17 +15,99 @@ */ package org.redisson.api; +import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; /** - * Distributed implementation of {@link java.util.concurrent.ScheduledExecutorService} + * Redis based implementation of {@link java.util.concurrent.ScheduledExecutorService} * * @author Nikita Koksharov * */ public interface RScheduledExecutorService extends RExecutorService, ScheduledExecutorService, RScheduledExecutorServiceAsync { + /** + * Creates and executes a one-shot action that becomes enabled + * after the given delay. + * + * @param command the task to execute + * @param delay the time from now to delay execution + * @param unit the time unit of the delay parameter + * @return a ScheduledFuture representing pending completion of + * the task and whose {@code get()} method will return + * {@code null} upon completion + */ + @Override + RScheduledFuture schedule(Runnable command, + long delay, TimeUnit unit); + + /** + * Creates and executes a ScheduledFuture that becomes enabled after the + * given delay. + * + * @param callable the function to execute + * @param delay the time from now to delay execution + * @param unit the time unit of the delay parameter + * @param the type of the callable's result + * @return a ScheduledFuture that can be used to extract result or cancel + */ + @Override + RScheduledFuture schedule(Callable callable, + long delay, TimeUnit unit); + + /** + * Creates and executes a periodic action that becomes enabled first + * after the given initial delay, and subsequently with the given + * period; that is executions will commence after + * {@code initialDelay} then {@code initialDelay+period}, then + * {@code initialDelay + 2 * period}, and so on. + * If any execution of the task + * encounters an exception, subsequent executions are suppressed. + * Otherwise, the task will only terminate via cancellation or + * termination of the executor. If any execution of this task + * takes longer than its period, then subsequent executions + * may start late, but will not concurrently execute. + * + * @param command the task to execute + * @param initialDelay the time to delay first execution + * @param period the period between successive executions + * @param unit the time unit of the initialDelay and period parameters + * @return a ScheduledFuture representing pending completion of + * the task, and whose {@code get()} method will throw an + * exception upon cancellation + */ + @Override + RScheduledFuture scheduleAtFixedRate(Runnable command, + long initialDelay, + long period, + TimeUnit unit); + + /** + * Creates and executes a periodic action that becomes enabled first + * after the given initial delay, and subsequently with the + * given delay between the termination of one execution and the + * commencement of the next. If any execution of the task + * encounters an exception, subsequent executions are suppressed. + * Otherwise, the task will only terminate via cancellation or + * termination of the executor. + * + * @param command the task to execute + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one + * execution and the commencement of the next + * @param unit the time unit of the initialDelay and delay parameters + * @return a ScheduledFuture representing pending completion of + * the task, and whose {@code get()} method will throw an + * exception upon cancellation + */ + @Override + RScheduledFuture scheduleWithFixedDelay(Runnable command, + long initialDelay, + long delay, + TimeUnit unit); + /** * Cancels scheduled task by id * diff --git a/redisson/src/main/java/org/redisson/api/RScheduledExecutorServiceAsync.java b/redisson/src/main/java/org/redisson/api/RScheduledExecutorServiceAsync.java index 3e293c08c..0aa9e9131 100644 --- a/redisson/src/main/java/org/redisson/api/RScheduledExecutorServiceAsync.java +++ b/redisson/src/main/java/org/redisson/api/RScheduledExecutorServiceAsync.java @@ -19,7 +19,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; /** - * Distributed implementation of {@link java.util.concurrent.ScheduledExecutorService} + * Redis based implementation of {@link java.util.concurrent.ScheduledExecutorService} * * @author Nikita Koksharov * diff --git a/redisson/src/main/java/org/redisson/api/RScoredSortedSet.java b/redisson/src/main/java/org/redisson/api/RScoredSortedSet.java index c6a2884a4..72b548972 100644 --- a/redisson/src/main/java/org/redisson/api/RScoredSortedSet.java +++ b/redisson/src/main/java/org/redisson/api/RScoredSortedSet.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Map; import java.util.Set; +import org.redisson.api.mapreduce.RCollectionMapReduce; import org.redisson.client.protocol.ScoredEntry; /** @@ -35,6 +36,15 @@ public interface RScoredSortedSet extends RScoredSortedSetAsync, Iterable< } + /** + * Returns RMapReduce object associated with this object + * + * @param output key + * @param output value + * @return MapReduce instance + */ + RCollectionMapReduce mapReduce(); + V pollFirst(); V pollLast(); @@ -117,8 +127,36 @@ public interface RScoredSortedSet extends RScoredSortedSetAsync, Iterable< Collection> entryRangeReversed(int startIndex, int endIndex); + /** + * Returns all values between startScore and endScore. + * + * @param startScore - start score. + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * @param startScoreInclusive - start score inclusive + * @param endScore - end score + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * + * @param endScoreInclusive - end score inclusive + * @return values + */ Collection valueRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); + /** + * Returns all values between startScore and endScore in reversed order. + * + * @param startScore - start score. + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * @param startScoreInclusive - start score inclusive + * @param endScore - end score + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * + * @param endScoreInclusive - end score inclusive + * @return values + */ Collection valueRangeReversed(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); Collection> entryRange(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); diff --git a/redisson/src/main/java/org/redisson/api/RScoredSortedSetAsync.java b/redisson/src/main/java/org/redisson/api/RScoredSortedSetAsync.java index 1f2d761b8..25bcfe5e4 100644 --- a/redisson/src/main/java/org/redisson/api/RScoredSortedSetAsync.java +++ b/redisson/src/main/java/org/redisson/api/RScoredSortedSetAsync.java @@ -92,8 +92,36 @@ public interface RScoredSortedSetAsync extends RExpirableAsync, RSortableAsyn RFuture>> entryRangeReversedAsync(int startIndex, int endIndex); + /** + * Returns all values between startScore and endScore. + * + * @param startScore - start score. + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * @param startScoreInclusive - start score inclusive + * @param endScore - end score + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * + * @param endScoreInclusive - end score inclusive + * @return values + */ RFuture> valueRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); + /** + * Returns all values between startScore and endScore in reversed order. + * + * @param startScore - start score. + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * @param startScoreInclusive - start score inclusive + * @param endScore - end score + * Use Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY + * to define infinity numbers + * + * @param endScoreInclusive - end score inclusive + * @return values + */ RFuture> valueRangeReversedAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); RFuture>> entryRangeAsync(double startScore, boolean startScoreInclusive, double endScore, boolean endScoreInclusive); diff --git a/redisson/src/main/java/org/redisson/api/RSet.java b/redisson/src/main/java/org/redisson/api/RSet.java index 2d0e92dc1..5ede25df5 100644 --- a/redisson/src/main/java/org/redisson/api/RSet.java +++ b/redisson/src/main/java/org/redisson/api/RSet.java @@ -17,6 +17,8 @@ package org.redisson.api; import java.util.Set; +import org.redisson.api.mapreduce.RCollectionMapReduce; + /** * Distributed and concurrent implementation of {@link java.util.Set} * @@ -26,6 +28,15 @@ import java.util.Set; */ public interface RSet extends Set, RExpirable, RSetAsync, RSortable> { + /** + * Returns RMapReduce object associated with this object + * + * @param output key + * @param output value + * @return MapReduce instance + */ + RCollectionMapReduce mapReduce(); + /** * Removes and returns random elements from set * diff --git a/redisson/src/main/java/org/redisson/api/RSetCache.java b/redisson/src/main/java/org/redisson/api/RSetCache.java index 7d2451307..e3fb82a40 100644 --- a/redisson/src/main/java/org/redisson/api/RSetCache.java +++ b/redisson/src/main/java/org/redisson/api/RSetCache.java @@ -18,6 +18,8 @@ package org.redisson.api; import java.util.Set; import java.util.concurrent.TimeUnit; +import org.redisson.api.mapreduce.RCollectionMapReduce; + /** *

Set-based cache with ability to set TTL for each object. *

@@ -37,6 +39,15 @@ import java.util.concurrent.TimeUnit; */ public interface RSetCache extends Set, RExpirable, RSetCacheAsync { + /** + * Returns RMapReduce object associated with this map + * + * @param output key + * @param output value + * @return MapReduce instance + */ + RCollectionMapReduce mapReduce(); + /** * Stores value with specified time to live. * Value expires after specified time to live. diff --git a/redisson/src/main/java/org/redisson/api/RSortedSet.java b/redisson/src/main/java/org/redisson/api/RSortedSet.java index 315e010f6..c609076c2 100644 --- a/redisson/src/main/java/org/redisson/api/RSortedSet.java +++ b/redisson/src/main/java/org/redisson/api/RSortedSet.java @@ -19,6 +19,8 @@ import java.util.Comparator; import java.util.Set; import java.util.SortedSet; +import org.redisson.api.mapreduce.RCollectionMapReduce; + /** * * @author Nikita Koksharov @@ -27,6 +29,15 @@ import java.util.SortedSet; */ public interface RSortedSet extends SortedSet, RObject { + /** + * Returns RMapReduce object associated with this object + * + * @param output key + * @param output value + * @return MapReduce instance + */ + RCollectionMapReduce mapReduce(); + Set readAll(); RFuture> readAllAsync(); diff --git a/redisson/src/main/java/org/redisson/api/RedissonClient.java b/redisson/src/main/java/org/redisson/api/RedissonClient.java index b38746061..f5de30013 100755 --- a/redisson/src/main/java/org/redisson/api/RedissonClient.java +++ b/redisson/src/main/java/org/redisson/api/RedissonClient.java @@ -385,7 +385,7 @@ public interface RedissonClient { * * @param type of value * @param name - name of object - * @return Lock object + * @return Set object */ RSet getSet(String name); diff --git a/redisson/src/main/java/org/redisson/api/listener/StatusListener.java b/redisson/src/main/java/org/redisson/api/listener/StatusListener.java index 77088c38f..d8e7704fc 100644 --- a/redisson/src/main/java/org/redisson/api/listener/StatusListener.java +++ b/redisson/src/main/java/org/redisson/api/listener/StatusListener.java @@ -28,7 +28,7 @@ public interface StatusListener extends EventListener { /** * Executes then Redisson successfully subscribed to channel. - * Invoked during re-connection + * Invoked during re-connection or failover process * * @param channel to subscribe */ diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RCollator.java b/redisson/src/main/java/org/redisson/api/mapreduce/RCollator.java new file mode 100644 index 000000000..92b155ea5 --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RCollator.java @@ -0,0 +1,41 @@ +/** + * 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.mapreduce; + +import java.io.Serializable; +import java.util.Map; + +/** + * Collates result from {@link RReducer} tasks and produces a single result object. + * Executes only once. + * + * @author Nikita Koksharov + * + * @param key type + * @param value type + * @param result type + */ +public interface RCollator extends Serializable { + + /** + * Collates result map from reduce phase of MapReduce process. + * + * @param resultMap contains reduced entires + * @return single result object + */ + R collate(Map resultMap); + +} diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RCollectionMapReduce.java b/redisson/src/main/java/org/redisson/api/mapreduce/RCollectionMapReduce.java new file mode 100644 index 000000000..dd212490f --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RCollectionMapReduce.java @@ -0,0 +1,118 @@ +/** + * 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.mapreduce; + +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RList; +import org.redisson.api.RScoredSortedSet; +import org.redisson.api.RSet; +import org.redisson.api.RSetCache; +import org.redisson.api.RSortedSet; + +/** + * + * MapReduce allows to process large amount of data stored in + * {@link RSet}, {@link RList}, {@link RSetCache}, {@link RScoredSortedSet}, {@link RSortedSet} and others + * using Mapper, Reducer and/or Collator tasks launched across Redisson Nodes. + *

+ * Usage example: + * + *

+ *   public class WordMapper implements RCollectionMapper<String, String, Integer> {
+ *
+ *       public void map(String value, RCollector<String, Integer> collector) {
+ *           String[] words = value.split("[^a-zA-Z]");
+ *           for (String word : words) {
+ *               collector.emit(word, 1);
+ *           }
+ *       }
+ *       
+ *   }
+ *   
+ *   public class WordReducer implements RReducer<String, Integer> {
+ *
+ *       public Integer reduce(String reducedKey, Iterator<Integer> iter) {
+ *           int sum = 0;
+ *           while (iter.hasNext()) {
+ *              Integer i = (Integer) iter.next();
+ *              sum += i;
+ *           }
+ *           return sum;
+ *       }
+ *       
+ *   }
+ *
+ *   public class WordCollator implements RCollator<String, Integer, Integer> {
+ *
+ *       public Integer collate(Map<String, Integer> resultMap) {
+ *           int result = 0;
+ *           for (Integer count : resultMap.values()) {
+ *               result += count;
+ *           }
+ *           return result;
+ *       }
+ *       
+ *   }
+ *   
+ *   RList<String> list = redisson.getList("myWords");
+ *   
+ *   Map<String, Integer> wordsCount = list.<String, Integer>mapReduce()
+ *     .mapper(new WordMapper())
+ *     .reducer(new WordReducer())
+ *     .execute();
+ *
+ *   Integer totalCount = list.<String, Integer>mapReduce()
+ *     .mapper(new WordMapper())
+ *     .reducer(new WordReducer())
+ *     .execute(new WordCollator());
+ *
+ * 
+ * + * @author Nikita Koksharov + * + * @param input value + * @param output key + * @param output value + */ +public interface RCollectionMapReduce extends RMapReduceExecutor { + + /** + * Defines timeout for MapReduce process + * + * @param timeout + * @param unit + * @return self instance + */ + RCollectionMapReduce timeout(long timeout, TimeUnit unit); + + /** + * Setup Mapper object + * + * @param mapper used during MapReduce + * @return self instance + */ + RCollectionMapReduce mapper(RCollectionMapper mapper); + + /** + * Setup Reducer object + * + * @param reducer used during MapReduce + * @return self instance + */ + RCollectionMapReduce reducer(RReducer reducer); + +} diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RCollectionMapper.java b/redisson/src/main/java/org/redisson/api/mapreduce/RCollectionMapper.java new file mode 100644 index 000000000..c313f4026 --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RCollectionMapper.java @@ -0,0 +1,42 @@ +/** + * 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.mapreduce; + +import java.io.Serializable; + +/** + * Mapper task invoked during map phase of MapReduce process and launched across Redisson Nodes. + * Every task stores transformed result of input key and value into {@link RCollector} instance. + * Collected results are handled by {@link RReducer} instance once + * all Mapper tasks have finished. + * + * @author Nikita Koksharov + * + * @param input value + * @param output key + * @param output value + */ +public interface RCollectionMapper extends Serializable { + + /** + * Invoked for each Collection source entry + * + * @param value - input value + * @param collector - instance shared across all Mapper tasks + */ + void map(VIn value, RCollector collector); + +} diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RCollector.java b/redisson/src/main/java/org/redisson/api/mapreduce/RCollector.java new file mode 100644 index 000000000..5a6c0aeaa --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RCollector.java @@ -0,0 +1,38 @@ +/** + * 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.mapreduce; + +/** + * Stores each key/value mapping during map phase of MapReduce process. + * Later used in reduce phase. + * + * + * @author Nikita Koksharov + * + * @param key type + * @param value type + */ +public interface RCollector { + + /** + * Store key/value + * + * @param key available to reduce + * @param value available to reduce + */ + void emit(K key, V value); + +} diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RMapReduce.java b/redisson/src/main/java/org/redisson/api/mapreduce/RMapReduce.java new file mode 100644 index 000000000..4f6ad7b82 --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RMapReduce.java @@ -0,0 +1,113 @@ +/** + * 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.mapreduce; + +import java.util.concurrent.TimeUnit; + +/** + * + * MapReduce allows to process large amount of data stored in Redis map + * using Mapper, Reducer and/or Collator tasks launched across Redisson Nodes. + *

+ * Usage example: + * + *

+ *   public class WordMapper implements RMapper<String, String, String, Integer> {
+ *
+ *       public void map(String key, String value, RCollector<String, Integer> collector) {
+ *           String[] words = value.split("[^a-zA-Z]");
+ *           for (String word : words) {
+ *               collector.emit(word, 1);
+ *           }
+ *       }
+ *       
+ *   }
+ *   
+ *   public class WordReducer implements RReducer<String, Integer> {
+ *
+ *       public Integer reduce(String reducedKey, Iterator<Integer> iter) {
+ *           int sum = 0;
+ *           while (iter.hasNext()) {
+ *              Integer i = (Integer) iter.next();
+ *              sum += i;
+ *           }
+ *           return sum;
+ *       }
+ *       
+ *   }
+ *
+ *   public class WordCollator implements RCollator<String, Integer, Integer> {
+ *
+ *       public Integer collate(Map<String, Integer> resultMap) {
+ *           int result = 0;
+ *           for (Integer count : resultMap.values()) {
+ *               result += count;
+ *           }
+ *           return result;
+ *       }
+ *       
+ *   }
+ *   
+ *   RMap<String, String> map = redisson.getMap("myWords");
+ *   
+ *   Map<String, Integer> wordsCount = map.<String, Integer>mapReduce()
+ *     .mapper(new WordMapper())
+ *     .reducer(new WordReducer())
+ *     .execute();
+ *
+ *   Integer totalCount = map.<String, Integer>mapReduce()
+ *     .mapper(new WordMapper())
+ *     .reducer(new WordReducer())
+ *     .execute(new WordCollator());
+ *
+ * 
+ * + * @author Nikita Koksharov + * + * @param input key + * @param input value + * @param output key + * @param output value + */ +public interface RMapReduce extends RMapReduceExecutor { + + /** + * Defines timeout for MapReduce process. + * 0 means infinity timeout. + * + * @param timeout + * @param unit + * @return self instance + */ + RMapReduce timeout(long timeout, TimeUnit unit); + + /** + * Setup Mapper object + * + * @param mapper used during MapReduce + * @return self instance + */ + RMapReduce mapper(RMapper mapper); + + /** + * Setup Reducer object + * + * @param reducer used during MapReduce + * @return self instance + */ + RMapReduce reducer(RReducer reducer); + +} diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RMapReduceExecutor.java b/redisson/src/main/java/org/redisson/api/mapreduce/RMapReduceExecutor.java new file mode 100644 index 000000000..bd016519f --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RMapReduceExecutor.java @@ -0,0 +1,85 @@ +/** + * 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.mapreduce; + +import java.util.Map; + +import org.redisson.api.RFuture; + +/** + * Contains methods for MapReduce process execution. + * + * @author Nikita Koksharov + * + * @param input value + * @param output key + * @param output value + */ +public interface RMapReduceExecutor { + + /** + * Executes MapReduce process across Redisson Nodes + * + * @return map containing reduced keys and values + */ + Map execute(); + + /** + * Executes MapReduce process across Redisson Nodes + * in asynchronous mode + * + * @return map containing reduced keys and values + */ + RFuture> executeAsync(); + + /** + * Executes MapReduce process across Redisson Nodes + * and stores result in map with resultMapName + * + * @param resultMapName - destination map name + */ + void execute(String resultMapName); + + /** + * Executes MapReduce process across Redisson Nodes + * in asynchronous mode and stores result in map with resultMapName + * + * @param resultMapName - destination map name + * @return void + */ + RFuture executeAsync(String resultMapName); + + /** + * Executes MapReduce process across Redisson Nodes + * and collides result using defined collator + * + * @param result type + * @param collator applied to result + * @return collated result + */ + R execute(RCollator collator); + + /** + * Executes MapReduce process across Redisson Nodes + * in asynchronous mode and collides result using defined collator + * + * @param result type + * @param collator applied to result + * @return collated result + */ + RFuture executeAsync(RCollator collator); + +} diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RMapper.java b/redisson/src/main/java/org/redisson/api/mapreduce/RMapper.java new file mode 100644 index 000000000..e4e0e50cd --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RMapper.java @@ -0,0 +1,44 @@ +/** + * 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.mapreduce; + +import java.io.Serializable; + +/** + * Mapper task invoked during map phase of MapReduce process and launched across Redisson Nodes. + * Every task stores transformed result of input key/value into {@link RCollector} instance. + * Collected results are handled by {@link RReducer} instance once + * all Mapper tasks have finished. + * + * @author Nikita Koksharov + * + * @param input key + * @param input value + * @param output key + * @param output value + */ +public interface RMapper extends Serializable { + + /** + * Invoked for each Map source entry + * + * @param key - input key + * @param value - input value + * @param collector - instance shared across all Mapper tasks + */ + void map(KIn key, VIn value, RCollector collector); + +} diff --git a/redisson/src/main/java/org/redisson/api/mapreduce/RReducer.java b/redisson/src/main/java/org/redisson/api/mapreduce/RReducer.java new file mode 100644 index 000000000..36aed233c --- /dev/null +++ b/redisson/src/main/java/org/redisson/api/mapreduce/RReducer.java @@ -0,0 +1,40 @@ +/** + * 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.mapreduce; + +import java.io.Serializable; +import java.util.Iterator; + +/** + * Reduces values mapped by key into single value. + * + * @author Nikita Koksharov + * + * @param key type + * @param value type + */ +public interface RReducer extends Serializable { + + /** + * Invoked for each key + * + * @param reducedKey - key + * @param iter - collection of values + * @return value + */ + V reduce(K reducedKey, Iterator iter); + +} diff --git a/redisson/src/main/java/org/redisson/cache/CachedValueReference.java b/redisson/src/main/java/org/redisson/cache/CachedValueSoftReference.java similarity index 78% rename from redisson/src/main/java/org/redisson/cache/CachedValueReference.java rename to redisson/src/main/java/org/redisson/cache/CachedValueSoftReference.java index c6e0c4bfb..b701e04b2 100644 --- a/redisson/src/main/java/org/redisson/cache/CachedValueReference.java +++ b/redisson/src/main/java/org/redisson/cache/CachedValueSoftReference.java @@ -18,11 +18,17 @@ package org.redisson.cache; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; -public class CachedValueReference extends SoftReference { +/** + * + * @author Nikita Koksharov + * + * @param value type + */ +public class CachedValueSoftReference extends SoftReference { private final CachedValue owner; - public CachedValueReference(CachedValue owner, V referent, ReferenceQueue q) { + public CachedValueSoftReference(CachedValue owner, V referent, ReferenceQueue q) { super(referent, q); this.owner = owner; } diff --git a/redisson/src/main/java/org/redisson/cache/CachedValueWeakReference.java b/redisson/src/main/java/org/redisson/cache/CachedValueWeakReference.java new file mode 100644 index 000000000..65814615d --- /dev/null +++ b/redisson/src/main/java/org/redisson/cache/CachedValueWeakReference.java @@ -0,0 +1,40 @@ +/** + * 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.cache; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * + * @author Nikita Koksharov + * + * @param value type + */ +public class CachedValueWeakReference extends WeakReference { + + private final CachedValue owner; + + public CachedValueWeakReference(CachedValue owner, V referent, ReferenceQueue q) { + super(referent, q); + this.owner = owner; + } + + public CachedValue getOwner() { + return owner; + } + +} diff --git a/redisson/src/main/java/org/redisson/cache/LRUCacheMap.java b/redisson/src/main/java/org/redisson/cache/LRUCacheMap.java index d90ebf471..388f09e84 100644 --- a/redisson/src/main/java/org/redisson/cache/LRUCacheMap.java +++ b/redisson/src/main/java/org/redisson/cache/LRUCacheMap.java @@ -15,8 +15,14 @@ */ package org.redisson.cache; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; /** * LRU (least recently used) cache. @@ -28,24 +34,38 @@ import java.util.concurrent.ConcurrentLinkedQueue; */ public class LRUCacheMap extends AbstractCacheMap { - private final Queue queue = new ConcurrentLinkedQueue(); + private final AtomicLong index = new AtomicLong(); + private final List>> queues = + new ArrayList>>(Runtime.getRuntime().availableProcessors()*2); public LRUCacheMap(int size, long timeToLiveInMillis, long maxIdleInMillis) { super(size, timeToLiveInMillis, maxIdleInMillis); + + for (int i = 0; i < Runtime.getRuntime().availableProcessors()*2; i++) { + Set> instance = Collections.synchronizedSet(new LinkedHashSet>()); + queues.add(instance); + } } @Override - protected void onValueCreate(CachedValue value) { + protected void onValueCreate(CachedValue value) { + Collection> queue = getQueue(value); queue.add(value); } + + private Collection> getQueue(CachedValue value) { + return queues.get(value.hashCode() % queues.size()); + } @Override - protected void onValueRemove(CachedValue value) { + protected void onValueRemove(CachedValue value) { + Collection> queue = getQueue(value); queue.remove(value); } @Override - protected void onValueRead(CachedValue value) { + protected void onValueRead(CachedValue value) { + Collection> queue = getQueue(value); // move value to tail of queue if (queue.remove(value)) { queue.add(value); @@ -54,15 +74,33 @@ public class LRUCacheMap extends AbstractCacheMap { @Override protected void onMapFull() { - CachedValue value = queue.poll(); - if (value != null) { - map.remove(value.getKey(), value); + int startIndex = -1; + while (true) { + int queueIndex = (int)Math.abs(index.incrementAndGet() % queues.size()); + if (queueIndex == startIndex) { + return; + } + if (startIndex == -1) { + startIndex = queueIndex; + } + Collection> queue = queues.get(queueIndex); + synchronized (queue) { + Iterator> iter = queue.iterator(); + if (iter.hasNext()) { + CachedValue value = iter.next(); + iter.remove(); + map.remove(value.getKey(), value); + return; + } + } } } @Override public void clear() { - queue.clear(); + for (Collection> collection : queues) { + collection.clear(); + } super.clear(); } diff --git a/redisson/src/main/java/org/redisson/cache/SoftCacheMap.java b/redisson/src/main/java/org/redisson/cache/ReferenceCacheMap.java similarity index 59% rename from redisson/src/main/java/org/redisson/cache/SoftCacheMap.java rename to redisson/src/main/java/org/redisson/cache/ReferenceCacheMap.java index d861fb37c..0f4493a24 100644 --- a/redisson/src/main/java/org/redisson/cache/SoftCacheMap.java +++ b/redisson/src/main/java/org/redisson/cache/ReferenceCacheMap.java @@ -17,6 +17,8 @@ package org.redisson.cache; import java.lang.ref.ReferenceQueue; +import org.redisson.cache.ReferenceCachedValue.Type; + /** * * @author Nikita Koksharov @@ -24,22 +26,33 @@ import java.lang.ref.ReferenceQueue; * @param key * @param value */ -public class SoftCacheMap extends AbstractCacheMap { +public class ReferenceCacheMap extends AbstractCacheMap { private final ReferenceQueue queue = new ReferenceQueue(); - public SoftCacheMap(long timeToLiveInMillis, long maxIdleInMillis) { + private ReferenceCachedValue.Type type; + + public static ReferenceCacheMap weak(long timeToLiveInMillis, long maxIdleInMillis) { + return new ReferenceCacheMap(timeToLiveInMillis, maxIdleInMillis, Type.WEAK); + } + + public static ReferenceCacheMap soft(long timeToLiveInMillis, long maxIdleInMillis) { + return new ReferenceCacheMap(timeToLiveInMillis, maxIdleInMillis, Type.SOFT); + } + + ReferenceCacheMap(long timeToLiveInMillis, long maxIdleInMillis, ReferenceCachedValue.Type type) { super(0, timeToLiveInMillis, maxIdleInMillis); + this.type = type; } protected CachedValue create(K key, V value, long ttl, long maxIdleTime) { - return new SoftCachedValue(key, value, ttl, maxIdleTime, queue); + return new ReferenceCachedValue(key, value, ttl, maxIdleTime, queue, type); } @Override protected boolean removeExpiredEntries() { while (true) { - CachedValueReference value = (CachedValueReference) queue.poll(); + CachedValueSoftReference value = (CachedValueSoftReference) queue.poll(); if (value == null) { break; } diff --git a/redisson/src/main/java/org/redisson/cache/SoftCachedValue.java b/redisson/src/main/java/org/redisson/cache/ReferenceCachedValue.java similarity index 61% rename from redisson/src/main/java/org/redisson/cache/SoftCachedValue.java rename to redisson/src/main/java/org/redisson/cache/ReferenceCachedValue.java index efdedc100..ed59f20c7 100644 --- a/redisson/src/main/java/org/redisson/cache/SoftCachedValue.java +++ b/redisson/src/main/java/org/redisson/cache/ReferenceCachedValue.java @@ -15,19 +15,26 @@ */ package org.redisson.cache; +import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; /** * Created by jribble on 2/20/17. */ -public class SoftCachedValue extends StdCachedValue implements CachedValue { +public class ReferenceCachedValue extends StdCachedValue implements CachedValue { - private final CachedValueReference ref; + public enum Type {SOFT, WEAK} + + private final Reference ref; - public SoftCachedValue(K key, V value, long ttl, long maxIdleTime, ReferenceQueue queue) { + public ReferenceCachedValue(K key, V value, long ttl, long maxIdleTime, ReferenceQueue queue, Type type) { super(key, null, ttl, maxIdleTime); - this.ref = new CachedValueReference(this, value, queue); + if (type == Type.SOFT) { + this.ref = new CachedValueSoftReference(this, value, queue); + } else { + this.ref = new CachedValueWeakReference(this, value, queue); + } } @Override diff --git a/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java b/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java index 8eaf72bef..e9f40a102 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java +++ b/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java @@ -226,6 +226,7 @@ public interface RedisCommands { RedisCommand EVAL_OBJECT = new RedisCommand("EVAL"); RedisCommand EVAL_MAP_VALUE = new RedisCommand("EVAL", ValueType.MAP_VALUE); RedisCommand>> EVAL_MAP_ENTRY = new RedisCommand>>("EVAL", new ObjectMapEntryReplayDecoder(), ValueType.MAP); + RedisCommand> EVAL_MAP = new RedisCommand>("EVAL", new ObjectMapReplayDecoder(), ValueType.MAP); RedisCommand> EVAL_MAP_VALUE_LIST = new RedisCommand>("EVAL", new ObjectListReplayDecoder(), ValueType.MAP_VALUE); RedisCommand> EVAL_MAP_KEY_SET = new RedisCommand>("EVAL", new ObjectSetReplayDecoder(), ValueType.MAP_KEY); @@ -280,6 +281,8 @@ public interface RedisCommands { RedisCommand SETNX = new RedisCommand("SETNX", new BooleanReplayConvertor(), 2); RedisCommand PSETEX = new RedisCommand("PSETEX", new VoidReplayConvertor(), 3); + RedisStrictCommand TOUCH_LONG = new RedisStrictCommand("TOUCH"); + RedisStrictCommand TOUCH = new RedisStrictCommand("TOUCH", new BooleanReplayConvertor()); RedisStrictCommand EXISTS_LONG = new RedisStrictCommand("EXISTS"); RedisStrictCommand EXISTS = new RedisStrictCommand("EXISTS", new BooleanReplayConvertor()); RedisStrictCommand NOT_EXISTS = new RedisStrictCommand("EXISTS", new BooleanNumberReplayConvertor(1L)); diff --git a/redisson/src/main/java/org/redisson/client/protocol/convertor/BooleanReplayConvertor.java b/redisson/src/main/java/org/redisson/client/protocol/convertor/BooleanReplayConvertor.java index 3c5d4559f..df782de4e 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/convertor/BooleanReplayConvertor.java +++ b/redisson/src/main/java/org/redisson/client/protocol/convertor/BooleanReplayConvertor.java @@ -15,6 +15,11 @@ */ package org.redisson.client.protocol.convertor; +/** + * + * @author Nikita Koksharov + * + */ public class BooleanReplayConvertor extends SingleConvertor { @Override diff --git a/redisson/src/main/java/org/redisson/client/protocol/convertor/Convertor.java b/redisson/src/main/java/org/redisson/client/protocol/convertor/Convertor.java index 012869702..8d0b5eb7b 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/convertor/Convertor.java +++ b/redisson/src/main/java/org/redisson/client/protocol/convertor/Convertor.java @@ -15,6 +15,12 @@ */ package org.redisson.client.protocol.convertor; +/** + * + * @author Nikita Koksharov + * + * @param type + */ public interface Convertor { Object convertMulti(Object obj); diff --git a/redisson/src/main/java/org/redisson/client/protocol/convertor/SingleConvertor.java b/redisson/src/main/java/org/redisson/client/protocol/convertor/SingleConvertor.java index f9d5b63f4..65d005087 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/convertor/SingleConvertor.java +++ b/redisson/src/main/java/org/redisson/client/protocol/convertor/SingleConvertor.java @@ -15,6 +15,12 @@ */ package org.redisson.client.protocol.convertor; +/** + * + * @author Nikita Koksharov + * + * @param type + */ public abstract class SingleConvertor implements Convertor { @Override diff --git a/redisson/src/main/java/org/redisson/client/protocol/decoder/MultiDecoder.java b/redisson/src/main/java/org/redisson/client/protocol/decoder/MultiDecoder.java index 4b3d09e49..180dd4ed8 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/decoder/MultiDecoder.java +++ b/redisson/src/main/java/org/redisson/client/protocol/decoder/MultiDecoder.java @@ -20,6 +20,12 @@ import java.util.List; import org.redisson.client.handler.State; import org.redisson.client.protocol.Decoder; +/** + * + * @author Nikita Koksharov + * + * @param type + */ public interface MultiDecoder extends Decoder { boolean isApplicable(int paramNum, State state); diff --git a/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectListReplayDecoder.java b/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectListReplayDecoder.java index 6722c58a6..4f461fd56 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectListReplayDecoder.java +++ b/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectListReplayDecoder.java @@ -21,6 +21,12 @@ import org.redisson.client.handler.State; import io.netty.buffer.ByteBuf; +/** + * + * @author Nikita Koksharov + * + * @param type + */ public class ObjectListReplayDecoder implements MultiDecoder> { @Override diff --git a/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapEntryReplayDecoder.java b/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapEntryReplayDecoder.java index 3689aaaa0..a133b3f86 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapEntryReplayDecoder.java +++ b/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapEntryReplayDecoder.java @@ -25,6 +25,11 @@ import org.redisson.client.handler.State; import io.netty.buffer.ByteBuf; +/** + * + * @author Nikita Koksharov + * + */ public class ObjectMapEntryReplayDecoder implements MultiDecoder>> { @Override diff --git a/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapReplayDecoder.java b/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapReplayDecoder.java index 4e84eed88..ac9c009b6 100644 --- a/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapReplayDecoder.java +++ b/redisson/src/main/java/org/redisson/client/protocol/decoder/ObjectMapReplayDecoder.java @@ -23,6 +23,11 @@ import org.redisson.client.handler.State; import io.netty.buffer.ByteBuf; +/** + * + * @author Nikita Koksharov + * + */ public class ObjectMapReplayDecoder implements MultiDecoder> { @Override diff --git a/redisson/src/main/java/org/redisson/codec/DefenceModule.java b/redisson/src/main/java/org/redisson/codec/DefenceModule.java new file mode 100644 index 000000000..377751b24 --- /dev/null +++ b/redisson/src/main/java/org/redisson/codec/DefenceModule.java @@ -0,0 +1,80 @@ +/** + * 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 java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.deser.ValueInstantiator; +import com.fasterxml.jackson.databind.deser.ValueInstantiators.Base; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.avro.PackageVersion; + +/** + * Fix for https://github.com/FasterXML/jackson-databind/issues/1599 + * + * @author Nikita Koksharov + * + * TODO remove after update to latest version of Jackson + * + */ +public class DefenceModule extends SimpleModule { + + private static final long serialVersionUID = -429891510707420220L; + + public static class DefenceValueInstantiator extends Base { + + protected final static Set DEFAULT_NO_DESER_CLASS_NAMES; + static { + Set s = new HashSet(); + // Courtesy of [https://github.com/kantega/notsoserial]: + // (and wrt [databind#1599] + s.add("org.apache.commons.collections.functors.InvokerTransformer"); + s.add("org.apache.commons.collections.functors.InstantiateTransformer"); + s.add("org.apache.commons.collections4.functors.InvokerTransformer"); + s.add("org.apache.commons.collections4.functors.InstantiateTransformer"); + s.add("org.codehaus.groovy.runtime.ConvertedClosure"); + s.add("org.codehaus.groovy.runtime.MethodClosure"); + s.add("org.springframework.beans.factory.ObjectFactory"); + s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); + DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s); + } + + @Override + public ValueInstantiator findValueInstantiator(DeserializationConfig config, BeanDescription beanDesc, + ValueInstantiator defaultInstantiator) { + if (DEFAULT_NO_DESER_CLASS_NAMES.contains(beanDesc.getClassInfo().getRawType().getName())) { + throw new IllegalArgumentException("Illegal type " + beanDesc.getClassInfo().getRawType().getName() + " to deserialize: prevented for security reasons"); + } + + return super.findValueInstantiator(config, beanDesc, defaultInstantiator); + } + + } + + public DefenceModule() { + super(PackageVersion.VERSION); + } + + @Override + public void setupModule(SetupContext context) { + context.addValueInstantiators(new DefenceValueInstantiator()); + } + +} diff --git a/redisson/src/main/java/org/redisson/codec/JsonJacksonCodec.java b/redisson/src/main/java/org/redisson/codec/JsonJacksonCodec.java index f31482fa7..ca8639400 100755 --- a/redisson/src/main/java/org/redisson/codec/JsonJacksonCodec.java +++ b/redisson/src/main/java/org/redisson/codec/JsonJacksonCodec.java @@ -17,6 +17,9 @@ package org.redisson.codec; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import org.redisson.client.codec.Codec; import org.redisson.client.handler.State; @@ -29,15 +32,19 @@ import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.deser.ValueInstantiators; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.type.TypeFactory; +import com.fasterxml.jackson.dataformat.avro.PackageVersion; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; @@ -136,6 +143,8 @@ public class JsonJacksonCodec implements Codec { } protected void init(ObjectMapper objectMapper) { + objectMapper.registerModule(new DefenceModule()); + objectMapper.setSerializationInclusion(Include.NON_NULL); objectMapper.setVisibilityChecker(objectMapper.getSerializationConfig().getDefaultVisibilityChecker() .withFieldVisibility(JsonAutoDetect.Visibility.ANY).withGetterVisibility(JsonAutoDetect.Visibility.NONE) diff --git a/redisson/src/main/java/org/redisson/command/CommandBatchService.java b/redisson/src/main/java/org/redisson/command/CommandBatchService.java index 73f570b8a..c2656ebf9 100644 --- a/redisson/src/main/java/org/redisson/command/CommandBatchService.java +++ b/redisson/src/main/java/org/redisson/command/CommandBatchService.java @@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.redisson.RedissonReference; +import org.redisson.RedissonShutdownException; import org.redisson.api.RFuture; import org.redisson.client.RedisAskException; import org.redisson.client.RedisConnection; @@ -126,14 +127,18 @@ public class CommandBatchService extends CommandReactiveService { } public List execute() { - return get(executeAsync()); + return get(executeAsync(0, 0, 0)); + } + + public List execute(long responseTimeout, int retryAttempts, long retryInterval) { + return get(executeAsync(responseTimeout, retryAttempts, retryInterval)); } public RFuture executeAsyncVoid() { - return executeAsyncVoid(false); + return executeAsyncVoid(false, 0, 0, 0); } - private RFuture executeAsyncVoid(boolean noResult) { + private RFuture executeAsyncVoid(boolean noResult, long responseTimeout, int retryAttempts, long retryInterval) { if (executed) { throw new IllegalStateException("Batch already executed!"); } @@ -145,11 +150,11 @@ public class CommandBatchService extends CommandReactiveService { if (noResult) { for (Entry entry : commands.values()) { RPromise s = connectionManager.newPromise(); - BatchCommandData commandData = new BatchCommandData(s, null, RedisCommands.CLIENT_REPLY, new Object[] { "OFF" }, index.incrementAndGet()); - entry.getCommands().addFirst(commandData); + BatchCommandData offCommand = new BatchCommandData(s, null, RedisCommands.CLIENT_REPLY, new Object[] { "OFF" }, index.incrementAndGet()); + entry.getCommands().addFirst(offCommand); RPromise s1 = connectionManager.newPromise(); - BatchCommandData commandData1 = new BatchCommandData(s1, null, RedisCommands.CLIENT_REPLY, new Object[] { "ON" }, index.incrementAndGet()); - entry.getCommands().add(commandData1); + BatchCommandData onCommand = new BatchCommandData(s1, null, RedisCommands.CLIENT_REPLY, new Object[] { "ON" }, index.incrementAndGet()); + entry.getCommands().add(onCommand); } } @@ -165,20 +170,24 @@ public class CommandBatchService extends CommandReactiveService { AtomicInteger slots = new AtomicInteger(commands.size()); for (java.util.Map.Entry e : commands.entrySet()) { - execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, true); + execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, true, responseTimeout, retryAttempts, retryInterval); } return voidPromise; } - public void executeSkipResult() { - get(executeSkipResultAsync()); + public void executeSkipResult(long timeout, int retryAttempts, long retryInterval) { + get(executeSkipResultAsync(timeout, retryAttempts, retryInterval)); } - public RFuture executeSkipResultAsync() { - return executeAsyncVoid(true); + public RFuture executeSkipResultAsync(long timeout, int retryAttempts, long retryInterval) { + return executeAsyncVoid(true, timeout, retryAttempts, retryInterval); } public RFuture> executeAsync() { + return executeAsync(0, 0, 0); + } + + public RFuture> executeAsync(long responseTimeout, int retryAttempts, long retryInterval) { if (executed) { throw new IllegalStateException("Batch already executed!"); } @@ -222,18 +231,19 @@ public class CommandBatchService extends CommandReactiveService { AtomicInteger slots = new AtomicInteger(commands.size()); for (java.util.Map.Entry e : commands.entrySet()) { - execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, false); + execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, false, responseTimeout, retryAttempts, retryInterval); } return promise; } - private void execute(final Entry entry, final NodeSource source, final RPromise mainPromise, final AtomicInteger slots, final int attempt, final boolean noResult) { + private void execute(final Entry entry, final NodeSource source, final RPromise mainPromise, final AtomicInteger slots, + final int attempt, final boolean noResult, final long responseTimeout, final int retryAttempts, final long retryInterval) { if (mainPromise.isCancelled()) { return; } if (!connectionManager.getShutdownLatch().acquire()) { - mainPromise.tryFailure(new IllegalStateException("Redisson is shutdown")); + mainPromise.tryFailure(new RedissonShutdownException("Redisson is shutdown")); return; } @@ -271,7 +281,11 @@ public class CommandBatchService extends CommandReactiveService { return; } - if (attempt == connectionManager.getConfig().getRetryAttempts()) { + int attempts = connectionManager.getConfig().getRetryAttempts(); + if (retryAttempts > 0) { + attempts = retryAttempts; + } + if (attempt == attempts) { if (details.getException() == null) { details.setException(new RedisTimeoutException("Batch command execution timeout")); } @@ -283,17 +297,22 @@ public class CommandBatchService extends CommandReactiveService { } int count = attempt + 1; - execute(entry, source, mainPromise, slots, count, noResult); + execute(entry, source, mainPromise, slots, count, noResult, responseTimeout, retryAttempts, retryInterval); } }; - Timeout timeout = connectionManager.newTimeout(retryTimerTask, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS); + long interval = connectionManager.getConfig().getRetryInterval(); + if (retryInterval > 0) { + interval = retryInterval; + } + + Timeout timeout = connectionManager.newTimeout(retryTimerTask, interval, TimeUnit.MILLISECONDS); details.setTimeout(timeout); connectionFuture.addListener(new FutureListener() { @Override public void operationComplete(Future connFuture) throws Exception { - checkConnectionFuture(entry, source, mainPromise, attemptPromise, details, connectionFuture, noResult); + checkConnectionFuture(entry, source, mainPromise, attemptPromise, details, connectionFuture, noResult, responseTimeout); } }); @@ -308,18 +327,20 @@ public class CommandBatchService extends CommandReactiveService { if (future.cause() instanceof RedisMovedException) { RedisMovedException ex = (RedisMovedException)future.cause(); entry.clearErrors(); - execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.MOVED), mainPromise, slots, attempt, noResult); + NodeSource nodeSource = new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.MOVED); + execute(entry, nodeSource, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval); return; } if (future.cause() instanceof RedisAskException) { RedisAskException ex = (RedisAskException)future.cause(); entry.clearErrors(); - execute(entry, new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.ASK), mainPromise, slots, attempt, noResult); + NodeSource nodeSource = new NodeSource(ex.getSlot(), ex.getAddr(), Redirect.ASK); + execute(entry, nodeSource, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval); return; } if (future.cause() instanceof RedisLoadingException) { entry.clearErrors(); - execute(entry, source, mainPromise, slots, attempt, noResult); + execute(entry, source, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval); return; } if (future.cause() instanceof RedisTryAgainException) { @@ -327,7 +348,7 @@ public class CommandBatchService extends CommandReactiveService { connectionManager.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { - execute(entry, source, mainPromise, slots, attempt, noResult); + execute(entry, source, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval); } }, 1, TimeUnit.SECONDS); return; @@ -346,7 +367,7 @@ public class CommandBatchService extends CommandReactiveService { } private void checkWriteFuture(final RPromise attemptPromise, AsyncDetails details, - final RedisConnection connection, ChannelFuture future, boolean noResult) { + final RedisConnection connection, ChannelFuture future, boolean noResult, long responseTimeout) { if (attemptPromise.isDone() || future.isCancelled()) { return; } @@ -355,21 +376,26 @@ public class CommandBatchService extends CommandReactiveService { details.setException(new WriteRedisConnectionException("Can't write command batch to channel: " + future.channel(), future.cause())); } else { details.getTimeout().cancel(); - TimerTask timeoutTask = new TimerTask() { + TimerTask timerTask = new TimerTask() { @Override public void run(Timeout timeout) throws Exception { attemptPromise.tryFailure( new RedisTimeoutException("Redis server response timeout during command batch execution. Channel: " + connection.getChannel())); } }; - Timeout timeout = connectionManager.newTimeout(timeoutTask, connectionManager.getConfig().getTimeout(), TimeUnit.MILLISECONDS); - details.setTimeout(timeout); + + long timeout = connectionManager.getConfig().getTimeout(); + if (responseTimeout > 0) { + timeout = responseTimeout; + } + Timeout timeoutTask = connectionManager.newTimeout(timerTask, timeout, TimeUnit.MILLISECONDS); + details.setTimeout(timeoutTask); } } private void checkConnectionFuture(final Entry entry, final NodeSource source, final RPromise mainPromise, final RPromise attemptPromise, final AsyncDetails details, - RFuture connFuture, final boolean noResult) { + RFuture connFuture, final boolean noResult, final long responseTimeout) { if (attemptPromise.isDone() || mainPromise.isCancelled() || connFuture.isCancelled()) { return; } @@ -401,7 +427,7 @@ public class CommandBatchService extends CommandReactiveService { details.getWriteFuture().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { - checkWriteFuture(attemptPromise, details, connection, future, noResult); + checkWriteFuture(attemptPromise, details, connection, future, noResult, responseTimeout); } }); diff --git a/redisson/src/main/java/org/redisson/config/BaseConfig.java b/redisson/src/main/java/org/redisson/config/BaseConfig.java index f4b98a65f..4d4915804 100644 --- a/redisson/src/main/java/org/redisson/config/BaseConfig.java +++ b/redisson/src/main/java/org/redisson/config/BaseConfig.java @@ -141,8 +141,8 @@ class BaseConfig> { } /** - * Error will be thrown if Redis command can't be sended to Redis server after retryAttempts. - * But if it sent succesfully then timeout will be started. + * Error will be thrown if Redis command can't be sent to Redis server after retryAttempts. + * But if it sent successfully then timeout will be started. *

* Default is 3 attempts * @@ -160,7 +160,9 @@ class BaseConfig> { } /** - * Time interval after which another one attempt to send Redis command will be executed. + * Defines time interval for another one attempt send Redis command + * if it hasn't been sent already. + * *

* Default is 1500 milliseconds * @@ -178,7 +180,7 @@ class BaseConfig> { } /** - * Redis server response timeout. + * Redis server response timeout. Starts to countdown when Redis command has been successfully sent. *

* Default is 3000 milliseconds * diff --git a/redisson/src/main/java/org/redisson/config/Config.java b/redisson/src/main/java/org/redisson/config/Config.java index e5c74e387..e8d0aedfb 100644 --- a/redisson/src/main/java/org/redisson/config/Config.java +++ b/redisson/src/main/java/org/redisson/config/Config.java @@ -26,6 +26,8 @@ import org.redisson.client.codec.Codec; import org.redisson.codec.CodecProvider; import org.redisson.codec.DefaultCodecProvider; import org.redisson.codec.JsonJacksonCodec; +import org.redisson.connection.ConnectionManager; +import org.redisson.connection.ReplicatedConnectionManager; import org.redisson.liveobject.provider.DefaultResolverProvider; import org.redisson.liveobject.provider.ResolverProvider; @@ -50,6 +52,8 @@ public class Config { private ElasticacheServersConfig elasticacheServersConfig; private ReplicatedServersConfig replicatedServersConfig; + + private ConnectionManager connectionManager; /** * Threads amount shared between all redis node clients @@ -122,6 +126,9 @@ public class Config { if (oldConf.getReplicatedServersConfig() != null) { setReplicatedServersConfig(new ReplicatedServersConfig(oldConf.getReplicatedServersConfig())); } + if (oldConf.getConnectionManager() != null) { + useCustomServers(oldConf.getConnectionManager()); + } } @@ -296,6 +303,28 @@ public class Config { void setReplicatedServersConfig(ReplicatedServersConfig replicatedServersConfig) { this.replicatedServersConfig = replicatedServersConfig; } + + /** + * Returns the connection manager if supplied via + * {@link #useCustomServers(ConnectionManager)} + * + * @return ConnectionManager + */ + ConnectionManager getConnectionManager() { + return connectionManager; + } + + /** + * This is an extension point to supply custom connection manager. + * + * @see ReplicatedConnectionManager on how to implement a connection + * manager. + * @param connectionManager for supply + */ + public void useCustomServers(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + /** * Init single server configuration. @@ -447,6 +476,7 @@ public class Config { throw new IllegalStateException("Replication servers config already used!"); } } + /** * Activates an unix socket if servers binded to loopback interface. diff --git a/redisson/src/main/java/org/redisson/config/ConfigSupport.java b/redisson/src/main/java/org/redisson/config/ConfigSupport.java index 7c55bb5e4..9e48cc479 100644 --- a/redisson/src/main/java/org/redisson/config/ConfigSupport.java +++ b/redisson/src/main/java/org/redisson/config/ConfigSupport.java @@ -265,7 +265,9 @@ public class ConfigSupport { } else if (configCopy.getReplicatedServersConfig() != null) { validate(configCopy.getReplicatedServersConfig()); return new ReplicatedConnectionManager(configCopy.getReplicatedServersConfig(), configCopy); - } else { + } else if (configCopy.getConnectionManager() != null) { + return configCopy.getConnectionManager(); + }else { throw new IllegalArgumentException("server(s) address(es) not defined!"); } } diff --git a/redisson/src/main/java/org/redisson/config/RedissonNodeConfig.java b/redisson/src/main/java/org/redisson/config/RedissonNodeConfig.java index 4facf4e65..8a3b59750 100644 --- a/redisson/src/main/java/org/redisson/config/RedissonNodeConfig.java +++ b/redisson/src/main/java/org/redisson/config/RedissonNodeConfig.java @@ -23,12 +23,14 @@ import java.util.Map; import org.redisson.api.RedissonNodeInitializer; /** + * Redisson Node configuration * * @author Nikita Koksharov * */ public class RedissonNodeConfig extends Config { + private int mapReduceWorkers = 0; private RedissonNodeInitializer redissonNodeInitializer; private Map executorServiceWorkers = new HashMap(); @@ -46,6 +48,23 @@ public class RedissonNodeConfig extends Config { this.redissonNodeInitializer = oldConf.redissonNodeInitializer; } + /** + * MapReduce workers amount. + * 0 = current_processors_amount + *

+ * Default is 0 + * + * @param mapReduceWorkers workers for MapReduce + * @return config + */ + public RedissonNodeConfig setMapReduceWorkers(int mapReduceWorkers) { + this.mapReduceWorkers = mapReduceWorkers; + return this; + } + public int getMapReduceWorkers() { + return mapReduceWorkers; + } + /** * Executor service workers amount per service name * @@ -60,10 +79,6 @@ public class RedissonNodeConfig extends Config { return executorServiceWorkers; } - public RedissonNodeInitializer getRedissonNodeInitializer() { - return redissonNodeInitializer; - } - /** * Redisson node initializer * @@ -74,6 +89,10 @@ public class RedissonNodeConfig extends Config { this.redissonNodeInitializer = redissonNodeInitializer; return this; } + public RedissonNodeInitializer getRedissonNodeInitializer() { + return redissonNodeInitializer; + } + /** * Read config object stored in JSON format from File diff --git a/redisson/src/main/java/org/redisson/connection/ConnectionManager.java b/redisson/src/main/java/org/redisson/connection/ConnectionManager.java index b3094aca4..0300a28d6 100644 --- a/redisson/src/main/java/org/redisson/connection/ConnectionManager.java +++ b/redisson/src/main/java/org/redisson/connection/ConnectionManager.java @@ -64,9 +64,9 @@ public interface ConnectionManager { boolean isShuttingDown(); - RFuture subscribe(Codec codec, String channelName, RedisPubSubListener listener); + RFuture subscribe(Codec codec, String channelName, RedisPubSubListener... listeners); - RFuture subscribe(Codec codec, String channelName, RedisPubSubListener listener, AsyncSemaphore semaphore); + RFuture subscribe(Codec codec, String channelName, AsyncSemaphore semaphore, RedisPubSubListener... listeners); ConnectionInitializer getConnectListener(); @@ -106,9 +106,9 @@ public interface ConnectionManager { PubSubConnectionEntry getPubSubEntry(String channelName); - RFuture psubscribe(String pattern, Codec codec, RedisPubSubListener listener); + RFuture psubscribe(String pattern, Codec codec, RedisPubSubListener... listeners); - RFuture psubscribe(String pattern, Codec codec, RedisPubSubListener listener, AsyncSemaphore semaphore); + RFuture psubscribe(String pattern, Codec codec, AsyncSemaphore semaphore, RedisPubSubListener... listeners); Codec unsubscribe(String channelName, AsyncSemaphore lock); diff --git a/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java b/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java index de0392b13..b903de260 100644 --- a/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java +++ b/redisson/src/main/java/org/redisson/connection/MasterSlaveConnectionManager.java @@ -55,8 +55,8 @@ import org.redisson.connection.ClientConnectionsEntry.FreezeReason; import org.redisson.misc.InfinitySemaphoreLatch; import org.redisson.misc.RPromise; import org.redisson.misc.RedissonPromise; +import org.redisson.misc.TransferListener; import org.redisson.pubsub.AsyncSemaphore; -import org.redisson.pubsub.TransferListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -364,41 +364,41 @@ public class MasterSlaveConnectionManager implements ConnectionManager { } @Override - public RFuture psubscribe(final String channelName, final Codec codec, final RedisPubSubListener listener) { + public RFuture psubscribe(final String channelName, final Codec codec, final RedisPubSubListener... listeners) { final AsyncSemaphore lock = getSemaphore(channelName); final RPromise result = newPromise(); lock.acquire(new Runnable() { @Override public void run() { - RFuture future = psubscribe(channelName, codec, listener, lock); + RFuture future = psubscribe(channelName, codec, lock, listeners); future.addListener(new TransferListener(result)); } }); return result; } - public RFuture psubscribe(String channelName, Codec codec, RedisPubSubListener listener, AsyncSemaphore semaphore) { + public RFuture psubscribe(String channelName, Codec codec, AsyncSemaphore semaphore, RedisPubSubListener... listeners) { RPromise promise = newPromise(); - subscribe(codec, channelName, listener, promise, PubSubType.PSUBSCRIBE, semaphore); + subscribe(codec, channelName, promise, PubSubType.PSUBSCRIBE, semaphore, listeners); return promise; } - public RFuture subscribe(final Codec codec, final String channelName, final RedisPubSubListener listener) { + public RFuture subscribe(final Codec codec, final String channelName, final RedisPubSubListener... listeners) { final AsyncSemaphore lock = getSemaphore(channelName); final RPromise result = newPromise(); lock.acquire(new Runnable() { @Override public void run() { - RFuture future = subscribe(codec, channelName, listener, lock); + RFuture future = subscribe(codec, channelName, lock, listeners); future.addListener(new TransferListener(result)); } }); return result; } - public RFuture subscribe(Codec codec, String channelName, RedisPubSubListener listener, AsyncSemaphore semaphore) { + public RFuture subscribe(Codec codec, String channelName, AsyncSemaphore semaphore, RedisPubSubListener... listeners) { RPromise promise = newPromise(); - subscribe(codec, channelName, listener, promise, PubSubType.SUBSCRIBE, semaphore); + subscribe(codec, channelName, promise, PubSubType.SUBSCRIBE, semaphore, listeners); return promise; } @@ -406,18 +406,11 @@ public class MasterSlaveConnectionManager implements ConnectionManager { return locks[Math.abs(channelName.hashCode() % locks.length)]; } - private void subscribe(final Codec codec, final String channelName, final RedisPubSubListener listener, - final RPromise promise, final PubSubType type, final AsyncSemaphore lock) { + private void subscribe(final Codec codec, final String channelName, + final RPromise promise, final PubSubType type, final AsyncSemaphore lock, final RedisPubSubListener... listeners) { final PubSubConnectionEntry connEntry = name2PubSubConnection.get(channelName); if (connEntry != null) { - connEntry.addListener(channelName, listener); - connEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - lock.release(); - promise.trySuccess(connEntry); - } - }); + subscribe(channelName, promise, type, lock, connEntry, listeners); return; } @@ -431,7 +424,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { final PubSubConnectionEntry freeEntry = freePubSubConnections.peek(); if (freeEntry == null) { - connect(codec, channelName, listener, promise, type, lock); + connect(codec, channelName, promise, type, lock, listeners); return; } @@ -445,14 +438,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { freeEntry.release(); freePubSubLock.release(); - oldEntry.addListener(channelName, listener); - oldEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - lock.release(); - promise.trySuccess(oldEntry); - } - }); + subscribe(channelName, promise, type, lock, oldEntry, listeners); return; } @@ -461,14 +447,7 @@ public class MasterSlaveConnectionManager implements ConnectionManager { } freePubSubLock.release(); - freeEntry.addListener(channelName, listener); - freeEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - lock.release(); - promise.trySuccess(freeEntry); - } - }); + subscribe(channelName, promise, type, lock, freeEntry, listeners); if (PubSubType.PSUBSCRIBE == type) { freeEntry.psubscribe(codec, channelName); @@ -480,8 +459,23 @@ public class MasterSlaveConnectionManager implements ConnectionManager { }); } - private void connect(final Codec codec, final String channelName, final RedisPubSubListener listener, - final RPromise promise, final PubSubType type, final AsyncSemaphore lock) { + private void subscribe(final String channelName, final RPromise promise, + final PubSubType type, final AsyncSemaphore lock, final PubSubConnectionEntry connEntry, + final RedisPubSubListener... listeners) { + for (RedisPubSubListener listener : listeners) { + connEntry.addListener(channelName, listener); + } + connEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + lock.release(); + promise.trySuccess(connEntry); + } + }); + } + + private void connect(final Codec codec, final String channelName, + final RPromise promise, final PubSubType type, final AsyncSemaphore lock, final RedisPubSubListener... listeners) { final int slot = calcSlot(channelName); RFuture connFuture = nextPubSubConnection(slot); connFuture.addListener(new FutureListener() { @@ -505,29 +499,15 @@ public class MasterSlaveConnectionManager implements ConnectionManager { releaseSubscribeConnection(slot, entry); freePubSubLock.release(); - - oldEntry.addListener(channelName, listener); - oldEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - lock.release(); - promise.trySuccess(oldEntry); - } - }); + + subscribe(channelName, promise, type, lock, oldEntry, listeners); return; } freePubSubConnections.add(entry); freePubSubLock.release(); - entry.addListener(channelName, listener); - entry.getSubscribeFuture(channelName, type).addListener(new FutureListener() { - @Override - public void operationComplete(Future future) throws Exception { - lock.release(); - promise.trySuccess(entry); - } - }); + subscribe(channelName, promise, type, lock, entry, listeners); if (PubSubType.PSUBSCRIBE == type) { entry.psubscribe(codec, channelName); diff --git a/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java b/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java index 2fab36ea6..dffca6a57 100644 --- a/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java +++ b/redisson/src/main/java/org/redisson/connection/MasterSlaveEntry.java @@ -211,21 +211,15 @@ public class MasterSlaveEntry { private void subscribe(final String channelName, final Collection> listeners, final Codec subscribeCodec) { - RFuture subscribeFuture = connectionManager.subscribe(subscribeCodec, channelName, null); + RFuture subscribeFuture = connectionManager.subscribe(subscribeCodec, channelName, listeners.toArray(new RedisPubSubListener[listeners.size()])); subscribeFuture.addListener(new FutureListener() { @Override public void operationComplete(Future future) throws Exception { - if (!future.isSuccess()) { - subscribe(channelName, listeners, subscribeCodec); - return; - } - PubSubConnectionEntry newEntry = future.getNow(); - for (RedisPubSubListener redisPubSubListener : listeners) { - newEntry.addListener(channelName, redisPubSubListener); + if (future.isSuccess()) { + log.debug("resubscribed listeners of '{}' channel to {}", channelName, future.getNow().getConnection().getRedisClient()); } - log.debug("resubscribed listeners of '{}' channel to {}", channelName, newEntry.getConnection().getRedisClient()); } }); } diff --git a/redisson/src/main/java/org/redisson/connection/PubSubConnectionEntry.java b/redisson/src/main/java/org/redisson/connection/PubSubConnectionEntry.java index f953f3ac4..38d59ea0c 100644 --- a/redisson/src/main/java/org/redisson/connection/PubSubConnectionEntry.java +++ b/redisson/src/main/java/org/redisson/connection/PubSubConnectionEntry.java @@ -26,7 +26,6 @@ import java.util.concurrent.atomic.AtomicInteger; import org.redisson.PubSubMessageListener; import org.redisson.PubSubPatternMessageListener; -import org.redisson.api.listener.MessageListener; import org.redisson.client.BaseRedisPubSubListener; import org.redisson.client.RedisPubSubConnection; import org.redisson.client.RedisPubSubListener; diff --git a/redisson/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java b/redisson/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java index 0e7cb4f27..996dc1f52 100644 --- a/redisson/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java +++ b/redisson/src/main/java/org/redisson/connection/decoder/CacheGetAllDecoder.java @@ -27,6 +27,11 @@ import org.redisson.client.protocol.decoder.MultiDecoder; import io.netty.buffer.ByteBuf; +/** + * + * @author Nikita Koksharov + * + */ public class CacheGetAllDecoder implements MultiDecoder> { private final List args; diff --git a/redisson/src/main/java/org/redisson/eviction/EvictionScheduler.java b/redisson/src/main/java/org/redisson/eviction/EvictionScheduler.java index d51cd8042..6f229ff90 100644 --- a/redisson/src/main/java/org/redisson/eviction/EvictionScheduler.java +++ b/redisson/src/main/java/org/redisson/eviction/EvictionScheduler.java @@ -55,8 +55,8 @@ public class EvictionScheduler { } } - public void schedule(String name) { - EvictionTask task = new SetCacheEvictionTask(name, executor); + public void schedule(String name, long shiftInMilliseconds) { + EvictionTask task = new ScoredSetEvictionTask(name, executor, shiftInMilliseconds); EvictionTask prevTask = tasks.putIfAbsent(name, task); if (prevTask == null) { task.schedule(); diff --git a/redisson/src/main/java/org/redisson/eviction/SetCacheEvictionTask.java b/redisson/src/main/java/org/redisson/eviction/ScoredSetEvictionTask.java similarity index 79% rename from redisson/src/main/java/org/redisson/eviction/SetCacheEvictionTask.java rename to redisson/src/main/java/org/redisson/eviction/ScoredSetEvictionTask.java index 826c879bc..f7ea0e8b3 100644 --- a/redisson/src/main/java/org/redisson/eviction/SetCacheEvictionTask.java +++ b/redisson/src/main/java/org/redisson/eviction/ScoredSetEvictionTask.java @@ -25,18 +25,20 @@ import org.redisson.command.CommandAsyncExecutor; * @author Nikita Koksharov * */ -public class SetCacheEvictionTask extends EvictionTask { +public class ScoredSetEvictionTask extends EvictionTask { private final String name; + private final long shiftInMilliseconds; - public SetCacheEvictionTask(String name, CommandAsyncExecutor executor) { + public ScoredSetEvictionTask(String name, CommandAsyncExecutor executor, long shiftInMilliseconds) { super(executor); this.name = name; + this.shiftInMilliseconds = shiftInMilliseconds; } @Override RFuture execute() { - return executor.writeAsync(name, LongCodec.INSTANCE, RedisCommands.ZREMRANGEBYSCORE, name, 0, System.currentTimeMillis()); + return executor.writeAsync(name, LongCodec.INSTANCE, RedisCommands.ZREMRANGEBYSCORE, name, 0, System.currentTimeMillis() - shiftInMilliseconds); } } diff --git a/redisson/src/main/java/org/redisson/executor/RedissonCompletionService.java b/redisson/src/main/java/org/redisson/executor/RedissonCompletionService.java new file mode 100644 index 000000000..30df6ce3c --- /dev/null +++ b/redisson/src/main/java/org/redisson/executor/RedissonCompletionService.java @@ -0,0 +1,112 @@ +/** + * 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.executor; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RFuture; +import org.redisson.api.RScheduledExecutorService; + +import io.netty.util.concurrent.FutureListener; + +/** + * A {@link CompletionService} that uses a supplied {@link Executor} + * to execute tasks. This class arranges that submitted tasks are, + * upon completion, placed on a queue accessible using {@code take}. + * The class is lightweight enough to be suitable for transient use + * when processing groups of tasks. + * + * @author Nikita Koksharov + * + * @param value type + */ +public class RedissonCompletionService implements CompletionService { + + protected final RScheduledExecutorService executorService; + + protected final BlockingQueue> completionQueue; + + public RedissonCompletionService(RScheduledExecutorService executorService) { + this(executorService, null); + } + + public RedissonCompletionService(RScheduledExecutorService executorService, BlockingQueue> completionQueue) { + if (executorService == null) { + throw new NullPointerException("executorService can't be null"); + } + + this.executorService = executorService; + if (completionQueue == null) { + completionQueue = new LinkedBlockingQueue>(); + } + + this.completionQueue = completionQueue; + } + + @Override + public Future submit(Callable task) { + if (task == null) { + throw new NullPointerException("taks can't be null"); + } + + final RFuture f = executorService.submit(task); + f.addListener(new FutureListener() { + @Override + public void operationComplete(io.netty.util.concurrent.Future future) throws Exception { + completionQueue.add(f); + } + }); + return f; + } + + @Override + public Future submit(Runnable task, V result) { + if (task == null) { + throw new NullPointerException("taks can't be null"); + } + + final RFuture f = executorService.submit(task, result); + f.addListener(new FutureListener() { + @Override + public void operationComplete(io.netty.util.concurrent.Future future) throws Exception { + completionQueue.add(f); + } + }); + return f; + } + + @Override + public Future take() throws InterruptedException { + return completionQueue.take(); + } + + @Override + public Future poll() { + return completionQueue.poll(); + } + + @Override + public Future poll(long timeout, TimeUnit unit) throws InterruptedException { + return completionQueue.poll(timeout, unit); + } + +} diff --git a/redisson/src/main/java/org/redisson/executor/RemoteExecutorServiceImpl.java b/redisson/src/main/java/org/redisson/executor/RemoteExecutorServiceImpl.java index bb0b4c4ae..fa7ade267 100644 --- a/redisson/src/main/java/org/redisson/executor/RemoteExecutorServiceImpl.java +++ b/redisson/src/main/java/org/redisson/executor/RemoteExecutorServiceImpl.java @@ -22,13 +22,16 @@ import java.util.Date; import java.util.concurrent.Callable; import org.redisson.RedissonExecutorService; +import org.redisson.RedissonShutdownException; import org.redisson.api.RFuture; import org.redisson.api.RedissonClient; import org.redisson.api.RemoteInvocationOptions; import org.redisson.api.annotation.RInject; +import org.redisson.client.RedisException; import org.redisson.client.codec.Codec; import org.redisson.client.protocol.RedisCommands; import org.redisson.command.CommandExecutor; +import org.redisson.misc.Injector; import org.redisson.remote.RemoteParams; import io.netty.buffer.ByteBuf; @@ -100,10 +103,10 @@ public class RemoteExecutorServiceImpl implements RemoteExecutorService, RemoteP RFuture future = asyncScheduledServiceAtFixed().scheduleAtFixedRate(className, classBody, state, newStartTime, period); try { executeRunnable(className, classBody, state); - } catch (Exception e) { + } catch (RuntimeException e) { // cancel task if it throws an exception future.cancel(true); - throw new RuntimeException(e); + throw e; } } @@ -113,10 +116,10 @@ public class RemoteExecutorServiceImpl implements RemoteExecutorService, RemoteP RFuture future = asyncScheduledServiceAtFixed().schedule(className, classBody, state, nextStartDate.getTime(), cronExpression); try { executeRunnable(className, classBody, state); - } catch (Exception e) { + } catch (RuntimeException e) { // cancel task if it throws an exception future.cancel(true); - throw new RuntimeException(e); + throw e; } } @@ -172,6 +175,11 @@ public class RemoteExecutorServiceImpl implements RemoteExecutorService, RemoteP Callable callable = decode(buf); return callable.call(); + } catch (RedissonShutdownException e) { + return null; + // skip + } catch (RedisException e) { + throw e; } catch (Exception e) { throw new IllegalArgumentException(e); } finally { @@ -183,18 +191,7 @@ public class RemoteExecutorServiceImpl implements RemoteExecutorService, RemoteP private T decode(ByteBuf buf) throws IOException { T task = (T) codec.getValueDecoder().decode(buf, null); - Field[] fields = task.getClass().getDeclaredFields(); - for (Field field : fields) { - if (RedissonClient.class.isAssignableFrom(field.getType()) - && field.isAnnotationPresent(RInject.class)) { - field.setAccessible(true); - try { - field.set(task, redisson); - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - } + Injector.inject(task, redisson); return task; } @@ -214,6 +211,10 @@ public class RemoteExecutorServiceImpl implements RemoteExecutorService, RemoteP Runnable runnable = decode(buf); runnable.run(); + } catch (RedissonShutdownException e) { + // skip + } catch (RedisException e) { + throw e; } catch (Exception e) { throw new IllegalArgumentException(e); } finally { diff --git a/redisson/src/main/java/org/redisson/jcache/JCache.java b/redisson/src/main/java/org/redisson/jcache/JCache.java index e3d128c76..cf898931d 100644 --- a/redisson/src/main/java/org/redisson/jcache/JCache.java +++ b/redisson/src/main/java/org/redisson/jcache/JCache.java @@ -26,7 +26,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; import javax.cache.Cache; import javax.cache.CacheManager; @@ -69,10 +68,9 @@ import org.redisson.client.protocol.decoder.MapScanResult; import org.redisson.client.protocol.decoder.ObjectListReplayDecoder; import org.redisson.client.protocol.decoder.ScanObjectEntry; import org.redisson.connection.decoder.MapGetAllDecoder; -import org.redisson.misc.Hash; - import org.redisson.jcache.JMutableEntry.Action; import org.redisson.jcache.configuration.JCacheConfiguration; +import org.redisson.misc.Hash; import io.netty.util.internal.ThreadLocalRandom; @@ -314,7 +312,7 @@ public class JCache extends RedissonObject implements Cache { V load(K key) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { V value = getValueLocked(key); if (value == null) { @@ -703,7 +701,7 @@ public class JCache extends RedissonObject implements Cache { try { if (!containsKey(key) || replaceExistingValues) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { if (!containsKey(key)|| replaceExistingValues) { V value; @@ -743,7 +741,7 @@ public class JCache extends RedissonObject implements Cache { private RLock getLockedLock(K key) { String lockName = getLockName(key); RLock lock = redisson.getLock(lockName); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); return lock; } @@ -761,7 +759,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { List result = getAndPutValueLocked(key, value); if (result.isEmpty()) { @@ -971,7 +969,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { List result = getAndPutValueLocked(key, value); if (result.isEmpty()) { @@ -1069,7 +1067,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); List result = getAndPutValue(key, value); if (result.isEmpty()) { @@ -1091,7 +1089,7 @@ public class JCache extends RedissonObject implements Cache { if (result.size() == 4) { val = (V) result.get(1); } - + deletedKeys.put(key, val); } cacheManager.getStatBean(this).addPuts(1); @@ -1173,7 +1171,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { boolean result = putIfAbsentValueLocked(key, value); if (result) { @@ -1255,7 +1253,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = System.currentTimeMillis(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { V oldValue = getValue(key); boolean result = removeValue(key); @@ -1396,7 +1394,7 @@ public class JCache extends RedissonObject implements Cache { boolean result; if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { result = removeValueLocked(key, value); if (result) { @@ -1488,7 +1486,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { Object value = getAndRemoveValue(key); if (value != null) { @@ -1691,7 +1689,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { long result = replaceValueLocked(key, oldValue, newValue); if (result == 1) { @@ -1935,7 +1933,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { boolean result = replaceValueLocked(key, value); if (result) { @@ -1989,7 +1987,7 @@ public class JCache extends RedissonObject implements Cache { long startTime = currentNanoTime(); if (config.isWriteThrough()) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); try { V result = getAndReplaceValueLocked(key, value); if (result != null) { @@ -2047,7 +2045,7 @@ public class JCache extends RedissonObject implements Cache { if (config.isWriteThrough()) { for (K key : keys) { RLock lock = getLock(key); - lock.lock(30, TimeUnit.MINUTES); + lock.lock(); V result = getAndRemoveValue(key); if (result != null) { deletedKeys.put(key, result); diff --git a/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java index 9a8847cbe..d690b0765 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java @@ -118,11 +118,11 @@ public class AccessorInterceptor { RObject rObject = objectBuilder.createObject(((RLiveObject) me).getLiveObjectId(), me.getClass().getSuperclass(), arg.getClass(), fieldName); if (arg != null) { if (rObject instanceof Collection) { - Collection c = (Collection) rObject; + Collection c = (Collection) rObject; c.clear(); c.addAll((Collection) arg); } else { - Map m = (Map) rObject; + Map m = (Map) rObject; m.clear(); m.putAll((Map) arg); } diff --git a/redisson/src/main/java/org/redisson/liveobject/core/FieldAccessorInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/FieldAccessorInterceptor.java index b5219276d..cf9f500ac 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/FieldAccessorInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/FieldAccessorInterceptor.java @@ -15,15 +15,16 @@ */ package org.redisson.liveobject.core; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; + +import org.redisson.api.RMap; +import org.redisson.liveobject.misc.ClassUtils; + import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.FieldValue; import net.bytebuddy.implementation.bind.annotation.Origin; import net.bytebuddy.implementation.bind.annotation.RuntimeType; import net.bytebuddy.implementation.bind.annotation.This; -import org.redisson.api.RMap; -import org.redisson.liveobject.misc.ClassUtils; /** * @@ -36,7 +37,7 @@ public class FieldAccessorInterceptor { @Origin Method method, @AllArguments Object[] args, @This Object me, - @FieldValue("liveObjectLiveMap") RMap map + @FieldValue("liveObjectLiveMap") RMap map ) throws Exception { if (args.length >= 1 && String.class.isAssignableFrom(args[0].getClass())) { String name = ((String) args[0]).substring(0, 1).toUpperCase() + ((String) args[0]).substring(1); diff --git a/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java index d1396d2d4..e0ae797e3 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/LiveObjectInterceptor.java @@ -50,13 +50,13 @@ public class LiveObjectInterceptor { private final RedissonClient redisson; private final CodecProvider codecProvider; - private final Class originalClass; + private final Class originalClass; private final String idFieldName; - private final Class idFieldType; + private final Class idFieldType; private final NamingScheme namingScheme; private final Class codecClass; - public LiveObjectInterceptor(RedissonClient redisson, CodecProvider codecProvider, Class entityClass, String idFieldName) { + public LiveObjectInterceptor(RedissonClient redisson, CodecProvider codecProvider, Class entityClass, String idFieldName) { this.redisson = redisson; this.codecProvider = codecProvider; this.originalClass = entityClass; @@ -79,7 +79,7 @@ public class LiveObjectInterceptor { @FieldValue("liveObjectId") Object id, @FieldProxy("liveObjectId") Setter idSetter, @FieldProxy("liveObjectId") Getter idGetter, - @FieldValue("liveObjectLiveMap") RMap map, + @FieldValue("liveObjectLiveMap") RMap map, @FieldProxy("liveObjectLiveMap") Setter mapSetter, @FieldProxy("liveObjectLiveMap") Getter mapGetter ) throws Exception { diff --git a/redisson/src/main/java/org/redisson/liveobject/core/RExpirableInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/RExpirableInterceptor.java index 3a59a3412..d0bfd7f54 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/RExpirableInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/RExpirableInterceptor.java @@ -34,9 +34,9 @@ public class RExpirableInterceptor { public static Object intercept( @Origin Method method, @AllArguments Object[] args, - @FieldValue("liveObjectLiveMap") RMap map + @FieldValue("liveObjectLiveMap") RMap map ) throws Exception { - Class[] cls = new Class[args.length]; + Class[] cls = new Class[args.length]; for (int i = 0; i < args.length; i++) { cls[i] = args[i].getClass(); } diff --git a/redisson/src/main/java/org/redisson/liveobject/core/RMapInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/RMapInterceptor.java index ecfc2ed29..26c682605 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/RMapInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/RMapInterceptor.java @@ -33,9 +33,9 @@ public class RMapInterceptor { public static Object intercept( @Origin Method method, @AllArguments Object[] args, - @FieldValue("liveObjectLiveMap") RMap map + @FieldValue("liveObjectLiveMap") RMap map ) throws Exception { - Class[] cls = new Class[args.length]; + Class[] cls = new Class[args.length]; for (int i = 0; i < args.length; i++) { cls[i] = args[i].getClass(); } diff --git a/redisson/src/main/java/org/redisson/liveobject/core/RObjectInterceptor.java b/redisson/src/main/java/org/redisson/liveobject/core/RObjectInterceptor.java index 41d2393ea..1f88dcbb0 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/RObjectInterceptor.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/RObjectInterceptor.java @@ -32,7 +32,7 @@ public class RObjectInterceptor { public static Object intercept( @Origin Method method, @AllArguments Object[] args, - @FieldValue("liveObjectLiveMap") RMap map + @FieldValue("liveObjectLiveMap") RMap map ) throws Exception { throw new UnsupportedOperationException("Please use RLiveObjectService instance for this type of functions"); } diff --git a/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java b/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java index 101414170..b53dc03b3 100644 --- a/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java +++ b/redisson/src/main/java/org/redisson/liveobject/core/RedissonObjectBuilder.java @@ -16,7 +16,6 @@ package org.redisson.liveobject.core; import java.lang.reflect.Field; -import java.util.Collection; import java.util.Deque; import java.util.LinkedHashMap; import java.util.List; @@ -123,7 +122,7 @@ public class RedissonObjectBuilder { return codecProvider.getCodec(anno, rEntity, rObjectClass, fieldName); } else { REntity anno = rEntity.getAnnotation(REntity.class); - return codecProvider.getCodec(anno, (Class) rEntity); + return codecProvider.getCodec(anno, (Class) rEntity); } } diff --git a/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java b/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java index c2b664731..e94d91ef0 100644 --- a/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java +++ b/redisson/src/main/java/org/redisson/liveobject/misc/ClassUtils.java @@ -105,7 +105,7 @@ public class ClassUtils { * * @return Method object */ - public static Method searchForMethod(Class type, String name, Class[] parms) { + public static Method searchForMethod(Class type, String name, Class[] parms) { try { return type.getMethod(name, parms); } catch (NoSuchMethodException e) {} @@ -116,7 +116,7 @@ public class ClassUtils { continue; } - Class[] types = methods[i].getParameterTypes(); + Class[] types = methods[i].getParameterTypes(); // Does it have the same number of arguments that we're looking for. if (types.length != parms.length) { continue; @@ -130,7 +130,7 @@ public class ClassUtils { return null; } - private static boolean areTypesCompatible(Class[] targets, Class[] sources) { + private static boolean areTypesCompatible(Class[] targets, Class[] sources) { if (targets.length != sources.length) { return false; } diff --git a/redisson/src/main/java/org/redisson/liveobject/resolver/RIdResolver.java b/redisson/src/main/java/org/redisson/liveobject/resolver/RIdResolver.java index 4b894dcc2..1b221ef49 100644 --- a/redisson/src/main/java/org/redisson/liveobject/resolver/RIdResolver.java +++ b/redisson/src/main/java/org/redisson/liveobject/resolver/RIdResolver.java @@ -24,7 +24,7 @@ import org.redisson.api.annotation.RId; * @param RId annotation to resolve * @param Value type */ -public interface RIdResolver extends Resolver{ +public interface RIdResolver extends Resolver, A, V>{ /** * RLiveObjectService instantiate the class and invokes this method to get @@ -35,6 +35,6 @@ public interface RIdResolver extends Resolver{ * @param redisson instance * @return resolved RId field value. */ - public V resolve(Class cls, A annotation, String idFieldName, RedissonClient redisson); + public V resolve(Class cls, A annotation, String idFieldName, RedissonClient redisson); } diff --git a/redisson/src/main/java/org/redisson/liveobject/resolver/UUIDGenerator.java b/redisson/src/main/java/org/redisson/liveobject/resolver/UUIDGenerator.java index a4f6cfe4b..81324610d 100644 --- a/redisson/src/main/java/org/redisson/liveobject/resolver/UUIDGenerator.java +++ b/redisson/src/main/java/org/redisson/liveobject/resolver/UUIDGenerator.java @@ -29,7 +29,7 @@ public class UUIDGenerator implements RIdResolver{ public static final UUIDGenerator INSTANCE = new UUIDGenerator(); @Override - public String resolve(Class value, RId id, String idFieldName, RedissonClient redisson) { + public String resolve(Class value, RId id, String idFieldName, RedissonClient redisson) { return UUID.randomUUID().toString(); } diff --git a/redisson/src/main/java/org/redisson/mapreduce/BaseMapperTask.java b/redisson/src/main/java/org/redisson/mapreduce/BaseMapperTask.java new file mode 100644 index 000000000..a079efe38 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/BaseMapperTask.java @@ -0,0 +1,76 @@ +/** + * 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.mapreduce; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.redisson.api.RedissonClient; +import org.redisson.api.annotation.RInject; + +/** + * + * @author Nikita Koksharov + * + * @param output key + * @param output value + */ +public abstract class BaseMapperTask implements Runnable, Serializable { + + private static final long serialVersionUID = 6224632826989873592L; + + @RInject + protected RedissonClient redisson; + + protected Class objectClass; + protected List objectNames = new ArrayList(); + protected Class objectCodecClass; + + protected int workersAmount; + protected String collectorMapName; + protected long timeout; + + public BaseMapperTask() { + } + + public BaseMapperTask(Class objectClass, Class objectCodecClass) { + super(); + this.objectClass = objectClass; + this.objectCodecClass = objectCodecClass; + } + + public void addObjectName(String objectName) { + this.objectNames.add(objectName); + } + + public void clearObjectNames() { + this.objectNames.clear(); + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public void setWorkersAmount(int workersAmount) { + this.workersAmount = workersAmount; + } + + public void setCollectorMapName(String collatorMapName) { + this.collectorMapName = collatorMapName; + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/CollatorTask.java b/redisson/src/main/java/org/redisson/mapreduce/CollatorTask.java new file mode 100644 index 000000000..8b5c5fd56 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/CollatorTask.java @@ -0,0 +1,70 @@ +/** + * 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.mapreduce; + +import java.util.concurrent.Callable; + +import org.redisson.api.RMap; +import org.redisson.api.RedissonClient; +import org.redisson.api.annotation.RInject; +import org.redisson.api.mapreduce.RCollator; +import org.redisson.client.codec.Codec; +import org.redisson.misc.Injector; + +/** + * + * @author Nikita Koksharov + * + * @param key type + * @param value type + * @param result type + */ +public class CollatorTask implements Callable { + + @RInject + private RedissonClient redisson; + + private RCollator collator; + + private String resultMapName; + private Class codecClass; + + private Codec codec; + + public CollatorTask() { + } + + public CollatorTask(RedissonClient redisson, RCollator collator, String resultMapName, Class codecClass) { + super(); + this.redisson = redisson; + this.collator = collator; + this.resultMapName = resultMapName; + this.codecClass = codecClass; + } + + @Override + public R call() throws Exception { + this.codec = (Codec) codecClass.getConstructor().newInstance(); + + Injector.inject(collator, redisson); + + RMap resultMap = redisson.getMap(resultMapName, codec); + R result = collator.collate(resultMap); + resultMap.delete(); + return result; + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/CollectionMapperTask.java b/redisson/src/main/java/org/redisson/mapreduce/CollectionMapperTask.java new file mode 100644 index 000000000..d74fd234c --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/CollectionMapperTask.java @@ -0,0 +1,92 @@ +/** + * 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.mapreduce; + +import org.redisson.api.RLexSortedSet; +import org.redisson.api.RList; +import org.redisson.api.RScoredSortedSet; +import org.redisson.api.RSet; +import org.redisson.api.RSetCache; +import org.redisson.api.RSortedSet; +import org.redisson.api.mapreduce.RCollectionMapper; +import org.redisson.api.mapreduce.RCollector; +import org.redisson.client.codec.Codec; +import org.redisson.misc.Injector; + +/** + * + * @author Nikita Koksharov + * + * @param input value type + * @param output key type + * @param output value type + */ +public class CollectionMapperTask extends BaseMapperTask { + + private static final long serialVersionUID = -2634049426877164580L; + + RCollectionMapper mapper; + + public CollectionMapperTask() { + } + + public CollectionMapperTask(RCollectionMapper mapper, Class objectClass, Class objectCodecClass) { + super(objectClass, objectCodecClass); + this.mapper = mapper; + } + + @Override + public void run() { + Codec codec; + try { + codec = (Codec) objectCodecClass.getConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + + Injector.inject(mapper, redisson); + + for (String objectName : objectNames) { + Iterable collection = null; + if (RSetCache.class.isAssignableFrom(objectClass)) { + collection = redisson.getSetCache(objectName, codec); + } else if (RSet.class.isAssignableFrom(objectClass)) { + collection = redisson.getSet(objectName, codec); + } else if (RSortedSet.class.isAssignableFrom(objectClass)) { + collection = redisson.getSortedSet(objectName, codec); + } else if (RScoredSortedSet.class.isAssignableFrom(objectClass)) { + collection = redisson.getScoredSortedSet(objectName, codec); + } else if (RLexSortedSet.class.isAssignableFrom(objectClass)) { + collection = (Iterable) redisson.getLexSortedSet(objectName); + } else if (RList.class.isAssignableFrom(objectClass)) { + collection = redisson.getList(objectName, codec); + } else { + throw new IllegalStateException("Unable to work with " + objectClass); + } + + RCollector collector = new Collector(codec, redisson, collectorMapName, workersAmount, timeout); + + for (VIn value : collection) { + if (Thread.currentThread().isInterrupted()) { + return; + } + + mapper.map(value, collector); + } + } + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/Collector.java b/redisson/src/main/java/org/redisson/mapreduce/Collector.java new file mode 100644 index 000000000..37f6f5836 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/Collector.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.mapreduce; + +import java.io.IOException; +import java.util.BitSet; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RListMultimap; +import org.redisson.api.RedissonClient; +import org.redisson.api.mapreduce.RCollector; +import org.redisson.client.codec.Codec; + +import net.openhft.hashing.LongHashFunction; + +/** + * + * @author Nikita Koksharov + * + * @param key + * @param value + */ +public class Collector implements RCollector { + + private RedissonClient client; + private String name; + private int parts; + private Codec codec; + private long timeout; + private BitSet expirationsBitSet = new BitSet(); + + public Collector(Codec codec, RedissonClient client, String name, int parts, long timeout) { + super(); + this.client = client; + this.name = name; + this.parts = parts; + this.codec = codec; + this.timeout = timeout; + expirationsBitSet = new BitSet(parts); + } + + @Override + public void emit(K key, V value) { + try { + byte[] encodedKey = codec.getValueEncoder().encode(key); + long hash = LongHashFunction.xx_r39().hashBytes(encodedKey); + int part = (int) Math.abs(hash % parts); + String partName = name + ":" + part; + + RListMultimap multimap = client.getListMultimap(partName, codec); + multimap.put(key, value); + if (timeout > 0 && !expirationsBitSet.get(part)) { + multimap.expire(timeout, TimeUnit.MILLISECONDS); + expirationsBitSet.set(part); + } + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/CoordinatorTask.java b/redisson/src/main/java/org/redisson/mapreduce/CoordinatorTask.java new file mode 100644 index 000000000..5dccb9a1c --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/CoordinatorTask.java @@ -0,0 +1,165 @@ +/** + * 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.mapreduce; + +import java.io.Serializable; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.redisson.api.RExecutorService; +import org.redisson.api.RFuture; +import org.redisson.api.RScheduledExecutorService; +import org.redisson.api.RedissonClient; +import org.redisson.api.annotation.RInject; +import org.redisson.api.mapreduce.RCollator; +import org.redisson.api.mapreduce.RReducer; +import org.redisson.client.codec.Codec; + +/** + * + * @author Nikita Koksharov + * + * @param output key + * @param output value + */ +public class CoordinatorTask implements Callable, Serializable { + + private static final long serialVersionUID = 7559371478909848610L; + + @RInject + protected RedissonClient redisson; + + private BaseMapperTask mapperTask; + private RCollator collator; + private RReducer reducer; + protected String objectName; + protected Class objectClass; + private Class objectCodecClass; + private String resultMapName; + private long timeout; + private long startTime; + + protected Codec codec; + + public CoordinatorTask() { + } + + public CoordinatorTask(BaseMapperTask mapperTask, RReducer reducer, + String mapName, String resultMapName, Class mapCodecClass, Class objectClass, + RCollator collator, long timeout, long startTime) { + super(); + this.mapperTask = mapperTask; + this.reducer = reducer; + this.objectName = mapName; + this.objectCodecClass = mapCodecClass; + this.objectClass = objectClass; + this.resultMapName = resultMapName; + this.collator = collator; + this.timeout = timeout; + this.startTime = startTime; + } + + @Override + public Object call() throws Exception { + long timeSpent = System.currentTimeMillis() - startTime; + if (isTimeoutExpired(timeSpent)) { + throw new MapReduceTimeoutException(); + } + + this.codec = (Codec) objectCodecClass.getConstructor().newInstance(); + + RScheduledExecutorService executor = redisson.getExecutorService(RExecutorService.MAPREDUCE_NAME); + int workersAmount = executor.countActiveWorkers(); + + UUID id = UUID.randomUUID(); + String collectorMapName = objectName + ":collector:" + id; + + mapperTask.setCollectorMapName(collectorMapName); + mapperTask.setWorkersAmount(workersAmount); + timeSpent = System.currentTimeMillis() - startTime; + if (isTimeoutExpired(timeSpent)) { + throw new MapReduceTimeoutException(); + } + if (timeout > 0) { + mapperTask.setTimeout(timeout - timeSpent); + } + + mapperTask.addObjectName(objectName); + RFuture mapperFuture = executor.submitAsync(mapperTask); + try { + if (timeout > 0 && !mapperFuture.await(timeout - timeSpent)) { + mapperFuture.cancel(true); + throw new MapReduceTimeoutException(); + } + if (timeout == 0) { + mapperFuture.await(); + } + } catch (InterruptedException e) { + mapperFuture.cancel(true); + return null; + } + + SubTasksExecutor reduceExecutor = new SubTasksExecutor(executor, workersAmount, startTime, timeout); + for (int i = 0; i < workersAmount; i++) { + String name = collectorMapName + ":" + i; + Runnable runnable = new ReducerTask(name, reducer, objectCodecClass, resultMapName, timeout - timeSpent); + reduceExecutor.submit(runnable); + } + + if (!reduceExecutor.await()) { + return null; + } + + return executeCollator(); + } + + private Object executeCollator() throws ExecutionException, Exception { + if (collator == null) { + if (timeout > 0) { + redisson.getMap(resultMapName).clearExpire(); + } + return null; + } + + Callable collatorTask = new CollatorTask(redisson, collator, resultMapName, objectCodecClass); + long timeSpent = System.currentTimeMillis() - startTime; + if (isTimeoutExpired(timeSpent)) { + throw new MapReduceTimeoutException(); + } + + if (timeout > 0) { + java.util.concurrent.Future collatorFuture = redisson.getConfig().getExecutor().submit(collatorTask); + try { + return collatorFuture.get(timeout - timeSpent, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + return null; + } catch (TimeoutException e) { + collatorFuture.cancel(true); + throw new MapReduceTimeoutException(); + } + } else { + return collatorTask.call(); + } + } + + private boolean isTimeoutExpired(long timeSpent) { + return timeSpent > timeout && timeout > 0; + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/MapReduceExecutor.java b/redisson/src/main/java/org/redisson/mapreduce/MapReduceExecutor.java new file mode 100644 index 000000000..a5d1f3aeb --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/MapReduceExecutor.java @@ -0,0 +1,166 @@ +/** + * 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.mapreduce; + +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; + +import org.redisson.api.RBatch; +import org.redisson.api.RExecutorService; +import org.redisson.api.RFuture; +import org.redisson.api.RMapAsync; +import org.redisson.api.RObject; +import org.redisson.api.RedissonClient; +import org.redisson.api.mapreduce.RCollator; +import org.redisson.api.mapreduce.RMapReduceExecutor; +import org.redisson.api.mapreduce.RReducer; +import org.redisson.client.codec.Codec; +import org.redisson.connection.ConnectionManager; +import org.redisson.misc.RPromise; +import org.redisson.misc.TransferListener; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; + +/** + * + * @author Nikita Koksharov + * + * @param mapper type + * @param input value type + * @param output key type + * @param output value type + */ +abstract class MapReduceExecutor implements RMapReduceExecutor { + + private final RedissonClient redisson; + private final RExecutorService executorService; + final String resultMapName; + + final Codec objectCodec; + final String objectName; + final Class objectClass; + + + private ConnectionManager connectionManager; + RReducer reducer; + M mapper; + long timeout; + + public MapReduceExecutor(RObject object, RedissonClient redisson, ConnectionManager connectionManager) { + this.objectName = object.getName(); + this.objectCodec = object.getCodec(); + this.objectClass = object.getClass(); + + this.redisson = redisson; + UUID id = UUID.randomUUID(); + this.resultMapName = object.getName() + ":result:" + id; + this.executorService = redisson.getExecutorService(RExecutorService.MAPREDUCE_NAME); + this.connectionManager = connectionManager; + } + + protected void check(Object task) { + if (task == null) { + throw new NullPointerException("Task is not defined"); + } + if (task.getClass().isAnonymousClass()) { + throw new IllegalArgumentException("Task can't be created using anonymous class"); + } + if (task.getClass().isMemberClass() + && !Modifier.isStatic(task.getClass().getModifiers())) { + throw new IllegalArgumentException("Task class is an inner class and it should be static"); + } + } + + @Override + public Map execute() { + return connectionManager.getCommandExecutor().get(executeAsync()); + } + + @Override + public RFuture> executeAsync() { + final RPromise> promise = connectionManager.newPromise(); + final RFuture future = executeMapperAsync(resultMapName, null); + addCancelHandling(promise, future); + future.addListener(new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + promise.tryFailure(future.cause()); + return; + } + + RBatch batch = redisson.createBatch(); + RMapAsync resultMap = batch.getMap(resultMapName, objectCodec); + resultMap.readAllMapAsync().addListener(new TransferListener>(promise)); + resultMap.deleteAsync(); + batch.executeAsync(); + } + }); + return promise; + } + + private void addCancelHandling(final RPromise promise, final RFuture future) { + promise.addListener(new FutureListener() { + @Override + public void operationComplete(Future f) throws Exception { + if (promise.isCancelled()) { + future.cancel(true); + } + } + }); + } + + @Override + public void execute(String resultMapName) { + connectionManager.getCommandExecutor().get(executeAsync(resultMapName)); + } + + @Override + public RFuture executeAsync(String resultMapName) { + return executeMapperAsync(resultMapName, null); + } + + + private RFuture executeMapperAsync(String resultMapName, RCollator collator) { + if (mapper == null) { + throw new NullPointerException("Mapper is not defined"); + } + if (reducer == null) { + throw new NullPointerException("Reducer is not defined"); + } + + Callable task = createTask(resultMapName, (RCollator) collator); + return (RFuture) executorService.submit(task); + } + + protected abstract Callable createTask(String resultMapName, RCollator collator); + + @Override + public R execute(RCollator collator) { + return connectionManager.getCommandExecutor().get(executeAsync(collator)); + } + + @Override + public RFuture executeAsync(final RCollator collator) { + check(collator); + + return executeMapperAsync(resultMapName, collator); + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/MapReduceTimeoutException.java b/redisson/src/main/java/org/redisson/mapreduce/MapReduceTimeoutException.java new file mode 100644 index 000000000..b6d0e1670 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/MapReduceTimeoutException.java @@ -0,0 +1,29 @@ +/** + * 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.mapreduce; + +import org.redisson.client.RedisException; + +/** + * + * @author Nikita Koksharov + * + */ +public class MapReduceTimeoutException extends RedisException { + + private static final long serialVersionUID = -198991995396319360L; + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/MapperTask.java b/redisson/src/main/java/org/redisson/mapreduce/MapperTask.java new file mode 100644 index 000000000..7927f90d2 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/MapperTask.java @@ -0,0 +1,80 @@ +/** + * 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.mapreduce; + +import java.util.Map.Entry; + +import org.redisson.api.RMap; +import org.redisson.api.RMapCache; +import org.redisson.api.mapreduce.RCollector; +import org.redisson.api.mapreduce.RMapper; +import org.redisson.client.codec.Codec; +import org.redisson.misc.Injector; + +/** + * + * @author Nikita Koksharov + * + * @param input key type + * @param input key type + * @param output key type + * @param output key type + */ +public class MapperTask extends BaseMapperTask { + + private static final long serialVersionUID = 2441161019495880394L; + + protected RMapper mapper; + + public MapperTask() { + } + + public MapperTask(RMapper mapper, Class objectClass, Class objectCodecClass) { + super(objectClass, objectCodecClass); + this.mapper = mapper; + } + + @Override + public void run() { + Codec codec; + try { + codec = (Codec) objectCodecClass.getConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + + Injector.inject(mapper, redisson); + RCollector collector = new Collector(codec, redisson, collectorMapName, workersAmount, timeout); + + for (String objectName : objectNames) { + RMap map = null; + if (RMapCache.class.isAssignableFrom(objectClass)) { + map = redisson.getMapCache(objectName, codec); + } else { + map = redisson.getMap(objectName, codec); + } + + for (Entry entry : map.entrySet()) { + if (Thread.currentThread().isInterrupted()) { + return; + } + + mapper.map(entry.getKey(), entry.getValue(), collector); + } + } + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/RedissonCollectionMapReduce.java b/redisson/src/main/java/org/redisson/mapreduce/RedissonCollectionMapReduce.java new file mode 100644 index 000000000..84ef92790 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/RedissonCollectionMapReduce.java @@ -0,0 +1,70 @@ +/** + * 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.mapreduce; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RObject; +import org.redisson.api.RedissonClient; +import org.redisson.api.mapreduce.RCollator; +import org.redisson.api.mapreduce.RCollectionMapReduce; +import org.redisson.api.mapreduce.RCollectionMapper; +import org.redisson.api.mapreduce.RReducer; +import org.redisson.connection.ConnectionManager; + +/** + * + * @author Nikita Koksharov + * + * @param input value type + * @param output key type + * @param output value type + */ +public class RedissonCollectionMapReduce extends MapReduceExecutor, VIn, KOut, VOut> + implements RCollectionMapReduce { + + public RedissonCollectionMapReduce(RObject object, RedissonClient redisson, ConnectionManager connectionManager) { + super(object, redisson, connectionManager); + } + + @Override + public RCollectionMapReduce timeout(long timeout, TimeUnit unit) { + this.timeout = unit.toMillis(timeout); + return this; + } + + @Override + public RCollectionMapReduce mapper(RCollectionMapper mapper) { + check(mapper); + this.mapper = mapper; + return this; + } + + @Override + public RCollectionMapReduce reducer(RReducer reducer) { + check(reducer); + this.reducer = reducer; + return this; + } + + @Override + protected Callable createTask(String resultMapName, RCollator collator) { + CollectionMapperTask mapperTask = new CollectionMapperTask(mapper, objectClass, objectCodec.getClass()); + return new CoordinatorTask(mapperTask, reducer, objectName, resultMapName, objectCodec.getClass(), objectClass, collator, timeout, System.currentTimeMillis()); + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/RedissonMapReduce.java b/redisson/src/main/java/org/redisson/mapreduce/RedissonMapReduce.java new file mode 100644 index 000000000..1af598736 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/RedissonMapReduce.java @@ -0,0 +1,71 @@ +/** + * 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.mapreduce; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RObject; +import org.redisson.api.RedissonClient; +import org.redisson.api.mapreduce.RCollator; +import org.redisson.api.mapreduce.RMapReduce; +import org.redisson.api.mapreduce.RMapper; +import org.redisson.api.mapreduce.RReducer; +import org.redisson.connection.ConnectionManager; + +/** + * + * @author Nikita Koksharov + * + * @param input key type + * @param input value type + * @param output key type + * @param output value type + */ +public class RedissonMapReduce extends MapReduceExecutor, VIn, KOut, VOut> + implements RMapReduce { + + public RedissonMapReduce(RObject object, RedissonClient redisson, ConnectionManager connectionManager) { + super(object, redisson, connectionManager); + } + + @Override + public RMapReduce timeout(long timeout, TimeUnit unit) { + this.timeout = unit.toMillis(timeout); + return this; + } + + @Override + public RMapReduce mapper(RMapper mapper) { + check(mapper); + this.mapper = mapper; + return this; + } + + @Override + public RMapReduce reducer(RReducer reducer) { + check(reducer); + this.reducer = reducer; + return this; + } + + @Override + protected Callable createTask(String resultMapName, RCollator collator) { + MapperTask mapperTask = new MapperTask(mapper, objectClass, objectCodec.getClass()); + return new CoordinatorTask(mapperTask, reducer, objectName, resultMapName, objectCodec.getClass(), objectClass, collator, timeout, System.currentTimeMillis()); + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/ReducerTask.java b/redisson/src/main/java/org/redisson/mapreduce/ReducerTask.java new file mode 100644 index 000000000..ff7ed4ffb --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/ReducerTask.java @@ -0,0 +1,88 @@ +/** + * 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.mapreduce; + +import java.io.Serializable; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RListMultimap; +import org.redisson.api.RMap; +import org.redisson.api.RedissonClient; +import org.redisson.api.annotation.RInject; +import org.redisson.api.mapreduce.RReducer; +import org.redisson.client.codec.Codec; +import org.redisson.misc.Injector; + +/** + * + * @author Nikita Koksharov + * + * @param key + * @param value + */ +public class ReducerTask implements Runnable, Serializable { + + private static final long serialVersionUID = 3556632668150314703L; + + @RInject + private RedissonClient redisson; + + private String name; + private String resultMapName; + private RReducer reducer; + private Class codecClass; + private Codec codec; + private long timeout; + + public ReducerTask() { + } + + public ReducerTask(String name, RReducer reducer, Class codecClass, String resultMapName, long timeout) { + this.name = name; + this.reducer = reducer; + this.resultMapName = resultMapName; + this.codecClass = codecClass; + this.timeout = timeout; + } + + @Override + public void run() { + try { + this.codec = (Codec) codecClass.getConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + + Injector.inject(reducer, redisson); + + RMap map = redisson.getMap(resultMapName); + RListMultimap multimap = redisson.getListMultimap(name, codec); + for (KOut key : multimap.keySet()) { + if (Thread.currentThread().isInterrupted()) { + break; + } + List values = multimap.get(key); + VOut out = reducer.reduce(key, values.iterator()); + map.put(key, out); + } + if (timeout > 0) { + map.expire(timeout, TimeUnit.MILLISECONDS); + } + multimap.delete(); + } + +} diff --git a/redisson/src/main/java/org/redisson/mapreduce/SubTasksExecutor.java b/redisson/src/main/java/org/redisson/mapreduce/SubTasksExecutor.java new file mode 100644 index 000000000..93a1d1599 --- /dev/null +++ b/redisson/src/main/java/org/redisson/mapreduce/SubTasksExecutor.java @@ -0,0 +1,115 @@ +/** + * 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.mapreduce; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RExecutorService; +import org.redisson.api.RFuture; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; + +/** + * + * @author Nikita Koksharov + * + */ +public class SubTasksExecutor { + + public static class LatchListener implements FutureListener { + + private CountDownLatch latch; + + public LatchListener() { + } + + public LatchListener(CountDownLatch latch) { + super(); + this.latch = latch; + } + + @Override + public void operationComplete(Future future) throws Exception { + latch.countDown(); + } + + } + + private final List> futures = new ArrayList>(); + private final CountDownLatch latch; + private final RExecutorService executor; + private final long startTime; + private final long timeout; + + public SubTasksExecutor(RExecutorService executor, int workersAmount, long startTime, long timeout) { + this.executor = executor; + this.latch = new CountDownLatch(workersAmount); + this.startTime = startTime; + this.timeout = timeout; + } + + public void submit(Runnable runnable) { + RFuture future = executor.submitAsync(runnable); + future.addListener(new LatchListener(latch)); + futures.add(future); + } + + private void cancel(List> futures) { + for (RFuture future : futures) { + future.cancel(true); + } + } + + private boolean isTimeoutExpired(long timeSpent) { + return timeSpent > timeout && timeout > 0; + } + + public boolean await() throws Exception { + if (Thread.currentThread().isInterrupted()) { + cancel(futures); + return false; + } + + long timeSpent = System.currentTimeMillis() - startTime; + if (isTimeoutExpired(timeSpent)) { + cancel(futures); + throw new MapReduceTimeoutException(); + } + try { + if (timeout > 0 && !latch.await(timeout - timeSpent, TimeUnit.MILLISECONDS)) { + cancel(futures); + throw new MapReduceTimeoutException(); + } + if (timeout == 0) { + latch.await(); + } + } catch (InterruptedException e) { + cancel(futures); + return false; + } + for (RFuture rFuture : futures) { + if (!rFuture.isSuccess()) { + throw (Exception) rFuture.cause(); + } + } + return true; + } + +} diff --git a/redisson/src/main/java/org/redisson/misc/Injector.java b/redisson/src/main/java/org/redisson/misc/Injector.java new file mode 100644 index 000000000..47972c8a8 --- /dev/null +++ b/redisson/src/main/java/org/redisson/misc/Injector.java @@ -0,0 +1,63 @@ +/** + * 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.misc; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.redisson.api.RedissonClient; +import org.redisson.api.annotation.RInject; + +/** + * + * @author Nikita Koksharov + * + */ +public class Injector { + + public static void inject(Object task, RedissonClient redisson) { + List allFields = new ArrayList(); + Class clazz = task.getClass(); + while (true) { + if (clazz != null) { + Field[] fields = clazz.getDeclaredFields(); + allFields.addAll(Arrays.asList(fields)); + } else { + break; + } + if (clazz.getSuperclass() != Object.class) { + clazz = clazz.getSuperclass(); + } else { + clazz = null; + } + } + + for (Field field : allFields) { + if (RedissonClient.class.isAssignableFrom(field.getType()) + && field.isAnnotationPresent(RInject.class)) { + field.setAccessible(true); + try { + field.set(task, redisson); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + } + } + +} diff --git a/redisson/src/main/java/org/redisson/pubsub/TransferListener.java b/redisson/src/main/java/org/redisson/misc/TransferListener.java similarity index 94% rename from redisson/src/main/java/org/redisson/pubsub/TransferListener.java rename to redisson/src/main/java/org/redisson/misc/TransferListener.java index 72ad48722..34fc861fc 100644 --- a/redisson/src/main/java/org/redisson/pubsub/TransferListener.java +++ b/redisson/src/main/java/org/redisson/misc/TransferListener.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.redisson.pubsub; - -import org.redisson.misc.RPromise; +package org.redisson.misc; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; diff --git a/redisson/src/main/java/org/redisson/misc/URLBuilder.java b/redisson/src/main/java/org/redisson/misc/URLBuilder.java index 4a7e0291b..9578cdf92 100644 --- a/redisson/src/main/java/org/redisson/misc/URLBuilder.java +++ b/redisson/src/main/java/org/redisson/misc/URLBuilder.java @@ -23,6 +23,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; +import java.util.concurrent.atomic.AtomicInteger; /** * @@ -32,7 +33,8 @@ import java.net.URLStreamHandlerFactory; public class URLBuilder { private static URLStreamHandlerFactory currentFactory; - + private static AtomicInteger refCounter = new AtomicInteger(); + private final static URLStreamHandlerFactory newFactory = new URLStreamHandlerFactory() { @Override public URLStreamHandler createURLStreamHandler(String protocol) { @@ -62,29 +64,44 @@ public class URLBuilder { } }; - public static synchronized void restoreURLFactory() { + private static Field getFactoryField() { + Field field; try { - Field field = URL.class.getDeclaredField("factory"); - field.setAccessible(true); - field.set(null, currentFactory); - } catch (Exception e) { - throw new IllegalStateException(e); + field = URL.class.getDeclaredField("factory"); + } catch (NoSuchFieldException e) { + try { + // used in Android + field = URL.class.getDeclaredField("streamHandlerFactory"); + } catch (Exception e1) { + throw new IllegalStateException(e); + } + } + return field; + } + + public static synchronized void restoreURLFactory() { + if (refCounter.decrementAndGet() == 0) { + try { + Field field = getFactoryField(); + field.setAccessible(true); + field.set(null, currentFactory); + currentFactory = null; + } catch (Exception e) { + throw new IllegalStateException(e); + } } } public static synchronized void replaceURLFactory() { try { - Field field = URL.class.getDeclaredField("factory"); + refCounter.incrementAndGet(); + Field field = getFactoryField(); field.setAccessible(true); - currentFactory = (URLStreamHandlerFactory) field.get(null); - if (currentFactory != null && currentFactory != newFactory) { + final URLStreamHandlerFactory temp = (URLStreamHandlerFactory) field.get(null); + if (temp != newFactory) { + currentFactory = temp; field.set(null, null); - } - - if (currentFactory != newFactory) { URL.setURLStreamHandlerFactory(newFactory); - } else { - currentFactory = null; } } catch (Exception e) { throw new IllegalStateException(e); diff --git a/redisson/src/main/java/org/redisson/pubsub/PublishSubscribe.java b/redisson/src/main/java/org/redisson/pubsub/PublishSubscribe.java index eb9a0fce1..35bed9a3b 100644 --- a/redisson/src/main/java/org/redisson/pubsub/PublishSubscribe.java +++ b/redisson/src/main/java/org/redisson/pubsub/PublishSubscribe.java @@ -27,6 +27,7 @@ import org.redisson.client.protocol.pubsub.PubSubType; import org.redisson.connection.ConnectionManager; import org.redisson.misc.PromiseDelegator; import org.redisson.misc.RPromise; +import org.redisson.misc.TransferListener; import io.netty.util.internal.PlatformDependent; @@ -97,7 +98,7 @@ abstract class PublishSubscribe> { } RedisPubSubListener listener = createListener(channelName, value); - connectionManager.subscribe(LongCodec.INSTANCE, channelName, listener, semaphore); + connectionManager.subscribe(LongCodec.INSTANCE, channelName, semaphore, listener); } }; semaphore.acquire(listener); diff --git a/redisson/src/main/java/org/redisson/reactive/RedissonListReactive.java b/redisson/src/main/java/org/redisson/reactive/RedissonListReactive.java index b9dd297b1..ef058c48e 100644 --- a/redisson/src/main/java/org/redisson/reactive/RedissonListReactive.java +++ b/redisson/src/main/java/org/redisson/reactive/RedissonListReactive.java @@ -56,12 +56,12 @@ public class RedissonListReactive extends RedissonExpirableReactive implement public RedissonListReactive(CommandReactiveExecutor commandExecutor, String name) { super(commandExecutor, name); - instance = new RedissonList(commandExecutor, name); + instance = new RedissonList(commandExecutor, name, null); } public RedissonListReactive(Codec codec, CommandReactiveExecutor commandExecutor, String name) { super(codec, commandExecutor, name); - instance = new RedissonList(codec, commandExecutor, name); + instance = new RedissonList(codec, commandExecutor, name, null); } @Override diff --git a/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java b/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java index 19a56653e..94f0cd1c4 100644 --- a/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java +++ b/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java @@ -70,12 +70,12 @@ public class RedissonMapCacheReactive extends RedissonExpirableReactive im public RedissonMapCacheReactive(UUID id, EvictionScheduler evictionScheduler, CommandReactiveExecutor commandExecutor, String name) { super(commandExecutor, name); - this.mapCache = new RedissonMapCache(id, evictionScheduler, commandExecutor, name); + this.mapCache = new RedissonMapCache(id, evictionScheduler, commandExecutor, name, null); } public RedissonMapCacheReactive(UUID id, EvictionScheduler evictionScheduler, Codec codec, CommandReactiveExecutor commandExecutor, String name) { super(codec, commandExecutor, name); - this.mapCache = new RedissonMapCache(id, codec, evictionScheduler, commandExecutor, name); + this.mapCache = new RedissonMapCache(id, codec, evictionScheduler, commandExecutor, name, null); } @Override diff --git a/redisson/src/main/java/org/redisson/reactive/RedissonMapReactive.java b/redisson/src/main/java/org/redisson/reactive/RedissonMapReactive.java index 96af7d427..e291fcab4 100644 --- a/redisson/src/main/java/org/redisson/reactive/RedissonMapReactive.java +++ b/redisson/src/main/java/org/redisson/reactive/RedissonMapReactive.java @@ -50,12 +50,12 @@ public class RedissonMapReactive extends RedissonExpirableReactive impleme public RedissonMapReactive(CommandReactiveExecutor commandExecutor, String name) { super(commandExecutor, name); - instance = new RedissonMap(null, codec, commandExecutor, name); + instance = new RedissonMap(null, codec, commandExecutor, name, null); } public RedissonMapReactive(Codec codec, CommandReactiveExecutor commandExecutor, String name) { super(codec, commandExecutor, name); - instance = new RedissonMap(null, codec, commandExecutor, name); + instance = new RedissonMap(null, codec, commandExecutor, name, null); } @Override diff --git a/redisson/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java b/redisson/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java index dcd390e1d..e21323249 100644 --- a/redisson/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java +++ b/redisson/src/main/java/org/redisson/reactive/RedissonSetCacheReactive.java @@ -59,12 +59,12 @@ public class RedissonSetCacheReactive extends RedissonExpirableReactive imple public RedissonSetCacheReactive(EvictionScheduler evictionScheduler, CommandReactiveExecutor commandExecutor, String name) { super(commandExecutor, name); - instance = new RedissonSetCache(evictionScheduler, commandExecutor, name); + instance = new RedissonSetCache(evictionScheduler, commandExecutor, name, null); } public RedissonSetCacheReactive(Codec codec, EvictionScheduler evictionScheduler, CommandReactiveExecutor commandExecutor, String name) { super(codec, commandExecutor, name); - instance = new RedissonSetCache(codec, evictionScheduler, commandExecutor, name); + instance = new RedissonSetCache(codec, evictionScheduler, commandExecutor, name, null); } @Override diff --git a/redisson/src/main/java/org/redisson/reactive/RedissonSetReactive.java b/redisson/src/main/java/org/redisson/reactive/RedissonSetReactive.java index fa5db8e9f..c3c40c33d 100644 --- a/redisson/src/main/java/org/redisson/reactive/RedissonSetReactive.java +++ b/redisson/src/main/java/org/redisson/reactive/RedissonSetReactive.java @@ -45,12 +45,12 @@ public class RedissonSetReactive extends RedissonExpirableReactive implements public RedissonSetReactive(CommandReactiveExecutor commandExecutor, String name) { super(commandExecutor, name); - instance = new RedissonSet(commandExecutor.getConnectionManager().getCodec(), commandExecutor, name); + instance = new RedissonSet(commandExecutor.getConnectionManager().getCodec(), commandExecutor, name, null); } public RedissonSetReactive(Codec codec, CommandReactiveExecutor commandExecutor, String name) { super(codec, commandExecutor, name); - instance = new RedissonSet(codec, commandExecutor, name); + instance = new RedissonSet(codec, commandExecutor, name, null); } @Override diff --git a/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java b/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java index fa7534278..4235f3fce 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java +++ b/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java @@ -26,6 +26,11 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +/** + * + * @author Nikita Koksharov + * + */ public class CacheConfigSupport { ObjectMapper jsonMapper = new ObjectMapper(); 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 374dca3cf..563c26066 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/NullValue.java +++ b/redisson/src/main/java/org/redisson/spring/cache/NullValue.java @@ -17,6 +17,11 @@ package org.redisson.spring.cache; import org.springframework.cache.Cache.ValueWrapper; +/** + * + * @author Nikita Koksharov + * + */ public class NullValue implements ValueWrapper { public static final NullValue INSTANCE = new NullValue(); diff --git a/redisson/src/main/java/org/redisson/spring/cache/RedissonCache.java b/redisson/src/main/java/org/redisson/spring/cache/RedissonCache.java index fc44f108d..bc648735d 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/RedissonCache.java +++ b/redisson/src/main/java/org/redisson/spring/cache/RedissonCache.java @@ -18,11 +18,11 @@ package org.redisson.spring.cache; import java.lang.reflect.Constructor; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import org.redisson.api.RLock; import org.redisson.api.RMap; import org.redisson.api.RMapCache; -import org.redisson.api.RedissonClient; import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; @@ -39,18 +39,18 @@ public class RedissonCache implements Cache { private CacheConfig config; - private final RedissonClient redisson; + private final AtomicLong hits = new AtomicLong(); - public RedissonCache(RedissonClient redisson, RMapCache mapCache, CacheConfig config) { + private final AtomicLong misses = new AtomicLong(); + + public RedissonCache(RMapCache mapCache, CacheConfig config) { this.mapCache = mapCache; this.map = mapCache; this.config = config; - this.redisson = redisson; } - public RedissonCache(RedissonClient redisson, RMap map) { + public RedissonCache(RMap map) { this.map = map; - this.redisson = redisson; } @Override @@ -66,12 +66,20 @@ public class RedissonCache implements Cache { @Override public ValueWrapper get(Object key) { Object value = map.get(key); + if (value == null) { + addCacheMiss(); + }else{ + addCacheHit(); + } return toValueWrapper(value); } public T get(Object key, Class type) { Object value = map.get(key); - if (value != null) { + if (value == null) { + addCacheMiss(); + }else{ + addCacheHit(); if (value.getClass().getName().equals(NullValue.class.getName())) { return null; } @@ -79,11 +87,12 @@ public class RedissonCache implements Cache { throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value); } } - return (T) value; + return (T) fromStoreValue(value); } @Override public void put(Object key, Object value) { + value = toStoreValue(value); if (mapCache != null) { mapCache.fastPut(key, value, config.getTTL(), TimeUnit.MILLISECONDS, config.getMaxIdleTime(), TimeUnit.MILLISECONDS); } else { @@ -92,6 +101,7 @@ public class RedissonCache implements Cache { } public ValueWrapper putIfAbsent(Object key, Object value) { + value = toStoreValue(value); Object prevValue; if (mapCache != null) { prevValue = mapCache.putIfAbsent(key, value, config.getTTL(), TimeUnit.MILLISECONDS, config.getMaxIdleTime(), TimeUnit.MILLISECONDS); @@ -124,6 +134,7 @@ public class RedissonCache implements Cache { public T get(Object key, Callable valueLoader) { Object value = map.get(key); if (value == null) { + addCacheMiss(); RLock lock = map.getLock(key); lock.lock(); try { @@ -141,18 +152,20 @@ public class RedissonCache implements Cache { throw new IllegalStateException(e); } } - map.put(key, value); + put(key, value); } } finally { lock.unlock(); } + }else{ + addCacheHit(); } return (T) fromStoreValue(value); } protected Object fromStoreValue(Object storeValue) { - if (storeValue == NullValue.INSTANCE) { + if (storeValue instanceof NullValue) { return null; } return storeValue; @@ -165,4 +178,26 @@ public class RedissonCache implements Cache { return userValue; } + /** The number of get requests that were satisfied by the cache. + * @return the number of hits + */ + long getCacheHits(){ + return hits.get(); + } + + /** A miss is a get request that is not satisfied. + * @return the number of misses + */ + long getCacheMisses(){ + return misses.get(); + } + + private void addCacheHit(){ + hits.incrementAndGet(); + } + + private void addCacheMiss(){ + misses.incrementAndGet(); + } + } diff --git a/redisson/src/main/java/org/redisson/spring/cache/RedissonCacheStatisticsAutoConfiguration.java b/redisson/src/main/java/org/redisson/spring/cache/RedissonCacheStatisticsAutoConfiguration.java new file mode 100644 index 000000000..9d6decf2e --- /dev/null +++ b/redisson/src/main/java/org/redisson/spring/cache/RedissonCacheStatisticsAutoConfiguration.java @@ -0,0 +1,44 @@ +/** + * 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.spring.cache; + +import org.springframework.boot.actuate.cache.CacheStatisticsProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + * @author Craig Andrews + * + * {@link EnableAutoConfiguration Auto-configuration} for {@link RedissonCacheStatisticsProvider} + * + */ +@Configuration +@AutoConfigureAfter(CacheAutoConfiguration.class) +@ConditionalOnBean(CacheManager.class) +@ConditionalOnClass(CacheStatisticsProvider.class) +public class RedissonCacheStatisticsAutoConfiguration { + @Bean + public RedissonCacheStatisticsProvider redissonCacheStatisticsProvider(){ + return new RedissonCacheStatisticsProvider(); + } +} diff --git a/redisson/src/main/java/org/redisson/spring/cache/RedissonCacheStatisticsProvider.java b/redisson/src/main/java/org/redisson/spring/cache/RedissonCacheStatisticsProvider.java new file mode 100644 index 000000000..b5364d724 --- /dev/null +++ b/redisson/src/main/java/org/redisson/spring/cache/RedissonCacheStatisticsProvider.java @@ -0,0 +1,38 @@ +/** + * 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.spring.cache; + +import org.springframework.boot.actuate.cache.CacheStatistics; +import org.springframework.boot.actuate.cache.CacheStatisticsProvider; +import org.springframework.boot.actuate.cache.DefaultCacheStatistics; +import org.springframework.cache.CacheManager; + +/** + * + * @author Craig Andrews + * + */ +public class RedissonCacheStatisticsProvider implements CacheStatisticsProvider { + + @Override + public CacheStatistics getCacheStatistics(final CacheManager cacheManager, final RedissonCache cache) { + final DefaultCacheStatistics defaultCacheStatistics = new DefaultCacheStatistics(); + defaultCacheStatistics.setSize((long) cache.getNativeCache().size()); + defaultCacheStatistics.setGetCacheCounts(cache.getCacheHits(), cache.getCacheMisses()); + return defaultCacheStatistics; + } + +} 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 1cfea6077..c03924842 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java +++ b/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java @@ -18,8 +18,8 @@ package org.redisson.spring.cache; import java.io.IOException; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.redisson.api.RMap; import org.redisson.api.RMapCache; @@ -48,7 +48,8 @@ public class RedissonSpringCacheManager implements CacheManager, ResourceLoaderA private RedissonClient redisson; - private Map configMap = new HashMap(); + private Map configMap = new ConcurrentHashMap(); + private Map instanceMap = new ConcurrentHashMap(); private String configLocation; @@ -159,34 +160,56 @@ public class RedissonSpringCacheManager implements CacheManager, ResourceLoaderA @Override public Cache getCache(String name) { + Cache cache = instanceMap.get(name); + if (cache != null) { + return cache; + } + CacheConfig config = configMap.get(name); if (config == null) { config = new CacheConfig(); configMap.put(name, config); - RMap map = createMap(name); - return new RedissonCache(redisson, map); + return createMap(name); } + if (config.getMaxIdleTime() == 0 && config.getTTL() == 0) { - RMap map = createMap(name); - return new RedissonCache(redisson, map); + return createMap(name); } - RMapCache map = createMapCache(name); - return new RedissonCache(redisson, map, config); + + return createMapCache(name, config); } - private RMap createMap(String name) { + private Cache createMap(String name) { + RMap map; if (codec != null) { - return redisson.getMap(name, codec); + map = redisson.getMap(name, codec); + } else { + map = redisson.getMap(name); + } + + Cache cache = new RedissonCache(map); + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; } - return redisson.getMap(name); + return cache; } - private RMapCache createMapCache(String name) { + private Cache createMapCache(String name, CacheConfig config) { + RMapCache map; if (codec != null) { - return redisson.getMapCache(name, codec); + map = redisson.getMapCache(name, codec); + } else { + map = redisson.getMapCache(name); + } + + Cache cache = new RedissonCache(map, config); + Cache oldCache = instanceMap.putIfAbsent(name, cache); + if (oldCache != null) { + cache = oldCache; } - return redisson.getMapCache(name); + return cache; } @Override diff --git a/redisson/src/main/resources/META-INF/spring.factories b/redisson/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..fd22323d4 --- /dev/null +++ b/redisson/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.redisson.spring.cache.RedissonCacheStatisticsAutoConfiguration diff --git a/redisson/src/main/resources/META-INF/spring.schemas b/redisson/src/main/resources/META-INF/spring.schemas index 1753d1003..274331f67 100644 --- a/redisson/src/main/resources/META-INF/spring.schemas +++ b/redisson/src/main/resources/META-INF/spring.schemas @@ -1,2 +1,2 @@ -http\://redisson.org/schema/redisson.xsd=org/redisson/spring/support/redisson-1.0.xsd -http\://redisson.org/schema/redisson-1.0.xsd=org/redisson/spring/support/redisson-1.0.xsd +http\://redisson.org/schema/redisson/redisson.xsd=org/redisson/spring/support/redisson-1.0.xsd +http\://redisson.org/schema/redisson/redisson-1.0.xsd=org/redisson/spring/support/redisson-1.0.xsd diff --git a/redisson/src/test/java/org/redisson/ClusterRunner.java b/redisson/src/test/java/org/redisson/ClusterRunner.java index b1c11228b..69af59386 100644 --- a/redisson/src/test/java/org/redisson/ClusterRunner.java +++ b/redisson/src/test/java/org/redisson/ClusterRunner.java @@ -9,6 +9,10 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.redisson.misc.BiHashMap; /** * @@ -17,9 +21,10 @@ import java.util.List; public class ClusterRunner { private final LinkedHashMap nodes = new LinkedHashMap<>(); + private final LinkedHashMap slaveMasters = new LinkedHashMap<>(); public ClusterRunner addNode(RedisRunner runner) { - nodes.put(runner, getRandomId()); + nodes.putIfAbsent(runner, getRandomId()); if (!runner.hasOption(RedisRunner.REDIS_OPTIONS.CLUSTER_ENABLED)) { runner.clusterEnabled(true); } @@ -36,11 +41,20 @@ public class ClusterRunner { return this; } - public List run() throws IOException, InterruptedException, RedisRunner.FailedToStartRedisException { - ArrayList processes = new ArrayList<>(); + public ClusterRunner addNode(RedisRunner master, RedisRunner... slaves) { + addNode(master); + for (RedisRunner slave : slaves) { + addNode(slave); + slaveMasters.put(nodes.get(slave), nodes.get(master)); + } + return this; + } + + public synchronized ClusterProcesses run() throws IOException, InterruptedException, RedisRunner.FailedToStartRedisException { + BiHashMap processes = new BiHashMap<>(); for (RedisRunner runner : nodes.keySet()) { List options = getClusterConfig(runner); - String confFile = runner.defaultDir() + File.pathSeparator + nodes.get(runner) + ".conf"; + String confFile = runner.dir() + File.separator + nodes.get(runner) + ".conf"; System.out.println("WRITING CONFIG: for " + nodes.get(runner)); try (PrintWriter printer = new PrintWriter(new FileWriter(confFile))) { options.stream().forEach((line) -> { @@ -48,15 +62,15 @@ public class ClusterRunner { System.out.println(line); }); } - processes.add(runner.clusterConfigFile(confFile).run()); + processes.put(nodes.get(runner), runner.clusterConfigFile(confFile).run()); } Thread.sleep(1000); - for (RedisRunner.RedisProcess process : processes) { + for (RedisRunner.RedisProcess process : processes.valueSet()) { if (!process.isAlive()) { throw new RedisRunner.FailedToStartRedisException(); } } - return processes; + return new ClusterProcesses(processes); } private List getClusterConfig(RedisRunner runner) { @@ -64,36 +78,79 @@ public class ClusterRunner { List nodeConfig = new ArrayList<>(); int c = 0; for (RedisRunner node : nodes.keySet()) { + String nodeId = nodes.get(node); StringBuilder sb = new StringBuilder(); String nodeAddr = node.getInitialBindAddr() + ":" + node.getPort(); - sb.append(nodes.get(node)).append(" "); + sb.append(nodeId).append(" "); sb.append(nodeAddr).append(" "); sb.append(me.equals(nodeAddr) ? "myself," - : "").append("master -").append(" "); + : ""); + boolean isMaster = !slaveMasters.containsKey(nodeId); + if (isMaster) { + sb.append("master -"); + } else { + sb.append("slave ").append(slaveMasters.get(nodeId)); + } + sb.append(" "); sb.append("0").append(" "); sb.append(me.equals(nodeAddr) ? "0" : "1").append(" "); sb.append(c + 1).append(" "); sb.append("connected "); - sb.append(getSlots(c, nodes.size())); - c++; + if (isMaster) { + sb.append(getSlots(c, nodes.size() - slaveMasters.size())); + c++; + } nodeConfig.add(sb.toString()); } nodeConfig.add("vars currentEpoch 0 lastVoteEpoch 0"); return nodeConfig; } - private String getSlots(int index, int groupNum) { + private static String getSlots(int index, int groupNum) { final double t = 16383; int start = index == 0 ? 0 : (int) (t / groupNum * index); int end = index == groupNum - 1 ? (int) t : (int) (t / groupNum * (index + 1)) - 1; return start + "-" + end; } - private String getRandomId() { + private static String getRandomId() { final SecureRandom r = new SecureRandom(); return new BigInteger(160, r).toString(16); } + + public static class ClusterProcesses { + private final BiHashMap processes; + + private ClusterProcesses(BiHashMap processes) { + this.processes = processes; + } + + public RedisRunner.RedisProcess getProcess(String nodeId) { + return processes.get(nodeId); + } + + public String getNodeId(RedisRunner.RedisProcess process) { + return processes.reverseGet(process); + } + + public Set getNodes() { + return processes.valueSet(); + } + + public Set getNodeIds() { + return processes.keySet(); + } + + public synchronized Map shutdown() { + return processes + .entrySet() + .stream() + .collect(Collectors.toMap( + e -> e.getKey(), + e -> e.getValue().stop())); + } + } } diff --git a/redisson/src/test/java/org/redisson/RedisRunner.java b/redisson/src/test/java/org/redisson/RedisRunner.java index e0990ffae..6f9190f8d 100644 --- a/redisson/src/test/java/org/redisson/RedisRunner.java +++ b/redisson/src/test/java/org/redisson/RedisRunner.java @@ -191,6 +191,7 @@ public class RedisRunner { protected static RedisRunner.RedisProcess defaultRedisInstance; private static int defaultRedisInstanceExitCode; + private String path = ""; private String defaultDir = Paths.get("").toString(); private boolean nosave = false; private boolean randomDir = false; @@ -271,7 +272,7 @@ public class RedisRunner { public RedisProcess runAndCheck() throws IOException, InterruptedException, FailedToStartRedisException { List args = new ArrayList(options.values()); if (sentinelFile != null && sentinelFile.length() > 0) { - String confFile = defaultDir + File.pathSeparator + sentinelFile; + String confFile = defaultDir + File.separator + sentinelFile; try (PrintWriter printer = new PrintWriter(new FileWriter(confFile))) { args.stream().forEach((arg) -> { if (arg.contains("--")) { @@ -289,10 +290,7 @@ public class RedisRunner { throw new FailedToStartRedisException(); } Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - rp.stop(); - } catch (InterruptedException ex) { - } + rp.stop(); })); return rp; } @@ -459,6 +457,7 @@ public class RedisRunner { public RedisRunner dir(String dir) { if (!randomDir) { addConfigOption(REDIS_OPTIONS.DIR, dir); + this.path = dir; } return this; } @@ -824,6 +823,10 @@ public class RedisRunner { public String defaultDir() { return this.defaultDir; } + + public String dir() { + return isRandomDir() ? defaultDir() : this.path; + } public String getInitialBindAddr() { return bindAddr.size() > 0 ? bindAddr.get(0) : "localhost"; @@ -839,7 +842,7 @@ public class RedisRunner { } public boolean deleteSentinelFile() { - File f = new File(defaultDir + File.pathSeparator + sentinelFile); + File f = new File(defaultDir + File.separator + sentinelFile); if (f.exists()) { System.out.println("REDIS RUNNER: Deleting sentinel config file " + f.getAbsolutePath()); return f.delete(); @@ -849,7 +852,7 @@ public class RedisRunner { public boolean deleteClusterFile() { File f = new File(clusterFile); - if (f.exists()) { + if (f.exists() && isRandomDir()) { System.out.println("REDIS RUNNER: Deleting cluster config file " + f.getAbsolutePath()); return f.delete(); } @@ -857,7 +860,7 @@ public class RedisRunner { } private void makeRandomDefaultDir() { - File f = new File(RedissonRuntimeEnvironment.tempDir + File.pathSeparator + UUID.randomUUID()); + File f = new File(RedissonRuntimeEnvironment.tempDir + File.separator + UUID.randomUUID()); if (f.exists()) { makeRandomDefaultDir(); } else { @@ -878,17 +881,36 @@ public class RedisRunner { this.runner = runner; } - public int stop() throws InterruptedException { + public int stop() { if (runner.isNosave() && !runner.isRandomDir()) { RedisClient c = createDefaultRedisClientInstance(); RedisConnection connection = c.connect(); - connection.async(new RedisStrictCommand("SHUTDOWN", "NOSAVE", new VoidReplayConvertor())) - .await(3, TimeUnit.SECONDS); + try { + connection.async(new RedisStrictCommand("SHUTDOWN", "NOSAVE", new VoidReplayConvertor())) + .await(3, TimeUnit.SECONDS); + } catch (InterruptedException interruptedException) { + //shutdown via command failed, lets wait and kill it later. + } c.shutdown(); connection.closeAsync().syncUninterruptibly(); } - redisProcess.destroy(); - int exitCode = redisProcess.isAlive() ? redisProcess.waitFor() : redisProcess.exitValue(); + Process p = redisProcess; + p.destroy(); + boolean normalTermination = false; + try { + normalTermination = p.waitFor(5, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + //OK lets hurry up by force kill; + } + if (!normalTermination) { + p = p.destroyForcibly(); + } + cleanup(); + int exitCode = p.exitValue(); + return exitCode == 1 && RedissonRuntimeEnvironment.isWindows ? 0 : exitCode; + } + + private void cleanup() { if (runner.isSentinel()) { runner.deleteSentinelFile(); } @@ -898,9 +920,8 @@ public class RedisRunner { if (runner.isRandomDir()) { runner.deleteDBfileDir(); } - return exitCode == 1 && RedissonRuntimeEnvironment.isWindows ? 0 : exitCode; } - + public Process getRedisProcess() { return redisProcess; } diff --git a/redisson/src/test/java/org/redisson/RedissonBucketTest.java b/redisson/src/test/java/org/redisson/RedissonBucketTest.java index 161c8851f..c72c91e82 100755 --- a/redisson/src/test/java/org/redisson/RedissonBucketTest.java +++ b/redisson/src/test/java/org/redisson/RedissonBucketTest.java @@ -80,6 +80,16 @@ public class RedissonBucketTest extends BaseTest { Assert.assertNull(bucket.get()); } + @Test + public void testTouch() { + RBucket bucket = redisson.getBucket("test"); + bucket.set("someValue"); + assertThat(bucket.touch()).isTrue(); + + RBucket bucket2 = redisson.getBucket("test2"); + assertThat(bucket2.touch()).isFalse(); + } + @Test public void testRenamenx() { RBucket bucket = redisson.getBucket("test"); diff --git a/redisson/src/test/java/org/redisson/RedissonCollectionMapReduceTest.java b/redisson/src/test/java/org/redisson/RedissonCollectionMapReduceTest.java new file mode 100644 index 000000000..0014cf28d --- /dev/null +++ b/redisson/src/test/java/org/redisson/RedissonCollectionMapReduceTest.java @@ -0,0 +1,229 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.redisson.api.RExecutorService; +import org.redisson.api.RList; +import org.redisson.api.RMap; +import org.redisson.api.RQueue; +import org.redisson.api.RedissonClient; +import org.redisson.api.annotation.RInject; +import org.redisson.api.mapreduce.RCollator; +import org.redisson.api.mapreduce.RCollectionMapReduce; +import org.redisson.api.mapreduce.RCollectionMapper; +import org.redisson.api.mapreduce.RCollector; +import org.redisson.api.mapreduce.RReducer; + +@RunWith(Parameterized.class) +public class RedissonCollectionMapReduceTest extends BaseTest { + + public static class WordMapper implements RCollectionMapper { + + @Override + public void map(String value, RCollector collector) { + String[] words = value.split("[^a-zA-Z]"); + for (String word : words) { + collector.emit(word, 1); + } + } + + } + + public static class WordReducer implements RReducer { + + @Override + public Integer reduce(String reducedKey, Iterator iter) { + int sum = 0; + while (iter.hasNext()) { + Integer i = (Integer) iter.next(); + sum += i; + } + return sum; + } + + } + + public static class WordCollator implements RCollator { + + @Override + public Integer collate(Map resultMap) { + int result = 0; + for (Integer count : resultMap.values()) { + result += count; + } + return result; + } + + } + + @Parameterized.Parameters(name = "{index} - {0}") + public static Iterable mapClasses() { + return Arrays.asList(new Object[][]{ + {RList.class}, {RQueue.class} + }); + } + + @Parameterized.Parameter(0) + public Class mapClass; + + + @Before + public void beforeTest() { + redisson.getExecutorService(RExecutorService.MAPREDUCE_NAME).registerWorkers(3); + } + + @Test + public void test() { + RList list = getCollection(); + + list.add("Alice was beginning to get very tired"); + list.add("of sitting by her sister on the bank and"); + list.add("of having nothing to do once or twice she"); + list.add("had peeped into the book her sister was reading"); + list.add("but it had no pictures or conversations in it"); + list.add("and what is the use of a book"); + list.add("thought Alice without pictures or conversation"); + + Map result = new HashMap<>(); + result.put("to", 2); + result.put("Alice", 2); + result.put("get", 1); + result.put("beginning", 1); + result.put("sitting", 1); + result.put("do", 1); + result.put("by", 1); + result.put("or", 3); + result.put("into", 1); + result.put("sister", 2); + result.put("on", 1); + result.put("a", 1); + result.put("without", 1); + result.put("and", 2); + result.put("once", 1); + result.put("twice", 1); + result.put("she", 1); + result.put("had", 2); + result.put("reading", 1); + result.put("but", 1); + result.put("it", 2); + result.put("no", 1); + result.put("in", 1); + result.put("what", 1); + result.put("use", 1); + result.put("thought", 1); + result.put("conversation", 1); + result.put("was", 2); + result.put("very", 1); + result.put("tired", 1); + result.put("of", 3); + result.put("her", 2); + result.put("the", 3); + result.put("bank", 1); + result.put("having", 1); + result.put("nothing", 1); + result.put("peeped", 1); + result.put("book", 2); + result.put("pictures", 2); + result.put("conversations", 1); + result.put("is", 1); + + RCollectionMapReduce mapReduce = list.mapReduce().mapper(new WordMapper()).reducer(new WordReducer()); + assertThat(mapReduce.execute()).isEqualTo(result); + Integer count = mapReduce.execute(new WordCollator()); + assertThat(count).isEqualTo(57); + + mapReduce.execute("resultMap"); + RMap resultMap = redisson.getMap("resultMap"); + assertThat(resultMap).isEqualTo(result); + resultMap.delete(); + } + + public static class WordMapperInject implements RCollectionMapper { + + @RInject + private RedissonClient redisson; + + @Override + public void map(String value, RCollector collector) { + redisson.getAtomicLong("test").incrementAndGet(); + + String[] words = value.split("[^a-zA-Z]"); + for (String word : words) { + collector.emit(word, 1); + } + } + + } + + public static class WordReducerInject implements RReducer { + + @RInject + private RedissonClient redisson; + + @Override + public Integer reduce(String reducedKey, Iterator iter) { + redisson.getAtomicLong("test").incrementAndGet(); + + int sum = 0; + while (iter.hasNext()) { + Integer i = (Integer) iter.next(); + sum += i; + } + return sum; + } + + } + + public static class WordCollatorInject implements RCollator { + + @RInject + private RedissonClient redisson; + + @Override + public Integer collate(Map resultMap) { + redisson.getAtomicLong("test").incrementAndGet(); + + int result = 0; + for (Integer count : resultMap.values()) { + result += count; + } + return result; + } + + } + + @Test + public void testInjector() { + RList list = getCollection(); + + list.add("Alice was beginning to get very tired"); + + RCollectionMapReduce mapReduce = list.mapReduce().mapper(new WordMapperInject()).reducer(new WordReducerInject()); + + mapReduce.execute(); + assertThat(redisson.getAtomicLong("test").get()).isEqualTo(8); + + mapReduce.execute(new WordCollatorInject()); + assertThat(redisson.getAtomicLong("test").get()).isEqualTo(16 + 1); + } + + private RList getCollection() { + RList list = null; + if (RList.class.isAssignableFrom(mapClass)) { + list = redisson.getList("list"); + } else if (RQueue.class.isAssignableFrom(mapClass)) { + list = (RList) redisson.getQueue("queue"); + } + return list; + } + +} diff --git a/redisson/src/test/java/org/redisson/RedissonDequeTest.java b/redisson/src/test/java/org/redisson/RedissonDequeTest.java index b070e90a6..3aa5b04c6 100644 --- a/redisson/src/test/java/org/redisson/RedissonDequeTest.java +++ b/redisson/src/test/java/org/redisson/RedissonDequeTest.java @@ -84,7 +84,7 @@ public class RedissonDequeTest extends BaseTest { queue2.addFirst(5); queue2.addFirst(4); - queue1.pollLastAndOfferFirstTo(queue2); + queue1.pollLastAndOfferFirstTo(queue2.getName()); assertThat(queue2).containsExactly(3, 4, 5, 6); } diff --git a/redisson/src/test/java/org/redisson/RedissonKeysTest.java b/redisson/src/test/java/org/redisson/RedissonKeysTest.java index 5127d732a..c19d167c5 100644 --- a/redisson/src/test/java/org/redisson/RedissonKeysTest.java +++ b/redisson/src/test/java/org/redisson/RedissonKeysTest.java @@ -15,15 +15,27 @@ import org.redisson.api.RType; public class RedissonKeysTest extends BaseTest { + @Test + public void testTouch() { + redisson.getSet("test").add("1"); + redisson.getSet("test10").add("1"); + + assertThat(redisson.getKeys().touch("test")).isEqualTo(1); + assertThat(redisson.getKeys().touch("test", "test2")).isEqualTo(1); + assertThat(redisson.getKeys().touch("test3", "test2")).isEqualTo(0); + assertThat(redisson.getKeys().touch("test3", "test10", "test")).isEqualTo(2); + } + + @Test public void testExists() { redisson.getSet("test").add("1"); redisson.getSet("test10").add("1"); - assertThat(redisson.getKeys().isExists("test")).isEqualTo(1); - assertThat(redisson.getKeys().isExists("test", "test2")).isEqualTo(1); - assertThat(redisson.getKeys().isExists("test3", "test2")).isEqualTo(0); - assertThat(redisson.getKeys().isExists("test3", "test10", "test")).isEqualTo(2); + assertThat(redisson.getKeys().countExists("test")).isEqualTo(1); + assertThat(redisson.getKeys().countExists("test", "test2")).isEqualTo(1); + assertThat(redisson.getKeys().countExists("test3", "test2")).isEqualTo(0); + assertThat(redisson.getKeys().countExists("test3", "test10", "test")).isEqualTo(2); } @Test diff --git a/redisson/src/test/java/org/redisson/RedissonListMultimapTest.java b/redisson/src/test/java/org/redisson/RedissonListMultimapTest.java index c7613d636..979beca71 100644 --- a/redisson/src/test/java/org/redisson/RedissonListMultimapTest.java +++ b/redisson/src/test/java/org/redisson/RedissonListMultimapTest.java @@ -115,6 +115,16 @@ public class RedissonListMultimapTest extends BaseTest { } } + + @Test + public void testReadAllKeySet() { + RListMultimap map = redisson.getListMultimap("test1"); + map.put("1", "4"); + map.put("2", "5"); + map.put("3", "6"); + + assertThat(map.readAllKeySet()).containsExactly("1", "2", "3"); + } @Test public void testSize() { diff --git a/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java b/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java index 9cbd71abe..de663a22c 100644 --- a/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java +++ b/redisson/src/test/java/org/redisson/RedissonLocalCachedMapTest.java @@ -6,7 +6,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -18,10 +17,10 @@ import org.redisson.RedissonMapTest.SimpleKey; import org.redisson.RedissonMapTest.SimpleValue; import org.redisson.api.LocalCachedMapOptions; import org.redisson.api.LocalCachedMapOptions.EvictionPolicy; -import org.redisson.cache.Cache; +import org.redisson.api.LocalCachedMapOptions.InvalidationPolicy; import org.redisson.api.RLocalCachedMap; import org.redisson.api.RMap; -import org.redisson.api.RedissonClient; +import org.redisson.cache.Cache; import mockit.Deencapsulation; @@ -62,7 +61,7 @@ public class RedissonLocalCachedMapTest extends BaseTest { expectedValuesSet.add(1); expectedValuesSet.add(2); expectedValuesSet.add(3); - HashSet actualValuesSet = new HashSet<>(m.readAllValues()); + Set actualValuesSet = new HashSet<>(m.readAllValues()); Assert.assertEquals(expectedValuesSet, actualValuesSet); Map expectedMap = new HashMap<>(); expectedMap.put("a", 1); @@ -91,7 +90,10 @@ public class RedissonLocalCachedMapTest extends BaseTest { @Test public void testInvalidationOnClear() throws InterruptedException { - LocalCachedMapOptions options = LocalCachedMapOptions.defaults().evictionPolicy(EvictionPolicy.LFU).cacheSize(5).invalidateEntryOnChange(true); + LocalCachedMapOptions options = LocalCachedMapOptions.defaults() + .evictionPolicy(EvictionPolicy.LFU) + .cacheSize(5); + RLocalCachedMap map1 = redisson.getLocalCachedMap("test", options); Cache cache1 = Deencapsulation.getField(map1, "cache"); @@ -125,7 +127,10 @@ public class RedissonLocalCachedMapTest extends BaseTest { @Test public void testInvalidationOnUpdate() throws InterruptedException { - LocalCachedMapOptions options = LocalCachedMapOptions.defaults().evictionPolicy(EvictionPolicy.LFU).cacheSize(5).invalidateEntryOnChange(true); + LocalCachedMapOptions options = LocalCachedMapOptions.defaults() + .evictionPolicy(EvictionPolicy.LFU) + .cacheSize(5); + RLocalCachedMap map1 = redisson.getLocalCachedMap("test", options); Cache cache1 = Deencapsulation.getField(map1, "cache"); @@ -151,7 +156,11 @@ public class RedissonLocalCachedMapTest extends BaseTest { @Test public void testNoInvalidationOnUpdate() throws InterruptedException { - LocalCachedMapOptions options = LocalCachedMapOptions.defaults().evictionPolicy(EvictionPolicy.LFU).cacheSize(5).invalidateEntryOnChange(false); + LocalCachedMapOptions options = LocalCachedMapOptions.defaults() + .evictionPolicy(EvictionPolicy.LFU) + .cacheSize(5) + .invalidationPolicy(InvalidationPolicy.NONE); + RLocalCachedMap map1 = redisson.getLocalCachedMap("test", options); Cache cache1 = Deencapsulation.getField(map1, "cache"); @@ -177,7 +186,11 @@ public class RedissonLocalCachedMapTest extends BaseTest { @Test public void testNoInvalidationOnRemove() throws InterruptedException { - LocalCachedMapOptions options = LocalCachedMapOptions.defaults().evictionPolicy(EvictionPolicy.LFU).cacheSize(5).invalidateEntryOnChange(false); + LocalCachedMapOptions options = LocalCachedMapOptions.defaults() + .evictionPolicy(EvictionPolicy.LFU) + .cacheSize(5) + .invalidationPolicy(InvalidationPolicy.NONE); + RLocalCachedMap map1 = redisson.getLocalCachedMap("test", options); Cache cache1 = Deencapsulation.getField(map1, "cache"); @@ -200,10 +213,10 @@ public class RedissonLocalCachedMapTest extends BaseTest { assertThat(cache1.size()).isEqualTo(1); assertThat(cache2.size()).isEqualTo(1); } - + @Test public void testInvalidationOnRemove() throws InterruptedException { - LocalCachedMapOptions options = LocalCachedMapOptions.defaults().evictionPolicy(EvictionPolicy.LFU).cacheSize(5).invalidateEntryOnChange(true); + LocalCachedMapOptions options = LocalCachedMapOptions.defaults().evictionPolicy(EvictionPolicy.LFU).cacheSize(5); RLocalCachedMap map1 = redisson.getLocalCachedMap("test", options); Cache cache1 = Deencapsulation.getField(map1, "cache"); @@ -570,6 +583,17 @@ public class RedissonLocalCachedMapTest extends BaseTest { assertThat(map.fastRemove(2)).isEqualTo(0); assertThat(map.size()).isEqualTo(1); } + + @Test + public void testFastRemoveEmpty() throws InterruptedException, ExecutionException { + LocalCachedMapOptions options = LocalCachedMapOptions.defaults() + .evictionPolicy(EvictionPolicy.NONE) + .cacheSize(3) + .invalidationPolicy(InvalidationPolicy.NONE); + RLocalCachedMap map = redisson.getLocalCachedMap("test", options); + assertThat(map.fastRemove("test")).isZero(); + } + @Test public void testFastPut() { diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java index 3c3d95904..541b893f8 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -20,6 +20,8 @@ import org.junit.Test; import org.redisson.api.RFuture; import org.redisson.api.RMap; import org.redisson.api.RMapCache; +import org.redisson.client.codec.LongCodec; +import org.redisson.client.codec.StringCodec; import org.redisson.codec.JsonJacksonCodec; import org.redisson.codec.MsgPackJacksonCodec; @@ -557,10 +559,15 @@ public class RedissonMapCacheTest extends BaseTest { map.putIfAbsent(new SimpleKey("4"), new SimpleValue("4"), 1, TimeUnit.SECONDS); Assert.assertEquals(new SimpleValue("4"), map.get(new SimpleKey("4"))); - + Thread.sleep(1000); Assert.assertNull(map.get(new SimpleKey("4"))); + + // this should be passed + map.putIfAbsent(new SimpleKey("4"), new SimpleValue("4"), 1, TimeUnit.SECONDS); + Assert.assertEquals(new SimpleValue("4"), map.get(new SimpleKey("4"))); + SimpleKey key1 = new SimpleKey("2"); SimpleValue value1 = new SimpleValue("4"); @@ -838,4 +845,53 @@ public class RedissonMapCacheTest extends BaseTest { this.testField = testField; } } + + @Test + public void testAddAndGet() { + RMapCache mapCache = redisson.getMapCache("test_put_if_absent", StringCodec.INSTANCE); + mapCache.putIfAbsent("4", 0L, 10000L, TimeUnit.SECONDS); + mapCache.addAndGet("4", 1L); + mapCache.putIfAbsent("4", 0L); + Assert.assertEquals("1", mapCache.get("4")); + mapCache = redisson.getMapCache("test_put_if_absent_1", StringCodec.INSTANCE); + mapCache.putIfAbsent("4", 0L); + mapCache.addAndGet("4", 1L); + mapCache.putIfAbsent("4", 0L); + Assert.assertEquals("1", mapCache.get("4")); + RMap map = redisson.getMap("test_put_if_absent_2", StringCodec.INSTANCE); + map.putIfAbsent("4", 0L); + map.addAndGet("4", 1L); + map.putIfAbsent("4", 0L); + Assert.assertEquals("1", map.get("4")); + RMapCache mapCache1 = redisson.getMapCache("test_put_if_absent_3"); + mapCache1.putIfAbsent("4", 1.23, 10000L, TimeUnit.SECONDS); + mapCache1.addAndGet("4", 1D); + Assert.assertEquals(2.23, mapCache1.get("4")); + } + + + @Test + public void testFastPutIfAbsentWithTTL() throws Exception { + RMapCache map = redisson.getMapCache("simpleTTL"); + SimpleKey key = new SimpleKey("1"); + SimpleValue value = new SimpleValue("2"); + map.fastPutIfAbsent(key, value, 1, TimeUnit.SECONDS); + assertThat(map.fastPutIfAbsent(key, new SimpleValue("3"), 1, TimeUnit.SECONDS)).isFalse(); + assertThat(map.get(key)).isEqualTo(value); + + Thread.sleep(1100); + + assertThat(map.fastPutIfAbsent(key, new SimpleValue("3"), 1, TimeUnit.SECONDS)).isTrue(); + assertThat(map.get(key)).isEqualTo(new SimpleValue("3")); + + assertThat(map.fastPutIfAbsent(key, new SimpleValue("4"), 1, TimeUnit.SECONDS)).isFalse(); + assertThat(map.get(key)).isEqualTo(new SimpleValue("3")); + + Thread.sleep(1100); + assertThat(map.fastPutIfAbsent(key, new SimpleValue("4"), 1, TimeUnit.SECONDS, 500, TimeUnit.MILLISECONDS)).isTrue(); + + Thread.sleep(550); + assertThat(map.fastPutIfAbsent(key, new SimpleValue("5"), 1, TimeUnit.SECONDS, 500, TimeUnit.MILLISECONDS)).isTrue(); + + } } diff --git a/redisson/src/test/java/org/redisson/RedissonMapReduceTest.java b/redisson/src/test/java/org/redisson/RedissonMapReduceTest.java new file mode 100644 index 000000000..80da71518 --- /dev/null +++ b/redisson/src/test/java/org/redisson/RedissonMapReduceTest.java @@ -0,0 +1,260 @@ +package org.redisson; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.redisson.api.RExecutorService; +import org.redisson.api.RFuture; +import org.redisson.api.RMap; +import org.redisson.api.RMapCache; +import org.redisson.api.RedissonClient; +import org.redisson.api.annotation.RInject; +import org.redisson.api.mapreduce.RCollator; +import org.redisson.api.mapreduce.RCollector; +import org.redisson.api.mapreduce.RMapReduce; +import org.redisson.api.mapreduce.RMapper; +import org.redisson.api.mapreduce.RReducer; +import org.redisson.mapreduce.MapReduceTimeoutException; + +@RunWith(Parameterized.class) +public class RedissonMapReduceTest extends BaseTest { + + public static class WordMapper implements RMapper { + + @Override + public void map(String key, String value, RCollector collector) { + String[] words = value.split("[^a-zA-Z]"); + for (String word : words) { + collector.emit(word, 1); + } + } + + } + + public static class WordReducer implements RReducer { + + @Override + public Integer reduce(String reducedKey, Iterator iter) { + int sum = 0; + while (iter.hasNext()) { + Integer i = (Integer) iter.next(); + sum += i; + } + return sum; + } + + } + + public static class WordCollator implements RCollator { + + @Override + public Integer collate(Map resultMap) { + int result = 0; + for (Integer count : resultMap.values()) { + result += count; + } + return result; + } + + } + + @Parameterized.Parameters(name = "{index} - {0}") + public static Iterable mapClasses() { + return Arrays.asList(new Object[][]{ + {RMap.class}, {RMapCache.class} + }); + } + + @Parameterized.Parameter(0) + public Class mapClass; + + + @Before + public void beforeTest() { + redisson.getExecutorService(RExecutorService.MAPREDUCE_NAME).registerWorkers(3); + } + + @Test + public void testCancel() throws InterruptedException, ExecutionException { + RMap map = getMap(); + for (int i = 0; i < 100000; i++) { + map.put("" + i, "ab cd fjks"); + } + + RMapReduce mapReduce = map.mapReduce().mapper(new WordMapper()).reducer(new WordReducer()); + RFuture> future = mapReduce.executeAsync(); + Thread.sleep(100); + future.cancel(true); + } + + @Test(expected = MapReduceTimeoutException.class) + public void testTimeout() { + RMap map = getMap(); + for (int i = 0; i < 100000; i++) { + map.put("" + i, "ab cd fjks"); + } + + RMapReduce mapReduce = map.mapReduce() + .mapper(new WordMapper()) + .reducer(new WordReducer()) + .timeout(1, TimeUnit.SECONDS); + + mapReduce.execute(); + } + + @Test + public void test() { + RMap map = getMap(); + + map.put("1", "Alice was beginning to get very tired"); + map.put("2", "of sitting by her sister on the bank and"); + map.put("3", "of having nothing to do once or twice she"); + map.put("4", "had peeped into the book her sister was reading"); + map.put("5", "but it had no pictures or conversations in it"); + map.put("6", "and what is the use of a book"); + map.put("7", "thought Alice without pictures or conversation"); + + Map result = new HashMap<>(); + result.put("to", 2); + result.put("Alice", 2); + result.put("get", 1); + result.put("beginning", 1); + result.put("sitting", 1); + result.put("do", 1); + result.put("by", 1); + result.put("or", 3); + result.put("into", 1); + result.put("sister", 2); + result.put("on", 1); + result.put("a", 1); + result.put("without", 1); + result.put("and", 2); + result.put("once", 1); + result.put("twice", 1); + result.put("she", 1); + result.put("had", 2); + result.put("reading", 1); + result.put("but", 1); + result.put("it", 2); + result.put("no", 1); + result.put("in", 1); + result.put("what", 1); + result.put("use", 1); + result.put("thought", 1); + result.put("conversation", 1); + result.put("was", 2); + result.put("very", 1); + result.put("tired", 1); + result.put("of", 3); + result.put("her", 2); + result.put("the", 3); + result.put("bank", 1); + result.put("having", 1); + result.put("nothing", 1); + result.put("peeped", 1); + result.put("book", 2); + result.put("pictures", 2); + result.put("conversations", 1); + result.put("is", 1); + + RMapReduce mapReduce = map.mapReduce().mapper(new WordMapper()).reducer(new WordReducer()); + assertThat(mapReduce.execute()).isEqualTo(result); + Integer count = mapReduce.execute(new WordCollator()); + assertThat(count).isEqualTo(57); + + mapReduce.execute("resultMap"); + RMap resultMap = redisson.getMap("resultMap"); + assertThat(resultMap).isEqualTo(result); + resultMap.delete(); + } + + private RMap getMap() { + RMap map = null; + if (RMapCache.class.isAssignableFrom(mapClass)) { + map = redisson.getMapCache("map"); + } else { + map = redisson.getMap("map"); + } + return map; + } + + public static class WordMapperInject implements RMapper { + + @RInject + private RedissonClient redisson; + + @Override + public void map(String key, String value, RCollector collector) { + redisson.getAtomicLong("test").incrementAndGet(); + + String[] words = value.split("[^a-zA-Z]"); + for (String word : words) { + collector.emit(word, 1); + } + } + + } + + public static class WordReducerInject implements RReducer { + + @RInject + private RedissonClient redisson; + + @Override + public Integer reduce(String reducedKey, Iterator iter) { + redisson.getAtomicLong("test").incrementAndGet(); + + int sum = 0; + while (iter.hasNext()) { + Integer i = (Integer) iter.next(); + sum += i; + } + return sum; + } + + } + + public static class WordCollatorInject implements RCollator { + + @RInject + private RedissonClient redisson; + + @Override + public Integer collate(Map resultMap) { + redisson.getAtomicLong("test").incrementAndGet(); + + int result = 0; + for (Integer count : resultMap.values()) { + result += count; + } + return result; + } + + } + + @Test + public void testInjector() { + RMap map = getMap(); + + map.put("1", "Alice was beginning to get very tired"); + + RMapReduce mapReduce = map.mapReduce().mapper(new WordMapperInject()).reducer(new WordReducerInject()); + + mapReduce.execute(); + assertThat(redisson.getAtomicLong("test").get()).isEqualTo(8); + + mapReduce.execute(new WordCollatorInject()); + assertThat(redisson.getAtomicLong("test").get()).isEqualTo(16 + 1); + } + +} diff --git a/redisson/src/test/java/org/redisson/RedissonTopicTest.java b/redisson/src/test/java/org/redisson/RedissonTopicTest.java index 3270fa6bf..5eacbddd9 100644 --- a/redisson/src/test/java/org/redisson/RedissonTopicTest.java +++ b/redisson/src/test/java/org/redisson/RedissonTopicTest.java @@ -6,15 +6,15 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.AfterClass; @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.redisson.ClusterRunner.ClusterProcesses; import org.redisson.RedisRunner.RedisProcess; import org.redisson.api.RSet; import org.redisson.api.RTopic; @@ -463,7 +464,7 @@ public class RedissonTopicTest { } @Test - public void testReattach() throws InterruptedException, IOException, ExecutionException, TimeoutException { + public void testReattach() throws Exception { RedisProcess runner = new RedisRunner() .nosave() .randomDir() @@ -475,14 +476,24 @@ public class RedissonTopicTest { RedissonClient redisson = Redisson.create(config); final AtomicBoolean executed = new AtomicBoolean(); + final AtomicInteger subscriptions = new AtomicInteger(); RTopic topic = redisson.getTopic("topic"); + topic.addListener(new StatusListener() { + + @Override + public void onUnsubscribe(String channel) { + } + + @Override + public void onSubscribe(String channel) { + subscriptions.incrementAndGet(); + } + }); topic.addListener(new MessageListener() { @Override public void onMessage(String channel, Integer msg) { - if (msg == 1) { - executed.set(true); - } + executed.set(true); } }); @@ -498,10 +509,77 @@ public class RedissonTopicTest { redisson.getTopic("topic").publish(1); - await().atMost(5, TimeUnit.SECONDS).untilTrue(executed); + await().atMost(2, TimeUnit.SECONDS).untilTrue(executed); + await().atMost(2, TimeUnit.SECONDS).until(() -> subscriptions.get() == 2); redisson.shutdown(); runner.stop(); } + + @Test + public void testReattachInCluster() throws Exception { + RedisRunner master1 = new RedisRunner().randomPort().randomDir().nosave(); + RedisRunner master2 = new RedisRunner().randomPort().randomDir().nosave(); + RedisRunner master3 = new RedisRunner().randomPort().randomDir().nosave(); + RedisRunner slave1 = new RedisRunner().randomPort().randomDir().nosave(); + RedisRunner slave2 = new RedisRunner().randomPort().randomDir().nosave(); + RedisRunner slave3 = new RedisRunner().randomPort().randomDir().nosave(); + + + ClusterRunner clusterRunner = new ClusterRunner() + .addNode(master1, slave1) + .addNode(master2, slave2) + .addNode(master3, slave3); + ClusterProcesses process = clusterRunner.run(); + + Config config = new Config(); + config.useClusterServers() + .addNodeAddress(process.getNodes().stream().findAny().get().getRedisServerAddressAndPort()); + RedissonClient redisson = Redisson.create(config); + + final AtomicBoolean executed = new AtomicBoolean(); + final AtomicInteger subscriptions = new AtomicInteger(); + + RTopic topic = redisson.getTopic("topic"); + topic.addListener(new StatusListener() { + + @Override + public void onUnsubscribe(String channel) { + } + + @Override + public void onSubscribe(String channel) { + subscriptions.incrementAndGet(); + } + }); + topic.addListener(new MessageListener() { + @Override + public void onMessage(String channel, Integer msg) { + executed.set(true); + } + }); + + process.getNodes().stream().filter(x -> Arrays.asList(slave1.getPort(), slave2.getPort(), slave3.getPort()).contains(x.getRedisServerPort())) + .forEach(x -> { + try { + x.stop(); + Thread.sleep(18000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }); + + Thread.sleep(15000); + + redisson.getTopic("topic").publish(1); + + await().atMost(75, TimeUnit.SECONDS).until(() -> subscriptions.get() == 2); + Assert.assertTrue(executed.get()); + + redisson.shutdown(); + process.shutdown(); + } + } diff --git a/redisson/src/test/java/org/redisson/codec/JsonJacksonCodecTest.java b/redisson/src/test/java/org/redisson/codec/JsonJacksonCodecTest.java new file mode 100644 index 000000000..abdd90a18 --- /dev/null +++ b/redisson/src/test/java/org/redisson/codec/JsonJacksonCodecTest.java @@ -0,0 +1,37 @@ +package org.redisson.codec; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; + +public class JsonJacksonCodecTest { + + static class Bean1599 { + public int id; + public Object obj; + } + + @Test(expected = JsonMappingException.class) + public void test() throws JsonParseException, JsonMappingException, IOException { + String JSON = + "{'id': 124,\n" + + " 'obj':[ 'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',\n" + + " {\n" + + " 'transletBytecodes' : [ 'AAIAZQ==' ],\n" + + " 'transletName' : 'a.b',\n" + + " 'outputProperties' : { }\n" + + " }\n" + + " ]\n" + + "}"; + JSON = JSON.replace("'", "\""); + + JsonJacksonCodec codec = new JsonJacksonCodec(); + codec.getObjectMapper().readValue(JSON, Bean1599.class); + Assert.fail("Should not pass"); + } + +} diff --git a/redisson/src/test/java/org/redisson/misc/SoftCacheMapTest.java b/redisson/src/test/java/org/redisson/misc/SoftCacheMapTest.java index dd074932e..69642660f 100644 --- a/redisson/src/test/java/org/redisson/misc/SoftCacheMapTest.java +++ b/redisson/src/test/java/org/redisson/misc/SoftCacheMapTest.java @@ -2,7 +2,7 @@ package org.redisson.misc; import org.junit.Test; import org.redisson.cache.Cache; -import org.redisson.cache.SoftCacheMap; +import org.redisson.cache.ReferenceCacheMap; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -13,7 +13,7 @@ public class SoftCacheMapTest { @Test public void testMaxIdleTimeEviction() throws InterruptedException { - Cache map = new SoftCacheMap(0, 0); + Cache map = ReferenceCacheMap.soft(0, 0); map.put(1, 0, 0, TimeUnit.MILLISECONDS, 400, TimeUnit.MILLISECONDS); assertThat(map.get(1)).isEqualTo(0); Thread.sleep(200); @@ -28,7 +28,7 @@ public class SoftCacheMapTest { @Test public void testTTLEviction() throws InterruptedException { - Cache map = new SoftCacheMap(0, 0); + Cache map = ReferenceCacheMap.soft(0, 0); map.put(1, 0, 500, TimeUnit.MILLISECONDS, 0, TimeUnit.MILLISECONDS); assertThat(map.get(1)).isEqualTo(0); Thread.sleep(100); @@ -40,7 +40,7 @@ public class SoftCacheMapTest { @Test public void testSizeEviction() { - Cache map = new SoftCacheMap(0, 0); + Cache map = ReferenceCacheMap.soft(0, 0); map.put(1, 0); map.put(2, 0); @@ -54,7 +54,7 @@ public class SoftCacheMapTest { // This test requires using -XX:SoftRefLRUPolicyMSPerMB=0 to pass @Test public void testSoftReferences() { - Cache map = new SoftCacheMap(0, 0); + Cache map = ReferenceCacheMap.soft(0, 0); for(int i=0;i<100000;i++) { map.put(i, new Integer(i)); } diff --git a/redisson/src/test/java/org/redisson/spring/cache/RedissonSpringCacheShortTTLTest.java b/redisson/src/test/java/org/redisson/spring/cache/RedissonSpringCacheShortTTLTest.java new file mode 100644 index 000000000..547e62901 --- /dev/null +++ b/redisson/src/test/java/org/redisson/spring/cache/RedissonSpringCacheShortTTLTest.java @@ -0,0 +1,174 @@ +package org.redisson.spring.cache; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.redisson.BaseTest; +import org.redisson.RedisRunner; +import org.redisson.RedisRunner.RedisProcess; +import org.redisson.api.RedissonClient; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Service; + +@RunWith(Parameterized.class) +public class RedissonSpringCacheShortTTLTest { + + public static class SampleObject { + + private String name; + private String value; + + public SampleObject() { + } + + public SampleObject(String name, String value) { + super(); + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + } + + @Service + public static class SampleBean { + + @CachePut(cacheNames = "testMap", key = "#key") + public SampleObject store(String key, SampleObject object) { + return object; + } + + @CachePut(cacheNames = "testMap", key = "#key") + public SampleObject storeNull(String key) { + return null; + } + + @CacheEvict(cacheNames = "testMap", key = "#key") + public void remove(String key) { + } + + @Cacheable(cacheNames = "testMap", key = "#key") + public SampleObject read(String key) { + throw new IllegalStateException(); + } + + @Cacheable(cacheNames = "testMap", key = "#key", sync = true) + public SampleObject readNullSync(String key) { + return null; + } + + @Cacheable(cacheNames = "testMap", key = "#key") + public SampleObject readNull(String key) { + return null; + } + + } + + @Configuration + @ComponentScan + @EnableCaching + public static class Application { + + @Bean(destroyMethod = "shutdown") + RedissonClient redisson() { + return BaseTest.createInstance(); + } + + @Bean + CacheManager cacheManager(RedissonClient redissonClient) throws IOException { + Map config = new HashMap(); + config.put("testMap", new CacheConfig(1 * 1000, 1 * 1000)); + return new RedissonSpringCacheManager(redissonClient, config); + } + + } + + @Configuration + @ComponentScan + @EnableCaching + public static class JsonConfigApplication { + + @Bean(destroyMethod = "shutdown") + RedissonClient redisson() { + return BaseTest.createInstance(); + } + + @Bean + CacheManager cacheManager(RedissonClient redissonClient) throws IOException { + return new RedissonSpringCacheManager(redissonClient, "classpath:/org/redisson/spring/cache/cache-config-shortTTL.json"); + } + + } + + private static RedisProcess p; + + @Parameterized.Parameters(name = "{index} - {0}") + public static Iterable data() throws IOException, InterruptedException { + if (p == null) { + p = RedisRunner.startDefaultRedisServerInstance(); + } + return Arrays.asList(new Object[][]{ + {new AnnotationConfigApplicationContext(Application.class)}, + {new AnnotationConfigApplicationContext(JsonConfigApplication.class)}, + }); + } + + @Parameterized.Parameter(0) + public AnnotationConfigApplicationContext context; + + @AfterClass + public static void after() throws InterruptedException, IOException { + RedissonSpringCacheShortTTLTest.data().forEach(e -> ((ConfigurableApplicationContext) e[0]).close()); + RedisRunner.shutDownDefaultRedisServerInstance(); + } + + @Test(expected = IllegalStateException.class) + public void testPutGet() throws InterruptedException { + SampleBean bean = context.getBean(SampleBean.class); + bean.store("object1", new SampleObject("name1", "value1")); + SampleObject s = bean.read("object1"); + assertThat(s.getName()).isEqualTo("name1"); + assertThat(s.getValue()).isEqualTo("value1"); + + Thread.sleep(1100); + + bean.read("object1"); + } + + + @Test(expected = IllegalStateException.class) + public void testPutGetSync() throws InterruptedException { + SampleBean bean = context.getBean(SampleBean.class); + bean.readNullSync("object1"); + assertThat(bean.read("object1")).isNull(); + + Thread.sleep(1100); + + bean.read("object1"); + } + +} diff --git a/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceTest.java b/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceTest.java index a2e36842c..588dbbf24 100644 --- a/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceTest.java +++ b/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceTest.java @@ -1,6 +1,8 @@ package org.redisson.spring.support; import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; import org.junit.After; import org.junit.AfterClass; import static org.junit.Assert.*; @@ -124,14 +126,24 @@ public class SpringNamespaceTest extends BaseTest { RedisRunner.getDefaultRedisServerInstance().getRedisServerPort(), 2).run(); System.setProperty("sentinel3Address", sentinel3.getRedisServerAddressAndPort()); - + RedisRunner slave = new RedisRunner().randomPort().randomDir().nosave(); ClusterRunner clusterRunner = new ClusterRunner() - .addNode(new RedisRunner().randomPort().randomDir().nosave()) - .addNode(new RedisRunner().randomPort().randomDir().nosave()) - .addNode(new RedisRunner().randomPort().randomDir().nosave()); - List nodes = clusterRunner.run(); - nodes.stream().forEach((node) -> { - System.setProperty("node" + (nodes.indexOf(node) + 1) + "Address", node.getRedisServerAddressAndPort()); + .addNode(new RedisRunner().randomPort().randomDir().nosave(),//master1 + new RedisRunner().randomPort().randomDir().nosave(),//slave1-1 + new RedisRunner().randomPort().randomDir().nosave(),//slave1-2 + slave)//slave1-3 + .addNode(new RedisRunner().randomPort().randomDir().nosave(),//master2 + new RedisRunner().randomPort().randomDir().nosave(),//slave2-1 + new RedisRunner().randomPort().randomDir().nosave())//slave2-2 + .addNode(new RedisRunner().randomPort().randomDir().nosave(),//master3 + new RedisRunner().randomPort().randomDir().nosave(),//slave3-1 + new RedisRunner().randomPort().randomDir().nosave())//slave3-2 + .addNode(slave,//slave1-3 + new RedisRunner().randomPort().randomDir().nosave(),//slave1-3-1 + new RedisRunner().randomPort().randomDir().nosave());//slave1-3-2 + final AtomicLong index = new AtomicLong(0); + clusterRunner.run().getNodes().stream().forEach((node) -> { + System.setProperty("node" + (index.incrementAndGet()) + "Address", node.getRedisServerAddressAndPort()); }); context = new ClassPathXmlApplicationContext("classpath:org/redisson/spring/support/namespace.xml"); diff --git a/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceWikiTest.java b/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceWikiTest.java index df1c38bf3..83d07f1e0 100644 --- a/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceWikiTest.java +++ b/redisson/src/test/java/org/redisson/spring/support/SpringNamespaceWikiTest.java @@ -180,16 +180,14 @@ public class SpringNamespaceWikiTest { .port(6381) .randomDir() .nosave()); - List nodes = clusterRunner.run(); + ClusterRunner.ClusterProcesses cluster = clusterRunner.run(); try { ((ConfigurableApplicationContext) new ClassPathXmlApplicationContext("classpath:org/redisson/spring/support/namespace_wiki_cluster.xml")) .close(); } finally { - for (RedisRunner.RedisProcess node : nodes) { - node.stop(); - } + cluster.shutdown(); } } } diff --git a/redisson/src/test/resources/org/redisson/spring/cache/cache-config-shortTTL.json b/redisson/src/test/resources/org/redisson/spring/cache/cache-config-shortTTL.json new file mode 100644 index 000000000..44fc9a7e1 --- /dev/null +++ b/redisson/src/test/resources/org/redisson/spring/cache/cache-config-shortTTL.json @@ -0,0 +1 @@ +{"testMap":{"ttl":1000,"maxIdleTime":1000}} \ No newline at end of file