博客 / 詳情

返回

OceanBase Session ID 之謎

一. 寫作原因

遇到了 3 個事情,讓我想到要好好梳理一下 session id 的相關知識。

  1. 發現一個事務中查到的 OceanBase 的 session id 會發生變化(其實也算沒有變,原因下文會説)。
  2. 查到的 session id 在使用 kill 命令時候報錯找不到該 ID(不是因為 ID 真的不存在或者變化了)。
  3. 通過不同的查詢方式,查到很多和 session id 有關的 ID 值,沒法在腦中組織成知識網絡,不理解部分 ID 為什麼這麼設計。

session id 概述

session id 是用於描述客户端和數據庫的訪問鏈路的,OceanBase 數據庫架構中使用了 ODP(一般也稱為proxy,下文也用 proxy 指代),所以客户端對數據庫的訪問鏈路被拆成了兩段,第一段是客户端和 ODP 之間的連接(Client session,下文用 C 連接指代),第二段是 ODP 和 OBServer 之間的連接(Server session,下文多用 S 連接指代)。

一個客户端的連接,會使用一個 C 連接,然後對應多個 S 連接,C 連接和 S 連接的對應關係是 1 : N。值得注意的是,在執行一條 SQL 的時候,同一時間,只有一個 S 連接在服務(先不考慮遠程計劃或分佈式計劃帶來的二次路由)。

那麼問題來了:用於定義 2 種連接的 Session id,卻可以在官方文檔中找到 10 種名詞定義:

  • ID
  • session id
  • client session id
  • server session id
  • connection id
  • proxy_sessid
  • cs_id
  • server_sessid
  • ss_id
  • MASTER_SESSID

這是為什麼呢?

二. 寫作思路

開始研究這個主題的時候,是想要寫一個 Session ID 的前世今生的,但在翻文檔、找人聊、自己測之後,發現問題比以前更多了……

在幾次草稿都被自己否了以後,我悟了 —— 追求細節和完備最終導致我無法搞定文章,一個理解框架才是我們需要的東西。通過這個框架可以串聯起知識,然後遇到問題之後可以通過框架解決問題,這才是最有價值的。

三. session id 介紹

從一張圖開始:

圖中直觀的展示了客户端(client)和數據庫(ODP + OBServer)之間的鏈路關係。

為了方便下面的描述,我們把圖中的 4 根連接線分別命名,client 和 ODP 之間的鏈路叫 C-1,ODP 分別連接 OBServer 的 3 個鏈路叫 S-1、S-2、S-3(下面沒有特殊説明,都不考慮直連的情況)。

那麼有一些事情需要大家能想清楚(可能會有點兒繞):

  1. C-1、S-1、S-2、S-3 都是獨立的連接,背後會對應各自的 ID。而且一個連接兩端的組件(比如 S-1 兩端分別對應了 ODP 和 OBServer)可能對同個連接 S-1,給予不同的 ID。不同組件對 S-1 不同的 ID 編號這個事情,這麼設計有很多種可能。大家能想到幾種呢?
  2. 一整個會話(包含 C-1、S-1、S-2、S-3),只要不斷開,不結束,都是被獨佔的。其他的會話不可能共用裏面的任何一條鏈路。
  3. 單一個 SQL 的執行,只會使用一種組合,C-1 + S-X(這裏的 S-X 就是指 3 條 S 中的其中一條)。也就是針對一個 SQL 只有一個 S 連接參與服務(這裏不考慮二次路由)。
  4. 同一個事務的不同 SQL,會使用到不同的連接組合,這也是 ODP 的事務內路由的功能,這樣可以讓更多的SQL 使用本地執行計劃。
  5. 查看會話的視圖,習慣是一個會話是展示一行(因為在同一時刻,一個會話中只能執行一條 SQL)。其實 OceanBase 這裏有 2 種展示模式,show processlist 是一個會話 C 連接的維度,show full processlist 就是展示 S 連接的維度(會有多條)。可以想見同一個會話中,最多也只有一條在工作,其他的都是 sleep。
  6. 一個客户端第一次連接上數據庫,返回連接成功,説明 C-1 + S-1 的鏈路已經建立了,這個時候其實 S-2 和 S-3 還不存在,直到 ODP 需要連接的時候才會建立連接。

