文档章节

也来谈谈RPC

suemi94
 suemi94
发布于 2016/06/20 14:00
字数 1834
阅读 14
收藏 0

问题描述

要想实现RPC,本质上是服务发布方与服务调用方的通信,主要集中在以下几个问题上:

  • 调用方首先需要知道如何找到对应服务所在的远程Server
  • 调用方与服务提供方能以特定的双方能够解析的数据格式封装自己的调用请求和调用结果
  • 对多个服务提供方Server提供的不同服务以及它们的状态应该予以有效的管理,比如新增服务需要注册该服务
  • 对于不同系统间的调用可能还需要一定的容错和调度

首先我们给出一个最简单的解决思路,就是直接利用HTTP协议,将调用方看做客户端,而服务提供方看做服务器,以JSON来做数据传输,默认一套key-value的含义解释,直接发起请求,然后等待响应并解析返回的JSON串。听起来似乎可行,但是却有许多弊端:

  • 每个调用者需要提前保有一份服务者以及它提供的相应服务的记录信息
  • 大量的Http请求与解析响应的代码混杂在原有的业务代码中,对于使用服务的开发者不透明,恶劣的设计
  • 对于服务变更和新增的情况不友好
  • 如果有多个提供者提供同一服务,不利于增加均衡策略,不利于扩展

。。。。

还有很多,这就是为什么许多RPC框架和标准存在的原因,下面我们就主要从RMI出发来继续探讨这一问题。

我们以Apache的CXF框架来说明RMI调用,首先介绍一下JAX-WS和JAX-RS的概念。

  • JAX-WS:JavaTM API forXML-Based Web Services,JAX-WS是面向消息的,每次请求的时候指定了请求的方法。J
  • JAX-RS:JavaTM API forRESTful Web Services,AX-RS是面向资源的。后则将网络上的东西当做一种资源,每次请求都是对该资源进行操作,比如对资源的增删查改。

SOAP和JAXRS分别代表了这两种模式。

JAXRS

首先关注服务提供方:

在ApplicatiionContext.xml中加入


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jaxrs="http://cxf.apache.org/jaxrs"
	xsi:schemaLocation="http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
	default-lazy-init="true">

	<description>Apache CXF的Restful Web Service配置</description>
	
	<!-- jax-rs endpoint定义  -->
	<jaxrs:server id="serviceContainer" address="/jaxrs">
		<jaxrs:serviceBeans>
			<ref bean="userJaxRsService" />
		</jaxrs:serviceBeans>
		<jaxrs:providers>
			<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider" />
		</jaxrs:providers>
	</jaxrs:server>

	<!-- WebService的实现Bean定义,注册服务 -->
	<bean id="userJaxRsService" class="jseed.webservice.jaxrs.service.UserJaxRsService" />
</beans>

然后添加服务


/**
 * WebService服务端实现类.
 * 
 * 为演示方便,直接调用了Service层.客户端实现见功能测试用例.
 * 
 */
@Path("/user")
public class UserJaxRsService {

	private static Logger logger = LoggerFactory.getLogger(UserJaxRsService.class);

	@Autowired
	private UserService userService;
	
	@GET
	@Path("/{id}.xml")
	@Produces(MediaTypes.APPLICATION_XML_UTF_8)
	public UserDTO getAsXml(@PathParam("id") Long id) {
		Optional<User> user = userService.load(id);
		if (!user.isPresent()) {
			String message = "用户不存在(id:" + id + ")";
			logger.warn(message);
			throw buildException(Status.NOT_FOUND, message);
		}
		return bindDTO(user.get());
	}
	
	@GET
	@Path("/{id}.json")
	@Produces(MediaTypes.JSON_UTF_8)
	public UserDTO getAsJson(@PathParam("id") Long id) {
		Optional<User> user = userService.load(id);
		if (!user.isPresent()) {
			String message = "用户不存在(id:" + id + ")";
			logger.warn(message);
			throw buildException(Status.NOT_FOUND, message);
		}
		return bindDTO(user.get());
	}

