All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rx.net.rpc.Remoting Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package org.rx.net.rpc;

import io.netty.util.concurrent.FastThreadLocal;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.rx.bean.*;
import org.rx.exception.ExceptionHandler;
import org.rx.net.Sockets;
import org.rx.net.rpc.impl.StatefulRpcClient;
import org.rx.net.rpc.protocol.EventFlag;
import org.rx.net.rpc.protocol.EventMessage;
import org.rx.net.rpc.protocol.MethodMessage;
import org.rx.core.*;
import org.rx.net.rpc.protocol.ErrorPacket;
import org.rx.util.BeanMapper;
import org.rx.util.Snowflake;
import org.rx.util.function.TripleAction;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

import io.netty.util.internal.ThreadLocalRandom;

import java.util.concurrent.TimeoutException;

import static org.rx.bean.$.$;
import static org.rx.core.App.*;
import static org.rx.core.Extends.*;

//snappy + protobuf
@Slf4j
public final class Remoting {
    public static class ClientBean {
        final ResetEventWait syncRoot = new ResetEventWait();
        MethodMessage pack;
    }

    @RequiredArgsConstructor
    public static class ServerBean {
        @AllArgsConstructor
        @RequiredArgsConstructor
        static class EventContext {
            final EventArgs computedArgs;
            volatile RpcClientMeta computingClient;
        }

        static class EventBean {
            final Set subscribe = ConcurrentHashMap.newKeySet();
            final Map contextMap = new ConcurrentHashMap<>();
        }

        @Getter
        final RpcServer server;
        final Map eventBeans = new ConcurrentHashMap<>();
    }

    static final Map serverBeans = new ConcurrentHashMap<>();
    static final Map clientPools = new ConcurrentHashMap<>();
    static final IdGenerator generator = new IdGenerator();
    static final Map> clientBeans = new ConcurrentHashMap<>();

    public static  T create(Class contract, RpcClientConfig facadeConfig) {
        return create(contract, facadeConfig, null);
    }

