聊聊RPC之Provider

RPC的全称是Remote Procedure Call,它是一个分布式系统必备的一个中间件,主要解决系统之间通信的问题。

一般来说一个RPC中间件的由以下组成:

rpc

  • Provider:服务提供者,提供服务给消费者调用
  • Consumer:服务消费者,提供可以像调用本地方法一样的方式,调用远程的服务
  • Register:注册中心,为提供者、消费者提供服务地址的注册服务,当提供者不可用时即时通知调用者
  • Protocal: 通信协议,定义服务提供者和服务调用者之间的契约
  • Governance: 服务治理,为服务治理提供支持,包括限流规则、黑白名单等
  • Heartbeat: 心跳,检测服务是否可用,服务不可用即时在本地服务地址列表里清除

以上是一个的RPC中间件的组成以及各部分的职责,那么他们各个部分具体有哪些特性呢?对于使用者来说,平常的使用过程中需要哪些注意的问题呢?(接下来示例全部以Dubbo为例)

我们今天先来看看Provider,Provider对于使用者来说,都很熟悉,我们经常开发一些服务提供者给别人调用。

Provider的注册过程

在开发中开发一个Provider一般需要如下工作:

  1. 定义一个服务接口

    public interface HelloService{
      String say(String name);
    }
  2. 实现定义的接口

    public class HelloServiceImpl implements{
     @Override
     public String say(String name){
        return "Hello,"+name;  
     }
    } 
  3. 注册服务

    <dubbo:registry address="zookeeper://192.168.0.122:2181" />
    
    <dubbo:protocol name="dubbo" port="20880" />
    
    <dubbo:service interface="info.yywang.service.HelloService" ref="helloService" />
    
    <bean id="helloService" class="info.yywang.service.impl.HelloServiceImpl" />

通过以上三步的开发,我们就完成了一个HelloService服务的开发,那么在这三步的背后RPC框架帮我们做了什么呢?

  1. 在启动时,首先加载Spring配置,然后进行解析,再创建bean,注册到Spring的上下文中,这个过程是Spring的bean的加载过程的范畴,不在细说
  2. 其中在解析阶段,在spring的配置中,dubbo的标签是dubbo实现的对spring的命名空间的一个扩展。当遇到dubbo标签的配置会由DubboNamespaceHandler的定义来解析相应的节点,解析完成后注册到spring的上下文
  3. 在注册完成后,会调用ServiceConfig的export执行方法暴露,在执行export方法时,首先进行一些配置检查,和默认值设置,中间很多判断,不在详细说,直接说下面的重点
  4. 在执行export方式时,首先根据host、port等创建服务url,然后根据url的协议和配置,做一些设置,最后由ProxyFactory把URL转换成可以Invoker,然后通过Protocol.export把服务暴露出去
  5. Protocol.export根据协议获取RegistryProtocol,在以上配置中,我们使用dubbo协议。
  6. 然后调用RegistryProtocol的export执行注册
  7. 首先根据设置的注册中心地址,解析出注册中心地址,比如以上就是使用的就是zookeeper作为注册中心,服务地址是192.168.0.122。获取到Register对象。
  8. 在zookeeper的注册中,实际就是调用zkClient创建一个URL路径节点,并且添加一个注册器,订阅url节点发生变化时通知,并提交给Listener处理
  9. 至此一个服务的暴露就完成了

以上是整个服务暴露的过程,对于使用者来说,以上过程需要了解,但是对于一个使用者来说,更重要的是如何设计一个让调用者用起来爽的接口。当然如何设计一个让调用者使用起来爽的接口,是另一个话题。我们另一篇文章再讲,读者也可以思考这个问题。

Provider的开发

