分類: 3C資訊

  • Fork/Join框架詳解

    Fork/Join框架詳解

    Fork/Join框架是Java 7提供的一個用於并行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。Fork/Join框架要完成兩件事情:

    • 任務分割:首先Fork/Join框架需要把大的任務分割成足夠小的子任務,如果子任務比較大的話還要對子任務進行繼續分割

    • 執行任務併合並結果:分割的子任務分別放到雙端隊列里,然後幾個啟動線程分別從雙端隊列里獲取任務執行。子任務執行完的結果都放在另外一個隊列里,啟動一個線程從隊列里取數據,然後合併這些數據

    ForkJoinTask

    使用Fork/Join框架,首先需要創建一個ForkJoin任務。該類提供了在任務中執行fork和join的機制。通常情況下我們不需要直接集成ForkJoinTask類,只需要繼承它的子類,Fork/Join框架提供了兩個子類:

    • RecursiveAction
      用於沒有返回結果的任務
    • RecursiveTask
      用於有返回結果的任務

    ForkJoinPool

    ForkJoinTask需要通過ForkJoinPool來執行。

    任務分割出的子任務會添加到當前工作線程所維護的雙端隊列中,進入隊列的頭部。當一個工作線程的隊列里暫時沒有任務時,它會隨機從其他工作線程的隊列的尾部獲取一個任務(工作竊取算法);

    Fork/Join框架的實現原理

    ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,ForkJoinTask數組負責將存放程序提交給ForkJoinPool,而ForkJoinWorkerThread負責執行這些任務;

    ForkJoinTask的fork方法的實現原理

    當我們調用ForkJoinTask的fork方法時,程序會把任務放在ForkJoinWorkerThread的pushTask的workQueue中,異步地執行這個任務,然後立即返回結果,代碼如下:

    public final ForkJoinTask<V> fork() {
        Thread t;
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            ForkJoinPool.common.externalPush(this);
        return this;
    }

    pushTask方法把當前任務存放在ForkJoinTask數組隊列里。然後再調用ForkJoinPool的signalWork()方法喚醒或創建一個工作線程來執行任務。代碼如下:

    final void push(ForkJoinTask<?> task) {
        ForkJoinTask<?>[] a; ForkJoinPool p;
        int b = base, s = top, n;
        if ((a = array) != null) {    // ignore if queue removed
            int m = a.length - 1;     // fenced write for task visibility
            U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
            U.putOrderedInt(this, QTOP, s + 1);
            if ((n = s - b) <= 1) {
                if ((p = pool) != null)
                    p.signalWork(p.workQueues, this);
            }
            else if (n >= m)
                growArray();
        }
    }

    ForkJoinTask的join方法的實現原理

    Join方法的主要作用是阻塞當前線程並等待獲取結果。讓我們一起看看ForkJoinTask的join方法的實現,代碼如下:

    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL){
            reportException(s);
        }
        return getRawResult();
    }

    它首先調用doJoin方法,通過doJoin()方法得到當前任務的狀態來判斷返回什麼結果,任務狀態有4種:已完成(NORMAL)、被取消(CANCELLED)、信號(SIGNAL)和出現異常(EXCEPTIONAL);
    如果任務狀態是已完成,則直接返回任務結果;
    如果任務狀態是被取消,則直接拋出CancellationException;
    如果任務狀態是拋出異常,則直接拋出對應的異常;
    doJoin方法的實現,代碼如下:

    private int doJoin() {
        int s;
        Thread t;
        ForkJoinWorkerThread wt;
        ForkJoinPool.WorkQueue w;
        return (s = status) < 0 ? s :
                ((t = Thread.currentThread()) instanceof                                ForkJoinWorkerThread) ? (w = (wt =                                      (ForkJoinWorkerThread)t).workQueue).tryUnpush(this) && (s =                 doExec()) < 0 ? s : wt.pool.awaitJoin(w, this, 0L) :                externalAwaitDone();
    }

    doExec() :

    final int doExec() {
        int s; 
        boolean completed;
        if ((s = status) >= 0) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                return setExceptionalCompletion(rex);
            }
            if (completed){
                s = setCompletion(NORMAL);
            }
        }
        return s;
    }

    在doJoin()方法里,首先通過查看任務的狀態,看任務是否已經執行完成,如果執行完成,則直接返回任務狀態;如果沒有執行完,則從任務數組裡取出任務並執行。如果任務順利執行完成,則設置任務狀態為NORMAL,如果出現異常,則記錄異常,並將任務狀態設置為EXCEPTIONAL

    Fork/Join框架的異常處理

    ForkJoinTask在執行的時候可能會拋出異常,但是我們沒辦法在主線程里直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經拋出異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。代碼如下:

    if(task.isCompletedAbnormally())
    {
        System.out.println(task.getException());
    }

    getException方法返回Throwable對象,如果任務被取消了則返回CancellationException。如果任務沒有完成或者沒有拋出異常則返回null:

    public final Throwable getException() {
        int s = status & DONE_MASK;
        return ((s >= NORMAL) ? null :
            (s == CANCELLED) ? new CancellationException() :
            getThrowableException());
    }

    DEMO

    需求:求1+2+3+4的結果
    分析:Fork/Join框架首先要考慮到的是如何分割任務,如果希望每個子任務最多執行兩個數的相加,那麼我們設置分割的閾值是2,由於是4個数字相加,所以Fork/Join框架會把這個任務fork成兩個子任務,子任務一負責計算1+2,子任務二負責計算3+4,然後再join兩個子任務的結果。因為是有結果的任務,所以必須繼承RecursiveTask,實現代碼如下:

    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.Future;
    import java.util.concurrent.RecursiveTask;
    
    /**
     *
     * @author aikq
     * @date 2018年11月21日 20:37
     */
    public class ForkJoinTaskDemo {
    
        public static void main(String[] args) {
            ForkJoinPool pool = new ForkJoinPool();
            CountTask task = new CountTask(1,4);
            Future<Integer> result = pool.submit(task);
            try {
                System.out.println("計算結果=" + result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    
    class CountTask extends RecursiveTask<Integer>{
        private static final long serialVersionUID = -7524245439872879478L;
    
        private static final int THREAD_HOLD = 2;
    
        private int start;
        private int end;
    
        public CountTask(int start,int end){
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected Integer compute() {
            int sum = 0;
            //如果任務足夠小就計算
            boolean canCompute = (end - start) <= THREAD_HOLD;
            if(canCompute){
                for(int i=start;i<=end;i++){
                    sum += i;
                }
            }else{
                int middle = (start + end) / 2;
                CountTask left = new CountTask(start,middle);
                CountTask right = new CountTask(middle+1,end);
                //執行子任務
                left.fork();
                right.fork();
                //獲取子任務結果
                int lResult = left.join();
                int rResult = right.join();
                sum = lResult + rResult;
            }
            return sum;
        }
    }

    通過這個例子,我們進一步了解ForkJoinTask,ForkJoinTask與一般任務的主要區別在於它需要實現compute方法,在這個方法里,首先需要判斷任務是否足夠小,如果足夠小就直接執行任務。如果不足夠小,就必須分割成兩個子任務,每個子任務在調用fork方法時,又會進入compute方法,看看當前子任務是否需要繼續分割成子任務,如果不需要繼續分割,則執行當前子任務並返回結果。使用join方法會等待子任務執行完並得到其結果

    本文由博客一文多發平台 發布!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • paper sharing :學習特徵演化的數據流

    paper sharing :學習特徵演化的數據流

    特徵演化的數據流

        數據流學習是近年來機器學習與數據挖掘領域的一個熱門的研究方向,數據流的場景和靜態數據集的場景最大的一個特點就是數據會發生演化,關於演化數據流的研究大多集中於概念漂移檢測(有監督學習),概念/聚類演化分析(無監督學習),然而,人們往往忽略了一個經常出現的演化場景:特徵演化。大多數研究都考慮數據流的特徵空間是固定的,然而,在很多場景下這一假設並不成立:例如,當有限壽命傳感器收集的數據被新的傳感器替代時,這些傳感器對應的特徵將發生變化。

        今天要分享的文章出自周志華的實驗室《Learning with Feature Evolvable Streams》(NIPS 2017),它提出了一個新的場景,即在數據流中會有特徵消亡也會有新特徵出現。當出現新的特徵空間時,我們並不直接拋棄之前學到的模型並在新的數據上重新創建模型,而是嘗試恢復消失的特徵來提升模型的表現。具體來說,通過從恢復的特徵和新的特徵空間中分別學習兩個模型。為了從恢復的特徵中獲得提升,論文中提出了兩種集成策略:第一種方法是合併兩個模型的預測結果;第二種是選擇最佳的預測模型。下面我們具體來理解特徵演化數據流以及論文中提出的一些有趣的方法吧~

    paper link:

     

    什麼是特徵演化數據流?

        在很多現實的任務中,數據都是源源不斷收集的,關於數據流學習的研究近年來受到越來越多的關注,雖然已經有很多有效的算法針對特定的場景對數據流進行挖掘,但是它們都基於一個假設就是數據流中數據的特徵空間是穩定的。不幸的是,這一假設在很多場景下都不滿足。針對特徵演化的場景,最直接的想法就是利用新的特徵空間的數據學習一個新的模型,但是這一方法有很多問題:首先,當新的特徵剛出現的時候,只有很少的數據樣本來描述這些信息,訓練樣本並不足夠去學習一個新的模型;其次,包含消失特徵的舊模型被直接丟棄了,其中可能包含對當前數據有用的信息。論文中定義了一種特徵演化數據流的場景:一般情況下,特徵不會任意改變,而在一些重疊時期,新特徵和舊特徵都存在,如下圖所示:

        其中,T1階段,原始特徵集都是有效的,B1階段出現了新的特徵集,T2階段原始特徵集消失,只有新的特徵集。

        論文提出的方法是通過使用重疊(B1)階段來發現新舊特徵之間的關係,嘗試學習新特徵到舊特徵的一個映射,這樣就可以通過重構舊特徵並使用舊模型對新數據進行預測

    問題描述

        論文中着重解決的是分類和回歸任務,在每一輪學習過程中,對每一個實例進行預測,結合它的真實標籤會得到一個loss(反映預測和真實標籤的差異),我們將上面提到的T1+B1+T的過程稱為一個周期,每個周期中只包含兩個特徵空間,所以,之後的研究主要關注一個周期內的模型的學習,而且,我們假設一個周期內的舊特徵會同時消失。定義Ω1和Ω2分別表示兩個特徵空間S1和S2上的線性模型,並定義映射,定義第i維特徵在第t輪的預測函數為線性模型,。損失函數是凸的,最直接的方式是使用在線梯度下降來求解w,但是在數據流上不適用。

     

    方法介紹

        上文提到的基本算法的主要限制是在第1,…T1輪學習的模型在T1+1,…T1+T2時候被忽略了,這是因為T1之後數據的特徵空間改變了,我們無法直接應用原來的模型。為了解決這一問題,我們假設新舊特徵空間之間有一種特定的關係:,我們嘗試通過重疊階段B1來學習這種關係。學習兩組特徵之間的關係的方法很多,如多元回歸,數據流多標籤學習等。但是在當前的場景下,由於重疊階段特別短,學習一個複雜的關係模型是不現實的。所以我們採用線性映射來近似。定義線性映射的係數矩陣為M,那麼在B1階段,M的估計可以基於如下的目標方程:

    M的最優解可以解得:

        然後,當我觀測到S2空間得數據,就可以通過M將其轉化到S1空間,並應用舊模型對其進行預測。

    除了學習這個關係映射之外,我們得算法主要包括兩個部分:

    1. 在T1-B1+1,…T1階段,我們學習兩個特徵空間之間得關係;

    2. 在T1之後,我們使用新特徵空間的數據轉化后的原特徵空間數據,持續更新舊模型以提升它的預測效果,然後集成兩個模型進行預測。

     

    預測結果集成

        論文中提出兩種集成方法,第一種是加權組合,即將兩個模型的預測結果求加權平均,權重是基於exponential of the cumulative loss。

    其中

        這種權重的更新規則表明,如果上一輪模型的損失較大,下一輪模型的權值將以指數速度下降,這是合理的,可以得到很好的理論結果。

        第二種集成方法是動態選擇。

        上面提到的組合的方法結合了幾個模型來提升整體性能,通常來說,組合多個分類器的表現會比單分類器的效果要好,但是,這基於一個重要的假設就是每個基分類器的表現不能太差(如,在Adaboost中,基分類器的預測精度不應低於0.5)。然而在這個問題中,由於新特徵空間剛出現的時候訓練集較小,訓練的模型不好,因此可能並不適合用組合的方法來預測,相反,用動態選擇最優模型的方法反而能獲得好的效果。

    有趣的靈魂在等你長按二維碼識別

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • Go 多變量賦值時注意事項

    說到多變量賦值時,先計算所有相關值,然後再從左到右依次賦值,但是這個規則不適用於python
    我們來看一例:

    package main
    
    import "fmt"
    
    func main() {
        data, i := [3]string{"喬幫主","慕容復","鳩摩智"}, 0
        i, data[i] = 2, "枯榮大師"
        fmt.Println(i, data)
    }

    輸出結果:

    2 [枯榮大師 慕容復 鳩摩智]  

    有的朋友會認為,結果不應該是這樣么?(但是python下輸出的結果卻是下面的)?

    2 [喬幫主 慕容復 枯榮大師]

    事實並如此,我們來看賦值順序這段的理解:

    1     data, i := [3]string{"喬幫主","慕容復","鳩摩智"}, 0
    2     i, data[i] = 2, "枯榮大師" //注意原則:先計算所有相關值,然後再從左到右依次賦值
    3     // 這裏變量i 的順序其實是(i = 0,因為上一行的變量i是0) -> (然後 i = 2), (data[i] 此時取的值是data[0],而不是data[2],也就是data[0] = 枯榮大師)
    4     fmt.Println(i, data) //所以這裏最終 輸出 i=2,[枯榮大師 慕容復 鳩摩智]

    同樣的多變量賦值卻不適用於python.

    data,i=["喬幫主", "慕容復", "鳩摩智"],0
    i, data[i] = 2, "枯榮大師" # 注意這裏data[i] 已經是 data[2]了,即data[2]="枯榮大師"
    print(i,data) # 輸出 2 ['喬幫主', '慕容復', '枯榮大師']

    另外:我們要注意重新賦值與定義新同名變量的區別:再看一例:

    package main
    
    func main() {
        name := "喬幫主"
        println(&name)
        name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同層次的代碼塊中,且有新的變量被定義。
        println(&name, age)    // 通常函數多返回值 err 會被重複使用。
        {
            name, weight := "清風揚", 50 // 定義新同名變量: 不在同層次代碼塊。
            println(&name, weight)
        }
    }

    輸出:

    0xc00002bf78
    0xc00002bf78 30
    0xc00002bf68 50

    注意:因個人機器不同,大家返回的內存引用地址可能和我的不一樣,但是 這步是重點。重點在這裏:
    同層級相同變量的賦值,內存地址並不會改變。不同層級相同變量的賦值,其實是定義了一個新同名變量,也就是大家看到的第三行內存地址變了。
    接着我們再看有點意思的一段代碼(大家來找茬):

    package main
    
    func main() {
        name := "喬幫主"
        println(&name)
        name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同 層次的代碼塊中,且有新的變量被定義。
        println(&name, age)    // 通常函數多返回值 err 會被重複使用。
    
        name, weight := 100, 50 // 定義新同名變量: 不在同 層次代碼塊。
        println(&name, weight, age)
    
    }

    輸出:

    cannot use 100 (type int) as type string in assignment

    原因很明顯,因為上面:name := “喬幫主” 已經隱試滴申明了name 是字符串,等同於 var name string. 同層級再次賦值100為整形。這是不允許滴,

    但是:重點來了,我們稍改下:

    package main
    
    func main() {
        name := "喬幫主"
        println(&name)
        name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同 層次的代碼塊中,且有新的變量被定義。
        println(&name, age)    // 通常函數多返回值 err 會被重複使用。
        {
            name, weight := 100, 50 // 定義新同名變量: 不在同層次代碼塊。
            println(&name, weight, age)
        }
    }

    區別就是層級發生了變化,因為{}裏面的name已經是新的變量了。
    好啦,到此介紹結束了。博友們有關golang變量使用中遇到的各種奇怪的“坑”,請留下寶貴滴足跡,歡迎拍磚留言.

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

  • 理解MySQL數據庫事務-隔離性

    理解MySQL數據庫事務-隔離性

    Transaction事務是指一個邏輯單元,執行一系列操作的SQL語句。

    事務中一組的SQL語句,要麼全部執行,要麼全部回退。在Oracle數據庫中有個名字,叫做transaction ID

    在關係型數據庫中,事務必須ACID的特性。

    • 原子性,事務中的操作,要不全部執行,要不都不執行
    • 一致性,事務完成前後,數據的必須保持一致。
    • 隔離性,多個用戶併發訪問數據庫時,每一個用戶開啟的事務,相互隔離,不被其他事務的操作所干擾。
    • 持久性,事務一旦commit,它對數據庫的改變是持久性的。

    目前重點討論隔離性。數據庫一共有四個隔離級別

    • 未提交讀(RU,Read Uncommitted)。它能讀到一個事物的中間狀態,不符合業務中安全性的保證,違背 了ACID特性,存在臟讀的問題,基本不會用到,可以忽略

    • 提交讀(RC,Read Committed)。顧名思義,事務提交之後,那麼我們可以看到。這是一種最普遍的適用的事務級別。我們生產環境常用的使用級別。

    • 可重複讀(RR,Repeatable Read)。是目前被使用得最多的一種級別。其特點是有GAP鎖,目前還是默認級別,這個級別下會經常發生死鎖,低併發等問題。

    • 可串行化,這種實現方式,其實已經是不是多版本了,而是單版本的狀態,因為它所有的實現都是通過鎖來實現的。

    因此目前數據庫主流常用的是RCRR隔離級別。

    隔離性的實現方式,我們通常用Read View表示一個事務的可見性。

    RC級別,事務可見性比較高,它可以看到已提交的事務的所有修改。因此在提交讀(RC,Read Committed)隔離級別下,每一次select語句,都會獲取一次Read View,得到數據庫最新的事務提交狀態。因此對於數據庫,併發性能也最好。

    RR級別,則不是。它為了避免幻讀和不可重複讀。保證在一個事務內前後數據讀取的一致。其可見性視圖Read View只有在自己當前事務提交之後,才會更新。

    那如何保證數據的一致性?其核心是通過redo logundo log來保證的。

    而在數據庫中,為了實現這種高併發訪問,就需要對數據庫進行多版本控制,通過事務的可見性來保證事務看到自己想看到的那個數據版本(或者是最新的Read View亦或者是老的Read View)。這種技術叫做MVCC

    多版本是如何實現的?通過undo日誌來保證。每一次數據庫的修改,undo日誌會存儲之前的修改記錄值。如果事務未提交,會回滾至老版本的數據。其MVCC的核心原理,以後詳談

    舉例論證:

    ##  開啟事務
    MariaDB [scott]> begin;                   
    Query OK, 0 rows affected (0.000 sec)
    
    ##查看當前的數據
    MariaDB [scott]>  select * from dept;
    +--------+------------+----------+
    | deptno | dname      | loc      |
    +--------+------------+----------+
    |     10 | ACCOUNTING | beijing  |
    |     20 | RESEARCH   | DALLAS   |
    |     30 | SALES      | CHICAGO  |
    |     40 | OPERATIONS | beijing  |
    |     50 | security   | beijing  |
    |     60 | security   | nanchang |
    +--------+------------+----------+
    6 rows in set (0.001 sec)
    
    ##更新數據
    MariaDB [scott]> update dept set loc ='beijing' where deptno = 20;
    Query OK, 1 row affected (0.001 sec)
    
    ## 其行記錄| 20 | RESEARCH | DALLAS |已經被放置在undo日誌中,目前最新的記錄被改為'beijing':
    MariaDB [scott]> select * from dept;
    +--------+------------+----------+
    | deptno | dname      | loc      |
    +--------+------------+----------+
    |     10 | ACCOUNTING | beijing  |
    |     20 | RESEARCH   | beijing  |
    |     30 | SALES      | CHICAGO  |
    |     40 | OPERATIONS | beijing  |
    |     50 | security   | beijing  |
    |     60 | security   | nanchang |
    +--------+------------+----------+
    
    ##事務不提交,回滾。數據回滾至老版本的數據。
    MariaDB [scott]> rollback;
    Query OK, 0 rows affected (0.004 sec)
    
    MariaDB [scott]> select * from dept;
    +--------+------------+----------+
    | deptno | dname      | loc      |
    +--------+------------+----------+
    |     10 | ACCOUNTING | beijing  |
    |     20 | RESEARCH   | DALLAS   |
    |     30 | SALES      | CHICAGO  |
    |     40 | OPERATIONS | beijing  |
    |     50 | security   | beijing  |
    |     60 | security   | nanchang |
    +--------+------------+----------+
    6 rows in set (0.000 sec)

    因為MVCC,讓數據庫有了很強的併發能力。隨着數據庫併發事務處理能力大大增強,從而提高了數據庫系統的事務吞吐量,可以支持更多的用戶併發訪問。但併發訪問,會出現帶來一系列問題。如下:

    數據庫併發帶來的問題 概述解釋
    臟讀(Dirty Reads) 當一個事務A正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務B也訪問這同一個數據,如不控制,事務B會讀取這些”臟”數據,並可能做進一步的處理。這種現象被稱為”臟讀”(Dirty Reads)
    不可重複讀(Non-Repeatable Reads) 指在一個事務A內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務B也訪問該同一數據。那麼,在事務A的兩次讀數據之間,由於第二個事務B的修改,那麼第一個事務兩次讀到的的數據可能是不一樣的 。出現了”不可重複讀”(Non-Repeatable Reads)的現象
    幻讀(Phantom Reads) 指在一個事務A內,按相同的查詢條件重新檢索以前檢索過的數據,同時發現有其他事務插入了數據,其插入的數據滿足事務A的查詢條件。因此查詢出了新的數據,這種現象就稱為”幻讀”(Phantom Reads)

    隔離級別和上述現象之間的聯繫。

    隔離級別有:未提交讀(RU,Read Uncommitted),提交讀(RC,Read Committed),可重複讀(RR,Repeatable Read),可串行化(Serializable)

    隔離級別 臟讀 不可重複讀 幻讀
    未提交讀(RU,Read Uncommitted) 可能 可能 可能
    提交讀(RC,Read Committed) 不可能 可能 可能
    可重複讀(RR,Repeatable Read) 不可能 不可能 可能
    (間隙鎖解決)
    可串行化(Serializable) 不可能 不可能 不可能

    實驗環節

    舉例在隔離級別RRRC下,說明“不可重複讀”問題。

    MySQL的默認級別是Repeatable Read,如下:

    MariaDB [(none)]> select @@global.tx_isolation;
    +-----------------------+
    | @@global.tx_isolation |
    +-----------------------+
    | REPEATABLE-READ       |
    +-----------------------+
    1 row in set (0.000 sec)

    這裏修改當前會話級別為Read Committed

    MariaDB [scott]> set session transaction isolation level read committed;
    Query OK, 0 rows affected (0.001 sec)
    
    MariaDB [scott]> select @@tx_isolation;
    +----------------+
    | @@tx_isolation |
    +----------------+
    | READ-COMMITTED |
    +----------------+
    1 row in set (0.000 sec)

    在隔離級別已提交讀(RC,Read Committed)下,出現了不可重複讀的現象。在事務A中可以讀取事務B中的數據。

    在隔離級別可重複讀(RR,Repeatable Read),不會出現不可重複讀現象,舉例如下:

    舉例說明“幻讀”的現象。

    行鎖可以防止不同事務版本的數據在修改(update)提交時造成數據衝突的問題。但是插入數據如何避免呢?

    在RC隔離級別下,其他事務的插入數據,會出現幻讀(Phantom Reads)的現象。

    而在RR隔離級別下,會通過Gap鎖,鎖住其他事務的insert操作,避免”幻讀”的發生。

    因此,在MySQL事務中,鎖的實現方式與隔離級別有關,如上述實驗所示。在RR隔離級別下,MySQL為了解決幻讀的問題,已犧牲并行度為代價,通過Gap鎖來防止數據的寫入。這種鎖,并行度差,衝突多。容易引發死鎖。

    目前流行的Row模式可以避免很多衝突和死鎖問題,因此建議數據庫使用ROW+RC(Read Committed)模式隔離級別,很大程度上提高數據庫的讀寫并行度,提高數據庫的性能。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • 應屆畢業生工作7個月小結

    應屆畢業生工作7個月小結

    前言: 不知不覺已經工作了快 7 個月了,去年這個時候還躋身在考研的大軍中,不禁有些感慨… 結合這 7 個月發生的一些事情,簡單做一下總結吧…

    為獲得更好的閱讀體驗,請訪問原文地址:

    一、那時候剛入職

    不同於其他同學忙於畢設的 4 月,提早安排趁寒假已經完成畢設的我,已經開始撲在了「找工作」這件事上,有了去年「秋招」打下的基礎,複習起來快了很多,沒過多久就開始投簡歷面試了,面試也總體比較順利,剛面沒幾家就迅速和一家自己看好的初創公司簽下了。

    公司使用的技術棧區別於自己熟悉的 Java/ MySQL 這一套,而是主要使用的 Rails/ MongoDB,所以剛入職的一段時間,基本上都是在自己熟悉技術棧,也趁着閑暇的時間,把自己入門時候的一些學習心得寫成了文章發表:

    • MongoDB【快速入門】:
    • Java轉Ruby【快速入門】:

    對於職場小白來說,所謂「職場」還是顯得有些陌生,剛來的時候,雖然跟周圍的同事都稀鬆平常地打了一圈兒招呼,坐下之後,隨着他們又埋頭噼里啪啦敲打鍵盤工作的深入,又頓覺周圍一片陌生,還挺奇妙的,在第一周完的周報裏面我寫道:

    剛來公司有些迷茫,只是看着CheckList對照着熟悉一些技術,也不了解自己應該要熟悉到哪種程度,就希望自己能再主動些,不管是技術問題還是其他問題多請教,然後儘快跟其他成員熟悉起來。

    剛開始上手的時候也有好多問題不懂,我都習慣性的選擇自己研究一陣兒,因為自己有寫博客的一些經歷,被問過好多一搜索 or 自己一嘗試就能解決的問題,所以比較克制,但是後來「入職 1v1」溝通的時候被說到有問題別自己死磕,半個小時沒解決盡量就找一下旁邊的同事。摁?我一下子就把我的「主動性」發揮了出來。

    不過好記性也不如爛筆頭,找了一些工具記錄把這些「問題的答案」都記錄了下來,方便之後再查找,當時對於 Git 都不是很熟悉,也記錄了很多常用的命令在裏面,還有一些問題的反饋,甚至知道了月會要自我介紹,也打了一遍草稿記錄在了這裏:(那段時間真的問了好多問題,周報里也手動感謝了坐我旁邊的兩位大佬..)

    入職兩周的時候,雖然已經開始上手做一些簡單的埋點工作,但自己對於 Ruby 還是不是特別了解和熟悉,趁着某一個雙休,抓着一本《Effetive-Ruby》啃了两天,也把自己的學習輸出了一下:

    • 《Effective-Ruby》讀書筆記:

    二、逐漸能夠上手

    就這樣一邊熟悉,一邊開始接一些小需求,我記得我寫下的第一個 BUG,就報出了 6K 條記錄.. 慌慌張張在修復之後我不禁感嘆:「不要太相信用戶的任何數據」。(包括 equal 反寫也是之後在錯誤之中學習到的..)

    剛上手沒有一段時間,就接到了一個新項目的需求,跟着一位大佬開發一個新功能,大佬負責搭建基礎代碼和設計,我負責完成其餘的功能代碼,沒敢一絲懈怠,下班回家之後也對照着別人寫的代碼敲敲敲,時間和完成度上倒是沒有一絲耽擱,只是現在回過頭一想,當時沒有什麼單元測試的概念和意識,就自己在本地 Post-Man 測試完就完,所幸比較簡單 + 自己測試得比較仔細,到現在也沒有出現過什麼問題。

    工作對我這樣的小白另一個好處就是:「見識和增加技術的廣度」。公司所使用技術棧不論是廣度還是深度,都是自己在大學本科的學習中不可企及的程度,Jekins?Docker?K8S?跳板機?一下子冒出來好多新鮮陌生的名詞,懷着好奇心也嘗試了解了一些:

    • 了解【Docker】從這裏開始:
    • 「消息隊列」看過來!:
    • Kafka【入門】就這一篇!:

    也隨着公司的逐漸壯大,各模塊的耦合也越發嚴重,各條業務線之間的協作溝通成本越來越大,逐漸開始提出「微服務」這樣的概念,具體怎麼樣理解就不作討論了,總之就是期望通過梳理/ 重構/ 拆服務的方式來解決「協作」問題,所以期間也開始了解學習一些這方面的東西:

    • 什麼是微服務?:
    • 《重構:改善既有代碼的設計》讀書筆記:

    甚至期間還做了一些「微服務」的調研,我們選用什麼樣的姿勢和技術棧更加合適,所以也輸出了一些關於「Spring Cloud」的東西,但是最終駁回的原因是待我們整個容器化之後 k8s 平台自帶了這麼一套東西,業務同學只需要關心業務代碼就行了,也就沒有繼續深入了:

    • 你想了解的「Spring Cloud」都在這裏:

    然後我們在拆解的過程中,也借鑒到一些「DDD」的思想,也嘗試進行了一波學習:

    • 【吐血推薦】領域驅動設計學習輸出:

    總之,這一段時間我一邊通過各種小需求,接觸和了解了公司的系統的大半,一邊學習和了解着各種不同的技術,增加了技術上的廣度。

    三、開始負責一些項目

    為了加速服務化的推進工作和驗證「DDD」的一些東西,部門老大把一個邊界足夠清晰,也足夠小的一個模塊單獨交給我,期望我快速上線,不過最終交付已經逾期快大半個月了.. 雖然從最終的結果來看,順利交付完成了拆解任務並從 MongoDB 數據庫轉變成了 MySQL.. 但期間也踩過好些坑,當然也學習到一些東西..

    例如我真實地意識到「完美」這個詞的理想化。就拿設計 API 來說吧.. 自己就基於 RESTful 風格設計了好幾版.. 左想右想都覺得差一些,有一些接口覺得怎麼設計都不優雅.. 後來糾結一陣子也就放棄了.. 再例如寫代碼這件事情吧,好的代碼整潔的代碼是一次一次迭代和重構中出來的,如果一開始就想着寫出「完美」的代碼,那麼最終的結果可能就是寫不出來代碼。

    另外一個小插曲是,在做數據遷移的時候,我差點把線上服務器搞掛了.. 我在測試環境驗證了一把之後,就直接在線上進行操作了,因為當時對於數據庫的操作管控還沒有那麼嚴格,加上自己對於線上環境的複雜程度認識不足,我就起了 50 個線程,去分批量地讀取 MongoDB 的數據遷移到 MySQL,造成了線上庫的性能報警,就趕緊停了.. 緊接着就被一群大佬抓進了一個會議室做事件的復盤..

    說實話,我緊張壞了,第一次經歷這樣的算是「事故」的情況吧,差一點線上就被我搞掛啦,一時間不知所措… 讓人感到溫暖的是部門老大隨即丟來的消息:

    那天還有一些相關的同事都陪我寫復盤郵件到了晚上 10:30,現在想來都十分感謝他們。後來回到家我還打電話給我媽,我說我在工作中犯錯了,我做了xxxx這些動作,你覺得我做的怎麼樣呢,老媽的回復也讓人安心,只是現在想來,一些後續的動作可以做得更好的…

    因為「埋點」這件事涉及到系統的方方面面,我也藉此了解了很多不同的模塊,也是拜這一點所賜吧,後來我被派到各種各樣的支援任務中,同樣也因為對不同模塊都還不算陌生,都還算完成得不錯吧…

    時間一晃,在公司就四個月過去了,也在這個過程中從各個大佬那兒都學到了一些東西,在 8 月底發的周報裏面我寫下了以下的總結:

    之後也跟着大佬碰了一些公司的核心模塊,期間也沒有停止在工作中不斷地做學習輸出:

    • Git 原理入門解析:
    • Java計時新姿勢√:
    • Java8流操作-基本使用&性能測試:
    • 《代碼整潔之道》讀書筆記:
    • React 入門學習:
    • 談一談依賴倒置原則:

    四、回顧做的不好的部分

    • 對代碼還沒有保持足夠的敬畏之心。

    特別是一開始上手的時候,有時候甚至是在線上環境搞測試,後來越來越注重 codereview 和單元測試好了很多。

    • 溝通還不夠深入/ 到位

    有一次是臨時接到一個需求,因為「通用語言」沒有達成一致,導致最終交付的結果不符合產品的期望,最終我們所有相關人員在一起開了一個會,統一了「通用語言」,造成了額外的工作和負擔,拿到需求就應該確認好相關事宜的,越底層越細節越好,這方面的能力我仍然欠缺,但我已經持續在注意當中。

    另一次也是因為這一點,我需要幫助 A 系統擁有某一項功能,之前 A 系統已經介入了 B 系統完成了部分功能,我因為沒有進一步地確認 B 系統的現狀,就去接入了有完整功能的 C 系統,但其實 B 系統已經在上一周和開發 C 系統和 A 系統的同學對接好了,並完成了相關功能的接入,少了這一部分的溝通,就造成了不少額外的工作量.. 所以「溝通」還是非常重要的,也只能說持續進步吧…

    • 缺少一些主動性

    當我頭上掛着一些事情的時候,還是能夠保持着效率的,只是當我做完了,就時常缺乏一些主動地思考了,通常都是被動地去詢問同小組的同事有什麼事情是需要幫忙的.. 雖然也积極地參与到自己感興趣的那些技術評審之類的事情之中,但似乎效果都不佳.. 也沒有什麼實際好的輸出..

    • 接了一些私活兒黑活兒(沒有充分考慮團隊之間的配合)

    因為「埋點」會接觸各個平台的童鞋,並且時常變化和有一些新的需求,有時候直接繞過了一些環節,直接找上我了,我心想直接自己弄弄改改就可以了,也就沒多想… 但是現在想來,這樣跨團隊的事情,不能越過「頂頭上司」私自進行,一方面經常我的 BOSS 不知道我接了活兒,另一方面這樣的私自對接就會造成一些信息的流失,對於團隊之內還是團隊之間都會造成影響…

    五、回顧做得好的部分

    • 養成了閱讀的習慣

    公司買書是免費的,也有自己的圖書館,同事也不乏喜歡閱讀學習的,所以跟着跟着就養成了閱讀的習慣,期間也學習到了一些方法論的東西,貼一下入職以來讀過的那些書吧:(技術類的就沒有囊括了)

    其實每天閱讀的時間也不長,想我大學總共捧起的那麼些課外書,不禁有些唏噓…

    • 早睡早起 + 晨間日記

    早睡早起,從步入職場以來,就發現這樣的習慣會帶來一些額外的價值,例如一些閱讀我會放在早上,後來還加入了「晨間日記」,用來「回顧前一天的事情」和提前部署「今天的任務」,這不禁讓我多了一份清醒,也讓現在不怎麼鍛煉的我每一天精力更加好一些:(目前正在從印象筆記往 Notion 逐步遷移的過程中)

    • 學習撰寫 Commit Message && 遵守一些 Git 規範

    起初使用 Git 十分不規範,後來向大佬那兒學習到了如何標準地提交 Commit,包括 Commit Message 應該怎麼寫,我覺得這是一個很好的習慣,每一個 Commit 都有上下文,並且還帶上了 JIRA 號,任務也很好跟蹤,雖然公司並沒有大範圍地盛行起來,但我覺得這樣好習慣應該堅持下來:

    • 任務進度及時反饋給相關人員

    自己比較注意這一點,因為不這樣做會讓別人感受不怎麼好.. 光是自己心裏清楚是不行的.. 要保持信息的通暢才行,及時反饋是很重要的一步..

    • 自己先 review 一遍代碼

    犯過一些白痴錯誤之後,就有些擔心,逐步養成了自己先 review 一遍代碼的習慣..

    六、小結 && 展望

    總的來說,看着自己這樣一步一步成長過來,沒有很懈怠,自己就算比較滿意了,在工作中學習了很多東西,不管是技術上的硬技能,還是溝通中的軟技能,也認識到了很多厲害的大佬和有趣的小夥伴們..

    感恩在路上相遇,有幸共同行走過一段已然算是幸運,突然翻看起自己的朋友圈有一句話說得好:「成長從來都不是告別過去,成長是更加堅定的看向未來!」

    期待一路同行的大家,都能夠 Be Better!

    按照慣例黏一個尾巴:

    歡迎轉載,轉載請註明出處!
    獨立域名博客:wmyskxz.com
    簡書ID:
    github:
    歡迎關注公眾微信號:wmyskxz
    分享自己的學習 & 學習資料 & 生活
    想要交流的朋友也可以加qq群:3382693

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • ThreadLocal深度解析和應用示例

    ThreadLocal深度解析和應用示例

    開篇明意

      ThreadLocal是JDK包提供的線程本地變量,如果創建了ThreadLocal<T>變量,那麼訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規避了線程安全問題。

      ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。其實,ThreadLocal並不是一個Thread,而是Thread的一個局部變量,也許把它命名ThreadLocalVariable更容易讓人理解一些。

      來看看官方的定義:這個類提供線程局部變量。這些變量與正常的變量不同,每個線程訪問一個(通過它的get或set方法)都有它自己的、獨立初始化的變量副本。ThreadLocal實例通常是類中的私有靜態字段,希望將狀態與線程關聯(例如,用戶ID或事務ID)。

    源碼解析

      1.核心方法之   set(T t)

     1     /**
     2      * Sets the current thread's copy of this thread-local variable
     3      * to the specified value.  Most subclasses will have no need to
     4      * override this method, relying solely on the {@link #initialValue}
     5      * method to set the values of thread-locals.
     6      *
     7      * @param value the value to be stored in the current thread's copy of
     8      *        this thread-local.
     9      */
    10     public void set(T value) {
    11         Thread t = Thread.currentThread();
    12         ThreadLocalMap map = getMap(t);
    13         if (map != null)
    14             map.set(this, value);
    15         else
    16             createMap(t, value);
    17     }

    解析:

      當調用ThreadLocal的set(T t)的時候,代碼首先會獲取當前線程的 ThreadLocalMap(ThreadLocal中的靜態內部類,同時也作為Thread的成員變量存在,後面會進一步了解ThreadLocalMap),如果ThreadLocalMap存在,將ThreadLocal作為map的key,要保存的值作為value來put進map中(如果map不存在就先創建map,然後再進行put);

      2.核心方法值 get()

    /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        public T get() {
           Thread t = Thread.currentThread();
           ThreadLocalMap map = getMap(t);        //此處和set方法一致,也是通過當前線程獲取對應的成員變量ThreadLocalMap,map中存放的是Entry(ThreadLocalMap的內部類(繼承了弱引用))
        
    if (map != null) {
          ThreadLocalMap.Entry e = map.getEntry(this);
          if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
          }
        }
        return setInitialValue();
    }

    解析:

      剛才把對象放到set到map中,現在根據key將其取出來,值得注意的是這裏的map裏面存的可不是鍵值對,而是繼承了WeakReference<ThreadLocal<?>> 的Entry對象,關於ThreadLocalMap.Entry類,後面會有更加詳盡的講述。

    核心方法之  remove()

        /**
         * Removes the current thread's value for this thread-local
         * variable.  If this thread-local variable is subsequently
         * {@linkplain #get read} by the current thread, its value will be
         * reinitialized by invoking its {@link #initialValue} method,
         * unless its value is {@linkplain #set set} by the current thread
         * in the interim.  This may result in multiple invocations of the
         * {@code initialValue} method in the current thread.
         *
         * @since 1.5
         */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }

    解析:

      通過getMap方法獲取Thread中的成員變量ThreadLocalMap,在map中移除對應的ThreadLocal,由於ThreadLocal(key)是一種弱引用,弱引用中key為空,gc會回收變量value,看一下核心的m.remove(this);方法

            /**
             * Remove the entry for key.
             */
            private void remove(ThreadLocal<?> key) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1); //定義Entry在數組中的標號 for (Entry e = tab[i];              //通過循環的方式remove掉Thread中所有的Entry
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {   
                    if (e.get() == key) {
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            } 

     

     

     

    靈魂提問

      問:threadlocal是做什麼用的,用在哪些場景當中?  

        結合官方對ThreadLocal類的定義,threadLocal主要滿足某些變量或者示例是線程隔離的,但是在相同線程的多個類或者方法中都能使用的到,並且當線程結束時該變量也應該銷毀。通俗點講:ThreadLocal保證每個線程有自己的數據副本,當線程結束后可  以獨立回收。由於ThreadLocal的特性,同一線程在某地方進行設置,在隨後的任意地方都可以獲取到。從而可以用來保存線程上下文信息。常用的比如每個請求怎麼把一串後續關聯起來,就可以用ThreadLocal進行set,在後續的任意需要記錄日誌的方法裏面進行get獲取到請求id,從而把整個請求串起來。     使用場景有很多,比如:

    • 基於用戶請求線程的數據隔離(每次請求都綁定userId,userId的值存在於ThreadLoca中)
    • 跟蹤一個請求,從接收請求,處理到返回的整個流程,有沒有好的辦法   思考:微服務中的鏈路追蹤是否利用了ThreadLocal特性
    • 數據庫的讀寫分離
    • 還有比如Spring的事務管理,用ThreadLocal存儲Connection,從而各個DAO可以獲取同一Connection,可以進行事務回滾,提交等操作。

        
    問:如果我啟動另外一個線程。那麼在主線程設置的Threadlocal值能被子線程拿到嗎?     原始的ThreadLocal是不具有繼承(或者說傳遞)特性的     
    問:那該如何解決ThreadLocal無法傳遞的問題呢?     用ThreadLocal的子類 InheritableThreadLocal,InheritableThreadLocal是具有傳遞性的

      /**
      * 重寫Threadlocal類中的getMap方法,在原Threadlocal中是返回
      * t.theadLocals,而在這麼卻是返回了inheritableThreadLocals,因為
      * Thread類中也有一個要保存父子傳遞的變量
      */ ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals; }
        /**  * 同理,在創建ThreadLocalMap的時候不是給t.threadlocal賦值  *而是給inheritableThreadLocals變量賦值  *  */
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }

    解析:因為InheritableThreadLocal重寫了ThreadLocal中的getMap 和createMap方法,這兩個方法維護的是Thread中的另外一個成員變量  inheritableThreadLocals,線程在創建的時候回複製inheritableThreadLocals中的值 ;

    /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
      //Thread類中維護的成員變量,ThreadLocal會維護該變量
    ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */
    //Thread中維護的成員變量 ,
    InheritableThreadLocal 中維護該變量
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;


     

    //Thread init方法中的關鍵代碼,簡單來說是將父類中inheritableThreadLocals中的值拷貝到當前線程的inheritableThreadLocals中(淺拷貝,拷貝的是value的地址引用)
     if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

    總結

    • ThreadLocal類封裝了getMap()、Set()、Get()、Remove()4個核心方法。
    • 通過getMap()獲取每個子線程Thread持有自己的ThreadLocalMap實例, 因此它們是不存在併發競爭的。可以理解為每個線程有自己的變量副本。
    • ThreadLocalMap中Entry[]數組存儲數據,初始化長度16,後續每次都是1.5倍擴容。主線程中定義了幾個ThreadLocal變量,Entry[]才有幾個key。
    • Entry的key是對ThreadLocal的弱引用,當拋棄掉ThreadLocal對象時,垃圾收集器會忽略這個key的引用而清理掉ThreadLocal對象, 防止了內存泄漏。

        tips:上面四個總結來源於其他技術博客,個人認為總結的比較合理所以直接摘抄過來了

    拓展:

      ThreadLocal在線程池中使用容易發生的問題: 內存泄漏,先看下圖

      

      每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以後,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結束以後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.

      所以得出一個結論就是只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間不會被回收的,就發生了我們認為的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現內存泄露。  

      PS.Java為了最小化減少內存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value。所以最怕的情況就是,threadLocal對象設null了,開始發生“內存泄露”,然後使用線程池,這個線程結束,線程放回線程池中不銷毀,這個線程一直不被使用,或者分配使用了又不再調用get,set方法,那麼這個期間就會發生真正的內存泄露。 

     

    1. JVM利用設置ThreadLocalMap的Key為弱引用,來避免內存泄露。
    2. JVM利用調用remove、get、set方法的時候,回收弱引用。
    3. 當ThreadLocal存儲很多Key為null的Entry的時候,而不再去調用remove、get、set方法,那麼將導致內存泄漏。
    4. 當使用static ThreadLocal的時候,延長ThreadLocal的生命周期,那也可能導致內存泄漏。因為,static變量在類未加載的時候,它就已經加載,當線程結束的時候,static變量不一定會回收。那麼,比起普通成員變量使用的時候才加載,static的生命周期加長將更容易導致內存泄漏危機。

     

      參考鏈接:

     

    在線程池中使用ThreadLocal

    通過上面的分析可以知道InheritableThreadLocal是通過Thread()的inint方法實現父子之間的傳遞的,但是線程池是統一創建線程並實現復用的,這樣就好導致下面的問題發生:

    •   線程不會銷毀,ThreadLocal也不會被銷毀,這樣會導致ThreadLoca會隨着Thread的復用而復用
    •   子線程無法通過InheritableThreadLocal實現傳遞性(因為沒有單獨的調用Thread的Init方法進行map的複製),子線程中get到的是null或者是其他線程復用的錯亂值(疑問點還沒搞清楚原因,後續補充::在異步線程中會出現null的情況,同步線程不會出現)     

        ps:線程池中的線程是什麼時候創建的?

     

      解決方案:

        下面兩個鏈接有詳細的說明,我就不重複寫了,後續我會將本文進一般優化並添加一些例子來幫助說明,歡迎收藏,關於本文有不同的意見歡迎評論指正……

        

        

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

  • 【併發編程】Java中的原子操作

    【併發編程】Java中的原子操作

    什麼是原子操作

    原子操作是指一個或者多個不可再分割的操作。這些操作的執行順序不能被打亂,這些步驟也不可以被切割而只執行其中的一部分(不可中斷性)。舉個列子:

    //就是一個原子操作
    int i = 1;
    
    //非原子操作,i++是一個多步操作,而且是可以被中斷的。
    //i++可以被分割成3步,第一步讀取i的值,第二步計算i+1;第三部將最終值賦值給i
    i++;

    Java中的原子操作

    在Java中,我們可以通過同步鎖或者CAS操作來實現原子操作。

    CAS操作

    CAS是Compare and swap的簡稱,這個操作是硬件級別的操作,在硬件層面保證了操作的原子性。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什麼都不做。Java中的sun.misc.Unsafe類提供了compareAndSwapIntcompareAndSwapLong等幾個方法實現CAS。

    另外,在jdk的atomic包下面提供了很多基於CAS實現的原子操作類,見下圖:

    下面我們就使用其中的AtomicInteger來看看怎麼使用這些原子操作類。

    package com.csx.demo.spring.boot.concurrent.atomic;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class AtomicIntegerDemo {
    
        private static int THREAD_COUNT = 100;
    
        public static void main(String[] args) throws InterruptedException {
    
    
            NormalCounter normalCounter = new NormalCounter("normalCounter",0);
            SafeCounter safeCounter = new SafeCounter("safeCounter",0);
            List<Thread> threadList = new ArrayList<>();
    
            for (int i = 0; i < THREAD_COUNT ; i++) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 10000; j++) {
                            normalCounter.add(1);
                            safeCounter.add(1);
                        }
                    }
                });
                threadList.add(thread);
            }
    
            for (Thread thread : threadList) {
                thread.start();
            }
            for (Thread thread : threadList) {
                thread.join();
            }
            System.out.println("normalCounter:"+normalCounter.getCount());
            System.out.println("safeCounter:"+safeCounter.getCount());
        }
    
    
        public static class NormalCounter{
            private String name;
            private Integer count;
    
            public NormalCounter(String name, Integer count) {
                this.name = name;
                this.count = count;
            }
    
            public void add(int delta){
                this.count = count+delta;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public Integer getCount() {
                return count;
            }
    
            public void setCount(Integer count) {
                this.count = count;
            }
        }
    
        public static class SafeCounter{
            private String name;
            private AtomicInteger count;
    
            public SafeCounter(String name, Integer count) {
                this.name = name;
                this.count = new AtomicInteger(count);
            }
    
            public void add(int delta){
                count.addAndGet(delta);
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public int getCount() {
                return count.get();
            }
    
            public void setCount(Integer count) {
                this.count.set(count);
            }
        }
    
    }

    上面的代碼中,我們分別創建了一個普通的計數器和一個原子操作的計數器(使用AtomicInteger進行計數)。然後創建了100個線程,每個線程進行10000次計數。理論上線程執行完之後,計數器的值都是1000000,但是結果如下:

    normalCounter:496527
    safeCounter:1000000

    每次執行,普通計數器的值都是不一樣的,而使用AtomicInteger進行計數的計數器都是1000000。

    CAS操作存在的問題

    • ABA問題:因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。

    從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置為給定的更新值。

    • 循環時間長開銷大:自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那麼效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序衝突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

    • 只能保證一個共享變量的原子操作:當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。

    使用鎖來保證原子操作

    還是以上面的列子為列,普通的計數器我們只需要在計數方法上加鎖就行了:

    public synchronized void  add(int delta){
      this.count = count+delta;
    }

    執行結果如下:

    normalCounter:1000000
    safeCounter:1000000

    兩個計數器都能拿到正確的結果

    CPU是怎麼實現原子操作的

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 關於GC(中):Java垃圾回收相關基礎知識

    關於GC(中):Java垃圾回收相關基礎知識

    Java內存模型

    (圖源: )

    區域名 英文名 訪問權限 作用 備註
    程序計數器 Program Counter Register 線程隔離 標記待取的下一條執行的指令 執行Native方法時為空; JVM規範中唯一不會發生OutOfMemoryError的區域
    虛擬機棧 VM Stack 線程隔離 每個Java方法執行時創建,用於存儲局部變量表,操作棧,動態鏈接,方法出口等信息 方法執行的內存模型
    本地方法棧 Native Method Stack 線程隔離 Native方法執行時使用 JVM規範沒有強制規定,如Hotspot將VM和Native兩個方法棧合二為一
    Java堆 Java Heap 線程共享 存放對象實例 更好的回收內存 vs 更快的分配內存
    方法區 Method Area 線程共享 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據 JVM規範不強制要求做垃圾收集
    運行時常量池 Runtime Constant Pool 線程共享 方法區的一部分
    直接內存 Direct Memory 堆外內存,通過堆的DirectByteBuffer訪問 不是運行時數據區的一部分,但也可能OutOfMemoryError

    對象的創建——new的時候發生了什麼

    討論僅限於普通Java對象,不包括數組和Class對象。

    1. 常量池查找類的常量引用,如果沒有先做類加載
    2. 分配內存,視堆內存是否是規整(由垃圾回收器是否具有壓縮功能而定)而使用“指針碰撞”或“空閑列表”模式
    3. 內存空間初始化為零值,可能提前在線程創建時分配TLAB時做初始化
    4. 設置必要信息,如對象是哪個類的示例、元信息、GC分代年齡等
    5. 調用<init>方法

    垃圾回收器總結

    垃圾回收,針對的都是堆。

    分代

    • 新生代:適合使用複製算法, 以下三個區一般佔比為8:1:1
      • Eden 新對象誕生區
      • From Survivor 上一次GC的倖存者(見“GC種類-minor GC”)
      • To Survivor 本次待存放倖存者的區域
    • 老年代:存活時間較久的,大小較大的對象,因此使用標記-整理或標記-清除算法比較合適
    • 永久代:存放類信息和元數據等不太可能回收的信息。Java8中被元空間(Metaspace)代替,不再使用堆,而是物理內存。

    分代的原因

    • 不同代的對象生命周期不同,可以針對性地使用不同的垃圾回收算法
    • 不同代可以分開進行回收

    回收算法

    名稱 工作原理 優點 缺點
    標記-清除 對可回收對對象做一輪標記,標記完成后統一回收被標記的對象 易於理解,內存利用率高 效率問題;內存碎片;分配大對象但無空間時提前GC
    複製 內存均分兩塊,只使用其中一塊。回收時將這一塊存活對象全部複製到另一塊 效率高 可用空間減少; 空間不夠時需老年代分配擔保
    標記-整理 對可回收對對象做一輪標記,標記完成后將存活對象統一左移,清理掉邊界外內存 內存利用率高 效率問題

    標記-X算法適用於老年代,複製算法適用於新生代。

    GC種類

    • Minor GC,只回收新生代,將Eden和From Survivor區的存活對象複製到To Survivor
    • Major GC,清理老年代。但因為伴隨着新生代的對象生命周期升級到老年代,一般也可認為伴隨着FullGC。
    • FullGC,整個堆的回收
    • Mixed GC,G1特有,可能會發生多次回收,可以參考

    垃圾回收器小結

    垃圾回收器名稱 特性 目前工作分代 回收算法 可否與Serial配合 可否與ParNew配合 可否與ParallelScavenge配合 可否與SerialOld配合 可否與ParallelOld配合 可否與CMS配合 可否與G1配合
    Serial 單線程 新生代 複製 Y N Y N/A
    ParNew 多線程 新生代 複製 N N Y N/A
    ParallelScavenge 多線程, 更關注吞吐量可調節 新生代 複製 N N Y N/A
    SerialOld 單線程 老年代 標記-整理 Y Y N N/A
    ParallelOld 多線程 老年代 標記-整理 N N Y N/A
    CMS 多線程,併發收集,低停頓。但無法處理浮動垃圾,標記-清除會產生內存碎片較多 老年代 標記-清除 Y Y N Y N/A
    G1 并行併發收集,追求可預測但回收時間,整體內存模型有所變化 新生代/老年代 整體是標記-整理,局部(兩Region)複製 N N N N N N

    在本系列的上一篇文章中,減少FullGC的方式是使用G1代替CMS,計劃在下一篇文章中對比CMS和G1的區別。

    理解GC日誌

    只舉比較簡單的例子,具體各項的格式視情況分析,不同回收器也會有差異。

    2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure) 2019-11-22T10:28:32.178+0800: 60188.392: [ParNew: 1750382K->2520K(1922432K), 0.0312604 secs] 1945718K->198045K(4019584K), 0.0315892 secs] [Times: user=0.09 sys=0.01, real=0.03 secs]

    開始時間-(方括號[)-發生區域(ParNew,命名和GC回收器有關)-回收前大小-回收后大小-(方括號])-GC前堆已使用容量-GC后堆已使用容量大小-回收時間-使用時間詳情(用戶態時間-內核時間-牆上時鐘時間)

    注意這裏沒有包括“2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure)”這部分的分析。

    可借鑒的編程模式

    對象分配的併發控制

    對象創建是很頻繁的,在線程共享的堆中會遇到併發的問題。兩種解決辦法:

    1. 同步鎖定:CAS+失敗重試,確保原子性
    2. 堆中預先給每個線程劃分一小塊內存區域——本地線程分配緩衝(TLAB),TLAB使用完並分配新的TLAB時才做同步鎖定。可看作1的優化。

    CAS: Conmpare And Swap,用於實現多線程同步的原子指令。 將內存位置的內容與給定值進行比較,只有在相同的情況下,將該內存位置的內容修改為新的給定值。關於CAS可以參考:

    對象訪問的定位方式

    前提條件:通過上本地變量表的reference訪問中的對象及它在方法區的對象類型數據(類信息)
    主流的兩種方式,這兩種方式各有優點,可以看出方式2是方式1的優化,但並不是全面超越方式1,無法完全取代。
    這裏可以看到要權衡垃圾回收和訪問速度兩方面。

    方式1: 直接指針訪問實例數據

    reference直接存放對象實例地址,只需要一次訪問即可,執行效率較高。

    方式2: 使用句柄池

    reference中地址穩定,對象被移動時只需要改句柄池的地址。相對的,訪問實例需要兩次指針定位。

    參考資料

    1. 周志明.著《深入理解JAVA虛擬機》

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • 三個月(敏捷)項目收穫

    三個月(敏捷)項目收穫

     

     

    項目背景

    客戶已有運行多年的官網老站(PC端),想在今年對老站進行一次UI全面更新、功能全部平移的升級,對接新的運營後端,然後建立官網小程序端且與官網PC端進行聯動,使得品牌自有渠道能夠更加全面化。

     

    挑戰

    • 時間緊。五月份進行Inception Workshop,確定項目交付範圍與架構方案。官網六月初開始開發,小程序八月份開始開發,整個項目九月中旬必須上線。
    • 系統集成和數據遷移。系統需要對接客戶的CRM,對接3個服務商,需要對老官網歷史數據(訂單、會員等)進行遷移。
    • 多團隊溝通。小程序設計稿由第三方提供,因此多出了溝通、確認的時間,以及把控第三方交付的時間,以避免交付進度的影響。

     

    迭代計劃

    Inception Workshop一結束,差不多就開始整理整個項目涉及的故事和技術卡,按照兩周一迭代進行迭代計劃安排並與客戶確認,每個迭代第一周周三安排跟客戶showcase上一周的預定的交付結果,得到反饋並安排進行改進。官網項目比較順利,改造自定義了一下SSR框架就能開始進行開發,並且因為歷史原因,還能享受到上一個項目遺留的一些福利,當然也少不了一些坑。

    小程序的時間比較緊,相當於整個複製了一遍官網的功能,主要是前端任務,後端可以復用官網後端,因此一開始就給團隊同學同步到整個項目的情況,讓大家有一個大概的心理準備。然後就是與官網類似的處理,整個交付內容進行迭代排期並與客戶確認,前期盡量能多做一些,避免後期怎麼努力都無法完成的囧鏡。

     

    項目進行時

    整個項目的過程中,PM會根據迭代完成情況靈活的找外援加入項目進行支援,以免交付延期。每日的站會(Standup Meeting)更新,讓團隊能對當前進度有一個大概的了解以及同步一些突發信息。定期的回顧會議(Retrospective Meeting)能暴露團隊內部問題,將風險扼殺於苗頭,鼓勵能為團隊帶來正向幫助的行為,及時停止不好的做法。

    迭代會議(IPM)能讓團隊對下一個迭代具體要做的事情有一個詳細的了解,進行大致的估點,以便check開發進度情況。技術人員定期的CodeReview成為一個大家交流的時段,發現風險,指出問題,互相提高,還可以幫助新人快速的融入團隊。

    根據團隊內部人員情況,可以定期進行一對一溝通,了解個人訴求或是給與近況反饋都是一個不錯的渠道。TL應考慮團隊內部人員提升自己的訴求,在一些安排上給與傾斜和鼓勵,發現問題也需要提前制止。

     

    不足之處

    • 後期對卡牆(Jira)的管理鬆懈。導致有些問題反覆修改,且丟失context
    • 項目對運營後台有一些的定製化配置,沒有提前準備運營需要了解的後台操作資料和培訓,導致後期花費大量精力幫助運營進行後台配置與更新
    • 人員(QA)變動頻繁。公司處於高速發展階段,項目經歷了4個QA,因此有些context可能丟失,測試不到位,導致項目上線出了一些低級問題。比如上線后發現部分瀏覽器有支付兼容問題
    • 甲乙方定位太角色化,不能站在專業角度評估客戶需求(項目做完感覺都一樣,客戶是爸爸)
    • 與第三方合作交付產物管控不到位,導致第三方設計稿的延遲影響到我們的交付計劃
    • 與客戶溝通的需求,後面有一些沒有進行郵件確認,導致交付驗收階段因一些需求上的問題產生不愉快(這個完全沒必要的)
    • 對第三方系統的了解不充分和集成系統的需求整理不清晰導致後續一系列的開發、測試都不到位,以致上線出了不可控的問題

     

    項目總結

    • 提前評估項目的風險點,且在項目進行過程中持續維護,後期安排足夠的時間進行調研與分析
    • 與第三方合作一定要有自己的規劃,並且將定好的規劃提前與第三方確認時間,然後派人提前專門細緻的了解第三方需求詳細點,確定好具體的業務場景,再來規劃己方與第三方的具體集成的點。此外,在進行的過程中,還應注意定時檢查合作進度,管控風險
    • 與客戶溝通的所有需求都要進行郵件的二次確認,一個是能夠對所有需求來源有所記錄,另一個是能避免後面的不必要的消耗
    • 開發管理不能鬆懈,盡量做到所有的改動都能有卡,能夠進行追溯
    • 對後期交付時所需要的資料提前準備,對需要進行培訓的人員提前約好時間進行溝通培訓

    在前端這塊的管理上,做的還不夠。前期經常codereview,然後效果都還不錯,讓我有了一些錯覺就是當前團隊趨於穩定,中後期即便是加班比較多,大家氣氛這塊我覺得都還好。不過在項目後期的時候,有些疏於管理,然後大家有些人也被分配到了其他項目,和同事們的交流不夠,沒有及時的顧及到一些個人情緒,這塊是可以加強的。

    作為一個Lead,不論同事是否還在一個項目都應該及時的去了解近況,給與自己力所能及的幫助,這樣才能產生向心力,以幫助一些比較迷茫的同學找到方向,看見燈塔。

    整個項目時間不長,得失還是挺多的,不論是管理還是技術上,都會有一些心得。然後項目的ROI還不錯,得到公司領導的肯定,最後客戶那邊的反饋也還不錯,算是對大家努力的一種認可。

    ps: 及時總結,靜心沉澱;如風少年,砥礪前行。

    如想了解更多,請移步

    歡迎關注我的公眾號 “和F君一起xx”

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 附009.Kubernetes永久存儲之GlusterFS獨立部署

    附009.Kubernetes永久存儲之GlusterFS獨立部署

    一 前期準備

    1.1 基礎知識


    Heketi提供了一個RESTful管理界面,可以用來管理GlusterFS卷的生命周期。Heketi會動態在集群內選擇bricks構建所需的volumes,從而確保數據的副本會分散到集群不同的故障域內。同時Heketi還支持任意數量的ClusterFS集群。

    提示:本實驗基於glusterfs和Kubernetes分開部署,heketi管理glusterfs,Kubernetes使用heketi提供的API,從而實現glusterfs的永久存儲,,而非Kubernetes部署glusterfs。

    1.2 架構示意






    提示:本實驗Heketi僅管理單zone的glusterfs集群。

    1.3 相關規劃

































    主機 IP 磁盤 備註
    servera 172.24.8.41 sdb glusterfs節點
    serverb 172.24.8.42 sdb glusterfs節點
    serverc 172.24.8.43 sdb glusterfs節點
    heketi 172.24.8.44 Heketi主機










































    servera serverb serverc
    PV sdb1 sdb1 sdb1
    VG vg0 vg0 vg0
    LV datalv datalv datalv
    bricks目錄 /bricks/data /bricks/data /bricks/data

    1.4 其他準備


    所有節點NTP配置;

    所有節點添加相應主機名解析:

    172.24.8.41 servera

    172.24.8.42 serverb

    172.24.8.43 serverc

    172.24.8.44 heketi

    注意:若非必要,建議關閉防火牆和SELinux。

    二 規劃相應存儲卷

    2.1 劃分LVM

      1 [root@servera ~]# fdisk /dev/sdb				#創建lvm的sdb1,過程略
      2 [root@servera ~]# pvcreate /dev/sdb1			#使用/dev/vdb1創建PV
      3 [root@servera ~]# vgcreate vg0 /dev/sdb1			#創建vg
      4 [root@servera ~]# lvcreate -L 15G -T vg0/thinpool		#創建支持thin的lv池
      5 [root@servera ~]# lvcreate -V 10G -T vg0/thinpool -n datalv	#創建相應brick的lv
      6 [root@servera ~]# vgdisplay					#驗證確認vg信息
      7 [root@servera ~]# pvdisplay					#驗證確認pv信息
      8 [root@servera ~]# lvdisplay					#驗證確認lv信息



    提示:serverb、serverc類似操作,根據規劃需求創建完所有基於LVM的brick。

    三 安裝glusterfs

    3.1 安裝相應RPM源

      1 [root@servera ~]# yum -y install centos-release-gluster


    提示:serverb、serverc、client類似操作,安裝相應glusterfs源;

    安裝相應源之後,會在/etc/yum.repos.d/目錄多出文件CentOS-Storage-common.repo,內容如下:

      1 # CentOS-Storage.repo
      2 #
      3 # Please see http://wiki.centos.org/SpecialInterestGroup/Storage for more
      4 # information
      5 
      6 [centos-storage-debuginfo]
      7 name=CentOS-$releasever - Storage SIG - debuginfo
      8 baseurl=http://debuginfo.centos.org/$contentdir/$releasever/storage/$basearch/
      9 gpgcheck=1
     10 enabled=0
     11 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage


    3.2 安裝glusterfs

      1 [root@servera ~]# yum -y install glusterfs-server


    提示:serverb、serverc類似操作,安裝glusterfs服務端。

    3.3 啟動glusterfs

      1 [root@servera ~]# systemctl start glusterd
      2 [root@servera ~]# systemctl enable glusterd



    提示:serverb、serverc類似操作,所有節點啟動glusterfs服務端;

    安裝完glusterfs之後建議exit退出終端重新登錄,從而可以補全glusterfs相關命令。

    3.4 添加信任池

      1 [root@servera ~]# gluster peer probe serverb
      2 peer probe: success.
      3 [root@servera ~]# gluster peer probe serverc
      4 peer probe: success.
      5 [root@servera ~]# gluster peer status		#查看信任池狀態
      6 [root@servera ~]# gluster pool list			#查看信任池列表




    提示:加信任池的操作,只需要在servera、serverb、serverc所有集群節點主機中的任意一台上面執行添加其他三個節點的操作即可。

    提示:若未關閉防火牆,在添加信任池之前必須放通防火牆相應規則,操作如下:

      1 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=glusterfs
      2 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=nfs
      3 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=rpc­bind
      4 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=mountd
      5 [root@servera ~]# firewall­cmd ­­permanent ­­add­port=5666/tcp
      6 [root@servera ~]# firewall­cmd ­­reload


    四 部署Heketi

    4.1 安裝heketi服務

      1 [root@heketi ~]# yum -y install centos-release-gluster
      2 [root@heketi ~]# yum -y install heketi heketi-client


    4.2 配置heketi

      1 [root@heketi ~]# vi /etc/heketi/heketi.json
      2 {
      3   "_port_comment": "Heketi Server Port Number",
      4   "port": "8080",					#默認端口
      5 
      6   "_use_auth": "Enable JWT authorization. Please enable for deployment",
      7   "use_auth": true,					#基於安全考慮開啟認證
      8 
      9   "_jwt": "Private keys for access",
     10   "jwt": {
     11     "_admin": "Admin has access to all APIs",
     12     "admin": {
     13       "key": "admin123"					#管理員密碼
     14     },
     15     "_user": "User only has access to /volumes endpoint",
     16     "user": {
     17       "key": "xianghy"					#普通用戶
     18     }
     19   },
     20 
     21   "_glusterfs_comment": "GlusterFS Configuration",
     22   "glusterfs": {
     23     "_executor_comment": [
     24       "Execute plugin. Possible choices: mock, ssh",
     25       "mock: This setting is used for testing and development.",	#用於測試
     26       "      It will not send commands to any node.",
     27       "ssh:  This setting will notify Heketi to ssh to the nodes.",	#ssh方式
     28       "      It will need the values in sshexec to be configured.",
     29       "kubernetes: Communicate with GlusterFS containers over",		#在GlusterFS由kubernetes創建時採用
     30       "            Kubernetes exec api."
     31     ],
     32     "executor": "ssh",
     33 
     34     "_sshexec_comment": "SSH username and private key file information",
     35     "sshexec": {
     36       "keyfile": "/etc/heketi/heketi_key",
     37       "user": "root",
     38       "port": "22",
     39       "fstab": "/etc/fstab"
     40     },
     41 ……
     42 ……
     43     "loglevel" : "warning"
     44   }
     45 }


    4.3 配置免秘鑰

      1 [root@heketi ~]# ssh-keygen -t rsa -q -f /etc/heketi/heketi_key -N ""
      2 [root@heketi ~]# chown heketi:heketi /etc/heketi/heketi_key
      3 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@servera
      4 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverb
      5 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverc


    4.4 啟動heketi

      1 [root@heketi ~]# systemctl enable heketi.service
      2 [root@heketi ~]# systemctl start heketi.service
      3 [root@heketi ~]# systemctl status heketi.service
      4 [root@heketi ~]# curl http://localhost:8080/hello		#測試訪問
      5 Hello from Heketi


    4.5 配置Heketi拓撲


           拓撲信息用於讓Heketi確認可以使用的存儲節點、磁盤和集群,必須自行確定節點的故障域。故障域是賦予一組節點的整數值,這組節點共享相同的交換機、電源或其他任何會導致它們同時失效的組件。必須確認哪些節點構成一個集群,Heketi使用這些信息來確保跨故障域中創建副本,從而提供數據冗餘能力,Heketi支持多個Gluster存儲集群。

    配置Heketi拓撲注意以下幾點:

    • 可以通過topology.json文件定義組建的GlusterFS集群;
    • topology指定了層級關係:clusters –> nodes –> node/devices –> hostnames/zone;
    • node/hostnames字段的manage建議填寫主機ip,指管理通道,注意當heketi服務器不能通過hostname訪問GlusterFS節點時不能填寫hostname;
    • node/hostnames字段的storage建議填寫主機ip,指存儲數據通道,與manage可以不一樣,生產環境管理網絡和存儲網絡建議分離;
    • node/zone字段指定了node所處的故障域,heketi通過跨故障域創建副本,提高數據高可用性質,如可以通過rack的不同區分zone值,創建跨機架的故障域;
    • devices字段指定GlusterFS各節點的盤符(可以是多塊盤),必須是未創建文件系統的裸設備。

      1 [root@heketi ~]# vi /etc/heketi/topology.json
      2 {
      3     "clusters": [
      4         {
      5             "nodes": [
      6                 {
      7                     "node": {
      8                         "hostnames": {
      9                             "manage": [
     10                                 "172.24.8.41"
     11                             ],
     12                             "storage": [
     13                                 "172.24.8.41"
     14                             ]
     15                         },
     16                         "zone": 1
     17                     },
     18                     "devices": [
     19                         "/dev/mapper/vg0-datalv"
     20                     ]
     21                 },
     22                 {
     23                     "node": {
     24                         "hostnames": {
     25                             "manage": [
     26                                 "172.24.8.42"
     27                             ],
     28                             "storage": [
     29                                 "172.24.8.42"
     30                             ]
     31                         },
     32                         "zone": 1
     33                     },
     34                     "devices": [
     35                         "/dev/mapper/vg0-datalv"
     36                     ]
     37                 },
     38                 {
     39                     "node": {
     40                         "hostnames": {
     41                             "manage": [
     42                                 "172.24.8.43"
     43                             ],
     44                             "storage": [
     45                                 "172.24.8.43"
     46                             ]
     47                         },
     48                         "zone": 1
     49                     },
     50                     "devices": [
     51                         "/dev/mapper/vg0-datalv"
     52                     ]
     53                 }
     54             ]
     55         }
     56     ]
     57 }
     58 
     59 [root@heketi ~]# echo "export HEKETI_CLI_SERVER=http://heketi:8080" >> /etc/profile.d/heketi.sh
     60 [root@heketi ~]# echo "alias heketi-cli='heketi-cli --user admin --secret admin123'" >> .bashrc
     61 [root@heketi ~]# source /etc/profile.d/heketi.sh
     62 [root@heketi ~]# source .bashrc
     63 [root@heketi ~]# echo $HEKETI_CLI_SERVER
     64 http://heketi:8080
     65 [root@heketi ~]# heketi-cli --server $HEKETI_CLI_SERVER --user admin --secret admin123 topology load --json=/etc/heketi/topology.json




    4.6 集群管理

      1 [root@heketi ~]# heketi-cli cluster list					#集群列表
      2 [root@heketi ~]# heketi-cli cluster info aa83b0045fafa362bfc7a8bfee0c24ad	#集群詳細信息
      3 Cluster id: aa83b0045fafa362bfc7a8bfee0c24ad
      4 Nodes:
      5 189ee41572ebf0bf1e297de2302cfb39
      6 46429de5666fc4c6cc570da4b100465d
      7 be0209387384299db34aaf8377c3964c
      8 Volumes:
      9 
     10 Block: true
     11 
     12 File: true
     13 [root@heketi ~]# heketi-cli topology info aa83b0045fafa362bfc7a8bfee0c24ad	#查看拓撲信息




      1 [root@heketi ~]# heketi-cli node list						#卷信息
      2 Id:189ee41572ebf0bf1e297de2302cfb39     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
      3 Id:46429de5666fc4c6cc570da4b100465d     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
      4 Id:be0209387384299db34aaf8377c3964c     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
      5 [root@heketi ~]# heketi-cli node info 189ee41572ebf0bf1e297de2302cfb39		#節點信息
      6 [root@heketi ~]# heketi-cli volume create --size=2 --replica=2			#默認為3副本的replica模式



      1 [root@heketi ~]# heketi-cli volume list						#卷信息
      2 [root@heketi ~]# heketi-cli volume info 7da55685ebeeaaca60708cd797a5e391
      3 [root@servera ~]# gluster volume info						#通過glusterfs節點查看


    4.7 測試驗證

      1 [root@heketi ~]# yum -y install centos-release-gluster
      2 [root@heketi ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse
      3 [root@heketi ~]# mount -t glusterfs 172.24.8.41:vol_7da55685ebeeaaca60708cd797a5e391 /mnt



      1 [root@heketi ~]# umount /mnt
      2 [root@heketi ~]# heketi-cli volume delete 7da55685ebeeaaca60708cd797a5e391	#驗證完畢刪除



    參考:https://www.jianshu.com/p/1069ddaaea78

    https://www.cnblogs.com/panwenbin-logs/p/10231859.html

    五 Kubernetes動態掛載glusterfs

    5.1 StorageClass動態存儲


    kubernetes共享存儲provider模式:

    靜態模式(Static):集群管理員手工創建PV,在定義PV時設置後端存儲的特性;

    動態模式(Dynamic):集群管理員不需要手工創建PV,而是通過StorageClass的設置對後端存儲進行描述,標記為某種”類型(Class)”;此時要求PVC對存儲的類型進行說明,系統將自動完成PV的創建及與PVC的綁定;PVC可以聲明Class為””,說明PVC禁止使用動態模式。

    基於StorageClass的動態存儲供應整體過程如下圖所示:


    1. 集群管理員預先創建存儲類(StorageClass);
    2. 用戶創建使用存儲類的持久化存儲聲明(PVC:PersistentVolumeClaim);
    3. 存儲持久化聲明通知系統,它需要一個持久化存儲(PV: PersistentVolume);
    4. 系統讀取存儲類的信息;
    5. 系統基於存儲類的信息,在後台自動創建PVC需要的PV;
    6. 用戶創建一個使用PVC的Pod;
    7. Pod中的應用通過PVC進行數據的持久化;
    8. 而PVC使用PV進行數據的最終持久化處理。


    提示:關於Kubernetes的部署參考《附003.Kubeadm部署Kubernetes》。

    5.2 定義StorageClass


    關鍵字說明:

    • provisioner:表示存儲分配器,需要根據後端存儲的不同而變更;
    • reclaimPolicy: 默認即”Delete”,刪除pvc后,相應的pv及後端的volume,brick(lvm)等一起刪除;設置為”Retain”時則保留數據,若需刪除則需要手工處理;
    • resturl:heketi API服務提供的url;
    • restauthenabled:可選參數,默認值為”false”,heketi服務開啟認證時必須設置為”true”;
    • restuser:可選參數,開啟認證時設置相應用戶名;
    • secretNamespace:可選參數,開啟認證時可以設置為使用持久化存儲的namespace;
    • secretName:可選參數,開啟認證時,需要將heketi服務的認證密碼保存在secret資源中;
    • clusterid:可選參數,指定集群id,也可以是1個clusterid列表,格式為”id1,id2”;
    • volumetype:可選參數,設置卷類型及其參數,如果未分配卷類型,則有分配器決定卷類型;如”volumetype: replicate:3”表示3副本的replicate卷,”volumetype: disperse:4:2”表示disperse卷,其中‘4’是數據,’2’是冗餘校驗,”volumetype: none”表示distribute卷


    提示:關於glusterfs各種不同類型的卷見《004.RHGS-創建volume》。

      1 [root@k8smaster01 ~]# kubectl create ns heketi		#創建命名空間
      2 [root@k8smaster01 ~]# echo -n "admin123" | base64		#將密碼轉換為64位編碼
      3 YWRtaW4xMjM=
      4 [root@k8smaster01 ~]# mkdir -p heketi
      5 [root@k8smaster01 ~]# cd heketi/
      6 [root@k8smaster01 ~]# vi heketi-secret.yaml			#創建用於保存密碼的secret
      7 apiVersion: v1
      8 kind: Secret
      9 metadata:
     10   name: heketi-secret
     11   namespace: heketi
     12 data:
     13   # base64 encoded password. E.g.: echo -n "mypassword" | base64
     14   key: YWRtaW4xMjM=
     15 type: kubernetes.io/glusterfs
     16 [root@k8smaster01 heketi]# kubectl create -f heketi-secret.yaml	#創建heketi
     17 [root@k8smaster01 heketi]# kubectl get secrets -n heketi
     18 NAME                  TYPE                                  DATA   AGE
     19 default-token-5sn5d   kubernetes.io/service-account-token   3      43s
     20 heketi-secret         kubernetes.io/glusterfs               1      5s
     21 [root@kubenode1 heketi]# vim gluster-heketi-storageclass.yaml	#正式創建StorageClass
     22 apiVersion: storage.k8s.io/v1
     23 kind: StorageClass
     24 metadata:
     25   name: gluster-heketi-storageclass
     26 parameters:
     27   resturl: "http://172.24.8.44:8080"
     28   clusterid: "aa83b0045fafa362bfc7a8bfee0c24ad"
     29   restauthenabled: "true"					#若heketi開啟認證此處也必須開啟auth認證
     30   restuser: "admin"
     31   secretName: "heketi-secret"				#name/namespace與secret資源中定義一致
     32   secretNamespace: "heketi"
     33   volumetype: "replicate:3"
     34 provisioner: kubernetes.io/glusterfs
     35 reclaimPolicy: Delete
     36 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-storageclass.yaml



    注意:storageclass資源創建后不可變更,如修改只能刪除后重建。

      1 [root@k8smaster01 heketi]# kubectl get storageclasses		#查看確認
      2 NAME                          PROVISIONER               AGE
      3 gluster-heketi-storageclass   kubernetes.io/glusterfs   85s
      4 [root@k8smaster01 heketi]# kubectl describe storageclasses gluster-heketi-storageclass




    5.3 定義PVC

      1 [root@k8smaster01 heketi]# cat gluster-heketi-pvc.yaml
      2 apiVersion: v1
      3 metadata:
      4   name: gluster-heketi-pvc
      5   annotations:
      6     volume.beta.kubernetes.io/storage-class: gluster-heketi-storageclass
      7 spec:
      8   accessModes:
      9   - ReadWriteOnce
     10   resources:
     11     requests:
     12       storage: 1Gi



    注意:accessModes可有如下簡寫:

    • ReadWriteOnce:簡寫RWO,讀寫權限,且只能被單個node掛載;
    • ReadOnlyMany:簡寫ROX,只讀權限,允許被多個node掛載;
    • ReadWriteMany:簡寫RWX,讀寫權限,允許被多個node掛載。

      1 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pvc.yaml
      2 [root@k8smaster01 heketi]# kubectl get pvc
      3 [root@k8smaster01 heketi]# kubectl describe pvc gluster-heketi-pvc
      4 [root@k8smaster01 heketi]# kubectl get pv
      5 [root@k8smaster01 heketi]# kubectl describe pv pvc-5f7420ef-082d-11ea-badf-000c29fa7a79




      1 [root@k8smaster01 heketi]# kubectl describe endpoints glusterfs-dynamic-5f7420ef-082d-11ea-badf-000c29fa7a79




    提示:由上可知:PVC狀態為Bound,Capacity為1G。查看PV詳細信息,除容量,引用storageclass信息,狀態,回收策略等外,同時可知GlusterFS的Endpoint與path。EndpointsName為固定格式:glusterfs-dynamic-PV_NAME,且endpoints資源中指定了掛載存儲時的具體地址。

    5.4 確認查看


    通過5.3所創建的信息:

    • volume與brick已經創建;
    • 主掛載點(通信)在172.24.8.41節點,其餘兩個節點備選;
    • 三副本的情況下,所有節點都會創建brick。

      1 [root@heketi ~]# heketi-cli topology info			#heketi主機查看
      2 [root@serverb ~]# lsblk						#glusterfs節點查看
      3 [root@serverb ~]# df -hT					#glusterfs節點查看
      4 [root@servera ~]# gluster volume list				#glusterfs節點查看
      5 [root@servera ~]# gluster volume info vol_e4c948687239df9833748d081ddb6fd5




    5.5 Pod掛載測試

      1 [root@xxx ~]# yum -y install centos-release-gluster
      2 [root@xxx ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse



    提示:所有需要使用glusterfs volume的Kubernetes節點都必須安裝glusterfs-fuse以便於正常掛載,同時版本需要和glusterfs節點一致。

      1 [root@k8smaster01 heketi]# vi gluster-heketi-pod.yaml
      2 kind: Pod
      3 apiVersion: v1
      4 metadata:
      5   name: gluster-heketi-pod
      6 spec:
      7   containers:
      8   - name: gluster-heketi-container
      9     image: busybox
     10     command:
     11     - sleep
     12     - "3600"
     13     volumeMounts:
     14     - name: gluster-heketi-volume			#必須和volumes中name一致
     15       mountPath: "/pv-data"
     16       readOnly: false
     17   volumes:
     18   - name: gluster-heketi-volume
     19     persistentVolumeClaim:
     20       claimName: gluster-heketi-pvc			#必須和5.3創建的PVC中的name一致
     21 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pod.yaml -n heketi		#創建Pod


    5.6 確認驗證

      1 [root@k8smaster01 heketi]# kubectl get pod -n heketi | grep gluster
      2 gluster-heketi-pod          1/1     Running   0          2m43s
      3 [root@k8smaster01 heketi]# kubectl exec -it gluster-heketi-pod /bin/sh		#進入Pod寫入測試文件
      4 / # cd /pv-data/
      5 /pv-data # echo "This is a file!" >> a.txt
      6 /pv-data # echo "This is b file!" >> b.txt
      7 /pv-data # ls
      8 a.txt  b.txt
      9 [root@servera ~]# df -hT					#在glusterfs節點查看Kubernetes節點的測試文件
     10 [root@servera ~]# cd /var/lib/heketi/mounts/vg_47c90d90e03de79696f90bd94cfccdde/brick_721243c3e0cf8a2372f05d5085a4338c/brick/
     11 [root@servera brick]# ls
     12 [root@servera brick]# cat a.txt
     13 [root@servera brick]# cat b.txt




    5.7 刪除資源

      1 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pod.yaml
      2 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pvc.yaml
      3 [root@k8smaster01 heketi]# kubectl get pvc
      4 [root@k8smaster01 heketi]# kubectl get pv
      5 [root@servera ~]# gluster volume list
      6 No volumes present in cluster



    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選