1. 程式人生 > >分散式技術(一)分散式鎖

分散式技術(一)分散式鎖

1. 資料庫鎖
有兩種方式:
(1)利用唯一鍵(主鍵):資料庫是有唯一主鍵規則的,主鍵不能重複,對於重複的主鍵會丟擲主鍵衝突異常。當我們想要鎖住某個方法時,執行以下SQL:

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’);

因為我們對method_name做了唯一性約束,這裡如果有多個請求同時提交到資料庫的話,資料庫會保證只有一個操作可以成功,那麼我們就可以認為操作成功的那個執行緒獲得了該方法的鎖,可以執行方法體內容。當方法執行完畢之後,想要釋放鎖的話,需要執行以下SQL:

delete from methodLock where method_name ='method_name';

優缺點

  • 嚴重依賴資料庫的可用性。
  • 沒有超時機制,一旦超時操作失敗,鎖將無法釋放。
  • 沒有阻塞機制,一旦獲取鎖失敗,就會失敗返回,需要重新發起獲取鎖的請求再次嘗試獲取鎖。
  • 無法重入,同一個鎖的持有者在沒有釋放鎖的前提下無法重新獲取鎖。

(2)利用排它鎖:在查詢語句後面增加for update,資料庫會在查詢過程中給資料庫表增加排他鎖。當某條記錄被加上排他鎖之後,其他執行緒無法再在該行記錄上增加排他鎖。我們可以認為獲得排它鎖的執行緒即可獲得分散式鎖,當獲取到鎖之後,可以執行方法的業務

 public void unlock(){
     connection.commit();//釋放鎖
 } 

優缺點:
- 嚴重依賴資料庫的可用性。
- 相比第一種方式,在一定程度上解決了超時的問題,服務宕機超過一定時間資料庫伺服器會自動斷掉,從而釋放鎖。
- 可以在資料庫伺服器端設定for update的機制為等待而不是立即失敗返回,可視為一種阻塞機制(如果阻塞請求較多會佔用大量資料庫連線)。
- 仍然無法可重入。
2. Redis快取鎖
setnx()命令,setnx的含義就是SET if Not Exists,其主要有兩個引數 setnx(key, value)。該方法是原子的,如果key不存在,則設定當前key成功,返回1;如果當前key已經存在,則設定當前key失敗,返回0。但是要注意的是setnx命令不能設定key的超時時間,只能通過expire()來對key設定。

           public boolean lock(String key, long timeout) {
                  boolean lockSuccess = false;
                  try {
                         long start = System.currentTimeMillis();
                         String lockKey = GlobalIdInitializer.DEFAULT_PREFIX_OF_LOCK + key;
                         do {
                             long result = setnx(lockKey, String.valueOf(
                             System.currentTimeMillis() + GlobalIdInitializer.LOCKKEY_EXPIRE_TIME + 1));
                             if (result == 1) {
                                 lockSuccess = true;
                                 break;
                             } else {
                                 String lockTimeStr = getStringFromRedis(lockKey);
                                 if (StringUtils.isNumeric(lockTimeStr)) {// 如果key存在,鎖存在
                                 long lockTime = Long.valueOf(lockTimeStr);
                                 if (lockTime < System.currentTimeMillis()) {// 鎖已過期
                                      String originStr = getSet(lockKey,
                                      String.valueOf(System.currentTimeMillis()
                                            + GlobalIdInitializer.LOCKKEY_EXPIRE_TIME + 1));
                                      if (StringUtils.isNoneBlank(originStr)
                                           && originStr.equals(lockTimeStr)) {// 表明鎖由該執行緒獲得
                                           lockSuccess = true;
                                           break;
                                      }
                                }
                             }
                         }
                         // 如果不等待,則直接返回
                         if (timeout == 0) {
                             break;
                         }
                         // 等待300ms繼續加鎖
                         Thread.sleep(300);
                      } while ((System.currentTimeMillis() - start) < timeout);

                } catch (Exception e) {
                   logger.error(e.getMessage());
                } finally {
              }
              return lockSuccess;
            }
            public void unLock(String key) {
                  try {
                         String lockTimeStr = getStringFromRedis(key);
                         long lockTime = Long.valueOf(lockTimeStr);
                         /*
                          * 判斷鎖是否過期,如果鎖過期,說明鎖可能被其他程序獲得,這時直接delete
                          * 會把其他程序已獲得的鎖釋放掉
                          */
                         if (lockTime > System.currentTimeMillis()) {
                              deleteKey(GlobalIdInitializer.DEFAULT_PREFIX_OF_LOCK + key);
                         }
                   } catch (Exception e) {
                       logger.error(e.getMessage());
                   } finally {
                   }
           }

