keycloak叢集化的思考

flydean發表於2021-01-13

簡介

單體服務如果想要突破到高併發服務就需要升級為叢集服務。同時叢集化也為高可用打下了堅實的基礎。縱觀現在比較流行的服務或者中介軟體,不管是RabbitMQ還是redis都提供了叢集的功能。

作為硬核工業代表的wildfly也不例外,最近研究了一下keycloak的叢集,發現它的底層伺服器用的也是wildfly,本文將會和大家探討一下keycloak的叢集的架構思路。

keycloak中的叢集

我們知道,keycloak中有兩種模式,一種叫做Standalone,一種叫做domain。

這兩種模式的區別只是在於部署檔案是否被集中管理,如果部署檔案需要一個一個的手動拷貝,那麼就是standalone模式。如果是一鍵化的自動安裝,那麼就是domain模式。

standalone模式下有一個配置檔案叫做 /standalone/configuration/standalone-ha.xml,這個就是在standalone模式下配置叢集的xml檔案了。

而domain模式下,配置檔案都是在domain controller這個機子上進行配置的,具體的檔案是 domain/configuration/domain.xml 。

我們看下ha具體是用的叢集相關的元件:

<profile name="full-ha">
...
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
                <proxy name="default" advertise-socket="modcluster" listener="ajp">
                    <dynamic-load-provider>
                        <load-metric type="cpu"/>
                    </dynamic-load-provider>
                </proxy>
</subsystem>

<subsystem xmlns="urn:jboss:domain:infinispan:11.0">
...
</subsystem>

<subsystem xmlns="urn:jboss:domain:jgroups:8.0">
                <channels default="ee">
                    <channel name="ee" stack="udp" cluster="ejb"/>
                </channels>
                <stacks>
                    <stack name="udp">
                       ...
                    </stack>
                    <stack name="tcp">
                       ...
                    </stack>
                </stacks>
            </subsystem>
...
</profile>

主要用的是modcluster,infinispan和jgroups。

除此之外,keycloak還介紹了一種叫做跨資料中心的叢集

這種模式主要用在服務是跨資料中心的情況,比如說異地機房這樣的容災性特別強的情況。

看完keycloak的基本叢集搭建之後,我們來講一下keycloak叢集中一些比較關鍵的概念和使用。

load balancing負載均衡

因為是叢集結構,所以我們後端是有多臺伺服器的,那麼使用者通過客戶端來訪問我們服務的時候,究竟應該定位到哪一臺伺服器呢?

這時就要用到負載均衡軟體了,也就是load balancing。

一般來說三種負載均衡的方式:

第一種,就是客戶端負載均衡,客戶端已經知道了服務端的多個服務地址,在傳送請求的時候由客戶端自行選擇要請求的服務地址。

這種模式一般都要配置一個強力的客戶端API,通過這個客戶端API來進行路由功能,比如說Memcached。

Memcached的神奇來自兩階段雜湊(two-stagehash)。Memcached就像一 個巨大的、儲存了很多<key,value>對的雜湊表。通過key,可以儲存或查詢任意的資料。

客戶端可以把資料儲存在多臺memcached上。當查詢資料時,客戶端首 先參考節點列表計算出key的雜湊值(階段一雜湊),進而選中一個節點;客戶端將請求傳送給選中的節點,然後memcached節點通過一個內部的雜湊演算法(階段二雜湊),查詢真正的資料(item)。

第二種,就是代理服務負載均衡,這種模式下,會有一個代理伺服器和後端的多個服務進行連線,客戶端是和這個代理伺服器進行互動,由代理伺服器來代替客戶端選擇到底要路由到哪個服務。

這種代理的路由的軟體就多了,比如我們熟悉的nginx和HTTPD,還有ildFly with mod_cluster, HA Proxy, 或者其他的硬體負載均衡。

第三種,是路由負載均衡,在這種模式下,使用者隨機選擇一個後端伺服器進行請求連線,然後在伺服器內部進行路由,將這個請求傳送到其他的伺服器中。

這種模式下,一般需要在伺服器內部實現特定的負載均衡功能。

暴露客戶端IP地址

不管使用的是什麼模式的負載均衡,我們都有可能在業務中需要使用到客戶訪問的IP地址。

我們在特定的業務中需要獲取到使用者的ip地址來進行一些操作,比如記錄使用者的操作日誌,如果不能夠獲取到真實的ip地址的話,則可能使用錯誤的ip地址。還有就是根據ip地址進行的認證或者防刷工作。

如果我們在服務之前使用了反向代理伺服器的話,就會有問題。所以需要我們配置反向代理伺服器,保證X-Forwarded-For和X-Forwarded-Proto這兩個HTTP header的值是有效的。

然後伺服器端就可以從X-Forwarded-For獲取到客戶的真實ip地址了。

在keycloak中,如果是http forwarding,則可以這樣配置:

