TTL 删除项目后 COSMOS DB 唯一索引约束冲突

COSMOS DB Unique index constraint violation after item was removed by TTL

我正在使用 azure-spring-boot-starter-cosmos 创建分布式锁库

我的库有两个方法:public void lockResource(String resourceUniqueIdentifier) 和
public void unlockResource(String resourceUniqueIdentifier)

lockResource()方法会收到我要锁定的资源的resourceUniqueIdentifier, 创建一个 Lock() 实例并将其保存在数据库中。

如果锁已经存在(通过在 Azure 门户中将 /appName 设置为分区键并将 /lockedResourceId 设置为唯一键),lockRepository.save() 方法将抛出状态代码为 409 的异常(冲突)因为已经有一个具有相同分区键和唯一键的实体)

因此,为了获得锁,先前获得锁的人需要调用 unlockResource(String resourceUniqueIdentifier) 或资源上的 ttl 需要过期(我还在 Lock dto 上设置了一个 ttl 字段并启用了它在 Azure 门户中)

我的逻辑将尝试使用 while(!isNewLock(lock) && isNotMaximumRetries(retries, resourceUniqueIdentifier)) 获取锁。完整代码如下

Cosmos Config:具有“会话”一致性级别的直接模式

问题: 即使由于 TTL EXPIRATION 而从数据库中删除了某个资源的锁(假设其他 service/thread 早些时候获得了它),当再次尝试获取锁时,它仍然会抛出一个状态码为 409 的 CosmosAccessException。 (“违反唯一索引约束”)。对我来说,尽管锁已从数据库中删除(因为我检查过),但它仍然在某处保留了一些关于该锁的剩余信息。

LOCK DTO(我没有添加getters和setters):

@Container(containerName = "distributed-lock", timeToLive = -1, autoCreateContainer = false)

public class锁定{

@Id
@GeneratedValue
private String id;

private  String lockedResourceId;

@PartitionKey
private  String appName;

private  Integer ttl;

private long time;

public Lock(String id, String lockedResourceId, String appName, Integer ttl, long time) {
    this.id = id;
    this.lockedResourceId = lockedResourceId;
    this.appName = appName;
    this.ttl = ttl;
    this.time = time; //todo remove
}

}

锁定服务:

 public void lockResource(String resourceUniqueIdentifier) {
    var retries = 0;
    var lock = new Lock(UUID.randomUUID().toString(), resourceUniqueIdentifier, appName, lockProperties.getTtl(), Instant.now().toEpochMilli());

    while(!isNewLock(lock) && isNotMaximumRetries(retries, resourceUniqueIdentifier)) {
        logger.info(I9001.getMessage(), lock.getLockedResourceId(), retries);
        waitToUnlock();
        retries++;
    }
}




private void waitToUnlock() {
    try {
        Thread.sleep(lockProperties.getRetryInterval());
    } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted exception while waiting to retry lock", e);
    }
}



private boolean isNewLock(Lock lockResource) {
    try {
        lockResource.setId(UUID.randomUUID().toString());
        var lock = lockRepository.save(lockResource);
        logger.info(LoggingUtil.X900.getMessage(), lock.getId());
        logger.info("SAVED LOCK WITH ID: {}", lock.getId());
        var sameLock = lockRepository.getByLockedResourceIdAndAppName(lockResource.getLockedResourceId(), lockResource.getAppName());
        logger.info("TESTED SAVED LOCK WITH ID: {}, UniqueId: {}", sameLock.getId(), sameLock.getLockedResourceId());

        return true;
    } catch (CosmosAccessException cosmosAccessException) {
        if (cosmosAccessException.getCosmosException().getStatusCode() == CONFLICT_STATUS_CODE) {
            lockResource.setTime(Instant.now().toEpochMilli());
            logger.info(LoggingUtil.X900.getMessage(), lockResource.getLockedResourceId(), lockResource.getId());
            var alreadyExisting = lockRepository.getByLockedResourceIdAndAppName(lockResource.getLockedResourceId(), lockResource.getAppName());
            logger.info("Retrieved duplicate with resId {} and id {}", alreadyExisting, alreadyExisting);

            return false;
        }
       else throw cosmosAccessException;
    }
}

问题在于,当资源的离开时间到期时,cosmos 只会执行部分删除。 如果有人调用该资源,则不会返回,因为 ttl 已过期。但是,如果您想在一小段时间后保存相同的资源(在容器上设置了一些约束,例如 uniqueKey 和 partitionKey),您可能会收到 409 状态代码,因为数据将当有足够的 RU(资源单元)可用时,将被完全删除。

https://docs.microsoft.com/bs-latn-ba/azure/cosmos-db/sql/time-to-live?view=sql-server-ver15