標籤: USB CONNECTOR

  • 蘋果傳將攜手韓廠研發電動車用電池

    蘋果公司持續擴大業務範圍,除新成立的蘋果能源(Apple Energy)成功取得售電許可外,市場上關注度十足的蘋果電動車專案「泰坦計畫」(Project Titan)也時常傳出新消息。近期傳言蘋果將與南韓某企業合作,運用該南韓公司的電池專利,攜手開發電動車用電池。

    根據日本蘋果情報網站iPhone Mania、南韓媒體ET News 等報導,蘋果看上某家南韓廠商所取得的鋰電池專利,打算與該廠合作開發電動車用電池。被看上的鋰電池專利技術,電池中央部位採中空設計,可使空氣流通來冷卻電池,等同降低冷卻系統的需求,騰出更多空間來增加電池容量。

    日、韓媒體並未披露可能將與蘋果合作的韓廠名稱,但美國MacRumors 透剁歐洲專利局得知,這款「中央中空」的電池是由一家稱為Orange Power 的南韓廠商取得。該廠係一小廠,員工人數僅33人,其中多數為研發人員。

    蘋果電動車計畫自曝光以來即備受矚目,但至今仍然只聞樓梯響。據了解,目前共有約1,000人參與蘋果電動車企劃,且蘋果已在柏林設立秘密開發實驗室。MoneyDJ引述The Information網站的說法,表示蘋果電動車可能在2021年亮相;但也有分析師認為,蘋果電動車可能會步上蘋果電視的後塵,胎死腹中。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

  • 計算機圖形學—— 隱藏線和隱藏面的消除(消隱算法)

    計算機圖形學—— 隱藏線和隱藏面的消除(消隱算法)

     

    一、概述

    由於投影變換失去了深度信息,往往導致圖形的二義性。要消除二義性,就必須在繪製時消除被遮擋的不可見的線或面,習慣上稱作消除隱藏線和隱藏面(或可見線判定、可見面判定),或簡稱為消隱。經過消隱得到的投影圖稱為物體的真實感圖形。

    下面這個圖就很好體現了這種二義性。

    消隱后的效果圖:

    消隱算法的分類

    所有隱藏面消隱算法必須確定:
    在沿透視投影的投影中心或沿平行投影的投影方向看過去哪些邊或面是可見的

    兩種基本算法

    1、以構成圖像的每一個像素為處理單元,對場景中的所有表面,確定相對於觀察點是可見的表面,用該表面的顏色填充該像素.
    適於面消隱。

    算法步驟:

    a.在和投影點到像素連線相交的表面中,找到離觀察點最近的表面;
    b.用該表面上交點處的顏色填充該像素;

    2、以三維場景中的物體對象為處理單元,在所有對象之間進行比較,除去完全不可見的物體和物體上不可見的部分.適於面消隱也適於線消隱。

    算法步驟:
    a.判定場景中的所有可見表面;
    b.用可見表面的顏色填充相應的像素以構成圖形;

    提醒注意

    1.假定構成物體的面不能相互貫穿,也不能有循環遮擋的情況。
    2.假定投影平面是oxy平面,投影方向為z軸的負方向。

    如果構成物體的面不滿足該假定,可以把它們剖分成互不貫穿和不循環遮擋的情況。
    例如,用圖b中的虛線便可把原來循環遮擋的三個平面,分割成不存在循環遮擋的四個面。  

    二、可見面判斷的有效技術

    1、邊界盒

    指能夠包含該物體的一個幾何形狀(如矩形/圓/長方體等),該形狀有較簡單的邊界。

     

    邊界盒技術用於判斷兩條直線是否相交。

    進一步簡化判斷

    2、後向面消除(Back-face Removal)

    思路:把顯然不可見的面去掉,減少消隱過程中的直線求交數目

     

     

    如何判斷:根據定義尋找外(或內)法向,若外法向背離觀察者,或內法向指向觀察者,則該面為後向面。

     

     

     

     

     

     

     

     

     

     

    注意:如果多邊形是凸的,則可只取一個三角形計算有向面積sp。如果多邊形不是凸的,只取一個三角形計算有向面積sp可能會出現錯誤,即F所在的面為前向面也可能出現sp≥0的情況,因此,需按上式計算多邊形F的有向面積。如果sp ≥0,則F所在的面為後向面。

    3、非垂直投影轉化成垂直投影

    物體之間的遮擋關係與投影中心和投影方向有着密切的關係,因此,對物體的可見性判定也和投影方式有密切的關係。

    垂直投影的優點:進行投影時可以忽略z值,即:實物的(x,y)可直接做為投影后的二維平面上的坐標(x,y)

    上述討論說明,垂直投影比非垂直投影容易實現,並且計算量小。因此在進行消隱工作之前,首先應將非垂直投影轉換成垂直投影,從而降低算法的複雜性,提高運算速度。

    如何把透視投影變為垂直投影,其本質是把稜台變成長方體。

    三、基於窗口的子分算法(Warnack算法)

    是一種分而治之(Divide-Conquer)的算法。

     

    1、關係判斷

    2、可見性判斷

    3、分隔結束條件

    4、提高效率的有效的處理技術

    5、算法描述

    用多邊形的邊界對區域作劃分,其目的是盡量減少對區域劃分的次數--利用裁剪算法

     

    四、八叉樹算法

    為了生成真實感圖形,關鍵問題之一就是對圖像空間的每一個像素進行處理。從場景中所有的在該像素上有投影的表面中確定相對於觀察點是可見表面。為了提高算法效率,自然是希望從可能在像素上有投影的面片中尋找可見表面。八叉樹算法是快速尋找可見面的一種有效方法,是一種搜索算法。

    基本思想:將能夠包含整個場景的立方體,即八叉樹的根結點,按照x,y,z三個方向中的剖面分割成八個子立方體,稱為根結點的八個子結點。對每一個子立方體,如果它包含的表面片少於一個給定的值,則該子立方體為八叉樹的終端結點,否則為非終端結點並將其進一步分割成八個子立方體;重複上述過程,直到每個小立方體所包含的表面片少於一個給定的值,分割即告終止。

     

     

    那麼對於上述圖所示,視圖平面法向量(1,1,1)那麼此時它的一個排列是0,1,2,4,3,5,6,7,即最遠的八分體是0,與八分體0共享一個面的三個相鄰八分體是1,2和4,與最近八分體7的3個相鄰八分體是3,5和6。

     

    五、Z緩衝器算法

    1、算法描述

    z緩衝器算法是最簡單的隱藏面消除算法之一。
    基本思想:對屏幕上每一個像素點,過像素中心做一條投影線,找到此投影線與所有多邊形交點中離觀察者最近的點,此點的屬性(顏色或灰度)值即為這一屏幕像素點的屬性值。

    需要兩個緩衝器數組,即:z緩衝器數組和幀緩衝器數組,分別設為 Zdepth[ ][ ] 與  Frame[ ][ ]
    z緩衝器是一組存貯單元,其單元個數和屏幕上像素的個數相同,也和幀緩衝器的單元個數相同,它們之間一一對應。
    幀緩衝器每個單元存放對應像素的顏色值;z緩衝器每個單元存放對應像素的深度值;

    2、算法實現

     

    算法的複雜性正比於m*n*N,在屏幕大小即m*n一定的情況下,算法的計算量只和多邊形個數N成正比

    3、優缺點

     z-Buffer算法沒有利用圖形的相關性和連續性,這是z-Buffer算法的嚴重缺陷,更為嚴重的是,該算法是像素級上的消隱算法。

     六、掃描線z緩衝器算法

    1、算法描述

    將z緩衝器的單元數置為和一條掃描線上的像素數目相同。
    從最上面的一條掃描線開始工作,向下對每一條掃描線作如下處理:

     

    掃描線算法也屬於圖像空間消隱算法。該算法可以看作是多邊形區域填充里介紹過的邊相關掃描線填充算法的延伸。不同的是在消隱算法中處理的是多個面片,而多邊形填充中是對單個多邊形面進行填充。

    2、數據結構

    對每個多邊形,檢查它在oxy平面上的投影和當前掃描線是否相交?
    若不相交,則不考慮該多邊形。
    如果相交,則掃描線和多邊形邊界的交點是成對地出現
    每對交點中間的像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較
    若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
    對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形。
    對幀緩衝器每一行的單元都填上相應內容后就得到了整個消隱后的圖。

    每處理一條掃描線,都要檢查各多邊形是否和該線相交,還要計算多邊形所在平面上很多點的z值,需要花費很大的計算
    為了提高算法效率,採用跟多邊形掃描轉換中的掃描線算法類似的數據結構和算法.

    多邊形Y表

     

    實際上是一個指針數組 ,每個表的深度和显示屏幕行數相同.將所有多邊形存在多邊形Y表中,根據多邊形頂點中Y坐標最大值,插入多邊形Y表中的相應位置,多邊形Y表中保存多邊形的序號和其頂點的最大y坐標.

    邊Y表

     要注意:Δx是下一條掃描線與邊交點的x減去當前的掃描線與邊交點的x。

    多邊形活化表

    邊對活化表

    其實這裏最難理解的就是Δyl和Δxr了,這裏的意思就是當前掃描線所處的y值和與該掃描線相交邊的最小y值的差值。

    就比如說掃描線y=6,與第一個三角形有兩個交點,左交點(4,6),右交點(7,6)那麼Δyl=6-3  Δyr=6-3

    3、重溫算法目標

     對每一條掃描線,檢查對每個多邊形的投影是否相交,如相交則交點成對出現,對每對交點中間的每個像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較,若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
    對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形,對幀緩衝器每一行的單元都填上相應內容后也就得到了整個消隱后的圖。

    4、算法步驟

     

    算法描述如下

    七、優先級排序表算法

    1、算法思想

    優先級排序表算法按多邊形離觀察者的遠近來建立一個多邊形排序表,距觀察者遠的優先級低,放在表頭;近的優先級高,放在表尾
    從優先級低的多邊形開始,依次把多邊形的顏色填入幀緩衝存儲器中
    表中距觀察者近的元素覆蓋幀緩衝存儲器中原有的內容
    當優先級最高的多邊形的圖形送入幀緩衝器后,整幅圖形就形成了
    類似於油畫家繪畫過程,因此又稱為油畫家算法。

     

     

     

     

     

     

    2、算法的優缺點

    算法的優點:
    簡單,容易實現,並且可以作為實現更複雜算法的基礎;
    缺點:
    只能處理不相交的面,而且深度優先級表中面的順序可能出錯.

    該算法不能處理某些特殊情況。

     

     

    解決辦法:把P沿Q平面一分為二,從多邊形序列中把原多邊形P去掉,把分割P生成的兩個多邊形加入鏈表中。具體實現時,當離視點最遠的多邊形P和其他多邊形交換時,要對P做一標誌,當有標誌的多邊形再換成離視點最遠的多邊形時,則說明出現了上述的現象,可用分割方法進行處理 。

    用來解決動態显示問題時,可大大提高效率

    八、光線投射算法

    1、算法原理

    要處理的場景中有無限多條光線,從採樣的角度講我們僅對穿過像素的光線感興趣,因此,可考慮從像素出發,逆向追蹤射入場景的光線路徑

     

     

     

     

     

    2、算法實現

    由視點出發穿過觀察平面上一像素向場景發射一條射線
    求出射線與場景中各物體表面的交點
    離視點最近的交點的顏色即為像素要填的顏色。
    光線投射算法對於包含曲面,特別是包含球面的場景有很高的效率。

     

     

     

     

     

     

     

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

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

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

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

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

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

  • PostgreSQL空間數據庫創建備份恢復(PostGIS vs ArcGIS)

    梯子

    PostGIS

    創建

    安裝就不必介紹了,windows下使用安裝工具Application Stack Builder,選擇空間擴展PostGIS即可自動安裝

    然後新建庫后,在庫中執行以下語句創建控件擴展

    CREATE EXTENSION postgis

    也可以創建數據庫時選擇postgis的模板庫創建

    create database postgisdb template postgis_template;

    新建數據庫后添加postgis擴展後會發現庫內public模式下函數序列觸發器等都會增加一些postgis相關功能
    然後就可以通過PostGIS Shapefile and DBF Loader工具導入shp數據

    備份

    postgersql的備份恢復主要有

    1. 增量備份和基於時間點恢復(RITR)
    2. pg_dump和pg_dumpall進行轉儲,從SQL轉儲文件恢復
    3. 文件系統級別備份

    這裏我們使用簡單,容易掌握的pg_dump命令,一般在安裝目錄bin下
    pg_dump備份單庫,不導出角色和表空間相關信息

    pg_dump -h localhost -U postgres postgisdb > D:\backup\postgisdb.bak

    有一些參數選項可以參考(很多,具體不列了,執行help可以查看到)

    pg_dump --help
    恢復

    恢復可以使用psql

    psql -h localhost -U postgres -d postgisdb2 < D:\backup\postgisdb.bak

    恢復時可以指定不同的數據庫,如果pg_dump時-C創建數據庫,那也可以不用先新建數據庫

    postgis庫的恢復備份還是挺簡單的,所有的東西都在public下

    ArcGIS

    創建

    ArcGIS要連接到postgresql,需要將postgresql安裝目錄lib下的libeay32.dll、libiconv-2.dll、libintl-8.dll、libpq.dll 和 ssleay32.dll拷貝到ArcGIS Desktop的安裝目錄bin下
    將ArcGIS Desktop目錄DatabaseSupport\PostgreSQL下的st_geometry.dll拷貝到postgresql的lib下

    使用ArcGIS工具箱中Create Enterprise Geodatabase工具創建SDE,完事後會在創建一個sde登陸角色並在庫中創建一個sde模式,包含諸多函數序列管理表等。(注意:ArcGIS雖然可以在系統庫postgres中創建SDE擴展,然並連不上,ArcGIS不允許連接訪問系統數據庫

    然後ArcGIS可以連接該數據庫,並且進行空間數據管理操作。(注意:默認ArcGIS創建空間數據,只能創建在和登陸用戶同名的模式下

    備份

    我們可以向上面PostGIS備份恢復一樣,直接備份整個庫

    恢復

    如果恢復至同名數據庫,像上面恢復是沒有問題的
    但如果數據庫改名了,則會有驚喜發生,ArcGIS管理空間報底層gdb_release之類的錯誤,同樣的問題不止恢復庫時,修改數據庫名稱也不像其他庫那麼隨心所欲,以含SDE擴展的庫為模板創建新庫也會有問題

    ArcGIS SDE未見文檔介紹內部結構邏輯,只能猜測大概,或不準確,願聞其詳

    ArcSDE空間數據創建時會在SDE管理表裡註冊相關信息,比如空間參考,列啊,表的唯一標識等,便於它做數據管理、版本控制

    修改庫名后,ArcSDE管理就出問題,主要是一些註冊項,安裝SDE時也會把該庫的信息註冊到SDE管理表中去,所以新庫名,它就不認識了

    如果修改了庫名,我們找到以下錶

    select * from sde.gdb_items
    you need modify : name physicalname path etc...

    update sde.sde_table_registry set database_name='testdb';
    update sde.sde_column_registry set database_name='testdb';
    update sde.sde_geometry_columns set f_table_catalog='testdb';
    update sde.sde_raster_columns set database_name='testdb';
    update sde.sde_layers set database_name='testdb';

    然後就一切正常

    當然我們建議不輕易改庫名

    這就是商業軟件,足夠強大不夠靈活,封裝和靈活總會互相博弈

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

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

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

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

  • 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  ?

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

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

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

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

  • 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網頁設計已成為網頁設計推薦首選

  • 關於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  ?

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

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

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

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

  • 附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網頁設計已成為網頁設計推薦首選

  • 類加載器 – ClassLoader詳解

    獲得ClassLoader的途徑

    • 獲得當前類的ClassLoader
      • clazz.getClassLoader()
    • 獲得當前線程上下文的ClassLoader
      • Thread.currentThread().getContextClassLoader();
    • 獲得系統的ClassLoader
      • ClassLoader.getSystemClassLoader()
    • 獲得調用者的ClassLoader
      • DriverManager.getCallerClassLoader

    ClassLoader源碼解析

    概述

    類加載器是用於加載類的對象,ClassLoader是一個抽象類。如果我們給定了一個類的二進制名稱,類加載器應嘗試去定位或生成構成定義類的數據。一種典型的策略是將給定的二進制名稱轉換為文件名,然後去文件系統中讀取這個文件名所對應的class文件。

    每個Class對象都會包含一個定義它的ClassLoader的一個引用。

    數組類的Class對象,不是由類加載器去創建的,而是在Java運行期JVM根據需要自動創建的。對於數組類的類加載器來說,是通過Class.getClassLoader()返回的,與數組當中元素類型的類加載器是一樣的;如果數組當中的元素類型是一個原生類型,數組類是沒有類加載器的【代碼一】。

    應用實現了ClassLoader的子類是為了擴展JVM動態加載類的方式。

    類加載器典型情況下時可以被安全管理器所使用去標識安全域問題。

    ClassLoader類使用了委託模型來尋找類和資源,ClassLoader的每一個實例都會有一個與之關聯的父ClassLoader,當ClassLoader被要求尋找一個類或者資源的時候,ClassLoader實例在自身嘗試尋找類或者資源之前會委託它的父類加載器去完成。虛擬機內建的類加載器,稱之為啟動類加載器,是沒有父加載器的,但是可以作為一個類加載器的父類加載器【雙親委託機制】。

    支持併發類加載的類加載器叫做并行類加載器,要求在初始化期間通過ClassLoader.registerAsParallelCapable 方法註冊自身,ClassLoader類默認被註冊為可以并行,但是如果它的子類也是并行加載的話需要單獨去註冊子類。

    在委託模型不是嚴格的層次化的環境下,類加載器需要并行,否則類加載會導致死鎖,因為加載器的鎖在類加載過程中是一直被持有的。

    通常情況下,Java虛擬機以平台相關的形式從本地的文件系統中加載類,比如在UNIX系統,虛擬機從CLASSPATH環境所定義的目錄加載類。
    然而,有些類並不是來自於文件;它們是從其它來源得到的,比如網絡,或者是由應用本身構建【動態代理】。定義類(defineClass )方法會將字節數組轉換為Class的實例,這個新定義類的實例可以由Class.newInstance創建。

    由類加載器創建的對象的方法和構造方法可能引用其它的類,為了確定被引用的類,Java虛擬機會調用最初創建類的類加載器的loadClass方法。

    二進制名稱:以字符串參數的形式向CalssLoader提供的任意一個類名,必須是一個二進制的名稱,包含以下四種情況

    • “java.lang.String” 正常類
    • “javax.swing.JSpinner$DefaultEditor” 內部類
    • “java.security.KeyStore\(Builder\)FileBuilder$1″ KeyStore的內部類Builder的內部類FileBuilder的第一個匿名內部類
    • “java.net.URLClassLoader$3$1” URLClassLoader類的第三個匿名內部類的第一個匿名內部類

    代碼一:

    public class Test12 {
        public static void main(String[] args) {
            String[] strings = new String[6];
            System.out.println(strings.getClass().getClassLoader());
            // 運行結果:null
    
            Test12[] test12s = new Test12[1];
            System.out.println(test12s.getClass().getClassLoader());
            // 運行結果:sun.misc.Launcher$AppClassLoader@18b4aac2
    
            int[] ints = new int[2];
            System.out.println(ints.getClass().getClassLoader());
            // 運行結果:null
        }
    }

    loadClass方法

    loadClass的源碼如下, loadClass方法加載擁有指定的二進制名稱的Class,默認按照如下順序尋找類:

    • 調用findLoadedClass(String)檢查這個類是否被加載
    • 調用父類加載器的loadClass方法,如果父類加載器為null,就會調用啟動類加載器
    • 調用findClass(String)方法尋找

    使用上述步驟如果類被找到且resolve為true,就會去調用resolveClass(Class)方法

    protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
    {
      synchronized (getClassLoadingLock(name)) {
          // First, check if the class has already been loaded
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime();
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }
    
              if (c == null) {
                  // If still not found, then invoke findClass in order
                  // to find the class.
                  long t1 = System.nanoTime();
                  c = findClass(name);
    
                  // this is the defining class loader; record the stats
                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                  sun.misc.PerfCounter.getFindClasses().increment();
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
    }

    findClass方法

    findClass的源碼如下,findClass尋找擁有指定二進制名稱的類,JVM鼓勵我們重寫此方法,需要自定義加載器遵循雙親委託機制,該方法會在檢查完父類加載器之後被loadClass方法調用,默認返回ClassNotFoundException異常。

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

    defineClass方法

    defineClass的源碼如下,defineClass方法將一個字節數組轉換為Class的實例。

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

    自定義類加載器

    /**
     * 繼承了ClassLoader,這是一個自定義的類加載器
     * @author 夜的那種黑丶
     */
    public class ClassLoaderTest extends ClassLoader {
        public static void main(String[] args) throws Exception {
            ClassLoaderTest loader = new ClassLoaderTest("loader");
           Class<?> clazz = loader.loadClass("classloader.Test01");
            Object object = clazz.newInstance();
            System.out.println(object);
            System.out.println(object.getClass().getClassLoader());
        }
        //------------------------------以上為測試代碼---------------------------------
    
        /**
         * 類加載器名稱,標識作用
         */
        private String classLoaderName;
    
        /**
         * 從磁盤讀物字節碼文件的擴展名
         */
        private String fileExtension = ".class";
    
        /**
         * 創建一個類加載器對象,將系統類加載器當做該類加載器的父加載器
         * @param classLoaderName 類加載器名稱
         */
        private ClassLoaderTest(String classLoaderName) {
            // 將系統類加載器當做該類加載器的父加載器
            super();
            this.classLoaderName = classLoaderName;
        }
    
        /**
         * 創建一個類加載器對象,显示指定該類加載器的父加載器
         * 前提是需要有一個類加載器作為父加載器
         * @param parent 父加載器
         * @param classLoaderName 類加載器名稱
         */
        private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
            // 显示指定該類加載器的父加載器
            super(parent);
            this.classLoaderName = classLoaderName;
        }
    
        /**
         * 尋找擁有指定二進制名稱的類,重寫ClassLoader類的同名方法,需要自定義加載器遵循雙親委託機制
         * 該方法會在檢查完父類加載器之後被loadClass方法調用
         * 默認返回ClassNotFoundException異常
         * @param className 類名
         * @return Class的實例
         * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
         */
        @Override
        protected Class<?> findClass(String className) throws ClassNotFoundException {
            byte[] data = this.loadClassData(className);
            /*
             * 通過defineClass方法將字節數組轉換為Class
             * defineClass:將一個字節數組轉換為Class的實例,在使用這個Class之前必須要被解析
             */
            return this.defineClass(className, data, 0 , data.length);
        }
    
        /**
         * io操作,根據類名找到對應文件,返回class文件的二進制信息
         * @param className 類名
         * @return class文件的二進制信息
         * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
         */
        private byte[] loadClassData(String className) throws ClassNotFoundException {
            InputStream inputStream = null;
            byte[] data;
            ByteArrayOutputStream byteArrayOutputStream = null;
    
            try {
                this.classLoaderName = this.classLoaderName.replace(".", "/");
                inputStream = new FileInputStream(new File(className + this.fileExtension));
                byteArrayOutputStream = new ByteArrayOutputStream();
    
                int ch;
                while (-1 != (ch = inputStream.read())) {
                    byteArrayOutputStream.write(ch);
                }
    
                data = byteArrayOutputStream.toByteArray();
            } catch (Exception e) {
                throw new ClassNotFoundException();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (byteArrayOutputStream != null) {
                        byteArrayOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return data;
        }
    }

    以上是一段自定義類加載器的代碼,我們執行這段代碼

    classloader.Test01@7f31245a
    sun.misc.Launcher$AppClassLoader@18b4aac2

    可以看見,這段代碼中進行類加載的類加載器還是系統類加載器(AppClassLoader)。這是因為jvm的雙親委託機製造成的,private ClassLoaderTest(String classLoaderName)將系統類加載器當做我們自定義類加載器的父加載器,jvm的雙親委託機制使自定義類加載器委託系統類加載器完成加載。

    改造以下代碼,添加一個path屬性用來指定類加載位置:

    public class ClassLoaderTest extends ClassLoader {
        public static void main(String[] args) throws Exception {
            ClassLoaderTest loader = new ClassLoaderTest("loader");
            loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
            Class<?> clazz = loader.loadClass("classloader.Test01");
            System.out.println("class:" + clazz);
    
            Object object = clazz.newInstance();
            System.out.println(object);
            System.out.println(object.getClass().getClassLoader());
        }
        //------------------------------以上為測試代碼---------------------------------
    
        /**
         * 從指定路徑加載
         */
        private String path;
    
        ......
        
        /**
         * io操作,根據類名找到對應文件,返回class文件的二進制信息
         * @param className 類名
         * @return class文件的二進制信息
         * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
         */
        private byte[] loadClassData(String className) throws ClassNotFoundException {
            InputStream inputStream = null;
            byte[] data;
            ByteArrayOutputStream byteArrayOutputStream = null;
    
            className = className.replace(".", "/");
    
            try {
                this.classLoaderName = this.classLoaderName.replace(".", "/");
                inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
                byteArrayOutputStream = new ByteArrayOutputStream();
    
                int ch;
                while (-1 != (ch = inputStream.read())) {
                    byteArrayOutputStream.write(ch);
                }
    
                data = byteArrayOutputStream.toByteArray();
            } catch (Exception e) {
                throw new ClassNotFoundException();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (byteArrayOutputStream != null) {
                        byteArrayOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return data;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    }

    運行一下

    class:class classloader.Test01
    classloader.Test01@7f31245a
    sun.misc.Launcher$AppClassLoader@18b4aac2

    修改一下測試代碼,並刪除工程下的Test01.class文件

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       loader.setPath("/home/fanxuan/桌面/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);
    
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }

    運行一下

    class:class classloader.Test01
    classloader.Test01@135fbaa4
    classloader.ClassLoaderTest@7f31245a

    分析

    改造后的兩塊代碼,第一塊代碼中加載類的是系統類加載器AppClassLoader,第二塊代碼中加載類的是自定義類加載器ClassLoaderTest。是因為ClassLoaderTest會委託他的父加載器AppClassLoader加載class,第一塊代碼的path直接是工程下,AppClassLoader可以加載到,而第二塊代碼的path在桌面目錄下,所以AppClassLoader無法加載到,然後ClassLoaderTest自身嘗試加載並成功加載到。如果第二塊代碼工程目錄下的Test01.class文件沒有被刪除,那麼依然是AppClassLoader加載。

    再來測試一塊代碼

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz.hashCode());
    
        Object object = clazz.newInstance();
        System.out.println(object.getClass().getClassLoader());
    
        ClassLoaderTest loader2 = new ClassLoaderTest("loader");
        loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz2 = loader2.loadClass("classloader.Test01");
        System.out.println("class:" + clazz2.hashCode());
    
        Object object2 = clazz2.newInstance();
        System.out.println(object2.getClass().getClassLoader());
    }

    結果顯而易見,類由系統類加載器加載,並且clazz和clazz2是相同的。

    class:2133927002
    sun.misc.Launcher$AppClassLoader@18b4aac2
    class:2133927002
    sun.misc.Launcher$AppClassLoader@18b4aac2

    在改造一下

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/桌面/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz.hashCode());
    
        Object object = clazz.newInstance();
        System.out.println(object.getClass().getClassLoader());
    
        ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
        loader2.setPath("/home/fanxuan/桌面/");
        Class<?> clazz2 = loader2.loadClass("classloader.Test01");
        System.out.println("class:" + clazz2.hashCode());
    
        Object object2 = clazz2.newInstance();
        System.out.println(object2.getClass().getClassLoader());
    }

    運行結果

    class:325040804
    classloader.ClassLoaderTest@7f31245a
    class:621009875
    classloader.ClassLoaderTest@45ee12a7

    ClassLoaderTest是顯而易見,但是clazz和clazz2是不同的,這是因為類加載器的命名空間的原因。

    我們可以通過設置父類加載器來讓loader和loader2處於同一命名空間

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/桌面/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz.hashCode());
    
        Object object = clazz.newInstance();
        System.out.println(object.getClass().getClassLoader());
    
        ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
        loader2.setPath("/home/fanxuan/桌面/");
        Class<?> clazz2 = loader2.loadClass("classloader.Test01");
        System.out.println("class:" + clazz2.hashCode());
    
        Object object2 = clazz2.newInstance();
        System.out.println(object2.getClass().getClassLoader());
    }

    運行結果

    class:325040804
    classloader.ClassLoaderTest@7f31245a
    class:325040804
    classloader.ClassLoaderTest@7f31245a

    擴展:命名空間

    • 每個類加載器都有自己的命名空間,命名空間由該加載器及所有的父加載器所加載的類組成
    • 在同一命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類
    • 在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類

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

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

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

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

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

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

  • 小程序雲開發:菜鳥也能全棧做產品

    小程序雲開發:菜鳥也能全棧做產品

    我想獨立實現一個全棧產品為什麼這麼難

    日常生活中,我們會使用很多軟件產品。在使用這些產品的時候,我們看得見的東西稱為“前端界面”如一個輸入框、一個按鈕,點擊按鈕之後發生的一切看不見的東西稱為“後端服務”。與之對應的創造者分別稱為“前端程序員”、“後端程序員”,然而,一個完整產品的開發不僅僅是只有前端和後端,還有設計師,架構師,運維等。有沒有可能這些所有的事情都一個人干呢?有可能,事實上如今就有很多的“全棧工程師”,他們身兼數職,是多面手。能獨立完成一個產品的方方面面。這種人固然十分了得,他們通常具有多年的經驗,涉獵廣泛,是老手,也是高手,當有一個產品想法的時候,他們可以用自己的全面專業技能,盡情的發揮去實現自己的想法。所以,從某種意義上講“全棧也是一種自由”,你可以自由的實現你的想法,這簡直太美妙了!

    然而,很多時候當我們有一個產品想法的時候,我們往往發現,前端寫完了,後端怎麼搞?數據庫怎麼搞?域名怎麼搞?域名還要備案?應用部署怎麼搞?我的買什麼樣的服務器啊?靜態資源 CDN 怎麼搞?文件上傳服務器怎麼搞?萬一訪問用戶多了能撐住嗎?等等……問題很多,導致你的一個個想法,都只是在腦海中曇花一現,從來都無法將她們實現,或者說你激情飽滿的實現了其中自己最擅長的一部分,當碰到其他難題的時候就止步了。於是仰天長嘯:我就想獨立做一個完整的產品為什麼這麼難?年輕人,這一切都不怪你……

    破局:小程序雲開發

    為什麼使用小程序雲開發來破局?

    為啥是用“小程序雲開發”來破局?首先,我們的目的是全棧實現一個產品。全棧可以有多種技術方案,你可用任何你能會的技能來達到全棧的目的。你可以開發安卓,IOS,或者 PC 站,然而小程序是最實際的!為啥?手機上能做的事情為啥要用 PC 版?OK,既然手機版比較好,那能不能再簡單一點?能,就是小程序,不需要開發IOS,安卓兩個版本。可以快速產出,快速試錯。

    其次,前面說到了,全棧實現一個產品並不容易,對很多人來說甚至是巨難!選擇了小程序已經是比較划算的方案。而再集成雲開發,全棧立馬就有了。這就是為什麼選擇“小程序雲開發”來破局。

    小程序雲開發是什麼?

    小程序雲開發是什麼?官方文檔是這麼說的:開發者可以使用雲開發開發微信小程序、小遊戲,無需搭建服務器,即可使用雲端能力。雲開發為開發者提供完整的原生雲端支持和微信服務支持,弱化後端和運維概念,無需搭建服務器,使用平台提供的 API 進行核心業務開發,即可實現快速上線和迭代,同時這一能力,同開發者已經使用的雲服務相互兼容,並不互斥。

    看完上面的描述,也許你仍然無法非常清楚的知道什麼是“小程序雲開發”,沒關係,你只需要注意加粗的部分,大概知道它“無需搭建服務器”,從傳統觀念將,這個似乎“毀三觀”咋可能沒服務器啊?是的,可以沒有傳統意義上的服務器,這種模式是 serveless 的。

    那麼,小程序雲開發提供了哪些東西來破局呢?且看下面的表格:

    能 力 作 用 說 明
    雲函數 無需自建服務器 在雲端運行的代碼,微信私有協議天然鑒權,開發者只需編寫自身業務邏輯代碼
    數據庫 無需自建數據庫 一個既可在小程序前端操作,也能在雲函數中讀寫的 JSON 數據庫
    存儲 無需自建存儲和 CDN 在小程序前端直接上傳/下載雲端文件,在雲開發控制台可視化管理
    雲調用 原生微信服務集成 基於雲函數免鑒權使用小程序開放接口的能力,包括服務端調用、獲取開放數據等能力

    上面的表格中提到了“雲開發”中的一些能力:“雲函數”,“數據庫”,“存儲”,“雲調用”,我們可以將這些詞帶入你曾經開發過的應用,看看它們分別代表了哪些部分。對於程序員來說,如果有疑問的話,沒有什麼是一個 helloword 解決不了的。

    實戰:獨立開發一個簡易的零售小程序

    哆嗦再多,不如實戰。下面我們就來使用小程序雲開發實現一個簡單的零售小程序。

    項目構思

    既然是一個零售小程序,那麼我們可以思考一下零售小程序的大致業務流程,以及粗略的梳理一下,其功能點。現根據自己的想法,大致畫一下草圖,如果沒有靈感可以參考一下別的 APP 是如何設計的。

    我根據自己的想法設計之後是這樣的:

    功能模塊:首頁,商品列表頁,購物車,確認訂單,個人中心,個人訂單,管你模塊(商品添加,分類添加)其中商品需要上傳圖片。

    梳理完功能之後,我們對於要實現的東西已經有個初步的概念了。接下來,我們需要大概畫一下頁面設計、及功能流轉。初次設計可能沒有太多經驗,沒關係,開始做就行了,做着做着就會想法越來越多,然後優化的越來越好。。我也是經過了多番修改調整,最終找到了一些思路。我的(拙劣)設計如下,圖片如果看不清楚可複製圖片鏈接在新窗口打開查看:

    說明,以上圖片是根據成品(我真的開發了一個雲小程序並上線使用了)截圖的,而實際我再設計的時候也是經過幾番修改才最終定成這樣。

    同時,補充說明一下,這裏前端頁面使用的是 vant-weapp控件,非常好用。推薦!如果你和我一樣是一個純後端程序員,建議使用 vant-weapp 來作為 ui,非常方便。否則自己寫頁面樣式的話可能就做不出來了。全棧不是那麼好乾的啊。選擇自己能駕馭的,能實現最終功能,就是一個合格的全棧。

    創建小程序雲開發項目

    我們先下載微信小程序開發工具,下載地址,安裝好了之後,新建項目,界面如下,APPID 需要你自己去註冊一個。然後注意,選擇“小程序雲開發”,如下圖所示:

    創建好了之後,項目目錄如下,先看 1 標註的地方:

    如果你曾經有過小程序的開發經驗,那麼miniprogram文件夾下面的結構你肯定熟悉了,miniprogram下面的子目錄分別是小程序對應的組件、圖片、頁面、樣式以及app.js,app.json,sitemap.json,其中components下面的vant-weapp就是上面提到的 ui 組件。

    最後一個比較重要的文件夾就是cloudfunctions,這個目錄是用來存放“雲函數的”,雲函數就是我們的後端。每一個雲函數提供一個服務。一個個的雲函數組成了我們整體的後端服務。雲函數可以看做是 FaaS(function as a service)。途中,2 標記的位置的“雲開發”按鈕,我們點進去,就可以看到“雲開發的控制台”,如下圖所示:

    如果上圖看不清楚,可以複製鏈接到新的瀏覽器窗口查看,如圖,小程序雲開發默認的免費套餐有一定的額度可供使用。首頁便是使用統計。然後我們能看到,有“數據庫”,“存儲”,“雲函數”。

    這裏的“數據庫”其實就是類似於一個 MongoDB,你可以點進去創建一個個的 collection(即:關係型數據庫中的table);這裏的“存儲”其實就是“文件夾”,我們可以通過微信提供的 api把圖片上傳到“存儲”中;這裏的“雲函數”就是我們需要實現的後端業務邏輯,他就是一個個的函數(函數由我們自己寫好後上傳)。一般開發過程中我們在開發者工具中的cloudfunctions目錄下創建雲函數(比方說是:user-add)開發完成之後在雲函數目錄點擊右鍵——上傳即可。然後就可以在小程序的代碼中調用這個user-add雲函數。

    雲開發之——3 分鐘實現文件上傳

    注意:在開始雲開發之前,我們現在 小程序代碼的 app.js 中加入wx.cloud.init,如下:

    App({
      onLaunch: function () {
        if (!wx.cloud) {
          console.error('請使用 2.2.3 或以上的基礎庫以使用雲能力')
        } else {
          wx.cloud.init({
            // env 參數說明:
            //   env 參數決定接下來小程序發起的雲開發調用(wx.cloud.xxx)會默認請求到哪個雲環境的資源
            //   此處請填入環境 ID, 環境 ID 可打開雲控制台查看
            //   如不填則使用默認環境(第一個創建的環境)
            env: 'your-env-id',
            traceUser: true,
          })
        }
        this.globalData = {}
      }
    })

    上面的圖中,我們已經看到了“商品添加”頁面的效果,它需要我們輸入商品名稱、價格、並上傳圖片,然後保存。傳統架構中,上傳圖片需要前端頁面擺一個控件,然後後端提供一個 api用來接收前端傳來的文件,通常來說這個後端 api 接收到圖片之後,會將圖片文件保存到自己的文件服務器或者是阿里雲存儲、或者是七牛雲存儲之類的。然後返回給你一個文件鏈接地址。非常麻煩,然而,小程序雲開發上傳文件超級簡單,上代碼:

    頁面代碼:
    <van-notice-bar
      scrollable="false"
      text="發布商品"
    />
      <van-field
        value="{{ productName }}"
        required
        clearable
        label="商品名稱"
        placeholder="請輸入商品名稱"
        bind:change="inputName"
      />
        <van-field
        value="{{ productPrice }}"
        required
        clearable
        label="價格"
        icon="question-o"
         bind:click-icon="onClickPhoneIcon"
        placeholder="請輸入價格"
        error-message="{{phoneerr}}"
        border="{{ false }}"
        bind:change="inputPrice"
      />
    
    <van-action-sheet
      required
      show="{{ showSelect }}"
      actions="{{ actions }}"
      close-on-click-overlay="true"
      bind:close="toggleSelect"
      bind:select="onSelect" cancel-text="取消"
    />
      <van-field
        value="{{ productCategory }}"
        center
        readonly
        label="商品分類"
        border="{{ false }}"
        use-button-slot
      >
        <van-button slot="button" size="small" plain type="primary"  
         bind:click="toggleSelect">選擇分類</van-button>
      </van-field>
      
      <van-button class="rightside" type="default" bind:click="uploadImage" >上傳商品圖片</van-button>
      <view class="imagePreview">
        <image src="{{productImg}}" />
      </view>
     <van-submit-bar
      price="{{ totalShow }}"
      button-text="提交"
      bind:submit="onSubmit"
      tip="{{ false }}"
     >
     </van-submit-bar> 
    <van-toast id="van-toast" />
    <van-dialog id="van-dialog" />

    這裡有個控件,綁定了uploadImage方法,其代碼為:

      uploadImage:function(){
        let that = this;
        wx.chooseImage({
          count: 1,
          sizeType: ['compressed'],
          sourceType: ['album', 'camera'],
          success(res) {
            wx.showLoading({
              title: '上傳中...',
            })
            const tempFilePath = res.tempFilePaths[0]
            const name = Math.random() * 1000000;
            const cloudPath = name + tempFilePath.match(/\.[^.]+?$/)[0]
            wx.cloud.uploadFile({
              cloudPath:cloudPath,//雲存儲圖片名字
              filePath: tempFilePath,//臨時路徑
              success: res => {
                let fileID = res.fileID;
                that.setData({
                  productImg: res.fileID,
                });
                wx.showToast({
                  title: '圖片上傳成功',
                })
              },
              fail: e =>{
                wx.showToast({
                  title: '上傳失敗',
                })
              },
              complete:()=>{
                wx.hideLoading();
              }
            });
          }
        })
      }

    這裏,wx.chooseImage用於調起手機選擇圖片(相冊/相機拍照),然後wx.cloud.uploadFile用於上傳圖片到上面說到的雲開發能力之一的“存儲”中。上傳圖片成功之後返回一個文件 ID,類似:

    cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  

    這個鏈接可以直接在小程序頁面展示:

    <image src="cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  " />

    也可以通過微信 api,裝換成 http 形式的圖片鏈接。

    雲開發之——操作數據庫,1 分鐘寫完保存商品到數據庫的代碼

    上面我們實現了商品圖片上傳,但是,商品圖片並沒有保存到數據庫。正常錄入商品的時候,我們會填好商品名稱,價格等,然後上傳圖片,最終點擊“保存”按鈕,將商品保存到數據庫。傳統模式下,前端仍然是需要調用一個後端接口,通過 post 提交數據,最終由後端服務(比如 java 服務)將數據保存到數據庫。小程序雲開發使得操作數據庫十分簡單,首先我們在雲開發控制台創建“商品表”,即一個 collection,取名為:products。然後我們就可以保存數據到數據庫了,代碼如下:

    onSubmit:function(){
        // 校驗代碼,略
        let product = {};
        product.imgId = this.data.productImg;
        product.name= this.data.productName;
        product.categoryId = this.data.productCategoryId;
        product.price = this.data.productPrice;
        // 其他賦值,略
        const db = wx.cloud.database();
        db.collection('products').add({
         data: product,
         success(res) {
           wx.showToast({
             title: '保存成功',
           })
         }
       });
      }

    以上就實現了數據入庫,就這點代碼,超簡單,1 分鐘寫完,誠不欺我。其中這裏的products就是我們的“商品表”,之前說過,類似 MongoDB 數據庫,這裏操作的是db.collection,這和 MongoDB 的語法差不多。

    雲開發之——使用雲函數完成後端業務邏輯,訂單創建

    小程序雲開發提供了幾大能力:“數據庫”,“存儲”,“雲函數”,前兩項我們已經有所體會了。下面我們能創建一個雲函數來實現訂單創建。這裏說明,雲函數其實就是 一段JavaScript 代碼,上傳至雲服務器之後,最終也是運行在 nodejs 環境的,只是這一切,我們不需要關心。我們只需要關心我們這個雲函數提供的功能是什麼就可以了。

    創建雲函數很簡單,直接在開發工具中右鍵“新建Node.js 雲函數”。然後以創建訂單為例,假設我們創建一個雲函數名為c-order-add,創建好了之後,目錄是這樣:

    雲函數的主要代碼在 index.js 中,其完整代碼是這樣:

    // 雲函數入口文件
    const cloud = require('wx-server-sdk')
    cloud.init({
      env: 'release-xxx'// your-env-id
    })
    const db = cloud.database()
    
    // 雲函數入口函數
    exports.main = async (event, context) => {
      const wxContext = cloud.getWXContext();
      console.log("雲函數 c-order-add : ")  
      // 這裡是一些邏輯處理...
      
      return await db.collection('uorder').add({
        data: {
          openid: event.userInfo.openId,
          address: event.address,
          userName: event.userName,
          phone: event.phone,
          shoppingInfo: event.shoppingInfo,
          totlePrice: event.totlePrice,
          shoppingStr: event.shoppingStr,
          remark:event.remark,
          createTime: now,
          // ...
        }
      });
    }

    這個雲函數寫好之後,需要上傳到服務器,直接在雲函數目錄點擊右鍵,然後點擊“上傳並部署”即可,這就相當於部署好了後端服務。前端小程序頁面調用的寫法是這樣的:

    let orderData={};
    orderData.userName = this.data.userName;
    orderData.phone = this.data.phone;
    orderData.address = this.data.address;
    // ....
    wx.cloud.callFunction({
          // 雲函數名稱
          name: 'c-order-add',
          // 傳給雲函數的參數
          data: orderData,
          complete: res => {
            Dialog.alert({
              title: '提交成功',
              message: '您的訂單成功,即將配送,請保持手機通暢。'
            }).then(() => {
              // ....
              wx.redirectTo({
                url: '../uorder/uorder'
              });
            });
          }
    })

    這裏,向程序前端,通過wx.cloud.callFunction完成了對雲函數的調用,也可以理解為對後端服務的調用。至此我們我們介紹完了,小程序雲開發的功能。雖然,我只貼出了少量的代碼,即保存商品,和提交訂單。由於時間和篇幅有限,我不可能把整個完整的程序代碼貼出來。但是你可以參照這個用法示例,將剩下的業務邏輯補充完整,最終完成“項目構思”一節中展示的成品截圖效果。

    小程序審核的一點經驗

    我開發的小程序審核在提交審核的時候遭遇了兩次退回,第一次是因為:“小程序具備電商性質,個人小程序號不支持”。所以,我只好申請了一個企業小程序號,使用的是超市的營業執照。服務類目的選擇也被打回了一次,最後選擇了食品還提交了食品經營許可證。第二次打回是因為:“用戶體驗問題”。其實就是“授權索取”的問題,微信不讓打開首頁就“要求授權”,同時不能強制用戶接受授權,得提供拒絕授權也能使用部分功能。

    上面兩條解決之後,更新新了好幾版,都沒有出現過被拒的情況。並且,有次我是夜晚 10 左右提價的審核,結果10 點多就提示審核通過,當時沒看具體時間,就是接盆水泡了個腳的時間審核通過了。所以,我推斷小程序審核初次審核會比較嚴,之後如果改動不大應該直接機審就過了。

    總結及對比

    這裏我們可以對小程序雲開發和傳統模式做一個對比:

    對比條目 傳統模式 雲開發
    是否需要後端服務 需要 (如一個java應用部署在 Tomcat 中) 不需要 只需要“雲函數”
    是否需要域名 需要 (還得在微信後台的把域名加入安全域名) 不需要
    是否需要購買服務器 需要 (你得部署後端 Java 應用,還得安裝數據庫) 不需要
    開通雲開發之後免費套餐夠用
    不夠的話購買套餐按調用量計費
    是否需要懂運維 需要
    (你得會折騰服務器,數據庫之類的
    還得配置好相關的用戶,端口,啟動服務)
    不需要
    圖片上傳及 CDN 麻煩 簡單
    獲取微信 openID 麻煩 超級簡單,雲函數中直接獲取
    ···

    就對比這麼多吧,總之,我非常喜歡小程序雲開發,小程序真的可以讓你輕鬆干全棧。或者咱們別動不動就提“全棧”,姑且說,小程序雲開發可以讓你更簡單、更快速、更便宜的實現你的產品落地。我自己開發的雲小程序上線之後,使用了一兩個月,沒出現任何問題。我也不用操心服務器什麼的。所以,我已經給身邊很多人安利了小程序雲開發了。這裏我就不貼出我的小程序碼了,因為已經正式給我同學的超市使用了,所以不方便讓別人去產生測試數據。如果你感興趣想看的話,可以聯繫我。

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

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

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

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

  • Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

    Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

    在 Redis 出現之前,我們的緩存框架各種各樣,有了 Redis ,緩存方案基本上都統一了,關於 Redis,松哥之前有一個系列教程,尚不了解 Redis 的小夥伴可以參考這個教程:

    使用 Java 操作 Redis 的方案很多,Jedis 是目前較為流行的一種方案,除了 Jedis ,還有很多其他解決方案,如下:

    除了這些方案之外,還有一個使用也相當多的方案,就是 Spring Data Redis。

    在傳統的 SSM 中,需要開發者自己來配置 Spring Data Redis ,這個配置比較繁瑣,主要配置 3 個東西:連接池、連接器信息以及 key 和 value 的序列化方案。

    在 Spring Boot 中,默認集成的 Redis 就是 Spring Data Redis,默認底層的連接池使用了 lettuce ,開發者可以自行修改為自己的熟悉的,例如 Jedis。

    Spring Data Redis 針對 Redis 提供了非常方便的操作模板 RedisTemplate 。這是 Spring Data 擅長的事情,那麼接下來我們就來看看 Spring Boot 中 Spring Data Redis 的具體用法。

    方案一:Spring Data Redis

    創建工程

    創建工程,引入 Redis 依賴:

    創建成功后,還需要手動引入 commos-pool2 的依賴,因此最終完整的 pom.xml 依賴如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>

    這裏主要就是引入了 Spring Data Redis + 連接池。

    配置 Redis 信息

    接下來配置 Redis 的信息,信息包含兩方面,一方面是 Redis 的基本信息,另一方面則是連接池信息:

    spring.redis.database=0
    spring.redis.password=123
    spring.redis.port=6379
    spring.redis.host=192.168.66.128
    spring.redis.lettuce.pool.min-idle=5
    spring.redis.lettuce.pool.max-idle=10
    spring.redis.lettuce.pool.max-active=8
    spring.redis.lettuce.pool.max-wait=1ms
    spring.redis.lettuce.shutdown-timeout=100ms

    自動配置

    當開發者在項目中引入了 Spring Data Redis ,並且配置了 Redis 的基本信息,此時,自動化配置就會生效。

    我們從 Spring Boot 中 Redis 的自動化配置類中就可以看出端倪:

    @Configuration
    @ConditionalOnClass(RedisOperations.class)
    @EnableConfigurationProperties(RedisProperties.class)
    @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
    public class RedisAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(
                        RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
                RedisTemplate<Object, Object> template = new RedisTemplate<>();
                template.setConnectionFactory(redisConnectionFactory);
                return template;
        }
        @Bean
        @ConditionalOnMissingBean
        public StringRedisTemplate stringRedisTemplate(
                        RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
                StringRedisTemplate template = new StringRedisTemplate();
                template.setConnectionFactory(redisConnectionFactory);
                return template;
        }
    }

    這個自動化配置類很好理解:

    1. 首先標記這個是一個配置類,同時該配置在 RedisOperations 存在的情況下才會生效(即項目中引入了 Spring Data Redis)
    2. 然後導入在 application.properties 中配置的屬性
    3. 然後再導入連接池信息(如果存在的話)
    4. 最後,提供了兩個 Bean ,RedisTemplate 和 StringRedisTemplate ,其中 StringRedisTemplate 是 RedisTemplate 的子類,兩個的方法基本一致,不同之處主要體現在操作的數據類型不同,RedisTemplate 中的兩個泛型都是 Object ,意味者存儲的 key 和 value 都可以是一個對象,而 StringRedisTemplate 的 兩個泛型都是 String ,意味者 StringRedisTemplate 的 key 和 value 都只能是字符串。如果開發者沒有提供相關的 Bean ,這兩個配置就會生效,否則不會生效。

    使用

    接下來,可以直接在 Service 中注入 StringRedisTemplate 或者 RedisTemplate 來使用:

    @Service
    public class HelloService {
        @Autowired
        RedisTemplate redisTemplate;
        public void hello() {
            ValueOperations ops = redisTemplate.opsForValue();
            ops.set("k1", "v1");
            Object k1 = ops.get("k1");
            System.out.println(k1);
        }
    }

    Redis 中的數據操作,大體上來說,可以分為兩種:

    1. 針對 key 的操作,相關的方法就在 RedisTemplate 中
    2. 針對具體數據類型的操作,相關的方法需要首先獲取對應的數據類型,獲取相應數據類型的操作方法是 opsForXXX

    調用該方法就可以將數據存儲到 Redis 中去了,如下:

    k1 前面的字符是由於使用了 RedisTemplate 導致的,RedisTemplate 對 key 進行序列化之後的結果。

    RedisTemplate 中,key 默認的序列化方案是 JdkSerializationRedisSerializer 。

    而在 StringRedisTemplate 中,key 默認的序列化方案是 StringRedisSerializer ,因此,如果使用 StringRedisTemplate ,默認情況下 key 前面不會有前綴。

    不過開發者也可以自行修改 RedisTemplate 中的序列化方案,如下:

    @Service
    public class HelloService {
        @Autowired
        RedisTemplate redisTemplate;
        public void hello() {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            ValueOperations ops = redisTemplate.opsForValue();
            ops.set("k1", "v1");
            Object k1 = ops.get("k1");
            System.out.println(k1);
        }
    }

    當然也可以直接使用 StringRedisTemplate:

    @Service
    public class HelloService {
        @Autowired
        StringRedisTemplate stringRedisTemplate;
        public void hello2() {
            ValueOperations ops = stringRedisTemplate.opsForValue();
            ops.set("k2", "v2");
            Object k1 = ops.get("k2");
            System.out.println(k1);
        }
    }

    另外需要注意 ,Spring Boot 的自動化配置,只能配置單機的 Redis ,如果是 Redis 集群,則所有的東西都需要自己手動配置,關於如何操作 Redis 集群,松哥以後再來和大家分享。

    方案二:Spring Cache

    通過 Spring Cache 的形式來操作 Redis,Spring Cache 統一了緩存江湖的門面,這種方案,松哥之前有過一篇專門的文章介紹,小夥伴可以移步這裏:。

    方案三:回歸原始時代

    第三種方案,就是直接使用 Jedis 或者 其他的客戶端工具來操作 Redis ,這種方案在 Spring Boot 中也是支持的,雖然操作麻煩,但是支持,這種操作松哥之前也有介紹的文章,因此這裏就不再贅述了,可以參考 。

    總結

    Spring Boot 中,Redis 的操作,這裏松哥給大家總結了三種方案,實際上前兩個使用廣泛一些,直接使用 Jedis 還是比較少,基本上 Spring Boot 中沒見過有人直接這麼搞。

    好了,本文就說到這裏,有問題歡迎留言討論。

    相關案例已經上傳到 GitHub,歡迎小夥伴們們下載:

    掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 免費 Java 學習乾貨

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

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

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

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

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

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