主頁 > 後端開發 > 通俗講解分布式鎖:場景和使用方法

通俗講解分布式鎖:場景和使用方法

2022-01-19 16:50:05 後端開發

前言

對于鎖大家肯定不會陌生,比如 synchronized 關鍵字 和 ReentrantLock 可重入鎖,一般我們用其在多執行緒環境中控制對資源的并發訪問,但是隨著業務的發展,分布式的概念逐漸出現在我們系統中,我們在開發的程序中經常需要進行多個系統之間的互動,于是上面的加鎖方法就會失去作用,于是在分布式鎖就自然而然的誕生了,接下來我們來聊一聊分布式鎖實作的幾種方式,

分布式鎖的使用場景

  • 效率性:使用分布式鎖可以避免不同節點重復相同的作業,

  • 正確性:分布式鎖可以避免破壞正確性的發生,如果兩個節點在同一條資料上面操作,比如多個節點機器對同一個訂單操作不同的流程有可能會導致該筆訂單最后狀態出現錯誤,造成損失,

分布式鎖的幾種特性

  • 互斥性:和我們本地鎖一樣互斥性是最基本,但是分布式鎖需要保證在不同節點的不同執行緒的互斥,

  • 可重入性:同一個節點上的同一個執行緒如果獲取了鎖之后那么也可以再次獲取這個鎖,

  • 鎖超時:和本地鎖一樣支持鎖超時,防止死鎖,

  • 高效,高可用:加鎖和解鎖需要高效,同時也需要保證高可用防止分布式鎖失效,可以增加降級,

  • 支持阻塞和非阻塞:和ReentrantLock一樣支持lock和trylock以及tryLock(long timeOut),

  • 支持公平鎖和非公平鎖(可選):公平鎖的意思是按照請求加鎖的順序獲得鎖,非公平鎖就相反是無序的,

分布式鎖的幾種實作方式

分布式鎖有以下幾個方式:

  • MySql
  • Zk
  • Redis
  • 一些自研的分布式鎖(Chubby)

一、基于 Mysql 實作分布式鎖

1、首先,我們需要創建一個鎖表:

CREATE TABLE `resource_lock` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `resource_name` varchar(128) NOT NULL DEFAULT '' COMMENT '資源名稱',
    'node_info' varchar(128) DEFAULT '0' COMMENT '節點資訊',
    'count' int(11) NOT NULL DEFAULT '0' COMMENT  '鎖的次數,統計可重入鎖',
    'desc' varchar(128) DEFAULT NULL COMMENT '額外的描述資訊',
    `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
    `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
    PRIMARY KEY ('id'),
    UNIQUE KEY 'un_resource_name' ('resource_name')
) ENGINE=InnoDB DEFAULT CHARSET = utf8mb4;

2、lock

先進行查詢,如果有值,那么需要比較 node_info 是否一致,這里的 node_info 可以用機器 IP 和執行緒名字來表示,如果一致那么就加可重入鎖 count 的值,如果不一致那么就回傳 false ,如果沒有值那么直接插入一條資料,偽代碼如下:

// 添加事務,原子性
@Transaction
public void lock() {
    if (select * from resource_lock where resource_name = 'xxx' for update;) {
        // 判斷節點資訊是否一致
        if (currentNodeInfo == resultNodeInfo) {
            // 保住鎖的可重入性
            update resource_lock set count = count + 1 where resource_name = 'xxx';
            return true;
        } else {
            return false;
        }
    } else {
        // 插入新資料
        insert into resourceLock;
        return true;
    }
}

3、tryLock

偽代碼如下:

public boolean tryLock(long timeOut) {
    long stTime = System.currentTimeMillis();
    long endTimeOut = stTime + timeOut;

    while (endTimeOut > stTime) {
        if (mysqlLock.lock()) {
            return true;
        }

        // 休眠3s后重試
        LockSupport.parkNanos(1000 * 1000 * 1000 * 1);
        stTime = System.currentTimeMillis();
    }
    return false;
}

4、unlock

偽代碼如下:

@Transaction
public boolean unlock() {
    // 查詢是否有資料
    if (select * from resource_lock where resource_name = 'xxx' for update;) {
        // count為1那么可以洗掉,如果大于1那么需要減去1,
        if (count > 1) {
            update count = count - 1;
        } else {
            delete;
        }
    } else {
        return false;
    }
}

5、定時清理因為機器宕機導致的鎖未被釋放的問題

啟動一個定時任務,當這個鎖遠超過任務的執行時間,沒有被釋放我們就可以認定是節點掛了然后將其直接釋放,

