├── README.md ├── images └── rpc-architure.png ├── lrpc-common ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── cccll │ ├── entity │ └── RpcServiceProperties.java │ ├── enumeration │ ├── RpcConfigProperties.java │ ├── RpcErrorMessage.java │ ├── RpcMessageType.java │ └── RpcResponseCode.java │ ├── exception │ ├── RpcException.java │ └── SerializeException.java │ ├── extension │ ├── ExtensionLoader.java │ └── SPI.java │ ├── factory │ └── SingletonFactory.java │ └── utils │ ├── Holder.java │ ├── concurrent │ └── threadpool │ │ ├── CustomThreadPoolConfig.java │ │ └── ThreadPoolFactoryUtils.java │ └── file │ └── PropertiesFileUtils.java ├── lrpc-demo-client ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── cccll │ │ ├── HelloController.java │ │ ├── NettyClientMain.java │ │ └── RpcFrameworkSimpleClientMain.java │ └── resources │ └── rpc.properties ├── lrpc-demo-interface ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── cccll │ ├── Hello.java │ └── HelloService.java ├── lrpc-demo-server ├── pom.xml └── src │ └── main │ ├── java │ ├── NettyServerMain.java │ ├── RpcFrameworkSimpleServerMain.java │ └── com │ │ └── cccll │ │ └── serviceimpl │ │ ├── HelloServiceImpl.java │ │ └── HelloServiceImpl2.java │ └── resources │ └── rpc.properties ├── lrpc-framework ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── cccll │ │ ├── annotation │ │ ├── RpcReference.java │ │ ├── RpcScan.java │ │ └── RpcService.java │ │ ├── config │ │ └── CustomShutdownHook.java │ │ ├── loadbalance │ │ ├── AbstractLoadBalance.java │ │ ├── LoadBalance.java │ │ └── RandomLoadBalance.java │ │ ├── provider │ │ ├── ServiceProvider.java │ │ └── ServiceProviderImpl.java │ │ ├── proxy │ │ └── RpcClientProxy.java │ │ ├── registry │ │ ├── ServiceDiscovery.java │ │ ├── ServiceRegistry.java │ │ └── zk │ │ │ ├── ZkServiceDiscovery.java │ │ │ ├── ZkServiceRegistry.java │ │ │ └── util │ │ │ └── CuratorUtils.java │ │ ├── remoting │ │ ├── dto │ │ │ ├── RpcMessageChecker.java │ │ │ ├── RpcRequest.java │ │ │ └── RpcResponse.java │ │ ├── handler │ │ │ └── RpcRequestHandler.java │ │ └── transport │ │ │ ├── ClientTransport.java │ │ │ ├── netty │ │ │ ├── client │ │ │ │ ├── ChannelProvider.java │ │ │ │ ├── NettyClient.java │ │ │ │ ├── NettyClientHandler.java │ │ │ │ ├── NettyClientTransport.java │ │ │ │ └── UnprocessedRequests.java │ │ │ ├── codec │ │ │ │ └── kyro │ │ │ │ │ ├── NettyKryoDecoder.java │ │ │ │ │ └── NettyKryoEncoder.java │ │ │ └── server │ │ │ │ ├── NettyServer.java │ │ │ │ └── NettyServerHandler.java │ │ │ └── socket │ │ │ ├── SocketRpcClient.java │ │ │ ├── SocketRpcRequestHandlerRunnable.java │ │ │ └── SocketRpcServer.java │ │ ├── serialize │ │ ├── Serializer.java │ │ └── kyro │ │ │ └── KryoSerializer.java │ │ └── spring │ │ ├── CustomScanner.java │ │ ├── CustomScannerRegistrar.java │ │ └── SpringBeanPostProcessor.java │ └── resources │ └── META-INF │ └── extensions │ ├── com.cccll.registry.ServiceDiscovery │ ├── com.cccll.registry.ServiceRegistry │ ├── com.cccll.remoting.transport.ClientTransport │ └── com.cccll.serialize.Serializer └── pom.xml /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | lrpc 是一种高性能,高可扩展性的Java RPC框架,RPC,即 Remote Procedure Call(远程过程调用),调用远程计算机上的服务,就像调用本地 3 | 方法一样。RPC可以很好的解耦系统,对于服务开发者来说,屏蔽了底层网络通信细节,从而更专注于业务自身逻辑实现。 4 | 5 | 6 | ### lrpc架构 7 | 8 | 此项目目前架构如图: 9 | 10 | 11 | ![](./images/rpc-architure.png) 12 | 13 | 服务提供端 Server 向注册中心注册服务,服务消费者 Client 通过注册中心拿到服务相关信息,然后再通过网络请求服务提供端 Server。 14 | 15 | 16 | 17 | ### 特性 18 | 1. 注释较为详细,并在注释中添加了些知识点,适合阅读学习(实际项目上不建议写这么啰嗦的注释,代码尽量表明自己用意就好。) 19 | 2. 客户端使用TCP长连接(在多次调用共享连接) 20 | 3. TCP心跳连接检测 21 | 4. 支持多种网络传输方式(支持基于javaIO方式的阻塞式传输,支持基于Netty的Reactor模型,推荐用后者) 22 | 5. 支持流控 23 | 6. 异步调用,支持Future机制,使用CompletableFuture接受服务提供端返回结果,优化了接收服务端返回值的过程 24 | 7. 支持不同的load balance策略(目前实现了一种随机方式,其他方式待实现) 25 | 8. 支持不同的序列化/反序列化(目前实现了Kryo方式,其他方式待实现) 26 | 9. 运用了SPI机制,具有高扩展性,同时降低耦合性,可根据需要轻松改进功能 27 | 10. 支持使用注解注册服务 28 | 11. 支持使用注解进行服务消费 29 | 30 | 31 | ### 待完善&添加的点 32 | ... 33 | -------------------------------------------------------------------------------- /images/rpc-architure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCCLL/lrpc/9574ae4e4cb0656d45df1c04ee5b2e6ddd30d6b6/images/rpc-architure.png -------------------------------------------------------------------------------- /lrpc-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | lrpc 7 | com.cccll 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | lrpc-common 13 | 14 | 15 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/entity/RpcServiceProperties.java: -------------------------------------------------------------------------------- 1 | package com.cccll.entity; 2 | 3 | import lombok.*; 4 | 5 | 6 | @AllArgsConstructor 7 | @NoArgsConstructor 8 | @Getter 9 | @Setter 10 | @Builder 11 | @ToString 12 | public class RpcServiceProperties { 13 | /** 14 | * 服务版本 15 | */ 16 | private String version; 17 | /** 18 | * 当接口有多个实现类时,按组进行区分 19 | */ 20 | private String group; 21 | private String serviceName; 22 | 23 | public String toRpcServiceName() { 24 | return this.getServiceName() + this.getGroup() + this.getVersion(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/enumeration/RpcConfigProperties.java: -------------------------------------------------------------------------------- 1 | package com.cccll.enumeration; 2 | 3 | public enum RpcConfigProperties { 4 | RPC_CONFIG_PATH("rpc.properties"), 5 | ZK_ADDRESS("rpc.zookeeper.address"); 6 | 7 | private final String propertyValue; 8 | 9 | 10 | RpcConfigProperties(String propertyValue) { 11 | this.propertyValue = propertyValue; 12 | } 13 | 14 | public String getPropertyValue() { 15 | return propertyValue; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/enumeration/RpcErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.cccll.enumeration; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | 7 | 8 | /** 9 | * @author cccll 10 | */ 11 | @AllArgsConstructor 12 | @Getter 13 | @ToString 14 | public enum RpcErrorMessage { 15 | CLIENT_CONNECT_SERVER_FAILURE("客户端连接服务端失败"), 16 | SERVICE_INVOCATION_FAILURE("服务调用失败"), 17 | SERVICE_CAN_NOT_BE_FOUND("没有找到指定的服务"), 18 | SERVICE_NOT_IMPLEMENT_ANY_INTERFACE("注册的服务没有实现任何接口"), 19 | REQUEST_NOT_MATCH_RESPONSE("返回结果错误!请求和返回的相应不匹配"); 20 | 21 | private final String message; 22 | } 23 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/enumeration/RpcMessageType.java: -------------------------------------------------------------------------------- 1 | package com.cccll.enumeration; 2 | 3 | /** 4 | * @author cccll 5 | */ 6 | public enum RpcMessageType { 7 | HEART_BEAT 8 | } 9 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/enumeration/RpcResponseCode.java: -------------------------------------------------------------------------------- 1 | package com.cccll.enumeration; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | 7 | /** 8 | * @author cccll 9 | */ 10 | @AllArgsConstructor 11 | @Getter 12 | @ToString 13 | public enum RpcResponseCode { 14 | SUCCESS(200, "远程调用成功"), 15 | FAIL(500, "远程调用失败"); 16 | 17 | private final int code; 18 | 19 | private final String message; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/exception/RpcException.java: -------------------------------------------------------------------------------- 1 | package com.cccll.exception; 2 | 3 | import com.cccll.enumeration.RpcErrorMessage; 4 | 5 | public class RpcException extends RuntimeException { 6 | public RpcException(RpcErrorMessage rpcErrorMessage, String detail) { 7 | super(rpcErrorMessage.getMessage() + ":" + detail); 8 | } 9 | 10 | public RpcException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | 14 | public RpcException(RpcErrorMessage rpcErrorMessage) { 15 | super(rpcErrorMessage.getMessage()); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/exception/SerializeException.java: -------------------------------------------------------------------------------- 1 | package com.cccll.exception; 2 | 3 | 4 | 5 | public class SerializeException extends RuntimeException{ 6 | public SerializeException(String message) { 7 | super(message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/extension/ExtensionLoader.java: -------------------------------------------------------------------------------- 1 | package com.cccll.extension; 2 | 3 | import com.cccll.utils.Holder; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.net.URL; 11 | import java.util.Enumeration; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | 16 | import static java.nio.charset.StandardCharsets.UTF_8; 17 | 18 | /** 19 | * refer to dubbo spi: https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html 20 | */ 21 | @Slf4j 22 | public final class ExtensionLoader { 23 | 24 | private static final String SERVICE_DIRECTORY = "META-INF/extensions/"; 25 | private static final Map, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); 26 | private static final Map, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); 27 | 28 | private final Class type; 29 | private final Map> cachedInstances = new ConcurrentHashMap<>(); 30 | private final Holder>> cachedClasses = new Holder<>(); 31 | 32 | private ExtensionLoader(Class type) { 33 | this.type = type; 34 | } 35 | 36 | public static ExtensionLoader getExtensionLoader(Class type) { 37 | if (type == null) { 38 | throw new IllegalArgumentException("Extension type should not be null."); 39 | } 40 | if (!type.isInterface()) { 41 | throw new IllegalArgumentException("Extension type must be an interface."); 42 | } 43 | if (type.getAnnotation(SPI.class) == null) { 44 | throw new IllegalArgumentException("Extension type must be annotated by @SPI"); 45 | } 46 | // 首先从缓存中获取,如果没有命中,就创建一个 47 | ExtensionLoader extensionLoader = (ExtensionLoader) EXTENSION_LOADERS.get(type); 48 | if (extensionLoader == null) { 49 | EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); 50 | extensionLoader = (ExtensionLoader) EXTENSION_LOADERS.get(type); 51 | } 52 | return extensionLoader; 53 | } 54 | 55 | public T getExtension(String name) { 56 | if (name == null || name.isEmpty()) { 57 | throw new IllegalArgumentException("Extension name should not be null or empty."); 58 | } 59 | // 首先从缓存中获取,如果没有命中,就创建一个 60 | Holder holder = cachedInstances.get(name); 61 | if (holder == null) { 62 | cachedInstances.putIfAbsent(name, new Holder<>()); 63 | holder = cachedInstances.get(name); 64 | } 65 | // 如果不存在实例,则创建一个单例 66 | Object instance = holder.get(); 67 | if (instance == null) { 68 | synchronized (holder) { 69 | instance = holder.get(); 70 | if (instance == null) { 71 | instance = createExtension(name); 72 | holder.set(instance); 73 | } 74 | } 75 | } 76 | return (T) instance; 77 | } 78 | 79 | private T createExtension(String name) { 80 | // 从文件中加载类型为T的所有扩展类,并通过名称获得特定的一个 81 | Class clazz = getExtensionClasses().get(name); 82 | if (clazz == null) { 83 | throw new RuntimeException("没有此名的扩展类 " + name); 84 | } 85 | T instance = (T) EXTENSION_INSTANCES.get(clazz); 86 | if (instance == null) { 87 | try { 88 | EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); 89 | instance = (T) EXTENSION_INSTANCES.get(clazz); 90 | } catch (Exception e) { 91 | log.error(e.getMessage()); 92 | throw new RuntimeException("创建扩展类的实例失败 " + clazz); 93 | } 94 | } 95 | return instance; 96 | } 97 | 98 | private Map> getExtensionClasses() { 99 | // 从缓存中获取加载的扩展类 100 | Map> classes = cachedClasses.get(); 101 | // double check 102 | if (classes == null) { 103 | synchronized (cachedClasses) { 104 | classes = cachedClasses.get(); 105 | if (classes == null) { 106 | classes = new HashMap<>(); 107 | // 从扩展文件加载所有扩展 108 | loadDirectory(classes); 109 | cachedClasses.set(classes); 110 | } 111 | } 112 | } 113 | return classes; 114 | } 115 | 116 | private void loadDirectory(Map> extensionClasses) { 117 | String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName(); 118 | try { 119 | Enumeration urls; 120 | ClassLoader classLoader = ExtensionLoader.class.getClassLoader(); 121 | urls = classLoader.getResources(fileName); 122 | if (urls != null) { 123 | while (urls.hasMoreElements()) { 124 | URL resourceUrl = urls.nextElement(); 125 | loadResource(extensionClasses, classLoader, resourceUrl); 126 | } 127 | } 128 | } catch (IOException e) { 129 | log.error(e.getMessage()); 130 | } 131 | } 132 | 133 | private void loadResource(Map> extensionClasses, ClassLoader classLoader, URL resourceUrl) { 134 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) { 135 | String line; 136 | // 读取每一行 137 | while ((line = reader.readLine()) != null) { 138 | // 获取注释位置 139 | final int ci = line.indexOf('#'); 140 | if (ci >= 0) { 141 | // #之后的字符串是注释,因此我们将其忽略 142 | line = line.substring(0, ci); 143 | } 144 | line = line.trim(); 145 | if (line.length() > 0) { 146 | try { 147 | final int ei = line.indexOf('='); 148 | String name = line.substring(0, ei).trim(); 149 | String clazzName = line.substring(ei + 1).trim(); 150 | // SPI使用键值对,因此两者都不能为空 151 | if (name.length() > 0 && clazzName.length() > 0) { 152 | //使用此类加载器加载此类,获得class 153 | Class clazz = classLoader.loadClass(clazzName); 154 | extensionClasses.put(name, clazz); 155 | } 156 | } catch (ClassNotFoundException e) { 157 | log.error(e.getMessage()); 158 | } 159 | } 160 | 161 | } 162 | } catch (IOException e) { 163 | log.error(e.getMessage()); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/extension/SPI.java: -------------------------------------------------------------------------------- 1 | package com.cccll.extension; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Documented 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Target(ElementType.TYPE) 8 | public @interface SPI { 9 | } 10 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/factory/SingletonFactory.java: -------------------------------------------------------------------------------- 1 | package com.cccll.factory; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * 获取单例对象的工厂类 9 | * 10 | */ 11 | public final class SingletonFactory { 12 | private static final Map OBJECT_MAP = new HashMap<>(); 13 | 14 | private SingletonFactory() { 15 | } 16 | 17 | public static T getInstance(Class c) { 18 | String key = c.toString(); 19 | Object instance = null; 20 | if (instance == null) { 21 | synchronized (SingletonFactory.class) { 22 | instance = OBJECT_MAP.get(key); 23 | if (instance == null) { 24 | try { 25 | instance = c.getDeclaredConstructor().newInstance(); 26 | OBJECT_MAP.put(key, instance); 27 | } catch (IllegalAccessException | InstantiationException e) { 28 | throw new RuntimeException(e.getMessage(), e); 29 | } catch (NoSuchMethodException | InvocationTargetException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | } 34 | } 35 | 36 | return c.cast(instance); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/utils/Holder.java: -------------------------------------------------------------------------------- 1 | package com.cccll.utils; 2 | 3 | public class Holder { 4 | 5 | private volatile T value; 6 | 7 | public T get() { 8 | return value; 9 | } 10 | 11 | public void set(T value) { 12 | this.value = value; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/utils/concurrent/threadpool/CustomThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package com.cccll.utils.concurrent.threadpool; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.concurrent.ArrayBlockingQueue; 7 | import java.util.concurrent.BlockingQueue; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * 线程池自定义配置类,可自行根据业务场景修改配置参数。 12 | * 13 | */ 14 | @Setter 15 | @Getter 16 | public class CustomThreadPoolConfig { 17 | /** 18 | * 线程池默认参数 19 | */ 20 | private static final int DEFAULT_CORE_POOL_SIZE = 10; 21 | private static final int DEFAULT_MAXIMUM_POOL_SIZE_SIZE = 100; 22 | private static final int DEFAULT_KEEP_ALIVE_TIME = 1; 23 | private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MINUTES; 24 | private static final int DEFAULT_BLOCKING_QUEUE_CAPACITY = 100; 25 | private static final int BLOCKING_QUEUE_CAPACITY = 100; 26 | /** 27 | * 可配置参数 28 | */ 29 | private int corePoolSize = DEFAULT_CORE_POOL_SIZE; 30 | private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE_SIZE; 31 | private long keepAliveTime = DEFAULT_KEEP_ALIVE_TIME; 32 | private TimeUnit unit = DEFAULT_TIME_UNIT; 33 | // 使用有界队列 34 | private BlockingQueue workQueue = new ArrayBlockingQueue<>(BLOCKING_QUEUE_CAPACITY); 35 | } 36 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/utils/concurrent/threadpool/ThreadPoolFactoryUtils.java: -------------------------------------------------------------------------------- 1 | package com.cccll.utils.concurrent.threadpool; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.*; 8 | 9 | /** 10 | * 创建 ThreadPool(线程池) 的工具类. 11 | * 12 | * @author cccll 13 | * @createTime 2020年07月28日 23:00:00 14 | */ 15 | @Slf4j 16 | public final class ThreadPoolFactoryUtils { 17 | 18 | /** 19 | * 通过 threadNamePrefix 来区分不同线程池(我们可以把相同 threadNamePrefix 的线程池看作是为同一业务场景服务)。 20 | * TODO :通过信号量机制( {@link Semaphore} 满足条件)限制创建的线程池数量(线程池和线程不是越多越好) 21 | * key: threadNamePrefix 22 | * value: threadPool 23 | */ 24 | private static final Map THREAD_POOLS = new ConcurrentHashMap<>(); 25 | 26 | private ThreadPoolFactoryUtils() { 27 | 28 | } 29 | 30 | public static ExecutorService createCustomThreadPoolIfAbsent(String threadNamePrefix) { 31 | CustomThreadPoolConfig customThreadPoolConfig = new CustomThreadPoolConfig(); 32 | //缺省为非守护线程 33 | return createCustomThreadPoolIfAbsent(customThreadPoolConfig, threadNamePrefix, false); 34 | } 35 | 36 | public static ExecutorService createCustomThreadPoolIfAbsent(String threadNamePrefix, CustomThreadPoolConfig customThreadPoolConfig) { 37 | return createCustomThreadPoolIfAbsent(customThreadPoolConfig, threadNamePrefix, false); 38 | } 39 | 40 | public static ExecutorService createCustomThreadPoolIfAbsent(CustomThreadPoolConfig customThreadPoolConfig, String threadNamePrefix, Boolean daemon) { 41 | ExecutorService threadPool = THREAD_POOLS.computeIfAbsent(threadNamePrefix, k -> createThreadPool(customThreadPoolConfig, threadNamePrefix, daemon)); 42 | // 如果 threadPool 被 shutdown 的话就重新创建一个 43 | if (threadPool.isShutdown() || threadPool.isTerminated()) { 44 | THREAD_POOLS.remove(threadNamePrefix); 45 | threadPool = createThreadPool(customThreadPoolConfig, threadNamePrefix, daemon); 46 | THREAD_POOLS.put(threadNamePrefix, threadPool); 47 | } 48 | return threadPool; 49 | } 50 | 51 | /** 52 | * shutDown 所有线程池 53 | */ 54 | public static void shutDownAllThreadPool() { 55 | log.info("调用 shutDownAllThreadPool 方法"); 56 | THREAD_POOLS.entrySet().parallelStream().forEach(entry -> { 57 | ExecutorService executorService = entry.getValue(); 58 | executorService.shutdown(); 59 | log.info("shut down thread pool [{}] [{}]", entry.getKey(), executorService.isTerminated()); 60 | try { 61 | executorService.awaitTermination(10, TimeUnit.SECONDS); 62 | } catch (InterruptedException e) { 63 | log.error("Thread pool never terminated"); 64 | executorService.shutdownNow(); 65 | } 66 | }); 67 | } 68 | 69 | private static ExecutorService createThreadPool(CustomThreadPoolConfig customThreadPoolConfig, String threadNamePrefix, Boolean daemon) { 70 | ThreadFactory threadFactory = createThreadFactory(threadNamePrefix, daemon); 71 | return new ThreadPoolExecutor(customThreadPoolConfig.getCorePoolSize(), customThreadPoolConfig.getMaximumPoolSize(), 72 | customThreadPoolConfig.getKeepAliveTime(), customThreadPoolConfig.getUnit(), customThreadPoolConfig.getWorkQueue(), 73 | threadFactory); 74 | } 75 | 76 | /** 77 | * 创建 ThreadFactory 。如果threadNamePrefix不为空则使用自建ThreadFactory,否则使用defaultThreadFactory 78 | * 为不同的线程池提供不同的ThreadFactory,可为不同的线程池的线程设置易读的名称,方便调试, 79 | * ThreadFactory也可为线程设置状态(可设置是否为守护线程,线程优先级等), 80 | * 使用ThreadFactory创建线程也是编程规范所推荐的 81 | * 82 | * @param threadNamePrefix 作为创建的线程名字的前缀 83 | * @param daemon 指定是否为 Daemon Thread(守护线程) 84 | * @return ThreadFactory 85 | */ 86 | private static ThreadFactory createThreadFactory(String threadNamePrefix, Boolean daemon) { 87 | if (threadNamePrefix != null) { 88 | if (daemon != null) { 89 | return new ThreadFactoryBuilder() 90 | .setNameFormat(threadNamePrefix + "-%d") 91 | .setDaemon(daemon).build(); 92 | } else { 93 | return new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + "-%d").build(); 94 | } 95 | } 96 | return Executors.defaultThreadFactory(); 97 | } 98 | 99 | /** 100 | * 打印线程池的状态 101 | * 102 | * @param threadPool 线程池对象 103 | */ 104 | public static void printThreadPoolStatus(ThreadPoolExecutor threadPool) { 105 | ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, createThreadFactory("print-thread-pool-status", false)); 106 | scheduledExecutorService.scheduleAtFixedRate(() -> { 107 | log.info("============ThreadPool Status============="); 108 | log.info("ThreadPool Size: [{}]", threadPool.getPoolSize()); 109 | log.info("Active Threads: [{}]", threadPool.getActiveCount()); 110 | log.info("Number of Tasks : [{}]", threadPool.getCompletedTaskCount()); 111 | log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size()); 112 | log.info("==========================================="); 113 | }, 0, 1, TimeUnit.SECONDS); 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /lrpc-common/src/main/java/com/cccll/utils/file/PropertiesFileUtils.java: -------------------------------------------------------------------------------- 1 | package com.cccll.utils.file; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.net.URL; 8 | import java.util.Properties; 9 | @Slf4j 10 | public class PropertiesFileUtils { 11 | private PropertiesFileUtils() { 12 | } 13 | 14 | public static Properties readPropertiesFile(String fileName) { 15 | URL url = Thread.currentThread().getContextClassLoader().getResource(""); 16 | String rpcConfigPath = ""; 17 | if (url != null) { 18 | rpcConfigPath = url.getPath() + fileName; 19 | } 20 | Properties properties = null; 21 | try (FileInputStream fileInputStream = new FileInputStream(rpcConfigPath)) { 22 | properties = new Properties(); 23 | properties.load(fileInputStream); 24 | } catch (IOException e) { 25 | log.error("读取属性文件时发生异常 [{}]", fileName); 26 | } 27 | return properties; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lrpc-demo-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | lrpc 7 | com.cccll 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | lrpc-demo-client 13 | 14 | 15 | 16 | com.cccll 17 | lrpc-framework 18 | ${project.version} 19 | compile 20 | 21 | 22 | com.cccll 23 | lrpc-demo-interface 24 | ${project.version} 25 | compile 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /lrpc-demo-client/src/main/java/com/cccll/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.cccll; 2 | 3 | import com.cccll.annotation.RpcReference; 4 | import org.springframework.stereotype.Component; 5 | 6 | 7 | @Component 8 | public class HelloController { 9 | 10 | @RpcReference(version = "version2", group = "test2") 11 | private HelloService helloService; 12 | 13 | public void test() throws InterruptedException { 14 | String hello = this.helloService.hello(new Hello("111", "222")); 15 | //如需使用 assert 断言,需要在 VM options 添加参数:-ea 16 | assert "Hello description is 222".equals(hello); 17 | Thread.sleep(10000); 18 | for (int i = 0; i < 1000; i++) { 19 | Thread.sleep(1000); 20 | helloService.hello(new Hello("111", "222")); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lrpc-demo-client/src/main/java/com/cccll/NettyClientMain.java: -------------------------------------------------------------------------------- 1 | package com.cccll; 2 | 3 | 4 | import com.cccll.annotation.RpcScan; 5 | import com.cccll.entity.RpcServiceProperties; 6 | import com.cccll.proxy.RpcClientProxy; 7 | import com.cccll.remoting.transport.ClientTransport; 8 | import com.cccll.remoting.transport.netty.client.NettyClientTransport; 9 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 10 | @RpcScan(basePackage = {"com.cccll"}) 11 | public class NettyClientMain { 12 | public static void main(String[] args) throws InterruptedException { 13 | // ClientTransport rpcClient = new NettyClientTransport(); 14 | // RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder() 15 | // .group("test1").version("version1").build(); 16 | // RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceProperties); 17 | // HelloService helloService = rpcClientProxy.getProxy(HelloService.class); 18 | // String hello = helloService.hello(new Hello("111", "222")); 19 | // //如需使用 assert 断言,需要在 VM options 添加参数:-ea 20 | // assert "Hello description is 222".equals(hello); 21 | // Thread.sleep(2000); 22 | // for (int i = 0; i < 100; i++) { 23 | // Thread.sleep(2000); 24 | // System.out.println(helloService.hello(new Hello("111", "222"))); 25 | // 26 | // } 27 | AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(NettyClientMain.class); 28 | HelloController helloController = (HelloController) applicationContext.getBean("helloController"); 29 | 30 | helloController.test(); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lrpc-demo-client/src/main/java/com/cccll/RpcFrameworkSimpleClientMain.java: -------------------------------------------------------------------------------- 1 | package com.cccll; 2 | 3 | import com.cccll.entity.RpcServiceProperties; 4 | import com.cccll.extension.ExtensionLoader; 5 | import com.cccll.proxy.RpcClientProxy; 6 | import com.cccll.remoting.transport.ClientTransport; 7 | import com.cccll.remoting.transport.socket.SocketRpcClient; 8 | 9 | public class RpcFrameworkSimpleClientMain { 10 | public static void main(String[] args) { 11 | ClientTransport clientTransport = ExtensionLoader.getExtensionLoader(ClientTransport.class).getExtension("socketRpcClient");; 12 | RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder() 13 | .group("test2").version("version2").build(); 14 | RpcClientProxy rpcClientProxy = new RpcClientProxy(clientTransport, rpcServiceProperties); 15 | HelloService helloService = rpcClientProxy.getProxy(HelloService.class); 16 | String hello = helloService.hello(new Hello("111", "222")); 17 | System.out.println(hello); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lrpc-demo-client/src/main/resources/rpc.properties: -------------------------------------------------------------------------------- 1 | rpc.zookeeper.address=127.0.0.1:2181 -------------------------------------------------------------------------------- /lrpc-demo-interface/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | lrpc 7 | com.cccll 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | lrpc-demo-interface 13 | 14 | 15 | -------------------------------------------------------------------------------- /lrpc-demo-interface/src/main/java/com/cccll/Hello.java: -------------------------------------------------------------------------------- 1 | package com.cccll; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @Getter 16 | @Setter 17 | @Builder 18 | @ToString 19 | public class Hello implements Serializable { 20 | private String message; 21 | private String description; 22 | } 23 | -------------------------------------------------------------------------------- /lrpc-demo-interface/src/main/java/com/cccll/HelloService.java: -------------------------------------------------------------------------------- 1 | package com.cccll; 2 | 3 | public interface HelloService { 4 | String hello(Hello hello); 5 | } -------------------------------------------------------------------------------- /lrpc-demo-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | lrpc 7 | com.cccll 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | lrpc-demo-server 13 | 14 | 15 | 16 | com.cccll 17 | lrpc-demo-interface 18 | ${project.version} 19 | 20 | 21 | com.cccll 22 | lrpc-framework 23 | ${project.version} 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /lrpc-demo-server/src/main/java/NettyServerMain.java: -------------------------------------------------------------------------------- 1 | import com.cccll.HelloService; 2 | import com.cccll.annotation.RpcScan; 3 | import com.cccll.entity.RpcServiceProperties; 4 | import com.cccll.remoting.transport.netty.server.NettyServer; 5 | import com.cccll.serviceimpl.HelloServiceImpl2; 6 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 7 | 8 | /** 9 | * Server: 通过 @RpcService 注解自动注册服务 10 | * 11 | */ 12 | @RpcScan(basePackage = {"com.cccll"}) 13 | public class NettyServerMain { 14 | public static void main(String[] args) { 15 | // 通过注解注册服务 16 | AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(NettyServerMain.class); 17 | NettyServer nettyServer = (NettyServer) annotationConfigApplicationContext.getBean("nettyServer"); 18 | // 手动注册服务 19 | HelloService helloService2 = new HelloServiceImpl2(); 20 | RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder() 21 | .group("test2").version("version2").build(); 22 | nettyServer.registerService(helloService2, rpcServiceProperties); 23 | nettyServer.start(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lrpc-demo-server/src/main/java/RpcFrameworkSimpleServerMain.java: -------------------------------------------------------------------------------- 1 | import com.cccll.HelloService; 2 | import com.cccll.entity.RpcServiceProperties; 3 | import com.cccll.remoting.transport.socket.SocketRpcServer; 4 | import com.cccll.serviceimpl.HelloServiceImpl; 5 | 6 | public class RpcFrameworkSimpleServerMain { 7 | public static void main(String[] args) { 8 | HelloService helloService = new HelloServiceImpl(); 9 | SocketRpcServer socketRpcServer = new SocketRpcServer(); 10 | RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder() 11 | .group("test2").version("version2").build(); 12 | socketRpcServer.registerService(helloService, rpcServiceProperties); 13 | socketRpcServer.start(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lrpc-demo-server/src/main/java/com/cccll/serviceimpl/HelloServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cccll.serviceimpl; 2 | 3 | import com.cccll.Hello; 4 | import com.cccll.HelloService; 5 | import com.cccll.annotation.RpcService; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | 9 | @Slf4j 10 | @RpcService(group = "test1", version = "version1") 11 | public class HelloServiceImpl implements HelloService { 12 | 13 | static { 14 | System.out.println("HelloServiceImpl被创建"); 15 | } 16 | 17 | @Override 18 | public String hello(Hello hello) { 19 | log.info("HelloServiceImpl收到: {}.", hello.getMessage()); 20 | String result = "Hello description is " + hello.getDescription(); 21 | log.info("HelloServiceImpl返回: {}.", result); 22 | return result; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lrpc-demo-server/src/main/java/com/cccll/serviceimpl/HelloServiceImpl2.java: -------------------------------------------------------------------------------- 1 | package com.cccll.serviceimpl; 2 | 3 | import com.cccll.Hello; 4 | import com.cccll.HelloService; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | 8 | @Slf4j 9 | public class HelloServiceImpl2 implements HelloService { 10 | 11 | static { 12 | System.out.println("HelloServiceImpl2被创建"); 13 | } 14 | 15 | @Override 16 | public String hello(Hello hello) { 17 | log.info("HelloServiceImpl2收到: {}.", hello.getMessage()); 18 | String result = "Hello description is " + hello.getDescription(); 19 | log.info("HelloServiceImpl2返回: {}.", result); 20 | return result; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lrpc-demo-server/src/main/resources/rpc.properties: -------------------------------------------------------------------------------- 1 | rpc.zookeeper.address=127.0.0.1:2181 -------------------------------------------------------------------------------- /lrpc-framework/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | lrpc 7 | com.cccll 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | lrpc-framework 13 | 14 | 15 | 16 | com.cccll 17 | lrpc-common 18 | ${project.version} 19 | 20 | 21 | io.netty 22 | netty-all 23 | ${netty.version} 24 | 25 | 26 | com.esotericsoftware 27 | kryo 28 | ${kryo.version} 29 | 30 | 31 | 32 | org.apache.curator 33 | curator-framework 34 | ${curator-version} 35 | 36 | 37 | org.apache.curator 38 | curator-recipes 39 | ${curator-version} 40 | 41 | 42 | org.springframework 43 | spring-context 44 | ${spring.version} 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/annotation/RpcReference.java: -------------------------------------------------------------------------------- 1 | package com.cccll.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Inherited; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * 标注此注解,可自动装配服务实现类 12 | * 13 | * @author cccll 14 | */ 15 | @Documented 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.FIELD}) 18 | @Inherited 19 | public @interface RpcReference { 20 | 21 | /** 22 | * Service version, default value is empty string 23 | */ 24 | String version() default ""; 25 | 26 | /** 27 | * Service group, default value is empty string 28 | */ 29 | String group() default ""; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/annotation/RpcScan.java: -------------------------------------------------------------------------------- 1 | package com.cccll.annotation; 2 | 3 | 4 | import com.cccll.spring.CustomScannerRegistrar; 5 | import org.springframework.context.annotation.Import; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * 扫描自定义注解的注解,模仿@ComponentScan 15 | * 16 | * 17 | * @author cccll 18 | */ 19 | @Target({ElementType.TYPE, ElementType.METHOD}) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | // @Import仅仅是引入,不会被Spring容器管理 22 | @Import(CustomScannerRegistrar.class) 23 | @Documented 24 | public @interface RpcScan { 25 | 26 | String[] basePackage(); 27 | 28 | } -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/annotation/RpcService.java: -------------------------------------------------------------------------------- 1 | package com.cccll.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Inherited; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * RpcService 注解,标记在服务实现类上 12 | * 13 | * @author cccll 14 | */ 15 | @Documented 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.TYPE}) 18 | @Inherited 19 | public @interface RpcService { 20 | 21 | /** 22 | * Service version, 默认值为空字符串 23 | */ 24 | String version() default ""; 25 | 26 | /** 27 | * Service group, 默认值为空字符串 28 | */ 29 | String group() default ""; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/config/CustomShutdownHook.java: -------------------------------------------------------------------------------- 1 | package com.cccll.config; 2 | 3 | import com.cccll.registry.zk.util.CuratorUtils; 4 | import com.cccll.utils.concurrent.threadpool.ThreadPoolFactoryUtils; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * 当服务端(provider)关闭的时候做一些事情比如取消注册所有服务 9 | * 10 | * @author cccll 11 | */ 12 | @Slf4j 13 | public class CustomShutdownHook { 14 | private static final CustomShutdownHook CUSTOM_SHUTDOWN_HOOK = new CustomShutdownHook(); 15 | 16 | public static CustomShutdownHook getCustomShutdownHook() { 17 | return CUSTOM_SHUTDOWN_HOOK; 18 | } 19 | 20 | public void clearAll() { 21 | log.info("addShutdownHook for clearAll"); 22 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 23 | //清除注册的所有服务 24 | CuratorUtils.clearRegistry(CuratorUtils.getZkClient()); 25 | //shutDown所有的线程池 26 | ThreadPoolFactoryUtils.shutDownAllThreadPool(); 27 | })); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/loadbalance/AbstractLoadBalance.java: -------------------------------------------------------------------------------- 1 | package com.cccll.loadbalance; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 负载均衡策略的抽象类 7 | * 8 | */ 9 | public abstract class AbstractLoadBalance implements LoadBalance { 10 | @Override 11 | public String selectServiceAddress(List serviceAddresses) { 12 | if (serviceAddresses == null || serviceAddresses.size() == 0) { 13 | return null; 14 | } 15 | if (serviceAddresses.size() == 1) { 16 | return serviceAddresses.get(0); 17 | } 18 | return doSelect(serviceAddresses); 19 | } 20 | 21 | protected abstract String doSelect(List serviceAddresses); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/loadbalance/LoadBalance.java: -------------------------------------------------------------------------------- 1 | package com.cccll.loadbalance; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 负载均衡策略的接口 7 | * 8 | * @author cccll 9 | */ 10 | public interface LoadBalance { 11 | /** 12 | * 在已有服务提供地址列表中选择一个 13 | * 14 | * @param serviceAddresses 服务地址列表 15 | * @return 目标服务地址 16 | */ 17 | String selectServiceAddress(List serviceAddresses); 18 | } 19 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/loadbalance/RandomLoadBalance.java: -------------------------------------------------------------------------------- 1 | package com.cccll.loadbalance; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | 6 | /** 7 | * 实现随机负载均衡策略 8 | * 9 | * @author cccll 10 | * @createTime 2020年07月25日 21:20:00 11 | */ 12 | public class RandomLoadBalance extends AbstractLoadBalance { 13 | @Override 14 | protected String doSelect(List serviceAddresses) { 15 | Random random = new Random(); 16 | return serviceAddresses.get(random.nextInt(serviceAddresses.size())); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/provider/ServiceProvider.java: -------------------------------------------------------------------------------- 1 | package com.cccll.provider; 2 | 3 | import com.cccll.entity.RpcServiceProperties; 4 | 5 | 6 | /** 7 | * 保存和提供服务实例对象。服务端使用。 8 | * 9 | * @author cccll 10 | */ 11 | public interface ServiceProvider { 12 | 13 | /** 14 | * 15 | * 保存服务实例对象和服务实例对象实现的接口类的对应关系 16 | * 17 | * @param service 服务实例对象(service object) 18 | * @param serviceClass 服务实例对象实现的接口类 19 | * @param rpcServiceProperties 服务相关的属性 20 | */ 21 | void addService(Object service, Class serviceClass, RpcServiceProperties rpcServiceProperties); 22 | 23 | /** 24 | * 25 | * 获取服务实例对象 26 | * 27 | * @param rpcServiceProperties 服务相关的属性 28 | * @return 服务实例对象 29 | */ 30 | Object getService(RpcServiceProperties rpcServiceProperties); 31 | 32 | /** 33 | * @param service 服务实例对象 34 | * @param rpcServiceProperties 服务相关的属性 35 | */ 36 | void publishService(Object service, RpcServiceProperties rpcServiceProperties); 37 | 38 | /** 39 | * 发布服务 40 | * @param service 服务实例对象 41 | */ 42 | void publishService(Object service); 43 | } 44 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/provider/ServiceProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.cccll.provider; 2 | 3 | import com.cccll.entity.RpcServiceProperties; 4 | import com.cccll.enumeration.RpcErrorMessage; 5 | import com.cccll.exception.RpcException; 6 | import com.cccll.extension.ExtensionLoader; 7 | import com.cccll.registry.ServiceRegistry; 8 | import com.cccll.remoting.transport.netty.server.NettyServer; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.net.InetAddress; 12 | import java.net.InetSocketAddress; 13 | import java.net.UnknownHostException; 14 | import java.util.Map; 15 | import java.util.Set; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | 18 | 19 | /** 20 | * 21 | * 实现了 ServiceProvider 接口,可以将其看做是一个保存和提供服务实例对象的示例 22 | * 23 | */ 24 | @Slf4j 25 | public class ServiceProviderImpl implements ServiceProvider { 26 | 27 | /** 28 | * 服务实例缓存 29 | * 30 | * 因一个接口可被多个实现类实现, 31 | * 所以处理一个接口被两个实现类实现的情况,可通过 group 分组 32 | * 33 | * key: rpc service name(interface name + version + group) 34 | * value: service object 35 | */ 36 | private final Map serviceMap; 37 | private final Set registeredService; 38 | private final ServiceRegistry serviceRegistry; 39 | 40 | 41 | public ServiceProviderImpl() { 42 | serviceMap = new ConcurrentHashMap<>(); 43 | registeredService = ConcurrentHashMap.newKeySet(); 44 | serviceRegistry = ExtensionLoader.getExtensionLoader(ServiceRegistry.class).getExtension("zk"); 45 | } 46 | 47 | @Override 48 | public void addService(Object service, Class serviceClass, RpcServiceProperties rpcServiceProperties) { 49 | String rpcServiceName = rpcServiceProperties.toRpcServiceName(); 50 | if (registeredService.contains(rpcServiceName)) { 51 | return; 52 | } 53 | registeredService.add(rpcServiceName); 54 | serviceMap.put(rpcServiceName, service); 55 | log.info("Add service: {} and interfaces:{}", rpcServiceName, service.getClass().getInterfaces()); 56 | } 57 | 58 | @Override 59 | public Object getService(RpcServiceProperties rpcServiceProperties) { 60 | Object service = serviceMap.get(rpcServiceProperties.toRpcServiceName()); 61 | if (null == service) { 62 | throw new RpcException(RpcErrorMessage.SERVICE_CAN_NOT_BE_FOUND); 63 | } 64 | return service; 65 | } 66 | 67 | @Override 68 | public void publishService(Object service) { 69 | this.publishService(service, RpcServiceProperties.builder().group("").version("").build()); 70 | } 71 | 72 | @Override 73 | public void publishService(Object service, RpcServiceProperties rpcServiceProperties) { 74 | try { 75 | String host = InetAddress.getLocalHost().getHostAddress(); 76 | //得到服务实例对象实现的接口 77 | Class serviceRelatedInterface = service.getClass().getInterfaces()[0]; 78 | String serviceName = serviceRelatedInterface.getCanonicalName(); 79 | rpcServiceProperties.setServiceName(serviceName); 80 | this.addService(service, serviceRelatedInterface, rpcServiceProperties); 81 | serviceRegistry.registerService(rpcServiceProperties.toRpcServiceName(), new InetSocketAddress(host, NettyServer.PORT)); 82 | } catch (UnknownHostException e) { 83 | log.error("occur exception when getHostAddress", e); 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/proxy/RpcClientProxy.java: -------------------------------------------------------------------------------- 1 | package com.cccll.proxy; 2 | 3 | import com.cccll.entity.RpcServiceProperties; 4 | import com.cccll.remoting.dto.RpcMessageChecker; 5 | import com.cccll.remoting.dto.RpcRequest; 6 | import com.cccll.remoting.dto.RpcResponse; 7 | import com.cccll.remoting.transport.ClientTransport; 8 | import com.cccll.remoting.transport.netty.client.NettyClientTransport; 9 | import com.cccll.remoting.transport.socket.SocketRpcClient; 10 | import lombok.SneakyThrows; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.lang.reflect.InvocationHandler; 14 | import java.lang.reflect.Method; 15 | import java.lang.reflect.Proxy; 16 | import java.util.UUID; 17 | import java.util.concurrent.CompletableFuture; 18 | 19 | /** 20 | * 动态代理类。当动态代理对象调用一个方法的时候,实际调用的是下面的 invoke 方法。 21 | * 正是因为动态代理才让客户端调用的远程方法像是调用本地方法一样(屏蔽了中间过程) 22 | * 23 | * @author cccll 24 | */ 25 | @Slf4j 26 | public class RpcClientProxy implements InvocationHandler { 27 | 28 | /** 29 | * 用于发送请求给服务端,对应socket和netty两种实现方式 30 | */ 31 | private final ClientTransport clientTransport; 32 | private final RpcServiceProperties rpcServiceProperties; 33 | 34 | public RpcClientProxy(ClientTransport clientTransport, RpcServiceProperties rpcServiceProperties) { 35 | this.clientTransport = clientTransport; 36 | if (rpcServiceProperties.getGroup() == null) { 37 | rpcServiceProperties.setGroup(""); 38 | } 39 | if (rpcServiceProperties.getVersion() == null) { 40 | rpcServiceProperties.setVersion(""); 41 | } 42 | this.rpcServiceProperties = rpcServiceProperties; 43 | } 44 | 45 | 46 | public RpcClientProxy(ClientTransport clientTransport) { 47 | this.clientTransport = clientTransport; 48 | this.rpcServiceProperties = RpcServiceProperties.builder().group("").version("").build(); 49 | } 50 | 51 | /** 52 | * 通过 Proxy.newProxyInstance() 方法获取某个类的代理对象 53 | */ 54 | @SuppressWarnings("unchecked") 55 | public T getProxy(Class clazz) { 56 | return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this); 57 | } 58 | 59 | /** 60 | * 当你使用代理对象调用方法的时候实际会调用到这个方法。代理对象就是你通过上面的 getProxy 方法获取到的对象。 61 | */ 62 | @SneakyThrows 63 | @SuppressWarnings("unchecked") 64 | @Override 65 | public Object invoke(Object proxy, Method method, Object[] args) { 66 | log.info("invoked method: [{}]", method.getName()); 67 | //构建rpc请求体 68 | RpcRequest rpcRequest = RpcRequest.builder().methodName(method.getName()) 69 | .parameters(args) 70 | .interfaceName(method.getDeclaringClass().getName()) 71 | .paramTypes(method.getParameterTypes()) 72 | .requestId(UUID.randomUUID().toString()) 73 | .group(rpcServiceProperties.getGroup()) 74 | .version(rpcServiceProperties.getVersion()) 75 | .build(); 76 | RpcResponse rpcResponse = null; 77 | //根据clientTransport的实际类型,会以不同方式发送rpc请求体,然后得到其rpcResponse,返回 78 | if (clientTransport instanceof NettyClientTransport) { 79 | CompletableFuture> completableFuture = (CompletableFuture>) clientTransport.sendRpcRequest(rpcRequest); 80 | //此处有可能会阻塞,但不会一直阻塞,如果请求发生错误,会在添加的 中调用CompletableFuture.completeExceptionally(future.cause()),即可解除阻塞 81 | rpcResponse = completableFuture.get(); 82 | } 83 | if (clientTransport instanceof SocketRpcClient) { 84 | rpcResponse = (RpcResponse) clientTransport.sendRpcRequest(rpcRequest); 85 | } 86 | //校验 RpcResponse 和 RpcRequest 87 | RpcMessageChecker.check(rpcResponse, rpcRequest); 88 | return rpcResponse.getData(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/registry/ServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package com.cccll.registry; 2 | 3 | import com.cccll.extension.SPI; 4 | 5 | import java.net.InetSocketAddress; 6 | 7 | /** 8 | * 服务发现接口 9 | * 10 | */ 11 | @SPI 12 | public interface ServiceDiscovery { 13 | /** 14 | * 根据 rpcServiceName 获取远程服务地址 15 | * 16 | * @param rpcServiceName 服务名称 17 | * @return 远程服务地址 18 | */ 19 | InetSocketAddress lookupService(String rpcServiceName); 20 | } 21 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/registry/ServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.cccll.registry; 2 | 3 | import com.cccll.extension.SPI; 4 | 5 | import java.net.InetSocketAddress; 6 | 7 | /** 8 | * 服务注册接口 9 | * 10 | * @author cccll 11 | */ 12 | @SPI 13 | public interface ServiceRegistry { 14 | /** 15 | * 注册服务 16 | * 17 | * @param rpcServiceName 服务名称 18 | * @param inetSocketAddress 提供服务的地址 19 | */ 20 | void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/registry/zk/ZkServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package com.cccll.registry.zk; 2 | 3 | import com.cccll.enumeration.RpcErrorMessage; 4 | import com.cccll.exception.RpcException; 5 | import com.cccll.loadbalance.LoadBalance; 6 | import com.cccll.loadbalance.RandomLoadBalance; 7 | import com.cccll.registry.ServiceDiscovery; 8 | import com.cccll.registry.zk.util.CuratorUtils; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.curator.framework.CuratorFramework; 11 | 12 | import java.net.InetSocketAddress; 13 | import java.util.List; 14 | 15 | /** 16 | * 基于 zookeeper 实现服务发现 17 | * 18 | * @author cccll 19 | */ 20 | @Slf4j 21 | public class ZkServiceDiscovery implements ServiceDiscovery { 22 | private final LoadBalance loadBalance; 23 | 24 | public ZkServiceDiscovery() { 25 | this.loadBalance = new RandomLoadBalance(); 26 | } 27 | 28 | @Override 29 | public InetSocketAddress lookupService(String rpcServiceName) { 30 | CuratorFramework zkClient = CuratorUtils.getZkClient(); 31 | //获取rpcServiceName下所有子节点(服务地址),相同服务被部署多份就有多个,如果此服务只部署一台,则此集合中只有一个服务地址 32 | List serviceUrlList = CuratorUtils.getChildrenNodes(zkClient, rpcServiceName); 33 | if (serviceUrlList.size() == 0) { 34 | throw new RpcException(RpcErrorMessage.SERVICE_CAN_NOT_BE_FOUND, rpcServiceName); 35 | } 36 | // 负载均衡 37 | String targetServiceUrl = loadBalance.selectServiceAddress(serviceUrlList); 38 | log.info("成功找到服务地址:[{}]", targetServiceUrl); 39 | String[] socketAddressArray = targetServiceUrl.split(":"); 40 | String host = socketAddressArray[0]; 41 | int port = Integer.parseInt(socketAddressArray[1]); 42 | return new InetSocketAddress(host, port); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/registry/zk/ZkServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.cccll.registry.zk; 2 | 3 | import com.cccll.registry.ServiceRegistry; 4 | import com.cccll.registry.zk.util.CuratorUtils; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.curator.framework.CuratorFramework; 7 | 8 | import java.net.InetSocketAddress; 9 | 10 | /** 11 | * 基于 zookeeper 实现服务注册 12 | * 13 | */ 14 | @Slf4j 15 | public class ZkServiceRegistry implements ServiceRegistry { 16 | /** 17 | *服务被注册进 zookeeper 的时候,将完整的服务名称 rpcServiceName(class name+group+version)作为根节点 , 18 | * 子节点是对应的服务地址(ip+端口号),一个根节点可能会对应多个服务地址(相同服务被部署多份的情况) 19 | * 20 | * @param rpcServiceName 服务名称 (class name+group+version 例如:com.cccll.HelloServicetest1version1) 21 | * class name : 服务接口名也就是类名比如:com.cccll.HelloService 22 | * version :(服务版本)主要是为后续不兼容升级提供可能 23 | * group :主要用于处理一个接口有多个类实现的情况 24 | * @param inetSocketAddress 提供服务的地址 25 | */ 26 | @Override 27 | public void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress) { 28 | //根节点下注册子节点:服务 29 | String servicePath = CuratorUtils.ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName + inetSocketAddress.toString(); 30 | CuratorFramework zkClient = CuratorUtils.getZkClient(); 31 | CuratorUtils.createPersistentNode(zkClient, servicePath); 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/registry/zk/util/CuratorUtils.java: -------------------------------------------------------------------------------- 1 | package com.cccll.registry.zk.util; 2 | 3 | import com.cccll.enumeration.RpcConfigProperties; 4 | import com.cccll.exception.RpcException; 5 | import com.cccll.utils.file.PropertiesFileUtils; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.curator.RetryPolicy; 8 | import org.apache.curator.framework.CuratorFramework; 9 | import org.apache.curator.framework.CuratorFrameworkFactory; 10 | import org.apache.curator.framework.imps.CuratorFrameworkState; 11 | import org.apache.curator.framework.recipes.cache.PathChildrenCache; 12 | import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; 13 | import org.apache.curator.retry.ExponentialBackoffRetry; 14 | import org.apache.zookeeper.CreateMode; 15 | 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Properties; 19 | import java.util.Set; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | @Slf4j 23 | public final class CuratorUtils { 24 | //重试之间等待的初始时间 25 | private static final int BASE_SLEEP_TIME = 1000; 26 | //最大重试次数 27 | private static final int MAX_RETRIES = 3; 28 | public static final String ZK_REGISTER_ROOT_PATH = "/my-rpc"; 29 | private static final Map> SERVICE_ADDRESS_MAP = new ConcurrentHashMap<>(); 30 | private static final Set REGISTERED_PATH_SET = ConcurrentHashMap.newKeySet(); 31 | private static CuratorFramework zkClient; 32 | private static String defaultZookeeperAddress = "127.0.0.1:2181"; 33 | 34 | private CuratorUtils() { 35 | } 36 | 37 | /** 38 | * 创建持久的节点。与临时节点不同,持久节点不会在客户端断开连接时被删除 39 | * 40 | * @param path node path 41 | */ 42 | public static void createPersistentNode(CuratorFramework zkClient, String path) { 43 | try { 44 | if (REGISTERED_PATH_SET.contains(path) || zkClient.checkExists().forPath(path) != null) { 45 | log.info("节点已经存在. The node is:[{}]", path); 46 | } else { 47 | //eg: /my-rpc/com.cccll.HelloServicetest1version1/127.0.0.1:9999 48 | zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path); 49 | log.info("节点已成功创建. The node is:[{}]", path); 50 | } 51 | REGISTERED_PATH_SET.add(path); 52 | } catch (Exception e) { 53 | throw new RpcException(e.getMessage(), e.getCause()); 54 | } 55 | } 56 | 57 | /** 58 | * Gets the children under a node 59 | * 60 | * @param rpcServiceName rpc service name (class name+group+version) eg: com.cccll.HelloServicetest1version1 61 | * @return All child nodes under the specified node 62 | */ 63 | public static List getChildrenNodes(CuratorFramework zkClient, String rpcServiceName) { 64 | //Get from cache 65 | if (SERVICE_ADDRESS_MAP.containsKey(rpcServiceName)) { 66 | return SERVICE_ADDRESS_MAP.get(rpcServiceName); 67 | } 68 | List result; 69 | String servicePath = ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName; 70 | try { 71 | result = zkClient.getChildren().forPath(servicePath); 72 | SERVICE_ADDRESS_MAP.put(rpcServiceName, result); 73 | registerWatcher(rpcServiceName, zkClient); 74 | } catch (Exception e) { 75 | throw new RpcException(e.getMessage(), e.getCause()); 76 | } 77 | return result; 78 | } 79 | 80 | /** 81 | * 清空注册表的数据 82 | */ 83 | public static void clearRegistry(CuratorFramework zkClient) { 84 | REGISTERED_PATH_SET.stream().parallel().forEach(p -> { 85 | try { 86 | zkClient.delete().forPath(p); 87 | } catch (Exception e) { 88 | throw new RpcException(e.getMessage(), e.getCause()); 89 | } 90 | }); 91 | log.info("服务器上的所有注册的服务将被清除:[{}]", REGISTERED_PATH_SET.toString()); 92 | } 93 | 94 | public static CuratorFramework getZkClient() { 95 | // 检查用户是否设置了zk地址 96 | Properties properties = PropertiesFileUtils.readPropertiesFile(RpcConfigProperties.RPC_CONFIG_PATH.getPropertyValue()); 97 | if (properties != null) { 98 | defaultZookeeperAddress = properties.getProperty(RpcConfigProperties.ZK_ADDRESS.getPropertyValue()); 99 | } 100 | // 如果 zkClient 已经启动,则直接返回 101 | if (zkClient != null && zkClient.getState() == CuratorFrameworkState.STARTED) { 102 | return zkClient; 103 | } 104 | // 重试策略. 重试3次,将增加重试之间的睡眠时间。 105 | RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIME, MAX_RETRIES); 106 | zkClient = CuratorFrameworkFactory.builder() 107 | // 要连接的服务器(可以是一个服务器列表) 108 | .connectString(defaultZookeeperAddress) 109 | .retryPolicy(retryPolicy) 110 | .build(); 111 | zkClient.start(); 112 | return zkClient; 113 | } 114 | 115 | /** 116 | * Registers to listen for changes to the specified node 117 | * 118 | * @param rpcServiceName rpc service name (class name+group+version) eg:com.cccll.HelloServicetest2version1 119 | */ 120 | private static void registerWatcher(String rpcServiceName, CuratorFramework zkClient) { 121 | String servicePath = ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName; 122 | PathChildrenCache pathChildrenCache = new PathChildrenCache(zkClient, servicePath, true); 123 | //当有服务节点发生改动,更新缓存 124 | PathChildrenCacheListener pathChildrenCacheListener = (curatorFramework, pathChildrenCacheEvent) -> { 125 | List serviceAddresses = curatorFramework.getChildren().forPath(servicePath); 126 | SERVICE_ADDRESS_MAP.put(rpcServiceName, serviceAddresses); 127 | }; 128 | pathChildrenCache.getListenable().addListener(pathChildrenCacheListener); 129 | try { 130 | pathChildrenCache.start(); 131 | } catch (Exception e) { 132 | throw new RpcException(e.getMessage(), e.getCause()); 133 | } 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/dto/RpcMessageChecker.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.dto; 2 | 3 | import com.cccll.enumeration.RpcErrorMessage; 4 | import com.cccll.enumeration.RpcResponseCode; 5 | import com.cccll.exception.RpcException; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * 校验 RpcRequest 和 RpcRequest 10 | * 11 | * @author cccll 12 | * @createTime 2020年06月28日 19:05:00 13 | */ 14 | @Slf4j 15 | public final class RpcMessageChecker { 16 | private static final String INTERFACE_NAME = "interfaceName"; 17 | 18 | private RpcMessageChecker() { 19 | } 20 | 21 | public static void check(RpcResponse rpcResponse, RpcRequest rpcRequest) { 22 | if (rpcResponse == null) { 23 | throw new RpcException(RpcErrorMessage.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName()); 24 | } 25 | 26 | if (!rpcRequest.getRequestId().equals(rpcResponse.getRequestId())) { 27 | throw new RpcException(RpcErrorMessage.REQUEST_NOT_MATCH_RESPONSE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName()); 28 | } 29 | 30 | if (rpcResponse.getCode() == null || !rpcResponse.getCode().equals(RpcResponseCode.SUCCESS.getCode())) { 31 | throw new RpcException(RpcErrorMessage.SERVICE_INVOCATION_FAILURE, INTERFACE_NAME + ":" + rpcRequest.getInterfaceName()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/dto/RpcRequest.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.dto; 2 | 3 | import com.cccll.entity.RpcServiceProperties; 4 | import com.cccll.enumeration.RpcMessageType; 5 | import lombok.*; 6 | 7 | import java.io.Serializable; 8 | 9 | 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Getter 13 | @Builder 14 | @ToString 15 | public class RpcRequest implements Serializable { 16 | private static final long serialVersionUID = 1905122041950251207L; 17 | private String requestId; 18 | private String interfaceName; 19 | private String methodName; 20 | private Object[] parameters; 21 | private Class[] paramTypes; 22 | private RpcMessageType rpcMessageType; 23 | //服务版本,主要是为后续不兼容升级提供可能 24 | private String version; 25 | //主要用于处理一个接口有多个类实现的情况 26 | private String group; 27 | 28 | public RpcServiceProperties toRpcProperties() { 29 | return RpcServiceProperties.builder().serviceName(this.getInterfaceName()) 30 | .version(this.getVersion()) 31 | .group(this.getGroup()).build(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/dto/RpcResponse.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.dto; 2 | 3 | import com.cccll.enumeration.RpcResponseCode; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | import lombok.ToString; 10 | 11 | import java.io.Serializable; 12 | 13 | /** 14 | * @author cccll 15 | */ 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Getter 19 | @Setter 20 | @Builder 21 | @ToString 22 | public class RpcResponse implements Serializable { 23 | 24 | private static final long serialVersionUID = 715745410605631233L; 25 | private String requestId; 26 | /** 27 | * 响应码 28 | */ 29 | private Integer code; 30 | /** 31 | * 响应消息 32 | */ 33 | private String message; 34 | /** 35 | * 响应数据 36 | */ 37 | private T data; 38 | 39 | public static RpcResponse success(T data, String requestId) { 40 | RpcResponse response = new RpcResponse<>(); 41 | response.setCode(RpcResponseCode.SUCCESS.getCode()); 42 | response.setMessage(RpcResponseCode.SUCCESS.getMessage()); 43 | response.setRequestId(requestId); 44 | if (null != data) { 45 | response.setData(data); 46 | } 47 | return response; 48 | } 49 | 50 | public static RpcResponse fail(RpcResponseCode rpcResponseCode) { 51 | RpcResponse response = new RpcResponse<>(); 52 | response.setCode(rpcResponseCode.getCode()); 53 | response.setMessage(rpcResponseCode.getMessage()); 54 | return response; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/handler/RpcRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.handler; 2 | 3 | import com.cccll.exception.RpcException; 4 | import com.cccll.factory.SingletonFactory; 5 | import com.cccll.provider.ServiceProvider; 6 | import com.cccll.provider.ServiceProviderImpl; 7 | import com.cccll.remoting.dto.RpcRequest; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * RpcRequest 处理器 15 | * 16 | * @author cccll 17 | */ 18 | @Slf4j 19 | public class RpcRequestHandler { 20 | private final ServiceProvider serviceProvider; 21 | 22 | public RpcRequestHandler() { 23 | serviceProvider = SingletonFactory.getInstance(ServiceProviderImpl.class); 24 | } 25 | 26 | /** 27 | * 处理 rpcRequest :调用对应的方法,然后返回方法执行结果 28 | * 29 | */ 30 | public Object handle(RpcRequest rpcRequest) { 31 | //通过注册中心获取到目标类(客户端需要调用的目标类) 32 | Object service = serviceProvider.getService(rpcRequest.toRpcProperties()); 33 | return invokeTargetMethod(rpcRequest, service); 34 | } 35 | 36 | /** 37 | * 根据 rpcRequest 和 service 对象特定的方法并返回结果 38 | * 39 | * @param rpcRequest 客户端请求 40 | * @param service 提供服务的对象 41 | * @return 目标方法执行的结果 42 | */ 43 | private Object invokeTargetMethod(RpcRequest rpcRequest, Object service) { 44 | Object result; 45 | try { 46 | Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes()); 47 | result = method.invoke(service, rpcRequest.getParameters()); 48 | log.info("service:[{}] successful invoke method:[{}]", rpcRequest.getInterfaceName(), rpcRequest.getMethodName()); 49 | } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { 50 | throw new RpcException(e.getMessage(), e); 51 | } 52 | return result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/ClientTransport.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport; 2 | 3 | import com.cccll.extension.SPI; 4 | import com.cccll.remoting.dto.RpcRequest; 5 | 6 | /** 7 | * send RpcRequest 8 | * 9 | */ 10 | @SPI 11 | public interface ClientTransport { 12 | /** 13 | * 发送rpc请求到服务器并获得结果 14 | * 15 | * @param rpcRequest message body 16 | * @return 来自服务器的返回值 17 | */ 18 | Object sendRpcRequest(RpcRequest rpcRequest); 19 | } 20 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/client/ChannelProvider.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.client; 2 | 3 | import com.cccll.factory.SingletonFactory; 4 | import io.netty.channel.Channel; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.net.InetSocketAddress; 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * 用于存取 Channel 实例 13 | * 14 | * @author cccll 15 | */ 16 | @Slf4j 17 | public final class ChannelProvider { 18 | /** 19 | * netty中,channel的实现一定是线程安全的,基于此,我们可以存储一个channel的引用, 20 | * 并且在需要向远端发送数据时,通过这个引用来写数据,即便当时有很多线程在使用它也不会出现线程安全的问题, 21 | * 而且消息一定是按照顺序发送的。 22 | * 23 | * channel之所以在netty中是线程安全的是因为: 24 | * 1.一个EventLoop在其生命周期内只和唯一的一个Thread线程绑定(即一个io线程) 25 | * 2.所有由EventLoop处理的各种io事件都将在其所关联的io线程上执行,因为是单线程保证了线程安全 26 | * 3.一个Channel在其生命周期内只会注册在一个EventLoop(selector)上 27 | * 4.运行期间,一个EventLoop会被分配给一个或多个channel 28 | * 29 | * netty在代码中具体的实现就是,在执行channelHandler时会判断当前线程是否是EventLoop中所绑定的那个唯一的io线程, 30 | * 如果是,则直接执行相应的channelHandler,处理该io事件。若不是,则将需要执行的channelHandler封装成一个任务交给EventLoop中io线程去执行, 31 | * EventLoop中具体保存任务的是一个FIFO队列,而且又是单线程执行,所以在保证线程安全的同时也保证了任务的有序性。 32 | * (这里同时要注意的是,在channelHandler中要避免执行耗时或阻塞的处理逻辑, 33 | * 这样会导致后续channelHandler或其他绑定在该EventLoop上的channel的事件响应阻塞,从而影响性能。 34 | * 解决方式:耗时任务或阻塞的任务在自定义的channelHandler中可以交给业务线程池去处理,从而避免阻塞) 35 | */ 36 | private final Map channelMap; 37 | private final NettyClient nettyClient; 38 | 39 | public ChannelProvider() { 40 | channelMap = new ConcurrentHashMap<>(); 41 | nettyClient = SingletonFactory.getInstance(NettyClient.class); 42 | } 43 | 44 | 45 | public Channel get(InetSocketAddress inetSocketAddress) { 46 | String key = inetSocketAddress.toString(); 47 | // 判断是否有对应地址的连接 48 | if (channelMap.containsKey(key)) { 49 | Channel channel = channelMap.get(key); 50 | // 如果有的话,判断连接是否可用,可用的话就直接获取 51 | if (channel != null && channel.isActive()) { 52 | return channel; 53 | } else { 54 | channelMap.remove(key); 55 | } 56 | } 57 | // 否则,重新连接获取 Channel 58 | Channel channel = nettyClient.doConnect(inetSocketAddress); 59 | channelMap.put(key, channel); 60 | return channel; 61 | } 62 | 63 | public void remove(InetSocketAddress inetSocketAddress) { 64 | String key = inetSocketAddress.toString(); 65 | channelMap.remove(key); 66 | log.info("Channel map size :[{}]", channelMap.size()); 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/client/NettyClient.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.client; 2 | 3 | import com.cccll.extension.ExtensionLoader; 4 | import com.cccll.remoting.dto.RpcRequest; 5 | import com.cccll.remoting.dto.RpcResponse; 6 | import com.cccll.remoting.transport.netty.codec.kyro.NettyKryoDecoder; 7 | import com.cccll.remoting.transport.netty.codec.kyro.NettyKryoEncoder; 8 | import com.cccll.serialize.Serializer; 9 | import com.cccll.serialize.kyro.KryoSerializer; 10 | import io.netty.bootstrap.Bootstrap; 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelFutureListener; 13 | import io.netty.channel.ChannelInitializer; 14 | import io.netty.channel.ChannelOption; 15 | import io.netty.channel.EventLoopGroup; 16 | import io.netty.channel.nio.NioEventLoopGroup; 17 | import io.netty.channel.socket.SocketChannel; 18 | import io.netty.channel.socket.nio.NioSocketChannel; 19 | import io.netty.handler.logging.LogLevel; 20 | import io.netty.handler.logging.LoggingHandler; 21 | import io.netty.handler.timeout.IdleStateHandler; 22 | import lombok.SneakyThrows; 23 | import lombok.extern.slf4j.Slf4j; 24 | 25 | 26 | import java.net.InetSocketAddress; 27 | import java.util.concurrent.CompletableFuture; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | /** 31 | * 用于初始化 和 关闭 Bootstrap 对象 32 | * 33 | * @author cccll 34 | */ 35 | @Slf4j 36 | public final class NettyClient { 37 | private final Bootstrap bootstrap; 38 | private final EventLoopGroup eventLoopGroup; 39 | 40 | // 初始化相关资源比如 EventLoopGroup、Bootstrap 41 | public NettyClient() { 42 | eventLoopGroup = new NioEventLoopGroup(); 43 | bootstrap = new Bootstrap(); 44 | Serializer kryoSerializer = ExtensionLoader.getExtensionLoader(Serializer.class).getExtension("kyro"); 45 | bootstrap.group(eventLoopGroup) 46 | .channel(NioSocketChannel.class) 47 | .handler(new LoggingHandler(LogLevel.INFO)) 48 | // 连接的超时时间,超过这个时间还是建立不上的话则代表连接失败 49 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) 50 | .handler(new ChannelInitializer() { 51 | @Override 52 | protected void initChannel(SocketChannel ch) { 53 | // 设定IdleStateHandler心跳检测每5秒进行一次写检测,如果5秒内write()方法未被调用则触发一次userEventTrigger()方法,如果 5 秒之内没有发送数据给服务端的话,就发送一次心跳请求 54 | ch.pipeline().addLast(new IdleStateHandler(0, 5, 0, TimeUnit.SECONDS)); 55 | /* 56 | 自定义序列化编解码器 57 | */ 58 | // RpcResponse -> ByteBuf 59 | ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class)); 60 | // ByteBuf -> RpcRequest 61 | ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class)); 62 | ch.pipeline().addLast(new NettyClientHandler()); 63 | } 64 | }); 65 | } 66 | 67 | 68 | /** 69 | * 连接服务器并获取channel,这样你就可以向服务器发送rpc消息 70 | * 71 | * @param inetSocketAddress server address 72 | * @return the channel 73 | */ 74 | @SneakyThrows //此注释省去检查时异常包层运行时异常抛出的模板代码的编写 75 | public Channel doConnect(InetSocketAddress inetSocketAddress) { 76 | CompletableFuture completableFuture = new CompletableFuture<>(); 77 | bootstrap.connect(inetSocketAddress).addListener((ChannelFutureListener) future -> { 78 | if (future.isSuccess()) { 79 | log.info("The client has connected [{}] successful!", inetSocketAddress.toString()); 80 | completableFuture.complete(future.channel()); 81 | } else { 82 | throw new IllegalStateException(); 83 | } 84 | }); 85 | return completableFuture.get(); 86 | } 87 | 88 | public void close() { 89 | log.info("call close method"); 90 | eventLoopGroup.shutdownGracefully(); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/client/NettyClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.client; 2 | 3 | import com.cccll.enumeration.RpcMessageType; 4 | import com.cccll.factory.SingletonFactory; 5 | import com.cccll.remoting.dto.RpcRequest; 6 | import com.cccll.remoting.dto.RpcResponse; 7 | import io.netty.channel.*; 8 | import io.netty.handler.timeout.IdleState; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import io.netty.util.ReferenceCountUtil; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.net.InetSocketAddress; 14 | 15 | /** 16 | * 自定义客户端ChannelHandler以处理服务器发送的数据 17 | * 18 | *

