分布式鎖與實現(一)——基于Redis實現

概述

目前幾乎很多大型網站及應用都是分布式部署的,分布式場景中的數據一致性問題一直是一個比較重要的話題。分布式的CAP理論告訴我們“任何一個分布式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多只能同時滿足兩項。”所以,很多系統在設計之初就要對這三者做出取舍。在互聯網領域的絕大多數的場景中,都需要犧牲強一致性來換取系統的高可用性,系統往往只需要保證“最終一致性”,只要這個最終時間是在用戶可以接受的范圍內即可。

在很多場景中,我們為了保證數據的最終一致性,需要很多的技術方案來支持,比如分布式事務、分布式鎖等。

選用Redis實現分布式鎖原因

  • Redis有很高的性能
  • Redis命令對此支持較好,實現起來比較方便

在此就不介紹Redis的安裝了,具體在Linux和Windows中的安裝可以查看我前面的博客。
http://www.cnblogs.com/liuyang0/p/6504826.html

使用命令介紹

SETNX

SETNX key val
當且僅當key不存在時,set一個key為val的字符串,返回1;若key存在,則什么都不做,返回0。

expire

expire key timeout
為key設置一個超時時間,單位為second,超過這個時間鎖會自動釋放,避免死鎖。

delete

delete key
刪除key

在使用Redis實現分布式鎖的時候,主要就會使用到這三個命令。

實現

使用的是jedis來連接Redis。

實現思想

  • 獲取鎖的時候,使用setnx加鎖,并使用expire命令為鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。
  • 獲取鎖的時候還設置一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
  • 釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。

分布式鎖的核心代碼如下:

  1 package com.distribute;
  2 
  3 import redis.clients.jedis.Jedis;
  4 import redis.clients.jedis.JedisPool;
  5 import redis.clients.jedis.Transaction;
  6 import redis.clients.jedis.exceptions.JedisException;
  7 
  8 import java.util.List;
  9 import java.util.UUID;
 10 
 11 /**
 12  * Created by liuyang on 2017/4/20.
 13  */
 14 public class DistributedLock {
 15     private final JedisPool jedisPool;
 16 
 17     public DistributedLock(JedisPool jedisPool) {
 18         this.jedisPool = jedisPool;
 19     }
 20 
 21     /**
 22      * 加鎖
 23      *
 24      * @param locaName       鎖的key
 25      * @param acquireTimeout 獲取超時時間
 26      * @param timeout        鎖的超時時間
 27      * @return 鎖標識
 28      */
 29     public String lockWithTimeout(String locaName, long acquireTimeout, long timeout) {
 30         Jedis conn = null;
 31         String retIdentifier = null;
 32         try {
 33             // 獲取連接
 34             conn = jedisPool.getResource();
 35             // 隨機生成一個value
 36             String identifier = UUID.randomUUID().toString();
 37             // 鎖名,即key值
 38             String lockKey = "lock:" + locaName;
 39             // 超時時間,上鎖后超過此時間則自動釋放鎖
 40             int lockExpire = (int) (timeout / 1000);
 41 
 42             // 獲取鎖的超時時間,超過這個時間則放棄獲取鎖
 43             long end = System.currentTimeMillis() + acquireTimeout;
 44             while (System.currentTimeMillis() < end) {
 45                 if (conn.setnx(lockKey, identifier) == 1) {
 46                     conn.expire(lockKey, lockExpire);
 47                     // 返回value值,用于釋放鎖時間確認
 48                     retIdentifier = identifier;
 49                     return retIdentifier;
 50                 }
 51                 // 返回-1代表key沒有設置超時時間,為key設置一個超時時間
 52                 if (conn.ttl(lockKey) == -1) {
 53                     conn.expire(lockKey, lockExpire);
 54                 }
 55 
 56                 try {
 57                     Thread.sleep(10);
 58                 } catch (InterruptedException e) {
 59                     Thread.currentThread().interrupt();
 60                 }
 61             }
 62         } catch (JedisException e) {
 63             e.printStackTrace();
 64         } finally {
 65             if (conn != null) {
 66                 conn.close();
 67             }
 68         }
 69         return retIdentifier;
 70     }
 71 
 72     /**
 73      * 釋放鎖
 74      *
 75      * @param lockName   鎖的key
 76      * @param identifier 釋放鎖的標識
 77      * @return
 78      */
 79     public boolean releaseLock(String lockName, String identifier) {
 80         Jedis conn = null;
 81         String lockKey = "lock:" + lockName;
 82         boolean retFlag = false;
 83         try {
 84             conn = jedisPool.getResource();
 85             while (true) {
 86                 // 監視lock,準備開始事務
 87                 conn.watch(lockKey);
 88                 // 通過前面返回的value值判斷是不是該鎖,若是該鎖,則刪除,釋放鎖
 89                 if (identifier.equals(conn.get(lockKey))) {
 90                     Transaction transaction = conn.multi();
 91                     transaction.del(lockKey);
 92                     List<Object> results = transaction.exec();
 93                     if (results == null) {
 94                         continue;
 95                     }
 96                     retFlag = true;
 97                 }
 98                 conn.unwatch();
 99                 break;
100             }
101         } catch (JedisException e) {
102             e.printStackTrace();
103         } finally {
104             if (conn != null) {
105                 conn.close();
106             }
107         }
108         return retFlag;
109     }
110 }

