分類: 3C資訊

  • 50行Python代碼實現視頻中物體顏色識別和跟蹤(必須以紅色為例)

    50行Python代碼實現視頻中物體顏色識別和跟蹤(必須以紅色為例)

    目前計算機視覺(CV)與自然語言處理(NLP)及語音識別並列為人工智能三大熱點方向,而計算機視覺中的對象檢測(objectdetection)應用非常廣泛,比如自動駕駛、視頻監控、工業質檢、醫療診斷等場景。

    目標檢測的根本任務就是將圖片或者視頻中感興趣的目標提取出來,目標的識別可以基於顏色、紋理、形狀。其中顏色屬性運用十分廣泛,也比較容易實現。下面就向大家分享一個我做的小實驗———通過OpenCV的Python接口來實現從視頻中進行顏色識別和跟蹤。

    下面就是我們完整的代碼實現(已調試運行):

    import numpy as np
    import cv2
    font = cv2.FONT_HERSHEY_SIMPLEX
    lower_green = np.array([35, 110, 106])  # 綠色範圍低閾值
    upper_green = np.array([77, 255, 255])  # 綠色範圍高閾值
    lower_red = np.array([0, 127, 128])  # 紅色範圍低閾值
    upper_red = np.array([10, 255, 255])  # 紅色範圍高閾值
    #需要更多顏色,可以去百度一下HSV閾值!
    # cap = cv2.VideoCapture('1.mp4')  # 打開視頻文件
    cap = cv2.VideoCapture(0)#打開USB攝像頭
    if (cap.isOpened()):  # 視頻打開成功
        flag = 1
    else:
        flag = 0
    num = 0
    if (flag):
        while (True):
            ret, frame = cap.read()  # 讀取一幀
           
            if ret == False:  # 讀取幀失敗
                break
            hsv_img = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
            mask_green = cv2.inRange(hsv_img, lower_green, upper_green)  # 根據顏色範圍刪選
            mask_red = cv2.inRange(hsv_img, lower_red, upper_red) 
     # 根據顏色範圍刪選
            mask_green = cv2.medianBlur(mask_green, 7)  # 中值濾波
            mask_red = cv2.medianBlur(mask_red, 7)  # 中值濾波
            mask = cv2.bitwise_or(mask_green, mask_red)
            mask_green, contours, hierarchy = cv2.findContours(mask_green, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
            mask_red, contours2, hierarchy2 = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    
            for cnt in contours:
                (x, y, w, h) = cv2.boundingRect(cnt)
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 255), 2)
                cv2.putText(frame, "Green", (x, y - 5), font, 0.7, (0, 255, 0), 2)
    
            for cnt2 in contours2:
                (x2, y2, w2, h2) = cv2.boundingRect(cnt2)
                cv2.rectangle(frame, (x2, y2), (x2 + w2, y2 + h2), (0, 255, 255), 2)
                cv2.putText(frame, "Red", (x2, y2 - 5), font, 0.7, (0, 0, 255), 2)
            num = num + 1
            cv2.imshow("dection", frame)
            cv2.imwrite("imgs/%d.jpg"%num, frame)
            if cv2.waitKey(20) & 0xFF == 27:
                break
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    如圖所示,我們將會檢測到紅色區域

    最終的效果圖:

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

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

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • Zabbix-(四)郵件、釘釘告警通知

    Zabbix-(四)郵件、釘釘告警通知

    Zabbix-(四)郵件、釘釘告警通知

    一.前言

    在之前的文章里,通過Zabbix對主機的磁盤、CPU以及內存進行了監控,並在首頁Dashboard里創建了監控圖形,但是只有當我們登錄到Zabbix后才能看到監控到的問題(Problem),因此在本篇文章里,將利用觸發器(Trigger),以及媒介(Media)等配置項,實現當觸發器觸發時,通過不同媒介,如:郵件、釘釘,發送動作(Action),實現實時通知告警功能。

    準備

    • Zabbix Server (Zabbix 4.4)
    • 在Zabbix中已配置一些監控項和觸發器(這些配置可以參考我的)

    二.安裝相關環境

    由於使用到腳本告警媒介,本文中通過調用Python腳本觸發告警,因此需要在Zabbix Server主機上安裝pip以及相關模塊。(這裏Python使用Centos7自帶的Python2.7.5)

    1. 安裝pip

      # yum install -y epel-release
      
      # yum install -y python-pip
    2. 安裝requests模塊

      # pip install requests

    三.配置告警媒介類型

    Zabbix默認自帶了2種報警媒介類型(Media Type)电子郵件以及短信,我們將修改电子郵件類型配置,並新建腳本類型和Webhook類型。希望通過腳本、Webhook告警媒介發送釘釘消息。

    注:Webhook告警媒介是Zabbix 4.4的新特性

    1. 修改电子郵件告警媒介

      點擊【管理】-【報警媒介類型】-【Email】

      修改Email配置,我這裏用的是Outlook郵箱,具體SMTP服務器可以參考。使用其他郵箱也可以去對應官網查詢SMTP配置。

      測試發送郵箱,點擊【測試】

      輸入收件人郵箱

      收到郵件

    2. 新增腳本告警媒介

      新建Python腳本告警媒介,用戶釘釘告警

      點擊【創建媒體類型】

      進行配置

      配置項
      * 名稱 Python腳本
      類型 腳本
      * 腳本名稱 pythonScript.py
      腳本參數(參數1) {ALERT.MESSAGE}
      腳本參數(參數2) {ALERT.SENDTO}
      腳本參數(參數3) {ALERT.SUBJECT}

      接下來新建Python腳本,Zabbix Server配置文件中可以配置告警腳本路徑,默認為 /usr/lib/zabbix/alertscripts

      # 查看告警腳本路徑
      # cat zabbix_server.conf | grep AlertScriptsPath

      編寫告警腳本

      # cd /usr/lib/zabbix/alertscripts
      # vim pythonScript.py

      腳本內容

      #!/usr/bin/env python
      #coding:utf-8
      
      import requests,json,sys,os,datetime
      
      # 釘釘機器人地址
      webhook="https://oapi.dingtalk.com/robot/send?access_token=your_dingding_robot_access_token"
      
      # 對應{ALERT.SENDTO}, Zabbix告警媒介配置界面第2個參數
      user=sys.argv[2]
      
      # 對應{ALERT.MESSAGE}, Zabbix告警媒介配置界面第1個參數
      text=sys.argv[1]
      data={
          "msgtype": "text",
          "text": {
              "content": text
          },
          "at": {
              "atMobiles": [
                  user
              ],
              "isAtAll": False
          }
      }
      headers = {'Content-Type': 'application/json'}
      x=requests.post(url=webhook,data=json.dumps(data),headers=headers)

      給腳本可執行權限

      # chmod uo+x /usr/lib/zabbix/alertscripts/pythonScript.py

      測試腳本

      釘釘收到消息

    3. 新增Webhook告警媒介

      配置項
      * 名稱 Webhook
      類型 Webhook
      參數: (名稱)
      user {ALERT.SENDTO}
      subject {ALERT.SUBJECT}
      message {ALERT.MESSAGE}

      腳本:

      try {
          Zabbix.Log(4, 'params= '+value);
      
          params = JSON.parse(value);
          req = new CurlHttpRequest();
          data = {};
          result = {};
      
          req.AddHeader('Content-Type: application/json');
      
          data.msgtype = "text";
          //   對應 message參數
          data.text = {"content" : params.message};
          //   對應 user參數
          data.at = {"atMobiles": [params.user], "isAtAll": "false"};
      
          //   釘釘機器人
          resp = req.Post('https://oapi.dingtalk.com/robot/send?access_token=your_access_token',
              JSON.stringify(data)
          );
      } catch (error) {
          result = {};
      }
      
      return JSON.stringify(result);

      測試Webhook

    四.為用戶添加告警媒介

    需要將新增的告警媒介添加給用戶

    點擊【用戶】-【告警媒介】

    將上述步驟添加的告警媒介(Python腳本、Webhoob、Email),進行添加(收件人根據告警媒介類型填寫郵箱手機號),嚴重性也根據需要勾選。

    五.配置動作

    完成上述配置完成后,需要創建動作(Action),將觸發器(Trigger)告警媒介(Media Type)進行關聯,一旦觸發器觸發,那麼Zabbix會執行動作,再去執行告警媒介。

    1. 添加動作

      點擊【配置】-【動作】-【創建動作】

    2. 配置【動作】相關信息

      配置項
      * 名稱 告警動作
      新的觸發條件 【觸發器】【等於】【Template Disk Free Size: 磁盤剩餘空間觸發器】

      操作步驟如下圖:

      群組選擇 ->Linux servers

      主機選擇 -> Template Disk Free Size 模板()

      勾選觸發器 -> 磁盤剩餘空間觸發器 ()

      勾選後點擊【選擇】

    3. 配置【操作】相關信息

      點擊【操作】

      先配置以下信息

      配置項
      * 默認操作步驟持續時間 1h(保持默認)
      默認標題 告警: {EVENT.NAME}
      消息內容 【磁盤空間不足告警】
      告警事件: {EVENT.DATE} {EVENT.TIME}
      告警問題: {EVENT.NAME}
      告警主機: {HOST.IP} {HOST.NAME}
      告警級別: {EVENT.SEVERITY}
      磁盤剩餘:{ITEM.VALUE}

      上述配置表格【默認標題】和【消息內容】值中形如{EVENT.NAME}的內容是Zabbix中的宏(Marco),宏是一個變量,例如 {HOST.IP} 表示告警主機的IP地址,Zabbix自帶的宏可以參考

      繼續配置操作

      點擊【新的】

      【操作類型】選擇發送消息,【發送到用戶】添加Admin

      【僅送到】根據需要選擇之前配置的,本文選擇Email和Python腳本(這裏只能單選或全選,所以需要先選擇一個,因此需要多次)

      添加完成後點擊【添加】

    六.測試

    向被監控主機拷貝或下載大文件,使其磁盤剩餘空間低於觸發器監控閾值,等待觸發器觸發問題,查看儀錶盤、郵件等。

    儀錶盤

    釘釘

    郵件

    七.參考文檔

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

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

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

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

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?

  • Dev 日誌 | 一次 Segmentation Fault 和 GCC Illegal Instruction 編譯問題排查

    Dev 日誌 | 一次 Segmentation Fault 和 GCC Illegal Instruction 編譯問題排查

    摘要

    筆者最近在重新整理和編譯 Nebula Graph 的第三方依賴,選出兩個比較有意思的問題給大家分享一下。

    Flex Segmentation Fault——Segmentation fault (core dumped)

    在編譯 Flex 過程中,遇到了 Segmentation fault:

    make[2]: Entering directory '/home/dutor/flex-2.6.4/src'
    ./stage1flex   -o stage1scan.c ./scan.l
    make[2]: *** [Makefile:1696: stage1scan.c] Segmentation fault (core dumped)

    使用 gdb 查看 coredump:

    Core was generated by `./stage1flex -o stage1scan.c ./scan.l'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  flexinit (argc=4, argv=0x7ffd25bea718) at main.c:976
    976             action_array[0] = '\0';
    (gdb) disas
    Dump of assembler code for function flexinit:
       0x0000556c1b1ae040 <+0>:     push   %r15
       0x0000556c1b1ae042 <+2>:     lea    0x140fd(%rip),%rax        # 0x556c1b1c2146
       ...
       0x0000556c1b1ae20f <+463>:   callq  0x556c1b1af460 <allocate_array> #這裏申請了buffer
       ...
    => 0x0000556c1b1ae24f <+527>:   movb   $0x0,(%rax) # 這裏向buffer[0]寫入一個字節,地址非法,掛掉了
       ...
    (gdb) disas allocate_array
    Dump of assembler code for function allocate_array:
       0x0000556c1b1af460 <+0>:     sub    $0x8,%rsp
       0x0000556c1b1af464 <+4>:     mov    %rsi,%rdx
       0x0000556c1b1af467 <+7>:     xor    %eax,%eax
       0x0000556c1b1af469 <+9>:     movslq %edi,%rsi
       0x0000556c1b1af46c <+12>:    xor    %edi,%edi
       0x0000556c1b1af46e <+14>:    callq  0x556c1b19a100 <reallocarray@plt> # 調用庫函數申請內存
       0x0000556c1b1af473 <+19>:    test   %eax,%eax # 判斷是否為 NULL
       0x0000556c1b1af475 <+21>:    je     0x556c1b1af47e <allocate_array+30># 跳轉至NULL錯誤處理
       0x0000556c1b1af477 <+23>:    cltq   # 將 eax 符號擴展至 rax,造成截斷
       0x0000556c1b1af479 <+25>:    add    $0x8,%rsp
       0x0000556c1b1af47d <+29>:    retq
       ...
    End of assembler dump.

    可以看到,問題出在了 allocate_array 函數。因為 reallocarray 返回指針,返回值應該使用 64 bit 寄存器rax,但 allocate_array 調用 reallocarray 之後,檢查的卻是 32 bit 的 eax,同時使用 cltq 指令將 eax 符號擴展 到 rax。原因只有一個:allocate_array 看到的 reallocarray 的原型,與 reallocarry 的實際定義不符。翻看編譯日誌,確實找到了 implicit declaration of function ‘reallocarray’ 相關的警告。configure 階段添加 CFLAGS=-D_GNU_SOURCE 即可解決此問題。

    注:此問題不是必現,但編譯/鏈接選項 -pie 和 內核參數 kernel.randomize_va_space 有助於復現。

    總結:

    • 隱式聲明的函數在 C 中,返回值被認為是 int
    • 關注編譯器告警,-Wall -Wextra 要打開,開發模式下最好打開 -Werror。

    GCC Illegal Instruction——internal compiler error: Illegal instruction

    前陣子,接到用戶反饋,在編譯 Nebula Graph 過程中遭遇了編譯器非法指令的錯誤,詳見(#978)[]

    錯誤信息大概是這樣的:

    Scanning dependencies of target base_obj_gch
    [ 0%] Generating Base.h.gch
    In file included from /opt/nebula/gcc/include/c++/8.2.0/chrono:40,
    from /opt/nebula/gcc/include/c++/8.2.0/thread:38,
    from /home/zkzy/nebula/nebula/src/common/base/Base.h:15:
    /opt/nebula/gcc/include/c++/8.2.0/limits:1599:7: internal compiler error: Illegal instruction
    min() _GLIBCXX_USE_NOEXCEPT { return FLT_MIN; }
    ^~~
    0xb48c5f crash_signal
    ../.././gcc/toplev.c:325
    Please submit a full bug report,
    with preprocessed source if appropriate.

    既然是 internal compiler error,想必是 g++ 本身使用了非法指令。為了定位具體的非法指令集及其所屬模塊,我們需要復現這個問題。幸運的是,下面的代碼片段就能觸發:

    #include <thread>
    int main() 
    {
        return 0;
    }

    非法指令一定會觸發 SIGILL,又因為 g++ 只是編譯器的入口,真正幹活的是 cc1plus。我們可以使用 gdb 來運行編譯命令,抓住子進程使用非法指令的第一現場:

    $ gdb --args /opt/nebula/gcc/bin/g++ test.cpp
    gdb> set follow-fork-mode child
    gdb> run
    Starting program: /opt/nebula/gcc/bin/g++ test.cpp
    [New process 31172]
    process 31172 is executing new program: /opt/nebula/gcc/libexec/gcc/x86_64-pc-linux-gnu/8.2.0/cc1plus
    Thread 2.1 "cc1plus" received signal SIGILL, Illegal instruction.
    [Switching to process 31172]
    0x00000000013aa0fb in __gmpn_mul_1 ()
    gdb> disas
    ...
    0x00000000013aa086 <+38>: mulx (%rsi),%r10,%r8
    ...

    Bingo!mulx 屬於 BMI2 指令集,報錯機器 CPU 不支持該指令集。
    仔細調查,引入該指令集的是 GCC 的依賴之一,GMP。默認情況下,GMP 會在 configure 階段探測當前機器的 CPU 具體類型,以期最大化利用 CPU 的擴展指令集,提升性能,但卻犧牲了二進制的可移植性。解決方法是,configure 之前,使用代碼目錄中的 configfsf.guess configfsf.sub 替換或者覆蓋默認的 config.guess 和 config.sub

    總結:

    • 某些依賴可能因為性能或者配置的原因,造成二進制的不兼容。
    • 缺省參數下,GCC 為了兼容性,不會使用較新的指令集。
    • 為了平衡兼容性和性能,你需要做一些額外的工作,比如像 glibc 那樣在運行時選擇和綁定某個具體實現。

    最後,如果你想嘗試編譯一下 Nebula 源代碼可參考以下方式:

    bash> git clone https://github.com/vesoft-inc/nebula.git
    bash> cd nebula && ./build_dep.sh N

    有問題請在 GitHub 或者微信公眾號上留言。

    附錄

    • Nebula Graph:一個開源的分佈式圖數據庫
    • GitHub:
    • 知乎:
    • 微博:

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

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

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

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

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

    小三通物流營運型態?

    ※快速運回,大陸空運推薦?

  • Feign 調用丟失Header的解決方案

    Feign 調用丟失Header的解決方案

    問題

    在 Spring Cloud 中 微服務之間的調用會用到Feign,但是在默認情況下,Feign 調用遠程服務存在Header請求頭丟失問題。

    解決方案

    首先需要寫一個 Feign請求攔截器,通過實現RequestInterceptor接口,完成對所有的Feign請求,傳遞請求頭和請求參數。

    Feign 請求攔截器

    public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
    
        private static final Logger logger = LoggerFactory.getLogger(FeignBasicAuthRequestInterceptor.class);
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                    .getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            Enumeration<String> headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String values = request.getHeader(name);
                    requestTemplate.header(name, values);
                }
            }
            Enumeration<String> bodyNames = request.getParameterNames();
            StringBuffer body =new StringBuffer();
            if (bodyNames != null) {
                while (bodyNames.hasMoreElements()) {
                    String name = bodyNames.nextElement();
                    String values = request.getParameter(name);
                    body.append(name).append("=").append(values).append("&");
                }
            }
            if(body.length()!=0) {
                body.deleteCharAt(body.length()-1);
                requestTemplate.body(body.toString());
                logger.info("feign interceptor body:{}",body.toString());
            }
        }
    }

    通過配置文件配置 讓 所有 FeignClient,來使用 FeignBasicAuthRequestInterceptor

    feign:
      client:
        config:
          default:
            connectTimeout: 5000
            readTimeout: 5000
            loggerLevel: basic
            requestInterceptors: com.leparts.config.FeignBasicAuthRequestInterceptor

    也可以配置讓 某個 FeignClient 來使用這個 FeignBasicAuthRequestInterceptor

    feign:
      client:
        config:
          xxxx: # 遠程服務名
            connectTimeout: 5000
            readTimeout: 5000
            loggerLevel: basic
            requestInterceptors: com.leparts.config.FeignBasicAuthRequestInterceptor

    經過測試,上面的解決方案可以正常的使用;但是出現了新的問題。

    在轉發Feign的請求頭的時候, 如果開啟了Hystrix, Hystrix的默認隔離策略是Thread(線程隔離策略), 因此轉發攔截器內是無法獲取到請求的請求頭信息的。

    可以修改默認隔離策略為信號量模式:

    hystrix.command.default.execution.isolation.strategy=SEMAPHORE

    但信號量模式不是官方推薦的隔離策略;另一個解決方法就是自定義Hystrix的隔離策略。

    自定義策略

    HystrixConcurrencyStrategy 是提供給開發者去自定義hystrix內部線程池及其隊列,還提供了包裝callable的方法,以及傳遞上下文變量的方法。所以可以繼承了HystrixConcurrencyStrategy,用來實現了自己的併發策略。

    @Component
    public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
    
        private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategy.class);
    
        private HystrixConcurrencyStrategy delegate;
    
        public FeignHystrixConcurrencyStrategy() {
            try {
                this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
                if (this.delegate instanceof FeignHystrixConcurrencyStrategy) {
                    // Welcome to singleton hell...
                    return;
                }
    
                HystrixCommandExecutionHook commandExecutionHook =
                        HystrixPlugins.getInstance().getCommandExecutionHook();
    
                HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
                HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
                HystrixPropertiesStrategy propertiesStrategy =
                        HystrixPlugins.getInstance().getPropertiesStrategy();
                this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
    
                HystrixPlugins.reset();
                HystrixPlugins instance = HystrixPlugins.getInstance();
                instance.registerConcurrencyStrategy(this);
                instance.registerCommandExecutionHook(commandExecutionHook);
                instance.registerEventNotifier(eventNotifier);
                instance.registerMetricsPublisher(metricsPublisher);
                instance.registerPropertiesStrategy(propertiesStrategy);
            } catch (Exception e) {
                log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
            }
        }
    
        private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
                                                     HystrixMetricsPublisher metricsPublisher,
                                                     HystrixPropertiesStrategy propertiesStrategy) {
            if (log.isDebugEnabled()) {
                log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
                        + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
                        + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
                log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
            }
        }
    
        @Override
        public <T> Callable<T> wrapCallable(Callable<T> callable) {
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            return new WrappedCallable<>(callable, requestAttributes);
        }
    
        @Override
        public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                                HystrixProperty<Integer> corePoolSize,
                                                HystrixProperty<Integer> maximumPoolSize,
                                                HystrixProperty<Integer> keepAliveTime,
                                                TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
                    unit, workQueue);
        }
    
        @Override
        public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                                HystrixThreadPoolProperties threadPoolProperties) {
            return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
        }
    
        @Override
        public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
            return this.delegate.getBlockingQueue(maxQueueSize);
        }
    
        @Override
        public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
            return this.delegate.getRequestVariable(rv);
        }
    
        static class WrappedCallable<T> implements Callable<T> {
            private final Callable<T> target;
            private final RequestAttributes requestAttributes;
    
            WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
                this.target = target;
                this.requestAttributes = requestAttributes;
            }
    
            @Override
            public T call() throws Exception {
                try {
                    RequestContextHolder.setRequestAttributes(requestAttributes);
                    return target.call();
                } finally {
                    RequestContextHolder.resetRequestAttributes();
                }
            }
        }
    }

    致此,Feign調用丟失請求頭的問題就解決的了 。

    參考

    https://blog.csdn.net/zl1zl2zl3/article/details/79084368
    https://cloud.spring.io/spring-cloud-static/spring-cloud-openfeign/2.2.0.RC2/reference/html/

    歡迎掃碼或微信搜索公眾號《程序員果果》關注我,關注有驚喜~

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

    【其他文章推薦】

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

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

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

    台灣寄大陸海運貨物規則及重量限制?

    大陸寄台灣海運費用試算一覽表

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

  • Kafka needs no Keeper(關於KIP-500的討論)

    Kafka needs no Keeper(關於KIP-500的討論)

    寫在前面的

    最近看了Kafka Summit上的這個分享,覺得名字很霸氣,標題直接沿用了。這個分享源於社區的,大體的意思今後Apache Kafka不再需要ZooKeeper。整個分享大約40幾分鐘。完整看下來感覺乾貨很多,這裏特意總結出來。如果你把這個分享看做是《三國志》的話,那麼姑且就把我的這篇看做是裴松之注吧:)

    客戶端演進

    首先,社區committer給出了Kafka Java客戶端移除ZooKeeper依賴的演進過程。下面兩張圖總結了0.8.x版本和0.11.x版本(是否真的是從0.11版本開始的變化並不重要)及以後的功能變遷:在Kafka 0.8時代,Kafka有3個客戶端,分別是Producer、Consumer和Admin Tool。其中Producer負責向Kafka寫消息,Consumer負責從Kafka讀消息,而Admin Tool執行各種運維任務,比如創建或刪除主題等。其中Consumer的位移數據保存在ZooKeeper上,因此Consumer端的位移提交和位移獲取操作都需要訪問ZooKeeper。另外Admin Tool執行運維操作也要訪問ZooKeeper,比如在對應的ZooKeeper znode上創建一個臨時節點,然後由預定義的Watch觸發相應的處理邏輯。

    後面隨着Kafka的演進,社區引入了__consumer_offsets位移主題,同時定義了OffsetFetch和OffsetCommit等新的RPC協議,這樣Consumer的位移提交和位移獲取操作全部轉移到與位移主題進行交互,避免了對ZooKeeper的訪問。同時社區引入了新的運維工具AdminClient以及相應的CreateTopics、DeleteTopics、AlterConfigs等RPC協議,替換了原先的Admin Tool,這樣創建和刪除主題這樣的運維操作也完全移動Kafka這一端來做,就像下面右邊這張圖展示的:

    至此, Kafka的3個客戶端基本上都不需要和ZooKeeper交互了。應該說移除ZooKeeper的工作完成了大部分,但依然還有一部分工作要在ZooKeeper的幫助下完成,即Consumer的Rebalance操作。在0.8時代,Consumer Group的管理是交由ZooKeeper完成的,包括組成員的管理和訂閱分區的分配。這個設計在新版Consumer中也得到了修正。全部的Group管理操作交由Kafka Broker端新引入的Coordinator組件來完成。要完成這些工作,Broker端新增了很多RPC協議,比如JoinGroup、SyncGroup、Heartbeat、LeaveGroup等。

      

    此時,Kafka的Java客戶端除了AdminClient還有一點要依賴ZooKeeper之外,所有其他的組件全部擺脫了對ZooKeeper的依賴。

    之後,社區引入了Kafka安全層,實現了對用戶的認證和授權。這個額外的安全層也是不需要訪問ZooKeeper的,因此之前依賴ZooKeeper的客戶端是無法“享用”這個安全層。一旦啟用,新版Clients都需要首先接入這一層並通過審核之後才能訪問到Broker,如下圖所示:

    這麼做的好處在於統一了Clients訪問Broker的模式,即定義RPC協議,比如我們熟知的PRODUCE協議、FETCH協議、METADATA協議、CreateTopics協議等。如果後面需要實現更多的功能,社區只需要定義新的RPC協議即可。同時新引入的安全層負責對這套RPC協議進行安全校驗,統一了訪問模式。另外這些協議都是版本化的(versioned),因此能夠獨立地進行演進,同時也兼顧了兼容性方面的考量。

    Broker間交互

    說完了Clients端,我們說下Broker端的現狀。目前,應該說Kafka Broker端對ZooKeeper是重度依賴的,主要表現在以下幾個方面:

    • Broker註冊管理
    • ACL安全層配置管理
    • 動態參數管理
    • 副本ISR管理
    • Controller選舉

    我們拿一張圖來說明,圖中有4個Broker節點和一個ZooKeeper,左上角的Broker充當Controller的角色。當前,所有的Broker啟動后都必須維持與ZooKeeper的會話。Kafka依賴於這個會話實現Broker端的註冊,而且Kafka集群中的所有配置信息、副本信息、主題信息也都保存在ZooKeeper上。最後Controller與集群中每個Broker都維持了一個TCP長連接用於向這些Broker發送RPC請求。當前的Controller RPC類型主要有3大類:

    • LeaderAndIsr:主要用於向集群廣播主題分區Leader和ISR的變更情況,比如對應的Broker應該是特定分區的Leader還是Follower
    • StopReplica:向集群廣播執行停止副本的命令
    • UpdateMetadata:向集群廣播執行變更元數據信息的命令

    圖中還新增了一個AlterISR RPC,這是KIP-497要實現的新RPC協議。現階段Kafka各個主題的ISR信息全部保存在ZooKeeper中。如果後續要捨棄ZooKeeper,必須要將這些信息從ZooKeeper中移出來,放在了Controller一端來做。同時還要在程序層面支持對ISR的管理。因此社區計劃在KIP-497上增加AlterISR協議。對了,還要提一句,當前Controller的選舉也是依靠ZooKeeper完成的。

    所以後面Broker端的演進可能和Clients端的路線差不多:首先是把Broker與ZooKeeper的交互全部幹掉,只讓Controller與ZooKeeper進行交互,而其他所有Broker都只與Controller交互,如下圖所示:

     

    看上去這種演進路線社區已經走得輕車熟路了,但實際上還有遺留了一些問題需要解決。

    Broker Liveness

    首先就是Broker的liveness問題,即Kafka如何判斷一個Broker到底是否存活?在目前的設計中,Broker的生存性監測完全依賴於與ZooKeeper之間的會話。一旦會話超時或斷開Controller自動觸發ZooKeeper端的Watch來移除該Broker,並對其上的分區做善後處理。如果移除了ZooKeeper,Kafka應該採用什麼機制來判斷Broker的生存性是一個問題。

    Network Partition

    如何防範網絡分區也是一個需要討論的話題。當前可能出現的Network Partition有4種:1、單個Broker完全與集群隔離;2、Broker間無法通訊;3、Broker與ZooKeeper無法通訊;4、Broker與Controller無法通訊。下面4張圖分別展示了這4種情況:

     

    我們分別討論下。首先是第一種情況,單Broker與集群其他Broker隔離,這其實並不算太嚴重的問題。當前的設計已然能夠保證很好地應對此種情況。一旦Broker被隔離,Controller會將其從集群中摘除,雖然可用性降低了,但是整個集群的一致性依然能夠得到保證。第二種情況是Broker間無法通訊,可能的後果是消息的備份機制無法執行,Kafka要收縮ISR,依然是可用性上的降低,但是一致性狀態並沒有被破壞。情況三是Broker無法與ZooKeeper通訊。Broker能正常運轉,它只是無法與ZooKeeper進行通訊。此時我們說該Broker處於殭屍狀態,即所謂的Zoobie狀態。因Zoobie狀態引入的一致性bug社區jira中一直沒有斷過,社區這幾年也一直在修正這方面的問題,主要對抗的機制就是fencing。比如leader epoch等。最後一類情況是Broker無法與Controller通訊,那麼所有的元數據更新通道被堵死,即使這個Broker依然是healthy的,但是它保存的元數據信息可能是非常過期的。這樣連接該Broker的客戶端可能會看到各種非常古怪的問題。之前在知乎上回答過類似的問題:4。目前,社區對這種情況並沒有太好的解決辦法,主要的原因是Broker的liveness完全交由ZooKeeper來做的。一旦Broker與ZooKeeper之間的交互沒有問題,其他原因導致的liveness問題就無法徹底規避。

    第四類Network Partition引入了一個經典的場景:元數據不一致。目前每個Broker都緩存了一份集群的元數據信息,這份數據是異步更新的。當第四類Partition發生時,Broker端緩存的元數據信息必然與Controller的不同步,從而造成各種各樣的問題。

    下面簡要介紹一下元數據更新的過程。主要的流程就是Controller啟動時會同步地從ZooKeeper上拉取集群全量的元數據信息,之後再以異步的方式同步給其他Broker。其他Broker與Controller之間的同步往往有一個時間差,也就是說可能Clients訪問的元數據並不是最新的。我個人認為現在社區很多flaky test failure都是因為這個原因導致的。 事實上,實際使用過程中有很多場景是Broker端的元數據與Controller端永遠不同步。通常情況下如果我們不重啟Broker的話,那麼這個Broker上的元數據將永遠“錯誤”下去。好在社區還給出了一個最後的“大招”: 登錄到ZooKeeper SHELL,手動執行rmr /controller,強迫Controller重選舉,然後重新加載元數據,並給所有Broker重刷一份。不過在實際生產環境,我懷疑是否有人真的要這麼干,畢竟代價不小,而且最關鍵的是這麼做依然可能存在兩個問題:1. 我們如何確保Controller和Broker的數據是一致的?2. 加載元數據的過程通常很慢。

    這裏詳細說說第二點,即加載元數據的性能問題。總體來說,加載元數據是一個O(N)時間複雜度的過程,這裏的N就是你集群中總的分區數。考慮到Controller從ZooKeeper加載之後還要推給其他的Broker,那麼做這件事的總的時間複雜度就是O(N * M),其中M是集群中Broker的數量。可以想見,當M和N都很大時,在集群中廣播元數據不是一個很快的過程。

    Metadata as an Event Log

    Okay,鑒於以上所提到的所有問題,當Kafka拋棄了ZooKeeper之後,社區應該如何解決它們呢?總體的思路就是Metadata as an Event Log + Controller quorum。我們先說metadata as an event log。如果你讀過Jay Kreps的《I ️Logs》,你應該有感觸,整個Kafka的架構其實都是構建在Log上的。每個topic的分區本質上就是一個Commit Log,但元數據信息的保存卻不是Log形式。在現有的架構設計中你基本上可以認為元數據的數據結構是KV形式的。這一次,社區採用了與消息相同的數據保存方式,即將元數據作為Log的方式保存起來,如下錶所示:

     

    這樣做的好處在於每次元數據的變更都被當做是一條消息保存在Log中,而這個Log可以被視作是一個普通的Kafka主題被備份到多台Broker上。Log的一個好處在於它有清晰的前後順序關係,即每個事件發生的時間是可以排序的,配合以恰當的處理邏輯,我們就能保證對元數據變更的處理是按照變更發生時間順序處理,不出現亂序的情形。另外Log機制還有一個好處是,在Broker間同步元數據時,我們可以選擇同步增量數據(delta),而非全量狀態。現在Kafka Broker間同步元數據都是全量狀態同步的。前面說過了,當集群分區數很大時,這個開銷是很可觀的。如果我們能夠只同步增量狀態,勢必能極大地降低同步成本。最後一個好處是,我們可以很容易地量化元數據同步的進度,因為對Log的消費有位移數據,因此通過監控Log Lag就能算出當前同步的進度或是落後的進度。

    採用Log機制后,其他Broker像是一個普通的Consumer,從Controller拉取元數據變更消息或事件。由於每個Broker都是一個Consumer,所以它們會維護自己的消費位移,就像下面這張圖一樣:

     這種設計下,Controller所在的Broker必須要承擔起所有元數據topic的管理工作,包括創建topic、管理topic分區的leader以及為每個元數據變更創建相應的事件等。既然社區選擇和__consumer_offsets類似的處理方式,一個很自然的問題在於這個元數據topic的管理是否能夠復用Kafka現有的副本機制?答案是:不可行。理由是現有的副本機制依賴於Controller,因此Kafka沒法依靠現有的副本機制來實現Controller——按照我們的俗語來說,這有點雞生蛋、蛋生雞的問題,屬於典型的循環依賴。為了實現這個,Kafka需要一套leader選舉協議,而這套協議或算法是不依賴於Controller的,即它是一個自管理的集群quorum(抱歉,在分佈式領域內,特別是分佈式共識算法領域中,針對quorum的恰當翻譯我目前還未找到,因此直接使用quorum原詞了)。最終社區決定採用Raft來實現這組quorum。這就是上面我們提到的第二個解決思路:Controller quorum。

    Controller Quorum

    與藉助Controller幫忙選擇Leader不同,Raft是讓自己的節點自行選擇Leader並最終令所有節點達成共識——對選擇Controller而言,這是一個很好的特性。其實Kafka現有的備份機制與Raft已經很接近了,下錶羅列了一下它們的異同:

     一眼掃過去,其實Kafka的備份機制和Raft很類似,比如Kafka中的offset其實就是Raft中的index,epoch對應於term。當然Raft中採用的半數機制來確保消息被提交以及Leader選舉,而Kafka設計了ISR機制來實現這兩點。總體來說,社區認為只需要對備份機製做一些小改動就應該可以很容易地切換到Raft-based算法。

    下面這張圖展示Controller quorum可能更加直觀:

    整個controller quorum類似於一個小的集群。和ZooKeeper類似,這個quorum通常是3台或5台機器,不需要讓Kafka中的每個Broker都自動稱為這個quorum中的一個節點。該quorum裏面有一個Leader負責處理客戶端發來的讀寫請求,這個Leader就是Kafka中的active controller。根據ZooKeeper的Zab協議,leader處理所有的寫請求,而follower是可以處理讀請求的。當寫請求發送給follower后,follower會將該請求轉發給leader處理。不過我猜Kafka應該不會這樣實現,它應該只會讓leader(即active controller)處理所有的讀寫請求,而客戶端(也就是其他Broker)壓根就不會發送讀寫請求給follower。在這一點上,這種設計和現有的Kafka請求處理機制是一致的。

    現在還需要解決一個問題,即Leader是怎麼被選出來的?既然是Raft-based,那麼採用的也是Raft算法中的Leader選舉策略。讓Raft選出的Leader稱為active controller。網上有很多關於Raft選主的文章,這裏就不在贅述了,有興趣的可以讀一讀Raft的論文:《In Search of an Understandable Consensus Algorithm(Extended Version)》。

    這套Raft quorum的一個好處在於它天然提供了低延時的failover,因此leader的切換會非常的迅速和及時,因為理論上不再有元數據加載的過程了,所有的元數據現在都同步保存follower節點的內存中,它已經有其他Broker需要拉取的所有元數據信息了!更酷的是,它避免了現在機制中一旦Controller切換要全量拉取元數據的低效行為,Broker無需重新拉取之前已經“消費”的元數據變更消息,它只需要從新Leader繼續“消費”即可。

    另一個好處在於:採用了這套機制后,Kafka可以做元數據的緩存了(metadata caching):即Broker能夠把元數據保存在磁盤上,同時就像剛才說的,Broker只需讀取它關心的那部分數據即可。還有,和現在snapshot機制類似,如果一個Broker保存的元數據落後Controller太多或者是一個全新的Broker,Kafka甚至可以像Raft那樣直接發送一個snapshot文件,快速令其追上進度。當然大多數情況下,Broker只需要拉取delta增量數據即可。

    Post KIP-500 Broker註冊

    當前Broker啟動之後會向ZooKeeper註冊自己的信息,比如自己的主機名、端口、監聽協議等數據。移除ZooKeeper之後,Broker的註冊機制也要發生變化:Broker需要向active controller發送心跳來進行註冊。Controller收集心跳中包含的Broker數據構建整個Kafka集群信息,如下圖所示:

     同時Controller也會對心跳進行響應,顯式地告知Broker它們是否被允許加入集群——如果不允許,則可能需要被隔離(fenced)。當然controller自己也可以對自己進行隔離。我們針對前面提到的隔離場景討論下KIP-500是怎麼應對的。

    Fencing

    首先是普通Broker與集群完全隔離的場景,比如該Broker無法與controller和其他Broker進行通信,但它依然可以和客戶端程序交互。此時,fencing機制就很簡單了,直接讓controller令其下線即可。這和現在依靠ZooKeeper會話機制維持Broker判活的機制是一模一樣的,沒有太大改進。

    第二種情況是Broker間的通訊中斷。此時消息無法在leader、follower間進行備份。但是對於元數據而言,我們不會看到數據不一致的情形,因為Broker依然可以和controller通訊,因此也不會有什麼問題。

    第三種情況是Broker與Controller的隔離。現有機制下這是個問題,但KIP-500之後,Controller僅僅將該Broker“踢出場”即可,不會造成元數據的不一致。

    最後一種情況是Broker與ZooKeeper的隔離, 既然ZooKeeper要被移除了,自然這也不是問題了。

    部署

    終於聊到KIP-500之後的Kafka運維了。下錶總結了KIP-500前後的部署情況對比:

    很簡單,現在任何時候部署和運維Kafka都要考慮對ZooKeeper的運維管理。在KIP-500之後我們只需要關心Kafka即可。

    Controller quorum共享模式

    如前所述,controller改成Raft quorum機制后,可能使用3或5台機器構成一個小的quorum。那麼一個很自然的問題是,這些Broker機器還能否用作他用,是唯一用作controller quorum還是和其他Broker一樣正常處理。社區對此也做了解釋:兩種都支持!

    如果你的Kafka集群資源很緊張,你可以使用共享controller模式(Shared Controller Mode),即充當controller quorum的Broker機器也能處理普通的客戶端請求;相反地,如果你的Kafka資源很充足,專屬controller模式(Separate Controller Mode)可能是更適合的,即在controller quorum中的Broker機器排它地用作Controller的選舉之用,不再對客戶端提供讀寫服務。這樣可以實現更好的資源隔離,適用於大集群。

    Roadmap

    最後說一下KIP-500的計劃。社區計劃分三步走:

    第一步是移除客戶端對ZooKeeper的依賴——這一步基本上已經完成了,除了目前AdminClient還有少量的API依賴ZooKeeper之外,其他客戶端應該說都不需要訪問ZooKeeper了;第二步是移除Broker端的ZooKeeper依賴:這主要包括移除Broker端需要訪問ZooKeeper的代碼,以及增加新的Broker端API,如前面所說的AlterISR等,最後是將對ZooKeeper的訪問全部集中在controller端;最後一步就是實現controller quorum,實現Raft-based的quorum負責controller的選舉。

    至於Kafka升級,如果從現有的Kafka直接升級到KIP-500之後的Kafka會比較困難,因此社區打算引入一個名為Bridge Release的中間過渡版本,如下圖所示:

    這個Bridge版本的特點在於所有對ZooKeeper的訪問都集中到了controller端,Broker訪問ZooKeeper的其他代碼都被移除了。 

    總結

    KIP-500應該說是最近幾年社區提出的最重磅的KIP改進了。它幾乎是顛覆了Kafka已有的使用模式,摒棄了之前重度依賴的Apache ZooKeeper。就我個人而言,我是很期待這個KIP,後續有最新消息我也會在一併同步出來。讓我們靜觀其變吧~~~

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

    【其他文章推薦】

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

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

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

    大陸寄台灣空運注意事項

    大陸海運台灣交貨時間多久?

    ※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

  • 前後端分離,我怎麼就選擇了 Spring Boot + Vue 技術棧?

    前後端分離,我怎麼就選擇了 Spring Boot + Vue 技術棧?

    前两天又有小夥伴私信松哥,問題還是職業規劃,Java 技術棧路線這種,實際上對於這一類問題我經常不太敢回答,每個人的情況都不太一樣,而小夥伴也很少詳細介紹自己的情況,大都是一兩句話就把問題拋出來了,啥情況都不了解,就要指出一個方向,這實在是太難了。

    因此今天我想從我學習 Spring Boot + Vue 這套技術棧的角度,來和大家聊一聊沒有人指導,我是如何一步一步建立起自己的技術體系的。

    線上大家看我經常寫文章,線下我其實比較宅,跟人交流比較少,我也很少問別人職業規劃或者技術規劃這些問題,因為這種學什麼的問題,我喜歡自己把握,我不太喜歡被別人牽着走。

    Rome was not built in a day,剛開始接觸 Spring Boot + Vue 時,我甚至都沒有一個明確的想法,只是覺得該學點什麼,不能讓時間浪費,沒有告訴我 Spring Boot 要火了,也沒有人告訴我 Vue 要超過 React 了,都是我自己一直在摸索摸索,一步一步,直到構建起這套技術大廈。

    Spring Boot

    先說說 Spring Boot 吧,三年前差不多也是這個時候,是我第一次接觸 Spring Boot ,那個時候我的正式身份還是一名 Android 工程師,那段時間在研究 Android7 的源碼,還寫了一些博客:

    但是那個時候 Android 的行情在慢慢下滑,而我剛畢業 1 年多,未來還有更加豐富的技術人生,我不願意這麼早就把技術棧定死,而且還定在一個行情日漸下滑的技術棧上。所以我打算學一點新的東西。

    Python、Go、前端 和 Java 都是備選的方向,但是最終還是選擇繼續做 Java,有三個原因:

    • 做 Java 當時可以在公司內部轉崗,做 Python 或者 Go 的話,可能就得換工作了,技術棧切換,一切從頭開始,當時心裏還是沒底,於是就選擇繼續做 Java
    • 剛好大學的時候也有 JavaEE 的底子,重新撿起來 JavaEE 相關的技術點倒也不是啥難事
    • 第三點也是最重要的一點,我一直希望能夠獨立接點私活,這樣有一天賺錢能夠不受工作地點的限制,基於這樣的初衷,我一直希望走全棧的路線,用 Python 和 Go 雖然也可以做企業級應用,但是在目前的技術環境下,這並不算是主流方案,主流方案依然是 Java ,雖然它被被多人吐槽

    基於以上三點,我決定還是走 Java 的方向吧。

    2016 年那會,CSDN 幾乎每個月送我一本技術圖書,10 月份的圖書我就和夢鴿美女要了一本 Spring Boot 相關的書,書到了之後,一直在忙各種事情沒時間看,到了當年 12 月份的時候,公司安排我去深圳出差,出差的話,每天下班后時間就比較充裕了,於是我就帶上了書,每天下班回到酒店,就開始搞 Spring Boot。

    一開始我就發現這玩意相比我大學時候搞得 XML 配置的 SSM 太好用了,還是 SSM 那套東西,但是有了自動化配置,不用再去寫讓人頭大的 XML 配置了,可以基於 Spring Boot 快速搞一個 SSM 應用出來。不過剛開始學的時候我還不知道 Spring Boot 在 Java 領域如此火爆,當我寫了幾篇博客之後,我發現每篇博客的閱讀量都暴漲,遠遠高於其他博客的閱讀,我隱隱約約感覺到這次稀里糊塗的技術棧切換,算是沒走錯路。

    不過老實說,Spring Boot 技術棧其實不算難,都是 SSM 那一套東西,只是多了自動化配置(當然,Spring Boot 也有不少自己的東西,不過整體上基於 SSM 這點應該沒啥爭議),我剛開始搞 Spring Boot 的時候,有時候會有一些東西看的雲里霧裡,後來發現問題出在 Spring + SpringMVC 上,好幾年不寫 JavaEE,這些東西有一點點生疏了,後來又花了一些時間把 SSM 這些東西系統過了一遍,然後再去看 Spring Boot 就順暢多了。

    所以有一些小夥伴問松哥能不能跳過 SSM 直接學習 Spring Boot,這個我不建議,大家在 Spring Boot 中見到的很多神奇的自動化配置大部分都是基於 Spring 現有功能實現的,要是不懂實現原理,你會發現 Spring Boot 用得時候雖然好用,但是出了問題,你就束手無策了。

    就這樣,跳入了 Spring Boot 的坑裡了。Spring Boot 學完沒多久,工作上,馬上要從 Android 切換到 JavaEE 了,亟需一個項目練練手,當時我的上司給我介紹了一個西藏大學的項目,我使用 Spring Boot+EasyUI 的技術棧花了不到一個禮拜做完了,從此就算是叩開了 JavaEE 的大門,那會是 2017 年。

    當時前端選擇 EasyUI 也是沒辦法,甲方催得緊,而我來不及搞其他的前端框架,當時只有 EasyUI 熟悉一些,不用花時間學,直接就能用,於是就選擇了 EasyUI,但是 EasyUI 太丑了,所以在做完西藏大學的項目后,我就一直思量着再整一個專業的前端框架,這樣以後再有私活,我就可以獨立做出來一個好看的後端管理系統了。

    就這樣,在綜合對比了 Vue、React 以及 Angular 之後,決定跳入 Vue 的坑。

    Vue

    前端其實還算接觸的比較早,最早的 jQuery Mobile,PhoneGap 上大學的時候就玩過,我的第一本 NodeJS 的書是在 2013 年買的,那個時候 NodeJS 還算是一個比較新的事物。當我還是一名 Android 工程師的時候,我就玩過 React 和 ReactNative,RN 是當時比較流行的一個跨平台解決方案。但是在我比較這三個技術棧的時候,我發現 Vue 更加好用,生態也更加豐富,而且大有超過 React 的架勢(當時 Vue 在 GitHub 上的 star 數還沒超過 React),於是我就選擇了 Vue。其實當時我心裏想,大不了學完 Vue 再學 React,反正我才剛畢業兩年多,沒必要這麼早就鎖定技術棧停止學習。

    Vue 的學習確實不費啥事,花了兩三天時間刷了一遍官網,然後就開始做項目,但是要去深入學習,又是一個漫長的過程了。

    Vue 有很多漂亮的 UI 庫,像 ElementUI 等都算是做的比較好的,這些東西只要會用其中一個,其他的就可以手到擒來。

    到 2018 年初,Spring Boot+Vue 技術棧基本上已經熟悉了,兩個開源項目 V 部落()和微人事()也受到小夥伴們的歡迎,常規的企業級應用可以一個人獨立完成了,5 月份的時候,經朋友介紹,接了哈爾濱工程大學一位老師的項目,毫無疑問我就使用了最擅長的 Spring Boot+Vue 技術棧來做了,前後端都是自己做,沒人扯皮,美滋滋。

    再後來,就是寫書(),業餘繼續搞點項目用 Spring Boot + Vue 來做,這些以前都和大家聊過我就不再多說了,業餘接點項目來做這塊倒是有一些經驗,以後和小夥伴們細聊。

    就這樣,沒有任何人的指引,我慢慢構建了 Spring Boot + Vue 這套技術體系,這個過程中,最大的學習經驗就是要寫博客,做筆記,寫博客不僅僅是記錄,也是總結提煉,在寫的過程中,融入自己的思考,加深對技術的理解。 掌握了這套技術棧之後,我覺得我離全棧又更近了一步,離賺錢不受工作地點的限制這個目標也更近一步了。

    結語

    有前輩大佬的指引,你可能走得快,自己摸索,走的踏實。其實從我第一天自學 Java 開始,基本上都是一直在摸索。大學時候一個 BUG 折騰兩三天才解決,但是一旦自己想明白解決了,以後類似的錯誤不會再犯,這是我的感受。

    好了,一點點學習經驗,和小夥伴們分享,要是覺得有啟發,歡迎轉發哦。

    掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 學習資源

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

    【其他文章推薦】

    ※專營大陸空運台灣貨物推薦

    台灣空運大陸一條龍服務

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

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

  • 020.掌握Pod-Pod基礎使用

    020.掌握Pod-Pod基礎使用

    一 Pod定義詳解

    1.1 完整Pod定義文件

      1 apiVersion: v1			#必選,版本號,例如v1,版本號必須可以用 kubectl api-versions 查詢到
      2 kind: Pod				#必選,Pod
      3 metadata:				#必選,元數據
      4   name: string			#必選,Pod名稱,需符合RFC 1035規範
      5   namespace: string			#必選,Pod所屬的命名空間,默認為"default"
      6   labels:				#自定義標籤
      7     - name: string			#自定義標籤名字
      8   annotations:			#自定義註釋列表
      9     - name: string
     10 spec:				#必選,Pod中容器的詳細定義
     11   containers:			#必選,Pod中容器列表
     12   - name: string			#必選,容器名稱,需符合RFC 1035規範
     13     image: string			#必選,容器的鏡像名稱
     14     imagePullPolicy: [ Always|Never|IfNotPresent ]	#獲取鏡像的策略,Alawys表示每次都嘗試下載鏡像,IfnotPresent表示優先使用本地鏡像,否則下載鏡像,Nerver表示僅使用本地鏡像
     15     command: [string]		#容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
     16     args: [string]			#容器的啟動命令參數列表
     17     workingDir: string		#容器的工作目錄
     18     volumeMounts:			#掛載到容器內部的存儲卷配置
     19     - name: string			#引用pod定義的共享存儲卷的名稱,需用volumes[]部分定義的的卷名
     20       mountPath: string		#存儲卷在容器內mount的絕對路徑,應少於512字符
     21       readOnly: boolean		#是否為只讀模式,默認為讀寫模式
     22     ports:				#需要暴露的端口庫號列表
     23     - name: string			#端口的名稱
     24       containerPort: int		#容器需要監聽的端口號
     25       hostPort: int		        #容器所在主機需要監聽的端口號,默認與Container相同
     26       protocol: string		#端口協議,支持TCP和UDP,默認TCP
     27     env:				#容器運行前需設置的環境變量列表
     28     - name: string			#環境變量名稱
     29       value: string		        #環境變量的值
     30     resources:			#資源限制和請求的設置
     31       limits:			#資源限制的設置
     32         cpu: string		        #CPU的限制,單位為core數,將用於docker run --cpu-shares參數
     33         memory: string		#內存限制,單位可以為Mib/Gib,將用於docker run --memory參數
     34       requests:			#資源請求的設置
     35         cpu: string		        #CPU請求,容器啟動的初始可用數量
     36         memory: string		#內存請求,容器啟動的初始可用數量
     37     livenessProbe:			#對Pod內各容器健康檢查的設置,當探測無響應幾次后將自動重啟該容器,檢查方法有exec、httpGet和tcpSocket,對一個容器只需設置其中一種方法即可
     38       exec:			        #對Pod容器內檢查方式設置為exec方式
     39         command: [string]		#exec方式需要制定的命令或腳本
     40       httpGet:			#對Pod內個容器健康檢查方法設置為HttpGet,需要制定Path、port
     41         path: string
     42         port: number
     43         host: string
     44         scheme: string
     45         HttpHeaders:
     46         - name: string
     47           value: string
     48       tcpSocket:			#對Pod內個容器健康檢查方式設置為tcpSocket方式
     49          port: number
     50        initialDelaySeconds: 0	#容器啟動完成后首次探測的時間,單位為秒
     51        timeoutSeconds: 0		#對容器健康檢查探測等待響應的超時時間,單位秒,默認1秒
     52        periodSeconds: 0		#對容器監控檢查的定期探測時間設置,單位秒,默認10秒一次
     53        successThreshold: 0
     54        failureThreshold: 0
     55        securityContext:
     56          privileged: false
     57     restartPolicy: [Always | Never | OnFailure]	#Pod的重啟策略,Always表示一旦不管以何種方式終止運行,kubelet都將重啟,OnFailure表示只有Pod以非0退出碼退出才重啟,Nerver表示不再重啟該Pod
     58     nodeSelector: obeject		#設置NodeSelector表示將該Pod調度到包含這個label的node上,以key:value的格式指定
     59     imagePullSecrets:		#Pull鏡像時使用的secret名稱,以key:secretkey格式指定
     60     - name: string
     61     hostNetwork: false		#是否使用主機網絡模式,默認為false,如果設置為true,表示使用宿主機網絡
     62     volumes:			#在該pod上定義共享存儲卷列表
     63     - name: string			#共享存儲卷名稱 (volumes類型有很多種)
     64       emptyDir: {}			#類型為emtyDir的存儲卷,與Pod同生命周期的一個臨時目錄。為空值
     65       hostPath: string		#類型為hostPath的存儲卷,表示掛載Pod所在宿主機的目錄
     66         path: string		#Pod所在宿主機的目錄,將被用於同期中mount的目錄
     67       secret:			#類型為secret的存儲卷,掛載集群與定義的secre對象到容器內部
     68         scretname: string
     69         items:
     70         - key: string
     71           path: string
     72       configMap:			#類型為configMap的存儲卷,掛載預定義的configMap對象到容器內部
     73         name: string
     74         items:
     75         - key: string
     76           path: string

    二 Pod的基本用法

    2.1 創建Pod


    Pod可以由1個或多個容器組合而成,通常對於緊耦合的兩個應用,應該組合成一個整體對外提供服務,則應該將這兩個打包為一個pod。

    屬於一個Pod的多個容器應用之間相互訪問只需要通過localhost即可通信,這一組容器被綁定在一個環境中。

      1 [root@k8smaster01 study]# vi frontend-localredis-pod.yaml
      2 apiVersion: v1
      3 kind: Pod
      4 metadata:
      5   name: redis-php
      6   label:
      7     name: redis-php
      8 spec:
      9   containers:
     10   - name: frontend
     11     image: kubeguide/guestbook-php-frontend:localredis
     12     ports:
     13     - containersPort: 80
     14   - name: redis-php
     15     image: kubeguide/redis-master
     16     ports:
     17     - containersPort: 6379
     18 
     19 [root@k8smaster01 study]# kubectl create -f frontend-localredis-pod.yaml
     20 


    2.2 查看Pod

      1 [root@k8smaster01 study]# kubectl get pods	                #READY為2/2,表示此Pod中運行了yaml定義的兩個容器
      2 NAME        READY   STATUS    RESTARTS   AGE
      3 redis-php   2/2     Running   0          7m45s
      4 [root@k8smaster01 study]# kubectl describe pod redis-php	#查看詳細信息
      5 


    三 靜態Pod

    3.1 靜態Pod概述


    靜態pod是由kubelet進行管理的僅存在於特定Node的Pod上,他們不能通過API Server進行管理,無法與ReplicationController、Deployment或者DaemonSet進行關聯,並且kubelet無法對他們進行健康檢查。靜態Pod總是由kubelet進行創建,並且總是在kubelet所在的Node上運行。

    創建靜態Pod有兩種方式:配置文件或者HTTP方式。

    3.2 配置文件方式創建

      1 [root@k8snode01 ~]# mkdir -p /etc/kubelet.d
      2 [root@k8snode01 ~]# vi /etc/kubelet.d/static-web.yaml
      3 apiVersion: v1
      4 kind: Pod
      5 metadata:
      6   name: static-web
      7   label:
      8     name: static-web
      9 spec:
     10   containers:
     11   - name: static-web
     12     image: nginx
     13     ports:
     14     - name: web
     15       containersPort: 80
     16 
     17 [root@k8snode01 ~]# vi /etc/systemd/system/kubelet.service
     18 ……
     19   --config=/etc/kubelet.d/ \·				#加入此參數
     20 ……
     21 [root@k8snode01 ~]# systemctl daemon-reload
     22 [root@k8snode01 ~]# systemctl restart kubelet.service	#重啟kubelet
     23 [root@k8snode01 ~]# docker ps				#查看創建的pod



    提示:由於靜態pod不能通過API Server進行管理,因此在Master節點執行刪除操作後會變為Pending狀態,且無法刪除。刪除該pod只能在其運行的node上,將定義POD的yaml刪除。

    3.3 HTTP方式


    通過設置kubelet的啟動參數–mainfest-url,會定期從該URL下載Pod的定義文件,並以.yaml或.json文件的格式進行解析,從而創建Pod。

    四 Pod容器共享Volume

    4.1 共享Volume


    在同一個Pod中的多個容器能夠共享Pod級別的存儲就Volume。Volume可以被定義為各種類型,多個容器各自進行掛載操作,將一個Volume掛載為容器內部需要的目錄。


    示例1:

    Pod級別設置Volume “app-logs”,同時Pod包含兩個容器,Tomcat向該Volume寫日誌,busybox讀取日誌文件。

      1 [root@k8smaster01 study]# vi pod-volume-applogs.yaml
      2 apiVersion: v1
      3 kind: Pod
      4 metadata:
      5   name: volume-pod
      6 spec:
      7   containers:
      8   - name: tomcat
      9     image: tomcat
     10     ports:
     11     - containerPort: 8080
     12     volumeMounts:
     13     - name: app-logs
     14       mountPath: /usr/local/tomcat/logs
     15   - name: logreader
     16     image: busybox
     17     command: ["sh","-c","tail -f /logs/catalina*.log"]
     18     volumeMounts:
     19     - name: app-logs
     20       mountPath: /logs
     21   volumes:
     22   - name: app-logs
     23     emptyDir: {}

    解釋:

    Volume名:app-logs;

    emptyDir:為Pod分配到Node的時候創建。無需指定宿主機的目錄文件,為Kubernetes自動分配的目錄。

      1 [root@k8smaster01 study]# kubectl create -f pod-volume-applogs.yaml	#創建
      2 [root@k8smaster01 study]# kubectl get pods				#查看
      3 [root@k8smaster01 study]# kubectl logs volume-pod -c busybox	#讀取log




      1 [root@k8smaster01 study]# kubectl exec -it volume-pod -c tomcat -- ls /usr/local/tomcat/logs
      2 catalina.2019-06-29.log      localhost_access_log.2019-06-29.txt
      3 host-manager.2019-06-29.log  manager.2019-06-29.log
      4 localhost.2019-06-29.log
      5 [root@k8smaster01 study]# kubectl exec -it volume-pod -c tomcat -- tail /usr/local/tomcat/logs/catalina.2019-06-29.log



    提示:通過tomcat容器可查看日誌,對比busybox通過共享Volume查看的日誌是否一致。

    五 Pod配置管理

    5.1 Pod配置概述


    應用部署的一個最佳實踐是將應用所需的配置信息與程序進行分離,使程序更加靈活。將相應的應用打包為鏡像,可以通過環境變量或者外掛volume的方式在創建容器的時候進行配置注入,從而實現更好的復用。

    Kubernetes提供一種統一的應用配置管理方案:ConfigMap。

    5.2 ConfigMap概述


    ConfigMap供容器使用的主要場景:

    • 生成容器內部的環境變量;
    • 設置容器的啟動命令的參數(需設置為環境變量);
    • 以volume的形式掛載為容器內部的文件或者目錄。


    ConfigMap以一個或多個key:value的形式定義。value可以是string也可以是一個文件內容,可以通過yaml配置文件或者通過kubectl create configmap 的方式創建configMap。

    5.3 創建ConfigMap資源對象——yaml方式

      1 [root@k8smaster01 study]# vi cm-appvars.yaml
      2 apiVersion: v1
      3 kind: ConfigMap
      4 metadata:
      5   name: cm-appvars
      6 data:
      7   apploglevel: info
      8   appdatadir: /var/data
      9 
     10 [root@k8smaster01 study]# kubectl create -f cm-appvars.yaml
     11 configmap/cm-appvars created
     12 [root@k8smaster01 study]# kubectl get configmaps
     13 NAME         DATA   AGE
     14 cm-appvars   2      8s
     15 [root@k8smaster01 study]# kubectl describe configmaps cm-appvars



      1 [root@k8smaster01 study]# kubectl get configmaps cm-appvars -o yaml


    5.4 創建ConfigMap資源對象——命令行方式


    語法1

      1 # kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source



    解釋:通過–from-file參數從文件中創建,可以指定key名稱,也可以制定多個key。

    語法2

      1 # kubectl create configmap NAME --from-file=config-files-dir



    解釋:通過–from-file參數從目錄中創建,該目錄下的每個配置文件名都被設置為key,文件的內容被設置為value。

    語法3

      1 # kubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2



    解釋:通過–from-literal參數從文本中創建,直接將指定的key#=value#創建為ConfigMap的內容。

    5.5 Pod使用ConfigMap


    容器應用使用ConfigMap有兩種方式:

    • 通過環境變量獲取ConfigMap中的內容;
    • 通過Volume掛載的方式將ConfigMap中的內容掛載為容器內容的文件或目錄。

      1 [root@k8smaster01 study]# vi cm-test-pod.yaml
      2 apiVersion: v1
      3 kind: Pod
      4 metadata:
      5   name: cm-test-pod
      6 spec:
      7   containers:
      8   - name: cm-test
      9     image: busybox
     10     command: ["/bin/sh","-c","env|grep APP"]	#容器里執行查看環境變量的命令
     11     env:
     12     - name: APPLOGLEVEL				#定義容器環境變量名稱
     13       valueFrom:
     14         configMapKeyRef:			#環境變量的值來自ConfigMap
     15           name: cm-appvars			#指定來自cm-appvars的ConfigMap
     16           key: apploglevel			#key為apploglevel
     17     - name: APPDATADIR
     18       valueFrom:
     19         configMapKeyRef:
     20           name: cm-appvars
     21           key: appdatadir
     22 
     23 [root@k8smaster01 study]# kubectl create -f cm-test-pod.yaml
     24 [root@k8smaster01 study]# kubectl get pods
     25 NAME          READY   STATUS      RESTARTS   AGE
     26 cm-test-pod   0/1     Completed   2          24s



    【掛載形式-待補充】

    5.6 ConfigMap限制


    • Configmap必須在pod創建之間創建;
    • ConfigMap受到namespace的限制,只有同一個命名空間下才能引用;
    • ConfigMap暫時無法配置配額;
    • 靜態的pod無法使用ConfigMap;
    • 在使用volumeMount掛載的時候,configMap基於items創建的文件會整體的將掛載數據卷的容器的目錄下的文件全部覆蓋。

    六 Pod獲取自身信息

    6.1 Downward API


    pod擁有唯一的名字、IP地址,並且處於某個Namespace中。pod的容器內獲取pod的信息科通過Downward API實現。具體有以下兩種方式:

    • 環境變量:用於單個變量,可以將pod信息和container信息注入容器內部;
    • volume掛載:將數組類信息生成為文件,掛載至容器內部。


    舉例1:通過Downward API將Pod的IP、名稱和所在的Namespace注入容器的環境變量。

      1 [root@k8smaster01 study]# vi dapi-test-pod.yaml
      2 apiVersion: v1
      3 kind: Pod
      4 metadata:
      5   name: dapi-test-pod
      6 spec:
      7   containers:
      8     - name: test-container
      9       image: busybox
     10       command: [ "/bin/sh", "-c", "env" ]
     11       env:
     12         - name: MY_POD_NAME
     13           valueFrom:
     14             fieldRef:
     15               fieldPath: metadata.name
     16         - name: MY_POD_NAMESPACE
     17           valueFrom:
     18             fieldRef:
     19               fieldPath: metadata.namespace
     20         - name: MY_POD_IP
     21           valueFrom:
     22             fieldRef:
     23               fieldPath: status.podIP
     24   restartPolicy: Never



    提示:Downward API提供如下變量:

    metadata.name:Pod的名稱,當Pod通過RC生成時,其名稱是RC隨機產生的唯一名稱;

    status.podIP:Pod的IP地址,POd的IP屬於狀態數據,而非元數據;

    metadata.namespace:Pod所在的namespace。

      1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod.yaml
      2 [root@k8smaster01 study]# kubectl logs dapi-test-pod | grep MY_POD
      3 MY_POD_NAMESPACE=default
      4 MY_POD_IP=172.30.240.4
      5 MY_POD_NAME=dapi-test-pod
      6 



    舉例2:通過Downward API將Container的自願請求和限制信息注入容器的環境變量。

      1 [root@k8smaster01 study]# vi dapi-test-pod-container-vars.yaml
      2 apiVersion: v1
      3 kind: Pod
      4 metadata:
      5   name: dapi-test-pod-container-vars
      6 spec:
      7   containers:
      8     - name: test-container
      9       image: busybox
     10       imagePullPolicy: Never
     11       command: [ "/bin/sh", "-c" ]
     12       args:
     13       - while true; do
     14           echo -en '\n';
     15           printenv MY_CPU_REQUEST MY_CPU_LIMIT;
     16           printenv MY_MEM_REQUEST MY_MEM_LIMIT;
     17           sleep 3600;
     18         done;
     19       resources:
     20         requests:
     21           memory: "32Mi"
     22           cpu: "125m"
     23         limits:
     24           memory: "64Mi"
     25           cpu: "250m"
     26       env:
     27         - name: MY_CPU_REQUEST
     28           valueFrom:
     29             resourceFieldRef:
     30               containerName: test-container
     31               resource: requests.cpu
     32         - name: MY_CPU_LIMIT
     33           valueFrom:
     34             resourceFieldRef:
     35               containerName: test-container
     36               resource: limits.cpu
     37         - name: MY_MEM_REQUEST
     38           valueFrom:
     39             resourceFieldRef:
     40               containerName: test-container
     41               resource: requests.memory
     42         - name: MY_MEM_LIMIT
     43           valueFrom:
     44             resourceFieldRef:
     45               containerName: test-container
     46               resource: limits.memory
     47   restartPolicy: Never



    提示:Downward API提供如下變量:

    requests.cpu:容器的CPU請求值;

    limits.cpu:容器的CPU限制值;

    requests.memory:容器的內存請求值;

    limits.memory:容器的內存限制值。

      1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod-container-vars.yaml
      2 [root@k8smaster01 study]# kubectl logs dapi-test-pod-container-vars
      3 1
      4 1
      5 33554432
      6 67108864



    舉例3:通過Downward API將Pod的Label、Annotation列表通過Volume掛載為容器內的一個文件。

      1 [root@k8smaster01 study]# vi dapi-test-pod-volume.yaml
      2 apiVersion: v1
      3 kind: Pod
      4 metadata:
      5   name: dapi-test-pod-volume
      6   labels:
      7     zone: us-est-coast
      8     cluster: test-cluster1
      9     rack: rack-22
     10   annotations:
     11     build: two
     12     builder: john-doe
     13 spec:
     14   containers:
     15     - name: test-container
     16       image: busybox
     17       imagePullPolicy: Never
     18       command: [ "/bin/sh", "-c" ]
     19       args:
     20       - while true; do
     21           if [[ -e /etc/labels ]]; then
     22             echo -en '\n\n'; cat /etc/labels; fi;
     23           if [[ -e /etc/annotations ]]; then
     24             echo -en '\n\n'; cat /etc/annotations; fi;
     25           sleep 3600;
     26         done;
     27       volumeMounts:
     28         - name: podinfo
     29           mountPath: /etc
     30           readOnly: false
     31   volumes:
     32     - name: podinfo
     33       downwardAPI:
     34         items:
     35           - path: "labels"
     36             fieldRef:
     37               fieldPath: metadata.labels
     38           - path: "annotations"
     39             fieldRef:
     40               fieldPath: metadata.annotations



    注意:Volume中的ddownwardAPI的items語法,將會以path的名稱生成文件。如上所示,會在容器內生產/etc/labels和/etc/annotations兩個文件,分別包含metadata.labels和metadata.annotations的全部Label。

      1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod-volume.yaml
      2 [root@k8smaster01 study]# kubectl logs dapi-test-pod-volume
      3 



    提示:DownwardAPI意義:

    在某些集群中,集群中的每個節點需要將自身的標識(ID)及進程綁定的IP地址等信息事先寫入配置文件中,進程啟動時讀取此類信息,然後發布到某個類似註冊服務中心。此時可通過DowanwardAPI,將一個預啟動腳本或Init Container,通過環境變量或文件方式獲取Pod自身的信息,然後寫入主程序配置文件中,最後啟動主程序。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • SpringBoot基本配置詳解

    SpringBoot基本配置詳解

    SpringBoot項目有一些基本的配置,比如啟動圖案(banner),比如默認配置文件application.properties,以及相關的默認配置項。

    示例項目代碼在:

    一、啟動圖案banner

    編寫banner.txt放入resources文件夾下,然後啟動項目即可修改默認圖案。

    關於banner的生成,可以去一些專門的網站。

    比如:https://www.bootschool.net/ascii

    二、配置文件application

    2.1 application.properties/yml

    resources下通常會默認生成一個application.properties文件,這個文件包含了SpringBoot項目的全局配置文件。裏面的配置項通常是這樣的:

    server.port=8080

    在這個文件里我們可以添加框架支持的配置項,比如項目端口號、JDBC連接的數據源、日誌級別等等。

    現在比較流行的是將properties文件改為yml文件。yml文件的格式yaml是這樣的:

    server:
        port: 8080

    yml和properties的作用是一樣的。而yml的好處是顯而易見的——更易寫易讀。

    屬性之間互相調用使用${name}:

    eknown:
        email: eknown@163.com
        uri: http://www.eknown.cn
        title: 'hello, link to ${eknown.uri} or email to ${eknown.email}'

    鏈接:

    2.2 多環境配置文件

    通常開發一個應用會有多個環境,常見如dev/prod,也會有test,甚至其他一些自定義的環境,SpringBoot支持配置文件的靈活切換。

    定義新配置文件需要遵循以下格式:application-{profile}.properties 或者application-{profile}.yml

    比如現在有dev和prod兩個環境,我需要在application.yml文件之外新建兩個文件:

    1. application-dev.yml

      server:
         port: 8080
    2. application-prod.yml

      server:
        port: 8081

    然後在application.yml中通過application.profiles.active={profile}指明啟用那個配置:

    application:
        profiles:
          active: dev

    除了在application.yml中指定配置文件外,還可以通過啟動命令指定:java -jar xxx.jar --spring.profiles.active=dev

    2.2 自定義配置項並獲取它

    主要介紹兩種方式,獲取單個配置項和獲取多個配置項。

    舉例:

    eknown:
        email: eknown@163.com
        uri: http://www.eknown.cn

    2.2.1 使用@Value註解獲取單個配置項

    @Value("${eknown.email}")
    private String email;
    
    @Value("${eknown.uri}")
    private String url;

    注意:使用@Value註解的時候,所在類必須被Spring容器管理,也就是被@Component、@Controller、@Service等註解定義的類。

    2.2.2 獲取多個配置項

    第一種,定義一個bean類,通過@Value獲取多個配置項:

    @Component
    public class MyConfigBean {
      
    }

    然後我們通過get方法來獲取這些值:

    @RestController
    public class BasicAction {
      
      @Autowired
      private MyConfigBean myConfigBean;
    
    }

    第二種,使用註解@ConfigurationProperties:

    @Component
    @ConfigurationProperties(perfix="eknown")
    public class MyConfigBean {
    
      private String email;
      private String uri;
    }

    這裏只需要通過prefix指定前綴即可,後面的值自動匹配。

    這裏我們還使用了@Component註解來讓spring容器管理這個MyConfigBean。

    此外,我們可以不需要引入@Component,轉而在Application啟動類上加上@EnableConfigurationProperties({MyConfigBean.class})來啟動這個配置。

    注意:我們這裡是從主配置文件,也就是SpringBoot默認的application-profile文件中獲取配置數據的。

    而從自定義的配置文件,比如test.yml這種形式中獲取配置項時,情況是有點不大一樣的。

    三、自定義配置文件

    上面介紹的配置文件都是springboot默認的application開頭的文件。如果要自定義一個配置文件呢,比如test.yml或test.properties,怎麼獲取其中的配置項呢?

    使用@PageResource註解即可。

    首先我們來看一下讀取自定義的properties文件里的內容:

    test.properties

    hello.time=2019.11.19
    hello.name=eknown

    定義Configuration類:

    @Configuration
    @PropertySource("classpath:test.properties")
    //@PropertySource("classpath:test.yml") // 注意,yml文件不能直接這樣寫,會讀不出數據
    @ConfigurationProperties(prefix = "hello")
    public class TestConfiguration {
        private String name;
        private String time;
    
        // hide get and set methods
    }
    

    測試一下:

    @RestController
    @RequestMapping(value = "test")
    public class TestAction {
    
        @Autowired
        private TestConfiguration testConfiguration;
    
        @GetMapping(value = "config")
        public String test() {
            return testConfiguration.getName() + "<br/>" + testConfiguration.getTime();
        }
    }

    如果將properties文件換成yml文件呢?

    我們嘗試一下,發現:

    讀不出數據?

    分析一下@PropertySource註解,發現其使用的PropertySourceFactory是DefaultPropertySourceFactory.

    這個類的源碼如下:

    public class DefaultPropertySourceFactory implements PropertySourceFactory {
        public DefaultPropertySourceFactory() {
        }
    
        public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
            return name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource);
        }
    }

    這個類只能處理properties文件,無法處理yml文件。所以我們需要自定義一個YmlSourceFactory。

    public class YamlSourceFactory extends DefaultPropertySourceFactory {
    
        @Override
        public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
            return new YamlPropertySourceLoader().load(resource.getResource().getFilename()
                    , resource.getResource()).get(0);
        }
    }

    然後定義test.yml文件的config類:

    @Configuration
    @PropertySource(value = "classpath:test.yml", encoding = "utf-8", factory = YamlSourceFactory.class)
    @ConfigurationProperties(prefix = "yml.hello")
    public class TestYamlConfiguration {
        private String name;
        private String time;
    
        // hide get and set methods
    }
    

    注:為了區分test.properties和test.yml,這裏的test.yml中的屬性以yml.hello開頭。

    編寫一下測試:

        @Autowired
        private TestYamlConfiguration ymlConfiguration;
    
        @GetMapping(value = "yml")
        public String testYml() {
            return "yml config: <br/>" + ymlConfiguration.getName() + "<br/>" + ymlConfiguration.getTime();
        }

    訪問:

    四、補充@ConfigurationProperties

    網上一些資料中,為配合使用@ConfigurationProperties,還使用了@EnableConfigurationProperties註解。

    經過測試發現:

    1. 從SpringBoot默認配置文件讀取配置信息,使用@ConfigurationProperties + @Component/@Configuration,或者@ConfigurationProperties + 在啟動類添加@EnableConfigurationProperties({class})。這兩種方式都能解決問題

    2. 從非默認配置文件讀取配置信息,需要利用@PropertySource註解。同樣兩種方式:

      2.1 @PropertySource + @ConfigurationProperties + @Component/@Configuration

      2.2 @PropertySource + @ConfigurationProperties + @Component/@Configuration + @EnableConfigurationProperties,第二種方式存在一個問題,即還是必須要使用@Component註解,如果不使用,則會導致讀取配置信息為null,但程序不會報錯;而如果採用了,則會導致bean類的set方法被執行兩次(也就是生成了兩個同樣類型的bean類)。這種方式不建議!

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

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

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

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

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

    ※專營大陸快遞台灣服務

    台灣快遞大陸的貨運公司有哪些呢?

  • Appium+python自動化(四十一)-Appium自動化測試框架綜合實踐 – 即將落下帷幕(超詳解)

    Appium+python自動化(四十一)-Appium自動化測試框架綜合實踐 – 即將落下帷幕(超詳解)

    1.簡介

      今天我們緊接着上一篇繼續分享Appium自動化測試框架綜合實踐 – 代碼實現。到今天為止,大功即將告成;框架所需要的代碼實現都基本完成。

    2.data數據封裝

    2.1使用背景

    在實際項目過程中,我們的數據可能是存儲在一個數據文件中,如txt,excel、csv文件類型。我們可以封裝一些方法來讀取文件中的數據來實現數據驅動。

    2.2案例

    將測試賬號存儲在account.csv文件,內容如下:

    account.csv

    hg2018

    hg2018

    hg2019

    zxw2019

    666

    222

    參考代碼

    2.3enumerate()簡介

    enumerate()是python的內置函數

    • enumerate在字典上是枚舉、列舉的意思
    • 對於一個可迭代的(iterable)/可遍歷的對象(如列表、字符串),enumerate將其組成一個索引序列,利用它可以同時獲得索引和值
    • enumerate多用於在for循環中得到計數。

    2.4enumerate()使用

    如果對一個列表,既要遍歷索引又要遍曆元素時,首先可以這樣寫:

    參考代碼
    list = ["", "", "一個", "測試","數據"]
    
    for i in range(len(list)):
    
        print(i,list[i])

    上述方法有些累贅,利用enumerate()會更加直接和優美:

    參考代碼
    list1 = ["", "", "一個", "測試","數據"]
    
    for index, item in enumerate(list1):
    
            print(index,item)

    3.數據讀取方法封裝

      數據讀取方法也屬於公共方法,這裏我們首先實現一下,然後將其封裝到裡邊即可。

    3.1數據讀取方法實現的參考代碼

    import csv
    
    
         def get_csv_data(csv_file,line):
    
            with open(csv_file, 'r', encoding='utf-8-sig') as file:
    
                reader=csv.reader(file)
    
                for index, row in enumerate(reader,1):
    
                    if index == line:
    
                        return row
    
     
    
        csv_file='../data/account.csv'
    
        data=get_csv_data(csv_file,3)
    
        print(data)

    3.2封裝

    將其封裝在公共方法中,在其他地方用到的時候,直接導入調用即可。

    4.utf-8與utf-8-sig兩種編碼格式的區別

    UTF-8以字節為編碼單元,它的字節順序在所有系統中都是一樣的,沒有字節序的問題,也因此它實際上並不需要BOM(“ByteOrder Mark”)。但是UTF-8 with BOM即utf-8-sig需要提供BOM。

    5.config文件配置

    各種配置文件都放在這個目錄下。

    5.1日誌文件配置 

    主要是一些日誌信息的配置。

    log.config

     參考代碼
    [loggers]
    keys=root,infoLogger
    
    [logger_root]
    level=DEBUG
    handlers=consoleHandler,fileHandler
    
    [logger_infoLogger]
    handlers=consoleHandler,fileHandler
    qualname=infoLogger
    propagate=0
    
    [handlers]
    keys=consoleHandler,fileHandler
    
    [handler_consoleHandler]
    class=StreamHandler
    level=INFO
    formatter=form02
    args=(sys.stdout,)
    
    [handler_fileHandler]
    class=FileHandler
    level=INFO
    formatter=form01
    args=('../logs/runlog.log', 'a')
    
    [formatters]
    keys=form01,form02
    
    [formatter_form01]
    format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
    
    [formatter_form02]
    format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

    6.測試用例封裝

    這裏宏哥舉例給小夥伴們演示:封裝註冊和登錄兩個測試用例。

    6.1測試用例執行開始結束操作封裝

    測試用例執行開始和結束的封裝,其他模塊用到直接導入,調用即可。

    myunit.py

    參考代碼
    # coding=utf-8
    # 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行
    
    # 2.註釋:包括記錄創建時間,創建人,項目名稱。
    '''
    Created on 2019-11-20
    @author: 北京-宏哥   QQ交流群:707699217
    Project:Appium自動化測試框架綜合實踐 - 代碼實現
    '''
    # 3.導入模塊
    import unittest
    from kyb_testProject.common.desired_caps import appium_desired
    import logging
    from time import sleep
    
    class StartEnd(unittest.TestCase):
        def setUp(self):
            logging.info('=====setUp====')
            self.driver=appium_desired()
    
        def tearDown(self):
            logging.info('====tearDown====')
            sleep(5)
            self.driver.close_app()

    6.2註冊用例

    開始註冊用例代碼邏輯的實現。

    test_register.py

    參考代碼
    # coding=utf-8
    # 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行
    
    # 2.註釋:包括記錄創建時間,創建人,項目名稱。
    '''
    Created on 2019-11-20
    @author: 北京-宏哥   QQ交流群:707699217
    Project:Appium自動化測試框架綜合實踐 - 代碼實現
    '''
    # 3.導入模塊
    from kyb_testProject.common.myunit import StartEnd
    from kyb_testProject.businessView.registerView import RegisterView
    import logging,random,unittest
    
    class RegisterTest(StartEnd):
        def test_user_register(self):
            logging.info('======test_user_register======')
            r=RegisterView(self.driver)
    
            username = 'bjhg2019' + 'fly' + str(random.randint(1000, 9000))
            password = 'bjhg2020' + str(random.randint(1000, 9000))
            email = 'bjhg' + str(random.randint(1000, 9000)) + '@163.com'
    
            self.assertTrue(r.register_action(username,password,email))
    
    if __name__ == '__main__':
        unittest.main()

    6.3登錄用例

    開始登錄用例代碼邏輯的實現。

    test_login.py

    參考代碼
    # coding=utf-8
    # 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行
    
    # 2.註釋:包括記錄創建時間,創建人,項目名稱。
    '''
    Created on 2019-11-13
    @author: 北京-宏哥   QQ交流群:707699217
    Project:Appium自動化測試框架綜合實踐 - 代碼實現
    '''
    # 3.導入模塊
    from kyb_testProject.common.myunit import StartEnd
    from kyb_testProject.businessView.loginView import LoginView
    import unittest
    import logging
    
    class TestLogin(StartEnd):
        csv_file='../data/account.csv'
    
        @unittest.skip('test_login_zxw2018')
        def test_login_zxw2018(self):
            logging.info('======test_login_zxw2018=====')
            l=LoginView(self.driver)
            data=l.get_csv_data(self.csv_file,2)
    
            l.login_action(data[0],data[1])
            self.assertTrue(l.check_loginStatus())
    
        # @unittest.skip('skip test_login_zxw2017')
        def test_login_zxw2017(self):
            logging.info('======test_login_zxw2017=====')
            l=LoginView(self.driver)
            data = l.get_csv_data(self.csv_file, 1)
    
            l.login_action(data[0], data[1])
            self.assertTrue(l.check_loginStatus())
    
        @unittest.skip('test_login_error')
        def test_login_error(self):
            logging.info('======test_login_error=====')
            l = LoginView(self.driver)
            data = l.get_csv_data(self.csv_file, 3)
    
            l.login_action(data[0], data[1])
            self.assertTrue(l.check_loginStatus(),msg='login fail!')
    
    if __name__ == '__main__':
        unittest.main()

    7.小結

    到此,Appium自動化測試框架就差下一篇就全部完成了,聰明的你都懂了嗎???嘿嘿!慢慢地來吧。

    下節預告

    下一篇,講解執行測試用例,生成測試報告,以及自動化平台,請關注宏哥,敬請期待!!!

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

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

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

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

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 一文帶你深入了解 Redis 的持久化方式及其原理

    一文帶你深入了解 Redis 的持久化方式及其原理

    Redis 提供了兩種持久化方式,一種是基於快照形式的 RDB,另一種是基於日誌形式的 AOF,每種方式都有自己的優缺點,本文將介紹 Redis 這兩種持久化方式,希望閱讀本文後你對 Redis 的這兩種方式有更加全面、清晰的認識。

    RDB 快照方式持久化

    先從 RDB 快照方式聊起,RDB 是 Redis 默認開啟的持久化方式,並不需要我們單獨開啟,先來看看跟 RDB 相關的配置信息:

    ################################ SNAPSHOTTING  ################################
    #
    # Save the DB on disk:
    #
    #   save <seconds> <changes>
    #
    #   Will save the DB if both the given number of seconds and the given
    #   number of write operations against the DB occurred.
    #
    #   In the example below the behaviour will be to save:
    #   after 900 sec (15 min) if at least 1 key changed
    #   after 300 sec (5 min) if at least 10 keys changed
    #   after 60 sec if at least 10000 keys changed
    #   save ""
    # 自動生成快照的觸發機制 中間的是時間,單位秒,後面的是變更數據 60 秒變更 10000 條數據則自動生成快照
    save 900 1
    save 300 10
    save 60 10000
    
    # 生成快照失敗時,主線程是否停止寫入
    stop-writes-on-bgsave-error yes
    
    # 是否採用壓縮算法存儲
    rdbcompression yes
    
    # 數據恢復時是否檢測 RDB文件有效性
    rdbchecksum yes
    
    # The filename where to dump the DB
    # RDB 快照生成的文件名稱
    dbfilename dump.rdb
    
    # 快照生成的路徑 AOF 也是存放在這個路徑下面
    dir .

    關於 RDB 相關配置信息不多,需要我們調整的就更少了,我們只需要根據自己的業務量修改生成快照的機制和文件存放路徑即可。

    RDB 有兩種持久化方式:手動觸發自動觸發手動觸發使用以下兩個命令:

    • save:會阻塞當前 Redis 服務器響應其他命令,直到 RDB 快照生成完成為止,對於內存 比較大的實例會造成長時間阻塞,所以線上環境不建議使用

    • bgsave:Redis 主進程會 fork 一個子進程,RDB 快照生成有子進程來負責,完成之後,子進程自動結束,bgsave 只會在 fork 子進程的時候短暫的阻塞,這個過程是非常短的,所以推薦使用該命令來手動觸發

    除了執行命令手動觸發之外,Redis 內部還存在自動觸發 RDB 的持久化機制,在以下幾種情況下 Redis 會自動觸發 RDB 持久化

    • 在配置中配置了 save 相關配置信息,如我們上面配置文件中的 save 60 10000 ,也可以把它歸類為“save m n”格式的配置,表示 m 秒內數據集存在 n 次修改時,會自動觸發 bgsave。

    • 在主從情況下,如果從節點執行全量複製操作,主節點自動執行 bgsave 生成 RDB 文件併發送給從節點

    • 執行 debug reload 命令重新加載 Redis 時,也會自動觸發 save 操作

    • 默認情況下執行 shutdown 命令時,如果沒有開啟 AOF 持久化功能則自動執行 bgsave

    上面就是 RDB 持久化的方式,可以看出 save 命令使用的比較少,大多數情況下使用的都是 bgsave 命令,所以這個 bgsave 命令還是有一些東西,那接下來我們就一起看看 bgsave 背後的原理,先從流程圖開始入手:

    bgsave 命令大概有以下幾個步驟:

    • 1、執行 bgsave 命令,Redis 主進程判斷當前是否存在正在執行的 RDB/AOF 子進程,如果存在, bgsave 命令直接返回不在往下執行。
    • 2、父進程執行 fork 操作創建子進程,fork 操作過程中父進程會阻塞,fork 完成後父進程將不在阻塞可以接受其他命令。
    • 3、子進程創建新的 RDB 文件,基於父進程當前內存數據生成臨時快照文件,完成後用新的 RDB 文件替換原有的 RDB 文件,並且給父進程發送 RDB 快照生成完畢通知

    上面就是 bgsave 命令背後的一些內容,RDB 的內容就差不多了,我們一起來總結 RDB 持久化的優缺點,RDB 方式的優點

    • RDB 快照是某一時刻 Redis 節點內存數據,非常適合做備份,上傳到遠程服務器或者文件系統中,用於容災備份
    • 數據恢復時 RDB 要遠遠快於 AOF

    有優點同樣存在缺點,RDB 的缺點有

    • RDB 持久化方式數據沒辦法做到實時持久化/秒級持久化。我們已經知道了 bgsave 命令每次運行都要執行 fork 操作創建子進程,屬於重量級操作,頻繁執行成本過高。
    • RDB 文件使用特定二進制格式保存,Redis 版本演進過程中有多個格式 的 RDB 版本,存在老版本 Redis 服務無法兼容新版 RDB 格式的問題

    如果我們對數據要求比較高,每一秒的數據都不能丟,RDB 持久化方式肯定是不能夠滿足要求的,那 Redis 有沒有辦法滿足呢,答案是有的,那就是接下來的 AOF 持久化方式

    AOF 持久化方式

    Redis 默認並沒有開啟 AOF 持久化方式,需要我們自行開啟,在 redis.conf 配置文件中將 appendonly no 調整為 appendonly yes,這樣就開啟了 AOF 持久化,與 RDB 不同的是 AOF 是以記錄操作命令的形式來持久化數據的,我們可以查看以下 AOF 的持久化文件 appendonly.aof

    *2
    $6
    SELECT
    $1
    0
    *3
    $3
    set
    $6
    mykey1
    $6
    你好
    *3
    $3
    set
    $4
    key2
    $5
    hello
    *1
    $8

    大概就是長這樣的,具體的你可以查看你 Redis 服務器上的 appendonly.aof 配置文件,這也意味着我們可以在 appendonly.aof 文件中國修改值,等 Redis 重啟時將會加載修改之後的值。看似一些簡單的操作命令,其實從命令到 appendonly.aof 這個過程中非常有學問的,下面時 AOF 持久化流程圖:

    在 AOF 持久化過程中有兩個非常重要的操作:一個是將操作命令追加到 AOF_BUF 緩存區,另一個是 AOF_buf 緩存區數據同步到 AOF 文件,接下來我們詳細聊一聊這兩個操作:

    1、為什麼要將命令寫入到 aof_buf 緩存區而不是直接寫入到 aof 文件?

    我們知道 Redis 是單線程響應,如果每次寫入 AOF 命令都直接追加到磁盤上的 AOF 文件中,這樣頻繁的 IO 開銷,Redis 的性能就完成取決於你的機器硬件了,為了提升 Redis 的響應效率就添加了一層 aof_buf 緩存層, 利用的是操作系統的 cache 技術,這樣就提升了 Redis 的性能,雖然這樣性能是解決了,但是同時也引入了一個問題,aof_buf 緩存區數據如何同步到 AOF 文件呢?由誰同步呢?這就是我們接下來要聊的一個操作:fsync 操作

    2、aof_buf 緩存區數據如何同步到 aof 文件中?

    aof_buf 緩存區數據寫入到 aof 文件是有 linux 系統去完成的,由於 Linux 系統調度機制周期比較長,如果系統故障宕機了,意味着一個周期內的數據將全部丟失,這不是我們想要的,所以 Linux 提供了一個 fsync 命令,fsync 是針對單個文件操作(比如這裏的 AOF 文件),做強制硬盤同步,fsync 將阻塞直到寫入硬盤完成后返回,保證了數據持久化,正是由於有這個命令,所以 redis 提供了配置項讓我們自行決定何時進行磁盤同步,redis 在 redis.conf 中提供了appendfsync 配置項,有如下三個選項:

    # appendfsync always
    appendfsync everysec
    # appendfsync no
    • always:每次有寫入命令都進行緩存區與磁盤數據同步,這樣保證不會有數據丟失,但是這樣會導致 redis 的吞吐量大大下降,下降到每秒只能支持幾百的 TPS ,這違背了 redis 的設計,所以不推薦使用這種方式
    • everysec:這是 redis 默認的同步機制,雖然每秒同步一次數據,看上去時間也很快的,但是它對 redis 的吞吐量沒有任何影響,每秒同步一次的話意味着最壞的情況下我們只會丟失 1 秒的數據, 推薦使用這種同步機制,兼顧性能和數據安全
    • no:不做任何處理,緩存區與 aof 文件同步交給系統去調度,操作系統同步調度的周期不固定,最長會有 30 秒的間隔,這樣出故障了就會丟失比較多的數據。

    這就是三種磁盤同步策略,但是你有沒有注意到一個問題,AOF 文件都是追加的,隨着服務器的運行 AOF 文件會越來越大,體積過大的 AOF 文件對 redis 服務器甚至是主機都會有影響,而且在 Redis 重啟時加載過大的 AOF 文件需要過多的時間,這些都是不友好的,那 Redis 是如何解決這個問題的呢?Redis 引入了重寫機制來解決 AOF 文件過大的問題。

    3、Redis 是如何進行 AOF 文件重寫的?

    Redis AOF 文件重寫是把 Redis 進程內的數據轉化為寫命令同步到新 AOF 文件的過程,重寫之後的 AOF 文件會比舊的 AOF 文件占更小的體積,這是由以下幾個原因導致的:

    • 進程內已經超時的數據不再寫入文件
    • 舊的 AOF 文件含有無效命令,如 del key1、hdel key2、srem keys、set a111、set a222等。重寫使用進程內數據直接生成,這樣新的AOF文件只保 留最終數據的寫入命令
    • 多條寫命令可以合併為一個,如:lpush list a、lpush list b、lpush list c可以轉化為:lpush list a b c。為了防止單條命令過大造成客戶端緩衝區溢 出,對於 list、set、hash、zset 等類型操作,以 64 個元素為界拆分為多條。

    重寫之後的 AOF 文件體積更小了,不但能夠節約磁盤空間,更重要的是在 Redis 數據恢復時,更小體積的 AOF 文件加載時間更短。AOF 文件重寫跟 RDB 持久化一樣分為手動觸發自動觸發,手動觸發直接調用 bgrewriteaof 命令就好了,我們後面會詳細聊一聊這個命令,自動觸發就需要我們在 redis.conf 中修改以下幾個配置

    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    • auto-aof-rewrite-percentage:代表當前 AOF文件空間 (aof_current_size)和上一次重寫后 AOF 文件空間(aof_base_size)的比值,默認是 100%,也就是一樣大的時候
    • auto-aof-rewrite-min-size:表示運行 AOF 重寫時 AOF 文件最小體積,默認為 64MB,也就是說 AOF 文件最小為 64MB 才有可能觸發重寫

    滿足了這兩個條件,Redis 就會自動觸發 AOF 文件重寫,AOF 文件重寫的細節跟 RDB 持久化生成快照有點類似,下面是 AOF 文件重寫流程圖:

    AOF 文件重寫也是交給子進程來完成,跟 RDB 生成快照很像,AOF 文件重寫在重寫期間建立了一個 aof_rewrite_buf 緩存區來保存重寫期間主進程響應的命令,等新的 AOF 文件重寫完成后,將這部分文件同步到新的 AOF 文件中,最後用新的 AOF 文件替換掉舊的 AOF 文件。需要注意的是在重寫期間,舊的 AOF 文件依然會進行磁盤同步,這樣做的目的是防止重寫失敗導致數據丟失,

    Redis 持久化數據恢復

    我們知道 Redis 是基於內存的,所有的數據都存放在內存中,由於機器宕機或者其他因素重啟了就會導致我們的數據全部丟失,這也就是要做持久化的原因,當服務器重啟時,Redis 會從持久化文件中加載數據,這樣我們的數據就恢復到了重啟前的數據,在數據恢復這一塊Redis 是如何實現的?我們先來看看數據恢復的流程圖:

    Redis 的數據恢複流程比較簡單,優先恢復的是 AOF 文件,如果 AOF 文件不存在時則嘗試加載 RDB 文件,為什麼 RDB 的恢復速度比 AOF 文件快,但是還是會優先加載 AOF 文件呢?我個人認為是 AOF 文件數據更全面並且 AOF 兼容性比 RDB 強,需要注意的是當存在 RDB/AOF 時,如果數據加載不成功,Redis 服務啟動會失敗。

    最後

    目前互聯網上很多大佬都有 Redis 系列教程,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支持。若文中有所錯誤之處,還望提出,謝謝。

    歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,和平頭哥一起學習,一起進步。

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

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

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

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

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?