org.jetlinks.supports.cluster.RpcDeviceOperationBroker Maven / Gradle / Ivy
The newest version!
package org.jetlinks.supports.cluster;
import com.google.common.cache.CacheBuilder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.scalecube.services.annotations.ServiceMethod;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.core.device.DeviceState;
import org.jetlinks.core.device.DeviceStateInfo;
import org.jetlinks.core.device.session.DeviceSessionManager;
import org.jetlinks.core.enums.ErrorCode;
import org.jetlinks.core.message.*;
import org.jetlinks.core.rpc.RpcManager;
import org.jetlinks.core.rpc.RpcService;
import org.jetlinks.core.trace.TraceHolder;
import org.jetlinks.core.utils.Reactors;
import org.jetlinks.core.utils.SerializeUtils;
import org.reactivestreams.Publisher;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.util.concurrent.Queues;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import static com.google.common.cache.RemovalCause.EXPIRED;
@Slf4j
public class RpcDeviceOperationBroker extends AbstractDeviceOperationBroker {
private final RpcManager rpcManager;
private final DeviceSessionManager sessionManager;
private final Sinks.Many sendToDevice = Sinks
.unsafe()
.many()
.unicast()
.onBackpressureBuffer(Queues.unboundedMultiproducer().get());
private final Map> awaits = CacheBuilder
.newBuilder()
.expireAfterWrite(Duration.ofMinutes(5))
.>removalListener(notify -> {
if (notify.getCause() == EXPIRED) {
try {
RpcDeviceOperationBroker.log.debug("discard await reply message[{}] message,{}", notify.getKey(), notify.getValue());
} catch (Throwable ignore) {
}
}
})
.build()
.asMap();
private final List>> handler = new CopyOnWriteArrayList<>();
public RpcDeviceOperationBroker(RpcManager rpcManager, DeviceSessionManager sessionManager) {
this.rpcManager = rpcManager;
this.sessionManager = sessionManager;
rpcManager.registerService(new ServiceImpl());
}
@Override
public Flux getDeviceState(String deviceGatewayServerId,
Collection deviceIdList) {
return Flux
.fromIterable(deviceIdList)
.flatMap(id -> sessionManager
.checkAlive(id, false)
.map(alive -> new DeviceStateInfo(id, alive ? DeviceState.online : DeviceState.offline))
);
}
@Override
public Disposable handleGetDeviceState(String serverId,
Function, Flux> stateMapper) {
return Disposables.disposed();
}
@Override
public Flux handleReply(String deviceId, String messageId, Duration timeout) {
return super.handleReply(deviceId, messageId, timeout);
}
@Override
public Mono send(String deviceGatewayServerId, Publisher extends Message> message) {
//发给同一个服务节点
if (rpcManager.currentServerId().equals(deviceGatewayServerId)) {
return Flux
.from(message)
.flatMap(this::handleSendToDevice)
.then(Reactors.ALWAYS_ONE);
}
return Flux
.from(message)
.flatMap(msg -> {
msg.addHeader(Headers.sendFrom, rpcManager.currentServerId());
// addAwaitReplyKey(msg);
return rpcManager
.getService(deviceGatewayServerId, Service.class)
.flatMap(service -> service
.send(encode(msg))
.then(Reactors.ALWAYS_ONE))
.switchIfEmpty(Reactors.ALWAYS_ZERO);
})
.reduce(0, Integer::sum);
}
private Mono handleSendToDevice(Message message) {
return doSendToDevice(message);
}
@Override
public Disposable handleSendToDeviceMessage(String serverId, Function> handler) {
this.handler.add(handler);
return () -> this.handler.remove(handler);
}
private void addAwaitReplyKey(Message message) {
if (message instanceof RepayableDeviceMessage && !message
.getHeader(Headers.sendAndForget)
.orElse(false)) {
RepayableDeviceMessage> msg = ((RepayableDeviceMessage>) message);
awaits.put(getAwaitReplyKey(msg), msg);
}
}
private Mono doSendToDevice(Message message) {
return TraceHolder
.writeContextTo(message, Message::addHeader)
.flatMap(msg -> {
if (sendToDevice.currentSubscriberCount() == 0 && handler.isEmpty()) {
log.warn("no handler for message {}", msg);
return doReply(createReply(msg).error(ErrorCode.SYSTEM_ERROR));
}
if (sendToDevice.currentSubscriberCount() != 0) {
try {
sendToDevice.emitNext(msg, Reactors.emitFailureHandler());
} catch (Throwable err) {
return doReply(createReply(msg).error(err));
}
}
return doSendToDevice(msg, handler)
.onErrorResume(error -> reply(createReply(message).error(error)).then());
});
}
private Mono doSendToDevice(Message message, List>> handlers) {
if (handlers.size() == 1) {
return handlers
.get(0)
.apply(message);
}
return Flux
.fromIterable(handlers)
.concatMap(h -> h.apply(message))
.then();
}
private DeviceMessageReply createReply(Message message) {
if (message instanceof RepayableDeviceMessage) {
return ((RepayableDeviceMessage>) message).newReply();
}
if (message instanceof DeviceMessage) {
return new CommonDeviceMessageReply<>()
.deviceId(((DeviceMessage) message).getDeviceId())
.messageId(message.getMessageId());
}
return new CommonDeviceMessageReply<>()
.messageId(message.getMessageId());
}
@Override
public Mono send(Publisher extends BroadcastMessage> message) {
return Reactors.ALWAYS_ZERO;
}
@Override
public Flux handleSendToDeviceMessage(String serverId) {
return sendToDevice.asFlux();
}
@Override
protected Mono doReply(DeviceMessageReply reply) {
RepayableDeviceMessage> request = awaits.remove(getAwaitReplyKey(reply));
String serviceId = null;
if (request != null) {
serviceId = request.getHeader(Headers.sendFrom).orElse(null);
}
Flux serviceFlux;
if (serviceId != null) {
serviceFlux = rpcManager
.getService(serviceId, Service.class)
.flux();
} else {
serviceFlux = rpcManager
.getServices(Service.class)
.map(RpcService::service);
}
return serviceFlux
.flatMap(service -> service.reply(encode(reply)))
.then();
}
@SneakyThrows
protected ObjectInput createInput(ByteBuf input) {
return new ObjectInputStream(new ByteBufInputStream(input, true));
}
@SneakyThrows
protected ObjectOutput createOutput(ByteBuf output) {
return new ObjectOutputStream(new ByteBufOutputStream(output));
}
static MessageType[] types = MessageType.values();
@SneakyThrows
private Message decode(ByteBuf buf) {
try (ObjectInput input = createInput(buf)) {
MessageType type = types[input.readByte()];
Message msg = type.forDevice();
if (msg != null) {
msg.readExternal(input);
return msg;
}
return (Message) SerializeUtils.readObject(input);
}
}
@SneakyThrows
private ByteBuf encode(Message message) {
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
try (ObjectOutput output = createOutput(buf)) {
output.writeByte(message.getMessageType().ordinal());
if (message.getMessageType().iSupportDevice()) {
message.writeExternal(output);
} else {
SerializeUtils.writeObject(message, output);
}
}
return buf;
}
@io.scalecube.services.annotations.Service
public interface Service {
@ServiceMethod
Mono send(ByteBuf payload);
@ServiceMethod
Mono reply(ByteBuf buf);
}
private class ServiceImpl implements Service {
@Override
public Mono send(ByteBuf payload) {
Message msg = decode(payload);
addAwaitReplyKey(msg);
return doSendToDevice(msg);
}
@Override
public Mono reply(ByteBuf buf) {
Message msg = decode(buf);
if (msg instanceof DeviceMessageReply) {
handleReply(((DeviceMessageReply) msg));
}
return Mono.empty();
}
}
}