測試

下面就用一個簡單的例子測試剛才實現的分布式鎖。
例子中使用50個線程模擬秒殺一個商品,使用--運算符來實現商品減少,從結果有序性就可以看出是否為加鎖狀態。

模擬秒殺服務,在其中配置了jedis線程池,在初始化的時候傳給分布式鎖,供其使用。

 1 import redis.clients.jedis.JedisPool;
 2 import redis.clients.jedis.JedisPoolConfig;
 3 
 4 /**
 5  * Created by liuyang on 2017/4/20.
 6  */
 7 public class Service {
 8     private static JedisPool pool = null;
 9 
10     static {
11         JedisPoolConfig config = new JedisPoolConfig();
12         // 設置最大連接數
13         config.setMaxTotal(200);
14         // 設置最大空閑數
15         config.setMaxIdle(8);
16         // 設置最大等待時間
17         config.setMaxWaitMillis(1000 * 100);
18         // 在borrow一個jedis實例時,是否需要驗證,若為true,則所有jedis實例均是可用的
19         config.setTestOnBorrow(true);
20         pool = new JedisPool(config, "127.0.0.1", 6379, 3000);
21     }
22 
23     DistributedLock lock = new DistributedLock(pool);
24 
25     int n = 500;
26 
27     public void seckill() {
28         // 返回鎖的value值,供釋放鎖時候進行判斷
29         String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
30         System.out.println(Thread.currentThread().getName() + "獲得了鎖");
31         System.out.println(--n);
32         lock.releaseLock("resource", indentifier);
33     }
34 }

?

// 模擬線程進行秒殺服務

 1 public class ThreadA extends Thread {
 2     private Service service;
 3 
 4     public ThreadA(Service service) {
 5         this.service = service;
 6     }
 7 
 8     @Override
 9     public void run() {
10         service.seckill();
11     }
12 }
13 
14 public class Test {
15     public static void main(String[] args) {
16         Service service = new Service();
17         for (int i = 0; i < 50; i++) {
18             ThreadA threadA = new ThreadA(service);
19             threadA.start();
20         }
21     }
22 }

?

結果如下,結果為有序的。

1 public void seckill() {
2     // 返回鎖的value值,供釋放鎖時候進行判斷
3     //String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
4     System.out.println(Thread.currentThread().getName() + "獲得了鎖");
5     System.out.println(--n);
6     //lock.releaseLock("resource", indentifier);
7 }

從結果可以看出,有一些是異步進行的。

在分布式環境中,對資源進行上鎖有時候是很重要的,比如搶購某一資源,這時候使用分布式鎖就可以很好地控制資源。
當然,在具體使用中,還需要考慮很多因素,比如超時時間的選取,獲取鎖時間的選取對并發量都有很大的影響,上述實現的分布式鎖也只是一種簡單的實現,主要是一種思想。

下一次我會使用zookeeper實現分布式鎖,使用zookeeper的可靠性是要大于使用redis實現的分布式鎖的,但是相比而言,redis的性能更好。

