极客时间已完结课程限时免费阅读

11 | 服务发布和引用的实践

11 | 服务发布和引用的实践-极客时间

11 | 服务发布和引用的实践

讲述:胡忠想

时长08:40大小3.98M

专栏第 4 期,我给你讲解了服务发布和引用常见的三种方式:Restful API、XML 配置以及 IDL 文件。今天我将以 XML 配置方式为例,给你讲解服务发布和引用的具体实践以及可能会遇到的问题
首先我们一起来看下 XML 配置方式,服务发布和引用的具体流程是什么样的。

XML 配置方式的服务发布和引用流程

1. 服务提供者定义接口
服务提供者发布服务之前首先要定义接口,声明接口名、传递参数以及返回值类型,然后把接口打包成 JAR 包发布出去。
比如下面这段代码,声明了接口 UserLastStatusService,包含两个方法 getLastStatusId 和 getLastStatusIds,传递参数一个是 long 值、一个是 long 数组,返回值一个是 long 值、一个是 map。
package com.weibo.api.common.status.service;
public interface UserLastStatusService {
* @param uids
* @return
*/
public long getLastStatusId(long uid);
/**
*
* @param uids
* @return
*/
public Map<Long, Long> getLastStatusIds(long[] uids);
}
2. 服务提供者发布接口
服务提供者发布的接口是通过在服务发布配置文件中定义接口来实现的。
下面我以一个具体的服务发布配置文件 user-last-status.xml 来给你讲解,它定义了要发布的接口 userLastStatusLocalService,对外暴露的协议是 Motan 协议,端口是 8882。并且针对两个方法 getLastStatusId 和 getLastStatusIds,通过 requestTimeout="300" 单独定义了超时时间是 300ms,通过 retries="0" 单独定义了调用失败后重试次数为 0,也就是不重试。
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
">
<motan:service ref="userLastStatusLocalService"
requestTimeout="50" retries="2" interface="com.weibo.api.common.status.service.UserLastStatusService"
basicService="serviceBasicConfig" export="motan:8882">
<motan:method name="getLastStatusId" requestTimeout="300"
retries="0" />
<motan:method name="getLastStatusIds" requestTimeout="300"
retries="0" />
</motan:service>
</beans>
然后服务发布者在进程启动的时候,会加载配置文件 user-last-status.xml,把接口对外暴露出去。
3. 服务消费者引用接口
服务消费者引用接口是通过在服务引用配置文件中定义要引用的接口,并把包含接口定义的 JAR 包引入到代码依赖中。
下面我再以一个具体的服务引用配置文件 user-last-status-client.xml 来给你讲解,它定义服务消费者引用了接口 commonUserLastStatusService,接口通信协议是 Motan。
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
">
<motan:protocol name="motan" default="true" loadbalance="${service.loadbalance.name}" />
<motan:basicReferer id="userLastStatusServiceClientBasicConfig"
protocol="motan" />
<!-- 导出接口 -->
<motan:referer id="commonUserLastStatusService" interface="com.weibo.api.common.status.service.UserLastStatusService"
basicReferer="userLastStatusServiceClientBasicConfig" />
</beans>
然后服务消费者在进程启动时,会加载配置文件 user-last-status-client.xml 来完成服务引用。
上面所讲的服务发布和引用流程看似比较简单,但在实际使用过程中,还是有很多坑的,比如在实际项目中经常会遇到这个问题:一个服务包含了多个接口,可能有上行接口也可能有下行接口,每个接口都有超时控制以及是否重试等配置,如果有多个服务消费者引用这个服务,是不是每个服务消费者都必须在服务引用配置文件中定义?
你可以先思考一下这个问题,联系自己的实践经验,是否有理想的解决方案呢?

服务发布和引用的那些坑

