├── .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 | ![https://travis-ci.org/classtag/MiniRpc](https://img.shields.io/travis/classtag/MiniRpc.svg) 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 out) throws Exception { 20 | if (in.readableBytes() < 4) { 21 | return; 22 | } 23 | in.markReaderIndex(); 24 | int dataLength = in.readInt(); 25 | if (dataLength < 0) { 26 | ctx.close(); 27 | } 28 | if (in.readableBytes() < dataLength) { 29 | in.resetReaderIndex(); 30 | } 31 | byte[] data = new byte[dataLength]; 32 | in.readBytes(data); 33 | 34 | Object obj = SerializationUtil.deserialize(data, genericClass); 35 | out.add(obj); 36 | } 37 | 38 | public static class RpcEncoder extends MessageToByteEncoder { 39 | 40 | private Class genericClass; 41 | 42 | public RpcEncoder(Class genericClass) { 43 | this.genericClass = genericClass; 44 | } 45 | 46 | @Override 47 | public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception { 48 | if (genericClass.isInstance(in)) { 49 | byte[] data = SerializationUtil.serialize(in); 50 | out.writeInt(data.length); 51 | out.writeBytes(data); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/me/anduo/rpc/common/RpcEncoder.java: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2015 meituan 2 | // All rights reserved 3 | package me.anduo.rpc.common; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToByteEncoder; 8 | 9 | /** 10 | * Summary: RPC加密 11 | * Author : anduo@qq.com 12 | * Version: 1.0 13 | * Date : 15/4/26 14 | * time : 21:51 15 | */ 16 | public class RpcEncoder extends MessageToByteEncoder { 17 | 18 | private Class genericClass; 19 | 20 | public RpcEncoder(Class genericClass) { 21 | this.genericClass = genericClass; 22 | } 23 | 24 | @Override 25 | public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception { 26 | if (genericClass.isInstance(in)) { 27 | byte[] data = SerializationUtil.serialize(in); 28 | out.writeInt(data.length); 29 | out.writeBytes(data); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /rpc-common/src/main/java/me/anduo/rpc/common/SerializationUtil.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.common; 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 | * @author duoan 15 | */ 16 | public class SerializationUtil { 17 | private static Map, Schema> cachedSchema = new ConcurrentHashMap<>(); 18 | 19 | private static Objenesis objenesis = new ObjenesisStd(true); 20 | 21 | private SerializationUtil() { 22 | } 23 | 24 | @SuppressWarnings("unchecked") 25 | private static Schema getSchema(Class cls) { 26 | Schema schema = (Schema) cachedSchema.get(cls); 27 | if (schema == null) { 28 | schema = RuntimeSchema.createFrom(cls); 29 | if (schema != null) { 30 | cachedSchema.put(cls, schema); 31 | } 32 | } 33 | return schema; 34 | } 35 | 36 | @SuppressWarnings("unchecked") 37 | public static byte[] serialize(T obj) { 38 | Class cls = (Class) obj.getClass(); 39 | LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 40 | try { 41 | Schema schema = getSchema(cls); 42 | return ProtostuffIOUtil.toByteArray(obj, schema, buffer); 43 | } catch (Exception e) { 44 | throw new IllegalStateException(e.getMessage(), e); 45 | } finally { 46 | buffer.clear(); 47 | } 48 | } 49 | 50 | public static T deserialize(byte[] data, Class cls) { 51 | try { 52 | T message = (T) objenesis.newInstance(cls); 53 | Schema schema = getSchema(cls); 54 | ProtostuffIOUtil.mergeFrom(data, message, schema); 55 | return message; 56 | } catch (Exception e) { 57 | throw new IllegalStateException(e.getMessage(), e); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rpc-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | rpc 7 | me.anduo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | rpc-example 13 | 14 | 15 | 16 | junit 17 | junit 18 | compile 19 | 20 | 21 | org.springframework 22 | spring-test 23 | compile 24 | 25 | 26 | org.springframework 27 | spring-context 28 | 29 | 30 | 31 | me.anduo 32 | rpc-server 33 | 0.0.1-SNAPSHOT 34 | 35 | 36 | 37 | me.anduo 38 | rpc-client 39 | 0.0.1-SNAPSHOT 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /rpc-example/src/main/java/me/anduo/rpc/example/HelloServiceTest.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.example; 2 | 3 | 4 | import me.anduo.rpc.client.RpcProxy; 5 | import me.anduo.rpc.server.modules.sample.HelloService; 6 | import org.junit.Assert; 7 | import org.springframework.context.support.ClassPathXmlApplicationContext; 8 | 9 | public class HelloServiceTest { 10 | 11 | private RpcProxy rpcProxy; 12 | 13 | public static void main(String[] args) { 14 | ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-client.xml"); 15 | HelloServiceTest test = new HelloServiceTest(); 16 | test.rpcProxy = ctx.getBean(RpcProxy.class); 17 | test.helloTest(); 18 | } 19 | 20 | private void helloTest() { 21 | try { 22 | HelloService helloService = rpcProxy.create(HelloService.class); 23 | String result = helloService.hello("World"); 24 | Assert.assertEquals("Hello! World", result); 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /rpc-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | rpc 7 | me.anduo 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | rpc-server 13 | 14 | 15 | 16 | me.anduo 17 | rpc-client 18 | 0.0.1-SNAPSHOT 19 | 20 | 21 | me.anduo 22 | rpc-common 23 | 0.0.1-SNAPSHOT 24 | 25 | 26 | 27 | junit 28 | junit 29 | 30 | 31 | 32 | 33 | org.slf4j 34 | slf4j-log4j12 35 | 36 | 37 | 38 | 39 | org.springframework 40 | spring-context 41 | 42 | 43 | org.springframework 44 | spring-test 45 | 46 | 47 | 48 | 49 | io.netty 50 | netty-all 51 | 52 | 53 | 54 | 55 | com.dyuproject.protostuff 56 | protostuff-core 57 | 58 | 59 | com.dyuproject.protostuff 60 | protostuff-runtime 61 | 62 | 63 | 64 | 65 | org.apache.zookeeper 66 | zookeeper 67 | 68 | 69 | 70 | 71 | org.apache.commons 72 | commons-collections4 73 | 74 | 75 | 76 | 77 | org.objenesis 78 | objenesis 79 | 80 | 81 | 82 | 83 | cglib 84 | cglib 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/me/anduo/rpc/server/core/RpcBootstrap.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.server.core; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | /** 6 | * RPC服务启动入口 7 | * @author duoan 8 | */ 9 | public class RpcBootstrap { 10 | @SuppressWarnings("resource") 11 | public static void main(String[] args) { 12 | new ClassPathXmlApplicationContext("spring-server.xml"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/me/anduo/rpc/server/core/RpcHandler.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.server.core; 2 | 3 | import io.netty.channel.ChannelFutureListener; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | 7 | import java.util.Map; 8 | 9 | import net.sf.cglib.reflect.FastClass; 10 | import net.sf.cglib.reflect.FastMethod; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import me.anduo.rpc.client.RpcRequest; 15 | import me.anduo.rpc.client.RpcResponse; 16 | 17 | /** 18 | * 19 | * @author duoan 20 | */ 21 | public class RpcHandler extends SimpleChannelInboundHandler { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcHandler.class); 24 | 25 | private final Map handlerMap; 26 | 27 | public RpcHandler(Map handlerMap) { 28 | this.handlerMap = handlerMap; 29 | } 30 | 31 | @Override 32 | public void channelRead0(final ChannelHandlerContext ctx, RpcRequest request) throws Exception { 33 | RpcResponse response = new RpcResponse(); 34 | response.setRequestId(request.getRequestId()); 35 | try { 36 | Object result = handle(request); 37 | response.setResult(result); 38 | } catch (Throwable t) { 39 | response.setError(t); 40 | } 41 | ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 42 | } 43 | 44 | private Object handle(RpcRequest request) throws Throwable { 45 | String className = request.getClassName(); 46 | Object serviceBean = handlerMap.get(className); 47 | 48 | Class serviceClass = serviceBean.getClass(); 49 | String methodName = request.getMethodName(); 50 | Class[] parameterTypes = request.getParameterTypes(); 51 | Object[] parameters = request.getParameters(); 52 | 53 | FastClass serviceFastClass = FastClass.create(serviceClass); 54 | FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes); 55 | return serviceFastMethod.invoke(serviceBean, parameters); 56 | } 57 | 58 | @Override 59 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 60 | LOGGER.error("server caught exception", cause); 61 | ctx.close(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/me/anduo/rpc/server/core/RpcServer.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.server.core; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 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 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import org.apache.commons.collections4.MapUtils; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.beans.BeansException; 19 | import org.springframework.beans.factory.InitializingBean; 20 | import org.springframework.context.ApplicationContext; 21 | import org.springframework.context.ApplicationContextAware; 22 | 23 | import me.anduo.rpc.client.RpcRequest; 24 | import me.anduo.rpc.client.RpcResponse; 25 | import me.anduo.rpc.common.RpcDecoder; 26 | 27 | /** 28 | * RPC服务实例。 29 | * 30 | * @author duoan 31 | */ 32 | public class RpcServer implements ApplicationContextAware, InitializingBean { 33 | 34 | private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class); 35 | 36 | 37 | private String serverAddress; 38 | private ServiceRegistry serviceRegistry; 39 | 40 | /** 41 | * 存放接口名与服务对象之间的映射关系 42 | */ 43 | private Map handlerMap = new HashMap<>(); 44 | 45 | public RpcServer(String serverAddress) { 46 | this.serverAddress = serverAddress; 47 | } 48 | 49 | public RpcServer(String serverAddress, ServiceRegistry serviceRegistry) { 50 | this.serverAddress = serverAddress; 51 | this.serviceRegistry = serviceRegistry; 52 | } 53 | 54 | @Override 55 | public void setApplicationContext(ApplicationContext ctx) throws BeansException { 56 | // 获取所有带有RpcService注解的SpringBean 57 | Map serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class); 58 | if (MapUtils.isNotEmpty(serviceBeanMap)) { 59 | for (Object serviceBean : serviceBeanMap.values()) { 60 | String interfaceName = serviceBean.getClass().getAnnotation(RpcService.class).value().getName(); 61 | handlerMap.put(interfaceName, serviceBean); 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public void afterPropertiesSet() throws Exception { 68 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 69 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 70 | try { 71 | ServerBootstrap bootstrap = new ServerBootstrap(); 72 | bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) 73 | .childHandler(new ChannelInitializer() { 74 | @Override 75 | public void initChannel(SocketChannel channel) { 76 | channel.pipeline().addLast( 77 | /** 将RPC请求进行解码(为了处理请求)*/ 78 | new RpcDecoder(RpcRequest.class)) 79 | /** 将RPC响应进行编码(为了返回响应)*/ 80 | .addLast(new RpcDecoder(RpcResponse.class)) 81 | /** 处理RPC请求*/ 82 | .addLast(new RpcHandler(handlerMap)); 83 | } 84 | }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true); 85 | 86 | String[] array = serverAddress.split(":"); 87 | String host = array[0]; 88 | int port = Integer.parseInt(array[1]); 89 | 90 | ChannelFuture future = bootstrap.bind(host, port).sync(); 91 | LOGGER.debug("server started on port {}", port); 92 | 93 | if (serviceRegistry != null) { 94 | serviceRegistry.register(serverAddress); // 注册服务地址 95 | } 96 | 97 | future.channel().closeFuture().sync(); 98 | } finally { 99 | workerGroup.shutdownGracefully(); 100 | bossGroup.shutdownGracefully(); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/me/anduo/rpc/server/core/RpcService.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.server.core; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | @Target({ ElementType.TYPE }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Component 13 | // 表明可被 Spring 扫描 14 | public @interface RpcService { 15 | 16 | Class value(); 17 | } 18 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/me/anduo/rpc/server/core/ServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.server.core; 2 | 3 | import java.io.IOException; 4 | import java.util.concurrent.CountDownLatch; 5 | 6 | import org.apache.zookeeper.CreateMode; 7 | import org.apache.zookeeper.KeeperException; 8 | import org.apache.zookeeper.WatchedEvent; 9 | import org.apache.zookeeper.Watcher; 10 | import org.apache.zookeeper.ZooDefs; 11 | import org.apache.zookeeper.ZooKeeper; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import me.anduo.rpc.common.Constant; 15 | 16 | 17 | /** 18 | * 提供服务注册到分布式服务发现系统。这里使用的zk实现。 19 | * 可以扩展的地方是client的zk缓存和订阅通知,还有client端的服务路由方式。 20 | * 21 | * @author duoan 22 | */ 23 | public class ServiceRegistry { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistry.class); 26 | 27 | /** 28 | * 29 | */ 30 | private CountDownLatch latch = new CountDownLatch(1); 31 | 32 | private String registryAddress; 33 | 34 | public ServiceRegistry(String registryAddress) { 35 | this.registryAddress = registryAddress; 36 | } 37 | 38 | public void register(String data) { 39 | if (data != null) { 40 | ZooKeeper zk = connectServer(); 41 | if (zk != null) { 42 | createNode(zk, data); 43 | } 44 | } 45 | } 46 | 47 | private ZooKeeper connectServer() { 48 | ZooKeeper zk = null; 49 | try { 50 | zk = new ZooKeeper(registryAddress, Constant.ZK_SESSION_TIMEOUT, event -> { 51 | if (event.getState() == Watcher.Event.KeeperState.SyncConnected) { 52 | latch.countDown(); 53 | } 54 | }); 55 | latch.await(); 56 | } catch (IOException | InterruptedException e) { 57 | LOGGER.error("Can't get connection for zookeeper,registryAddress:{}", registryAddress, e); 58 | } 59 | return zk; 60 | } 61 | 62 | private void createNode(ZooKeeper zk, String data) { 63 | try { 64 | byte[] bytes = data.getBytes(); 65 | String path = zk.create(Constant.ZK_DATA_PATH, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); 66 | LOGGER.debug("create zookeeper node ({} => {})", path, data); 67 | } catch (KeeperException | InterruptedException e) { 68 | LOGGER.error("", e); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/me/anduo/rpc/server/modules/sample/HelloService.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.server.modules.sample; 2 | 3 | public interface HelloService { 4 | 5 | String hello(String name); 6 | } 7 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/me/anduo/rpc/server/modules/sample/impl/HelloServiceImpl.java: -------------------------------------------------------------------------------- 1 | package me.anduo.rpc.server.modules.sample.impl; 2 | 3 | import me.anduo.rpc.server.core.RpcService; 4 | import me.anduo.rpc.server.modules.sample.HelloService; 5 | 6 | // 指定远程接口 7 | @RpcService(HelloService.class) 8 | public class HelloServiceImpl implements HelloService { 9 | 10 | @Override 11 | public String hello(String name) { 12 | return "Hello! " + name; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /rpc-server/src/main/resources/server-config.properties: -------------------------------------------------------------------------------- 1 | # ZooKeeper \u670d\u52a1\u5668 2 | registry.address=127.0.0.1:2181 3 | # RPC \u670d\u52a1\u5668 4 | server.address=127.0.0.1:8000 -------------------------------------------------------------------------------- /rpc-server/src/main/resources/spring-server.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------