使用netty+zookeeper+protobuf实现一个RPC过程
来源:互联网 发布:删除数据库sql 编辑:程序博客网 时间:2024/06/05 15:53
上次实现了一个基于java序列化和阻塞IO模型的RPC过程,效率很低,这次换用NIO来实现。代码有点多,尽量写清楚一点。
这是maven的版本依赖,先放在前面,接下来就可以复制了。。。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- SLF4J --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency> <!-- Netty --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha1</version> </dependency> <!-- protostuff --> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.6.0</version> </dependency> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.6.0</version> </dependency> <!-- ZooKeeper --> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.6</version> </dependency> <!-- Apache Commons Collections --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> <!-- Objenesis --> <dependency> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> <version>2.1</version> </dependency> <!-- CGLib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency>
写完的目录结构:
好,先从两个服务说起:加减法的计算
这是接口:
public interface IDiff { double diff(double a,double b);}public interface ISum { public int sum(int a, int b);}public class DiffImpl implements IDiff { @Override public double diff(double a, double b) { return a - b; }}public class SumImpl implements ISum { public int sum(int a, int b) { return a + b; }}
1.服务器端使用zookeeper动态注册服务节点:
服务注册代码,这里有个注意的地方就是:CreateMode.EPHEMERAL_SEQUENTIAL,使用临时节点的方式注册,这样在服务关闭时就会自动消失。不会留下死服务。
/** * 连接ZK注册中心,创建服务注册目录 */public class ServiceRegistry { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistry.class); private final CountDownLatch latch = new CountDownLatch(1); private ZooKeeper zk; public ServiceRegistry() { } public void register(String data) { if (data != null) { zk = connectServer(); if (zk != null) { createNode(Constant.ZK_DATA_PATH, data); } } } private ZooKeeper connectServer() { ZooKeeper zk = null; try { zk = new ZooKeeper(Constant.ZK_CONNECT, Constant.ZK_SESSION_TIMEOUT, new Watcher() { @Override public void process(WatchedEvent event) { // 判断是否已连接ZK,连接后计数器递减. if (event.getState() == Event.KeeperState.SyncConnected) { latch.countDown(); } } }); // 若计数器不为0,则等待. latch.await(); } catch (IOException | InterruptedException e) { LOGGER.error("", e); } return zk; } private void createNode(String dir, String data) { try { byte[] bytes = data.getBytes(); String path = zk.create(dir, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); LOGGER.info("create zookeeper node ({} => {})", path, data); } catch (KeeperException | InterruptedException e) { LOGGER.error("", e); } }}
这是用到一个常量接口:
public interface Constant { int ZK_SESSION_TIMEOUT = 10000; String ZK_CONNECT = "s1:2181,s2:2181,s3:2181"; String ZK_REGISTRY_PATH = "/registry"; String ZK_DATA_PATH = ZK_REGISTRY_PATH + "/data"; String ZK_IP_SPLIT = ":";}
2.服务器开发:
nioServer的半包处理,还有服务注册,以及相应服务的确定。
public class RPCServer { private Map<String, Object> getServices() { Map<String, Object> services = new HashMap<String, Object>(); // 先将服务确定好,才能区调用,不允许客户端自动添加服务 services.put(ISum.class.getName(), new SumImpl()); services.put(IDiff.class.getName(), new DiffImpl()); return services; } private void bind(int port) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); Map<String, Object> handlerMap = getServices(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline() .addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2)) .addLast(new RPCDecoder(RPCRequest.class)) .addLast(new LengthFieldPrepender(2)) .addLast(new RPCEncoder(RPCResponse.class)) .addLast(new RPCServerHandler(handlerMap)); } }); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static String getAddress(){ InetAddress host = null; try {// 获取本机ip host = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } String address = host.getHostAddress(); return address; } public void initService(int port) { ServiceRegistry serviceRegistry = new ServiceRegistry(); String ip = getAddress();// 向zookeeper注册服务地址 serviceRegistry.register(ip+Constant.ZK_IP_SPLIT+port); bind(port); } public static void main(String[] args) { int port = 9090; new RPCServer().initService(port); }}
服务器的处理逻辑:
public class RPCServerHandler extends ChannelHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(RPCServerHandler.class); private final Map<String, Object> handlerMap; public RPCServerHandler(Map<String, Object> handlerMap) { this.handlerMap = handlerMap; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { RPCResponse response = new RPCResponse(); RPCRequest request = (RPCRequest) msg; response.setRequestId(request.getRequestId()); try { Object result = handle(request); response.setResult(result); } catch (Throwable t) { response.setError(t); } ctx.writeAndFlush(response); } private Object handle(RPCRequest request) throws Throwable { String className = request.getClassName(); Object serviceBean = handlerMap.get(className); Class<?> serviceClass = serviceBean.getClass(); String methodName = request.getMethodName(); Class<?>[] parameterTypes = request.getParameterTypes(); Object[] parameters = request.getParameters(); FastClass serviceFastClass = FastClass.create(serviceClass); FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes); return serviceFastMethod.invoke(serviceBean, parameters); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { LOGGER.error("server caught exception", cause); ctx.close(); }}
3.RPC请求和响应的封装
比较简单没什么好说的,唯一值得注意的就是请求和响应都带有Id,这是因为NIO通信是异步的,如果出现一个客户端发送了多个请求,那么也会有多个响应,由于是异步的,那么就免不了,出现不一致的对应情况,这时候可以用ID将每个请求和响应对应起来。
public class RPCRequest { private String requestId; private String className; private String methodName; private Class<?>[] parameterTypes; private Object[] parameters; public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public Class<?>[] getParameterTypes() { return parameterTypes; } public void setParameterTypes(Class<?>[] parameterTypes) { this.parameterTypes = parameterTypes; } public Object[] getParameters() { return parameters; } public void setParameters(Object[] parameters) { this.parameters = parameters; }}public class RPCResponse { private String requestId; private Throwable error; private Object result; public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } public Throwable getError() { return error; } public void setError(Throwable error) { this.error = error; } public Object getResult() { return result; } public void setResult(Object result) { this.result = result; }}
4.使用protobuf序列化:
这里做成工具,好处是,如果换其他序列化工具,你就可以只改这个类,不用去改其他类。
public class SerializationUtil { private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>(); private static Objenesis objenesis = new ObjenesisStd(true); private SerializationUtil() { } @SuppressWarnings("unchecked") private static <T> Schema<T> getSchema(Class<T> cls) { Schema<T> schema = (Schema<T>) cachedSchema.get(cls); if (schema == null) { schema = RuntimeSchema.createFrom(cls); if (schema != null) { cachedSchema.put(cls, schema); } } return schema; } @SuppressWarnings("unchecked") public static <T> byte[] serialize(T obj) { Class<T> cls = (Class<T>) obj.getClass(); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { Schema<T> schema = getSchema(cls); return ProtostuffIOUtil.toByteArray(obj, schema, buffer); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } finally { buffer.clear(); } } public static <T> T deserialize(byte[] data, Class<T> cls) { try { T message = (T) objenesis.newInstance(cls); Schema<T> schema = getSchema(cls); ProtostuffIOUtil.mergeFrom(data, message, schema); return message; } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } }}
5.编解码类
就是把byte[] –> Object,Object –> byte[];
public class RPCDecoder extends ByteToMessageDecoder { private Class<?> genericClass; public RPCDecoder(Class<?> genericClass) { this.genericClass = genericClass; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { final int length = in.readableBytes(); final byte[] bytes = new byte[length]; in.readBytes(bytes, 0, length); Object obj = SerializationUtil.deserialize(bytes, genericClass); out.add(obj); }}public class RPCEncoder extends MessageToByteEncoder<Object> { private Class<?> genericClass; public RPCEncoder(Class<?> genericClass) { this.genericClass = genericClass; } @Override public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception { if (genericClass.isInstance(in)) { byte[] data = SerializationUtil.serialize(in); out.writeBytes(data); } }}
6客户端代理
public class RPCProxy { private String serverAddress; private ServiceDiscovery serviceDiscovery; public RPCProxy(String serverAddress) { this.serverAddress = serverAddress; } public RPCProxy(ServiceDiscovery serviceDiscovery) { this.serviceDiscovery = serviceDiscovery; } @SuppressWarnings("unchecked") public <T> T create(Class<?> interfaceClass) { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] { interfaceClass }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { RPCRequest request = new RPCRequest(); // 创建并初始化 RPC 请求 request.setRequestId(UUID.randomUUID().toString()); request.setClassName(method.getDeclaringClass().getName()); request.setMethodName(method.getName()); request.setParameterTypes(method.getParameterTypes()); request.setParameters(args); if (serviceDiscovery != null) { serverAddress = serviceDiscovery.discover(); // 发现服务 }// "123.23.213.23:9090" String[] array = serverAddress.split(Constant.ZK_IP_SPLIT); String host = array[0]; int port = Integer.parseInt(array[1]); RPCClient client = new RPCClient(host, port); // 初始化 RPC // 客户端 RPCResponse response = client.send(request); // 通过 RPC if (response.getError() != null) { throw response.getError(); } else { return response.getResult(); } } }); }}
7寻找和发现zookeeper /registry目录下的服务。
/** * 服务发现:连接ZK,添加watch事件 */public class ServiceDiscovery { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceDiscovery.class); private final CountDownLatch latch = new CountDownLatch(1); private volatile List<String> dataList = new ArrayList<>(); private final String registryAddress; public ServiceDiscovery(String registryAddress) { this.registryAddress = registryAddress; ZooKeeper zk = connectServer(); if (zk != null) { watchNode(zk); } } public String discover() { String data = null; int size = dataList.size(); if (size > 0) { if (size == 1) { data = dataList.get(0); LOGGER.debug("using only data: {}", data); } else { data = dataList.get(ThreadLocalRandom.current().nextInt(size)); LOGGER.debug("using random data: {}", data); } } return data; } private ZooKeeper connectServer() { ZooKeeper zk = null; try { zk = new ZooKeeper(registryAddress, Constant.ZK_SESSION_TIMEOUT, new Watcher() { @Override public void process(WatchedEvent event) { if (event.getState() == Event.KeeperState.SyncConnected) { latch.countDown(); } } }); latch.await(); } catch (IOException | InterruptedException e) { LOGGER.error("", e); } return zk; } private void watchNode(final ZooKeeper zk) { try { List<String> nodeList = zk.getChildren(Constant.ZK_REGISTRY_PATH, new Watcher() { @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.NodeChildrenChanged) { watchNode(zk); } } }); List<String> dataList = new ArrayList<>(); for (String node : nodeList) { byte[] bytes = zk.getData(Constant.ZK_REGISTRY_PATH + "/" + node, false, null); dataList.add(new String(bytes)); } LOGGER.debug("node data: {}", dataList); this.dataList = dataList; } catch (KeeperException | InterruptedException e) { LOGGER.error("", e); } }}
8,开发客户端。
public class RPCClient { private final String host; private final int port; private final CountDownLatch latch; public RPCClient(String host,int port) { this.host = host; this.port = port; this.latch = new CountDownLatch(1); } public RPCResponse send(RPCRequest request){ EventLoopGroup group = new NioEventLoopGroup(); final RPCClientHandler handler = new RPCClientHandler(request,latch); RPCResponse response = null; try { Bootstrap b = new Bootstrap().group(group) .channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline() .addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2)) .addLast(new RPCDecoder(RPCResponse.class)) .addLast(new LengthFieldPrepender(2)) .addLast(new RPCEncoder(RPCRequest.class)) .addLast(handler); } }); ChannelFuture f = b.connect(host, port).sync(); latch.await(); response = handler.getResponse(); if(response != null) f.channel().close(); } catch (InterruptedException e) { e.printStackTrace(); }finally{ group.shutdownGracefully(); } return response; }}
客户端handler
public class RPCClientHandler extends ChannelHandlerAdapter { private final RPCRequest request; private RPCResponse response; private final CountDownLatch latch; public RPCClientHandler(RPCRequest request, CountDownLatch latch) { this.request = request; this.latch = latch; } public RPCResponse getResponse() { return response; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.getCause().printStackTrace(); ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(request); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { response = (RPCResponse) msg; latch.countDown(); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); }}
基本上就这么多,接下来测试。
启动服务器:
public class RPCServiceTest { public static void main(String[] args) { int port = 9090; new RPCServer().initService(port); }}
查看zookeeper
可以清晰的看到注册的服务地址和端口。
启动服务器:
public class RPCTest { public static void serviceTest() { ServiceDiscovery discovery = new ServiceDiscovery(Constant.ZK_CONNECT); RPCProxy rpcProxy = new RPCProxy(discovery); IDiff diff = rpcProxy.create(IDiff.class); double result = diff.diff(1321, 32.2); ISum sum = rpcProxy.create(ISum.class); int result2 = sum.sum(1000, 1000); System.out.println(result+":"+result2);// -20.1:1100 } public static void main(String[] args) { serviceTest(); }}
客户端会在zookeeper上获取服务器地址,然后对服务器RPC
总结:花了一天时间,使用netty+zookeeper+protobuf实现了一个RPC调用,主要是想对大数据底层(hadoop,spark,hbase)的RPC有一个更加深刻的了解,不能只是停留在使用的层面。
阅读全文
0 0
- 使用netty+zookeeper+protobuf实现一个RPC过程
- Netty+Zookeeper实现一个类似Dubbo的RPC框架
- Netty+Zookeeper实现一个类似Dubbo的RPC框架
- 基于Netty和Zookeeper实现RPC框架
- 通过 Spring + Netty + Protostuff + ZooKeeper 实现了一个轻量级 RPC 框架
- 使用google protobuf RPC实现echo service
- 使用python通过protobuf实现rpc
- 【转】Spring+Netty+Protostuff+ZooKeeper实现轻量级RPC服务(一)
- 【转】Spring+Netty+Protostuff+ZooKeeper实现轻量级RPC服务(二)
- netty-protobuf-rpc与protobuf-socket-rpc互通
- protobuf RPC实现
- Netty实现简单RPC
- Triple is an RPC framework(Netty,ZooKeeper)
- 基于protobuf的RPC实现
- 基于protobuf的RPC实现
- 基于protobuf的RPC实现
- 基于protobuf的RPC实现
- 基于protobuf的RPC实现
- SVN系统实现多系统环境下的代码审核与版本同步
- 多线程断点下载
- SecureCrt 配置脚本查看日志
- style.left和offsetLeft和offsetWidth和scrollTop区别
- Austin 第二天 | 炫技!Google 语音操控 GKE 部署集群及扩容服务
- 使用netty+zookeeper+protobuf实现一个RPC过程
- Java虚拟机详细解析--JVM类加载过程+内存分配+GC算法+垃圾回收器分类
- sklearn整理
- 点播系统hadoop存储视频长时间无响应
- Gym
- 静态方法和非静态方法的区别
- JAVA连接mysql数据库,动态创建表以及动态插入数据
- QT控件大全 三十七 QColplug
- VMware Workstation10 下安装 CentOS6.5( 安装图文教程 )