在当前广泛流行的分布式系统中,确保系统数据的一致性和正确性是一项重大挑战。为了解决分布式事务问题,涌现了许多理论和业务实践,其中BASE理论是目前业界广泛接受的分布式一致性理论。
基于BASE理论,采用柔性事务并优先保障系统的可用性和数据的最终一致性已逐渐成为技术共识。 为了确保分布式服务的可用性和数据一致性,并防止由于网络抖动、连接超时等问题导致短时不可用的情况,根据"墨菲定律",在核心流程中增加重试和数据核对校验的动作成为提高系统鲁棒性常用的技术方案。
在此背景下EasyRetry应运而生。EasyRetry是一款基于BASE思想实现的分布式服务重试组件,旨在通过重试机制确保数据的最终一致性。它提供了控制台任务观测、可配置的重试策略、重试后执行回调以及丰富地告警配置等功能。
通过这些手段,可以对异常数据进行全面监测和回放,从而在确保系统高可用性的同时,大大提升数据的一致性。
通常的业务场景有:
但由于正常业务场景较难触发重试流程,从而导致研发测试对重试场景和流量并不重视,始终处于重要但无序的"管理真空"
Easy-RETRY 是一个针对业务系统重试流量的治理平台,其自身具有高可用高性能高负载的特点,服务特性有:
地址: http://preview.easyretry.com/ 账号: admin 密码: admin
为了小伙伴们能更快了解EasyRetry的使用场景和优势,以下新增了一些模拟案例仅供大家参考。 同时期待大家积极参与并分享自己在项目中使用EasyRetry的经验和案例,共同推动技术的发展和应用的实践。
这样可以让更多需要使用EasyRetry的小伙伴们找到适合自己的应用场景, 并充分利用EasyRetry的优势,以提升系统的可靠性和稳定性。
强通知性
在某些业务场景下,需要强制保证将通知、消息等数据发送到目标端接口,但由于网络的不确定性以及目标系统、应用、服务的不确定性,可能会造成通知消息的发送失败。
此类场景下可以使用LOCAL_REMOTE
或者ONLY_REMOTE
模式进行重试。
众所周知消息队列的异步、削峰、解耦优点, 在业务系统承担着十分重要的角色,如果保障消息的可达性就尤为重要了。
下面模拟一个常见的的下单流程。
订单中心下单完成后回抛出下单成功消息从而解耦了订单和其他业务系统的耦合关系,其他相关的业务系统只需要监听订单的下单成功的消息即可完成自己的业务逻辑。
但是若由于网络的不稳定、消息队列故障等等,可能导致消息未发送出去,这时候就需要增加重试流程来保障消息的强可达性。
然后接入EasyRetry后将变的非常简单,您只需要简单的一个注解就保障强可达性
以下代码案例仅供参考
@Retryable(scene = "create-order-success", retryStrategy = RetryType.ONLY_REMOTE)
public void sendCreateOrderSuccessMessage(Message message) {
......
// 发送消息
mqProducer.publish("主题", "key", message);
}
如果您不想使用注解的方式您可是使用手动模式
public void createOrder(Order order) {
// 其他逻辑
// 发送消息
try {
mqProducer.publish("主题", "key", order);
} catch (Exception e) {
// 发送出现异常
EasyRetryTemplate retryTemplate = RetryTaskTemplateBuilder.newBuilder()
.withExecutorMethod(RetrySendMqMessageExecutorMethod.class)
.withParam(order)
.withScene(RetrySendMqMessageExecutorMethod.SCENE)
.build();
retryTemplate.executeRetry();
}
}
@ExecutorMethodRegister(scene = RetrySendMqMessageExecutorMethod.SCENE, async = true, forceReport = true)
public class RetrySendMqMessageExecutorMethod implements ExecutorMethod {
public static final String SCENE = "retrySendMqMessageExecutorMethod";
@Override
public Object doExecute(Object objs) {
Object[] params = (Object[]) objs;
// 发送消息
mqProducer.publish("主题", "key", params[0]);
return null;
}
}
这里引用一个使用EasyRetry的小伙伴重试框架-Easy-Retry接入之路,他们线上真实的使用场景。
他们一个paas平台, 其中功能模块有“事件中心”,“审核中心”,“支付中心”等相关的一些组件。他们都有一个类似的东西,当我发起事件的时候,需要将事件通知到其他的应用, 比如
然而,我们的其他应用可能会有不可用的状态, 可能会导致回调通知的时候会报错, 所以不难想象到我们需要做一个重试机制来保障回调的可达性。
下面是一个支付中心的调用过程
用户在商品中心下单然后通过支付中心唤起收银台进行付款,第三方支付平台回调支付中心,支付平台回调商品中心完成业务流程;
但是若回调失败了就会导致商品中心和 支付中心数据不一致,这肯定不是我们所期望的。
所以需要新增一个重试机制来保障数据的最终一致性。
以下代码案例仅供参考
@Retryable(scene = "callbackProductCenter", retryStrategy = RetryType.ONLY_REMOTE)
public void callbackProductCenter(CallbackDTO callback) {
......
// 回调商品中心
String responseStr = restTemplate.postForObject("第三方接口", "参数", String.class);
}
如果您不想使用注解的方式您可是使用手动模式
public void callbackProductCenter(CallbackDTO callback) {
// 其他逻辑
try {
// 回调商品中心
String responseStr = restTemplate.postForObject("第三方接口", "参数", String.class);
} catch (Exception e) {
// 发送出现异常
EasyRetryTemplate retryTemplate = RetryTaskTemplateBuilder.newBuilder()
.withExecutorMethod(RetrySendMqMessageExecutorMethod.class)
.withParam(order)
.withScene(RetrySendMqMessageExecutorMethod.SCENE)
.build();
retryTemplate.executeRetry();
}
}
@ExecutorMethodRegister(scene = CallbackProductCenterExecutorMethod.SCENE, async = true, forceReport = true)
public class CallbackProductCenterExecutorMethod implements ExecutorMethod {
public static final String SCENE = "callbackProductCenterExecutorMethod";
@Override
public Object doExecute(Object objs) {
Object[] params = (Object[]) objs;
// 回调商品中心
String responseStr = restTemplate.postForObject("第三方接口", "参数", String.class);
return null;
}
}
在一些核心的接口上,我们总是想不断的提高接口的性能,我们知道提高接口性能的方式常用的就是异步、缓存、并行等,
下单完成后会有一些非核心的流程,主要特点实时性要求不高、耗时比较高的操作等;
一般会把这些流程进行异步化操作、进程异步化: 比如可以通过发送MQ消息, 如果保存MQ的可达性可以参考发送MQ场景
⬆️⬆️⬆️
线程异步化: 开启一个异步线程进行异步处理, 但是出现异常就会导致数据丢失,因此需要增加重试保证数据的一致性, 可以使用LOCAL_REMOTE先本地重试,如果本地重试仍未解决就上报服务端
以下代码案例仅供参考
@Retryable(scene = "sendEmail", retryStrategy = RetryType.LOCAL_REMOTE)
public void sendEmail(EmailDTO email) {
......
// 发送下单确认邮件
String responseStr = restTemplate.postForObject("邮箱地址", email, String.class);
}