session.setAttribute()失效:RESTful导入状态无法实时更新求助
问题分析与解决方案
首先,咱们来聊聊你遇到的核心问题:你的deltaImport接口是同步执行的——当用户调用这个接口时,整个请求线程会被耗时的Solr导入操作完全阻塞,直到导入完成才会结束请求。这就导致两个关键问题:
- 用户调用
deltaImport后要等很久才能收到响应,体验很差; - 虽然你在代码里先设置了
IMPORT_STATUS为RUNNING,但由于整个请求线程被阻塞,要么你在测试时是串行调用(比如浏览器等POST请求完成才发GET查询),根本没机会读到RUNNING状态;要么即使并行调用,Web容器对Session的处理可能因为原请求未结束,导致状态更新的可见性延迟。
改进方案:异步执行导入操作
解决思路很简单:把耗时的导入逻辑从Web请求线程中剥离,放到后台异步线程里执行,这样deltaImport接口可以立刻返回响应,同时中间状态(RUNNING)能被query_Status接口及时读取到。
步骤1:开启Spring异步支持
如果是Spring Boot项目,只需要在启动类上添加@EnableAsync注解;如果是传统Spring项目,需要在XML配置里添加异步相关的配置。
步骤2:创建线程安全的状态存储(替代直接用Session)
直接在异步线程里操作HttpSession有风险——因为HttpSession和原请求线程绑定,原请求结束后Session可能被容器回收,导致异步线程访问失效。所以我们用一个全局的线程安全容器来存储每个会话的导入状态:
@Component public class ImportStatusHolder { // 用ConcurrentHashMap保证线程安全 private final ConcurrentHashMap<String, String> statusMap = new ConcurrentHashMap<>(); public void setStatus(String sessionId, String status) { statusMap.put(sessionId, status); } public String getStatus(String sessionId) { // 默认返回IDLE表示无导入操作 return statusMap.getOrDefault(sessionId, "IDLE"); } // 可选:定时清理过期状态,避免内存泄漏 @Scheduled(fixedRate = 86400000) // 每天清理一次 public void cleanExpiredStatus() { // 这里可以根据业务逻辑移除过期状态,比如移除已完成超过24小时的记录 statusMap.entrySet().removeIf(entry -> { String status = entry.getValue(); return "FINISH".equals(status) || "FAILED".equals(status); }); } }
步骤3:修改SolrService,添加异步导入方法
把原来的同步导入逻辑包装成异步方法,在异步线程里更新状态:
@Service public class SolrService { @Autowired private ImportStatusHolder statusHolder; // 标记为异步方法,Spring会用线程池执行 @Async public void deltaImportAsync(long startPosition, long maxResult, String sessionId) { try { // 设置运行状态 statusHolder.setStatus(sessionId, "RUNNING"); // 执行耗时的导入操作 this.deltaImport(startPosition, maxResult); // 导入完成设置结束状态 statusHolder.setStatus(sessionId, "FINISH"); } catch (Exception e) { // 异常时设置失败状态 statusHolder.setStatus(sessionId, "FAILED"); // 记得记录异常日志 log.error("Delta import failed", e); } } // 原来的同步导入方法,保持不变 public void deltaImport(long startPosition, long maxResult) { // 你的耗时导入逻辑... } }
步骤4:修改控制器接口
更新两个接口,使用新的状态存储:
@RestController public class ImportController { @Autowired private SolrService solrService; @Autowired private ImportStatusHolder statusHolder; @RequestMapping(value = "deltaImport", method = RequestMethod.POST) public ResponseEntity<String> deltaImport(HttpSession session, @RequestParam long startPosition, @RequestParam long maxResult) { String sessionId = session.getId(); String currentStatus = statusHolder.getStatus(sessionId); // 检查是否已有导入在运行,避免重复执行 if ("RUNNING".equals(currentStatus)) { return ResponseEntity.badRequest().body("An import task is already running for your session"); } // 启动异步导入 solrService.deltaImportAsync(startPosition, maxResult, sessionId); // 立刻返回响应,不用等导入完成 return ResponseEntity.ok("Delta import started successfully"); } @RequestMapping(value = "query_Status", method = RequestMethod.GET) public ResponseEntity<String> queryStatus(HttpSession session) { String status = statusHolder.getStatus(session.getId()); return ResponseEntity.ok(status); } }
额外注意事项
- 自定义异步线程池:Spring默认的异步线程池可能不够用,建议自定义线程池来控制线程数量,避免大量请求导致线程耗尽。可以通过
@Async("customTaskExecutor")指定自定义线程池。 - 状态清理:别忘了定时清理
ImportStatusHolder里的过期状态,否则会导致内存泄漏,比如上面示例中的@Scheduled方法。 - 异常处理:异步方法里的异常不会直接反馈给用户,所以一定要做好日志记录,方便排查问题。
内容的提问来源于stack exchange,提问作者zy_sun
相关产品推荐
相关产品推荐

