1 Dubbo并发控制
http://ifeve.com/dubbo-para-control/
Dubbo调用模型
1、当consumer发起一个请求时,首先经过active limit(参数actives)进行方法级别的限制,其实现方式为CHM中存放计数器(AtomicInteger),请求时加1,请求完成(包括异常)减1,如果超过actives则等待有其他请求完成后重试或者超时后失败;
2、从多个连接(connections)中选择一个连接发送数据,对于默认的netty实现来说,由于可以复用连接,默认一个连接就可以。不过如果你在压测,且只有一个consumer,一个provider,此时适当的加大connections确实能够增强网络传输能力。但线上业务由于有多个consumer多个provider,因此不建议增加connections参数;
dubbo协议是单一长连接,所以保证服务提供者开放的链接数,满足消费者的需求
3、连接到达provider时(如dubbo的初次连接),首先会判断总连接数是否超限(acceps),超过限制连接将被拒绝;
4、连接成功后,具体的请求交给io thread处理。io threads虽然是处理数据的读写,但io部分为异步,更多的消耗的是cpu,因此iothreads默认cpu个数+1是比较合理的设置,不建议调整此参数;
5、数据读取并反序列化以后,交给业务线程池处理,默认情况下线程池为fixed,且排队队列为0(queues),这种情况下,最大并发等于业务线程池大小(threads),如果希望有请求的堆积能力,可以调整queues参数。如果希望快速失败由其他节点处理(官方推荐方式),则不修改queues,只调整threads;
6、execute limit(参数executes)是方法级别的并发限制,原理与actives类似,只是少了等待的过程,即受限后立即失败;
7、tps,控制指定时间内(默认60s)的请求数。注意目前dubbo默认没有支持该参数,需要加一个META-INF/dubbo/com.alibaba.dubbo.rpc.Filter文件,文件内容为: tps=com.alibaba.dubbo.rpc.filter.TpsLimitFilter
从上面的分析,可以看出如果consumer数*actives>provider数*threads且queues=0,则会存在部分请求无法申请到资源,重试也有很大几率失败。 当需要对一个接口的不同方法进行不同的并发控制时使用executes,否则调整threads就可以。
常用性能调优参数
协议dubbo:protocol
name | provider | dubbo | 服务通过什么协议暴露 | |
port | provider | 20880 | 当前协议的服务通过本机哪个端口暴露,调用服务都从这个接口进入 | |
dubbo协议:适合小数据量(建议小于100K)大并发的服务调用,以及消费者机器远大于生产者机器数的情况,不适合传输大数据量的服务比如文件、视频等,除非请求量很低。
服务提供者
参数名 | 作用范围 | 默认值 | 说明 | 备注 |
threads | provider | 200 | 业务处理线程池大小 | |
iothreads | provider | CPU+1 | io线程池大小 | |
queues | provider | 0 | 线程池队列大小,当线程池满时,排队等待执行的队列大小, 建议不要设置,当线程程池时应立即失败, 重试其它服务提供机器,而不是排队,除非有特殊需求 | |
acceptes | provider | 0 | 服务提供方最大可接受连接数 | 0表示不限制 |
executes | provider | 0 | 服务提供者每服务每个方法最大可并行执行请求数 | 0表示不限制 |
为防止被大量连接撑挂,可在服务提供方限制大接收连接数,以实现服务提供方自我保护。
<dubbo:protocol name="dubbo" accepts="1000" />
connections | consumer | 0 | 对每个提供者的最大连接数, rmi、http、hessian等短连接协议表示限制连接数, Dubbo等长连接协表示建立的长连接个数 | Dubbo协议默认共享一个长连接 (建议优先使用默认) |
actives | consumer | 0 | 每服务消费者每服务每个方法最大并发调用数 | 0表示不限制 |
<dubbo:service connections=”0”>或<dubbo:reference connections=”0”>表示该服务使用JVM共享长连接。(缺省)
<dubbo:service connections=”1”>或<dubbo:reference connections=”1”>表示该服务使用独立长连接。
<dubbo:service connections=”2”>或<dubbo:reference connections=”2”>表示该服务使用独立两条长连接。
一个连接可以同时处理10个、100个并发都是没有问题的
2 Dubbo性能优化
3 Dubbo分布式服务框架常见问题解答汇总
4 细节问题
4.1 springboot消费者、提供者扫描包,扫描使用@Reference/@Service的包。
配置文件中spring.dubbo.scan
4.2 服务提供未能注册,则消费者报com.alibaba.dubbo.rpc.RpcException或java.lang.NullPointerException
此情况不会执行Mock降级
4.3 服务降级:reference标签里,有一个参数mock。服务端响应超时后,会降级到Mock
https://www.jianshu.com/p/ce8de35986cf
该参数有四个值,false
,default
,true
,或者Mock类的全类名
。分别代表如下含义:
false
,不调用mock服务。true
,当服务调用失败时,使用mock服务(需要实现接口的Mock类)。default
,当服务调用失败时,使用mock服务。force
,强制使用Mock服务(不管服务能否调用成功)。(使用xml配置
不生效,使用ReferenceConfig
API可以生效)
4.4 在DubboAdmin中也可以设置消费者降级,为指定消费者点击“容错”,调用失败返回null
4.5 服务提供者,消费者,引用接口API的包路径要一致,所以最好把API接口单独打JAR包
4.6 2.5版dubbo 服务端同时使用 dubbo.@Service、spring.@Transactional,事务会失效
5 DubboAdmin
5.1 可以下载原始DubboAdmin,是war项目,在webapps\dubbo- admin-2.x.x\WEB-INF目录下,找到dubbo.properties,修改zk注册中心地址后Tomcat运行即可,端口默认8080
下载:https://github.com/apache/incubator-dubbo/releases(dubbo-2.6.1以前版本Source中有)
https://github.com/apache/incubator-dubbo/tree/dubbo-2.6.0
5.2 dubbo-2.6.1以后的版本提供了新的管理工具incubator-dubbo-ops,在Dubbo官网“运维管理”中有安装使用说明
5.3 消费者页面上有服务的屏蔽/容错功能。容错:设置服务提供者报错后返回NULL,屏蔽:消费者不远程调用直接降级返回NULL。
6 dubbo+springboot 引起注解事务失效、无法注册问题
springboot中集成spring事务的时候,遇到了一个大坑。如果(springboot+dubbo)中添加 @Service、@Transactional 两个注解的时候,就不能进行dubbo服务注册了。spring-boot-starter-dubbo依赖dubbo2.5,老版本不支持注解的事务,于是提高到dubbo2.6.2版本(2018年)
1,首先提升dubbo的版本到2.6以后,再就是需要明确接口名称。
@EnableAspectJAutoProxy,@EnableTransactionManagement,spring.aop.proxy-target-class=true,maven 添加spring-boot-starter-aop依赖
2,好多文章说要么注解实现dubbo,配置文件实现Transactional;要么注解实现Transactional,配置文件实现dubbo,不能同时注解两个,应该是老版本原因,现在可以同时支持了。
3,添加dubbo2.6.2的zk依赖
<!--spring-boot-dubbo依赖-->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>1.0.0</version>
<exclusions>
<!--去除com.alibaba依赖-->
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加2.6.2的dubbo依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!--zookeeper客户端相关的curator依赖-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<!--添加zookeeper依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.5</version>
</dependency>
配置dubbo-xml
当xml中错误提示 http://code.alibabatech.com/schema/dubbo 或 http://dubbo.apache.org/schema/dubbo uri is not regist 时请核对maven中依赖的JAR是否缺失
<?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:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="dubbo-service" />
<!-- 注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://localhost:2181" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.example.dubboservice.service.DubboService" ref="dubboService" />
<!-- 和本地bean一样实现服务 -->
<bean id="dubboService" class="com.example.dubboservice.service.impl.DobboServiceImpl" />
</beans>
7 请尽量在Provider端为Consumer配置一些默认属性。依据Dubbo配置的优先级,若某一个Consumer自定义配置后其会优先使用自定义配置。没有自定义的Consumer会采用Provider设置的默认配置
如:timeout,retries,loadbalance,active 这些都是消费者属性,但是服务提供者可以为每个消费者配置默认配置。
8 版本过渡自然迭代发布
当一个接口实现,出现不兼容升级时,可以用版本号过渡:
利用dubbo该特性,我们能够实现一些功能的灰度发布,实现步骤如下:
- 接口旧的实现定义version="1.0.0",接口新的实现version="2.0.0"
- Consumer端定义version="*"
这样定义Provider和Consumer后,新旧接口实现各承担50%
的流量;
retries重试
默认是重试两次(有其他提供者重试其他,没有则继续重试当前提供者)。调用响应超时/报错时启动重试。重试一定要注意幂等性设计,或是修改类接口不能重试。
响应已超时重试为例:响应超时后消费者端会执行重试,但是第一次请求的服务提供者若没异常会继续执行。此时可能会有两个消费者线程同时执行,所以关键的业务消费一定要做好幂等性。
Mock服务降级类
本地伪装通常用于服务降级,例如某验权服务,当服务提供方全部挂掉后无法连接、响应超时、抛出异常,客户端不抛出异常,而是通过 Mock 数据,取代远程返回结果。
经测试mock与retries有兼容问题,mock不会等待全部重试完成后返回,而是第一次请求失败/超时就直接mock降级返回,此时浏览器已经返回了降级的信息。但是服务端仍在自己继续重试。
服务端幂等性设计
超时后如果重试,一定要在服务提供者端做幂等设计。或是选择性的取消重试。
记录处理日志
如果想记录每次请求信息,可开启访问日志,类似于Ngnix的访问日志。注意:此日志量比较大,请注意磁盘容量。使用方式(如果配置局部,全局访问日志就会失效):
配置全局:
<dubbo:provider accesslog="/app/dubbo-demo.log"/>
配置局部:
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" accesslog="/app/demo.log"/>
<dubbo:service interface="com.alibaba.dubbo.demo.TestService" ref="testService" accesslog="/app/test.log"/>
日志格式样式:
[2017-11-22 10:23:20] 172.18.1.205:56144 -> 172.18.1.205:20886 - com.alibaba.dubbo.demo.DemoService:1.0.0 sayHello(java.lang.String) ["afei"]
方法参数和返回值POJO必须实现Serializable接口
方法参数对象要实现序列化接口,否则会出现异常 或 执行mock降级
参数及返回值自定义实现List、Map、Number、Date、Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失
RpcContext
url->View
通过隐式参数传递信息
RpcContext 是一个 ThreadLocal 的临时状态记录器,可以通过 RpcContext 的 setAttachment()
和 getAttachment()
在Consumer和Provider之间进行参数的隐式传递,例如Controller层拦截登录token,把根据token得到的memberId传给dubbo服务就能使用隐式参数传递的方式,setAttachment(String key, String value)设置的 KV 对,在完成一次远程调用会被清空,即多次远程调用要多次设置。使用方式:
1.服务端set:
RpcContext.getContext().setAttachment("CRT_MEMBER_ID", "13828886888");
2.客户端get:
RpcContext.getContext().getAttachment("CRT_MEMBER_ID")
异常处理
dubbo处理异常的逻辑依次执行:
1 如果是checked异常,直接抛出
2 方法签名上有声明,直接抛出
3 异常类与接口类在同一个JAR包,直接抛出
4 JDK自带异常,直接抛出
5 dubbo自身异常[RpcException],直接抛出
6 其他包装成RuntimeException抛出
例如JAVA异常:普通的JDK异常,例如:RuntimeException,IOException等。Dubbo都会将异常正常传递给消费者,消费者可以捕获到对应的异常。
下图是消费者端接收到的服务端抛出的异常信息。可以看到是服务端代码DubboServiceImpl中抛出异常。
自定义异常:
RPCException:dubbo框架跑出的异常,例如:超时
本地存根
在消费者方充当提供者的代理,可以在sub代理中增加验证/缓存等功能,以减少RPC远程调用
http://dubbo.apache.org/zh-cn/docs/user/demos/local-stub.html
XML与properties冲突
同时配置XML与properties时,properties失效
Spring 初始化死锁
老版本的Spring可能出现。解决方法是配置<dubbo:provider deplay= -1>,使dubbo在Spring初始化完成后再暴露服务。