• Dubbo基本知识点笔记


    【需求】
    在大规模服务化之前,应用可能只是通过 RMI 或 Hessian 等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过 F5 等硬件进行负载均衡。

    当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。

    当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。

    接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? 为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阈值,记录此时的访问量,再以此访问量乘以机器数反推总容量。

    以上是 Dubbo 最基本的几个需求
    ---------------------------------------------------------------------------------------
    【调用关系说明--调用顺序】
    1.服务容器负责启动,加载,运行服务提供者。
    2.服务提供者在启动时,向注册中心注册自己提供的服务。
    3.服务消费者在启动时,向注册中心订阅自己所需的服务。
    4.注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
    服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
    服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

    ---------------------------------------------------------------------------------------
    【特性】
    连通性
    注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
    监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
    服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
    >>>>服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
    >>>>注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
    >>>>注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
    >>>>注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
    >>>>注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
    健壮性
    监控中心宕掉不影响使用,只是丢失部分采样数据
    数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务 --- 注册中心的数据库??
    注册中心对等集群,任意一台宕掉后,将自动切换到另一台
    注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
    服务提供者无状态,任意一台宕掉后,不影响使用
    服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
    伸缩性
    注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
    服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者


    ---------------------------------------------------------------------------------------
    【高级特性--稳定的功能】
    1.分组聚合 Tested 分组聚合返回值,用于菜单聚合等服务 特殊场景使用 可用于生产环境
    按组合并返回结果:接口一样,但有多种实现,用group区分,现在消费方需要从每种group中调用一次返回结果,合并结果返回
    2.泛化引用 Stable 泛化调用,无需业务接口类进行远程调用,用于测试平台,开放网关桥接等 可用于生产环境 Alibaba
    泛化调用,最最直接的表现就是服务消费者不需要有任何接口的实现,就能完成服务的调用
    3.泛化实现 Stable 泛化实现,无需业务接口类实现任意接口,用于Mock平台 可用于生产环境 Alibaba
    4.隐式传参 Stable 附加参数 可用于生产环境
    5.本地存根 Stable 在客户端执行部分逻辑 可用于生产环境 Alibaba
    6.本地伪装 Stable 伪造返回结果,可在失败时执行,或直接执行,用于服务降级 需注册中心支持 可用于生产环境 Alibaba
    7.延迟暴露 Stable 延迟暴露服务,用于等待应用加载warmup数据,或等待spring加载完成 可用于生产环境 Alibaba

    Zookeeper注册中心 Stable 支持基于网络的集群方式,有广泛周边开源产品,建议使用dubbo-2.3.3以上版本(推荐使用) 依赖于Zookeeper的稳定性 可用于生产环境
    Simple监控中心 Stable 支持JFreeChart统计报表 没有集群支持,可能单点故障,但故障后不影响RPC运行 可用于生产环境

    Dubbo协议 Stable 采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用) 在大文件传输时,单一连接会成为瓶颈 可用于生产环境 Alibaba

    Hessian Serialization Stable 性能较好,多语言支持(推荐使用) Hessian的各版本兼容性不好,可能和应用使用的Hessian冲突,Dubbo内嵌了hessian3.2.1的源码 可用于生产环境 Alibaba

    ......

    ---------------------------------------------------------------------------------------
    【不同粒度配置的覆盖关系】

    1.方法级优先,接口级次之,全局配置再次之。
    2.如果级别一样,则消费方优先,提供方次之。
    消费方和提供方的层级不一样时,以第一条优先

    priority:
    method > interface > global(dubbo:comsumer/dubbo:provider)
    consumer > provider

    以timeout为例

    ---------------------------------------------------------------------------------------
    【配置优先级从高到低:】

    JVM -D参数,当你部署或者启动应用时,它可以轻易地重写配置,比如,改变dubbo协议端口;
    XML, XML中的当前配置会重写dubbo.properties中的;
    Properties,默认配置,仅仅作用于以上两者没有配置时。
    1:如果在classpath下有超过一个dubbo.properties文件,比如,两个jar包都各自包含了dubbo.properties,dubbo将随机选择一个加载,并且打印错误日志。


    JVM System Properties,-D参数
    Externalized Configuration,外部化配置 -- 动态配置中心 v2.7.0以上
    ServiceConfig、ReferenceConfig等编程接口采集的配置
    本地配置文件dubbo.properties

    ---------------------------------------------------------------------------------------

    【配置内容可以在API中实现】
    API使用范围说明:API 仅用于 OpenAPI, ESB, Test, Mock 等系统集成,普通服务提供方或消费方,请采用XML 配置方式使用 Dubbo

    ---------------------------------------------------------------------------------------

    【注解配置】
    需要dubbo2.6.3以上
    ---------------------------------------------------------------------------------------
    【配置中心(v2.7.0)】
    在Dubbo中承担两个职责:
    外部化配置。启动配置的集中式存储 (简单理解为dubbo.properties的外部化存储)。
    服务治理。服务治理规则的存储与通知。
    ---------------------------------------------------------------------------------------
    【启动时检查】
    Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。

    可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

    另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。

    >>>> Spring配置示例(其它方式也可以配置)

    关闭某个服务的启动时检查 (没有提供者时报错):
    <dubbo:reference interface="com.foo.BarService" check="false" />

    关闭所有服务的启动时检查 (没有提供者时报错):
    <dubbo:consumer check="false" />

    关闭注册中心启动时检查 (注册订阅失败时报错):
    <dubbo:registry check="false" />

    ---------------------------------------------------------------------------------------

    【Failover Cluster】
    失败自动切换,当出现失败,重试其它服务器 [1]。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。

    重试次数配置如下:
    <dubbo:service retries="2" />

    <dubbo:reference retries="2" />

    <dubbo:reference>
    <dubbo:method name="findFoo" retries="2" />
    </dubbo:reference>

    ---------------------------------------------------------------------------------------
    【负载均衡策略】
    Random LoadBalance
    随机,按权重设置随机概率。
    在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
    RoundRobin LoadBalance
    轮询,按公约后的权重设置轮询比率。
    存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
    LeastActive LoadBalance
    最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
    使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
    ConsistentHash LoadBalance
    一致性 Hash,相同参数的请求总是发到同一提供者。
    当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
    算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
    缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
    缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />

    示例:
    服务端服务级别
    <dubbo:service interface="..." loadbalance="roundrobin" />
    客户端服务级别
    <dubbo:reference interface="..." loadbalance="roundrobin" />
    服务端方法级别
    <dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
    </dubbo:service>
    客户端方法级别
    <dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
    </dubbo:reference>

    ---------------------------------------------------------------------------------------
    【【【线程模型】】】

    -----此部分需要对线程池,Netty等比较了解,先大概知道有这么个东西

    如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。

    但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。

    如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。

    ---------------------------------------------------------------------------------------
    【开发测试可能需要用到的操作】

    >直连
    在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
    ==>思考的方案:用版本号将服务端的各种服务区分开,然后在消费端消费时设置url(具体配置参考官方文档),绕过zk(也是要配置和连接的)指定特定的服务,进行测试。但是消费端是否可以配置版本号呢?这感觉直连测试没啥用啊,另外,本地启动一个zk,单节点的也不是很困难啊。。。

    >只订阅
    dev-provider,可以不注册,只订阅,让dev-consumer直连dev-provider。这个是针对线上线下都用的一个zk的情况

    >只注册
    如果有两个镜像环境,两个注册中心,有一个服务只在其中一个注册中心有部署,另一个注册中心还没来得及部署,而两个注册中心的其它应用都需要依赖此服务。这个时候,可以让服务提供者方只注册服务到另一注册中心,而不从另一注册中心订阅服务。

    >多协议
    Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。

    >group区分同接口
    当一个接口有多种实现时,可以用 group 区分。

    >多版本
    当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

    可以按照以下的步骤进行版本迁移:

    在低压力时间段,先升级一半提供者为新版本
    再将所有消费者升级为新版本
    然后将剩下的一半提供者升级为新版本

    老版本服务提供者配置:
    <dubbo:service interface="com.foo.BarService" version="1.0.0" />
    新版本服务提供者配置:
    <dubbo:service interface="com.foo.BarService" version="2.0.0" />
    老版本服务消费者配置:
    <dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
    新版本服务消费者配置:
    <dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
    如果不需要区分版本,可以按照以下的方式配置:
    <dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

    >热点缓存
    消费端设置缓存策略,可以使用本地缓存数据,尽量少地调用服务端的内容。
    lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
    threadlocal 当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。
    jcache 与 JSR107 集成,可以桥接各种缓存实现。

    >回声测试

    Spring 配置:
    <dubbo:reference id="memberService" interface="com.xxx.MemberService" />
    代码:

    // 远程服务引用
    MemberService memberService = ctx.getBean("memberService");
    EchoService echoService = (EchoService) memberService; // 强制转型为EchoService
    // 回声测试可用性
    String status = echoService.$echo("OK");
    assert(status.equals("OK"));

    >V2.7.0之后,消费端支持异步调用
    >Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。

    -->用到了再详细研究

    >参数回调 可以研究一下

    >访问日志

    如果你想记录每一次请求信息,可开启访问日志,类似于apache的访问日志。注意:此日志量比较大,请注意磁盘容量。
    将访问日志输出到当前应用的log4j日志:
    <dubbo:protocol accesslog="true" />
    将访问日志输出到指定文件:
    <dubbo:protocol accesslog="http://10.20.160.198/wiki/display/dubbo/foo/bar.log" />

    >dubbo不支持分布式事务

    >线程dump

    当业务线程池满时,我们需要知道线程都在等待哪些资源、条件,以找到系统的瓶颈点或异常点。dubbo通过Jstack自动导出线程堆栈来保留现场,方便排查问题

    默认策略:
    导出路径,user.home标识的用户主目录
    导出间隔,最短间隔允许每隔10分钟导出一次

    指定导出路径:
    # dubbo.properties
    dubbo.application.dump.directory=/tmp
    <dubbo:application ...>
    <dubbo:parameter key="dump.directory" value="/tmp" />
    </dubbo:application>

    >简易注册中心
    mvn dubbo:registry 没用过

    ===================================================

    【最佳实践】

    强烈建议不要在服务的实现类中有 applicationContext.getBean() 的调用,全部采用 IoC 注入的方式使用 Spring的Bean。
    如果实在要调 getBean(),可以将 Dubbo 的配置放在 Spring 的最后加载。
    如果不想依赖配置顺序,可以使用 <dubbo:provider delay=”-1” />,使 Dubbo 在 Spring 容器初始化完后,再暴露服务。
    如果大量使用 getBean(),相当于已经把 Spring 退化为工厂模式在用,可以将 Dubbo 的服务隔离单独的 Spring 容器。

    API包
    建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。

    粒度
    服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。
    服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。
    不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。

    版本
    每个接口都应定义版本号,为后续不兼容升级提供可能,如: <dubbo:service interface="com.xxx.XxxService" version="1.0" />。
    建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。
    当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。
    服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。
    业务种类,不建议用枚举,避免后期的兼容问题。可以用String

    异常
    建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。
    如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。
    查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try...catch,并且不能进行有效处理。
    服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。

    调用
    不要只是因为是 Dubbo 调用,而把调用 try...catch 起来。try...catch 应该加上合适的回滚边界上。
    Provider 端需要对输入参数进行校验。如有性能上的考虑,服务实现者可以考虑在 API 包上加上服务 Stub 类来完成检验。

    配置

    在 Provider 端尽量多配置 Consumer 端属性
    原因如下:
    1.作服务的提供方,比服务消费方更清楚服务的性能参数,如调用的超时时间、合理的重试次数等
    2.在 Provider 端配置后,Consumer 端不配置则会使用 Provider 端的配置,即 Provider 端的配置可以作为 Consumer 的缺省值 。否则,Consumer 会使用 Consumer 端的全局设置,这对于 Provider 是不可控的,并且往往是不合理的
    timeout:方法调用的超时时间
    retries:失败重试次数,缺省是 2 [2]
    loadbalance:负载均衡算法 [3],缺省是随机 random。还可以配置轮询 roundrobin、最不活跃优先 [4] leastactive 和一致性哈希 consistenthash 等
    actives:消费者端的最大并发调用限制,即当 Consumer 对一个服务的并发调用到上限后,新调用会阻塞直到超时,在方法上配置 dubbo:method 则针对该方法进行并发限制,在接口上配置 dubbo:service,则针对该服务进行并发限制

    在 Provider 端配置合理的 Provider 端属性
    threads:服务线程池大小
    executes:一个服务提供者并行执行请求上限,即当 Provider 对一个服务的并发调用达到上限后,新调用会阻塞,此时 Consumer 可能会超时。在方法上配置 dubbo:method 则针对该方法进行并发限制,在接口上配置 dubbo:service,则针对该服务进行并发限制

    配置 Dubbo 缓存文件
    提供者列表缓存文件:
    <dubbo:registry file=”${user.home}/output/dubbo.cache” />
    注意:
    可以根据需要调整缓存文件的路径,保证这个文件不会在发布过程中被清除;
    如果有多个应用进程,请注意不要使用同一个文件,避免内容被覆盖;
    该文件会缓存注册中心列表和服务提供者列表。配置缓存文件后,应用重启过程中,若注册中心不可用,应用会从该缓存文件读取服务提供者列表,进一步保证应用可靠性。

    异常与日志的处理
    尽可能携带完整的上下文信息,比如出错原因,出错的机器地址,调用对方的地址,连的注册中心地址,使用 Dubbo 的版本等。
    尽量将直接原因写在最前面,所有上下文信息,在原因后用键值对显示。
    抛出异常的地方不用打印日志,由最终处理异常者决定打印日志的级别,吃掉异常必需打印日志。
    打印 ERROR 日志表示需要报警,打印 WARN 日志表示可以自动恢复,打印 INFO 表示正常信息或完全不影响运行。
    建议应用方在监控中心配置 ERROR 日志实时报警,WARN 日志每周汇总发送通知。
    RpcException 是 Dubbo 对外的唯一异常类型,所有内部异常,如果要抛出给用户,必须转为 RpcException。
    RpcException 不能有子类型,所有类型信息用 ErrorCode 标识,以便保持兼容。

    单元和集成测试
    单元测试统一用 JUnit 和 EasyMock,集成测试用 TestNG,数据库测试用 DBUnit。
    保持单元测试用例的运行速度,不要将性能和大的集成用例放在单元测试中。
    保持单元测试的每个用例都用 try...finally 或 tearDown 释放资源。
    减少 while 循环等待结果的测试用例,对定时器和网络的测试,用以将定时器中的逻辑抽为方法测试。

    防止空指针和下标越界
    这是我最不喜欢看到的异常,尤其在核心框架中,我更愿看到信息详细的参数不合法异常。这也是一个编写健壮程序的开发人员,在写每一行代码都应在潜意识中防止的异常。基本上要能确保每一次写完的代码,在不测试的情况下,都不会出现这两个异常才算合格。

    尽早失败也应该成为潜意识,在有传入参数和状态变化时,均在入口处全部断言。一个不合法的值和状态,在第一时间就应报错,而不是等到要用时才报错。因为等到要用时,可能前面已经修改其它相关状态,而在程序中很少有人去处理回滚逻辑。这样报错后,其实内部状态可能已经混乱,极易在一个隐蔽分支上引发程序不可恢复。

    这里的可测性主要指 Mock 的容易程度,和测试的隔离性。至于测试的自动性,可重复性,非偶然性,无序性,完备性(全覆盖),轻量性(可快速执行),一般开发人员,加上 JUnit 等工具的辅助基本都能做到,也能理解它的好处,只是工作量问题。这里要特别强调的是测试用例的单一性(只测目标类本身)和隔离性(不传染失败)。现在的测试代码,过于强调完备性,大量重复交叉测试,看起来没啥坏处,但测试代码越多,维护代价越高。经常出现的问题是,修改一行代码或加一个判断条件,引起 100 多个测试用例不通过。时间一紧,谁有这个闲功夫去改这么多形态各异的测试用例?久而久之,这个测试代码就已经不能真实反应代码现在的状况,很多时候会被迫绕过。最好的情况是,修改一行代码,有且只有一行测试代码不通过。如果修改了代码而测试用例还能通过,那也不行,表示测试没有覆盖到。另外,可 Mock 性是隔离的基础,把间接依赖的逻辑屏蔽掉。可 Mock 性的一个最大的杀手就是静态方法,尽量少用。

  • 相关阅读:
    【leetcode】对称二叉树
    【leetcode】判断回文数
    053686
    053685
    053684
    053683
    053682
    053681
    053680
    053477
  • 原文地址:https://www.cnblogs.com/bruceChan0018/p/14854548.html
Copyright © 2020-2023  润新知