在开发过程中,作为Provider的接口开发者需要注意哪些问题呢?可以从以下几个方面进行探讨

  1. 扩展性

    对于一个服务接口来说,只要在线上使用过的接口,都要保证历史的接口可用。扩展性不好的接口,在每次新增需求都要修改接口或者直接新增接口。为保持接口的扩展性,在设计时需要注意一下问题

    • 保证接口职责单一,一个接口只干一件事。基于业务的本质去抽象接口,一个接口逻辑越重,面对新的需求时,越是力不从心,当接口职责足够单一时,面对新的需求可以更加灵活的去组合,实现新的业务
    • 为接口保留扩展字段,在对于一些需求时,可能字段经常增加。这时可以考虑为接口增加扩展字段,但是这时需要注意扩展字段滥用的风险,需要对于扩展字段的名称以及用途做好管控,比如可以在一个常量类里定义好可以使用的扩展字段,并注意维护
  2. 易用性

    接口的易用性可以从命名、参数、注释、设计等多个方面去考虑。在设计一个易用性的接口可以从如下方面考虑

    • 相同的操作约定的统一的命名。比如同样是查询操作,都使用query开头或者get开头,不要有的用query有的用get
    • 相同的业务含义使用统一的单词。比如相同的业务不要使用两个意义接近的单词
    • 尽量使用简单的参数类型,简单的参数类型可以减少调用者的使用成本
    • 对于复杂的参数类型,提供常用create方法,对于复杂的参数类型有些参数必填有些选填,可以提供最常用的几种create方法,方便调用者快速创建
    • 注意参数的顺序,对于不同的参数比如一个接口的顺序是getUser(int classId,int schooIId);另一个接口get(int schoolId,int classId);这种情况,调用者就非常容易使用错误
    • 对于从方法名看不出来的逻辑,需要在注释中写明
    • 对于可选参数标明,并且注明用途以及对于接口逻辑有什么影响
    • 从调用者的角度考虑,提供相应的工具方法,方便调用者使用,比如返回值,封装是否成功,调用者使用时只需要知道isSuccess就可以知道是否成功,不需要知道你是使用SUCCESS作为code还是直接用的布尔类型
  3. 兼容性

    一旦服务发布之后,以后任何的变更都需要考虑兼容性,需要从以下几个方面考虑兼容性

    • 在业务发展时,如果新老业务重合度不是非常高,尽量使用新增接口的方式
    • 接口的兼容性,在修改时通过增加参数来实现新业务,不能减少参数以及修改参数的类型
    • 在接口重构时,需要实现原有的业务逻辑,并保证输出的一致性,来保证接口的兼容性
    • 新老接口替换时,采用相同风格的命名、参数列表、使用方法等等,让接口之间自然替换
  4. 异常处理

    异常处理是否完善是评判一个接口健壮性的标准,异常从分类上来讲,有系统异常和业务异常。

    • 对于系统异常来说,一般来说是网络中断、调用超时、服务挂掉等等,这些异常在rpc框架层面做了一些处理,比如RPC的心跳机制来检测服务是否可用等,对于系统异常作为服务提供方不可避免,在服务的调用方需要考虑这个问题。
    • 对于业务异常,作为Provider的开发需要特别关注,一般业务的异常处理方式有两种,一种是把继承自RunningTimeException异常直接抛出,另一种是使用错误码的方式来报出异常。
    • 使用异常直接抛出,不同的业务异常对应不同的Exception,在调用者需要通过异常类型来区分不同的异常,在出现异常可以通过异常栈迅速定位到代码的位置,但是如果异常栈比较深,占用内存较大,在出现大量错误时,对系统的影响比较大
    • 使用错误码的方式,需要定义一套错误码标准,外部在调用时,需要对错误码做一定判断。错误码对异常做了包装,不利于错误定位,发生异常时抛出需要依赖Provider端打印的日志。一般业务性异常,使用错误码的方式
    • 在Provider实现层,对于异常一定要记录日志,方便问题排查和监控,如何做好异常日志打印也是个值得思考的话题,我自己也在思考这个问题,也欢迎大家一起来讨论这个话题
  5. 性能

    对于Provider的开发来说,性能也是一个很重要的话题,不同的接口对于性能的要求不一样。要达到不同的性能要求实现的成本也不一样。所以接口的性能指标,需要根据不同的业务场景结合实现成本来制定,对于接口性能一般可以从响应时间、并发数、吞吐量方面来衡量。一般考虑提高接口性能的方法有以下几种

    • 使用缓存,包括分布式缓存和JVM缓存,对于查询接口来说,使用缓存是提高响应时间的捷径,但是使用缓存的同时,需要考虑数据的一致性是否需要强一致。
    • 对于一个业务操作很多的接口,还可以考虑使用异步来提高接口的响应时间,对业务操作进行分类,区分哪些是可以异步完成的,交给消息中间件或者异步中间件来完成
    • 另外对于高并发的系统,需要支持集群部署,采用集群的方式支持高并发的请求
    • 回归根本,优化代码。比如尽量避免访问数据库的次数、缓存中间结果、并行处理、优化算法等等
    • 一般的业务操作的性能问题,多出在数据库操作上,所以优化数据存储也是解决性能问题的重要方式

欢迎关注我的公众号MyArtNote

MyArtNote

