RCP 远程过程调用
随着业务的发展,架构越来越复杂,垂直拆分、横向扩展、高可用、高性能等要求,业务渐渐模块化微服务化,一个系统往往有好几个小系统组成,每个小系统提供不同的服务,如果调用其他服务像本地服务一样很方便的调用 ,而让调用者对网络连接通信等这些复杂的细节透明,这将大大提高生产力,这就是今天要聊的话题RPC(远程过程调用),好在业界已有很多成熟的解决方案,在各大互联网公司中已经被广泛使用,如阿里巴巴的hsf、dubbo(开源)、Facebook的thrift(开源)、Google grpc(开源)、Twitter的finagle等。
我们先来看看RPC的调用过程:
1)服务消费方(client)调用以本地调用方式调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3)client stub找到服务地址,并将消息发送到服务端;
4)server stub收到消息后进行解码;
5)server stub根据解码结果调用本地的服务;
6)本地服务执行并将结果返回给server stub;
7)server stub将返回结果打包成消息并发送至消费方;
8)client stub接收到消息,并进行解码;
9)服务消费方得到最终结果。
RPC的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明。这里面的难点有如下几点:
1.客户端拿到的是接口,我们知道接口是不能实例化的,不能实例化怎样才能调用其实例方法?
2.客户端和服务端如何通信?
3.我们知道消息在网络中是以二进制的形式传输的,那么就要解决如何序列化和反序列化消息?
4.服务端在新增服务器或者服务端有服务器宕机后,客户端如何感知?一个接口也许有很多台服务器提供服务,如何负载均衡?
基于TCP方式的简单RPC实现
带着这几个问题我们来简单用代码来实现以下:
我们先来解决问题1,如何解决不能实例化但又需要调用实例接口呢?对java来说就是使用代理!java代理有两种方式:1) jdk 动态代理;2)字节码生成。尽管字节码生成方式实现的代理更为强大和高效,但代码不易维护,大部分公司实现RPC框架时还是选择动态代理方式。
解决了问题1,我们再来看看问题2:如何通讯? 我们使用java 提供的socket ,socket分装了底层的复杂连接如TCP三次握手等,当然你也可以选择更强大的netty,这里只是探讨RPC的实现,重在原理。
问题3:消息的序列化与反序列化,是序列化?序列化就是将数据结构或对象转换成二进制串的过程,也就是编码的过程。什么是反序列化?将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。现如今序列化的方案越来越多,每种序列化方案都有优点和缺点,它们在设计之初有自己独特的应用场景,那到底选择哪种呢?从RPC的角度上看,主要看三点:1)通用性,比如是否能支持Map等复杂的数据结构;2)性能,包括时间复杂度和空间复杂度,由于RPC框架将会被公司几乎所有服务使用,如果序列化上能节约一点时间,对整个公司的收益都将非常可观,同理如果序列化上能节约一点内存,网络带宽也能省下不少;3)可扩展性,对互联网公司而言,业务变化快,如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,删除老的字段,而不影响老的服务,这将大大提供系统的健壮性。目前国内各大互联网公司广泛使用hessian、protobuf、thrift、avro等成熟的序列化解决方案来搭建RPC框架,这些都是久经考验的解决方案。为了方便我们使用java提供的相关的类ObjectOutputStream/ObjectInputStream,进行序列与反序列化对象。
问题4:服务端服务器的上线下线对客户端应该是透明的,不能说服务端新增一台机器,客户端就修改代码新增一个ip和端口,下线的时候客户端去掉一个ip和端口,那如何透明? zookeeper 分布式协调服务,这个能很好的解决这个问题,我们把服务注册到一个节点(内容是提供服务的ip+端口),然后利用其提供的监听功能监听这个节点的变化,服务的增加和减少都能感知到,如何负载呢?这个相对简单,可以采用轮询,随机,原地址hash等方式对服务器均衡的访问。
解决了上面的问题,话不多说我们直接上代码:
接口:
package com.ssm.rpc.common;
public interface SayHelloService {
public String sayHello(String helloArg);
}
服务端代理:
package com.ssm.rpc.common;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import org.springframework.util.StringUtils;
public class ServiceProxy implements InvocationHandler {
private ZookeeperClientHook zookeeperHook = new ZookeeperClientHook();
private Class<?> targetClass;
public Object bind(Class<?> targetClass){
this.targetClass = targetClass;
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[] { targetClass}, this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String server = getServer();
if(StringUtils.isEmpty(server)){
return null;
}
String ip = server.split(":")[0];
String port = server.split(":")[1];
@SuppressWarnings("resource")
Socket socket = new Socket(ip, Integer.valueOf(port));
//将方法名和参数传递到远端
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(targetClass.getName()); //接口名
output.writeUTF(method.getName()); //方法名
output.writeObject(method.getParameterTypes()); //方法参数类型
output.writeObject(args); //参数
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
Object result = inputStream.readObject();
return result;
}
public String getServer(){
return zookeeperHook.rondomGetServer();
}
}
zookeeper类:
package com.ssm.rpc.common;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.CollectionUtils;
public class ZookeeperClientHook implements InitializingBean{
public static final String ROOT_PATH = "/ssm/rpc";
public static final String PATH_SEPARATOR = "/";
private static final String ZKCONNSTR = "172.18.33.11:2181,172.18.33.12:2181,172.18.33.13:2181";
private AtomicInteger count = new AtomicInteger();
private ZkClient zkClient;
private List<String> serverList;
public ZookeeperClientHook() {
afterPropertiesSet();
}
public ZkClient getZkClient() {
return zkClient;
}
public List<String> getServerList() {
if(CollectionUtils.isEmpty(serverList)){
serverList = zkClient.getChildren(ROOT_PATH);
}
return serverList;
}
public String roundrobinGetServer(){
if(CollectionUtils.isEmpty(getServerList())){
return null;
}
if(count.get() >= serverList.size()){
count.set(0);
}
return serverList.get(count.getAndIncrement());
}
public String rondomGetServer(){
if(CollectionUtils.isEmpty(getServerList())){
return null;
}
int rondom = new Random().nextInt(serverList.size());
return serverList.get(rondom);
}
public void afterPropertiesSet() {
if(zkClient == null){
zkClient = new ZkClient(ZKCONNSTR);
}
//当子节点变话的时候
zkClient.subscribeChildChanges(ROOT_PATH, new IZkChildListener() {
public void handleChildChange(String parentPath, List<String> currentChilds)
throws Exception {
serverList = currentChilds;
}
});
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
if(zkClient !=null){
System.out.println("zkClient close ..");
zkClient.close();
}
}
});
}
}
服务端接口实现:
package com.ssm.rpc.service;
import com.ssm.rpc.common.SayHelloService;
public class SayHelloServiceImpl implements SayHelloService{
public String sayHello(String helloArg) {
if(helloArg.equals("hello")){
return "hello";
}else{
return "bye bye";
}
}
}
服务注册:
package com.ssm.rpc.service;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.ssm.rpc.common.CalculateService;
import com.ssm.rpc.common.SayHelloService;
public class ProviderServiceRegister implements InitializingBean{
public static final String PORT = "1234";
public ProviderServiceRegister(){
afterPropertiesSet();
}
private ZookeeperServiceHook zookeeperHook = new ZookeeperServiceHook();
public ZookeeperServiceHook getZookeeperHook() {
return zookeeperHook;
}
public void setZookeeperHook(ZookeeperServiceHook zookeeperHook) {
this.zookeeperHook = zookeeperHook;
}
public String getServer(){
try {
InetAddress addr = InetAddress.getLocalHost();
return addr.getHostAddress()+":"+PORT;
} catch (UnknownHostException e) {
e.printStackTrace();
}
return null;
}
public void afterPropertiesSet() {
registerImpl(SayHelloService.class,SayHelloServiceImpl.class);
}
@SuppressWarnings("unchecked")
public void registerImpl(Class<?> interfaceClass,Class<?> implClass){
Map<String,String> map;
String ipPort = getServer();
String services = zookeeperHook.getInterfaceName(ipPort);
if(!StringUtils.isEmpty(services)){
map = JSON.parseObject(services, HashMap.class);
}else{
map = Maps.newHashMap();
}
map.put(interfaceClass.getName(), implClass.getName());
zookeeperHook.registerInterface(ipPort, JSON.toJSONString(map));
}
@SuppressWarnings("unchecked")
public void registerImpl(Map<String,String> servicesMap){
Map<String,String> map;
String ipPort = getServer();
String services = zookeeperHook.getInterfaceName(ipPort);
if(!StringUtils.isEmpty(services)){
map = JSON.parseObject(services, HashMap.class);
}else{
map = Maps.newHashMap();
}
map.putAll(servicesMap);
zookeeperHook.registerInterface(ipPort, JSON.toJSONString(map));
}
}
zookeeper监听类:
package com.ssm.rpc.service;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.InitializingBean;
public class ZookeeperServiceHook implements InitializingBean{
public static final String ROOT_PATH = "/ssm/rpc";
public static final String PATH_SEPARATOR = "/";
private static final String ZKCONNSTR = "172.18.33.11:2181,172.18.33.12:2181,172.18.33.13:2181";
private ZkClient zkClient;
public ZookeeperServiceHook() {
afterPropertiesSet();
}
public ZkClient getZkClient() {
return zkClient;
}
public String getInterfaceName(String server){
return zkClient.readData(ROOT_PATH+PATH_SEPARATOR+server, true);
}
public void registerInterface(String server,String data){
if(!zkClient.exists(ROOT_PATH)){
zkClient.createPersistent(ROOT_PATH, true);
}
String path = ROOT_PATH+PATH_SEPARATOR+server;
if(!zkClient.exists(path)){
zkClient.createEphemeral(path, data);
}else{
zkClient.writeData(path, data);
}
}
public void afterPropertiesSet() {
if(zkClient == null){
zkClient = new ZkClient(ZKCONNSTR);
}
if(!zkClient.exists(ROOT_PATH)){
zkClient.createPersistent(ROOT_PATH, true);
}
//当根节点被删除的时候,自动创建
zkClient.subscribeDataChanges(ROOT_PATH, new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
zkClient.createPersistent(ROOT_PATH, true);
}
public void handleDataChange(String dataPath, Object data) throws Exception {
}
});
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
if(zkClient !=null){
zkClient.close();
}
}
});
}
}
服务提供者:
package com.ssm.rpc.service;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson.JSON;
public class Provider {
private static ProviderServiceRegister providerServiceRegister = new ProviderServiceRegister();
private static AtomicBoolean atomicBoolean = new AtomicBoolean(true);
private static ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception{
@SuppressWarnings("resource")
ServerSocket serverSocket = new ServerSocket(Integer.valueOf(ProviderServiceRegister.PORT));
System.out.println("Start Provider .... ");
while(atomicBoolean.get()){
Socket socket = serverSocket.accept();
executorService.submit(new ProviderHandled(socket));
}
executorService.shutdown();
System.out.println("End Provider .... ");
}
public static Object getServiceImpl(String interfaceName){
ZookeeperServiceHook zookeeperHook = providerServiceRegister.getZookeeperHook();
String services = zookeeperHook.getInterfaceName(providerServiceRegister.getServer());
if(StringUtils.isEmpty(services)){
return null;
}
@SuppressWarnings("unchecked")
Map<String,String> map = JSON.parseObject(services, HashMap.class);
String impl = map.get(interfaceName);
if(StringUtils.isEmpty(impl)){
return null;
}
try {
return Class.forName(impl).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
class ProviderHandled implements Callable<Object>{
private Socket socket;
public ProviderHandled(Socket socket){
this.socket = socket;
}
public Object call() throws Exception {
//读取服务信息
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
String interfaceName = inputStream.readUTF(); //接口名
String methodName = inputStream.readUTF(); //方法名
Class<?>[] parameterTypes = (Class<?>[])inputStream.readObject(); //方法参数类型
Object[] arguments = (Object[])inputStream.readObject(); //参数
Class<?> serviceInterfaceClass = Class.forName(interfaceName);
Object service = Provider.getServiceImpl(interfaceName);
Method method = serviceInterfaceClass.getMethod(methodName, parameterTypes);
Object result = method.invoke(service, arguments);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(result);
objectOutputStream.flush();
socket.close();
return result;
}
}
客户端:
package com.ssm.rpc.client;
import com.ssm.rpc.common.CalculateService;
import com.ssm.rpc.common.SayHelloService;
import com.ssm.rpc.common.ServiceProxy;
public class Consume {
public static void main(String[] args) {
ServiceProxy serviceProxy = new ServiceProxy();
SayHelloService sayHelloService = (SayHelloService) serviceProxy.bind(SayHelloService.class);
String result = sayHelloService.sayHello("hello");
System.out.println(result);
}
}
Maven依赖:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>