分布式重试的场景应用 (Use Case Scenarios)
本文件详细介绍了 SnailJob 在实际业务中的应用场景和使用案例。
🌈 特别说明
为了帮助用户更好地理解 SnailJob 的使用场景和优势,我们提供了一些典型应用案例供参考。 同时,我们期待用户分享自己在项目中使用 SnailJob 的经验和案例,共同推动技术发展和应用实践。 这样可以帮助更多用户找到适合自己的应用场景,充分利用 SnailJob 的优势,提升系统的可靠性和稳定性。
1. 强通知场景
强通知性
在某些业务场景下,需要强制保证将通知、消息等数据发送到目标端接口。由于网络的不确定性以及目标系统、应用、服务的不确定性,可能会造成通知消息的发送失败。 此类场景下可以使用 LOCAL_REMOTE
或者 ONLY_REMOTE
模式进行重试。
1.1 消息队列场景
消息队列在业务系统中承担着异步、削峰、解耦的重要角色,保障消息的可达性尤为重要。下面以常见的下单流程为例:

订单中心下单完成后会发送下单成功消息,从而解耦了订单和其他业务系统的耦合关系。其他相关的业务系统只需要监听订单的下单成功消息即可完成自己的业务逻辑。 但是,由于网络不稳定、消息队列故障等原因,可能导致消息未发送出去,这时候就需要增加重试流程来保障消息的强可达性。

接入 SnailJob 后,您只需要一个简单的注解就能保障消息的强可达性:
代码示例
@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) {
// 发送出现异常
SnailJobTemplate 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;
}
}
1.2 回调场景
这里引用一个使用 SnailJob 的真实案例:重试框架-SnailJob接入之路
该案例中的 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) {
SnailJobTemplate 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;
}
}
1.3 异步场景
在核心接口上,我们总是希望不断提高接口性能。提高接口性能的常用方式包括异步、缓存、并行等。这里我们重点讨论异步场景:

下单完成后会有一些非核心流程,主要特点是实时性要求不高、耗时较长的操作。一般会将这些流程进行异步化处理:
- 进程异步化:通过发送 MQ 消息(可参考发送 MQ 场景)
- 线程异步化:开启异步线程处理,但出现异常会导致数据丢失,因此需要重试保证数据一致性
- 可以使用
LOCAL_REMOTE
先本地重试 - 如果本地重试未解决,则上报服务端
- 可以使用
代码示例
@Retryable(scene = "sendEmail", retryStrategy = RetryType.LOCAL_REMOTE)
public void sendEmail(EmailDTO email) {
// 发送下单确认邮件
String responseStr = restTemplate.postForObject("邮箱地址", email, String.class);
}