精通 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 平台的强大功能,同时避免触及其固有的限制。

评论

此博客中的热门博文

Salesforce 登录取证:深入解析用户访问监控与安全

Salesforce Experience Cloud 技术深度解析:构建社区站点 (Community Sites)

精通 Salesforce Email Studio:咨询顾问指南之 AMPscript 与数据扩展实现动态个性化邮件