	private UserDTO bindDTO(User user) {
		UserDTO dto = BeanMapper.map(user, UserDTO.class);
		// 补充Dozer不能自动绑定的属性
		return dto;
	}

	private WebApplicationException buildException(Status status, String message) {
		return new WebApplicationException(Response.status(status).entity(message).type(MediaTypes.TEXT_PLAIN_UTF_8)
				.build());
	}
	
}

接下来看服务调用方

/**
 * 对基于JAX-RS的实现Restful的测试
 * 
 * @author calvin
 */
public class UserJaxRsFT extends BaseFunctionalTestCase {

	private static String resourceUrl = baseUrl + "/cxf/jaxrs/user";

	private RestTemplate restTemplate = new RestTemplate();

	@Test
	public void getUser() {
		UserDTO user = restTemplate.getForObject(resourceUrl + "/{id}.xml", UserDTO.class, 1L);
		assertThat(user.getLoginName()).isEqualTo("admin");
		assertThat(user.getName()).isEqualTo("管理员");
		assertThat(user.getTeamId()).isEqualTo(1);

		try {
			user = restTemplate.getForObject(resourceUrl + "/{id}.json", UserDTO.class, 1L);
		} catch (HttpStatusCodeException e) {
			fail(e.getMessage());
		}
		assertThat(user.getLoginName()).isEqualTo("admin");
		assertThat(user.getName()).isEqualTo("管理员");
		assertThat(user.getTeamId()).isEqualTo(1);
	}
}

SOAP

首先解释一下SOAP的概念:一种轻量的、简单的、基于XML的交换数据协议规范,也就是说它定义了Server之间通信的规范。SOAP的使用方式较为复杂,故我们只截取其中的核心部分阐述。

首先,看服务提供方:

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xsi:schemaLocation="http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
	default-lazy-init="true">

	<description>Apache CXF的 SOAP Web Service配置</description>
	
	<!-- jax-ws endpoint定义  -->
	<jaxws:endpoint address="/soap/accountservice">
		<jaxws:implementor ref="accountSoapService" />
	</jaxws:endpoint>

	<!-- WebService的实现Bean定义 -->
	<bean id="accountSoapService" class="jseed.webservice.soap.service.AccountSoapServiceImpl" />
</beans>

然后是服务的实现:

/**
 * WebService服务端实现类.
 * 
 * 为演示方便,直接调用了Dao层.客户端实现见功能测试用例.
 * 
 */
// serviceName指明WSDL中<wsdl:service>与<wsdl:binding>元素的名称, endpointInterface属性指向Interface类全称.
@WebService(serviceName = "AccountService", endpointInterface = "org.springside.examples.showcase.webservice.soap.AccountSoapService", targetNamespace = WsConstants.NS)
// 增加inbound/outbound SOAP内容的日志
@Features(features = "org.apache.cxf.feature.LoggingFeature")
public class AccountSoapServiceImpl implements AccountSoapService {

	private static Logger logger = LoggerFactory.getLogger(AccountSoapServiceImpl.class);

	@Autowired
	private AccountService accountService;

	@Autowired
	private Validator validator;

	/**
	 * @see AccountSoapService#getUser(Long)
	 */
	@Override
	public GetUserResult getUser(Long id) {
		GetUserResult result = new GetUserResult();
		try {

			Validate.notNull(id, "id参数为空");

			User user = accountService.getUser(id);

			Validate.notNull(user, "用户不存在(id:" + id + ")");

			UserDTO dto = BeanMapper.map(user, UserDTO.class);
			result.setUser(dto);

			return result;

		} catch (IllegalArgumentException e) {
			return handleParameterError(result, e);
		} catch (RuntimeException e) {
			return handleGeneralError(result, e);
		}
	}