    @SneakyThrows
    public static  T create(@NonNull Class contract, @NonNull RpcClientConfig facadeConfig, TripleAction onInit) {
        FastThreadLocal isCompute = new FastThreadLocal<>();
        $ sync = $();
        //onInit由调用方触发可能spring还没起来的情况
        return proxy(contract, (m, p) -> {
            if (Reflects.OBJECT_METHODS.contains(m)) {
                return p.fastInvokeSuper();
            }
            if (Reflects.isCloseMethod(m)) {
                synchronized (sync) {
                    if (sync.v != null) {
                        sync.v.close();
                        sync.v = null;
                    }
                }
                return null;
            }

            Serializable pack = null;
            Object[] args = p.arguments;
            ClientBean clientBean = new ClientBean();
            switch (m.getName()) {
                case "attachEvent":
                    switch (args.length) {
                        case 2:
                            return invokeSuper(m, p);
                        case 3:
                            setReturnValue(clientBean, invokeSuper(m, p));
                            String eventName = (String) args[0];
                            pack = new EventMessage(eventName, EventFlag.SUBSCRIBE);
                            log.info("clientSide event {} -> SUBSCRIBE", eventName);
                            break;
                    }
                    break;
                case "detachEvent":
                    if (args.length == 2) {
                        setReturnValue(clientBean, invokeSuper(m, p));
                        String eventName = (String) args[0];
                        pack = new EventMessage(eventName, EventFlag.UNSUBSCRIBE);
                        log.info("clientSide event {} -> UNSUBSCRIBE", eventName);
                    }
                    break;
                case "raiseEvent":
                case "raiseEventAsync":
                    if (args.length == 2) {
                        if (!(args[0] instanceof String) || BooleanUtils.isTrue(isCompute.get())) {
                            return invokeSuper(m, p);
                        }
                        isCompute.remove();

                        setReturnValue(clientBean, invokeSuper(m, p));
                        EventMessage eventMessage = new EventMessage((String) args[0], EventFlag.PUBLISH);
                        eventMessage.eventArgs = (EventArgs) args[1];
                        pack = eventMessage;
                        log.info("clientSide event {} -> PUBLISH", eventMessage.eventName);
                    }
                    break;
                case "eventFlags":
                case "asyncScheduler":
                    if (args.length == 0) {
                        return invokeSuper(m, p);
                    }
                    break;
            }

            if (pack == null) {
                pack = clientBean.pack = new MethodMessage(generator.increment(), m.getName(), args);
            }
            RpcClientPool pool = clientPools.computeIfAbsent(facadeConfig, k -> {
                log.info("RpcClientPool {}", toJsonString(k));
                return RpcClientPool.createPool(k);
            });

            if (sync.v == null) {
                synchronized (sync) {
                    if (sync.v == null) {
                        init(sync.v = pool.borrowClient(), p.getProxyObject(), isCompute);
                        sync.v.onReconnected.combine((s, e) -> {
                            if (onInit != null) {
                                onInit.invoke((T) p.getProxyObject(), (StatefulRpcClient) s);
                            }
                            s.asyncScheduler().runAsync(() -> {
                                for (ClientBean value : getClientBeans((StatefulRpcClient) s).values()) {
                                    if (value.syncRoot.getHoldCount() == 0) {
                                        continue;
                                    }
                                    log.info("clientSide resent pack[{}] {}", value.pack.id, value.pack.methodName);
                                    try {
                                        s.send(value.pack);
                                    } catch (ClientDisconnectedException ex) {
                                        log.warn("clientSide resent pack[{}] fail", value.pack.id);
                                    }
                                }
                            });
                        });
                        if (onInit != null) {
                            onInit.invoke((T) p.getProxyObject(), sync.v);
                            //onHandshake returnObject的情况
                            if (sync.v == null) {
                                init(sync.v = pool.borrowClient(), p.getProxyObject(), isCompute);
                            }
                        }
                    }
                }
            }
            StatefulRpcClient client = sync.v;
            Map waitBeans = null;

            MethodMessage methodMessage = as(pack, MethodMessage.class);
            ProceedEventArgs eventArgs = methodMessage != null ? new ProceedEventArgs(contract, methodMessage.parameters, false) : null;
            try {
                client.send(pack);
                if (eventArgs != null) {
                    waitBeans = getClientBeans(client);
                    waitBeans.put(clientBean.pack.id, clientBean);
                    try {
                        clientBean.syncRoot.waitOne(client.getConfig().getConnectTimeoutMillis());
                        clientBean.syncRoot.reset();
                    } catch (TimeoutException e) {
                        if (!client.isConnected()) {
                            throw new ClientDisconnectedException(e);
                        }
                        if (clientBean.pack.returnValue == null) {
                            throw e;
                        }
                    }
                }
                if (clientBean.pack.errorMessage != null) {
                    throw new RemotingException(clientBean.pack.errorMessage);
                }
            } catch (ClientDisconnectedException e) {
                if (!client.getConfig().isEnableReconnect()) {
                    pool.returnClient(client);
                    sync.v = null;
                    throw e;
                }

                if (eventArgs == null) {
                    throw e;
                }
                waitBeans = getClientBeans(client);
                waitBeans.put(clientBean.pack.id, clientBean);
                try {
                    clientBean.syncRoot.waitOne(client.getConfig().getConnectTimeoutMillis());
                    clientBean.syncRoot.reset();
                } catch (TimeoutException ie) {
                    if (clientBean.pack.returnValue == null) {
                        eventArgs.setError(e);
                        throw e;
                    }
                }
            } catch (Throwable e) {
                if (eventArgs != null) {
                    eventArgs.setError(e);
                }
                throw e;
            } finally {
                if (eventArgs != null) {
                    log(eventArgs, msg -> {
                        msg.appendLine("Client invoke %s.%s [%s -> %s]", contract.getSimpleName(), methodMessage.methodName,
                                Sockets.toString(client.getLocalEndpoint()),
                                Sockets.toString(client.getConfig().getServerEndpoint()));
                        msg.appendLine("Request:\t%s", toJsonString(methodMessage.parameters));
                        if (eventArgs.getError() != null) {
                            msg.appendLine("Response:\t%s", eventArgs.getError().getMessage());
                        } else if (clientBean.pack == null) {
                            msg.appendLine("Response:\tNULL");
                        } else {
                            msg.appendLine("Response:\t%s", toJsonString(clientBean.pack.returnValue));
                        }
                    });
                }
                if (waitBeans != null) {
                    waitBeans.remove(clientBean.pack.id);
                    if (waitBeans.isEmpty()) {
                        synchronized (sync) {
                            sync.v = pool.returnClient(client);
                        }
                    }
                }
            }
            return clientBean.pack != null ? clientBean.pack.returnValue : null;
        });
    }

