Alfresco站点级自定义唯一ID生成的并发问题及优化方案咨询
Hi David,
我来帮你梳理下当前遇到的核心问题,同时给出几个可落地的优化方向:
一、当前方案的核心痛点
你现在的问题本质是依赖Solr索引的FTS查询不具备事务内实时性:当多个请求同时触发onCreateNode行为,且当前站点还没有对应前缀的计数器时,第一个请求刚创建完计数器节点,但Solr的异步索引还没完成,后续请求的FTS查询就查不到这个新节点,进而重复创建多个相同前缀的计数器,最终导致ID重复。
另外即使是已有计数器的场景,当前逻辑也没有加锁机制,高并发下可能出现多个请求同时读取到同一个旧后缀值,各自加1后生成重复的ID。
二、优化方案建议
1. 绕过Solr,改用事务内可见的查询方式
既然Solr索引延迟是核心问题,那可以直接用Alfresco内部的Lucene查询(事务内实时可见),或者直接遍历节点结构来查找计数器,完全不依赖Solr:
- 替换FTS查询为Lucene查询,示例代码:
// 改用Lucene查询,事务内可实时获取刚创建的节点 String query = "TYPE:\"test:myDatalist\" AND @test:prefix:\"" + prefix + "\""; ResultSet resultSet = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_LUCENE, query);
- 或者直接定位到站点的数据列表文件夹,遍历子节点匹配前缀:先获取站点的
cm:dataLists文件夹NodeRef,再用nodeService.getChildren()遍历该文件夹下的test:myDatalist节点,匹配test:prefix属性。
2. 增加并发锁机制
不管用哪种查询方式,高并发场景下必须加锁来保证同一时间只有一个请求能创建或更新计数器:
- 用Alfresco自带的
LockService:针对站点的数据列表文件夹加读写锁,防止多个请求同时创建计数器;// 先获取站点数据列表文件夹的NodeRef NodeRef dataListsFolder = getSiteDataListsFolder(siteShortName); // 加10秒超时的读写锁 lockService.lock(dataListsFolder, LockType.READ_WRITE_LOCK, 10000); try { // 在这里执行查询、创建或更新计数器的逻辑 // ... } finally { // 确保锁被释放 lockService.unlock(dataListsFolder); } - 或者用外部分布式锁(比如Redis):以
{siteShortName}-{prefix}作为锁键,确保同一时间只有一个请求进入计数器操作逻辑。
3. 改用数据库存储计数器(推荐高并发场景)
如果Alfresco节点操作的并发控制还是有瓶颈,可以直接在Alfresco数据库中新建一张计数器表,结构设计为site_short_name、prefix、current_suffix,利用数据库的行级锁保证原子性:
- 用JDBC执行
SELECT ... FOR UPDATE查询计数器行,不存在则插入; - 用
UPDATE ... SET current_suffix = current_suffix + 1 WHERE ...原子更新后缀,完全绕过Alfresco的节点和索引机制,并发安全性最高。
4. 调整Solr实时性配置(不推荐)
虽然可以通过修改Solr的autoCommit和softCommit参数缩短索引延迟,但这会大幅增加Solr的性能开销,而且即使调到最短,仍存在毫秒级的并发窗口,高并发下还是可能出问题,不建议作为核心解决方案。
三、行业常用实践
做Alfresco自定义ID生成的场景,大多会优先选择数据库计数器+行级锁或者分布式锁+事务内节点查询的方案,这两种方案的并发安全性最高,性能也更稳定。你当前设计的「站点+前缀」作为计数器唯一键的思路是合理的,只需要补上并发控制的环节即可。
备注:内容来源于stack exchange,提问作者David Dejmal

