├── .idea ├── .name ├── compiler.xml ├── inspectionProfiles │ └── Project_Default.xml ├── libraries │ ├── Arquillian_JUnit_Release.xml │ ├── Maven__com_caucho_hessian_4_0_38.xml │ ├── Maven__com_dyuproject_protostuff_protostuff_api_1_0_8.xml │ ├── Maven__com_dyuproject_protostuff_protostuff_collectionschema_1_0_8.xml │ ├── Maven__com_dyuproject_protostuff_protostuff_core_1_0_8.xml │ ├── Maven__com_dyuproject_protostuff_protostuff_runtime_1_0_8.xml │ ├── Maven__junit_junit_4_12.xml │ ├── Maven__org_hamcrest_hamcrest_core_1_3.xml │ └── Maven__org_objenesis_objenesis_2_6.xml ├── misc.xml ├── modules.xml ├── uiDesigner.xml ├── vcs.xml └── workspace.xml ├── README.md ├── note ├── img │ ├── 1.png │ ├── 2.png │ ├── 20191108234611174.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ ├── 9.png │ └── image-20191110180140681.png └── note.md ├── pom.xml ├── soft-rpc.iml └── src └── main ├── java └── softrpc │ └── framework │ ├── invoker │ ├── ClientProxyBeanFactory.java │ ├── NettyChannelPoolFactory.java │ ├── NettyClientHandler.java │ ├── ResponseReceiver.java │ ├── ResponseReceiverHolder.java │ └── RevokerServiceCallable.java │ ├── loadBalance │ ├── common │ │ ├── LoadBalanceEngine.java │ │ └── LoadBalanceStrategyEnum.java │ └── strategy │ │ ├── LoadBalanceStategy.java │ │ └── impl │ │ ├── HashLoadBalanceStrategyImpl.java │ │ ├── PollingLoadBalanceStrategyImpl.java │ │ ├── RandomLoadBalanceStrategyImpl.java │ │ ├── WeightPollingLoadBalanceStrategyImpl.java │ │ └── WeightRandomLoadBalanceStrategyImpl.java │ ├── provider │ ├── NettyDecodeHandler.java │ ├── NettyEncodeHandler.java │ ├── NettyServer.java │ ├── NettyServerHandler.java │ └── ServiceProvider.java │ ├── serialization │ ├── common │ │ ├── SerializerEngine.java │ │ ├── SerializerFactory.java │ │ └── SerializerType.java │ ├── message │ │ ├── RequestMessage.java │ │ └── ResponseMessage.java │ └── serializer │ │ ├── HessianSerializer.java │ │ ├── JDKSerializer.java │ │ ├── ProtoStuffSerializer.java │ │ └── Serializer.java │ ├── spring │ ├── factoryBean │ │ ├── RpcReferenceFactoryBean.java │ │ └── RpcServiceFactoryBean.java │ ├── handler │ │ ├── RpcReferenceNamespaceHandler.java │ │ └── RpcServiceNamespaceHandler.java │ └── parser │ │ ├── RpcReferenceBeanDefinitionParser.java │ │ └── RpcServiceBeanDefinitionParser.java │ ├── test │ └── service │ │ ├── MainClient.java │ │ ├── MainServer.java │ │ ├── Service1.java │ │ ├── Service2.java │ │ └── imp │ │ ├── Service1imp.java │ │ ├── Service2imp1.java │ │ └── Service2imp2.java │ ├── utils │ ├── IPutil.java │ └── PropertyConfigUtil.java │ └── zookeeper │ ├── RegisterCenter.java │ ├── RegisterCenter4Governance.java │ ├── RegisterCenter4Invoker.java │ ├── RegisterCenter4Provider.java │ └── message │ ├── InvokerRegisterMessage.java │ └── ProviderRegisterMessage.java └── resources ├── META-INF ├── soft-reference.xsd ├── soft-service.xsd ├── spring.handlers └── spring.schemas ├── log4j.properties ├── rpc-reference.xml ├── rpc-service.xml └── soft-rpc.properties /.idea/.name: -------------------------------------------------------------------------------- 1 | softrpc -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/libraries/Arquillian_JUnit_Release.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_caucho_hessian_4_0_38.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_dyuproject_protostuff_protostuff_api_1_0_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_dyuproject_protostuff_protostuff_collectionschema_1_0_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_dyuproject_protostuff_protostuff_core_1_0_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_dyuproject_protostuff_protostuff_runtime_1_0_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_objenesis_objenesis_2_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Soft-RPC使用文档 2 | 3 | ​ Soft-RPC是一种基本功能完整的RPC框架的实现,供学习交流使用,也可以基于此方案进行二次开发,结合业务需要实现个性化RPC框架的研究和定制。 4 | 5 | ​ Email: xctian@zju.edu.cn 6 | 7 | ## 简介 8 | 9 | ​ Soft-RPC是一种轻量级的RPC框架实现方案,并未达到商用水准,但它具备RPC框架所需的核心功能,实现过程涉及**Netty、Zookeeper、Spring、JUC包下某些类的使用、序列化与反序列化、负载均衡算法、并发编程、网络编程、IO**、**设计模式**等后台开发技能领域,非常适合作为一个学习分布式服务框架、远程过程调用原理等内容的参考项目。以下简要介绍Soft-RPC的功能组成和特性: 10 | 11 | ### 配置文件 12 | 13 | - 使用自定义配置标签,支持高度自定义、可优化 14 | - 【soft-rpc.properties】进行全局配置,如zk超时时间,线程池大小等;【rpc-reference.xml】利用自定义标签进行服务消费者的相关参数配置;【rpc-service.xml】利用自定义标签进行服务提供者相关参数配置 15 | - 大部分配置项支持默认配置和容错配置 16 | 17 | ### 集成Spring 18 | 19 | - 集成Spring,支持“一键启动”:服务端只需要在启动类中加载“rpc-service.xml"文件生成ApplicationContext实例即可将启动服务;客户端只需要在启动类中加载“rpc-reference.xml"文件即可完成客户端初始化 20 | - 将项目打包至本地仓库,即可直接当RPC框架进行使用 21 | 22 | ### 序列化/反序列化方案 23 | 24 | - 支持三种序列化/反序列化协议:JDK原生序列化、ProtoStuff、Hessian,并且支持使用者自行配置 25 | - 支持客户端和服务端从上述协议中自由选择序列化协议 26 | - 为解决TCP传输过程中的粘包、半包问题,对消息进行简单的数据格式定义:消息头+消息体。其中消息头由2个int类型变量组成,分别表示采用的序列化协议code和消息体的长度;消息体则表示所要进行传输的数据。 27 | 28 | ### 负载均衡策略 29 | 30 | - 提供Random(随机)/WeightRandom(加权随机)/Polling(轮询)/WeightPolling(加权轮询)/Hash(IP哈希)五种负载均衡算法的选用 31 | - 支持客户端对负载均衡策略进行指定:在rpc-reference.xml中各客户端可配置不同的负载均衡策略 32 | 33 | ### Zookeeper注册中心 34 | 35 | - 通过自定义标签发布服务和引用服务,可实现服务的自动注册与发现 36 | - 支持服务地址变更自动感知,服务自动下线和自动扩容。监听服务提供者列表并实时推送变化至各客户端 37 | 38 | ### 优化和改进 39 | 40 | - 针对异步通信框架Netty实现结果同步等待机制:使用BlockingQueue实现对Netty异步返回结果的同步等待 41 | - 客户端初始化时,自动获取服务提供者地址并预初始化生成对应的ChannelPool,提高连接速度 42 | - 发布服务时可以对该服务的客户端连接数进行限制(通过信号量机制),以提升系统的稳定性 43 | - 线程池隔离:客户端对不同的服务进行RPC调用时使用不同的线程池,以隔离RPC服务风险(线程池大小可配置) 44 | 45 | ## 使用说明 46 | 47 | ### 先RUN起来 48 | 49 | 本项目在`softrpc.framework.test.service`提供了简单的调用RPC服务测试用例,直接RUN即可运行测试。以下简单介绍如何将本项目在你的电脑上RUN起来: 50 | 51 | 1. 参考[此处](https://www.cnblogs.com/xubao/p/10693202.html),在Windows环境下安装和部署zookeeper,找到bin文件夹下面的zkServer.cmd服务,运行启动。注意使用zookeeper时不要关闭CMD窗口。 52 | 53 | 2. clone本项目到本地,使用IDEA打开,配置maven和本地仓库,等待依赖自动导入完成。 54 | 55 | 3. 找到`softrpc.framework.test.service.MainServer`,运行main方法,可以看到控制台打印服务端的启动日志;再找到`softrpc.framework.test.service.MainClient`,同样运行它的main方法,可以看到控制台打印客户端的运行日志。 56 | 57 | ![1](note/img/1.png) 58 | 59 | ![2](note/img/1.png) 60 | 61 | 62 | ### 自定义RUN 63 | 64 | 你也可以设置自己的接口和实现类,但是注意需要在rpc-service.xml和rpc-reference.xml中发布和引用你创建的服务。本项目自定义的配置文件有:soft-rpc.properties / rpc-service.xml / rpc-reference.xml 65 | 66 | #### soft-rpc.properties 67 | 68 | 全局配置文件,涉及ZK地址和相关参数、默认负载均衡策略、序列化/反序列化协议等,具体可以参考该文件中的注释说明。除了以下情况必须配置的参数项,其他配置项都支持缺省配置 69 | 70 | - soft.rpc.zookeeper.address代表的ZK地址必须配置 71 | - 发布服务时, 如果没有在rpc-service.xml配置appName属性的soft:service标签, 那么soft.rpc.server.app.name必须配置 72 | - 引用服务时, 如果没有在rpc-reference.xml配置appName属性的soft:reference标签, 那么spft.rpc.client.app.name必须配置 73 | 74 | ```properties 75 | # 注册中心ZK地址,必须进行配置,无默认值 76 | soft.rpc.zookeeper.address=localhost:2181 77 | # session超时时间,默认500 78 | soft.rpc.zookeeper.session.timeout=3000 79 | # 连接超时时间,默认500 80 | soft.rpc.zookeeper.connection.timeout=3000 81 | # 服务端序列化协议,Default,可选值:Default/Hessian/ProtoStuff 82 | soft.rpc.server.serializer=Default 83 | # 客户端序列化协议,默认Default,可选Hessian/ProtoStuff/Default 84 | soft.rpc.client.serializer=Default 85 | # 负载均衡算法可选值:Random/WeightRandom/Polling/WeightPolling/Hash,若配置有误,自动采用Random算法 86 | soft.rpc.client.clusterStrategy.default=WeightRandom 87 | # 客户端对每个主机的初始化Channel数量,默认10 88 | soft.rpc.client.channelPoolSize=10 89 | # 客户端调用RPC服务线程池的大小,默认10 90 | soft.rpc.client.threadWorkers=10 91 | # 发布服务时默认命名空间(标签没有配置appName时采用) 92 | soft.rpc.server.app.name=test 93 | # 引入服务时采用的默认命名空间(标签没有配置appName时采用) 94 | soft.rpc.client.app.name=test 95 | ``` 96 | 97 | #### rpc-service.xml 98 | 99 | 服务发布相关的配置文件,主要涉及soft:service标签,示例如下 100 | 101 | ```xml 102 | 103 | 104 | 113 | ``` 114 | 115 | - id:不同的soft:service要求id属性不一样 116 | - appName:该属性如果缺失,就会采用全局配置文件 soft-rpc.properties 中的 soft.rpc.server.app.name 值,如果两者都缺失,则抛出异常 117 | - interface:接口的全限定名(appName + interface是该服务在注册中心的key) 118 | - ref:该接口的实现类bean标签id 119 | - weight:提供服务时该主机的权重值(范围[1,100]) 120 | - workThreads:该主机提供该服务的限流数,即同一时刻与客户端建立的最大连接数 121 | - serverPort:该主机发布该服务的端口号 122 | - timeout:服务超时时间 123 | - groupName:应用所属分组名称, 本项目未用到。如果要在ZK中设置更复杂的注册路径,以实现服务治理相关功能,则可以使用 124 | 125 | 本框架支持将多个服务发布在不同的端口,同时也支持对同一个接口,发布不同的服务实现类: 126 | 127 | ```xml 128 | 129 | 130 | 139 | ``` 140 | 141 | ```xml 142 | 143 | 144 | 153 | ``` 154 | 155 | #### rpc-reference.xml 156 | 157 | 服务引用的相关配置文件,主要涉及soft:reference标签,示例如下 158 | 159 | ```xml 160 | 161 | 167 | ``` 168 | 169 | - id:不同的soft:reference要求id属性不一样 170 | - appName:该属性如果缺失,就会采用全局配置文件soft.properties 中的 soft.rpc.client.app.name 值,如果两者都缺失,就会抛出异常 171 | - interface:接口的全限定名(appName + interface是该服务在注册中心的key) 172 | - clusterStrategy:采用的载均衡策略,缺省时就使用全局配置文件 soft-rpc.properties 中的 soft.rpc.client.clusterStrategy.default 值, 如果两者都缺省,就使用框架的默认值(Random) 173 | - timeout:服务超时时间 174 | - groupName:应用所属分组名称, 本项目未用到。如果要在ZK中设置更复杂的注册路径,以实现服务治理相关功能,则可以使用 175 | 176 | ### 依赖RUN 177 | 178 | 该项目是一个RPC框架,它可以作为jar包被其他项目所依赖使用。使用maven的install命令,将项目打包到本地仓库,其他项目依赖时的dependency如下 179 | 180 | ```xml 181 | 182 | com.xctian.rpc 183 | soft-rpc 184 | 1.0-SNAPSHOT 185 | 186 | ``` 187 | 188 | 作为依赖在新项目中使用时,必须提供配置文件:soft-rpc.properties / rpc-service.xml / rpc-reference.xml, 且它们都要放在 /resource 根目录下,然后根据自己业务需要创建自己的接口和实现类,并按上述说明提供配置标签即可使用(类似于Dubbo的使用要求) 189 | 190 | ## Soft-RPC开发说明 191 | 192 | [这里](note/note.md)教你如何从零开发Soft-RPC框架,介绍本项目各个功能模块的实现要点,开发思路,以及一些学习笔记。 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /note/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/1.png -------------------------------------------------------------------------------- /note/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/2.png -------------------------------------------------------------------------------- /note/img/20191108234611174.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/20191108234611174.png -------------------------------------------------------------------------------- /note/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/3.png -------------------------------------------------------------------------------- /note/img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/4.png -------------------------------------------------------------------------------- /note/img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/5.png -------------------------------------------------------------------------------- /note/img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/6.png -------------------------------------------------------------------------------- /note/img/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/7.png -------------------------------------------------------------------------------- /note/img/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/8.png -------------------------------------------------------------------------------- /note/img/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/9.png -------------------------------------------------------------------------------- /note/img/image-20191110180140681.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/note/img/image-20191110180140681.png -------------------------------------------------------------------------------- /note/note.md: -------------------------------------------------------------------------------- 1 | ## Soft-RPC介绍 2 | 3 | ### RPC简介 4 | 5 | > RPC(Remote Procedure Call,远程过程调用)用于实现部署在不同机器上的系统之间的方法调用,使得程序可以像访问本地服务一样,通过网络传输调用远程系统提供的服务。 6 | 7 | 具体而言: 8 | 9 | - RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。 10 | - RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模型。 11 | - 客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。 12 | 13 | web项目中,**调用本地服务的过程**:编写接口和实现类,然后将实现类托管至Spring容器,之后需要用到该服务时直接使用@Autowired将其注入即可。但在实际的开发环境中,并不是所有的服务都是由我们自己来进行开发,我们经常需要调用由别的开发人员开发的服务,那么通过使用RPC框架,在本地导入其他服务接口的依赖包之后,就可以类似上述过程一样去调用远程的服务。 14 | 15 | - 为什么不直接在本地导入外部服务的接口+实现类的依赖包? 16 | 17 | 实际开发环境中,接口的实现类通常很容易改变,如果该服务被大量的调用者所依赖,那么一旦这个服务的实现类发生改变,就要去发包让所有调用者更新本地的依赖包,系统耦合性增加;如果使用RPC的方式,则实现类只需要交给服务的提供者进行维护即可。 18 | 19 | **调用远程服务的过程(RPC方式):** 20 | 21 | image-20191108234611174 22 | 23 | 1)服务消费方(client)调用以本地调用方式调用服务; 24 | 25 | 2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; 26 | 27 | 3)client stub找到服务地址,并将消息发送到服务端; 28 | 29 | 4)server stub收到消息后进行解码; 30 | 31 | 5)server stub根据解码结果调用本地的服务; 32 | 33 | 6)本地服务执行并将结果返回给server stub; 34 | 35 | 7)server stub将返回结果打包成消息并发送至消费方; 36 | 37 | 8)client stub接收到消息,并进行解码; 38 | 39 | 9)服务消费方得到最终结果。 40 | 41 | RPC框架的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明。从而使得用户对远程服务的调用可以像本地服务一样。 42 | 43 | ### 序列化及反序列化 44 | 45 | - 序列化(Serialization):将对象的状态信息转换为可存储或传输的形式的过程。 46 | - 反序列化(Deserialization):序列化的逆过程。将字节序列恢复为对象的过程。 47 | 48 | **通过序列化可以解决的问题** 49 | 50 | - 通过将对象序列化为字节数组,使得不共享内存通过网络连接的系统之间能够进行对象的传输; 51 | - 通过将对象序列化为字节数组,可以将对象永久存储到存储设备; 52 | - 解决远程接口调用JVM之间内存无法共享的问题。 53 | 54 | **评价序列化算法优劣的指标** 55 | 56 | - 序列化后码流的大小; 57 | - 序列化本身的速度及系统资源开销大小(内存,CPU)。 58 | 59 | #### 常用的序列化工具介绍 60 | 61 | **JDK默认的序列化工具** 62 | 63 | > JAVA原生序列化方式,主要通过对象输入流ObjectInputStream和对象输出流ObjectOutputStream来实现,序列化对象需要实现Serializable接口。 64 | 65 | - 序列化时,只对对象的状态进行保存,而不管对象的方法; 66 | 67 | - 父类实现序列化时,子类自动实现序列化,不需要显式实现Serializable接口; 68 | 69 | - 一个对象的实例变量引用其他对象时,序列化该对象时也把引用对象进行序列化; 70 | 71 | - 字段被声明为transient后,JDK默认序列化机制会忽略该字段。 72 | 73 | **优点:** 74 | 75 | - Java语言自带,无需引入第三方依赖; 76 | - 与Java有天然的最好的易用性与亲和性。 77 | 78 | **缺点:** 79 | 80 | - 只支持Java语言,不支持跨语言; 81 | - 性能欠佳,序列化后产生的码流大小过大,对引用过深的对象序列化可能导致OOM。 82 | 83 | Java序列化会把要序列化的对象类的元数据和业务数据全部序列化为字节流,而且是把整个继承关系上的东西全部序列化了。它序列化出来的字节流是对那个对象结构到内容的完全描述,包含所有的信息,因此效率较低而且字节流比较大。但是由于确实是序列化了所有内容,所以可以说什么都可以传输,因此也更可用和可靠。 84 | 85 | **Hessian** 86 | 87 | > Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。序列化对象需要实现Serializable接口。 88 | 89 | - Hessian相比Java原生序列化, 序列化后的二进制数据量更小, 因此传输速度和解析速度都更快 90 | 91 | - 采用简单的结构化标记, 并且只存储对象数据内容部分, 而JJDK默认的序列化工具还会存一些继承关系之类; 92 | - 采用引用取代重复遇到的对象, 避免了重复编码; 93 | - 支持多种不同语言。 94 | 95 | **ProtoStuff** 96 | 97 | protostuff 基于Google protobuf,但是提供了更多的功能和更简易的用法。其中,protostuff-runtime 实现了无需预编译对java bean进行protobuf序列化/反序列化的能力。protostuff-runtime的局限是序列化前需预先传入schema(可以由代码方法生成, 不需要手动改创建),其中,schema中包含了对象进行序列化和反序列化的逻辑;反序列化不负责对象的创建只负责复制,因而**必须提供默认构造函数**。此外,protostuff 还可以按照protobuf的配置序列化成json/yaml/xml等格式。 98 | 99 | 在性能上,protostuff不输原生的protobuf,甚至有反超之势。 100 | 101 | #### **序列化工具引擎** 102 | 103 | 本项目用到三种:Default / Hessian / ProtoStuff, 一个Serializer接口有多种实现类, 如何优雅的进行选择? 使用可配置化的序列化工具引擎,有两种实现思路: 104 | 105 | - 工厂模式方案: 106 | 107 | 添加一个工厂类, 提供根据名称获取Serializer实现类的方法, 最后用一个Engine类即可以实现优雅的选择。这样做的缺陷是每次序列化/反序列化请求都需要生成新的Serializer,消耗存储空间。 108 | 109 | - 使用Map+Enum枚举类: 110 | 111 | 添加一个枚举类, 其中主要存储代表不同实现类的枚举值。在Engine类里新增常量map, key存储枚举类里的不同枚举,value存储对应具体的Serializer实现类,Engine类加载时在static代码块初始化map,根据这个传入的Serializer名称在map中找对应的实现类对象,执行实际的功能方法。**可以解决单例问题**。 112 | 113 | image-20191110180140681 114 | 115 | #### 自定义消息格式 116 | 117 | ##### 粘包/半包问题 118 | 119 | 上面介绍的是几种序列化/反序列化工具,RPC调用的底层使用的Netty框架基于TCP/IP,是基于字节流进行传输的,像流水一样连接在一起,TCP底层并无法感知业务数据的具体的含义,无法按照具体的业务含义对消息进行分包,而只会按照TCP缓冲区的实际大小情况来对包进行划分。当业务数据被拆分为多个数据包,这些数据包达到目的端后有以下三种情况: 120 | 121 | - 刚好按照业务数据本身的边界逐个到达目的(业务数据的边界刚好是数据包的边界) 122 | 123 | image-20191110180140681 124 | 125 | - 多个业务数据组合成为一个数据包,即**粘包**现象(数据包大小刚好等于多个业务数据) 126 | 127 | image-20191110180140681 128 | 129 | - 到达目的的数据包中只包含了部分不完整的业务数据,数据包大小小于n个业务数据,那么第n个业务数据将被拆分到多个数据包传输,即**半包**现象。 130 | 131 | image-20191110180140681 132 | 133 | 解决半包/粘包问题的关键是能够区分完整的业务应用的数据边界,能够按照此边界完整地接收Netty传输的数据。 134 | 135 | 本项目中,利用自定义的消息格式,结合Netty自定义编解码器开发,作为半包/粘包问题的简单解决方案。 136 | 137 | ##### 自定义消息格式 (消息编解码规则) 138 | 139 | 消息格式定义如下:serializerCode|dataLength|messageData 140 | 141 | 上述消息格式规定了字节流在传输的时候由三部分组成: 142 | 143 | 1. int类型的serializerCode,它是序列化协议对应枚举中的code(见项目serializer\common\Serializer.java) 144 | 145 | 用于标识该次传输所采用的序列化/反序列化协议 146 | 147 | 2. int类型的消息长度dataLength,它表示需要传输的数据的大小 148 | 149 | 3. 需要传输的业务数据 150 | 151 | **Netty自定义编解码器** 152 | 153 | - MessageToByteEncoder是Netty为消息转换为byte提供的一个抽象类,本项目的编码器只需继承MessageToByteEncoder,并严格按照上述规定的自定义编码格式,重写encode方法如下: 154 | 155 | ```java 156 | public void encode(ChannelHandlerContext channelHandlerContext, Object in, ByteBuf out) throws Exception { 157 | long startTime = System.currentTimeMillis(); 158 | // 获取序列化协议code 159 | int serializerCode = serializeType.getSerializeCode(); 160 | // 将其写入消息头部第一个int 161 | out.writeInt(serializerCode); 162 | // 将对象进行序列化 163 | byte[] data = SerializerEngine.serialize(in,serializeType); 164 | // 将data长度写入消息头部第二个int 165 | out.writeInt(data.length); 166 | // 将消息体写入 167 | out.writeBytes(data); 168 | } 169 | ``` 170 | 171 | > encode方法被调用时将会传入需要被编码的消息in,然后将编码后的消息存入ByteBuf类型的out,该ByteBuf随后会被转发给pipeline中的下一个handler 172 | 173 | ​ 以上实现了按照自定义消息格式(int+int+data)对消息进行编码并写入NettyChannel 174 | 175 | - ByteToMessageEncoder是Netty为byte转换为消息提供的一个抽象类,本项目的解码器只需继承ByteToMessageEncoder,并严格按照上述规定的自定义编码格式,重写decode方法如下: 176 | 177 | ```java 178 | public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 179 | // 消息头部长度8字节=序列化协议int + 消息长度int 180 | if(in.readableBytes() < 8){ 181 | return; 182 | } 183 | in.markReaderIndex(); 184 | int serializerCode = in.readInt(); 185 | String serializer = SerializerType.getByCode(serializerCode).getSerializeName(); 186 | int dataSize = in.readInt(); 187 | if(dataSize < 0){ 188 | ctx.close(); 189 | } 190 | // 若当前可读字节数小于消息长度,则重置readerIndex,直至可以获取到消息长度的字节数 191 | if(in.readableBytes() < dataSize){ 192 | in.resetReaderIndex(); 193 | return; 194 | } 195 | byte[] data = new byte[dataSize]; 196 | // 从channel读取数据至byte数组data 197 | in.readBytes(data); 198 | Object obj = SerializerEngine.deserialize(data,genericClass,serializer); 199 | out.add(obj); 200 | } 201 | ``` 202 | 203 | > decode方法调用时将会传入ByteBuf类型的待解码的数据in,以及一个用来添加解码后的消息的List。对这个方法的调用将会重复进行,直至确定没有新的元素被添加到List,或者该ByteBuf中没有更多可读取的字节为止。然后若该List不空,那么它将会被传递给Pipeline中的下一个handler 204 | 205 | 两个int占8个字节,所以在能读取到的消息字节小于8时,先不读数据,直接return。等有数据后,按照上述消 息编码规则,先读取第一个int:序列化协议code,再读第二个Int:待解码的消息长度,最后根据该长度读取待 解码消息,并根据第一个int获取序列化协议,按照该协议将待解码消息反序列化成为java对象,然后存入list。 以上完成了一个完整的消息解码过程。 206 | 207 | 208 | 209 | 以上,完成了Netty网络传输中重要的三个部分:**序列化/反序列化协议、消息编解码规则、解决半包/粘包问题**。 210 | 211 | ### Netty网络传输 212 | 213 | #### 特性 214 | 215 | - ChannelPool:客户端在启动完成,获得服务注册地址后,会针对每一个服务地址预先建立配置数量的channel进行复用 216 | - ThreadWorkers: 客户端调用rpc服务使用的线程数量是可配置的 217 | - 使用线程池处理客户端提交的请求,阻塞等待RPC调用的返回结果 218 | - 使用concurrent包的工具BlockingQueue,在Netty异步返回后同步阻塞等待结果 219 | - 服务端发布服务时可以自行设置限流信号量大小,以使得每个服务同时支持连接的客户端数量是可控的 220 | 221 | #### ChannelPool实现Channel复用 222 | 223 | NettyChannelPoolFactory是单例工厂类,成员变量Map> channelPoolMao,用于存储对应不同主机地址的channelPool,每个channelPool存储一定数量的channel,该ch数量由soft-rpc.properties下的channelPoolSize指定。channelPool数据结构使用的是阻塞队列,这样的好处是并发条件下channel的存取不会出错。 224 | 225 | 由于Netty是异步框架,在创建channel时,为返回的ChannelFuture添加监听器,用于监听channel的创建情况。为达到同步等待channel创建结果的目的,使用AQS工具countdownLatch,在channel创建结果未产生前,调用await()方法阻塞线程,直到channel创建结果产生,无论该结果是创建成功还是失败,最后均调用countDown()让线程继续。创建单个channel的方法(方法签名Channel registerChannel(InetSocketAddress socketAddress))核心代码如下: 226 | 227 | ```java 228 | ChannelFuture channelFuture = bootstrap.connect().sync(); 229 | final Channel newChannel = channelFuture.channel(); 230 | final CountDownLatch countDownLatch = new CountDownLatch(1); 231 | final List isSuccessHolder = Lists.newArrayListWithCapacity(1); 232 | // 监听是否channel创建成功 233 | channelFuture.addListener(new ChannelFutureListener() { 234 | @Override 235 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 236 | if (channelFuture.isSuccess()) { 237 | isSuccessHolder.add(Boolean.TRUE); 238 | } else { 239 | channelFuture.cause().printStackTrace(); 240 | isSuccessHolder.add(Boolean.FALSE); 241 | } 242 | countDownLatch.countDown(); 243 | } 244 | }); 245 | 246 | // 阻塞等待Channel创建的结果 247 | countDownLatch.await(); 248 | 249 | if (isSuccessHolder.get(0)) { 250 | return newChannel; 251 | }else{ 252 | return null; 253 | } 254 | ``` 255 | 256 | 创建channelPool时候,会不断调用上述registerChannel方法产生新的channel放入BlockingQueue,由于channe的创建很可能失败,所以创建每一个channel时候都需要使用一个外部循环,直到channel创建成功为止: 257 | 258 | ```java 259 | while (null == channel) { 260 | // 创建失败则重试 261 | channel = registerChannel(socketAddress); 262 | } 263 | ``` 264 | 265 | 在每次利用完channel后,需要调用release方法将其回收至对应的BlockingQueue,以便复用(详情参照项目代码对应的方法)。 266 | 267 | #### ThreadWorkers 268 | 269 | 客户端代理类ClientProxyBeanFactory,用于为客户端创建代理对象,其包含一个静态成员变量fixedThreadPool,负责submit线程提交的请求,其大小由soft-rpc.properties中ThreadWorkers配置项确定。如下:在每个请求使用一个线程进行submit的模式下,可以同步阻塞等待结果: 270 | 271 | ```java 272 | // 任务扔到线程池执行 273 | responseMessageFuture = threadPool.submit(new RevokerServiceCallable(inetSocketAddress, requestMessage)); 274 | //阻塞等待response 275 | ResponseMessage response = responseMessageFuture.get(requestMessage.getTimeout(), TimeUnit.MILLISECONDS); 276 | ``` 277 | 278 | #### 返回结果的封装设计 279 | 280 | 提供一个返回结果的包装类ResponseReceiver,其中含有一个大小为1的BlockingQueue成员变量用于存储结果,以及一个responseTime属性这个返回结果创建的时间,提供isExpire方法校验该结果是否过期,实现思路是检查当前时间-responseTime,如果大于timeout参数则过期。 281 | 282 | 提供一个返回结果容器ResponseReceiverHolder,含有一个属性MapresponseMap,用于存储结果,key是请求id,整个类会提供一个Excutors.newSingleThreadExecutor()的单线程线程池不断检查map中过期的ResponseReceiver,如过期则移除,以避免内存泄漏(异常情况下ResponseMessage进入容器后并不会被立刻取走而发生超时,因此该线程池是有必要存在的) 283 | 284 | Netty客户端发起请求调用时候,提交RevokerServiceCallable进入线程池执行,RevokerServiceCallable的call方法里面先初始化了本次请求消息对应的结果容器,然后将该消息write进入NettyChannel进行netty通信,然后在ResponseReceiverHolder.getValue处被阻塞(该阻塞唤醒机制由包装类的BlockingQueue提供)。当服务端执行完本地方法获得执行结果responseMessage并将其写入channel后,由客户端NettyClientHandler调用channelRead0方法将其存入结果容器ResponseReceiverHolder,从阻塞处重新恢复继续执行。 285 | 286 | #### 服务端限流设计 287 | 288 | 利用JUC具类Semaphore作为流控基础设施,实现服务端的限流,即同时执行的最大请求数。Semaphore大小threadWorkers在服务发布时可以进行配置,客户端引入服务时会将该变量读取并封装至requestMessage,然后服务端在执行每一个请求时都可以通过客户端传入的requestMessage获取threadWorkers。服务端在最开始时,初始化一个threadWorkers大小的Semaphore,此后每次服务端线程想要对传入的requestMessage执行调用前,需要执行semaphore的tryAcquire方法获取一个信号量,若成功才能继续执行,否则返回调用超时异常,以此来保证该服务提供者同时最多执行的任务数。 289 | 290 | 服务端限流的必要性:因为服务端在接收到调用端的requestMessage后,实际上是通过反射的操作去执行本地方法并得到返回结果的,而反射操作的执行效率低下,故为保证服务资源的可用性及维护服务稳定性,需要对同时执行的请求进行数量上的限制。 291 | 292 | ```java 293 | try { 294 | // 利用semaphore实现服务端限流,因为反射操作执行效率低下,如果大量反射同时执行,将占用资源 295 | acquire = semaphore.tryAcquire(consumeTimeout, TimeUnit.MILLISECONDS); 296 | if(acquire){ 297 | // 成功则发起反射调用,调用服务 298 | responseMessage = ServiceProvider.excuteMethodFromRequestMessage(requestMessage); 299 | }else { 300 | LOGGER.warn("服务限流,请求超时"); 301 | } 302 | }catch (Exception e){ 303 | LOGGER.error("服务方反射调用本地方法时产生错误",e); 304 | throw new RuntimeException("服务方反射调用本地方法时产生错误"); 305 | } 306 | ``` 307 | 308 | ### Zookeeper注册中心 309 | 310 | 本项目使用Zookeeper作为服务注册中心 311 | 312 | #### 特性 313 | 314 | - 服务自动注册:集成了spring,只需按照约定提供配置文件和xml即可发布服务并自动注册至ZK 315 | - 服务推送:由于对结点注册了监听器,当服务信息发送变化时,会自动将服务新同步至本地缓存 316 | 317 | #### 服务的注册与发现 318 | 319 | > 在基于SOA架构的应用中,应用提供对外服务的同时也会调用外部系统所提供的服务。当应用越来越多,服务越来越多的时候,服务之间的依赖关系会变得越来越复杂,此时依靠人工来维护服务之间的依赖关系以及管理服务的上下线变得十分困难。此时我们需要服务注册中心来解决服务的自动发现、服务自动上下线等问题。 320 | 321 | 注册中心相当于信息仓库,存储了rpc调用需要的信息,对于服务端而言,需要将发布的服务信息进行注册,对于消费端而言,需要按照规定格式去注册中心获取其所引用服务的注册信息,然后将自己的信息注册到注册中心: 322 | 323 | - 服务的服务端启动时,将服务提供者信息ProviderRegisterMessage(IP,端口,服务接口类路径等)组成的znode路径写入Zookeeper(临时结点),然后对服务的消费者结点路径注册监听器,获取消费者信息缓存到本地,在某个客户端注册信息(临时结点)失去连接时,会触发监听器使服务端更新消费者缓存信息,这样即可完成服务的注册操作。 324 | - 服务消费端在发起服务的调用之前,先连接Zookeeper,对服务提供者结点路径注册监听器,同时获取服务提供者信息缓存到本地,发起调用时,采用某种负载均衡算法选择本地缓存列表中的其中一个服务提供者发起调用,最终完成本次rpc调用,这样即可完成服务的发现操作。 325 | 326 | 由上所述,服务的消费端和服务端对注册中心的需求功能有所差异,所以可以设计一个注册中心类RegisterCenter,实现不同的接口,消费端接口声明消费端需要的功能,服务端接口声明服务所需功能,RegisterCenter同时实现以上两个接口,使用时可以使用不同的接口引用指向相同的RegisterCenter单例对象,这样做可以使得代码逻辑清晰: 327 | 328 | ```java 329 | /** 330 | * 客户端注册中心 331 | */ 332 | private static RegisterCenter4Invoker registerCenter4Invoker = RegisterCenter.getInstance(); 333 | /** 334 | * 服务端注册中心 335 | */ 336 | private static final RegisterCenter4Provider registerCenter4Provider = RegisterCenter.getInstance(); 337 | ``` 338 | 339 | 除了服务端接口和消费端接口以外,还可以提供一个服务治理接口,声明一些服务治理所需的相关功能,同样也在RegisterCenter中实现。 340 | 341 | image-20191110180140681 342 | 343 | 下图为zookeeper节点结构,当不同服务端发布服务时,在appName节点层进行区分,所以即使appX和appY之间拥有完全相同的接口全限定名,引用服务时也不会混淆,因为服务的key是appName+接口全限定名。每个key下面都有provider和invoker结点,这两个结点下都是临时结点,分别存储的是服务提供者信息和服务消费者信息。 344 | 345 | image-20191110180140681 346 | 347 | 结点信息都是使用fastjson转换成为jason字符串然后作为临时节点的名称进行存储,即可作为节点的注册过程。当获取节点注册信息时,通过fastjson将临时节点直接转换成java对象。 348 | 349 | ### 负载均衡 350 | 351 | #### 特性 352 | 353 | - 多种软负载策略自由选择:随机/轮询/IP hash/加权随机/加权轮询 354 | - 支持组合策略:每个引用服务均可以单独配置软负载策略(在引用标签中) 355 | - 支持懒配置:若不在配置标签中手动配置,则使用配置文件的默认策略,若配置文件也未声明,就使用随机(Random)策略 356 | 357 | #### 软负载引擎类 358 | 359 | 与序列化协议的处理方法类似,一种功能有多种实现方法,我们需要将以上算法整合到我们的RPC框架的实现中去。为此定义一个软负载策略引擎类LoadBalanceEngine。使用门面模式,对外暴露统一简单的API界面,根据不同的策略配置来选取不同的策略服务 360 | 361 | - 首先,在LoadBalanceEngine类里面的静态代码块中将上述软负载算法的实现预加载到clusterStrategyMap中,key代表算法的枚举字符串,value为该软负载算法的具体实现类 362 | 363 | - 对外暴露统一的静态方法API,根据传入的枚举字符串,从map中获取对应的软负载算法实现 364 | 365 | ```java 366 | /** 367 | * 缓存的负载均衡接口实现类对象Map,相当于存储了实现类的单例 368 | */ 369 | private static final Map STATEGY_MAP = Maps.newConcurrentMap(); 370 | 371 | // 饿汉单例,可以理解为缓存map 372 | static { 373 | STATEGY_MAP.put(LoadBalanceStrategyEnum.Random, new RandomLoadBalanceStrategyImpl()); 374 | STATEGY_MAP.put(LoadBalanceStrategyEnum.Polling, new PollingLoadBalanceStrategyImpl()); 375 | ....... 376 | ....... 377 | } 378 | 379 | public static ProviderRegisterMessage select(List providerRegisterMessages,String loadBalanceStrategy){ 380 | LoadBalanceStrategyEnum loadBalanceStrategyEnum = LoadBalanceStrategyEnum.queryByCode(loadBalanceStrategy); 381 | if(null != loadBalanceStrategyEnum){ 382 | return STATEGY_MAP.get(loadBalanceStrategyEnum).select(providerRegisterMessages); 383 | }else{ 384 | return STATEGY_MAP.get(LoadBalanceStrategyEnum.Random).select(providerRegisterMessages); 385 | } 386 | } 387 | ``` 388 | 389 | 包结构如下: 390 | 391 | image-20191110180140681 392 | 393 | #### 软负载实现类 394 | 395 | 随机/轮询/IP hash/加权随机/加权轮询 396 | 397 | - 轮询:该算法依赖于成员变量index的值的自增来实现轮询,使用了ReetrantLock,保证高并发情况下获取Index时是严格按照逻辑顺序进行读取和使用的。对于产生异常的情形,则直接采用Random策略,也可以重试轮询,但递归存在内存耗尽的风险。 398 | 399 | ```java 400 | private Lock lock = new ReentrantLock(); 401 | 402 | @Override 403 | public ProviderRegisterMessage select(List providerRegisterMessages) { 404 | ProviderRegisterMessage providerRegisterMessage = null; 405 | try { 406 | // 尝试获取锁,10ms的超时时间 407 | lock.tryLock(10, TimeUnit.MILLISECONDS); 408 | if (index >= providerRegisterMessages.size()) { 409 | index = 0; 410 | } 411 | providerRegisterMessage = providerRegisterMessages.get(index); 412 | index++; 413 | } catch (InterruptedException e) { 414 | e.printStackTrace(); 415 | } finally { 416 | lock.lock(); 417 | } 418 | 419 | // 兜底策略:如果获取失败则使用随机负载均衡算法选取一个 420 | return null == providerRegisterMessage ? new RandomLoadBalanceStrategyImpl().select(providerRegisterMessages) : providerRegisterMessage; 421 | } 422 | ``` 423 | 424 | - IP hash:获取本地IP对应hashcode,再对服务提供信息列表大小进行取模,得到的值成为目标服务节点在列表中的索引,根据该索引返回最终的目标服务节点。 425 | 426 | ```java 427 | @Override 428 | public ProviderRegisterMessage select(List providerRegisterMessages) { 429 | // 直接通过本地IP地址的hash值对服务列表大小取模,得到的结果作为结果索引 430 | String localIP = IPutil.localIp(); 431 | int hashCode = localIP.hashCode(); 432 | return providerRegisterMessages.get(hashCode % providerRegisterMessages.size()); 433 | } 434 | ``` 435 | 436 | - 加权随机/加权轮询:服务发布时带有权重weight属性,范围是[1,100],且有缺省配置,为体现权重,新建一个泛型类为Integer的ArrayList,然后按顺序遍历服务注册信息列表,单个服务注册信息权重是多少,就往该list里面添加个多少个该服务注册信息在列表中的索引下标,最后无论是随机轮询还是顺序轮询,都基于该list选取服务注册信息即可。 437 | 438 | ```java 439 | public static List getIndexListByWeight(List providerRegisterMessages) { 440 | if (null == providerRegisterMessages || providerRegisterMessages.size() == 0) { 441 | return null; 442 | } 443 | ArrayList list = new ArrayList<>(); 444 | for (ProviderRegisterMessage each : providerRegisterMessages) { 445 | int index = 0; 446 | int weight = each.getWeight(); 447 | while (weight-- > 0) { 448 | list.add(index); 449 | } 450 | index++; 451 | } 452 | return list; 453 | } 454 | ``` 455 | 456 | ### Spring集成 457 | 458 | #### 特性 459 | 460 | - 自定义标签配置:服务发布和引入都可以使用自定义标签 461 | 462 | - 支持可配置/懒配置:支持多属性配置,除了少量必须配置如zk地址/appName需要用户输入显式配置以外,大部分的配置都支持默认值 463 | 464 | - 支持快速启动:只需在启动类中加载ApplicationContext实现类即可让服务端或者客户端启动起来,代理对象的生成以及初始化过程都可以直接交给Spring 465 | 466 | ```java 467 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("rpc-xxx.xml"); 468 | ``` 469 | 470 | #### FactoryBean介绍 471 | 472 | Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象。创建出来的对象是否属于单例由isSingleton中的返回决定。 473 | 474 | 一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程涉及比较复杂的业务逻辑,如果按照传统的方式,则需要在xml中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。 475 | 476 | FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean。 477 | 478 | 详情可以参照:[**FactoryBean的使用**](https://www.jianshu.com/p/d37737e823dc) 479 | 480 | #### 自定义标签的实现 481 | 482 | 本项目通过自定义标签,让spring对标签进行解析,再通过标签内容完成对象的创建和一些初始化的过程。下面以服务消费端为例,详细说明如何实现服务消费端的自定义标签reference 483 | 484 | 这些流程只涉及IOC核心,所以只需引入以下maven依赖 485 | 486 | ```xml 487 | 488 | m2.repository.org.springframework 489 | spring-context 490 | 4.1.3.RELEASE 491 | 492 | ``` 493 | 494 | 如下是我们使用自定义标签reference在rpc-reference.xml文件中为其声明对象的配置: 495 | 496 | ```xml 497 | 498 | 503 | 504 | 510 | 511 | 517 | 518 | ``` 519 | 520 | ##### 编写xsd文件 521 | 522 | > ​ xsd文件描述了XML文档的结构。可以用一个指定的xsd文件来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过xsd文件指定一个XML文档所允许的结构和内容,并可据此检查一个XML文档是否是有效的。xsd本身是一个XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。 523 | > ​ 一个xsd文件会定义:文档中出现的元素、文档中出现的属性、子元素、子元素的数量、子元素的顺序、元素是否为空、元素和属性的数据类型、元素或属性的默认和固定值等。简而言之,XSD文件用来定义Xml的格式的文件,而XML是按照一定的Xsd格式生成的数据文档。 524 | 525 | `xmlns:soft="http://www.soft-rpc.com/schema/soft-reference"`声明了一个命名空间soft,我们可以通过他的url寻找到对应的xsd文件,该xsd文件里描述了reference标签里都有哪些属性,它们的类型分别是什么。 526 | 527 | *关于命名空间相关内容可以参照*:[XML配置文件的命名空间与Spring配置文件中的头](https://www.cnblogs.com/gonjan-blog/p/6637106.html) 528 | 529 | soft-reference.xsd内容如下,值得注意的是``标签,它声明了这个reference标签都有哪些属性 530 | 531 | ```xml 532 | 533 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | ``` 555 | 556 | ##### 编写标签解析器完成解析工作 557 | 558 | 要完成解析工作,会用到NamespaceHandler和BeanDefinitionParser这两个概念。具体说来NamespaceHandler会根据下述schema文件和标签名名找到某个BeanDefinitionParser,然后由BeanDefinitionParser完成具体的解析工作。因此需要分别完成NamespaceHandler和BeanDefinitionParser的实现类,Spring提供了默认实现类NamespaceHandlerSupport和AbstractSingleBeanDefinitionParser,简单的方式就是去继承这两个类。 559 | 560 | ```java 561 | public class RpcReferenceNamespaceHandler extends NamespaceHandlerSupport { 562 | // 给soft:reference标签注册对应的BeanDefinitionParser解析器 563 | @Override 564 | public void init() { 565 | registerBeanDefinitionParser("reference",new RpcReferenceBeanDefinitionParser()); 566 | } 567 | } 568 | ``` 569 | 570 | RpcReferenceNamespaceHandler只是注册了reference的标签的处理逻辑类,真正的标签解析的逻辑在RpcReferenceBeanDefinitionParser中。这里注册的reference必须与Spring的rpc-reference.xml文件中soft:reference标签后的reference保持一致,否则将找不到相应的处理逻辑。如下是RpcReferenceBeanDefinitionParser的处理逻辑: 571 | 572 | ```java 573 | public class RpcReferenceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{ 574 | @Override 575 | protected Class getBeanClass(Element element) { 576 | // 对标签reference的解析结果是一个factoryBean类对象 577 | return RpcReferenceFactoryBean.class; 578 | } 579 | 580 | @Override 581 | protected void doParse(Element element, BeanDefinitionBuilder builder) { 582 | try { 583 | long startTime = System.currentTimeMillis(); 584 | String id = element.getAttribute("id"); 585 | String timeout = element.getAttribute("timeout"); 586 | String targetInterface = element.getAttribute("interface"); 587 | String clusterStrategy = element.getAttribute("clusterStrategy"); 588 | String groupName = element.getAttribute("groupName"); 589 | String appName = element.getAttribute("appName"); 590 | builder.addPropertyValue("timeout", timeout); 591 | builder.addPropertyValue("targetInterface", Class.forName(targetInterface)); 592 | if (StringUtils.isNotBlank(groupName)) { 593 | builder.addPropertyValue("groupName", groupName); 594 | } 595 | if (StringUtils.isNotBlank(appName)) { 596 | builder.addPropertyValue("appName", appName); 597 | } else { 598 | String appName4Client = PropertyConfigUtil.getAppName4Client(); 599 | // soft-rpc.properties中的appName也没有配置则抛出异常 600 | if (StringUtils.isNotBlank(appName4Client)) { 601 | LOGGER.error("请配置{}标签的appName属性或在soft-rpc.properties中配置soft.rpc.client.app.name属性", id); 602 | throw new RuntimeException(String.format("%s%s", id, "标签缺少appName属性")); 603 | } 604 | builder.addPropertyValue("appName", appName4Client); 605 | } 606 | long duration = System.currentTimeMillis() - startTime; 607 | LOGGER.info("[{}]标签解析超时{}ms",id,duration); 608 | } catch (Exception e) { 609 | LOGGER.error("RevokerFactoryBeanDefinitionParser Error.", e); 610 | throw new RuntimeException(e); 611 | } 612 | } 613 | } 614 | ``` 615 | 616 | 可以看到,该处理逻辑中主要是获取当前标签中定义的属性的值,然后将其按照一定的处理逻辑注册到当前的BeanDefinition中。这里还实现了一个getBeanClass()方法,该方法用于表明当前自定义标签对应的BeanDefinition所代表的类的类型。所以整个标签解析的过程是:执行doParse方法,解析标签并封装得到一个BeanDefinition,然后结合getBeanClass方法,根据这个BeanDefinition的类型生成了一个 RpcReferenceFactoryBean对象。 617 | 618 | 如前所述,RpcReferenceFactoryBean是一个FactoryBean接口实现类,他只是用来接收reference标签的属性参数,其getObject方法返回的实例才是reference标签id在IOC容器中对应的实例。 619 | 620 | 此外,为了能够在标签配置的bean对象被加载到IOC容器之前初始化某些资源,RpcReferenceFactoryBean除了实现FactoryBean接口外,还需实现InitializingBean接口,然后重写afterPropertiesSet()方法编写初始化逻辑,该方法会先于FactoryBean接口执行。 621 | 622 | ```java 623 | public class RpcReferenceFactoryBean implements FactoryBean, InitializingBean { 624 | 625 | private static Set socketAddressSet = Sets.newHashSet(); 626 | 627 | private static NettyChannelPoolFactory nettyChannelPoolFactory = NettyChannelPoolFactory.getInstance(); 628 | 629 | private static RegisterCenter registerCenter = RegisterCenter.getInstance(); 630 | 631 | private Class targetInterface; 632 | 633 | private int timeout; 634 | 635 | private String appName; 636 | 637 | private String groupName = "default"; 638 | 639 | private String loadBalanceStrategy = "default"; 640 | 641 | @Override 642 | public Object getObject() throws Exception { 643 | return ClientProxyBeanFactory.getProxyInstance(appName, targetInterface, timeout, loadBalanceStrategy); 644 | } 645 | 646 | @Override 647 | public Class getObjectType() { 648 | return targetInterface; 649 | } 650 | 651 | @Override 652 | public boolean isSingleton() { 653 | return true; 654 | } 655 | 656 | @Override 657 | public void afterPropertiesSet() throws Exception { 658 | // 将soft-reference内容注册到ZK,同时获取服务地址到本地 659 | InvokerRegisterMessage invoker = new InvokerRegisterMessage(); 660 | invoker.setServicePath(targetInterface.getName()); 661 | invoker.setGroupName(groupName); 662 | invoker.setAppName(appName); 663 | // 本机所有invoker的machineID是相同的 664 | invoker.setInvokerMachineID4Server(InvokerRegisterMessage.getInvokerMachineID4Client()); 665 | // 根据标签内容从注册中心获取服务地址列表 666 | List providerRegisterMessageList = registerCenter.registerInvoker(invoker); 667 | // 提前为不同的服务地址创建channelPool 668 | for (ProviderRegisterMessage provider : providerRegisterMessageList) { 669 | InetSocketAddress socketAddress = new InetSocketAddress(provider.getServerIp(), provider.getServerPort()); 670 | boolean isFirstAdd = socketAddressSet.add(socketAddress); 671 | if (isFirstAdd) { 672 | nettyChannelPoolFactory.registerChannelQueueToMap(socketAddress); 673 | } 674 | } 675 | } 676 | } 677 | ``` 678 | 679 | ##### **编写spring.handlers和spring.schemas串联起所有部件** 680 | 681 | 上面几个步骤走下来会发现开发好的handler与xsd还没法让应用感知到,就这样放上去是没法把前面做的工作纳入体系中的,spring提供了spring.handlers和spring.schemas这两个配置文件来完成这项工作,这两个文件需要我们自己编写并放入META-INF文件夹中,这两个文件的地址必须是META-INF/spring.handlers和META-INF/spring.schemas,spring会默认去载入它们。 682 | 683 | spring.handlers 文件的内容如下: 684 | 685 | ```properties 686 | http\://www.soft-rpc.com/schema/soft-reference=softrpc.framework.spring.handler.RpcReferenceNamespaceHandler 687 | ``` 688 | 689 | 表示当使用到对应的命名空间时,会通过softrpc.framework.spring.handler.RpcReferenceNamespaceHandler来完成解析 690 | 691 | spring.schemas文件的内容如下 692 | 693 | ``` 694 | http\://www.soft-rpc.com/schema/soft-reference.xsd=META-INF/soft-reference.xsd 695 | ``` 696 | 697 | 以上标示载入的xsd文件的位置 698 | 699 | Spring中自定义标签所需的完整文件如下:spring.schemas+xxx.xsd+spring.handlers+xxHandler.java+XxParser.java+XxFactoryBean.java 700 | 701 | image-20191110180140681 702 | 703 | 704 | 705 | #### rpc-reference.xml解析的完整流程 706 | 707 | 首先,对rpc-reference.xml的加载会触发对标签reference的解析,即触发Parser类的doParse()方法,Parser类解析完毕后,会生成一个RpcReferenceFactoryBean对象(属性值已经由标签配置确定),这个对象首先调用afterPropertiesSet方法,利用RpcReferenceFactoryBean中的属性进行了一系列的初始化步骤,然后调用getObject()方法将此标签id对应的实例加载到IOC容器(若标签不需生成实例,则让getObject方法返回null)。整个过程都交给Spring自动管理。 708 | 709 | 总结一下,自定义标签需要自定义xsd文件+解析类,解析类中,我们最关心的是RpcReferenceFactoryBean中的afterProperties()方法和getObject()方法,afterProperties()方法可以完成我们想要执行的初始化过程,getObject()方法则可以按照意愿生成一个自定义标签绑定的对象到IOC容器.读取标签内容--->执行一定逻辑--->生成标签对象,正是自定义标签的最终目的。 710 | 711 | 服务提供端自定义标签以及和Spring的集成步骤与消费端类似,区别在于服务提供者在进行服务发布时,不需要实例化自定义标签内容到IOC容器,它只需要在afterProperties()方法中完成Netty服务端的启动以及ZK的注册即可。getObject()方法可以返回Null。 712 | 713 | ### 性能分析 714 | 715 | 为了提升rpc调用速度, 为主机地址提前建立channelpool并进行复用, 716 | 717 | 更换了更新的netty版本, 性能提升明显 718 | 719 | 初始化过程进行类的预加载 / ChannelPool的创建, 提升第一次访问速度 720 | 721 | #### 测试 722 | 723 | 一次测试里, 包含了三种接口方法: 无方法参数无返回值的方法 / 方法参数是简单类型的方法 / 方法参数和返回值是复杂类型的方法. 将这个测试分别执行100次和10000次(跳过第一次后的次数), 相当于分别执行了300个和30000个方法, 对比三种序列化方案的平均时间, 如下. 可以看到它们性能差别不大 724 | 725 | | 序列化协议 | 100(ms) | 10000(ms) | 726 | | :---------: | :-----: | :-------: | 727 | | Deafult | 1.06 | 0.60 | 728 | | Hessain | 1.04 | 0.59 | 729 | | Proto_Stuff | 1.03 | 0.58 | 730 | 731 | ## 使用指南 732 | 733 | 使用maven-install命令将maven项目打包到本地仓库 734 | 735 | ### 配置文件:soft-rpc.properties 736 | 737 | 配置文件的名称必须是soft-rpc.properties, 路径必须放在/resources根目录下 738 | 739 | - 注册中心zk的地址soft.rpc.zookeeper.address必须手动配置 740 | - 引用服务时, 如果存在没有配置appName属性的标签, 那么soft.rpc.client.app.name必须手动配置 741 | - 发布服务时, 如果存在没有配置appName属性的标签, 那么softrpc.server.app.name必须手动配置 742 | 743 | 客户端能够准确引用一个服务的key是引用服务标签的appName(标签没有则用配置文件值)+interface 744 | 745 | ``` 746 | # 注册中心ZK地址,必须进行配置,无默认值 747 | soft.rpc.zookeeper.address=localhost:2181 748 | # session超时时间,默认500 749 | soft.rpc.zookeeper.session.timeout=3000 750 | # 连接超时时间,默认500 751 | soft.rpc.zookeeper.connection.timeout=3000 752 | # 服务端序列化协议,Default,可选值:Default/Hessian/ProtoStuff 753 | soft.rpc.server.serializer=Default 754 | # 客户端序列化协议,默认Default,可选Hessian/ProtoStuff/Default 755 | soft.rpc.client.serializer=Default 756 | # 负载均衡算法可选值:Random/WeightRandom/Polling/WeightPolling/Hash,若配置有误,自动采用Random算法 757 | soft.rpc.client.clusterStrategy.default=WeightRandom 758 | # 客户端对每个主机的初始化ChannelPool大小,默认10 759 | soft.rpc.client.channelPoolSize=10 760 | # 客户端调用RPC服务线程池的大小,同时也是服务端限流大小,默认10 761 | soft.rpc.client.threadWorkers=10 762 | # 发布服务时默认命名空间(标签没有配置appName时采用) 763 | soft.rpc.server.app.name=test 764 | # 引入服务时采用的默认命名空间(标签没有配置appName时采用) 765 | soft.rpc.client.app.name=test 766 | ``` 767 | 768 | ### 引用服务:rpc-reference.xml 769 | 770 | id / interface / timeout属性是必须要有的, timeout是设置的服务超时时间, appName属性值可以在标签里声明, 也可以在soft-rpc.properties里声明, 标签里优先级更高(会覆盖soft-rpc.properties中配置的appName属性值). 因为服务的key就是appName+interface, 所以appName值也是一定要有: 标签和配置文件至少有一个. 通过appName属性, 客户端就可以引用不同应用的rpc服务, 并且不怕interface 重名冲突. 771 | 772 | clusterStrategy / groupName是可选项配置, 它们默认值都是default, clusterStrategy 不配置, 或者配置为"default"时, 表示负载均衡策略采用soft-rpc.properties中soft.rpc.client.clusterStrategy.default属性值, 如果配置文件没有这个属性, 则使用Random策略. groupName用于服务路由分组, 在本项目中没有实际用到. 773 | 774 | 本项目和spring集成, 引用服务的标签配置文件名不一定要是`rpc-reference.xml`, 只要声明让sprigIOC容器加载即可, 发布服务的配置文件`rpc-service.xml`也同理. 775 | 776 | ```xml 777 | 778 | 783 | 784 | 790 | 791 | 797 | 798 | ``` 799 | 800 | ### 发布服务:rpc-service.xml 801 | 802 | id / ref / interface / serverPort / timeout, ref标签是该服务的实例执行类在IOC容器中的id, serverPort是该服务发布的端口, 本项目支持一台主机在不同端口上发布服务, 即serverPort可以不同, appName和前面其他属性参考引用服务中的说明. weight / workerThreads是可选配置, weight 代表这个服务的权重, 范围[1,100], 默认为1, 越高使用权重相关负载均衡策略时, 该服务被采用的机率就越高; workerThreads是该服务在执行时的限流数(同时使用该服务的最大客户端数) 803 | 804 | ```xml 805 | 806 | 811 | 812 | 813 | 814 | 823 | 824 | 825 | 834 | 835 | 836 | 845 | 846 | 847 | ``` 848 | 849 | 850 | 851 | ## JUC类的使用 852 | 853 | ### Semaphore 854 | 855 | 信号量 Semaphore 是一个控制访问多个共享资源的计数器,和 CountDownLatch 一样,其本质上是一个“**共享锁**”。Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。本项目在NettyServerHandler用semaphore作为限流工具, NettyServerHandler中有一个静态变量`Map serviceKeySemaphoreMap` , 其中key是rpc调用服务的实现类全限定名, value是一个固定计数量大小的Semaphore对象, 计数量大小由发布服务的标签配置. 服务端每次需要执行反射调用实际服务方法时, 需要acquire一个计数量, 支持超时失败, 执行完方法一定会release. 由此实现了对每个服务的限流控制 856 | 857 | **Semaphore创建** 858 | 859 | ```java 860 | // 根据方法名称定位到具体某一个服务提供者 861 | String serviceKey = request.getServiceImplPath(); 862 | Semaphore semaphore = serviceKeySemaphoreMap.get(serviceKey); 863 | // 为null时类似于单例对象的同步创建,两次检查null 864 | if (semaphore == null) { 865 | synchronized (serviceKeySemaphoreMap) { 866 | semaphore = serviceKeySemaphoreMap.get(serviceKey); 867 | if (semaphore == null) { 868 | int workerThread = request.getWorkerThread(); 869 | // 新建对象时,指定计数量 870 | semaphore = new Semaphore(workerThread); 871 | serviceKeySemaphoreMap.put(serviceKey, semaphore); 872 | } 873 | } 874 | } 875 | ``` 876 | 877 | **Semaphore的使用** 878 | 879 | ```java 880 | ResponseMessage response = null; 881 | boolean acquire = false; 882 | try { 883 | // 利用semaphore实现限流,支持超时,返回boolean变量 884 | acquire = semaphore.tryAcquire(consumeTimeOut, TimeUnit.MILLISECONDS); 885 | if (acquire) { 886 | // 利用反射发起服务调用 887 | response = new ServiceProvider().execute(request); 888 | } else { 889 | logger.warn("因为服务端限流,请求超时"); 890 | } 891 | } catch (Exception e) { 892 | logger.error("服务方使用反射调用服务时发生错误", e); 893 | } finally { 894 | // 一定记得release 895 | if (acquire) { 896 | semaphore.release(); 897 | } 898 | } 899 | ``` 900 | 901 | ### CountDownLatch 902 | 903 | CountDownLatch是一个倒计时器, 在多线程执行任务时, 部分线程需要依赖另一部分线程的执行结果, 也就是说它们执行有先后顺序的, 此时就可以用CountDownLatch, 准备线程执行完, 倒计时器就-1, 减到0的时候, 被CountDownLatch对象await的线程就会开始执行. (就像火箭发射前需要很多准备工作一样) 904 | 905 | 本项目中, 在NettyChannelPoolFactory创建channel时, 需要用到CountDownLatch, 因为netty创建channel是异步的, 而channelpool的容量是一定的, 因此在while循环中, 每次创建channel都要等待创建结果, 如果没有创建成功, 需要重新创建。 906 | 907 | **CountDownLatch的使用** 908 | 909 | ```java 910 | ChannelFuture channelFuture = bootstrap.connect().sync(); 911 | final Channel newChannel = channelFuture.channel(); 912 | final CountDownLatch connectedLatch = new CountDownLatch(1); 913 | final List isSuccessHolder = Lists.newArrayListWithCapacity(1); 914 | // 监听Channel是否建立成功 915 | channelFuture.addListener(new ChannelFutureListener() { 916 | @Override 917 | public void operationComplete(ChannelFuture future) throws Exception { 918 | // 若Channel建立成功,保存建立成功的标记 919 | if (future.isSuccess()) { 920 | isSuccessHolder.add(Boolean.TRUE); 921 | } else { 922 | // 若Channel建立失败,保存建立失败的标记 923 | future.cause().printStackTrace(); 924 | isSuccessHolder.add(Boolean.FALSE); 925 | } 926 | // 表示监听线程完成,创建channel线程可以返回结果 927 | connectedLatch.countDown(); 928 | } 929 | }); 930 | // 等待监听线程完成 931 | connectedLatch.await(); 932 | // 如果Channel建立成功,返回新建的Channel 933 | return isSuccessHolder.get(0) ? newChannel : null; 934 | ``` 935 | 936 | **CountDownLatch 与 CyclicBarrier 的区别** 937 | 938 | 1. CountDownLatch 的作用是允许 1 或 N 个线程等待其他线程完成执行;而 CyclicBarrier 则是允许 N 个线程相互等待。 939 | 2. CountDownLatch 的计数器无法被重置;CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier 。 940 | 941 | ### ArrayBlockingQueue 942 | 943 | ArrayBlockingQueue,一个由**数组**实现的**有界**阻塞队列。该队列采用 FIFO 的原则对元素进行排序添加的。 944 | 945 | ArrayBlockingQueue 为**有界且固定**,其大小在构造时由构造函数来决定,确认之后就不能再改变了。 946 | 947 | ArrayBlockingQueue支持对等待线程的可选性公平策略, 默认为非公平, 公平性会降低并发量 948 | 949 | 本项目中NettyChannelPoolFactory中每个主机地址channel的存储, 还有返回结果包装类都使用ArrayBlockingQueue, 其保证了多线程访问和支持超时失败. 如下为从结果容器中取结果 950 | 951 | ```java 952 | ResponseReceiver responseReceiver = responseMap.get(traceId); 953 | try { 954 | // 阻塞Queue在取值时会阻塞当前线程(等待),timeout时间后还未取到值,则返回null 955 | return responseReceiver.getResponseQueue().poll(timeout, TimeUnit.MILLISECONDS); 956 | } catch (InterruptedException e) { 957 | logger.error("从结果容器中获取返回结果线程被中断!"); 958 | throw new RuntimeException(e); 959 | } finally { 960 | // 无论取没取到,本次请求已经处理过了,所以不需要再缓存它的结果 961 | responseMap.remove(traceId); 962 | } 963 | ``` 964 | 965 | **入队** 966 | 967 | - `#add(E e)` 方法:将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则抛出 IllegalStateException 异常。 968 | - `#offer(E e)` 方法:将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则返回 false 。 969 | - `#offer(E e, long timeout, TimeUnit unit)` 方法:将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。 970 | - `#put(E e)` 方法:将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。 971 | 972 | **出队** 973 | 974 | - `#poll()` 方法:获取并移除此队列的头,如果此队列为空,则返回 `null` 。 975 | - `#poll(long timeout, TimeUnit unit)` 方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。 976 | - `#take()` 方法:获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。 977 | - `#remove(Object o)` 方法:从此队列 978 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.xctian.rpc 8 | soft-rpc 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.7.2 13 | 19.0 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 21 | 1.8 22 | 1.8 23 | 24 | 25 | 26 | 27 | 28 | 29 | junit 30 | junit 31 | 4.12 32 | 33 | 34 | com.caucho 35 | hessian 36 | 4.0.38 37 | 38 | 39 | com.dyuproject.protostuff 40 | protostuff-api 41 | 1.0.8 42 | 43 | 44 | com.dyuproject.protostuff 45 | protostuff-runtime 46 | 1.0.8 47 | 48 | 49 | com.dyuproject.protostuff 50 | protostuff-core 51 | 1.0.8 52 | 53 | 54 | org.objenesis 55 | objenesis 56 | 2.6 57 | 58 | 59 | 60 | 61 | org.slf4j 62 | slf4j-log4j12 63 | ${org.slf4j.slf4j-log4j12.version} 64 | 65 | 66 | io.netty 67 | netty-all 68 | 4.1.32.Final 69 | 70 | 71 | com.google.guava 72 | guava 73 | ${com.google.guava.version} 74 | 75 | 76 | m2.repository.org.springframework 77 | spring-context 78 | 4.1.3.RELEASE 79 | 80 | 81 | com.github.sgroschupf 82 | zkclient 83 | 0.1 84 | 85 | 86 | com.alibaba 87 | fastjson 88 | 1.2.38 89 | 90 | 91 | org.apache.commons 92 | commons-lang3 93 | 3.6 94 | 95 | 96 | io.netty 97 | netty 98 | 3.10.5.Final 99 | 100 | 101 | javax.servlet 102 | javax.servlet-api 103 | 3.1.0 104 | 105 | 106 | m2.repository.org.slf4j 107 | slf4j-api 108 | 1.5.6 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /soft-rpc.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/invoker/ClientProxyBeanFactory.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.invoker; 2 | 3 | import io.netty.util.concurrent.DefaultThreadFactory; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import softrpc.framework.loadBalance.common.LoadBalanceEngine; 7 | import softrpc.framework.serialization.message.RequestMessage; 8 | import softrpc.framework.serialization.message.ResponseMessage; 9 | import softrpc.framework.utils.PropertyConfigUtil; 10 | import softrpc.framework.zookeeper.RegisterCenter; 11 | import softrpc.framework.zookeeper.RegisterCenter4Invoker; 12 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 13 | 14 | import java.lang.reflect.*; 15 | import java.net.InetSocketAddress; 16 | import java.util.List; 17 | import java.util.UUID; 18 | import java.util.concurrent.*; 19 | 20 | /** 21 | * 客户端代理工厂类,为每个rpc服务接口的引用对象都由getProxyInstance产生 22 | * 23 | * @author xctian 24 | * @date 2019/12/25 25 | */ 26 | public class ClientProxyBeanFactory { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(ClientProxyBeanFactory.class); 29 | 30 | /** 31 | * 饿汉单例 32 | */ 33 | private static ClientProxyBeanFactory instance = new ClientProxyBeanFactory(); 34 | /** 35 | * 客户端注册中心 36 | */ 37 | private static RegisterCenter4Invoker registerCenter4Invoker = RegisterCenter.getInstance(); 38 | /** 39 | * 线程池:RPC服务调用都需要走此线程池中的线程 40 | */ 41 | private static ExecutorService threadPool; 42 | /** 43 | * soft-reference/soft-service配置文件中标签的属性默认值的等价字符串 44 | */ 45 | private static final String DEFAULT_VALUE_IN_LABEL = "default"; 46 | 47 | private ClientProxyBeanFactory() { 48 | } 49 | 50 | public static ClientProxyBeanFactory getInstance() { 51 | return instance; 52 | } 53 | 54 | /** 55 | * 通过动态代理,生成引用服务的代理对象 56 | * 57 | * @param appName 应用名 58 | * @param serviceInterface 接口名 59 | * @param consumeTimeout 超时时间 60 | * @param loadBalanceStrategy 负载均衡策略 61 | * @param 代理对象的实际类型 62 | * @return 生成的代理对象 63 | */ 64 | public static T getProxyInstance(final String appName, final Class serviceInterface, final int consumeTimeout, final String loadBalanceStrategy) { 65 | long startTime = System.currentTimeMillis(); 66 | // 动态代理 67 | Object proxy = Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class[]{serviceInterface}, new InvocationHandler() { 68 | @Override 69 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 70 | // 获取该接口的服务地址 71 | String nameSpace = appName + "/" + serviceInterface.getName(); 72 | List providerRegisterMessages = registerCenter4Invoker.getProviderMap().get(nameSpace); 73 | // 判断是否采用默认的负载均衡策略 74 | String strategy = loadBalanceStrategy; 75 | if (DEFAULT_VALUE_IN_LABEL.equalsIgnoreCase(strategy)) { 76 | strategy = PropertyConfigUtil.getDefaultClusterStrategy(); 77 | } 78 | // 根据负载均衡策略选取provider 79 | ProviderRegisterMessage providerRegisterMessage = LoadBalanceEngine.select(providerRegisterMessages, strategy); 80 | if (null == providerRegisterMessage) { 81 | throw new RuntimeException("无可用服务节点"); 82 | } 83 | // 封装请求消息的内容 84 | RequestMessage requestMessage = new RequestMessage(); 85 | // 标识请求消息的ID,随机生成 86 | requestMessage.setMessageId(UUID.randomUUID().toString()); 87 | // 服务方接口实现类的bean标签的id(经过负载均衡后) 88 | requestMessage.setRefId(providerRegisterMessage.getRefId()); 89 | // 服务限流大小 90 | requestMessage.setMaxWorkThread(providerRegisterMessage.getWorkThread()); 91 | // 服务调用超时时间 92 | requestMessage.setTimeout(consumeTimeout); 93 | // 服务接口名称(接口全限定名) 94 | requestMessage.setServicePath(serviceInterface.getName()); 95 | // 调用方法名 96 | requestMessage.setMethodName(method.getName()); 97 | // 设置方法和参数类型,用于反射调用 98 | if (null != args && args.length > 0) { 99 | // 实际传参 100 | requestMessage.setParameters(args); 101 | // 初始化方法参数类型列表 102 | requestMessage.setParameterTypes(new String[args.length]); 103 | // java.lang.reflect.Method.getGenericParameterTypes()方法返回一个Type对象的数组,它以声明顺序表示此Method对象表 104 | // 示的方法的形式参数类型(含泛型),并且是全限定名 105 | Type[] types = method.getGenericParameterTypes(); 106 | // 参数化类型(ParameterType)举例:如List 这就是个参数化类型,而 List 就是个原生类型。 107 | for (int i = 0; i < args.length; i++) { 108 | // 根据反射调用的特点,如果参数的类型是ParameterType,则需要找到它的原生类型 109 | if (types[i] instanceof ParameterizedType) { 110 | requestMessage.getParameterTypes()[i] = ((ParameterizedType) types[i]).getRawType().getTypeName(); 111 | } else { 112 | requestMessage.getParameterTypes()[i] = types[i].getTypeName(); 113 | } 114 | } 115 | } 116 | Future responseMessageFuture = null; 117 | try { 118 | if (null == threadPool) { 119 | synchronized (ClientProxyBeanFactory.class) { 120 | if (null == threadPool) { 121 | int corePoolSize = PropertyConfigUtil.getThreadWorkerSize(); 122 | threadPool = new ThreadPoolExecutor(corePoolSize, corePoolSize, 0, TimeUnit.MILLISECONDS, 123 | new ArrayBlockingQueue<>(100), new DefaultThreadFactory("Soft-Rpc-InvokerPool"), new ThreadPoolExecutor.DiscardPolicy()); 124 | } 125 | } 126 | } 127 | String serverIp = providerRegisterMessage.getServerIp(); 128 | int serverPort = providerRegisterMessage.getServerPort(); 129 | InetSocketAddress inetSocketAddress = new InetSocketAddress(serverIp, serverPort); 130 | // 任务扔到线程池执行 131 | responseMessageFuture = threadPool.submit(new RevokerServiceCallable(inetSocketAddress, requestMessage)); 132 | //阻塞等待response 133 | ResponseMessage response = responseMessageFuture.get(requestMessage.getTimeout(), TimeUnit.MILLISECONDS); 134 | // 返回结果 135 | return response.getResultValue(); 136 | } catch (InterruptedException e) { 137 | LOGGER.error("请求超时,线程中断!"); 138 | responseMessageFuture.cancel(true); 139 | } 140 | return null; 141 | } 142 | }); 143 | long duration = System.currentTimeMillis() - startTime; 144 | LOGGER.info("创建代理对象耗时{}ms:[{}]", duration, serviceInterface.getName()); 145 | return (T) proxy; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/invoker/NettyChannelPoolFactory.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.invoker; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Maps; 5 | import io.netty.bootstrap.Bootstrap; 6 | import io.netty.channel.*; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import softrpc.framework.provider.NettyDecodeHandler; 13 | import softrpc.framework.provider.NettyEncodeHandler; 14 | import softrpc.framework.serialization.common.SerializerType; 15 | import softrpc.framework.serialization.message.ResponseMessage; 16 | import softrpc.framework.utils.PropertyConfigUtil; 17 | 18 | import java.net.InetSocketAddress; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.concurrent.ArrayBlockingQueue; 22 | import java.util.concurrent.CountDownLatch; 23 | 24 | /** 25 | * 客户端ChannelPool工厂 26 | * 27 | * @author xctian 28 | * @date 2019/12/23 29 | */ 30 | public class NettyChannelPoolFactory { 31 | 32 | private static final Logger LOGGER = LoggerFactory.getLogger(NettyChannelPoolFactory.class); 33 | 34 | /** 35 | * 饿汉单例模式 36 | */ 37 | private static final NettyChannelPoolFactory INSTANCE = new NettyChannelPoolFactory(); 38 | /** 39 | * 缓存ChannelPool的Map:Key是服务地址,value是存放这个地址对应的Channel阻塞队列 40 | */ 41 | private static final Map> CHANNEL_POOL_MAP = Maps.newConcurrentMap(); 42 | /** 43 | * 每个服务地址ChannelPool的Channel数量,可在soft-rpc.properties中进行配置 44 | */ 45 | private static final int CHANNEL_POOL_SIZE = PropertyConfigUtil.getChannelPoolSize(); 46 | 47 | private NettyChannelPoolFactory() { 48 | } 49 | 50 | public static NettyChannelPoolFactory getInstance() { 51 | return INSTANCE; 52 | } 53 | 54 | /** 55 | * 为服务地址创建ChannelPool并缓存到Map中 56 | * 57 | * @param socketAddress 服务地址 58 | */ 59 | public void registerChannelQueueToMap(InetSocketAddress socketAddress) { 60 | long startTime = System.currentTimeMillis(); 61 | int existedChannel = 0; 62 | while (existedChannel < CHANNEL_POOL_SIZE) { 63 | Channel channel = null; 64 | while (null == channel) { 65 | // 创建失败则重试 66 | channel = registerChannel(socketAddress); 67 | } 68 | existedChannel++; 69 | // 将创建后的channel加入对应的阻塞队列 70 | ArrayBlockingQueue channelArrayBlockingQueue = CHANNEL_POOL_MAP.get(socketAddress); 71 | if (null == channelArrayBlockingQueue) { 72 | channelArrayBlockingQueue = new ArrayBlockingQueue(CHANNEL_POOL_SIZE); 73 | CHANNEL_POOL_MAP.put(socketAddress, channelArrayBlockingQueue); 74 | } 75 | // offer方法当队列满,而且放入时间超过设定时间时,返回false; 76 | // put方法当队列满时,会调用wait方法,put方法会等待一个空的位置出来,然后再执行insert 77 | channelArrayBlockingQueue.offer(channel); 78 | long duation = System.currentTimeMillis() - startTime; 79 | LOGGER.info("创建channelPool耗时{}ms:[{}:{}]", duation, socketAddress.getHostName(), socketAddress.getPort()); 80 | } 81 | } 82 | 83 | /** 84 | * 基于netty为服务地址创建Channel 85 | * 86 | * @param socketAddress 87 | * @return 创建过后的Channel 88 | */ 89 | public Channel registerChannel(InetSocketAddress socketAddress) { 90 | try { 91 | EventLoopGroup group = new NioEventLoopGroup(10); 92 | Bootstrap bootstrap = new Bootstrap(); 93 | bootstrap.remoteAddress(socketAddress); 94 | final String serializer = PropertyConfigUtil.getClientSerializer(); 95 | bootstrap.group(group) 96 | .channel(NioSocketChannel.class) 97 | .option(ChannelOption.TCP_NODELAY, true) 98 | // handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行,这是两者的区别。 99 | .handler(new ChannelInitializer() { 100 | // 父通道调用initChannel方法时,会将新接收到的Channel作为参数传递给initChannel方法 101 | @Override 102 | public void initChannel(SocketChannel ch) throws Exception { 103 | ch.pipeline().addLast(new NettyEncodeHandler(SerializerType.getByType(serializer))); 104 | ch.pipeline().addLast(new NettyDecodeHandler(ResponseMessage.class)); 105 | ch.pipeline().addLast(new NettyClientHandler()); 106 | } 107 | }); 108 | 109 | ChannelFuture channelFuture = bootstrap.connect().sync(); 110 | final Channel newChannel = channelFuture.channel(); 111 | final CountDownLatch countDownLatch = new CountDownLatch(1); 112 | final List isSuccessHolder = Lists.newArrayListWithCapacity(1); 113 | // 监听是否channel创建成功 114 | channelFuture.addListener(new ChannelFutureListener() { 115 | @Override 116 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 117 | if (channelFuture.isSuccess()) { 118 | isSuccessHolder.add(Boolean.TRUE); 119 | } else { 120 | channelFuture.cause().printStackTrace(); 121 | isSuccessHolder.add(Boolean.FALSE); 122 | } 123 | countDownLatch.countDown(); 124 | } 125 | }); 126 | // 阻塞等待Channel创建的结果 127 | countDownLatch.await(); 128 | if (isSuccessHolder.get(0)) { 129 | return newChannel; 130 | } 131 | } catch (Exception e) { 132 | throw new RuntimeException(e); 133 | } 134 | return null; 135 | } 136 | 137 | /** 138 | * 根据地址获取对应的ChannelPool缓存队列 139 | * 140 | * @param socketAddress 141 | * @return 142 | */ 143 | public ArrayBlockingQueue acquire(InetSocketAddress socketAddress) { 144 | ArrayBlockingQueue arrayBlockingQueue = CHANNEL_POOL_MAP.get(socketAddress); 145 | if (null == arrayBlockingQueue) { 146 | registerChannelQueueToMap(socketAddress); 147 | return CHANNEL_POOL_MAP.get(socketAddress); 148 | } else { 149 | return arrayBlockingQueue; 150 | } 151 | } 152 | 153 | /** 154 | * Channel使用完毕之后,回收到阻塞队列 155 | * 156 | * @param arrayBlockingQueue 157 | * @param channel 158 | * @param inetSocketAddress 159 | */ 160 | public void release(ArrayBlockingQueue arrayBlockingQueue, Channel channel, InetSocketAddress inetSocketAddress) { 161 | if (null == arrayBlockingQueue) { 162 | return; 163 | } 164 | // 回收之前判断channel是否可用,若不可用则重新注册一个放入阻塞队列 165 | if (null == channel || !channel.isActive() || !channel.isOpen() || !channel.isWritable()) { 166 | if (channel != null) { 167 | channel.deregister().syncUninterruptibly().awaitUninterruptibly(); 168 | channel.closeFuture().syncUninterruptibly().awaitUninterruptibly(); 169 | } 170 | Channel newChannel = null; 171 | while (null == newChannel) { 172 | newChannel = registerChannel(inetSocketAddress); 173 | } 174 | arrayBlockingQueue.offer(newChannel); 175 | return; 176 | } 177 | arrayBlockingQueue.offer(channel); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/invoker/NettyClientHandler.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.invoker; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import softrpc.framework.serialization.message.ResponseMessage; 8 | 9 | /** 10 | * @author xctian 11 | * @date 2019/12/24 12 | */ 13 | public class NettyClientHandler extends SimpleChannelInboundHandler { 14 | 15 | private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientHandler.class); 16 | 17 | public NettyClientHandler(){}; 18 | 19 | @Override 20 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 21 | ctx.flush(); 22 | } 23 | 24 | @Override 25 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 26 | cause.printStackTrace();; 27 | ctx.close(); 28 | } 29 | 30 | @Override 31 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, ResponseMessage responseMessage) throws Exception { 32 | // netty异步获取结果response后存入结果阻塞队列 33 | ResponseReceiverHolder.putResultValue(responseMessage); 34 | LOGGER.info("客户端接收返回结果:[content:{}]",responseMessage,responseMessage.getMessageId()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/invoker/ResponseReceiver.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.invoker; 2 | 3 | import softrpc.framework.serialization.message.ResponseMessage; 4 | 5 | import java.util.concurrent.ArrayBlockingQueue; 6 | import java.util.concurrent.BlockingQueue; 7 | 8 | /** 9 | * 返回结果response的包装类,用于实现同步阻塞等待结果 10 | * 11 | * @author xctian 12 | * @date 2019/12/27 13 | */ 14 | public class ResponseReceiver { 15 | 16 | /** 17 | * 存储异步返回结果的阻塞队列 18 | */ 19 | private BlockingQueue responseQueue = new ArrayBlockingQueue(1); 20 | /** 21 | * 用于记录异步结果返回的时刻,以便判断超时 22 | */ 23 | private long responseTime; 24 | 25 | public boolean isExpire(){ 26 | ResponseMessage response = responseQueue.peek(); 27 | if(null == response){ 28 | // 有可能是异步结果还未加入queue,也有可能是结果已经被取走 29 | // 但如果结果被取走,则会在Holder的Map中将其移除,此对象的isExpire方法不会被调用 30 | return false; 31 | } 32 | long time = response.getTimeout(); 33 | if(System.currentTimeMillis() - responseTime > time){ 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | public BlockingQueue getResponseQueue() { 40 | return responseQueue; 41 | } 42 | 43 | public long getResponseTime() { 44 | return responseTime; 45 | } 46 | 47 | public void setResponseTime(long responseTime) { 48 | this.responseTime = responseTime; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/invoker/ResponseReceiverHolder.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.invoker; 2 | 3 | import com.google.common.collect.Maps; 4 | import io.netty.util.concurrent.DefaultThreadFactory; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import softrpc.framework.serialization.message.ResponseMessage; 8 | 9 | import java.util.Map; 10 | import java.util.concurrent.ArrayBlockingQueue; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.ThreadPoolExecutor; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * @author xctian 17 | * @date 2019/12/27 18 | */ 19 | public class ResponseReceiverHolder { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(ResponseReceiverHolder.class); 22 | 23 | /** 24 | * 用于缓存返回结果包装类的Map,key:请求id,value:返回结果包装类 25 | */ 26 | private static final Map responseMap = Maps.newConcurrentMap(); 27 | /** 28 | * 清除过期结果的线程池,类似SingleThreadExecutor 29 | */ 30 | private static final ExecutorService removeExpireKeyExecutor = new ThreadPoolExecutor(1,1,0, TimeUnit.MILLISECONDS, 31 | new ArrayBlockingQueue(1),new DefaultThreadFactory("removeExpireKeyExecutor")); 32 | // 删除超时未获取结果的key,防止内存泄漏 33 | static { 34 | // 线程池的execute方法没有返回对象,且只接收runnable,sumit有future封装的返回对象,且还可以接收callable为callable的call方法返回类型 35 | removeExpireKeyExecutor.execute(new Runnable() { 36 | @Override 37 | public void run() { 38 | while(true){ 39 | try { 40 | for(Map.Entry entry :responseMap.entrySet()){ 41 | boolean isExpired = entry.getValue().isExpire(); 42 | if(isExpired){ 43 | responseMap.remove(entry.getKey()); 44 | } 45 | Thread.sleep(10); 46 | } 47 | } catch (InterruptedException e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | } 52 | }); 53 | } 54 | 55 | /** 56 | * 为请求创建一个返回封装, 缓存至Map 57 | * @param traceId 消息id 58 | */ 59 | public static void initResponseData(String traceId){ 60 | responseMap.put(traceId,new ResponseReceiver()); 61 | } 62 | 63 | /** 64 | * 将Netty异步返回结果放入阻塞队列 65 | */ 66 | public static void putResultValue(ResponseMessage responseMessage){ 67 | ResponseReceiver responseReceiver = responseMap.get(responseMessage.getMessageId()); 68 | if(null == responseReceiver){ 69 | responseReceiver = new ResponseReceiver(); 70 | responseMap.put(responseMessage.getMessageId(),responseReceiver); 71 | } 72 | responseReceiver.setResponseTime(System.currentTimeMillis()); 73 | responseReceiver.getResponseQueue().add(responseMessage); 74 | } 75 | 76 | /** 77 | * 从阻塞队列获取netty异步返回的结果值 78 | */ 79 | public static ResponseMessage getValue(String messageId,long timeout) throws InterruptedException{ 80 | ResponseReceiver responseReceiver = responseMap.get(messageId); 81 | try { 82 | // 阻塞等待队列有值然后取出,等待超时时间为timeout,超时则产生中断异常 83 | return responseReceiver.getResponseQueue().poll(timeout,TimeUnit.MILLISECONDS); 84 | }catch (InterruptedException e){ 85 | LOGGER.error("结果队列取值超时,线程中断!"); 86 | throw new InterruptedException(); 87 | }finally { 88 | // 无论是否成功取到,本次请求已经结束,从缓存中移除 89 | responseMap.remove(messageId); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/invoker/RevokerServiceCallable.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.invoker; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import softrpc.framework.serialization.message.RequestMessage; 8 | import softrpc.framework.serialization.message.ResponseMessage; 9 | 10 | import java.net.InetSocketAddress; 11 | import java.util.concurrent.ArrayBlockingQueue; 12 | import java.util.concurrent.Callable; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * @author xctian 17 | * @date 2019/12/27 18 | */ 19 | public class RevokerServiceCallable implements Callable { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(RevokerServiceCallable.class); 22 | /** 23 | * 服务地址 24 | */ 25 | private InetSocketAddress inetSocketAddress; 26 | /** 27 | * 请求消息 28 | */ 29 | private RequestMessage requestMessage; 30 | /** 31 | * 连接服务的Channel 32 | */ 33 | private Channel channel; 34 | 35 | public RevokerServiceCallable(InetSocketAddress inetSocketAddress, RequestMessage requestMessage) { 36 | this.inetSocketAddress = inetSocketAddress; 37 | this.requestMessage = requestMessage; 38 | } 39 | 40 | @Override 41 | public ResponseMessage call() throws Exception { 42 | // 创建返回结果包装类,存入结果容器 43 | ResponseReceiverHolder.initResponseData(requestMessage.getMessageId()); 44 | // 根据本地调用服务提供者地址获取对应的netty通道channel队列 45 | ArrayBlockingQueue blockingQueue = NettyChannelPoolFactory.getInstance().acquire(inetSocketAddress); 46 | try { 47 | if (null == channel) { 48 | // 尝试从channelPool阻塞队列取出一个可用channel 49 | channel = blockingQueue.poll(100, TimeUnit.MILLISECONDS); 50 | } 51 | // 若无效则重新创建一个 52 | while (null == channel || !channel.isOpen() || !channel.isActive() || !channel.isWritable()) { 53 | channel = NettyChannelPoolFactory.getInstance().registerChannel(inetSocketAddress); 54 | } 55 | // 本次调用信息写入netty通道,发起异步调用,nettyServer端会相应channel变化情况 56 | LOGGER.info("客户端发送请求消息:[content:{} id:{}]", requestMessage, requestMessage.getMessageId()); 57 | ChannelFuture channelFuture = channel.writeAndFlush(requestMessage); 58 | channelFuture.syncUninterruptibly(); 59 | // 从结果包装类中取出结果,此时会同步阻塞,等待nettyServer处理完毕后在NettyClientHandler中将结果Put进阻塞队列 60 | return ResponseReceiverHolder.getValue(requestMessage.getMessageId(), requestMessage.getTimeout()); 61 | } catch (InterruptedException e) { 62 | LOGGER.error("请求超时,线程中断!"); 63 | throw new InterruptedException(); 64 | } finally { 65 | // 调用完毕过后将channel释放会对应的pool 66 | NettyChannelPoolFactory.getInstance().release(blockingQueue, channel, inetSocketAddress); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/common/LoadBalanceEngine.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.common; 2 | 3 | import com.google.common.collect.Maps; 4 | import softrpc.framework.loadBalance.strategy.LoadBalanceStategy; 5 | import softrpc.framework.loadBalance.strategy.impl.*; 6 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author xctian 14 | * @date 2019/12/26 15 | */ 16 | public class LoadBalanceEngine { 17 | 18 | /** 19 | * 缓存的负载均衡接口实现类对象Map,相当于存储了实现类的单例 20 | */ 21 | private static final Map STATEGY_MAP = Maps.newConcurrentMap(); 22 | 23 | // 饿汉单例 24 | static { 25 | STATEGY_MAP.put(LoadBalanceStrategyEnum.Random, new RandomLoadBalanceStrategyImpl()); 26 | STATEGY_MAP.put(LoadBalanceStrategyEnum.Polling, new PollingLoadBalanceStrategyImpl()); 27 | STATEGY_MAP.put(LoadBalanceStrategyEnum.WeightPolling, new WeightPollingLoadBalanceStrategyImpl()); 28 | STATEGY_MAP.put(LoadBalanceStrategyEnum.WeightRandom, new WeightRandomLoadBalanceStrategyImpl()); 29 | STATEGY_MAP.put(LoadBalanceStrategyEnum.Hash, new HashLoadBalanceStrategyImpl()); 30 | } 31 | 32 | public static ProviderRegisterMessage select(List providerRegisterMessages,String loadBalanceStrategy){ 33 | if(null == providerRegisterMessages || providerRegisterMessages.size() == 0){ 34 | return null; 35 | }else if(providerRegisterMessages.size() == 1){ 36 | return providerRegisterMessages.get(0); 37 | } 38 | LoadBalanceStrategyEnum loadBalanceStrategyEnum = LoadBalanceStrategyEnum.queryByCode(loadBalanceStrategy); 39 | if(null != loadBalanceStrategyEnum){ 40 | return STATEGY_MAP.get(loadBalanceStrategyEnum).select(providerRegisterMessages); 41 | }else{ 42 | return STATEGY_MAP.get(LoadBalanceStrategyEnum.Random).select(providerRegisterMessages); 43 | } 44 | } 45 | 46 | /*以下是通用方法,相当于工具类方法*/ 47 | 48 | /** 49 | * 根据权重,获取服务地址的索引列表(服务的权重为多少,就会往列表中添加几次服务地址索引的值) 50 | * 51 | * @param providerRegisterMessages 服务地址列表 52 | * @return 索引列表 53 | */ 54 | public static List getIndexListByWeight(List providerRegisterMessages) { 55 | if (null == providerRegisterMessages || providerRegisterMessages.size() == 0) { 56 | return null; 57 | } 58 | ArrayList list = new ArrayList<>(); 59 | int index = 0; 60 | for (ProviderRegisterMessage each : providerRegisterMessages) { 61 | int weight = each.getWeight(); 62 | while (weight-- > 0) { 63 | list.add(index); 64 | } 65 | index++; 66 | } 67 | return list; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/common/LoadBalanceStrategyEnum.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.common; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | /** 6 | * @author xctian 7 | * @date 2019/12/26 8 | */ 9 | public enum LoadBalanceStrategyEnum { 10 | 11 | // 随机算法 12 | Random("Random"), 13 | 14 | // 权重随机算法 15 | WeightRandom("WeightRandom"), 16 | 17 | // 权重轮询 18 | WeightPolling("WeightPolling"), 19 | 20 | // 轮询算法 21 | Polling("Polling"), 22 | 23 | // IP地址hash算法 24 | Hash("Hash"); 25 | 26 | private String code; 27 | 28 | public String getCode() { 29 | return code; 30 | } 31 | 32 | public void setCode(String code) { 33 | this.code = code; 34 | } 35 | 36 | LoadBalanceStrategyEnum(String code) { 37 | this.code = code; 38 | } 39 | 40 | public static LoadBalanceStrategyEnum queryByCode(String code){ 41 | if(null == code || StringUtils.isBlank(code)){ 42 | return null; 43 | } 44 | for(LoadBalanceStrategyEnum strategyEnum : values()){ 45 | if(StringUtils.equalsIgnoreCase(code,strategyEnum.getCode())){ 46 | return strategyEnum; 47 | } 48 | } 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/strategy/LoadBalanceStategy.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.strategy; 2 | 3 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 负载均衡接口 9 | * 10 | * @author xctian 11 | * @date 2019/12/26 12 | */ 13 | public interface LoadBalanceStategy { 14 | 15 | /** 16 | * 负载均衡算法:从服务器地址列表中选取一个服务地址 17 | * 18 | * @param providerRegisterMessages 服务地址列表 19 | * @return 最终选取的服务地址 20 | */ 21 | ProviderRegisterMessage select(List providerRegisterMessages); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/strategy/impl/HashLoadBalanceStrategyImpl.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.strategy.impl; 2 | 3 | import softrpc.framework.loadBalance.strategy.LoadBalanceStategy; 4 | import softrpc.framework.utils.IPutil; 5 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 软负载hash算法的实现 11 | * 12 | * @author xctian 13 | * @date 2019/12/26 14 | */ 15 | public class HashLoadBalanceStrategyImpl implements LoadBalanceStategy { 16 | @Override 17 | public ProviderRegisterMessage select(List providerRegisterMessages) { 18 | // 直接通过本地IP地址的hash值对服务列表大小取模,得到的结果作为结果索引 19 | String localIP = IPutil.localIp(); 20 | int hashCode = localIP.hashCode(); 21 | return providerRegisterMessages.get(hashCode % providerRegisterMessages.size()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/strategy/impl/PollingLoadBalanceStrategyImpl.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.strategy.impl; 2 | 3 | import softrpc.framework.loadBalance.strategy.LoadBalanceStategy; 4 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 5 | 6 | import java.util.List; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.locks.Lock; 9 | import java.util.concurrent.locks.ReentrantLock; 10 | 11 | /** 12 | * 软负载轮询算法的实现 13 | * 14 | * @author xctian 15 | * @date 2019/12/26 16 | */ 17 | public class PollingLoadBalanceStrategyImpl implements LoadBalanceStategy { 18 | 19 | /** 20 | * 计数器 21 | */ 22 | private int index = 0; 23 | 24 | /** 25 | * 计数器锁 26 | * 并发情况下对index的改变需要加锁,以保证轮询算法的正确性 27 | */ 28 | private Lock lock = new ReentrantLock(); 29 | 30 | @Override 31 | public ProviderRegisterMessage select(List providerRegisterMessages) { 32 | ProviderRegisterMessage providerRegisterMessage = null; 33 | try { 34 | // 尝试获取锁,10ms的超时时间 35 | lock.tryLock(10, TimeUnit.MILLISECONDS); 36 | if (index >= providerRegisterMessages.size()) { 37 | index = 0; 38 | } 39 | providerRegisterMessage = providerRegisterMessages.get(index); 40 | index++; 41 | } catch (InterruptedException e) { 42 | e.printStackTrace(); 43 | } finally { 44 | lock.lock(); 45 | } 46 | 47 | // 兜底策略:如果获取失败则使用随机负载均衡算法选取一个 48 | return null == providerRegisterMessage ? new RandomLoadBalanceStrategyImpl().select(providerRegisterMessages) : providerRegisterMessage; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/strategy/impl/RandomLoadBalanceStrategyImpl.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.strategy.impl; 2 | 3 | import org.apache.commons.lang3.RandomUtils; 4 | import softrpc.framework.loadBalance.strategy.LoadBalanceStategy; 5 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 软负载随机算法实现 11 | * 12 | * @author xctian 13 | * @date 2019/12/26 14 | */ 15 | public class RandomLoadBalanceStrategyImpl implements LoadBalanceStategy { 16 | 17 | @Override 18 | public ProviderRegisterMessage select(List providerRegisterMessages) { 19 | int index = RandomUtils.nextInt(0, providerRegisterMessages.size()); 20 | return providerRegisterMessages.get(index); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/strategy/impl/WeightPollingLoadBalanceStrategyImpl.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.strategy.impl; 2 | 3 | import softrpc.framework.loadBalance.common.LoadBalanceEngine; 4 | import softrpc.framework.loadBalance.strategy.LoadBalanceStategy; 5 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 6 | 7 | import java.util.List; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.locks.Lock; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | 12 | /** 13 | * 软负载加权轮询算法实现 14 | * 15 | * @author xctian 16 | * @date 2019/12/27 17 | */ 18 | public class WeightPollingLoadBalanceStrategyImpl implements LoadBalanceStategy { 19 | 20 | /** 21 | * 计数器 22 | */ 23 | private int index = 0; 24 | /** 25 | * 计数器锁 26 | */ 27 | private Lock lock = new ReentrantLock(); 28 | 29 | @Override 30 | public ProviderRegisterMessage select(List providerRegisterMessages) { 31 | ProviderRegisterMessage providerRegisterMessage = null; 32 | try { 33 | lock.tryLock(10, TimeUnit.MILLISECONDS); 34 | List indexList = LoadBalanceEngine.getIndexListByWeight(providerRegisterMessages); 35 | if (index >= indexList.size()) { 36 | index = 0; 37 | } 38 | providerRegisterMessage = providerRegisterMessages.get(indexList.get(index)); 39 | index++; 40 | } catch (InterruptedException e) { 41 | e.printStackTrace(); 42 | } finally { 43 | lock.unlock(); 44 | } 45 | // 兜底策略:如果获取失败则使用随机负载均衡算法选取一个 46 | return null == providerRegisterMessage ? new RandomLoadBalanceStrategyImpl().select(providerRegisterMessages) : providerRegisterMessage; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/loadBalance/strategy/impl/WeightRandomLoadBalanceStrategyImpl.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.loadBalance.strategy.impl; 2 | 3 | import org.apache.commons.lang3.RandomUtils; 4 | import softrpc.framework.loadBalance.common.LoadBalanceEngine; 5 | import softrpc.framework.loadBalance.strategy.LoadBalanceStategy; 6 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 软负载加权随机算法实现 12 | * 13 | * @author xctian 14 | * @date 2019/12/27 15 | */ 16 | public class WeightRandomLoadBalanceStrategyImpl implements LoadBalanceStategy { 17 | 18 | @Override 19 | public ProviderRegisterMessage select(List providerRegisterMessages) { 20 | //根据加权创建服务索引列表:比如权重为3,则该服务的索引在列表出现三次。建立该列表后对其进行随机查找 21 | List indexList = LoadBalanceEngine.getIndexListByWeight(providerRegisterMessages); 22 | int index = RandomUtils.nextInt(0,indexList.size()); 23 | return providerRegisterMessages.get(indexList.get(index)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/provider/NettyDecodeHandler.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.provider; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import softrpc.framework.serialization.common.SerializerEngine; 9 | import softrpc.framework.serialization.common.SerializerType; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 解码器handler 15 | * 16 | * @author xctian 17 | * @date 2019/12/15 18 | */ 19 | public class NettyDecodeHandler extends ByteToMessageDecoder { 20 | 21 | public static final Logger LOGGER = LoggerFactory.getLogger(NettyDecodeHandler.class); 22 | 23 | /** 24 | * 解码类的Class对象 25 | */ 26 | private Class genericClass; 27 | 28 | /** 29 | * 构造解码器必须提供反序列化的对象类型 30 | */ 31 | public NettyDecodeHandler(Class genericClass) { 32 | this.genericClass = genericClass; 33 | } 34 | 35 | /** 36 | * 解码器重写decode方法,decode方法处理完成后,会继续后面的传递处理:将list结果列表传递到下一个InboundHandler 37 | * @param ctx 38 | * @param in 39 | * @param out 40 | * @throws Exception 41 | */ 42 | @Override 43 | public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 44 | long startTime = System.currentTimeMillis(); 45 | // 消息头部长度8字节=序列化协议int + 消息长度int 46 | if(in.readableBytes() < 8){ 47 | return; 48 | } 49 | in.markReaderIndex(); 50 | int serializerCode = in.readInt(); 51 | String serializer = SerializerType.getByCode(serializerCode).getSerializeName(); 52 | int dataSize = in.readInt(); 53 | if(dataSize < 0){ 54 | ctx.close(); 55 | } 56 | // 若当前可读字节数小于消息长度,则重置readerIndex,直至可以获取到消息长度的字节数 57 | if(in.readableBytes() < dataSize){ 58 | in.resetReaderIndex(); 59 | return; 60 | } 61 | byte[] data = new byte[dataSize]; 62 | // 从channel读取数据至byte数组data 63 | in.readBytes(data); 64 | Object obj = SerializerEngine.deserialize(data,genericClass,serializer); 65 | out.add(obj); 66 | long duration = System.currentTimeMillis()-startTime; 67 | LOGGER.info("[{}]协议解码耗时{}ms",serializer,duration); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/provider/NettyEncodeHandler.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.provider; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import softrpc.framework.serialization.common.SerializerEngine; 9 | import softrpc.framework.serialization.common.SerializerType; 10 | 11 | /** 12 | * 编码器Handler,消息编码格式为:消息头部(序列化协议code+消息长度)+消息内容 13 | * 14 | * @author xctian 15 | * @date 2019/12/16 16 | */ 17 | public class NettyEncodeHandler extends MessageToByteEncoder { 18 | 19 | public static final Logger LOGGER = LoggerFactory.getLogger(NettyEncodeHandler.class); 20 | 21 | private SerializerType serializeType; 22 | 23 | /** 24 | * 构造编码器实例必须提供所选择的序列化协议 25 | */ 26 | public NettyEncodeHandler(SerializerType serializeType){ 27 | this.serializeType = serializeType; 28 | } 29 | 30 | /** 31 | * 消息编码方法,NettyServerHandler里调用write方法将response传入至参数in 32 | * @param channelHandlerContext 33 | * @param in 34 | * @param out 35 | * @throws Exception 36 | */ 37 | @Override 38 | public void encode(ChannelHandlerContext channelHandlerContext, Object in, ByteBuf out) throws Exception { 39 | long startTime = System.currentTimeMillis(); 40 | // 获取序列化协议code 41 | int serializerCode = serializeType.getSerializeCode(); 42 | // 将其写入消息头部第一个int 43 | out.writeInt(serializerCode); 44 | // 将对象进行序列化 45 | byte[] data = SerializerEngine.serialize(in,serializeType); 46 | // 将data长度写入消息头部第二个int 47 | out.writeInt(data.length); 48 | // 将消息体写入 49 | out.writeBytes(data); 50 | long duration = System.currentTimeMillis() - startTime; 51 | LOGGER.info("[{}]序列化协议编码耗时{}ms",serializeType.getSerializeName(),duration); 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/provider/NettyServer.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.provider; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelOption; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.logging.LogLevel; 12 | import io.netty.handler.logging.LoggingHandler; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import softrpc.framework.serialization.common.SerializerType; 16 | import softrpc.framework.serialization.message.RequestMessage; 17 | import softrpc.framework.utils.PropertyConfigUtil; 18 | 19 | /** 20 | * Netty传输服务端 21 | * 22 | * @author xctian 23 | * @date 2019/12/14 24 | */ 25 | public class NettyServer { 26 | 27 | private static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class); 28 | 29 | /** 30 | * 服务端boss线程组,负责新连接的监听和接受 31 | */ 32 | private EventLoopGroup bossGroup; 33 | 34 | /** 35 | * 服务端worker线程组,负责执行Handler中的业务处理,如数据的输入输出 36 | */ 37 | private EventLoopGroup workerGroup; 38 | 39 | /** 40 | * 绑定端口的Channel 41 | */ 42 | private Channel channel; 43 | 44 | /** 45 | * netty服务端的启动 46 | */ 47 | public void startServer(final int port){ 48 | if (bossGroup != null || workerGroup != null){ 49 | return; 50 | } 51 | bossGroup = new NioEventLoopGroup(); 52 | workerGroup = new NioEventLoopGroup(); 53 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 54 | final SerializerType serialize = PropertyConfigUtil.getServerSerializer(); 55 | serverBootstrap 56 | .group(bossGroup,workerGroup) 57 | .channel(NioServerSocketChannel.class) 58 | // 服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小 59 | .option(ChannelOption.SO_BACKLOG,1024) 60 | // 心跳包检测机制,设置该选项以后,连接会测试链接的状态 61 | .childOption(ChannelOption.SO_KEEPALIVE,true) 62 | // 禁用Nagle算法,使用于小数据即时传输 63 | .childOption(ChannelOption.TCP_NODELAY,true) 64 | .handler(new LoggingHandler(LogLevel.INFO)) 65 | // 装配子通道的handler流水线。泛型参数代表需要初始化的通道类型 66 | .childHandler(new ChannelInitializer() { 67 | @Override 68 | protected void initChannel(SocketChannel serverSocketChannel) throws Exception { 69 | // 向channelPipline注册消息解码器 70 | serverSocketChannel.pipeline().addLast(new NettyDecodeHandler(RequestMessage.class)); 71 | // 向channelPipline注册消息编码器 72 | serverSocketChannel.pipeline().addLast(new NettyEncodeHandler(serialize)); 73 | // 向channelPipline注册业务逻辑处理器 74 | serverSocketChannel.pipeline().addLast(new NettyServerHandler()); 75 | } 76 | }); 77 | try { 78 | channel = serverBootstrap.bind(port).sync().channel(); 79 | }catch (InterruptedException e){ 80 | throw new RuntimeException(e); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/provider/NettyServerHandler.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.provider; 2 | 3 | import com.google.common.collect.Maps; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import softrpc.framework.serialization.message.RequestMessage; 9 | import softrpc.framework.serialization.message.ResponseMessage; 10 | 11 | import java.util.Map; 12 | import java.util.concurrent.Semaphore; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | 16 | /** 17 | * 服务端业务逻辑处理器 18 | * 19 | * SimpleChannelInboundHandler在接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。 20 | * 而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而服务器端有可能在channelRead方法返回前还 21 | * 没有写完数据,因此不能让它自动release。 22 | * 23 | * @author xctian 24 | * @date 2019/12/16 25 | */ 26 | public class NettyServerHandler extends ChannelInboundHandlerAdapter{ 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(NettyServerHandler.class); 29 | 30 | /** 31 | * 服务端限流Map,可在rpc-service.xml中进行配置 32 | */ 33 | private static final Map SERVICE_SEMAPHORE_MAP = Maps.newConcurrentMap(); 34 | 35 | @Override 36 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 37 | long startTime = System.currentTimeMillis(); 38 | RequestMessage requestMessage = (RequestMessage)msg; 39 | LOGGER.info("服务端接收请求消息:[content:{}id:{}]",requestMessage,requestMessage.getMessageId()); 40 | if(ctx.channel().isWritable()){ 41 | long consumeTimeout = requestMessage.getTimeout(); 42 | // 获取具体的接口实现类的bean id 43 | String serviceProviderId = requestMessage.getRefId(); 44 | // 进行服务端限流 45 | int maxWorkThread = requestMessage.getMaxWorkThread(); 46 | Semaphore semaphore = SERVICE_SEMAPHORE_MAP.get(serviceProviderId); 47 | // 双锁校验式单例 48 | if(null == semaphore){ 49 | synchronized (SERVICE_SEMAPHORE_MAP){ 50 | //第二次判断是为了避免重复new Semaphore 51 | if(null == semaphore){ 52 | semaphore = new Semaphore(maxWorkThread); 53 | SERVICE_SEMAPHORE_MAP.put(serviceProviderId,semaphore); 54 | } 55 | } 56 | } 57 | ResponseMessage responseMessage = null; 58 | boolean acquire = false; 59 | try { 60 | // 利用semaphore实现服务端限流,因为反射操作执行效率低下,如果大量反射同时执行,将占用资源 61 | acquire = semaphore.tryAcquire(consumeTimeout, TimeUnit.MILLISECONDS); 62 | if(acquire){ 63 | // 成功则发起反射调用,调用服务 64 | responseMessage = ServiceProvider.excuteMethodFromRequestMessage(requestMessage); 65 | }else { 66 | LOGGER.warn("服务限流,请求超时"); 67 | } 68 | }catch (Exception e){ 69 | LOGGER.error("服务方反射调用本地方法时产生错误",e); 70 | throw new RuntimeException("服务方反射调用本地方法时产生错误"); 71 | }finally { 72 | if (acquire){ 73 | // 恢复semaphore 74 | semaphore.release(); 75 | } 76 | } 77 | if(null == responseMessage){ 78 | throw new RuntimeException("服务方反射调用本地方法时产生错误"); 79 | } 80 | // 将调用方法得到的responseMessage写回channel 81 | long duration = System.currentTimeMillis() - startTime; 82 | LOGGER.info("服务端调用服务耗时{}ms",duration); 83 | LOGGER.info("服务端方法调用返回结果:[content:{} messageId:{}]",responseMessage,responseMessage.getMessageId()); 84 | ctx.writeAndFlush(responseMessage); 85 | }else { 86 | LOGGER.error("Channel异常,请求失败"); 87 | throw new RuntimeException("Channel异常,请求失败"); 88 | } 89 | } 90 | 91 | @Override 92 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 93 | ctx.flush(); 94 | } 95 | 96 | @Override 97 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 98 | cause.printStackTrace(); 99 | ctx.close(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/provider/ServiceProvider.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.provider; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.context.support.ClassPathXmlApplicationContext; 7 | import softrpc.framework.serialization.message.RequestMessage; 8 | import softrpc.framework.serialization.message.ResponseMessage; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * 利用反射调用服务 15 | * 16 | * @author xctian 17 | * @date 2019/12/16 18 | */ 19 | public class ServiceProvider { 20 | 21 | public static final Logger LOGGER = LoggerFactory.getLogger(ServiceProvider.class); 22 | 23 | public static final ApplicationContext CONTEXT = new ClassPathXmlApplicationContext("rpc-service.xml"); 24 | 25 | public static ResponseMessage excuteMethodFromRequestMessage(RequestMessage requestMessage) throws ClassNotFoundException { 26 | // IOC容器中获取接口的实现类 27 | Object provider = CONTEXT.getBean(requestMessage.getRefId()); 28 | // 确定方法形参类型列表,用于获取Method对象 29 | Class[] parameterClasses = null; 30 | // parameterTypes已经封装在client发送至服务端的RequestMessage中 31 | String[] parameterTypes = requestMessage.getParameterTypes(); 32 | if (null != parameterTypes && parameterTypes.length > 0){ 33 | parameterClasses = new Class[parameterTypes.length]; 34 | for (int i = 0; i < parameterTypes.length; i++){ 35 | try { 36 | //使用反射,根据Class的全限定名拿到对应的Class对象 37 | parameterClasses[i] = Class.forName(parameterTypes[i]); 38 | } catch (ClassNotFoundException e) { 39 | LOGGER.error("未找到该参数的类型:"+parameterTypes[i]); 40 | e.printStackTrace(); 41 | throw new ClassNotFoundException(); 42 | } 43 | 44 | } 45 | } 46 | try { 47 | // 根据反射获取一个类对象中的Method。该方法的第一个参数name是要获得方法的名字,第二个参数parameterTypes是按声明顺序标识该方法形参类型。 48 | Method method = provider.getClass().getMethod(requestMessage.getMethodName(),parameterClasses); 49 | // 执行服务端本地方法后的返回结果,invoke第一个参数为类的实例,第二个参数为传入的对应参数的值 50 | Object resultValue = method.invoke(provider,requestMessage.getParameters()); 51 | ResponseMessage responseMessage = new ResponseMessage(); 52 | responseMessage.setMessageId(requestMessage.getMessageId()); 53 | responseMessage.setResultValue(resultValue); 54 | responseMessage.setTimeout(requestMessage.getTimeout()); 55 | return responseMessage; 56 | } catch (NoSuchMethodException e) { 57 | LOGGER.error("该方法不存在:" + requestMessage.getMethodName()); 58 | throw new RuntimeException("反射调用产生错误"); 59 | } catch (IllegalAccessException e) { 60 | LOGGER.error("无法访问该方法:" + requestMessage.getMethodName()); 61 | throw new RuntimeException("反射调用产生错误"); 62 | } catch (InvocationTargetException e) { 63 | throw new RuntimeException("反射调用产生错误"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/common/SerializerEngine.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.common; 2 | 3 | import softrpc.framework.serialization.serializer.Serializer; 4 | 5 | /** 6 | * 序列化工具引擎,通过传入参数serializerType灵活选择序列化方案,有两种实现方式 7 | * 8 | * @author xctian 9 | * @date 2019/11/10 10 | */ 11 | public class SerializerEngine { 12 | /** 13 | * 序列化(工厂模式实现方案) 14 | * 15 | * @param t 序列化对象 16 | * @param serializerType 指定的序列化协议 17 | * @return 序列化后的字节数组 18 | */ 19 | public static byte[] serialize(T t, SerializerType serializerType){ 20 | // 通过工厂获取指定的Serializer 21 | Serializer serializer = SerializerFactory.getSerializer(serializerType.getSerializeName()); 22 | return serializer.serialize(t); 23 | } 24 | 25 | /** 26 | * 反序列化(工厂模式实现方案) 27 | * 28 | * @param data 序列化生成的字符数组 29 | * @param clazz 反序列化后的对象类型 30 | * @param serializerType 指定的序列化协议 31 | * @return 序列化后的字节数组 32 | */ 33 | public static T deserialize(byte[] data, Class clazz, String serializerType){ 34 | Serializer serializer = SerializerFactory.getSerializer(serializerType); 35 | return serializer.deserialize(data, clazz); 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/common/SerializerFactory.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.common; 2 | 3 | import softrpc.framework.serialization.serializer.HessianSerializer; 4 | import softrpc.framework.serialization.serializer.JDKSerializer; 5 | import softrpc.framework.serialization.serializer.ProtoStuffSerializer; 6 | import softrpc.framework.serialization.serializer.Serializer; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Serializer简单工厂,提供根据序列化协议名称获得Serializer对象实例的功能 13 | * 14 | * @author xctian 15 | * @date 2019/11/10 16 | */ 17 | public class SerializerFactory { 18 | 19 | static Map SERIALIZER_MAP = new HashMap<>(); 20 | 21 | static { 22 | SERIALIZER_MAP.put("hessian",new HessianSerializer()); 23 | SERIALIZER_MAP.put("protostuff",new ProtoStuffSerializer()); 24 | SERIALIZER_MAP.put("default",new JDKSerializer()); 25 | } 26 | public static Serializer getSerializer(String serializerName){ 27 | String validName = serializerName.toLowerCase(); 28 | Serializer serializer = SERIALIZER_MAP.get(validName); 29 | if(serializer != null){ 30 | return serializer; 31 | }else { 32 | return SERIALIZER_MAP.get("default"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/common/SerializerType.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.common; 2 | 3 | import com.sun.deploy.util.StringUtils; 4 | 5 | /** 6 | * 支持的序列化工具枚举类,用作SerializerEngine的Map常量的key 7 | * 8 | * @author xctian 9 | * @date 2019/11/10 10 | */ 11 | public enum SerializerType { 12 | /** 13 | * JDK默认的序列化工具 14 | */ 15 | JDKSerializer("Default",0), 16 | 17 | /** 18 | * Hessian 19 | */ 20 | HessianSerianlizer("Hessian",1), 21 | 22 | /** 23 | * ProtoStuff 24 | */ 25 | ProtoStuffSerializer("ProtoStuff",2); 26 | 27 | private String serializeName; 28 | private int serializeCode; 29 | 30 | public String getSerializeName() { 31 | return serializeName; 32 | } 33 | 34 | public int getSerializeCode() { 35 | return serializeCode; 36 | } 37 | 38 | SerializerType(String serializeName){ 39 | this.serializeName = serializeName; 40 | } 41 | 42 | SerializerType(String serializeName,int serializeCode){ 43 | this.serializeName = serializeName; 44 | this.serializeCode = serializeCode; 45 | } 46 | 47 | 48 | public static SerializerType getByType(String serializeName){ 49 | if (serializeName.isEmpty()) { 50 | return JDKSerializer; 51 | } 52 | for (SerializerType serialize : SerializerType.values()){ 53 | if(serializeName.equalsIgnoreCase(serialize.getSerializeName())){ 54 | return serialize; 55 | } 56 | } 57 | // 如果传入的type都不匹配则返回JDK默认序列化工具 58 | return JDKSerializer; 59 | } 60 | 61 | public static SerializerType getByCode(int serializeCode){ 62 | switch (serializeCode){ 63 | case 1: 64 | return HessianSerianlizer; 65 | case 2: 66 | return ProtoStuffSerializer; 67 | default: 68 | return JDKSerializer; 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/message/RequestMessage.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.message; 2 | 3 | import java.io.Serializable; 4 | import java.util.Arrays; 5 | 6 | /** 7 | * 自定义请求消息类 8 | * 9 | * @author xctian 10 | * @date 2019/12/16 11 | */ 12 | public class RequestMessage implements Serializable { 13 | /** 14 | * 用于唯一标识消息的id 15 | */ 16 | private String messageId; 17 | 18 | /*====以下参数可以从配置中读取====*/ 19 | /** 20 | * 服务接口的全限定名 21 | */ 22 | private String servicePath; 23 | /** 24 | * 响应超时时间 25 | */ 26 | private long timeout = 3000; 27 | /*====以下参数利用反射获取====*/ 28 | /** 29 | * 待执行的方法名字 30 | */ 31 | private String methodName; 32 | /** 33 | * 待执行的方法参数值(实参) 34 | */ 35 | private Object[] parameters; 36 | /** 37 | * 待执行的方法参数类型的全限定名,如java.lang.String 38 | */ 39 | private String[] parameterTypes; 40 | 41 | /*====以下参数从客户端本地缓存的服务方信息中获取====*/ 42 | /** 43 | * 服务端限流的信号量大小 44 | */ 45 | private Integer maxWorkThread; 46 | /** 47 | * 服务方接口实现类的bean标签的id(经过负载均衡后) 48 | */ 49 | private String refId; 50 | 51 | public String getMessageId() { 52 | return messageId; 53 | } 54 | 55 | public void setMessageId(String messageId) { 56 | this.messageId = messageId; 57 | } 58 | 59 | public String getServicePath() { 60 | return servicePath; 61 | } 62 | 63 | public void setServicePath(String servicePath) { 64 | this.servicePath = servicePath; 65 | } 66 | 67 | public long getTimeout() { 68 | return timeout; 69 | } 70 | 71 | public void setTimeout(long timeout) { 72 | this.timeout = timeout; 73 | } 74 | 75 | public String getMethodName() { 76 | return methodName; 77 | } 78 | 79 | public void setMethodName(String methodName) { 80 | this.methodName = methodName; 81 | } 82 | 83 | public Object[] getParameters() { 84 | return parameters; 85 | } 86 | 87 | public void setParameters(Object[] parameters) { 88 | this.parameters = parameters; 89 | } 90 | 91 | public String[] getParameterTypes() { 92 | return parameterTypes; 93 | } 94 | 95 | public void setParameterTypes(String[] parameterTypes) { 96 | this.parameterTypes = parameterTypes; 97 | } 98 | 99 | public Integer getMaxWorkThread() { 100 | return maxWorkThread; 101 | } 102 | 103 | public void setMaxWorkThread(Integer workThread) { 104 | this.maxWorkThread = workThread; 105 | } 106 | 107 | public String getRefId() { 108 | return refId; 109 | } 110 | 111 | public void setRefId(String refId) { 112 | this.refId = refId; 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | return "RequestMessage{" + 118 | "messageId='" + messageId + '\'' + 119 | ", servicePath='" + servicePath + '\'' + 120 | ", timeout=" + timeout + 121 | ", methodName='" + methodName + '\'' + 122 | ", parameters=" + Arrays.toString(parameters) + 123 | ", parameterTypes=" + Arrays.toString(parameterTypes) + 124 | ", workThread=" + maxWorkThread + 125 | ", refId='" + refId + '\'' + 126 | '}'; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/message/ResponseMessage.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.message; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 自定义响应消息类 7 | * 8 | * @author xctian 9 | * @date 2019/12/16 10 | */ 11 | public class ResponseMessage implements Serializable { 12 | 13 | /** 14 | * 用于唯一标识消息的id,RequestMessage里也有相同字段 15 | */ 16 | private String messageId; 17 | 18 | /** 19 | * 方法执行结果 20 | */ 21 | private Object resultValue; 22 | 23 | /** 24 | * 响应超时时间 25 | */ 26 | private long timeout; 27 | 28 | public String getMessageId() { 29 | return messageId; 30 | } 31 | 32 | public void setMessageId(String messageId) { 33 | this.messageId = messageId; 34 | } 35 | 36 | public Object getResultValue() { 37 | return resultValue; 38 | } 39 | 40 | public void setResultValue(Object resultValue) { 41 | this.resultValue = resultValue; 42 | } 43 | 44 | public long getTimeout() { 45 | return timeout; 46 | } 47 | 48 | public void setTimeout(long timeout) { 49 | this.timeout = timeout; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "ResponseMessage{" + 55 | "messageId='" + messageId + '\'' + 56 | ", resultValue=" + resultValue + 57 | ", timeout=" + timeout + 58 | '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/serializer/HessianSerializer.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.serializer; 2 | 3 | import com.caucho.hessian.io.HessianInput; 4 | import com.caucho.hessian.io.HessianOutput; 5 | 6 | import java.io.ByteArrayInputStream; 7 | import java.io.ByteArrayOutputStream; 8 | 9 | /** 10 | * Hessian序列化工具,要求序列化对象必须实现Serializable接口 11 | * 12 | * @author xctian 13 | * @date 2019/11/9 14 | */ 15 | public class HessianSerializer implements Serializer { 16 | @Override 17 | public byte[] serialize(T t) { 18 | if (null == t){ 19 | throw new NullPointerException(); 20 | } 21 | 22 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 23 | HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream); 24 | try { 25 | hessianOutput.writeObject(t); 26 | hessianOutput.close(); 27 | return byteArrayOutputStream.toByteArray(); 28 | } catch (Exception e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | @Override 34 | public T deserialize(byte[] data, Class clazz) { 35 | if(null ==data){ 36 | throw new NullPointerException(); 37 | } 38 | 39 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data); 40 | HessianInput hessianInput = new HessianInput(byteArrayInputStream); 41 | try { 42 | return (T)hessianInput.readObject(); 43 | } catch (Exception e) { 44 | throw new RuntimeException(e); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/serializer/JDKSerializer.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.serializer; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * JDK默认的序列化工具,需要被序列化的对象实现Serializable接口 7 | * 8 | * @author xctian 9 | * @date 2019/11/9 10 | */ 11 | public class JDKSerializer implements Serializer{ 12 | 13 | @Override 14 | public byte[] serialize(T t) { 15 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 16 | try { 17 | // 通过ByteArrayOutputStream构建对象输出流ObjectOutputStream 18 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); 19 | objectOutputStream.writeObject(t); 20 | objectOutputStream.close(); 21 | return byteArrayOutputStream.toByteArray(); 22 | } catch (Exception e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | 27 | @Override 28 | public T deserialize(byte[] data, Class clazz) { 29 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data); 30 | try { 31 | // 通过ByteArrayInputStream构建对象输入流ObjectInputStream 32 | ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); 33 | return (T) objectInputStream.readObject(); 34 | } catch (Exception e) { 35 | throw new RuntimeException(e); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/serializer/ProtoStuffSerializer.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.serializer; 2 | 3 | import com.dyuproject.protostuff.LinkedBuffer; 4 | import com.dyuproject.protostuff.ProtostuffIOUtil; 5 | import com.dyuproject.protostuff.Schema; 6 | import com.dyuproject.protostuff.runtime.RuntimeSchema; 7 | import org.objenesis.Objenesis; 8 | import org.objenesis.ObjenesisStd; 9 | 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * 基于protostuff的序列化和反序列化 15 | * 16 | * @author xctian 17 | * @date 2019/11/9 18 | */ 19 | public class ProtoStuffSerializer implements Serializer { 20 | /** 21 | * schema中包含了对象进行序列化和反序列化的逻辑 22 | */ 23 | private static Map, Schema> cachedSchema = new ConcurrentHashMap<>(); 24 | private static Objenesis objenesis = new ObjenesisStd(true); 25 | 26 | /** 27 | * 获取/构造Schema 28 | * 29 | * @param cls Class 30 | * @return Schema实例 31 | */ 32 | @SuppressWarnings("unchecked") 33 | private static Schema getSchema(Class cls){ 34 | Schema schema = (Schema) cachedSchema.get(cls); 35 | // 构造schema 36 | if(null == schema){ 37 | schema = RuntimeSchema.createFrom(cls); 38 | cachedSchema.put(cls,schema); 39 | } 40 | return schema; 41 | } 42 | 43 | @Override 44 | @SuppressWarnings("unchecked") 45 | public byte[] serialize(T t) { 46 | Class cls = (Class) t.getClass(); 47 | //通过对象的类构建对应的schema 48 | Schema schema = getSchema(cls); 49 | //使用LinkedBuffer分配一块默认大小的buffer空间 50 | LinkedBuffer linkedBuffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 51 | try { 52 | //使用给定的schema将对象序列化为一个byte数组,并返回 53 | byte[] res = ProtostuffIOUtil.toByteArray(t, schema, linkedBuffer); 54 | return res; 55 | }finally { 56 | linkedBuffer.clear(); 57 | } 58 | } 59 | 60 | @Override 61 | @SuppressWarnings("unchecked") 62 | public T deserialize(byte[] data, Class clazz) { 63 | if(null == data){ 64 | throw new NullPointerException(); 65 | } 66 | 67 | try { 68 | T message = (T)objenesis.newInstance(clazz); 69 | Schema schema = getSchema(clazz); 70 | //使用给定的schema将byte数组和对象合并 71 | ProtostuffIOUtil.mergeFrom(data, message, schema); 72 | return message; 73 | } catch (Exception e) { 74 | throw new RuntimeException(e); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/serialization/serializer/Serializer.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.serialization.serializer; 2 | 3 | /** 4 | * 序列化/反序列化通用接口 5 | * 6 | * @author xctian 7 | * @date 2019/11/9 8 | */ 9 | public interface Serializer { 10 | /** 11 | * 序列化 12 | * 13 | * @param t 序列化的对象 14 | * @return 序列化后的byte数组 15 | */ 16 | public byte[] serialize(T t); 17 | 18 | /** 19 | * 反序列化 20 | * 21 | * @param data 序列化的byte数组 22 | * @param clazz 指定的反序列化后的Class对象 23 | * @return 反序列化后的对象实例 24 | */ 25 | public T deserialize(byte[] data,Class clazz); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/spring/factoryBean/RpcReferenceFactoryBean.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.spring.factoryBean; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.springframework.beans.factory.FactoryBean; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import softrpc.framework.invoker.ClientProxyBeanFactory; 7 | import softrpc.framework.invoker.NettyChannelPoolFactory; 8 | import softrpc.framework.zookeeper.RegisterCenter; 9 | import softrpc.framework.zookeeper.message.InvokerRegisterMessage; 10 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 11 | 12 | import java.net.InetSocketAddress; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | /** 17 | * RpcReferenceFactoryBean类 18 | * 由于parser类的作用,对于标签的解析生成的是一个已经组装了的factoryBean对象,而factoryBean返回的实际对象是getObject里指定的类对象 19 | * 20 | * @author xctian 21 | * @date 2019/12/18 22 | */ 23 | public class RpcReferenceFactoryBean implements FactoryBean, InitializingBean { 24 | 25 | /** 26 | * 缓存的服务地址集合(IP+PORT) 27 | */ 28 | private static Set socketAddressSet = Sets.newHashSet(); 29 | 30 | /** 31 | * ChanelPool 工厂 32 | */ 33 | private static NettyChannelPoolFactory nettyChannelPoolFactory = NettyChannelPoolFactory.getInstance(); 34 | 35 | /** 36 | * 注册中心 37 | */ 38 | private static RegisterCenter registerCenter = RegisterCenter.getInstance(); 39 | 40 | /* 标签中必须配置的参数 */ 41 | /** 42 | * 服务接口 43 | */ 44 | private Class targetInterface; 45 | /** 46 | * 超时时间 47 | */ 48 | private int timeout; 49 | /** 50 | * 服务所属应用名称 51 | */ 52 | private String appName; 53 | 54 | /* soft-reference中可选配置项 */ 55 | /** 56 | * 服务分组名(本项目没用到,可以自行在注册中心进行扩展) 57 | * 如果soft-reference没有配置,则不会执行parser里对应的if对该变量赋值,该变量则为默认值default,下同。 58 | */ 59 | private String groupName = "default"; 60 | /* 本地使用参数,不需要传到ZK 61 | /** 62 | * 负载均衡策略 63 | */ 64 | private String loadBalanceStrategy = "default"; 65 | 66 | /** 67 | * 生成soft:reference标签所引用的服务接口的代理对象 68 | * 返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中 69 | * 70 | * @return 服务接口的代理对象 71 | */ 72 | @Override 73 | public Object getObject() throws Exception { 74 | return ClientProxyBeanFactory.getProxyInstance(appName, targetInterface, timeout, loadBalanceStrategy); 75 | } 76 | 77 | /** 78 | * 声明接口代理对象的类型 79 | * 返回FactoryBean创建的bean类型 80 | * 81 | * @return 82 | */ 83 | @Override 84 | public Class getObjectType() { 85 | return targetInterface; 86 | } 87 | 88 | /** 89 | * 声明是否单例 90 | * 91 | * @return 92 | */ 93 | @Override 94 | public boolean isSingleton() { 95 | return true; 96 | } 97 | 98 | /** 99 | * invoker初始化:从ZK获取引用服务的地址,并为每一个地址生成channelPool 100 | * 101 | * @throws Exception 102 | */ 103 | @Override 104 | public void afterPropertiesSet() throws Exception { 105 | // 将soft-reference内容注册到ZK,同时获取服务地址到本地 106 | InvokerRegisterMessage invoker = new InvokerRegisterMessage(); 107 | invoker.setServicePath(targetInterface.getName()); 108 | invoker.setGroupName(groupName); 109 | invoker.setAppName(appName); 110 | // 本机所有invoker的machineID是相同的 111 | invoker.setInvokerMachineID4Server(InvokerRegisterMessage.getInvokerMachineID4Client()); 112 | // 根据标签内容从注册中心获取服务地址列表 113 | List providerRegisterMessageList = registerCenter.registerInvoker(invoker); 114 | // 提前为不同的服务地址创建channelPool 115 | for (ProviderRegisterMessage provider : providerRegisterMessageList) { 116 | InetSocketAddress socketAddress = new InetSocketAddress(provider.getServerIp(), provider.getServerPort()); 117 | boolean isFirstAdd = socketAddressSet.add(socketAddress); 118 | if (isFirstAdd) { 119 | nettyChannelPoolFactory.registerChannelQueueToMap(socketAddress); 120 | } 121 | } 122 | } 123 | 124 | public static Set getSocketAddressSet() { 125 | return socketAddressSet; 126 | } 127 | 128 | public static void setSocketAddressSet(Set socketAddressSet) { 129 | RpcReferenceFactoryBean.socketAddressSet = socketAddressSet; 130 | } 131 | 132 | public RegisterCenter getRegisterCenter() { 133 | return registerCenter; 134 | } 135 | 136 | public Class getTargetInterface() { 137 | return targetInterface; 138 | } 139 | 140 | public void setTargetInterface(Class targetInterface) { 141 | this.targetInterface = targetInterface; 142 | } 143 | 144 | public int getTimeout() { 145 | return timeout; 146 | } 147 | 148 | public void setTimeout(int timeout) { 149 | this.timeout = timeout; 150 | } 151 | 152 | public String getAppName() { 153 | return appName; 154 | } 155 | 156 | public void setAppName(String appName) { 157 | this.appName = appName; 158 | } 159 | 160 | public String getGroupName() { 161 | return groupName; 162 | } 163 | 164 | public void setGroupName(String groupName) { 165 | this.groupName = groupName; 166 | } 167 | 168 | public String getLoadBalanceStrategy() { 169 | return loadBalanceStrategy; 170 | } 171 | 172 | public void setLoadBalanceStrategy(String loadBalanceStrategy) { 173 | this.loadBalanceStrategy = loadBalanceStrategy; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/spring/factoryBean/RpcServiceFactoryBean.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.spring.factoryBean; 2 | 3 | import com.google.common.collect.Maps; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.FactoryBean; 7 | import org.springframework.beans.factory.InitializingBean; 8 | import softrpc.framework.provider.NettyServer; 9 | import softrpc.framework.utils.IPutil; 10 | import softrpc.framework.zookeeper.RegisterCenter; 11 | import softrpc.framework.zookeeper.RegisterCenter4Provider; 12 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 13 | 14 | import java.util.Map; 15 | 16 | /** 17 | * RpcServiceFactoryBean 18 | * 由于parser类的作用,对于标签的解析生成的是一个已经组装了的factoryBean对象,而factoryBean返回的实际对象是getObject里指定的类对象 19 | * 20 | * @author xctian 21 | * @date 2019/12/18 22 | */ 23 | public class RpcServiceFactoryBean implements FactoryBean, InitializingBean { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcServiceFactoryBean.class); 26 | 27 | /** 28 | * 该Map用于缓存每个NettyServer实例 29 | * key:已经开启服务的端口;value:对应的NettyServer,避免重复注册端口,以及便于NettyServer管理 30 | */ 31 | private static final Map NETTY_SERVER_MAP = Maps.newConcurrentMap(); 32 | 33 | /** 34 | * 注册中心 35 | */ 36 | private static final RegisterCenter4Provider registerCenter4Provider = RegisterCenter.getInstance(); 37 | 38 | /* 以下信息注册到ZK*/ 39 | /* 必选项 */ 40 | /** 41 | * 接口所在应用名 42 | */ 43 | private String appName; 44 | /** 45 | * 服务接口 46 | */ 47 | private String servicePath; 48 | /** 49 | * 该服务接口实现类对象,用于查找对应的class标签内容,即实现类的全限定名 50 | */ 51 | private String ref; 52 | /** 53 | * 服务端口 54 | */ 55 | private Integer serverPort; 56 | /** 57 | * 服务超时时间 58 | */ 59 | private Integer timeout; 60 | 61 | /* 以下为可选项 */ 62 | /** 63 | * 服务分组组名 64 | */ 65 | private String groupName = "default"; 66 | /** 67 | * Provider权重:1-100 68 | */ 69 | private int weight = 1; 70 | /** 71 | * 服务端限流大小 72 | */ 73 | private int workThreads = 10; 74 | 75 | @Override 76 | public Object getObject() throws Exception { 77 | // 服务端只需要加载xml文件完成相关初始化即可,无需返回bean对象 78 | return null; 79 | } 80 | 81 | @Override 82 | public Class getObjectType() { 83 | return Object.class; 84 | } 85 | 86 | @Override 87 | public boolean isSingleton() { 88 | return true; 89 | } 90 | 91 | @Override 92 | public void afterPropertiesSet() throws Exception { 93 | // 组装service标签信息,以上成员变量的值由parser类解析后已经配置完毕 94 | ProviderRegisterMessage provider = new ProviderRegisterMessage(); 95 | provider.setAppName(appName); 96 | provider.setServicePath(servicePath); 97 | provider.setRefId(ref); 98 | // 获取本机ip 99 | provider.setServerIp(IPutil.localIp()); 100 | provider.setServerPort(serverPort); 101 | provider.setTimeout(timeout); 102 | // 以下项并不强制要求标签内有内容,若标签无内容则设置为本类中定义的值 103 | provider.setWorkThread(workThreads); 104 | provider.setWeight(weight); 105 | provider.setGroupName(groupName); 106 | // 注册服务到ZK 107 | registerCenter4Provider.registerProvider(provider); 108 | // 发布代理服务 109 | long nowTime = System.currentTimeMillis(); 110 | NettyServer nettyServer = NETTY_SERVER_MAP.get(serverPort); 111 | // 双锁校验式单例 112 | if(null == nettyServer){ 113 | // 开启新的nettyServer并缓存 114 | synchronized (RpcServiceFactoryBean.class){ 115 | if(null == NETTY_SERVER_MAP.get(serverPort)){ 116 | nettyServer = new NettyServer(); 117 | nettyServer.startServer(serverPort); 118 | NETTY_SERVER_MAP.put(serverPort,nettyServer); 119 | long duration = System.currentTimeMillis() - nowTime; 120 | LOGGER.info("[{}]端口开启代理服务耗时:{}ms",serverPort,duration); 121 | } 122 | } 123 | } 124 | } 125 | 126 | public String getAppName() { 127 | return appName; 128 | } 129 | 130 | public void setAppName(String appName) { 131 | this.appName = appName; 132 | } 133 | 134 | public String getServicePath() { 135 | return servicePath; 136 | } 137 | 138 | public void setServicePath(String servicePath) { 139 | this.servicePath = servicePath; 140 | } 141 | 142 | public String getRef() { 143 | return ref; 144 | } 145 | 146 | public void setRef(String ref) { 147 | this.ref = ref; 148 | } 149 | 150 | public Integer getServerPort() { 151 | return serverPort; 152 | } 153 | 154 | public void setServerPort(Integer serverPort) { 155 | this.serverPort = serverPort; 156 | } 157 | 158 | public Integer getTimeout() { 159 | return timeout; 160 | } 161 | 162 | public void setTimeout(Integer timeout) { 163 | this.timeout = timeout; 164 | } 165 | 166 | public String getGroupName() { 167 | return groupName; 168 | } 169 | 170 | public void setGroupName(String groupName) { 171 | this.groupName = groupName; 172 | } 173 | 174 | public int getWeight() { 175 | return weight; 176 | } 177 | 178 | public void setWeight(int weight) { 179 | this.weight = weight; 180 | } 181 | 182 | public int getWorkThreads() { 183 | return workThreads; 184 | } 185 | 186 | public void setWorkThreads(int workThreads) { 187 | this.workThreads = workThreads; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/spring/handler/RpcReferenceNamespaceHandler.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.spring.handler; 2 | 3 | import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 | import softrpc.framework.spring.parser.RpcReferenceBeanDefinitionParser; 5 | 6 | /** 7 | * 命名空间handler 8 | * 9 | * @author xctian 10 | * @date 2019/12/18 11 | */ 12 | public class RpcReferenceNamespaceHandler extends NamespaceHandlerSupport { 13 | // 给soft:reference标签注册对应的BeanDefinitionParser解析器 14 | @Override 15 | public void init() { 16 | registerBeanDefinitionParser("reference",new RpcReferenceBeanDefinitionParser()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/spring/handler/RpcServiceNamespaceHandler.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.spring.handler; 2 | 3 | import org.springframework.beans.factory.xml.NamespaceHandlerSupport; 4 | import softrpc.framework.spring.parser.RpcServiceBeanDefinitionParser; 5 | 6 | /** 7 | * 命名空间handler 8 | * 9 | * @author xctian 10 | * @date 2019/12/18 11 | */ 12 | public class RpcServiceNamespaceHandler extends NamespaceHandlerSupport{ 13 | @Override 14 | public void init() { 15 | // 给soft:service标签注册对应的BeanDefinitionParser解析器 16 | registerBeanDefinitionParser("service",new RpcServiceBeanDefinitionParser()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/spring/parser/RpcReferenceBeanDefinitionParser.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.spring.parser; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 7 | import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; 8 | import org.w3c.dom.Element; 9 | import softrpc.framework.spring.factoryBean.RpcReferenceFactoryBean; 10 | import softrpc.framework.utils.PropertyConfigUtil; 11 | 12 | /** 13 | * 自定义标签解析器,通过解析soft:reference标签内容生成一个getBeanClass方法返回的类对象 14 | * 15 | * @author xctian 16 | * @date 2019/12/18 17 | */ 18 | public class RpcReferenceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcReferenceBeanDefinitionParser.class); 21 | 22 | @Override 23 | protected Class getBeanClass(Element element) { 24 | // 对标签reference的解析结果是一个factoryBean类对象 25 | return RpcReferenceFactoryBean.class; 26 | } 27 | 28 | /** 29 | * 解析soft-reference标签,封装到RpcReferenceFactoryBean中 30 | * 31 | * @param element 32 | * @param builder 33 | */ 34 | @Override 35 | protected void doParse(Element element, BeanDefinitionBuilder builder) { 36 | try { 37 | long startTime = System.currentTimeMillis(); 38 | String id = element.getAttribute("id"); 39 | String timeout = element.getAttribute("timeout"); 40 | String targetInterface = element.getAttribute("interface"); 41 | String clusterStrategy = element.getAttribute("clusterStrategy"); 42 | String groupName = element.getAttribute("groupName"); 43 | String appName = element.getAttribute("appName"); 44 | builder.addPropertyValue("timeout", timeout); 45 | builder.addPropertyValue("targetInterface", Class.forName(targetInterface)); 46 | if (StringUtils.isNotBlank(groupName)) { 47 | builder.addPropertyValue("groupName", groupName); 48 | } 49 | if (StringUtils.isNotBlank(appName)) { 50 | builder.addPropertyValue("appName", appName); 51 | } else { 52 | String appName4Client = PropertyConfigUtil.getAppName4Client(); 53 | // soft-rpc.properties中的appName也没有配置则抛出异常 54 | if (StringUtils.isNotBlank(appName4Client)) { 55 | LOGGER.error("请配置{}标签的appName属性或在soft-rpc.properties中配置soft.rpc.client.app.name属性", id); 56 | throw new RuntimeException(String.format("%s%s", id, "标签缺少appName属性")); 57 | } 58 | builder.addPropertyValue("appName", appName4Client); 59 | } 60 | long duration = System.currentTimeMillis() - startTime; 61 | LOGGER.info("[{}]标签解析超时{}ms",id,duration); 62 | } catch (Exception e) { 63 | LOGGER.error("RevokerFactoryBeanDefinitionParser Error.", e); 64 | throw new RuntimeException(e); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/spring/parser/RpcServiceBeanDefinitionParser.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.spring.parser; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.commons.lang3.math.NumberUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 8 | import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; 9 | import org.w3c.dom.Element; 10 | import softrpc.framework.spring.factoryBean.RpcServiceFactoryBean; 11 | import softrpc.framework.utils.PropertyConfigUtil; 12 | 13 | /** 14 | * 自定义标签解析器,通过解析soft:service标签内容生成一个getBeanClass方法返回的类对象 15 | * 16 | * @author xctian 17 | * @date 2019/12/18 18 | */ 19 | public class RpcServiceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcServiceBeanDefinitionParser.class); 22 | 23 | @Override 24 | protected Class getBeanClass(Element element) { 25 | return RpcServiceFactoryBean.class; 26 | } 27 | 28 | @Override 29 | protected void doParse(Element element, BeanDefinitionBuilder builder) { 30 | long startTime = System.currentTimeMillis(); 31 | try { 32 | String id = element.getAttribute("id"); 33 | String serviceItf = element.getAttribute("interface"); 34 | String timeout = element.getAttribute("timeout"); 35 | String serverPort = element.getAttribute("serverPort"); 36 | String ref = element.getAttribute("ref"); 37 | String weight = element.getAttribute("weight"); 38 | String workThreads = element.getAttribute("workThreads"); 39 | String groupName = element.getAttribute("groupName"); 40 | String appName = element.getAttribute("appName"); 41 | builder.addPropertyValue("servicePath", serviceItf); 42 | // 作用是设置接口实现类全限定名 43 | builder.addPropertyValue("ref", ref); 44 | builder.addPropertyValue("serverPort", Integer.parseInt(serverPort)); 45 | builder.addPropertyValue("timeout", Integer.parseInt(timeout)); 46 | if (StringUtils.isNotBlank(groupName)) { 47 | builder.addPropertyValue("groupName", groupName); 48 | } 49 | if (NumberUtils.isDigits(weight)) { 50 | builder.addPropertyValue("weight", Integer.parseInt(weight)); 51 | } 52 | if (NumberUtils.isDigits(workThreads)) { 53 | builder.addPropertyValue("workThreads", workThreads); 54 | } 55 | if (StringUtils.isNotBlank(appName)) { 56 | builder.addPropertyValue("appName", appName); 57 | } else { 58 | // 如果soft-Service没有配置,则利用PropertyConfigUtil获取soft-rpc.properties中的appName 59 | String appName4Server = PropertyConfigUtil.getAppName4Server(); 60 | // soft-rpc.properties中的appName也没有配置则抛出异常 61 | if (StringUtils.isNotBlank(appName4Server)) { 62 | LOGGER.error("请配置{}标签的appName属性或在soft-rpc.properties中配置soft.rpc.server.app.name属性", id); 63 | throw new RuntimeException(String.format("%s%s", id, "标签缺少appName属性")); 64 | } 65 | builder.addPropertyValue("appName", appName4Server); 66 | } 67 | long duration = System.currentTimeMillis() - startTime; 68 | LOGGER.info("[{}]标签解析耗时{}ms", id, duration); 69 | } catch (Exception e) { 70 | LOGGER.error("ProviderFactoryBeanDefinitionParser Error.",e); 71 | throw new RuntimeException(e); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/test/service/MainClient.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.test.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.context.support.ClassPathXmlApplicationContext; 6 | 7 | /** 8 | * @author xctian 9 | * @date 2020/1/1 10 | */ 11 | public class MainClient { 12 | 13 | private static final Logger LOGGER = LoggerFactory.getLogger(MainClient.class); 14 | 15 | private static Service1 service1; 16 | private static Service2 service2; 17 | 18 | static { 19 | long startTime = System.currentTimeMillis(); 20 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("rpc-reference.xml"); 21 | // 通过 getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于 22 | // FactoryBean#getObject()代理了getBean()方法 23 | service1 = (Service1)context.getBean("Service1"); 24 | service2 = (Service2) context.getBean("Service2"); 25 | long duration = System.currentTimeMillis() - startTime; 26 | LOGGER.info("客户端初始化完成,耗时{}ms",duration); 27 | } 28 | 29 | public static void main(String[] args) { 30 | int executeOfService1 = 0; 31 | int executeOfService2 = 0; 32 | String message = "SOFT"; 33 | String res1 = String.format("[Service2imp1] The message you have given is %s",message); 34 | String res2 = String.format("[Service2imp2] The message you have given is %s",message); 35 | for(int i = 0;i < 1000;i++){ 36 | String str = service2.sayMessage(message); 37 | if(str.equals(res1)){ 38 | executeOfService1 ++; 39 | } else if (str.equals(res2)) { 40 | executeOfService2 ++; 41 | } 42 | } 43 | LOGGER.info("权重为50的SayServiceImpl1被调用{}次,权重为100的SayServiceImpl2被调用{}次", 44 | executeOfService1, executeOfService2); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/test/service/MainServer.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.test.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.context.support.ClassPathXmlApplicationContext; 6 | 7 | /** 8 | * @author xctian 9 | * @date 2020/1/2 10 | */ 11 | public class MainServer { 12 | 13 | private static final Logger LOGGER = LoggerFactory.getLogger(MainServer.class); 14 | 15 | public static void main(String[] args) { 16 | long startTime = System.currentTimeMillis(); 17 | 18 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("rpc-service.xml"); 19 | 20 | long duration = System.currentTimeMillis() - startTime; 21 | LOGGER.info("服务端启动成功,耗时{}ms",duration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/test/service/Service1.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.test.service; 2 | 3 | /** 4 | * 测试RPC服务接口 5 | * 6 | * @author xctian 7 | * @date 2019/12/17 8 | */ 9 | public interface Service1 { 10 | /** 11 | * 测试方法:直接打印信息 12 | */ 13 | void sayHi(); 14 | 15 | /** 16 | * 测试方法:打印传入的信息 17 | */ 18 | String sayMessage(String message); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/test/service/Service2.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.test.service; 2 | 3 | /** 4 | * @author xctian 5 | * @date 2019/12/17 6 | */ 7 | public interface Service2 { 8 | /** 9 | * 测试方法:直接打印信息 10 | */ 11 | void sayHi(); 12 | 13 | /** 14 | * 测试方法:打印传入的信息 15 | */ 16 | String sayMessage(String message); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/test/service/imp/Service1imp.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.test.service.imp; 2 | 3 | import softrpc.framework.test.service.Service1; 4 | 5 | /** 6 | * @author xctian 7 | * @date 2019/12/17 8 | */ 9 | public class Service1imp implements Service1{ 10 | 11 | @Override 12 | public void sayHi() { 13 | System.out.println("Hello,soft-RPC,this is [Service1]"); 14 | } 15 | 16 | @Override 17 | public String sayMessage(String message) { 18 | return String.format("[Service1] The message you have given is %s",message); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/test/service/imp/Service2imp1.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.test.service.imp; 2 | 3 | import softrpc.framework.test.service.Service2; 4 | 5 | /** 6 | * @author xctian 7 | * @date 2019/12/17 8 | */ 9 | public class Service2imp1 implements Service2 { 10 | @Override 11 | public void sayHi() { 12 | System.out.println("Hello,soft-RPC,this is [Service2imp1]"); 13 | } 14 | 15 | @Override 16 | public String sayMessage(String message) { 17 | System.out.println(message); 18 | return String.format("[Service2imp1] The message you have given is %s",message); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/test/service/imp/Service2imp2.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.test.service.imp; 2 | 3 | import softrpc.framework.test.service.Service2; 4 | 5 | /** 6 | * @author xctian 7 | * @date 2019/12/17 8 | */ 9 | public class Service2imp2 implements Service2 { 10 | @Override 11 | public void sayHi() { 12 | System.out.println("Hello,soft-RPC,this is [Service2imp2]"); 13 | } 14 | 15 | @Override 16 | public String sayMessage(String message) { 17 | return String.format("[Service2imp2] The message you have given is %s",message); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/utils/IPutil.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.net.*; 8 | import java.util.Enumeration; 9 | import java.util.List; 10 | 11 | /** 12 | * @author xctian 13 | * @date 2019/12/23 14 | */ 15 | public class IPutil { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(IPutil.class); 18 | 19 | private static String hostIp; 20 | 21 | public static String localIp() { 22 | return hostIp; 23 | } 24 | 25 | static { 26 | String ip = null; 27 | Enumeration allNetInterfaces; 28 | try { 29 | allNetInterfaces = NetworkInterface.getNetworkInterfaces(); 30 | while (allNetInterfaces.hasMoreElements()) { 31 | NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); 32 | List InterfaceAddress = netInterface.getInterfaceAddresses(); 33 | for (InterfaceAddress add : InterfaceAddress) { 34 | InetAddress inetAddress = add.getAddress(); 35 | if (inetAddress instanceof Inet4Address) { 36 | if (StringUtils.equals(inetAddress.getHostAddress(), "127.0.0.1")) { 37 | continue; 38 | } 39 | ip = inetAddress.getHostAddress(); 40 | break; 41 | } 42 | } 43 | } 44 | } catch (SocketException e) { 45 | LOGGER.error("获取本机Ip失败:异常信息:" + e.getMessage()); 46 | throw new RuntimeException(e); 47 | } 48 | hostIp = ip; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/utils/PropertyConfigUtil.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import softrpc.framework.serialization.common.SerializerType; 6 | 7 | import java.io.InputStream; 8 | import java.util.Properties; 9 | 10 | /** 11 | * 配置文件 12 | * 13 | * @author xctian 14 | * @date 2019/12/15 15 | */ 16 | public class PropertyConfigUtil { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(PropertyConfigUtil.class); 19 | 20 | private static final String PROPERTY_CLASSPATH = "/soft-rpc.properties"; 21 | private static final Properties properties = new Properties(); 22 | /* 必须手动设置的配置项,没有提供默认值 */ 23 | // ZK服务的地址 24 | private static String zkService = ""; 25 | // 服务注册应用名 26 | private static String appName4Server = ""; 27 | // 消费方引用应用名 28 | private static String appName4Client = ""; 29 | 30 | /* 有默认值的配置项 */ 31 | // ZK session超时时间 32 | private static int zkSessionTimeout; 33 | // ZK connect超时时间 34 | private static int zkConnectTimeout; 35 | // ChannelPool大小 36 | private static int channelPoolSize; 37 | // 客户端调用rpc服务线程池的大小 38 | private static int threadWorkerSize; 39 | // 默认的负载均衡策略 40 | private static String defaultClusterStrategy; 41 | // 服务端序列化协议 42 | private static SerializerType serverSerializer; 43 | // 客户端序列化协议 44 | private static String clientSerializer; 45 | 46 | /** 47 | 48 | * 初始化 49 | */ 50 | static { 51 | InputStream is = PropertyConfigUtil.class.getResourceAsStream(PROPERTY_CLASSPATH); 52 | if (null == is) { 53 | throw new IllegalStateException("soft-rpc.properties cannot be found in the classpath"); 54 | } 55 | try { 56 | // load方法其实就是传进去一个输入流,字节流或者字符流,字节流利用InputStreamReader转化为字符流,然后字符流用 57 | // BufferedReader包装,BufferedReader读取properties配置文件,每次读取一行,分割成两个字符串,因为Properties是 58 | // Map的子类,然后用put将两个字符串装进Properties对象。 59 | properties.load(is); 60 | zkService = properties.getProperty("soft.rpc.zookeeper.address"); 61 | appName4Server = properties.getProperty("soft.rpc.server.app.name"); 62 | appName4Client = properties.getProperty("soft.rpc.client.app.name"); 63 | serverSerializer = SerializerType.getByType(properties.getProperty("soft.rpc.server.serializer", "Default")); 64 | zkSessionTimeout = Integer.parseInt(properties.getProperty("soft.rpc.zookeeper.session.timeout","500")); 65 | zkConnectTimeout = Integer.parseInt(properties.getProperty("soft.rpc.zookeeper.connection.timeout","500")); 66 | channelPoolSize = Integer.parseInt(properties.getProperty("soft.rpc.client.channelPoolSize","10")); 67 | threadWorkerSize = Integer.parseInt(properties.getProperty("soft.rpc.client.threadWorkers","10")); 68 | defaultClusterStrategy = properties.getProperty("soft.rpc.client.clusterStrategy.default","random"); 69 | clientSerializer = properties.getProperty("soft.rpc.client.serializer","Default"); 70 | } catch (Throwable throwable) { 71 | LOGGER.warn("配置文件加载失败",throwable); 72 | throw new RuntimeException(throwable); 73 | }finally { 74 | if(null != is){ 75 | try { 76 | is.close(); 77 | }catch (Exception e){ 78 | e.printStackTrace(); 79 | } 80 | } 81 | } 82 | } 83 | 84 | public static String getPropertyClasspath() { 85 | return PROPERTY_CLASSPATH; 86 | } 87 | 88 | public static Properties getProperties() { 89 | return properties; 90 | } 91 | 92 | public static String getZkService() { 93 | return zkService; 94 | } 95 | 96 | public static void setZkService(String zkService) { 97 | PropertyConfigUtil.zkService = zkService; 98 | } 99 | 100 | public static String getAppName4Server() { 101 | return appName4Server; 102 | } 103 | 104 | public static String getAppName4Client() { 105 | return appName4Client; 106 | } 107 | 108 | public static int getZkSessionTimeout() { 109 | return zkSessionTimeout; 110 | } 111 | 112 | public static void setZkSessionTimeout(int zkSessionTimeout) { 113 | PropertyConfigUtil.zkSessionTimeout = zkSessionTimeout; 114 | } 115 | 116 | public static int getZkConnectTimeout() { 117 | return zkConnectTimeout; 118 | } 119 | 120 | public static void setZkConnectTimeout(int zkConnectTimeout) { 121 | PropertyConfigUtil.zkConnectTimeout = zkConnectTimeout; 122 | } 123 | 124 | public static int getChannelPoolSize() { 125 | return channelPoolSize; 126 | } 127 | 128 | public static void setChannelPoolSize(int channelPoolSize) { 129 | PropertyConfigUtil.channelPoolSize = channelPoolSize; 130 | } 131 | 132 | public static int getThreadWorkerSize() { 133 | return threadWorkerSize; 134 | } 135 | 136 | public static void setThreadWorkerSize(int threadWorkerSize) { 137 | PropertyConfigUtil.threadWorkerSize = threadWorkerSize; 138 | } 139 | 140 | public static String getDefaultClusterStrategy() { 141 | return defaultClusterStrategy; 142 | } 143 | 144 | public static void setDefaultClusterStrategy(String defaultClusterStrategy) { 145 | PropertyConfigUtil.defaultClusterStrategy = defaultClusterStrategy; 146 | } 147 | 148 | public static SerializerType getServerSerializer() { 149 | return serverSerializer; 150 | } 151 | 152 | public static void setServerSerializer(SerializerType serverSerializer) { 153 | PropertyConfigUtil.serverSerializer = serverSerializer; 154 | } 155 | 156 | public static String getClientSerializer() { 157 | return clientSerializer; 158 | } 159 | 160 | public static void setClientSerializer(String clientSerializer) { 161 | PropertyConfigUtil.clientSerializer = clientSerializer; 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/zookeeper/RegisterCenter.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.zookeeper; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Maps; 6 | import com.google.common.collect.Sets; 7 | import org.I0Itec.zkclient.IZkChildListener; 8 | import org.I0Itec.zkclient.ZkClient; 9 | import org.I0Itec.zkclient.serialize.SerializableSerializer; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import softrpc.framework.utils.PropertyConfigUtil; 13 | import softrpc.framework.zookeeper.message.InvokerRegisterMessage; 14 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Set; 20 | 21 | /** 22 | * @author xctian 23 | * @date 2019/12/18 24 | */ 25 | public class RegisterCenter implements RegisterCenter4Governance, RegisterCenter4Invoker, RegisterCenter4Provider { 26 | 27 | private static final Logger LOGGER = LoggerFactory.getLogger(RegisterCenter.class); 28 | 29 | /** 30 | * 饿汉式单例模式 31 | */ 32 | private static final RegisterCenter INSTANCE = new RegisterCenter(); 33 | 34 | /** 35 | * 缓存的服务地址列表。key:服务的AppName+ServicePath,value:该服务下注册的提供者列表 36 | */ 37 | private static final Map> PROVIDER_MAP = Maps.newConcurrentMap(); 38 | 39 | /** 40 | * 缓存的消费者地址列表。key:服务的AppName+ServicePath,value:该服务下注册的消费者列表 41 | */ 42 | private static final Map> INVOKER_MAP = Maps.newConcurrentMap(); 43 | 44 | /** 45 | * 缓存的服务地址列表。key:服务的AppName+ServicePath,value:该服务下注册的消费者列表 46 | */ 47 | private static Set inovokerNodeListenerSet = Sets.newConcurrentHashSet(); 48 | 49 | /** 50 | * Zookeeper地址 51 | */ 52 | private static final String ZK_SERVICE = PropertyConfigUtil.getZkService(); 53 | 54 | /** 55 | * Zookeeper session超时时间 56 | * Session是指当Client创建一个同Server的连接时产生的会话。连接Connected之后Session状态就开启, 57 | * Zookeeper服务器和Client采用长连接方式(Client会不停地向Server发送心跳)保证session在不出现 58 | * 网络问题、服务器宕机或Client宕机情况下可以一直存在。因此,在正常情况下,session会一直有效, 59 | * 并且ZK集群上所有机器都会保存这个Session信息。 60 | */ 61 | private static final int ZK_SESSION_TIME_OUT = PropertyConfigUtil.getZkSessionTimeout(); 62 | 63 | /** 64 | * Zookeeper 连接超时时间 65 | */ 66 | private static final int ZK_CONNECT_TIME_OUT = PropertyConfigUtil.getZkConnectTimeout(); 67 | 68 | /** 69 | * 服务注册使用的根节点路径 70 | */ 71 | private static final String ROOT_PATH = "/zookeeper/soft-rpc"; 72 | 73 | /** 74 | * 每个服务下标识服务提供者的父节点名字 75 | */ 76 | private static final String PROVIDER_TYPE = "provider"; 77 | 78 | /** 79 | * 每个服务下标识服务消费者的父节点名字 80 | */ 81 | private static final String INVOKER_TYPE = "invoker"; 82 | 83 | /** 84 | * ZK客户端 85 | */ 86 | private static final ZkClient zkClient = new ZkClient(ZK_SERVICE, ZK_SESSION_TIME_OUT, ZK_CONNECT_TIME_OUT, new SerializableSerializer()); 87 | 88 | private RegisterCenter() { 89 | } 90 | 91 | public static RegisterCenter getInstance() { 92 | return INSTANCE; 93 | } 94 | 95 | /** 96 | * 类初始化时注册根路径至zk 97 | */ 98 | static { 99 | boolean exist = zkClient.exists(ROOT_PATH); 100 | if (!exist) { 101 | // 创建永久结点 102 | zkClient.createPersistent(ROOT_PATH, true); 103 | } 104 | } 105 | 106 | /** 107 | * 注册单个invoker 108 | * 109 | * @param invoker 待注册的invoker信息 110 | * @return 该服务下的Provider列表 111 | */ 112 | @Override 113 | public List registerInvoker(InvokerRegisterMessage invoker) { 114 | long startTime = System.currentTimeMillis(); 115 | List providerRegisterMessages = null; 116 | // 创建服务接口的命名空间 117 | final String nameSpace = invoker.getAppName() + "/" + invoker.getServicePath(); 118 | // 对RegisterCenter加锁的原因是避免注册provider的同时注册有其他线程注册invoker,导致不同步 119 | synchronized (RegisterCenter.class) { 120 | // 创建invoker命名空间(持久结点) 121 | String invokerPath = ROOT_PATH + "/" + nameSpace + "/" + INVOKER_TYPE; 122 | boolean exist = zkClient.exists(invokerPath); 123 | if (!exist) { 124 | zkClient.createPersistent(invokerPath, true); 125 | } 126 | // 注册invoker信息结点(临时结点),直接将InvokerRegisterMessage转换为Jason拼接invokerPath后作为结点信息注册至ZK 127 | String invokerNode = invokerPath + "/" + JSON.toJSONString(invoker); 128 | exist = zkClient.exists(invokerNode); 129 | if (!exist) { 130 | zkClient.createEphemeral(invokerNode); 131 | } 132 | // 获取服务结点 133 | final String servicePath = ROOT_PATH + "/" + nameSpace + "/" + PROVIDER_TYPE; 134 | // 若本地providermap没有该key,即该服务没有添加过任何provider,则该接口是第一次添加引用,需要为该接口注册监听器 135 | if (null == PROVIDER_MAP.get(nameSpace)) { 136 | // 为服务注册监听,实现服务自动发现(监听的是servicePath下的子节点) 137 | zkClient.subscribeChildChanges(servicePath, new IZkChildListener() { 138 | /** 139 | * IZKChildListener事件说明针对于下面三个事件触发:新增子节点/减少子节点/删除节点。注意:不监听节点内容的变化 140 | * 141 | * @param parentPath 监听节点全路径 142 | * @param currentChilds 新的子节点列表 143 | * @throws Exception 144 | */ 145 | @Override 146 | public void handleChildChange(String parentPath, List currentChilds) throws Exception { 147 | if (null == currentChilds || currentChilds.size() == 0) { 148 | PROVIDER_MAP.remove(nameSpace); 149 | LOGGER.warn("[{}]节点发生变化,该结点下已无可用服务", parentPath); 150 | return; 151 | } 152 | // 否则将更新后的子节点重新依次加载至PROVIDER_MAP 153 | List newProviderList = Lists.newArrayList(); 154 | for (String each : currentChilds) { 155 | // 反序列化子结点成为ProviderRegisterMessage对象后存List 156 | newProviderList.add(JSON.parseObject(each, ProviderRegisterMessage.class)); 157 | } 158 | // 更新本地缓存PROVIDER_MAP 159 | PROVIDER_MAP.put(nameSpace, newProviderList); 160 | LOGGER.info("[{}]节点发生了变化,重加载该节点下的服务提供者信息如下:", parentPath); 161 | System.out.println(newProviderList); 162 | } 163 | }); 164 | } 165 | // 否则直接获取该服务结点下的所有临时结点,即provider 166 | List providers = zkClient.getChildren(servicePath); 167 | // 将结点内容从json string还原成ProviderRegisterMessage以便存入list 168 | providerRegisterMessages = new ArrayList<>(); 169 | for (String each : providers) { 170 | providerRegisterMessages.add(JSON.parseObject(each, ProviderRegisterMessage.class)); 171 | } 172 | // 注册信息缓存至本地Map 173 | PROVIDER_MAP.put(nameSpace, providerRegisterMessages); 174 | 175 | 176 | long duration = System.currentTimeMillis() - startTime; 177 | LOGGER.info("获取provider列表耗时{}ms:[{}]", duration, nameSpace); 178 | return providerRegisterMessages; 179 | } 180 | } 181 | 182 | /** 183 | * 注册单个Provider 184 | * 185 | * @param provider 需要注册的provider 186 | */ 187 | @Override 188 | public void registerProvider(ProviderRegisterMessage provider) { 189 | long startTime = System.currentTimeMillis(); 190 | // 服务接口命名空间 191 | final String nameSpace = provider.getAppName() + "/" + provider.getServicePath(); 192 | // 对RegisterCenter加锁的原因是避免注册provider的同时注册有其他线程注册invoker,导致不同步 193 | synchronized (RegisterCenter.class) { 194 | // ROOT_PATH/应用名/接口限定名/provider,创建永久节点 195 | String providerPath = ROOT_PATH + "/" + nameSpace + "/" + PROVIDER_TYPE; 196 | if (!zkClient.exists(providerPath)) { 197 | zkClient.createPersistent(providerPath, true); 198 | } 199 | // 注册provider(临时节点),并创建 200 | String serviceNode = providerPath + "/" + JSON.toJSONString(provider); 201 | if (!zkClient.exists(serviceNode)) { 202 | zkClient.createEphemeral(serviceNode); 203 | } 204 | String invokerPath = ROOT_PATH + "/" + nameSpace + "/" + INVOKER_TYPE; 205 | if (!zkClient.exists(invokerPath)) { 206 | zkClient.createPersistent(invokerPath); 207 | } 208 | boolean firstAdd = inovokerNodeListenerSet.add(invokerPath); 209 | if (firstAdd) { 210 | zkClient.subscribeChildChanges(invokerPath, new IZkChildListener() { 211 | @Override 212 | public void handleChildChange(String parentPath, List currentChilds) throws Exception { 213 | if (null == currentChilds || currentChilds.size() == 0) { 214 | INVOKER_MAP.remove(nameSpace); 215 | LOGGER.warn("[{}]节点发生了变化,该服务节点下已无调用者", parentPath); 216 | return; 217 | } 218 | // 反序列化还原invoker,然后更新invoker map 219 | List newInvokerList = new ArrayList<>(); 220 | for (String each : currentChilds) { 221 | newInvokerList.add(JSON.parseObject(each, InvokerRegisterMessage.class)); 222 | } 223 | INVOKER_MAP.put(nameSpace, newInvokerList); 224 | LOGGER.info("[{}]节点发生变化,重新加载该节点下的所有invoker如下:", parentPath); 225 | System.out.println(newInvokerList); 226 | } 227 | }); 228 | } 229 | } 230 | long duration = System.currentTimeMillis() - startTime; 231 | LOGGER.info("注册服务耗时{}ms[服务路径为:/zookeeper/{}/{}]", duration, nameSpace, provider.getRefId()); 232 | 233 | } 234 | 235 | @Override 236 | public Map> getInvokersOfProvider() { 237 | return INVOKER_MAP; 238 | } 239 | 240 | @Override 241 | public Map> getProviderMap() { 242 | return PROVIDER_MAP; 243 | } 244 | 245 | @Override 246 | public Map> getProvidersOfInvoker() { 247 | // TODO 248 | return null; 249 | } 250 | 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/zookeeper/RegisterCenter4Governance.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.zookeeper; 2 | 3 | import softrpc.framework.zookeeper.message.InvokerRegisterMessage; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * 服务治理接口 10 | * 11 | * @author xctian 12 | * @date 2019/12/18 13 | */ 14 | public interface RegisterCenter4Governance { 15 | /** 16 | * 获取当前时刻每一个服务的调用者,key:服务接口全限定名,value:服务调用者列表 17 | * 调用者信息在该项目里暂时只有machineID,唯一标识调用者机器 18 | */ 19 | Map> getInvokersOfProvider(); 20 | 21 | /** 22 | * 获取当前时刻每一个调用者都在使用哪些服务 23 | * key:调用者machineID,value:服务者命名空间(appName+servicePath)列表 24 | */ 25 | Map> getProvidersOfInvoker(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/zookeeper/RegisterCenter4Invoker.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.zookeeper; 2 | 3 | import softrpc.framework.zookeeper.message.InvokerRegisterMessage; 4 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * 消费端注册中心接口 11 | * 12 | * @author xctian 13 | * @date 2019/12/18 14 | */ 15 | public interface RegisterCenter4Invoker { 16 | 17 | /** 18 | * 获取本地服务列表 19 | * key:服务的AppName+ServicePath,value:该服务下注册的提供者列表 20 | */ 21 | Map> getProviderMap(); 22 | 23 | /** 24 | * 注册服务消费者到Zookeeper,返回该服务下的Provider列表 25 | */ 26 | List registerInvoker(InvokerRegisterMessage invokerRegisterMessage); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/zookeeper/RegisterCenter4Provider.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.zookeeper; 2 | 3 | import softrpc.framework.zookeeper.message.ProviderRegisterMessage; 4 | 5 | /** 6 | * 服务端注册中心接口 7 | * 8 | * @author xctian 9 | * @date 2019/12/18 10 | */ 11 | public interface RegisterCenter4Provider { 12 | 13 | /** 14 | * 注册服务到Zookeeper 15 | */ 16 | void registerProvider(ProviderRegisterMessage providerRegisterMessage); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/zookeeper/message/InvokerRegisterMessage.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.zookeeper.message; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * zk的Provider结点类 7 | * 8 | * @author xctian 9 | * @date 2019/12/18 10 | */ 11 | public class InvokerRegisterMessage { 12 | 13 | /** 14 | * 接口所在应用名 15 | */ 16 | private String appName; 17 | /** 18 | * 接口全限定名 19 | */ 20 | private String servicePath; 21 | /** 22 | * 本机机器码,作为本机的唯一标识 23 | */ 24 | private static String invokerMachineID4Client = UUID.randomUUID().toString(); 25 | /** 26 | * 服务分组组名,本项目未用到,默认为default 27 | */ 28 | private String groupName; 29 | /** 30 | * 服务治理时需要统计所有消费者的机器ID,此时用成员变量进行区分(在读结点时用到) 31 | */ 32 | private String invokerMachineID4Server; 33 | 34 | public String getAppName() { 35 | return appName; 36 | } 37 | 38 | public void setAppName(String appName) { 39 | this.appName = appName; 40 | } 41 | 42 | public String getServicePath() { 43 | return servicePath; 44 | } 45 | 46 | public void setServicePath(String servicePath) { 47 | this.servicePath = servicePath; 48 | } 49 | 50 | public static String getInvokerMachineID4Client() { 51 | return invokerMachineID4Client; 52 | } 53 | 54 | public static void setInvokerMachineID4Client(String invokerMachineID4Client) { 55 | InvokerRegisterMessage.invokerMachineID4Client = invokerMachineID4Client; 56 | } 57 | 58 | public String getGroupName() { 59 | return groupName; 60 | } 61 | 62 | public void setGroupName(String groupName) { 63 | this.groupName = groupName; 64 | } 65 | 66 | public String getInvokerMachineID4Server() { 67 | return invokerMachineID4Server; 68 | } 69 | 70 | public void setInvokerMachineID4Server(String invokerMachineID4Server) { 71 | this.invokerMachineID4Server = invokerMachineID4Server; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/softrpc/framework/zookeeper/message/ProviderRegisterMessage.java: -------------------------------------------------------------------------------- 1 | package softrpc.framework.zookeeper.message; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * zk的Provider结点类 7 | * 8 | * @author xctian 9 | * @date 2019/12/18 10 | */ 11 | public class ProviderRegisterMessage implements Serializable{ 12 | 13 | /** 14 | * 接口所在应用名 15 | */ 16 | private String appName; 17 | /** 18 | * 接口全限定名,用appName+servicePath唯一标识一个服务 19 | */ 20 | private String servicePath; 21 | /** 22 | * 接口实现类对应的xml配置中的bean id 23 | */ 24 | private String refId; 25 | /** 26 | * 服务主机地址 27 | */ 28 | private String serverIp; 29 | /** 30 | * 服务端口 31 | */ 32 | private int serverPort; 33 | /** 34 | * 服务端配置超时时间 35 | */ 36 | private long timeout; 37 | /** 38 | * 服务端限流,默认大小是10 39 | */ 40 | private int workThread; 41 | /** 42 | * 服务提供者负载均衡权重:1~100 43 | */ 44 | private int weight; 45 | /** 46 | * 服务分组名,本项目未用到,默认default 47 | */ 48 | private String groupName; 49 | 50 | public String getAppName() { 51 | return appName; 52 | } 53 | 54 | public void setAppName(String appName) { 55 | this.appName = appName; 56 | } 57 | 58 | public String getServicePath() { 59 | return servicePath; 60 | } 61 | 62 | public void setServicePath(String servicePath) { 63 | this.servicePath = servicePath; 64 | } 65 | 66 | public String getRefId() { 67 | return refId; 68 | } 69 | 70 | public void setRefId(String refId) { 71 | this.refId = refId; 72 | } 73 | 74 | public String getServerIp() { 75 | return serverIp; 76 | } 77 | 78 | public void setServerIp(String serverIp) { 79 | this.serverIp = serverIp; 80 | } 81 | 82 | public int getServerPort() { 83 | return serverPort; 84 | } 85 | 86 | public void setServerPort(int serverPort) { 87 | this.serverPort = serverPort; 88 | } 89 | 90 | public long getTimeout() { 91 | return timeout; 92 | } 93 | 94 | public void setTimeout(long timeout) { 95 | this.timeout = timeout; 96 | } 97 | 98 | public int getWorkThread() { 99 | return workThread; 100 | } 101 | 102 | public void setWorkThread(int workThread) { 103 | this.workThread = workThread; 104 | } 105 | 106 | public int getWeight() { 107 | return weight; 108 | } 109 | 110 | public void setWeight(int weight) { 111 | this.weight = weight; 112 | } 113 | 114 | public String getGroupName() { 115 | return groupName; 116 | } 117 | 118 | public void setGroupName(String groupName) { 119 | this.groupName = groupName; 120 | } 121 | 122 | @Override 123 | public String toString() { 124 | return "ProviderRegisterMessage{" + 125 | "appName='" + appName + '\'' + 126 | ", servicePath='" + servicePath + '\'' + 127 | ", refId='" + refId + '\'' + 128 | ", serverIp='" + serverIp + '\'' + 129 | ", serverPort=" + serverPort + 130 | ", timeout=" + timeout + 131 | ", workThread=" + workThread + 132 | ", weight=" + weight + 133 | ", groupName='" + groupName + '\'' + 134 | '}'; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/soft-reference.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/soft-service.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.handlers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/src/main/resources/META-INF/spring.handlers -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.schemas: -------------------------------------------------------------------------------- 1 | http\://www.soft-rpc.com/schema/soft-reference.xsd=META-INF/soft-reference.xsd 2 | http\://www.soft-rpc.com/schema/soft-service.xsd=META-INF/soft-service.xsd 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/src/main/resources/log4j.properties -------------------------------------------------------------------------------- /src/main/resources/rpc-reference.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/rpc-service.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | 24 | 33 | 34 | 35 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/soft-rpc.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcNew/soft-RPC/e5c6951704f71306efaee1a3ef365e1dcfad8ace/src/main/resources/soft-rpc.properties --------------------------------------------------------------------------------