根据我的项目经验,在一个服务被多个服务消费者引用的情况下,由于业务经验的参差不齐,可能不同的服务消费者对服务的认知水平不一,比如某个服务可能调用超时了,最好可以重试来提供调用成功率。但可能有的服务消费者会忽视这一点,并没有在服务引用配置文件中配置接口调用超时重试的次数,因此最好是可以在服务发布的配置文件中预定义好类似超时重试次数,即使服务消费者没有在服务引用配置文件中定义,也能继承服务提供者的定义。这就是下面要讲的服务发布预定义配置。
1. 服务发布预定义配置
以下面的服务发布配置文件 server.xml 为例,它提供了一个服务 contentSliceRPCService,并且明确了其中三个方法的调用超时时间为 500ms 以及超时重试次数为 3。
<motan:service ref="contentSliceRPCService" interface="cn.sina.api.data.service.ContentSliceRPCService"
basicService="serviceBasicConfig" export="motan:8882" >
<motan:method name="saveContent" requestTimeout="500"
retries="3" />
<motan:method name="deleteContent" requestTimeout="500"
retries="3" />
<motan:method name="updateContent" requestTimeout="500"
retries="3" />
</motan:service>
假设服务引用的配置文件 client.xml 的内容如下,那么服务消费者就会默认继承服务发布配置文件中设置的方法调用的超时时间以及超时重试次数。
<motan:referer id="contentSliceRPCService" interface="cn.sina.api.data.service.ContentSliceRPCService" basicReferer="contentSliceClientBasicConfig" >
</motan:referer>
通过服务发布预定义配置可以解决多个服务消费者引用服务可能带来的配置复杂的问题,这样是不是最优的解决方案呢?
实际上我还遇到过另外一种极端情况,一个服务提供者发布的服务有上百个方法,并且每个方法都有各自的超时时间、重试次数等信息。服务消费者引用服务时,完全继承了服务发布预定义的各项配置。这种情况下,服务提供者所发布服务的详细配置信息都需要存储在注册中心中,这样服务消费者才能在实际引用时从服务发布预定义配置中继承各种配置。
这里就存在一种风险,当服务提供者发生节点变更,尤其是在网络频繁抖动的情况下,所有的服务消费者都会从注册中心拉取最新的服务节点信息,就包括了服务发布配置中预定的各项接口信息,这个信息不加限制的话可能达到 1M 以上,如果同时有上百个服务消费者从注册中心拉取服务节点信息,在注册中心机器部署为百兆带宽的情况下,很有可能会导致网络带宽打满的情况发生。
面对这种情况,最好的办法是把服务发布端的详细服务配置信息转移到服务引用端,这样的话注册中心中就不需要存储服务提供者发布的详细服务配置信息了。这就是下面要讲的服务引用定义配置。
2. 服务引用定义配置
以下面的服务发布配置文件为例,它详细定义了服务 userInfoService 的各个方法的配置信息,比如超时时间和重试次数等。
<motan:service ref="userInfoService" requestTimeout="50" retries="2" interface="cn.sina.api.user.service.UserInfoService" basicService="serviceBasicConfig">
<motan:method name="addUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="modifyUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addUserTags" requestTimeout="300" retries="0"/>
<motan:method name="delUserTags" requestTimeout="300" retries="0"/>
<motan:method name="processUserCacheByNewMyTriggerQ" requestTimeout="300" retries="0"/>
<motan:method name="modifyObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="add" requestTimeout="300" retries="0"/>
<motan:method name="deleteObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="getUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttrList" requestTimeout="300" retries="1" />
<motan:method name="getAllUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttr2" requestTimeout="300" retries="1" />
</motan:service>
可以像下面一样,把服务 userInfoService 的详细配置信息转移到服务引用配置文件中。
<motan:referer id="userInfoService" interface="cn.sina.api.user.service.UserInfoService" basicReferer="userClientBasicConfig">
<motan:method name="addUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="modifyUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addUserTags" requestTimeout="300" retries="0"/>
<motan:method name="delUserTags" requestTimeout="300" retries="0"/>
<motan:method name="processUserCacheByNewMyTriggerQ" requestTimeout="300" retries="0"/>
<motan:method name="modifyObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="addObjectUserInfo" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectUserPortrait" requestTimeout="300" retries="0"/>
<motan:method name="updateObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="add" requestTimeout="300" retries="0"/>
<motan:method name="deleteObjectManager" requestTimeout="300" retries="0"/>
<motan:method name="getUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttrList" requestTimeout="300" retries="1" />
<motan:method name="getAllUserAttr" requestTimeout="300" retries="1" />
<motan:method name="getUserAttr2" requestTimeout="300" retries="1" />
</motan:referer>
这样的话,服务发布配置文件可以简化为下面这段代码,是不是信息精简了许多。
<motan:service ref="userInfoService" requestTimeout="50" retries="2" interface="cn.sina.api.user.service.UserInfoService" basicService="serviceBasicConfig">
</motan:service>
在进行类似的服务详细信息配置,由服务发布配置文件迁移到服务引用配置文件的过程时,尤其要注意迁移步骤问题,这就是接下来我要给你讲的服务配置升级问题。
3. 服务配置升级
实际项目中,我就经历过一次服务配置升级的过程。由于引用服务的服务消费者众多,并且涉及多个部门,升级步骤就显得异常重要,通常可以按照下面步骤操作。
各个服务消费者在服务引用配置文件中添加服务详细信息。
服务提供者升级两台服务器,在服务发布配置文件中删除服务详细信息,并观察是否所有的服务消费者引用时都包含服务详细信息。
如果都包含,说明所有服务消费者均完成升级,那么服务提供者就可以删除服务发布配置中的服务详细信息。
如果有不包含服务详细信息的服务消费者,排查出相应的业务方进行升级,直至所有业务方完成升级。

