Pod和容器設計模式

為什么需要 Pod;

Pod 的實現機制;

詳解容器設計模式。

一、為什么需要 Pod

容器的基本概念

現在來看第一個問題:為什么需要 Pod?我們知道 Pod 是 Kubernetes 項目里面一個非常重要的概念,也是非常重要的一個原子調度單位,但是為什么我們會需要這樣一個概念呢?我們在使用容器 Docker 的時候,也沒有這個說法。其實如果要理解 Pod,我們首先要理解容器,所以首先來回顧一下容器的概念:
容器的本質實際上是一個進程,是一個視圖被隔離,資源受限的進程。
容器里面 PID=1 的進程就是應用本身,這意味著管理虛擬機等于管理基礎設施,因為我們是在管理機器,但管理容器卻等于直接管理應用本身。這也是之前說過的不可變基礎設施的一個最佳體現,這個時候,你的應用就等于你的基礎設施,它一定是不可變的。

在以上面的例子為前提的情況下,Kubernetes 又是什么呢?我們知道,很多人都說 Kubernetes 是云時代的操作系統,這個非常有意思,因為如果以此類推,容器鏡像就是這個操作系統的軟件安裝包,它們之間是這樣的一個類比關系。

真實操作系統里的例子

如果說 Kubernetes 就是操作系統的話,那么我們不妨看一下真實的操作系統的例子。

例子里面有一個程序叫做 Helloworld,這個 Helloworld 程序實際上是由一組進程組成的,需要注意一下,這里說的進程實際上等同于 Linux 中的線程。

因為 Linux 中的線程是輕量級進程,所以如果從 Linux 系統中去查看 Helloworld 中的 pstree,將會看到這個 Helloworld 實際上是由四個線程組成的,分別是 {api、main、log、compute}。也就是說,四個這樣的線程共同協作,共享 Helloworld 程序的資源,組成了 Helloworld 程序的真實工作情況。

這是操作系統里面進程組或者線程組中一個非常真實的例子,以上就是進程組的一個概念。
在這里插入圖片描述

那么大家不妨思考一下,在真實的操作系統里面,一個程序往往是根據進程組來進行管理的。Kubernetes 把它類比為一個操作系統,比如說 Linux。針對于容器我們前面提到可以類比為進程,就是前面的 Linux 線程。那么 Pod 又是什么呢?實際上 Pod 就是我們剛剛提到的進程組,也就是 Linux 里的線程組
在這里插入圖片描述

進程組概念

說到進程組,首先建議大家至少有個概念上的理解,然后我們再詳細的解釋一下。

還是前面那個例子:Helloworld 程序由四個進程組成,這些進程之間會共享一些資源和文件。那么現在有一個問題:假如說現在把 Helloworld 程序用容器跑起來,你會怎么去做?

當然,最自然的一個解法就是,我現在就啟動一個 Docker 容器,里面運行四個進程。可是這樣會有一個問題,這種情況下容器里面 PID=1 的進程該是誰? 比如說,它應該是我的 main 進程,那么問題來了,“誰”又負責去管理剩余的 3 個進程呢?

這個核心問題在于,容器的設計本身是一種“單進程”模型,不是說容器里只能起一個進程,由于容器的應用等于進程,所以只能去管理 PID=1 的這個進程,其他再起來的進程其實是一個托管狀態。 所以說服務應用進程本身就具有“進程管理”的能力。

比如說 Helloworld 的程序有 system 的能力,或者直接把容器里 PID=1 的進程直接改成 systemd,否則這個應用,或者是容器是沒有辦法去管理很多個進程的。因為 PID=1 進程是應用本身,如果現在把這個 PID=1 的進程給 kill 了,或者它自己運行過程中死掉了,那么剩下三個進程的資源就沒有人回收了,這個是非常非常嚴重的一個問題。

而反過來真的把這個應用本身改成了 systemd,或者在容器里面運行了一個 systemd,將會導致另外一個問題:使得管理容器,不再是管理應用本身了,而等于是管理 systemd,這里的問題就非常明顯了。比如說我這個容器里面 run 的程序或者進程是 systemd,那么接下來,這個應用是不是退出了?是不是 fail 了?是不是出現異常失敗了?實際上是沒辦法直接知道的,因為容器管理的是 systemd。這就是為什么在容器里面運行一個復雜程序往往比較困難的一個原因。

