All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.turbospaces.ebean.JGroupsBroadcastChannel Maven / Gradle / Ivy
package com.turbospaces.ebean;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.jgroups.blocks.MethodCall;
import org.jgroups.blocks.RequestOptions;
import org.jgroups.blocks.RpcDispatcher;
import org.springframework.beans.factory.DisposableBean;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.EventTranslator;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.dsl.Disruptor;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.common.PlatformUtil;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheType;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.cache.CachedManyIds;
import io.micrometer.core.instrument.MeterRegistry;
import io.vavr.CheckedRunnable;
import io.vavr.concurrent.Promise;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.blockhound.integration.DefaultBlockHoundIntegration;
@Slf4j
public class JGroupsBroadcastChannel implements BroadcastChannel, DisposableBean {
private static final Duration TOO_LONG_WARN = Duration.ofSeconds(1);
private final Disruptor disruptor;
@SuppressWarnings("unchecked")
public JGroupsBroadcastChannel(ApplicationProperties props, MeterRegistry meterRegistry, RpcDispatcher dispatcher) {
//
// ~ parallel workers (more thread just because it is it casts down to network stack )
//
int count = props.CACHE_BROADCAST_WORKERS.get();
int ringBufferSize = props.CACHE_RING_BUFFER_SIZE.get();
WorkHandler[] workers = new WorkHandler[count];
for (int i = 0; i < workers.length; i++) {
workers[i] = new WorkHandler() {
@Override
public void onEvent(MethodCallEvent event) throws Exception {
StopWatch outer = event.stopWatch;
//
// ~ broadcast over network
//
long now = System.currentTimeMillis();
if (dispatcher.getChannel().isOpen()) {
dispatcher.callRemoteMethods(null, event.call, RequestOptions.ASYNC());
}
outer.stop();
long time = outer.getTime(TimeUnit.MILLISECONDS);
if (time > TOO_LONG_WARN.toMillis()) {
log.error("{} took too long: {} own: {}ms", event.operation, outer, System.currentTimeMillis() - now);
}
event.promise.trySuccess(outer);
}
};
}
ThreadFactoryBuilder factory = new ThreadFactoryBuilder();
factory.setDaemon(false);
factory.setNameFormat("jgroups-rpc-dispatcher-%d");
disruptor = new Disruptor<>(MethodCallEvent.FACTORY, ringBufferSize, factory.build());
disruptor.handleEventsWithWorkerPool(workers);
disruptor.start();
}
@Override
public void destroy() throws Exception {
disruptor.shutdown();
}
@Override
public Promise broadcastPutAsync(String cacheKey, ServerCacheType type, String key, Object obj) throws Throwable {
UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get();
//
// ~ based on cache type
//
byte[] data = switch (type) {
case NATURAL_KEY -> SerializationUtils.serialize(Serializable.class.cast(obj));
case BEAN -> PlatformUtil.serialize(out, CachedBeanData.class.cast(obj));
case COLLECTION_IDS -> PlatformUtil.serialize(out, CachedManyIds.class.cast(obj));
default -> throw new IllegalArgumentException("Unexpected cache type: " + type);
};
//
// ~ dispatch event to ring buffer
//
Promise promise = Promise.make(MoreExecutors.directExecutor());
DefaultBlockHoundIntegration.allowBlockingUnchecked(new CheckedRunnable() {
@Override
public void run() throws Throwable {
disruptor.publishEvent(new EventTranslator() {
@Override
public void translateTo(MethodCallEvent event, long sequence) {
event.call = new MethodCall(JGroupsCacheManager.METHOD_ON_CACHE_PUT, cacheKey, SerializationUtils.serialize(key), data);
event.stopWatch = StopWatch.createStarted();
event.operation = "put(%s)".formatted(cacheKey);
event.promise = promise;
}
});
}
});
return promise;
}
@Override
public Promise broadcastPutAllAsync(String cacheKey, ServerCacheType type, Map map) throws Throwable {
//
// ~ fast output stream
//
UnsynchronizedByteArrayOutputStream stream = UnsynchronizedByteArrayOutputStream.builder().get();
try (ObjectOutputStream out = new ObjectOutputStream(stream)) {
out.writeInt(map.size());
for (Entry entry : map.entrySet()) {
out.writeUTF(entry.getKey());
switch (type) {
case NATURAL_KEY: {
out.writeObject(entry.getValue());
break;
}
case BEAN: {
CachedBeanData.class.cast(entry.getValue()).writeExternal(out);
break;
}
case COLLECTION_IDS: {
CachedManyIds.class.cast(entry.getValue()).writeExternal(out);
break;
}
default: {
throw new IllegalArgumentException("Unexpected cache type: " + type);
}
}
}
out.flush();
}
//
// ~ dispatch event to ring buffer
//
Promise promise = Promise.make(MoreExecutors.directExecutor());
DefaultBlockHoundIntegration.allowBlockingUnchecked(new CheckedRunnable() {
@Override
public void run() throws Throwable {
disruptor.publishEvent(new EventTranslator() {
@Override
public void translateTo(MethodCallEvent event, long sequence) {
event.call = new MethodCall(JGroupsCacheManager.METHOD_ON_CHANGE_PUT_ALL, cacheKey, stream.toByteArray());
event.stopWatch = StopWatch.createStarted();
event.operation = "putAll(%s)".formatted(cacheKey);
event.promise = promise;
}
});
}
});
return promise;
}
@Override
public Promise broadcastRemoveAsync(String cacheKey, String key) throws Throwable {
//
// ~ dispatch event to ring buffer
//
Promise promise = Promise.make(MoreExecutors.directExecutor());
DefaultBlockHoundIntegration.allowBlockingUnchecked(new CheckedRunnable() {
@Override
public void run() throws Throwable {
disruptor.publishEvent(new EventTranslator() {
@Override
public void translateTo(MethodCallEvent event, long sequence) {
event.call = new MethodCall(JGroupsCacheManager.METHOD_ON_CHANGE_REMOVE, cacheKey, SerializationUtils.serialize(key));
event.stopWatch = StopWatch.createStarted();
event.operation = "remove(%s)".formatted(cacheKey);
event.promise = promise;
}
});
}
});
return promise;
}
@Override
public Promise broadcastRemoveAllAsync(String cacheKey, Collection keys) throws Throwable {
//
// ~ fast output stream
//
UnsynchronizedByteArrayOutputStream stream = UnsynchronizedByteArrayOutputStream.builder().get();
try (ObjectOutputStream out = new ObjectOutputStream(stream)) {
out.writeInt(keys.size());
for (String it : keys) {
out.writeUTF(it);
}
out.flush();
}
//
// ~ dispatch event to ring buffer
//
Promise promise = Promise.make(MoreExecutors.directExecutor());
DefaultBlockHoundIntegration.allowBlockingUnchecked(new CheckedRunnable() {
@Override
public void run() throws Throwable {
disruptor.publishEvent(new EventTranslator() {
@Override
public void translateTo(MethodCallEvent event, long sequence) {
event.call = new MethodCall(JGroupsCacheManager.METHOD_ON_CHANGE_REMOVE_ALL, cacheKey, stream.toByteArray());
event.stopWatch = StopWatch.createStarted();
event.operation = "removeAll(%s)".formatted(cacheKey);
event.promise = promise;
}
});
}
});
return promise;
}
@Override
public Promise broadcastClearAllAsync(String cacheKey) {
//
// ~ dispatch event to ring buffer
//
Promise promise = Promise.make(MoreExecutors.directExecutor());
DefaultBlockHoundIntegration.allowBlocking(new Runnable() {
@Override
public void run() {
disruptor.publishEvent(new EventTranslator() {
@Override
public void translateTo(MethodCallEvent event, long sequence) {
event.call = new MethodCall(JGroupsCacheManager.METHOD_ON_CACHE_CLEAR, cacheKey);
event.stopWatch = StopWatch.createStarted();
event.operation = "clear(%s)".formatted(cacheKey);
event.promise = promise;
}
});
}
});
return promise;
}
@Override
public Promise broadcastClearAllAsync() {
//
// ~ dispatch event to ring buffer
//
Promise promise = Promise.make(MoreExecutors.directExecutor());
DefaultBlockHoundIntegration.allowBlocking(new Runnable() {
@Override
public void run() {
disruptor.publishEvent(new EventTranslator() {
@Override
public void translateTo(MethodCallEvent event, long sequence) {
event.call = new MethodCall(JGroupsCacheManager.METHOD_ON_CACHE_CLEAR_ALL, true);
event.stopWatch = StopWatch.createStarted();
event.operation = "clearAll";
event.promise = promise;
}
});
}
});
return promise;
}
@Override
public Promise broadcastAsync(ServerCacheNotification notification, String data) {
//
// ~ dispatch event to ring buffer
//
Promise promise = Promise.make(MoreExecutors.directExecutor());
DefaultBlockHoundIntegration.allowBlocking(new Runnable() {
@Override
public void run() {
disruptor.publishEvent(new EventTranslator() {
@Override
public void translateTo(MethodCallEvent event, long sequence) {
event.call = new MethodCall(JGroupsCacheManager.METHOD_ON_MODIFIED, data);
event.stopWatch = StopWatch.createStarted();
event.operation = "notify(%s)".formatted(data);
event.promise = promise;
}
});
}
});
return promise;
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
private static class MethodCallEvent {
public static final EventFactory FACTORY = new EventFactory() {
@Override
public MethodCallEvent newInstance() {
return new MethodCallEvent();
}
};
public Promise promise;
public StopWatch stopWatch;
public MethodCall call;
public String operation;
}
}