总结

今天我给你介绍了 XML 配置方式的服务发布和引用的具体流程,简单来说就是服务提供者定义好接口,并且在服务发布配置文件中配置要发布的接口名,在进程启动时加载服务发布配置文件就可以对外提供服务了。而服务消费者通过在服务引用配置文件中定义相同的接口名,并且在服务引用配置文件中配置要引用的接口名,在进程启动时加载服务引用配置文件就可以引用服务了。
在业务具体实践过程中可能会遇到引用服务的服务消费者众多,对业务的敏感度参差不齐的问题,所以在服务发布的时候,最好预定义好接口的各种配置。在服务规模不大,业务比较简单的时候,这样做比较合适。但是对于复杂业务,虽然服务发布时预定义好接口的各种配置,但在引用的服务消费者众多且同时访问的时候,可能会引起网络风暴。这种情况下,比较保险的方式是,把接口的各种配置放在服务引用配置文件里。
在进行服务配置升级过程时,要考虑好步骤,在所有服务消费者完成升级之前,服务提供者还不能把服务的详细信息去掉,否则可能会导致没有升级的服务消费者引用异常。

思考题

如果你在实际项目中采用过 XML 配置的服务发布和应用方式,是否还遇到过其他问题?你是如何解决的呢?
欢迎你在留言区写下自己的思考,与我一起讨论。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 8

提建议

上一篇
10 | Dubbo框架里的微服务组件
下一篇
12 | 如何将注册中心落地?
unpreview
 写留言

