數據專欄

智能大數據搬運工,你想要的我們都有

科技資訊:

科技學院:

科技百科:

科技書籍:

網站大全:

軟件大全:

Redis中當內存達到極限時,主要采用了 6種內存淘汰策略/方式 進行內存對象的釋放操作。 V olatile-lru:從設置了過期時間的數據集中,選擇最近最少使用的數據釋放。 A llkeys-lru:從數據集中(包括設置過期時間以及未設置過期時間的數據集中),選擇最近最少使用的數據釋放。 V olatile-random:從設置了過期時間的數據集中,隨機選擇一個數據進行釋放。 A llkeys-random:從數據集中(包括了設置過期時間以及未設置過期時間的數據集)隨機選擇一個數據進行入釋放。 V olatile-ttl:從設置了過期時間的數據集中,選擇馬上就要過期的數據進行釋放。 N oeviction:不刪除任意數據(但Redis還會根據引用計數器進行釋放),這時如果內存不夠時,會直接返回錯誤。 參考文檔: https://www.cnblogs.com/WJ5888/p/4371647.html
來源:OSCHINA
發布時間:2020-03-26 16:23:00
Redis使用的內存回收算法是 引用計數算法 和 LRU算法 。 1.引用計數算法: 對于創建的每一個對象都有一個與之關聯的計數器,這個計數器記錄著該對象被使用的次數,垃圾收集器在進行垃圾回收時,對掃描到的每一個對象判斷一下計數器是否等于0,若等于0,就會釋放該對象占用的內存空間,同時將該對象引用的其他對象的計數器進行減一操作。 算法實現方式: 引用計數算法的垃圾收集一般有侵入式與非侵入式兩種,侵入式的實現就是將引用計數器直接根植在對象內部,用C++的思想進行解釋就是,在對象的構造或者拷貝構造中進行加一操作,在對象的析構中進行減一操作;非侵入式思想就是有一塊單獨的內存區域,用作引用計數器。 算法優點 : 使用引用計數器,內存回收可以穿插在程序的運行中,在程序運行中,當發現某一對象的引用計數器為0時,可以立即對該對象所占用的內存空間進行回收,這種方式可以避免FULL GC(完全垃圾收集)時帶來的程序暫停,Redis中就是在引用計數器為0時,對內存進行了回收。 算法缺點: 采用引用計數器進行垃圾回收,最大的缺點就是不能解決循環引用的問題,例如一個父對象持有一個子對象的引用,子對象也持有父對象的引用,這種情況下,父子對象將一直存在于JVM的堆中,無法進行回收。 2.LRU算法 : LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。該算法賦予每個頁面一個訪問字段,用來記錄該頁面自上次被訪問以來所經歷的時間 t,當必須淘汰一個頁面時,選擇現有頁面中其 t 值最大的,即最近最少使用的頁面給予淘汰。 LRU算法最為經典的實現,就是 HashMap+Double LinkedList , 時間復雜度為 O(1) ,但是 如果按照HashMap和雙向鏈表實現,需要額外的存儲存放next和prev指針,犧牲比較大的存儲空間,顯然是不劃算的。 所以Redis中的LRU算法 ,就是隨機取出若干個key,然后按照訪問時間排序后,淘汰掉最不經常使用的那個。 參考文檔: https://my.oschina.net/u/4480939/blog/write https://www.cnblogs.com/WJ5888/p/4371647.html https://www.cnblogs.com/WJ5888/p/4359783.html
來源:OSCHINA
發布時間:2020-03-26 16:08:00
Kubernetes爆出中等嚴重性安全漏洞——Kubernetes API Server拒絕服務漏洞CVE-2019-1002100。 本文將進行漏洞解讀和情景再現,并分享漏洞修復方案,Rancher用戶來看應對之策了! CVE-2019-1002100漏洞 美國當地時間2019年3月2日,Kubernetes社區發布了Kubernetes API server拒絕服務的漏洞(CVE-2019-1002100),即有API寫入權限的用戶在寫入資源時會導致Kubernetes API server過度消耗資源,此漏洞被評級為【中等嚴重性】。 此漏洞表現為用戶在向Kubernetes API server發送 json-patch規則的補丁包來更新資源對象時(例如kubectl patch xxx --type json 或者“Content-Type: application/json-patch+json”),Kubernetes API server會消耗極大的資源,最終導致API server拒絕連接。 https://github.com/kubernetes/kubernetes/issues/74534 情景再現 一個json-patch的例子: kubectl patch deployment test --type='json' -p '[{"op": "add", "path": "/metadata/labels/test", "value": "test"},{"op": "add", "path": "/metadata/labels/app", "value": "test"} ,{…} ]' 當我們向Kubernetes頻繁地發送多個json-patch請求來更新資源對象時,可以發現Kubernetes API server會消耗很多資源來處理我們的請求。 此時會有一部分資源的patch請求失敗,無法得到Kubernetes API server的響應。 受此漏洞影響的Kubernetes API server的版本包括: v1.0.0 – 1.10.x v1.11.0 – 1.11.7 v1.12.0 – 1.12.5 v1.13.0 – 1.13.3 Kubernetes官方建議用戶在升級至修復版本之前,可針對此漏洞的采取的緩解措施為: 對不受信任用戶移除patch權限 漏洞修復 Kubernetes社區很快地修復了此漏洞,增加了對用戶json-patch操作數量的限制。 當用戶對某一資源對象修改的 json-patch 內容超過10000個操作時,Kubernetes API server會返回413(RequestEntityTooLarge)的錯誤。 錯誤信息如下: Request entity too large: The allowed maximum operations in a JSON patch is 10000, got 10004 修復的Kubernetes版本包括: v1.11.8 v1.12.6 v1.13.4 Rancher已發布最新版本應對此次漏洞 此次也一如既往,在Kubernetes自身爆出漏洞之后,Rancher Labs團隊都第一時間響應,保障使用Rancher平臺管理Kubernetes集群的用戶的安全。 如果你是使用Rancher平臺管理Kubernetes集群,不用擔心,Rancher已于今日發布了最新版本,支持包含漏洞修復的Kubernetes版本,保障所有Rancher用戶的Kubernetes集群不受此次漏洞困擾。 最新發布的Rancher版本為: v2.1.7(提供Kubernetes v1.11.8, v1.12.6, v1.13.4支持) v2.0.12(提供Kubernetes v1.11.8支持) 對于Rancher 1.6.x的用戶,可以在Rancher v1.6.26的Catalog中使用Kubernetes發布的修復版本 v1.11.8和v1.12.6 此次漏洞會影響的Kubernetes版本范圍較廣,建議中招的用戶盡快升級喲! 為用戶的Docker & K8S之旅護航 Rancher Kubernetes平臺擁有著超過一億次下載量,我們深知安全問題對于用戶而言的重要性,更遑論那些通過Rancher平臺在生產環境中運行Docker及Kubernetes的數千萬用戶。 2018年年底Kubernetes被爆出的首個嚴重安全漏洞CVE-2018-1002105,就是由Rancher Labs聯合創始人及首席架構師Darren Shepherd發現的。 2019年1月Kubernetes被爆出儀表盤和外部IP代理安全漏洞CVE-2018-18264時,Rancher Labs也是第一時間向用戶響應,確保所有Rancher 2.x和1.6.x的用戶都完全不被漏洞影響。 2019年2月爆出的嚴重的runc容器逃逸漏洞CVE-2019-5736,影響到大多數Docker與Kubernetes用戶,Rancher Kubernetes管理平臺和RancherOS操作系統均在不到一天時間內緊急更新,是業界第一個緊急發布新版本支持Docker補丁版本的平臺,還幫忙將修復程序反向移植到所有版本的Docker并提供給用戶,且提供了連Docker官方都不支持的針對Linux 3.x內核的修復方案。 負責、可靠、快速響應、以用戶為中心,是Rancher始終不變的初心;在每一次業界出現問題時,嚴謹踏實為用戶提供相應的應對之策,也是Rancher一如既往的行事之道。未來,Rancher也將一如既往支持與守護在用戶的K8S之路左右,確保大家安全、穩妥、無虞地繼續前進??
來源:OSCHINA
發布時間:2019-03-08 10:15:00
根據 Gartner 對全球 CIO 的調查結果顯示,人工智能將成為 2019 年組織革命的顛覆性力量。對于人工智能來說,算力即正義,成本即能力,利用 Docker 和 Kubernetes 代表云原生技術為 AI 提供了一種新的工作模式,將 GPU 機器放到統一的資源池進行調度和管理,這避免了GPU 資源利用率低下和人工管理的成本。因此,全球主要的容器集群服務廠商 Kubernetes 都提供了 Nvidia GPU 容器集群調度能力,但是通常都是將一個 GPU 卡分配給一個容器。這雖然可以實現比較好的隔離性,確保使用 GPU 的應用不會被其他應用影響;對于深度學習模型訓練的場景也非常適合,但是,針對模型開發和模型預測的場景還是會顯得比較浪費?;诖?,大家有了共享 GPU 的集群調度需求。 Kubernetes 共享 GPU 集群調度 共享 GPU 的集群調度就是能夠讓更多的模型開發和預測服務共享同一個 GPU 卡,進而提高集群中 Nvidia GPU 的利用率。而這就需要提供 GPU 資源的劃分,而這里 GPU 資源劃分的維度指的就是 GPU 顯存和 Cuda Kernel 線程的劃分。通常在集群級別談支持共享 GPU 是以下兩件事情: 1.調度 2.隔離,我們這里主要討論的是調度,隔離的方案目前需要用戶通過應用限制(比如使用 Tensorflow 的per_process_gpu_memory_fraction 來控制),未來會提供基于 Nvidia 的 MPS 的可選項, 也會考慮 GPU 的方案。 而對于細粒度的 GPU 卡調度,目前 Kubernetes 社區并沒有很好的方案,這是由于 Kubernetes 對于 GPU 這類擴展資源的定義僅僅支持整數粒度的加加減減,無法支持復雜資源的分配。比如用戶希望使用 Pod A 占用半張 GPU卡,這在目前 Kubernetes 的架構設計中無法實現資源分配的記錄和調用。這里挑戰是多卡 GPU 共享是實際矢量資源問題,而 Extened Resource 是標量資源的描述。 針對此問題,我們設計了一個 Out Of Tree 的共享 GPU 調度方案,該方案依賴于 Kubernetes 的現有的工作機制: Extended Resource 定義 Scheduler Extender 機制 Device Plugin 機制 Kubectl 的擴展機制 這個 GPU 共享調度擴展的好處是:利用 Kubernetes 的擴展和插件機制實現,對于 API Server,Scheduler,Controller Manager 以及 Kubelet 等核心組件沒有侵入性。這就方便了使用者可以在不同 Kubernetes 版本上應用這個方案,無需 rebase 代碼和重新構建 Kubernetes 二進制包。 用戶場景 集群管理員:“我想提高集群的 GPU 使用率;在開發過程中,多個用戶共享模型開發環境?!?應用開發人員:“我希望能夠同時在 Volta GPU 上運行多個推理任務?!? []( https://www.atatech.org/articles/132268#2) 目標 能夠讓使用者通過 API 描述對于一個可共享資源的申請, 并能實現該種資源的調度 []( https://www.atatech.org/articles/132268#3) 非目標 不支持該共享資源的隔離 不支持超賣 []( https://www.atatech.org/articles/132268#4) 設計原則 明確問題簡化設計,第一步只負責調度和部署,后續再實現運行時顯存管控。 有很多的客戶明確的訴求是首先可以支持多AI應用可以調度到同一個 GPU 上,他們可以接受從應用級別控制顯存的大小,利用類似 gpu_options.per_process_gpu_memory_fraction 控制應用的顯存使用量。那我們要解決的問題就先簡化到以顯存為調度標尺,并且把顯存使用的大小以參數的方式傳遞給容器內部。 不做侵入式修改 本設計中不會修改 Kubernetes 核心的 Extended Resource 的設計, Scheduler 的實現,Device Plugin 的機制以及 Kubelet 的相關設計。重用 Extended Resource 描述共享資源的申請 API。這樣的好處在于提供一個可以移植的方案,用戶可以在原生 Kubernetes 上使用這個方案。 按顯存和按卡調度的方式可以在集群內并存,但是同一個節點內是互斥的,不支持二者并存;要么是按卡數目,要么是按顯存分配。 詳細設計 []( https://www.atatech.org/articles/132268#6) 前提: 依舊延用 Kubernetes Extended Resource 定義,但是衡量維度最小單位從 1 個 GPU 卡變為 GPU 顯存的 MiB。如果所節點使用的 GPU 為單卡 16GiB 顯存,它對應的資源就是 16276MiB; 由于用戶對于共享GPU的訴求在于模型開發和模型預測場景,在此場景下,用戶申請的GPU資源上限不會超過一張卡,也就是申請的資源上限為單卡。 而我們的工作首先是定義了兩個新的 Extended Resource: 第一個是 gpu-mem, 對應的是 GPU 顯存;第二個是 gpu-count,對應的是 GPU 卡數。 通過兩個標量資源描述矢量資源, 并且結合這一資源,提供支持共享 GPU 的工作機制。下面是基本的架構圖: []( https://www.atatech.org/articles/132268#7) 核心功能模塊: GPU Share Scheduler Extender : 利用 Kubernetes 的調度器擴展機制,負責在全局調度器 Filter 和 Bind 的時候判斷節點上單個 GPU 卡是否能夠提供足夠的 GPU Mem,并且在 Bind 的時刻將 GPU 的分配結果通過 annotation 記錄到 Pod Spec 以供后續 Filter 檢查分配結果。 GPU Share Device Plugin : 利用 Device Plugin 機制,在節點上被 Kubelet 調用負責 GPU 卡的分配,依賴 scheduler Extender 分配結果執行。 []( https://www.atatech.org/articles/132268#8) 具體流程: 資源上報 GPU Share Device Plugin 利用 nvml 庫查詢到 GPU 卡的數量和每張 GPU 卡的顯存, 通過 ListAndWatch() 將節點的 GPU 總顯存(數量 顯存)作為另外 Extended Resource 匯報給 Kubelet; Kubelet 進一步匯報給 Kubernetes API Server。 舉例說明,如果節點含有兩塊 GPU 卡,并且每塊卡包含 16276MiB,從用戶的角度來看:該節點的 GPU 資源為 16276 2 = 32552; 同時也會將節點上的 GPU 卡數量 2 作為另外一個 Extended Resource 上報。 2. 擴展調度 GPU Share Scheduler Extender 可以在分配 gpu-mem 給 Pod 的同時將分配信息以 annotation 的形式保留在 Pod spec 中,并且在過濾時刻根據此信息判斷每張卡是否包含足夠可用的 gpu-mem 分配。 2.1 Kubernetes 默認調度器在進行完所有過濾(filter)行為后會通過 http 方式調用 GPU Share Scheduler Extender的filter 方法, 這是由于默認調度器計算 Extended Resource 時,只能判斷資源總量是否有滿足需求的空閑資源,無法具體判斷單張卡上是否滿足需求;所以就需要由 GPU Share Scheduler Extender 檢查單張卡上是否含有可用資源。 以下圖為例, 在由 3 個包含兩塊 GPU 卡的節點組成的 Kubernetes 集群中,當用戶申請 gpu-mem=8138 時,默認調度器會掃描所有節點,發現 N1 所剩的資源為 (16276 * 2 - 16276 -12207 = 4069 )不滿足資源需求,N1 節點被過濾掉。 而 N2 和 N3 節點所剩資源都為 8138MiB,從整體調度的角度看,都符合默認調度器的條件;此時默認調度器會委托 GPU Share Scheduler Extender 進行二次過濾,在二次過濾中,GPU Share Scheduler Extender 需要判斷單張卡是否滿足調度需求,在查看 N2 節點時發現該節點雖然有 8138MiB 可用資源,但是落到每張卡上看,GPU0 和分別 GPU1 只有 4069MiB 的可用資源,無法滿足單卡 8138MiB 的訴求。而 N3 節點雖然也是總共有 8138MiB 可用資源,但是這些可用資源都屬于 GPU0,滿足單卡可調度的需求。由此,通過 GPU Share Scheduler Extender 的篩選就可以實現精準的條件篩選。 2.2 當調度器找到滿足條件的節點,就會委托 GPU Share Scheduler Extender 的 bind 方法進行節點和 Pod 的綁定,這里 Extender 需要做的是兩件事情 以 binpack 的規則找到節點中最優選擇的 GPU 卡 id,此處的最優含義是對于同一個節點不同的 GPU 卡,以 binpack 的原則作為判斷條件,優先選擇空閑資源滿足條件但同時又是所剩資源最少的 GPU 卡,并且將其作為 ALIYUN_COM_GPU_MEM_IDX 保存到 Pod 的 annotation 中;同時也保存該 Pod 申請的 GPU Memory 作為 ALIYUN_COM_GPU_MEM_POD 和 ALIYUN_COM_GPU_MEM_ASSUME_TIME 保存至 Pod 的 annotation 中,并且在此時進行 Pod 和所選節點的綁定。 注意:這時還會保存 ALIYUN_COM_GPU_MEM_ASSIGNED 的 Pod annotation,它被初始化為“false”。它表示該 Pod 在調度時刻被指定到了某塊 GPU 卡,但是并沒有真正在節點上創建該 Pod。 ALIYUN_COM_GPU_MEM_ASSUME_TIME 代表了 指定 時間。 如果此時發現分配節點上沒有 GPU 資源符合條件,此時不進行綁定,直接不報錯退出,默認調度器會在 assume 超時后重新調度。 調用 Kubernetes API 執行節點和 Pod 的綁定 以下圖為例,當 GPU Share Scheduler Extender 要把 gpu-mem:8138 的 Pod 和經過篩選出來的節點 N1 綁定,首先會比較不同 GPU 的可用資源,分別為 GPU0(12207),GPU1(8138),GPU2(4069),GPU3(16276),其中 GPU2 所剩資源不滿足需求,被舍棄掉;而另外三個滿足條件的 GPU 中, GPU1 恰恰是符合空閑資源滿足條件但同時又是所剩資源最少的 GPU 卡,因此 GPU1 被選出。 3. 節點上運行 當 Pod 和節點綁定的事件被 Kubelet 接收到后,Kubelet 就會在節點上創建真正的 Pod 實體,在這個過程中, Kubelet 會調用 GPU Share Device Plugin 的 Allocate 方法, Allocate 方法的參數是 Pod 申請的 gpu-mem。而在 Allocate 方法中,會根據 GPU Share Scheduler Extender 的調度決策運行對應的 Pod 會列出該節點中所有狀態為 Pending 并且 ALIYUN_COM_GPU_MEM_ASSIGNED 為 false 的 GPU Share Pod 選擇出其中 Pod Annotation 的 ALIYUN_COM_GPU_MEM_POD 的數量與 Allocate 申請數量一致的 Pod。如果有多個符合這種條件的 Pod,就會選擇其中 ALIYUN_COM_GPU_MEM_ASSUME_TIME 最早的 Pod。 將該 Pod 的 annotation ALIYUN_COM_GPU_MEM_ASSIGNED 設置為 true ,并且將 Pod annotation 中的 GPU 信息轉化為環境變量返回給 Kubelet 用以真正的創建 Pod。 []( https://www.atatech.org/articles/132268#9) 相關項目 目前項目已經開源到 github.com 上 gpushare-scheduler-extender gpushare-device-plugin 部署 請參照 部署文檔 []( https://www.atatech.org/articles/132268#11) 測試樣例 首先創建一個使用 aliyun.com/gpu-mem 的應用 apiVersion: apps/v1 kind: Deployment metadata: name: binpack-1 labels: app: binpack-1 spec: replicas: 1 selector: # define how the deployment finds the pods it manages matchLabels: app: binpack-1 template: # define the pods specifications metadata: labels: app: binpack-1 spec: containers: - name: binpack-1 image: cheyang/gpu-player:v2 resources: limits: # MiB aliyun.com/gpu-mem: 1024 使用 請參照 使用文檔 構建 請參照 如何構建 視頻 Demo []( https://www.atatech.org/articles/132268#15)Demo 1: 部署多個 GPU Share 的 Pod,發現他們以 binpack 的方式被放置到同一個 GPU 卡上 []( https://www.atatech.org/articles/132268#16)Demo 2: 避免錯誤調度申請資源超過單個 GPU 可用資源的 Pod Roadmap 在 Device Plugin 中提供 Nvidia MPS 的可選支持; 支持該方案可以在由 kubeadm 初始化的 Kubernetes 集群自動化部署; 提升 Scheduler Extener 的高可用性; 為 GPU, RDMA 和彈性網卡提供通用方案。 作者: jessie筱姜 原文鏈接 本文為云棲社區原創內容,未經允許不得轉載。
來源:OSCHINA
發布時間:2019-03-07 16:27:00
HDFS上傳流程 客戶端通過Distributed FileSystem模塊向NameNode請求上傳文件,NameNode檢查目標文件是否已存在,父目錄是否存在。 NameNode返回是否可以上傳。 客戶端請求第一個 block上傳到哪幾個datanode服務器上。 NameNode返回3個datanode節點,分別為dn1、dn2、dn3。 客戶端通過FSDataOutputStream模塊請求dn1上傳數據,dn1收到請求會繼續調用dn2,然后dn2調用dn3,將這個通信管道建立完成。 dn1、dn2、dn3逐級應答客戶端。 客戶端開始往dn1上傳第一個block(先從磁盤讀取數據放到一個本地內存緩存),以packet為單位,dn1收到一個packet就會傳給dn2,dn2傳給dn3;dn1每傳一個packet會放入一個應答隊列等待應答。 當一個block傳輸完成之后,客戶端再次請求NameNode上傳第二個block的服務器。(重復執行3-7步)。 HDFS讀流程 客戶端跟namenode通信,請求下載某個數據。 namenode查詢元數據信息以及block位置信息。 將數據所在的datanode信息返回給客戶端。 客戶端根據數據所在的datanode,挑選一臺距離自己最近的datanode,并向其發出下載文件的請求(若所需數據不在一臺datanode上保存,則分別向多臺datanode發出請求)。 datanode響應客戶端請求,將數據返回給客戶端。 從多個datanode獲得的數據不斷在客戶端追加,形成完整的數據
來源:OSCHINA
發布時間:2020-03-26 12:01:00
本文是Choerodon 的微服務系列推文第五篇,上一篇《 Choerodon 的微服務之路(四):深入理解微服務配置中心 》介紹了配置中心在微服務架構中的作用,本篇將介紹微服務監控的重要性和必要性。 ▌文章的主要內容包括: 為什么要監控 開發者需要監控哪些 豬齒魚的解決方案 在前面的幾期的文章里,介紹了在 Choerodon 的微服務架構中,系統被拆分成多個有著獨立部署能力的業務服務,每個服務可以使用不同的編程語言,不同的存儲介質,來保持最低限度的集中式管理。 這樣的架構決定了功能模塊的部署是分布式的,不同的業務服務單獨部署運行的,運行在獨立的容器進程中,彼此之間通過網絡進行服務調用交互。一次完整的業務流程會經過很多個微服務的處理和傳遞。 在這種情況下,如何監控服務的錯誤和異常,如何快速地定位和處理問題,以及如何在復雜的容器拓撲中篩選出用戶所需要的指標,是 Choerodon 在監控中面臨的首要問題。本文將會分享 Choerodon 在微服務下的監控思考,以及結合社區流行的 Spring Cloud、Kubernetes、Prometheus 等開源技術,打造的 Choerodon 的監控方案。 為什么要監控 在談到 Choerodon 的監控之前,大家需要清楚為什么需要微服務下的監控。 傳統的單體應用中,由于應用部署在具體的服務器上,開發者一般會從不同的層級對應用進行監控。比如豬齒魚團隊常用的方式是將監控分成基礎設施、系統、應用、業務和用戶端這幾層,并對每一層分別進行監控。如下圖所示。 而在微服務系統中,開發者對于監控的關心點是一樣的,但是視角卻發生了改變,從分層 + 機器的視角轉化為以服務為中心的視角。在 Choerodon 中,傳統的分層已經不太適用。服務是部署在 k8s 的 pod 中,而不是直接部署在服務器上。團隊除了對服務器的監控之外,還需要考慮到 k8s 中容器的監控。同樣,由于一個業務流程可能是通過一系列的業務服務而實現的,如何追蹤業務流的處理也同樣至關重要。 所以在微服務中,大家同樣離不開監控,有效的監控能夠幫開發者快速的定位故障,保護系統健康的運行。 平開發者需要監控哪些 在 Choerodon 中,將系統的使用人員分為應用的管理人員,開發人員,運維人員。而不同的人員在平臺中所關心的問題則分別不同。 作為應用的管理人員,需要查看到系統中各個節點實例的運行狀態以及實例中應用的狀態。 作為開發人員,需要查看自己開發的服務在運行中的所有信息,也需要跟蹤請求流的處理順序和結果,并快速定位問題。 作為運維人員,需要查看系統集群中服務器的 CPU、內存、堆棧等信息。需要查看K8S集群的運行狀態,同時也需要查看各個服務的運行日志。 除了這些以外,還需要監控到如下的一些信息: 服務的概覽信息:服務的名稱,相關的配置等基本信息。 服務的拓撲關系:服務之間的調用關系。 服務的調用鏈:服務之間的請求調用鏈。 服務的性能指標:服務的CPU,內存等。 接口的調用監控:接口的吞吐量,錯誤率,響應時間等。 服務的日志數據:服務運行中產生的日志異常。 簡而概之,對于 Choerodon 而言,開發者將監控聚焦在指標監控,調用監控和日志監控。 豬齒魚的解決方案 在開源社區中,有很多對監控的解決方案,比如指標監控有 Prometheus,鏈路監控有 zipkin、pinpoint,skywalking,日志則有 elk。 Choerodon 具有多集群多環境管理能力,Choerodon 為需要監控的集群配置監控組件,并與Choerodon 所在集群的監控組件互通以及過濾多余數據,可以最大限度地減少多集群非同一局域網的外網帶寬需求。在多集群環境中仍然可以感知所管理應用的運行狀態和配置預警信息。 ▌指標監控 Spring Boot 的執行器包含一系列的度量指標(Metrics)接口。當你請求 metrics 端點,你可能會看到類似以下的響應: { "counter.status.200.root": 20, "counter.status.200.metrics": 3, "counter.status.200.star-star": 5, "counter.status.401.root": 4, "gauge.response.star-star": 6, "gauge.response.root": 2, "gauge.response.metrics": 3, "classes": 5808, "classes.loaded": 5808, "classes.unloaded": 0, "heap": 3728384, "heap.committed": 986624, "heap.init": 262144, "heap.used": 52765, "nonheap": 0, "nonheap.committed": 77568, "nonheap.init": 2496, "nonheap.used": 75826, "mem": 986624, "mem.free": 933858, "processors": 8, "threads": 15, "threads.daemon": 11, "threads.peak": 15, "threads.totalStarted": 42, "uptime": 494836, "instance.uptime": 489782, "datasource.primary.active": 5, "datasource.primary.usage": 0.25 } 這些系統指標具體含義如下: 系統內存總量(mem),單位:KB 空閑內存數量(mem.free),單位:KB 處理器數量(processors) 系統正常運行時間(uptime),單位:毫秒 應用上下文(應用實例)正常運行時間(instance.uptime),單位:毫秒 系統平均負載(systemload.average) 堆信息(heap,heap.committed,heap.init,heap.used),單位:KB 線程信息(threads,thread.peak,thead.daemon) 類加載信息(classes,classes.loaded,classes.unloaded) 垃圾收集信息(gc.xxx.count, gc.xxx.time) 有了這些指標,我們只需要做簡單的修改,就可以使這些指標被 Prometheus 所監測到。Prometheus 是一套開源的系統監控報警框架。默認情況下 Prometheus 暴露的metrics endpoint為/prometheus。 在項目的pom.xml文件中添加依賴,該依賴包含了 micrometer 和 prometheus 的依賴,并對監控的指標做了擴充。 io.choerodon choerodon-starter-hitoa ${choerodon.starters.version} Prometheus提供了4中不同的 Metrics 類型: Counter , Gauge , Histogram , Summary 。通過Gauge,Choerodon對程序的線程指標進行了擴充,添加了 NEW , RUNNABLE , BLOCKED , WAITING , TIMED_WAITING , TERMINATED 這幾種類型,具體代碼如下。 @Override public void bindTo(MeterRegistry registry) { Gauge.builder("jvm.thread.NEW.sum", threadStateBean, ThreadStateBean::getThreadStatusNEWCount) .tags(tags) .description("thread state NEW count") .register(registry); Gauge.builder("jvm.thread.RUNNABLE.sum", threadStateBean, ThreadStateBean::getThreadStatusRUNNABLECount) .tags(tags) .description("thread state RUNNABLE count") .register(registry); Gauge.builder("jvm.thread.BLOCKED.sum", threadStateBean, ThreadStateBean::getThreadStatusBLOCKEDCount) .tags(tags) .description("thread state BLOCKED count") .register(registry); Gauge.builder("jvm.thread.WAITING.sum", threadStateBean, ThreadStateBean::getThreadStatusWAITINGCount) .tags(tags) .description("thread state WAITING count") .register(registry); Gauge.builder("jvm.thread.TIMEDWAITING.sum", threadStateBean, ThreadStateBean::getThreadStatusTIMEDWAITINGCount) .tags(tags) .description("thread state TIMED_WAITING count") .register(registry); Gauge.builder("jvm.thread.TERMINATED.sum", threadStateBean, ThreadStateBean::getThreadStatusTERMINATEDCount) .tags(tags) .description("thread state TERMINATED count") .register(registry); } ▌調用監控 在微服務架構中,一個請求可能會涉及到多個服務,請求的路徑則可能構成一個網狀的調用鏈。而如果其中的某一個節點發生異常,則整個鏈條都可能受到影響。 針對這種情況,團隊需要有一款調用鏈監控的工具,來支撐系統監控分布式的請求追蹤。目前開源社區中有一些工具:Zipkin、Pinpoint、SkyWalking。Choerodon 使用的是 SkyWalking,它是一款國產的 APM 工具,包括了分布式追蹤、性能指標分析、應用和服務依賴分析等。 Skywalking 包含 Agent 和 Collecter,具體的部署和原理在這里不在做具體的介紹,Choerodon 的服務在每個服務的 DockerFile 中,添加了對 Skywalking Agent 的支持。具體如下: FROM registry.cn-hangzhou.aliyuncs.com/choerodon-tools/javabase:0.7.1 COPY app.jar /iam-service.jar ENTRYPOINT exec java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap $JAVA_OPTS $SKYWALKING_OPTS -jar /iam-service.jar 部署時通過配置容器的環境變量 SKYWALKING_OPTS 來實現客戶端的配置。 ▌日志監控 日志是程序在運行中產生的遵循一定格式(通常包含時間戳)的文本數據,通常由Choerodon的服務生成,輸出到不同的文件中,一般會有系統日志、應用日志、安全日志等等。這些日志分散地存儲在不同的容器、機器中。當開發者在排查故障的時候,日志會幫助他們快速地定位到故障的原因。 Choerodon 采用了業界通用的日志數據管理解決方案,主要包括 elasticsearch 、 fluent-bit 、 fluentd 和 Kibana 。對于日志的采集分為如下幾個步驟。 日志收集:通過 fluent-bit 讀取 k8s 集群中 cluster 的日志,并進行收集。 日志過濾:通過 fluentd 將讀取到的日志進行過濾,并進行緩存。 日志存儲:將過濾后的日志存儲至 elasticsearch 集群中。 日志展示:通過 kibana 查詢 elasticsearch 中的日志數據,并用于展示。 通過端對端可視化的日志集中管理,給開發團隊帶來如下的一些好處: 故障查找:通過檢索日志信息,定位相應的 bug ,找出解決方案。 服務分析:通過對日志信息進行統計、分析,了解服務器的負荷和服務運行狀態。 數據分析:分析數據,對用戶進行行為分析。 寫在最后 回顧一下這篇文章,介紹了微服務監控的重要性和必要性,以及 Choerodon 是如何應對指標監控,調用監控和日志監控這三種監控的。微服務架構下的服務規模大,系統相對復雜,也使得眾多開發者成為了微服務的受害者。如何做好微服務下的監控,保障系統健康地運行,我們仍有許多需要繼續努力的。 更多關于微服務系列的文章,點擊藍字即可閱讀 ▼ Choerodon的微服務之路(一):如何邁出關鍵的第一步 Choerodon的微服務之路(二):微服務網關 Choerodon 的微服務之路(三):服務注冊與發現 Choerodon 的微服務之路(四):深入理解微服務配置中心 關于Choerodon豬齒魚 Choerodon豬齒魚 開源多云集成平臺,基于開源技術Kubernetes,Istio,knative,Gitlab和Spring Cloud來實現本地和云端環境的集成,實現企業多云/混合云應用環境的一致性。平臺通過提供精益敏捷、持續交付、容器環境、微服務、DevOps等能力來幫助組織團隊來完成軟件的生命周期管理,從而更快、更頻繁地交付更穩定的軟件。 大家也可以通過以下社區途徑了解豬齒魚的最新動態、產品特性,以及參與社區貢獻: 官網: http://choerodon.io 論壇: http://forum.choerodon.io Github: https://github.com/choerodon 微信公眾號:Choerodon豬齒魚 微博:Choerodon豬齒魚 歡迎加入Choerodon豬齒魚社區,共同為企業數字化服務打造一個開放的生態平臺。
來源:OSCHINA
發布時間:2019-03-25 19:47:00
速度快 ,因為數據都存于內存中,類似于HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1)。 支持豐富的數據類型 ,支持string、list、set、zset、hashmap等數據類型。 支持事務,操作都是原子性 ,即對數據的更改要么全部執行,要么全部不執行;R edis 對事務是部分支持的,如果是在入隊時報錯,那么都不會執行;在非入隊時報錯,那么成功的就會成功執行。 豐富的特性 , 可用作數據庫、緩存和消息中間件,可以按 key 設置過期時間,過期后將會自動刪除。
來源:OSCHINA
發布時間:2020-03-26 11:37:00
UCloud外網網關是為了承載外網IP、負載均衡等產品的外網出入向流量,當前基于Linux內核的OVS/GRE tunnel/netns/iptables等實現,很好地支撐了現有業務。同時,我們也在不斷跟蹤開源社區的新技術發展,并將之用于下一代外網網關的設計。這些新特性可將系統性能和管理能力再提上一檔,滿足未來幾年的需求。在方案設計研發過程中發現,新特性存在不少缺陷和Bug,為此我們向開源社區回饋了10多個patch,并融入到kernel 5.0版本中,幫助完善kernel功能并提升穩定性。 當前業界的多租戶外網網關很多都是基于OpenFlow的OpenvSwitch(OVS)方案,然而隨著內核路由轉發功能的不斷完善,利用內核原生路由轉發方式進行設計多租戶外網網關系統成為一種可能。在這種方式下能有效的使用傳統iproute2路由工具以及iptables、nftables等Firewall工具,并且隨著SwitchDev技術的興起,未來將網關系統遷移到Linux Switch上也成為一種可能。 現有kernel 3.x的不足 當前廣泛使用的內核版本為3.x系列,例如CentOS 7全系列標準支持的內核為3.10版本,Fedora/Ubuntu等Linux發行版也有大量使用。在3.x系列內核下存在著IP tunnel管理復雜、租戶隔離性能損耗等問題。 1. IP tunnel管理復雜 Linux內核創建IP tunnel設備來建立點對點的隧道連接,創建時需指定tunnel dst和 tunnel key。因為宿主機之間兩兩建立連接,面向宿主機的目的地址眾多,這樣就會導致網關節點上需要創建成千上萬的tunnel設備,在大規模業務環境下,tunnel的管理將變得及其復雜。 2. 多租戶隔離導致的性能下降 a. 公有云需要實現多租戶隔離以確保用戶間的安全和隱私。由于VPC網絡下不同租戶的內網地址可以重合,導致路由也有重合的可能性,此時需要通過大量的策略路由去隔離租戶的路由規則,由于策略路由的鏈表屬性,性能會隨著鏈表長度的增加而急劇下降。 b. 由于Firewall和NAT的實現基于同樣鏈式的iptables,性能損耗同樣可觀。 3. netns帶來性能開銷 通過netns實現租戶路由和Firewall規則的隔離,但是netns會引入虛擬網卡和協議棧重入開銷,使整體性能下降20%左右。 三項內核新技術 為了解決原有方案存在的困擾,我們調研了大量行業主流方案和內核上游的新動向,發現Lightweight tunneling(輕量級隧道,簡稱lwtunnel)、Virtual Routing Forwarding(虛擬路由轉發,簡稱VRF)以及nftable & netfilter flow offload(流卸載)三項內核新技術的特性,可以幫助規避原方案存在的缺陷。 1. Lightweight tunneling Linux內核在4.3版本中引入了輕量級隧道Lightweight tunneling,它提供了通過route方式設置tunnel屬性的方法,這樣可以避免管理大量的tunnel設備。 創建隧道設備時指定external模式,利用路由設置的輕量級隧道通過tun設備發送報文。 2. Virtual Routing Forwarding Linux內核在4.3版本中引入了VRF的初步支持,并在4.8版本形成完備版本。Virtual Routing Forwarding虛擬路由轉發,可以將一臺Linux Box的物理路由器當多臺虛擬路由器使用,能很好的解決租戶路由隔離問題,避免直接使用策略路由。因此,可以將不同租戶的網卡加入租戶所屬的虛擬路由器中來實現多租戶的虛擬路由。 3. flow offload Nftables是一種新的數據包分類框架,旨在替代現存的{ip,ip6,arp,eb}_tables。在nftables中,大部分工作是在用戶態完成的,內核只知道一些基本指令(過濾是用偽狀態機實現的)。nftables的一個高級特性就是映射,可以使用不同類型的數據并映射它們。例如,我們可以映射iif device到專用的規則集合(之前創建的存儲在一個鏈中)。由于是hash映射的方式,可以完美的避免鏈式規則跳轉的性能開銷。 Linux內核在版本4.16引入了flow offload功能,它為IP forward提供了基于流的卸載功能。當一條新建連接完成首回合原方向和反方向的報文時,完成路由,Firewall和NAT工作后,在處理反方向首報文的forward hook,根據報文路由、NAT等信息創建可卸載flow到接收網卡ingress hook上。后續的報文可以在接收ingress hook上直接轉發,不需要再進入IP stack處理。此外,將來flow offload還將支持hardware offload模式,這將極大提高系統轉發性能。 方案設計與優化實踐 通過對上述三項新技術的研究,我們發現可以嘗試設計一套基于路由的方式,實現多租戶overlay網絡的外網網關。在方案設計過程中,我們也碰到了諸如lwtunnel和flow offload功能不足,以及VRF和flow offload不能一起有效的工作等問題。最終我們都設法解決了,并針對這些內核的不足提交patch給Linux開源社區。 1. lwtunnel發送報文tunnel_key丟失 **問題描述:**我們利用lwtunnel路由方式發送報文時,創建了一個external類型的gretap tunnel,我們將命令設置了id為1000,但是發送成功報文中沒有tunnel_key字段。 **問題定位:**我們研究iproute2代碼,發現由于TUNNEL_KEY flag并沒有開放給用戶態,所以iproute2工具并沒有對lwtunnel路由設置TUNNEL_KEY,導致報文不會創建tunnel_key字段。 提交patch: 我們給內核和用戶態iproute2分別提交patch來解決這一問題: iptunnel: make TUNNEL_FLAGS available in uapi https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git/commit/ ? id=1875a9ab01dfa96b06cb6649cb1ce56efa86c7cb iproute: Set ip/ip6 lwtunnel flags https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=3d65cefbefc86a53877f1e6461a9461e5b8fd7b3 提交patch后,可以通過以下方式設置路由。 ip r r 2.2.2.11 via 1.1.1.11 dev tun encap ip id 1000 dst 172.168.0.1 key 2. lwtunnel對指定key的IP tunnel無效 **問題發現:**為了能有效隔離租戶路由,我們給每個租戶創建一個基于tunnel_key的gretap tunnel設備。如下圖,創建一個tunnel_key 1000的gretap tunnel設備,把tunnel設備加入租戶所屬VRF,tunnel設備能有效地接收報文,但并不能發送報文。 問題定位:研究內核發現,IP tunnel在非external模式下即使指定了輕量級隧道路由,發送報文也沒有使用它,導致報文路由錯誤被丟棄。 提交patch: ip_tunnel: Make none-tunnel-dst tunnel port work with lwtunnel https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d71b57532d70c03f4671dd04e84157ac6bf021b0 提交patch后,在未指定tunnel_dst的非external模式IP tunnel下,能使用輕量級隧道路由進行發送報文。 3. external IP tunnel ARP無法正常運行 **問題描述:**鄰居IP tunnel進行了ARP請求,但是本端的ARP回應報文的隧道頭中并沒帶tunnel_key字段。 **問題定位:**研究代碼發現,tunnel收到了對端的ARP 請求,在發送報文ARP回復的時候會復制請求報文的tunnel信息,但是遺漏了所有tun_flags。 提交patch: iptunnel: Set tun_flags in the iptunnel_metadata_reply from src https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bdca378b2301b1fc6a95c60d6d428408ae4e39e 4. Flow offload不能與DNAT有效工作 **問題描述:**Firewall創建規則從eth0收到目的地址2.2.2.11的報文,DNAT為10.0.0.7, flow offload無法工作。 **問題定位:**分析發現,客戶端1.1.1.7 —> 2.2.2.7 DNAT到server 10.0.0.7,第一個reply反向報文(syc+ack)使用了錯的目的地址獲取反向路由 daddr = ct->tuplehash[!dir].tuple.dst.u3.ip 此時dir為反方向,所以daddr獲取為原方向的目的地址,這個值是2.2.2.7, 但是由于被DNAT過,真正的路由不應該通過2.2.2.7去獲取,而是應該根據10.0.0.7這個值去獲取 addr = ct->tuplehash[dir].tuple.src.u3.ip 提交patch: netfilter: nft_flow_offload: Fix reverse route lookup https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a799aea0988ea0d1b1f263e996fdad2f6133c680 5. Flow offload不能與VRF有效工作 問題描述: 將網卡eth0和eth1加入VFR后,flow offload不起作用。 **問題定位:**查看代碼發現,原方向和反方向首報文進入協議堆棧后skb->dev會設置為vrf device user1,創建flow offload規則的iif就是user1。但是offload規則下發在eth0和eth1的ingress hook上,所以后續報文在eth0和eth1的ingress hook上不能匹配flow規則。 提交patch: netfilter: nft_flow_offload: fix interaction with vrf slave device https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=10f4e765879e514e1ce7f52ed26603047af196e2 最終,我們根據兩個方向查找路由的結果,設置flow offload規則的iif和oif信息來解決此問題。 6. VRF PREROUTING hook重入問題 **問題描述:**配置網卡加入VRF,firewall ingress方向規則為接收目的地址2.2.2.11 、TCP 目的端口22的報文,egress方向規則為丟棄TCP 目的端口 22的報文。出現異常結果: 收到目的地址2.2.2.11 TCP 22目的端口的報文卻被丟棄。 **問題定位:**研究發現網卡加入VRF后收到的報文會兩次進入PREROUTING hook,因為在進入IP stack時會進第一次PREROUTING hook,然后被VRF設備接管后會再次進入PREROUTING hook。上述規則第一次在rule-1000-ingress chain中dst nat為10.0.0.7,第二次由于報文被DNAT后會錯誤的進入rule-1000-egress,導致報文被丟棄。 **提交patch:**我們給內核加了一個支持判斷網卡類型的match項目,讓用戶態避免可知的第二次無效重入,內核態和用戶態nftables分別提交了如下的patch: netfilter: nft_meta: Add NFT_META_I/OIFKIND meta type https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git/commit/?id=0fb4d21956f4a9af225594a46857ccf29bd747bc meta: add iifkind and oifkind support http://git.netfilter.org/nftables/commit/?id=512795a673f999fb04b84dbbbe41174e9c581430 使用方法: nft add rule firewall rules-all meta iifkind "vrf" counter accept 原型驗證 最終,我們成功地利用lwtunnel、VRF和flow offload實現多租戶外網網關的原型驗證。驗證過程如下: 1. 首先創建原型環境。 a. netns cl模擬外網client, 地址為1.1.1.7,tunnel src 172.168.0.7,配置發送路由; b. netns ns1模擬租戶1,內網地址為10.0.0.7,外網地址為 2.2.2.11,tunnel src 172.168.0.11 tunnel_key 1000,配置發送路由; c. netns ns2模擬租戶2,內網地址為10.0.0.7,外網地址為 2.2.2.12,tunnel src 172.168.0.12 tunnel_key 2000,配置發送路由; d. Host模擬外網網關,tunnel src 172.168.0.1,創建租戶VRF user1和use2,創建租戶IP tunnel tun1和tun2,配置轉發路由。 原型環境圖如下: 2. 創建firewall規則: a. 租戶1入向允許TCP目的端口22和ICMP訪問,出向禁止訪問外部TCP 22目的端口; b. 租戶2入向允許TCP端口23和ICMP訪問,出向禁止訪問外部TCP 23目的端口; c. 在租戶tun1和tun2設備上支持flow offload。 最終,client可以通過2.2.2.11成功訪問user1 tcp 22端口服務,user1不能訪問client tcp 22端口服務;client可以通過2.2.2.12成功訪問user2 tcp 23端口服務,user1不能訪問client tcp 23端口服務。 待后續hardware offload功能完善以及網卡廠商支持后,我們會做進一步的開發驗證。 寫在最后 以上是本項目涉及的部分核心問題,這些patch特性都可以在Linux kernel 5.0版本里獲取。我們把這期間為Linux kernel社區貢獻的patch整理成了一份列表,希望能為開發者提供幫助,讀者可以點擊 “閱讀原文” 閱覽完整patch list。 Linux作為成熟的開源套件,一直是云廠商使用的主流操作系統,但在技術的更新迭代過程中,一些新特性在實際應用上也會存在穩定性、兼容性等方面的問題。我們在研究使用上游技術的同時,也一直積極探索、豐富開源技術功能,幫助提高開源技術穩定性。并將產出持續回饋給社區,與社區共同構建一個繁榮的開源生態。
來源:OSCHINA
發布時間:2019-03-25 17:57:00
如果master異常,則會進行master-slave切換,將其中一個slave作為master,將之前的master作為slave。 哨兵作用 哨兵是Redis集群架構中非常重要的一個組件,主要功能如下: 集群監控:負責監控redis master和slave進程是否正常 消息通知:如果某個redis實例有故障,那么哨兵負責發送消息作為報警通知給管理員 故障轉移:如果master節點掛掉了,會自動轉移到slave節點上 配置中心:如果故障轉移發生了,通知client客戶端新的master地址 哨兵的核心知識 故障轉移時,判斷一個master節點是否宕機了,需要大部分的哨兵都同意才行,涉及到了分布式選舉的問題 哨兵至少需要3個實例,來保證自己的健壯性 哨兵+redis主從的部署架構,是不會保證數據零丟失的,只能保證redis集群的高可用性 sdown和odown sdown和odown兩種失敗的狀態 sdown是主觀宕機,就一個哨兵如果自己覺得一個master宕機了,那么就是主觀宕機 odown是客觀宕機,如果quorum數量的哨兵都覺得一個master宕機了,那么就是客觀宕機 sdown達成的條件:如果一個哨兵ping一個master,超過了is-master-down-after-milliseconds指定的毫秒數之后,就認為master宕機 odown達成條件:如果一個哨兵在指定的時間內,收到了quorum指定數量的其他哨兵也認為那個master是宕機了,那么就認為是odown了,客觀認為master宕機了 quorum和majority quorum:確認odown的最少哨兵數量 majority:授權進行主從切換的最少哨兵數量 每一個哨兵要做主備切換,首先需要quorum數量的哨兵認為odown,然后選舉出一個哨兵來做切換,這個哨兵還得得到majority哨兵的特權,才能進行切換。 如果quorummajority,那么必須quorum數量的哨兵都授權,比如5個哨兵,quorum是5,那么必須5個哨兵都同意授權才能執行。(誰多聽誰的) 為什么哨兵至少3個節點? 哨兵集群必須部署兩個以上節點。如果哨兵集群僅僅部署了2個哨兵實例,那么它的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),如果其中一個哨兵宕機了,就無法滿足majority>=2這個條件,那么master發生故障時也就無法進行主從切換了。 工作原理 每個Sentienl以每秒鐘一次的頻率向他所知的Master,Slave以及其他的Sentinel實例發送一個ping命令 如果一個實例距離最后一次有效回復ping命令的時間超過了down-after-milliseconds選項所指的值,則這個實例會被Sentinel標記為主觀宕機 如果一個master被標記為主觀宕機,則正在監視這個master的所有sentinel要以每一秒一次的頻率確認Master的確進入了主觀宕機狀態 當有足夠數量的Sentinel(大于等于配置文件所指的值)在指定的時間范圍內確認master的確進入了主觀宕機狀態,則master會被標記為客觀狀態 在一般情況下,每個Sentinel會以1次/10秒的頻率向他一致的所有master,slave發送INFO命令 當master被Sentinel標記為客觀宕機是,Sentinel向下線的master的所有slave發送INFO命令的頻率會從1次/10秒改為1次/秒 若沒有足夠數量的Sentinel同意master已經下線,master的客觀宕機狀態就會被移除;若master重新想Sentinel的ping命令返回有效回復,master的主觀宕機狀態就會被移除。 哨兵模式的配置 首先 配置redis的主從服務器 ,修改redis.conf文件如下 # 使得Redis服務器可以跨網絡訪問 bind 0.0.0.0 # 設置密碼 requirepass "123456" # 指定主服務器,注意:有關slaveof的配置只是配置從服務器,主服務器不需要配置 slaveof 192.168.11.128 6379 # 主服務器密碼,注意:有關slaveof的配置只是配置從服務器,主服務器不需要配置 masterauth 123456 上述內容主要是配置Redis服務器,從服務器比主服務器多了一個slaveof的配置和密碼 配置3個哨兵 ,每個哨兵都是一樣的。在Redis安裝目錄下有一個sentinel.conf文件,copy一份進行修改 # 禁止保護模式 protected-mode no # 配置監聽的主服務器,這里sentinel monitor代表監控,mymaster代表服務器的名稱,可以自定義,192.168.11.128代表監控的主服務器,6379代表端口,2代表只有兩個或兩個以上的哨兵認為主服務器不可用的時候,才會進行failover操作。 sentinel monitor mymaster 192.168.11.128 6379 2 # sentinel author-pass定義服務的密碼,mymaster是服務名稱,123456是Redis服務器密碼 # sentinel auth-pass sentinel auth-pass mymaster 123456 啟動服務器和哨兵 ,進入Redis安裝目錄的src目錄 # 啟動Redis服務器進程 ./redis-server ../redis.conf # 啟動哨兵進程 ./redis-sentinel ../sentinel.conf 注意啟動順序: 首先是主機(192.168.11.128)的Redis服務進程,然后啟動叢機的服務進程,最后啟動3個哨兵的服務進程 Java中使用哨兵模式 /** * 測試Redis哨兵模式 * @author liu */ public class TestSentinels { @SuppressWarnings("resource") @Test public void testSentinel() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(10); jedisPoolConfig.setMaxIdle(5); jedisPoolConfig.setMinIdle(5); // 哨兵信息 Set sentinels = new HashSet<>(Arrays.asList("192.168.11.128:26379", "192.168.11.129:26379","192.168.11.130:26379")); // 創建連接池 JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels,jedisPoolConfig,"123456"); // 獲取客戶端 Jedis jedis = pool.getResource(); // 執行兩個命令 jedis.set("mykey", "myvalue"); String value = jedis.get("mykey"); System.out.println(value); } }
來源:OSCHINA
發布時間:2020-03-26 11:24:00
MySql的鏡像,默認情況下,MySql5.7中的sql_mode含有only_full_group_by,group by語句有時候會報錯。通過手動修改sql_mode,那么如果刪除容器或者新建容器,就會導致我們手動設置的sql_mode失效,所以自己制作一個基于mysql鏡像的鏡像,解決sql_mode含有only_full_group_by的問題。 運行容器 List-1 mjduan@mjduan:/opt % docker run -d -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD --name mysql1 mysql:5.7.9 5514c31a4e0bc524cee3cdcb962ac73b4fdeb1b5b32d70fa2840e9029b203a8c 進入容器安裝vim List-2 mjduan@mjduan:/opt % docker exec -ti mysql1 /bin/bash #在容器內執行如下命令 root@44504961189a:/opt % apt-get update ...... root@44504961189a:/opt % apt-get install vim ...... 在容器內,/etc/mysql/conf.d/下,新建.cnf文件,將配置寫入到里面,我們新建custom.cnf文件,寫入如下內容: List-3 root@44504961189a:/# more /etc/mysql/conf.d/custom.cnf [mysqld] sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 退出容器,用docker commit命令由容器制作鏡像, List-4 mjduan@mjduan:/opt % docker commit mysql1 mysql_custom:1.0 之后docker images就可以看到鏡像mysql_custom:1.0了。 mysql_custom:1.0就是我們需要的,用它來啟動容器,如下List-5 List-5 mjduan@mjduan:/opt % docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=新密碼 --name mysql_mjduan2 mysql_custom:1.0 44504961189a45442a6b33e5945778b73bc3dd058ab9e794c56b0bbfc3e603bf 之后進入容器mysql_mjduan2,用mysql命令進入mysql命令行時就會提示要root密碼了。再次查看sql_mode,就會看到sql_mode沒有only_full_group_by了。 注意,不要讓別人拿到你的鏡像,否則通過docker inspect命令就可以看到你設置的root密碼。不過可以在創建容器的時候用MYSQL_ROOT_PASSWORD來設置新的root密碼。 注: 也可以直接用Dockerfile基于mysql的鏡像進行制作。
來源:OSCHINA
發布時間:2019-03-24 20:48:00
Redis不僅支持簡單的key-value數據類型,同時還提供string、list、set、zset、hash等數據結構的存儲;而Memcached僅僅支持簡單的key-value數據類型。 Redis支持數據的持久化,可以將內存中的數據保存到磁盤中,重啟的時候再次加載使用;而Memcached將數據全部存于內存中。 Redis支持數據備份,即master-slave模式的數據備份。 Redis比Memcached的(讀寫)速度要快很多。 Redis使用的是單線程IO復用模型;而Memcached使用的是多線程非阻塞IO復用模型。
來源:OSCHINA
發布時間:2020-03-26 11:24:00
RadosClient.h class librados::RadosClient : public Dispatcher //繼承自Dispatcher(消息分發類) { public: using Dispatcher::cct; md_config_t *conf; //配置文件 private: enum { DISCONNECTED, CONNECTING, CONNECTED, } state; //網絡連接狀態 MonClient monclient; // monc Messenger *messenger; //網絡消息接口 uint64_t instance_id; //相關消息分發 Dispatcher類的函數重寫 bool _dispatch(Message *m); bool ms_dispatch(Message *m); bool ms_get_authorizer(int dest_type, AuthAuthorizer **authorizer, bool force_new); void ms_handle_connect(Connection *con); bool ms_handle_reset(Connection *con); void ms_handle_remote_reset(Connection *con); Objecter *objecter; // Osdc模塊中的 用于發送封裝好的OP消息 Mutex lock; Cond cond; SafeTimer timer; //定時器 int refcnt; version_t log_last_version; rados_log_callback_t log_cb; void *log_cb_arg; string log_watch; int wait_for_osdmap(); public: Finisher finisher; // 回調函數的類 explicit RadosClient(CephContext *cct_); ~RadosClient(); int ping_monitor(string mon_id, string *result); int connect(); // 連接 void shutdown(); int watch_flush(); int async_watch_flush(AioCompletionImpl *c); uint64_t get_instance_id(); int wait_for_latest_osdmap(); // 根據pool名字或id創建ioctx int create_ioctx(const char *name, IoCtxImpl **io); int create_ioctx(int64_t, IoCtxImpl **io); int get_fsid(std::string *s); // pool相關操作 int64_t lookup_pool(const char *name); bool pool_requires_alignment(int64_t pool_id); int pool_requires_alignment2(int64_t pool_id, bool *requires); uint64_t pool_required_alignment(int64_t pool_id); int pool_required_alignment2(int64_t pool_id, uint64_t *alignment); int pool_get_auid(uint64_t pool_id, unsigned long long *auid); int pool_get_name(uint64_t pool_id, std::string *auid); int pool_list(std::list >& ls); int get_pool_stats(std::list& ls, map& result); int get_fs_stats(ceph_statfs& result); /* -1 was set as the default value and monitor will pickup the right crush rule with below order: a) osd pool default crush replicated ruleset b) the first ruleset in crush ruleset c) error out if no value find */ // 同步創建pool 和 異步創建pool int pool_create(string& name, unsigned long long auid=0, int16_t crush_rule=-1); int pool_create_async(string& name, PoolAsyncCompletionImpl *c, unsigned long long auid=0, int16_t crush_rule=-1); int pool_get_base_tier(int64_t pool_id, int64_t* base_tier); //同步刪除和異步刪除 int pool_delete(const char *name); int pool_delete_async(const char *name, PoolAsyncCompletionImpl *c); int blacklist_add(const string& client_address, uint32_t expire_seconds); //Mon命令處理,調用monclient.start_mon_command 把命令發送給Mon處理 int mon_command(const vector& cmd, const bufferlist &inbl, bufferlist *outbl, string *outs); int mon_command(int rank, const vector& cmd, const bufferlist &inbl, bufferlist *outbl, string *outs); int mon_command(string name, const vector& cmd, const bufferlist &inbl, bufferlist *outbl, string *outs); //OSD命令處理,objector->osd_command 把命令發送給OSD處理 int osd_command(int osd, vector& cmd, const bufferlist& inbl, bufferlist *poutbl, string *prs); //PG命令處理,objector->pg_command 把命令發送給PG處理 int pg_command(pg_t pgid, vector& cmd, const bufferlist& inbl, bufferlist *poutbl, string *prs); void handle_log(MLog *m); int monitor_log(const string& level, rados_log_callback_t cb, void *arg); void get(); bool put(); void blacklist_self(bool set); }; connect() 連接 int librados::RadosClient::connect() { common_init_finish(cct); int err; // already connected? if (state == CONNECTING) return -EINPROGRESS; if (state == CONNECTED) return -EISCONN; state = CONNECTING; // get monmap err = monclient.build_initial_monmap(); //通過配置文件獲取初始化的Monitor if (err < 0) goto out; err = -ENOMEM; messenger = Messenger::create_client_messenger(cct, "radosclient"); //創建通信模塊 if (!messenger) goto out; // require OSDREPLYMUX feature. this means we will fail to talk to // old servers. this is necessary because otherwise we won't know // how to decompose the reply data into its consituent pieces. messenger->set_default_policy(Messenger::Policy::lossy_client(0, CEPH_FEATURE_OSDREPLYMUX)); ldout(cct, 1) << "starting msgr at " << messenger->get_myaddr() << dendl; ldout(cct, 1) << "starting objecter" << dendl; //創建objecter objecter = new (std::nothrow) Objecter(cct, messenger, &monclient, &finisher, cct->_conf->rados_mon_op_timeout, cct->_conf->rados_osd_op_timeout); if (!objecter) goto out; objecter->set_balanced_budget(); // mc添加 messenger monclient.set_messenger(messenger); // objecter 初始化 objecter->init(); // messenger添加 dispather messenger->add_dispatcher_tail(objecter); messenger->add_dispatcher_tail(this); // messenger啟動 messenger->start(); ldout(cct, 1) << "setting wanted keys" << dendl; monclient.set_want_keys(CEPH_ENTITY_TYPE_MON | CEPH_ENTITY_TYPE_OSD); ldout(cct, 1) << "calling monclient init" << dendl; // mc 初始化 err = monclient.init(); if (err) { ldout(cct, 0) << conf->name << " initialization error " << cpp_strerror(-err) << dendl; shutdown(); goto out; } err = monclient.authenticate(conf->client_mount_timeout); if (err) { ldout(cct, 0) << conf->name << " authentication error " << cpp_strerror(-err) << dendl; shutdown(); goto out; } messenger->set_myname(entity_name_t::CLIENT(monclient.get_global_id())); objecter->set_client_incarnation(0); // objecter 啟動 objecter->start(); lock.Lock(); // 定時器初始化 timer.init(); monclient.renew_subs(); //執行回調的完成類start finisher.start(); // 更新 狀態為已連接 state = CONNECTED; instance_id = monclient.get_global_id(); ... } create_ioctx 根據pool創建ioctx int librados::RadosClient::create_ioctx(const char *name, IoCtxImpl **io) { // 獲取 poolid int64_t poolid = lookup_pool(name); ... // 創建 IoCtxImpl *io = new librados::IoCtxImpl(this, objecter, poolid, CEPH_NOSNAP); return 0; } Mon OSD pg 命令操作 int librados::RadosClient::mon_command(const vector& cmd, const bufferlist &inbl, bufferlist *outbl, string *outs) { // mc start_mon_command 發送到monitor monclient.start_mon_command(cmd, inbl, outbl, outs, new C_SafeCond(&mylock, &cond, &done, &rval)); } int librados::RadosClient::osd_command(int osd, vector& cmd, const bufferlist& inbl, bufferlist *poutbl, string *prs) { // 發送到osd int r = objecter->osd_command(osd, cmd, inbl, &tid, poutbl, prs, new C_SafeCond(&mylock, &cond, &done, &ret)); } int librados::RadosClient::pg_command(pg_t pgid, vector& cmd, const bufferlist& inbl, bufferlist *poutbl, string *prs) { // 發送到pg int r = objecter->pg_command(pgid, cmd, inbl, &tid, poutbl, prs, new C_SafeCond(&mylock, &cond, &done, &ret)); } Ioctximpl librados::IoCtx的實現IoCtxImpl 把請求封裝成ObjectOperation 類(osdc 中的) 把相關的pool信息添加到里面,封裝成Objecter::Op對像 調用相應的函數 objecter- >op_submit 發送給相應的OSD 操作完成后,調用相應的回調函數。 如rados_write extern "C" int rados_write(rados_ioctx_t io, const char *o, const char *buf, size_t len, uint64_t off) { librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io; object_t oid(o); bufferlist bl; bl.append(buf, len); int retval = ctx->write(oid, bl, len, off); } 調用IoCtxImpl::write int librados::IoCtxImpl::write(const object_t& oid, bufferlist& bl, size_t len, uint64_t off) { ::ObjectOperation op; prepare_assert_ops(&op); // assert ops bufferlist mybl; mybl.substr_of(bl, 0, len); op.write(off, mybl); // 封裝到op.write Objecter.h ObjectOperation write return operate(oid, &op, NULL); // IoCtxImpl::operate } int librados::IoCtxImpl::operate(const object_t& oid, ::ObjectOperation *o, ceph::real_time *pmtime, int flags) { int op = o->ops[0].op.op; Objecter::Op *objecter_op = objecter->prepare_mutate_op(oid, oloc, *o, snapc, ut, flags, NULL, oncommit, &ver); objecter->op_submit(objecter_op); } AioCompletionImpl OSDC ObjectOperation struct ObjectOperation { vector ops; // ops集合 int flags; int priority; vector out_bl; // 輸出bufferlist vector out_handler; // 回調函數 vector out_rval; // 返回碼集合 size_t size() { // op個數 return ops.size(); } /** * This is a more limited form of C_Contexts, but that requires * a ceph_context which we don't have here. */ // 用戶添加回調函數 class C_TwoContexts : public Context { Context *first; Context *second; }; /** * Add a callback to run when this operation completes, * after any other callbacks for it. */ // 添加回調函數 void add_handler(Context *extra) { size_t last = out_handler.size() - 1; Context *orig = out_handler[last]; if (orig) { Context *wrapper = new C_TwoContexts(orig, extra); out_handler[last] = wrapper; } else { out_handler[last] = extra; } } // 添加操作 OSDOp& add_op(int op) { int s = ops.size(); ops.resize(s+1); ops[s].op.op = op; out_bl.resize(s+1); out_bl[s] = NULL; out_handler.resize(s+1); out_handler[s] = NULL; out_rval.resize(s+1); out_rval[s] = NULL; return ops[s]; } // 添加data void add_data(int op, uint64_t off, uint64_t len, bufferlist& bl) { OSDOp& osd_op = add_op(op); osd_op.op.extent.offset = off; osd_op.op.extent.length = len; osd_op.indata.claim_append(bl); } void add_clone_range(int op, uint64_t off, uint64_t len, const object_t& srcoid, uint64_t srcoff, snapid_t srcsnapid) {} void add_xattr(int op, const char *name, const bufferlist& data) {} void add_xattr_cmp(int op, const char *name, uint8_t cmp_op, uint8_t cmp_mode, const bufferlist& data) {} // 添加call method void add_call(int op, const char *cname, const char *method, bufferlist &indata, bufferlist *outbl, Context *ctx, int *prval) { OSDOp& osd_op = add_op(op); unsigned p = ops.size() - 1; out_handler[p] = ctx; out_bl[p] = outbl; out_rval[p] = prval; osd_op.op.cls.class_len = strlen(cname); osd_op.op.cls.method_len = strlen(method); osd_op.op.cls.indata_len = indata.length(); osd_op.indata.append(cname, osd_op.op.cls.class_len); osd_op.indata.append(method, osd_op.op.cls.method_len); osd_op.indata.append(indata); } void add_pgls(int op, uint64_t count, collection_list_handle_t cookie, epoch_t start_epoch) {} void add_pgls_filter(int op, uint64_t count, const bufferlist& filter, collection_list_handle_t cookie, epoch_t start_epoch) {} void add_alloc_hint(int op, uint64_t expected_object_size, uint64_t expected_write_size) {} // ------ // pg 操作 void pg_ls(uint64_t count, bufferlist& filter, collection_list_handle_t cookie, epoch_t start_epoch) {} void pg_nls(uint64_t count, const bufferlist& filter, collection_list_handle_t cookie, epoch_t start_epoch) {} // 創建 操作 void create(bool excl) { OSDOp& o = add_op(CEPH_OSD_OP_CREATE); o.op.flags = (excl ? CEPH_OSD_OP_FLAG_EXCL : 0); } // 狀態 struct C_ObjectOperation_stat : public Context { bufferlist bl; uint64_t *psize; ceph::real_time *pmtime; time_t *ptime; struct timespec *pts; int *prval; // 完成大小,時間等 void finish(int r) {} } }; // 查看狀態,獲取C_ObjectOperation_stat void stat(uint64_t *psize, ceph::real_time *pmtime, int *prval) {} void stat(uint64_t *psize, time_t *ptime, int *prval) {} void stat(uint64_t *psize, struct timespec *pts, int *prval) {} // object data // 讀操作 void read(uint64_t off, uint64_t len, bufferlist *pbl, int *prval, Context* ctx) { bufferlist bl; add_data(CEPH_OSD_OP_READ, off, len, bl); unsigned p = ops.size() - 1; out_bl[p] = pbl; out_rval[p] = prval; out_handler[p] = ctx; } void sparse_read(uint64_t off, uint64_t len, std::map *m, bufferlist *data_bl, int *prval) {} // 寫操作 void write(uint64_t off, bufferlist& bl, uint64_t truncate_size, uint32_t truncate_seq) { add_data(CEPH_OSD_OP_WRITE, off, bl.length(), bl); // 添加data, 將WRITE存入ops,將數據放在op中 OSDOp& o = *ops.rbegin(); o.op.extent.truncate_size = truncate_size; o.op.extent.truncate_seq = truncate_seq; } void write(uint64_t off, bufferlist& bl) {} void write_full(bufferlist& bl) {} void append(bufferlist& bl) {} void zero(uint64_t off, uint64_t len) {} void truncate(uint64_t off) {} void remove() {} void mapext(uint64_t off, uint64_t len) {} void sparse_read(uint64_t off, uint64_t len) {} void clone_range(const object_t& src_oid, uint64_t src_offset, uint64_t len, uint64_t dst_offset) {} // object attrs // 屬性操作 void getxattr(const char *name, bufferlist *pbl, int *prval) {} void getxattrs(std::map *pattrs, int *prval) {} void setxattr(const char *name, const bufferlist& bl) {} void setxattr(const char *name, const string& s) {} void cmpxattr(const char *name, uint8_t cmp_op, uint8_t cmp_mode, const bufferlist& bl) {} void rmxattr(const char *name) {} void setxattrs(map& attrs) {} void resetxattrs(const char *prefix, map& attrs) {} // trivialmap void tmap_update(bufferlist& bl) {} void tmap_put(bufferlist& bl) {} void tmap_get(bufferlist *pbl, int *prval) {} void tmap_get() {} void tmap_to_omap(bool nullok=false) {} // objectmap void omap_get_keys(const string &start_after, uint64_t max_to_get, std::set *out_set, int *prval) { OSDOp &op = add_op(CEPH_OSD_OP_OMAPGETKEYS); bufferlist bl; ::encode(start_after, bl); ::encode(max_to_get, bl); op.op.extent.offset = 0; op.op.extent.length = bl.length(); op.indata.claim_append(bl); if (prval || out_set) { unsigned p = ops.size() - 1; C_ObjectOperation_decodekeys *h = new C_ObjectOperation_decodekeys(out_set, prval); out_handler[p] = h; out_bl[p] = &h->bl; out_rval[p] = prval; } } void omap_get_vals(const string &start_after, const string &filter_prefix, uint64_t max_to_get, std::map *out_set, int *prval) {} void omap_get_vals_by_keys(const std::set &to_get, std::map *out_set, int *prval) {} void omap_cmp(const std::map > &assertions, int *prval) {} void copy_get(object_copy_cursor_t *cursor, uint64_t max, uint64_t *out_size, ceph::real_time *out_mtime, std::map *out_attrs, bufferlist *out_data, bufferlist *out_omap_header, bufferlist *out_omap_data, vector *out_snaps, snapid_t *out_snap_seq, uint32_t *out_flags, uint32_t *out_data_digest, uint32_t *out_omap_digest, vector > *out_reqids, uint64_t *truncate_seq, uint64_t *truncate_size, int *prval) {} void undirty() {} struct C_ObjectOperation_isdirty : public Context {}; void is_dirty(bool *pisdirty, int *prval) {} void omap_get_header(bufferlist *bl, int *prval) {} void omap_set(const map &map) {} void omap_set_header(bufferlist &bl) {} void omap_clear() {} void omap_rm_keys(const std::set &to_remove) {} // object classes void call(const char *cname, const char *method, bufferlist &indata) {} void call(const char *cname, const char *method, bufferlist &indata, bufferlist *outdata, Context *ctx, int *prval) {} void rollback(uint64_t snapid) {} void copy_from(object_t src, snapid_t snapid, object_locator_t src_oloc, version_t src_version, unsigned flags, unsigned src_fadvise_flags) {} }; OSDOp osd_types.h struct OSDOp { ceph_osd_op op; // 操作 sobject_t soid; // oid bufferlist indata, outdata; // 輸入輸出data int32_t rval; // 返回碼 }; Objecter class Objecter : public md_config_obs_t, public Dispatcher { public: Messenger *messenger; // 消息 MonClient *monc; // mc Finisher *finisher; private: OSDMap *osdmap; // osdmap public: using Dispatcher::cct; std::multimap crush_location; atomic_t initialized; private: atomic64_t last_tid; atomic_t inflight_ops; atomic_t client_inc; uint64_t max_linger_id; atomic_t num_unacked; atomic_t num_uncommitted; atomic_t global_op_flags; // flags which are applied to each IO op bool keep_balanced_budget; bool honor_osdmap_full; public: void maybe_request_map(); private: void _maybe_request_map(); version_t last_seen_osdmap_version; version_t last_seen_pgmap_version; mutable boost::shared_mutex rwlock; using lock_guard = std::unique_lock; using unique_lock = std::unique_lock; using shared_lock = boost::shared_lock; using shunique_lock = ceph::shunique_lock; ceph::timer timer; PerfCounters *logger; uint64_t tick_event; void start_tick(); void tick(); void update_crush_location(); public: /*** track pending operations ***/ // read public: struct OSDSession; struct op_target_t {} // 操作目標 struct Op : public RefCountedObject {}; // 操作 }; op_target_t 操作目標,封裝pg信息,osd信息 struct op_target_t { int flags; object_t base_oid; object_locator_t base_oloc; object_t target_oid; // 目標oid object_locator_t target_oloc; // 位置 // 是否 base_pgid bool precalc_pgid; ///< true if we are directed at base_pgid, not base_oid // 直接的 pgid pg_t base_pgid; ///< explciti pg target, if any pg_t pgid; ///< last pg we mapped to unsigned pg_num; ///< last pg_num we mapped to unsigned pg_num_mask; ///< last pg_num_mask we mapped to // 啟動的osd vector up; ///< set of up osds for last pg we mapped to // acting osd vector acting; ///< set of acting osds for last pg we mapped to // primary int up_primary; ///< primary for last pg we mapped to based on the up set int acting_primary; ///< primary for last pg we mapped to based on the /// acting set // pool 大小 int size; ///< the size of the pool when were were last mapped // pool 最小size int min_size; ///< the min size of the pool when were were last mapped // 是否按位排序 bool sort_bitwise; ///< whether the hobject_t sort order is bitwise // 是否副本 bool used_replica; bool paused; int osd; ///< the final target osd, or -1 }; 操作 struct Op : public RefCountedObject { OSDSession *session; // session 連接 int incarnation; op_target_t target; // 操作目標 ConnectionRef con; // for rx buffer only uint64_t features; // explicitly specified op features vector ops; // 操作集合 snapid_t snapid; SnapContext snapc; ceph::real_time mtime; bufferlist *outbl; vector out_bl; vector out_handler; vector out_rval; int priority; Context *onack, *oncommit; uint64_t ontimeout; Context *oncommit_sync; // used internally by watch/notify ceph_tid_t tid; eversion_t replay_version; // for op replay int attempts; version_t *objver; epoch_t *reply_epoch; ceph::mono_time stamp; epoch_t map_dne_bound; bool budgeted; /// true if we should resend this message on failure bool should_resend; /// true if the throttle budget is get/put on a series of OPs, /// instead of per OP basis, when this flag is set, the budget is /// acquired before sending the very first OP of the series and /// released upon receiving the last OP reply. bool ctx_budgeted; int *data_offset; epoch_t last_force_resend; osd_reqid_t reqid; // explicitly setting reqid }; 分片 Striper 擴展 ObjectExtent 記錄分片信息 class ObjectExtent { public: object_t oid; // object id uint64_t objectno; // 序號 uint64_t offset; // in object object內偏移 uint64_t length; // in object 分片長度 uint64_t truncate_size; // in object object_locator_t oloc; // object locator (pool etc) pool位置 vector > buffer_extents; // off -> len. extents in buffer being mapped (may be fragmented bc of striping!) };
來源:OSCHINA
發布時間:2019-03-23 18:45:00
Redis是一個高性能的、開源免費的、遵守BSD協議的key-value數據庫。有以下三個特點:第一,Redis支持數據的持久化,可以將內存中數據保存在磁盤中,重啟的時候可以再次加載進行使用;第二,Redis不僅僅支持簡單的key-value類型的數據,同時還支持string、list、set、sorted set(zset)、hash數據類型;第三,Redis支持數據的備份,即master-slave模式的數據備份。
來源:OSCHINA
發布時間:2020-03-26 10:38:00
本文作者:AIOps智能運維 干貨概覽 人生病了要去看醫生,程序生病了看的就是運維工程師了。醫生給病人看病要做很多檢查,然后拿著結果單子分析病因。運維工程師分析系統故障也會查看采集的監控指標,然后判斷根因是什么。 查看指標這事兒,說起來也不難。只要畫出指標的趨勢圖(指標值隨時間變化的曲線),有經驗的工程師很容易就能看出有沒有毛病,進而推斷故障的原因。不過呢,都說脫離開劑量說食物的毒性是耍流氓,查看指標這事也差不多。如果只有幾條指標需要查看,做個儀表盤就能一目了然,可是如果有成千上萬的指標呢?人家查抄大老虎的時候點鈔機都燒壞了好幾臺,如果人工查看這么多指標,腦子的下場估計也好不到哪兒去。所以說還是得靠“機器人”。 等等,“機器人”怎么能知道什么指標有毛病,什么指標沒毛病呢?就算能知道,把有毛病的指標挑出來工程師憑啥就能知道根因呢?所以,我們的“機器人醫生”必須能夠識別出指標的異常,然后還需要能把識別出的異常整理成工程師容易理解的報告才行。 傳統的辦法 人工診斷故障的時候,工程師往往是根據腦子里的模塊調用關系圖(圖 1)來排查系統。很多時候,故障都是因為在最上游的前端模塊(圖 1中的A)上看到了很多失敗的請求發現的。這時,工程師就會沿著A往下查。因為A調用了B模塊,所以需要查看B的指標,如果有指標異常那么就懷疑是B導致了故障。然后再檢查B的直接下游模塊C,以此類推。在這個過程中,懷疑通過模塊的調用關系不斷往下傳遞,直到傳不下去為止。在圖 1的例子中懷疑最后就停在了倒霉蛋G的頭上,誰讓它沒有下游模塊呢。 總的來說,這就是模塊間把責任想辦法往下游推的過程。當然,真實的場景要更加復雜一些。并不是只要下游有異常就可以推的,還需要考察異常的程度。比如,如果倒霉蛋G的異常程度比E的異常程度小很多,根因就更有可能在E里面。 找到了根因模塊再去分析根因就容易多了,所以尋找根因模塊是故障診斷中很重要的步驟。 上面的過程可以很直接地變成一個工具: 做一個頁面展示模塊調用關系圖 工程師為每個指標配置黃金指標,以及黃金指標的閾值 在模塊圖中標出黃金指標有異常的模塊以及它們到達前端模塊的可能路徑 這個工具通過配置黃金指標及閾值的方式解決了指標以及如何判斷異常的問題,然后再通過模塊調用關系圖的方式呈現異常判斷的結果,解決了異常判斷和結果整理這兩個核心問題。 不過,傳統的辦法在實際使用中還是會碰到很多問題: 1.活的系統一定是不斷演化的,模塊的調用關系也隨之發生改變。為了保證工具里面的關系圖不會過時,就需要不斷從真實系統同步。干過系統梳理這種活的工程師都知道,這可不容易。如果整個系統使用統一的RPC中間件在模塊中通訊,那就可以通過分析RPC trace log的方式挖掘出調用關系圖來,不過“歷史代碼”通常會趴在路中間攔著你。 2.每個黃金指標通常只能覆蓋一部分的故障類型,新的故障一出現,就需要增加黃金指標。這樣一來配置工作——尤其是閾值的配置——就會不斷出現。另外,指標多了,就很容易出現“全國山河一片紅”的情況。大多數的模塊都被標出來的時候,工具也就沒啥用了。 3.大型的系統為了保證性能和可用性,常常需要在好幾個機房中部署鏡像系統。因為大多數的故障只發生在一個機房的系統中,所以工程師不但需要知道根因模塊是誰,還需要知道在哪個機房。這樣一來,每個機房都得有一個調用關系圖,工程師得一個一個地看。 理想的效果 傳統的方法作出來的診斷工具最多也就是半自動的,應用起來也受到很多的限制,所以我們就想做一個真正全自動、智能化的工具。 首先,我們希望新工具不要過于依賴于黃金指標,這樣指標的配置工作就能減少。但是,這反過來說明全自動的工具必須能夠掃描所有模塊上的所有指標,這樣才能做到沒有遺漏。所以,異常判斷不能再通過人工設置閾值的方式來進行,而必須是基本上無監督的(Unsupervised)。另外,不同指標的語意有很大差異,異常判斷的算法也必須足夠靈活,以適應不同指標的特點。 其次,我們希望工具不要太過依賴于調用關系圖,這意味著我們需要尋找一種新的方式來整理和呈現結果。其實,調用關系圖并不是必須的。在使用傳統診斷方法時,我們就發現一部分工程師經常脫離調用關系圖,直接按照黃金指標的異常程度從大到小檢查模塊。這是因為這部分工程師負責的系統黃金指標代表性強、容易理解,更重要的是不同模塊黃金指標的異常程度可以比較。 所以說,我們完全可以做一個診斷工具來產出根因模塊的推薦報告,報告的內容必須易于理解,推薦的順序也必須足夠準確。 實例指標的自動排查分析 我們以實例指標為例,介紹如何實現一個指標排查工具,達成理想的效果。 在第一步,所有被收集來的指標都會通過異常檢測算法賦予它們一個異常分數。比較兩個指標的異常分數就能夠知道它們的異常程度誰大誰小了。這一步的核心是要尋找一個方法能夠量化地衡量每個指標的異常,而且這個量化衡量出來的分數還可以在不同實例的不同指標之間比較。 第二步,我們把異常分數按照它們所屬的實例分組,每組形成一個向量(vector)。這時,每個實例都會對應一個向量,向量中的每個元素就是一個指標的異常分數。然后,模式(pattern)差不多的向量就可以通過聚類(clustering)算法聚成若干個摘要(digest)。這樣一來,工程師們就容易理解分析的結果了。 第三步,我們可以根據摘要中包含的實例以及指標的異常分數排序(ranking),形成推薦報告。 總結 本文介紹了一種在服務發生故障時自動排查監控指標的工具,第一步利用了概率統計的方式估算每個指標的異常分數,第二步用聚類的方式把異常模式相近的實例聚集在一起形成摘要,第三步用ranking的方式向工程師推薦最有可能是根因的摘要。 由于運維場景的特點是數據量大,但是標定很少,生成標定的代價高昂而且容易出錯,接下來我們會詳細介紹如何利用概率統計、非監督學習和監督學習的方法來解決這個問題,敬請期待吧~ 原文鏈接地址: https://developer.baidu.com/topic/show/290066
來源:OSCHINA
發布時間:2019-03-30 12:21:00
session cluster和per job 因為是源碼分析,所以會分為服務端和客戶端兩個部分的代碼分析,下面我先看服務端
session cluster模式是類似standalone,先去向yarn申請好資源,然后供業務方提交,主要的入口類是YarnSessionClusterEntrypoint(這里指的是服務端的入口)