這里再幫大家梳理一下:由于容器實際上是一個“單進程”模型,所以如果你在容器里啟動多個進程,只有一個可以作為 PID=1 的進程,而這時候,如果這個 PID=1 的進程掛了,或者說失敗退出了,那么其他三個進程就會自然而然的成為孤兒,沒有人能夠管理它們,沒有人能夠回收它們的資源,這是一個非常不好的情況。

注意:Linux 容器的“單進程”模型,指的是容器的生命周期等同于 PID=1
的進程(容器應用進程)的生命周期,而不是說容器里不能創建多進程。當然,一般情況下,容器應用進程并不具備進程管理能力,所以你通過 exec
或者 ssh 在容器里創建的其他進程,一旦異常退出(比如 ssh 終止)是很容易變成孤兒進程的。

反過來,其實可以在容器里面 run 一個 systemd,用它來管理其他所有的進程。這樣會產生第二個問題:實際上沒辦法直接管理我的應用了,因為我的應用被 systemd 給接管了,那么這個時候應用狀態的生命周期就不等于容器生命周期。這個管理模型實際上是非常非常復雜的。
在這里插入圖片描述

Pod = “進程組”

在 kubernetes 里面,Pod 實際上正是 kubernetes 項目為你抽象出來的一個可以類比為進程組的概念。

前面提到的,由四個進程共同組成的一個應用 Helloworld,在 Kubernetes 里面,實際上會被定義為一個擁有四個容器的 Pod,這個概念大家一定要非常仔細的理解。

就是說現在有四個職責不同、相互協作的進程,需要放在容器里去運行,在 Kubernetes 里面并不會把它們放到一個容器里,因為這里會遇到兩個問題。那么在 Kubernetes 里會怎么去做呢?它會把四個獨立的進程分別用四個獨立的容器啟動起來,然后把它們定義在一個 Pod 里面。

所以當 Kubernetes 把 Helloworld 給拉起來的時候,你實際上會看到四個容器,它們共享了某些資源,這些資源都屬于 Pod,所以我們說 Pod 在 Kubernetes 里面只有一個邏輯單位,沒有一個真實的東西對應說這個就是 Pod,不會有的。真正起來在物理上存在的東西,就是四個容器。這四個容器,或者說是多個容器的組合就叫做 Pod。并且還有一個概念一定要非常明確,Pod 是 Kubernetes 分配資源的一個單位,因為里面的容器要共享某些資源,所以 Pod 也是 Kubernetes 的原子調度單位。
在這里插入圖片描述
上面提到的 Pod 設計,也不是 Kubernetes 項目自己想出來的, 而是早在 Google 研發 Borg 的時候,就已經發現了這樣一個問題。這個在 Borg paper 里面有非常非常明確的描述。簡單來說 Google 工程師發現在 Borg 下面部署應用時,很多場景下都存在著類似于“進程與進程組”的關系。更具體的是,這些應用之前往往有著密切的協作關系,使得它們必須部署在同一臺機器上并且共享某些信息。

以上就是進程組的概念,也是 Pod 的用法。

為什么 Pod 必須是原子調度單位?

可能到這里大家會有一些問題:雖然了解這個東西是一個進程組,但是為什么要把 Pod 本身作為一個概念抽象出來呢?或者說能不能通過調度把 Pod 這個事情給解決掉呢?為什么 Pod 必須是 Kubernetes 里面的原子調度單位?

下面我們通過一個例子來解釋。

假如現在有兩個容器,它們是緊密協作的,所以它們應該被部署在一個 Pod 里面。具體來說,第一個容器叫做 App,就是業務容器,它會寫日志文件;第二個容器叫做 LogCollector,它會把剛剛 App 容器寫的日志文件轉發到后端的 ElasticSearch 中。