	/**
	 * @see AccountSoapService#searchUser(String, String)
	 */
	@Override
	public SearchUserResult searchUser(String loginName, String name) {
		SearchUserResult result = new SearchUserResult();
		try {
			List<User> userList = accountService.searchUser(loginName, name);

			List<UserDTO> dtoList = BeanMapper.mapList(userList, UserDTO.class);
			result.setUserList(dtoList);
			return result;
		} catch (RuntimeException e) {
			return handleGeneralError(result, e);
		}
	}

	/**
	 * @see AccountSoapService#createUser(UserDTO)
	 */
	@Override
	public IdResult createUser(UserDTO user) {
		IdResult result = new IdResult();
		try {
			Validate.notNull(user, "用户参数为空");

			User userEntity = BeanMapper.map(user, User.class);
			BeanValidators.validateWithException(validator, userEntity);

			accountService.saveUser(userEntity);

			return new IdResult(userEntity.getId());
		} catch (ConstraintViolationException e) {
			String message = StringUtils.join(BeanValidators.extractPropertyAndMessageAsList(e, " "), "\n");
			return handleParameterError(result, e, message);
		} catch (RuntimeException e) {
			if (Exceptions.isCausedBy(e, DuplicateKeyException.class)) {
				String message = "新建用户参数存在唯一性冲突(用户:" + user + ")";
				return handleParameterError(result, e, message);
			} else {
				return handleGeneralError(result, e);
			}
		}
	}

	private <T extends WSResult> T handleParameterError(T result, Exception e, String message) {
		logger.error(message, e.getMessage());
		result.setError(WSResult.PARAMETER_ERROR, message);
		return result;
	}

	private <T extends WSResult> T handleParameterError(T result, Exception e) {
		logger.error(e.getMessage());
		result.setError(WSResult.PARAMETER_ERROR, e.getMessage());
		return result;
	}

	private <T extends WSResult> T handleGeneralError(T result, Exception e) {
		logger.error(e.getMessage());
		result.setDefaultError();
		return result;
	}
}

下面我们再来看服务调用者:

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	                    http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
	                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"
	default-lazy-init="true">
	<description>Apache CXF Web Service Client端配置</description>

	<context:property-placeholder ignore-resource-not-found="true"
			location="classpath*:/application.functional.properties,
          			  classpath*:/application.functional-local.properties" />	
	
	<jaxws:client id="accountWebServiceClient" serviceClass="org.springside.examples.showcase.webservice.soap.AccountSoapService"
		address="${baseUrl}/cxf/soap/accountservice" />
</beans>

调用流程

/**
 * 
 * 以用JAXWS的API, 根据AccountWebService接口自行创建.
 * 使用在Spring applicaitonContext.xml中用<jaxws:client/>,根据AccountWebService接口创建的Client.
 * 
 */
public class AccountWebServiceWithDynamicCreateClientFT extends BaseFunctionalTestCase {
	
	//predefine client
	@Autowired
	private AccountSoapService accountWebServiceClient;


	//dynamic client
	public AccountSoapService creatClient() {
		String address = baseUrl + "/cxf/soap/accountservice";

		JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean();
		proxyFactory.setAddress(address);
		proxyFactory.setServiceClass(AccountSoapService.class);
		AccountSoapService accountWebServiceProxy = (AccountSoapService) proxyFactory.create();

		// (可选)演示重新设定endpoint address.
		((BindingProvider) accountWebServiceProxy).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
				address);

		// (可选)演示重新设定Timeout时间
		Client client = ClientProxy.getClient(accountWebServiceProxy);
		HTTPConduit conduit = (HTTPConduit) client.getConduit();
		HTTPClientPolicy policy = conduit.getClient();
		policy.setReceiveTimeout(600000);

