精通 Salesforce 异步 Apex:开发者指南(Future、Batch、Queueable 与 Scheduled)
背景与应用场景
大家好,我是一名 Salesforce 开发人员。在日常的 Salesforce 应用开发中,我们经常会遇到平台对同步执行代码的严格限制,即所谓的 Governor Limits (管控限制)。这些限制是为了确保在多租户环境中,没有任何一个组织的进程会独占共享资源,从而影响其他组织的性能。例如,一个同步的 Apex 事务 (transaction) 对 CPU 执行时间、SOQL 查询数量、DML 操作行数等都有严格的上限。
当我们面临需要处理大量数据、执行耗时复杂的业务逻辑,或者需要与外部系统进行长时间交互(即 Callouts)时,同步执行的方式就会显得力不从心,很容易就会触及这些 Governor Limits,导致操作失败。为了解决这些问题,Salesforce 平台提供了强大的异步处理框架——Asynchronous Apex (异步 Apex)。
Asynchronous Apex 允许我们将某些操作放到后台执行,每个异步任务都会在一个新的、独立的事务中运行,并且拥有更高、更独立的 Governor Limits。这不仅能帮助我们突破同步限制,还能极大地提升用户体验,因为用户无需在界面上等待一个长时间运行的操作完成。
典型的应用场景包括:
- 数据批量处理: 当需要对成千上万条记录进行更新、清洗或迁移时,例如,每季度更新所有客户的评级。
- 外部系统集成: 当需要调用外部 Web 服务的 API 时。由于网络延迟等不可控因素,API 调用可能耗时较长。将 Callout 放在异步方法中是 Salesforce 的最佳实践,尤其是在触发器 (Trigger) 场景下,同步 Callout 是不被允许的。
- 复杂计算: 当业务逻辑需要进行大量复杂的计算,可能超出同步 CPU 时间限制时,例如,为某个区域的所有商机重新计算复杂的佣金模型。
- 定时任务: 当需要按计划执行任务时,例如,每天凌晨生成一份报告、每周清理一次过期数据等。
理解并熟练运用 Asynchronous Apex 是每一位 Salesforce 开发人员构建可扩展、高性能应用的核心技能之一。接下来,我将深入探讨 Asynchronous Apex 的几种主要实现方式及其原理。
原理说明
Salesforce 平台提供了四种主要的异步执行方式,每种方式都有其特定的适用场景和特性。作为开发者,我们需要根据具体需求选择最合适的工具。
1. Future Methods (@future)
Future 方法是最早引入的一种简单的异步处理方式。通过在方法上添加 @future
注解,我们可以告诉平台这个方法应该在后台异步执行。它非常适合那些相对简单、“一次性”的后台任务。
核心特点:
- 独立事务: Future 方法在自己的事务中运行,拥有独立的、更高的 Governor Limits。
- 参数限制: 它只能接受基本数据类型(如 String, Integer)、基本数据类型的集合或数组作为参数。不支持复杂的 sObject 对象作为参数。你需要传递记录的 ID,然后在方法内部重新查询。
- 调用限制: 一个 Future 方法不能调用另一个 Future 方法,也无法追踪其执行状态(没有 Job ID 返回)。
- Callouts: 如果 Future 方法需要执行外部调用,必须在注解中指定
(callout=true)
。
2. Batch Apex (Database.Batchable 接口)
Batch Apex 是专门为处理海量数据集(从几千到数百万条记录)而设计的。它将一个大的作业分解成多个小的批次 (chunk) 来处理,每个批次都在一个独立的事务中执行,从而可以有效规避 Governor Limits。
核心特点:
- 三个核心方法:
start()
: 在作业开始时调用一次,用于收集需要处理的记录,通常返回一个Database.QueryLocator
或一个Iterable
。execute()
: 对start
方法返回的每一批数据执行一次。这是核心的业务逻辑所在。finish()
: 在所有批次处理完成后调用一次,通常用于执行一些总结性的操作,比如发送通知邮件。
- 状态保持: 如果需要在批次之间传递状态(例如,统计总共处理失败的记录数),可以实现
Database.Stateful
接口。 - 可扩展性: Batch Apex 具有极强的可扩展性,是处理大规模数据的首选方案。
3. Queueable Apex (Queueable 接口)
Queueable Apex 可以看作是 Future 方法的增强版和演进版。它克服了 Future 方法的许多限制,提供了更强大、更灵活的异步处理能力。
核心特点:
- 支持复杂类型: 它的构造函数和
execute
方法可以接受非基本数据类型,如 sObject 或自定义的 Apex 类对象。 - 作业链 (Job Chaining): 一个 Queueable 作业可以启动另一个 Queueable 作业,从而形成一个作业链。这对于处理有顺序依赖的复杂业务流程非常有用。
- 作业监控: 当你通过
System.enqueueJob()
方法提交一个 Queueable 作业时,它会返回一个 Job ID。你可以使用这个 ID 来查询AsyncApexJob
对象,从而监控作业的状态。
4. Scheduled Apex (Schedulable 接口)
Scheduled Apex 用于在特定的时间点或按固定的周期来执行 Apex 代码。它非常适合那些需要定期运行的维护性或自动化任务。
核心特点:
- 定时执行: 你可以通过用户界面(Setup -> Apex Classes -> Schedule Apex)或者通过
System.schedule()
方法来设定一个作业的执行计划。 - Cron 表达式: 调度计划使用标准的 Cron expression (Cron 表达式) 来定义,提供了非常灵活的调度选项(例如,"每周一凌晨2点")。
- 与 Batch/Queueable 结合: Scheduled Apex 的
execute
方法通常会调用一个 Batch Apex 作业或一个 Queueable Apex 作业来完成实际的业务逻辑。它本身只是一个触发器。
示例代码
以下示例均来自 Salesforce 官方文档,以确保其准确性和最佳实践。
1. Future Method 示例 (带 Callout)
这个例子展示了如何定义一个 Future 方法来进行外部 Web 服务调用。注意 @future(callout=true)
注解的使用。
// 假设有一个名为 WebServiceCallout 的类 public class WebServiceCallout { // 使用 @future(callout=true) 注解将此方法标记为可进行外部调用的异步方法 @future(callout=true) public static void sendNotification(String message) { // 构造一个对外部服务的 HTTP 请求 Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://example.com/api/notification'); request.setMethod('POST'); request.setHeader('Content-Type', 'application/json;charset=UTF-8'); // 设置请求体,这里我们将传入的消息作为 JSON 的一部分 request.setBody('{"message":"' + message + '"}'); try { // 发送请求并获取响应 HttpResponse response = http.send(request); // 检查响应状态码是否为 200 (OK) if (response.getStatusCode() == 200) { // 可以在这里处理成功的响应,例如记录日志 System.debug('Callout successful: ' + response.getBody()); } else { // 如果状态码不是 200,则记录错误信息 System.debug('Callout failed: ' + response.getStatus() + ' ' + response.getBody()); } } catch (System.CalloutException e) { // 捕获并处理可能发生的 Callout 异常 System.debug('Callout error: ' + e.getMessage()); } } }
2. Batch Apex 示例 (更新客户记录)
这个 Batch Apex 类的作用是查询所有状态为 'CA' 的客户 (Account) 记录,并在它们的描述字段后追加一段文字。
// 实现 Database.Batchable 接口,并指定处理的记录类型为 sObject public class UpdateAccountFields implements Database.Batchable<sObject> { // start 方法:收集需要处理的数据 // 返回一个 Database.QueryLocator,平台会用它来获取数据块 public Database.QueryLocator start(Database.BatchableContext bc) { // 查询所有 BillingState 为 'CA' 的客户 return Database.getQueryLocator( 'SELECT Id, Description FROM Account WHERE BillingState = \'CA\'' ); } // execute 方法:处理每一批数据 // accounts 参数是 start 方法查询结果的一个子集(一个批次) public void execute(Database.BatchableContext bc, List<Account> accounts) { // 遍历当前批次的所有客户记录 for (Account acc : accounts) { // 在现有描述后面追加信息 acc.Description = (acc.Description == null ? '' : acc.Description) + ' Updated by Batch Apex.'; } // 对当前批次的所有记录执行一次 DML 更新操作,符合最佳实践 update accounts; } // finish 方法:所有批次处理完成后执行 public void finish(Database.BatchableContext bc) { // 可以发送一封邮件通知作业已完成 System.debug('Batch job finished. All CA accounts have been updated.'); } }
3. Queueable Apex 示例 (作业链)
这个例子展示了 Queueable Apex 的一个强大功能:作业链。第一个作业 (FirstJob
) 在完成它的任务后,会启动第二个作业 (SecondJob
)。
// 第一个 Queueable 作业 public class FirstJob implements Queueable { public void execute(QueueableContext context) { // 执行第一个作业的逻辑,例如创建一个客户 Account a = new Account(Name='Acme'); insert a; // 执行完毕后,将第二个作业加入队列,形成作业链 System.enqueueJob(new SecondJob()); } } // 第二个 Queueable 作业 public class SecondJob implements Queueable { public void execute(QueueableContext context) { // 执行第二个作业的逻辑 // 例如,基于第一个作业的结果进行某些操作 System.debug('Second job executed successfully.'); } }
4. Scheduled Apex 示例 (调用 Batch Apex)
这个例子定义了一个 Scheduled Apex 类,它会在指定时间执行,并启动上面定义的 `UpdateAccountFields` Batch Apex 作业。
// 实现 Schedulable 接口 public class ScheduledBatchable implements Schedulable { // execute 方法是 Schedulable 接口的唯一方法 public void execute(SchedulableContext sc) { // 创建一个 Batch Apex 类的实例 UpdateAccountFields batchJob = new UpdateAccountFields(); // 提交 Batch 作业,可以指定批次大小(例如 100) Database.executeBatch(batchJob, 100); } } // 在匿名执行窗口中调度这个任务 (例如,每天晚上10点执行) // String cron = '0 0 22 * * ?'; // System.schedule('Update Account Fields Daily', cron, new ScheduledBatchable());
注意事项
- Governor Limits: 虽然异步 Apex 有更高的限制,但它们并非无限。例如,在单个事务中,你最多只能调用 50 个 Future 方法或将 50 个 Queueable 作业入队。你需要熟悉并遵守异步 Apex 的 Governor Limits。
- 幂等性 (Idempotency): 异步作业可能会因为平台维护或其他原因而重试。因此,设计你的异步逻辑时,应确保它是幂等的,即多次执行同一个作业不会产生意外的副作用。例如,不要使用 `++i`,而是应该基于查询结果来执行操作。
- 错误处理: 必须在代码中实现健全的 `try-catch` 逻辑来捕获和处理异常。对于 Batch Apex,可以在 `finish` 方法中检查作业状态,或者使用 `Database.Stateful` 来聚合错误信息。对于 Queueable Apex,可以实现 `Finalizer` 接口(在 Winter '22 版本引入)来处理作业成功或失败后的逻辑。
- 作业监控: 你可以通过 Setup 菜单下的 "Apex Jobs" 页面来监控所有异步作业的状态。对于 Queueable 和 Batch Apex,也可以通过 SOQL 查询 `AsyncApexJob` 对象来编程方式地获取作业状态。
- 测试: 测试异步 Apex 需要使用
Test.startTest()
和Test.stopTest()
。将你的异步方法调用放在这两个方法之间,Test.stopTest()
会强制所有异步作业在测试方法继续执行前同步完成,这样你就可以对结果进行断言 (assert)。
总结与最佳实践
Asynchronous Apex 是 Salesforce 平台不可或缺的一部分,它为开发者提供了构建强大、可扩展和用户友好型应用的工具。正确选择和使用异步模式是衡量一个优秀 Salesforce 开发者的重要标准。
选择指南:
- 当你需要一个简单的、独立的后台任务,尤其是涉及 Callout 时,请选择 Future Method。
- 当你需要处理大规模数据集(数千到数百万条记录)时,Batch Apex 是无可替代的选择。
- 当你需要比 Future 方法更强的功能,如作业链、传递复杂数据类型或获取 Job ID 进行监控时,请选择 Queueable Apex。它正在逐渐成为 Future 方法的现代替代品。
- 当你需要按预定计划(例如每天、每周、每月)自动运行任务时,请使用 Scheduled Apex,并通常由它来启动一个 Batch 或 Queueable 作业。
最后,作为开发者,我们的目标是始终以平台的健康和性能为重。即使在异步上下文中,也要遵循批量化 (Bulkification) 的原则,编写高效的 SOQL 查询,并始终考虑代码的整体性能。通过深思熟虑地应用这些异步模式,我们可以充分利用 Salesforce 平台的强大功能,同时避免触及其固有的限制。
评论
发表评论