You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
                }
            });
}

几个优化点

  1. ID长度调整:如果你的用户量极大,可以把ID长度加到20位,进一步降低碰撞概率。
  2. 重试次数限制:可以给递归/重试加个次数上限,避免极端情况下无限循环(比如数据库一直失败),比如重试5次就抛出异常。
  3. 性能优化:如果担心频繁查库,可以在内存里加一个Guava的LoadingCache做短期缓存(比如缓存5分钟内生成的ID),减少数据库查询次数,但要注意分布式环境下的缓存一致性问题(如果是多节点部署,缓存可能不一致,所以还是以数据库检查为准)。

内容的提问来源于stack exchange,提问作者Amr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:47:07