從上圖可以看出來,startCluster()方法前后是兩個分界線,startCluster之前是獲取配置,之后是進行集群相關的創建,包括haService/blobServer/heartBeatService/resourceManger/webMonitorEndpoint。
這里有一點是需要說明的是有關executionGraphStore, 這里實際有兩種, 1.將可執行圖放在內存中, 2.將可執行圖持久化到文件。
yarn session:將executionGraph持久化到文件
per job:將executionGraph持久化到文件
對于per job模式是每個任務對應一個集群,其實就是將上圖中的YarnSessionClusterEntrypoint改成YarnJobClusterEntrypoint,其它流程基本一致(除去executionGrap的存儲)。
下面來看一下兩個主類的繼承關系圖

從圖上可以看到主要的區別就是createSerializeableExecutionGraphStore方法,也就是executionGraph的存儲位置不同。
session client和per job 由于flink不同的版本代碼變動較大,所以在這里需要區分flink的版本進行一下說明 flink1.9之前的基本一致,提交至yarn的主要流程都在CliFrontend和FlinkYarnSessionCli中, 我們來看一下主要流程

這里session和per job的在流程上的最大區別就是clusterId是否為空 flink1.9之后進行了流程統一,抽象出了一個PipelineExecutor接口,統籌所有的提交,不過在看繼承關系之前還是先看一下yarn-client的提交流程其實主要入口還是CLiFrontened,不過在加載完配置文件之后就直接反射調用invokeInteractiveModeForExecution,這個類會調用用戶的main函數,加載完用戶業務代碼之后,會去走正常的提交流程。 到這里已經將所有的提交流程都說完了,大家對于flink爭個提交流程應該有了更加清晰的認識。
最后在來說一下flink submit的接口,這是在flink-1.10才出現的一個新的統一,流程圖如下

