[Curator] Distributed Atomic Long 的使用与分析

Distributed Atomic Long

分布式原子长整型

一个可以进行原子递增操作的计数器。 内部使用乐观锁实现,当失败时,再尝试加互斥排他锁。不论是乐观锁还是排他锁,都会按照重试策略进行重试操作。

1. 关键 API

org.apache.curator.framework.recipes.atomic.DistributedAtomicLong

org.apache.curator.framework.recipes.atomic.AtomicValue

org.apache.curator.framework.recipes.atomic.PromotedToLock

2. 机制说明

内部通过DistributedAtomicValue来实现分布式原子数据的操作。

  • 使用乐观锁
    • 先尝试无锁的版本更新
    • 不行再尝试加锁,并再次重试
  • 使用重试策略管理
  • 需要检查返回值是否成功(类似mongodb

3. 用法

3.1 创建

  1. 乐观锁模式
public DistributedAtomicLong(CuratorFramework client,
                                String counterPath,
                                RetryPolicy retryPolicy)
  • 需要指定重试策略
  1. 排他锁模式
public DistributedAtomicLong(CuratorFramework client,
                                String counterPath,
                                RetryPolicy retryPolicy,
                                PromotedToLock promotedToLock)
  • 需要指定重试策略
  • 配置锁的相关信息

3.2 使用

  1. 对数值的操作:
    • get()
    • increment()
    • decrement()
    • add()
    • subtract()
  2. 检查结果
    • 操作完之后,必须调用succeeded()进行检查
      • 只有当返回true时,才能说明操作成功
    • 如果操作成功,还可以:
      • preValue() 获取操作之前的值
      • postValue() 获取操作之后的值

4. 错误处理

这些原子实例的每一次方法调用都会访问ZK服务。所以,都会使用重试机制,而且一旦发生错误,就会抛出异常。

5. 源码分析

5.1 类定义

public interface DistributedAtomicNumber<T>{}

public class DistributedAtomicLong implements DistributedAtomicNumber<Long>{}
  • 实现了org.apache.curator.framework.recipes.atomic.DistributedAtomicNumber接口
    • 定义了原子数值类型的基本操作api
      • AtomicValue<T> add(T delta)
        • 增加,并返回新值
      • AtomicValue<T> compareAndSet(T expectedValue, T newValue)
        • CAS更新,并返回新值
      • AtomicValue<T> decrement()
        • 递减,并返回新值
      • void forceSet(T newValue)
        • 强制覆盖更新
      • AtomicValue<T> get()
        • 获取当前值
      • AtomicValue<T> increment()
        • 递增,并返回新值
      • boolean initialize(T value)
        • 初始化
        • 如果设值成功,则返回true
        • 如果节点已经存在,则返回false
      • AtomicValue<T> subtract(T delta)
        • 减去,并返回新值
      • AtomicValue<T> trySet(T newValue)
        • 尝试更新新值
        • 需要调用AtomicValue.succeeded(),对操作结果进行检查

5.2 成员变量

public class DistributedAtomicLong implements DistributedAtomicNumber<Long>
{
    private final DistributedAtomicValue        value;
}

很简单,只定义了一个org.apache.curator.framework.recipes.atomic.DistributedAtomicValue

public class DistributedAtomicValue
{
    private final CuratorFramework  client;
    private final String            path;
    private final RetryPolicy       retryPolicy;
    private final PromotedToLock    promotedToLock;
    private final InterProcessMutex mutex;
}

在DistributedAtomicValue中,定义了:

  • retryPolicy
    • 重试策略
  • promotedToLock
    • org.apache.curator.framework.recipes.atomic.PromotedToLock
    • 锁升级的相关信息
  • mutex
    • org.apache.curator.framework.recipes.locks.InterProcessMutex
    • 互斥锁

发现DistributedAtomicNumber的大部分操作都是通过value来完成的。

5.3 构造器

public DistributedAtomicLong(CuratorFramework client, String counterPath, RetryPolicy retryPolicy)
{
    this(client, counterPath, retryPolicy, null);
}

public DistributedAtomicLong(CuratorFramework client, String counterPath, RetryPolicy retryPolicy, PromotedToLock promotedToLock)
{
    value = new DistributedAtomicValue(client, counterPath, retryPolicy, promotedToLock);
}

都是为了初始化DistributedAtomicValue

public DistributedAtomicValue(CuratorFramework client, String path, RetryPolicy retryPolicy, PromotedToLock promotedToLock)
{
    this.client = client;
    this.path = PathUtils.validatePath(path);
    this.retryPolicy = retryPolicy;
    this.promotedToLock = promotedToLock;
    mutex = (promotedToLock != null) ? new InterProcessMutex(client, promotedToLock.getPath()) : null;
}

需要注意默认情况下,mutex使用的是InterProcessMutex

5.4 初始化

分布式原子数值,在使用之前需要调用initialize进行初始化。否则,取值就会是null。

DistributedAtomicLong:

public boolean initialize(Long initialize) throws Exception
{
    return value.initialize(valueToBytes(initialize));
}

DistributedAtomicValue:

public boolean initialize(byte[] value) throws Exception
{
    try
    {
        client.create().creatingParentContainersIfNeeded().forPath(path, value);
    }
    catch ( KeeperException.NodeExistsException ignore )
    {
        // ignore
        return false;
    }
    return true;
}
  • 创建一个普通节点
  • 使用initialize值作为节点数据

5.5 获取值

DistributedAtomicLong:

public AtomicValue<Long>     get() throws Exception
{
    return new AtomicLong(value.get());
}

对于DistributedAtomicValue返回的AtomicValue对象,使用了一个内部类org.apache.curator.framework.recipes.atomic.DistributedAtomicLong.AtomicLong进行了包装。

DistributedAtomicValue:

public AtomicValue<byte[]>     get() throws Exception
{
    MutableAtomicValue<byte[]>  result = new MutableAtomicValue<byte[]>(null, null, false);
    getCurrentValue(result, new Stat());
    result.postValue = result.preValue;
    result.succeeded = true;
    return result;
}

private boolean getCurrentValue(MutableAtomicValue<byte[]> result, Stat stat) throws Exception
{
    boolean             createIt = false;
    try
    {
        result.preValue = client.getData().storingStatIn(stat).forPath(path);
    }
    catch ( KeeperException.NoNodeException e )
    {
        result.preValue = null;
        createIt = true;
    }
    return createIt;
}
  • 返回结果是org.apache.curator.framework.recipes.atomic.MutableAtomicValue
    • 从名字中也能看出这个对象是不保障原子操作的
  • path节点中获取数据
    • 如果节点不存在
      • 还未初始化
      • 则数值为null

5.6 强制覆盖更新

DistributedAtomicLong:

public void forceSet(Long newValue) throws Exception
{
    value.forceSet(valueToBytes(newValue));
}

DistributedAtomicValue:

public void forceSet(byte[] newValue) throws Exception
{
    try
    {
        client.setData().forPath(path, newValue);
    }
    catch ( KeeperException.NoNodeException dummy )
    {
        try
        {
            client.create().creatingParentContainersIfNeeded().forPath(path, newValue);
        }
        catch ( KeeperException.NodeExistsException dummy2 )
        {
            client.setData().forPath(path, newValue);
        }
    }
}

按不同的概率进行了三种赋值动作,也为了减少多个创建节点动作的冲突。也是一种乐观设计。

  • 对zk节点进行常规写操作
    • 如果节点不存在,再创建,并赋值
      • 如果节点已存在(上一步有并发,其他人创建了节点)
        • 再次对zk节点进行常规写操作

5.7 尝试更新新值

DistributedAtomicLong:

public AtomicValue<Long>   trySet(Long newValue) throws Exception
{
    return new AtomicLong(value.trySet(valueToBytes(newValue)));
}

DistributedAtomicValue:

public AtomicValue<byte[]>   trySet(final byte[] newValue) throws Exception
{
    MutableAtomicValue<byte[]>  result = new MutableAtomicValue<byte[]>(null, null, false);

    MakeValue                   makeValue = new MakeValue()
    {
        @Override
        public byte[] makeFrom(byte[] previous)
        {
            return newValue;
        }
    };
    tryOptimistic(result, makeValue);
    if ( !result.succeeded() && (mutex != null) )
    {
        tryWithMutex(result, makeValue);
    }

    return result;
}

private void tryOptimistic(MutableAtomicValue<byte[]> result, MakeValue makeValue) throws Exception
{
    long            startMs = System.currentTimeMillis();
    int             retryCount = 0;

    boolean         done = false;
    while ( !done )
    {
        result.stats.incrementOptimisticTries();
        if ( tryOnce(result, makeValue) )
        {
            result.succeeded = true;
            done = true;
        }
        else
        {
            if ( !retryPolicy.allowRetry(retryCount++, System.currentTimeMillis() - startMs, RetryLoop.getDefaultRetrySleeper()) )
            {
                done = true;
            }
        }
    }

    result.stats.setOptimisticTimeMs(System.currentTimeMillis() - startMs);
}

private boolean tryOnce(MutableAtomicValue<byte[]> result, MakeValue makeValue) throws Exception
{
    Stat        stat = new Stat();
    boolean     createIt = getCurrentValue(result, stat);

    boolean     success = false;
    try
    {
        byte[]  newValue = makeValue.makeFrom(result.preValue);
        if ( createIt )
        {
            client.create().creatingParentContainersIfNeeded().forPath(path, newValue);
        }
        else
        {
            client.setData().withVersion(stat.getVersion()).forPath(path, newValue);
        }
        result.postValue = Arrays.copyOf(newValue, newValue.length);
        success = true;
    }
    catch ( KeeperException.NodeExistsException e )
    {
        // do Retry
    }
    catch ( KeeperException.BadVersionException e )
    {
        // do Retry
    }
    catch ( KeeperException.NoNodeException e )
    {
        // do Retry
    }

    return success;
}

private void tryWithMutex(MutableAtomicValue<byte[]> result, MakeValue makeValue) throws Exception
 {
     long            startMs = System.currentTimeMillis();
     int             retryCount = 0;

     if ( mutex.acquire(promotedToLock.getMaxLockTime(), promotedToLock.getMaxLockTimeUnit()) )
     {
         try
         {
             boolean         done = false;
             while ( !done )
             {
                 result.stats.incrementPromotedTries();
                 if ( tryOnce(result, makeValue) )
                 {
                     result.succeeded = true;
                     done = true;
                 }
                 else
                 {
                     if ( !promotedToLock.getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMs, RetryLoop.getDefaultRetrySleeper()) )
                     {
                         done = true;
                     }
                 }
             }
         }
         finally
         {
             mutex.release();
         }
     }

     result.stats.setPromotedTimeMs(System.currentTimeMillis() - startMs);
}