精选留言(33)

  • 沙漠之鹰
    2018-09-23
    一个接口上百个方法,设计上是否不合理
    28
  • echo_陈
    2018-09-15
    遇到过版本变更时序列化兼容问题 我们用的dubbo,经常会出现,某个dubbo接口的API升级:包含新增方法,或者某个方法的入参或者返回值新增字段。 我们的服务提供者更新消费者并不是一定要更新,如果我的api改动没有改动某个消费者调用的方法或者那个消费者可以兼容提供者的改动,那么消费者是可以不升级的。也就是允许系统中存在:服务提供者依赖的api是1.1版本,而服务消费者依赖的的api的jar包是1.0版本……这样的情况。 以前用hessian2做序列化方式,服务提供者单方面引用新版本api,老的消费者一样能正常调用。可是有同事听说FST序列化更快更强……于是某些接口改动了序列化方式为FST……发现这时依赖老版本api的服务都异常了…… 经验:性能是一方面,但也要考虑业务兼容性
    展开

    作者回复: 是的,兼容性很重要

    15
  • Jerry
    2018-11-02
    这个服务引用配置文件放在哪里

    作者回复: 放在客户端本地

    共 2 条评论
    7
  • LYy
    2019-05-26
    对于超大规模的分布式系统来说 服务详细配置信息放到消费者侧的方案不可取 因为涉及服务众多 底层服务根本不知道有多少上层服务对其有依赖 所以服务详细描述文件还是要放在配置中心里 解耦提供者与消费者 同时对提供者和配置中心提出要求 1 提供者:保证接口前向兼容 2 配置中心:明确性能规格 设置限流策略
    展开
    5
  • 智哥哥
    2018-10-24
    <motan:service ref="userLastStatusLocalService" requestTimeout="50" retries="2" interface="com.weibo.api.common.status.service.UserLastStatusService" basicService="serviceBasicConfig" export="motan:8882"> <motan:method name="getLastStatusId" requestTimeout="300" retries="0" /> <motan:method name="getLastStatusIds" requestTimeout="300" retries="0" /> </motan:service> 这里的userLastStatusLocalService定义在哪呢? 可以提个建议:每一章都把源码打包附加到文章尾部可以吗?只提供部分源码比较容易把人弄的越来越晕
    展开

    作者回复: 极客目前没有这个功能,这里是抽取的代码片段,可以看下开源motan的实现

    4
  • 波波安
    2018-10-13
    使用dubbo遇到的一些坑。 之前没有形成规范,很多发布的服务都没有配置retry-time和和timeout导致经常出一些莫名其妙的问题。有些业务办理接口没有做幂等,接口超时导致重试,产生脏数据等。 后续主要通过团队形成一些规范来规避问题。
    展开
    4
  • Sonny721
    2018-09-20
    多个服务消费者调用了服务提供者A,如果服务提供者A的接口参数发生变化,那所有消费者都需要变更,是否有好的解决方案呢?
    共 4 条评论
    3
  • Geek_af3d01
    2018-09-17
    目前只会spring cloud ,dubbo没有细研究过 只是看dubbo 官网会使用,觉得体系都差不多 没有太多机会去实战
    3
  • 俯瞰风景.
    2021-10-07
    服务发布和引用的步骤是: 1、服务提供者定义接口,包括接口名,方法名,方法入参,方法出参等 2、在服务发布配置文件中配置接口名、通信协议、端口号,方法调用超时时间和重试次数 3、在服务引用配置文件中配置接口名、通信协议 服务发布预定义配置:为了解决多个服务消费者引用同一个服务的问题,可以用服务发布预定义配置的方式来减少消费者端的配置工作。 服务引用定义配置:但是随着服务方法数量的增多,每个方法都有自己的超时时间和重试次数信息,服务提供者所发布服务的详细配置信息都需要存储在注册中心中,配置信息占据内存容量会过大,每个消费者通过注册中心拉取最新配置信息时,会导致网络带宽被打满的情况。这种情况下,最好是把服务发布端的详细服务配置信息转移到服务引用端,注册中心中就不需要存储服务提供者发布的详细服务配置信息。 服务配置升级:服务升级就是把配置信息从服务发布配置文件中迁移到服务引用配置文件中。需要按照特定的流程来,先通过升级两台服务器观察升级是否成功,如果没问题再进行全部服务器的升级。
    展开
    3
  • 技术修行者
    2020-11-06
    目前用到的微服务,都是基于REST的接口,没有使用XML形式的接口,对于文中提到的服务引用文件,不是很清楚,是不是可以把它理解为Feign中的服务接口文件?
    共 1 条评论
    2
  • 2018-12-26
    服务发布预定义配置如果遇到提供者接口超级多的极端情况的话建议把配置信息转移到服务信用配制中。请问老师如果这么做的话那不就每一个客户端都要配置一份了吗?这样的话客户端配制参差不齐的问题又出现了,总觉得这里跟您前面讲的矛盾了
    3
  • 第一装甲集群司令克莱...
    2021-12-06
    motan这么多方法定义在xml,上一个见到的,还是struts2.
    1
  • 2019-06-14
    MOTAN是细到方法级别的,我们的JSF是到接口级别,我认为调用的详细信息应该放在调用端。方法级别针对每个调用方的限流MOTAN是怎么实现的?
    2
  • 张迪
    2019-02-22
    客户端是怎么知道服务端在哪里,ip地址或者域名没看到在配置文件中配置呀
    共 1 条评论
    1
  • shine
    2019-02-16
    课程中提到的服务引用配置文件:user-last-status-client.xml和client.xml是同一个文件吗? 那些坑里,1. 服务发布预定义配置 和 2. 服务引用定义配置 是不是只有这二种配置方式,而且是相互对立的,要根据实际情况选择?
    2
  • WeDataSphere
    2021-12-09
    如果服务提供者和服务消费者不在同一台机器,是不是消费者机器需要拷贝提供者的jar,以达到本地调用的效果?
  • 木石头
    2021-06-03
    目前我们的微服务用的springcloud,服务间调用用Feigh,这个Feign调用应该是另一种形式的服务发布和应用吧
    共 1 条评论
  • 悦开心
    2021-02-23
    请教:在服务配置升级过程中,服务消费者众多,如何观察是否所有服务消费者引用时都包含了服务详细信息?
    1
  • 悦开心
    2021-02-23
    请教下,在服务配置升级过程中,如何观察所有服务消费者引用时都包含了服务详细信息?
  • 马振
    2020-05-28
    使用mota协议,不需要引入包之类的操作吗?