上面的代碼可以在我的GitHub中進行查看,地址如下:
https://github.com/yangliu0/DistributedLock

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254530.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254530.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254530.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

淺析軟件項目管理中十個誤區(來自:http://manager.csdn.net/n/20051213/30907.html)

隨著計算機硬件水平的不斷提高&#xff0c;計算機軟件的規模和復雜度也隨之增加。計算機軟件開發從“個人英雄”時代向團隊時代邁進&#xff0c;計算機軟件項目的管理也從“作坊式”管理向“軟件工廠式”管理邁進。這就要求軟件開發人員特別是軟件項目管理人員更深一步地理解和…

倆孩隨筆

倆孩隨筆 有人給我貼了技術男加奶爸的標簽&#xff0c;不過這兩項都不是我的強項。我深知自己最大的長處在哪&#xff1a;普通&#xff0c;扔人堆里&#xff0c;不是認不出來&#xff0c;而是壓根看不著。想把事情做好&#xff0c;常常會用力過度。工作平平淡淡&#xff0c;需…

Inpainting圖像修復halcon算子,持續更新

目錄harmonic_interpolationinpainting_anisoinpainting_cedinpainting_ctinpainting_mcfinpainting_textureharmonic_interpolation 功能&#xff1a;對一個圖像區域執行諧波插值。 inpainting_aniso 功能&#xff1a;通過各向異性擴散執行圖像修復。 inpainting_ced 功能…

算法(偽代碼)的書寫

q ? Queue&#xff08;隊列&#xff09;, s ? Set&#xff08;集合&#xff09;pq ? PriorityQueue&#xff08;優先隊列&#xff09;d ? distance1. initialization 變量&#xff0c;數據結構的定義&#xff0c;及初始值的賦值&#xff1b;比如著名的 Dijkstra &#xff0…

第八次作業——系統設計與團隊分配(個人)

團隊作業地址&#xff1a;https://www.cnblogs.com/clio-hhhhhhl/p/9079157.html 個人碼云地址&#xff1a;https://gitee.com/Cliohl/events 團隊碼云地址&#xff1a;https://gitee.com/Cliohl/zhuoyue 項目進展&#xff1a; 上禮拜進展&#xff1a;把部分前端頁面實現出來&a…

深入理解Activity啟動流程(二)–Activity啟動相關類的類圖

本文原創作者:Cloud Chou. 歡迎轉載&#xff0c;請注明出處和本文鏈接 本系列博客將詳細闡述Activity的啟動流程&#xff0c;這些博客基于Cm 10.1源碼研究。 在介紹Activity的詳細啟動流程之前&#xff0c;先為大家介紹Activity啟動時涉及到的類&#xff0c;這樣大家可以有大概…

Lines色線halcon算子,持續更新

目錄bandpass_imagelines_colorlines_facetlines_gaussbandpass_image 功能&#xff1a;使用帶通濾波器提取邊緣。 lines_color 功能&#xff1a;檢測色線和它們的寬度。 lines_facet 功能&#xff1a;使用面模型檢測線。 lines_gauss 功能&#xff1a;檢測線和它們的寬…

疑問+軟件

問題 1學好軟件工程需要打好哪些基礎&#xff1f; 2怎樣學好軟件工程&#xff1f; 3軟件工程的發展前景如何&#xff1f; 4軟件工程具體能從事哪些職業&#xff1f; 5怎樣準確的找到自己的定位&#xff1f; 軟件 1支付寶 優點&#xff1a;支付便捷&#xff0c;轉賬方便&#xf…

成功的項目管理

內容提要 第一講 項目與項目管理 1.項目的基本概念 2.項目管理的概念 3.項目管理的重要性 第二講 項目管理的最新發展 1.現代項目管理的創立過程 2.現代項目管理的發展趨勢 3.項目管理流行的原因 第三講 信息時代的項目管理 1.信息時代的特點 2.信息時代項目管理的特點 3.按項目…

5月23日

11.1 LAMP架構介紹一、LAMP架構介紹LAMP是LinuxApache(httpd)MySQLPHP的簡寫&#xff0c;即把Apache、MySQL以及PHP安裝在linux系統上&#xff0c;組成一個運行環境來運行PHP腳本語言&#xff0c;通常是網站。比如Google、淘寶、百度、51cto博客、猿課論壇等就是用PHP語言寫出來…

Kotlin入門簡介

Kotlin的“簡歷” 來自于著名的IDE IntelliJ IDEA(Android Studio基于此開發) 軟件開發公司 JetBrains(位于東歐捷克)起源來自JetBrains的圣彼得堡團隊&#xff0c;名稱取自圣彼得堡附近的一個小島(Kotlin Island)一種基于JVM的靜態類型編程語言語法簡單&#xff0c;不啰嗦 1 2…

LaTeX基礎一:安裝與基本操作

一、安裝 1.首先下載texlive2015.iso文件。再在解壓的鏡像文件中運行install-tl-advanced.bat批處理命令。注意要關閉殺毒軟件&#xff0c;否則可能會出現錯誤。2.可以修改一下安裝路徑&#xff0c;只要更改一個&#xff0c;其他也隨之更改&#xff1a;3.把不要安裝的語言包去掉…

Match圖像匹配halcon算子,持續更新

目錄exhaustive_matchexhaustive_match_mggen_gauss_pyramidmonotonyexhaustive_match 功能&#xff1a;模板和圖像的匹配。 exhaustive_match_mg 功能&#xff1a;在一個分辨率塔式結構中匹配模板和圖像。 gen_gauss_pyramid 功能&#xff1a;計算一個高斯金字塔。 mono…

c++虛函數的前奏--函數指針與回調

聲明函數指針并實現回調程序員常常需要實現回調。本文將討論函數指針的基本原則并說明如何使用函數指針實現回調。注意這里針對的是普通的函數&#xff0c;不包括完全依賴于不同語法和語義規則的類成員函數&#xff08;類成員指針將在另文中討論&#xff09;。 聲明函數指針 …

你的微信,到底「連接」多少人?

作為一個技術人&#xff0c;用微信這么多年&#xff0c;也加了不少的好友和技術群。大半個月前&#xff0c;大概是5月初的時候&#xff0c;萌生了這個想法。 前后磕磕絆絆&#xff0c;經過了來美帝出差前期差點餓死、項目緊急期等艱難階段以及各種自學、溫習和設計&#xff0c;…

團隊項目第一周總結

團隊項目第一周總結 今天是周一&#xff0c;項目的進展周期已經進行了一周&#xff0c;做一個大概的總結吧。 周的工作先是定下團隊的項目方向&#xff0c;大家技術程度各有不同&#xff0c;因此想大一統的定下一個有些稍稍的花費時間。定好《基于大數據的日志分析》這個題目后…

電路寬度測量halcon例子

一個halcon處理的例子 目錄處理要求&#xff1a;原圖&#xff1a;處理程序&#xff1a;處理結果&#xff1a;處理要求&#xff1a; 根據客戶給的寬度&#xff0c;計算出電路寬度太窄的為NG 原圖&#xff1a; 處理程序&#xff1a; read_image (Image, 1.png) rgb1_to_gray(I…

深入理解C/C++函數指針

函數指針數組的妙用 筆者在開發某軟件過程中遇到這樣一個問題&#xff0c;前級模塊傳給我二進制數據&#xff0c;輸入參數為 char* buffer和 int length&#xff0c;buffer是數據的首地址&#xff0c;length表示這批數據的長度。數據的特點是&#xff1a;長度不定&#xff0c…

iOS顯示性能優化過程講解

點我跳轉原文地址 卡頓的原理 iOS系統界面滑動流暢性的保持主要是依靠CPU和GPU兩大處理硬件間通力合作的結果&#xff0c;一個視圖的顯示需要先經過CPU創建、布局計算、對圖片解碼、文本繪制&#xff0c;然后CPU將計算的結果交給GPU&#xff0c;GPU可能需要對圖形進行變換、合成…

asp.net web api集成微信服務(使用Senparc微信SDK)

/// <summary>/// 微信請求轉發控制器/// </summary>[RoutePrefix("weixin")]public class WeixinController : ApiController{#region 創建微信菜單/// <summary>/// 創建微信菜單/// </summary>/// <returns></returns>[HttpP…