二、基于單Redis節點的分布式鎖

首先,Redis客戶端為了獲取鎖,向Redis節點發送如下命令:

SET resource_name my_random_value NX PX 30000

上面的命令如果執行成功,則客戶端成功獲取到了鎖,接下來就可以訪問共享資源了;而如果上面的命令執行失敗,則說明獲取鎖失敗,

注意,在上面的SET命令中:

  • my_random_value是由客戶端生成的一個隨機字串,它要保證在足夠長的一段時間內在所有客戶端的所有獲取鎖的請求中都是唯一的,
  • NX表示只有當resource_name對應的key值不存在的時候才能SET成功,這保證了只有第一個請求的客戶端才能獲得鎖,而其它客戶端在鎖被釋放之前都無法獲得鎖,
  • PX 30000表示這個鎖有一個30秒的自動過期時間,當然,這里30秒只是一個例子,客戶端可以選擇合適的過期時間,

最后,當客戶端完成了對共享資源的操作之后,執行下面的Redis Lua腳本來釋放鎖:

if redis.call("get",KEYS[1]) == ARGV[1] then
     return redis.call("del",KEYS[1])
 else
     return 0
 end

這段Lua腳本在執行的時候要把前面的my_random_value作為 ARGV[1] 的值傳進去,把 resource_name 作為 KEYS[1] 的值傳進去,

至此,基于單Redis節點的分布式鎖的演算法就描述完了,

關鍵點總結

第一點:過期時間

首先第一個問題,這個鎖必須要設定一個過期時間,否則的話,當一個客戶端獲取鎖成功之后,假如它崩潰了,或者由于發生了網路分割(network partition)導致它再也無法和Redis節點通信了,那么它就會一直持有這個鎖,而其它客戶端永遠無法獲得鎖了,而且把這個過期時間稱為鎖的有效時間(lock validity time),獲得鎖的客戶端必須在這個時間之內完成對共享資源的訪問,

第二點:獲取鎖

第二個問題,第一步獲取鎖的操作,網上不少文章把它實作成了兩個Redis命令:

SETNX resource_name my_random_value
EXPIRE resource_name 30

雖然這兩個命令和前面演算法描述中的一個SET命令執行效果相同,但卻不是原子的,如果客戶端在執行完SETNX后崩潰了,那么就沒有機會執行EXPIRE了,導致它一直持有這個鎖,

第三點:my_random_value

第三個問題,設定一個隨機字串 my_random_value 是很有必要的,它保證了一個客戶端釋放的鎖必須是自己持有的那個鎖,

假如獲取鎖時SET的不是一個隨機字串,而是一個固定值,那么可能會發生下面的執行序列:

  • 客戶端1獲取鎖成功,
  • 客戶端1在某個操作上阻塞了很長時間,
  • 過期時間到了,鎖自動釋放了,
  • 客戶端2獲取到了對應同一個資源的鎖,
  • 客戶端1從阻塞中恢復過來,釋放掉了客戶端2持有的鎖,
  • 之后,客戶端2在訪問共享資源的時候,就沒有鎖為它提供保護了,
第四點:Lua腳本

第四個問題,釋放鎖的操作必須使用Lua腳本來實作,釋放鎖其實包含三步操作:獲取、判斷和洗掉,用Lua腳本來實作能保證這三步的原子性,

否則,如果把這三步操作放到客戶端邏輯中去執行的話,就有可能發生與前面第三個問題類似的執行序列:

  • 客戶端1獲取鎖成功,
  • 客戶端1訪問共享資源,
  • 客戶端1為了釋放鎖,先執行'GET'操作獲取隨機字串的值,
  • 客戶端1判斷隨機字串的值,與預期的值相等,
  • 客戶端1由于某個原因阻塞住了很長時間,
  • 過期時間到了,鎖自動釋放了,
  • 客戶端2獲取到了對應同一個資源的鎖,
  • 客戶端1從阻塞中恢復過來,執行DEL操縱,釋放掉了客戶端2持有的鎖,

實際上,在上述第三個問題和第四個問題的分析中,如果不是客戶端阻塞住了,而是出現了大的網路延遲,也有可能導致類似的執行序列發生,

這四個問題,只要實作分布式鎖的時候加以注意,就都能夠被正確處理,

但除此之外,還有一個問題,是由 failover(故障轉移) 引起的,卻是基于單Redis節點的分布式鎖無法解決的,正是這個問題催生了Redlock的出現,

多個Redis節點的情況下會產生的問題

