曾經和別人聊天聊到服務端和客戶端技術棧差異,你說服務端有數據庫,客戶端也有;服務端有緩存,客戶端也有;服務端有多線程、有鎖,客戶端也有;在技術棧上,服務端有的,客戶端其實也有。但哪種是服務端有、客戶端沒有的呢?我想那就是分佈式,客戶端不管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();
}
}
參考資料:https://blog.csdn.net/crazymakercircle/article/details/85956246