19 | * 如果继承自 SimpleChannelInboundHandler 的话就不要考虑 ByteBuf 的释放 ,{@link SimpleChannelInboundHandler} 内部的 20 | * channelRead 方法会替你释放 ByteBuf ,避免可能导致的内存泄露问题。详见《Netty进阶之路 跟着案例学 Netty》 21 | * 22 | * @author cccll 23 | * @createTime 2020年07月01日 22:18:00 24 | */ 25 | @Slf4j 26 | public class NettyClientHandler extends ChannelInboundHandlerAdapter { 27 | private final UnprocessedRequests unprocessedRequests; 28 | private final ChannelProvider channelProvider; 29 | 30 | public NettyClientHandler() { 31 | this.unprocessedRequests = SingletonFactory.getInstance(UnprocessedRequests.class); 32 | this.channelProvider = SingletonFactory.getInstance(ChannelProvider.class); 33 | } 34 | 35 | /** 36 | * 利用Netty提供的高水位机制,对客户端做流控,避免netty发送队列积压发生OOM, 37 | * 当发送队列发送的字节数组到达高水位时,对应channel变为不可写状态,注意在此状态下调用write方法仍可将消息 38 | * 加入待发送队列,所以须在代码中对channel状态做判断 39 | * 40 | */ 41 | @Override 42 | public void channelActive(ChannelHandlerContext ctx) { 43 | //此值须根据业务的QPS规划、客户端处理性能、网络带宽、链路数、消息平均码流大小等综合因素计算 44 | ctx.channel().config().setWriteBufferHighWaterMark(20*1024*1024); 45 | } 46 | 47 | /** 48 | * 读取从服务端返回的消息 49 | */ 50 | @Override 51 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 52 | try { 53 | log.info("client receive msg: [{}]", msg); 54 | if (msg instanceof RpcResponse) { 55 | RpcResponse rpcResponse = (RpcResponse) msg; 56 | unprocessedRequests.complete(rpcResponse); 57 | } 58 | } finally { 59 | //msg为netty申请的请求ByteBuf,需业务主动释放,否则会造成内存泄漏 60 | ReferenceCountUtil.release(msg); 61 | } 62 | } 63 | 64 | /** 65 | * Netty 心跳机制,当发生空闲超时时,会触发此方法,给服务器发送一个心跳请求,保证客户端和服务端的连接不被断掉,避免重连。 66 | * Netty通过IdleStateHandler实现的此种心跳机制不是一种双向心跳的PING-PONG模式,而是客户端发送心跳数据包, 67 | * 服务端接收心跳但不回复,因为一般应用rpc框架的服务端可能同时有上千个连接,心跳的回复需要消耗大量网络资源; 68 | * 如果服务端一段时间内内有收到客户端的心跳数据包则认为客户端已经下线,将通道关闭避免资源的浪费; 69 | * 在这种心跳模式下服务端可以感知客户端的存活情况,无论是宕机的正常下线还是网络问题的非正常下线,服务端都能感知到, 70 | * 而客户端不能感知到服务端的非正常下线。 71 | * 要想实现客户端感知服务端的存活情况,需要进行双向的心跳;Netty中的channelInactive()方法是通过Socket连接关闭时挥手数据包触发的, 72 | * 因此可以通过channelInactive()方法感知正常的下线情况,但是因为网络异常等非正常下线则无法感知。 73 | */ 74 | @Override 75 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 76 | if (evt instanceof IdleStateEvent) { 77 | IdleState state = ((IdleStateEvent) evt).state(); 78 | if (state == IdleState.WRITER_IDLE) { 79 | log.info("write idle happen [{}]", ctx.channel().remoteAddress()); 80 | Channel channel = channelProvider.get((InetSocketAddress) ctx.channel().remoteAddress()); 81 | RpcRequest rpcRequest = RpcRequest.builder().rpcMessageType(RpcMessageType.HEART_BEAT).build(); 82 | channel.writeAndFlush(rpcRequest).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 83 | } 84 | } else { 85 | super.userEventTriggered(ctx, evt); 86 | } 87 | } 88 | 89 | /** 90 | * 处理客户端消息发生异常的时候被调用 91 | */ 92 | @Override 93 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 94 | log.error("client catch exception:", cause); 95 | cause.printStackTrace(); 96 | ctx.close(); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/client/NettyClientTransport.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.client; 2 | 3 | import com.cccll.extension.ExtensionLoader; 4 | import com.cccll.factory.SingletonFactory; 5 | import com.cccll.registry.ServiceDiscovery; 6 | import com.cccll.remoting.dto.RpcRequest; 7 | import com.cccll.remoting.dto.RpcResponse; 8 | import com.cccll.remoting.transport.ClientTransport; 9 | import io.netty.channel.Channel; 10 | import io.netty.channel.ChannelFutureListener; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.net.InetSocketAddress; 14 | import java.util.concurrent.CompletableFuture; 15 | 16 | /** 17 | * 基于 Netty 传输 RpcRequest。 18 | * 19 | * @author cccll 20 | */ 21 | 22 | @Slf4j 23 | public class NettyClientTransport implements ClientTransport { 24 | private final ServiceDiscovery serviceDiscovery; 25 | private final UnprocessedRequests unprocessedRequests; 26 | private final ChannelProvider channelProvider; 27 | 28 | public NettyClientTransport() { 29 | this.serviceDiscovery = ExtensionLoader.getExtensionLoader(ServiceDiscovery.class).getExtension("zk"); 30 | this.unprocessedRequests = SingletonFactory.getInstance(UnprocessedRequests.class); 31 | this.channelProvider = SingletonFactory.getInstance(ChannelProvider.class); 32 | } 33 | 34 | @Override 35 | public CompletableFuture> sendRpcRequest(RpcRequest rpcRequest) { 36 | // 构建返回值 37 | CompletableFuture> resultFuture = new CompletableFuture<>(); 38 | // 通过rpcRequest构建rpc服务名 eg:com.cccll.HelloServicetest2version2 39 | String rpcServiceName = rpcRequest.toRpcProperties().toRpcServiceName(); 40 | // get server address 41 | InetSocketAddress inetSocketAddress = serviceDiscovery.lookupService(rpcServiceName); 42 | // 获取 server address 对应的 channel 43 | Channel channel = channelProvider.get(inetSocketAddress); 44 | // 此处的channel.isWritable()检查主要是利用Netty的高水位机制来实现流控 45 | if (channel != null && channel.isActive() && channel.isWritable()) { 46 | // 放入未处理的请求 47 | unprocessedRequests.put(rpcRequest.getRequestId(), resultFuture); 48 | channel.writeAndFlush(rpcRequest).addListener((ChannelFutureListener) future -> { 49 | if (future.isSuccess()) { 50 | log.info("client send message: [{}]", rpcRequest); 51 | } else { 52 | future.channel().close(); 53 | //当调用CompletableFuture.get()被阻塞的时候,那么这个方法就是结束阻塞,并且get()获取发生的异常. 54 | resultFuture.completeExceptionally(future.cause()); 55 | log.error("Send failed:", future.cause()); 56 | } 57 | }); 58 | } else { 59 | throw new IllegalStateException(); 60 | } 61 | 62 | return resultFuture; 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/client/UnprocessedRequests.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.client; 2 | 3 | import com.cccll.remoting.dto.RpcResponse; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * 未处理的请求。 11 | * 12 | * @author cccll 13 | * @createTime 2020年07月09日 21:33:00 14 | */ 15 | public class UnprocessedRequests { 16 | //此集合相当于Dubbo中的Futures,可限制 map 容器大小,避免未处理请求过多 OOM 17 | private static final Map>> UNPROCESSED_RESPONSE_FUTURES = new ConcurrentHashMap<>(); 18 | 19 | public void put(String requestId, CompletableFuture> future) { 20 | UNPROCESSED_RESPONSE_FUTURES.put(requestId, future); 21 | } 22 | 23 | public void complete(RpcResponse rpcResponse) { 24 | CompletableFuture> future = UNPROCESSED_RESPONSE_FUTURES.remove(rpcResponse.getRequestId()); 25 | if (null != future) { 26 | future.complete(rpcResponse); 27 | } else { 28 | throw new IllegalStateException(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/codec/kyro/NettyKryoDecoder.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.codec.kyro; 2 | 3 | import com.cccll.serialize.Serializer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ByteToMessageDecoder; 7 | import lombok.AllArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 自定义解码器。负责处理"入站"消息,将消息格式转换为我们需要的业务对象 14 | * 15 | * @author cccll 16 | */ 17 | @AllArgsConstructor 18 | @Slf4j 19 | public class NettyKryoDecoder extends ByteToMessageDecoder { 20 | 21 | private final Serializer serializer; 22 | private final Class genericClass; 23 | 24 | /** 25 | * Netty传输的消息长度也就是对象序列化后对应的字节数组的大小,存储在 ByteBuf 头部 26 | */ 27 | private static final int BODY_LENGTH = 4; 28 | 29 | /** 30 | * 解码 ByteBuf 对象 31 | * 32 | * @param ctx 解码器关联的 ChannelHandlerContext 对象 33 | * @param in "入站"数据,也就是 ByteBuf 对象 34 | * @param out 解码之后的数据对象需要添加到 out 对象里面 35 | */ 36 | @Override 37 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 38 | 39 | //1.byteBuf中写入的消息长度所占的字节数已经是4了,所以 byteBuf 的可读字节必须大于 4, 40 | if (in.readableBytes() >= BODY_LENGTH) { 41 | //2.标记当前readIndex的位置,以便后面重置readIndex 的时候使用 42 | in.markReaderIndex(); 43 | //3.读取消息的长度 44 | //注意: 消息长度是encode的时候我们自己写入的,参见 NettyKryoEncoder 的encode方法 45 | int dataLength = in.readInt(); 46 | //4.遇到不合理的情况直接 return 47 | if (dataLength < 0 || in.readableBytes() < 0) { 48 | log.error("data length or byteBuf readableBytes is not valid"); 49 | return; 50 | } 51 | //5.如果可读字节数小于消息长度的话,说明是不完整的消息,重置readIndex 52 | if (in.readableBytes() < dataLength) { 53 | in.resetReaderIndex(); 54 | return; 55 | } 56 | // 6.走到这里说明没什么问题了,可以序列化了 57 | byte[] body = new byte[dataLength]; 58 | in.readBytes(body); 59 | // 将bytes数组转换为我们需要的对象 60 | Object obj = serializer.deserialize(body, genericClass); 61 | out.add(obj); 62 | log.info("successful decode ByteBuf to Object"); 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/codec/kyro/NettyKryoEncoder.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.codec.kyro; 2 | 3 | import com.cccll.serialize.Serializer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | import lombok.AllArgsConstructor; 8 | 9 | /** 10 | * 自定义编码器。负责处理"出站"消息,将消息格式转换字节数组然后写入到字节数据的容器 ByteBuf 对象中。 11 | *

12 | * 网络传输需要通过字节流来实现,ByteBuf 可以看作是 Netty 提供的字节数据的容器,使用它会让我们更加方便地处理字节数据。 13 | * 14 | * @author cccll 15 | */ 16 | @AllArgsConstructor 17 | public class NettyKryoEncoder extends MessageToByteEncoder { 18 | private final Serializer serializer; 19 | private final Class genericClass; 20 | 21 | /** 22 | * 将对象转换为字节码然后写入到 ByteBuf 对象中 23 | */ 24 | @Override 25 | protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) { 26 | if (genericClass.isInstance(o)) { 27 | // 1. 将对象转换为byte 28 | byte[] body = serializer.serialize(o); 29 | // 2. 读取消息的长度 30 | int dataLength = body.length; 31 | // 3.写入消息对应的字节数组长度,writerIndex 加 4 32 | byteBuf.writeInt(dataLength); 33 | //4.将字节数组写入 ByteBuf 对象中 34 | byteBuf.writeBytes(body); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/server/NettyServer.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.server; 2 | 3 | import com.cccll.config.CustomShutdownHook; 4 | import com.cccll.entity.RpcServiceProperties; 5 | import com.cccll.extension.ExtensionLoader; 6 | import com.cccll.factory.SingletonFactory; 7 | import com.cccll.provider.ServiceProvider; 8 | import com.cccll.provider.ServiceProviderImpl; 9 | import com.cccll.remoting.dto.RpcRequest; 10 | import com.cccll.remoting.dto.RpcResponse; 11 | import com.cccll.remoting.transport.netty.codec.kyro.NettyKryoDecoder; 12 | import com.cccll.remoting.transport.netty.codec.kyro.NettyKryoEncoder; 13 | import com.cccll.serialize.Serializer; 14 | import com.cccll.serialize.kyro.KryoSerializer; 15 | import io.netty.bootstrap.ServerBootstrap; 16 | import io.netty.channel.ChannelFuture; 17 | import io.netty.channel.ChannelInitializer; 18 | import io.netty.channel.ChannelOption; 19 | import io.netty.channel.EventLoopGroup; 20 | import io.netty.channel.nio.NioEventLoopGroup; 21 | import io.netty.channel.socket.SocketChannel; 22 | import io.netty.channel.socket.nio.NioServerSocketChannel; 23 | import io.netty.handler.logging.LogLevel; 24 | import io.netty.handler.logging.LoggingHandler; 25 | import io.netty.handler.timeout.IdleStateHandler; 26 | import lombok.SneakyThrows; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.springframework.stereotype.Component; 29 | 30 | import java.net.InetAddress; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | /** 34 | * 服务端。接收客户端消息,并且根据客户端的消息调用相应的方法,然后返回结果给客户端。 35 | * 36 | * @author cccll 37 | */ 38 | @Slf4j 39 | @Component 40 | public class NettyServer { 41 | 42 | private final Serializer kryoSerializer = ExtensionLoader.getExtensionLoader(Serializer.class).getExtension("kyro"); 43 | public static final int PORT = 9998; 44 | 45 | private final ServiceProvider serviceProvider = SingletonFactory.getInstance(ServiceProviderImpl.class); 46 | 47 | public void registerService(Object service) { 48 | serviceProvider.publishService(service); 49 | } 50 | 51 | public void registerService(Object service, RpcServiceProperties rpcServiceProperties) { 52 | serviceProvider.publishService(service, rpcServiceProperties); 53 | } 54 | 55 | @SneakyThrows 56 | public void start() { 57 | CustomShutdownHook.getCustomShutdownHook().clearAll(); 58 | String host = InetAddress.getLocalHost().getHostAddress(); 59 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 60 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 61 | try { 62 | ServerBootstrap b = new ServerBootstrap(); 63 | b.group(bossGroup, workerGroup) 64 | .channel(NioServerSocketChannel.class) 65 | // TCP默认开启了 Nagle 算法,该算法的作用是尽可能的发送大数据快,减少网络传输。TCP_NODELAY 参数的作用就是控制是否启用 Nagle 算法。 66 | .childOption(ChannelOption.TCP_NODELAY, true) 67 | // 是否开启 TCP 底层心跳机制 68 | .childOption(ChannelOption.SO_KEEPALIVE, true) 69 | //表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数 70 | .option(ChannelOption.SO_BACKLOG, 128) 71 | .handler(new LoggingHandler(LogLevel.INFO)) 72 | // 当客户端第一次进行请求的时候才会进行初始化 73 | .childHandler(new ChannelInitializer() { 74 | @Override 75 | protected void initChannel(SocketChannel ch) { 76 | // 30 秒之内没有收到客户端请求的话就关闭连接 77 | ch.pipeline().addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS)); 78 | ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcRequest.class)); 79 | ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcResponse.class)); 80 | ch.pipeline().addLast(new NettyServerHandler()); 81 | } 82 | }); 83 | 84 | // 绑定端口,同步等待绑定成功 85 | ChannelFuture f = b.bind(host, PORT).sync(); 86 | // 等待服务端监听端口关闭 87 | f.channel().closeFuture().sync(); 88 | } catch (InterruptedException e) { 89 | log.error("occur exception when start server:", e); 90 | } finally { 91 | log.error("shutdown bossGroup and workerGroup"); 92 | bossGroup.shutdownGracefully(); 93 | workerGroup.shutdownGracefully(); 94 | } 95 | } 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/netty/server/NettyServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.netty.server; 2 | 3 | import com.cccll.enumeration.RpcMessageType; 4 | import com.cccll.enumeration.RpcResponseCode; 5 | import com.cccll.factory.SingletonFactory; 6 | import com.cccll.remoting.dto.RpcRequest; 7 | import com.cccll.remoting.dto.RpcResponse; 8 | import com.cccll.remoting.handler.RpcRequestHandler; 9 | import io.netty.channel.ChannelFutureListener; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.channel.ChannelInboundHandlerAdapter; 12 | import io.netty.channel.SimpleChannelInboundHandler; 13 | import io.netty.handler.timeout.IdleState; 14 | import io.netty.handler.timeout.IdleStateEvent; 15 | import io.netty.util.ReferenceCountUtil; 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | /** 19 | * 自定义服务端的 ChannelHandler 来处理客户端发过来的数据。 20 | *

21 | * 如果继承自 SimpleChannelInboundHandler 的话就不要考虑 ByteBuf 的释放 ,{@link SimpleChannelInboundHandler} 内部的 22 | * channelRead 方法会替你释放 ByteBuf ,避免可能导致的内存泄露问题。详见《Netty进阶之路 跟着案例学 Netty》 23 | * 24 | * @author cccll 25 | */ 26 | @Slf4j 27 | public class NettyServerHandler extends ChannelInboundHandlerAdapter { 28 | 29 | private final RpcRequestHandler rpcRequestHandler; 30 | 31 | public NettyServerHandler() { 32 | this.rpcRequestHandler = SingletonFactory.getInstance(RpcRequestHandler.class); 33 | } 34 | 35 | /** 36 | * 读取从客户端发送的消息,然后调用目标服务的目标方法并返回给客户端。 37 | * 38 | * TODO: 因考虑到RPC框架一般用于拆分服务,所以考虑到服务之间的调用会较为频繁,因此框架服务端与客户端使用的是长连接。 39 | * 长连接的弊端就是如果较多客户端连接服务端,则服务端就要维持大量与客户端的连接,这对服务端是一种负担,如果遇到 40 | * 某些客户端正常通信后长时间不再与服务端正常通信,只是发心跳请求维持链接,而服务端还要维持着链接则无疑是对服务端 41 | * 内存和机器性能的负担,所以可以考虑加个心跳计数器,每个Channel对应一个心跳数,每次有心跳信息过来就给对应Channel 42 | * 加1,非心跳请求过来就把对应Channel计数器置0,当心跳数达到值则把对应Channel关掉。 43 | */ 44 | @Override 45 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 46 | try { 47 | log.info("server receive msg: [{}] ", msg); 48 | RpcRequest rpcRequest = (RpcRequest) msg; 49 | //如果是心跳请求则忽略 50 | if (rpcRequest.getRpcMessageType() == RpcMessageType.HEART_BEAT) { 51 | log.info("receive heat beat msg from client"); 52 | return; 53 | } 54 | // 执行目标方法(客户端需要执行的方法)并返回方法结果 55 | Object result = rpcRequestHandler.handle(rpcRequest); 56 | log.info(String.format("server get result: %s", result.toString())); 57 | if (ctx.channel().isActive() && ctx.channel().isWritable()) { 58 | //返回方法执行结果给客户端 59 | RpcResponse rpcResponse = RpcResponse.success(result, rpcRequest.getRequestId()); 60 | //增加ChannelFutureListener.CLOSE_ON_FAILURE监听器,如果传送未成功,将会关闭此链接(Channel) 61 | ctx.writeAndFlush(rpcResponse).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 62 | } else { 63 | RpcResponse rpcResponse = RpcResponse.fail(RpcResponseCode.FAIL); 64 | ctx.writeAndFlush(rpcResponse).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 65 | log.error("not writable now, message dropped"); 66 | } 67 | } finally { 68 | //确保ByteBuf被释放,否则可能会出现内存泄漏 69 | ReferenceCountUtil.release(msg); 70 | } 71 | } 72 | 73 | @Override 74 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 75 | if (evt instanceof IdleStateEvent) { 76 | IdleState state = ((IdleStateEvent) evt).state(); 77 | if (state == IdleState.READER_IDLE) { 78 | log.info("idle check happen, so close the connection"); 79 | ctx.close(); 80 | } 81 | } else { 82 | super.userEventTriggered(ctx, evt); 83 | } 84 | } 85 | 86 | @Override 87 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 88 | log.error("server catch exception"); 89 | cause.printStackTrace(); 90 | ctx.close(); 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/socket/SocketRpcClient.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.socket; 2 | 3 | import com.cccll.entity.RpcServiceProperties; 4 | import com.cccll.exception.RpcException; 5 | import com.cccll.extension.ExtensionLoader; 6 | import com.cccll.registry.ServiceDiscovery; 7 | import com.cccll.remoting.dto.RpcRequest; 8 | import com.cccll.remoting.transport.ClientTransport; 9 | 10 | import lombok.AllArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.io.IOException; 14 | import java.io.ObjectInputStream; 15 | import java.io.ObjectOutputStream; 16 | import java.net.InetSocketAddress; 17 | import java.net.Socket; 18 | 19 | /** 20 | * 基于 Socket 传输 RpcRequest 21 | * 22 | * @author cccll 23 | * @createTime 2020年06月13日 18:40:00 24 | */ 25 | @AllArgsConstructor 26 | @Slf4j 27 | public class SocketRpcClient implements ClientTransport { 28 | private final ServiceDiscovery serviceDiscovery; 29 | 30 | public SocketRpcClient() { 31 | this.serviceDiscovery = ExtensionLoader.getExtensionLoader(ServiceDiscovery.class).getExtension("zk"); 32 | } 33 | 34 | @Override 35 | public Object sendRpcRequest(RpcRequest rpcRequest) { 36 | // 通过rpcRequest构建rpc服务名称 37 | String rpcServiceName = RpcServiceProperties.builder().serviceName(rpcRequest.getInterfaceName()) 38 | .group(rpcRequest.getGroup()).version(rpcRequest.getVersion()).build().toRpcServiceName(); 39 | InetSocketAddress inetSocketAddress = serviceDiscovery.lookupService(rpcServiceName); 40 | try (Socket socket = new Socket()) { 41 | socket.connect(inetSocketAddress); 42 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); 43 | // 通过 output stream 将数据发送到服务器 44 | objectOutputStream.writeObject(rpcRequest); 45 | ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); 46 | // 从input stream读取RpcResponse 47 | return objectInputStream.readObject(); 48 | } catch (IOException | ClassNotFoundException e) { 49 | throw new RpcException("调用服务失败:", e); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/socket/SocketRpcRequestHandlerRunnable.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.socket; 2 | 3 | import com.cccll.factory.SingletonFactory; 4 | import com.cccll.remoting.dto.RpcRequest; 5 | import com.cccll.remoting.dto.RpcResponse; 6 | import com.cccll.remoting.handler.RpcRequestHandler; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.io.IOException; 10 | import java.io.ObjectInputStream; 11 | import java.io.ObjectOutputStream; 12 | import java.net.Socket; 13 | 14 | /** 15 | * @author cccll 16 | * @createTime 2020年06月13日 07:20:00 17 | */ 18 | @Slf4j 19 | public class SocketRpcRequestHandlerRunnable implements Runnable { 20 | private final Socket socket; 21 | private final RpcRequestHandler rpcRequestHandler; 22 | 23 | 24 | public SocketRpcRequestHandlerRunnable(Socket socket) { 25 | this.socket = socket; 26 | this.rpcRequestHandler = SingletonFactory.getInstance(RpcRequestHandler.class); 27 | } 28 | 29 | @Override 30 | public void run() { 31 | log.info("server handle message from client by thread: [{}]", Thread.currentThread().getName()); 32 | try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); 33 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) { 34 | // 读取出 rpcRequest 35 | RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject(); 36 | // 交由给 rpcRequest 处理类去处理 37 | Object result = rpcRequestHandler.handle(rpcRequest); 38 | // 返回RpcResponse给客户端 39 | objectOutputStream.writeObject(RpcResponse.success(result, rpcRequest.getRequestId())); 40 | objectOutputStream.flush(); 41 | } catch (IOException | ClassNotFoundException e) { 42 | log.error("occur exception:", e); 43 | } 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/remoting/transport/socket/SocketRpcServer.java: -------------------------------------------------------------------------------- 1 | package com.cccll.remoting.transport.socket; 2 | 3 | import com.cccll.config.CustomShutdownHook; 4 | import com.cccll.entity.RpcServiceProperties; 5 | import com.cccll.factory.SingletonFactory; 6 | import com.cccll.provider.ServiceProvider; 7 | import com.cccll.provider.ServiceProviderImpl; 8 | import com.cccll.utils.concurrent.threadpool.ThreadPoolFactoryUtils; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.io.IOException; 12 | import java.net.InetAddress; 13 | import java.net.InetSocketAddress; 14 | import java.net.ServerSocket; 15 | import java.net.Socket; 16 | import java.util.concurrent.ExecutorService; 17 | 18 | import static com.cccll.remoting.transport.netty.server.NettyServer.PORT; 19 | 20 | /** 21 | * @author cccll 22 | */ 23 | @Slf4j 24 | public class SocketRpcServer { 25 | 26 | private final ExecutorService threadPool; 27 | private final ServiceProvider serviceProvider; 28 | 29 | 30 | public SocketRpcServer() { 31 | threadPool = ThreadPoolFactoryUtils.createCustomThreadPoolIfAbsent("socket-server-rpc-pool"); 32 | SingletonFactory.getInstance(ServiceProviderImpl.class); 33 | serviceProvider = SingletonFactory.getInstance(ServiceProviderImpl.class); 34 | } 35 | 36 | 37 | public void registerService(Object service) { 38 | serviceProvider.publishService(service); 39 | } 40 | 41 | public void registerService(Object service, RpcServiceProperties rpcServiceProperties) { 42 | serviceProvider.publishService(service, rpcServiceProperties); 43 | } 44 | 45 | public void start() { 46 | try (ServerSocket server = new ServerSocket()) { 47 | String host = InetAddress.getLocalHost().getHostAddress(); 48 | server.bind(new InetSocketAddress(host, PORT)); 49 | CustomShutdownHook.getCustomShutdownHook().clearAll(); 50 | Socket socket; 51 | //阻塞获取客户端链接 52 | while ((socket = server.accept()) != null) { 53 | log.info("client connected [{}]", socket.getInetAddress()); 54 | //得到的客户端链接直接交由线程池处理 55 | threadPool.execute(new SocketRpcRequestHandlerRunnable(socket)); 56 | } 57 | threadPool.shutdown(); 58 | } catch (IOException e) { 59 | log.error("occur IOException:", e); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/serialize/Serializer.java: -------------------------------------------------------------------------------- 1 | package com.cccll.serialize; 2 | 3 | import com.cccll.extension.SPI; 4 | 5 | /** 6 | * 序列化接口,所有序列化类都要实现这个接口 7 | * 8 | * @author cccll 9 | */ 10 | @SPI 11 | public interface Serializer { 12 | /** 13 | * 序列化 14 | * 15 | * @param obj 要序列化的对象 16 | * @return 字节数组 17 | */ 18 | byte[] serialize(Object obj); 19 | 20 | /** 21 | * 反序列化 22 | * 23 | * @param bytes 序列化后的字节数组 24 | * @param clazz 目标类 25 | * @param 类的类型。举个例子, {@code String.class} 的类型是 {@code Class}. 26 | * 如果不知道类的类型的话,使用 {@code Class} 27 | * @return 反序列化的对象 28 | */ 29 | T deserialize(byte[] bytes, Class clazz); 30 | } 31 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/serialize/kyro/KryoSerializer.java: -------------------------------------------------------------------------------- 1 | package com.cccll.serialize.kyro; 2 | 3 | import com.cccll.exception.SerializeException; 4 | import com.cccll.remoting.dto.RpcRequest; 5 | import com.cccll.remoting.dto.RpcResponse; 6 | import com.cccll.serialize.Serializer; 7 | import com.esotericsoftware.kryo.Kryo; 8 | import com.esotericsoftware.kryo.io.Input; 9 | import com.esotericsoftware.kryo.io.Output; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.ByteArrayOutputStream; 13 | 14 | /** 15 | * Kryo序列化类,Kryo序列化效率很高,但是只兼容 Java 语言 16 | * 17 | * @author cccll 18 | */ 19 | public class KryoSerializer implements Serializer { 20 | 21 | /** 22 | * 由于 Kryo 不是线程安全的。每个线程都应该有自己的 Kryo,Input 和 Output 实例。 23 | * 所以,使用 ThreadLocal 存放 Kryo 对象 24 | */ 25 | private final ThreadLocal kryoThreadLocal = ThreadLocal.withInitial(() -> { 26 | Kryo kryo = new Kryo(); 27 | kryo.register(RpcResponse.class); 28 | kryo.register(RpcRequest.class); 29 | kryo.setReferences(true); //默认值为true,是否关闭注册行为,关闭之后可能存在序列化问题,一般推荐设置为 true 30 | kryo.setRegistrationRequired(false); //默认值为false,是否关闭循环引用,可以提高性能,但是一般不推荐设置为 true 31 | return kryo; 32 | }); 33 | 34 | 35 | /** 36 | * 序列化 37 | * 38 | * @param obj 要序列化的对象 39 | * @return 字节数组 40 | */ 41 | @Override 42 | public byte[] serialize(Object obj) { 43 | try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 44 | Output output = new Output(byteArrayOutputStream)) { 45 | Kryo kryo = kryoThreadLocal.get(); 46 | // Object->byte:将对象序列化为byte数组 47 | kryo.writeObject(output, obj); 48 | kryoThreadLocal.remove(); //如果线程不关闭,那么此线程的ThreadLocalMap里就会一直存着此kryoThreadLocal,造成内存泄漏情况,所以记得用完后remove 49 | return output.toBytes(); 50 | } catch (Exception e) { 51 | throw new SerializeException("Serialization failed"); 52 | } 53 | } 54 | 55 | /** 56 | * 反序列化 57 | * 58 | * @param bytes 序列化后的字节数组 59 | * @param clazz 目标类 60 | * @return 反序列化的对象 61 | */ 62 | @Override 63 | public T deserialize(byte[] bytes, Class clazz) { 64 | try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); 65 | Input input = new Input(byteArrayInputStream)) { 66 | Kryo kryo = kryoThreadLocal.get(); 67 | // byte->Object:从byte数组中反序列化出对对象 68 | Object o = kryo.readObject(input, clazz); 69 | kryoThreadLocal.remove(); 70 | return clazz.cast(o); 71 | } catch (Exception e) { 72 | throw new SerializeException("Deserialization failed"); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/spring/CustomScanner.java: -------------------------------------------------------------------------------- 1 | package com.cccll.spring; 2 | 3 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 4 | import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; 5 | import org.springframework.core.type.filter.AnnotationTypeFilter; 6 | 7 | import java.lang.annotation.Annotation; 8 | 9 | /** 10 | * 自定义包扫描器 11 | * 12 | * @author cccll 13 | */ 14 | public class CustomScanner extends ClassPathBeanDefinitionScanner { 15 | 16 | public CustomScanner(BeanDefinitionRegistry registry, Class annoType) { 17 | super(registry); 18 | /** 19 | * ClassPathBeanDefinitionScanner 继承了 ClassPathScanningCandidateComponentProvider, 20 | * 在ClassPathScanningCandidateComponentProvider 中有两个TypeFilter集合,includeFilters、excludeFilters, 21 | * 满足任意includeFilters会被加载,同样的满足任意excludeFilters不会被加载。 22 | * 23 | * 此处是添加一个includeFilter,带有annoType类型注解或是继承annoType类型注解的注解的bean,都将被扫描进ioc容器 24 | */ 25 | super.addIncludeFilter(new AnnotationTypeFilter(annoType)); 26 | } 27 | 28 | @Override 29 | public int scan(String... basePackages) { 30 | return super.scan(basePackages); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/spring/CustomScannerRegistrar.java: -------------------------------------------------------------------------------- 1 | package com.cccll.spring; 2 | 3 | import com.cccll.annotation.RpcScan; 4 | import com.cccll.annotation.RpcService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 7 | import org.springframework.context.ResourceLoaderAware; 8 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 9 | import org.springframework.core.annotation.AnnotationAttributes; 10 | import org.springframework.core.io.ResourceLoader; 11 | import org.springframework.core.type.AnnotationMetadata; 12 | import org.springframework.core.type.StandardAnnotationMetadata; 13 | import org.springframework.stereotype.Component; 14 | 15 | /** 16 | * 扫描和过滤指定的注解 17 | * 18 | * @author cccll 19 | */ 20 | @Slf4j 21 | public class CustomScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { 22 | private static final String SPRING_BEAN_BASE_PACKAGE = "com.cccll.spring"; 23 | private static final String BASE_PACKAGE_ATTRIBUTE_NAME = "basePackage"; 24 | private ResourceLoader resourceLoader; 25 | 26 | /** 27 | * Spring启动时会自动调用:implements了ResourceLoaderAware接口类的实现方法:setResourceLoader(), 28 | * 将ResourceLoader注入进去,此处的ResourceLoader就是我们创建的ApplicationContext对象, 29 | * 因为ApplicationContext相关类或其父类都实现了ResourceLoader接口,或其子接口ResourcePatternResolver, 30 | * 后续代码会将此resourceLoader传递给我们自定义的包扫描器。 31 | * 实现以Aware后缀的接口,一般都是为了获得spring的一些数据 32 | * @param resourceLoader 33 | */ 34 | @Override 35 | public void setResourceLoader(ResourceLoader resourceLoader) { 36 | this.resourceLoader = resourceLoader; 37 | 38 | } 39 | 40 | @Override 41 | public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { 42 | // 获取RpcScan注解属性和值的map,然后转成AnnotationAttributes,AnnotationAttributes是Spring提供的注解的属性和值的包装类,底层也是map结构,它继承了LinkedHashMap 43 | AnnotationAttributes rpcScanAnnotationAttributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(RpcScan.class.getName())); 44 | String[] rpcScanBasePackages = new String[0]; 45 | if (rpcScanAnnotationAttributes != null) { 46 | // 获取basePackage属性的值 47 | rpcScanBasePackages = rpcScanAnnotationAttributes.getStringArray(BASE_PACKAGE_ATTRIBUTE_NAME); 48 | } 49 | //满足此条件则说明我们没有给@RpcScan的basePackage赋值 50 | if (rpcScanBasePackages.length == 0) { 51 | //得到标注@RpcScan的类的包名,作为被扫描的包(这里借用Spring Boot的思想,把标注@RpcScan的类放到最外层包下就可扫描所有子包下的类) 52 | rpcScanBasePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()}; 53 | } 54 | // 扫描标注 RpcService 注解的类 55 | CustomScanner rpcServiceScanner = new CustomScanner(beanDefinitionRegistry, RpcService.class); 56 | // 扫描标注 Component 注解的类 57 | CustomScanner springBeanScanner = new CustomScanner(beanDefinitionRegistry, Component.class); 58 | if (resourceLoader != null) { 59 | rpcServiceScanner.setResourceLoader(resourceLoader); 60 | springBeanScanner.setResourceLoader(resourceLoader); 61 | } 62 | int springBeanAmount = springBeanScanner.scan(SPRING_BEAN_BASE_PACKAGE); 63 | log.info("springBeanScanner扫描的数量 [{}]", springBeanAmount); 64 | int rpcServiceCount = rpcServiceScanner.scan(rpcScanBasePackages); 65 | log.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/java/com/cccll/spring/SpringBeanPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.cccll.spring; 2 | 3 | import com.cccll.annotation.RpcReference; 4 | import com.cccll.annotation.RpcService; 5 | import com.cccll.entity.RpcServiceProperties; 6 | import com.cccll.extension.ExtensionLoader; 7 | import com.cccll.factory.SingletonFactory; 8 | import com.cccll.provider.ServiceProvider; 9 | import com.cccll.provider.ServiceProviderImpl; 10 | import com.cccll.proxy.RpcClientProxy; 11 | import com.cccll.remoting.transport.ClientTransport; 12 | import lombok.SneakyThrows; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.beans.BeansException; 15 | import org.springframework.beans.factory.config.BeanPostProcessor; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.lang.reflect.Field; 19 | 20 | /** 21 | * 在创建bean之前调用此方法,以查看是否对类进行了注解,如果类上有@RpcService,说明是我们的服务实现类, 22 | * 则进行注册服务相关操作。 23 | * 24 | * postProcessBeforeInitialization 方法是在bean的构造方法执行完,我们自己指定的初始化方法(例如:@Bean(initMethod = "init")中指定的方法、 25 | * 实现InitializingBean接口的afterPropertiesSet方法、使用@PostConstruct注解的方法) 执行前执行。 26 | * 27 | * postProcessAfterInitialization 方法是在执行完我们自己指定的初始化方法之后执行。 28 | * 29 | * 这两个方法对要注册进IOC容器的所有bean都会执行,所以我们我可以写过滤条件,只对服务实现类执行 30 | * 31 | * 当这些方法都执行完毕后spring的容器才会创建 32 | * @author cccll 33 | */ 34 | @Component 35 | @Slf4j 36 | public class SpringBeanPostProcessor implements BeanPostProcessor { 37 | 38 | 39 | private final ServiceProvider serviceProvider; 40 | private final ClientTransport rpcClient; 41 | 42 | public SpringBeanPostProcessor() { 43 | serviceProvider = SingletonFactory.getInstance(ServiceProviderImpl.class); 44 | this.rpcClient = ExtensionLoader.getExtensionLoader(ClientTransport.class).getExtension("nettyClientTransport"); 45 | 46 | } 47 | 48 | /** 49 | * 在调用bean的初始化方法前调用此方法,以查看是否对类进行了注解,如果类上有@RpcService,说明是我们的服务实现类, 50 | * 则进行注册服务相关操作。 51 | * @param bean 52 | * @param beanName 53 | * @return 54 | * @throws BeansException 55 | */ 56 | @SneakyThrows 57 | @Override 58 | public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 59 | //过滤条件,只对类上有 @RpcService 的bean进行服务注册相关操作 60 | if (bean.getClass().isAnnotationPresent(RpcService.class)) { 61 | log.info("[{}] is annotated with [{}]", bean.getClass().getName(), RpcService.class.getCanonicalName()); 62 | // get RpcService annotation 63 | RpcService rpcService = bean.getClass().getAnnotation(RpcService.class); 64 | // build RpcServiceProperties 65 | RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder() 66 | .group(rpcService.group()).version(rpcService.version()).build(); 67 | serviceProvider.publishService(bean, rpcServiceProperties); 68 | } 69 | return bean; 70 | } 71 | 72 | /** 73 | * 执行完初始化方法后,查看每个bean中的每个字段是否有标注了@RpcReference的,对标注了此注解的字段,进行消费服务相关操作 74 | * @param bean 75 | * @param beanName 76 | * @return 77 | * @throws BeansException 78 | */ 79 | @Override 80 | public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 81 | Class targetClass = bean.getClass(); 82 | Field[] declaredFields = targetClass.getDeclaredFields(); 83 | for (Field declaredField : declaredFields) { 84 | RpcReference rpcReference = declaredField.getAnnotation(RpcReference.class); 85 | if (rpcReference != null) { 86 | RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder() 87 | .group(rpcReference.group()).version(rpcReference.version()).build(); 88 | RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceProperties); 89 | Object clientProxy = rpcClientProxy.getProxy(declaredField.getType()); 90 | declaredField.setAccessible(true); 91 | try { 92 | declaredField.set(bean, clientProxy); 93 | } catch (IllegalAccessException e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | 98 | } 99 | return bean; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /lrpc-framework/src/main/resources/META-INF/extensions/com.cccll.registry.ServiceDiscovery: -------------------------------------------------------------------------------- 1 | zk=com.cccll.registry.zk.ZkServiceDiscovery -------------------------------------------------------------------------------- /lrpc-framework/src/main/resources/META-INF/extensions/com.cccll.registry.ServiceRegistry: -------------------------------------------------------------------------------- 1 | zk=com.cccll.registry.zk.ZkServiceRegistry -------------------------------------------------------------------------------- /lrpc-framework/src/main/resources/META-INF/extensions/com.cccll.remoting.transport.ClientTransport: -------------------------------------------------------------------------------- 1 | nettyClientTransport=com.cccll.remoting.transport.netty.client.NettyClientTransport 2 | socketRpcClient=com.cccll.remoting.transport.socket.SocketRpcClient -------------------------------------------------------------------------------- /lrpc-framework/src/main/resources/META-INF/extensions/com.cccll.serialize.Serializer: -------------------------------------------------------------------------------- 1 | kyro=com.cccll.serialize.kyro.KryoSerializer -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.cccll 8 | lrpc 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | 13 | UTF-8 14 | 1.8 15 | 1.8 16 | 4.1.42.Final 17 | 18 | 4.0.2 19 | 29.0-jre 20 | 3.1.1 21 | 5.2.7.RELEASE 22 | 4.2.0 23 | 24 | 5.5.2 25 | 1.5.2 26 | 27 | 2.9.0 28 | 1.7.25 29 | 30 | 31 | 32 | lrpc-framework 33 | lrpc-common 34 | lrpc-demo-client 35 | lrpc-demo-interface 36 | lrpc-demo-server 37 | 38 | 39 | 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | 1.18.8 45 | provided 46 | 47 | 48 | 49 | com.google.guava 50 | guava 51 | ${guava.version} 52 | 53 | 54 | 55 | org.slf4j 56 | slf4j-api 57 | ${slf4j.version} 58 | 59 | 60 | org.slf4j 61 | slf4j-simple 62 | ${slf4j.version} 63 | 64 | 65 | 66 | org.junit.jupiter 67 | junit-jupiter-engine 68 | ${junit.jupiter.version} 69 | test 70 | 71 | 72 | org.junit.platform 73 | junit-platform-runner 74 | ${junit.platform.version} 75 | test 76 | 77 | 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-compiler-plugin 83 | 3.8.1 84 | 85 | ${maven.compiler.source} 86 | ${maven.compiler.target} 87 | ${encoding} 88 | 89 | 90 | 91 | 92 | 93 | 94 | --------------------------------------------------------------------------------