兩個容器的資源需求是這樣的:App 容器需要 1G 內存,LogCollector 需要 0.5G 內存,而當前集群環境的可用內存是這樣一個情況:Node_A:1.25G 內存,Node_B:2G 內存。

假如說現在沒有 Pod 概念,就只有兩個容器,這兩個容器要緊密協作、運行在一臺機器上。可是,如果調度器先把 App 調度到了 Node_A 上面,接下來會怎么樣呢?這時你會發現:LogCollector 實際上是沒辦法調度到 Node_A 上的,因為資源不夠。其實此時整個應用本身就已經出問題了,調度已經失敗了,必須去重新調度。
在這里插入圖片描述
以上就是一個非常典型的成組調度失敗的例子。英文叫做:Task co-scheduling 問題,這個問題不是說不能解,在很多項目里面,這樣的問題都有解法。

比如說在 Mesos 里面,它會做一個事情,叫做資源囤積(resource hoarding):即當所有設置了 Affinity 約束的任務都達到時,才開始統一調度,這是一個非常典型的成組調度的解法。

所以上面提到的“App”和“LogCollector”這兩個容器,在 Mesos 里面,他們不會說立刻調度,而是等兩個容器都提交完成,才開始統一調度。這樣也會帶來新的問題,首先調度效率會損失,因為需要等待。由于需要等還會有外一個情況會出現,就是產生死鎖,就是互相等待的一個情況。這些機制在 Mesos 里都是需要解決的,也帶來了額外的復雜度。

另一種解法是 Google 的解法。它在 Omega 系統(就是 Borg 下一代)里面,做了一個非常復雜且非常厲害的解法,叫做樂觀調度。比如說:不管這些沖突的異常情況,先調度,同時設置一個非常精妙的回滾機制,這樣經過沖突后,通過回滾來解決問題。這個方式相對來說要更加優雅,也更加高效,但是它的實現機制是非常復雜的。這個有很多人也能理解,就是悲觀鎖的設置一定比樂觀鎖要簡單。

而像這樣的一個 Task co-scheduling 問題,在 Kubernetes 里,就直接通過 Pod 這樣一個概念去解決了。因為在 Kubernetes 里,這樣的一個 App 容器和 LogCollector 容器一定是屬于一個 Pod 的,它們在調度時必然是以一個 Pod 為單位進行調度,所以這個問題是根本不存在的。

再次理解 Pod

在講了前面這些知識點之后,我們來再次理解一下 Pod,首先 Pod 里面的容器是“超親密關系”。

這里有個“超”字需要大家理解,正常來說,有一種關系叫做親密關系,這個親密關系是一定可以通過調度來解決的。
在這里插入圖片描述

比如說現在有兩個 Pod,它們需要運行在同一臺宿主機上,那這樣就屬于親密關系,調度器一定是可以幫助去做的。但是對于超親密關系來說,有一個問題,即它必須通過 Pod 來解決。因為如果超親密關系賦予不了,那么整個 Pod 或者說是整個應用都無法啟動。

什么叫做超親密關系呢?大概分為以下幾類:

比如說兩個進程之間會發生文件交換,前面提到的例子就是這樣,一個寫日志,一個讀日志;
兩個進程之間需要通過 localhost 或者說是本地的 Socket 去進行通信,這種本地通信也是超親密關系;
這兩個容器或者是微服務之間,需要發生非常頻繁的 RPC 調用,出于性能的考慮,也希望它們是超親密關系;
兩個容器或者是應用,它們需要共享某些 Linux Namespace。最簡單常見的一個例子,就是我有一個容器需要加入另一個容器的 Network Namespace。這樣我就能看到另一個容器的網絡設備,和它的網絡信息。

像以上幾種關系都屬于超親密關系,它們都是在 Kubernetes 中會通過 Pod 的概念去解決的。

現在我們理解了 Pod 這樣的概念設計,理解了為什么需要 Pod。它解決了兩個問題:

我們怎么去描述超親密關系;
我們怎么去對超親密關系的容器或者說是業務去做統一調度,這是 Pod 最主要的一個訴求。

二、Pod 的實現機制

Pod 要解決的問題