回到上面很多的名詞問題,這裏給出一個初步的解釋:

  • 連接的類型是隻有 2 種,C 連接和 S 連接。
  • 描述他們的不同編碼(小編理解這裏的 “編碼” 就是序號或者 ID 的含義) 一共 4 種,其他的名詞都是這 4 種的其中一種的指代。

    • cs_id:這是由 proxy 生成的用於描述 C 連接的編碼,也是我們最常見的 session id 表述之一,show processlist 裏的 ID 列通常指代的就是這個 cs_id(注意是通常)。
    • proxy_sessid:這也是由 proxy 生成的用於描述 C 連接的編碼,編碼比較長一些,在 proxy 和 OBServer 建立連接的時候,proxy 會告訴 OBServer,我上游服務的 C 連接的編碼就是這個。(注意 proxy 沒有告訴下游編碼是 cs_id,而是 proxy_sessid 。簡單的記憶就是,同樣一個 C 連接,對客户端 proxy 使用 cs_id ,對 OBServer 端則使用 proxy_sessid。挺住,別暈。
    • server_sessid:由 OBServer 生成的用於描述 S 連接的編碼,在整個 OceanBase 集羣內是唯一的,也是 OBServer 中用的很多的編碼,比如 OB_LOCKS 視圖中用的就是這個。
    • ss_id:由 proxy 生成的用於描述 S 連接的編碼,用到這個編碼的地方非常少,瞭解即可。

總結:proxy 生成了 3 個編碼,包括 cs_id、proxy_sessid、ss_id。其中 cs_id 和 ss_id 偏向於 proxy 內部使用,proxy_sessid 會傳遞給 OBServer,在 OBServer 的日誌中可能會有查詢的需求。OBServer 生成了一個編碼,即 server_sessid,用於定義 S 連接的 session id。如果是直連的情況,則只有 server_sessid,因為其他 3 個都是在 proxy 體系下使用的。

如果你能繞清楚,那就繼續來到下一關,先和這些 session 相關名詞混個臉熟:

  • ID:在不同的查詢場景下,這個列有時顯示的是 cs_id 值,有時是 server_sessid 值。
  • session id:泛指 session 的編碼,出現在 OceanBase 官方文檔各處,一般查表中沒有這個字段。
  • client session id:一種新版本的 cs_id,後面會介紹為什麼需要重新設計 cs_id。有時也指代 C 連接。
  • server session id:就是 server_sessid ,有時也指代 S 連接。
  • connection id:MySQL 租户使用函數 connection_id() 查詢獲得,就是 server_sessid。需要注意的是使用 obclient 連接 proxy 時候,登陸信息中會有輸出 Your Mysql connection id is xxx,這裏顯示的 id 是 cs_id。

  • MASTER_SESSID:這個比較特殊,指的是分佈式查詢中的主 server_sessid,不屬於 S 連接(本文暫不討論)。

四. 查詢 session id 的例子

下面是模擬的一個例子,大家通過這個例子可以更好的理解相關概念。

架構中一個客户端連接了一個 proxy,然後 proxy 連接了後面的 2 個 OBServer。

也就是 C-1 ,對應了後面的 S-1 和 S-2 。

在確保 C-1,S-1,S-2 都被啓用過以後,執行下面這些命令:

show proxysession;

show proxysession attribute;

show processlist;

show full processlist;

select connection_id();

select * from gv$ob_session;

select * from gv$ob_processlist;

分別得到下面的結果:

  • show proxysession;

    • 這裏的 id 就是 cs_id。
    • 直連是無法執行這個命令的(需要有 proxy)。

  • show proxysession attribute; 這裏就是查看這個 cs_id 的明細了。這個 cs_id 對應了 2 個 server_sessid ,分別是 3221550874 和 3221876366,它們的區別在 info 這個列:

    • last used ss 指的就是我執行這個查詢 SQL 是用的這個 S 連接。
    • ss pool 就是已經建立了這個 S 連接,之前是用到過的。

同樣的,這個命令在直連時候是無法執行的,因為這個命令只有 proxy 能處理。

  • show processlist;

    • 使用 proxy 的時候,這個 ID 是 cs_id。需要注意 show processlist 在 proxy 的語境下,就是查這個 proxy 上的 session,以 C 連接的視角(也就是一個客户端連接就顯示一條)。
    • 使用直連時候,這個 ID 是 server_sessid。

  • show full processlist;

    • 這個就是查看所有的 S 連接了,這裏的 ID 是 server_sessid。
    • 圖中標藍的部分都屬於同一個 C 連接,可以對照 show proxysession attribute 的內容看。

  • select connection_id();

    • 查到的是執行這個 SQL 的 S 連接,是 server_sessid。

  • select * from gv$ob_session;

    • 這裏標藍的部分都屬於同一個 C 連接,它們擁有同一個 proxy_sessid,按理只有 2 個 S 連接,為什麼有 4 條數據對應 4 個 ID 呢?
    • 你會發現 4 個 ID 中有 2 個是前面例子中出現過的 server_sessid,而剩下的 2 個是新 server_sessid。
    • 注意看 info,PX DFO EXECUTING 意思是分佈式的執行。因為 gv$ob_session 的查詢需要去所有的 OBServer 上看相關信息,所以實際執行這個 proxy_sessid 的 SQL 的 OBServer,建立新的連接(S 連接背後對應的新的分佈式執行連接),分別查詢了 OBServer 的內部信息。
    • 我們可以通過其他列(比如TRACE_ID,HOST等)的信息來理解。

這也可以看到,實際上 OceanBase 數據庫等訪問連接是比較複雜的,這個案例中連接分了 3 段,第一段是客户端到 proxy,第二段是 proxy 到 OBServer,第三段是 OBServer到 OBServer(自己也需要重新訪問自己的)。

  • select * from gv$ob_processlist;

    • 這裏和 show full processlist 的結果是一樣的。
    • ID 代表的是 server_sessid,不會展示 S 連接後續的連接(有別於 gv$ob_session 視圖)。

如果能堅持看完上面這些例子,要麼你已經對 session id 的情況有了全面的瞭解,要麼你已經徹底暈菜。沒關係,如果暈了,可以先收藏,後面遇到問題時再有的放矢地看。

如果你能堅持看到這裏的話,那我們就繼續再説一個與 session id 配套的命令 —— kill。

五. session id 與 kill 命令

kill 後面跟的是 session id,執行後這個會話就會被殺死,執行的 SQL 會停止,之前鎖定的資源也將釋放。

但是因為 session id 本身的複雜性,kill 命令的實現,也有不同的邏輯。我們一般會使用取到的 ID 列中的值來作為 kill 後跟隨的編碼,那麼可能是 cs_id 或者是 server_sessid。

還有一些特點,大家理解一下:

  1. 執行 kill 的 session 本身除了可以殺自己,還可以殺別的 session。
  2. kill 在 ODP 4.2.3 版本之前(以下稱為不透傳 kill),是由 proxy 自己執行的,直接從 proxy 側斷開相關 session 的 C 連接和 S 連接。
  3. kill 在 ODP 4.2.3 版本開始(以下稱為透傳 kill),會把 kill 信息轉交給 OBServer 去執行,然後通過錯誤碼告訴 proxy,proxy 再斷開相關 C 連接和 S 連接。
  4. proxy 本身是無狀態的,看不到其他 proxy 中的 cs_id。
  5. cs_id 是 proxy 內部對 C 連接的編碼,OBServer 對 C 連接的認知是 proxy_sessid。

不透傳 kill 的情況,做了個表格:

*\組件(列)ID(行)* proxy (C 連接在此 proxy) proxy (C 連接不在此 proxy) 直連 (不區分 S 連接是不是在這台 OBServer 上)
cs_id 認識,可以 kill。 不認識,因為 proxy 無狀態,無法 kill。 或者恰巧有個一樣的 cs_id,kill 錯。 不認識,根本沒有 cs_id 信息。
server_sessid(主) 認識,可以 kill。 可以認識,但是不歸這個 proxy 管,無法 kill。 認識,kill 成功,proxy 感知後,後續把對應的 C 連接斷開。
server_sessid(非主) 認識,可以 kill。 可以認識,但是不歸這個 proxy 管,無法 kill。 多種情況,比較複雜。

先説建議:就是如果用的是 proxy,那就不要使用直連方式去 kill。

如果遇到 kill 不掉(可能是因為 F5 這樣的負載均衡把 kill 語句路由到了其他無法處理的 proxy),就多執行幾遍,一般總有一次 kill 成功的(成功一次就算 kill 成功了)。雖然上面的表格中寫了有可能 kill 錯,但是概率很低。

因為上面不透傳的情況,會遇到各種的問題。後來 OceanBase 研發對 kill 進行了重新設計。主要解決了 2 個問題:

  1. C 連接不歸 proxy 管的問題(現在統一交給後面的 OBServer 執行,然後相關的 proxy 就可以收到來自 OBServer 的返回碼後,處理斷開 C 連接了)。
  2. cs_id 可能在不同的 proxy 之間重複的問題(啓用新設計的 client session id 替代 cs_id,通過合理編碼來使其具有唯一性),另外 client session id 也將在 proxy 和 OBServer 之間建立連接的時候告訴 OBServer,這樣 OBServer 就都認識了。

透傳 kill 的情況,也做了一張對應表格:

*\組件(列)ID(行)* proxy (C 連接在此 proxy) proxy (C連接不在此 proxy) 直連 (不區分 S 連接是不是在這台 OBServer 上)
client session id 認識,可以 kill。 可以認識(問 OBServer 嘛)然後透傳給 OBServer執行,可以kill。不會有重複問題 認識,可以 kill。
server_sessid(主) 認識,可以 kill。 可以認識,透傳,可以 kill。 認識,kill 成功,效果就等於 proxy 把 kill 透傳了。
server_sessid(非主) 認識,可以 kill。 可以認識,透傳,可以 kill。 多種情況,比較複雜。

透傳以後,是不是情況好多了?但我的建議依然還是建議都使用 proxy 來管理 kill,(而且2個組件都升級到新的使用 client session id 的版本)按照常用情況來管理,問題最少。

值得一提的是,透傳後的情況表格漏掉了很多情況,你看出來了嗎?原因就是這個時候 proxy 和 OBServer 面對 kill 命令都有 2 套邏輯,這裏的例子都是新 proxy 邏輯對新 OBServer 邏輯,還有老 proxy 邏輯對新 OBServer 邏輯和新 proxy 邏輯對老 OBServer 邏輯的情況。

理想中是要列出所有情況的,但這篇文章的作者今川説他偷懶不想整理其他情況了,那我們還是選擇原諒吧~

六. 總結

最後返回文章開頭的問題,為什麼要使用這麼多的名詞來管理 2 種連接。

  1. 最大的原因還是分佈式的架構引入的複雜性決定的。而且 proxy 被設計成無狀態的,在前面還加入 F5 這樣的負載均衡組件,也增加了管理難度。
  2. 數據庫軟件複雜度很高,多個團隊協作開發,必然有一些東西是不統一的,並且存在冗餘設計,否則溝通成本將被無限放大。
  3. 數據庫軟件是一個不斷演進的系統,有些東西雖然現在看來已經沒有用了,但是也不能去掉,作為兼容性的一部分長久留在代碼中。如同我們的 DNA,很多序列都是不表達的,沒什麼用處,但是是人類演進過程中的一部分也就被保存在 DNA 中了。

七. 後續

還有一個出現在 proxy 日誌中,用於指代 C 連接的代碼 sm_id,相關的信息比較少。sm_id 是 proxy 中的 proxy_sm 模塊對 cs_id 的指代。只在日誌中出現,由於數據庫裏沒有視圖查到這個信息,我認為主要是給 proxy_sm 模塊研發使用的。一般排查日誌可以通過 trace_id 來過濾。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.