<think>好的,我现在需要回答用户关于Model-Driven SAL (MD-SAL) 开发细节的问题,包括架构设计、实现方式和应用场景。首先,我需要回忆一下MD-SAL的基本概念和相关知识。根据用户提供的引用内容,尤其是引用[1]提到的yang模型和数据结构定义,以及引用[2]中关于SAL的基础服务和REST API的调用,还有引用[3]中的RPC服务代码示例,这些都可能与MD-SAL相关。 首先,MD-SAL是OpenDaylight项目的核心组件,采用模型驱动的架构,通过YANG模型定义数据结构和接口,实现南北向的解耦。架构设计方面,MD-SAL分为数据存储层、服务抽象层和消费者层。数据存储层可能包括operational和config两种数据存储,如引用[1]中提到的。服务抽象层提供基础服务,如RPC和通知,消费者层则是应用通过REST API或插件调用这些服务,如引用[2]所述。 实现方式方面,YANG模型是关键,需要定义数据结构、RPC接口和通知。然后生成Java代码,使用提供的API进行服务注册和调用,如引用[3]中的代码示例。应用场景可能包括网络自动化、服务链编排和跨域协作,这些都是SDN控制器常见的应用。 接下来需要确保回答结构清晰,分点说明架构、实现和应用。同时注意用户提到的LaTeX格式要求,行内公式用$...$,独立公式用$$...$$。虽然当前问题可能不涉及数学公式,但如果有的话需要正确应用。另外,生成的相关问题要符合用户需求,可能涉及开发流程、YANG模型作用、性能优化等。 需要检查引用是否正确,引用[1]提到YANG模型和数据结构,引用[2]关于SAL的基础服务和REST API,引用[3]的RPC服务调用,这些都应在回答中适当引用。例如,在架构设计的数据存储层部分引用[1],在实现方式部分引用[2]和[3]。 最后,确保使用中文,避免Markdown格式,代码块用正确格式包裹,并生成相关问题。现在组织这些内容,确保符合用户的所有要求。</think>### Model-Driven Service Abstraction Layer (MD-SAL) 开发详解 #### 一、架构设计 MD-SAL是OpenDaylight(ODL)控制器的核心组件,采用**模型驱动架构**,通过YANG模型定义数据结构与接口,实现南向设备与北向应用解耦[^1]。其架构分为三层: 1. **数据存储层** 包含`operational data store`(运行时状态数据)和`config data store`(配置数据),通过YANG模型定义存储结构。例如: ```yang container network-topology { list topology { key "topology-id"; leaf topology-id { type string; } } } ``` 引用[1]中提到的`list & key`语法即用于定义此类数据结构。 2. **服务抽象层** 提供统一API,包括: - **RPC服务**:如设备配置下发 - **通知服务**:如网络状态变更事件 - **数据树服务**:读写`operational/config`数据 3. **消费者层** 北向应用通过REST API或插件调用MD-SAL服务,如引用[2]提到的可视化界面开发。 #### 二、实现方式 1. **YANG模型定义** - 定义数据结构(如容器、列表、叶子节点) - 声明RPC接口与通知事件 - 示例:定义一个RPC接口 ```yang rpc update-config { input { leaf device-id { type string; } leaf config-data { type binary; } } output { leaf result { type boolean; } } } ``` 2. **代码生成与绑定** 使用`maven-sal-plugin`将YANG模型编译为Java接口,自动生成: - **Data Object**:数据存储实体 - **RPC Consumer/Provider**:服务调用与实现类 - **Notification Listener**:事件监听器 3. **服务注册与调用** - **Provider端**:实现生成的服务接口并注册 ```java public class ConfigServiceImpl implements ConfigService { @Override public Future<RpcResult<UpdateConfigOutput>> updateConfig(UpdateConfigInput input) { // 实现配置下发逻辑 } } ``` - **Consumer端**:通过`RpcConsumerRegistry`获取服务代理 ```java ConfigService service = rpcConsumerRegistry.getRpcService(ConfigService.class); service.updateConfig(input); ``` 如引用[3]中的`HiService`调用模式。 #### 三、应用场景 1. **网络自动化配置** 通过YANG模型定义设备配置模板,结合RPC实现批量下发。 2. **服务链编排** 利用数据树服务监听网络状态,动态调整流量路径。例如: $$ \text{流量路径} = \arg\min_{p \in Paths} \sum_{link \in p} \text{负载}(link) $$ 3. **跨域协作** 通过MD-SAL将多厂商设备抽象为统一模型,支持混合SDN环境管理[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值