像 Pod 這樣一個東西,本身是一個邏輯概念。那在機器上,它究竟是怎么實現的呢?這就是我們要解釋的第二個問題。

既然說 Pod 要解決這個問題,核心就在于如何讓一個 Pod 里的多個容器之間最高效的共享某些資源和數據。

因為容器之間原本是被 Linux Namespace 和 cgroups 隔開的,所以現在實際要解決的是怎么去打破這個隔離,然后共享某些事情和某些信息。這就是 Pod 的設計要解決的核心問題所在。

所以說具體的解法分為兩個部分:網絡和存儲。

1.共享網絡

第一個問題是 Pod 里的多個容器怎么去共享網絡?下面是個例子:

比如說現在有一個 Pod,其中包含了一個容器 A 和一個容器 B,它們兩個就要共享 Network Namespace。在 Kubernetes 里的解法是這樣的:它會在每個 Pod 里,額外起一個 Infra container 小容器來共享整個 Pod 的 Network Namespace。

Infra container 是一個非常小的鏡像,大概 100~200KB 左右,是一個匯編語言寫的、永遠處于“暫停”狀態的容器。由于有了這樣一個 Infra container 之后,其他所有容器都會通過 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。

所以說一個 Pod 里面的所有容器,它們看到的網絡視圖是完全一樣的。即:它們看到的網絡設備、IP地址、Mac地址等等,跟網絡相關的信息,其實全是一份,這一份都來自于 Pod 第一次創建的這個 Infra container。這就是 Pod 解決網絡共享的一個解法。

在 Pod 里面,一定有一個 IP 地址,是這個 Pod 的 Network Namespace 對應的地址,也是這個 Infra container 的 IP 地址。所以大家看到的都是一份,而其他所有網絡資源,都是一個 Pod 一份,并且被 Pod 中的所有容器共享。這就是 Pod 的網絡實現方式。

由于需要有一個相當于說中間的容器存在,所以整個 Pod 里面,必然是 Infra container 第一個啟動。并且整個 Pod 的生命周期是等同于 Infra container 的生命周期的,與容器 A 和 B 是無關的。這也是為什么在 Kubernetes 里面,它是允許去單獨更新 Pod 里的某一個鏡像的,即:做這個操作,整個 Pod 不會重建,也不會重啟,這是非常重要的一個設計。
在這里插入圖片描述
2.共享存儲

第二問題:Pod 怎么去共享存儲?Pod 共享存儲就相對比較簡單。

比如說現在有兩個容器,一個是 Nginx,另外一個是非常普通的容器,在 Nginx 里放一些文件,讓我能通過 Nginx 訪問到。所以它需要去 share 這個目錄。我 share 文件或者是 share 目錄在 Pod 里面是非常簡單的,實際上就是把 volume 變成了 Pod level。然后所有容器,就是所有同屬于一個 Pod 的容器,他們共享所有的 volume。
在這里插入圖片描述

比如說上圖的例子,這個 volume 叫做 shared-data,它是屬于 Pod level 的,所以在每一個容器里可以直接聲明:要掛載 shared-data 這個 volume,只要你聲明了你掛載這個 volume,你在容器里去看這個目錄,實際上大家看到的就是同一份。這個就是 Kubernetes 通過 Pod 來給容器共享存儲的一個做法。

所以在之前的例子中,應用容器 App 寫了日志,只要這個日志是寫在一個 volume 中,只要聲明掛載了同樣的 volume,這個 volume 就可以立刻被另外一個 LogCollector 容器給看到。以上就是 Pod 實現存儲的方式。

三、詳解容器設計模式

現在我們知道了為什么需要 Pod,也了解了 Pod 這個東西到底是怎么實現的。最后,以此為基礎,詳細介紹一下 Kubernetes 非常提倡的一個概念,叫做容器設計模式。

舉例

接下來將會用一個例子來給大家進行講解。

