Dubbo 服务治理:设计实现中的健壮性原则
Dubbo 负责服务的暴露、调用与治理,堪称应用运转的'经络'。其本身实现的健壮性直接决定了系统的稳定性,这一点不言而喻。
这里梳理一些 Dubbo 在设计中用到的核心原则和方法,供参考。
日志规范
日志是发现问题、排查问题的第一手资料。很多时候日志质量被忽视,缺乏明确的使用约定。重视 Log 的使用,提高信息浓度至关重要。日志过多或过于混乱,会导致关键信息被淹没。
要有效利用这个工具,建议注意以下几点:
严格约定 WARN、ERROR 级别记录的内容
- WARN:表示可以自动恢复的问题,无需人工介入。
- ERROR:表示需要人工介入处理的问题。
有了这样的约定,监控系统一旦捕获 ERROR 级别日志便触发报警,既能及时响应又能避免误报。过多的报警会让人产生疲劳,导致对真正重要的警报失去警惕,使 ERROR 日志失去意义。此外,辅以人工定期查看 WARN 级别信息,有助于评估系统的'亚健康'状态。
日志中尽量收集关键信息
哪些是关键信息?
- 现场信息:即排查问题所需的数据。例如服务调用失败时,应包含 Dubbo 版本、服务提供者 IP、注册中心类型、调用的具体服务及方法等。如果这些信息缺失,事后人工收集往往面临现场已复原的困境,极大增加排查难度。
- 原因与建议:如果可能,在日志中给出问题的原因和解决方法。这能让维护和问题解决变得简单,减少对特定专家(往往是实现者)的依赖。
避免同类问题重复记录
同一个或一类异常日志连续出现几十遍的情况很常见。人工排查时极易遗漏其中夹杂的关键异常。在可预见的情况下,有必要通过逻辑控制来避免重复打印。
例如为某个问题设置一个标志位,出问题后打日志并设置标志,问题恢复后清除标志。虽然略显繁琐,但这能保证日志的信息浓度,让监控更有效。
资源界限设置
资源是有限的,CPU、内存、IO 等皆然。不能因为外部请求或数据不受限而导致系统崩溃。
线程池大小与饱和策略
Server 端用于处理请求的 ExecutorService 必须设置上限。任务等待队列应使用有界队列,避免资源耗尽。当任务等待队列饱和时,选择一个合适的饱和策略,保证系统平滑劣化。
在 Dubbo 中,默认饱和策略通常是丢弃数据,或者仅返回请求超时。达到饱和时,说明已达到服务提供方的负荷上限,此时应在饱和策略的操作中记录日志以发出监控警报。切记不要重复多次记录(缺省的饱和策略通常没有这些附加操作)。根据警报频率,可以决定扩容调整等,避免系统问题被忽略。
集合容量控制
如果能确保进入集合的元素可控且数量较少,则可以放心使用普通集合。这是大部分情况。如果不能保证,则使用有界集合。当到达界限时,选择一个合适的丢弃策略。
容错与恢复机制
高可用组件必须容忍其依赖组件的失败。
服务注册中心
目前服务注册中心使用数据库保存服务提供者和消费者的信息。注册中心集群之间也通过数据库同步数据,以感知其他注册中心上提供者的变化。注册中心会在内存中保存一份提供者和消费者数据。当数据库不可用时,注册中心独立对外提供服务以保证正常运转,只是无法获取其他注册中心的数据。当数据库恢复时,重试逻辑会将内存中修改的数据写回数据库,并拉取数据库中的新数据。
服务消费者
服务消费者从注册中心拿到提供者列表后,会将其保存到内存和磁盘文件中。这样即使注册中心宕机,消费者也能正常运转,甚至可以在注册中心宕机过程中重启消费者。消费者启动时,若发现注册中心不可用,会读取保存在磁盘文件中的提供者列表。重试逻辑保证注册中心恢复后,能更新最新信息。
重试延迟策略
这是上一部分的延伸场景。在 Dubbo 的实际运行中,主要涉及两种情况。
数据库上的活锁
注册中心会定时更新数据库一条记录的时间戳,以便集群中其他注册中心感知其存活。过期注册中心及其相关数据会被清除。数据库正常时,这个机制运行良好。但是数据库负荷高时,其上的每个操作都会变慢。这就可能出现活锁:
A 注册中心判定 B 已过期并删除其数据,B 发现数据丢失后尝试重新写入,如此反复。这些反复的操作又加重了数据库的负荷,恶化问题。
可以使用以下逻辑解决:
当 B 发现自己数据被删除时(写入失败),选择等待一段时间再重试。重试时间可以选择指数级增长,如第一次等 1 分钟,第二次 10 分钟,第三次 100 分钟。