優缺點:
- 有單點問題,可能出現數據丟失。
3. RedLock
redis作者鑑於單點redis作為分散式鎖的可能出現的鎖資料丟失問題,提出了Redlock演算法,該演算法實現了比單一節點更安全、可靠的分散式鎖管理(DLM)。演算法的步驟如下:
(1)客戶端獲取當前時間,以毫秒為單位。
(2)客戶端嘗試獲取N個節點的鎖,(每個節點獲取鎖的方式和前面說的快取鎖一樣),N個節點以相同的key和value獲取鎖。客戶端需要設定介面訪問超時,介面超時時間需要遠遠小於鎖超時時間,比如鎖自動釋放的時間是10s,那麼介面超時大概設定5-50ms。這樣可以在有redis節點宕機後,訪問該節點時能儘快超時,而減小鎖的正常使用。
(3)客戶端計算在獲得鎖的時候花費了多少時間,方法是用當前時間減去在步驟一獲取的時間,只有客戶端獲得了超過3個節點的鎖,而且獲取鎖的時間小於鎖的超時時間,客戶端才獲得了分散式鎖。
(4)客戶端獲取的鎖的時間為設定的鎖超時時間減去步驟三計算出的獲取鎖花費時間。
(5)如果客戶端獲取鎖失敗了,客戶端會依次刪除所有的鎖。使用Redlock演算法,可以保證在掛掉最多2個節點的時候,分散式鎖服務仍然能工作,這相比之前的資料庫鎖和快取鎖大大提高了可用性,由於redis的高效效能,分散式快取鎖效能並不比資料庫鎖差。
優缺點:
- Redis所有節點之間是獨立的,必須由客戶端控制寫入的一致性。
- 當叢集中若干節點宕機時,客戶端需要等到超時時間之後才會返回,影響效能 。
- 會有衝突造成假死鎖問題。如果5個節點,由於需要獲取3個節點以上的鎖才算成功獲取鎖,如果都獲取了1-2個節點的鎖,那麼沒有一個客戶端能夠成功獲取鎖。redis作者借鑑了raft演算法的精髓,通過沖突後在隨機時間開始,可以大大降低衝突時間,但是這問題並不能很好的避免,特別是在第一次獲取鎖的時候,所以獲取鎖的時間成本增加了。兩個Redlock的問題,最關鍵的一點在於Redlock需要客戶端去保證寫入的一致性,後端5個節點完全獨立,所有的客戶端都得操作這5個節點。如果
4. Zookeeper分散式鎖
提到分散式協調服務,自然就想到了zookeeper。zookeeper實現了類似paxos協議,是一個擁有多個節點分散式協調服務。對zookeeper寫入請求會轉發到leader,leader寫入完成,並同步到其他節點,直到所有節點都寫入完成,才返回客戶端寫入成功。zookeeper還有幾個特質,讓它非常適合作為分散式鎖服務。
(1)zookeeper支援watcher機制,這樣實現阻塞鎖,可以watch鎖資料,等到資料被刪除, zookeeper會通知客戶端去重新競爭鎖。
(2)zookeeper的資料可以支援臨時節點的概念,即客戶端寫入的資料是臨時資料,在客戶端宕機後,臨時資料會被刪除,這樣就實現了鎖的異常釋放。使用這樣的方式,就不需要給鎖增加超時自動釋放的特性了。
zookeeper實現鎖的方式有兩種:
(1)客戶端一起競爭寫某條資料,比如/path/lock,只有第一個客戶端能寫入成功,其他的客戶端都會寫入失敗。寫入成功的客戶端就獲得了鎖,寫入失敗的客戶端,註冊watch事件,等待鎖的釋放,從而繼續競爭該鎖。
(2)客戶端一起寫入資料(建立自己的節點,臨時順序節點),所以每個節點的名字裡都有序號,然後檢查自己的序號是否是最小的,如果是成功獲取鎖,否則阻塞並新增一個watch監控自己前面的那個節點是否存在,當一個節點釋放鎖時會刪除節點,這是會通知監控該節點狀態的節點(下個節點),節點被啟用之後去嘗試獲取鎖(check自己的ID是不是最小的),如此類推。
優缺點:
- 叢集中的節點有一個leader,客戶端只要從leader獲取鎖,其他節點能同步leader的資料,這樣使用強一致性的分散式協調服務,分割槽、超時、衝突等問題都不會存在。所以為了保證分佈。