比如我現在有一個非常常見的一個訴求:我現在要發布一個應用,這個應用是 JAVA 寫的,有一個 WAR 包需要把它放到 Tomcat 的 web APP 目錄下面,這樣就可以把它啟動起來了。可是像這樣一個 WAR 包或 Tomcat 這樣一個容器的話,怎么去做,怎么去發布?這里面有幾種做法。
在這里插入圖片描述

  • 第一種方式:可以把 WAR 包和 Tomcat 打包放進一個鏡像里面。但是這樣帶來一個問題,就是現在這個鏡像實際上揉進了兩個東西。那么接下來,無論是我要更新 WAR 包還是說我要更新 Tomcat,都要重新做一個新的鏡像,這是比較麻煩的;

  • 第二種方式:就是鏡像里面只打包 Tomcat。它就是一個 Tomcat,但是需要使用數據卷的方式,比如說 hostPath,從宿主機上把 WAR 包掛載進我們 Tomcat 容器中,掛到我的 web APP 目錄下面,這樣把這個容器啟用起來之后,里面就能用了。

但是這時會發現一個問題:這種做法一定需要維護一套分布式存儲系統。因為這個容器可能第一次啟動是在宿主機 A 上面,第二次重新啟動就可能跑到 B 上去了,容器它是一個可遷移的東西,它的狀態是不保持的。所以必須維護一套分布式存儲系統,使容器不管是在 A 還是在 B 上,都可以找到這個 WAR 包,找到這個數據。

注意,即使有了分布式存儲系統做 Volume,你還需要負責維護 Volume 里的 WAR 包。比如:你需要單獨寫一套 Kubernetes
Volume 插件,用來在每次 Pod 啟動之前,把應用啟動所需的 WAR 包下載到這個 Volume 里,然后才能被應用掛載使用到。

這樣操作帶來的復雜程度還是比較高的,且這個容器本身必須依賴于一套持久化的存儲插件(用來管理 Volume 里的 WAR 包內容)。

InitContainer

所以大家有沒有考慮過,像這樣的組合方式,有沒有更加通用的方法?哪怕在本地 Kubernetes 上,沒有分布式存儲的情況下也能用、能玩、能發布。

實際上方法是有的,在 Kubernetes 里面,像這樣的組合方式,叫做 Init Container。
在這里插入圖片描述
還是同樣一個例子:在上圖的 yaml 里,首先定義一個 Init Container,它只做一件事情,就是把 WAR 包從鏡像里拷貝到一個 Volume 里面,它做完這個操作就退出了,所以 Init Container 會比用戶容器先啟動,并且嚴格按照定義順序來依次執行。

然后,這個關鍵在于剛剛拷貝到的這樣一個目的目錄:APP 目錄,實際上是一個 Volume。而我們前面提到,一個 Pod 里面的多個容器,它們是可以共享 Volume 的,所以現在這個 Tomcat 容器,只是打包了一個 Tomcat 鏡像。但在啟動的時候,要聲明使用 APP 目錄作為我的 Volume,并且要把它們掛載在 Web APP 目錄下面。

而這個時候,由于前面已經運行過了一個 Init Container,已經執行完拷貝操作了,所以這個 Volume 里面已經存在了應用的 WAR 包:就是 sample.war,絕對已經存在這個 Volume 里面了。等到第二步執行啟動這個 Tomcat 容器的時候,去掛這個 Volume,一定能在里面找到前面拷貝來的 sample.war。

所以可以這樣去描述:這個 Pod 就是一個自包含的,可以把這一個 Pod 在全世界任何一個 Kubernetes 上面都順利啟用起來。不用擔心沒有分布式存儲、Volume 不是持久化的,它一定是可以公布的。

所以這是一個通過組合兩個不同角色的容器,并且按照這樣一些像 Init Container 這樣一種編排方式,統一的去打包這樣一個應用,把它用 Pod 來去做的非常典型的一個例子。像這樣的一個概念,在 Kubernetes 里面就是一個非常經典的容器設計模式,叫做:“Sidecar”。

容器設計模式:Sidecar

什么是 Sidecar?就是說其實在 Pod 里面,可以定義一些專門的容器,來執行主業務容器所需要的一些輔助工作,比如我們前面舉的例子,其實就干了一個事兒,這個 Init Container,它就是一個 Sidecar,它只負責把鏡像里的 WAR 包拷貝到共享目錄里面,以便被 Tomcat 能夠用起來。

