# dubbo-demo **Repository Path**: jsjack_wang/dubbo-demo ## Basic Information - **Project Name**: dubbo-demo - **Description**: dubbo-demo - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2019-01-24 - **Last Updated**: 2021-07-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # dubbo-demo #### 官方文档 http://dubbo.apache.org/zh-cn/docs/user/demos/registry-only.html #### dubbo架构(图 images/dubbo-architecture-future.jpg) Provider(暴露服务的服务提供方) dubbo-provider Consumer(调用远程服务的服务消费方) dubbo-consumer Registry(服务注册与发现的注册中心) zookeeper Monitor(统计服务的调用次数和调用时间的监控中心) Container(服务运行容器) Deployer(自动部署服务的本地代理) Repository(仓库用于存储服务应用发布包) Scheduler(调度中心基于访问压力自动增减服务提供者) Admin(统一管理控制台) dubbo-admin #### 项目结构 Springboot整合dubbo(https://github.com/apache/incubator-dubbo-spring-boot-project) dubbo-interface 公共接口 dubbo-provider 服务提供者8081 dubbo-provider-8082 服务提供者8082 dubbo-consumer 服务消费者9091 dubbo-consumer-9092 服务消费者9092 dubbo-admin 服务监控中心7071 #### 服务监控中心搭建 https://github.com/apache/incubator-dubbo/tree/2.5.x 调整dubbo-common(这可能是我编译器问题) com.alibaba hessian-lite 3.2.4 修改WEB-INF/dubbo.properties(zookeeper注册中心) dubbo.registry.address=zookeeper://172.22.7.14:2181 # root password dubbo.admin.root.password=123456 # guest password dubbo.admin.guest.password=123456 启动dubbo-admin 访问 http://localhost:7071/dubbo-admin/ #### 启动时检查 dubbo.reference.check=false 强制改变所有reference的check值 就算配置中有声明也会被覆盖 dubbo.consumer.check=false 设置check的缺省值 如果配置中有显式的声明不会受影响 dubbo.registry.check=false 前面两个都是指订阅成功 但提供者列表是否为空是否报错 如果注册订阅失败时也允许启动 需使用此选项 将在后台定时重试 #### 集群容错(缺省为failover重试) Failfast Cluster(快速失败) 用于非幂等性的写操作 比如新增记录 Failsafe Cluster(失败安全) 出现异常时 直接忽略 通常用于写入审计日志等操作 Failback Cluster(失败自动恢复) 后台记录失败请求 定时重发 通常用于消息通知操作 Forking Cluster(并行调用多个服务器) 只要一个成功即返回 通常用于实时性要求较高的读操作 但需要浪费更多服务资源 可通过forks="2"来设置最大并行数 Broadcast Cluster(广播调用所有提供者) 逐个调用 任意一台报错则报错 通常用于通知所有提供者更新缓存或日志等本地资源信息 #### 负载均衡(可以继承AbstractLoadBalance重写) Random LoadBalance(随机) 按权重设置随机概率 RoundRobin LoadBalance(轮询) 按公约后的权重设置轮询比率 LeastActive LoadBalance(最少活跃调用数) 相同活跃数的随机,活跃数指调用前后计数差 ConsistentHash LoadBalance(一致性 Hash) 相同参数的请求总是发到同一提供者 #### 线程模型 Dispatcher all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等 direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行 message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行 execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行 connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。 ThreadPool fixed 固定大小线程池,启动时建立线程,不关闭,一直持有 cached 缓存线程池,空闲一分钟自动删除,需要时重建 在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列 limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题 eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException #### 直连提供者 点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表 #### 只订阅 可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务 #### 只注册 #### 静态服务 有时候希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式 #### 多协议 Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议 1.不同服务不同协议 不同服务在性能上适用不同协议进行传输 比如大数据用短连接协议 小数据大并发用长连接协议 2.多协议暴露服务 需要与 http 客户端互操作 #### 多注册中心 Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的 1.多注册中心注册 2.不同服务使用不同注册中心 CRM 有些服务是专门为国际站设计的,有些服务是专门为中文站设计的 3.不同服务使用不同注册中心 多注册中心引用 #### 服务分组 当一个接口有多种实现时,可以用 group 区分 1.服务 2.引用 3.任意组 #### 多版本 当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用 可以按照以下的步骤进行版本迁移 1.在低压力时间段,先升级一半提供者为新版本 2.再将所有消费者升级为新版本 3.然后将剩下的一半提供者升级为新版本 老版本服务提供者 新版本服务提供者配置 老版本服务消费者配置 新版本服务消费者配置 如果不需要区分版本,可以按照以下的方式配置 #### 分组聚合 搜索所有分组 合并指定分组 ... #### 参数验证 maven依赖 javax.validation validation-api 1.0.0.GA org.hibernate hibernate-validator 4.2.0.Final 参数标注示例 public class ValidationParameter implements Serializable { private static final long serialVersionUID = 7158911668568000392L; @NotNull @Size(min = 1, max = 20) private String name; } 参数验证示例 public interface ValidationService { void save(@NotNull ValidationParameter parameter); } 配置 1.在客户端验证参数 2.在服务器端验证参数 #### 结果缓存 结果缓存,用于加速热门数据的访问速度,Dubbo 提供声明式缓存,以减少用户加缓存的工作量 缓存类型 lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存 threadlocal 当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问 jcache 与 JSR107 集成,可以桥接各种缓存实现 配置 或者 #### 使用泛化调用 泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成 比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现 配置 调用 GenericService barService = (GenericService) applicationContext.getBean("barService"); Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" }); #### 实现泛化调用 代码中实现GenericService接口 public class MyGenericService implements GenericService { public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException { if ("sayHello".equals(methodName)) { return "Welcome " + args[0]; } } } 通过Spring暴露泛化实现 通过 API 方式暴露泛化实现 GenericService xxxService = new XxxGenericService(); ServiceConfig service = new ServiceConfig(); service.setInterface("com.xxx.XxxService"); service.setVersion("1.0.0"); service.setRef(xxxService); service.export(); #### 回声测试 回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控 所有服务自动实现 EchoService 接口,只需将任意服务引用强制转型为 EchoService,即可使用 Spring 配置 代码 MemberService memberService = ctx.getBean("memberService"); EchoService echoService = (EchoService) memberService; String status = echoService.$echo("OK"); assert(status.equals("OK")); #### 上下文信息 上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数,参见 schema 配置参考手册 中的对应URL参数一列 RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C, 则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息。 服务消费方 // 远程调用 xxxService.xxx(); // 本端是否为消费端,这里会返回true boolean isConsumerSide = RpcContext.getContext().isConsumerSide(); // 获取最后一次调用的提供方IP地址 String serverIP = RpcContext.getContext().getRemoteHost(); // 获取当前服务配置信息,所有配置信息都将转换为URL的参数 String application = RpcContext.getContext().getUrl().getParameter("application"); // 注意:每发起RPC调用,上下文状态会变化 yyyService.yyy(); 服务提供方 // 本端是否为提供端,这里会返回true boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 获取调用方IP地址 String clientIP = RpcContext.getContext().getRemoteHost(); // 获取当前服务配置信息,所有配置信息都将转换为URL的参数 String application = RpcContext.getContext().getUrl().getParameter("application"); // 注意:每发起RPC调用,上下文状态会变化 yyyService.yyy(); // 此时本端变成消费端,这里会返回false boolean isProviderSide = RpcContext.getContext().isProviderSide(); #### 隐式参数 可以通过 RpcContext 上的 setAttachment 和 getAttachment 在服务消费方和提供方之间进行参数的隐式传递 1.在服务消费方端设置隐式参数 RpcContext.getContext().setAttachment("index", "1"); xxxService.xxx(); 2.在服务提供方端获取隐式参数 String index = RpcContext.getContext().getAttachment("index"); #### 异步调用 基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小 配置 调用代码 // 此调用会立即返回null fooService.findFoo(fooId); // 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future Future fooFuture = RpcContext.getContext().getFuture(); Foo foo = fooFuture.get(); 你也可以设置是否等待消息发出 sent="true" 等待消息发出,消息发送失败将抛出异常 sent="false" 不等待消息发出,将消息放入 IO 队列,即刻返回 如果你只是想异步,完全忽略返回值,可以配置 return="false",以减少 Future 对象的创建和管理成本 #### 本地调用 本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链 配置 1.定义 injvm 协议 2.设置默认协议 3.设置服务协议 4.优先使用 injvm 或者 注:从 2.2.0 开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务。如果希望引用远程服务可以使用一下配置强制引用远程服务 #### 参数回调 参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。 Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑 #### 事件通知 在调用之前、调用之后、出现异常时,会触发 oninvoke、onreturn、onthrow 三个事件,可以配置当事件发生时,通知哪个类的哪个方法 1.服务提供者与消费者共享服务接口 interface IDemoService { public Person get(int id); } 2.服务提供者实现 class NormalDemoService implements IDemoService { public Person get(int id) { return new Person(id, "charles`son", 4); } } 3.服务提供者配置 4.服务消费者 Callback 接口 interface Notify { public void onreturn(Person msg, Integer id); public void onthrow(Throwable ex, Integer id); } 5.服务消费者 Callback 实现 class NotifyImpl implements Notify { public Map ret = new HashMap(); public Map errors = new HashMap(); public void onreturn(Person msg, Integer id) { System.out.println("onreturn:" + msg); ret.put(id, msg); } public void onthrow(Throwable ex, Integer id) { errors.put(id, ex); } } 6.服务消费者 Callback 配置 注: callback 与 async 功能正交分解,async=true 表示结果是否马上返回,onreturn 表示是否需要回调 两者叠加存在以下几种组合情况 1.异步回调模式:async=true onreturn="xxx" 2.同步回调模式:async=false onreturn="xxx" 3.异步无回调 :async=true 4.同步无回调 :async=false #### 本地存根 远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等, 此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub [1],然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy #### 本地伪装 本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败 #### 延迟暴露 如果你的服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露。我们在 Dubbo 2.6.5 版本中对服务延迟暴露逻辑进行了细微的调整,将需要延迟暴露(delay > 0) 服务的倒计时动作推迟到了 Spring 初始化完成后进行。你在使用 Dubbo 的过程中,并不会感知到此变化,因此请放心使用。 Dubbo-2.6.5 之前版本 1.延迟到 Spring 初始化完成后,再暴露服务 2.延迟 5 秒暴露服务 Dubbo-2.6.5 及以后版本 1.所有服务都将在 Spring 初始化完成后进行暴露,如果你不需要延迟暴露服务,无需配置 delay 2.延迟 5 秒暴露服务 #### 并发控制 1.限制 com.foo.BarService 的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个 2.限制 com.foo.BarService 的 sayHello 方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个 3.限制 com.foo.BarService 的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个 或者 4.限制 com.foo.BarService 的 sayHello 方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个 或者 注:如果 都配了actives, 优先 Load Balance 均衡 配置服务的客户端的 loadbalance 属性为 leastactive,此 Loadbalance 会调用并发数最小的 Provider(Consumer端并发数) #### 连接控制 1.服务端连接控制(限制服务器端接受的连接不能超过 10 个) 或者 2.客户端连接控制(限制客户端服务使用连接不能超过 10 个) 或者 注:如果 都配了 connections, 优先 #### 连接控制 延迟连接用于减少长连接数。当有调用发起时,再创建长连接 注:该配置只对使用长连接的 dubbo 协议生效 #### 粘滞连接 粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。粘滞连接将自动开启延迟连接,以减少长连接数 #### 令牌验证 通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者 可以全局设置开启令牌验证 或者 服务级别设置 或者 还可在协议级别设置 或者 #### 路由规则 路由规则决定一次dubbo服务调用的目标服务器 分为条件路由规则和脚本路由规则 并且支持可扩展 #### 配置规则 向注册中心写入动态配置覆盖规则 该功能通常由监控中心或治理中心的页面完成 #### 服务降级 可以通过服务降级功能 临时屏蔽某个出错的非关键服务,并定义降级后的返回策略 #### 优雅停机 Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行 原理 1.服务提供方 停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器 然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭 2.服务消费方 停止时,不再发起新的调用请求,所有新的调用在客户端即报错 然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭 设置方式 设置优雅停机超时时间,缺省超时时间是 10 秒,如果超时则强制关闭 # dubbo.properties dubbo.service.shutdown.wait=15000 如果 ShutdownHook 不能生效,可以自行调用,使用tomcat等容器部署的場景,建议通过扩展ContextListener等自行调用以下代码实现优雅停机 ProtocolConfig.destroyAll(); #### 主机绑定 1.查找顺序(缺省主机 IP 查找顺序) 通过 LocalHost.getLocalHost() 获取本机地址 如果是 127.* 等 loopback 地址,则扫描各网卡,获取网卡 IP 2.主机配置(注册的地址如果获取不正确,比如需要注册公网地址) dubbo.protocol.host=205.182.23.201 3.端口配置 dubbo 20880 rmi 1099 http 80 hessian 80 webservice 80 memcached 11211 redis 6379 #### 日志适配 dubbo开始内置log4j、slf4j、jcl、jdk这些日志框架的适配 可以通过以下方式显示配置日志输出策略 java -Ddubbo.application.logger=log4j dubbo.application.logger=log4j #### 访问日志 如果你想记录每一次请求信息,可开启访问日志,类似于apache的访问日志。注意:此日志量比较大,请注意磁盘容量。 #### 服务容器 服务容器是一个standalone的启动程序 因为后台服务不需要Tomcat或JBoss等Web容器的功能 如果硬要用Web容器去加载服务提供方 增加复杂性 也浪费资源; 服务容器只是一个简单的 Main 方法,并加载一个简单的 Spring 容器,用于暴露服务 服务容器的加载内容可以扩展,内置了 spring, jetty, log4j 等加载,可通过容器扩展点进行扩展。配置配在 java 命令的 -D 参数或者 dubbo.properties 中 容器类型 Spring Container 自动加载 META-INF/spring 目录下的所有 Spring 配置(dubbo.spring.config=classpath*:META-INF/spring/*.xml) Jetty Container(启动一个内嵌 Jetty,用于汇报状态) dubbo.jetty.port=8080:配置 jetty 启动端口 dubbo.jetty.directory=/foo/bar:配置可通过 jetty 直接访问的目录,用于存放静态文件 dubbo.jetty.page=log,status,system:配置显示的页面,缺省加载所有页面 Log4j Container(自动配置 log4j 的配置,在多进程启动时,自动给日志文件按进程分目录) dubbo.log4j.file=/foo/bar.log:配置日志文件路径 dubbo.log4j.level=WARN:配置日志级别 dubbo.log4j.subdirectory=20880:配置日志子目录,用于多进程启动,避免冲突 #### ReferenceConfig 缓存 #### 分布式事务 分布式事务基于 JTA/XA 规范实现(两阶段提交) #### 线程栈自动dump 当业务线程池满时,我们需要知道线程都在等待哪些资源、条件,以找到系统的瓶颈点或异常点。dubbo通过Jstack自动导出线程堆栈来保留现场,方便排查问题 # dubbo.properties dubbo.application.dump.directory=/tmp #### netty4 dubbo 2.5.6版本新增了对netty4通信模块的支持 1.provider端 2.consumer端 #### netty4 高效的Java序列化(Kryo和FST) 1.注册被序列化类(要让Kryo和FST完全发挥出高性能,最好将那些需要被序列化的类注册到dubbo系统中) public class SerializationOptimizerImpl implements SerializationOptimizer { public Collection getSerializableClasses() { List classes = new LinkedList(); classes.add(BidRequest.class); classes.add(BidResponse.class); classes.add(Device.class); classes.add(Geo.class); classes.add(Impression.class); classes.add(SeatBid.class); return classes; } } 2.配置 #### 协议参考 (用到时参考文档) 1.dubbo:// Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况 反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低 2.rmi:// RMI协议采用JDK标准的java.rmi.*实现,采用阻塞式短连接和JDK标准序列化方式 注意:如果正在使用RMI提供服务给外部访问,同时应用里依赖了老的common-collections包的情况下,存在反序列化安全风险 3.hessian:// Hessian协议用于集成Hessian的服务,Hessian底层采用Http通讯,采用Servlet暴露服务,Dubbo缺省内嵌Jetty作为服务器实现 4.http:// 基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现 5.webservice:// 基于WebService的远程调用协议,基于ApacheCXF的frontend-simple和transports-http实现 6.thrift:// 当前dubbo支持的thrift协议是对thrift原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如service name,magic number等 7.memcached:// 基于memcached实现的RPC协议 8.redis:// 基于Redis实现的RPC协议 9.redis:// 基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写)实现的REST调用支持 #### 注册中心(官方推荐zookeeper) 1.Multicast 注册中心 2.zookeeper 注册中心 3.Redis 注册中心 4.Simple 注册中心