4 回答
TA贡献1803条经验 获得超3个赞
IMO 对您的问题最明显的直接答案是稍微更改代码:
@RequestMapping(method = RequestMethod.POST)
public ret_type updateUser(param) {
updateSharedStateByCommunityBlocks(resolveIds);
}
...
And in Service introduce a new method (if you can't change the code of the service provide an intermediate class that you'll call from controller with the following functionality):
@Transactional
public updateSharedStatedByCommunityBlocks(resolveIds) {
List<String> [] blocks = split(resolveIds, 100000); // 100000 - bulk size
for(List<String> block :blocks) {
updateSharedStateByCommunity(block);
}
}
如果此方法位于同一服务中,则@Transactional
原始方法updateSharedStateByCommunity
不会执行任何操作,因此它会起作用。如果您将此代码放入其他类中,那么它将起作用,因为 spring 事务的默认传播级别是“必需”
因此它满足了苛刻的要求:您想要进行一次交易 - 您已经做到了。现在所有代码都在同一个事务中运行。现在每个方法都使用 100000 个 ID 运行,而不是使用所有 id,一切都是同步的:)
然而,这种设计由于许多不同的原因而存在问题。
正如您在问题的最后一句中所说,它不允许跟踪进度(向用户显示)。REST 是同步的。
它假设网络是可靠的,并且等待 30 分钟在技术上不是问题(不考虑 UX 和必须等待的“紧张”用户:))
除此之外,网络设备可以强制关闭连接(例如具有预先配置的请求超时的负载均衡器)。
这就是为什么人们建议某种异步流。
我可以说,您仍然可以使用异步流,生成任务,并在每次批量更新后一些共享状态(在单个实例的情况下在内存中)和持久状态(如集群情况下的数据库)。
这样与客户端的交互就会改变:
客户端使用 200000 个 id 调用“updateUser”
服务会“立即”响应,例如“我收到了您的请求,这是一个请求 ID,请偶尔对我进行 ping 操作,看看会发生什么情况。
服务启动异步任务并在单个事务中逐块处理数据
客户端使用该 id 调用“get”方法,服务器从共享状态读取进度。
一旦准备好,“获取”方法将响应“完成”。
如果事务执行期间出现故障,则回滚完成,并且进程将数据库状态更新为“失败”。
您还可以使用更现代的技术来通知服务器(例如网络套接字),但这超出了这个问题的范围。
这里需要考虑的另一件事是:据我所知,处理 200000 个对象应该在不到 30 分钟的时间内完成,对于现代 RDBMS 来说还不够。当然,在不知道您的用例的情况下,很难判断那里发生了什么,但也许您可以优化流程本身(使用批量操作、减少对数据库的请求数量、缓存等)。
TA贡献1811条经验 获得超5个赞
在这些场景中,我的首选方法是使调用异步(Spring Boot 允许使用注释@Async
),因此客户端不会期望任何 HTTP 响应。该通知可以通过 WebSocket 完成,该 WebSocket 将向客户端推送一条消息,其中包含每个 X 项的处理进度。
当然,它会给您的应用程序增加更多的复杂性,但如果您正确设计该机制,您将能够将其重用于您将来可能面临的任何其他类似操作。
TA贡献1868条经验 获得超4个赞
从技术角度来看,可以通过传播来完成org.springframework.transaction.annotation.Propagation#NESTED
,NESTED 行为使嵌套 Spring 事务使用相同的物理事务,但在嵌套调用之间设置保存点,因此内部事务也可以独立于外部事务回滚,或者让它们传播。但限制仅适用于org.springframework.jdbc.datasource.DataSourceTransactionManager
数据源。
但是对于非常大的数据集,它仍然需要更多的时间来处理并使客户端等待,因此从解决方案的角度来看,也许使用异步方法会更好,但这取决于您的要求。
TA贡献1784条经验 获得超2个赞
该@Transactional
注释接受 atimeout
(尽管并非所有底层实现都支持它)。我反对尝试将 ID 拆分为两个调用,而是尝试修复超时(毕竟,您真正想要的是单个、全有或全无的事务)。您可以为整个应用程序设置超时,而不是针对每个方法设置超时。
添加回答
举报