其它有哪些操作呢?比如說:

  • 原本需要在容器里面執行 SSH 需要干的一些事情,可以寫腳本、一些前置的條件,其實都可以通過像 Init Container 或者另外像 Sidecar 的方式去解決;

  • 當然還有一個典型例子就是我的日志收集,日志收集本身是一個進程,是一個小容器,那么就可以把它打包進 Pod 里面去做這個收集工作;

  • 還有一個非常重要的東西就是 Debug 應用,實際上現在 Debug 整個應用都可以在應用 Pod 里面再次定義一個額外的小的 Container,它可以去 exec 應用 pod 的 namespace;

  • 查看其他容器的工作狀態,這也是它可以做的事情。不再需要去 SSH 登陸到容器里去看,只要把監控組件裝到額外的小容器里面就可以了,然后把它作為一個 Sidecar 啟動起來,跟主業務容器進行協作,所以同樣業務監控也都可以通過 Sidecar 方式來去做。

這種做法一個非常明顯的優勢就是在于其實將輔助功能從我的業務容器解耦了,所以我就能夠獨立發布 Sidecar 容器,并且更重要的是這個能力是可以重用的,即同樣的一個監控 Sidecar 或者日志 Sidecar,可以被全公司的人共用的。這就是設計模式的一個威力。
在這里插入圖片描述

Sidecar:應用與日志收集

接下來,我們再詳細細化一下 Sidecar 這樣一個模式,它還有一些其他的場景。

比如說前面提到的應用日志收集,業務容器將日志寫在一個 Volume 里面,而由于 Volume 在 Pod 里面是被共享的,所以日志容器 —— 即 Sidecar 容器一定可以通過共享該 Volume,直接把日志文件讀出來,然后存到遠程存儲里面,或者轉發到另外一個例子。現在業界常用的 Fluentd 日志進程或日志組件,基本上都是這樣的工作方式。
在這里插入圖片描述

Sidecar:代理容器

Sidecar 的第二個用法,可以稱作為代理容器 Proxy。什么叫做代理容器呢?

假如現在有個 Pod 需要訪問一個外部系統,或者一些外部服務,但是這些外部系統是一個集群,那么這個時候如何通過一個統一的、簡單的方式,用一個 IP 地址,就把這些集群都訪問到?有一種方法就是:修改代碼。因為代碼里記錄了這些集群的地址;另外還有一種解耦的方法,即通過 Sidecar 代理容器。

簡單說,單獨寫一個這么小的 Proxy,用來處理對接外部的服務集群,它對外暴露出來只有一個 IP 地址就可以了。所以接下來,業務容器主要訪問 Proxy,然后由 Proxy 去連接這些服務集群,這里的關鍵在于 Pod 里面多個容器是通過 localhost 直接通信的,因為它們同屬于一個 network Namespace,網絡視圖都一樣,所以它們倆通信 localhost,并沒有性能損耗。

所以說代理容器除了做了解耦之外,并不會降低性能,更重要的是,像這樣一個代理容器的代碼就又可以被全公司重用了。
在這里插入圖片描述

Sidecar:適配器容器

Sidecar 的第三個設計模式 —— 適配器容器 Adapter,什么叫 Adapter 呢?

現在業務暴露出來的 API,比如說有個 API 的一個格式是 A,但是現在有一個外部系統要去訪問我的業務容器,它只知道的一種格式是 API B ,所以要做一個工作,就是把業務容器怎么想辦法改掉,要去改業務代碼。但實際上,你可以通過一個 Adapter 幫你來做這層轉換。
在這里插入圖片描述
現在有個例子:現在業務容器暴露出來的監控接口是 /metrics,訪問這個這個容器的 metrics 的這個 URL 就可以拿到了。可是現在,這個監控系統升級了,它訪問的 URL 是 /health,我只認得暴露出 health 健康檢查的 URL,才能去做監控,metrics 不認識。那這個怎么辦?那就需要改代碼了,但可以不去改代碼,而是額外寫一個 Adapter,用來把所有對 health 的這個請求轉發給 metrics 就可以了,所以這個 Adapter 對外暴露的是 health 這樣一個監控的 URL,這就可以了,你的業務就又可以工作了。

