在分布式环境中,为了保证数据一致性,经常在程序某个点(例如,减少库存操作和流水号生成等)需要进行同步控制。以一个”流水号生成”的场景为例,普通的后台应用通常都是使用时间戳来生成流水号,但是用户量非常的时候,可能会出现并发的情况。下面示例演示一个典型的并发问题。
package org.apache.curator;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
/**
* 一个典型的时间戳生成并发问题
* @author Mett.Min
*
*/
public class RecipesNoLock {
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0 ; i < 1000 ; i++ ) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
countDownLatch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss||SSSS");
String orderNo = sdf.format(new Date());
System.err.println("生成的订单号是:"+orderNo);
}
}).start();
}
countDownLatch.countDown();
}
}
运行结果如下图所示:
生成的订单号是:14:58:55||0167
生成的订单号是:14:58:55||0167
生成的订单号是:14:58:55||0167
生成的订单号是:14:58:55||0167
生成的订单号是:14:58:55||0167
生成的订单号是:14:58:55||0167
生成的订单号是:14:58:55||0168
生成的订单号是:14:58:55||0168
生成的订单号是:14:58:55||0167
生成的订单号是:14:58:55||0169
生成的订单号是:14:58:55||0167
上面这个示例就借助Curator来实现一个简单的分布式锁,其核心接口如下:
public implements InterProcessLock
-public void acquire() throws Exception;
-public boolean acquire(long time, TimeUnit unit) throws Exception ;
这两个接口分别用来实现分布式锁的获取许释放过程。
分布式计算器
有了上述分布式实现的基础后,我们就很容易基于其实现一个分布式计数器。分布式计数器是一个典型的统计系统在线人数。基础Zookeeper的分布式计算器的实现思路也非常简单。
指定一个Zookeeper数据节点作为计算器,多个应用实例在分布式锁的控制下,通过更新数据节点的内容来实现计数功能。
Curator同样将这一系列的逻辑封装在DistributedAtomicInteger类中。从其类名我们就可以看出这是一个可以再分布式环境中使用的原子整形,其具体操作可以参考如下实例:
package org.apache.curator;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.atomic.AtomicValue;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;
/**
* 使用Curator实现分布式计数
* @author Mett.Min
*
*/
public class RecipesDistAtomicInt {
static String distatomicint_path="/curator_recipes_distatomicint_path";
static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("hadoop01:2181").retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
public static void main(String[] args) throws Exception {
client.start();
DistributedAtomicInteger atomicInteger = new DistributedAtomicInteger(client, distatomicint_path, new RetryNTimes(3, 1000));
AtomicValue<Integer> atomicValue = atomicInteger.add(8);
System.err.println("result:"+atomicValue.succeeded());
}
}
分布式Barrier
Barrier是一个用来控制多线程之间同步的经典方式,在JDK中也自带了CyclicBarrier实现。下面通过模拟赛跑比赛来演示CyclicBarrier的方法。
package org.apache.curator;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 使用CyclicBarrier模拟一个赛跑比赛
* 可以看到并发的情况下,都会准确的等待所有线程处于就绪状态才开始同时执行其他业务逻辑。
* 如果是在分布式下如何解决呢????
* @author Mett.Min
*
*/
public class RecipesCyclicBarrier {
public static CyclicBarrier barrier = new CyclicBarrier(3);
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
threadPool.submit(new Runner("一号选手"));
threadPool.submit(new Runner("二号选手"));
threadPool.submit(new Runner("三号选手"));
threadPool.shutdown();
}
}
class Runner implements Runnable {
private String name;
public Runner(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "准备好了。");
try {
RecipesCyclicBarrier.barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(name + "起跑!!");
}
}
Curator中提供的DistributedBarrier就是用来实现分布式Barrier的。