Spring Boot异步迁移Shopify客户出现数据错乱问题求助
我正在将客户数据迁移至Shopify,每个国家(如FR、DE、IT等)对应一个Shopify店铺,迁移目标店铺由customer.shopId确定。因数据量较大,采用异步方法处理:通过ShopifyRestClientFactory根据shopId获取对应店铺的ShopifyRestClient,同时有服务维护内部客户ID与Shopify ID的映射表。
但使用@Async注解后出现数据错乱:本该迁移到法国店铺的客户出现在德国店铺,反之亦然,映射表数据也不一致;同步执行时一切正常。现有逻辑是获取所有客户,逐个调用CustomerExporter的upsertShopify方法。附上相关代码,恳请协助排查问题。
相关代码
CustomerExporter
@Component @RequiredArgsConstructor public class CustomerExporter { private ShopifyRestClient shopifyRestClient; private ShopifyGraphQLClient shopifyGraphQLClient; private final ShopifyRestClientFactory shopifyRestClientFactory; private final ShopifyGraphQLClientFactory shopifyGraphQLClientFactory; private final ShopifyImportedCustomerSyncService shopifyImportedCustomerSyncService; private final ShopifyCustomerMapper mapper; @Transactional @Async("CustomerExportThredExec") public void upsertShopify(Customer customer) { init(customer.getShopId()); ShopifyCustomer shopifyCustomer = mapper.toShopifyCustomer(customer); if (!shopifyCustomerExist(customer)) { insertNewShopifyCustomer(shopifyCustomer); return; } updateShopifyCustomer(shopifyCustomer); } private void init(Long shopId) { shopifyRestClient = shopifyRestClientFactory.getByShopId(shopId); shopifyGraphQLClient = shopifyGraphQLClientFactory.getByShopId(shopId); } }
ShopifyRestClientFactory
@Component public class ShopifyRestClientFactory { @Autowired(required = false) @Qualifier("shopifyFrRestClient") private ShopifyRestClient shopifyFrRestClient; @Autowired(required = false) @Qualifier("shopifyDeRestClient") private ShopifyRestClient shopifyDeRestClient; public ShopifyRestClient getByShopId(Long shopId) { ShopifyRestClient client; switch (shopId.intValue()) { case 2: client = shopifyFrRestClient; break; case 3: client = shopifyDeRestClient; break; default: throw new IllegalStateException("shopId " + shopId + " is not associated with any Rest client"); } return client; } }
AsyncConfig
@Configuration @EnableAsync public class AsyncConfig { @Bean(name = "CustomerExportThredExec") public Executor customerThreadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(5); executor.setQueueCapacity(0); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setThreadNamePrefix("CustomerExport_Thread_"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.initialize(); return executor; } }
问题根源与解决方案
核心问题
CustomerExporter是Spring单例组件,其中的shopifyRestClient和shopifyGraphQLClient属于实例变量。异步执行时,多个线程会同时修改这两个变量,导致线程间的客户端实例互相覆盖,最终出现数据提交到错误店铺的情况。
修复方案
移除实例变量,改为方法内局部变量
将shopifyRestClient和shopifyGraphQLClient改为upsertShopify方法的局部变量,彻底避免线程间共享:@Transactional @Async("CustomerExportThredExec") public void upsertShopify(Customer customer) { // 直接在方法内获取客户端,作为局部变量 ShopifyRestClient shopifyRestClient = shopifyRestClientFactory.getByShopId(customer.getShopId()); ShopifyGraphQLClient shopifyGraphQLClient = shopifyGraphQLClientFactory.getByShopId(customer.getShopId()); ShopifyCustomer shopifyCustomer = mapper.toShopifyCustomer(customer); if (!shopifyCustomerExist(customer, shopifyRestClient)) { insertNewShopifyCustomer(shopifyCustomer, shopifyRestClient, shopifyGraphQLClient); return; } updateShopifyCustomer(shopifyCustomer, shopifyRestClient, shopifyGraphQLClient); }同步修改
shopifyCustomerExist、insertNewShopifyCustomer、updateShopifyCustomer方法,将客户端作为参数传入,不再依赖实例变量。验证客户端线程安全性
确认ShopifyRestClient和ShopifyGraphQLClient本身是线程安全的(常见HTTP客户端如RestTemplate、WebClient默认线程安全),若客户端非线程安全,需为每个请求创建新实例或用线程池隔离。检查事务边界(可选)
@Transactional在异步方法中仅作用于当前线程,需确认事务传播逻辑符合预期,避免异步导致的事务异常。
内容的提问来源于stack exchange,提问作者user3187960

