2019独角兽企业重金招聘Python工程师标准>>>
前言
RPC——远程过程调用。简单的理解,调用远程服务,就像是调用本地方法一样。
如果服务仅是对同一工程内使用,例如定义一个服务,接口UserService,实现UserServiceImpl,仅需创建一个对象,就可以轻松的调用。但如果把这样的服务暴露给其它工程调用,特别是目前流行的微服务框架,抽象出的模块化服务间可能要相互调用,这就需要“RPC框架”来实现了。
目前主流的RPC框架有很多,例如阿里的Dubbo,谷歌的gRPC等等。
各种实现的框架虽不尽相同,但主流的思想是相似的:都离不开中心化的服务治理(服务的发现与注册)。对此可选用的方案有多种,例如ZooKeeper、Eureka、Redis等等。本篇将采用ZooKeeper作为注册中心的方式,用代码讲解RPC调用的几个关键点。
源码地址:https://gitee.com/marvelcode/marvelcode-rpc.git
架构分析
接下来,将会对我所采用的架构进行代入式的分析,如果对RPC框架有所了解的可以忽略此部分。
那就从RPC调用的源头方开始吧,来思考下一次调用的大致流程是怎样的:
- 首先,调用方发起服务调用。无论是通过服务的接口层,还是仅仅依靠服务对应的名称。
- 但无论是哪一种,必然要先知道该服务部署机器的IP、以及开放的端口号。这就是服务治理重要的一环,服务发现。即通过要调用的服务信息,来获取到这些服务所在的机器信息。
- 第二步,假使我们已经获取到了机器信息,接下来便是传入参数、调用服务、得到响应结果。
- 和普通HTTP接口不同的是,服务间调用传递的对象有可能是复杂的Java对象,仅靠Json格式的数据可能无法支撑(Ps:比如要穿一个Class对象)。这就引出了对象传输的序列化与反序列化。
- 第三步,假使服务提供方已经接收到服务调用的请求,最后只需根据请求的数据定位到提供服务的实例,进行反射调用即可。
- 既然能通过服务发现找到该服务,前提必然有服务注册这一步。当然,这一环节涉及到的服务定位,根据哪些数据能唯一标识一个服务呢?
- 分析完主要问题后,我们来看源码。
源码
服务提供方
根据上面的分析,我们来看下服务注册这一步的代码实现。
首先来看下用于标识对外暴露的服务的注解定义:
package com.menghao.rpc.provider.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 服务提供方注解.
*
用于标识在接口上,只提供出接口jar
*
* @author MarvelCode.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Provider {
}
该注解起的作用就是个标记作用,在合适的时间将标识的服务注册到 ZooKeeper 上。来看下它是何时被解析的:
package com.menghao.rpc.spring;
import com.menghao.rpc.RpcConstants;
import com.menghao.rpc.provider.annotation.Provider;
import com.menghao.rpc.provider.regisiter.ProviderRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.util.HashMap;
import java.util.Map;
/**
* 内部系统服务提供方注册.
* 获取内部所有的 @Provider 接口并注册到服务提供者仓库
*
* @author MarvelCode
* @see ProviderRepository
*/
public class ProviderPostProcessor implements BeanPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(ProviderPostProcessor.class);
private Map candidates = new HashMap<>(8);
private ProviderRepository providerRepository;
public ProviderPostProcessor(ProviderRepository providerRepository) {
this.providerRepository = providerRepository;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
for (Class sourceInterface : bean.getClass().getInterfaces()) {
// 递归遍历所有父接口
recursiveInterface(sourceInterface, beanName, candidates);
}
return bean;
}
private void recursiveInterface(Class sourceInterface, String beanName, Map candidates) {
// 接口被@Provider标识
if (sourceInterface.getAnnotation(Provider.class) != null) {
LOGGER.info(RpcConstants.LOG_RPC_PREFIX + "find @Provider-" + sourceInterface.getName());
candidates.put(beanName, sourceInterface);
}
// 直到无父接口,递归结束
if (sourceInterface.getInterfaces().length == 0) {
return;
}
// 否则递归遍历父接口
for (Class superInterface : sourceInterface.getInterfaces()) {
recursiveInterface(superInterface, beanName, candidates);
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 将符合条件的服务进行注册
if (candidates.containsKey(beanName)) {
Class sourceInterface = candidates.get(beanName);
providerRepository.register(sourceInterface, beanName, bean);
}
return bean;
}
}
借助 Spring提供的 bean 生命周期扩展接口,在 bean 的初始化前,遍历获取该实例的所有父接口,将被 @Provider 注解标识的接口筛选出来,放入候选者列表(candidates :key-beanName,value-Interface)等待处理。在 bean 的初始化后,将之前筛选出的服务进行注册。
接着来看注册逻辑:
package com.menghao.rpc.provider.regisiter;
import com.menghao.rpc.RpcConstants;
import com.menghao.rpc.provider.exception.InitializationException;
import com.menghao.rpc.provider.model.ProviderKey;
import com.menghao.rpc.spring.ProviderPostProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 本地服务仓库.
* 存放了契约与调用bean的映射关系,用于rpc调用时取出,从而反射调用
*
* @author MarvelCode
* @see ProviderRegister
*/
public class ProviderRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(ProviderPostProcessor.class);
private ConcurrentMap providerMapping = new ConcurrentHashMap<>();
private Set providers = Collections.unmodifiableSet(providerMapping.keySet());
/**
* 注册服务,维护契约与单例的映射关系
*
* @param sourceInterface @Provider标识的接口
* @param implCode 服务实现的beanName
* @param bean 服务单例
*/
public void register(Class sourceInterface, String implCode, Object bean) {
ProviderKey providerKey = new ProviderKey(sourceInterface.getName(), implCode);
LOGGER.info(RpcConstants.LOG_RPC_PREFIX + "register provider-" + providerKey);
if (providerMapping.containsKey(providerKey)) {
throw new InitializationException(MessageFormat.format("contract: {} ,implCode: {} confilic",
sourceInterface.getName(), implCode));
}
providerMapping.putIfAbsent(providerKey, bean);
}
/**
* 发现服务单例,根据契约与单例的映射关系查到单例,以便反射调用
*
* @param providerKey 服务单例的键(契约)
* @return 服务单例
*/
public Object getProvider(ProviderKey providerKey) {
return providerMapping.get(providerKey);
}
/**
* 获取所有满足条件的(@Provider)的契约
*
* @return 服务键集合
*/
Set getProviders() {
return providers;
}
}
该类作为服务实例的仓库,存放了 ProviderKey 与 服务实例的对应关系。这也是服务定位的逻辑所在,通过接口的全限定名,beanName作为服务的坐标(ProviderKey),唯一确定一个服务。这样服务调用方将这些信息告诉服务提供方时,就可以知道反射调用哪个服务实例了。
服务信息收集完毕后,接下来就会进行服务注册:
package com.menghao.rpc.provider.regisiter;
import com.menghao.rpc.provider.exception.InitializationException;
import com.menghao.rpc.provider.model.ProviderKey;
import com.menghao.rpc.util.ProviderHostUtils;
import com.menghao.rpc.zookeeper.CuratorClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.util.Assert;
import java.text.MessageFormat;
/**
* 服务提供方注册.
* 需要的信息:契约、实现、IP、端口
*
* @author MarvelCode
*/
public class ProviderRegister implements ApplicationListener {
private CuratorClient curatorClient;
private Integer port;
public ProviderRegister(CuratorClient curatorClient, Integer port) {
this.curatorClient = curatorClient;
this.port = port;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext currentContext = event.getApplicationContext();
// 忽略子容器
if (currentContext.getParent() != null) {
return;
}
ProviderRepository apiRepository = currentContext.getBean(ProviderRepository.class);
Assert.notNull(apiRepository, "初始化失败");
String ip = ProviderHostUtils.getLocalHost();
String nodeValue = ip + ":" + port;
for (ProviderKey providerKey : apiRepository.getProviders()) {
String path = getNodePath(providerKey) + "/" + nodeValue;
try {
curatorClient.createEphemeralNode(path, null);
} catch (Exception e) {
e.printStackTrace();
throw new InitializationException(MessageFormat.format("create node: {} value: {} excecption", path, nodeValue));
}
}
}
private String getNodePath(ProviderKey providerKey) {
return "/" + providerKey.getContract() + ":" + providerKey.getImplCode();
}
}
这一步利用了Spring的事件机制:在容器启动后,将服务信息与机器信息注册到ZooKeeper上。
其中服务所在机器的IP是通过 ProviderHostUtils 工具类获取的,端口号是依赖外部配置文件指定的。
ZooKeeper 客户端使用了Curator框架,该框架对ZooKeeper Api进行了封装,像重连机制、Watch的多次触发等都无需考虑。
注册路径的规则可自行设计,我的实现为:统一前置路径,例如 /marvel/rpc/provider,下一级路径为“接口全限定名 :beanName”,在下一级为“ip:端口号”。其实到这里已经很明显了,服务消费方只要根据服务的“契约”,就可获取指定节点下的所有子节点,也就是该服务对应的所有机器信息了。
服务消费方
介绍完服务提供方的启动流程,接下来看下服务消费方是如何进行服务调用的。
首先依然是一个注解,该注解用于将服务进行注入,类似Spring注入Bean一样:
package com.menghao.rpc.consumer.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Rpc服务消费方注解.
*
* @author MarvelCode.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Reference {
/**
* 用于指定implCode
*/
String value();
}
接下来是@Reference注解的处理逻辑,类比于 Spring 的@Autowired注解来看:
package com.menghao.rpc.spring;
import com.menghao.rpc.consumer.annotation.Reference;
import com.menghao.rpc.consumer.discovery.ReferenceRepository;
import com.menghao.rpc.consumer.model.ReferenceKey;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
/**
* @Reference注解增强:注入代理对象.
*
时机:属性填充时,代理对象由JDK动态代理实现
*
* @author MarvelCode
* @see ReferenceRepository
*/
public class ReferenceAutowired implements InstantiationAwareBeanPostProcessor {
private ReferenceRepository referenceRepository;
public ReferenceAutowired(ReferenceRepository referenceRepository) {
this.referenceRepository = referenceRepository;
}
@Override
public Object postProcessBeforeInstantiation(Class> beanClass, String beanName) throws BeansException {
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
Class sourceClass = bean.getClass();
do {
// 遍历成员变量找出所有被 @Reference注解的成员进行注入
for (Field field : sourceClass.getDeclaredFields()) {
Reference reference = field.getAnnotation(Reference.class);
if (reference != null) {
// 找到对应的代理对象并赋值
Object candidate = findReference(field.getType(), reference);
field.setAccessible(true);
try {
field.set(bean, candidate);
} catch (IllegalAccessException e) {
throw new BeanCreationException(beanName, "@Reference dependencies autowired failed", e);
}
}
}
sourceClass = sourceClass.getSuperclass();
} while (sourceClass != null && sourceClass != Object.class);
return pvs;
}
private Object findReference(Class sourceInterface, Reference reference) {
// 根据注入类型和指定实现
ReferenceKey referenceKey = new ReferenceKey(sourceInterface, reference.value());
return referenceRepository.getReference(referenceKey);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
同样借助了 Bean 生命周期扩展接口,不过这次处理是在“属性填充”的阶段,将 @Reference 标识的服务接口进行代理赋值。即,通过代理拦截该接口的所有方法调用,转而以Http请求的方式请求服务提供方(本篇为Http方式,以后会提供Tcp方式的讲解,主要逻辑大致相似)。
同提供方相似的,消费方同样有仓库 ReferenceRepository ,通过接口的全限定名,implCode(对应服务端的beanName)构成 ProviderKey:
package com.menghao.rpc.consumer.discovery;
import com.menghao.rpc.consumer.JdkProxyFactory;
import com.menghao.rpc.consumer.RpcAgent;
import com.menghao.rpc.consumer.model.HttpReferenceAgent;
import com.menghao.rpc.consumer.model.ReferenceKey;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Http方式代理对象仓库.
*
* @author MarvelCode
*/
public class HttpReferenceRepository implements ReferenceRepository {
private JdkProxyFactory proxyFactory;
private ProviderDiscovery providerDiscovery;
private RestTemplate restTemplate;
private final ConcurrentMap referenceCache = new ConcurrentHashMap<>(8);
public HttpReferenceRepository(JdkProxyFactory proxyFactory, ProviderDiscovery providerDiscovery,
RestTemplate restTemplate) {
this.proxyFactory = proxyFactory;
this.providerDiscovery = providerDiscovery;
this.restTemplate = restTemplate;
}
@Override
public Object getReference(ReferenceKey referenceKey) {
if (referenceCache.get(referenceKey) != null) {
return proxyFactory.getProxy(referenceCache.get(referenceKey));
}
RpcAgent referenceAgent = new HttpReferenceAgent(referenceKey, restTemplate);
// Http方式 @Reference 代理初始化
providerDiscovery.initReferenceAgent(referenceAgent);
referenceCache.putIfAbsent(referenceKey, referenceAgent);
return proxyFactory.getProxy(referenceAgent);
}
}
这里有一层缓存,意图是将已经初始化的ReferenceAgent直接代理返回,否则初始化过程需要从ZooKeeper获取一次机器信息,缓存后就可以避免同一个服务的多次ZK请求。ReferenceAgent 就是真正发送Http请求代理类,用来与服务通信以获取响应结果:
package com.menghao.rpc.consumer.model;
import com.menghao.rpc.RpcConstants;
import com.menghao.rpc.consumer.ReferenceAgent;
import com.menghao.rpc.consumer.balance.LoadBalancer;
import com.menghao.rpc.consumer.balance.RandomLoadBalancer;
import com.menghao.rpc.provider.exception.InvokeException;
import com.menghao.rpc.provider.model.RpcResponse;
import lombok.Getter;
import lombok.Setter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.List;
/**
* @Reference代理对象.
*
调用原始接口的任意方法会被该类的invoke方法代理:使用RestTemplate发送请求
* sourceInterface/implCode:唯一标识一个服务
*
* @author MarvelCode
*/
public class HttpReferenceAgent implements ReferenceAgent {
@Getter
private Class sourceInterface;
@Getter
private String implCode;
@Setter
private List providerHosts;
private RestTemplate restTemplate;
private LoadBalancer defaultBalancer = new RandomLoadBalancer();
public HttpReferenceAgent(ReferenceKey referenceKey, RestTemplate restTemplate) {
this.sourceInterface = referenceKey.getSourceInterface();
this.implCode = referenceKey.getName();
this.restTemplate = restTemplate;
}
@Override
public Object invoke(Method method, Object[] args) {
// 构造请求参数
RpcRequest apiParam = makeParam(method, args);
if (providerHosts == null || providerHosts.size() == 0) {
throw new InvokeException("There are currently no service providers available");
}
String url = select(providerHosts);
// 发送Http请求
ResponseEntity responseEntity = restTemplate.postForEntity(url, apiParam, RpcResponse.class);
if (responseEntity.getStatusCode() == HttpStatus.OK) {
if (responseEntity.getBody().getResult() != null) {
return responseEntity.getBody().getResult();
}
if (responseEntity.getBody().getThrowable() != null) {
throw new InvokeException(responseEntity.getBody().getThrowable());
}
}
return new InvokeException(MessageFormat.format(RpcConstants.LOG_RPC_PREFIX + "invoke {} response status code {}",
sourceInterface.getName() + ":" + method.getName(), responseEntity.getStatusCode()));
}
private RpcRequest makeParam(Method method, Object[] args) {
return RpcRequest.builder()
.method(method.getName())
.contract(sourceInterface.getName())
.implCode(implCode)
.args(args)
.argsType(method.getParameterTypes())
.build();
}
private String select(List providerHosts) {
// 负载均衡
String ip = defaultBalancer.select(providerHosts);
return "http://" + ip + "/marvel/rpc/entrance";
}
}
这个类封装了具体的调用过程,包含了请求实体的封装、负载均衡(随机访问),最后通过 Spring RestTemplate 发送请求(其中序列化与反序列化后面再展开讲解)。接下来看下代理对象是如何跟服务接口建立关系的,使用到了JDK动态代理:
package com.menghao.rpc.consumer;
import com.menghao.rpc.consumer.model.ReferenceAgent;
import com.menghao.rpc.provider.exception.InitializationException;
import com.menghao.rpc.provider.exception.InvokeException;
import lombok.Getter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.MessageFormat;
/**
* JDK动态代理工厂
*
对@Reference标识的成员变量进行动态代理,最终进行http请求
*
* @author MarvelCode
* @see ReferenceAgent
*/
public class JdkProxyFactory {
/**
* 根据@Reference创建代理对象
*
* @param referenceAgent 实际处理对象
* @return 代理后的对象
*/
public T getProxy(ReferenceAgent referenceAgent) {
return new RpcProxy(referenceAgent).getProxy();
}
private static class RpcProxy implements InvocationHandler {
private ReferenceAgent referenceAgent;
@Getter
private T proxy;
private static Method hashcodeMethod;
private static Method toStringMethod;
private static Method equalsMethod;
static {
try {
hashcodeMethod = Object.class.getMethod("hashCode");
toStringMethod = Object.class.getMethod("toString");
equalsMethod = Object.class.getMethod("equals", Object.class);
} catch (NoSuchMethodException e) {
throw new InitializationException("");
}
}
@SuppressWarnings("unchecked")
RpcProxy(ReferenceAgent referenceAgent) {
this.referenceAgent = referenceAgent;
proxy = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{referenceAgent.getSourceInterface()}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
if (hashcodeMethod == method) {
return System.identityHashCode(proxy);
}
if (toStringMethod == method) {
return referenceAgent.toString();
}
if (equalsMethod == method) {
return proxy == args[0];
}
return new InvokeException(MessageFormat.format("method {} not support", method.getName()));
}
return referenceAgent.invoke(method, args);
}
}
}
可以看到,除了 toString、hashCode、equals方法外,其余方法调用都转而调用 ReferenceAgent.invoke 。
到目前为之,大体的调用逻辑已经逐渐清晰,但最重要的一步,要向谁去请求呢?这时需要依赖 ZooKeeper 进行服务发现:
package com.menghao.rpc.consumer.discovery;
import com.menghao.rpc.consumer.model.ReferenceAgent;
import com.menghao.rpc.zookeeper.ChildChangeListener;
import com.menghao.rpc.zookeeper.CuratorClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 对于 @Reference代理所需信息的初始化.
*
需要根据契约及实现找到服务提供方机器信息
*
* @author MarvelCode
*/
public class ProviderDiscovery {
private final static Logger LOG = LoggerFactory.getLogger(ProviderDiscovery.class);
private CuratorClient curatorClient;
public ProviderDiscovery(CuratorClient curatorClient) {
this.curatorClient = curatorClient;
}
public void initReferenceAgent(ReferenceAgent referenceAgent) {
detect(referenceAgent);
}
private void detect(final ReferenceAgent referenceAgent) {
// 服务信息节点路径
final String nodePath = "/" + referenceAgent.getContract() + ":" + referenceAgent.getImplCode();
LOG.info("look up zookeeper node {}", nodePath);
try {
// 监听 ZK节点
curatorClient.addPathListener(nodePath, new ChildChangeListener() {
@Override
public void onChange(String path, String data) {
try {
referenceAgent.setProviderHosts(curatorClient.getChildren(nodePath));
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 设置服务机器信息列表
referenceAgent.setProviderHosts(curatorClient.getChildren(nodePath));
} catch (Exception e) {
LOG.error("zookeeper getNode child {} exception", nodePath, e);
}
}
}
可以看到,通过服务“契约”,借助 Curator 我们很容易可以获取到指定服务的服务信息列表。当然我们还需要设置对指定节点的监听,当服务所在的机器宕机时,临时服务节点就会下线,我们需要重新获取机器列表,以保证请求的有效性。
序列化与反序列化
最后就是请求的发送与响应了,来看下请求实体类定义:
package com.menghao.rpc.consumer.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* Rpc统一请求封装.
*
包含了服务契约、调用方法名、入参类型、入参数组
*
* @author MarvelCode
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RpcRequest implements Serializable {
private String contract;
private String implCode;
private String method;
private Class[] argsType;
private Object[] args;
}
包含了:接口的全限定名、implCode,这两者用于从容器中定位到服务。除此之外就是反射调用需要的三个参数了,方法、入参类型、以及具体的参数。
这些数据将会被序列化后传输到服务提供方,这里将使用 Hessian 序列化框架。考虑到本次采用的 Http 请求的方式,为了将调用代码对序列化无感知,借助了 Spring 的 HttpMessageConverter ,将Http请求和响应自动的序列化与反序列化。
package com.menghao.rpc.spring;
import com.menghao.rpc.consumer.model.RpcRequest;
import com.menghao.rpc.serialize.hessian.HessianObjectInput;
import com.menghao.rpc.serialize.hessian.HessianObjectOutput;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
/**
* RpcRequest消息转换器.
*
* @author MarvelCode.
*/
public class RpcRequestConvert extends AbstractHttpMessageConverter {
public RpcRequestConvert(MediaType supportedMediaType) {
super(supportedMediaType);
}
@Override
protected boolean supports(Class> clazz) {
return RpcRequest.class.isAssignableFrom(clazz);
}
@Override
protected RpcRequest readInternal(Class extends RpcRequest> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
HessianObjectInput input = new HessianObjectInput(inputMessage.getBody());
return (RpcRequest) input.readObject(RpcRequest.class);
}
@Override
protected void writeInternal(RpcRequest rpcRequest, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
HessianObjectOutput output = new HessianObjectOutput(outputMessage.getBody());
output.writeObject(rpcRequest);
output.flush();
}
}
package com.menghao.rpc.spring;
import com.menghao.rpc.provider.model.RpcResponse;
import com.menghao.rpc.serialize.hessian.HessianObjectInput;
import com.menghao.rpc.serialize.hessian.HessianObjectOutput;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
/**
* RpcResponse消息转换器.
*
* @author MarvelCode.
*/
public class RpcResponseConvert extends AbstractHttpMessageConverter {
public RpcResponseConvert(MediaType supportedMediaType) {
super(supportedMediaType);
}
@Override
protected boolean supports(Class> clazz) {
return RpcResponse.class.isAssignableFrom(clazz);
}
@Override
protected RpcResponse readInternal(Class extends RpcResponse> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
HessianObjectInput input = new HessianObjectInput(inputMessage.getBody());
return (RpcResponse) input.readObject(RpcResponse.class);
}
@Override
protected void writeInternal(RpcResponse rpcRequest, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
HessianObjectOutput output = new HessianObjectOutput(outputMessage.getBody());
output.writeObject(rpcRequest);
output.flush();
}
}
以上这两个转换器,装配到 RestTemplate、以及 mvc后,就能实现自动序列化的功能。接下来看下服务提供方接到请求后是如何处理的:
package com.menghao.rpc.provider;
import com.menghao.rpc.consumer.model.RpcRequest;
import com.menghao.rpc.provider.exception.InvokeException;
import com.menghao.rpc.provider.model.ProviderKey;
import com.menghao.rpc.provider.model.RpcResponse;
import com.menghao.rpc.provider.regisiter.ProviderRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.lang.reflect.Method;
/**
* 服务提供方统一入口.
*
* @author MarvelCode
*/
@RequestMapping("/marvel/rpc/entrance")
public class HttpProviderEntrance {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpProviderEntrance.class);
private ProviderRepository providerRepository;
public HttpProviderEntrance(ProviderRepository providerRepository) {
this.providerRepository = providerRepository;
}
@ResponseBody
@RequestMapping(method = RequestMethod.POST)
public RpcResponse api(@RequestBody RpcRequest rpcRequest) {
// 通过 contract、implCode定位服务bean
ProviderKey providerKey = new ProviderKey(rpcRequest.getContract(), rpcRequest.getImplCode());
Object bean = providerRepository.getProvider(providerKey);
if (bean == null) {
return new RpcResponse(new InvokeException(""));
}
try {
// 反射调用服务方法
Method method = ReflectionUtils.findMethod(bean.getClass(), rpcRequest.getMethod(), rpcRequest.getArgsType());
Object result = ReflectionUtils.invokeMethod(method, bean, rpcRequest.getArgs());
return new RpcResponse(result);
} catch (Throwable e) {
LOGGER.error("invoke exception,", e);
return new RpcResponse(e);
}
}
}
响应实体:
package com.menghao.rpc.provider.model;
import lombok.Data;
import java.io.Serializable;
/**
* Rpc调用响应实体封装.
*
* @author MarvelCode.
* @version 2018/8/1.
*/
@Data
public class RpcResponse implements Serializable {
private Object result;
private Throwable throwable;
public RpcResponse(Object result) {
this.result = result;
}
public RpcResponse(Throwable throwable) {
this.throwable = throwable;
}
}
至此,基于Http的远程服务调用的大致流程就结束了。
配置
该工程基于 Spring Boot开发,配置采用自配置方式。
依赖
com.menghao
marvelcode-rpc
1.0-SNAPSHOT
服务提供方
# 自定义端口
server.port=
# ZooKeeper所在机器 ip:port
marvel.rpc.zkServerHost =
marvel.rpc.consumerEnable=false
marvel.rpc.providerEnable=true
marvel.rpc.type=http
服务消费方
# ZooKeeper所在机器 ip:port
marvel.rpc.zkServerHost=
marvel.rpc.providerEnable=false
marvel.rpc.consumerEnable=true
marvel.rpc.type=http
总结
代码均已测试,这里测试结果就不赘述了。各位可以本地先简写一个服务接口层,在服务提供方实现,并部署等待消费方调用。消费方引入服务接口层,编写调用代码,同样部署。工程运行依赖Spring Boot框架。
本篇目的在于讲解RPC调用实现的一种思路,很多功能尚不完备,例如负载均衡采用了简单的随机访问方式。完整的Rpc框架需要包括权重负载、幂等、重试等额外功能。另外,采用 Http 方式的远程调用性能上,不如 Tcp 方式的。
有时间我会整理基于 Tcp 方式远程服务调用,敬请期待。