    private static void init(StatefulRpcClient client, Object proxyObject, FastThreadLocal isCompute) {
        client.onError.combine((s, e) -> e.setCancel(true));
        client.onReceive.combine((s, e) -> {
            if (tryAs(e.getValue(), EventMessage.class, x -> {
                switch (x.flag) {
                    case BROADCAST:
                    case COMPUTE_ARGS:
                        try {
                            isCompute.set(true);
                            EventTarget target = (EventTarget) proxyObject;
                            target.raiseEvent(x.eventName, x.eventArgs);
                            log.info("clientSide event {} -> {} OK & args={}", x.eventName, x.flag, toJsonString(x.eventArgs));
                        } catch (Exception ex) {
                            ExceptionHandler.INSTANCE.log("clientSide event {} -> {}", x.eventName, x.flag, ex);
                        } finally {
                            if (x.flag == EventFlag.COMPUTE_ARGS) {
                                s.send(x);  //import
                            }
                        }
                        break;
                }
            })) {
                return;  //import
            }

            MethodMessage svrPack = (MethodMessage) e.getValue();
//            log.debug("recv: {}", svrPack.returnValue);
            ClientBean clientBean = getClientBeans(client).get(svrPack.id);
            if (clientBean == null) {
                log.warn("clientSide callback pack[{}] fail", svrPack.id);
                return;
            }
            clientBean.pack = svrPack;
            clientBean.syncRoot.set();
        });
    }

    private static Map getClientBeans(StatefulRpcClient client) {
        return clientBeans.computeIfAbsent(client, k -> new ConcurrentHashMap<>());
    }

    private static void setReturnValue(ClientBean clientBean, Object value) {
        if (clientBean.pack == null) {
            clientBean.pack = new MethodMessage(generator.increment(), null, null);
        }
        clientBean.pack.returnValue = value;
    }

    @SneakyThrows
    private static Object invokeSuper(Method m, DynamicProxy p) {
        if (m.isDefault()) {
            return Reflects.invokeDefaultMethod(m, p.getProxyObject(), p.arguments);
        }
        return p.fastInvokeSuper();
    }

    public static RpcServer listen(Object contractInstance, int listenPort, boolean enableEventCompute) {
        RpcServerConfig conf = new RpcServerConfig(listenPort);
        if (enableEventCompute) {
            conf.setEventComputeVersion(RpcServerConfig.EVENT_LATEST_COMPUTE);
        }
        return listen(contractInstance, conf);
    }