定义了一个org.apache.curator.framework.recipes.atomic.MakeValue

  • 拿到旧值之后如何转换成新值的装换器
  1. 首先调用tryOptimistic,进行乐观尝试
    1. 按照重试策略,不断重试调用tryOnce
      1. 读取当前数值
      2. 如果节点还不存在,则直接以新值进行节点创建
      3. 如果节点已经存在,则使用当前数值版本进行更新
      4. 将新值赋值保存
      5. 返回成功
      • 任何zk异常,都忽略了,因为可以重试
    2. 如果重试策略认定不再重试了,则退出循环
    3. 记录乐观尝试所消耗的时间
  2. 如果尝试不成功,并且设置了排他锁(默认是带锁的),则尝试加锁操作tryWithMutex
    1. 按照promotedToLock中配置的锁等待时间,申请排他锁
      1. 加锁成功后,再次尝试tryOnce
      2. 再次使用重试策略
      3. 释放锁
    2. 记录加锁操作所消耗的时间

5.8 递增

DistributedAtomicLong:

public AtomicValue<Long>    increment() throws Exception
{
    return worker(1L);
}
private AtomicValue<Long>   worker(final Long addAmount) throws Exception
{
    Preconditions.checkNotNull(addAmount, "addAmount cannot be null");

    MakeValue               makeValue = new MakeValue()
    {
        @Override
        public byte[] makeFrom(byte[] previous)
        {
            long        previousValue = (previous != null) ? bytesToValue(previous) : 0;
            long        newValue = previousValue + addAmount;
            return valueToBytes(newValue);
        }
    };

    AtomicValue<byte[]>     result = value.trySet(makeValue);
    return new AtomicLong(result);
}
  • 通过构建一个org.apache.curator.framework.recipes.atomic.MakeValue来定义如何产生新值
    • 参见上一节
  • 调用DistributedAtomicValuetrySet方法,进行尝试更新
    • 参见上一节

可见,递增方法也是需要AtomicValue.succeeded()方法

5.9 减值

DistributedAtomicLong:

public AtomicValue<Long> subtract(Long delta) throws Exception
{
    return worker(-1 * delta);
}

逻辑,参见上一节

可见,此方法也是需要AtomicValue.succeeded()方法

6 小结

DistributedAtomicLong的操作大部分都是使用DistributedAtomicValuetrySet方法。

所以,使用时务必需要检查返回的AtomicValue对象的succeeded()方法。

对比org.apache.curator.framework.recipes.shared.SharedCount,DistributedAtomicLong能够更细节,更安全(原子操作重试策略)的控制数值步进。

除了长整型,还有org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger,逻辑和用法都和DistributedAtomicLong类似

转载于:https://my.oschina.net/roccn/blog/917128

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值