簡介
單體服務如果想要突破到高并發服務就需要更新為叢集服務。同時叢集化也為高可用打下了堅實的基礎。縱觀現在比較流行的服務或者中間件,不管是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的部落格
歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!