從上圖可以看出來,AbstractSessionClusterExecutor中的主要調用邏輯其實和上面我們已經看到的session cluster的提交流程是一致的,只不過代碼更加的抽象,這樣其實擴展性也更加好,AbstractJobClusterExecutor主要主要就是為了向已有集群提交任務的,LocalExecutor其實是為了用戶本地調試所用 歡迎關注我的公眾號
來源:OSCHINA
發布時間:2020-03-26 10:31:00
本文作者:AIOps智能運維 干貨概覽 AIOps(Artificial Intelligence for IT Operations ),即智能運維,是將人工智能的能力與運維相結合,通過機器學習的方法來提升運維效率。 在傳統的自動化運維體系中,重復性運維工作的人力成本和效率問題得到了有效解決。但在復雜場景下的故障處理、變更管理、容量管理、服務資源過程中,仍需要人來掌控決策的過程,這阻礙了運維效率的進一步提升。而AI方法的引入,使得機器能夠代替人來做出決策,從而讓真正意義上的實現完全自動化成為了可能。 在AIOps的落地實施過程中,最關鍵的因素還是人,即AIOps的建設者們。 AIOps作為一個全新的技術發展和應用方向,并不是簡單地說具備某一種技能或招募一兩個大牛就可以完成的,它需要不同角色、多個團隊的配合才可以達成。根據近幾年來整個業界對AIOps的理解和實踐,AIOps參與角色的劃分也越來越清晰。在百度4年的AIOps實踐中,我們總結得出了如下四種不可或缺的角色: 運維工程師 運維研發工程師 平臺研發工程師 運維AI工程師 可以看到,除了運維AI工程師外,其他角色并不是AIOps產生之后才出現的,他們在傳統運維中也發揮了重要作用。我們今天主要想和大家探討一下,在AIOps時代,他們的職責究竟發生了哪些變化。為了方便大家理解,我們會基于百度AIOps的實踐案例,來進行具體說明。 單機房故障自愈場景 單機房故障自愈是一個典型的AIOps落地項目。該方案主要解決的問題場景如下:某個業務由于網絡、設備、變更、程序Bug、容量等原因造成故障,但故障范圍僅局限在單個機房或單個Region內部。那么,我們可以基于流量調度等手段,將訪問流量調度到非故障機房或Region,實現該類型故障的自動止損。 在這個過程中,需要AIOps四種角色分工明確、緊密配合,來完成整個AIOps解決方案的落地實現。 運維工程師 在單機房故障自愈項目中,運維工程師基于日常運維工作中所積累的場景、問題和經驗,確定以單機房故障止損作為主要需求和突破口,通過定義單機房故障止損的問題域、解決思路以及風險點,明確AI可以發力的領域。 在完成問題域的定義后,運維工程師需要跟蹤整個單機房故障自愈解決方案的落地,包括在策略設計前期提供數據標注支持,在中期進行效果的驗收,在后期將單機房故障自愈方案實際部署運行到生產環境。 AIOps時代的職責和技能變化 運維工程師承擔線上服務質量的責任,是服務質量的關鍵保證。在工作過程中,會與研發、產品、運營等各類角色、不同團隊進行深度的溝通和協作。 傳統運維中,運維工程師的主要職責分為三個方面:質量、成本、效率。 在AIOps落地實施中,運維工程師是處于中心的角色,也賦予了新的職責,他們是AIOps具體實施的需求提出者和成果驗收者。具體職責包括: 在AIOps時代,運維工程師一方面需要熟悉運維領域的知識,了解運維的難題和解決思路;另一方面需要了解人工智能和機器學習的思路,能夠理解哪些場景問題適合用機器學習方法解決,需要提供怎樣的樣本和數據,即成為AI在運維領域落地實施的解決方案專家。 運維AI工程師 在單機房故障自愈場景中,運維AI工程師將機器學習的算法與實際的故障處理業務場景相結合,針對單機房故障場景的風險點,進行策略研發與實驗工作。如下圖所示: 運維AI工程師分別設計了如下算法策略來滿足整個復雜故障場景的自動決策: 異常檢測算法:解決故障發現時指標異常判斷問題,基于AI方法實現較高的準確率和召回率,作為整個故障自愈的數據基礎。 策略編排算法:基于當前線上的實際流量和服務狀態,設計損益計算模型,判斷基于何種方式的操作組合或步驟,能夠使整個自動止損帶來收益最大,風險最小。 流量調度算法:基于線上服務容量與實時流量情況,進行精確流量比例計算,防御容量不足或不準風險,并實現流量調度收益最大化。 在完成策略設計與研發后,需要根據歷史數據進行Case回溯,并進行仿真Case模擬,來驗證策略效果,并進行逐步迭代調優,以達到線上運行的準確率和召回率要求。 AIOps時代的職責和技能變化 運維AI工程師是將AI引入運維的核心角色。他們針對運維數據、運維經驗進行理解和梳理,使用機器學習的方法將海量運維數據進行匯總、歸納,使得數據中的價值顯現出來。 運維AI工程師首先需要具備AI工程師的技能,需要對數學及機器學習方法有足夠的掌握程度,并能應用實踐。 如單機房故障自愈場景中的介紹,運維AI工程師需要具備機器學習知識并在運維領域落地的能力。 平臺研發工程師 在單機房故障自愈場景中,平臺研發工程師需要關注三類平臺的建設。 基礎運維平臺:提供單機房故障自愈場景中的依賴平臺,如:監控平臺和流量調度平臺。在日常運維中提供標準化運維數據獲取和運維操作的基礎,而在AIOps中,這部分接口需要能夠同時支持人工和自動的數據獲取和運維操作。 智能運維平臺:提供對AI能力的支持,如:統一的數據服務(運維知識庫)、運維開發框架,以及給AI策略實驗和運行的運維策略框架等。 故障自愈機器人:針對單個業務場景進行平臺化抽象,使之成為一個基礎服務,基于AIOps平臺研發和運行。 AIOps時代的職責和技能變化 平臺研發工程師負責運維平臺及基礎組件的研發與建設。 在傳統運維場景中,平臺研發工程師負責平臺、基礎組件、類庫和工具的研發工作。在針對運維的場景中,會覆蓋運維相關的服務管理、監控、變更、流量調度等相關平臺。 這部分平臺是運維的基礎,在AIOps時代仍然需要依賴于這些平臺的建設。 同時在AIOps場景中,數據成為了中心,運維各種狀態信息轉換為大數據,機器學習則作用在大數據上進行分析。在百度AIOps的實踐中,運維開發框架、運維知識庫、運維策略框架共同組成了完整的智能運維平臺,三大平臺的建設和實施離不開大數據、機器學習架構的引入。這就要求平臺研發工程師具備大數據、機器學習平臺架構師的多重身份,具備流式計算、分布式存儲、機器學習平臺、算法策略平臺等一系列大數據和機器學習平臺架構能力。 運維研發工程師 基于多個業務線場景抽象出的單機房故障自愈解決方案,能夠滿足大部分場景需求,但并不意味著可以直接提供給各個業務線來使用。原因如下: 策略和參數需要進行調整 流量調度、容災策略等策略,針對不同的業務線,配置并不相同。例如某些業務對響應時間敏感,跨地域的調度會帶來較大的延遲,影響用戶體驗,這時就需要根據業務情況配置機房之間的跨機房流量調度延遲系數,來實現流量優先調度到延遲系數最低的機房。 通用框架無法滿足所有需求 部分業務線需要對原有的策略進行部分重寫才能夠滿足需求。例如,部分業務在流量調度時,需要聯動服務降級來滿足容量需求,這就需要額外增加服務降級聯動的邏輯。 那么,就需要運維研發工程師出手來解決這個問題。根據業務線的實際情況,對策略和參數進行配置和調優,對通用框架無法滿足的需求,進行定制化研發,使得單機房故障自愈方案能夠實際應用在不同業務線上。 AIOps時代的職責和技能變化 運維研發工程師負責基于業務線特征的運維研發工作,在傳統運維中,是運維自動化的實施者,實現了針對業務場景的自動化運維實施落地。 在AIOps時代,運維研發工程師承擔了AIOps智能化運維解決方案在業務線實施落地的職責。他們是AIOps場景的實踐者,將AIOps解決方案與業務架構特征相結合,實現AIOps在業務線的落地。 一方面,他們會與運維工程師緊密配合,對業務問題進行深度分析,理解業務的特點。另一方面,他們與平臺研發工程師、AI工程師相配合,基于AIOps解決方案的策略和框架,進行定制化開發,使其適合自身業務線的特征。 總結 本文介紹了運維工程師、運維AI工程師、平臺研發工程師、運維研發工程師四種角色在自動化運維時代和AIOps智能化運維時代,其職責和技能的拓展和變化。AIOps技術為運維技術的發展帶來了更多的機遇,對于每個參與到AIOps實施的個人或團隊也是如此。四種角色既有術業專攻,同時又緊密協作,共同將AI能力引入為運維賦能。那么,你的選擇是什么呢? 原文鏈接地址: https://developer.baidu.com/topic/show/290065
來源:OSCHINA
發布時間:2019-03-30 12:10:00
本文作者:AIOps智能運維 干貨概覽 在運小皮《百度自動化運維演講》文章中提到,2014年以來,百度運維開始向智能化方向邁進。智能運維時代,如何提高智能運維效率,降低通用運維操作(典型如故障場景)開發難度和成本,成為首要難題。本文將向大家介紹面向感知、決策、執行的百度智能運維工程化解決方案。 背景介紹 故障處理和操作變更是運維兩大主題。在過去,為維護系統穩定,各業務線都投入大量人力進行故障處理工作,除直接人肉運維外,各產品線深度定制的運維工具、系統被研發出來。隨著業務規模擴張和形態變遷,傳統運維模式受到極大挑戰: 無統一的開發管理模式,運維服務開發及維護成本大,運維效率低。 橫向擴展能力差,運維經驗難以復用,各產品線”重復造輪子”。 智能運維開發框架,提供了一種以軟件工程方式解決運維問題的解決方案。通過提供統一的開發模型和管理機制,支持不同產品線運維操作的設計、實現和管理。從而: 降低設計、開發難度與成本,使業務OP專注自身的業務邏輯,提高開發和迭代效率。 促進基于代碼的跨產品線經驗積累與分享,提升百度整體的業務運維能力。 充分運用和發揮自動控制、機器學習、人工智能等領域的技術成果,提高運維效率。 解決思路 智能運維開發框架以Noah(百度自動化運維管理平臺)時代的運維經驗為基礎,通過對運維概念和操作的統一,整合當前運維系統,提供運維操作的統一入口;讓更多的業務線OP加入到運維社區建設中,共享運維經驗,滿足業務日益多樣化的需要。 具體解決思路如下: 1運維模式標準化 統一開發模式:提供統一的開發規范,社區化開發模式,業務線OP共同參與運維操作開發,沉淀運維經驗。 統一運維對象:通過知識庫,統一描述機器、實例、服務、應用等運維對象的屬性,聚集分散的運維狀態數據,達到公司內運維對象的統一。 統一運維操作:屏蔽具體平臺操作實現,提供統一的運維對象操作接口。 2運維開發工程化 提供統一的運維開發框架:封裝常用功能組件,提供高擴展的開發框架,使產品線專注于自身業務邏輯,開發”智能運維機器人”。 提供仿真系統:通過提供服務拓撲搭建及模擬故障的能力,完成機器人上線前功能驗證,提高”機器人”可靠性。 提供托管平臺:通過提供高可用的機器人托管環境,降低服務運維成本。 3運維操作智能化 智能感知:依賴監控系統提供的智能異常檢測、多維度異常分析,感知滿足時效性和準確度的異常事件。 智能決策:自定義算法實現決策機制,充分利用機器學習、人工智能成果,提供決策可靠性。并沉淀人對問題的決策經驗,做到經驗可遷移。 智能執行:提供豐富的執行策略,滿足業務線通用運維操作的需求。 實現方案 整體解決方案如下: 以智能運維機器人為主體,深度整合公司內代碼管理工具,持續交付平臺,部署系統等devops工具鏈,幫助產品線同學快速完成源碼構建、鏡像打包、應用部署,提供開發、測試、運維整套解決方案,大幅提升開發效率。 智能運維開發框架自身提供的功能如下: 智能運維開發框架提供了高擴展、易使用的智能運維機器人開發框架,具備線上服務拓撲結構搭建和query級別異常模擬能力的仿真系統,具備單地域故障處理能力的高可用服務部署托管平臺,完成開發至上線流程的全覆蓋,用戶只需要在智能運維開發框架基礎上嵌入自己的業務代碼,即可完成滿足自身業務的運維操作。 總結 智能運維開發框架以變革運維模式為目標,提供了開發、驗證、運維工程化解決方案。一經上線,便作為各類故障自愈、高可用架構項目的基礎支撐,大幅提高了項目開發效率,減小了開發難度和成本,表現出了極強的穩定性。 相信在不久的將來,智能運維開發框架會成為百度運維操作的載體,不斷達成智能運維的使命。 智能運維開發框架的具體實現和最佳實踐將在后續文章中詳細介紹,敬請期待! 原文鏈接地址: https://developer.baidu.com/topic/show/290064
來源:OSCHINA
發布時間:2019-03-30 11:59:00
hdfs的集群中的三種角色:Namenode(NN),Datanode(DN),SecondNamenode(SN) Namenode的工作機制 NN是hdfs的管理節點,主要職責包括:1.管理文件系統的命名空間,維護著hdfs中的虛擬文件目錄樹結構。2.保存文件系統中所有文件和目錄元數據信息(機制復雜,內存中一份完整的,fsimage和edits log加起來是一份完整的)。3.相應客戶端的請求,無論讀寫hdfs都會先訪問NN,節點的交互細節都被封裝在FileSystem類中了。4.NN中也會記載每個文件的各個塊所在數據節點信息,但是NN不會持久化保存文件塊與節點的映射信息,因為這些信息會在系統啟動時候根據數據節點匯報上來的信息進行重建。 客戶端向集群中存儲數據之前,首先需要訪問NN申請寫入文件,NN檢測對應的虛擬文件是否存在,如果不存在就向客戶端返回可以存入并返回分配的DN,客戶端對文件進行切分,將各個block存入NN返回的DN列表中寫入。而一個blk的多個副本是由DN向其他的節點寫入的,而不是由客戶端來直接寫入的,當DN在寫某個副本時候失敗了,會將失敗信息返回給NN,NN會重新分配DN進行副本寫入。文件的最后一個blk可能不滿128M。但是這樣的一個blk也要在NN中含有一條元信息記錄(一個blk的元數據信息大約150B)。所以hdfs不善于存儲小文件,因為小文件耗費NN的存儲空間,MapReduce的性能也降低。 向集群中讀寫文件,都需要訪問NN,所以NN的負載很大。那么如何提高NN的相應速度呢?每次對NN的訪問,都會涉及到元數據的讀取,為了盡可能快速讀取元數據,可以將元數據全放內存,但是內存是遺失性存儲,這樣做安全性無法得到保證,但是元數據全放磁盤則查詢又會太慢。 所以hdfs維護了兩份元數據:內存中一份元數據(為了提高查詢速度,所以也可以看到,NN需要將元數據全部都放在內存中,所以內存大小可能會限制整個hdfs的文件存儲規模),磁盤中一份數據fsimage(為了元數據持久化),還有一個用于記錄hdfs集群最新操作日志的小文件edits log。內存中的元數據和fsimage不總是完全一致的,內存中的元數據總是領先fsimage一個edits log的內容。因為最新的元數據總是先寫入edits log,寫入成功后讓客戶端去寫文件,文件寫入成功之后,NN將這份最新的元數據加載到內存中。當SN執行checkpoint操作時,會將fsimage和editslog的內容合并成新的新的fsimage,這個時候fsimage和內存中的元數據幾乎相同(可能仍然還差著一個edits.new)。 edits log是一個小文件(默認是64M),并不能存太多的數據,以免影響寫入速度。所以當edits log寫滿了的時候,則應該將edits log中的數據全部合并到fsimage中。但是edits log是日志格式,與fsimage的格式不一樣,所以需要一定的資源用于合并,如果這個任務交給NN,則會加大NN的壓力,所以引入了SN來解決fsimage和edits log合并的問題。當edits log寫滿時或者到了一個額定時間(默認3600s),NN通知SN來做checkpoint操作合并fsimage和edits log。SN進行check point操作:首先通知NN不要繼續向原來edits中寫入新數據了,NN把最新的元數據寫入到edits.new中,然后SN從NN下載fsimage和edits,SN將fsimage導入內存,然后對其應用edits中的日志操作,最后生成一個fsimage.checkpoint文件并保存,然后將fsimage.checkpoint上傳到NN。NN用將fsimage.checkpoint重命名為fsimage,edits.new重命名為edits,以替換掉舊的fsimage和edits。 以上的這一套機制,只能做到數據可靠而無法保證系統高可用。猜想:雙NN,其中一個作為完全熱備,但是需要保證雙NN上的元數據的一致性。 Datanode的工作機制 DN提供真實數據的存儲服務和數據庫檢索服務,DN會定期向NN發送他們所存儲的塊的列表。hdfs參數dfs.block.size能夠控制塊大小,默認是128M。即使一個文件沒有達到block的大小,仍然會占用一條元數據。所以說hdfs最好存大文件,小文件比較費NN的元數據存儲空間。hdfs存儲文件block不會添加任何額外的內容,完全就是按照字節數來切分,到一個大小就切,到一個大小就開始切。 HDFS的java客戶端編寫 啟動centos的圖形界面命令:init 5或者startx eclipse添加依賴包的過程:java build path->Libraries->Add Library->User Library->User libraries->New->add extermal JARs import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.LocatedFileStatus; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.RemoteIterator; import org.junit.Before; import org.junit.Test; public class HdfsUtil { FileSystem fs = null; public void init() throws Exception{ //讀取classpath下的xxx-site.xml 配置文件,并解析其內容,封裝到conf對象中 Configuration conf = new Configuration(); //也可以在代碼中對conf中的配置信息進行手動設置,會覆蓋掉配置文件中的讀取的值 conf.set("fs.defaultFS", "hdfs://weekend110:9000/"); //根據配置信息,去獲取一個具體文件系統的客戶端操作實例對象,記得hdfs的權限問題 fs = FileSystem.get(new URI("hdfs://weekend110:9000/"),conf,"hadoop"); } /** * 上傳文件,比較底層的寫法 * * @throws Exception */ public void upload() throws Exception { init(); Path dst = new Path("hdfs://weekend110:9000/aa/qingshu.txt"); FSDataOutputStream os = fs.create(dst); FileInputStream is = new FileInputStream("c:/qingshu.txt"); IOUtils.copy(is, os); } /** * 上傳文件,封裝好的寫法 * @throws Exception * @throws IOException */ public void upload2() throws Exception, IOException{ init(); fs.copyFromLocalFile(new Path("c:/qingshu.txt"), new Path("hdfs://weekend110:9000/aaa/bbb/ccc/qingshu2.txt")); } /** * 下載文件,比較底層的寫法 * @throws Exception */ public void download2() throws Exception { init(); FSDataInputStream is = fs.open(new Path("/jdk-7u65-linux-i586.tar.gz")); FileOutputStream os = new FileOutputStream("c:/jdk7.tgz"); IOUtils.copy(is, os); } /** * 下載文件,封裝好的寫法 * @throws Exception * @throws IllegalArgumentException */ public void download() throws Exception { init(); fs.copyToLocalFile(new Path("hdfs://weekend110:9000/aa/qingshu2.txt"), new Path("c:/qingshu2.txt")); } /** * 查看文件信息 * @throws IOException * @throws IllegalArgumentException * @throws FileNotFoundException * */ public void listFiles() throws FileNotFoundException, IllegalArgumentException, IOException { init(); // listFiles列出的只是文件信息,不會列出文件夾的信息,可以對文件夾遞歸遍歷 RemoteIterator files = fs.listFiles(new Path("/"), true); while(files.hasNext()){ LocatedFileStatus file = files.next(); Path filePath = file.getPath(); String fileName = filePath.getName(); System.out.println(fileName); } System.out.println("---------------------------------"); //listStatus 可以列出文件和文件夾的信息,但是不提供自帶的遞歸遍歷 FileStatus[] listStatus = fs.listStatus(new Path("/")); for(FileStatus status: listStatus){ String name = status.getPath().getName(); System.out.println(name + (status.isDirectory()?" is dir":" is file")); } } /** * 創建文件夾 * @throws Exception * @throws IllegalArgumentException */ public void mkdir() throws IllegalArgumentException, Exception { init(); fs.mkdirs(new Path("/aaa/bbb/ccc")); } /** * 刪除文件或文件夾 * @throws IOException * @throws IllegalArgumentException */ public void rm() throws IllegalArgumentException, IOException { init(); fs.delete(new Path("/aa"), true); } public static void main(String[] args) throws Exception { download(); } } fileSystem設計思想 將所有的文件系統抽象成了一個FileSystem之后,訪問具體的對象時候,只管調用方法,不關心底層的實例對象到底是誰,這樣MapReduce程序與底層的文件系統解耦合了。 hadoop框架中的RPC調用機制 RPC 遠程過程調用,主要應用于分布式系統。比如有三臺機器,a,b,c。a是一個客戶端,訪問了b,b中沒有請求的服務,這個服務在遠程的c機器上。b通過RPC機制能夠向服務在本地一樣地去調用c上的服務程序。具體如下:b將調用的服務類,方法,方法的參數等信息封裝以下通過本地的socket client發送給c上的socket server,然后c機器根據調用信息,使用反射機制,獲得一個服務類對象,調用相應的方法,然后再將方法的執行結果通過本地socket client發送給b上的一個socket server,然后b將這個結果解析出來。 NN和client,NN和DN,DN和DN之間很多通信,使用了大量的RPC通信機制,例如DN會定期的向NN報告本身的block情況。HADOOP實現了一個RPC框架——代理類機制(使用了動態代理和反射和socket技術)。1.生成調用端socket程序的動態代理對象。2.調用動態代理對象的業務方法。3.調用socket的請求方法。4發送調用請求。5.服務端開始了:生成業務類的動態代理對象。6.調用業務類動態代理對象的具體業務方法。7.獲取調用結果。8.返回調用結果給服務端。9.給原始調用者返回結果。 Hadoop的RPC框架調用 主要的以依賴包都在share/common中。使用hadoop的RPC框架的最基本的部分:1.協議(表現為java接口),代理類和業務類都需要實現這個接口已確保方法的一致性。值得注意的是在協議接口中需要指定一個 public static final long versionID 的協議版本號,后面代理類調用方法時候,會用到這個參數,用來驗證代理類和業務類的協議版本的一致性。2.業務類:一般類名以Impl結尾,在業務類中需要實現協議接口中的所有方法,這些被實現的方法都會以服務的形式發布出去。3.controller類,controller類接收客戶端的請求,利用RPC.getProxy()方法生成一個代理類對象,然后用這個代理類對象調用一個協議接口中的方法,通過RPC機制遠程調用業務類的方法,得到結果再返回給客戶端。4.Starter類這個類名隨意,主要的作用是將業務類中實現的協議接口的方法發布為一個服務:新建一個RPC.Builder類對象,然后設置服務的地址,端口,協議以及業務類,之后利用builder對象獲得一個server對象,再啟動起來就ok了。 1.協議接口 接口實現: public interface LoginServiceInterface { public static final long versionID=1L;//用于協定協議的版本號,RPC調用的時候會用到 public String login(String username,String password); } 2.服務端 業務類的實現: public class LoginServiceImpl implements LoginServiceInterface { @Override public String login(String username, String password) { return username + " logged in successfully!"; } } 啟動類的實現: import java.io.IOException; import org.apache.hadoop.HadoopIllegalArgumentException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.ipc.RPC; import org.apache.hadoop.ipc.RPC.Builder; import org.apache.hadoop.ipc.RPC.Server; public class Starter { public static void main(String[] args) throws HadoopIllegalArgumentException, IOException { Builder builder = new RPC.Builder(new Configuration()); builder.setBindAddress("weekend110").setPort(10000).setProtocol(LoginServiceInterface.class).setInstance(new LoginServiceImpl()); Server server = builder.build(); server.start(); } } 客戶端 import java.net.InetSocketAddress; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.ipc.RPC; public class LoginController { public static void main(String[] args) throws Exception { LoginServiceInterface proxy = RPC.getProxy(LoginServiceInterface.class, 1L, new InetSocketAddress("weekend110", 10000), new Configuration()); String result = proxy.login("mijie", "123456"); System.out.println(result); } } 思考題:服務的動態轉發和負載均衡???zookeeper hdfs 源碼分析 FileSystem對象的創建過程(創建與NN進行通信的RPC代理類) hdfs的一個核心類:FileSystem,fs必要的成員:1.rpcProxy代理類,這個代理類應該實現一個接口clientProtocal 源碼重點:如何根據conf來獲取對應的fs,獲取fs時候,如何獲得RPCProxy代理類對象。 首先獲取conf中的URI,然后將URI解析成scheme(hdfs)和authority(weekend110:9000) getInternal方法:單例模式,懶漢模式。先從一個Map中根據Key來獲取,如果沒有獲取到,再去創建Filesysytem createFileSystem方法:獲取對應的fileSystem的class,在反射出來一個相應對象(這個對象是空的,里面的各個數據域都是基本值) fs.initalize()方法:這是具體實現類的方法,設置DFS類的dfs,uri,workDir。設置dfs時候是構造了一個DFSClient對象。dfs的namenode就是rpc代理對象。 總結:根據conf對象,利用反射機制拿到DistributedFileSystem(是FileSystem的一個繼承子類)對象,然后設置fs的各個數據域,其中有一個很重要的數據域 打開輸入流的過程(創建與DN進行通信的RPC代理類) 先利用那個namenode代理類與NN通信,拿到文件的元信息(各個block的位置) DFSInputStream的blockReader是與DN進行交互獲得各個blk的詳細過程。 locatedBlocks記錄各個blk的詳細信息。
來源:OSCHINA
發布時間:2020-03-25 23:02:00
本文作者:AIOps智能運維 背景:為什么要做智能運維 百度云智能運維團隊在運維工具和平臺研發方向歷史悠久,支撐了全百度數十萬規模的服務器上的運維服務,所提供的服務包括服務管理、資源定位、監控、部署、分布式任務調度等等。最近幾年,團隊著力于發展智能化運維能力以及AIOps產品化建設。 眾所周知,百度除了搜索業務之外,還有很多其他的業務線,有像地圖、百科、知道、網盤這樣的老牌業務,也有諸如像教育、醫療這樣的新興業務,每個業務在規模上、服務架構上都有很大差異。業務本身對穩定性的要求很高,需要保持99.995%的高可用,同時在業務上云的背景下,虛擬化、混合云等都給我們帶來了新的挑戰。 百度運維經歷了從腳本&工具、基礎運維平臺、開放可定制運維平臺到我們現在的智能運維平臺,這樣四個階段的轉變。過去運維的核心目標是提升效果,比如持續交付的速度、服務穩定性、運營成本等。經過這么多年的建設,整個運維行業已經非常成熟,而我們所支撐業務規模仍在不斷增長,越來越多的運維場景和問題無法用傳統方法來解決,而運維效率也難以繼續支撐業務規模的快速擴張,所以我們更加關注怎么樣解放運維自身的效率,以及解決傳統運維方法(人工、自動化)所解決不了的問題。 這就好比從馬車到汽車是為了提升運輸效率,而到汽車已經接近飽和的時候,我們又希望用自動駕駛把駕駛員從開車這項體力勞動中解放出來,不僅可以增加運行效率,同時也可以減少交通事故率,這也是我們對智能運維的訴求。 發展:AIOps,從理念到落地 2016年Gartner報告中提出了AIOps概念,也就是Algorithmic IT Operations;基于算法的IT運維,主要指用大數據、機器學習驅動自動化、服務臺、監控這些場景下的能力提升。 我們從2014年開始做智能運維方面的探索,最開始也是集中在監控指標分析、報警分析、故障根因分析、性能和成本分析這些方面,到2016年我們已經完成將AI應用于完整的運維平臺研發的論證。在我們語義下的AIOps,目標是將人的知識和運維經驗與大數據、機器學習技術相結合,開發成一系列的智能策略,融入到運維系統中。用這樣的智能運維系統去完成運維任務,是我們所認為的AIOps,也就是Artificial Intelligence IT Operations。有意思的是,2017年之后的Gartner報告也將AIOps的概念改成了Artificial Intelligence IT Operations。 我們認為AIOps中有三部分不可或缺,一個是運維開發框架,這個是我們后續智能運維研發的骨架,第二個是運維知識庫,這是讓骨架能與我們真實線上環境關聯起來的關鍵因素,起到了血肉的作用,讓骨架能動起來。而最后一個則是運維策略庫,這是運維的大腦,控制著運維平臺的行為。 使用運維開發框架實現的運維程序,我們稱其為運維機器人。運維機器人可以在多種不同的運維場景下提供多樣的運維能力,服務不同類型的業務和用戶。 框架:新的運維開發模式 運維開發框架基于這樣一個抽象,就是如果我們把線上環境看做一個黑盒服務,那么我們對它的操作無非讀寫兩類,所謂的寫也就是操作控制流,是那種要對線上狀態做一些改變的操作,我們常說的部署、執行命令,都屬于這一類;另一類是讀,指的是數據流,也就是要從線上獲取狀態數據,并進行一些聚合統計之類的處理,我們常說的指標匯聚、異常檢測、報警都在這個里面。通過運維知識庫,可以在這兩種操作的基礎上,封裝出多種不同的運維機器人,對業務提供高效率、高質量以及高可用方面的能力。 根據操作流和數據流的不同,我們把框架分成了兩部分,最基礎的是運維執行框架,在這之上,加上分布式計算組件的支持,我們還建設了用于運維大數據計算的計算框架。 1工程化 運維開發框架給開發者提供一系列的開發套件,除了包含了一系列的基礎能力,還包含了一個標準的運維工程研發流程。 在過去,運維研發采用簡單的開發-使用方式,缺少必要的測試維護。而現在,在代碼開發階段,可以通過執行框架,用統一的操作接口庫提升研發效率。在測試階段,開發套件提供了單測和仿真系統,簡化測試環境搭建。在上線后的階段,通過狀態服務和托管系統,可滿足在各災難場景下的運維機器人的自維護。 2組件化 運維開發框架通過三種不同的組件功能組合成運維機器人。分別是感知器、決策器和執行器。這三種組件針對各自使用場景,提供了多種架構能力。 感知器運維機器人的眼睛和耳朵感,就像人有兩個眼睛和兩個耳朵一樣。運維機器人也可以掛載多個感知器來獲取不同事件源的消息,比如監控的指標數據或者是報警事件,變更事件這些,甚至可以是一個定時器。這些消息可以以推拉兩種方式被感知器獲取到。這些消息也可以做一定的聚合,達到閾值再觸發后續處理。 決策器是運維機器人的大腦,所以為了保證決策的唯一,機器人有且只能有一個決策器。決策器也是使用者主要要擴展實現的部分。除了常見的邏輯判斷規則之外,未來我們還會加入決策樹等模型,讓運維機器人自主控制決策路徑。 執行器是運維機器人的手腳,所以同樣的,執行器可以并行的執行多個不同的任務。執行器將運維長流程抽象成狀態機和工作流兩種模式。這樣框架就可以記住當前的執行狀態,如果運維機器人發生了故障遷移,還可以按照已經執行的狀態讓長流程斷點續起。 知識庫:運維的知識圖譜 知識庫是智能運維架構中非常重要的一部分:所有要處理的數據都來自知識庫,以及所有處理后的數據也都會再進入到知識庫中。知識庫由三部分組成,分別是元數據、狀態數據和事件數據。持續的數據建設,是智能運維建設的關鍵。 考慮到未來需要對接不同的內部云平臺和公有云平臺,所以我們的運維數據也需要從底層的多種不同的運維平臺中抽取,清洗和做數據的整合。并以盡可能高的時效性提供給平臺用戶使用。因此我們知識庫建設遵照這四個能力指標進行,分別是全、準、新、穩。 由于知識庫涉及的存儲的內容篇幅太大,并且是相對獨立的一塊工作,所以這里就不再展開了。 實踐:運維機器人 單機房故障自愈是2017年我們完成的重點項目,目標是將單機房范圍的故障自愈水平普遍提升到L4級(整個處理過程,包括決策過程基本無人介入)。當然,另一部分原因是過去一兩年發生的幾次業界重大線上事故,我們希望可以防微杜漸,進一步提升MTTR水平。 相比較原有的單機房故障處理方式,在感知、決策、執行三個方面,L4級的單機房故障自愈系統效果顯著: 1.感知方面,智能異常檢測算法替代過去大量誤報漏報的閾值檢測方法; 2.決策方面,具備全局信息、自動決策的算法組件替代了過去“老中醫會診”的人工決策模式; 3.執行方面,狀態機等執行長流程組件的加入,讓執行過程可定位、可復用。 目前L4級的單機房故障自愈,已經覆蓋百度大多數核心業務線,止損效率可做到分鐘級,最快秒級止損,較人工止損效率提升60%-99%。 總結 隨著AIOps逐漸走向成熟和產品化,必將有越來越多的運維場景被AIOps所變革,而我們,百度云智能運維團隊,也希望秉承著這個方向,為行業貢獻更多的創新理念、技術和產品,歡迎大家一起加入探討。 最后,用一句話來總結下工程架構對于智能運維的意義: 框架在手,AI我有:智能時代,框架會越來越重要,從機器學習框架TensorFlow到自動駕駛框架Apollo,概莫能外。 原文鏈接地址: https://developer.baidu.com/topic/show/290063
來源:OSCHINA
發布時間:2019-03-30 11:52:00
UK8S是UCloud推出的Kubernetes容器云產品,完全兼容原生API,為用戶提供一站式云上Kubernetes服務。我們團隊自研了CNI(Container Network Interface)網絡插件,深度集成VPC,使UK8S容器應用擁有與云主機間等同的網絡性能(目前最高可達10Gb/s, 100萬pps),并打通容器和物理云/托管云的網絡。過程中,我們解決了開源kubelet創建多余Sandbox容器導致Pod IP莫名消失的問題,確保CNI插件正常運行,并準備將修復后的kubelet源碼提交給社區。 深度集成VPC的網絡方案 按照我們的設想,開發者可以在UK8S上部署、管理、擴展容器化應用,無需關心Kubernetes集群自身的搭建及維護等運維類工作。UK8S完全兼容原生的Kubernetes API, 以UCloud 公有云資源為基礎, 通過自研的插件整合打通了ULB、UDisk、EIP等公有云網絡和存儲產品,為用戶提供一站式云上Kubernetes服務。 其中VPC既保障網絡隔離,又提供靈活的IP地址定義等,是用戶對網絡的必備需求之一。UK8S研發團隊經過考察后認為,UCloud基礎網絡平臺具有原生、強大的底層網絡控制能力,令我們能拋開Overlay方案,把VPC的能力上移到容器這一層,通過VPC的能力去實現控制和轉發。 UK8S每創建一個Pod都為其申請一個VPC IP并通過VethPair配置到Pod上,再配置策略路由。 原理如下圖所示。 此方案具有以下優勢: 無Overlay,網絡性能高。50臺Node下的測試數據表明,容器與容器之間的網絡性能,相對于云主機與云主機之間,只有輕微差異(小包場景下,pps 會有 3~5% 損耗),而且Pod網絡性能各項指標(吞吐量,包量,延遲等)不會隨著節點規模增大而削減。而Flannel UDP,VXLan模式和Calico IPIP的模式存在明顯的性能消耗。 Pod能直通公有云和物理云。對于使用公有云和物理云的用戶而言,業務上K8S少了一層障礙,多了一份便利。而Flannel的host gw模式下,容器無法訪問公有云和物理云主機。 而CNI的工作流程如下所示。 創建Pod網絡過程: 刪除Pod網絡過程: Pod IP 消失問題的排查與解決 為了測試CNI插件的穩定性,測試同學在UK8S上部署了一個CronJob,每分鐘運行一個Job任務,一天要運行1440個任務。該CronJob定義如下: apiVersion: batch/v1beta1 kind: CronJob metadata: name: hello spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure 每運行一次Job都要創建一個Pod, 每創建一個Pod,CNI插件需要申請一次VPC IP,當Pod被銷毀時,CNI插件需要釋放該VPC IP。 因此理論上,通過該CronJob每天需要進行1440次申請VPC IP和釋放VPC IP操作。 然而,經過數天的測試統計,發現通過該CronJob,集群每天申請IP次數高達2500以上, 而釋放的的IP次數也達到了1800。申請和釋放次數都超過了1440,而且申請次數超過了釋放次數,意味著,部分分配給Pod的VPC IP被無效占用而消失了。 CNI:待刪除的IP去哪兒了? 仔細分析CNI插件的運行日志,很快發現,CNI在執行拆除SandBox網絡動作(CNI_COMMAND=DEL)中,存在不少無法找到Pod IP的情況。由于UK8S 自研的CNI查找Pod IP依賴正確的Pod網絡名稱空間路徑(格式:/proc/10001/net/ns),而kubelet傳給CNI的NETNS環境變量參數為空字符串,因此,CNI無法獲取待釋放的VPC IP,這是造成IP泄露的直接原因,如下圖所示。 問題轉移到kubelet, 為什么kubelet會傳入一個空的CNI_NETNS環境變量參數給CNI插件? 隨后跟蹤kubelet的運行日志,發現不少Job Pod創建和銷毀的時候,生成了一個額外的Sandbox容器。Sandbox容器是k8s pod中的Infra容器,它是Pod中第一個創建出來的容器,用于創建Pod的網絡名稱空間和初始化Pod網絡,例如調用CNI分配Pod IP,下發策略路由等。它執行一個名為pause的進程,這個進程絕大部分時間處于Sleep狀態,對系統資源消耗極低。奇怪的是,當任務容器busybox運行結束后,kubelet為Pod又創建了一個新的Sandbox容器,創建過程中自然又進行了一次CNI ADD調用,再次申請了一次VPC IP。 回到UK8S CNI,我們再次分析重現案例日志。這一次有了更進一步的發現,所有kubelet傳遞給NETNS參數為空字符串的情形都發生在kubelet試圖銷毀Pod中第二個Sandbox的過程中。反之,kubelet試圖銷毀第二個Sandbox時,給CNI傳入的NETNS參數也全部為空字符串。 到這里,思路似乎清晰了不少,所有泄露的VPC IP都是來自第二個Sandbox容器。因此,我們需要查清楚兩個問題: 為什么會出現第二個Sandbox容器? 為什么kubelet在銷毀第二個Sandbox容器時,給CNI傳入了不正確的NETNS參數? 第二個Sandbox:我為何而生? 在了解的第二個Sandbox的前世今生之前,需要先交待一下kubelet運行的基本原理和流程。 kubelet是kubernetes集群中Node節點的工作進程。當一個Pod被kube-sheduler成功調度到Node節點上后, kubelet負責將這個Pod創建出來,并把它所定義的各個容器啟動起來。kubelet也是按照控制器模式工作的,它的工作核心是一個控制循環,源碼中稱之為syncLoop,這個循環關注并處理以下事件: Pod更新事件,源自API Server; Pod生命周期(PLEG)變化, 源自Pod本身容器狀態變化, 例如容器的創建,開始運行,和結束運行; kubelet本身設置的周期同步(Sync)任務; Pod存活探測(LivenessProbe)失敗事件; 定時的清理事件(HouseKeeping)。 在上文描述的CronJob任務中, 每次運行Job任務都會創建一個Pod。這個Pod的生命周期中,理想情況下,需要經歷以下重要事件: Pod被成功調度到某個工作節點,節點上的Kubelet通過Watch APIServer感知到創建Pod事件,開始創建Pod流程; kubelet為Pod創建Sandbox容器,用于創建Pod網絡名稱空間和調用CNI插件初始化Pod網絡,Sandbox容器啟動后,會觸發第一次kubelet PLEG(Pod Life Event Generator)事件。 主容器創建并啟動,觸發第二次PLEG事件。 主容器date命令運行結束,容器終止,觸發第三次PLEG事件。 kubelet殺死Pod中殘余的Sandbox容器。 Sandbox容器被殺死,觸發第四次PLEG事件。 其中3和4由于時間間隔短暫,可能被歸并到同一次PLEG事件(kubelet每隔1s進行一次PLEG事件更新)。 然而,在我們觀察到的所有VPC IP泄露的情況中,過程6之后“意外地”創建了Pod的第二個Sandbox容器,如下圖右下角所示。在我們對Kubernetes的認知中,這不應該發生。 對kubelet源碼(1.13.1)抽絲剝繭 前文提到,syncLoop循環會監聽PLEG事件變化并處理之。而PLEG事件,則來源kubelet內部的一個pleg relist定時任務。kubelet每隔一秒鐘執行一次relist操作,及時獲取容器的創建,啟動,容器,刪除事件。 relist的主要責任是通過CRI來獲取Pod中所有容器的實時狀態,這里的容器被區分成兩大類:Sandbox容器和非Sandbox容器,kubelet通過給容器打不同的label來識別之。CRI是一個統一的容器操作gRPC接口,kubelet對容器的操作,都要通過CRI請求來完成,而Docker,Rkt等容器項目則負責實現各自的CRI實現,Docker的實現即為dockershim,dockershim負責將收到的CRI請求提取出來,翻譯成Docker API發給Docker Daemon。 relist通過CRI請求更新到Pod中Sandbox容器和非Sandbox容器最新狀態,然后將狀態信息寫入kubelet的緩存podCache中,如果有容器狀態發生變化,則通過pleg channel通知到syncLoop循環。對于單個pod,podCache分配了兩個數組,分別用于保存Sandbox容器和非Sandbox容器的最新狀態。 syncLoop收到pleg channel傳來事件后,進入相應的sync同步處理流程。對于PLEG事件來說,對應的處理函數是HandlePodSyncs。這個函數開啟一個新的pod worker goroutine,獲取pod最新的podCache信息,然后進入真正的同步操作:syncPod函數。 syncPod將podCache中的pod最新狀態信息(podStatus)轉化成Kubernetes API PodStatus結構。這里值得一提的是,syncPod會通過podCache里各個容器的狀態,來計算出Pod的狀態(getPhase函數),比如Running,Failed或者Completed。然后進入Pod容器運行時同步操作:SyncPod函數,即將當前的各個容器狀態與Pod API定義的SPEC期望狀態做同步。下面源碼流程圖可以總結上述流程。 SyncPod:我做錯了什么? SyncPod首先計算Pod中所有容器的當前狀態與該Pod API期望狀態做對比同步。這一對比同步分為兩個部分: 檢查podCache中的Sandbox容器的狀態是否滿足此條件:Pod中有且只有一個Sandbox容器,并且該容器處于運行狀態,擁有IP。如不滿足,則認為該Pod需要重建Sandbox容器。如果需要重建Sandbox容器,Pod內所有容器都需要銷毀并重建。 檢查podCache中非Sandbox容器的運行狀態,保證這些容器處于Pod API Spec期望狀態。例如,如果發現有容器主進程退出且返回碼不為0,則根據Pod API Spec中的RestartPolicy來決定是否重建該容器。 回顧前面提到的關鍵線索:所有的VPC IP泄露事件,都源于一個意料之外的Sandbox容器,被泄露的IP即為此Sandbox容器的IP。剛才提到,SyncPod函數中會對Pod是否需要重建Sandbox容器進行判定,這個意外的第二個Sandbox容器是否和這次判定有關呢? 憑kubelet的運行日志無法證實該猜測,必須修改源碼增加日志輸出。重新編譯kubelet后,發現第二個Sandbox容器確實來自SyncPod函數中的判定結果。進一步確認的是,該SyncPod調用是由第一個Sandbox容器被kubelet所殺而導致的PLEG觸發的。 那為什么SyncPod在第一個Sandbox容器被銷毀后認為Pod需要重建Sandbox容器呢?進入判定函數podSandboxChanged仔細分析。 podSandboxChanged獲取了podCache中Sandbox容器結構體實例,發現第一個Sandbox已經被銷毀,處于NOT READY狀態,于是認為pod中已無可用的Sandbox容器,需要重建之,源碼如下圖所示。 注意本文前面我們定位的CronJob yaml配置, Job模板里的restartPolicy被設置成了OnFailure。SyncPod完成Sandbox容器狀態檢查判定后,認為該Pod需要重建Sandbox容器,再次檢查Pod的restartPolicy為OnFailure后,決定重建Sandbox容器,對應源碼如下。 可以看出kubelet在第一個Sandbox容器死亡后觸發的SyncPod操作中,只是簡單地發現唯一的Sandbox容器處于NOT READY狀態,便認為Pod需要重建Sandbox,忽視了Job的主容器已經成功結束的事實。 事實上,在前面syncPod函數中通過podCache計算API PodStatus Phase的過程中,kubelet已經知道該Pod處于Completed狀態并存入apiPodStatus變量中作為參數傳遞給SyncPod函數。如下圖所示。 Job已經進入Completed狀態,此時不應該重建Sandbox容器。而SyncPod函數在判定Sandbox是否需要重建時, 并沒有參考調用者syncPod傳入的apiPodStatus參數,甚至這個參數是被忽視的。 第二個Sandbox容器的來源已經水落石出,解決辦法也非常簡單,即kubelet不為已經Completed的Pod創建Sandbox,具體代碼如下所示。 重新編譯kubelet并更新后,VPC IP泄露的問題得到解決。 下圖可以總結上面描述的第二個Sandbox容器誕生的原因。 事情離真相大白還有一段距離。還有一個問題需要回答: 為什么kubelet在刪除第二個Sandbox容器的時候, 調用CNI拆除容器網絡時,傳入了不正確的NETNS環境變量參數? 失去的NETNS 還記得前面介紹kubelet工作核心循環syncLoop的時候,里面提到的定期清理事件(HouseKeeping)嗎?HouseKeeping是一個每隔2s運行一次的定時任務,負責掃描清理孤兒Pod,刪除其殘余的Volume目錄并停止該Pod所屬的Pod worker goroutine。HouseKeeping發現Job Pod進入Completed狀態后,會查找該Pod是否還有正在運行的殘余容器,如有則請理之。由于第二個Sandbox容器依然在運行,因此HouseKeeping會將其清理,其中的一個步驟是清理該Pod所屬的cgroup,殺死該group中的所有進程,這樣第二個Sandbox容器里的pause進程被殺,容器退出。 已經死亡的第二個Sandbox容器會被kubelet里的垃圾回收循環接管,它將被徹底停止銷毀。然而由于之前的Housekeeping操作已經銷毀了該容器的cgroup, 網絡名稱空間不復存在,因此在調用CNI插件拆除Sandbox網絡時,kubelet無法獲得正確的NETNS參數傳給CNI,只能傳入空字符串。 到此,問題的原因已經確認。 問題解決 一切水落石出后,我們開始著手解決問題。為了能確保找到所刪除的Pod對應的VPC IP,CNI需要在ADD操作成功后,將PodName,Sandbox容器ID,NameSpace,VPC IP等對應關聯信息進行額外存儲。這樣當進入DEL操作后,只需要通過kubelet傳入的PodName,Sandbox容器ID和NameSpace即可找到VPC IP,然后通過UCloud 公有云相關API刪除之,無需依賴NETNS操作。 考慮到問題的根因是出現在kubelet源碼中的SyncPod函數,UK8S團隊也已修復kubelet相關源碼并準備提交patch給Kubernetes社區。 寫在最后 Kubernetes依然是一個高速迭代中的開源項目,生產環境中會不可用避免遇見一些異?,F象。UK8S研發團隊在學習理解Kubernetes各個組件運行原理的同時,積極根據現網異?,F象深入源碼逐步探索出問題根因,進一步保障UK8S服務的穩定性和可靠性,提升產品體驗。 2019年內UK8S還將支持節點彈性伸縮(Cluster AutoScaler)、物理機資源、GPU資源、混合云和ServiceMesh等一系列特性,敬請期待。 歡迎掃描下方二維碼,加入UCloud K8S技術交流群,和我們共同探討Kubernetes前沿技術。 如顯示群人數已加滿,可添加群主微信zhaoqi628543,備注K8S即可邀請入群。
來源:OSCHINA
發布時間:2019-04-12 16:53:00
行業內接入網絡去堆疊已經逐步成為主流方向,在大型互聯網公司也已經批量部署。但由于京東集團不同的業務需求及歷史原因,沒有條件完全復制目前主流的ARP轉主機路由方式的去堆疊方案,這促使我們設計一種盡可能滿足各類業務需求的方案。 近幾年來云市場迅速發展,越來越多的用戶把關鍵應用甚至全部應用部署到公有云上。云服務商在產品收入快速增長同時,也深刻體會到產品的高可用性對用戶發展和用戶留存的重要性。面向用戶的產品SLA的實現效果取決于其依賴的各個環節,而基礎網絡是所有產品需要依賴的一個重要環節,因此,提升網絡高可用SLA對整體提升產品整體SLA有著重要促進作用。今天我們要談的異構去堆疊就是京東云在提高網絡高可用SLA方面的新技術研究。用戶自有網絡其實也面臨同樣的問題,有提高網絡可靠性需求的用戶可以考慮在自有網絡中使用類似方案。 網絡高可用通用解決方案 首先,讓我們先來看一下為了實現網絡高可用,當下的四種服務器連接主流方案: 由上圖可以看出,雙網卡/交換機堆疊和雙網卡/去交換機堆疊提供了更好的高可用保證。 通用堆疊方式 V.S 通用去堆疊方式 01 堆疊方案 優勢 服務器雙網卡捆綁,無需特別改造 交換機控制面統一,支持服務器BGP路由方式接入 劣勢 多設備統一控制面,可靠性低 升級困難 橫向連接浪費端口 02 去堆疊方案 常見的去堆疊方案有以下兩種: 去堆疊方案- 路由方式 優勢 交換機完全獨立 支持異構 路由方式接入的服務器無需特別改造 劣勢 2層方式接入的服務器需要進行改造 服務器除網卡IP外需配置單獨業務IP 服務器多IP增加業務方運維復雜度 靜態路由方式不適合VM或Docker漂移 動態路由方式要求全部服務器運行路由協議 2.去堆疊方案- ARP轉主機路由方式 優勢 交換機完全獨立 服務器網卡捆綁方式不變,便于運維 服務器網卡捆綁方式不變,便于運維 劣勢 一組交換機要求同廠商,不支持異構 需要修改服務器Linux內核ARP處理部分 不支持服務器BGP路由方式接入 通過以上對比,可以發現堆疊與去堆疊方式其實都可以實現網絡的高可用,但各有利弊。針對這樣的情況,京東云提出了一種理想的去堆疊方式,可以滿足下圖中的所有需求。 異構去堆疊方案 01 實現方法 交換機側 IP配置/24掩碼,兩臺交換機可配置相同IP或者不同IP; 啟用ARP代理及轉主機路由功能; 配置ARP超時時間為5s以便在服務器不響應ARP時快速撤回主機路由。 服務器側 服務器雙網卡配置相同IP地址(掩碼/32); 采用onlink方式配置目的為交換機IP的靜態路由指向對應網卡; 采用onlink方式配置缺省路由同時指向兩塊網卡; 需要運行Ifplugd進程監控物理連接,物理連接發生UP/DOWN時執行相應ARP Ping和路由修改操作。 02 冗余測試 從上圖中的測試拓撲和結果中可以看出,不論是軟件操作上的禁用還是硬件拔出,在設定的收斂時間內,服務器的網絡一直保持高可用。 03 異構去堆疊方案小結 異構去堆疊方案優勢 交換機完全獨立 異構避免單一廠商風險 異構推動自研交換機快速上線 服務器側支持2層方式或BGP路由方式 不修改Linux內核 注意事項 需要內核版本支持L4 HASH方式ECMP(Centos 7.4以上) 需要運行Ifplugd進程監控物理連接 總結 綜上所述,異構去堆疊有助于實現苛刻的網絡SLA和極致的用戶體驗。京東云在設計去堆疊方案時首先考慮了異構,一方面因為單一廠商對網絡高可用SLA還是一個重要風險點,另一方面異構方式可以促進京東正在進行的自研交換機實現快速部署。眾所周知,自研交換機在各大互聯網公司都是重點項目,但軟件和硬件的穩定性一直是批量部署的重大障礙。而異構去堆疊利用自研和商用交換機1+1的方式可以大大降低自研交換機穩定性的影響。 ·END·
來源:OSCHINA
發布時間:2019-04-11 11:36:00
今天, Google Cloud NEXT 2019大會召開,在這場規模三萬人的盛會上,Google宣布推出Anthos作為多云服務新方案,提供跨云(目前僅支持AWS和Azure)管理Kubernetes集群。 Anthos(前身為Cloud Service Platform)讓用戶可以跨環境構建和管理現代混合應用程序,用戶可以在Anthos之上交付和管理容器、Kubernetes、微服務架構和Service Mesh等。據Google CEO Sundar Pichai在大會發布時的介紹,Google自身作為公有云提供商,產品Anthos平臺甚至可以支持部分競爭對手的云,如目前支持AWS和Azure,但除這二者之外的其他公有云如阿里云、Oracle、IBM等的云都尚不支持。 技術前瞻性和持續創新得到證明 Anthos的發布,對Rancher來說是一則十足的好消息。在Google Anthos中,我們看到了它與Rancher的愿景的完美契合。因為Rancher始終相信Kubernetes將成為所有公有云和私有云都會提供的、標準化的基礎架構,企業Kubernetes平臺必須提供多集群、多云管理的功能。 早在兩年前,Rancher就預見到企業將需要跨不同的公有云和私有數據中心管理多個Kubernetes集群。正是懷著這種理念,我們構建了Rancher 2.0,它可以與所有Kubernetes集群協同工作,包括Google GKE、Amazon EKS、Azure AKS、華為云、阿里云、騰訊云等等,是業界第一個、也是目前唯一一個與所有主流公有云廠商完成官方合作及對接的Kubernetes平臺。 在過去的12個月里,已有成千上萬的具有前瞻性思維的企業在生產環境中應用了Rancher 2.0。但是在初期,當我們向用戶闡釋Rancher能夠管理多Kubernetes服務(如GKE、AKS、EKS等)的這一功能的優勢時,有用戶雖然對這一創新特性興奮與好奇,卻仍忍不住問我們,“管理多Kubernetes集群與服務?Rancher是對的嗎?”如今,Google正式推出Anthos足以強力地證明,Rancher對多集群多云Kubernetes的堅信和前瞻性,是對的。 Container和Kubernetes技術均正處于采用的早期階段。我們相信創新的產品和服務是實現大規模采用的關鍵。我們喜歡Google Anthos和Google Cloud Run(同樣是今天全新發布的)等新服務,同時也為Rancher團隊開發的創新技術感到自豪。Rancher 2.0是業界第一個多集群、多云Kubernetes管理平臺。除此之外,Rancher Labs的創新步伐從未放緩,如最近推出的輕量級Kubernetes發行版K3s、多Kubernetes集群網絡框架Submariner、對單一應用程序跨多Kubernetes集群的部署與管理等等,都廣受客戶和用戶稱贊。 Anthos:競爭者?推動者? 那Google會成為Rancher的競爭對手嗎?答案是否定的。首先,Rancher是一個開源的平臺,而Anthos是一種云服務。我們甚至相信,Anthos的發布將毫無疑問加速Rancher被更大規模地采用。正如Google發布GKE而極大地幫助普及了Kubernetes技術一樣,我們也相信Anthos將促進將多集群、多云Kubernetes管理帶入更主流的階段。 其次,作為一個完全開源的Kubernetes管理平臺,Rancher自身并非公有云提供商,因而一如既往的是所有公有云提供商、包括他們提供的托管Kubernetes服務的合作伙伴而非競爭對手。Rancher也才更能中立地提供對其他廠商Kubernetes服務的最一流支持。因為公有云廠商之間天然的競爭關系,我們很難想象AWS樂意致力于把運行在EKS上的應用無縫遷移到Google GKE或者其他云服務商提供的Kubernetes服務上,反之亦然。 從世界范圍內包含Google GKE、Amazon EKS、Azure AKS、華為云、阿里云、騰訊云以及正在加入Rancher合作伙伴列表的眾多主流公有云與Rancher一直以來的緊密合作中可見,Rancher也始終是所有第三方公有云廠商普遍最為信任的統一管理平臺。 寵物與牛的故事 聽過那個云計算時代“寵物”與“?!钡墓适聠?? 傳統的物理機時代,物理機(及它所代表的計算資源)被工程師稱為“寵物”,它們價格高昂,需要精心維護和保養。而在云計算時代,工程師可以把服務器當成牛,一頭牛倒下沒關系,只要放牧人活著就行,服務器價格便宜,就像一次性工具,即時它們倒下,一臺設備上的任務也能轉移到另一臺,對公司業務不會造成任何影響。 我們將公司及產品命名為“Rancher(放牧人)”,就是因為我們愿景中的未來里,企業可以自由選擇并搭配著從他們喜歡的云提供商那里獲得計算資源,而Rancher會開發一個軟件平臺來完美實現這種統一體驗。隨著Anthos的發布,我們相信整個行業又向實現這一愿景邁進了一步! 本文基于Rancher Labs聯合創始人及CEO梁勝所寫博客刪改編輯而成: https://rancher.com/blog/2019/era-of-multi-cluster-multi-cloud-kubernetes-has-arrived/
來源:OSCHINA
發布時間:2019-04-11 09:20:00
問題演變過程 時間點1:高防+WAF+SLB+2臺ECS 時間點2:高防+WAF+SLB+4臺ECS 問題描述 在時間點1時,沒有發現明顯的負載不均衡的情況。在時間點2時,出現大部分請求都打到了其中一臺ECS上。需要定位問題原因 問題梳理 問題鏈路 是SLB后端的ECS出現負載不均衡的請求,那么直接影響這個轉發算法的,是WAF以及SLB。那么和高防沒有關系了。 配置情況 SLB:TCP監聽,WRR轉發算法,開啟會話保持 WAF:無特殊配置,域名直接回源負載均衡IP 問題點1:輪詢算法+會話保持 措施: 嘗試修改輪詢算法為WLC,會話保持時間調短。 然而這個優化措施效果并不明顯,由于開啟了會話保持,那原有負載不均衡的情況下,調整WRR算法到WLC的算法,沒有實現預期的WLC。 但是從另外一個角度來說,如果源IP非常分散的場景下,即使有會話保持,理論上還是應該在經過一個較長的時間段之后,依然能夠到達均衡。 這里由于是使用WAF的回源地址進行訪問,所以對負載均衡來說,客戶端的公網IP地址是固定的,一直是固定的幾個;從而調整WLC+會話保持的調整收效甚微。 問題點2:會話保持模板刷新問題 措施: 嘗試關閉會話保持。 稍有成效: 關閉會話保持后,經過一段時間的通信,4臺ECS初步的開始均衡,但是到了一個固定值之后;沒有繼續均衡,一直保持著1:2的狀態。 這里有 2個知識點 : 1、WLC算法的計數開始是從調整為這個算法的時間點開始的;那么如果歷史開始就出現不均衡,那么開啟后還是會不均衡的。 2、由于WAF的回源地址與SLB的通信一直在,沒有斷過所以歷史的會話保持的效果依然存在,已經會話保持的IP,依然會發給對應負載均衡的RS,導致不均衡。 推薦的解法為: 使用負載均衡的權重功能,將連接數多的機器的權重調低,待4臺機器的連接數基本均衡后,將RS的權重都調整為一致。 作者:楓凡 原文鏈接 ? 本文為云棲社區原創內容,未經允許不得轉載。
來源:OSCHINA
發布時間:2019-05-14 12:11:00
【ZStack學堂】第二季第5期報名開始啦!前幾期的分享收獲了很多小伙伴的好評,第5期課程將與大家一起分享ZStack GPU透傳、IOMMU/ VT-d簡介、典型場景:3D渲染等內容。歡迎掃描海報二維碼報名參與, 轉發海報或參與直播間互動還有機會獲取ZStack精心準備的小禮品哦!
來源:OSCHINA
發布時間:2019-05-14 09:19:00
作者:ZStack 社區 王彬 Iaas云服務的普及,讓我們在使用服務器的時候享受了飛一般的感覺,新興企業在構建自己的系統時,往往都會選擇購買云廠商的云服務器(虛擬機)進行使用,使用這樣的虛擬機企業不需要購置任何硬件,不需要考慮機房、網絡、服務器維護相關的工作便可以獲取到一個低成本、安全免維護、伸縮性強、可靈活遷移的云服務器。在這個云服務器上我們可以快速的構建企業的業務系統。 隨著企業的不斷發展,使用公有云服務在虛擬機的數量到達一個數量級的時候,成本會高于私有云,且虛擬機數量越大這個成本差就會越大,大多數互聯網企業在創業初期都是使用主流的公有云服務,但是隨著企業內部業務量不斷上升虛擬機數量也不斷上升,相關的成本也在一直增加。并且隨著業務類型越來越多時,業務的安全需求也受到越來越多的挑戰,此時就需要考慮自建機房、自建私有云,將公有云的服務遷移到私有云上降低成本并進行內部的安全控制。 關于私有云的構建,目前業界有很多的解決方案。我也有多方了解和比較。前段時間接觸到了ZStack云平臺,為了更好的理解和掌握ZStack的產品,特地前往ZStack進行參觀學習。 ZStack(上海云軸)成立于2015年,是一家堅持自主創新、專注于產品化的云計算公司。 在本次學習過程中,我了解到 ZStack有一個極大的亮點——操作簡單 。使用ZStack的時候你不需要考慮什么SSO、數據庫、控制服務部署調試,也不需要考慮什么Nova、Swift、Glance、Keystone等等。你所要的在ZStack的鏡像中已經完整的進行了配置。 下載ISO ZStack-x86_64-DVD-3.3.0-c74.iso 安裝ZStack系統,簡單點說就是裝個LINUX,和安裝CentOS一樣,系統裝好后就完成了一個私有云云平臺的安裝。 2. 平臺初始化 3.添加鏡像 4.創建虛機 5.安裝Windows系統虛擬機并加載驅動 通過以上的方式,我們可以快速的構建一個私有云環境并部署一個Windows2012的虛擬機,接著就可以運行業務了。 云計算平臺承載用戶核心業務,其系統架構設計必須穩定且高效?,F階段一些大的集群或者云平臺擴容或上線的話需要特別大的資源占用和極高硬件成本。另外,在部署時需要購買更高級的一些License授權才可以使用。然而ZStack不需要很高配置的硬件和授權,也可以支持兩千多個節點的部署?,F在已經有很多客戶在使用這個平臺了,比如餓了么等一些數據量訪問很大的公司。ZStack的擴容性、擴展性很強,可以在不停機的情況下實現在線的、無縫的升級和擴展一些其他的常用功能。 重啟zstack平臺不影響業務 云計算基礎平臺小到3~5服務器,大到數萬臺,資源管理平臺必須有強的適配性。以訂餐平臺為例,基本上有兩個業務高峰期,在這兩業務高峰期的時候人員的訪問會急劇增加,使用的普通服務器或者普通云平臺的部署來支持的話,會造成大量的資源浪費,這時,ZStack的彈性伸縮功能就派上用場了。它可以設定一系列的計劃,當某個時間段的時候,企業的虛擬云主機數量不夠用,它可以按照彈性伸縮的計劃自動擴展,包括添加一些可以支持高并發的訪問操作等,在幾分鐘之內就可以出現幾百臺所需的虛擬云主機。 另外, ZStack具有良好的兼容性,不會有硬件捆綁情況,可將現有設備進行利舊統一成資源池。減輕企業采購壓力,促進節能環保。 以上就是我在使用ZStack過程的心得體會。 根據IDC的數據,到2021年,中國IT市場,公有云占比約27%,私有云占比約26%,剩下的45.8%是沒有云化的傳統IT,中國私有云市場近幾年的增速會超過公有云。Gartner明確說中國未來將是市場上最大的私有云市場之一。 因此,中國私有云的市場前景非常廣闊,私有云市場與公有云市場將會產生非常大的交集,這就是混合云市場。 日后,我司將持續使用ZStack云管平臺,完成客戶從公有云到私有云部署的全過程。
來源:OSCHINA
發布時間:2019-05-10 17:53:00
6月20日,北京,由Rancher Labs主辦的【2019企業容器創新大會】限免報名已開啟!全天18場演講,特邀中國人壽、中國聯通、平安科技、新東方、阿里云、百度云等著名企業的IT負責人,分享容器技術的企業級落地經驗。 大會上,Rancher Labs研發經理還將現場Demo即將發布的Rancher 2.3中Istio、Windows容器的功能及使用!還有K3s、Rio等的現場交流。點擊 http://hdxu.cn/hMsQ8 了解詳情及在線報名啦~ 2019年6月6日,Rancher Labs發布了Rancher全新版本2.2.4,該版本修復了近期發現的兩個安全漏洞CVE-2019-12303 和 CVE-2019-12274,項目級別的監控功能也在此版本回歸,還有一系列功能與優化。 CVE修復 2.2.4版本包含兩個安全漏洞修復 CVE-2019-12303 和 CVE-2019-12274 第一個漏洞會影響v2.0.0到v2.2.3的版本,項目管理員可以在對接日志系統時,通過注入額外的Fluentd配置參數,來讀取到Fluentd容器內的文件或執行任意命令,例如其他項目管理員配置的ElasticSearch。 具體鏈接: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12274 第二個漏洞會影響v1.6.0到v1.6.27(使用Cattle和Kubernetes的用戶)以及v2.0.0到v2.2.3的版本。某些內嵌的主機驅動可以配置一個文件路徑的參數。通過主機驅動創建主機時,創建者可以通過該漏洞獲取Rancher Server內任意容器的內容,例如 /root/.kube/config 。這樣,主機創建者可以獲取Rancher管理平面的訪問權限。 具體鏈接: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12303 功能與優化 項目級別監控回歸 AKS集群新增多個區域的支持 RKE AWS集群新增了多個區域的支持 增強了全局DNS對CloudFlare的支持 內置監控新版本,該版本對性能和穩定性進行了增強。 解決了加載含有大規模Kubernetes資源的集群時,Rancher UI加載延遲很大的問題。 https://github.com/rancher/rancher/issues/18522 通過壓縮Rancher服務器與Rancher Agent之前的通訊信息,從而優化了性能。 https://github.com/rancher/rancher/issues/19493 其他優化與增強 下面是2.2.4中修復的一些主要的問題,更多詳情可以通過下面的鏈接查看。 https://github.com/rancher/rancher/milestone/160 增強了應用商店穩定性和性能 https://github.com/rancher/rancher/issues/19881 https://github.com/rancher/rancher/issues/19880 優化了Rancher性能 https://github.com/rancher/rancher/issues/18522 https://github.com/rancher/rancher/issues/16605 https://github.com/rancher/rancher/issues/19934 支持激活OTC主機驅動 普通用戶可以創建全局DNS OpenStack集成增強 https://github.com/rancher/rancher/issues/20456 https://github.com/rancher/rancher/issues/20562 etcd快照功能增強 https://github.com/rancher/rancher/issues/18807 https://github.com/rancher/rancher/issues/18793 https://github.com/rancher/rancher/issues/19639 修復了多集群應用模版刪除后無法顯示的問題 https://github.com/rancher/rancher/issues/20432 修復了HA模式下,Rancher從節點中無法更新應用模版的問題 https://github.com/rancher/rancher/issues/20299
來源:OSCHINA
發布時間:2019-06-11 14:23:04
阿里妹導讀:軟件質量不是測出來的,但為什么又有這么多測試工程師為了質量而工作?測試是一個成本部門,測試創造的價值是什么?研發的模式在不斷地變化,測試的定位如何不斷去定義,未來的測試又會是什么形態?今天,阿里巴巴高級測試開發專家傲野總結了對未來測試形態的一些思考,希望對正在做測試的同學有所啟發。 前言 從社會發展上來說,各領域的分工越來越細。但從技術部門的發展上來看,測試和開發的角色卻是在不斷融合,背后的原因是什么?是互聯網迭代的速度越來越快促成的多角色融合,還是因為技術(特別是質量技術)先進生產力在逐漸取代落后的生產力? 在回答這些問題之前,我們先來回顧“測試工程師”作為一個職能或者個體在過去的發展歷程: 10年前,最初級的測試產出工件是比較一次性的,比如項目中寫的文本型測試用例,基本在項目發布后就廢棄了。 那個時期測試工作的進階是方法論,比如能夠把測試用例的設計方法,項目流程管理講得頭頭是道已經是高階了。 有一些技術能力的測試同學,投身于自動化腳本的編寫。自動化在“軟件”測試時代和互聯網初期,是真正的硬核能力。 但這樣的測試模式和效率都是非常低的,顯然無法支撐互聯網無快不破的浪潮。2010年以后,在頭部企業的測試團隊發生了一系列的變革,快速地從上述的這些初級能力,擴大到以 CI/CD 為驅動的技術體系,并最終推動了測試技術產品化進程,形成一個較為清晰的測試平臺發展脈絡。 在這個將近十年的周期中,由于測試工具、平臺的不斷創新,測試團隊得到了一個突破性的發展。但工具作為傳統測試模式的輔助手段,仍然會遇到突破的瓶頸。比如,從全球來看質量也發生了一定的分支: 一種是不斷堅持平臺化的發展路徑:項目質量是基礎,不斷孵化出各類的效能平臺,解決的問題也從傳統的質量領域本身,往研發各環節拓展。有些大型的企業也開始沉淀了通用的研發協同平臺(研發流水線)。 一種是從內往外突破:比如 Google 的 SRE 團隊,以純技術的手段,打造一個內建且自洽的質量體系(傳統以證偽為理論依據的是一個外建的質量體系)。[1] 這兩者的方向和目標,是有一定的重合的,比如有些公司以測試負責線下,SRE 負責線上進行區分。但如果從質量這個大的目標來看,未來的成功畫面應該是:“質量和效率的結合”和“外建與自洽的結合”。因為只有這樣,才能打造一個真正完整的技術質量生態。 實時質量 也是基于上述的一些思考和實踐,我們在2017年底提出了“實時質量”的概念?!八皇且粋€具體的測試技術產品,而是一種面向未來解決質量問題的方法和手段?!? 它的主要特性是:運行含測試,實時可反饋。 為什么要往這個方向發展? 隨著技術的不斷創新和交付模式的不斷改變,對于測試團隊來說,需要盡快地從交付型質量往實時質量方向進行轉移。傳統的交付型質量,把測試作為一道道關卡,以任務的方式布防在開發提測、項目發布時。這種方式存在不同角色之間的過多交互,只能起到單點的質量保障。而實時質量的目標是:將質量手段以模塊、組件乃至系統化的方式嵌入到業務型應用中,形成實時保障質量的能力。未來開發和測試人員之間的合作(或者就不區分開發測試了),不僅僅是人與人之間的協同,更多是雙方分別為完成“業務特性服務的代碼”和為完成”業務質量服務的代碼“而相互配合,并形成系統級的依賴關系。在提供的這些質量系統上,我們希望公司內部的各種角色都能成為質量的操作者。只在做到這些,我們才可能將測試工作真正從面向過程到面向對象。 圖示:理想的測試工作方式 實時質量的架構 要做到質量的實時反饋和面向對象測試,這意味著我們的測試方法和協同方式發生了較為根本性的變化。我們需要以一個合適的方式參與到業務應用中,與此同時我們還需要把測試的各種能力封裝成一個個服務,而不是現在的工具。工具終究是需要人來操作的,而我們希望未來測試任務的主體是機器、算法。測試人員只構建測試服務,而不參與測試過程,這也是最符合測試開發 Test Development Engineer 的 job design 。 圖示:實時質量架構 那測試到底還需不需要做功能測試?可能在很長一段時間內仍然是需要的,但那一定只是日常工作中很小一部分。 實時質量是基于現有測試能力改造 我們在推進一個新的方向時,盡量不要去推翻重來。如果要面向未來,實時質量必須是可以向下兼容的,因為只是這樣才能繼承現有的測試沉淀,也才能被團隊中的測試人員所接受和支持。只有自己不斷進化才符合自然規律。所以我們需要更多強調對現有測試能力的改造,而避免另起爐灶。以下用運營頁面測試的實時質量改造作為一個案例。 案例:運營頁面的實時質量改造 作為電商域的同學對于運營頁面應該非常熟悉,在之前也非常痛恨。比如: “CBU的一次大促,運營人員至少需要配置千級以上的活動頁面,而每一個頁面上又包含幾百上千個商品等活動元素,平均一個頁面需要5到10分鐘的人肉檢測,同時運營和測試人員需要不斷就測試標準和 Bug 來回討論、提交。一次大促下來,我們至少需要十幾人/日的測試資源才能保證會場的正確性?!? 這個過程很痛苦,運營人員需要不斷去找對應的測試同學協同,幸福感很差。而測試人員來說,這些頁面的測試更多是一個重復勞動,一個黑盒。能力也得不到什么成長。我們如何對它來進行實時質量的改造呢? 總共分兩步: 我們對傳統的測試體系進行了改造。把以往通過人工測試的各個測試點,通過自動化的方式來實現。比如基于 DOM 樹制定一系列規則,例如403這些的錯誤都可以被很好地掃描出來。同時,針對于一些無法通過規則排查的問題,我們運用了算法能力。例如空坑檢測,一致性檢測等。 把以上測試組件,通過消息的方式跟運營頁面發布系統對接。 它的系統依賴關系是如下的: 圖示:運營頁面檢測系統依賴圖【示意】 同時針對于不同的業務場景,我們開發了不同的頁面檢測能力,比如針對于 DOM 樹的頁面檢查: 還有基于算法能力的識別能力: 通過上述的改造后,對于運營人員發布頁面以及頁面的測試就極簡化為三步一站式的能力。從以往運營、測試、開發之間的來回交接,變成了運營跟系統之間的交互。不僅提升了運營人員的頁面搭建體驗,也極大地提升了測試的效率。 在某次運行中活動中實際的執行結果【示意圖】: 以上的過程和結果數據,也充分體現了“運行含測試,實時可反饋”的價值。 數據和算法是實時質量的核心 測試出現以來,我們一直習慣于代碼邏輯類的測試,但數據一直都是測試很重要的生產材料。因為人肉執行任務的局限性,我們發明了等價類和邊界值等測試理論和方法來用盡可能少的成本來盡可能多的驗證問題。但一方面算法的不斷應用,每一個數據都可能存在個性化的業務表達,我們可能無法找到一個通用的預期結果較驗(還是會有一些通用的預期結果的,比如非空判斷和區間等,但這類的預期不能很好地做業務判斷)。因此,我們也需要用數據和算法能力來武裝自己。 在以數據驅動的業務發展進程中,我們的測試主體已經從簡單的代碼轉變為數據+算法?;蛘哒f,業務對質量的核心述求,已經從簡單的頁面錯誤、代碼 BUG 到數據的準確性、算法的有效性(我老板在每次大促前,都要再三叮囑我數據不能錯)。如何來感知質量風險,以及捕獲各類的異常?那必須先把數據、流量、監控來做收口,同時提升測試工具在大數據分析上的能力。 基于這些思考,我們構建了全域實時數據校驗能力,是一款通過實時獲取線上 DB 中的海量業務數據,完成業務數據校驗、質量風險感知的產品。 案例:Captain 全域實時數據校驗 圖示:數據對比框架【示意】 它具備的一些能力: 嚴格的安全策略。 實時獲取線上數據:通過強大的數據支持能力,平臺可以在無損線上數據庫表的前提下,通過 SQL 查詢獲取線上 DB 中的真實業務數據,且做到了實時獲取,通過數據可以進行完善健壯的數據校驗,從根本上提高對于業務的把控。 多樣的數據獲取方式:目前平臺支持多種數據獲取方式:單庫單表查詢、單庫多表聯表查詢、分庫分表查詢、跨庫的多表的聯表查詢。 多種比對方式支持,比如跨庫查詢和聯表查詢等等。 最主要,它可以用一套腳本無損地支持測試環境、灰度、生產環境等。讓線下測試的所有經驗可以得到復用和沉淀。(我們內部調侃說,這才是帶著測試的靈魂的,而其他的很多產品都只是一個面向開發的工具) 在前期解決數據一致性,對賬等常用的基本需求上,我們可以依賴于這些數據和測試的服務,展開更多的業務形態。 實時質量需要不斷突破測試的邊界 測試的邊界在哪里? 過去有人告訴我,不能去修改業務應用的代碼,只能讓在盒子外面或者調用的方式來測試。還有人說,我們只開發工具,不能接觸任何的業務?,F在這些都在逐漸模糊,大家努力一起,讓測試的很多活動,從簡單的功能測試,往研發工具和業務質量等或前或后地遷移。 在過去的一兩年,我們團隊也已經慢慢承接了更多的職責,有些甚至于是直接服務于客服、運營和產品人員的。我認為,一支強的團隊一定是不斷走在突破原來工作邊界的道路上。沒有什么是一成不變的。 但每個職能團隊都是有自己的核心價值的,而至于哪些應該由測試來做,哪些由開發做。我們的標準是:判斷這件事情是更為了“讓技術更有品質”還是“讓技術創造新商業”?(“讓技術更有品質”是我們團隊的使命,“讓技術拓展業務邊界”是開發團隊的目標) 以下雖然是幾年前的例子,但也很好的體現了我們在邊界的突破,以及如何用實時質量的思想來開裝自己,創造提交 BUG 以外更多的價值。 案例:Offer 360提升客服端實時質量能力 商品鏈路復雜,線上問題排查難度大,之前開發每天平均投入2-3個小時處理線上問題,但實際上大部分的問題都是正常業務邏輯,并且可以讓客滿或者技術支持自助查詢的。因此,我們通過提供實時查詢錯誤日志以及 debug 信息的服務,把用戶反饋問題的排查,開放給客服。幫助他們第一時間解決用戶的問題。 實時質量未來規劃 實時質量是一種思想,我覺得它未來是可以跨越在當前兩種不同的發展分支上的。 測試這么多年來一直被弱化,我也看到集團很多優秀的測試 leader 轉型開發、產品。如果我們還不多些思考,多些探索。如果做測試都還沒有夢想,那跟咸魚有什么區別? 圖示:測試未來的發展 后記 上周在內部的論壇上看到一個開發專家的留言,還是挺有感觸的。我們一直以來都在強調測試能力不斷演進,強調開發能力,但測試的初心不能丟。我們在工具、測試能力上不斷改進,但是從人和組織的角度上來看,在追求最高效的同時,我們是需要一定的組織設計來形成崗位間的相互監督。這也是在測試1.0階段開始,測試被賦予的一種職責。 作者: 傲野 原文鏈接 ? 本文為云棲社區原創內容,未經允許不得轉載。
來源:OSCHINA
發布時間:2019-06-11 12:18:00
阿里云對中國用戶來說是最好的云計算服務廠商了,而它提供的優惠也多種多樣,不管對玩主機的新手還是老手,面對各種各樣的活動都會陷入眼花繚亂的困境,因此,我專門寫一篇文章來給大家抽絲剝繭,詳細描繪一下對不同的用戶怎么購買主機才是最優惠的。 優惠活動圖 本人是擁有十年程序猿開發經驗,大學本科四年,工作6年,軟件工程出身,所以我用我們程序猿特有的軟件來繪制一個優惠活動圖。 大概花了半個多小時繪制玩這張活動圖 /(ㄒoㄒ)/~~ 名詞解釋: 新用戶:沒注冊過阿里云的用戶或者注冊且實名了但從未購買任何產品的用戶 首購用戶:表示已注冊并實名認證后但從未購買該產品的用戶 老用戶:已實名并購買過該產品的用戶 活動列表: 阿里云新用戶2000元新手紅包 阿里云Hi拼團活動 阿里云首購3折活動 優惠路線 新用戶路線 領取2000元紅包 —> 找老用戶 開hi拼團活動 —> 2.4折優惠 領取2000元紅包 —> 參加首購活動 —> 3折優惠 首購用戶路線 找新用戶 開hi拼團活動 —> 可以找到 —> 2.4折優惠 找新用戶 開hi拼團活動 —> 沒找到 —> 3折優惠 參加 首購活動 —> 3折優惠 老用戶且非首購路線 找新用戶 開hi拼團活動 —> 可以找到 —> 2.4折優惠 找新用戶 開hi拼團活動 —> 沒找到 —> 直接購買 —> 3折優惠 花了大概一個多小時認真寫了這篇,也是想給自己剛上線的網站【 zhaozhuji.info 】做做推廣,謝謝拜訪! 其實做這個圖也是得益于我的一個微信群"獨立開發者"的,感謝小程序【魅力拍】作者徐玉豐,算也是給他做做推廣,感謝他的啟發!
來源:OSCHINA
發布時間:2019-06-08 19:29:00
或許很多人可能認為資源消耗并非安全問題,但實際上不合理的資源消耗會讓黑客有可乘之機,來攻擊K8s的組件。本文將介紹如何處理資源消耗或noisy neighbor問題,包括如何管理Pods中的資源以及管理項目和資源配額等。 本文是關于Kubernetes安全系列三篇文章中的最后一篇。在第一篇文章中,我們分享了如何確保企業的Kubernetes集群免受外部攻擊;第二篇文章介紹了三種保護Kubernetes免受內部威脅的方法。在本文中,我們將介紹如何處理資源消耗或noisy neighbor問題。 對于那些設置了多租戶Kubernetes集群的集群管理員而言,他們十分關注和擔心的一個問題是,如何防止共同租戶成為“noisy neighbor”,即一個壟斷了CPU、內存、存儲和其他資源的人。Noisy neighbor會對共享基礎設施的其他用戶資源的性能產生極壞的影響。 如此一來,跟蹤Kubernetes容器和Pod的資源使用情況,對集群管理而言非常重要,因為它不僅可以保持容器編排系統處于最佳運行狀態,降低運維成本,還可以加強Kubernetes的整體安全狀況。 一些運維團隊可能不認為資源消耗是一種重要的安全問題,至少沒有保護Kubernetes免受內部和外部網絡攻擊重要。但這種觀點是不正確的。因為厲害的黑客會利用功能不良的基礎設施,來找到攻擊Kubernetes組件的方法。 “安全不僅僅是‘不要闖進我的房子’,而是‘我怎么能讓我的房子一直保持良好的運行狀態’,”Rancher Labs的高級解決方案架構師Adrian Goins表示。 運維團隊需要最大限度地利用Kubernetes Pods(一組具有共享存儲和網絡資源的一個或多個容器)所消耗的資源,以確保每個用戶都能擁有最佳性能,并且能監控成本分配的使用情況?!笆褂玫扔诔杀?,”Goins說,“因為Kubernetes資源都是運行在AWS、谷歌云、阿里云等等云提供商的底層計算基礎設施上,一切資源消耗都以為著金錢成本。即使集群是在數據中心的裸機上運行,過多的使用也會花費硬件、電力和其他資源?!? 默認情況下,配置容器時,對其可以使用的資源量沒有任何限制。如果容器不能高效運行,部署容器的組織必將支付超額費用。值得慶幸的是,Kubernetes具有幫助運維團隊管理和優化Kubernetes資源利用能力的功能。 管理Pods中的資源 當管理員定義Pod時,他們可以選擇指定每個容器需要多少CPU和內存(RAM)。當容器指定了資源請求時,調度程序可以更好地決定將Pod放在哪個節點上。根據Kubernetes的文檔,當容器指定了限制時,可以按指定的方式處理節點上的資源爭用。 默認情況下,Kubernetes集群中的所有資源都是在默認的命名空間中創建的。命名空間是一種邏輯地將集群資源進行分組的方法,包括用于指定資源配額的選項。 管理員可以在命名空間上設置資源限制或配額,為在命名空間中運行的工作負載或應用程序分配一定量的CPU、RAM或存儲——Kubernetes集群中的三個資源?!叭绻诿臻g中啟動另一個資源會超出預設的配額,那么任何新資源都無法啟動,”Goins指出。 “當你應用了資源配額時,意味著你強制在該命名空間中運行的所有內容為其自身設置資源限制。限制有兩種類型:預留,和最大限制,”Goins解釋說。例如,通過預留,管理員可以讓Kubernetes集群為WordPress站點分配128 MB的RAM。對于部署的每個WordPress Pod,服務器本身將保證128 MB的RAM。因此,如果管理員將資源請求與1GB的資源配額相結合,則用戶只能在超過其限制之前運行八個WordPress Pod。在那之后,他們將無法再使用RAM了。 資源限制的第二部分是最大限度。管理員可以預留128 MB的資源請求和最多256 MB的RAM?!叭绻鸓od超過256 MB的RAM使用量,Kubernetes會殺死它并重新啟動它,”Goins說?!叭绱艘詠?,用戶可以免受失控過程和noisy neighbor的影響?!? 項目和資源配額 像Rancher這樣的平臺,旨在通過提供直觀的界面和集中管理任務(如全局層的角色描述)來簡化Kubernetes的管理。 正如前一篇關于內部威脅防護的文章所述,Rancher包含一個有助于減輕集群管理負擔的“項目(Project)”資源,來超越命名空間。在Rancher中,Project允許管理員將多個命名空間作為單個實體進行管理。因此,Rancher可以將資源配額應用于Projects。 在標準Kubernetes部署中,資源配額只能應用于單獨的命名空間。但是,管理員無法通過單次操作,同時將配額應用于命名空間。資源配額必須經過多次操作。 然而在Rancher中,管理員可以將資源配額應用于Project,然后將配額傳播到每個命名空間。然后,Kubernetes會使用本機版本的資源配額,來強制執行管理員限制。如果管理員希望更改特定命名空間的配額,則可以覆蓋以前的配額。 強化和優化Kubernetes 毋庸置疑,Kubernetes已成為容器編排的標準,這也促使大多數云和虛擬化供應商將其作為標準基礎架構來提供。但是,對與Kubernetes環境相關的安全問題的普遍缺乏認識,可能會使各種組件暴露于來自網絡集群內外的攻擊中。 本系列文章的上兩篇中提供了一些可行的步驟,來告訴大家如何通過使用Kubernetes功能和容器管理解決方案(如Rancher),來加強Kubernetes對外部和內部網絡威脅的防范。企業應通過基于角色的訪問控制(RBAC)和強身份驗證從外部保護Kubernetes API訪問。對于內部人員保護,由于Kubernetes集群是多用戶,因此組織需要通過RBAC、邏輯隔離和NetworkPolicies來保護交叉通信。 為了防止其他租戶壟斷CPU、內存、存儲和其他資源從而拖累整個集群的性能,Kubernetes提供資源限制和配額等功能,以幫助運維團隊管理和優化Kubernetes資源利用功能。最后,除了可用的默認設置之外,業界還有一些非常有效的工具可以幫助用戶完成Kubernetes集群的管理和保護。例如像Rancher這樣的平臺就是一種高度優化的容器管理解決方案,專為將多個集群部署到生產環境中的組織而構建,企業用戶可以更輕松地管理和運行各地的Kubernetes。它可以保護Kubernetes集群免受外部黑客威脅、內部隱患甚至noisy neighbor。
來源:OSCHINA
發布時間:2019-07-03 10:50:00
元數據服務是 BeeGFS 中用來維護文件和目錄關系及其屬性配置的服務,其多線程epoll設計實現非常高效,主要流程如下: ConnAcceptor(PThread)類(一個線程)負責監聽端口,并接受客戶端連接,然后把;連接信息(包含接收的套接字)寫入管道; StreamListenerV2(PThread)類(多個線程,可配置)從管道讀取連接信息,使用epoll輪詢接收數據,然后生成IncomingPreprocessedMsgWork(Work),寫入MultiWorkQueue先進先出隊列; Worker(PThread)類(多個線程,可配置)從MultiWorkQueue隊列取出消息進行處理。 程序初始化 主函數 創建App對象,App對象是程序的主要載體: // fhgfs_meta\source\program\main.cpp #include "Program.h" int main(int argc, char** argv) { return Program::main(argc, argv); } // fhgfs_meta\source\program\Program.cpp #include #include "Program.h" #include App* Program::app = NULL; int Program::main(int argc, char** argv) { BuildTypeTk::checkDebugBuildTypes(); AbstractApp::runTimeInitsAndChecks(); // must be called before creating a new App app = new App(argc, argv); app->startInCurrentThread(); int appRes = app->getAppResult(); delete app; return appRes; } 創建ConnAcceptor 主程序中會初始化一個線程,監聽服務端口,由ConnAcceptor類負責: // fhgfs_meta\source\app\App.cpp void App::initComponents(TargetConsistencyState initialConsistencyState) throw(ComponentInitException) { this->log->log(Log_DEBUG, "Initializing components..."); this->dgramListener = new DatagramListener( netFilter, localNicList, ackStore, cfg->getConnMetaPortUDP() ); if(cfg->getTuneListenerPrioShift() ) dgramListener->setPriorityShift(cfg->getTuneListenerPrioShift() ); streamListenersInit(); unsigned short listenPort = cfg->getConnMetaPortTCP(); this->connAcceptor = new ConnAcceptor(this, localNicList, listenPort); this->statsCollector = new StatsCollector(workQueue, STATSCOLLECTOR_COLLECT_INTERVAL_MS, STATSCOLLECTOR_HISTORY_LENGTH); this->buddyResyncer = new BuddyResyncer(); this->internodeSyncer = new InternodeSyncer(initialConsistencyState); this->timerQueue = new TimerQueue(1, 1); this->modificationEventFlusher = new ModificationEventFlusher(); workersInit(); commSlavesInit(); this->log->log(Log_DEBUG, "Components initialized."); } 創建StreamListener 根據配置創建多個StreamListener實例,每個實例對應線程,用于從ConnAcceptor接收新連接,已及從從連接讀取數據,生成Work: // fhgfs_meta\source\app\App.cpp void App::streamListenersInit() throw(ComponentInitException) { this->numStreamListeners = cfg->getTuneNumStreamListeners(); for(unsigned i=0; i < numStreamListeners; i++) { StreamListenerV2* listener = new StreamListenerV2( std::string("StreamLis") + StringTk::uintToStr(i+1), this, workQueue); if(cfg->getTuneListenerPrioShift() ) listener->setPriorityShift(cfg->getTuneListenerPrioShift() ); if(cfg->getTuneUseAggressiveStreamPoll() ) listener->setUseAggressivePoll(); streamLisVec.push_back(listener); } } 創建WorkQueue 創建WorkQueue,用于保存StreamListener生成的Work: // fhgfs_meta\source\app\App.cpp /** * Init basic shared objects like work queues, node stores etc. */ void App::initDataObjects() throw(InvalidConfigException) { ... this->workQueue = new MultiWorkQueue(); this->commSlaveQueue = new MultiWorkQueue(); if(cfg->getTuneUsePerUserMsgQueues() ) workQueue->setIndirectWorkList(new UserWorkContainer() ); ... } 創建Worker 根據配置創建Worker線程,從WorkQueue讀取Work并進行處理: // fhgfs_meta\source\app\App.cpp void App::workersInit() throw(ComponentInitException) { unsigned numWorkers = cfg->getTuneNumWorkers(); for(unsigned i=0; i < numWorkers; i++) { Worker* worker = new Worker( std::string("Worker") + StringTk::uintToStr(i+1), workQueue, QueueWorkType_INDIRECT); worker->setBufLens(cfg->getTuneWorkerBufSize(), cfg->getTuneWorkerBufSize() ); workerList.push_back(worker); } for(unsigned i=0; i < APP_WORKERS_DIRECT_NUM; i++) { Worker* worker = new Worker( std::string("DirectWorker") + StringTk::uintToStr(i+1), workQueue, QueueWorkType_DIRECT); worker->setBufLens(cfg->getTuneWorkerBufSize(), cfg->getTuneWorkerBufSize() ); workerList.push_back(worker); } } 連接監聽 監聽類ConnAcceptor ConnAcceptor類的定義: // fhgfs_common\source\common\components\streamlistenerv2\ConnAcceptor.h class ConnAcceptor : public PThread { public: ConnAcceptor(AbstractApp* app, NicAddressList& localNicList, unsigned short listenPort) throw(ComponentInitException); virtual ~ConnAcceptor(); private: AbstractApp* app; LogContext log; StandardSocket* tcpListenSock; StandardSocket* sdpListenSock; RDMASocket* rdmaListenSock; int epollFD; bool initSocks(unsigned short listenPort, NicListCapabilities* localNicCaps); virtual void run(); void listenLoop(); void onIncomingStandardConnection(StandardSocket* sock); void onIncomingRDMAConnection(RDMASocket* sock); void applySocketOptions(StandardSocket* sock); public: // getters & setters }; 連接監聽循環 使用epool來輪詢監聽端口,并建立新連接: // fhgfs_common\source\common\components\streamlistenerv2\ConnAcceptor.cpp void ConnAcceptor::run() { try { registerSignalHandler(); listenLoop(); log.log(Log_DEBUG, "Component stopped."); } catch(std::exception& e) { PThread::getCurrentThreadApp()->handleComponentException(e); } } void ConnAcceptor::listenLoop() { const int epollTimeoutMS = 3000; struct epoll_event epollEvents[EPOLL_EVENTS_NUM]; // (just to have these values on the stack...) const int epollFD = this->epollFD; RDMASocket* rdmaListenSock = this->rdmaListenSock; StandardSocket* sdpListenSock = this->sdpListenSock; StandardSocket* tcpListenSock = this->tcpListenSock; // wait for incoming events and handle them... while(!getSelfTerminate() ) { //log.log(Log_DEBUG, std::string("Before poll(). pollArrayLen: ") + // StringTk::uintToStr(pollArrayLen) ); int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS); if(unlikely(epollRes < 0) ) { // error occurred if(errno == EINTR) // ignore interruption, because the debugger causes this continue; log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() ); break; } // handle incoming connection attempts for(size_t i=0; i < (size_t)epollRes; i++) { struct epoll_event* currentEvent = &epollEvents[i]; Pollable* currentPollable = (Pollable*)currentEvent->data.ptr; //log.log(Log_DEBUG, std::string("Incoming data on FD: ") + // StringTk::intToStr(pollArray[i].fd) ); // debug in if(currentPollable == rdmaListenSock) onIncomingRDMAConnection(rdmaListenSock); else if(currentPollable == tcpListenSock) onIncomingStandardConnection(tcpListenSock); else if(currentPollable == sdpListenSock) onIncomingStandardConnection(sdpListenSock); else { // unknown connection => should never happen log.log(Log_WARNING, "Should never happen: Ignoring event for unknown connection. " "FD: " + StringTk::uintToStr(currentPollable->getFD() ) ); } } } } 套接字監聽處理(派發給流) 把建立的套接字發送給指定的StreamListener: // fhgfs_common\source\common\components\streamlistenerv2\ConnAcceptor.cpp /** * Accept the incoming connection and add new socket to StreamListenerV2 queue. * * Note: This is for standard sockets like TCP and SDP. */ void ConnAcceptor::onIncomingStandardConnection(StandardSocket* sock) { try { struct sockaddr_in peerAddr; socklen_t peerAddrLen = sizeof(peerAddr); StandardSocket* acceptedSock = (StandardSocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen); // (note: level Log_DEBUG to avoid spamming the log until we have log topics) log.log(Log_DEBUG, std::string("Accepted new connection from " + Socket::endpointAddrToString(&peerAddr.sin_addr, ntohs(peerAddr.sin_port) ) ) + std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) + std::string("]") ); applySocketOptions(acceptedSock); // hand the socket over to a stream listener StreamListenerV2* listener = app->getStreamListenerByFD(acceptedSock->getFD() ); StreamListenerV2::SockReturnPipeInfo returnInfo( StreamListenerV2::SockPipeReturn_NEWCONN, acceptedSock); listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) ); } catch(SocketException& se) { log.logErr(std::string("Trying to continue after connection accept error: ") + se.what() ); } } 流處理的選擇 選擇StreamListener時,是根據fd的數值取模運算得來: // fhgfs_meta\source\app\App.h class App : public AbstractApp { public: /** * Get one of the available stream listeners based on the socket file descriptor number. * This is to load-balance the sockets over all available stream listeners and ensure that * sockets are not bouncing between different stream listeners. * * Note that IB connections eat two fd numbers, so 2 and multiples of 2 might not be a good * value for number of stream listeners. */ virtual StreamListenerV2* getStreamListenerByFD(int fd) { return streamLisVec[fd % numStreamListeners]; } } 數據包流處理 流處理類StreamListenerV2 StreamListener的定義: // fhgfs_common\source\common\components\streamlistenerv2\StreamListenerV2.h class StreamListenerV2 : public PThread { public: /** * This is what we will send over the socket return pipe */ struct SockReturnPipeInfo { /** * Standard constructor for creating/sending a returnInfo. */ SockReturnPipeInfo(SockPipeReturnType returnType, Socket* sock) : returnType(returnType), sock(sock) {} /** * For receiving only (no initialization of members). */ SockReturnPipeInfo() {} SockPipeReturnType returnType; Socket* sock; }; } 流處理循環 StreamListener使用epoll同時處理新連接以及數據接收: // fhgfs_common\source\common\components\streamlistenerv2\StreamListenerV2.cpp void StreamListenerV2::run() { try { registerSignalHandler(); listenLoop(); log.log(Log_DEBUG, "Component stopped."); } catch(std::exception& e) { PThread::getCurrentThreadApp()->handleComponentException(e); } } void StreamListenerV2::listenLoop() { const int epollTimeoutMS = useAggressivePoll ? 0 : 3000; struct epoll_event epollEvents[EPOLL_EVENTS_NUM]; // (just to have these values on the stack...) const int epollFD = this->epollFD; FileDescriptor* sockReturnPipeReadEnd = this->sockReturnPipe->getReadFD(); bool runRDMAConnIdleCheck = false; // true just means we call the method (not enforce the check) // wait for incoming events and handle them... while(!getSelfTerminate() ) { //log.log(Log_DEBUG, std::string("Before poll(). pollArrayLen: ") + // StringTk::uintToStr(pollArrayLen) ); int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS); if(unlikely(epollRes < 0) ) { // error occurred if(errno == EINTR) // ignore interruption, because the debugger causes this continue; log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() ); break; } else if(unlikely(!epollRes || (rdmaCheckForceCounter++ > RDMA_CHECK_FORCE_POLLLOOPS) ) ) { // epollRes==0 is nothing to worry about, just idle // note: we can't run idle check here directly because the check might modify the // poll set, which will be accessed in the loop below runRDMAConnIdleCheck = true; } // handle incoming data & connection attempts for(size_t i=0; i < (size_t)epollRes; i++) { struct epoll_event* currentEvent = &epollEvents[i]; Pollable* currentPollable = (Pollable*)currentEvent->data.ptr; if(currentPollable == sockReturnPipeReadEnd) onSockReturn(); else onIncomingData( (Socket*)currentPollable); } if(unlikely(runRDMAConnIdleCheck) ) { // note: whether check actually happens depends on elapsed time since last check runRDMAConnIdleCheck = false; rdmaConnIdleCheck(); } } } 新連接處理 如果是新連接,則加入epoll的fd中: // fhgfs_common\source\common\components\streamlistenerv2\StreamListenerV2.cpp /** * Receive pointer to returned socket through the sockReturnPipe and re-add it to the pollList. */ void StreamListenerV2::onSockReturn() { SockReturnPipeInfo returnInfos[SOCKRETURN_SOCKS_NUM]; // try to get multiple returnInfos at once (if available) ssize_t readRes = sockReturnPipe->getReadFD()->read(&returnInfos, sizeof(SockReturnPipeInfo) ); // loop: walk over each info and handle the contained socket for(size_t i=0; ; i++) { SockReturnPipeInfo& currentReturnInfo = returnInfos[i]; // make sure we have a complete SockReturnPipeInfo if(unlikely(readRes < (ssize_t)sizeof(SockReturnPipeInfo) ) ) { // only got a partial SockReturnPipeInfo => recv the rest char* currentReturnInfoChar = (char*)¤tReturnInfo; sockReturnPipe->getReadFD()->readExact( ¤tReturnInfoChar[readRes], sizeof(SockReturnPipeInfo)-readRes); readRes = sizeof(SockReturnPipeInfo); } // handle returnInfo depending on contained returnType... Socket* currentSock = currentReturnInfo.sock; SockPipeReturnType returnType = currentReturnInfo.returnType; switch(returnType) { case SockPipeReturn_MSGDONE_NOIMMEDIATE: { // most likely case: worker is done with a msg and now returns the sock to the epoll set struct epoll_event epollEvent; epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; epollEvent.data.ptr = currentSock; int epollRes = epoll_ctl(epollFD, EPOLL_CTL_MOD, currentSock->getFD(), &epollEvent); if(likely(!epollRes) ) { // sock was successfully re-armed in epoll set pollList.add(currentSock); break; // break out of switch } else if(errno != ENOENT) { // error log.logErr("Unable to re-arm sock in epoll set. " "FD: " + StringTk::uintToStr(currentSock->getFD() ) + "; " "SockTypeNum: " + StringTk::uintToStr(currentSock->getSockType() ) + "; " "SysErr: " + System::getErrString() ); log.log(Log_NOTICE, "Disconnecting: " + currentSock->getPeername() ); delete(currentSock); break; // break out of switch } /* for ENOENT, we fall through to NEWCONN, because this socket appearently wasn't used with this stream listener yet, so we need to add it (instead of modify it) */ } // might fall through here on ENOENT case SockPipeReturn_NEWCONN: { // new conn from ConnAcceptor (or wasn't used with this stream listener yet) // add new socket file descriptor to epoll set struct epoll_event epollEvent; epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; epollEvent.data.ptr = currentSock; int epollRes = epoll_ctl(epollFD, EPOLL_CTL_ADD, currentSock->getFD(), &epollEvent); if(likely(!epollRes) ) { // socket was successfully added to epoll set pollList.add(currentSock); } else { // adding to epoll set failed => unrecoverable error log.logErr("Unable to add sock to epoll set. " "FD: " + StringTk::uintToStr(currentSock->getFD() ) + " " "SockTypeNum: " + StringTk::uintToStr(currentSock->getSockType() ) + " " "SysErr: " + System::getErrString() ); log.log(Log_NOTICE, "Disconnecting: " + currentSock->getPeername() ); delete(currentSock); } } break; case SockPipeReturn_MSGDONE_WITHIMMEDIATE: { // special case: worker detected that immediate data is available after msg processing // data immediately available => recv header and so on onIncomingData(currentSock); } break; default: { // should never happen: unknown/unhandled returnType log.logErr("Should never happen: " "Unknown socket return type: " + StringTk::uintToStr(returnType) ); } break; } // end of switch(returnType) readRes -= sizeof(SockReturnPipeInfo); if(!readRes) break; // all received returnInfos have been processed } // end of "for each received SockReturnPipeInfo" loop } 數據包處理(生成工作) 生成Work(IncomingPreprocessedMsgWork),并放進隊列(MultiWorkQueue): // fhgfs_common\source\common\components\streamlistenerv2\StreamListenerV2.cpp /** * Receive msg header and add the socket to the work queue. */ void StreamListenerV2::onIncomingData(Socket* sock) { // check whether this is just a false alarm from a RDMASocket if( (sock->getSockType() == NICADDRTYPE_RDMA) && isFalseAlarm( (RDMASocket*)sock) ) { return; } try { const int recvTimeoutMS = 5000; char msgHeaderBuf[NETMSG_HEADER_LENGTH]; NetMessageHeader msgHeader; // receive & deserialize message header sock->recvExactT(msgHeaderBuf, NETMSG_HEADER_LENGTH, 0, recvTimeoutMS); NetMessage::deserializeHeader(msgHeaderBuf, NETMSG_HEADER_LENGTH, &msgHeader); /* (note on header verification: we leave header verification work to the worker threads to save CPU cycles in the stream listener and instead just take what we need to know here, no matter whether the header is valid or not.) */ // create work and add it to queue //log.log(Log_DEBUG, "Creating new work for to the queue"); IncomingPreprocessedMsgWork* work = new IncomingPreprocessedMsgWork(app, sock, &msgHeader); int sockFD = sock->getFD(); /* note: we store this here for delayed pollList removal, because worker thread might disconnect, so the sock gets deleted by the worker and thus "sock->" pointer becomes invalid */ sock->setHasActivity(); // mark sock as active (for idle disconnect check) // (note: userID intToStr (not uint) because default userID (~0) looks better this way) LOG_DEBUG("StreamListenerV2::onIncomingData", Log_DEBUG, "Incoming message: " + NetMsgStrMapping().defineToStr(msgHeader.msgType) + "; " "from: " + sock->getPeername() + "; " "userID: " + StringTk::intToStr(msgHeader.msgUserID) + (msgHeader.msgTargetID ? "; targetID: " + StringTk::uintToStr(msgHeader.msgTargetID) : "") ); if (sock->getIsDirect()) getWorkQueue(msgHeader.msgTargetID)->addDirectWork(work, msgHeader.msgUserID); else getWorkQueue(msgHeader.msgTargetID)->addIndirectWork(work, msgHeader.msgUserID); /* notes on sock handling: *) no need to remove sock from epoll set, because we use edge-triggered mode with oneshot flag (which disables further events after first one has been reported). *) a sock that is closed by a worker is not a problem, because it will automatically be removed from the epoll set by the kernel. *) we just need to re-arm the epoll entry upon sock return. */ pollList.removeByFD(sockFD); return; } catch(SocketTimeoutException& e) { log.log(Log_NOTICE, "Connection timed out: " + sock->getPeername() ); } catch(SocketDisconnectException& e) { // (note: level Log_DEBUG here to avoid spamming the log until we have log topics) log.log(Log_DEBUG, std::string(e.what() ) ); } catch(SocketException& e) { log.log(Log_NOTICE, "Connection error: " + sock->getPeername() + ": " + std::string(e.what() ) ); } // socket exception occurred => cleanup pollList.removeByFD(sock->getFD() ); IncomingPreprocessedMsgWork::invalidateConnection(sock); // also includes delete(sock) } 工作處理 工人類(Worker) // fhgfs_common\source\common\components\streamlistenerv2\ConnAcceptor.cpp #define WORKER_BUFIN_SIZE (1024*1024*4) #define WORKER_BUFOUT_SIZE WORKER_BUFIN_SIZE class Worker : public PThread { public: Worker(std::string workerID, MultiWorkQueue* workQueue, QueueWorkType workType) throw(ComponentInitException); virtual ~Worker() { SAFE_FREE(bufIn); SAFE_FREE(bufOut); } private: LogContext log; bool terminateWithFullQueue; // allow self-termination when queue not empty (see setter nodes) size_t bufInLen; char* bufIn; size_t bufOutLen; char* bufOut; MultiWorkQueue* workQueue; QueueWorkType workType; HighResolutionStats stats; virtual void run(); void workLoopAnyWork(); void workLoopDirectWork(); void initBuffers(); // inliners bool maySelfTerminateNow() { if(terminateWithFullQueue || (!workQueue->getDirectWorkListSize() && !workQueue->getIndirectWorkListSize() ) ) return true; return false; } public: // setters & getters /** * Note: Do not use this after the run method of this component has been called! */ void setBufLens(size_t bufInLen, size_t bufOutLen) { this->bufInLen = bufInLen; this->bufOutLen = bufOutLen; } /** * WARNING: This will only work if there is only a single worker attached to a queue. * Otherwise the queue would need a getWorkAndDontWait() method that is used during the * termination phase of the worker, because the queue might become empty before the worker * calls waitForWork() after the maySelfTerminateNow check. */ void disableTerminationWithFullQueue() { this->terminateWithFullQueue = false; } }; 工作類(Work) // fhgfs_common\source\common\components\worker\Work.h class Work { public: Work() { HighResolutionStatsTk::resetStats(&stats); } virtual ~Work() {} Work(const Work&) = delete; Work(Work&&) = delete; Work& operator=(const Work&) = delete; Work& operator=(Work&&) = delete; virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) = 0; protected: HighResolutionStats stats; public: HighResolutionStats* getHighResolutionStats() { return &stats; } #ifdef BEEGFS_DEBUG_PROFILING TimeFine* getAgeTime() { return &age; } private: TimeFine age; #endif }; // fhgfs_common\source\common\components\streamlistenerv2\IncomingPreprocessedMsgWork.h class IncomingPreprocessedMsgWork : public Work { public: /** * Note: Be aware that this class is only for stream connections that need to be returned * to a StreamListenerV2 after processing. * * @param msgHeader contents will be copied */ IncomingPreprocessedMsgWork(AbstractApp* app, Socket* sock, NetMessageHeader* msgHeader) { this->app = app; this->sock = sock; this->msgHeader = *msgHeader; } virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); static void releaseSocket(AbstractApp* app, Socket** sock, NetMessage* msg); static void invalidateConnection(Socket* sock); static bool checkRDMASocketImmediateData(AbstractApp* app, Socket* sock); private: AbstractApp* app; Socket* sock; NetMessageHeader msgHeader; }; 工作循環 從WorkQueens獲取Work并進行處理: // fhgfs_common\source\common\components\worker\Worker.cpp void Worker::workLoop(QueueWorkType workType) { LOG(DEBUG, "Ready", as("TID", System::getTID()), workType); workQueue->incNumWorkers(); // add this worker to queue stats while(!getSelfTerminate() || !maySelfTerminateNow() ) { Work* work = waitForWorkByType(stats, personalWorkQueue, workType); #ifdef BEEGFS_DEBUG_PROFILING TimeFine workStartTime; #endif HighResolutionStatsTk::resetStats(&stats); // prepare stats // process the work packet work->process(bufIn, bufInLen, bufOut, bufOutLen); // update stats stats.incVals.workRequests = 1; HighResolutionStatsTk::addHighResIncStats(*work->getHighResolutionStats(), stats); #ifdef BEEGFS_DEBUG_PROFILING TimeFine workEndTime; const auto workElapsedMS = workEndTime.elapsedSinceMS(&workStartTime); const auto workLatencyMS = workEndTime.elapsedSinceMS(work->getAgeTime()); if (workElapsedMS >= 10) { if (workLatencyMS >= 10) LOG_TOP(WORKQUEUES, DEBUG, "Work processed.", as("Elapsed ms", workElapsedMS), as("Total latency (ms)", workLatencyMS)); else LOG_TOP(WORKQUEUES, DEBUG, "Work processed.", as("Elapsed ms", workElapsedMS), as("Total latency (us)", workEndTime.elapsedSinceMicro(work->getAgeTime()))); } else { if (workLatencyMS >= 10) { LOG_TOP(WORKQUEUES, DEBUG, "Work processed.", as("Elapsed us", workEndTime.elapsedSinceMicro(&workStartTime)), as("Total latency (ms)", workEndTime.elapsedSinceMS(work->getAgeTime()))); } else { LOG_TOP(WORKQUEUES, DEBUG, "Work processed.", as("Elapsed us", workEndTime.elapsedSinceMicro(&workStartTime)), as("Total latency (us)", workEndTime.elapsedSinceMicro(work->getAgeTime()))); } } #endif // cleanup delete(work); } } 工作處理(消息生成和處理) 處理Work時,使用Work基類的processIncoming虛函數進行處理: // fhgfs_common\source\common\components\streamlistenerv2\IncomingPreprocessedMsgWork.cpp void IncomingPreprocessedMsgWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) { const char* logContextStr = "Work (process incoming msg)"; const int recvTimeoutMS = 5000; unsigned numReceived = NETMSG_HEADER_LENGTH; // (header actually received by stream listener) NetMessage* msg = NULL; try { // attach stats to sock (stream listener already received the msg header) stats.incVals.netRecvBytes += NETMSG_HEADER_LENGTH; sock->setStats(&stats); // make sure msg length fits into our receive buffer unsigned msgLength = msgHeader.msgLength; unsigned msgPayloadLength = msgLength - numReceived; if(unlikely(msgPayloadLength > bufInLen) ) { // message too big LogContext(logContextStr).log(Log_NOTICE, std::string("Received a message that is too large. Disconnecting: ") + sock->getPeername() ); sock->unsetStats(); invalidateConnection(sock); return; } // receive the message payload if(msgPayloadLength) sock->recvExactT(bufIn, msgPayloadLength, 0, recvTimeoutMS); // we got the complete message buffer => create msg object AbstractApp* app = PThread::getCurrentThreadApp(); ICommonConfig* cfg = app->getCommonConfig(); AbstractNetMessageFactory* netMessageFactory = app->getNetMessageFactory(); msg = netMessageFactory->createFromPreprocessedBuf(&msgHeader, bufIn, msgPayloadLength); if(unlikely(msg->getMsgType() == NETMSGTYPE_Invalid) ) { // message invalid LogContext(logContextStr).log(Log_NOTICE, std::string("Received an invalid message. Disconnecting: ") + sock->getPeername() ); sock->unsetStats(); invalidateConnection(sock); delete(msg); return; } // process the received msg bool processRes = false; if(likely(!cfg->getConnAuthHash() || sock->getIsAuthenticated() || (msg->getMsgType() == NETMSGTYPE_AuthenticateChannel) ) ) { // auth disabled or channel is auth'ed or this is an auth msg => process NetMessage::ResponseContext rctx(NULL, sock, bufOut, bufOutLen, &stats); processRes = msg->processIncoming(rctx); } else LogContext(logContextStr).log(Log_NOTICE, std::string("Rejecting message from unauthenticated peer: ") + sock->getPeername() ); // processing completed => cleanup bool needSockRelease = msg->getReleaseSockAfterProcessing(); delete(msg); msg = NULL; if(!needSockRelease) return; // sock release was already done within msg->processIncoming() method if(unlikely(!processRes) ) { // processIncoming encountered messaging error => invalidate connection LogContext(logContextStr).log(Log_NOTICE, std::string("Problem encountered during processing of a message. Disconnecting: ") + sock->getPeername() ); invalidateConnection(sock); return; } releaseSocket(app, &sock, NULL); return; } catch(SocketTimeoutException& e) { LogContext(logContextStr).log(Log_NOTICE, std::string("Connection timed out: ") + sock->getPeername() ); } catch(SocketDisconnectException& e) { // (note: level Log_DEBUG here to avoid spamming the log until we have log topics) LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) ); } catch(SocketException& e) { LogContext(logContextStr).log(Log_NOTICE, std::string("Connection error: ") + sock->getPeername() + std::string(": ") + std::string(e.what() ) ); } // socket exception occurred => cleanup if(msg && msg->getReleaseSockAfterProcessing() ) { sock->unsetStats(); invalidateConnection(sock); } SAFE_DELETE(msg); } 消息工廠 消息工廠類(NetMessageFactory) StreamListener收到數據時使用消息工廠類生成各種類型的消息: // fhgfs_meta\source\net\message\NetMessageFactory.h class NetMessageFactory : public AbstractNetMessageFactory { public: NetMessageFactory() {} protected: virtual NetMessage* createFromMsgType(unsigned short msgType); } ; 消息工廠初始化 // fhgfs_meta\source\app\App.cpp /** * Init basic networking data structures. * * Note: no RDMA is detected here, because this needs to be done later */ void App::initBasicNetwork() { // check if management host is defined if(!cfg->getSysMgmtdHost().length() ) throw InvalidConfigException("Management host undefined"); // prepare filter for outgoing packets/connections this->netFilter = new NetFilter(cfg->getConnNetFilterFile() ); this->tcpOnlyFilter = new NetFilter(cfg->getConnTcpOnlyFilterFile() ); // prepare filter for interfaces StringList allowedInterfaces; std::string interfacesList = cfg->getConnInterfacesList(); if(!interfacesList.empty() ) { log->log(Log_DEBUG, "Allowed interfaces: " + interfacesList); StringTk::explodeEx(interfacesList, ',', true, &allowedInterfaces); } // discover local NICs and filter them NetworkInterfaceCard::findAllInterfaces(allowedInterfaces, cfg->getConnUseSDP(), localNicList); if(localNicList.empty() ) throw InvalidConfigException("Couldn't find any usable NIC"); localNicList.sort(&NetworkInterfaceCard::nicAddrPreferenceComp); // prepare factory for incoming messages this->netMessageFactory = new NetMessageFactory(); } 生成消息 消息實例的生成均根據msgType來確定: // fhgfs_meta\source\net\message\NetMessageFactory.cpp /** * @return NetMessage that must be deleted by the caller * (msg->msgType is NETMSGTYPE_Invalid on error) */ NetMessage* NetMessageFactory::createFromMsgType(unsigned short msgType) { NetMessage* msg; switch(msgType) { // The following lines are grouped by "type of the message" and ordered alphabetically inside // the groups. There should always be one message per line to keep a clear layout (although // this might lead to lines that are longer than usual) // control messages case NETMSGTYPE_Ack: { msg = new AckMsgEx(); } break; case NETMSGTYPE_AuthenticateChannel: { msg = new AuthenticateChannelMsgEx(); } break; case NETMSGTYPE_GenericResponse: { msg = new GenericResponseMsg(); } break; case NETMSGTYPE_SetChannelDirect: { msg = new SetChannelDirectMsgEx(); } break; case NETMSGTYPE_PeerInfo: { msg = new PeerInfoMsgEx(); } break; // nodes messages case NETMSGTYPE_ChangeTargetConsistencyStatesResp: { msg = new ChangeTargetConsistencyStatesRespMsg(); } break; case NETMSGTYPE_GenericDebug: { msg = new GenericDebugMsgEx(); } break; case NETMSGTYPE_GetClientStats: { msg = new GetClientStatsMsgEx(); } break; case NETMSGTYPE_GetMirrorBuddyGroupsResp: { msg = new GetMirrorBuddyGroupsRespMsg(); } break; case NETMSGTYPE_GetNodeCapacityPools: { msg = new GetNodeCapacityPoolsMsgEx(); } break; case NETMSGTYPE_GetNodeCapacityPoolsResp: { msg = new GetNodeCapacityPoolsRespMsg(); } break; case NETMSGTYPE_GetNodes: { msg = new GetNodesMsgEx(); } break; case NETMSGTYPE_GetNodesResp: { msg = new GetNodesRespMsg(); } break; case NETMSGTYPE_GetStatesAndBuddyGroupsResp: { msg = new GetStatesAndBuddyGroupsRespMsg(); } break; case NETMSGTYPE_GetTargetMappings: { msg = new GetTargetMappingsMsgEx(); } break; case NETMSGTYPE_GetTargetMappingsResp: { msg = new GetTargetMappingsRespMsg(); } break; case NETMSGTYPE_GetTargetStatesResp: { msg = new GetTargetStatesRespMsg(); } break; case NETMSGTYPE_HeartbeatRequest: { msg = new HeartbeatRequestMsgEx(); } break; case NETMSGTYPE_Heartbeat: { msg = new HeartbeatMsgEx(); } break; case NETMSGTYPE_MapTargets: { msg = new MapTargetsMsgEx(); } break; case NETMSGTYPE_PublishCapacities: { msg = new PublishCapacitiesMsgEx(); } break; case NETMSGTYPE_RegisterNodeResp: { msg = new RegisterNodeRespMsg(); } break; case NETMSGTYPE_RemoveNode: { msg = new RemoveNodeMsgEx(); } break; case NETMSGTYPE_RemoveNodeResp: { msg = new RemoveNodeRespMsg(); } break; case NETMSGTYPE_RefreshCapacityPools: { msg = new RefreshCapacityPoolsMsgEx(); } break; case NETMSGTYPE_RefreshTargetStates: { msg = new RefreshTargetStatesMsgEx(); } break; case NETMSGTYPE_SetMirrorBuddyGroup: { msg = new SetMirrorBuddyGroupMsgEx(); } break; case NETMSGTYPE_SetRootNodeIDResp: { msg = new SetRootNodeIDRespMsg(); } break; case NETMSGTYPE_SetTargetConsistencyStates: { msg = new SetTargetConsistencyStatesMsgEx(); } break; case NETMSGTYPE_SetTargetConsistencyStatesResp: { msg = new SetTargetConsistencyStatesRespMsg(); } break; // storage messages case NETMSGTYPE_FindEntryname: { msg = new FindEntrynameMsgEx(); } break; case NETMSGTYPE_FindLinkOwner: { msg = new FindLinkOwnerMsgEx(); } break; case NETMSGTYPE_FindOwner: { msg = new FindOwnerMsgEx(); } break; case NETMSGTYPE_FindOwnerResp: { msg = new FindOwnerRespMsg(); } break; case NETMSGTYPE_GetChunkFileAttribsResp: { msg = new GetChunkFileAttribsRespMsg(); } break; case NETMSGTYPE_GetStorageTargetInfo: { msg = new GetStorageTargetInfoMsgEx(); } break; case NETMSGTYPE_GetEntryInfo: { msg = new GetEntryInfoMsgEx(); } break; case NETMSGTYPE_GetEntryInfoResp: { msg = new GetEntryInfoRespMsg(); } break; case NETMSGTYPE_GetHighResStats: { msg = new GetHighResStatsMsgEx(); } break; case NETMSGTYPE_GetMetaResyncStats: { msg = new GetMetaResyncStatsMsgEx(); } break; case NETMSGTYPE_RequestExceededQuotaResp: {msg = new RequestExceededQuotaRespMsg(); } break; case NETMSGTYPE_SetExceededQuota: {msg = new SetExceededQuotaMsgEx(); } break; case NETMSGTYPE_StorageResyncStarted: { msg = new StorageResyncStartedMsgEx(); } break; case NETMSGTYPE_StorageResyncStartedResp: { msg = new StorageResyncStartedRespMsg(); } break; case NETMSGTYPE_GetXAttr: { msg = new GetXAttrMsgEx(); } break; case NETMSGTYPE_Hardlink: { msg = new HardlinkMsgEx(); } break; case NETMSGTYPE_HardlinkResp: { msg = new HardlinkRespMsg(); } break; case NETMSGTYPE_ListDirFromOffset: { msg = new ListDirFromOffsetMsgEx(); } break; case NETMSGTYPE_ListDirFromOffsetResp: { msg = new ListDirFromOffsetRespMsg(); } break; case NETMSGTYPE_ListXAttr: { msg = new ListXAttrMsgEx(); } break; case NETMSGTYPE_LookupIntent: { msg = new LookupIntentMsgEx(); } break; case NETMSGTYPE_LookupIntentResp: { msg = new LookupIntentRespMsg(); } break; case NETMSGTYPE_MkDir: { msg = new MkDirMsgEx(); } break; case NETMSGTYPE_MkDirResp: { msg = new MkDirRespMsg(); } break; case NETMSGTYPE_MkFile: { msg = new MkFileMsgEx(); } break; case NETMSGTYPE_MkFileResp: { msg = new MkFileRespMsg(); } break; case NETMSGTYPE_MkFileWithPattern: { msg = new MkFileWithPatternMsgEx(); } break; case NETMSGTYPE_MkFileWithPatternResp: { msg = new MkFileWithPatternRespMsg(); } break; case NETMSGTYPE_MkLocalDir: { msg = new MkLocalDirMsgEx(); } break; case NETMSGTYPE_MkLocalDirResp: { msg = new MkLocalDirRespMsg(); } break; case NETMSGTYPE_MkLocalFileResp: { msg = new MkLocalFileRespMsg(); } break; case NETMSGTYPE_MovingDirInsert: { msg = new MovingDirInsertMsgEx(); } break; case NETMSGTYPE_MovingDirInsertResp: { msg = new MovingDirInsertRespMsg(); } break; case NETMSGTYPE_MovingFileInsert: { msg = new MovingFileInsertMsgEx(); } break; case NETMSGTYPE_MovingFileInsertResp: { msg = new MovingFileInsertRespMsg(); } break; case NETMSGTYPE_RefreshEntryInfo: { msg = new RefreshEntryInfoMsgEx(); } break; case NETMSGTYPE_RefreshEntryInfoResp: { msg = new RefreshEntryInfoRespMsg(); } break; case NETMSGTYPE_ResyncRawInodes: { msg = new ResyncRawInodesMsgEx(); } break; case NETMSGTYPE_ResyncRawInodesResp: { msg = new ResyncRawInodesRespMsg(); } break; case NETMSGTYPE_ResyncSessionStore: { msg = new ResyncSessionStoreMsgEx(); } break; case NETMSGTYPE_ResyncSessionStoreResp: { msg = new ResyncSessionStoreRespMsg(); } break; case NETMSGTYPE_RemoveXAttr: { msg = new RemoveXAttrMsgEx(); } break; case NETMSGTYPE_RemoveXAttrResp: { msg = new RemoveXAttrRespMsg(); } break; case NETMSGTYPE_Rename: { msg = new RenameV2MsgEx(); } break; case NETMSGTYPE_RenameResp: { msg = new RenameRespMsg(); } break; case NETMSGTYPE_RmChunkPathsResp: { msg = new RmChunkPathsRespMsg(); } break; case NETMSGTYPE_RmDirEntry: { msg = new RmDirEntryMsgEx(); } break; case NETMSGTYPE_RmDir: { msg = new RmDirMsgEx(); } break; case NETMSGTYPE_RmDirResp: { msg = new RmDirRespMsg(); } break; case NETMSGTYPE_RmLocalDir: { msg = new RmLocalDirMsgEx(); } break; case NETMSGTYPE_RmLocalDirResp: { msg = new RmLocalDirRespMsg(); } break; case NETMSGTYPE_SetAttr: { msg = new SetAttrMsgEx(); } break; case NETMSGTYPE_SetAttrResp: { msg = new SetAttrRespMsg(); } break; case NETMSGTYPE_SetDirPattern: { msg = new SetDirPatternMsgEx(); } break; case NETMSGTYPE_SetDirPatternResp: { msg = new SetDirPatternRespMsg(); } break; case NETMSGTYPE_SetLocalAttrResp: { msg = new SetLocalAttrRespMsg(); } break; case NETMSGTYPE_SetMetadataMirroring: { msg = new SetMetadataMirroringMsgEx(); } break; case NETMSGTYPE_SetStorageTargetInfoResp: { msg = new SetStorageTargetInfoRespMsg(); } break; case NETMSGTYPE_SetXAttr: { msg = new SetXAttrMsgEx(); } break; case NETMSGTYPE_SetXAttrResp: { msg = new SetXAttrRespMsg(); } break; case NETMSGTYPE_Stat: { msg = new StatMsgEx(); } break; case NETMSGTYPE_StatResp: { msg = new StatRespMsg(); } break; case NETMSGTYPE_StatStoragePath: { msg = new StatStoragePathMsgEx(); } break; case NETMSGTYPE_StatStoragePathResp: { msg = new StatStoragePathRespMsg(); } break; case NETMSGTYPE_TruncFile: { msg = new TruncFileMsgEx(); } break; case NETMSGTYPE_TruncFileResp: { msg = new TruncFileRespMsg(); } break; case NETMSGTYPE_TruncLocalFileResp: { msg = new TruncLocalFileRespMsg(); } break; case NETMSGTYPE_UnlinkFile: { msg = new UnlinkFileMsgEx(); } break; case NETMSGTYPE_UnlinkFileResp: { msg = new UnlinkFileRespMsg(); } break; case NETMSGTYPE_UnlinkLocalFileResp: { msg = new UnlinkLocalFileRespMsg(); } break; case NETMSGTYPE_UpdateBacklinkResp: { msg = new UpdateBacklinkRespMsg(); } break; case NETMSGTYPE_UpdateDirParent: { msg = new UpdateDirParentMsgEx(); } break; case NETMSGTYPE_UpdateDirParentResp: { msg = new UpdateDirParentRespMsg(); } break; // session messages case NETMSGTYPE_BumpFileVersion: { msg = new BumpFileVersionMsgEx(); } break; case NETMSGTYPE_BumpFileVersionResp: { msg = new BumpFileVersionRespMsg(); } break; case NETMSGTYPE_OpenFile: { msg = new OpenFileMsgEx(); } break; case NETMSGTYPE_OpenFileResp: { msg = new OpenFileRespMsg(); } break; case NETMSGTYPE_CloseFile: { msg = new CloseFileMsgEx(); } break; case NETMSGTYPE_CloseFileResp: { msg = new CloseFileRespMsg(); } break; case NETMSGTYPE_CloseChunkFileResp: { msg = new CloseChunkFileRespMsg(); } break; case NETMSGTYPE_WriteLocalFileResp: { msg = new WriteLocalFileRespMsg(); } break; case NETMSGTYPE_FSyncLocalFileResp: { msg = new FSyncLocalFileRespMsg(); } break; case NETMSGTYPE_FLockAppend: { msg = new FLockAppendMsgEx(); } break; case NETMSGTYPE_FLockEntry: { msg = new FLockEntryMsgEx(); } break; case NETMSGTYPE_FLockEntryResp: { msg = new FLockEntryRespMsg(); } break; case NETMSGTYPE_FLockRange: { msg = new FLockRangeMsgEx(); } break; case NETMSGTYPE_FLockRangeResp: { msg = new FLockRangeRespMsg(); } break; case NETMSGTYPE_GetFileVersion: { msg = new GetFileVersionMsgEx(); } break; case NETMSGTYPE_AckNotify: { msg = new AckNotifiyMsgEx(); } break; case NETMSGTYPE_AckNotifyResp: { msg = new AckNotifiyRespMsg(); } break; //admon messages case NETMSGTYPE_RequestMetaData: { msg = new RequestMetaDataMsgEx(); } break; case NETMSGTYPE_GetNodeInfo: { msg = new GetNodeInfoMsgEx(); } break; // fsck messages case NETMSGTYPE_RetrieveDirEntries: { msg = new RetrieveDirEntriesMsgEx(); } break; case NETMSGTYPE_RetrieveInodes: { msg = new RetrieveInodesMsgEx(); } break; case NETMSGTYPE_RetrieveFsIDs: { msg = new RetrieveFsIDsMsgEx(); } break; case NETMSGTYPE_DeleteDirEntries: { msg = new DeleteDirEntriesMsgEx(); } break; case NETMSGTYPE_CreateDefDirInodes: { msg = new CreateDefDirInodesMsgEx(); } break; case NETMSGTYPE_FixInodeOwners: { msg = new FixInodeOwnersMsgEx(); } break; case NETMSGTYPE_FixInodeOwnersInDentry: { msg = new FixInodeOwnersInDentryMsgEx(); } break; case NETMSGTYPE_LinkToLostAndFound: { msg = new LinkToLostAndFoundMsgEx(); } break; case NETMSGTYPE_CreateEmptyContDirs: { msg = new CreateEmptyContDirsMsgEx(); } break; case NETMSGTYPE_UpdateFileAttribs: { msg = new UpdateFileAttribsMsgEx(); } break; case NETMSGTYPE_UpdateDirAttribs: { msg = new UpdateDirAttribsMsgEx(); } break; case NETMSGTYPE_RemoveInodes: { msg = new RemoveInodesMsgEx(); } break; case NETMSGTYPE_RecreateFsIDs: { msg = new RecreateFsIDsMsgEx(); } break; case NETMSGTYPE_RecreateDentries: { msg = new RecreateDentriesMsgEx(); } break; case NETMSGTYPE_FsckSetEventLogging: { msg = new FsckSetEventLoggingMsgEx(); } break; case NETMSGTYPE_AdjustChunkPermissions: { msg = new AdjustChunkPermissionsMsgEx(); } break; default: { msg = new SimpleMsg(NETMSGTYPE_Invalid); } break; } return msg; }
來源:OSCHINA
發布時間:2019-07-02 23:23:00
近日,Rancher Labs宣布在Rancher 2.3 Preview2版本上支持Istio,簡化了Istio的安裝和配置,讓部署和管理Istio的旅程變得簡單而快速。 近日,業界領先的容器管理軟件提供商Rancher Labs(以下簡稱Rancher)宣布在Rancher 2.3 Preview 2版本上支持Istio,讓部署和管理Istio的旅程變得簡單而快速。 為什么選擇Istio? Istio,以及整個Service Mesh技術,是近一兩年來Kubernetes生態系統中最亮眼的明星之一。Istio增加了容錯、金絲雀部署、A/B測試、監控、跟蹤和可觀察性、身份認證和授權,開發人員無需再測試或編寫特定代碼,即可實現上述功能。如此一來,開發人員可以只專注于他們的業務邏輯,將剩下的工作交給Kubernetes和Istio。 上面這些說法其實并不新鮮。早在大約10年前,PaaS供應商們就提出了類似的說法,甚至在一定程度上兌現了這一要求。但問題在于,他們的產品需要特定的語言和框架,并且在大部分情況下只能用于非常簡單的應用程序。用戶的工作負載也會和供應商獨特的方案關聯在一起。這就意味著如果您希望應用程序使用PaaS服務,您可能會被鎖定相當長的一段時間。 但如今,對于容器和Kubernetes而言,這些限制、這些被鎖定的風險都不復存在。只要您將您的應用程序容器化,Kubernetes就可以為您運行它。 Istio在Rancher 2.3 Preview 2中如何工作 大量Rancher用戶喜歡Rancher平臺的原因,就是Rancher讓管理和操作Kubernetes及相關的工具和技術變得極其簡單,且用戶們不必擔心會被特定的云供應商鎖定。而如今對于Istio,我們采取了同樣的方法,致力于帶給用戶同樣的體驗。 在Rancher 2.3 Preview中,我們為用戶提供了一個簡單而友好的用戶界面,在UI中使用工具菜單,即可啟動Istio。系統提供了合理的默認配置,用戶也可以根據需要進行修改: 為了監控流量,Istio需要注入Envoy sidecar。在Rancher 2.3 Preview當中,用戶可以為每個空間名稱注入自動sidecar。一旦您勾選了這個選項,Rancher會將sidecar容器注入到每個工作負載當中: Rancher簡化了Istio的安裝和配置,內置了一個支持Kiali的儀表盤,用于流量和遙測的可視化,然后用Jaeger進行追蹤,甚至還有自己的Prometheus和Grafana(與用于高級監控的實例不同)。 在啟用自動sidecar注入的命名空間中部署工作負載后,您可以跳轉到Istio菜單目錄,觀察微服務應用程序的流量: 點擊Kiali、Jaeger、Prometheus或者Grafana,您將進入每個工具相應的用戶界面,您可以在其中找到詳細信息和選項: 正如前面所提到的,Istio的強大之處在于它能為您的服務帶來諸如容錯、斷路、金絲雀部署等功能。要啟用這些功能,您需要開發和應用適當的YAML文件。目前Windows工作負載還不支持Istio,因此不應在Windows集群中啟用它。 結 語 Istio是當前Rancher及Kubernetes社區中最受關注的功能之一。但是,如何最達到Istio部署和管理的最佳實踐,前路仍然漫長。在Rancher 2.3 Preview 2中,我們的目標是沿襲Rancher一如既往的理念,讓部署和管理Istio的旅程變得簡單而快速。 2019年6月20日,在Rancher于北京舉辦的千人容器技術盛典“2019企業容器創新大會”上,Rancher大中華區研發經理張浩在演講中分享了Rancher 2.3 Preview的一系列新功能,包括正式支持Windows Kubernetes、鏡像倉庫、鏡像掃描、服務網格、Google登陸、集群模版、集群安全掃描和集群自動擴縮容等等,并且demo了如何在Rancher中使用Istio進行金絲雀發布。您可在Rancher微信公眾號(RancherLabs)后臺回復“ECIC”獲取大會完整PPT下載喔~ 有關發行說明和安裝步驟,請訪問GitHub: https://github.com/rancher/rancher/releases/tag/v2.3.0-alpha5
來源:OSCHINA
發布時間:2019-07-02 10:01:00
Dev Ops源于Development和 Op eration s 的組合 常見的定義 DevOps是一種重視“軟件開發人員(Dev)”和“IT運維技術人員(Ops)”之間溝通合作的文化、運動或慣例。透過自動化“軟件交付”和“架構變更”的流程,來使得構建、測試、發布軟件能夠更加地快捷、頻繁和可靠。 下面這個戴明環也是常見的表達形式: 點擊此處添加圖片說明文字 藍鯨在深度實踐DevOps后,結合對DevOps理解和經驗總結,重新定義了DevOps。即下圖這6個英文單詞的首字母組成: Do 、 Efficiency 、 Value 、 Open 、 Progress 、 Security 。 點擊此處添加圖片說明文字 結合這六個詞、結合藍鯨產品團隊在今年6月藍鯨DevOps活動上的分享以及個人理解,我們將從藍鯨的視角展開來談談DevOps: Do實踐:以實踐為基礎推行DevOps DevOps文化、理論體系的宣導者眾多,各種大會也會去介紹各種“道、法、術”;大一些的企業基本都會有設立教練的角色,指導各個研發團隊開展DevOps轉型。但一些企業用戶在聽完各種“道、法、術”之后,要么是講的聽不懂,要么是懂也不會做、做也做不好。也有企業先找咨詢公司做咨詢,但咨詢完后卻不知道怎么落地。 藍鯨DevOps認為DevOps的第一要素,就是實踐,即所謂的“事上練”。沒有實踐過DevOps的經歷就沒有感悟,談論再多的文化、理論,還不如貼近業務研發痛點,動手行動,用實踐來驗證想法和理論,點滴積累,繪成逐漸強大的DevOps體系。 Efficiency效能:效能是DevOps追求的目標 在我們開展實踐之后,需要有目標。DevOps 根本的目標就是提升研發效能。 首先,效能體現在可以讓大家可以“Focus On Your Job”。開發人員的職責是寫代碼和合并代碼,合并代碼完就去抽煙,其他的交給平臺自動化執行;而不是去推動打包、申請資源、部署、測試、生產上線。 其次,效能體現在可以讓大家在同一套平臺中進行工作和協同,而不是在不同的工具中做不同的事情。一個企業IT部門有18套研發、測試、運維工具,這代表先進還是落后呢?很顯然,這是一種落后的表現,因為這幾乎將無法實現跨系統自動調度。藍鯨DevOps平臺可以將DevOps工具鏈進行整合,讓不同的角色專注于其本職工作,達到提升效能的目標。 Value價值:DevOps必須輸出價值 DevOps要為用戶不斷的輸出價值,就要為DevOps體系中融入更多的提供價值的功能。例如: 在DevOps平臺中加入質量紅線,可以提供給用戶來建立各種質量門禁,如:代碼準入門禁、迭代驗收門禁、發布控制門禁; 在DevOps平臺中提供編譯加速,幫助用戶提升編譯的性能; 在DevOps平臺中提供構建資源池,在構建的時候可以自動調度構建資源,完成構建之后可以自動釋放資源等等; 在DevOps平臺中優化流水線的體驗和原子,用戶可以輕松組裝出來各種復雜的業務場景…… 價值也應該是可以復制的,企業通常有多個團隊同時開展多個項目,我們對某個項目團隊進行了大量DevOps方面的改進,并邀請工信部對項目進行了DevOps能力成熟度評級,我們團隊達到了3級。但是,其他的項目或團隊呢?他們能否達到3級標準?我們在DevOps方面做出的努力,是否可以平行復制到其他團隊? 藍鯨DevOps有一個理念是—— 價值應被平行復制到各個項目團隊 。每一個價值點的輸出,都可以讓用戶真正的感受到DevOps所能帶來的改變,這樣才能把用戶凝聚在平臺上,而不是總是考慮哪里用得不順,自己建立一套平臺。藍鯨DevOps帶來的體系完善、效能提升,不是針對某個團隊,而是可以平行復制到所有的研發團隊,這就是最大的價值。 Open開放:以開放的心態面對各種場景 不同的企業甚至同一家企業的不同團隊,其DevOps落地的進程和對DevOps的要求都有差異的,我們必須用開放的心態接受這種差異。 例如:藍鯨DevOps平臺里面有敏捷協同模塊,可以管理項目的需求、任務、缺陷、迭代計劃等等,但是許多傳統行業,基于企業的研發管控制度等原因,已經建立了適合自己的需求管理平臺、研發任務管理平臺等工具平臺,我們的解決方案是不斷給用戶洗腦讓用戶放棄現有的協同和管理模式,還是以開放的心態來面對客戶現有的管理體系呢? 藍鯨的選擇是以開放的體系面對不同團隊的需求,提供盡可能靈活的架構和工具,通過工具開放的方式來兼容不同團隊的模式。藍鯨本身也是面向CI-CD-CO的研運一體化平臺。 Progress演進:持續交付核心在于不斷演進 DevOps的一個重要理念就在于持續改進。我們可以通過各個子系統的數據進行整體的度量,來發現哪個項目、哪些環節經常出現停滯、失敗率比較高、耗時比較長,并且進行針對性的改進。 例如:如果研發效能瓶頸在測試環節,就需要深究導致測試耗時長的問題。如果是因為沒有引入自動化的測試、手工測試耗時較長,就可以逐步補充自動化測試用例;如果研發效能瓶頸需要人工響應才能推進,就可以引入自動化的流水線和優化研發流程,減少人工參與和不必要的審核節點。 只有通過不斷的改進,企業才能將原來的每月迭代和發布,縮短為每周迭代和發布,甚至逐步改進為每天迭代和發布,最終達到Google、FaceBook等企業達到的1天若干次發布的效果。 各個團隊可以跟自己比,每一階段都相比前一階段有進步,就是團隊的自我發展。而藍鯨DevOps平臺也是不斷演進的成果。 Security安全:安全是基礎 一個企業級的DevOps平臺,安全是非常重要的。研發人員電腦、代碼庫、構建機、測試環境、制品庫都可能導致代碼及軟件包的泄露,這也導致游戲行業大量私服的出現。而軟件上線之后還要考慮漏洞被利用、跨站攻擊、數據竊取等等問題。 不論DevOps平臺本身,還是從平臺流出的制品,一切要以安全為依歸。DevOps平臺本身應該提供監、管、控手段,可以進行細粒度的權限控制,避免非法訪問和非法竊取數據、代碼、軟件包。DevOps平臺也應該提供代碼掃描、安全掃描、質量紅線等安全工具,可以獨立運行或者結合到流水線里面自動調用,保證交付的軟件的可靠性,給平臺使用者以及產出軟件的用戶一個安全保障。 作者:方勇
來源:OSCHINA
發布時間:2019-07-23 18:15:00
本系列將利用阿里云容器服務,幫助您上手 Kubeflow Pipelines. 介紹 機器學習的工程復雜度,除了來自于常見的軟件開發問題外,還和機器學習數據驅動的特點相關。而這就帶來了其工作流程鏈路更長,數據版本失控,實驗難以跟蹤、結果難以重現,模型迭代成本巨大等一系列問題。為了解決這些機器學習固有的問題,很多企業構建了內部機器學習平臺來管理機器學習生命周期,其中最有名的是 Google 的 Tensorflow Extended, Facebook 的 FBLearner Flow, Uber 的 Michelangelo,遺憾的是這些平臺都需要綁定在公司內部的基礎設施之上,無法徹底開源。而這些機器學習平臺的骨架就是機器學習工作流系統,它可以讓數據科學家靈活定義自己的機器學習流水線,重用已有的數據處理和模型訓練能力,進而更好的管理機器學習生命周期。 談到機器學習工作流平臺,Google 的工程經驗非常豐富,它的 TensorFlow Extended 機器學習平臺支撐了 Google 的搜索,翻譯,視頻等核心業務;更重要的是其對機器學習領域工程效率問題的理解深刻,Google 的 Kubeflow 團隊于 2018 年底開源了 Kubeflow Pipelines(KFP),? KFP 的設計與 Google 內部機器學習平臺? TensorFlow Extended ?一脈相承,唯一的區別是 KFP 運行在 Kubernetes 的平臺上,TFX 是運行在 Borg 之上的。 什么是 Kubeflow Pipelines Kubeflow Pipelines 平臺包括: 能夠運行和追蹤實驗的管理控制臺 能夠執行多個機器學習步驟的工作流引擎 (Argo) 用來自定義工作流的 SDK,目前只支持 Python 而 Kubeflow Pipelines 的目標在于: 端到端的任務編排 : 支持編排和組織復雜的機器學習工作流,該工作流可以被直接觸發,定時觸發,也可以由事件觸發,甚至可以實現由數據的變化觸發; 簡單的實驗管理 : 幫助數據科學家嘗試眾多的想法和框架,以及管理各種試驗。并實現從實驗到生產的輕松過渡; 通過組件化方便重用 : 通過重用 Pipelines 和組件快速創建端到端解決方案,無需每次從 0 開始的重新構建。 在阿里云上運行 Kubeflow Pipelines 看到 Kubeflow Piplines 的能力,大家是不是都摩拳擦掌,想一睹為快?但是目前國內想使用 Kubeflow Pipeline 有兩個挑戰: Pipelines 需要通過 Kubeflow 部署;而 Kubeflow 默認組件過多,同時通過 Ksonnet 部署 Kubeflow 也是很復雜的事情; Pipelines 本身和谷歌云平臺有深度耦合,無法運行在其他云平臺上或者裸金屬服務器的環境。 為了方便國內的用戶安裝 Kubeflow Pipelines,阿里云容器服務團隊提供了基于 Kustomize 的 Kubeflow Pipelines 部署方案。和普通的 Kubeflow 基礎服務不同,Kubeflow Pipelines 需要依賴于 mysql 和 minio 這些有狀態服務,也就需要考慮如何持久化和備份數據。在本例子中,我們借助阿里云 SSD 云盤作為數據持久化的方案,分別自動的為 mysql 和 minio 創建 SSD 云盤。
您可以在阿里云上嘗試一下單獨部署最新版本 Kubeflow Pipelines。 前提條件 您需要安裝? kustomize 在 Linux 和 Mac OS 環境,可以執行 opsys=linux # or darwin, or windows curl -s https://api.github.com/repos/kubernetes-sigs/kustomize/releases/latest |\ grep browser_download |\ grep $opsys |\ cut -d '"' -f 4 |\ xargs curl -O -L mv kustomize_*_${opsys}_amd64 /usr/bin/kustomize chmod u+x /usr/bin/kustomize 在 Windows 環境,可以下載? kustomize_2.0.3_windows_amd64.exe 在阿里云容器服務創建 Kubernetes 集群, 可以參考? 文檔 部署過程 通過 ssh 訪問 Kubernetes 集群,具體方式可以參考 文檔 下載源代碼 yum install -y git git clone --recursive https://github.com/aliyunContainerService/kubeflow-aliyun 安全配置 3.1 配置 TLS 證書。如果沒有 TLS 證書,可以通過下列命令生成 yum install -y openssl domain="pipelines.kubeflow.org" openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout kubeflow-aliyun/overlays/ack-auto-clouddisk/tls.key -out kubeflow-aliyun/overlays/ack-auto-clouddisk/tls.crt -subj "/CN=$domain/O=$domain" 如果您有TLS證書,請分別將私鑰和證書保存到 kubeflow-aliyun/overlays/ack-auto-clouddisk/tls.key 和 kubeflow-aliyun/overlays/ack-auto-clouddisk/tls.crt 下 3.2 配置 admin 的登錄密碼 yum install -y httpd-tools htpasswd -c kubeflow-aliyun/overlays/ack-auto-clouddisk/auth admin New password: Re-type new password: Adding password for user admin 首先利用 kustomize 生成部署 yaml cd kubeflow-aliyun/ kustomize build overlays/ack-auto-clouddisk > /tmp/ack-auto-clouddisk.yaml 查看所在的 Kubernetes 集群節點所在的地域和可用區,并且根據其所在節點替換可用區,假設您的集群所在可用區為? cn-hangzhou-g , 可以執行下列命令 sed -i.bak 's/regionid: cn-beijing/regionid: cn-hangzhou/g' \ /tmp/ack-auto-clouddisk.yaml sed -i.bak 's/zoneid: cn-beijing-e/zoneid: cn-hangzhou-g/g' \ /tmp/ack-auto-clouddisk.yaml 建議您檢查一下 /tmp/ack-auto-clouddisk.yaml 修改是否已經設置 將容器鏡像地址由? gcr.io ?替換為? registry.aliyuncs.com sed -i.bak 's/gcr.io/registry.aliyuncs.com/g' \ /tmp/ack-auto-clouddisk.yaml 建議您檢查一下 /tmp/ack-auto-clouddisk.yaml 修改是否已經設置 調整使用磁盤空間大小, 比如需要調整磁盤空間為 200G sed -i.bak 's/storage: 100Gi/storage: 200Gi/g' \ /tmp/ack-auto-clouddisk.yaml 驗證 pipelines 的 yaml 文件 kubectl create --validate=true --dry-run=true -f /tmp/ack-auto-clouddisk.yaml 利用 kubectl 部署 pipelines kubectl create -f /tmp/ack-auto-clouddisk.yaml 查看訪問 pipelines 的方式,我們通過 ingress 暴露 pipelines 服務,在本例子中,訪問 IP 是 112.124.193.271。而 Pipelines 管理控制臺的鏈接是:? https://112.124.193.271/pipeline/ kubectl get ing -n kubeflow NAME HOSTS ADDRESS PORTS AGE ml-pipeline-ui * 112.124.193.271 80, 443 11m 訪問 pipelines 管理控制臺 如果使用自簽發證書,會提示此鏈接非私人鏈接,請點擊顯示詳細信息, 并點擊訪問此網站。 請輸入步驟 2.2 中的用戶名 admin 和設定的密碼。 這時就可以使用 pipelines 管理和運行訓練任務了。 Q&A 為什么這里要使用阿里云的 SSD 云盤? 這是由于阿里云的 SSD 云盤可以設置定期的自動備份,保證 pipelines 中的元數據不會丟失。 如何進行云盤備份? 如果您想備份云盤的內容,可以為云盤? 手動創建快照 ?或者? 為硬盤設置自動快照策略 ?按時自動創建快照。 如何清理 Kubeflow Piplines 部署? 這里的清理工作分為兩個部分: 刪除 Kubeflow Pipelines 的組件 kubectl delete -f /tmp/ack-auto-clouddisk.yaml 通過 釋放云盤 分別釋放 mysql 和 minio 存儲對應的兩個云盤 如何使用現有云盤作為數據庫存儲,而避免自動創建云盤? 請參考 文檔 總結 本文為您初步介紹了 Kubeflow Pipelines 的背景和其所要解決的問題,以及如何在阿里云上通過 Kustomize 快速構建一套服務于機器學習的 Kubeflow Pipelines, 后續我們會分享如何利用 Kubeflow Pipelines 開發一個完整的機器學習流程。
來源:OSCHINA
發布時間:2019-07-23 17:53:00
本文作者:HelloDeveloper 在 7 月 2 號由百度開發者中心、百度開放云聯合舉辦的第 64 期“百度開放云移動游戲和直播技術解讀”沙龍上,來自百度的高級產品經理魯瑋,介紹了百度開放云在移動游戲方面的整體解決方案,并就相關實際案例給出具體講解。 演講者簡介: 魯瑋,百度高級產品經理,2015 年加入百度云計算事業部,現作為百度開放云核心產品“計算與網絡產品線”的產品負責人,領導、推動了云服務器、專屬機、虛擬私有網絡、VPN 服務、專線、彈性 IP 的產品化,實現了百度開放云相關產品從無到有,從有到優的明顯提升。 一、移動游戲行業的發展現狀 首先,魯瑋老師介紹了近年來中國移動游戲發展的現狀,從 2008 年到 2015 年,大概 8 年的時間里,游戲收入從 185 億增長到 1400 億,從各個細分領域里的收入增速和發展趨勢來看,排名前三的分別是手游、端游和頁游。 近幾年來,手游經過了爆發式的增長,2015 年的時候,手游的增長率達到 87.2%,手游已經變成了游戲行業里面最主要的而且是發展速度最快的方向。對于移動發行和移動應用來講,游戲一直都是最主要的垂直模塊,移動游戲也已變成了移動互聯網里面變現最快的領域。 二、手游發展趨勢-向中重度方向發展 手游發展到今天,從剛開始的休閑游戲到卡牌、MMO、MOBA 等,手游也在向中重度方向發展。歸結原因,第一點是技術層面,移動處理器和 GPU 的快速發展會保證游戲跑得更加順暢,重度游戲不會像原來那么卡頓;精細化的 3D 和情景式的游戲,對用戶黏性很大,而且開發難度越來越低,這是手游往中重度發展的原因。另一個是運營層面,不能光靠游戲的長期下載量,要讓用戶沉浸在游戲里的時間更長,而最早出的游戲是偏休閑類的,比如斗地主、連連看,這種相對黏性較低,因為缺少互動模塊,它會很容易流失掉用戶,所以需要提高互動(幫會、聊天等模塊)吸引用戶,來產生更多的黏性。另外一個重要原因,這些玩家會付費購買道具來提升自己的級別,支付意愿較明顯,所以中重度手游變現能力非常強。 三、移動游戲在部署和運行過程中遇到的痛點 游戲作為一個移動行業里面變現最快的行業,部署和運行過程中肯定會遇到很多問題。 首先手游生命周期短,購買物理資源浪費嚴重。手游對于其它 APP 來講生命周期較短,很多游戲的廠商為了支撐游戲峰值業務,購買大量的硬件,而 90% 的情況下不能利用,導致成本和收益不成正比。 第二,流量峰值無法預測,已有 IT 資源無法支撐。很多時候要進行活動大推,或者由于社會熱點(影視劇、網絡小說)等因素導致游戲受到關注,從而引入大量玩家。這種情況下,原來的服務集群的計算性能無法支撐新涌入的玩家,所以搞活動、大推的時候,流量無法很好的預估。原來游戲部署在 IDC 時會選用物理機,但是物理機宕機恢復時間非常長,至少需要 30 分鐘,這種游戲體驗對玩家來講很難接受,它會導致用戶大量流失,而且這種傳統的架構系統也是基于煙囪式的單點上面部署 OS、搭建應用服務,擴展性很差。 另外,中小游戲廠商使用 IDC,本身其防攻擊能力很弱,所以會經常受到攻擊,嚴重影響游戲收入。 四、百度開放云移動游戲解決方案介紹 下面我們從百度開放云游戲客戶的實際案例中,分享不同游戲類型的架構解決方案。 卡牌類型手游 這個是卡牌類游戲的解決方案,通過百度開放云高性能的對象存儲(BOS)和內容分發網絡(CDN)把游戲安裝包推送給玩家,玩家登錄游戲時,經過云安全(BSS)的嚴格審查。另外游戲服運行在高性能云服務器(BCC)上,每個 BCC 配置一個單獨的高性能數據庫(RDS),這種方案大大提高了整個游戲系統的穩定性。通過支付服連接各個游戲渠道,保證了不同渠道引入的玩家購買道具等付費行為,支付數據庫也記錄了交易信息, 方便游戲 CP 和運營商對玩家的支付行為再進行二次分析。 社交類型手游 這個就是偏向于社交類游戲的解決方案,通過游戲加速服務系統將游戲安裝包發送到手機端。玩家經過 BSS 的安全檢測后,接入到登錄服。因為游戲系統相對比較大,會有游戲服的資源管理、版本管理等模塊。游戲大廳,類似于傳統棋牌大廳的形式,每一個游戲服里有多個頻道,每一個頻道有多個房間。這類游戲的周邊系統會把各個頻道里邊的聊天記錄保存,同時因為大家在這里要排名,那排名也會有個專門的服務系統;通過跨區接入功能,也實現了玩家跨區的游戲 PK;另外為了增強游戲的流暢度,系統 DB 之前也會有緩存層。同時所有系統模塊都會由智能云監控(BCM)來監測業務運行狀況,包括通用運維指標、游戲程序等,游戲 CP 根據業務場景自定義設置報警規則,及時發現、處理宕機、業務性能壓力過大的風險。 MOBA類型手游 這個是 MOBA 類型游戲的解決方案,MOBA 類型游戲對實時戰斗的要求極強,但由于玩家位置不同、接入的運營商不同,導致南方和北方玩家訪問質量不一致,游戲體驗很差,所以百度開放云推薦此類游戲在南方和北方分別部署游戲集群。根據用戶的不同位置,通過智能 DNS 接入最佳機房,實現用戶更流暢的游戲體驗;基于百度雄厚的基礎網絡資源,使用專線打通北京和廣州區域機房,實現數據高速的同步和備份;另外游戲 CP 使用百度開放云業界領先的大數據服務,實現對玩家日志信息、玩家支付信息的精細分析,為游戲精細化運營提供寶貴數據。 五、移動游戲客戶案例說明 沙巴克傳奇,是今年盛大游戲重磅推出的 MMORPG 類型的手游,沙巴克傳奇對服務器性能、網絡安全的要求非常高。盛大游戲不僅需要高 PPS 轉發的服務器、數據中心內網之間高速連通,還要求資源獨享、靈活計費和完整的數據分析等能力。百度基于高性能的內核優化技術、自建的高質量 BGP 帶寬、高性能的計算集群系統、超強的大數據服務,滿足了盛大游戲對業務系統的嚴格要求。 在這個游戲里,不管前期測試、大推階段還是后期運行管理,我們做了很多事情。沙巴克傳奇封測階段,我們給了很多支持與建議,包括定向內核優化、高可靠和彈性架構方案的推薦、結合上線節奏分配資源專區等。游戲大推階段,我們提供超高性能的抗 D 服務、網絡優化型云服務器、7*24 小時專人運維、游戲駐場支持、全國網絡質量的實時監控和預測。后期運維階段,我們也會定期的安全巡檢,給出資源的生命周期管理的建議等。百度開放云提供了一整套完美的游戲解決方案,也幫助了沙巴克傳奇實現了平穩的上線和火爆的大推,滿足了沙巴克傳奇游戲對性能、穩定性、安全等方面的高要求。 原文鏈接地址: https://developer.baidu.com/topic/show/290237
來源:OSCHINA
發布時間:2019-07-23 17:24:00
本文作者:HelloDeveloper 在 1 月 16 日,由百度開發中心和 InfoQ 聯合主辦的“縱談前端接入技術、SEO 和安全運維”主題沙龍活動中,來自百度開發者中心的資深運維工程師們熱情洋溢的分享了百度在前端技術、搜索速度優化和全站使用 HTTPS 技術的進展及成果,以及百度在這些方面有哪些寶貴經驗可供參考的。演講嘉賓分別為百度 Golang 委員會成員陶春華、專注于網頁搜索無線訪問速度的工程師許霞,和處理網頁搜索可達性、安全搜索等方向事務的主要技術負責人陳曦洋。 Go 語言在 Baidu Front-End 方面的應用實踐 Go 語言的廣泛流行取決于部署簡單、并發性好、良好的語言設計,以及執行性能好。這也是在重寫百度前端這一項目上為什么考慮選用 Go 語言的原因所在。陶春華老師介紹說,促使重寫 Baidu Front-End 的誘因主要基于以下三點:一是修改成本高。事件驅動的編程模型本身的編碼和調試難度都很大;C 語言本身的難度和開發效率有很多限制。二是配置管理方式落后。為單產品線設計,無法支持平臺化要求;配置變更(修改、重載、驗證)能力差。三是變更和穩定性的矛盾。例如程序出 core 也是比較頭疼的事情。 在此前提之下,團隊決定使用 Go 語言來重寫前端,但是這里也遇到了一些問題,那就是 GC(Gabage Collection)本身難以避免的時間延遲。BFE 的需求是要在 1ms 以內,最大不超過 10ms,一旦超過這個平均值,那么用戶體驗將大打折扣。而 Go-BFE 實測 100 萬連接,400ms GC延遲。這就需要不斷的對 GC 進行優化。 在這里陶老師也介紹了兩種優化思路,第一個常見的方法就是將掃描的小對象合并成大對象,利用 Array 來合并一組對象。第二種方法精算性更高一點,可以把消耗內存較多的內容放到 C 里面,因為 Go 語言有一個 CGO 接口,直接通過 Go 調到 C 可以解決這個問題,只不過代價比較大。但是,問題和方案永遠是相生相伴的。用 Array 技術重寫網絡庫,所有的 BFE 將永遠用 Array 來寫,理論上可行。這里的問題又來了,第一風險太大,第二如果 Go 語言升級了,還能不能繼續使用下去。 陶老師在這里介紹的解決方法叫做車輪大戰。即,在一組工作進程中,進程和服務是等價的,某一個進程跟服務運作到一定時間之后關閉GC,讓它休息,第二個進程代替它服務,以此輪換,構成一個車輪大戰的局面。如果在不能直接解決GC問題的時候直接關掉服務,然后繞過它。這基本的方案思路也就是關閉繼承多進程的輪轉戰。(如上圖) 搜索速度優化的前進之路 在整個百度接入服務里,百度搜索一直秉承提供最基礎的三個保障,那就是安全、快速、可靠。許霞首先介紹說,在對速度進行度量之前,先要對數據檢測、收集。對客戶端數據監測的特點是:可以檢測任何對象,成本高,并且監測的指標很固定。JS 埋點檢測數據的特點是:可以檢測任何指標,甚至可以檢測每一條結果的速度。第三方數據檢測的特點是:可以定制,并且有一定的海外監測能力,但成本高。 收集數據的意義在于可以很清晰的了解掌握用戶的搜索習慣,這對 PV、UV 以及變現收入有很大影響。那么如何貼切搜索引擎的特點做搜索速度的優化?通過三個方面:接入質量提升、后端處理優化和前端渲染優化。接入質量提升主要有兩個考察因素:延遲和帶寬,對應的也就是優化 RTT 和傳輸效率。 后端優化其實就是整個搜索引擎的優化了,分為緩存優化和檢索優化。緩存優化最基礎的方式就是進入、淘汰機制等等,保證淘汰機制是最合理的。檢索優化,則需要對硬件以及硬件方案的選擇做一些深入考慮。在前端渲染優化方面,除了考慮節省時間之外還要考慮怎樣讓它定性化。 對優化做決定性決策只是其中的一種方法,還有更聰明的創新方法,那就是關于無線技術。這里面所涉及的內容包括手機終端、機站以及 IP 網絡,傳輸速度當然是跟這三者有密不可分關系的。機站會根據自己能獲得多少收益來處理用戶的請求,盡量會縮小頭部信息,進行一定程度的數據壓縮。手機跟機站之間建立連接以維持這種連接關系。但電耗大是很關鍵的問題。百度搜索做了維持連接的一些機制,當用戶頁面空閑很長時間或者放在后臺,就可以減少電量的消耗。(如上圖) 全站 HTTPS 能否確保網站徹底安全? 2015 年 3 月,百度搜索成為國內首家完成全站 HTTPS 改造的大型站點;且目前來看,全站 HTTPS 已經成為百度產品的首要標準;同時,統一接入平臺也大幅提升了 HTTPS 的接入效率和性能。陳曦洋老師在開講前是這樣介紹大背景的。全站 HTTPS 的原因是為了讓用戶保持良好的使用體驗,解決反饋較多的劫持和隱私泄露等問題。這些問題的具體 case,包括頁面被加上 URL 參數,不停刷新;頁面被 DNS 劫持到其他網站;用戶手機號碼遭泄漏;白頁,搜索功能異常等等。正是出于對用戶數據的安全保密,維護網站正常運作的考量,百度專門成立了由百度搜索和運維部組成的 HTTPS-SUPPORT 團隊,對 HTTPS 進行深入研究,提供完整的服務,保障用戶正常訪問百度原始產品。 陳曦洋老師在這里詳細介紹了全站 HTTPS 改造的成本,這也是很多人都比較關心的焦點問題,這不僅涉及到證書的部署,還會涉及到激增的計算量,需要多次協商和握手,而用戶端搜索的延遲將會給 HTTPS 改造需要解決的問題。除此以外,對于一個大型網站而言,架構如何解決多業務部署HTTPS的問題,巨大的頁面和模板數量,以及如何解決實際部署中的各種問題,讓用戶無損 / 平滑的完成切換,其實是更具有挑戰性的工作。 計算性能涉及到密鑰(證書)的長度,1024 和 2048 位在性能有什么差別呢?原來使用 HTTP 協議的時候,假設 cps 可以達到 2w+,而轉換成 HTTPS 之后,cps 只能達到 2-3 千;在訪問速度方面,使用了 HTTPS 之后,不做任何優化,訪問百度的速度可能會惡化 250-500ms, 一些設計比較差的頁面可能會惡化 500-1200ms;在架構和產品成本方面,對于百度這樣的綜合性網站,牽一發而動全身,僅僅是在頁面形式上就要改大量的模板,成本相當大。 那么有沒有可選的優化方案呢?陳老師認為,性能優化上優先使用 ECC。這里使用 ECC 密鑰長度大小要比 RSA 和 DH 密鑰長度短。在硬件的優化上則可以使用硬件加速卡,可以做 TLS 的遠程卸載 (小型站點在不面對大量的惡意請求時 完全可以通過純軟件卸載, 只需要保證連接復用率)。在訪問速度上的優化上,通過復用連接和協議優化可以盡量減少握手次數,就可以讓它接近于原始 HTTP 的性能。怎么去減少握手次數?比如 Session cache 和 Session ticket 可以極大的減少用戶在一定時間內再次訪問時的計算開銷,而 HSTS 能在瀏覽器內部完成 HTTP 到 HTTPS 的跳轉,不再經過一次網絡傳輸和瀏覽器開銷。另外還可以用 SPDY-HTTP2 方案,優點是基于單連接,能進一步提升連接復用比例,協議支持 header 壓縮,在無線網絡下有重要意義,這些都可以提高訪問速度。 除了對協議層進行優化之外,也可以在應用層做些優化,預連接就是一個很好的優化方案。比如在網頁端或者客戶端,用戶發起訪問請求之前提前把這個握手過程完成,減少延遲,這一點也很重要。另外陳老師建議站點在發展到一定規模時一定要做整體的接入規劃,控制域名數量,一些服務需要變成公共的,比如圖片,靜態資源的存儲和訪問。 在最后,陳老師也回答了大家普遍比較關心的問題,那就是使用 HTTPS 就代表著絕對安全嗎?事實上并沒有絕對地安全,代碼是人寫的,很多問題都是實際的實現上或者依賴的其他環境上出現了漏洞,OpenSSL HeartBlood 就是最典型的案例,甚至連隨機數的生成和一些加密算法上也可能有人為埋下的漏洞,CDN 回源這樣的路徑很多情況下也是使用的 HTTP。百度使用 HTTPS 只能保護用戶在瀏覽百度產品的時候的安全,但是很多手機號的泄露是第三方站點導致的 (它們會通過非法渠道購買識別用戶手機號的服務),這個問題并不能通過百度的 HTTPS 解決。但是相對于 HTTP,HTTPS 的安全防范性能更高,增加了壞人的做惡難度。 原文鏈接地址: https://developer.baidu.com/topic/show/290236
來源:OSCHINA
發布時間:2019-07-23 17:20:00
本文作者:y****n 自然語言處理(NLP)素有“人工智能皇冠上的明珠”盛譽,這也意味著語言與知識等認知層面的技術突破將進一步促進AI深入發展。 8月25日,以“掌握知識、理解語言、擁有智能” 為主題的百度大腦語言與知識技術峰會舉行,百度CTO王海峰發表主旨演講,解讀百度語言與知識技術的發展歷程與最新成果,與產學研各界分享技術及產業發展趨勢和展望,百度集團副總裁吳甜和百度技術委員會主席吳華分別發布百度語言與知識技術系列產品和數據集共建計劃,重磅推出5款產品 的新發布,全面加速AI技術大規模應用。這是一場凝聚了百度在語言與知識領域十年技術積累和產業實踐的盛會,必將帶來深遠影響。 十年: 開拓者、深耕者、引領者 語言與知識技術是人工智能認知能力的核心。2010年,百度成立自然語言處理部,在前瞻技術與產業格局上不斷引領、創新,十年間已成為中國NLP發展的一面旗幟。 峰會上,王海峰回顧,“在百度語言與知識技術的布局和發展中,我們始終在注意把握兩個趨勢,即技術發展趨勢和產業發展趨勢 ,并力爭引領趨勢?!笨v覽百度語言與知識技術發展歷程, 從研究方法、研究對象、研究方向、產業應用 等各個層面,布局完整,不斷打磨成熟,始終與應用的發展趨勢、需求一脈相承,與產業接軌。 十年來,百度大腦語言與知識技術成果豐碩,獲得包括國家科技進步獎在內的20多個獎項,30多項國際競賽冠軍,發表學術論文超過300篇,申請專利2000多項 。技術不斷突破創新的同時,也在產品上創新探索,同時將領先的技術輸出給開發者與合作伙伴,提升各行業智能化水平。 全面分享語言與知識技術成果 王海峰全面分享了百度語言與知識技術完整布局和最新成果。首先,知識圖譜是機器認知世界的重要基礎,百度打造了世界上最大規模知識圖譜, 擁有超過50億實體和5500億事實 ,并在不斷演進和更新。百度知識圖譜應用于各行各業,每天的調用次數超過400億次 。 其次,在融入知識的基礎上,語言理解能力不斷增強。2019年3月,百度提出知識增強的語義理解框架ERNIE,在深度學習的基礎上融入知識,同時具備持續學習能力,曾一舉登頂全球權威數據集GLUE榜單,首次突破90分大關,刷新榜單歷史 ?;谥R圖譜和語義表示,突破了閱讀理解、對話理解以及跨模態深度語義理解等技術。第三,語言生成是語言與知識技術中的重要組成部分?;陬A訓練技術的成功經驗,百度提出基于多流機制的語言生成預訓練技術,兼顧詞、短語等不同粒度的語義信息,顯著提升生成效果。百度也探索了多文檔摘要生成,通過圖結構語義表示引入篇章知識,在單文檔和多文檔摘要生成效果都有提升。 應用系統層面,對話系統和機器翻譯等成績卓著。 百度提出了知識圖譜驅動的對話控制技術,以及首個基于隱空間的大規模開放域對話模型PLATO等 ,并推出智能對話定制和服務平臺UNIT,幫助開發者高效構建智能對話系統,實現規?;瘧?。百度翻譯支持200多種語言,每天響應超過千億字符的翻譯請求,支持超過40多萬家第三方應用 ,技術上,提出了多智能體聯合學習、基于語義單元的同傳模型、稀缺語種分組混合訓練算法等。 百度大腦語言與知識技術的持續探索和創新取得了令業界矚目的成績,同時這些技術以平臺化的方式輸出,賦能千行萬業,持續提升產業智能化水平。 重磅推出5款產品的 新發布、2大計劃 王海峰首次發布了百度大腦語言與知識產品全景圖。百度集團副總裁吳甜接續發布語義理解技術與平臺文心、智能文檔分析平臺TextMind和AI同傳會議解決方案 3大新產品,同時發布了6項升級,包括智能創作平臺的3個場景方案、以及智能對話定制與服務平臺UNIT的3項全新升級。吳甜表示,“我們一直致力于將語言與知識技術凝聚成一系列技術平臺和產品,在應用中產生大量價值,為廣大開發者和產業實踐者提供以語言與知識技術為核心驅動的系列產品?!?百度推出的 語義理解技術與平臺文心 ,基于深度學習平臺飛槳打造,依托領先的語義理解核心技術,集成優秀的預訓練模型、全面的NLP算法集、端到端開發套件和平臺,提供一站式NLP開發與服務,讓開發者更簡單、高效地定制企業級NLP模型。文心經過了大量真實應用場景的淬煉,具備優秀的工業級落地實力。 全新發布的智能文檔分析平臺TextMind ,基于OCR、NLP技術,以文檔解析為核心能力,支持文檔對比與文檔審核,具備“多快好省”的核心優勢,促進企業辦公智能升級。 百度大腦智能創作平臺針對媒體應用場景再升級,全新推出智能策劃、智能采編、智能審校三大媒體場景方案,進一步助力媒體人更快、更好地創作,可謂切中媒體人的“痛點”。 智能對話定制與服務平臺UNIT 升級3大特性:更智能的任務式對話理解、極致便捷的表格問答和融合通用的新對話引擎。此次UNIT全新升級的三大能力,將進一步降低任務式對話、智能問答的定制成本,并融合通用對話能力,提升交互體驗。 全新發布的AI同傳會議解決方案 ,覆蓋會議全場景、全流程,旨在打造用戶隨身的“會議同傳專家”。吳甜現場展示了如何只用一臺電腦和一部手機快速搭建一套同傳服務,只需點點鼠標、打幾個字,就能快速獲得專業的同傳服務。 據匱乏、算力不足歷來是語言與知識技術研發中面臨的瓶頸。為突破瓶頸,百度聯合中國計算機學會、中國中文信息學會發起中文自然語言處理數據共建計劃——千言 ,解決數據稀缺問題。千言一期由來自國內11家高校和企業的數據資源研發者共同建設,已涵蓋開放域對話、閱讀理解等7大任務,20余個中文開源數據集。百度技術委員會主席吳華表示,“未來,我們希望有更多的數據集作者能夠參與共建千言,共同推動中文信息處理技術的進步,建設世界范圍的中文信息處理影響力。 我們計劃在未來3年,面向20多個任務,收集和建設不少于100個中文自然語言處理數據集,覆蓋語言與知識技術全部領域?!?吳華還發布了百度語言與知識技術算力共享計劃,通過百度AI Studio平臺提供算力支持,讓廣大開發者破除算力桎梏,專注于技術創新。 十年征程,百度語言與知識技術發展歷程中培養、吸引了大量全球頂尖人才。會上,百度推出以王海峰為代表的百度NLP“十年十人”,十年堅守,不忘初心,秉持“技術信仰”,勇攀技術高峰,矢志不渝致力于讓機器更好地理解世界、更好地服務于人。 正如王海峰所言,“我們致力于更好地與學術界、產業界攜手,推動語言與知識技術發展,進而推動人工智能技術持續進步,為產業智能升級、社會經濟高質量發展貢獻力量。我們對未來充滿信心,堅持研究和發展讓機器掌握知識、理解語言、擁有智能,繼續突破和創新,為技術和社會進步做出更大貢獻?!? 原文鏈接地址: https://developer.baidu.com/topic/show/291190
來源:OSCHINA
發布時間:2020-08-26 10:22:00
本文作者:y****n 要說生活里最常見、最便民的AI應用技術,OCR(Optical Character Recognition,光學字符識別)當屬其中之一。尋常到日常辦理各種業務時的身份證識別,前沿到自動駕駛車輛的路牌識別,都少不了它的加持。作為一名開發者,各種OCR相關的需求自然也少不了:卡證識別、票據識別、汽車場景、教育場景文字識別…… OCR領域向來開源repo比較少,大部分核心算法用在了商業化產品。今年算是OCR開源領域的豐收年,chineseocr_lite,easyocr,以及百度飛槳推出的PaddleOCR先后橫空出世。 確實喜大普奔 對于OCR方向開發者而言,開源repo最吸引人的莫過于: ① 高質量的預訓練模型 ② 簡單易上手的訓練代碼 ③ 好用無坑的部署能力 簡單對比一下目前主流OCR方向開源repo的核心能力對于 語種方面 ,easyOCR的優勢在于多語言支持,非常適合有小語種需求的開發者; 從預訓練模型 來看,easyOCR目前暫無超輕量模型,chineseocr_lite最新的模型是10M左右,而PaddleOCR提供的8.6M是目前業界已知最輕量的 ; 對于部署方面 ,easyOCR模型較大不適合端側部署,Chineseocr_lite和PaddleOCR都具備端側部署能力; 對于自定義訓練 ,實際業務場景中,預訓練模型往往不能滿足需求,對于自定義訓練和模型Finetuning,目前只有PaddleOCR支持 ; PaddleOCR項目地址: https://github.com/PaddlePaddle/PaddleOCR PaddleOCR 8.6M超輕量模型,支持自定義訓練、豐富的部署方式(覆蓋服務器端、移動端/嵌入式端(apk/sdk)多場景需求)。提供的超級開源開發者大禮包,無疑讓開發者大呼過癮 ,看一下repo中提供的教程文檔,真心全覆蓋。高質量的內容也換來了開發者的廣泛認可,GitHub Trending 第一,Star數量已經超過2.5k。 這僅僅是開始。。。 隨著大量用戶涌入,也實實在在提出了很多業務場景常見的問題,比如: 能否解決自然場景任意形狀文本檢測問題? 文字識別結果能否通過語義信息自動糾正? 既然開發者有需求,百度飛槳也是誠意滿滿! 百度自研SAST、SRN 兩大SOTA算法開源啦! 核心信息先睹為快: 面向自然場景任意形狀文字檢測問題,開源ACM Multimedia 2019上發表的SAST(A Single-Shot Arbitrarily-Shaped Text Detector based on,Context Attended Multi-Task Learning)算法,在多個公開數據集(包括SCUT-CTW1500,Total-Text,ICDAR15 和 MLT),準確度取得了SOTA或可比的結果,速度上位列領先行列。 面向場景文本圖像中兼顧視覺信息和語義信息的需求,開源CVPR2020中發表的SRN(Towards Accurate Scene Text Recognition with Semantic Reasoning Networks )算法,在包括ICDAR13、ICDAR15,IIIT5K,SVT,SVTP,CUTE80數據集,準確度取得了SOTA或可比的結果。 開源算法詳細解讀 01 單階段任意形態文字檢測器-SAST(ACM MM2019) 論文地址:https://arxiv.org/abs/1908.05498第一列為語義分割圖,黃色框標記的為較長文字分割響應斷裂的情況;第二列為SAST利用Pixel-to-Quad思想對實例分割的處理結果,相同顏色為同一個文字實例;第三列紅色框為最終檢測結果,藍色為真值,青色為EAST算法檢測結果。 目前業界解決任意形態文字檢測問題的主流思路有兩種: 一種基于Mask-RCNN思想的自頂向下的檢測方法,例如LOMO、PMTD等; 另外一種是基于語義分割的自底向上的檢測方法,例如TextField、TextMontain 等。 自頂而下的方法往往由于算法級聯的模塊比較多、特征抽取比較復雜導致實際使用效率沒法得到很好保證;語義分割的方法由于缺乏實例(instance)的先驗,在面臨距離較近的文字條難以分割開、過長的文本條則容易出現響應不連續等問題。 SAST (“A Single-Shot Arbitrarily-Shaped Text Detector based on Context Attended Multi-Task Learning”) 收錄于ACM Multimedia 2019,是百度研發的面向自然場景任意形狀文字檢測問題,兼顧效率和準確率的解決方案。 SAST使用多任務學習對文字中心線區域進行語分割的同時學習了文字實例的多種幾何信息,進而實現文字的實例分割和多邊形表達的重建。該方法的整體算法流程,如圖1所示: Ⅰ 首先,通過多任務學習方法學習文字實例的多種幾何信息,包括文字條中心線響應圖 (Text Center Line, TCL),中心線像素與四角點偏移量 (Text Vertex Offset, TVO),中心線像素與文字中心點偏移量 (Text Center Offset, TCO) 和中心線到文字上下邊界偏移量 (Text Border Offset, TBO); Ⅱ 其次,使用Pixel-to-Quad方法對TCL進行實例分割,該方法結合了高層檢測信息和底層分割信息,具體過程如圖2虛線框中所示。 Ⅲ 最后,在實例分割的基礎上,針對每個文字實例結合TBO信息,即可恢復出任意形狀文字的幾何表達。 論文中對SAST在多個公開數據集上進行了效果驗證,包括SCUT-CTW1500,Total-Text,ICDAR15 和 MLT數據集,準確度上取得了SOTA或可比的結果,速度上位列領先行列。此次SAST開源工作中,通過對模型的Backbone和訓練數據做了適當的升級和調整, 在icdar2015數據集上hmean達到87.33%,在彎曲文本數據集total-text上hmean達到84.03%。 02 利用語義推理網絡強化的場景文字識別器-SRN(CVPR2020) 論文地址:https://arxiv.org/abs/2003.12294(a)是較難識別的場景文本圖像,(b)是從(a)中圖像里分離抽取出的字符圖像,(c)是(a)中圖像對應的語義內容。 場景文本圖像包含兩種層次的內容:視覺信息和語義信息。近年來致力于提高識別性能的工作,大多從提取魯棒且有效的視覺特征的視角出發,例如升級模型Backbone,增加矯正模塊,校準Attention機制等,往往忽視了改進語義特征。場景文字識別不單依賴視覺感知信息,也依賴高層次的語義信息的認知理解,例如圖3所示 ,僅依靠視覺信息是很難準確識別分離抽取出的字符圖像,尤其是(b)中紅框標出的字符;相反地,結合整個單詞完整語義信息進行推理的時候,我們是可以很容易識別出完整單詞內容的。 為了解決如下主流Attention-based隱含式語義信息建模的方法缺陷: 1)僅僅感知了歷史時刻的語義信息,而無法獲取其他時刻的語義信息; 2)如果較早時刻解碼出的錯誤字符,會為余下時刻的解碼傳遞錯誤的語義信息,導致誤差積累效應; 3)串行的解碼模式是相對低效的(特別是在模型預測的環節) 百度在CVPR 2020錄用工作SRN(Towards Accurate Scene Text Recognition with Semantic Reasoning Networks )中借鑒Transformer和NLP預訓練模型思想提出了一種高效的全局并行語義推理模塊GSRM,其信息傳遞方式采用如圖 4(b)所示方式。 SRN是端到端可訓練的場景文字識別網絡,由四部分組成:基礎網絡Backbone、并行的視覺特診提取模塊(PVAM)、全局語義推理模塊(GSRM) 和視覺語義融合的解碼器(VSFD)。給定一張輸入的文本圖像,基于ResNet50 + Transformer unit的Backbone從中提取出視覺2D feature map V ;之后PVAM會針對每個目標字符獲取其相應的視覺特征G ;GSRM會基于視覺特征G 獲取全局語義信息,并轉化為每個目標字符的語義特征S ;最后VSFD融合對齊的視覺特征和語義特征,預測出相應字符。在訓練階段和推斷階段,每個序列中各個字符之間是并行。 SRN在多個公開數據集上進行了效果驗證,包括ICDAR13、ICDAR15,IIIT5K,SVT,SVTP,CUTE80數據集,在準確度上取得了SOTA或者可比的結果 。同時,也在中文長詞數據集合TRW上與主流方法做了精度對比,證明了該方法對于中文的適用性。圖 6中展示了語義推理模塊的使用與否在中英文上的可視化對比效果。加了GSRM模塊后,能將一些不符合語義邏輯的單字識別結果進行糾錯。 參考DTRB文字識別訓練和評估流程,使用MJSynth和SynthText兩個文字識別數據集訓練,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE數據集上進行評估,算法效果如下:可以看到SRN檢測算法指標明顯優于其它算法,效果提升明顯。而且值得一提的是,此次PaddleOCR開源SRN工作中,對訓練方法做了適當的優化,采用一步到位端到端的訓練方式,原始論文使用兩階段訓練平均精度為89.74%,PaddleOCR中使用one-stage訓練,平均精度為88.33%。精度雖然略有下降,但是訓練效率和實用性明顯增加。 開源數據集建設 除了開源自研算法,百度也一直致力于推動開源數據集的建設。ICDAR“Robust Reading Competitions”競賽是評估自然場景/網絡圖片/復雜視頻文本提取與智能識別新技術進展的權威國際賽事及評測標準,競賽中涌現出諸多方法持續推動業界新技術的創新與應用。 作為ICDAR 2019 Robust Reading Competition競賽主要組織者之一,百度聯合學術界共同發布了兩項極具挑戰的競賽任務, ICDAR 2019-LSVT ( L arge-scale S treet V iew T ext with Partial Labeling, 弱標注大規模街景文字識別競賽)、 ICDAR 2019-ArT ( Ar bitrary-Shaped T ext, 任意形狀場景文字識別競賽)。 ICDAR 2019-LSVT競賽數據聚焦探索大規模數據場景下深度學習文字識別能力極限,是業界最大的中文場景OCR集合。ICDAR2019-LSVT數據集源于百度真實應用場景,作為首個提出弱標注數據的場景文字數據集,包括精標注5萬張街景圖像,40萬張弱標注圖像,總計45萬,數據量是現有公開數據集(ICDAR 2017、ICPR 2018等)的14倍以上。場景文字識別具有廣泛應用場景,例如:拍照翻譯、圖像檢索、街景地標識別、室外場景理解等。 ICDAR2019-ArT競賽數據總計10176張,是業界最大的任意形狀場景文字集合,聚焦推動自然場景下任意形狀文字檢測識別能力新突破。 PaddleOCR開源的超輕量和通用版中英文模型,訓練數據組成中的中文真實數據集,主要就是上述開源的LSVT數據集,此外,本次SAST算法開源模型total-text指標超過論文指標約4%,主要原因也是由于加入了ArT數據集進行了優化。以上數據集也已經在PaddleOCR中開源了,歡迎使用: https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/datasets.md 原文鏈接地址: https://developer.baidu.com/topic/show/291177
來源:OSCHINA
發布時間:2020-08-25 09:20:00
本文作者:y****n 8月21日,百度在滄州開放Apollo Go自動駕駛出租車服務,滄州市民通過百度地圖,即可一鍵呼叫免費搭乘體驗,滄州由此成為中國第一個可以在主城區打到Robotaxi的城市。這也是繼今年4月在長沙全面開放之后, Apollo在第二個城市上線常態化的打車服務,這意味著Apollo正在加速規?;渴?,百度無人車服務Apollo Go邁入多地運營的全新階段。 百度此次在滄州開啟的Apollo Go Robotaxi服務,由Apollo和生態伙伴云圖科技聯合運營。 行車路線將覆蓋高鐵站、學校、星級酒店、博物館、產業園等滄州核心區域。值得一提的是,Apollo 首次嘗試將Robotaxi服務深入到高鐵站、星級酒店等主城公共區域之間的網約車通行,“駛進”更多普通用戶的主流生活場景。 滄州是京津冀城市群和京滬大通道上的重要節點城市,近幾年,滄州大力發展智能網聯技術,在自動駕駛落地應用方面,已經走在了國內城市的前列。滄州是中國北方第一個開展自動駕駛載人測試的城市,擁有全國第二大智能網聯測試路網。 2019年9月,滄州與百度達成自動駕駛、智能交通戰略合作。2019年11月,中國首個區級全域自動駕駛可載人測試路網在滄州開放,百度Apollo自動駕駛車隊在當地的載人測試也隨之開啟,而此次Apollo Go Robotaxi在滄州主城區開放,更將加速滄州成為北方的智能網聯創新高地。 百度Apollo是全球最大的自動駕駛開放平臺,截止目前, 百度Apollo擁有自動駕駛路測牌照數總計超過150張、測試車隊規模達到500輛級別、獲得全球智能駕駛專利1800件、測試里程總計超過600萬公里、實現安全載客超過10萬人次。 2019年,百度Apollo與一汽紅旗合作的前裝量產的Robotaxi亮相長沙開啟試運營,今年4月,百度Robotaxi向長沙市民全面開放試乘服務。目前,除了在長沙、滄州外,Apollo也已經在北京、重慶、陽泉等地開展自動駕駛載人測試。 Apollo Go Robotaxi在長沙、滄州多地開啟載客試運營,是百度落地自動駕駛技術又一新的里程碑。 基于真實場景的載客運營,將加快自動駕駛的技術迭代和商業探索,加速Apollo實現惠及人人的簡單、安全、美好的出行體驗。 原文鏈接地址: https://developer.baidu.com/topic/show/291172
來源:OSCHINA
發布時間:2020-08-24 16:00:00
作者 | 易立? 阿里巴巴資深技術專家 導讀 :WebAssembly 技術已經走出瀏覽器,讓計算無處不在。本文利用 containerd 的擴展機制,可以為 WebAssembly 應用提供與其他容器應用一致的、抽象的、應用分發、交付和運維模型,可以在 Kubernetes 集群中進行統一調度和管理。 無處不在的 WebAssembly 如果評選 2019 年編程技術的“網紅”,無論是前端圈還是后端圈,WebAssembly?(WASM) 都絕對能夠高票入選。然而,如果評選最被“低估”的技術,我覺得?WebAssembly 也可以輕松入圍。 借用伏爾泰曾評價神圣羅馬帝國的句式 “既不神圣,也不羅馬,更非帝國”,我們也可以說WebAssembly “既不限于 Web,更不是 Assembly(匯編語言)”。 在 2019 年 12 月,萬維網聯盟 (World Wide Web Consortium? - W3C) 宣布 ?WebAssembly 核心規范正式成為 Web 標準 ,? 這使得 WebAssembly 成為互聯網上與 HTML, CSS, and JavaScript 并列的第四種官方語言,可以原生的運行在瀏覽器上。而更加重要的是,WebAssembly 作為一個安全的、可移植、高效率的虛擬機沙箱,可以在 Internet 的任何地方、任何平臺(不同操作系統,不同 CPU 體系架構下)安全運行應用。WebAssembly 已得到了所有主流瀏覽器廠商的廣泛支持(Google Chrome, Microsoft Edge, Apple Safari, Mozilla Firefox 等),然而它的影響已經遠超 Web。 WebAssembly 的設計初衷之一是為了解決 JavaScript 的性能問題,使得 Web 網頁應用有接近本機原生應用的性能。作為一個通用、開放、高效的底層虛擬機抽象,眾多編程語言(如 C/C++, Rust 等)可以將現有應用編譯成為 WASM 的目標代碼,運行在瀏覽器中 。這讓應用開發技術與運行時技術解耦,極大促進了代碼復用。 Mozilla 在 2019 年 3 月推出了 WebAssembly System Interface(WASI),來標準化 WebAssembly 應用與系統資源的交互抽象,比如文件系統訪問,內存管理,網絡連接等,類似 POSIX 這樣的標準 API。WASI 規范大大拓展了 WASM 應用的場景,可以讓其可以超越瀏覽器環境,作為一個獨立的虛擬機運行各種類型的應用。同時,平臺開發商可以針對具體的操作系統和運行環境提供 WASI 接口不同的實現,可以在不同設備和操作系統上運行跨平臺的 WebAssembly 應用。這可以讓應用執行與具體平臺環境實現解耦。這一切使得“Build Once, Run Anywhere”的理想逐漸形成現實。WASI 的示意圖如下所示。2019 年 11 月,為了進一步推動模塊化 WebAssembly 生態系統,Mozilla、Fastly、英特爾和紅帽公司攜手成立了字節碼聯盟( Bytecode Alliance ),共同領導 WASI 標準、 WebAssembly 運行時、語言工具等工作。 圖片來源: https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/ WASM 與容器相愛相殺 WebAssembly 是否會取代容器? 正因為 WebAssembly 所具備的的安全、可移植、高效率,輕量化的特點,非常適于應用安全沙箱場景。WASM 得到了容器、函數計算、IoT / 邊緣計算等社區的廣泛關注。Docker 創始人 Solomon Hykes 在 WASI 發布之際的一句 Twitter,更是成為了去年容器和 WebAssembly 社區引用頻率最高的一句話之一。 Fastly, Cloudflare 等 CDN 廠商基于 WebAssembly 技術實現了更加輕量化的應用安全沙箱,可以在一個進程內部運行多個獨立的用戶應用。阿里云 CDN 團隊 EdgeRoutine 也實現了類似技術。與容器技術相比,WASM 可以實現毫秒級冷啟動時間和極低的資源消耗。 原圖: https://blog.cloudflare.com/cloud-computing-without-containers/ 當然,世界上沒有完美的技術。任何沙箱技術不可能同時滿足執行效率、安全隔離性和通用性這三個維度的要求。WASM 在安全隔離和通用性等方面與 Docker Container 等存在差距。雖然如此,我們還是看到了 WebAssembly 技術巨大的潛力。 WebAssembly 容器 我的理解是 WebAssmebly 可以成為一種容器類型,類似 Linux Container 或者 Windows Container 一樣。成為一個跨平臺的標準應用分發方式和運行時環境。 應用分發 Docker 容器的一個重要貢獻是其標準化了容器化應用打包規范 Docker Image,而且它已經成為開放容器計劃 (Open Container Initiative - OCI) 的鏡像格式標準。Docker 鏡像提供了自包含、自描述的鏡像格式。它可以將應用以及其依賴的環境信息打包在一起,從而實現應用與運行環境解耦,讓容器應用可以輕松運行在從本地開發環境到云端生產環境的不同場景中。并且社區圍繞 Docker 鏡像構建了繁榮的工具鏈生態,如 Docker Hub 可以進行應用分發和 CI / CD 協同,Nortary / TUF 項目可以保障應用可信地分發、交付。 對與 WebAssembly,目前社區提供了類似 NPM 的包管理實現 WAPM ,可以較好地支持應用的分發。 為 WebAssembly 應用構建 Docker 鏡像,可以實現雙贏的局面。 WebAssembly 開發者可以完全復用 Docker/OCI 鏡像規范和工具鏈,進一步簡化應用分發和交付。比如,我們可以將 Nginx 的 WASM 鏡像作為基礎鏡像,基于這個鏡像可以構建包含不同 Web 內容的應用鏡像;我們可以利用 tag 對應用版本進行追蹤;利用 Docker Registry 進行應用分發;在這個過程我們還可以進一步利用數字簽名來保障安全的軟件供應鏈; Docker 鏡像規范支持? Multi-Arch ?鏡像,可以簡化不同 CPU 體系架構(如 x86, ARM, RISC-V 等)的應用鏡像的構建與分發。而 WebAssembly 天生具備可移植性,大大簡化了跨平臺 Docker 應用鏡像的構建和分發。參考: 利用 Docker 加速 ARM 容器應用開發和測試流程 。 我提供了一個技術原型 示例項目 ,大家可以參考其中的例子來構建 WASM 容器鏡像。由于 WebAssembly 應用采用緊湊的二進制格式,而且沒有任何操作系統依賴,WASM 應用可以構建出非常小的容器鏡像。大家可以自行感受一下: $ sudo ctr image ls REF TYPE DIGEST SIZE PLATFORMS LABELS docker.io/denverdino/c-http-server-wasm:latest application/vnd.docker.distribution.manifest.v2+json sha256:2efa759f46f901cda2e6a9b4228c423b17a960c06e957964e72c21dc5b42408f 29.2 KiB linux/amd64 - docker.io/denverdino/hellowasm:latest application/vnd.docker.distribution.manifest.v2+json sha256:cadcc8b07eb82b18db2c8f500fa2b11e5ebf2e9054cfa687e4ffe44861860132 8.2 KiB linux/amd64 - docker.io/denverdino/nginxwasm:latest application/vnd.docker.distribution.manifest.v2+json sha256:8735c82524a463b842b7c79f2c1be8094ee1c57cfd34154f68752fbe79c25998 582.7 KiB linux/amd64 - 安全隔離 WebAssembly 的最初設計目標是讓應用可以安全運行在瀏覽器中。WASM 虛擬機提供的 沙箱和內存隔離機制 ,可以有效減少安全攻擊面。而當 WebAssembly 走出瀏覽器,面向更加通用的場景。WASM 也面對更加復雜的安全挑戰。 WASI 提供了 基于能力的安全模型 。WASI 應用遵循最小權限原則,應用只能訪問其執行所需的確切資源。傳統上,如果應用需要打開文件,它會帶路徑名字符串調用系統操作 open。然后系統調用會檢查應用是否具有訪問該文件的相關權限,比如 Linux 實現了基于用戶/組的權限模型。這樣隱式的安全模型,依賴于正確的安全管理配置,比如一旦特權用戶執行了一個惡意應用,它就可以訪問系統中任意的資源。而對于 WASI 應用而言,如果它需要需要訪問指定文件等系統資源,需要從外部顯式傳入加有權限的文件描述符引用,而不能訪問任何其他未授權資源。這中依賴注入的方式可以避免傳統安全模型的潛在風險。 一個示意圖如下: 原圖: https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/ 我們可以看到 WASI 的安全模型與傳統操作系統安全模型非常不同,而且還在持續演進中。比如字節碼聯盟提出了 nanoprocess 來解決應用模塊間的安全協同和信任傳遞。 WebAssembly/WASI 的安全模型依然存在不足,比如: 資源隔離 對于內存資源,WebAssembly 實現了線性內存模型。WebAssembly 應用只能利用索引訪問傳入的一段邏輯線性內存。而 WASM 虛擬機負責確定內存的實際物理地址,WASM 應用無法獲知內存的真實地址,也無法通過越界訪問等方式發動攻擊。所以理論上,可以對 WASM 應用進行資源容量限制。但是目前部分 WASM 虛擬機還無法對內存進行精確的隔離限制。 對于 CPU 資源,部分的 WASM 虛擬機實現可以對應用使用的 CPU 資源進行計量,但是大多無法實現精確的配額限制、優先級和搶占式調度。 I/O 資源,比如 IOPS 等,WASM 目前完全沒有相關的隔離能力。 網絡安全 WASI 的 Capability 模型對于文件系統訪問相對比較容易保護。但是這個靜態的安全模型無法適用于動態的網絡應用場景。在微服務架構中,應用經常通過 Service Registry 進行服務發現,為服務的調用者和提供者實現動態的調用綁定。這個語義是無法用靜態的 capability 模型描述和注入的。這也導致了 WASI 的網絡部分 API 還處于討論之中?,F有的? WASI 網絡安全模型 ,以及相關 討論 。 Linux 操作系統和容器技術已經提供了非常完備的資源隔離和安全隔離實現。與 WebAssembly 結合在一起可以應對不同場景對不同隔離級別的需求。 共享進程資源 - 多個 WASM 應用模塊運行在一個 WASM 虛擬機進程內部,依賴 WASM 運行時進行隔離。隔離級別低,控制粒度比較粗,資源開銷極小??梢砸暂^小代價保障系統安全。適合受限問題域的應用安全隔離; 獨立進程資源?- 不同 WASM 應用模塊運行在不同的 WASM 虛擬機進程中,可以復用操作系統的進程級隔離能力,比如 CGroup。此外,還可以利用類似 Kubernetes 中的 Network Policy (網絡策略),或者服務網格(如Istio)等技術,對進程的網絡訪問進行細粒度的控制,甚至實現零信任網絡。隔離級別比較高,控制粒度比較細,資源開銷適中??梢詰糜诟油ㄓ玫膱鼍?。 注:當然利用安全沙箱如虛擬化等技術,結合 WebAssembly,可以進一步最小化安全攻擊面,但是 ROI 不高。 調度與編排 在云時代,Kubernetes 已經成為分布式環境下資源調度和應用編排的事實標準。Kubernetes 可以屏蔽底層設施的差異性??梢栽谕粋€ K8s 集群中包含 x86、ARM 等不同體系架構的節點,可以支持 Linux,Windows 等不同的操作系統。Kubernetes 和 WebAssembly 相結合可以進一步提升應用的可移植性。 微軟的 Deis Labs 年初發布了一個 實驗項目 ,來利用 Virtual Kubelet 類似的架構調度 WebAssembly 應用。但是這個方式有很多局限,無法借助容器方式進行應用分發,也無法利用?K8s 的語義進行資源編排。? 難得有一個春節假期可以宅在家里,在此期間我基于 Derek McGowan 去年的一個 實驗性項目 ,完善了 containerd 的 WASM shim 實現??梢宰?containerd 支持 WASM container,并且可以利用 Kubernetes 集群管理和調度 WASM container。 項目的代碼實現: https://github.com/denverdino/containerd-wasm 注:這個項目更多是概念驗證,進程管理、資源限制,性能優化等的細節并沒未完整實現。 整個系統的架構設計如下,“container-shim-wasm-v1”作為 Containerd 的擴展,利用 wasmer 作為 WASM 應用運行時環境,可以實現與 runc 容器一致的用戶體驗。 我們還會將其注冊為 K8s 的一個? RuntimeClass ,允許用戶利用 K8s 來交付和運維 WASM 應用。 注:RuntimeClass 是 Kubernetes v1.12 引入的新概念,可以讓 Kubernetes 支持多種不同的容器運行時,比如 runc 容器、或者 Kata Containers,gVisor 等安全沙箱容器。更多細節可以參考: containerd 與安全沙箱的 Kubernetes 初體驗 。 Talk is Cheap, 放碼過來 首先,我們將利用 Minikube 創建一個 K8s 測試環境,并將 Containerd 作為 Kubernetes 集群的容器運行時。 創建虛擬機測試環境 創建 Minikube K8s 集群,并將 Containerd 作為 Kubernetes 集群容器運行時。 minikube start --image-mirror-country cn \ --iso-url=https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/iso/minikube-v1.6.0.iso \ --registry-mirror=https://tgtsuwdg.mirror.aliyuncs.com \ --container-runtime=containerd 進入 Minikube 虛擬機: $ minikube ssh _ _ _ _ ( ) ( ) ___ ___ (_) ___ (_)| |/') _ _ | |_ __ /' _ ` _ `\| |/' _ `\| || , < ( ) ( )| '_`\ /'__`\ | ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )( ___/ (_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____) 配置環境所需依賴: wasmer 0.13; minikube 缺省安裝了 container 1.2.x,需要升級 containerd 1.3.x; 我提供了一個預編譯的 containerd-wasm-shim-v1,也可自己編譯一個版本。 cd ~ # Install Wasmer 0.13.1 curl -L -O https://github.com/wasmerio/wasmer/releases/download/0.13.1/wasmer-linux-amd64.tar.gz gunzip wasmer-linux-amd64.tar.gz tar xvf wasmer-linux-amd64.tar sudo cp bin/* /usr/bin/ # Upgrade containerd to v1.3.2 curl -L -O https://github.com/containerd/containerd/releases/download/v1.3.2/containerd-1.3.2.linux-amd64.tar.gz gunzip containerd-1.3.2.linux-amd64.tar.gz tar xvf containerd-1.3.2.linux-amd64.tar sudo systemctl stop containerd sudo cp bin/* /usr/bin/ sudo systemctl restart containerd # Install containerd-wasm-shim wget http://kubernetes.oss-cn-hangzhou.aliyuncs.com/containerd-wasm/containerd-shim-wasm-v1 chmod +x containerd-shim-wasm-v1 sudo mv containerd-shim-wasm-v1 /usr/bin/ 配置 containerd 支持 WASM shim 在 containerd 配置文件中添加 wasm shim 相關配置,并重啟 containerd。 $ cat <
來源:OSCHINA
發布時間:2020-03-11 11:09:00
隨著技術的發展,各種應用變得越來越復雜,開發的壓力也與日俱增,速度與質量等等各種要求更是讓企業的基礎架構、IT 團隊及工作流程壓力山大。 假設一下,你正在使用筆記本電腦開發應用,并且開發環境有特定配置,其他開發人員的環境配置可能稍有不同。而你正在開發的應用不止依賴于你當前的配置,還需要某些特定的庫、依賴項和文件。另一邊,你的企業還有標準化的開發和生產環境、配置和支持文件等等,你希望盡可能多在本地模擬這些環境,而不產生額外的開銷。這個時候你該如何確保應用能在這些環境中運行和通過質檢,并在部署過程中沒有讓人頭疼的問題,也不需要重新寫代碼和故障修復? 答案就是:使用容器。 容器可以確保你的應用有用必需的庫、依賴項和文件,你可以在生產中自如遷移這些應用,無需擔心出現任何負面影響。 容器這個詞,字面釋義代表的是“可容納物料的器具”,簡單來說就是“裝”。那么在 IT 世界里,容器技術就是 Linux Container 的直譯。沒錯,自從 Linux 提出以后,容器已經發展成為云平臺上不可或缺的技術。Linux 可以幫助你跨多種環境,作為云原生中最基礎的計算單元,其標準化、輕量級、易移植、安全等特點正在受到越來越多的企業歡迎。 除了 Linux,在當前,Docker 更是大家都在討論的熱點,幾乎是容器的代名詞。但在容器世界里,要想了解 Docker,得先搞清楚: 容器的基本構成是什么?容器的典型應用是怎么樣的?有哪些使用容器的優秀案例值得參考?如何將容器應用到實際業務中提升效率? 六周玩轉云原生 為了讓開發者們在這個特殊的時期里可以學習到更多干貨,京東智聯云開發者特別策劃 《六周玩轉云原生》系列課程 ,讓你迅速入門,持續充電。 3 月 12 日,第一節針對容器方向的公開課《容器入門,Docker、Pod初探》將隆重開講! 本次公開課邀請到京東云與 AI 云產品研發部專家架構師 劉俊輝老師,從容器的基本構成出發,深入淺出地勾勒出容器的基本情況,包括容器的結構、基本使用方式等。不僅如此,還將引出 Docker 這一重要概念,為開發者們撥開容器應用的迷霧。此外,劉俊輝還會著重講解 POD、了解 POD 的構成和基本應用,幫助開發者對容器有更深入的理解。 容器入門,Docker、Pod初探 主講人:劉俊輝 (京東云與AI云產品研發部專家架構師) 課程大綱 課程時間 03月12日(周四)20:00-21:00 掃描下方二維碼 立即報名 注意??!報名成功后,開課前會有短信/郵件提醒,所以報名時請填寫正確的手機號碼及郵箱地址哦! 添加小助手,回復: 玩轉云原生 進入公開課交流群 ?????? 歡迎點擊“ 京東云 ”了解更多精彩內容!
來源:OSCHINA
發布時間:2020-03-10 23:08:00
女人个人私人电话联系杭州的|热久久久久香蕉无品码|爱情岛亚洲永久自拍品质|国产丶欧美丶日本不卡