如何确保代码块串行执行?解决Cosmos DB自增ID重复问题
解决Cosmos DB生成无重复自增ID的方案
1. 用Cosmos DB事务+专用计数器文档(推荐)
创建一个专门存计数的文档,比如{ "id": "user_id_counter", "current_value": 0 },每次生成新ID时,通过原子性的事务更新来递增这个值,从根源避免并发冲突。
具体操作步骤:
- 查询当前计数器文档
- 在同一事务里,把
current_value加1,同时返回更新后的值作为新ID - 借助Cosmos DB的乐观并发控制(通过
ETag),如果更新时发现ETag不匹配(说明已有其他请求修改过),就自动重试
示例代码(C#):
public async Task<int> GetNextUserIdAsync() { var container = _cosmosClient.GetContainer("YourDb", "YourContainer"); var counterId = "user_id_counter"; int retries = 3; // 可根据业务调整重试次数 while (retries-- > 0) { // 获取当前计数器文档 var response = await container.ReadItemAsync<Counter>(counterId, new PartitionKey(counterId)); var counter = response.Resource; var newId = counter.CurrentValue + 1; // 原子更新计数器 counter.CurrentValue = newId; try { await container.ReplaceItemAsync(counter, counterId, new PartitionKey(counterId), new ItemRequestOptions { IfMatchEtag = response.ETag // 乐观并发校验,确保只有当前版本能被修改 }); return newId; } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.PreconditionFailed) { // 并发冲突,重试 continue; } } throw new InvalidOperationException("生成ID失败,重试次数耗尽"); } // 计数器实体类 public class Counter { public string Id { get; set; } public int CurrentValue { get; set; } }
2. 自动生成ID+映射友好ID
如果业务允许,可以先用Cosmos DB自动生成的GUID作为内部唯一ID,再给用户分配独立的友好自增ID并存入用户文档:
- 创建用户时,尝试插入带自增友好ID的文档
- 如果因ID重复抛出冲突异常,就递增ID后重试
3. 分布式锁替代SemaphoreSlim
如果你的服务是多实例部署,单进程的SemaphoreSlim只对当前实例有效,其他实例的请求不受限制,自然解决不了并发问题。这时可以基于Cosmos DB实现分布式锁:
- 生成ID前,先创建一个临时锁文档(设置自动过期),拿到锁再执行后续操作
- 操作完成后删除锁文档释放资源
不过这种方式性能不如计数器方案,锁会导致并发请求排队,且锁的过期时间需要合理配置。
为什么SemaphoreSlim无效?
如果是多实例部署,SemaphoreSlim仅作用于当前进程,其他实例的请求不受约束,仍会出现并发取到相同最大ID的情况;如果是单实例但无效,大概率是SemaphoreSlim的使用方式有误——比如WaitAsync和Release没有正确配对,或者作用域设置不对。
内容的提问来源于stack exchange,提问作者CSharp User
相关产品推荐
相关产品推荐