    public static RpcServer listen(@NonNull Object contractInstance, @NonNull RpcServerConfig config) {
        return serverBeans.computeIfAbsent(contractInstance, k -> {
            ServerBean bean = new ServerBean(new RpcServer(config));
            bean.server.onClosed.combine((s, e) -> serverBeans.remove(contractInstance));
            bean.server.onError.combine((s, e) -> {
                e.setCancel(true);
                s.send(e.getClient(), new ErrorPacket(String.format("server error: %s", e.getValue().toString())));
            });
            bean.server.onReceive.combine((s, e) -> {
                if (tryAs(e.getValue(), EventMessage.class, p -> {
                    ServerBean.EventBean eventBean = bean.eventBeans.computeIfAbsent(p.eventName, x -> new ServerBean.EventBean());
                    switch (p.flag) {
                        case SUBSCRIBE:
                            EventTarget eventTarget = (EventTarget) contractInstance;
                            eventTarget.attachEvent(p.eventName, (sender, args) -> {
                                synchronized (eventBean) {
                                    ServerBean.EventContext eCtx = new ServerBean.EventContext(args);
                                    if (config.getEventComputeVersion() == RpcServerConfig.EVENT_DISABLE_COMPUTE) {
                                        eCtx.computingClient = null;
                                    } else {
                                        RpcClientMeta computingClient;
                                        if (config.getEventComputeVersion() == RpcServerConfig.EVENT_LATEST_COMPUTE) {
                                            computingClient = NQuery.of(eventBean.subscribe).groupBy(x -> x.getHandshakePacket().getEventVersion(), (p1, p2) -> {
                                                int i = ThreadLocalRandom.current().nextInt(0, p2.count());
                                                return p2.skip(i).first();
                                            }).orderByDescending(x -> x.getHandshakePacket().getEventVersion()).firstOrDefault();
                                        } else {
                                            computingClient = NQuery.of(eventBean.subscribe).where(x -> x.getHandshakePacket().getEventVersion() == config.getEventComputeVersion())
                                                    .orderByRand().firstOrDefault();
                                        }
                                        if (computingClient == null) {
                                            log.warn("serverSide event {} subscribe empty", p.eventName);
                                        } else {
                                            eCtx.computingClient = computingClient;
                                            EventMessage pack = new EventMessage(p.eventName, EventFlag.COMPUTE_ARGS);
                                            pack.computeId = Snowflake.DEFAULT.nextId();
                                            pack.eventArgs = args;
                                            eventBean.contextMap.put(pack.computeId, eCtx);
                                            try {
                                                s.send(computingClient, pack);
                                                log.info("serverSide event {} {} -> COMPUTE_ARGS WAIT {}", pack.eventName, computingClient.getRemoteEndpoint(), s.getConfig().getConnectTimeoutMillis());
                                                eventBean.wait(s.getConfig().getConnectTimeoutMillis());
                                            } catch (Exception ex) {
                                                ExceptionHandler.INSTANCE.log("serverSide event {} {} -> COMPUTE_ARGS ERROR", pack.eventName, computingClient.getRemoteEndpoint(), ex);
                                            } finally {
                                                //delay purge
                                                Tasks.setTimeout(() -> eventBean.contextMap.remove(pack.computeId), s.getConfig().getConnectTimeoutMillis() * 2L);
                                            }
                                        }
                                    }
                                    broadcast(s, p, eventBean, eCtx);
                                }
                            }, false); //必须false
                            log.info("serverSide event {} {} -> SUBSCRIBE", p.eventName, e.getClient().getRemoteEndpoint());
                            eventBean.subscribe.add(e.getClient());
                            break;
                        case UNSUBSCRIBE:
                            log.info("serverSide event {} {} -> UNSUBSCRIBE", p.eventName, e.getClient().getRemoteEndpoint());
                            eventBean.subscribe.remove(e.getClient());
                            break;
                        case PUBLISH:
                            synchronized (eventBean) {
                                log.info("serverSide event {} {} -> PUBLISH", p.eventName, e.getClient().getRemoteEndpoint());
                                broadcast(s, p, eventBean, new ServerBean.EventContext(p.eventArgs, e.getClient()));
                            }
                            break;
                        case COMPUTE_ARGS:
                            synchronized (eventBean) {
                                ServerBean.EventContext ctx = eventBean.contextMap.get(p.computeId);
                                if (ctx == null) {
                                    log.warn("serverSide event {} [{}] -> COMPUTE_ARGS FAIL", p.eventName, p.computeId);
                                } else {
                                    //赋值原引用对象
                                    BeanMapper.DEFAULT.map(p.eventArgs, ctx.computedArgs);
                                    log.info("serverSide event {} {} -> COMPUTE_ARGS OK & args={}", p.eventName, ctx.computingClient.getRemoteEndpoint(), toJsonString(ctx.computedArgs));
                                }
                                eventBean.notifyAll();
                            }
                            break;
                    }
                })) {
                    return;
                }

                MethodMessage pack = (MethodMessage) e.getValue();
                ProceedEventArgs args = new ProceedEventArgs(contractInstance.getClass(), pack.parameters, false);
                try {
                    pack.returnValue = RemotingContext.invoke(() -> args.proceed(() ->
                            Reflects.invokeMethod(contractInstance, pack.methodName, pack.parameters)
                    ), s, e.getClient());
                } catch (Throwable ex) {
                    Throwable cause = ifNull(ex.getCause(), ex);
                    args.setError(ex);
                    pack.errorMessage = String.format("ERROR: %s %s", cause.getClass().getSimpleName(), cause.getMessage());
                } finally {
                    log(args, msg -> {
                        msg.appendLine("Server invoke %s.%s [%s]-> %s", contractInstance.getClass().getSimpleName(), pack.methodName,
                                s.getConfig().getListenPort(), Sockets.toString(e.getClient().getRemoteEndpoint()));
                        msg.appendLine("Request:\t%s", toJsonString(args.getParameters()));
                        if (args.getError() != null) {
                            msg.appendLine("Response:\t%s", pack.errorMessage);
                        } else {
                            msg.appendLine("Response:\t%s", toJsonString(args.getReturnValue()));
                        }
                    });
                }
                Arrays.fill(pack.parameters, null);
                s.send(e.getClient(), pack);
            });
            bean.server.start();
            return bean;
        }).server;
    }

    private static void broadcast(RpcServer s, EventMessage p, ServerBean.EventBean eventBean, ServerBean.EventContext context) {
        List allow = s.getConfig().getEventBroadcastVersions();
        EventMessage pack = new EventMessage(p.eventName, EventFlag.BROADCAST);
        pack.eventArgs = context.computedArgs;
        tryAs(pack.eventArgs, RemotingEventArgs.class, x -> x.setBroadcastVersions(allow));
        for (RpcClientMeta client : eventBean.subscribe) {
            if (!client.isConnected()) {
                eventBean.subscribe.remove(client);
                continue;
            }
            if (client == context.computingClient
                    || (!allow.isEmpty() && !allow.contains(client.getHandshakePacket().getEventVersion()))) {
                continue;
            }

            s.send(client, pack);
            log.info("serverSide event {} {} -> BROADCAST", pack.eventName, client.getRemoteEndpoint());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy