Lagom项目:如何实现非UUID随机唯一文档ID的迭代生成函数
嘿,这个场景在分布式文档系统里挺常见的,结合Lagom的异步模型,我给你梳理几个落地的实现思路,直接就能适配你现有的DocumentRepository和ReadSide处理逻辑~
核心思路回顾
你想的逻辑没问题:生成随机字符串 → 查库验重 → 重复直到找到唯一ID。但因为Lagom的数据库操作都是异步的(返回CompletionStage/Future),所以不能用普通的同步循环/递归,得用异步链式调用的方式实现。
第一步:实现随机字符串生成工具
首先得有一个靠谱的随机字符串生成方法,别用普通的Random,要用SecureRandom保证不可预测性,比如生成16位包含大小写字母+数字的字符串(碰撞概率极低,几乎不会多次重试):
Java示例
private String generateRandomString() { int length = 16; String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; SecureRandom random = new SecureRandom(); StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { sb.append(chars.charAt(random.nextInt(chars.length()))); } return sb.toString(); }
Scala示例
private def generateRandomString(): String = { val length = 16 val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" val random = new java.security.SecureRandom() (1 to length).map(_ => chars(random.nextInt(chars.length))).mkString }
第二步:实现异步的唯一ID生成函数
这里用递归或者异步循环都可以,本质是利用thenCompose(Java)/flatMap(Scala)来链式调用异步操作。
Java版(递归实现)
假设你的DocumentRepository有一个existsById(String docId)方法,返回CompletionStage<Boolean>:
public CompletionStage<String> generateUniqueDocumentId() { String candidateId = generateRandomString(); return documentRepository.existsById(candidateId) // 检查ID是否存在,存在就递归重试,不存在就返回该ID .thenCompose(exists -> { if (!exists) { return CompletableFuture.completedFuture(candidateId); } else { return generateUniqueDocumentId(); } }) // 处理数据库查询异常,比如连接失败,触发重试 .exceptionally(ex -> { log.error("Failed to check document ID existence, retrying...", ex); return generateUniqueDocumentId().toCompletableFuture().join(); }); }
Scala版(递归实现)
对应Scala的FutureAPI:
def generateUniqueDocumentId(): Future[String] = { val candidateId = generateRandomString() documentRepository.existsById(candidateId) .flatMap { exists => if (!exists) Future.successful(candidateId) else generateUniqueDocumentId() } .recoverWith { case ex: Exception => log.error(s"Failed to check existence for $candidateId, retrying...", ex) generateUniqueDocumentId() } }
第三步:加一层数据库兜底保障
极端情况下,可能出现“检查ID不存在 → 另一个请求生成了相同ID并插入 → 当前请求插入”的竞态问题,所以一定要在数据库的doc_id字段加唯一索引,作为最后一道防线。
如果插入时触发唯一约束冲突,就在服务层捕获异常并重试:
Java示例(插入时重试)
public CompletionStage<Document> createDocument(Document document) { return generateUniqueDocumentId() .thenCompose(docId -> { document.setId(docId); return documentRepository.save(document); }) .exceptionally(ex -> { // 捕获唯一约束冲突异常 if (ex.getCause() instanceof SQLIntegrityConstraintViolationException) { log.warn("Document ID collision detected, retrying creation..."); return createDocument(document).toCompletableFuture().join(); } else { throw new RuntimeException("Failed to create document", ex); } }); }
几个优化点
- ID长度调整:如果你的用户量极大,可以把ID长度加到20位,进一步降低碰撞概率。
- 重试次数限制:可以给递归/重试加个次数上限,避免极端情况下无限循环(比如数据库一直失败),比如重试5次就抛出异常。
- 性能优化:如果担心频繁查库,可以在内存里加一个Guava的LoadingCache做短期缓存(比如缓存5分钟内生成的ID),减少数据库查询次数,但要注意分布式环境下的缓存一致性问题(如果是多节点部署,缓存可能不一致,所以还是以数据库检查为准)。
内容的提问来源于stack exchange,提问作者Amr
相关产品推荐
相关产品推荐