這樣的關鍵還在于 Pod 之中的容器是通過 localhost 直接通信的,所以沒有性能損耗,并且這樣一個 Adapter 容器可以被全公司重用起來,這些都是設計模式給我們帶來的好處。

總結

  • Pod 是 Kubernetes 項目里實現“容器設計模式”的核心機制;
  • “容器設計模式”是 Google Borg 的大規模容器集群管理最佳實踐之一,也是 Kubernetes 進行復雜應用編排的基礎依賴之一;
  • 所有“設計模式”的本質都是:解耦和重用。
    Pod和容器設計模式

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/716775.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/716775.shtml
英文地址,請注明出處:http://en.pswp.cn/news/716775.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

144. 二叉樹的前序遍歷

給你二叉樹的根節點 root ,返回它節點值的 前序 遍歷。 示例 1: 輸入:root [1,null,2,3] 輸出:[1,2,3]示例 2: 輸入:root [] 輸出:[]示例 3: 輸入:root [1] 輸出&am…

java方法

目錄 方法的定義 方法的命名規則 方法的調用與重載 方法調用實例 方法的重載 變量的作用域 算法中常見的方法 1:gcd(求兩個整數中的最大公約數) 2:lcm(求兩個整數的最小公倍數) 3:判斷一個整數是否…

SpringCloud(18)之Sleuth +Zipkin鏈路追蹤

一、Zipkin介紹 Zipkin是一個開放源代碼分布式的跟蹤系統,它可以幫助收集服務的時間數據,以解決微服務架構中的延遲問 題,包括數據的收集、存儲、查找和展現。每個服務向zipkin報告計時數據,zipkin會根據調用關系通 過Zipkin UI…

LeetCode: 數組中的第K個最大元素

問題描述 在未排序的數組中找到第k個最大的元素。請注意,你需要找的是數組排序后的第k個最大的元素,而不是第k個不同的元素。 解題思路 解決這個問題有多種方法,下面是幾種常見的解題策略: 排序后選擇: 將數組排序&#xff0c…

ProChat 如何接入 WebSocket

WebSocket是一種在單個TCP連接上進行全雙工通信的協議,允許客戶端和服務器之間進行雙向實時通信。與Server-Sent Events (SSE)類似,WebSocket也能實現實時數據推送,但其功能更為強大且靈活。 全雙工通信:WebSocket不僅允許服務器向…

【TestNG】(4) 重試機制與監聽器的使用

在UI自動化測試用例執行過程中,經常會有很多不確定的因素導致用例執行失敗,比如網絡原因、環境問題等,所以我們有必要引入重試機制(失敗重跑),來提高測試用例成功率。 在不寫代碼的情況沒有提供可配置方式…

Mysql 慢查詢日志

查詢是否開啟慢SQL日志 show variables like %slow_query_log; 開啟慢查詢日志 set global slow_query_logON; 可以通過修改MySQL的配置my.cfg或者my.ini永久生效 slow_query_logON # 開啟慢查詢日志開關 slow_query_log_file/var/lib/mysql/alvin-slow.log # 慢查詢日志…

1.2 在卷積神經網絡中,如何計算各層感受野的大小

1.2 在卷積神經網絡中,如何計算各層感受野的大小 分析與解答: 在卷積神經網絡中,由于卷積的局部連接性,輸出特征圖上的每個節點的取值,是由卷積核在輸入特征圖對應位置的局部區域內進行卷積而得到的,因此這…

COM - IWbemClassObject對象屬性的遍歷

文章目錄 COM - IWbemClassObject對象屬性的遍歷概述筆記場景封裝好的函數bool CWmiBase::enumObjVaule(IWbemClassObject* obj, std::wstring& val)bool CWmiBase::appendVarToString(BSTR& strName, VARIANT& var, std::wstring& val)bool CWmiBase::get_var…

【筆試強訓錯題選擇題】Day5.習題(錯題)解析

