分布式系统及基于zookeeper的分布式锁示例
曾经和别人聊天聊到服务端和客户端技术栈差异,你说服务端有数据库,客户端也有;服务端有缓存,客户端也有;服务端有多线程、有锁,客户端也有;在技术栈上,服务端有的,客户端其实也有。但哪种是服务端有、客户端没有的呢?我想那就是分布式,客户端不管android还是ios,终究都是单机,而服务端怎么着至少也得2台机器吧,线上环境部署单点机器(不过现在云服务器貌似也挺稳定),一旦出现故障,那业务请求也就没法处理了。
初看起来,分布式系统很像是放大了的单机。一台机器通过总线把cpu、内存、磁盘、网卡、显卡等部件连到一起,一个分布式系统通过网络把服务进程连到一起,网络就是总线。不过,分布式终究是像单机,而不是真的能像单机那样写程序。比如一次业务请求异常,调用方很难区分是:网络故障还是机器故障?软件错误还是硬件错误?去请求路上出错还是返回路上出错?对方有没有收到请求,能不能重试?分布式很复杂,还好分布式开发中遇到问题差不多都有解法。比如分布式开发中最基础、常见的一个问题,就是分布式锁的设计。很多时候我们需要保证一个请求在同一时间内只能被一台机器上的服务执行,这就需要用到分布式锁来保证。本文就不讲什么是分布式锁了,这方面文章太多了。如果查查网上资料,会发现当前主要有两种比较成熟的分布式锁实现方案:基于redis及基于zookeeper实现。
在技术选型时,你说哪种好?基于redis的分布式锁有一个失效时间设置问题。设置的失效时间过短,方法没等执行完,锁就自动释放了,就会产生并发问题;如果设置的时间过长,其他获取锁的线程就可能要平白的多等一段时间。你说看样子这种方式不咋地呀,但是很多公司用的就是这种方式,跑在线上环境,也没听说有啥问题。因为极端情况出现很少,大部分日常场景都可以满足,该方式性能又高,在做技术选型时,选择的人刚好以前用过,说白了成本也低,那就把以前的代码拿过来直接用就好了,反正也验证过,所以基于redis的分布式锁用的也挺多的。那么基于zookeeper实现的分布式锁呢,貌似没有明显缺点,最大缺点可能是性能上没有基于redis的高。我平时工作中使用的就是基于zookeeper实现的分布式锁,该实现原理可以参考这篇文章,写的很详细。工作中zk除了用于分布式锁,还可以用来做注册中心,真是分布式系统中的神器。下面是一些封装的示例代码(有一些删减,主要提供思路,源码不方便放),封装好后即可以避免一些坑,用起来也非常省事,对于一个团队而言,只需要一个人维护代码即可,团队其他成员直接用就行:
public class ZookeeperCuatorClientHolder {
private CuratorFramework curatorFramework;
private String connectString;
private int baseSleepTimeMs = 1000;
private int maxRetries = 3;
public ZookeeperCuatorClientHolder(String connectString, int baseSleepTimeMs, int maxRetries) {
this.connectString = connectString;
this.baseSleepTimeMs = baseSleepTimeMs;
this.maxRetries = maxRetries;
initCuratorFramework();
}
private void initCuratorFramework() {
this.curatorFramework = CuratorFrameworkFactory.newClient(connectString, new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries));
this.curatorFramework.start();
}
public void closeCuratorFramework() {
if (null == this.curatorFramework) {
return;
}
if (CuratorFrameworkState.STOPPED != this.curatorFramework.getState()) {
this.curatorFramework.close();
}
}
}
public class DistributeLockTemplate {
private ZookeeperCuatorClientHolder zookeeperCuatorClientHolder;
public ZookeeperCuatorClientHolder getZookeeperCuatorClientHolder() {
return zookeeperCuatorClientHolder;
}
public void setZookeeperCuatorClientHolder(ZookeeperCuatorClientHolder zookeeperCuatorClientHolder) {
this.zookeeperCuatorClientHolder = zookeeperCuatorClientHolder;
}
public Object execute(String lockPath, long timeout, LockCallback callback) {
if (ZookeeperKitsUtil.isEmpty(lockPath) || callback == null) {
throw new IllegalArgumentException("lockPath=" + lockPath + ",callback=" + callback);
}
Object result = null;
String actualLockPath = ZookeeperKitsUtil.LOCK_PATH_PREFIX + lockPath;
InterProcessMutex lock = new InterProcessMutex(zookeeperCuatorClientHolder.getCuratorFrameworkInstance(), actualLockPath);
boolean hasLock = false;
try {
hasLock = tryLock(lock, actualLockPath, timeout);
if (hasLock) {
result = callback.onLocked();
} else {
result = callback.onLockFailed();
}
} catch (LockException le) {
result = callback.onLockException(le);
} catch (Throwable be) {
throw new BusinessException(be);
} finally {
if (hasLock) {
releaseLock(lock, actualLockPath);
}
}
return result;
}
}
@Configuration
public class ZookeeperKitAutoConfiguration implements DisposableBean {
@Autowired private ZookeeperCuatorClientHolder zookeeperCuatorClientHolder;
@Bean
public DistributeLockTemplate distributeLockTemplate() {
DistributeLockTemplate distributeLockTemplate = new DistributeLockTemplate();
distributeLockTemplate.setZookeeperCuatorClientHolder(zookeeperCuatorClientHolder);
return distributeLockTemplate;
}
@Bean
public ZookeeperCuatorClientHolder zookeeperCuatorClientHolder() {
ZookeeperCuatorClientHolder zookeeperCuatorClientHolder = new ZookeeperCuatorClientHolder(zkconnectString, baseSleepTimeMs, maxRetries);
return zookeeperCuatorClientHolder;
}
@Override
public void destroy() throws Exception {
zookeeperCuatorClientHolder.closeCuratorFramework();
}
}
上一篇: 淘宝技术这十年