月份: 2020 年 5 月

  • 2016上海國際汽車新能源及智慧技術展覽會

    創新技術 智能演繹 展會日期:2016年6月28日-30日 展會地點:上海新國際博覽中心   觀眾數量:30,000人次(預計) 展覽週期:二年一屆,2016年首屆 同期展會:上海國際汽車零部件及相關服務展覽會   主辦單位: 中國國際貿易促進委員會上海市分會 中國國際貿易促進委員會汽車行業分會 中國汽車工程學會 上海市國際展覽有限公司   展位價格: 室內光地:650元/平方米(36平米起租) 標準展位:900元/平方米(9平米起租)   參展範圍: -節能汽車、純電動車,混合動力車,燃料電池車,輕型電動車,天然氣(液化氣)車,醇類及其他代用燃料車和節能汽車。 -電池、電機、電控等核心零部件和先進技術應用;輕量化材料、整車優化設計及混合動力等節能技術產品; 自動駕駛,汽車共用,模組化(集成化)構造,汽車車載資訊系統及智慧化設備;汽車相關後市場服務及產品。 -充電樁、充電機、配電櫃、充換電池及電池管理系統、停車場充電設施、智慧監控、充電站供電解決方案、充電站-智慧電網解決方案等。 -檢測,維修,監控,實驗,安全防護裝備及媒體等.   展會諮詢: 北京盛大超越國際展覽有限公司 連絡人:岳巍 先生 電話(微信):135 5286 5285 郵箱:     附:其他推薦展會   2016中國國際汽車新能源及技術應用展覽會 節能與新能源汽車產業發展規劃成果展覽會   展會時間:2016年10月13-16日 展會地點:北京•國家會議中心 展覽規模:30,000平方米(2015年) 觀眾數量:60,000人次(2015年) 展覽週期:每年一屆,2013年首屆 詳情請點擊  

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

    【其他文章推薦】

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

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

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

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

  • 2016中國國際汽車新能源及技術應用展覽會|節能與新能源汽車產業發展規劃成果展覽會

    展會時間:2016年10月13-16日

    展會地點:北京-國家會議中心

    展會定位:
    唯一的國家級新能源汽車展覽展示平臺;中國參展企業最多、展覽面積最大、展品最為豐富的節能與新能源汽車展。

    展覽規模:30,000平方米(2015年)

    觀眾數量:60,000人次(2015年)

    展覽週期:每年一屆,2013年首屆

    支援單位:中華人民共和國工業和資訊化部

    批准單位:中華人民共和國科學技術部、中國國際貿易促進委員會

    主辦單位:中國國際貿易促進委員會機械行業分會、中國電工技術學會、汽車知識雜誌社、寰球時代汽車投資管理(北京)有限公司

    合作單位:北京盛大超越國際展覽有限公司

    展位價格:
    室內光地:1280元/平方米
    標準展位:11800元/個(3m*3m)

    參展範圍:

    整車類

    純電動車,混合動力車,燃料電池車,輕型電動車,天然氣(液化氣)車,醇類及其他代用燃料車和節能汽車。

    零部件類

    電池、電機、電控等核心零部件和先進技術應用;先進內燃機、高效變速器、輕量化材料、整車優化設計及混合動力等節能技術產品。

    充電設施

    充電樁、充電機、配電櫃、充換電池及電池管理系統、停車場充電設施、智慧監控、充電站供電解決方案、充電站-智慧電網解決方案等。

    新能源汽車發展成果展示;檢測,維修,監控,實驗,安全防護裝備及媒體等.

    連絡人:岳巍 先生  
    手機(微信):135 5286 5285
    郵箱:sales2@s-expo.com  

    附:其他推薦展會

    2016上海國際汽車新能源及智慧技術展覽會
    上海車展新能源姐妹展
    展會日期:2016年6月28日-30日
    展會地點:上海新國際博覽中心
    詳情請點擊

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

    【其他文章推薦】

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

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

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

    南投搬家前需注意的眉眉角角,別等搬了再說!

  • 重慶新建住宅將100%配備充電基礎設施

    1月11日,重慶市政府辦公廳公佈《重慶市加快電動汽車充電基礎設施建設實施方案》。方案要求,新建住宅配建的停車庫必須100%建設電動汽車充電基礎設施或預留建設安裝條件。到2020年,主城區將累計建成不少於30座公共充換電站。

    在主城區,凡新建的交通樞紐、超市賣場、商務樓宇,黨政機關、企事業單位辦公場所、學校、醫院、文化體育場館以及獨立用地的公共停車場、停車換乘(P+R)停車場等,按照不低於總停車位數量10%的比例,建設電動汽車充電基礎設施。

    對已建成的住宅社區及上述場所,要通過改造、加裝等方式,到2020年,達到不低於10%的比例,提供充電基礎設施。

    其他區縣(自治縣)城區要因地制宜,同樣按照不低於總停車位數量10%的比例,建設電動汽車充電基礎設施或預留建設安裝條件(包括預埋電力管線和預留電力容量)。

    此外,還將建設電動汽車公共充換電站。到2020年,主城區原則上按服務半徑每1公里提供1座公共充換電站,累計建成不少於30座公共充換電站;其他每個區縣(自治縣)城區至少建成1座公共充換電站;每個重點旅遊景區至少建成1-2座公共充換電站;凡具備安全條件的加油站、加氣站、高速公路服務區等實現充換電設施全覆蓋。

    不同場所執行不同電價 住宅區按合表用戶電價收費

    對向電力企業直接報裝接電的經營性集中式電動汽車充換電設施用電,執行大工業用電價格,2020年前暫免收取基本電費;其他充電基礎設施用電按其所在場所執行分類目錄電價。其中,向電力企業報裝的居民住宅區充電基礎設施用電,執行居民用電價格中的合表用戶電價;對居民自用充電基礎設施的用電與家庭用電分表計量,執行居民生活用電價格,且不納入居民生活用電階梯電價計算範圍;黨政機關、企事業單位和社會公共停車場等場所的充電基礎設施用電,執行一般工商業及其他類用電價格。電動汽車充電基礎設施用電按重慶市峰穀分時電價相關政策執行。

    電動汽車充電服務企業向使用者收取的充電服務費執行政府指導價,針對不同類別充電基礎設施,以電價為計費依據,服務費暫按每千瓦時不超過執行電價的50%收取,試行一年;試行期滿後,結合市場發展情況,逐步放開充電服務費,由市場競爭形成價格。

    此外,在安裝充電設施時,也將簡化審批手續。個人在自有停車庫、停車位元,各居住區、單位在已有停車泊位安裝電動汽車充電設施的,無需辦理建設用地規劃許可證、建設工程規劃許可證和施工許可證。

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

    【其他文章推薦】

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

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

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

  • macOS 使用Miniconda配置本地數據運算環境

    macOS 使用Miniconda配置本地數據運算環境

    目前,做數據分析工作,基本人手Numpy,pandas,scikit-learn。而這些計算程序包都是基於python平台的,所以搞數據的都得先裝個python環境。。。(當然,你用R或Julia請忽略本文)

    在macOS上,默認安裝有python 2.7,鑒於python2即將停止更新,如果沒有大量的python2代碼需要維護,就直接安裝python3吧。

    版本選擇

    做數據運算,流行的方式是直接下載Anaconda安裝包,大概500M左右,各種依賴包(綁定了四五百個科學計算程序包),開發工具(jupyter notebook,spyder)一股腦兒都包含了,按照步驟安裝完成,開箱即用,不過裝完後會佔用幾個G的硬盤空間。

    我這邊由於硬盤空間有限,採用Miniconda這個發行版本,最新的基於python3.7版本的不到50M。而Miniconda一樣使用conda作為包管理器,可以輕鬆的安裝自己需要的包,例如Numpy,pandas, matplotlib等等。

    當然,也可以從安裝包或homebrew開始裝,然後再使用pip來安裝相關的程序包。總體上來說,python自身的版本和執行路徑是相當混亂的,可參考下圖。

    安裝步驟

    • 下載
      先從官網下載適合自己操作系統的版本,Miniconda
      支持Windows/Linux/macOS這三種主流操作系統。如果遇到官網下載慢的問題,可以考慮國內的鏡像站點,如。

    下載完成后,可以先核對下hash值,與官網的值(5cf91dde8f6024061c8b9239a1b4c34380238297adbdb9ef2061eb9d1a7f69bc)是否一致保證安裝文件未被篡改。

    $ shasum -a 256 Miniconda3-latest-MacOSX-x86_64.sh 
    5cf91dde8f6024061c8b9239a1b4c34380238297adbdb9ef2061eb9d1a7f69bc  Miniconda3-latest-MacOSX-x86_64.sh
    • 執行安裝
    $ bash ./Miniconda3-latest-MacOSX-x86_64.sh 
    
    Welcome to Miniconda3 4.7.12
    
    In order to continue the installation process, please review the license
    agreement.
    Please, press ENTER to continue
    
    
    Do you accept the license terms? [yes|no]
    [no] >>> yes
    
    Miniconda3 will now be installed into this location:
    /Users/shenfeng/miniconda3
    
      - Press ENTER to confirm the location
      - Press CTRL-C to abort the installation
      - Or specify a different location below
    
    [/Users/shenfeng/miniconda3] >>> 
    
    >>> 

    按照提示,敲擊回車。中間需要同意使用條款,需要輸入yes,按照路徑點回車默認即可。

    Do you wish the installer to initialize Miniconda3
    by running conda init? [yes|no]
    [yes] >>> yes
    
    ==> For changes to take effect, close and re-open your current shell. <==
    
    If you'd prefer that conda's base environment not be activated on startup, 
       set the auto_activate_base parameter to false: 
    
    conda config --set auto_activate_base false
    
    Thank you for installing Miniconda3!

    最後的提示是,可以用conda config --set auto_activate_base false命令取消python3環境在啟動時自行加載。

    • 重新開一個新的終端
      可以發現,python3的env已經生效了。
    (base) my:~ shenfeng$ python
    Python 3.7.4 (default, Aug 13 2019, 15:17:50) 
    [Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 
    • 查看env配置
    $ conda env list
    # conda environments:
    #
    base                  *  /Users/shenfeng/miniconda3

    使用conda deactivate可以python3的執行環境,使用conda activate base可以激活默認的python3環境。

    • 添加國內鏡像源
      由於conda的包服務器都在海外,直接連接安裝可能出現連接超時無法完成的時候,所以可以通過修改用戶目錄下的.condarc 文件。
    channels:
      - defaults
    show_channel_urls: true
    default_channels:
      - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
      - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
      - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
    custom_channels:
      conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
      msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
      bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
      menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
      pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
      simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
    • 使用conda安裝相應的程序包
      先使用conda list檢查已經安裝的包,使用conda install需要的程序包
    $ $ conda list numpy
    # packages in environment at /Users/shenfeng/miniconda3:
    #
    # Name                    Version                   Build  Channel
    
    $ conda install numpy
    
    $ conda list numpy
    # packages in environment at /Users/shenfeng/miniconda3:
    #
    # Name                    Version                   Build  Channel
    numpy                     1.17.3           py37h4174a10_0    defaults
    numpy-base                1.17.3           py37h6575580_0    defaults

    相同的方式,我們可以安裝scipy,pandas等包,不再贅述。

    交互式工具安裝

    大家耳熟能詳的交互式工具肯定就是Jupyter notebook,但我在本機同樣由於磁盤空間問題只安裝ipython。實際上,Jupyter是基於ipython notebook的瀏覽器版本。

    • 安裝
    $ conda install ipython
    • 執行ipython交互
    $ ipython
    Python 3.7.4 (default, Aug 13 2019, 15:17:50) 
    Type 'copyright', 'credits' or 'license' for more information
    IPython 7.9.0 -- An enhanced Interactive Python. Type '?' for help.
    
    In [1]: import numpy as np                                               
    In [2]: dataset= [2,6,8,12,18,24,28,32]                                   
    In [3]: sd= np.std(dataset,ddof=1)                                       
    In [4]: print(sd)                                                        
    10.977249200050075

    樣例數據處理

    先從網上下載一個樣例數據,為excel文件,另存為成csv進行處理。

    以下結合上周文章中的,計算這組數據的概括性度量。

    • 讀取數據
    import numpy as np
    from scipy import stats
    
    dataset = np.genfromtxt('/Users/shenfeng/Downloads/test1.csv',delimiter=',', skip_header=1)
    print('Shape of numpy array: ', dataset.shape)
    Shape of numpy array:  (699,)
    

    集中趨勢的度量

    • 眾數
    mode = stats.mode(dataset)                                
    print('該組數據的眾數為: ', mode)         
    該組數據的眾數為:  ModeResult(mode=array([1.]), count=array([145]))
    # 結果說明眾數為1,出現了145次
    • 中位數
    print('該組數據的中位數為: ', np.median(dataset))
    該組數據的中位數為:  4.0
    • 四分位數
    # 不需要提前排序
    print("1/4分位數: ", np.percentile(dataset, 25, interpolation='linear')) 
    1/4分位數:  2.0
    
    print("1/2分位數: ", np.percentile(dataset, 50, interpolation='linear')) 
    1/2分位數:  4.0
    
    print("3/4分位數: ", np.percentile(dataset, 75, interpolation='linear')) 
    3/4分位數:  6.0
    • 平均數
    print('該組數據的平均數為: ', np.mean(dataset))
    該組數據的平均數為:  4.417739628040057

    離散程度的度量

    • 標準差
    print('該組數據的總體標準差為: ', np.std(dataset,ddof=0))
    該組數據的總體標準差為:  2.8137258170785375
    • 標準分數
    # 變量值與其平均數的離差除以標準差后的稱為標準分數(standard score)
    print('該組數據的標準分數為: ', stats.zscore(dataset))
    該組數據的標準分數為:  [ 0.20693572  0.20693572 -0.50386559  0.56233637 -0.14846494  1.27313768
     -1.2146669  -0.85926625 -0.85926625 -0.14846494 -1.2146669  -0.85926625 ...省略 ]
    • 離散係數
    # 離散係數是測度數據離散程度的統計量,主要用於比較不同樣本數據的離散程度。
    print('該組數據的離散係數為: ', stats.variation(dataset))
    該組數據的離散係數為:  0.6369152675317026

    偏態與峰態的度量

    • 數據分布圖
    import matplotlib.pyplot as plt 
    plt.style.use('ggplot') 
    plt.hist(dataset, bins=30) 

    獲得以下分布圖

    • 偏態
    print('該組數據的偏態係數為: ', stats.skew(dataset))
    該組數據的偏態係數為:  0.5915855449527385
    # 偏態係數在0.5~1或-1~-0.5之間,則認為是中等偏態分佈
    • 峰態係數
    print('該組數據的峰態係數為: ', stats.kurtosis(dataset))
    該組數據的峰態係數為:  -0.6278342838815454
    # 當K<0時為扁平分佈,數據的分佈更分散

    總結

    本文使用Miniconda發行版配置本地數據運算環境,並對樣例做數據的概括性度量。

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

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

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

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

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

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

  • js數組方法大全(下)

    js數組方法大全(下)

    # js數組方法大全(下)

    記錄一下整理的js數組方法,免得每次要找方法都找不到。圖片有點多,注意流量,嘻嘻!

    本期分享

    • forEach()
    • map()
    • filer()
    • every()
    • some()
    • reduce()
    • reduceRight()
    • indexOf()
    • lastIndex()

    上期分享

    • join()
    • reverse()
    • sort()
    • concat()
    • slice()
    • splice()
    • push()
    • pop()
    • unshift()
    • shift()
    • toString()
    • toLocaleString()

    forEach() —>遍歷

    • 使用熱度:經常用
    • 是否改變原始數組:否
    • 返回:無
    • 參數:
    參數位置 參數類型 是否必選 說明
    1 function 三個參數分別是:數組元素、元素的索引、數組本身
    • 說明:該方法無法提前終止運行,如果要提前終止運行,只能使用try塊中,然後拋出一個異常。
    • 小技巧:如果數組是個數組對象形式可以直接操作數組元素改變原始數組本身,因為對象是個引用數據類型嘛!
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    var sum =0;
    data.forEach(value=>{
      sum+=value;
    })
    log(sum);
    
    data.forEach((v,i,a)=>{
      a[i]=v+1;
    })
    log(data);
    
    var data_post=[{a:1},{a:2}]
    data_post.forEach(value=>{
      value.a++;
    })
    log(data_post)

    map() —>映射

    • 使用熱度:經常用
    • 是否改變原始數組:否
    • 返回:返回一個新函數
    • 參數:
    參數位置 參數類型 是否必選 說明
    1 function 三個參數分別是:數組元素、元素的索引、數組本身
    • 說明:傳遞給map函數應該有返回值,返回值是新數組的元素。
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    var b= data.map(x=>{
      return x*x;
    })
    log(b)

    filter() —>過濾

    • 使用熱度:常用
    • 是否改變原始數組:否
    • 返回:返回過濾后的數組
    • 參數:
    參數位置 參數類型 是否必選 說明
    1 function 三個參數分別是:數組元素、元素的索引、數組本身
    • 說明:如果返回值是true或者可以轉化為true的值,那麼這個值就是新數組的元素。
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    var b= data.filter(x=>{
      return x<3;
    })
    log(b)

    every() —>檢測

    • 使用熱度:不常用
    • 是否改變原始數組:否
    • 返回:true或者false
    • 參數:
    參數位置 參數類型 是否必選 說明
    1 function 三個參數分別是:數組元素、元素的索引、數組本身
    • 說明:當且僅當針對數組中的所有元素調用綁定函數都返回true時,它才返回true。
    • 注意:一旦every或者some已經確定了改返回什麼值得時候就不會遍曆數組了。根據數學上的慣例,在空數組調用時,every返回true,some返回false。
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    var b= data.every(x=>{
      return x<10;
    })
    log(b)
    
    var c= data.every(x=>{
      return x%2===0;
    })
    log(c)

    some() —>檢測

    • 使用熱度:不常用
    • 是否改變原始數組:否
    • 返回:true或者false
    • 參數:
    參數位置 參數類型 是否必選 說明
    1 function 三個參數分別是:數組元素、元素的索引、數組本身
    • 說明:當數組中至少有一個元素調用綁定函數返回true時,它就返回true。
    • 注意:一旦every或者some已經確定了改返回什麼值得時候就不會遍曆數組了。根據數學上的慣例,在空數組調用時,every返回true,some返回false。
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    var b= data.some(x=>{
      return x>10;
    })
    log(b)
    
    var c= data.some(x=>{
      return x%2===0;
    })
    log(c)

    reduce() —>簡化

    • 使用熱度:不常用
    • 是否改變原始數組:否
    • 返回:返回一個值
    • 參數:
    參數位置 參數類型 是否必選 作用
    1 function 四個參數分別是:初始化值/數組元素、數組元素、元素的索引、數組本身
    2 number 供計算的初始化值
    • 說明:第一個參數是到目前為止的簡化操作累計的結果
    • 注意:如果沒有初始化值,第一次調用函數的第一個參數就是第一個數組元素,第二個參數則是第二個數組元素。如果有初始化值,第一次調用函數的第一個參數就是初始化值,二個參數則是第一個數組元素。
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    var b= data.reduce((x,y)=>{
      return x+y;
    },0)
    log(b)
    
    var c= data.reduce((x,y)=>{
      return x*y;
    },1)
    log(c)
    
    var d= data.reduce((x,y)=>{
      return x>y?x:y;
    },1)
    log(d)

    reduceRight() —>簡化

    • 使用熱度:不常用
    • 是否改變原始數組:否
    • 返回:返回一個值
    • 參數:
    參數位置 參數類型 是否必選 作用
    1 function 四個參數分別是:初始化值/數組元素、數組元素、元素的索引、數組本身
    2 number 供計算的初始化值
    • 說明:第一個參數是到目前為止的簡化操作累計的結果。不同於reduce這個僅僅是從右到左計算。
    • 注意:如果沒有初始化值,第一次調用函數的第一個參數就是第一個數組元素,第二個參數則是第二個數組元素。如果有初始化值,第一次調用函數的第一個參數就是初始化值,二個參數則是第一個數組元素。
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    data.reduceRight((x,y)=>{
      log(y)
      return x+y;
    },0)

    indexOf() —>搜索

    • 使用熱度:經常用
    • 是否改變原始數組:否
    • 返回:返回數組索引或者-1
    • 參數:
    參數位置 參數類型 是否必選 作用
    1 * 要搜索的數組元素
    2 number 從數組哪個索引開始搜索
    • 說明:如果能搜索到結果將返回第一個索引,如果搜索不到就返回-1
    • 注意:如果第二個參數是負數,指的是從數組元素末尾的偏移量位置開始向後搜索,而不是到偏移量位置就截止搜索了,這裡是很容易和splice弄混的。
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,4,5];
    var b=data.indexOf(1,-5);
    log(b);

    lastIndexOf() —>搜索

    • 使用熱度:經常用
    • 是否改變原始數組:否
    • 返回:返回數組索引或者-1
    • 參數:
    參數位置 參數類型 是否必選 作用
    1 * 要搜索的數組元素
    2 number 從數組哪個索引開始搜索
    • 說明:和indexOf不同的是lashIndexOf是反向搜索
    • 注意:因為是反向搜索,當第二個參數是負數的時候,就是指從末尾偏移量的位置向前搜索
    • 實例如下:
    var log=console.log;
    var data=[1,2,3,2,5];
    var b=data.lastIndexOf(2,-3)
    log(b)
    
    var c=data.lastIndexOf(1,-5)
    log(c)

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

  • Ansible之playbook拓展

      一、handlers和notify結合使用觸發條件

      handlers同tasks是屬同級,相當於一個特殊任務列表,這些任務同前文說的tasks里的任務沒有本質的不同,用於當關注的資源發生變化時,才會採取一定的操作。notify此action可用於在每一個play的最後被觸發,這樣可避免多次有改變發生時都執行指定的操作,僅在所有的變化發生完成后一次性地執行指定操作,在notify中列出的操作稱為handler,換句話說當所關注的資源發生變化時notify將調用handlers中定義的操作。其中notify所在任務就是被監控的任務資源變化的任務,notify可以調用多個handlers定義的操作,一個handlers里可以定義很多任務。

    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: create apache group
          group: name=apache gid=80 system=yes
        - name: create apache user
          user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
        - name: install httpd
          yum: name=httpd
        - name: copy config file
          copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
          notify: restart httpd service
    
        - name: start httpd service
          service: name=httpd state=started enabled=yes
    
      handlers:
        - name: restart httpd service
          service: name=httpd state=restarted   
    

      說明:notify后指定的名稱必須要和handlers里的任務名稱相同,如不同handlers所定義的任務將不會執行,相當於沒有notify調用handlers里的任務。

      在某些情況下,我們可能同時需要調用多個handlers,或者需要使用handlers其他handlers,ansible可以很簡單的實現這些功能,如下所示

      1)調用多個handlers

    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: create apache group
          group: name=apache gid=80 system=yes
        - name: create apache user
          user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
        - name: install httpd
          yum: name=httpd
        - name: copy config file
          copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
          notify: 
            - restart httpd service
            - check httpd process
    
        - name: start httpd service
          service: name=httpd state=started enabled=yes
    
      handlers:
        - name: restart httpd service
          service: name=httpd state=restarted
        - name: check httpd process                                                                                      
          shell: /usr/bin/killall -0 httpd &> /tmp/httpd.log
    

      說明:調用多個handlers我們需要在notify中寫成列表的形式,同樣我們被觸發的任務名稱需要同handlers里的被調用的任務名稱完全相同

      2)handlers調用handlers

    ---
    - hosts: websers
      remote_user: root
    
      tasks:
        - name: create apache group
          group: name=apache gid=80 system=yes
        - name: create apache user
          user: name=apache uid=80 group=apache system=yes shell=/sbin/nologin home=/var/www/html 
        - name: install httpd
          yum: name=httpd
        - name: copy config file
          copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
          notify: restart httpd service
    
        - name: start httpd service
          service: name=httpd state=started enabled=yes
    
      handlers:
        - name: restart httpd service
          service: name=httpd state=restarted
          notify: check httpd process                                                                                    
        - name: check httpd process
          shell: /usr/bin/killall -0 httpd &> /tmp/httpd.log
    

      說明:handlers調用handlers,則直接在handlers中使用notify選項就可以。

    在使用handlers我們需要注意一下幾點:

      1)handlers只有在其所在任務被執行時才會被運行,handlers定義的任務它不會像task任務那樣,自動會從上至下依次執行,它只會被notify所在的任務發生狀態改變時才會觸發handlers 的任務執行,如果一個任務中定義了notify調用handlers,但由於條件的判斷等原因,該任務尚未執行,那麼notify調用的handlers同樣也不會執行。

      2)handlers只會在play的末尾運行一次;如果想要在一個playbook的中間運行handlers,則需要使用meta模塊來實現,如:-mate: flush_handlers

      二、playbook中變量的使用

    ansible中變量的命名規範同其他語言或系統中變量命名規則非常類似。變量名以英文大小寫字母開頭,中間可以包含下劃線和数字,ansible變量的來源有很多,具體有以下幾點:

      1)ansible setup模塊,這個模塊可以從遠程主機上獲取很多遠程主機的基本信息,它所返回的所有變量都可以直接調用,有關setup說明請參考本人博客

      2)在/etc/ansible/hosts中定義,此文件是ansible執行名時默認加載的主機清單文件,在裏面除了可定義我們要管理的主機外,我們還可以定義針對單個主機定義單獨的變量,我們把針對單獨某一台主機定義的變量叫做普通變量(也可叫做主機變量);還有一種變量它不是針對單獨一個主機,它針對某一個組裡的所有主機,我們把這種變量叫做公共組變量。主機清單中定義的變量優先級是普通變量高於公共變量。

        2.1)主機變量,可以在主機清單中定義主機時為其添加主機變量以便於在playbook中使用,如下所示

    [websers]
    192.168.0.128 http_port=80 maxRequestsPerChild=808
    192.168.0.218 http_port=81 maxRequestsPerChild=909
    

        2.2)主機組變量,組變量是指定賦予給指定組內所有主機上的在playbook中可使用的變量,如下所示

    [websers]
    192.168.0.128 http_port=80 
    192.168.0.218 http_port=81 
    [websers:vars]
    maxRequestsPerChild=909

      3)通過命令行指定變量(-e指定變量賦值,可以說多個但需要用引號引起或者一個變量用一個-e指定賦值),這種在命令行指定的優先級最高。如下所示

    ansible-playbook -e 'package_name1=httpd package_name2=nginx' test_vars.yml

      4)在playbook中定義變量,最常見的定義變量的方法是使用vars代碼塊,如下所示

    ---
    - hosts: websers
      remote_user: root
      vars:
        - abc: xxx 
        - bcd: aaa  
    

      5)在獨立的變量yml文件中定義,在playbook中使用vars_files代碼塊引用其變量文件,如下所示

    [root@test ~]#cat vars.yml 
    ---
    package_name1: vsftpd
    package_name2: nginx
    [root@test ~]#cat test_vars.yml 
    ---
    - hosts: websers
      remote_user: root
      vars_files:
        - vars.yml
      tasks:
        - name: install package1
          yum: name={{ package_name1 }}
        - name: install package2
          yum: name={{ package_name2 }}
    [root@test ~]#

      6)在role中定義,這個後續說到角色在做解釋

      變量的調用方式:第一種在playbook中使用變量需要用“{{}}”將變量括起來,表示括號里的內容是一個變量,有時用“{{  variable_name }}”才生效;第二種是ansible-playbook -e 選項指定其變量,ansible-playbook -e “hosts=www user=xxxx” test.yml

      在主機清單中定義變量的方法雖然簡單直觀,但是當所需要定義的變量有很多時,並且被多台主機使用時,這種方法顯得非常麻煩,事實上ansible的官方手冊中也不建議我們把變量直接定義到hosts文件中;在執行ansible命令時,ansible會默認會從/etc/ansible/host_vars/和/etc/ansible/group_vars/兩個目錄下讀取變量定義文件,如果/etc/ansible/下沒有以上這兩個目錄,我們可以手動創建,並且可以在這兩個目錄下創建與hosts文件中的主機名或主機組同名的文件來定義變量。比如我們要給192.168.0.218 這個主機定義個變量文件,我們可以在/etc/ansible/host_vars/目錄下創建一個192.168.0.218的空白文件,然後在文件中以ymal語法來定義所需變量即可。如下所示

    [root@test ~]#tail -6 /etc/ansible/hosts 
    ## db-[99:101]-node.example.com
    [websers]
    192.168.0.128 
    192.168.0.218 
    [appsers]
    192.168.0.217
    [root@test ~]#cat /etc/ansible/host_vars/192.168.0.218 
    ---
    file1: abc
    file2: bcd
    [root@test ~]#cat test.yml 
    ---
    - hosts: 192.168.0.218
      remote_user: root
      
      tasks:
        - name: touch file1
          file: name={{ file1 }} state=touch
        - name: toch file2
          file: name={{ file2 }} state=touch
    [root@test ~]#ansible-playbook test.yml 
    
    PLAY [192.168.0.218] ************************************************************************************************
    
    TASK [Gathering Facts] **********************************************************************************************
    ok: [192.168.0.218]
    
    TASK [touch file1] **************************************************************************************************
    changed: [192.168.0.218]
    
    TASK [toch file2] ***************************************************************************************************
    changed: [192.168.0.218]
    
    PLAY RECAP **********************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#ansible 192.168.0.218 -m shell -a 'ls -l /root'
    192.168.0.218 | SUCCESS | rc=0 >>
    總用量 12
    -rw-r--r--. 1 root   root    0 11月 17 16:49 abc
    -rw-r--r--. 1 root   root    0 11月 17 16:49 bcd
    drwxr-xr-x. 2 qiuhom root 4096 11月 11 19:18 scripts
    drwxr-xr-x. 3 qiuhom root 4096 11月 11 19:28 test
    -rw-r--r--. 1 root   root   57 11月 13 19:15 test_cron_file
    
    [root@test ~]#
    

      說明:可看到我們定義在/etc/ansible/host_vars/下的主機變量文件中的變量生效了。

    同理,我們要想針對某個組的主機定義一些變量,我們只需要在/etc/ansible/group_vars/目錄下創建與主機清單中的主機組同名的文件即可。

      三、使用高階變量

      對於普通變量,例如由ansible命令行設定的,hosts文件中定義的以及playbook中定義的和變量文件中定義的,這些變量都被稱為普通變量或者叫簡單變量,我們可以在playbook中直接用雙大括號加變量名來讀取變量內容;除此以外ansible還有數組變量或者叫做列表變量,如下所示:

    [root@test ~]#cat vars.yml 
    ---
    packages_list:
      - vsftpd
      - nginx
    [root@test ~]#
    

      列表定義完成后我們要使用其中的變量可以列表名加下標的方式去訪問,有點類似shell腳本里的數組的使用,如下所示

    [root@test ~]#cat test.yml 
    ---
    - hosts: 192.168.0.218
      remote_user: root
      
      vars_files:
        - vars.yml
      tasks:
        - name: touch file
          file: name={{ packages_list[0] }} state=touch
        - name: mkdir dir
          file: name={{ packages_list[1] }} state=directory
    [root@test ~]#
    

      說明:我們要使用列表中的第一個元素變量,我們可以寫成vars_list[0],使用第二個變量則下標就是1,依此類推

    [root@test ~]#ansible *218 -m shell -a 'ls -l /root'
    192.168.0.218 | SUCCESS | rc=0 >>
    總用量 12
    -rw-r--r--. 1 root   root    0 11月 17 16:49 abc
    -rw-r--r--. 1 root   root    0 11月 17 16:49 bcd
    drwxr-xr-x. 2 qiuhom root 4096 11月 11 19:18 scripts
    drwxr-xr-x. 3 qiuhom root 4096 11月 11 19:28 test
    -rw-r--r--. 1 root   root   57 11月 13 19:15 test_cron_file
    
    [root@test ~]#ansible-playbook test.yml 
    
    PLAY [192.168.0.218] ************************************************************************************************
    
    TASK [Gathering Facts] **********************************************************************************************
    ok: [192.168.0.218]
    
    TASK [touch file] ***************************************************************************************************
    changed: [192.168.0.218]
    
    TASK [mkdir dir] ****************************************************************************************************
    changed: [192.168.0.218]
    
    PLAY RECAP **********************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#ansible *218 -m shell -a 'ls -l /root'
    192.168.0.218 | SUCCESS | rc=0 >>
    總用量 16
    -rw-r--r--. 1 root   root    0 11月 17 16:49 abc
    -rw-r--r--. 1 root   root    0 11月 17 16:49 bcd
    drwxr-xr-x. 2 root   root 4096 11月 17 17:23 nginx
    drwxr-xr-x. 2 qiuhom root 4096 11月 11 19:18 scripts
    drwxr-xr-x. 3 qiuhom root 4096 11月 11 19:28 test
    -rw-r--r--. 1 root   root   57 11月 13 19:15 test_cron_file
    -rw-r--r--. 1 root   root    0 11月 17 17:23 vsftpd
    
    [root@test ~]#
    

      說明:可看到我們創建的文件和目錄在目標主機已經生成

    上面的用法是典型的python列表的用法,在python中讀取列表中的元素就是用下標的表示來讀取相應的元素的值。接下我們將介紹另外一種更為複雜的變量,它類似python中的字典概念,但比字典的維度要高,更像是二維字典。ansible內置變量ansible_eth0就是這樣一種,它用來保存遠端主機上面eth0接口的信息,包括ip地址和子網掩碼等。如下所示

    [root@test ~]#cat test.yml     
    ---
    - hosts: 192.168.0.218
      remote_user: root
      
      tasks:
        - debug: var=ansible_eth0 
    [root@test ~]#ansible-playbook test.yml 
    
    PLAY [192.168.0.218] ************************************************************************************************
    
    TASK [Gathering Facts] **********************************************************************************************
    ok: [192.168.0.218]
    
    TASK [debug] ********************************************************************************************************
    ok: [192.168.0.218] => {
        "ansible_eth0": {
            "active": true, 
            "device": "eth0", 
            "features": {
                "fcoe_mtu": "off [fixed]", 
                "generic_receive_offload": "on", 
                "generic_segmentation_offload": "on", 
                "highdma": "off [fixed]", 
                "large_receive_offload": "off [fixed]", 
                "loopback": "off [fixed]", 
                "netns_local": "off [fixed]", 
                "ntuple_filters": "off [fixed]", 
                "receive_hashing": "off [fixed]", 
                "rx_checksumming": "on", 
                "rx_vlan_filter": "on [fixed]", 
                "rx_vlan_offload": "on [fixed]", 
                "scatter_gather": "on", 
                "tcp_segmentation_offload": "on", 
                "tx_checksum_fcoe_crc": "off [fixed]", 
                "tx_checksum_ip_generic": "on", 
                "tx_checksum_ipv4": "off", 
                "tx_checksum_ipv6": "off", 
                "tx_checksum_sctp": "off [fixed]", 
                "tx_checksum_unneeded": "off", 
                "tx_checksumming": "on", 
                "tx_fcoe_segmentation": "off [fixed]", 
                "tx_gre_segmentation": "off [fixed]", 
                "tx_gso_robust": "off [fixed]", 
                "tx_lockless": "off [fixed]", 
                "tx_scatter_gather": "on", 
                "tx_scatter_gather_fraglist": "off [fixed]", 
                "tx_tcp6_segmentation": "off", 
                "tx_tcp_ecn_segmentation": "off", 
                "tx_tcp_segmentation": "on", 
                "tx_udp_tnl_segmentation": "off [fixed]", 
                "tx_vlan_offload": "on [fixed]", 
                "udp_fragmentation_offload": "off [fixed]", 
                "vlan_challenged": "off [fixed]"
            }, 
            "hw_timestamp_filters": [], 
            "ipv4": {
                "address": "192.168.0.218", 
                "broadcast": "192.168.0.255", 
                "netmask": "255.255.255.0", 
                "network": "192.168.0.0"
            }, 
            "ipv6": [
                {
                    "address": "fe80::20c:29ff:fee8:f67b", 
                    "prefix": "64", 
                    "scope": "link"
                }
            ], 
            "macaddress": "00:0c:29:e8:f6:7b", 
            "module": "e1000", 
            "mtu": 1500, 
            "pciid": "0000:02:01.0", 
            "promisc": false, 
            "speed": 1000, 
            "timestamping": [
                "rx_software", 
                "software"
            ], 
            "type": "ether"
        }
    }
    
    PLAY RECAP **********************************************************************************************************
    192.168.0.218              : ok=2    changed=0    unreachable=0    failed=0   
    
    [root@test ~]#
    

      說明:以上playbook就實現了對ansible_eth0這個變量進行調試並打印,可以看到ansible_eth0是一個相對比較複雜的變量,裡面包含了字典,列表混合一起的一個大字典。

    我們可以看到ansible_eht0裡面包含了很多內容,我們要想讀取其中的IPV4地址,我們可以採用“.”或者下標的方式去訪問,如下所示

    [root@test ~]#cat test.yml 
    ---
    - hosts: 192.168.0.218
      remote_user: root
      
      tasks:
        - name: print ipv4  
          shell: echo {{ ansible_eth0["ipv4"]["address"] }} 
        - name: print mac
          shell: echo  {{ ansible_eth0.macaddress }}
    [root@test ~]#ansible-playbook test.yml -v
    Using /etc/ansible/ansible.cfg as config file
    
    PLAY [192.168.0.218] ************************************************************************************************
    
    TASK [Gathering Facts] **********************************************************************************************
    ok: [192.168.0.218]
    
    TASK [print ipv4] ***************************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "echo 192.168.0.218", "delta": "0:00:00.001680", "end": "2019-11-17 18:30:21.926368", "rc": 0, "start": "2019-11-17 18:30:21.924688", "stderr": "", "stderr_lines": [], "stdout": "192.168.0.218", "stdout_lines": ["192.168.0.218"]}
    
    TASK [print mac] ****************************************************************************************************
    changed: [192.168.0.218] => {"changed": true, "cmd": "echo 00:0c:29:e8:f6:7b", "delta": "0:00:00.001746", "end": "2019-11-17 18:30:22.650541", "rc": 0, "start": "2019-11-17 18:30:22.648795", "stderr": "", "stderr_lines": [], "stdout": "00:0c:29:e8:f6:7b", "stdout_lines": ["00:0c:29:e8:f6:7b"]}
    
    PLAY RECAP **********************************************************************************************************
    192.168.0.218              : ok=3    changed=2    unreachable=0    failed=0   
    
    [root@test ~]#

      說明:由此可以看出ansible多級變量的調用,使用中括號和點號都是可以的

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※評比南投搬家公司費用收費行情懶人包大公開

  • 【algo&ds】2.線性表

    【algo&ds】2.線性表

    1.線性表

    線性表(英語:Linear List)是由n(n≥0)個元素()a[0],a[1],a[2]…,a[n-1]組成的。

    其中:

    • 數據元素的個數n定義為表的長度 = “list”.length() (”list”.length() = 0(表裡沒有一個元素)時稱為空表)
    • 將非空的線性表(n>=1)記作:(a[0],a[1],a[2],…,a[n-1])
    • 數據元素a[i](0≤i≤n-1)只是個抽象符號,其具體含義在不同情況下可以不同

    一個數據元素可以由若干個數據項組成。數據元素稱為記錄,含有大量記錄的線性表又稱為文件。這種結構具有下列特點:存在一個唯一的沒有前驅的(頭)數據元素;存在一個唯一的沒有後繼的(尾)數據元素;此外,每一個數據元素均有一個直接前驅和一個直接後繼數據元素。

    2.線性表的存儲結構

    • 鏈表
      • 單鏈表
        • 動態單鏈表
        • 靜態單鏈表
      • 循環鏈表
        • 單循環鏈表
        • 雙循環鏈表
      • 靜態鏈表

    3.順序表

    利用數組的連續存儲空間順序存放線性表的各元素

    3.1結構體定義

    如果需要使用自定義的結構體來維護一個順序表,通常來講結構體的元素一般是一個固定大小的數組(可用長度足夠大),以及當前數組存放的元素個數,也即數組的長度

    typedef struct LNode *List;
    struct LNode {
        ElementType Data[MAXSIZE];
        int Last;//記錄順序表的最後一個元素的下標
    } ;
    struct LNode L;
    List PtrL;

    訪問結構體的成員

    • 訪問下標為 i 的元素:L.Data[i] 或 PtrL->Data[i]
    • 線性表的長度:L.Last+1 或 PtrL->Last+1
    • 指針變量PtrL還可以這樣訪問兩個屬性(*PtrL).Data[i](*PtrL).Last,不過這種訪問方式並不常用

    而一般來講,為了簡單,不會去維護這樣一個結構體,(因為一旦維護了這個結構體,就需要去封裝相應的函數,比如說常見的插入、刪除、查找等操作),而是直接類似下面這樣

    ElementType data[MaxSize];
    int length;

    定義一個足夠大的數組,然後定義一個對應關聯的變量來時刻維護數組的長度。

    這兩種定義方式沒有什麼區別,一種是把常用操作封裝好,方便調用,另一種則是需要時刻自己維護對應的屬性。因為順序表的結構足夠簡單,所以不定義結構體也是可以的。

    3.2順序表的常見操作

    為了方便,這一節內容記錄的都是在定義的結構體基礎上,封裝的常見操作。

    1.初始化

    List MakeEmpty( ) {
        List PtrL;
        PtrL = (List )malloc( sizeof(struct LNode) );
        PtrL->Last = -1;
        return PtrL;
    }

    初始化的順序表,長度為0,所以Last為-1

    2.查找

    int Find( ElementType X, List PtrL ) {
        int i = 0;
        while( i <= PtrL->Last && PtrL->Data[i]!= X )
            i++;
        if (i > PtrL->Last) return -1; /* 如果沒找到,返回-1 */
        else return i; /* 找到后返回的是存儲位置 */
    }

    查找操作比較簡單,從順序表的第一個元素(下標為0開始)開始遍歷。

    還有一種更加巧妙一點的實現方式,就是引入哨兵思想。

    int Find( ElementType X, List PtrL ) {
        PtrL->Data[0] = x;//順序表第一個元素就是哨兵,賦值為x
        int i = PtrL->Last;//從最後一個元素開始遍歷
        while( PtrL->Data[i]!= X )
            i--;
        return i;
    }

    這樣做的好處很明顯,少了邊界的判斷,可以優化時間複雜度,編碼也更加簡單。

    注意:這裏把下標為0的元素設置為哨兵,則要求順序表從下標為1開始存儲。而且,函數如果沒有找到,則一定返回i=0

    3.插入

    看圖示應該要注意,移動的方向是從后往前移,如果從前往後移,則Data[i]=Data[i+1]=…=Data[n],因為後面的元素都被前面移過來的元素給覆蓋了。

    void Insert( ElementType X, int i, List PtrL ) {
        int j;
        if ( PtrL->Last == MAXSIZE-1 ) { /* 表空間已滿,不能插入*/
            printf("表滿");
            return;
        }
        if ( i < 1 || i > PtrL->Last+2) { /*檢查插入位置的合法性*/
            printf("位置不合法");
            return;
        }
        for ( j = PtrL->Last; j >= i-1; j-- )
            PtrL->Data[j+1] = PtrL->Data[j]; /*將 ai~ an倒序向後移動*/
        PtrL->Data[i-1] = X; /*新元素插入*/
        PtrL->Last++; /*Last仍指向最後元素*/
        return;
    }

    為什麼這裏需要判斷順序表的空間是否已滿?

    因為這個數組,是在初始化之後就固定了數組可容納的元素個數MaxSize,一旦超出,則程序下標就會越界。C++提供了動態數組vector,可以很方便的支持動態擴展數組長度,而且基本的插入刪除等操作都封裝好了,可以很方便的使用。

    4.刪除

    同樣的,需要注意元素移動的方向,如果從后往前移,則最後面的元素會一直覆蓋到Data[i]。

    void Delete( int i, List PtrL ) {
        int j;
        if( i < 1 || i > PtrL->Last+1 ) { /*檢查空表及刪除位置的合法性*/
            printf (“不存在第%d個元素”, i );
            return ;
        }
        for ( j = i; j <= PtrL->Last; j++ )
            PtrL->Data[j-1] = PtrL->Data[j]; /*將 ai+1~ an順序向前移動*/
        PtrL->Last--; /*Last仍指向最後元素*/
        return;
    }

    5.排序

    因為排序算法比較多,本文不展開講解,可以參考以下博文,內容包括了常見的十大排序算法的算法分析,時間複雜度和空間複雜度分析以及c實現和動圖圖解。

    4.鏈表

    不要求邏輯上相鄰的兩個元素物理上也相鄰;通過“鏈”建立起數據元素之間的邏輯關係。插入、刪除不需要移動數據元素,只需要修改“鏈”。

    4.1單鏈表

    typedef struct LNode *List;
    struct LNode {
        ElementType Data;
        List Next;
    };
    struct Lnode L;
    List PtrL;
    1.求表長
    int Length ( List PtrL ) {
        List p = PtrL; /* p指向表的第一個結點*/
        int j = 0;
        while ( p ) {
            p = p->Next;
            j++; /* 當前p指向的是第 j 個結點*/
        }
        return j;
    }

    時間複雜度O(n)

    2.查找

    按序查找

    List FindKth( int K, List PtrL ) {
        List p = PtrL;
        int i = 1;
        while (p !=NULL && i < K ) {
            p = p->Next;
            i++;
        }
        if ( i == K ) return p;
        /* 找到第K個,返回指針 */
        else return NULL;
        /* 否則返回空 */
    }

    時間複雜度O(n)

    按值查找

    List Find( ElementType X, List PtrL ) {
        List p = PtrL;
        while ( p!=NULL && p->Data != X )
            p = p->Next;
        return p;
    }

    時間複雜度O(n)

    3.插入

    (1)先構造一個新結點,用s指向;
    (2)再找到鏈表的第 i-1個結點,用p指向;
    (3)然後修改指針,插入結點 ( p之後插入新結點是 s)

    List Insert( ElementType X, int i, List PtrL ) {
        List p, s;
        if ( i == 1 ) { /* 新結點插入在表頭 */
            s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
            s->Data = X;
            s->Next = PtrL;
            return s; /*返回新表頭指針*/
        }
        p = FindKth( i-1, PtrL ); /* 查找第i-1個結點 */
        if ( p == NULL ) { /* 第i-1個不存在,不能插入 */
            printf("參數i錯");
            return NULL;
        } else {
            s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
            s->Data = X;
            s->Next = p->Next; /*新結點插入在第i-1個結點的後面*/
            p->Next = s;
            return PtrL;
        }
    }
    4.刪除

    (1)先找到鏈表的第 i-1個結點,用p指向;
    (2)再用指針s指向要被刪除的結點(p的下一個結點);
    (3)然後修改指針,刪除s所指結點;
    (4)最後釋放s所指結點的空間。

    List Delete( int i, List PtrL ) {
        List p, s;
        if ( i == 1 ) { /* 若要刪除的是表的第一個結點 */
            s = PtrL; /*s指向第1個結點*/
            if (PtrL!=NULL) PtrL = PtrL->Next; /*從鏈表中刪除*/
            else return NULL;
            free(s); /*釋放被刪除結點 */
            return PtrL;
        }
        p = FindKth( i-1, PtrL ); /*查找第i-1個結點*/
        if ( p == NULL ) {
            printf("第%d個結點不存在", i-1);
            return NULL;
        } else if ( p->Next == NULL ) {
            printf("第%d個結點不存在", i);
            return NULL;
        } else {
            s = p->Next; /*s指向第i個結點*/
            p->Next = s->Next; /*從鏈表中刪除*/
            free(s); /*釋放被刪除結點 */
            return PtrL;
        }
    }

    4.2雙鏈表

    雙向鏈表,又稱為雙鏈表,是的一種,它的每個數據結點中都有兩個,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。

    typedef struct DuLNode {
        ElemType data;
        struct DuLNode *prior, *next;
    } DuLNode, *DuLinkList;

    4.3循環鏈表

    4.3.1單循環鏈表

    存儲結構和單鏈表相同。

    typedef struct LNode {
        ElemType data;
        struct LNode *next;
    } LNode, *LinkList;
    
    // 設立尾指針的單循環鏈表的12個基本操作
    void InitList(LinkList *L) { // 操作結果:構造一個空的線性表L
        *L = (LinkList)malloc(sizeof(struct LNode)); // 產生頭結點,並使L指向此頭結點
        if (!*L) // 存儲分配失敗
            exit(OVERFLOW);
        (*L)->next = *L; // 指針域指向頭結點
    }
    
    void DestroyList(LinkList *L) { // 操作結果:銷毀線性表L
        LinkList q, p = (*L)->next; // p指向頭結點
        while (p != *L) { // 沒到表尾
            q = p->next;
            free(p);
            p = q;
        }
        free(*L);
        *L = NULL;
    }
    
    void ClearList(LinkList *L) /* 改變L */ { // 初始條件:線性表L已存在。操作結果:將L重置為空表
        LinkList p, q;
        *L = (*L)->next; // L指向頭結點
        p = (*L)->next; // p指向第一個結點
        while (p != *L) { // 沒到表尾
            q = p->next;
            free(p);
            p = q;
        }
        (*L)->next = *L; // 頭結點指針域指向自身
    }
    
    Status ListEmpty(LinkList L) { // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
        if (L->next == L) // 空
            return TRUE;
        else
            return FALSE;
    }
    
    int ListLength(LinkList L) { // 初始條件:L已存在。操作結果:返回L中數據元素個數
        int i = 0;
        LinkList p = L->next; // p指向頭結點
        while (p != L) { // 沒到表尾
            i++;
            p = p->next;
        }
        return i;
    }
    
    Status GetElem(LinkList L, int i, ElemType *e) { // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
        int j = 1; // 初始化,j為計數器
        LinkList p = L->next->next; // p指向第一個結點
        if (i <= 0 || i > ListLength(L)) // 第i個元素不存在
            return ERROR;
        while (j < i) { // 順指針向後查找,直到p指向第i個元素
            p = p->next;
            j++;
        }
        *e = p->data; // 取第i個元素
        return OK;
    }
    
    int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { // 初始條件:線性表L已存在,compare()是數據元素判定函數
        // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
        //           若這樣的數據元素不存在,則返回值為0
        int i = 0;
        LinkList p = L->next->next; // p指向第一個結點
        while (p != L->next) {
            i++;
            if (compare(p->data, e)) // 滿足關係
                return i;
            p = p->next;
        }
        return 0;
    }
    
    Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e) { // 初始條件:線性表L已存在
        // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
        //           否則操作失敗,pre_e無定義
        LinkList q, p = L->next->next; // p指向第一個結點
        q = p->next;
        while (q != L->next) { // p沒到表尾
            if (q->data == cur_e) {
                *pre_e = p->data;
                return TRUE;
            }
            p = q;
            q = q->next;
        }
        return FALSE; // 操作失敗
    }
    
    Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e) { // 初始條件:線性表L已存在
        // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
        //           否則操作失敗,next_e無定義
        LinkList p = L->next->next; // p指向第一個結點
        while (p != L) { // p沒到表尾
            if (p->data == cur_e) {
                *next_e = p->next->data;
                return TRUE;
            }
            p = p->next;
        }
        return FALSE; // 操作失敗
    }
    
    Status ListInsert(LinkList *L, int i, ElemType e) /* 改變L */ { // 在L的第i個位置之前插入元素e
        LinkList p = (*L)->next, s; // p指向頭結點
        int j = 0;
        if (i <= 0 || i > ListLength(*L) + 1) // 無法在第i個元素之前插入
            return ERROR;
        while (j < i - 1) { // 尋找第i-1個結點
            p = p->next;
            j++;
        }
        s = (LinkList)malloc(sizeof(struct LNode)); // 生成新結點
        s->data = e; // 插入L中
        s->next = p->next;
        p->next = s;
        if (p == *L) // 改變尾結點
            *L = s;
        return OK;
    }
    
    Status ListDelete(LinkList *L, int i, ElemType *e) /* 改變L */ { // 刪除L的第i個元素,並由e返回其值
        LinkList p = (*L)->next, q; // p指向頭結點
        int j = 0;
        if (i <= 0 || i > ListLength(*L)) // 第i個元素不存在
            return ERROR;
        while (j < i - 1) { // 尋找第i-1個結點
            p = p->next;
            j++;
        }
        q = p->next; // q指向待刪除結點
        p->next = q->next;
        *e = q->data;
        if (*L == q) // 刪除的是表尾元素
            *L = p;
        free(q); // 釋放待刪除結點
        return OK;
    }
    
    void ListTraverse(LinkList L, void(*vi)(ElemType)) { // 初始條件:L已存在。操作結果:依次對L的每個數據元素調用函數vi()
        LinkList p = L->next->next; // p指向首元結點
        while (p != L->next) { // p不指向頭結點
            vi(p->data);
            p = p->next;
        }
        printf("\n");
    }

    4.3.2雙循環鏈表

    // 線性表的雙向鏈表存儲結構
    typedef struct DuLNode {
        ElemType data;
        struct DuLNode *prior, *next;
    } DuLNode, *DuLinkList;
    
    // 帶頭結點的雙向循環鏈表的基本操作(14個)
    void InitList(DuLinkList *L) {
        // 產生空的雙向循環鏈表L
        *L = (DuLinkList)malloc(sizeof(DuLNode));
        if (*L)
            (*L)->next = (*L)->prior = *L;
        else
            exit(OVERFLOW);
    }
    
    void DestroyList(DuLinkList *L) {
        // 操作結果:銷毀雙向循環鏈表L
        DuLinkList q, p = (*L)->next; // p指向第一個結點
        while (p != *L) { // p沒到表頭
            q = p->next;
            free(p);
            p = q;
        }
        free(*L);
        *L = NULL;
    }
    
    void ClearList(DuLinkList L) { // 不改變L
        // 初始條件:L已存在。操作結果:將L重置為空表
        DuLinkList q, p = L->next; // p指向第一個結點
        while (p != L) { // p沒到表頭
            q = p->next;
            free(p);
            p = q;
        }
        L->next = L->prior = L; // 頭結點的兩個指針域均指向自身
    }
    
    Status ListEmpty(DuLinkList L) {
        // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
        if (L->next == L && L->prior == L)
            return TRUE;
        else
            return FALSE;
    }
    
    int ListLength(DuLinkList L) {
        // 初始條件:L已存在。操作結果:返回L中數據元素個數
        int i = 0;
        DuLinkList p = L->next; // p指向第一個結點
        while (p != L) { // p沒到表頭
            i++;
            p = p->next;
        }
        return i;
    }
    
    Status GetElem(DuLinkList L, int i, ElemType *e) {
        // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
        int j = 1; // j為計數器
        DuLinkList p = L->next; // p指向第一個結點
        while (p != L && j < i) { // 順指針向後查找,直到p指向第i個元素或p指向頭結點
            p = p->next;
            j++;
        }
        if (p == L || j > i) // 第i個元素不存在
            return ERROR;
        *e = p->data; // 取第i個元素
        return OK;
    }
    
    int LocateElem(DuLinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
        // 初始條件:L已存在,compare()是數據元素判定函數
        // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
        // 若這樣的數據元素不存在,則返回值為0
        int i = 0;
        DuLinkList p = L->next; // p指向第1個元素
        while (p != L) {
            i++;
            if (compare(p->data, e)) // 找到這樣的數據元素
                return i;
            p = p->next;
        }
        return 0;
    }
    
    Status PriorElem(DuLinkList L, ElemType cur_e, ElemType *pre_e) {
        // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
        // 否則操作失敗,pre_e無定義
        DuLinkList p = L->next->next; // p指向第2個元素
        while (p != L) { // p沒到表頭
            if (p->data == cur_e) {
                *pre_e = p->prior->data;
                return TRUE;
            }
            p = p->next;
        }
        return FALSE;
    }
    
    Status NextElem(DuLinkList L, ElemType cur_e, ElemType *next_e) {
        // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
        // 否則操作失敗,next_e無定義
        DuLinkList p = L->next->next; // p指向第2個元素
        while (p != L) { // p沒到表頭
            if (p->prior->data == cur_e) {
                *next_e = p->data;
                return TRUE;
            }
            p = p->next;
        }
        return FALSE;
    }
    
    DuLinkList GetElemP(DuLinkList L, int i) { // 另加
        // 在雙向鏈表L中返回第i個元素的地址。i為0,返回頭結點的地址。若第i個元素不存在,
        // 返回NULL
        int j;
        DuLinkList p = L; // p指向頭結點
        if (i < 0 || i > ListLength(L)) // i值不合法
            return NULL;
        for (j = 1; j <= i; j++)
            p = p->next;
        return p;
    }
    
    Status ListInsert(DuLinkList L, int i, ElemType e) {
        // 在帶頭結點的雙鏈循環線性表L中第i個位置之前插入元素e,i的合法值為1≤i≤表長+1
        // 改進算法2.18,否則無法在第表長+1個結點之前插入元素
        DuLinkList p, s;
        if (i < 1 || i > ListLength(L) + 1) // i值不合法
            return ERROR;
        p = GetElemP(L, i - 1); // 在L中確定第i個元素前驅的位置指針p
        if (!p) // p=NULL,即第i個元素的前驅不存在(設頭結點為第1個元素的前驅)
            return ERROR;
        s = (DuLinkList)malloc(sizeof(DuLNode));
        if (!s)
            return OVERFLOW;
        s->data = e;
        s->prior = p; // 在第i-1個元素之後插入
        s->next = p->next;
        p->next->prior = s;
        p->next = s;
        return OK;
    }
    
    Status ListDelete(DuLinkList L, int i, ElemType *e) {
        // 刪除帶頭結點的雙鏈循環線性表L的第i個元素,i的合法值為1≤i≤表長
        DuLinkList p;
        if (i < 1) // i值不合法
            return ERROR;
        p = GetElemP(L, i); // 在L中確定第i個元素的位置指針p
        if (!p) // p = NULL,即第i個元素不存在
            return ERROR;
        *e = p->data;
        p->prior->next = p->next; // 此處並沒有考慮鏈表頭,鏈表尾
        p->next->prior = p->prior;
        free(p);
        return OK;
    }
    
    void ListTraverse(DuLinkList L, void(*visit)(ElemType)) {
        // 由雙鏈循環線性表L的頭結點出發,正序對每個數據元素調用函數visit()
        DuLinkList p = L->next; // p指向頭結點
        while (p != L) {
            visit(p->data);
            p = p->next;
        }
        printf("\n");
    }
    
    void ListTraverseBack(DuLinkList L, void(*visit)(ElemType)) {
        // 由雙鏈循環線性表L的頭結點出發,逆序對每個數據元素調用函數visit()
        DuLinkList p = L->prior; // p指向尾結點
        while (p != L) {
            visit(p->data);
            p = p->prior;
        }
        printf("\n");
    }

    4.4靜態鏈表

    前面講解的都是動態鏈表,即需要指針來建立結點之間的連接關係。而對有些問題來說結點的地址是比較小的整數(例如5位數的地址),這樣就沒有必要去建立動態鏈表,而應使用方便得多的靜態鏈表。
    靜態鏈表的實現原理是hash,即通過建立一個結構體數組,並令數組的下標直接表示結點的地址,來達到直接訪問數組中的元素就能訪問結點的效果。另外,由於結點的訪問非常方便,因此靜態鏈表是不需要頭結點的。靜態鏈表結點定義的方法如下:

    struct Node{
        typename data;//數據域
        int next;//指針域
    }node[size];

    參考資料:

    • 《算法筆記》
    • 《數據結構和算法》-極客時間專欄

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • X-Admin&ABP框架開發-RBAC

    X-Admin&ABP框架開發-RBAC

      在業務系統需求規劃過程中,通常對於諸如組織機構、用戶和角色等這種基礎功能,通常是將這部分功能規劃到通用子域中,這也說明了,對於這部分功能來講,是系統的基石,整個業務體系是建立於這部分基石之上的,當然,還有諸如多語言、設置管理、認證和授權等。對於這部分功能,ABP中存在這些概念,並且通過Module Zero模塊完成了這些概念。

     

    一、角色訪問控制之RBAC

      RBAC:Role Based Access Control,基於角色的訪問控制,這在目前大多數軟件中來講已經算得上是普遍應用了,最常見的結構如下,結構簡單,設計思路清晰。

      

      但是也存在其它升級版的設計,諸如用戶權限表、角色組、用戶組的概念等,具體分類有RBAC0、RBAC1、RBAC2等,後者功能越來越強大,也越來越複雜。

    • RBAC0:是RBAC的核心思想。
    • RBAC1:是把RBAC的角色分層模型。
    • RBAC2:增加了RBAC的約束模型。
    • RBAC3:整合RBAC2 + RBAC1。

     

    二、ABP中的RBAC

      在Abp中,已經集成了這些概念,並在ModuleZero模塊中實現了這些概念,基於IdentityServer4的ModuleZero模塊完成了封裝。對於我們大多數以業務為中心的開發人員來講,不應該又去造一個輪子,而是應該開好這輛車。首先看下Abp中的RBAC模型

      

      在這其中權限表中記錄了用戶與權限,角色與權限兩部分。對於權限通常指的是功能權限和數據權限兩部分,一般來講,大多指的是功能權限,這種通過角色與權限進行管理即可,如還有用戶部分的功能區分,則可以再使用上用戶與權限,而對於數據權限,可以利用用戶與權限部分,個人用的比較少,但是,可以想象到這麼一個場景,針對於一家門店內的多個店長,角色相同即相應的權限相同,但各自關心的數據來源不同,關心東部、南部等數據,而不關心西部、北部數據,因此可以在數據層面進行劃分,比如設置數據來源,東南西北,對於數據來源進行權限關聯,這樣一來用戶本身如果擁有東部數據權限,則只能看到東部數據。

     

    1、權限聲明及應用

      在Abp中,需要首先在Core層/Authorization/PermissionNames.cs中聲明權限,Abp權限部分設計原則是:先聲明再使用

    /// <summary>
    /// 權限命名
    /// </summary>
    public static class PermissionNames
    {
        #region 頂級權限
        public const string Pages = "Pages";
        #endregion
    
        #region 基礎支撐平台
        public const string Pages_Frame = "Pages.Frame";
    
        #region 租戶管理
        public const string Pages_Frame_Tenants = "Pages.Frame.Tenants";
        #endregion
    
        #region 組織機構
        public const string Pages_Frame_OrganizationUnits = "Pages.Frame.OrganizationUnits";
        public const string Pages_Frame_OrganizationUnits_Create = "Pages.Frame.OrganizationUnits.Create";
        public const string Pages_Frame_OrganizationUnits_Update = "Pages.Frame.OrganizationUnits.Update";
        public const string Pages_Frame_OrganizationUnits_Delete = "Pages.Frame.OrganizationUnits.Delete";
        #endregion
    
        #region 用戶管理
        public const string Pages_Frame_Users = "Pages.Frame.Users";
        public const string Pages_Frame_Users_Create = "Pages.Frame.Users.Create";
        public const string Pages_Frame_Users_Update = "Pages.Frame.Users.Update";
        public const string Pages_Frame_Users_Delete = "Pages.Frame.Users.Delete";
        public const string Pages_Frame_Users_ResetPassword = "Pages.Frame.Users.ResetPassword";
        #endregion
    
        #region 角色管理
        public const string Pages_Frame_Roles = "Pages.Roles";
        public const string Pages_Frame_Roles_Create = "Pages.Frame.Roles.Create";
        public const string Pages_Frame_Roles_Update = "Pages.Frame.Roles.Update";
        public const string Pages_Frame_Roles_Delete = "Pages.Frame.Roles.Delete";
        #endregion
    
    }

      然後在Core層/Authorization/XXXAuthorizationProvider.cs中設置具體權限,在此處設置權限時,可以根據權限設計時候的職責劃分,比如如果僅僅是多租戶需要這部分,那便設置權限範圍為多租戶即可。

    public class SurroundAuthorizationProvider : AuthorizationProvider
    {
        public override void SetPermissions(IPermissionDefinitionContext context)
        {
            #region 頂級權限
            var pages = context.CreatePermission(PermissionNames.Pages, L("Pages"));
            #endregion
    
            #region 基礎支撐平台
            var frame = pages.CreateChildPermission(PermissionNames.Pages_Frame, L("Frame"));
    
            #region 租戶管理
            frame.CreateChildPermission(PermissionNames.Pages_Frame_Tenants, L("Tenants"), multiTenancySides: MultiTenancySides.Host);
            #endregion
    
            #region 組織機構
            var organizationUnits = frame.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits, L("OrganizationUnits"));
            organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Create, L("CreateOrganizationUnit"));
            organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Update, L("EditOrganizationUnit"));
            organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Delete, L("DeleteOrganizationUnit"));
            #endregion
    
            #region 用戶管理
            var users = frame.CreateChildPermission(PermissionNames.Pages_Frame_Users, L("Users"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Create, L("CreateUser"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Update, L("UpdateUser"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Delete, L("DeleteUser"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_ResetPassword, L("ResetPassword"));
            #endregion
    
            #region 角色管理
            var roles = frame.CreateChildPermission(PermissionNames.Pages_Frame_Roles, L("Roles"));
            roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Create, L("CreateRole"));
            roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Update, L("UpdateRole"));
            roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Delete, L("DeleteRole"));
            #endregion
        }
    }

      在設置完畢后,需要將該類集成到Core層/XXXCoreModule當前模塊中,才能使得該部分權限設置生效。

    //配置權限管理
    Configuration.Authorization.Providers.Add<SurroundAuthorizationProvider>();

       作為業務的入口,菜單是較為直觀的體現方式,現在可以,為菜單分配權限了,擁有權限的人才能看的到菜單,同時後台方法中也要有權限判定,菜單僅作為前端入口上的控制,權限判定作為後端的控制。在MVC層的Startup/XXXNavigationProvider.cs中完成菜單的配置工作,可以配置多級菜單,每個菜單可以配置相應的權限,在生成菜單判定時,如果父級菜單權限不足,則直接會跳過子級菜單的判定。

    new MenuItemDefinition(//基礎支撐
        PageNames.FrameManage,
        L(PageNames.FrameManage),
        icon: "&#xe828;",
        requiredPermissionName: PermissionNames.Pages_Frame
    ).AddItem(
        new MenuItemDefinition(//組織機構
            PageNames.OrganizationUnits,
            L(PageNames.OrganizationUnits),
            url: "/OrganizationUnits",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_OrganizationUnits
        )
    ).AddItem(
        new MenuItemDefinition(//用戶管理
            PageNames.Users,
            L(PageNames.Users),
            url: "/Users",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_Users
        )
    ).AddItem(
        new MenuItemDefinition(//角色管理
            PageNames.Roles,
            L(PageNames.Roles),
            url: "/Roles",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_Roles
        )
    ).AddItem(
        new MenuItemDefinition(//系統設置
            PageNames.HostSettings,
            L(PageNames.HostSettings),
            url: "/HostSettings",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_HostSettings
        )
    )

      在前端頁面上,對於按鈕級別的控制也通過權限判定,Abp提供了判定方法,利用Razor語法進行按鈕控制

    @if (await PermissionChecker.IsGrantedAsync(PermissionNames.Pages_Core_DataDictionary_Create))
    {
        <button class="layui-btn layuiadmin-btn-dataDictionary" data-type="addDataDictionary">添加類型</button>
    }

      在後端方法上,通常我喜歡直接在應用服務中的方法上做權限判定(當然也可以前移到MVC層,但是這樣一來,針對於WebApi形式的Host層,又得多加一次判定了),利用AbpAuthorize特性,判定該方法需要哪幾個權限才能訪問,而在mvc的控制器上做訪問認證。

    [AbpAuthorize(PermissionNames.Pages_Core_DataDictionary_Create)]
    private async Task CreateDataDictionaryAsync(CreateOrUpdateDataDictionaryInput input)
    {
    
    }

     

    2、角色與權限

       在Abp中,角色信息存儲在abprole表中,角色與權限間的關聯存儲在abppermission這張表中,一個角色有多個權限,如果某個角色的權限被去掉了,這張表中的相關記錄將由abp負責刪除,我們只需要完成掌控哪些權限是這個角色有的就行。Abp中已經完成了角色的所有操作,但是前端部分採用的是bootstrap弄的,將其改造一波,成為layui風格。

      

      在創建角色中,主要是將選中的權限掛鈎到具體的某個角色上,該部分代碼沿用abp中自帶的角色權限處理方法。

    private async Task CreateRole(CreateOrUpdateRoleInput input)
    {
        var role = ObjectMapper.Map<Role>(input.Role);
        role.SetNormalizedName();
    
        CheckErrors(await _roleManager.CreateAsync(role));
    
        var grantedPermissions = PermissionManager
            .GetAllPermissions()
            .Where(p => input.PermissionNames.Contains(p.Name))
            .ToList();
    
        await _roleManager.SetGrantedPermissionsAsync(role, grantedPermissions);
    }

      指定角色Id,租戶Id及之前聲明的權限名稱,在abppermission中可查看到具體角色權限。

      

     

    3、用戶與角色

       一個用戶可以承擔多個角色,履行不同角色的義務,作為一個業務系統最基本的單元,abp中提供了這些概念並在Module Zero模塊中已經完成了對用戶的一系列操作,用戶信息存儲在AbpUsers表中,用戶直接關聯的角色保存在AbpUserRoles表中,abp中MVC版本採用的是bootstrap風格,因此,用layui風格完成一次替換,並且,改動一些頁面布局。

      

      Abp版本中,由於是土耳其大佬所開發的習慣,針對於姓和名做了拆分,因此對於我們的使用要做一次處理,我這先簡單處理了一下,並且在業務系統中,郵箱時有時無,因此也需要進行考慮。

    [AbpAuthorize(PermissionNames.Pages_Frame_Users_Create)]
    private async Task CreateUser(CreateOrUpdateUserInput input)
    {
        var user = ObjectMapper.Map<User>(input.User);
        user.TenantId = AbpSession.TenantId;
        user.IsEmailConfirmed = true;
        user.Name = "Name";
        user.Surname = "Surname";
        //user.EmailAddress = string.Empty;
    
        await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
        foreach (var validator in _passwordValidators)
        {
            CheckErrors(await validator.ValidateAsync(UserManager, user, AppConsts.DefaultPassword));
        }
    
        user.Password = _passwordHasher.HashPassword(user, AppConsts.DefaultPassword);
    
        await _userManager.InitializeOptionsAsync(AbpSession.TenantId);
    
        CheckErrors(await _userManager.CreateAsync(user, AppConsts.DefaultPassword));
    
        if (input.AssignedRoleNames != null)
        {
            CheckErrors(await _userManager.SetRoles(user, input.AssignedRoleNames));
        }
    
        if (input.OrganizationUnitIds != null)
        {
            await _userManager.SetOrganizationUnitsAsync(user, input.OrganizationUnitIds);
        }
    
        CurrentUnitOfWork.SaveChanges();
    }

      此處對用戶個人單獨的權限沒有去做處理,依照Abp的文檔有那麼一句話,大多數應用程序中,基於角色的已經足夠使用了,如果想聲明特定權限給用戶,那麼針對於用戶本身的角色權限則被覆蓋。    

     

     至此,修改整合用戶、角色和權限加入到系統中初步完成了,至於一些更為豐富的功能,待逐步加入中,車子再好,司機也得睡覺。

     

     倉庫地址:

    2019-11-17,望技術有成后能回來看見自己的腳步

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

  • 如何給HTML標籤中的文本設置修飾線

    如何給HTML標籤中的文本設置修飾線

    text-decoration屬性介紹

    • text-decoration屬性是用來設置文本修飾線呢,text-decoration屬性一共有4個值。

    text-decoration屬性值說明表

    作用
    none 去掉文本修飾線
    underline 設置下劃線
    overline 設置上劃線
    line-through 設置刪除線

    HTML標籤自帶修飾線

    • 在開始實踐text-decoration屬性之前,筆者先給大家普及下HTML中的標籤自帶修飾線如:u標籤s標籤,若有不全大家可以在下面評論中告訴筆者,畢竟筆者也是前端的一個小白,希望和大家相互交流,互幫互助,共同進步。

    u標籤

    • 下面讓我們進入u標籤的實踐,u標籤自帶的是文本下劃線。
    • 代碼塊

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>設置文本修飾線</title>
      
    </head>
    <body>
        <u>成功不是擊敗別人,而是改變自己</u>
    </body>
    </html>
    • 結果圖

    • 注意:u標籤也可以配合HTML中的其他標籤使用,舉例:將u標籤嵌套到h1標籤中使用。

    • 代碼塊

    <h1><u>成功不是擊敗別人,而是改變自己</u></h1>

    s標籤

    • 下面讓我們進入s標籤的實踐,s標籤自帶的是文本刪除線。
    • 代碼塊

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>設置文本修飾線</title>
      
    </head>
    <body>
        <s>成功不是擊敗別人,而是改變自己</s>
    </body>
    </html>
    • 結果圖

    • 注意:s標籤也可以嵌套,和u標籤一致,筆者就不過多的介紹了。

    none去除修飾線

    • 讓我們進入text-decoration屬性的none值實踐,實踐內容如:筆者將HTML頁面中的s標籤自帶的刪除線給去除掉。

    • 代碼塊

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>設置文本修飾線</title>
        <style>
            s{
                text-decoration: none;
            }
        </style>
    </head>
    <body>
        <s>成功不是擊敗別人,而是改變自己</s>
    </body>
    </html>
    • 結果圖

    • 注意:u標籤、s標籤、包括text-decoration屬性值的所有的修飾線都可以去掉哦。

    underline設置下劃線

    • 讓我們進入text-decoration屬性的underline值實踐,實踐內容如:筆者將HTML頁面中的h2標籤中的文本設置一個下劃線。
    • 代碼塊

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>設置文本修飾線</title>
        <style>
            h2{
                text-decoration: underline;
            }
        </style>
    </head>
    <body>
        <h2>成功不是擊敗別人,而是改變自己</h2>
    </body>
    </html>
    • 結果圖

    overline設置上劃線

    • 讓我們進入text-decoration屬性的overline值實踐,實踐內容如:筆者將HTML頁面中的h2標籤中的文本設置一個上劃線。

    • 代碼塊

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>設置文本修飾線</title>
        <style>
            h2{
                text-decoration: overline;
            }
        </style>
    </head>
    <body>
        <h2>成功不是擊敗別人,而是改變自己</h2>
    </body>
    </html>
    • 結果圖

    line-through設置刪除線

    • 讓我們進入text-decoration屬性的line-through值實踐,實踐內容如:筆者將HTML頁面中的h2標籤中的文本設置一個刪除線。

    • 代碼塊

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>設置文本修飾線</title>
        <style>
            h2{
                text-decoration: line-through;
            }
        </style>
    </head>
    <body>
        <h2>成功不是擊敗別人,而是改變自己</h2>
    </body>
    </html>
    • 結果圖

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    南投搬家前需注意的眉眉角角,別等搬了再說!

  • 6. SOFAJRaft源碼分析— 透過RheaKV看線性一致性讀

    開篇

    其實這篇文章我本來想在講完選舉的時候就開始講線性一致性讀的,但是感覺直接講沒頭沒尾的看起來比比較困難,所以就有了RheaKV的系列,這是RheaKV,終於可以講一下SOFAJRaft的線性一致性讀是怎麼做到了的。所謂線性一致性,一個簡單的例子是在 T1 的時間寫入一個值,那麼在 T1 之後讀一定能讀到這個值,不可能讀到 T1 之前的值。

    其中部分內容參考SOFAJRaft文檔:

    RheaKV讀取數據

    RheaKV的讀取數據的入口是DefaultRheaKVStore的bGet。

    DefaultRheaKVStore#bGet

    public byte[] bGet(final String key) {
        return FutureHelper.get(get(key), this.futureTimeoutMillis);
    }

    bGet方法中會一直調用到DefaultRheaKVStore的一個get方法中:
    DefaultRheaKVStore#get

    private CompletableFuture<byte[]> get(final byte[] key, final boolean readOnlySafe,
                                          final CompletableFuture<byte[]> future, final boolean tryBatching) {
        //校驗started狀態
        checkState();
        Requires.requireNonNull(key, "key");
        if (tryBatching) {
            final GetBatching getBatching = readOnlySafe ? this.getBatchingOnlySafe : this.getBatching;
            if (getBatching != null && getBatching.apply(key, future)) {
                return future;
            }
        }
        internalGet(key, readOnlySafe, future, this.failoverRetries, null, this.onlyLeaderRead);
        return future;
    }

    get方法會根據傳入的參數來判斷是否採用批處理的方式來讀取數據,readOnlySafe表示是否開啟線程一致性讀,由於我們調用的是get方法,所以readOnlySafe和tryBatching都會返回true。
    所以這裡會調用getBatchingOnlySafe的apply方法,將key和future傳入。
    getBatchingOnlySafe是在我們初始化DefaultRheaKVStore的時候初始化的:
    DefaultRheaKVStore#init

    .....
    this.getBatchingOnlySafe = new GetBatching(KeyEvent::new, "get_batching_only_safe",
            new GetBatchingHandler("get_only_safe", true));
    .....

    在初始化getBatchingOnlySafe的時候傳入的處理器是GetBatchingHandler。

    然後我們回到getBatchingOnlySafe#apply中,看看這個方法做了什麼:

    public boolean apply(final byte[] message, final CompletableFuture<byte[]> future) {
        //GetBatchingHandler
        return this.ringBuffer.tryPublishEvent((event, sequence) -> {
            event.reset();
            event.key = message;
            event.future = future;
        });
    }

    apply方法會向Disruptor發送一個事件進行異步處理,並把我們的key封裝到event的key中。getBatchingOnlySafe的處理器是GetBatchingHandler。

    批量獲取數據

    GetBatchingHandler#onEvent

    public void onEvent(final KeyEvent event, final long sequence, final boolean endOfBatch) throws Exception {
        this.events.add(event);
        this.cachedBytes += event.key.length;
        final int size = this.events.size();
        //校驗一下數據量,沒有達到MaxReadBytes並且不是最後一個event,那麼直接返回
        if (!endOfBatch && size < batchingOpts.getBatchSize() && this.cachedBytes < batchingOpts.getMaxReadBytes()) {
            return;
        }
    
        if (size == 1) {
            reset();
            try {
                //如果只是一個get請求,那麼不需要進行批量處理
                get(event.key, this.readOnlySafe, event.future, false);
            } catch (final Throwable t) {
                exceptionally(t, event.future);
            }
        } else {
            //初始化一個剛剛好大小的集合
            final List<byte[]> keys = Lists.newArrayListWithCapacity(size);
            final CompletableFuture<byte[]>[] futures = new CompletableFuture[size];
            for (int i = 0; i < size; i++) {
                final KeyEvent e = this.events.get(i);
                keys.add(e.key);
                futures[i] = e.future;
            }
            //遍歷完events數據到entries之後,重置
            reset();
            try {
                multiGet(keys, this.readOnlySafe).whenComplete((result, throwable) -> {
                    //異步回調處理數據
                    if (throwable == null) {
                        for (int i = 0; i < futures.length; i++) {
                            final ByteArray realKey = ByteArray.wrap(keys.get(i));
                            futures[i].complete(result.get(realKey));
                        }
                        return;
                    }
                    exceptionally(throwable, futures);
                });
            } catch (final Throwable t) {
                exceptionally(t, futures);
            }
        }
    }
    }

    onEvent方法首先會校驗一下當前的event數量有沒有達到閾值以及當前的event是不是Disruptor中最後一個event;然後會根據不同的events集合中的數量來走不同的實現,這裏做了一個優化,如果是只有一條數據那麼不會走批處理;最後將所有的key放入到keys集合中並調用multiGet進行批處理。

    multiGet方法會調用internalMultiGet返回一個Future,從而實現異步的返回結果。
    DefaultRheaKVStore#internalMultiGet

    private FutureGroup<Map<ByteArray, byte[]>> internalMultiGet(final List<byte[]> keys, final boolean readOnlySafe,
                                                                 final int retriesLeft, final Throwable lastCause) {
        //因為不同的key是存放在不同的region中的,所以一個region會對應多個key,封裝到map中
        final Map<Region, List<byte[]>> regionMap = this.pdClient
                .findRegionsByKeys(keys, ApiExceptionHelper.isInvalidEpoch(lastCause));
        //返回值
        final List<CompletableFuture<Map<ByteArray, byte[]>>> futures =
                Lists.newArrayListWithCapacity(regionMap.size());
        //lastCause傳入為null
        final Errors lastError = lastCause == null ? null : Errors.forException(lastCause);
    
        for (final Map.Entry<Region, List<byte[]>> entry : regionMap.entrySet()) {
            final Region region = entry.getKey();
            final List<byte[]> subKeys = entry.getValue();
            //重試次數減1,設置一個重試函數
            final RetryCallable<Map<ByteArray, byte[]>> retryCallable = retryCause -> internalMultiGet(subKeys,
                    readOnlySafe, retriesLeft - 1, retryCause);
            final MapFailoverFuture<ByteArray, byte[]> future = new MapFailoverFuture<>(retriesLeft, retryCallable);
            //發送MultiGetRequest請求,獲取數據
            internalRegionMultiGet(region, subKeys, readOnlySafe, future, retriesLeft, lastError, this.onlyLeaderRead);
            futures.add(future);
        }
        return new FutureGroup<>(futures);
    }

    internalMultiGet里會根據key去組裝region,不同的key會對應不同的region,數據時存在region中的,所以要從不同的region中獲取數據,region和key是一對多的關係所以這裡會封裝成一個map。然後會遍歷regionMap,每個region所對應的數據作為一個批次調用到internalRegionMultiGet方法中,根據不同的情況獲取數據。

    DefaultRheaKVStore#internalRegionMultiGet

    private void internalRegionMultiGet(final Region region, final List<byte[]> subKeys, final boolean readOnlySafe,
                                        final CompletableFuture<Map<ByteArray, byte[]>> future, final int retriesLeft,
                                        final Errors lastCause, final boolean requireLeader) {
        //因為當前的是client,所以這裡會是null
        final RegionEngine regionEngine = getRegionEngine(region.getId(), requireLeader);
        // require leader on retry
        //設置重試函數
        final RetryRunner retryRunner = retryCause -> internalRegionMultiGet(region, subKeys, readOnlySafe, future,
                retriesLeft - 1, retryCause, true);
        final FailoverClosure<Map<ByteArray, byte[]>> closure = new FailoverClosureImpl<>(future,
                false, retriesLeft, retryRunner);
        if (regionEngine != null) {
            if (ensureOnValidEpoch(region, regionEngine, closure)) {
                //如果不是null,那麼會獲取rawKVStore,並從中獲取數據
                final RawKVStore rawKVStore = getRawKVStore(regionEngine);
                if (this.kvDispatcher == null) {
                    rawKVStore.multiGet(subKeys, readOnlySafe, closure);
                } else {
                    //如果是kvDispatcher不為空,那麼放入到kvDispatcher中異步執行
                    this.kvDispatcher.execute(() -> rawKVStore.multiGet(subKeys, readOnlySafe, closure));
                }
            }
        } else {
            final MultiGetRequest request = new MultiGetRequest();
            request.setKeys(subKeys);
            request.setReadOnlySafe(readOnlySafe);
            request.setRegionId(region.getId());
            request.setRegionEpoch(region.getRegionEpoch());
            //調用rpc請求
            this.rheaKVRpcService.callAsyncWithRpc(request, closure, lastCause, requireLeader);
        }
    }

    因為我們這裡是client端調用internalRegionMultiGet方法的,所以是沒有設置regionEngine的,那麼會直接向server的當前region所對應的leader節點發送一個MultiGetRequest請求。

    因為上面的這些方法基本上和put是一致的,我們已經在講過了,所以這裏不重複的講了。

    server端處理MultiGetRequest請求

    MultiGetRequest請求會被KVCommandProcessor所處理,KVCommandProcessor里會根據請求的magic方法返回值來判斷是用什麼方式來進行處理。我們這裡會調用到DefaultRegionKVService的handleMultiGetRequest方法中處理請求。

    public void handleMultiGetRequest(final MultiGetRequest request,
                                      final RequestProcessClosure<BaseRequest, BaseResponse<?>> closure) {
        final MultiGetResponse response = new MultiGetResponse();
        response.setRegionId(getRegionId());
        response.setRegionEpoch(getRegionEpoch());
        try {
            KVParameterRequires.requireSameEpoch(request, getRegionEpoch());
            final List<byte[]> keys = KVParameterRequires.requireNonEmpty(request.getKeys(), "multiGet.keys");
            //調用MetricsRawKVStore的multiGet方法
            this.rawKVStore.multiGet(keys, request.isReadOnlySafe(), new BaseKVStoreClosure() {
    
                @SuppressWarnings("unchecked")
                @Override
                public void run(final Status status) {
                    if (status.isOk()) {
                        response.setValue((Map<ByteArray, byte[]>) getData());
                    } else {
                        setFailure(request, response, status, getError());
                    }
                    closure.sendResponse(response);
                }
            });
        } catch (final Throwable t) {
            LOG.error("Failed to handle: {}, {}.", request, StackTraceUtil.stackTrace(t));
            response.setError(Errors.forException(t));
            closure.sendResponse(response);
        }
    }

    handleMultiGetRequest方法會調用MetricsRawKVStore的multiGet方法來批量獲取數據。

    MetricsRawKVStore#multiGet

    public void multiGet(final List<byte[]> keys, final boolean readOnlySafe, final KVStoreClosure closure) {
        //實例化MetricsKVClosureAdapter對象
        final KVStoreClosure c = metricsAdapter(closure, MULTI_GET, keys.size(), 0);
        //調用RaftRawKVStore的multiGet方法
        this.rawKVStore.multiGet(keys, readOnlySafe, c);
    }

    multiGet方法會傳入一個MetricsKVClosureAdapter實例,通過這個實例實現異步回調response。然後調用RaftRawKVStore的multiGet方法。

    RaftRawKVStore#multiGet

    public void multiGet(final List<byte[]> keys, final boolean readOnlySafe, final KVStoreClosure closure) {
        if (!readOnlySafe) {
            this.kvStore.multiGet(keys, false, closure);
            return;
        }
        // KV 存儲實現線性一致讀
        // 調用 readIndex 方法,等待回調執行
        this.node.readIndex(BytesUtil.EMPTY_BYTES, new ReadIndexClosure() {
    
            @Override
            public void run(final Status status, final long index, final byte[] reqCtx) {
                //如果狀態返回成功,
                if (status.isOk()) {
                    RaftRawKVStore.this.kvStore.multiGet(keys, true, closure);
                    return;
                }
                //readIndex 讀取失敗嘗試應用鍵值讀操作申請任務於 Leader 節點的狀態機 KVStoreStateMachine
                RaftRawKVStore.this.readIndexExecutor.execute(() -> {
                    if (isLeader()) {
                        LOG.warn("Fail to [multiGet] with 'ReadIndex': {}, try to applying to the state machine.",
                                status);
                        // If 'read index' read fails, try to applying to the state machine at the leader node
                        applyOperation(KVOperation.createMultiGet(keys), closure);
                    } else {
                        LOG.warn("Fail to [multiGet] with 'ReadIndex': {}.", status);
                        // Client will retry to leader node
                        new KVClosureAdapter(closure, null).run(status);
                    }
                });
            }
        });
    }

    multiGet調用node的readIndex方法進行一致性讀操作,並設置回調,如果返回成功那麼就直接調用RocksRawKVStore讀取數據,如果返回不是成功那麼申請任務於 Leader 節點的狀態機 KVStoreStateMachine。

    線性一致性讀readIndex

    所謂線性一致讀,一個簡單的例子是在 t1 的時刻我們寫入了一個值,那麼在 t1 之後,我們一定能讀到這個值,不可能讀到 t1 之前的舊值(想想 Java 中的 volatile 關鍵字,即線性一致讀就是在分佈式系統中實現 Java volatile 語義)。簡而言之是需要在分佈式環境中實現 Java volatile 語義效果,即當 Client 向集群發起寫操作的請求並且獲得成功響應之後,該寫操作的結果要對所有後來的讀請求可見。和 volatile 的區別在於 volatile 是實現線程之間的可見,而 SOFAJRaft 需要實現 Server 之間的可見。

    SOFAJRaft提供的線性一致讀是基於 Raft 協議的 ReadIndex 實現用 ;Node#readIndex(byte [] requestContext, ReadIndexClosure done) 發起線性一致讀請求,當安全讀取時傳入的 Closure 將被調用,正常情況從狀態機中讀取數據返回給客戶端。

    Node#readIndex

    public void readIndex(final byte[] requestContext, final ReadIndexClosure done) {
        if (this.shutdownLatch != null) {
            //異步執行回調
            Utils.runClosureInThread(done, new Status(RaftError.ENODESHUTDOWN, "Node is shutting down."));
            throw new IllegalStateException("Node is shutting down");
        }
        Requires.requireNonNull(done, "Null closure");
        //EMPTY_BYTES
        this.readOnlyService.addRequest(requestContext, done);
    }

    readIndex會調用ReadOnlyServiceImpl#addRequest將requestContext和回調方法done傳入,requestContext傳入的是BytesUtil.EMPTY_BYTES
    接着往下看

    ReadOnlyServiceImpl#addRequest

    public void addRequest(final byte[] reqCtx, final ReadIndexClosure closure) {
        if (this.shutdownLatch != null) {
            Utils.runClosureInThread(closure, new Status(RaftError.EHOSTDOWN, "Was stopped"));
            throw new IllegalStateException("Service already shutdown.");
        }
        try {
            EventTranslator<ReadIndexEvent> translator = (event, sequence) -> {
                event.done = closure;
                //EMPTY_BYTES
                event.requestContext = new Bytes(reqCtx);
                event.startTime = Utils.monotonicMs();
            };
            int retryTimes = 0;
            while (true) {
                //ReadIndexEventHandler
                if (this.readIndexQueue.tryPublishEvent(translator)) {
                    break;
                } else {
                    retryTimes++;
                    if (retryTimes > MAX_ADD_REQUEST_RETRY_TIMES) {
                        Utils.runClosureInThread(closure,
                            new Status(RaftError.EBUSY, "Node is busy, has too many read-only requests."));
                        this.nodeMetrics.recordTimes("read-index-overload-times", 1);
                        LOG.warn("Node {} ReadOnlyServiceImpl readIndexQueue is overload.", this.node.getNodeId());
                        return;
                    }
                    ThreadHelper.onSpinWait();
                }
            }
        } catch (final Exception e) {
            Utils.runClosureInThread(closure, new Status(RaftError.EPERM, "Node is down."));
        }
    }

    addRequest方法里會將傳入的reqCtx和closure封裝成一個時間,傳入到readIndexQueue隊列中,事件發布成功後會交由ReadIndexEventHandler處理器處理,發布失敗會進行重試,最多重試3次。

    ReadIndexEventHandler

    private class ReadIndexEventHandler implements EventHandler<ReadIndexEvent> {
        // task list for batch
        private final List<ReadIndexEvent> events = new ArrayList<>(
                                                      ReadOnlyServiceImpl.this.raftOptions.getApplyBatch());
    
        @Override
        public void onEvent(final ReadIndexEvent newEvent, final long sequence, final boolean endOfBatch)
                                                                                                         throws Exception {
            if (newEvent.shutdownLatch != null) {
                executeReadIndexEvents(this.events);
                this.events.clear();
                newEvent.shutdownLatch.countDown();
                return;
            }
    
            this.events.add(newEvent);
            //批量執行
            if (this.events.size() >= ReadOnlyServiceImpl.this.raftOptions.getApplyBatch() || endOfBatch) {
                executeReadIndexEvents(this.events);
                this.events.clear();
            }
        }
    }

    ReadIndexEventHandler是ReadOnlyServiceImpl裏面的內部類,裏面有一個全局的events集合用來做事件的批處理,如果當前的event已經達到了32個或是整個Disruptor隊列里最後一個那麼會調用ReadOnlyServiceImpl的executeReadIndexEvents方法進行事件的批處理。

    ReadOnlyServiceImpl#executeReadIndexEvents

    private void executeReadIndexEvents(final List<ReadIndexEvent> events) {
        if (events.isEmpty()) {
            return;
        }
        //初始化ReadIndexRequest
        final ReadIndexRequest.Builder rb = ReadIndexRequest.newBuilder() //
            .setGroupId(this.node.getGroupId()) //
            .setServerId(this.node.getServerId().toString());
    
        final List<ReadIndexState> states = new ArrayList<>(events.size());
    
        for (final ReadIndexEvent event : events) {
            rb.addEntries(ZeroByteStringHelper.wrap(event.requestContext.get()));
            states.add(new ReadIndexState(event.requestContext, event.done, event.startTime));
        }
        final ReadIndexRequest request = rb.build();
    
        this.node.handleReadIndexRequest(request, new ReadIndexResponseClosure(states, request));
    }

    executeReadIndexEvents封裝好ReadIndexRequest請求和將ReadIndexState集合封裝到ReadIndexResponseClosure中,為後續的操作做裝備

    NodeImpl#handleReadIndexRequest

    public void handleReadIndexRequest(final ReadIndexRequest request, final RpcResponseClosure<ReadIndexResponse> done) {
        final long startMs = Utils.monotonicMs();
        this.readLock.lock();
        try {
            switch (this.state) {
                case STATE_LEADER:
                    readLeader(request, ReadIndexResponse.newBuilder(), done);
                    break;
                case STATE_FOLLOWER:
                    readFollower(request, done);
                    break;
                case STATE_TRANSFERRING:
                    done.run(new Status(RaftError.EBUSY, "Is transferring leadership."));
                    break;
                default:
                    done.run(new Status(RaftError.EPERM, "Invalid state for readIndex: %s.", this.state));
                    break;
            }
        } finally {
            this.readLock.unlock();
            this.metrics.recordLatency("handle-read-index", Utils.monotonicMs() - startMs);
            this.metrics.recordSize("handle-read-index-entries", request.getEntriesCount());
        }
    }

    因為線性一致讀在任何集群內的節點發起,並不需要強制要求放到 Leader 節點上,允許在 Follower 節點執行,因此大大降低 Leader 的讀取壓力。
    當在Follower節點執行一致性讀的時候實際上Follower 節點調用 RpcService#readIndex(leaderId.getEndpoint(), newRequest, -1, closure) 方法向 Leader 發送 ReadIndex 請求,交由Leader節點實現一致性讀。所以我這裏主要介紹Leader的一致性讀。

    繼續往下走調用NodeImpl的readLeader方法
    NodeImpl#readLeader

    private void readLeader(final ReadIndexRequest request, final ReadIndexResponse.Builder respBuilder,
                            final RpcResponseClosure<ReadIndexResponse> closure) {
        //1. 獲取集群節點中多數選票數是多少
        final int quorum = getQuorum();
        if (quorum <= 1) {
            // Only one peer, fast path.
            //如果集群中只有一個節點,那麼直接調用回調函數,返回成功
            respBuilder.setSuccess(true) //
                    .setIndex(this.ballotBox.getLastCommittedIndex());
            closure.setResponse(respBuilder.build());
            closure.run(Status.OK());
            return;
        }
    
        final long lastCommittedIndex = this.ballotBox.getLastCommittedIndex();
        //2. 任期必須相等
        //日誌管理器 LogManager 基於投票箱 BallotBox 的 lastCommittedIndex 獲取任期檢查是否等於當前任期
        // 如果不等於當前任期表示此 Leader 節點未在其任期內提交任何日誌,需要拒絕只讀請求;
        if (this.logManager.getTerm(lastCommittedIndex) != this.currTerm) {
            // Reject read only request when this leader has not committed any log entry at its term
            closure
                    .run(new Status(
                            RaftError.EAGAIN,
                            "ReadIndex request rejected because leader has not committed any log entry at its term, " +
                             "logIndex=%d, currTerm=%d.",
                            lastCommittedIndex, this.currTerm));
            return;
        }
        respBuilder.setIndex(lastCommittedIndex);
    
        if (request.getPeerId() != null) {
            // request from follower, check if the follower is in current conf.
            final PeerId peer = new PeerId();
            peer.parse(request.getServerId());
            //3. 來自 Follower 的請求需要檢查 Follower 是否在當前配置
            if (!this.conf.contains(peer)) {
                closure
                        .run(new Status(RaftError.EPERM, "Peer %s is not in current configuration: {}.", peer,
                         this.conf));
                return;
            }
        }
    
        ReadOnlyOption readOnlyOpt = this.raftOptions.getReadOnlyOptions();
        //4. 如果使用的是ReadOnlyLeaseBased,確認leader是否是在在租約有效時間內
        if (readOnlyOpt == ReadOnlyOption.ReadOnlyLeaseBased && !isLeaderLeaseValid()) {
            // If leader lease timeout, we must change option to ReadOnlySafe
            readOnlyOpt = ReadOnlyOption.ReadOnlySafe;
        }
    
        switch (readOnlyOpt) {
            //5
            case ReadOnlySafe:
                final List<PeerId> peers = this.conf.getConf().getPeers();
                Requires.requireTrue(peers != null && !peers.isEmpty(), "Empty peers");
                //設置心跳的響應回調函數
                final ReadIndexHeartbeatResponseClosure heartbeatDone = new ReadIndexHeartbeatResponseClosure(closure,
                        respBuilder, quorum, peers.size());
                // Send heartbeat requests to followers
                //向 Followers 節點發起一輪 Heartbeat,如果半數以上節點返回對應的
                // Heartbeat Response,那麼 Leader就能夠確定現在自己仍然是 Leader
                for (final PeerId peer : peers) {
                    if (peer.equals(this.serverId)) {
                        continue;
                    }
                    this.replicatorGroup.sendHeartbeat(peer, heartbeatDone);
                }
                break;
            //6. 因為在租約期內不會發生選舉,確保 Leader 不會變化
            //所以直接返回回調結果
            case ReadOnlyLeaseBased:
                // Responses to followers and local node.
                respBuilder.setSuccess(true);
                closure.setResponse(respBuilder.build());
                closure.run(Status.OK());
                break;
        }
    }
    1. 獲取集群節點中多數選票數是多少,即集群節點的1/2+1,如果當前的集群里只有一個節點,那麼直接返回成功,並調用回調方法
    2. 校驗 Raft 集群節點數量以及 lastCommittedIndex 所屬任期符合預期,那麼響應構造器設置其索引為投票箱 BallotBox 的 lastCommittedIndex
    3. 來自 Follower 的請求需要檢查 Follower 是否在當前配置,如果不在當前配置中直接調用回調方法設置異常
    4. 獲取 ReadIndex 請求級別 ReadOnlyOption 配置,ReadOnlyOption 參數默認值為 ReadOnlySafe。如果設置的是ReadOnlyLeaseBased,那麼會調用isLeaderLeaseValid檢查leader是否是在在租約有效時間內
    5. 配置為ReadOnlySafe 調用 Replicator#sendHeartbeat(rid, closure) 方法向 Followers 節點發送 Heartbeat 心跳請求,發送心跳成功執行 ReadIndexHeartbeatResponseClosure 心跳響應回調;ReadIndex 心跳響應回調檢查是否超過半數節點包括 Leader 節點自身投票贊成,半數以上節點返回客戶端Heartbeat 請求成功響應,即 applyIndex 超過 ReadIndex 說明已經同步到 ReadIndex 對應的 Log 能夠提供 Linearizable Read
    6. 配置為ReadOnlyLeaseBased,因為Leader 租約有效期間認為當前 Leader 是 Raft Group 內的唯一有效 Leader,所以忽略 ReadIndex 發送 Heartbeat 確認身份步驟,直接返回 Follower 節點和本地節點 Read 請求成功響應。Leader 節點繼續等待狀態機執行,直到 applyIndex 超過 ReadIndex 安全提供 Linearizable Read

    無論是ReadOnlySafe還是ReadOnlyLeaseBased,最後發送成功響應都會調用ReadIndexResponseClosure的run方法。

    ReadIndexResponseClosure#run

    public void run(final Status status) {
        //fail
        //傳入的狀態不是ok,響應失敗
        if (!status.isOk()) {
            notifyFail(status);
            return;
        }
        final ReadIndexResponse readIndexResponse = getResponse();
        //Fail
        //response沒有響應成功,響應失敗
        if (!readIndexResponse.getSuccess()) {
            notifyFail(new Status(-1, "Fail to run ReadIndex task, maybe the leader stepped down."));
            return;
        }
        // Success
        //一致性讀成功
        final ReadIndexStatus readIndexStatus = new ReadIndexStatus(this.states, this.request,
            readIndexResponse.getIndex());
        for (final ReadIndexState state : this.states) {
            // Records current commit log index.
            //設置當前提交的index
            state.setIndex(readIndexResponse.getIndex());
        }
    
        boolean doUnlock = true;
        ReadOnlyServiceImpl.this.lock.lock();
        try {
            //校驗applyIndex 是否超過 ReadIndex
            if (readIndexStatus.isApplied(ReadOnlyServiceImpl.this.fsmCaller.getLastAppliedIndex())) {
                // Already applied, notify readIndex request.
                ReadOnlyServiceImpl.this.lock.unlock();
                doUnlock = false;
                //已經同步到 ReadIndex 對應的 Log 能夠提供 Linearizable Read
                notifySuccess(readIndexStatus);
            } else {
                // Not applied, add it to pending-notify cache.
                ReadOnlyServiceImpl.this.pendingNotifyStatus
                    .computeIfAbsent(readIndexStatus.getIndex(), k -> new ArrayList<>(10)) //
                    .add(readIndexStatus);
            }
        } finally {
            if (doUnlock) {
                ReadOnlyServiceImpl.this.lock.unlock();
            }
        }
    }

    Run方法首先會校驗一下是否需要響應失敗,如果響應成功,那麼會將所有封裝的ReadIndexState更新一下index,然後校驗一下applyIndex 是否超過 ReadIndex,超過了ReadIndex代表所有已經複製到多數派上的 Log(可視為寫操作)被視為安全的 Log,該 Log 所體現的數據就能對客戶端 Client 可見。

    ReadOnlyServiceImpl#notifySuccess

    private void notifySuccess(final ReadIndexStatus status) {
        final long nowMs = Utils.monotonicMs();
        final List<ReadIndexState> states = status.getStates();
        final int taskCount = states.size();
        for (int i = 0; i < taskCount; i++) {
            final ReadIndexState task = states.get(i);
            final ReadIndexClosure done = task.getDone(); // stack copy
            if (done != null) {
                this.nodeMetrics.recordLatency("read-index", nowMs - task.getStartTimeMs());
                done.setResult(task.getIndex(), task.getRequestContext().get());
                done.run(Status.OK());
            }
        }
    }

    如果是響應成功,那麼會調用notifySuccess方法,會將status里封裝的ReadIndexState集合遍歷一遍,調用當中的run方法。

    這個run方法會調用到我們在multiGet中設置的run方法中
    RaftRawKVStore#multiGet

    public void multiGet(final List<byte[]> keys, final boolean readOnlySafe, final KVStoreClosure closure) {
        if (!readOnlySafe) {
            this.kvStore.multiGet(keys, false, closure);
            return;
        }
        // KV 存儲實現線性一致讀
        // 調用 readIndex 方法,等待回調執行
        this.node.readIndex(BytesUtil.EMPTY_BYTES, new ReadIndexClosure() {
    
            @Override
            public void run(final Status status, final long index, final byte[] reqCtx) {
                //如果狀態返回成功,
                if (status.isOk()) {
                    RaftRawKVStore.this.kvStore.multiGet(keys, true, closure);
                    return;
                }
                //readIndex 讀取失敗嘗試應用鍵值讀操作申請任務於 Leader 節點的狀態機 KVStoreStateMachine
                RaftRawKVStore.this.readIndexExecutor.execute(() -> {
                    if (isLeader()) {
                        LOG.warn("Fail to [multiGet] with 'ReadIndex': {}, try to applying to the state machine.",
                                status);
                        // If 'read index' read fails, try to applying to the state machine at the leader node
                        applyOperation(KVOperation.createMultiGet(keys), closure);
                    } else {
                        LOG.warn("Fail to [multiGet] with 'ReadIndex': {}.", status);
                        // Client will retry to leader node
                        new KVClosureAdapter(closure, null).run(status);
                    }
                });
            }
        });

    這個run方法會調用RaftRawKVStore的multiGet從RocksDB中直接獲取數據。

    總結

    我們這篇文章從RheaKVStore的客戶端get方法一直講到,RheaKVStore服務端使用JRaft實現線性一致性讀,並講解了線性一致性讀是怎麼實現的,通過這個例子大家應該對線性一致性讀有了一個相對不錯的理解了。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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