上一篇剖析了SimpleRpc的服务端,这里来看看客户端调用java
惯例,先看看时序图:node
1:spring初始化zk服务发现类ZooKeeperServiceDiscovery和服务代理类,且后者依赖前者spring
2:获取服务代理类bean,并建立请求代理类(代理的是HelloService接口的实现类)bootstrap
3:建立代理时,首先利用zk服务发现类去zk获取服务类节点信息(包括服务类及其提供者地址)服务器
4:利用获取到的地址发起调用(Netty发起调用)并发
5:服务端处理完毕-->获取服务端的返回值dom
服务端已介绍过,这里看看客户端如何处理吧ide
启动main方法:oop
public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); RpcProxy rpcProxy = context.getBean(RpcProxy.class); HelloService helloService = rpcProxy.create(HelloService.class); String result = helloService.hello("World"); System.out.println(result); HelloService helloService2 = rpcProxy.create(HelloService.class, "sample.hello2"); String result2 = helloService2.hello("世界"); System.out.println(result2); System.exit(0); }
首先进行spring的初始化:this
<context:property-placeholder location="classpath:rpc.properties"/> <bean id="serviceDiscovery" class="com.xxx.rpc.registry.zookeeper.ZooKeeperServiceDiscovery"> <constructor-arg name="zkAddress" value="${rpc.registry_address}"/> </bean> <bean id="rpcProxy" class="com.xxx.rpc.client.RpcProxy"> <constructor-arg name="serviceDiscovery" ref="serviceDiscovery"/> </bean>
初始化zk服务发现类及服务代理类,这里的zk地址经过配置文件注入,和服务端的地址相同
初始化完毕后,获得服务代理类Bean,建立代理,传入接口参数(恰好这里利用的是jdk的代理)
@SuppressWarnings("unchecked") public <T> T create(final Class<?> interfaceClass, final String serviceVersion) { // 建立动态代理对象 return (T) Proxy.newProxyInstance( interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 建立 RPC 请求对象并设置请求属性 RpcRequest request = new RpcRequest(); request.setRequestId(UUID.randomUUID().toString()); request.setInterfaceName(method.getDeclaringClass().getName()); request.setServiceVersion(serviceVersion); request.setMethodName(method.getName()); request.setParameterTypes(method.getParameterTypes()); request.setParameters(args); // 获取 RPC 服务地址 if (serviceDiscovery != null) { String serviceName = interfaceClass.getName(); if (StringUtil.isNotEmpty(serviceVersion)) { serviceName += "-" + serviceVersion; } serviceAddress = serviceDiscovery.discover(serviceName); LOGGER.debug("discover service: {} => {}", serviceName, serviceAddress); } if (StringUtil.isEmpty(serviceAddress)) { throw new RuntimeException("server address is empty"); } // 从 RPC 服务地址中解析主机名与端口号 String[] array = StringUtil.split(serviceAddress, ":"); String host = array[0]; int port = Integer.parseInt(array[1]); // 建立 RPC 客户端对象并发送 RPC 请求 RpcClient client = new RpcClient(host, port); long time = System.currentTimeMillis(); RpcResponse response = client.send(request); LOGGER.debug("time: {}ms", System.currentTimeMillis() - time); if (response == null) { throw new RuntimeException("response is null"); } // 返回 RPC 响应结果 if (response.hasException()) { throw response.getException(); } else { return response.getResult(); } } } ); }
在建立代理的过程当中就发生了rpc调用
先是建立请求对象RpcRequest,接着利用注入的zk发现类去zk服务端获取服务类地址(这里利用多态,使用接口形式,实际使用注入bean)
@Override public String discover(String name) { // 建立 ZooKeeper 客户端 ZkClient zkClient = new ZkClient(zkAddress, Constant.ZK_SESSION_TIMEOUT, Constant.ZK_CONNECTION_TIMEOUT); LOGGER.debug("connect zookeeper"); try { // 获取 service 节点 String servicePath = Constant.ZK_REGISTRY_PATH + "/" + name; if (!zkClient.exists(servicePath)) { throw new RuntimeException(String.format("can not find any service node on path: %s", servicePath)); } List<String> addressList = zkClient.getChildren(servicePath); if (CollectionUtil.isEmpty(addressList)) { throw new RuntimeException(String.format("can not find any address node on path: %s", servicePath)); } // 获取 address 节点 String address; int size = addressList.size(); if (size == 1) { // 若只有一个地址,则获取该地址 address = addressList.get(0); LOGGER.debug("get only address node: {}", address); } else { // 若存在多个地址,则随机获取一个地址 address = addressList.get(ThreadLocalRandom.current().nextInt(size)); LOGGER.debug("get random address node: {}", address); } // 获取 address 节点的值 String addressPath = servicePath + "/" + address; return zkClient.readData(addressPath); } finally { zkClient.close(); } }
拿到服务类信息,也就有了服务提供者的ip+port,这些信息就是服务端在启动时注册到zk中的
有了这些信息,就能够利用netty发起请求调用了
public RpcResponse send(RpcRequest request) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { // 建立并初始化 Netty 客户端 Bootstrap 对象 Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group); bootstrap.channel(NioSocketChannel.class); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new RpcEncoder(RpcRequest.class)); // 编码 RPC 请求 pipeline.addLast(new RpcDecoder(RpcResponse.class)); // 解码 RPC 响应 pipeline.addLast(RpcClient.this); // 处理 RPC 响应 } }); bootstrap.option(ChannelOption.TCP_NODELAY, true); // 链接 RPC 服务器 ChannelFuture future = bootstrap.connect(host, port).sync(); // 写入 RPC 请求数据并关闭链接 Channel channel = future.channel(); channel.writeAndFlush(request).sync(); channel.closeFuture().sync(); // 返回 RPC 响应对象 return response; } finally { group.shutdownGracefully(); } }
返回的信息由全局变量RpcResponse接收,实际的返回结果被其包装着