org.redisson.pubsub.PublishSubscribeService Maven / Gradle / Ivy
/**
* Copyright 2018 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.pubsub;
import java.util.Collection;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RFuture;
import org.redisson.client.BaseRedisPubSubListener;
import org.redisson.client.ChannelName;
import org.redisson.client.RedisNodeNotFoundException;
import org.redisson.client.RedisPubSubConnection;
import org.redisson.client.RedisPubSubListener;
import org.redisson.client.RedisTimeoutException;
import org.redisson.client.SubscribeListener;
import org.redisson.client.codec.Codec;
import org.redisson.client.protocol.pubsub.PubSubType;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;
/**
*
* @author Nikita Koksharov
*
*/
public class PublishSubscribeService {
private static final Logger log = LoggerFactory.getLogger(PublishSubscribeService.class);
private final ConnectionManager connectionManager;
private final MasterSlaveServersConfig config;
private final AsyncSemaphore[] locks = new AsyncSemaphore[50];
private final AsyncSemaphore freePubSubLock = new AsyncSemaphore(1);
protected final ConcurrentMap name2PubSubConnection = PlatformDependent.newConcurrentHashMap();
protected final Queue freePubSubConnections = new ConcurrentLinkedQueue();
public PublishSubscribeService(ConnectionManager connectionManager, MasterSlaveServersConfig config) {
super();
this.connectionManager = connectionManager;
this.config = config;
for (int i = 0; i < locks.length; i++) {
locks[i] = new AsyncSemaphore(1);
}
}
public PubSubConnectionEntry getPubSubEntry(ChannelName channelName) {
return name2PubSubConnection.get(channelName);
}
public RFuture psubscribe(ChannelName channelName, Codec codec, RedisPubSubListener>... listeners) {
return subscribe(PubSubType.PSUBSCRIBE, codec, channelName, new RedissonPromise(), listeners);
}
public RFuture psubscribe(String channelName, Codec codec, AsyncSemaphore semaphore, RedisPubSubListener>... listeners) {
RPromise promise = new RedissonPromise();
subscribe(codec, new ChannelName(channelName), promise, PubSubType.PSUBSCRIBE, semaphore, listeners);
return promise;
}
public RFuture subscribe(Codec codec, ChannelName channelName, RedisPubSubListener>... listeners) {
return subscribe(PubSubType.SUBSCRIBE, codec, channelName, new RedissonPromise(), listeners);
}
private RFuture subscribe(final PubSubType type, final Codec codec, final ChannelName channelName,
final RPromise promise, final RedisPubSubListener>... listeners) {
final AsyncSemaphore lock = getSemaphore(channelName);
lock.acquire(new Runnable() {
@Override
public void run() {
if (promise.isDone()) {
lock.release();
return;
}
final RPromise result = new RedissonPromise();
promise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
}
}
});
result.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
return;
}
promise.trySuccess(result.getNow());
}
});
subscribe(codec, channelName, result, type, lock, listeners);
}
});
return promise;
}
public RFuture subscribe(Codec codec, String channelName, AsyncSemaphore semaphore, RedisPubSubListener>... listeners) {
RPromise promise = new RedissonPromise();
subscribe(codec, new ChannelName(channelName), promise, PubSubType.SUBSCRIBE, semaphore, listeners);
return promise;
}
public AsyncSemaphore getSemaphore(ChannelName channelName) {
return locks[Math.abs(channelName.hashCode() % locks.length)];
}
private void subscribe(final Codec codec, final ChannelName channelName,
final RPromise promise, final PubSubType type, final AsyncSemaphore lock, final RedisPubSubListener>... listeners) {
final PubSubConnectionEntry connEntry = name2PubSubConnection.get(channelName);
if (connEntry != null) {
subscribe(channelName, promise, type, lock, connEntry, listeners);
return;
}
freePubSubLock.acquire(new Runnable() {
@Override
public void run() {
if (promise.isDone()) {
lock.release();
freePubSubLock.release();
return;
}
final PubSubConnectionEntry freeEntry = freePubSubConnections.peek();
if (freeEntry == null) {
connect(codec, channelName, promise, type, lock, listeners);
return;
}
int remainFreeAmount = freeEntry.tryAcquire();
if (remainFreeAmount == -1) {
throw new IllegalStateException();
}
final PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, freeEntry);
if (oldEntry != null) {
freeEntry.release();
freePubSubLock.release();
subscribe(channelName, promise, type, lock, oldEntry, listeners);
return;
}
if (remainFreeAmount == 0) {
freePubSubConnections.poll();
}
freePubSubLock.release();
subscribe(channelName, promise, type, lock, freeEntry, listeners);
if (PubSubType.PSUBSCRIBE == type) {
freeEntry.psubscribe(codec, channelName);
} else {
freeEntry.subscribe(codec, channelName);
}
}
});
}
private void subscribe(final ChannelName channelName, final RPromise promise,
final PubSubType type, final AsyncSemaphore lock, final PubSubConnectionEntry connEntry,
final RedisPubSubListener>... listeners) {
for (RedisPubSubListener> listener : listeners) {
connEntry.addListener(channelName, listener);
}
SubscribeListener listener = connEntry.getSubscribeFuture(channelName, type);
final Future subscribeFuture = listener.getSuccessFuture();
subscribeFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!promise.trySuccess(connEntry)) {
for (RedisPubSubListener> listener : listeners) {
connEntry.removeListener(channelName, listener);
}
if (!connEntry.hasListeners(channelName)) {
unsubscribe(channelName, lock);
} else {
lock.release();
}
} else {
lock.release();
}
}
});
connectionManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (promise.tryFailure(new RedisTimeoutException())) {
subscribeFuture.cancel(false);
}
}
}, config.getRetryInterval(), TimeUnit.MILLISECONDS);
}
private void releaseSubscribeConnection(int slot, PubSubConnectionEntry pubSubEntry) {
MasterSlaveEntry entry = connectionManager.getEntry(slot);
if (entry == null) {
log.error("Node for slot: " + slot + " can't be found");
} else {
entry.returnPubSubConnection(pubSubEntry);
}
}
private RFuture nextPubSubConnection(int slot) {
MasterSlaveEntry entry = connectionManager.getEntry(slot);
if (entry == null) {
RedisNodeNotFoundException ex = new RedisNodeNotFoundException("Node for slot: " + slot + " hasn't been discovered yet");
return RedissonPromise.newFailedFuture(ex);
}
return entry.nextPubSubConnection();
}
private void connect(final Codec codec, final ChannelName channelName,
final RPromise promise, final PubSubType type, final AsyncSemaphore lock, final RedisPubSubListener>... listeners) {
final int slot = connectionManager.calcSlot(channelName.getName());
final RFuture connFuture = nextPubSubConnection(slot);
promise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
((RPromise)connFuture).tryFailure(future.cause());
}
}
});
connFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
freePubSubLock.release();
lock.release();
promise.tryFailure(future.cause());
return;
}
RedisPubSubConnection conn = future.getNow();
final PubSubConnectionEntry entry = new PubSubConnectionEntry(conn, config.getSubscriptionsPerConnection());
entry.tryAcquire();
final PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, entry);
if (oldEntry != null) {
releaseSubscribeConnection(slot, entry);
freePubSubLock.release();
subscribe(channelName, promise, type, lock, oldEntry, listeners);
return;
}
freePubSubConnections.add(entry);
freePubSubLock.release();
subscribe(channelName, promise, type, lock, entry, listeners);
if (PubSubType.PSUBSCRIBE == type) {
entry.psubscribe(codec, channelName);
} else {
entry.subscribe(codec, channelName);
}
}
});
}
public RFuture unsubscribe(final ChannelName channelName, final AsyncSemaphore lock) {
final PubSubConnectionEntry entry = name2PubSubConnection.remove(channelName);
if (entry == null) {
lock.release();
return RedissonPromise.newSucceededFuture(null);
}
final RedissonPromise result = new RedissonPromise();
entry.unsubscribe(channelName, new BaseRedisPubSubListener() {
@Override
public boolean onStatus(PubSubType type, CharSequence channel) {
if (type == PubSubType.UNSUBSCRIBE && channel.equals(channelName)) {
if (entry.release() == 1) {
freePubSubConnections.add(entry);
}
lock.release();
result.trySuccess(null);
return true;
}
return false;
}
});
return result;
}
public RFuture unsubscribe(final ChannelName channelName, final PubSubType topicType) {
final RPromise result = new RedissonPromise();
final AsyncSemaphore lock = getSemaphore(channelName);
lock.acquire(new Runnable() {
@Override
public void run() {
final PubSubConnectionEntry entry = name2PubSubConnection.remove(channelName);
if (entry == null) {
lock.release();
result.trySuccess(null);
return;
}
freePubSubLock.acquire(new Runnable() {
@Override
public void run() {
freePubSubConnections.remove(entry);
freePubSubLock.release();
final Codec entryCodec = entry.getConnection().getChannels().get(channelName);
RedisPubSubListener
© 2015 - 2025 Weber Informatics LLC | Privacy Policy