文章目錄 前言 錯題題目 錯題解析 總結 前言 錯題題目 1. ? ? 2. 3. ? 4. ? 5. ? 錯題解析 1. 移位運算符的使用 2. 3. 4. 5. 總結

如何用TCC實現分布式事務?

TCC事務介紹 TCC(Try-Confirm-Cancel)是除可靠消息隊列以外的另一種常見的分布式事務機制,它是由數據庫專家帕特 赫蘭德(Pat Helland)在2007年撰寫的論文《Life beyond Distributed Transactions: An Apostate’s Op…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的體育賽事目標檢測系統(Python+PySide6界面+訓練代碼)

摘要:開發和研究體育賽事目標檢測系統對于增強體育分析和觀賞體驗至關重要。本篇博客詳細講述了如何運用深度學習技術構建一個體育賽事目標檢測系統,并提供了完整的實現代碼。系統基于先進的YOLOv8算法,對比了YOLOv7、YOLOv6、YOLOv5的性能&a…

【webrtc】p2p_transport_channel 中忽略Hyper-V

【win11】更改網絡適配器設置 刪掉了hype-v,這時候wsl2 打不開了,但是重啟后,還是存在hyper-v那么,讓webrtc自己不適用hyper-v的網絡Hyper-V 的全程:Hyper-V Virtual Ethernet Adapter https://github.com/SophistSolutions/Stroika/blob/2cd5e8bf4ee01cb5c423367b4df628f…

MFC 模態對話框退出機制的探究

一位讀者問了這樣一個問題: ” 如果我創建了一個可見的模態對話框,卻對用戶來說不可用。舉個例子,假設我在程序中的其他位置收到一個事件,并且我從事件中調用模態 CDialog 上的 DestroyWindow。我注意到 OnDestroy 是在 CDialog 上調用的,但在將 WM_QUIT 消息發送到模態對…

在MyBatis中自定義JsonTypeHandler

在MyBatis中使用自定義的JsonTypeHandler 在處理數據庫中的JSON字段時,我們經常需要將JSON字符串映射到Java對象,或者將Java對象序列化為JSON字符串以存儲在數據庫中。MyBatis作為一個流行的Java持久層框架,允許我們通過自定義類型處理器&am…

爬蟲入門到精通_實戰篇7(Requests+正則表達式爬取貓眼電影)_ 抓取單頁內容,正則表達式分析,保存至文件,開啟循環及多線程

1 目標 貓眼榜單TOP100:https://www.maoyan.com/board 2 流程框架 抓取單頁內容:利用requests請求目標站點,得到單個網頁HTML代碼,返回結果。正則表達式分析:根據HTML代碼分析得到電影名稱,主演,上映時間,評分,圖片…

跨域問題與解決方法

跨域問題與解決方法 同源策略 瀏覽器很容易受到XSS、CSFR等攻擊。所謂同源是指"協議域名端口"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。 同源策略限制以下幾種行為: Cookie、LocalStorage 和 IndexDB 無法讀取 DO…

C語言中的分支和循環語句:從入門到精通

分支和循環語句 1. 前言2. 預備知識2.1 getchar函數2.2 putchar函數2.3 計算數組的元素個數2.4 清屏2.5 程序的暫停2.6 字符串的比較 3. 結構化3.1 順序結構3.2 分支結構3.3 循環結構 4. 真假性5. 分支語句(選擇結構)5.1 if語句5.1.1 語法形式5.1.2 else…

Java網絡通信UDP

目錄 網絡通信基礎 UDP通信 服務器 1.想要使用UDP通信 要先打開DatagramSocket文件 端口號可以手動指定或系統隨機分配 2.阻塞等待接收客戶端數據;創建DatagramPacket接收客戶端傳來的數據 3.處理客戶端傳來的數據,并進行業務處理(這里…

MySQL 教程 2.4

MySQL UNION 操作符 本教程為大家介紹 MySQL UNION 操作符的語法和實例。 描述 MySQL UNION 操作符用于連接兩個以上的 SELECT 語句的結果組合到一個結果集合,并去除重復的行。 UNION 操作符必須由兩個或多個 SELECT 語句組成,每個 SELECT 語句的列數…