基于HTTP的远程服务调用

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

前言

    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 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 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 方式远程服务调用,敬请期待。

转载于:https://my.oschina.net/marvelcode/blog/2050486

你可能感兴趣的:(基于HTTP的远程服务调用)