這個問題是這樣的,假如Redis節點宕機了,那么所有客戶端就都無法獲得鎖了,服務變得不可用,為了提高可用性,我們可以給這個Redis節點掛一個Slave,當Master節點不可用的時候,系統自動切到Slave上(failover),但由于Redis的主從復制(replication)是異步的,這可能導致在failover程序中喪失鎖的安全性,

例如下面的執行序列:

  • 客戶端1從Master獲取了鎖,
  • Master宕機了,存盤鎖的key還沒有來得及同步到Slave上,
  • Slave升級為Master,
  • 客戶端2從新的Master獲取到了對應同一個資源的鎖,

于是,客戶端1和客戶端2同時持有了同一個資源的鎖,鎖的安全性被打破,

三、分布式鎖 Redlock

前面介紹的基于單Redis節點的分布式鎖在failover的時候會產生解決不了的安全性問題,因此antirez提出了新的分布式鎖的演算法Redlock,它基于N個完全獨立的Redis節點(通常情況下N可以設定成5),

運行Redlock演算法的客戶端依次執行下面各個步驟,來完成獲取鎖的操作:

1、獲取當前時間(毫秒數),

2、按順序依次向N個Redis節點執行獲取鎖的操作,這個獲取操作跟前面基于單Redis節點的獲取鎖的程序相同,包含隨機字串my_random_value,也包含過期時間(比如PX 30000,即鎖的有效時間),

為了保證在某個Redis節點不可用的時候演算法能夠繼續運行,這個獲取鎖的操作還有一個超時時間(time out),它要遠小于鎖的有效時間(幾十毫秒量級),客戶端在向某個Redis節點獲取鎖失敗以后,應該立即嘗試下一個Redis節點,

這里的失敗,應該包含任何型別的失敗,比如該Redis節點不可用,或者該Redis節點上的鎖已經被其它客戶端持有(注:Redlock原文中這里只提到了Redis節點不可用的情況,但也應該包含其它的失敗情況),

3、計算整個獲取鎖的程序總共消耗了多長時間,計算方法是用當前時間減去第1步記錄的時間,如果客戶端從大多數Redis節點(>= N/2+1)成功獲取到了鎖,并且獲取鎖總共消耗的時間沒有超過鎖的有效時間(lock validity time),那么這時客戶端才認為最侄訓取鎖成功;否則,認為最侄訓取鎖失敗,

4、如果最侄訓取鎖成功了,那么這個鎖的有效時間應該重新計算,它等于最初的鎖的有效時間減去第3步計算出來的獲取鎖消耗的時間,

5、如果最侄訓取鎖失敗了(可能由于獲取到鎖的Redis節點個數少于N/2+1,或者整個獲取鎖的程序消耗的時間超過了鎖的最初有效時間),那么客戶端應該立即向所有Redis節點發起釋放鎖的操作(即前面介紹的Redis Lua腳本),

上面描述的只是獲取鎖的程序,而釋放鎖的程序比較簡單:客戶端向所有Redis節點發起釋放鎖的操作,不管這些節點當時在獲取鎖的時候成功與否,

由于N個Redis節點中的大多數能正常作業就能保證Redlock正常作業,因此理論上它的可用性更高,我們前面討論的單Redis節點的分布式鎖在failover的時候鎖失效的問題,在Redlock中不存在了,但如果有節點發生崩潰重啟,還是會對鎖的安全性有影響的,具體的影響程度跟Redis對資料的持久化程度有關,

節點崩潰可能導致的問題

假設一共有5個Redis節點:A, B, C, D, E,設想發生了如下的事件序列:

1、客戶端1成功鎖住了A, B, C,獲取鎖成功(但D和E沒有鎖住),

2、節點C崩潰重啟了,但客戶端1在C上加的鎖沒有持久化下來,丟失了,

3、節點C重啟后,客戶端2鎖住了C, D, E,獲取鎖成功,

4、這樣,客戶端1和客戶端2同時獲得了鎖(針對同一資源),

在默認情況下,Redis的AOF持久化方式是每秒寫一次磁盤(即執行fsync),因此最壞情況下可能丟失1秒的資料,為了盡可能不丟資料,Redis允許設定成每次修改資料都進行fsync,但這會降低性能,當然,即使執行了fsync也仍然有可能丟失資料(這取決于系統而不是Redis的實作),

所以,上面分析的由于節點重啟引發的鎖失效問題,總是有可能出現的,為了應對這一問題,antirez又提出了延遲重啟(delayed restarts)的概念,