		return accountWebServiceProxy;
	}

	public void getUser() {
		GetUserResult response = accountWebServiceClient.getUser(1L);
		return response;
	}

	/**
	 * 测试搜索用户
	 */
	@Test
	public void searchUser() {
		SearchUserResult response = accountWebServiceClient.searchUser(null, null);
		return response;
	}

	/**
	 * 测试创建用户.
	 */
	@Test
	public void createUser() {
		User user = UserData.randomUser();
		UserDTO userDTO = BeanMapper.map(user, UserDTO.class);

		IdResult response = accountWebServiceClient.createUser(userDTO);
		assertThat(response.getId()).isNotNull();
		GetUserResult response2 = accountWebServiceClient.getUser(response.getId());
	}

}

© 著作权归作者所有

suemi94
粉丝 17
博文 21
码字总数 57847
作品 0
海淀
程序员
私信 提问
谈谈RPC与套接字以及信号

Rpc的linux实现是很简洁的,这是有目共睹的。事实上rpc机制在linux上只是其n分之一而已,windows才是rpc大行其道的舞台。可是为何rpc没有在unix/linux上得到大规模应用呢?这还得从unix的设计...

晨曦之光
2012/04/10
201
0
谈谈后台服务的RPC和路由管理

版权声明:本文由廖念波原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/147 来源:腾云阁 https://www.qcloud.com/community 为什么要用RPC和路由管理 ...

偶素浅小浅
2016/11/05
25
0
简单谈谈服务间的连接

最近又把RPC框架的底层协议翻出来回顾了一遍,梳理一下有什么可以学习和借鉴的地方,重点看了一下RPC连接的实现方案。看了之后,觉得可以谈谈服务间连接的方式及区别,所以按照自己的理解写了...

谢东升Forest
2017/07/18
0
0
RocketMQ(六):namesrv再探

匠心零度 转载请注明原创出处,谢谢! RocketMQ网络部署图 NameServer:在系统中是做命名服务,更新和发现 broker服务。 Broker-Master:broker 消息主机服务器。 Broker-Slave: broker 消息...

匠心零度
2018/10/29
0
0
【Dubbo】-- 掌握Dubbo原理你需要明白这些

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 https://blog.csdn.net/YYZZHC999/article/details/98490704 把这些问题都搞明白说明源码没白...

杨晓慧_Hepburn
08/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

PCB设计-Allegro软件入门系列-allegro环境变量和快捷键

Allegro作为一款高速PCB设计的EDA软件,有完善的约束规则设计和信号完整性电源完整性仿真等各种专业工具深受电子行业从业者喜爱。 对于该软件来说,电子从业者接触最多的就是Allegro的画板功...

demyar
27分钟前
4
0
腾讯云存储

1、进入腾讯云平台,创建 2、进入配置查看域名 3、查看KEY参数 4、将2、3中的参数录入到cms后台 然后点测试按钮查看情况

迅睿CMS-PHP开源CMS程序
29分钟前
4
0
ES 6.x 版本 待验证的CURL命令查询操作

1. 查询数据 curl -H "Content-Type: application/json" -XGET http://elastic:123456@127.0.0.1:9200/alias1/_search -d '{"query": {"match_all": {}}}' 2. 添加数据 如果有不指定ID可以自......

coord
36分钟前
4
0
如何写好论文摘要:研究人员不得不知的小秘诀

我们为何要写摘要? 它的目的为何? 简而言之,摘要的目的就是简单的讨论这篇文章让读者更容易的了解这篇文。 它能在读者与作者之间搭起一条桥梁。当您搜索信息时,您无法一下阅读整篇文章,...

论文辅导员
39分钟前
4
0
移动端、PC端(前后台)、小程序常用的UI框架

移动端、PC端(前后台)、小程序常用的UI框架 1.移动端UI库 ①.Vant UI 官方地址:https://youzan.github.io/vant/#/zh-CN/intro github地址:https://github.com/youzan/vant 优点:用来做移...

jason_kiss
39分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部