├── .gitignore
├── .travis.yml
├── README.md
├── pom.xml
├── rpc-client
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── me
│ │ └── anduo
│ │ └── rpc
│ │ └── client
│ │ ├── RpcClient.java
│ │ ├── RpcProxy.java
│ │ ├── RpcRequest.java
│ │ ├── RpcResponse.java
│ │ └── ServiceDiscovery.java
│ └── resources
│ ├── client-config.properties
│ └── spring-client.xml
├── rpc-common
├── pom.xml
└── src
│ └── main
│ └── java
│ └── me
│ └── anduo
│ └── rpc
│ └── common
│ ├── Constant.java
│ ├── RpcDecoder.java
│ ├── RpcEncoder.java
│ └── SerializationUtil.java
├── rpc-example
├── pom.xml
└── src
│ └── main
│ └── java
│ └── me
│ └── anduo
│ └── rpc
│ └── example
│ └── HelloServiceTest.java
└── rpc-server
├── pom.xml
└── src
└── main
├── java
└── me
│ └── anduo
│ └── rpc
│ └── server
│ ├── core
│ ├── RpcBootstrap.java
│ ├── RpcHandler.java
│ ├── RpcServer.java
│ ├── RpcService.java
│ └── ServiceRegistry.java
│ └── modules
│ └── sample
│ ├── HelloService.java
│ └── impl
│ └── HelloServiceImpl.java
└── resources
├── server-config.properties
└── spring-server.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Java template
3 | *.class
4 | *.DS_Store
5 |
6 | # Mobile Tools for Java (J2ME)
7 | .mtj.tmp/
8 |
9 | # Package Files #
10 | *.jar
11 | *.war
12 | *.ear
13 |
14 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
15 | hs_err_pid*
16 |
17 |
18 | ### Example user template template
19 | ### Example user template
20 |
21 | # IntelliJ project files
22 | .idea
23 | *.iml
24 | out
25 | gen
26 |
27 | ### Maven template
28 | target/
29 | pom.xml.tag
30 | pom.xml.releaseBackup
31 | pom.xml.versionsBackup
32 | pom.xml.next
33 | release.properties
34 | dependency-reduced-pom.xml
35 | buildNumber.properties
36 | .mvn/timing.properties
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A Minimal RPC Implement with Java.
2 | ===========
3 | 
4 |
5 | Spring + Netty + Protostuff + ZooKeeper 实现了一个轻量级 RPC 框架,使用 Spring 提供依赖注入与参数配置,使用 Netty 实现 NIO 方式的数据传输,使用 Protostuff 实现对象序列化,使用 ZooKeeper 实现服务注册与发现。使用该框架,可将服务部署到分布式环境中的任意节点上,客户端通过远程接口来调用服务端的具体实现,让服务端与客户端的开发完全分离,为实现大规模分布式应用提供了基础支持。
6 |
7 |
8 | ## 如何测试
9 |
10 | 0. 环境准备:maven, intellij-idea
11 | 1. 下载安装并启动zookeeper https://www.apache.org/dyn/closer.cgi/zookeeper/
12 | 2. 启动服务端 `me.anduo.rpc.server.core.RpcBootstrap`
13 | 3. 启动测试 `me.anduo.rpc.example.HelloServiceTest`
14 |
15 |
16 | ## TODO LIST
17 | - [ ] add metrics.
18 | - [ ] support client pool
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | me.anduo
5 | rpc
6 | pom
7 | 0.0.1-SNAPSHOT
8 |
9 | rpc-client
10 | rpc-server
11 | rpc-common
12 | rpc-example
13 |
14 |
15 | 3.2.12.RELEASE
16 | 4.1.42.Final
17 | 1.0.8
18 |
19 | scratch-rpc
20 |
21 |
22 |
23 |
24 |
25 | junit
26 | junit
27 | 4.13.1
28 | test
29 |
30 |
31 |
32 |
33 | org.slf4j
34 | slf4j-log4j12
35 | 1.7.7
36 |
37 |
38 |
39 |
40 | org.springframework
41 | spring-context
42 | ${spring.version}
43 |
44 |
45 | org.springframework
46 | spring-test
47 | ${spring.version}
48 | test
49 |
50 |
51 |
52 |
53 | io.netty
54 | netty-all
55 | ${netty.version}
56 |
57 |
58 |
59 |
60 | com.dyuproject.protostuff
61 | protostuff-core
62 | ${protostuff.version}
63 |
64 |
65 | com.dyuproject.protostuff
66 | protostuff-runtime
67 | ${protostuff.version}
68 |
69 |
70 |
71 |
72 | org.apache.zookeeper
73 | zookeeper
74 | 3.4.14
75 |
76 |
77 |
78 |
79 | org.apache.commons
80 | commons-collections4
81 | 4.1
82 |
83 |
84 |
85 |
86 | org.objenesis
87 | objenesis
88 | 2.1
89 |
90 |
91 |
92 |
93 | cglib
94 | cglib
95 | 3.1
96 |
97 |
98 |
99 |
100 |
101 |
102 | rpc
103 |
104 |
105 | org.apache.maven.plugins
106 | maven-compiler-plugin
107 |
108 | 1.8
109 | 1.8
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/rpc-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | rpc
7 | me.anduo
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 | rpc-client
12 |
13 |
14 |
15 | me.anduo
16 | rpc-common
17 | 0.0.1-SNAPSHOT
18 |
19 |
20 |
21 | org.slf4j
22 | slf4j-log4j12
23 |
24 |
25 |
26 |
27 | org.springframework
28 | spring-context
29 |
30 |
31 |
32 | io.netty
33 | netty-all
34 |
35 |
36 |
37 |
38 | com.dyuproject.protostuff
39 | protostuff-core
40 |
41 |
42 | com.dyuproject.protostuff
43 | protostuff-runtime
44 |
45 |
46 |
47 |
48 | org.apache.zookeeper
49 | zookeeper
50 |
51 |
52 |
53 |
54 | org.apache.commons
55 | commons-collections4
56 |
57 |
58 |
59 |
60 | org.objenesis
61 | objenesis
62 |
63 |
64 |
65 |
66 | cglib
67 | cglib
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/rpc-client/src/main/java/me/anduo/rpc/client/RpcClient.java:
--------------------------------------------------------------------------------
1 | package me.anduo.rpc.client;
2 |
3 | import io.netty.bootstrap.Bootstrap;
4 | import io.netty.channel.ChannelFuture;
5 | import io.netty.channel.ChannelHandlerContext;
6 | import io.netty.channel.ChannelInitializer;
7 | import io.netty.channel.ChannelOption;
8 | import io.netty.channel.EventLoopGroup;
9 | import io.netty.channel.SimpleChannelInboundHandler;
10 | import io.netty.channel.nio.NioEventLoopGroup;
11 | import io.netty.channel.socket.SocketChannel;
12 | import io.netty.channel.socket.nio.NioSocketChannel;
13 |
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 | import me.anduo.rpc.common.RpcDecoder;
17 | import me.anduo.rpc.common.RpcEncoder;
18 |
19 |
20 | public class RpcClient extends SimpleChannelInboundHandler {
21 |
22 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcClient.class);
23 |
24 | private String host;
25 | private int port;
26 | private int timeout = 1000;
27 |
28 | private RpcResponse response;
29 |
30 | private final Object obj = new Object();
31 |
32 | public RpcClient(String host, int port) {
33 | this.host = host;
34 | this.port = port;
35 | }
36 |
37 | @Override
38 | public void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
39 | this.response = response;
40 | synchronized (obj) {
41 | obj.notifyAll(); // 收到响应,唤醒线程
42 | }
43 | }
44 |
45 | @Override
46 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
47 | LOGGER.error("client caught exception", cause);
48 | ctx.close();
49 | }
50 |
51 | public RpcResponse send(RpcRequest request) throws Exception {
52 | EventLoopGroup group = new NioEventLoopGroup();
53 | try {
54 | Bootstrap bootstrap = new Bootstrap();
55 | bootstrap.group(group)
56 | .channel(NioSocketChannel.class)
57 | .handler(new ChannelInitializer() {
58 | @Override
59 | public void initChannel(SocketChannel channel) throws Exception {
60 | channel.pipeline().addLast(new RpcEncoder(RpcRequest.class)) // 将 RPC 请求进行编码(为了发送请求)
61 | .addLast(new RpcDecoder(RpcResponse.class)) // 将 RPC 响应进行解码(为了处理响应)
62 | .addLast(RpcClient.this); // 使用 RpcClient 发送 RPC 请求
63 | }
64 | })
65 | .option(ChannelOption.SO_TIMEOUT, timeout)
66 | .option(ChannelOption.SO_KEEPALIVE, true);
67 |
68 |
69 | ChannelFuture future = bootstrap.connect(host, port).sync();
70 | future.channel().writeAndFlush(request).sync();
71 |
72 | synchronized (obj) {
73 | obj.wait(); // 未收到响应,使线程等待
74 | }
75 |
76 | if (response != null) {
77 | future.channel().closeFuture().sync();
78 | }
79 | return response;
80 | } finally {
81 | group.shutdownGracefully();
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/rpc-client/src/main/java/me/anduo/rpc/client/RpcProxy.java:
--------------------------------------------------------------------------------
1 | package me.anduo.rpc.client;
2 |
3 | import net.sf.cglib.proxy.Proxy;
4 |
5 | import java.util.UUID;
6 |
7 | public class RpcProxy {
8 | private String serverAddress;
9 | private ServiceDiscovery serviceDiscovery;
10 |
11 | public RpcProxy(String serverAddress) {
12 | this.serverAddress = serverAddress;
13 | }
14 |
15 | public RpcProxy(ServiceDiscovery serviceDiscovery) {
16 | this.serviceDiscovery = serviceDiscovery;
17 | }
18 |
19 | @SuppressWarnings("unchecked")
20 | public T create(Class> interfaceClass) {
21 | return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class>[]{interfaceClass},
22 | (proxy, method, args) -> {
23 | RpcRequest request = new RpcRequest(); // 创建并初始化 RPC 请求
24 | request.setRequestId(UUID.randomUUID().toString());
25 | request.setClassName(method.getDeclaringClass().getName());
26 | request.setMethodName(method.getName());
27 | request.setParameterTypes(method.getParameterTypes());
28 | request.setParameters(args);
29 |
30 | if (serviceDiscovery != null) {
31 | serverAddress = serviceDiscovery.discover(); // 发现服务
32 | }
33 | String[] array = serverAddress.split(":");
34 | String host = array[0];
35 | int port = Integer.parseInt(array[1]);
36 |
37 | RpcClient client = new RpcClient(host, port); // 初始化 RPC 客户端
38 | RpcResponse response = client.send(request); // 通过 RPC客户端发送RPC请求并获取RPC响应
39 | if (response.isError()) {
40 | throw response.getError();
41 | } else {
42 | return response.getResult();
43 | }
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/rpc-client/src/main/java/me/anduo/rpc/client/RpcRequest.java:
--------------------------------------------------------------------------------
1 | package me.anduo.rpc.client;
2 |
3 | /**
4 | * 系统内部RPC请求封装,用来将本地普通的java函数调用包装为远程的服务接口调用。
5 | * @author duoan
6 | */
7 | public class RpcRequest {
8 | private String requestId;
9 | private String className;
10 | private String methodName;
11 | private Class>[] parameterTypes;
12 | private Object[] parameters;
13 |
14 | public String getRequestId() {
15 | return requestId;
16 | }
17 |
18 | public void setRequestId(String requestId) {
19 | this.requestId = requestId;
20 | }
21 |
22 | public String getClassName() {
23 | return className;
24 | }
25 |
26 | public void setClassName(String className) {
27 | this.className = className;
28 | }
29 |
30 | public String getMethodName() {
31 | return methodName;
32 | }
33 |
34 | public void setMethodName(String methodName) {
35 | this.methodName = methodName;
36 | }
37 |
38 | public Class>[] getParameterTypes() {
39 | return parameterTypes;
40 | }
41 |
42 | public void setParameterTypes(Class>[] parameterTypes) {
43 | this.parameterTypes = parameterTypes;
44 | }
45 |
46 | public Object[] getParameters() {
47 | return parameters;
48 | }
49 |
50 | public void setParameters(Object[] parameters) {
51 | this.parameters = parameters;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/rpc-client/src/main/java/me/anduo/rpc/client/RpcResponse.java:
--------------------------------------------------------------------------------
1 | package me.anduo.rpc.client;
2 |
3 | public class RpcResponse {
4 | private String requestId;
5 | private Throwable error;
6 | private Object result;
7 |
8 | public String getRequestId() {
9 | return requestId;
10 | }
11 |
12 | public void setRequestId(String requestId) {
13 | this.requestId = requestId;
14 | }
15 |
16 | public boolean isError(){
17 | return error == null;
18 | }
19 |
20 | public Throwable getError() {
21 | return error;
22 | }
23 |
24 | public void setError(Throwable error) {
25 | this.error = error;
26 | }
27 |
28 | public Object getResult() {
29 | return result;
30 | }
31 |
32 | public void setResult(Object result) {
33 | this.result = result;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/rpc-client/src/main/java/me/anduo/rpc/client/ServiceDiscovery.java:
--------------------------------------------------------------------------------
1 | package me.anduo.rpc.client;
2 |
3 | import io.netty.util.internal.ThreadLocalRandom;
4 |
5 | import java.io.IOException;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.concurrent.CountDownLatch;
9 |
10 | import org.apache.zookeeper.KeeperException;
11 | import org.apache.zookeeper.WatchedEvent;
12 | import org.apache.zookeeper.Watcher;
13 | import org.apache.zookeeper.ZooKeeper;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 | import me.anduo.rpc.common.Constant;
17 |
18 |
19 | public class ServiceDiscovery {
20 | private static final Logger LOGGER = LoggerFactory.getLogger(ServiceDiscovery.class);
21 |
22 | private CountDownLatch latch = new CountDownLatch(1);
23 |
24 | private volatile List dataList = new ArrayList();
25 |
26 | private String registryAddress;
27 |
28 | public ServiceDiscovery(String registryAddress) {
29 | this.registryAddress = registryAddress;
30 |
31 | ZooKeeper zk = connectServer();
32 | if (zk != null) {
33 | watchNode(zk);
34 | }
35 | }
36 |
37 | public String discover() {
38 | String data = null;
39 | int size = dataList.size();
40 | if (size > 0) {
41 | if (size == 1) {
42 | data = dataList.get(0);
43 | LOGGER.debug("using only data: {}", data);
44 | } else {
45 | data = dataList.get(ThreadLocalRandom.current().nextInt(size));
46 | LOGGER.debug("using random data: {}", data);
47 | }
48 | }
49 | return data;
50 | }
51 |
52 | private ZooKeeper connectServer() {
53 | ZooKeeper zk = null;
54 | try {
55 | zk = new ZooKeeper(registryAddress, Constant.ZK_SESSION_TIMEOUT, new Watcher() {
56 | @Override
57 | public void process(WatchedEvent event) {
58 | if (event.getState() == Event.KeeperState.SyncConnected) {
59 | latch.countDown();
60 | }
61 | }
62 | });
63 | latch.await();
64 | } catch (IOException | InterruptedException e) {
65 | LOGGER.error("", e);
66 | }
67 | return zk;
68 | }
69 |
70 | private void watchNode(final ZooKeeper zk) {
71 | try {
72 | List nodeList = zk.getChildren(Constant.ZK_REGISTRY_PATH, new Watcher() {
73 | @Override
74 | public void process(WatchedEvent event) {
75 | if (event.getType() == Event.EventType.NodeChildrenChanged) {
76 | watchNode(zk);
77 | }
78 | }
79 | });
80 | List dataList = new ArrayList<>();
81 | for (String node : nodeList) {
82 | byte[] bytes = zk.getData(Constant.ZK_REGISTRY_PATH + "/" + node, false, null);
83 | dataList.add(new String(bytes));
84 | }
85 | LOGGER.debug("node data: {}", dataList);
86 | this.dataList = dataList;
87 | } catch (KeeperException | InterruptedException e) {
88 | LOGGER.error("", e);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/rpc-client/src/main/resources/client-config.properties:
--------------------------------------------------------------------------------
1 | # ZooKeeper \u670d\u52a1\u5668
2 | registry.address=127.0.0.1:2181
--------------------------------------------------------------------------------
/rpc-client/src/main/resources/spring-client.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/rpc-common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | rpc
7 | me.anduo
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 |
13 | rpc-common
14 |
15 |
16 |
17 | org.slf4j
18 | slf4j-log4j12
19 |
20 |
21 |
22 | io.netty
23 | netty-all
24 |
25 |
26 |
27 | com.dyuproject.protostuff
28 | protostuff-core
29 |
30 |
31 | com.dyuproject.protostuff
32 | protostuff-runtime
33 |
34 |
35 |
36 | org.apache.zookeeper
37 | zookeeper
38 |
39 |
40 | org.objenesis
41 | objenesis
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/rpc-common/src/main/java/me/anduo/rpc/common/Constant.java:
--------------------------------------------------------------------------------
1 | package me.anduo.rpc.common;
2 |
3 | /**
4 | * @author duoan
5 | */
6 | public interface Constant {
7 | int ZK_SESSION_TIMEOUT = 5000;
8 | /**
9 | * 服务注册ZK根节点
10 | */
11 | String ZK_REGISTRY_PATH = "/registry";
12 | /**
13 | * 服务数据存储ZK根节点
14 | */
15 | String ZK_DATA_PATH = ZK_REGISTRY_PATH + "/data";
16 | }
17 |
--------------------------------------------------------------------------------
/rpc-common/src/main/java/me/anduo/rpc/common/RpcDecoder.java:
--------------------------------------------------------------------------------
1 | package me.anduo.rpc.common;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.handler.codec.ByteToMessageDecoder;
6 | import io.netty.handler.codec.MessageToByteEncoder;
7 |
8 | import java.util.List;
9 |
10 | public class RpcDecoder extends ByteToMessageDecoder {
11 |
12 | private Class> genericClass;
13 |
14 | public RpcDecoder(Class> genericClass) {
15 | this.genericClass = genericClass;
16 | }
17 |
18 | @Override
19 | public final void decode(ChannelHandlerContext ctx, ByteBuf in, List