keycloak集羣化的思考 簡介 keycloak中的集羣 load balancing負載均衡 shared databases multicasting 總結

簡介

單體服務如果想要突破到高併發服務就需要升級爲集羣服務。同時集羣化也爲高可用打下了堅實的基礎。縱觀現在比較流行的服務或者中間件,不管是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的博客

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章