下面是Zookeeper分散式鎖的簡單例子:

package com.zoo.example;

import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.Semaphore;

import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;

import com.zoo.util.ConnectionUtil;

/**
 * 
 * @author Tim
 *
 */
public class Locks {
    public static final Logger logger = Logger.getLogger(Locks.class);
    /**
     * 這個訊號量的作用是讓獲取鎖的執行緒阻塞,這也是分散式鎖設計的一個特性,否則執行緒獲取不到鎖立即失敗返回
     */
    private Semaphore semaphore = new Semaphore(1);

    private static ThreadLocal<String> myZnode = new ThreadLocal<String>();
    private ZooKeeper zk =  
    ConnectionUtil.connect("172.23.27.1:2181,172.23.27.2:2181,172.23.27.3:2181", 
                           20000,
                           semaphore);
    private static String root = "/locks";
    private static String separator = "/";

    /**
     * 嘗試建立Zookeeper節點,從順序值最小的節點開始依次獲取鎖,節點刪除(釋放鎖),通過Zookeeper監聽機制會喚醒
     * 下一個節點,節點的順序代表獲取鎖的順序,節點的型別是 CreateMode.EPHEMERAL_SEQUENTIAL。
     * @return
     * @throws InterruptedException
     * @throws KeeperException
     */
    boolean tryAquire() throws InterruptedException, KeeperException {
        semaphore.acquire();

        myZnode.set(zk.create(root + separator, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL));

        return acquireQueued();
    }