<subsystem xmlns="urn:jboss:domain:undertow:10.0">
   <buffer-cache name="default"/>
   <server name="default-server">
      <ajp-listener name="ajp" socket-binding="ajp"/>
      <http-listener name="default" socket-binding="http" redirect-socket="https"
          proxy-address-forwarding="true"/>
      ...
   </server>
   ...
</subsystem>

如果是AJP forward, 比如使用的是Apache HTTPD + mod-cluster, 則這樣配置:

<subsystem xmlns="urn:jboss:domain:undertow:10.0">
     <buffer-cache name="default"/>
     <server name="default-server">
         <ajp-listener name="ajp" socket-binding="ajp"/>
         <http-listener name="default" socket-binding="http" redirect-socket="https"/>
         <host name="default-host" alias="localhost">
             ...
             <filter-ref name="proxy-peer"/>
         </host>
     </server>
        ...
     <filters>
         ...
         <filter name="proxy-peer"
                 class-name="io.undertow.server.handlers.ProxyPeerAddressHandler"
                 module="io.undertow.core" />
     </filters>
 </subsystem>

sticky sessions 和 非sticky sessions

如果是在存在session的環境中,比如說web應用程式中,如果後端伺服器是cluster的情況下還需要考慮session共享的問題。

因為對於每個伺服器來說,它的session都是本地維護的,如果是多臺伺服器想要session共享該怎麼辦呢?

一種辦法就是所有的伺服器都將session存放在同一個外部快取系統中,比如說redis。這樣不管使用者訪問到哪個server,都可以讀取到同一份session資料。

當然,這個快取系統可以是單點也可以是叢集,如果是不同的資料中心的話,快取叢集甚至還需要跨資料中心進行同步。

快取同步當然是一個很好的辦法,但是同步行動自然是有開銷的。有沒有更加簡單方便的處理方式呢? 比如固定一個使用者只訪問同一個伺服器這樣是不是就能解決快取同步的問題呢?

這種固定使用者訪問特定某個伺服器的模式,我們叫做sticky sessions模式。在這種模式下,可以不用考慮session同步的問題。當然,這種模式下,如果某個伺服器down機了,使用者的session就會丟失。所以還是要做一些session同步的工作,只不過不需要實時的同步而已。

另外,sticky session還有一個缺點:如果是後臺的請求,則獲取不到session的資訊,也就無法實現sticky session,這個時候就需要進行後臺資料的拷貝,這樣才能保證不管請求傳送到哪裡都能夠表現一致。

shared databases

所有的應用都需要儲存資料。通常來說,我們會有兩種資料:

一種是資料庫資料,這種資料將會永久儲存使用者資訊。

一種是cache,用作資料庫和應用程式的緩衝。

不管是哪種資料,都可以有叢集模式,也就是多臺伺服器同時讀寫資料。這樣對於共享的資料就涉及到了叢集資料更新的問題。

叢集資料的更新有兩種更新模式:

一種是可靠優先,Active/Active mode,一個節點更新的資料會立馬同步到另外一個節點。

一種是效能優先,Active/Passive mode,一個節點更新的資料不會立馬同步到另外一個節點中。

可靠優先的執行邏輯是,一個更新請求需要等待所有的叢集服務返回更新成功才算成功。而效能優先的執行邏輯就是更新完主資料就算成功了,其他的節點會去非同步和主資料節點進行同步。

keycloak中使用的快取是infinispan,並且構建了多種session快取,不同的快取使用的是不同的同步策略:

  • authenticationSessions:這個快取儲存的是登入使用者的資訊,如果在sticky sessions模式下,是不需要進行資料同步的。

  • Action tokens:如果使用者需要非同步的進行郵件驗證,比如說忘記密碼等操作,則需要用到這種型別的快取。因為這種操作中的token只能夠被使用一次,所以需要資料的同步。

  • 非認證的session資訊:因為不能保證sticky session模式的使用,所以需要複製。

  • loginFailures: 統計使用者的登入異常情況,不需要被複制。

在快取儲存資料,需要注意資料更新後的失效問題。

在keycloak中,使用了一個單獨的work快取,這個快取是所有資料中心同步的,它不儲存實際的資料,只儲存要無效的資料通知。各個資料的服務從work快取中讀取無效的資料列表,進行相應的資料快取無效化處理。

multicasting

最後,如果叢集需要動態發現和管理節點的功能的話,還需要進行IP廣播。比如說可以使用JGroups來實現這個功能。

總結

keycloak的底層是wildfly,本身已經支援很多強大的工業元件,它的設計理念是讓程式業務邏輯和其他的通用的生產級特性(高可用,負載均衡,快取叢集,訊息佇列等)區分開,只用專注於業務邏輯的實現和編寫,其他的事情交給伺服器去做即可。

大家可以多研究下這些優秀的伺服器框架,可以得到一些不同的體會。

本文作者:flydean程式那些事

本文連結:http://www.flydean.com/keycloak-cluster-in-depth/

本文來源:flydean的部落格

歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

相關文章