也就是說,一個節點崩潰后,先不立即重啟它,而是等待一段時間再重啟,這段時間應該大于鎖的有效時間(lock validity time),這樣的話,這個節點在重啟前所參與的鎖都會過期,它在重啟后就不會對現有的鎖造成影響,

客戶端應該向所有Redis節點發起釋放鎖的操作?

在最后釋放鎖的時候,antirez在演算法描述中特別強調,客戶端應該向所有Redis節點發起釋放鎖的操作,也就是說,即使當時向某個節點獲取鎖沒有成功,在釋放鎖的時候也不應該漏掉這個節點,這是為什么呢?

設想這樣一種情況,客戶端發給某個Redis節點的獲取鎖的請求成功到達了該Redis節點,這個節點也成功執行了SET操作,但是它回傳給客戶端的回應包卻丟失了,這在客戶端看來,獲取鎖的請求由于超時而失敗了,但在Redis這邊看來,加鎖已經成功了,

因此,釋放鎖的時候,客戶端也應該對當時獲取鎖失敗的那些Redis節點同樣發起請求,實際上,這種情況在異步通信模型中是有可能發生的:客戶端向服務器通信是正常的,但反方向卻是有問題的,

四、基于zk實作分布式鎖

ZooKeeper是以Paxos演算法為基礎分布式應用程式協調服務,Zk的資料節點和檔案目錄類似,所以我們可以用此特性實作分布式鎖,

基本實作步驟如下:

1、客戶端嘗試創建一個znode節點,比如/lock,那么第一個客戶端就創建成功了,相當于拿到了鎖;而其它的客戶端會創建失敗(znode已存在),獲取鎖失敗,

2、持有鎖的客戶端訪問共享資源完成后,將znode刪掉,這樣其它客戶端接下來就能來獲取鎖了,

注意:這里的znode應該被創建成ephemeral的(臨時節點),這是znode的一個特性,它保證如果創建znode的那個客戶端崩潰了,那么相應的znode會被自動洗掉,這保證了鎖一定會被釋放,

可能存在的問題

看起來這個鎖相當完美,沒有Redlock過期時間的問題,而且能在需要的時候讓鎖自動釋放,但其實也存在這其中也存在問題,

ZooKeeper是怎么檢測出某個客戶端已經崩潰了呢?

實際上,每個客戶端都與ZooKeeper的某臺服務器維護著一個Session,這個Session依賴定期的心跳(heartbeat)來維持,如果ZooKeeper長時間收不到客戶端的心跳(這個時間稱為Sesion的過期時間),那么它就認為Session過期了,通過這個Session所創建的所有的ephemeral型別的znode節點都會被自動洗掉,

假如按照下面的順序執行:

1、客戶端1創建了znode節點/lock,獲得了鎖,

2、客戶端1進入了長時間的GC pause,

3、客戶端1連接到ZooKeeper的Session過期了,znode節點/lock被自動洗掉,

4、客戶端2創建了znode節點/lock,從而獲得了鎖,

5、客戶端1從GC pause中恢復過來,它仍然認為自己持有鎖,

由上面的執行順序,可以發現最后客戶端1和客戶端2都認為自己持有了鎖,沖突了,所以說,用ZooKeeper實作的分布式鎖也不一定就是安全的,該有的問題它還是有,

zk的watch機制

ZooKeeper有個很特殊的機制--watch機制,這個機制可以這樣來使用,比如當客戶端試圖創建 /lock 節點的時候,發現它已經存在了,這時候創建失敗,但客戶端不一定就此對外宣告獲取鎖失敗,

客戶端可以進入一種等待狀態,等待當/lock節點被洗掉的時候,ZooKeeper通過watch機制通知它,這樣它就可以繼續完成創建操作(獲取鎖),這可以讓分布式鎖在客戶端用起來就像一個本地的鎖一樣:加鎖失敗就阻塞住,直到獲取到鎖為止,

參考文章

  • https://juejin.cn/post/6844903688088059912
  • Redlock的演算法:https://redis.io/topics/distlock
  • https://github.com/redisson/redisson
  • linux的同步IO操作函式: sync、fsync與fdatasync:https://my.oschina.net/u/1377774/blog/529847
  • https://mp.weixin.qq.com/s/JTsJCDuasgIJ0j95K8Ay8w
【Java知音】公眾號,每天早上8:30為您準時推送一篇技術文章 在Java知音公眾號內回復“面試題聚合”,送你一份Java面試題寶典,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/415221.html

標籤:Java

上一篇:換掉 Log4j2!tinylog 橫空出世,無需定義 logger 變數,簡單、輕量、性能爆炸!

下一篇:python錯誤與例外

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more