    /**
     * 獲取失敗,監聽前繼節點,阻塞等待喚醒
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    boolean acquireQueued() throws KeeperException, InterruptedException {

        List<String> list = zk.getChildren(root, false);        
        TreeSet<String> allNodes = new TreeSet<String>(list);

        if (!myZnode.get().equals(root + "/" + allNodes.first())) {

            /**
             * 在當前節點的前繼節點上新增監視器,節點刪除時會往客戶端傳送一個監聽事件,
             * 這個事件只會被監聽這個節點的節點(後繼節點)的監視器捕獲,監視節點代表的執行緒獲得訊號量被喚醒
             */
            if(null != zk.exists( root + "/" + String.format("%05d",                                                                                               
            Integer.parseInt(myZnode.get().substring(
            myZnode.get().lastIndexOf(separator)+1))-1), 
            new Watcher(){
                @Override
                public void process(WatchedEvent event){
                    if(event.getType() == Event.EventType.NodeDeleted){
                        System.out.println(Thread.currentThread().getName()+"節點被刪除!");
                        semaphore.release();
                    }
                }
            })){
                semaphore.acquire();
            }
        }
        return true;
    }

    /**
     * 刪除節點,釋放鎖
     */
    void release() {
        try {
            zk.delete(myZnode.get(), -1);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

}

5. Curator
Curator提供了一套Java類庫, 可以更容易的使用ZooKeeper。 ZooKeeper本身提供了JavaClient的訪問類,但是API太底層,不宜使用, 容易出錯。 Curator提供了三個元件。 Curatorclient用來替代ZOoKeeper提供的類, 它封裝了底層的管理並提供了一些有用的工具。Curator framework提供了高階的API來簡化ZooKeeper的使用。它增加了很多基於ZooKeeper的特性,幫助管理ZooKeeper的連線以及重試操作。Curator Recipes提供了使用ZooKeeper的一些通用的技巧(方法)。 除此之外, Curator Test提供了基於ZooKeeper的單元測試工具。所謂技巧(Recipes),也可以稱之為解決方案, 或者叫實現方案, 是指ZooKeeper的使用方法, 比如分散式的配置管理, Leader選舉等。提供了fluent程式設計模型,提供了master選舉,分散式鎖,分散式基數,分散式barrier,可以很方便的為日常生產所使用。

    public class CuratorLock {
               public static CuratorFramework curator(){
               String servers = "172.77.77.77:2181,172.77.77.78:2181,172.77.77.79:2181";
               CuratorFramework curator = CuratorFrameworkFactory.builder().retryPolicy(new                   
                                      ExponentialBackoffRetry(10000,3)).connectString(servers).build();
               curator.start();
               return curator;
           }
           public static void main(String[] args) {
               final InterProcessMutex lock = new InterProcessMutex(CuratorLock.curator(), "/dlock");

               Executor pool = Executors.newFixedThreadPool(10);
               for (int i = 0; i < 10; i ++) {
                   pool.execute(new Runnable() {
                   @Override
                   public void run() {
                        try {
                             System.out.println("trying to acquire lock!");
                             lock.acquire();
                             System.out.println(Thread.currentThread().getName());
                             TimeUnit.SECONDS.sleep(5);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }finally{
                            try {
                                lock.release();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                 });
             }
         }
       }

相關推薦

分散式技術分散式

1. 資料庫鎖 有兩種方式: (1)利用唯一鍵(主鍵):資料庫是有唯一主鍵規則的,主鍵不能重複,對於重複的主鍵會丟擲主鍵衝突異常。當我們想要鎖住某個方法時,執行以下SQL: insert into methodLock(me

01分散式基礎-分散式架構概述

一、分散式架構概述 什麼是分散式 分散式和叢集的關係 計算機的發展歷史 計算機的發展歷史 分散式架構的發展 架構的發展演變過程 第一版應用 第二版 單擊負載越來越高,資料庫伺服器和應用伺服器分離

APM監控-- 分散式系統服務跟蹤技術選型參考

選型目的        隨著公司業務的與日俱增,各個系統也越來越複雜,服務間的呼叫,服務的依賴,以及分析服務的效能問題也越棘手,因此引入服務追蹤系統尤為重要。現有的服務追蹤體系,基本都是參考Google的Dapper的體系來做的。通過跟蹤請求的處理過程,來對應用系統在前後端處

從Paxos到Zookeeper分散式一致性原理與實踐 讀書筆記之 分散式架構

1.1 從集中式到分散式  1 集中式特點  結構簡單,無需考慮對多個節點的部署和節點之間的協作。  2  分散式特點 分不性:在時間可空間上隨意分佈,機器的分佈情況隨時變動 對等性:計算機之間沒有主從之分,所有計算機之間是對等的。副本是分散式系統對資料

訊息中介軟體分散式系統事務一致性解決方案大對比,誰最好使?

原文轉載至:https://blog.csdn.net/lovesomnus/article/details/51785108   在分散式系統中,同時滿足“一致性”、“可用性”和“分割槽容錯性”三者是不可能的。分散式系統的事務一致性是一個技術難題,各種解決方案孰優孰劣? 在OLTP系統領域,

asp.net core mcroservices 機構之 分散式日誌

      一 簡介                     

使用queue 做一個分散式爬蟲

這個作為調配的 taskMaster.py #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/12/23 15:21 # @author : libaibuaidufu # @File : taskMaster.py #

分散式事務事務的隔離級別

事務的ACID **Atomic:**原子性,一堆SQL,要麼都成功,要麼都不執行,不允許一部分SQL執行成功,一部分SQL執行失敗。 **Consistency:**一致性,在一堆SQL執行之前,資料必須是正確的,執行之後,資料也必須是準確的。 **Isolation:**隔離

搭建redis叢集實現分散式快取

redis是一個高階快取,使用到redis作為我們專案的快取,所以就花了一天時間研究了一下redis的一些用法,因為沒轉linux虛擬機器,所以就決定先研究一下windows版本的redis叢集。主要是redis叢集的皮毛:   1.首先下載windows版本的redis:https://github.co

訊息中介軟體分散式系統事務一致性解決方案大對比,誰最好使?

在分散式系統中,同時滿足“一致性”、“可用性”和“分割槽容錯性”三者是不可能的。分散式系統的事務一致性是一個技術難題,各種解決方案孰優孰劣? 在OLTP系統領域,我們在很多業務場景下都會面臨事務一致性方面的需求,例如最經典的Bob給Smith轉賬的案例。傳統的企業開發,

10分鐘看懂: zookeeper 分散式ID

瘋狂創客圈 Java 分散式聊天室【 億級流量】實戰系列之 -25【 部落格園 總入口 】 文章目錄 寫在前面 1.1. **ZK 的分散式命名服務** 1.1.1. 分散式 ID 生成器的型別 UUID方案 1.1

用java搭建一個分散式伺服器

本專欄主要介紹如何用java實現一個小型分散式(單機多程序模式)伺服器,希望能給剛開始學分散式的朋友一些幫助。講得不對的地方歡迎在評論區指出。 用到的主要技術棧: netty 網路傳輸框架 spring-boot java網路程式設計 Java多執行緒

大資料學習總結 分散式Hadoop系統

Scala tips:在前面的類層次結構圖中可以看到,Null型別是所有AnyRef型別的子型別,也即它處於AnyRef類的底層,對應java中的null引用。而Nothing是scala類中所有類的子類,它處於scala類的最底層。     近期投入大資料組工作,就寫一

spring cloud 分散式實戰-- 初見

spring cloud Spring Cloud是一系列框架的有序集合。它利用Spring Boot的開發便利性巧妙地簡化了分散式系統基礎設施的開發,如服務發現註冊、配置中心、訊息匯流排、負載

Spark偽分散式安裝

筆者是在已安裝好的偽分散式Hadoop環境下安裝的Spark。虛擬機器環境為:centos7。 Hadoop版本號為: [centosm@centosm spark]$ hadoop version Hadoop 2.7.2 一、Spark偽分散式安裝

[虛擬機器VM][Ubuntu12.04]搭建Hadoop完全分散式環境

前言 大家都知道,Hadoop的部署方式可分為 單機模式 偽分散式 完全分散式 本篇主要講解的就是完全分散式。 搭建完全分散式的叢集環境,需要多臺的硬體裝置,作為初學者,為了搭建叢集去買多臺電腦,多少有點不現實,所以這裡我採用的是VM虛擬機器,模擬搭

MySQL的分片——分散式資料庫概述

系統分析:OLAP or OLTP? 在網際網路時代,海量資料的儲存與訪問成為系統設計與使用的瓶頸問題,對於海量資料處理,按照使用場景,主要分為兩種型別:聯機事務處理(OLTP)和聯機分析處理(OLAP)。   聯機事務處理(OLTP)也稱為面向交易的處理系統,其基本特徵

dubbo分散式學習---------專案搭建demo

         private static final long serialVersionUID = 1L;               private Integer id;     private String userAdress;//使用者地址     private String userId

JMeter學習—006—JMeter 命令列非GUI模式詳解-分散式遠端執行指令碼及檢視指定結果、日誌

JMeter分散式執行指令碼,以更好的達到預設的效能測試(併發)場景,前文解說了jmeter使用命令列執行各個引數的作用以及命令列使用範例,那麼此文就繼續前文,針對 JMeter 的命令列模式之分散式遠端執行模式進行詳細解說。一、應用場景 1、無需互動介面或受環境限制(l

初遇分散式系統

簡述 前期博文主要是針對一些分散式系統資料的學習筆記. 首先需要說明關於分散式中的一些認知誤區: 網路是可靠的 延遲為零 頻寬無限 網路非常安全 網路拓撲不會改變 只有一個管理者 傳輸開銷為零 網路同構(使用相同的配置和協議) 以上所述均為人生錯