Ⅰ 面試問題redis有哪些集群方案
P2P模式,無中心化
把key分成16384個slot
每個實例負責一部分slot
客戶端請求若不在連接的實例,該實例會轉發給對應的實例。
通過Gossip協議同步節點信息
優點:
- 組件all-in-box,部署簡單,節約機器資源
- 性能比proxy模式好
- 自動故障轉移、Slot遷移中數據可用
- 官方原生集群方案,更新與支持有保障
缺點:
- 架構比較新,最佳實踐較少
- 多鍵操作支持有限(驅動可以曲線救國)
- 為了性能提升,客戶端需要緩存路由表信息
- 節點發現、reshard操作不夠自動化
Ⅱ redis架構模式(4) Codis
codis是在redis官方集群方案redis-cluster發布之前便已被業界廣泛使用的redis集群解決方案。
codis server :這是進行了二次開發的 Redis 實例,其中增加了額外的數據結構,支持
數據遷移操作,主要負責處理具體的數據讀寫請求。
codis proxy :接收客戶端請求,並把請求轉發給 codis server。
Zookeeper 集群 :保存集群元數據,例如數據位置信息和 codis proxy 信息。
codis dashboard 和 codis fe :共同組成了集群管理工具。其中,codis dashboard 負
責執行集群管理工作,包括增刪 codis server、codis proxy 和進行數據遷移。
而 codis fe 負責提供dashboard的Web 操作界面,便於我們直接在 Web 界面上進行集群管理
codis proxy本身支持 Redis 的 RESP 交互協議,所以只需和proxy建立連接,就相當於和
整個codis集群建立連接,codis proxy 接收到請求,就會查詢請求數據和 codis server 的
映射關系,並把請求轉發給相應的 codis server 進行處理。當 codis server 處理完請求後,
會把結果返回給codis proxy,proxy 再把數據返回給客戶端。
codis集群共有1024個slot,編號從0-1023,可以自定義或者自動均勻分配到各個server上,
使用 CRC32 演算法計算數據 key 的哈希值然後對1024取模,得到slot編號,然後通過slot
和server的映射關系請求到具體實例。
Slot 和 codis server 的映射關系稱為數據路由表,我們在 codis dashboard 上分配好路由表後,
dashboard 會把路由表發送給 codis proxy,同時,dashboard 也會把路由表保存在
Zookeeper 中。codis-proxy 會把路由表緩存在本地,當它接收到客戶端請求後,直接查詢
本地的路由表進而完成請求的轉發。
跟redis cluster的區別是,codis的把映射關系存在zookeeper和proxy中,redis cluster
是通過各個實例相互通信進而存在各個實例本地,當實例較多時,可能會受到網路狀況
影響而且整個集群的網路資源消耗相較之下也比較大。
codis按照slot粒度進行數據遷移,比如從serverA遷移至serverB,會從A要遷移的slot中
隨機選擇一個數據,發送給B,A得到確認消息後,刪除本地數據。重復這個過程直到
遷移完畢。
遷移方式有兩種,一種是同步遷移,一種是非同步遷移。
同步遷移時,源server是阻塞的,由於遷移過程中需要數據的序列化、網路傳輸、刪除
等等操作,如果是bigkey則會阻塞較長時間。
所以通常採用非同步遷移。非同步遷移模式下,源server發送完遷移數據之後,就可以繼續接受
處理請求,等到目標server返回ack之後,再執行刪除操作,這個過程中,為了防止數據
不一致的情況,被遷移的數據是只讀模式。
對於bigkey的數據,非同步遷移採用拆分指令的方式,對bigkey中的每個元素用一條指令
進行遷移,而不是等待整個數據序列化完畢再遷移,進而避免了需要序列化大量數據而
阻塞的風險,因為整個bigkey的數據是被拆分的,如果遷移期間server宕機,便會破壞
遷移的原子性,所以codis對bigkey的數據設置了一個過期時間,如果原子性遭到破壞,
目標server的數據就會過期後刪除以保證遷移的原子性。可以通過非同步遷移命令
SLOTSMGRTTAGSLOT-ASYNC 的參數numkeys 設置每次遷移的 key 數量,以提升
bigkey非同步遷移的速度。
最後分享一下我的一些架構設計心得,通過redis cluster和codis的slot設計,把原本
數百上千萬的key和實例的映射抽象成slot和實例的映射,大大減少了映射量,進而把
slot和key的映射分擔到各個實例中,這種粒度的抽象,值得借鑒,另外codis與redis cluser
不同的是,codis通過zk作為第三方儲存系統,保存了集群實例、狀態和slot分配等信息,
這樣避免了實例間的大量心跳來同步集群狀態進而減少實例間的網路通信量,對於實現
大規模的集群是有益的設計,網路帶寬可以集中在客戶端請求上面,需要注意的是需要保證
zk的通信帶寬,畢竟集群的信息同步都交給了zk
Ⅲ 何時部署要啟動lua nginx
Lua是一個可以嵌入到Nginx配置文件中的動態腳本語言,從而可以在Nginx請求處理的任何階段執行各種Lua代碼。剛開始我們只是用Lua 把請求路由到後端伺服器,但是它對我們架構的作用超出了我們的預期。下面就講講我們所做的工作。
強制搜索引擎只索引mixlr.com
Google把子域名當作完全獨立的網站,我們不希望爬蟲抓取子域名的頁面,降低我們的Page rank
location /robots.txt {
rewrite_by_lua '
if ngx.var.http_host ~= "mixlr.com" then
return ngx.exec("/robots_disallow.txt");
end
';
}
如果對robots.txt的請求不是mixlr.com域名的話,則內部重寫到robots_diallow.txt,雖然標準的重寫指令也可以實現這個需求,但是 Lua的實現更容易理解和維護。
根據程序邏輯設置響應頭
Lua提供了比Nginx默認配置規則更加靈活的設置方式。 在下面的例子中,我們要保證正確設置響應頭,這樣瀏覽器如果發送了指定請求頭後,就可以 無限期緩存靜態文件,是的用戶只需下載一次即可。
這個重寫規則使得任何靜態文件,如果請求參數中包含時間戳值,那麼就設置相應的Expires和Cache-Control響應頭。
location / {
header_filter_by_lua '
if ngx.var.query_string and ngx.re.match( ngx.var.query_string, "^([0-9]{10})$" ) then
ngx.header["Expires"] = ngx.http_time( ngx.time() + 31536000 );
ngx.header["Cache-Control"] = "max-age=31536000";
end
';
try_files $uri @dynamic;}
刪除jQuery JSONP請求的時間戳參數
很多外部客戶端請求JSONP介面時,都會包含一個時間戳類似的參數,從而導致Nginx proxy緩存無法命中(因為無法忽略指定的HTTP參數)。下面的 規則刪除了時間戳參數,使得Nginx可以緩存upstream server的響應內容,減輕後端伺服器的負載。
location / {
rewrite_by_lua '
if ngx.var.args ~= nil then
-- /some_request?_=1346491660 becomes /some_request
local fixed_args, count = ngx.re.sub( ngx.var.args, "&?_=[0-9]+", "" );
if count > 0 then
return ngx.exec(ngx.var.uri, fixed_args);
end
end
';}
把後端的慢請求日誌記錄到Nginx的錯誤日誌
如果後端請求響應很慢,可以把它記錄到Nginx的錯誤日誌,以備後續追查。
location / {
log_by_lua '
if tonumber(ngx.var.upstream_response_time) >= 1 then
ngx.log(ngx.WARN, "[SLOW] Ngx upstream response time: " .. ngx.var.upstream_response_time .. "s from " .. ngx.var.upstream_addr);
end
';}
基於Redis的實時IP封禁
某些情況下,需要阻止流氓爬蟲的抓取,這可以通過專門的封禁設備去做,但是通過Lua,也可以實現簡單版本的封禁。
lua_shared_dict banned_ips 1m;
location / {
access_by_lua '
local banned_ips = ngx.shared.banned_ips;
local updated_at = banned_ips:get("updated_at");
-- only update banned_ips from Redis once every ten seconds:
if updated_at == nil or updated_at < ( ngx.now() - 10 ) then
local redis = require "resty.redis";
local red = redis:new();
red:set_timeout(200);
local ok, err = red:connect("your-redis-hostname", 6379);
if not ok then
ngx.log(ngx.WARN, "Redis connection error retrieving banned_ips: " .. err);
else
local updated_banned_ips, err = red:smembers("banned_ips");
if err then
ngx.log(ngx.WARN, "Redis read error retrieving banned_ips: " .. err);
else
-- replace the locally stored banned_ips with the updated values:
banned_ips:flush_all();
for index, banned_ip in ipairs(updated_banned_ips) do
banned_ips:set(banned_ip, true);
end
banned_ips:set("updated_at", ngx.now());
end
end
end
if banned_ips:get(ngx.var.remote_addr) then
ngx.log(ngx.WARN, "Banned IP detected and refused access: " .. ngx.var.remote_addr);
return ngx.exit(ngx.HTTP_FORBIDDEN);
end
';}
現在就可以阻止特定IP的訪問:
1
ruby> $redis.sadd("banned_ips", "200.1.35.4")
Nginx進程每隔10秒從Redis獲取一次最新的禁止IP名單。需要注意的是,如果架構中使用了Haproxy這樣類似的負載均衡伺服器時, 需要把$remote_addr設置為正確的遠端IP地址。
這個方法還可以用於HTTP User-Agent欄位的檢查,要求滿足指定條件。
使用Nginx輸出CSRF(form_authenticity_token)
Mixlr大量使用頁面緩存,由此引入的一個問題是如何給每個頁面輸出會話級別的CSRF token。我們通過Nginx的子請求,從upstream web server 獲取token,然後利用Nginx的SSI(server-side include)功能輸出到頁面中。這樣既解決了CSRF攻擊問題,也保證了cache能被正常利用。
location /csrf_token_endpoint {
internal;
include /opt/nginx/conf/proxy.conf;
proxy_pass "http://upstream";}
location @dynamic {
ssi on;
set $csrf_token '';
rewrite_by_lua '
-- Using a subrequest, we our upstream servers for the CSRF token for this session:
local csrf_capture = ngx.location.capture("/csrf_token_endpoint");
if csrf_capture.status == 200 then
ngx.var.csrf_token = csrf_capture.body;
-- if this is a new session, ensure it sticks by passing through the new session_id
-- to both the subsequent upstream request, and the response:
if not ngx.var.cookie_session then
local match = ngx.re.match(csrf_capture.header["Set-Cookie"], "session=([a-zA-Z0-9_+=/+]+);");
if match then
ngx.req.set_header("Cookie", "session=" .. match[1]);
ngx.header["Set-Cookie"] = csrf_capture.header["Set-Cookie"];
end
end
else
ngx.log(ngx.WARN, "No CSRF token returned from upstream, ignoring.");
end
';
try_files /maintenance.html /rails_cache$uri @thin;}
CSRF token生成 app/metal/csrf_token_endpoint.rb:
class CsrfTokenEndpoint
def self.call(env)
if env["PATH_INFO"] =~ /^\/csrf_token_endpoint/
session = env["rack.session"] || {}
token = session[:_csrf_token]
if token.nil?
token = SecureRandom.base64(32)
session[:_csrf_token] = token
end
[ 200, { "Content-Type" => "text/plain" }, [ token ] ]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
endend
我們的模版文件示例:
<meta name="csrf-param" value="authenticity_token"/>
<meta name="csrf-token" value="<!--# echo var="csrf_token" default="" encoding="none" -->"/>
Again you could make use of lua_shared_dict to store in memory the CSRF token for a particular session. This minimises the number of trips made to /csrf_token_endpoint.
Ⅳ 京東活動系統--億級流量架構應對之術
京東活動系統 是一個可在線編輯、實時編輯更新和發布新活動,並對外提供頁面訪問服務的系統。其高時效性、靈活性等特徵,極受青睞,已發展成京東幾個重要流量入口之一。近幾次大促,系統所承載的pv已經達到數億級。隨著京東業務的高速發展,京東活動系統的壓力會越來越大。急需要一個更高效,穩定的系統架構,來支持業務的高速發展。本文主要對活動頁面瀏覽方面的性能,進行探討。
活動頁面瀏覽性能提升的難點:
1. 活動與活動之間差異很大,不像商品頁有固定的模式。每個頁面能抽取的公共部分有限,可復用性差。
2. 活動頁面內容多樣,業務繁多。依賴大量外部業務介面,數據很難做到閉環。外部介面的性能,以及穩定性,嚴重製約了活動頁的渲染速度、穩定性。
經過多年在該系統下的開發實踐,提出「頁面渲染、瀏覽非同步化」的思想,並以此為指導,對該系統進行架構升級改造。通過近幾個月的運行,各方面性能都有顯著提升。在分享"新架構"之前,先看看我們現有web系統的架構現狀。
以京東活動系統架構的演變為例,這里沒有畫出具體的業務邏輯,只是簡單的描述下架構:
2.第二步,一般是在消耗性能的地方加緩存,這里對部分查庫操作加redis緩存
3.對頁面進行整頁redis緩存:由於活動頁面內容繁多,渲染一次頁面的成本是很高。這里可以考慮把渲染好的活動內容整頁緩存起來,下次請求到來時,如果緩存中有值,直接獲取緩存返回。
以上是系統應用服務層面架構演進的,簡單示意。為了減少應用伺服器的壓力,可以在應用伺服器前面,加cdn和nginx的proxy_caxhe,降低回源率。
4.整體架構(老)
除了前3步講的「瀏覽服務」,老架構還做了其他兩個大的優化:「介面服務」、靜態服務
1.訪問請求,首先到達瀏覽服務,把整個頁面框架返回給瀏覽器(有cdn、nginx、redis等各級緩存)。
2.對於實時數據(如秒殺)、個性化數據(如登陸、個人坐標),採用前端實時介面調用,前端介面服務。
3.靜態服務:靜態資源分離,所有靜態js、css訪問靜態服務。
要點:瀏覽服務、介面服務分離。頁面固定不變部分走瀏覽服務,實時變化、個性化採用前端介面服務實現。
介面服務:分兩類,直接讀redis緩存、調用外部介面。這里可以對直接讀redis的介面採用nginx+lua進行優化( openresty ),不做詳細講解。 本次分享主要對「瀏覽服務」架構
在講新架構之前先看看新老架構下的新能對比
擊穿cdn緩存、nginx緩存,回源到應用伺服器的流量大約為20%-40%之間,這里的性能對比,只針對回源到應用伺服器的部分。
2015雙十一, 瀏覽方法tp99如下:(物理機)
Tp99 1000ms左右,且抖動幅度很大,內存使用近70%,cpu 45%左右。
1000ms內沒有緩存,有阻塞甚至掛掉的風險。
2.新架構瀏覽服務新能
本次2016 618採用新架構支持,瀏覽tp99如下(分app端活動和pc端活動):
移動活動瀏覽tp99穩定在8ms, pc活動瀏覽tp99 穩定在15ms左右。全天幾乎一條直線,沒有性能抖動。
新架構支持,伺服器(docker)cpu性能如下
cpu消耗一直平穩在1%,幾乎沒有抖動。
對比結果:新架構tp99從1000ms降低到 15ms,cpu消耗從45%降低到1%,新架構性能得到質的提升。
why!!!
下面我們就來揭開新架構的面紗。
1. 頁面瀏覽,頁面渲染 非同步化
再來看之前的瀏覽服務架構,20%-40%的頁面請求會重新渲染頁面,渲染需要重新計算、查詢、創建對象等導致 cpu、內存消耗增加,tp99性能下降。
如果能保證每次請求都能獲取到redis整頁緩存,這些性能問題就都不存在了。
即:頁面瀏覽,與頁面渲染 非同步。
理想情況下,如果頁面數據變動可以通過 手動觸發渲染(頁面發布新內容)、外部數據變化通過監聽mq 自動觸發渲染。
但是有些外部介面不支持mq、或者無法使用mq,比如活動頁面置入的某個商品,這個商品名稱變化。
為了解決這個問題,view工程每隔指定時間,向engine發起重新渲染請求-最新內容放入redis。下一次請求到來時即可獲取到新內容。由於活動很多,也不能確定哪些活動在被訪問,所以不建議使用timer。通過加一個緩存key來實現,處理邏輯如下:
好處就是,只對有訪問的活動定時重新發起渲染。
整理架構(不包含業務):
view工程職責 :
a.直接從緩存或者硬碟中獲取靜態html返回,如果沒有返回錯誤頁面。(文件系統的存取性能比較低,超過 100ms級別,這里沒有使用)
b.根據緩存key2是否過期,判斷是否向engine重新發起渲染。(如果,你的項目外面介面都支持mq,這個 功能就不需要了)
engine工程職責 :渲染活動頁面,把結果放到 硬碟、redis。
publish工程、mq 職責 :頁面發生變化,向engine重新發起渲染。 具體的頁面邏輯,這里不做講解
Engine工程的工作 就是當頁面內容發生變化時,重新渲染頁面,並將整頁內容放到redis,或者推送到硬碟。
View工程的工作,就是根據鏈接從redis中獲取頁面內容返回。
3.view 工程架構 ( 硬碟 版)
兩個版本對比
a.Redis版
優點:接入簡單、 性能好,尤其是在大量頁面情況下,沒有性能抖動 。單個docker tps達到 700。
缺點:嚴重依賴京東redis服務,如果redis服務出現問題,所有頁面都無法訪問。
b.硬碟版
優點:不依賴任何其他外部服務,只要應用服務不掛、網路正常 就可以對外穩定服務。
在頁面數量不大的情況下,性能優越。單個docker tps達到 2000。
缺點:在頁面數據量大的情況下(系統的所有活動頁有xx個G左右),磁碟io消耗增加(這里採用的java io,如果採用nginx+lua,io消耗應該會控制在10%以內)。
解決方案:
a. 對所有頁面訪問和存儲 採用url hash方式,所有頁面均勻分配到各個應用伺服器上。
b. 採用nginx+lua 利用nginx的非同步io,代替java io。
現在通過nginx+lua做應用服務,所具有的高並發處理能力、高性能、高穩定性已經越來越受青睞。通過上述講解,view工程沒有任何業務邏輯。可以很輕易的就可以用lua實現,從redis或者硬碟獲取頁面,實現更高效的web服務。如果想學習Java工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java進階qun:694549689,裡面有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家。
1.具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。
2.在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。
3.如果沒有工作經驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的可以加。
通過測試對比,view工程讀本地硬碟的速度,比讀redis還要快(同一個頁面,讀redis是15ms,硬碟是8ms)。所以終極版架構我選擇用硬碟,redis做備份,硬碟讀不到時在讀redis。
這里前置機的url hash是自己實現的邏輯,engine工程採用同樣的規則推送到view伺服器硬碟即可,具體邏輯這里不細講。後面有時間再單獨做一次分享。
優點:具備硬碟版的全部優點,同時去掉tomcat,直接利用nginx高並發能力,以及io處理能力。各項性能、以及穩定性達到最優。
缺點:1、硬碟壞掉,影響訪問。2.方法監控,以及日誌列印,需使用lua腳本重寫。
無論是redis版、硬碟版、openresty+硬碟版,基礎都是頁面瀏覽與頁面渲染非同步化。
優勢:
1、所有業務邏輯都剝離到engine工程,新view工程理論上永遠無需上線。
2、災備多樣化(redis、硬碟、文件系統),且更加簡單,外部介面或者服務出現問題後,切斷engine工程渲染,不再更新redis和硬碟即可。
3、新view工程,與業務邏輯完全隔離,不依賴外部介面和服務,大促期間,即便外部介面出現新能問題,或者有外部服務掛掉,絲毫不影響view工程正常訪問。
4、性能提升上百倍,從1000ms提升到10ms左右。詳見前面的性能截圖。
5、穩定性:只要view伺服器的網路還正常,可以做到理論上用不掛機。
6、大幅度節省伺服器資源,按此架構,4+20+30=54個docker足以支持10億級pv。(4個nginx proxy_cache、20個view,30個engine)
從事開發已有近10載,一直就像寄生蟲一樣吸取著網路上的資源。前段時間受「張開濤」大神所託,對活動系統新架構做了一次簡單整理分享給大家,希望能給大家帶來一絲幫助。第一次在網上做分享,難免有些沒有考慮周全的地方,以後會慢慢的多分享一些自己的心得,大家一起成長。最後再來點心靈雞湯。。。
Ⅳ 架構高可用高並發系統的設計原則
通過學習《億級流量網站架構核心技術》及《linux就該這么學》學習筆記及自己的感悟:架構設計之高可用高並發系統設計原則,架構設計包括墨菲定律、康威定律和二八定律三大定律,而系統設計包括高並發原則、高可用和業務設計原則等。
架構設計三大定律
墨菲定律 – 任何事沒有表面看起來那麼簡單 – 所有的事都會比預計的時間長 – 可能出錯的事情總會出錯 – 擔心某種事情發生,那麼它就更有可能發生
康威定律 – 系統架構師公司組織架構的反映 – 按照業務閉環進行系統拆分/組織架構劃分,實現閉環、高內聚、低耦合,減少溝通成本 – 如果溝通出現問題,應該考慮進行系統和組織架構的調整 – 適合時機進行系統拆分,不要一開始就吧系統、服務拆分拆的非常細,雖然閉環,但是每個人維護的系統多,維護成本高 – 微服務架構的理論基礎 – 康威定律https://yq.aliyun.com/articles/8611– 每個架構師都應該研究下康威定律http://36kr.com/p/5042735.html
二八定律 – 80%的結果取決於20%的原因
系統設計遵循的原則
1.高並發原則
無狀態
無狀態應用,便於水平擴展
有狀態配置可通過配置中心實現無狀態
實踐: Disconf、Yaconf、Zookpeer、Consul、Confd、Diamond、Xdiamond等
拆分
系統維度:按照系統功能、業務拆分,如購物車,結算,訂單等
功能維度:對系統功能在做細粒度拆分
讀寫維度:根據讀寫比例特徵拆分;讀多,可考慮多級緩存;寫多,可考慮分庫分表
AOP維度: 根據訪問特徵,按照AOP進行拆分,比如商品詳情頁可分為CDN、頁面渲染系統,CDN就是一個AOP系統
模塊維度:對整體代碼結構劃分Web、Service、DAO
服務化
服務化演進: 進程內服務-單機遠程服務-集群手動注冊服務-自動注冊和發現服務-服務的分組、隔離、路由-服務治理
考慮服務分組、隔離、限流、黑白名單、超時、重試機制、路由、故障補償等
實踐:利用Nginx、HaProxy、LVS等實現負載均衡,ZooKeeper、Consul等實現自動注冊和發現服
消息隊列
目的: 服務解耦(一對多消費)、非同步處理、流量削峰緩沖等
大流量緩沖: 犧牲強一致性,保證最終一致性(案例:庫存扣減,現在Redis中做扣減,記錄扣減日誌,通過後台進程將扣減日誌應用到DB)
數據校對: 解決非同步消息機制下消息丟失問題
數據異構
數據異構: 通過消息隊列機制接收數據變更,原子化存儲
數據閉環: 屏蔽多從數據來源,將數據異構存儲,形成閉環
緩存銀彈
用戶層:
DNS緩存
瀏覽器DNS緩存
操作系統DNS緩存
本地DNS服務商緩存
DNS伺服器緩存
客戶端緩存
瀏覽器緩存(Expires、Cache-Control、Last-Modified、Etag)
App客戶緩存(js/css/image…)
代理層:
CDN緩存(一般基於ATS、Varnish、Nginx、Squid等構建,邊緣節點-二級節點-中心節點-源站)
接入層:
Opcache: 緩存PHP的Opcodes
Proxy_cache: 代理緩存,可以存儲到/dev/shm或者SSD
FastCGI Cache
Nginx+Lua+Redis: 業務數據緩存
Nginx為例:
PHP為例:
應用層:
頁面靜態化
業務數據緩存(Redis/Memcached/本地文件等)
消息隊列
數據層:
NoSQL: Redis、Memcache、SSDB等
MySQL: Innodb/MyISAM等Query Cache、Key Cache、Innodb Buffer Size等
系統層:
CPU : L1/L2/L3 Cache/NUMA
內存
磁碟:磁碟本身緩存、dirtyratio/dirtybackground_ratio、陣列卡本身緩存
並發化
2.高可用原則
降級
降級開關集中化管理:將開關配置信息推送到各個應用
可降級的多級讀服務:如服務調用降級為只讀本地緩存
開關前置化:如Nginx+lua(OpenResty)配置降級策略,引流流量;可基於此做灰度策略
業務降級:高並發下,保證核心功能,次要功能可由同步改為非同步策略或屏蔽功能
限流
目的: 防止惡意請求攻擊或超出系統峰值
實踐:
惡意請求流量只訪問到Cache
穿透後端應用的流量使用Nginx的limit處理
惡意IP使用Nginx Deny策略或者iptables拒絕
切流量
目的:屏蔽故障機器
實踐:
DNS: 更改域名解析入口,如DNSPOD可以添加備用IP,正常IP故障時,會自主切換到備用地址;生效實踐較慢
HttpDNS: 為了繞過運營商LocalDNS實現的精準流量調度
LVS/HaProxy/Nginx: 摘除故障節點
可回滾
發布版本失敗時可隨時快速回退到上一個穩定版本
3.業務設計原則
防重設計
冪等設計
流程定義
狀態與狀態機
後台系統操作可反饋
後台系統審批化
文檔注釋
備份
4.總結
先行規劃和設計時有必要的,要對現有問題有方案,對未來有預案;欠下的技術債,遲早都是要還的。
本文作者為網易高級運維工程師
Ⅵ 細說分布式redis
IT培訓>資料庫教程
細說分布式Redis架構設計和踩過的那些坑
作者:課課家教育2015-12-14 10:15:25
摘要:本文章主要分成五個步驟內容講解
Redis、RedisCluster和Codis;
我們更愛一致性;
Codis在生產環境中的使用的經驗和坑們;
對於分布式資料庫和分布式架構的一些看法;
Q & A環節。
Codis是一個分布式Redis解決方案,與官方的純P2P的模式不同,Codis採用的是Proxy-based的方案。今天我們介紹一下Codis及下一個大版本RebornDB的設計,同時會介紹一些Codis在實際應用場景中的tips。最後拋磚引玉,會介紹一下我對分布式存儲的一些觀點和看法,望各位首席們雅正。
細說分布式Redis架構設計和踩過的那些坑_redis 分布式_ redis 分布式鎖_分布式緩存redis
一、 Redis,RedisCluster和Codis
Redis:想必大家的架構中,Redis已經是一個必不可少的部件,豐富的數據結構和超高的性能以及簡單的協議,讓Redis能夠很好的作為資料庫的上游緩存層。但是我們會比較擔心Redis的單點問題,單點Redis容量大小總受限於內存,在業務對性能要求比較高的情況下,理想情況下我們希望所有的數據都能在內存裡面,不要打到資料庫上,所以很自然的就會尋求其他方案。 比如,SSD將內存換成了磁碟,以換取更大的容量。更自然的想法是將Redis變成一個可以水平擴展的分布式緩存服務,在Codis之前,業界只有Twemproxy,但是Twemproxy本身是一個靜態的分布式Redis方案,進行擴容/縮容時候對運維要求非常高,而且很難做到平滑的擴縮容。Codis的目標其實就是盡量兼容Twemproxy的基礎上,加上數據遷移的功能以實現擴容和縮容,最終替換Twemproxy。從豌豆莢最後上線的結果來看,最後完全替換了Twem,大概2T左右的內存集群。
Redis Cluster :與Codis同期發布正式版的官方cl
Ⅶ 如何實現高可用的 redis 集群
Redis 因具有豐富的數據結構和超高的性能以及簡單的協議,使其能夠很好的作為資料庫的上游緩存層。但在大規模的 Redis 使用過程中,會受限於多個方面:單機內存有限、帶寬壓力、單點問題、不能動態擴容等。
基於以上, Redis 集群方案顯得尤為重要。通常有 3 個途徑:官方 Redis Cluster ;通過 Proxy 分片;客戶端分片 (Smart Client) 。以上三種方案各有利弊。
Redis Cluster( 官方 ) :雖然正式版發布已經有一年多的時間,但還缺乏最佳實踐;對協議進行了較大修改,導致主流客戶端也並非都已支持,部分支持的客戶端也沒有經過大規模生產環境的驗證;無中心化設計使整個系統高度耦合,導致很難對業務進行無痛的升級。
Proxy :現在很多主流的 Redis 集群都會使用 Proxy 方式,例如早已開源的 Codis 。這種方案有很多優點,因為支持原聲 redis 協議,所以客戶端不需要升級,對業務比較友好。並且升級相對平滑,可以起多個 Proxy 後,逐個進行升級。但是缺點是,因為會多一次跳轉,平均會有 30% 左右的性能開銷。而且因為原生客戶端是無法一次綁定多個 Proxy ,連接的 Proxy 如果掛了還是需要人工參與。除非類似 Smart Client 一樣封裝原有客戶端,支持重連到其他 Proxy ,但這也就帶來了客戶端分片方式的一些缺點。並且雖然 Proxy 可以使用多個,並且可以動態增加 proxy 增加性能,但是所有客戶端都是共用所有 proxy ,那麼一些異常的服務有可能影響到其他服務。為每個服務獨立搭建 proxy ,也會給部署帶來額外的工作。
而我們選擇了第三種方案,客戶端分片 (Smart Client) 。客戶端分片相比 Proxy 擁有更好的性能,及更低的延遲。當然也有缺點,就是升級需要重啟客戶端,而且我們需要維護多個語言的版本,但我們更愛高性能。
下面我們來介紹一下我們的Redis集群:
概貌:
如圖0所示,
我們的 Redis 集群一共由四個角色組成:
Zookeeper :保存所有 redis 集群的實例地址, redis 實例按照約定在特定路徑寫入自身地址,客戶端根據這個約定查找 redis 實例地址,進行讀寫。
Redis 實例:我們修改了 redis 源碼,當 redis 啟動或主從切換時,按照約定自動把地址寫到 zookeeper 特定路徑上。
Sentinel : redis 自帶的主從切換工具,我們通過 sentinel 實現集群高可用。
客戶端( Smart Client ):客戶端通過約定查找 redis 實例在 ZooKeeper 中寫入的地址。並且根據集群的 group 數,進行一致性哈希計算,確定 key 唯一落入的 group ,隨後對這個 group 的主庫進行操作。客戶端會在Z ooKeeper 設置監視,當某個 group 的主庫發生變化時,Z ooKeeper 會主動通知客戶端,客戶端會更新對應 group 的最新主庫。
我們的Redis 集群是以業務為單位進行劃分的,不同業務使用不同集群(即業務和集群是一對一關系)。一個 Redis 集群會由多個 group 組成 ( 一個 group 由一個主從對 redis 實例組成 ) 。即 group 越多,可以部署在更多的機器上,可利用的內存、帶寬也會更多。在圖0中,這個業務使用的 redis 集群由 2 個 group 組成,每個 group 由一對主從實例組成。
Failover
如圖1所示,
當 redis 啟動時,會 把自己的 IP:Port 寫入到 ZooKeeper 中。其中的 主實例模式啟動時會在 /redis/ 業務名 / 組名 永久節點寫入自己的 IP:Port (如果節點不存在則創建)。由 主模式 變成 從模式 時,會創建 /redis/ 業務名 / 組名 /slaves/ip:port 臨時節 點,並寫入自己的 IP:Port (如果相同節點已經存在,則先刪除,再創建)。而從實例 模式 啟動時會創建 /redis/ 業務名 / 組名 /slaves/ip:port 臨時節點,並寫入自己的 ip:port (如果相同節點已經存在,則先刪除,再創建)。由 從模式 變成 主模式 時,先刪除 /redis/ 業務名 / 組名 /slaves/ip:port 臨時節點,並在 /redis/ 業務名 / 組名 永久節點寫入自己的 IP:Port 。
ZooKeeper 會一直保存當前有效的 主從實例 IP:Port 信息。至於主從自動切換過程,使用 redis 自帶的 sentinel 實現,現設置為超過 30s 主 server 無響應,則由 sentinel 進行主從實例的切換,切換後就會觸發以主、從實例通過以上提到的一系列動作,從而完成最終的切換。
而客戶端側通過給定業務名下的所有 groupName 進行一致性哈希計算,確定 key 落入哪個組。 客戶端啟動時,會從 ZooKeeper 獲取指定業務名下所有 group 的 主從 IP:Port ,並在 ZooKeeper 中設置監視(監視的作用是當 ZooKeeper 的節點發生變化時,會主動通知客戶端)。若客戶端從 Zookeeper 收到節點變化通知,會重新獲取最新的 主從 I:Port ,並重新設置監視( ZooKeeper 監視是一次性的)。通過此方法,客戶端可以實時獲知當前可訪問最新的 主從 IP:Port 信息。
因為我們的所有 redis 實例信息都按照約定保存在 ZooKeeper 上,所以不需要針對每個實例部署監控,我們編寫了一個可以自動通過 ZooKeeper 獲取所有 redis 實例信息,並且監控 cpu 、 qps 、內存、主從延遲、主從切換、連接數等的工具。
發展:
現在 redis 集群在某些業務內存需求超過預期很多後,無法通過動態擴容進行擴展。所以我們正在做動態擴容的支持。原先的客戶端我們是通過一致性哈希進行 key 的
路由策略,但這種方式在動態擴容時會略顯復雜,所以我們決定採用實現起來相對簡單的預分片方式。一致性哈希的好處是可以無限擴容,而預分片則不是。預分片
時我們會在初始化階段指定一個集群的所有分片數量,這個數量一旦指定就不能再做改變,這個預分片數量就是後續可以擴容到最大的 redis 實例數。假設預分片 128 個 slot ,每個實例 10G 也可以達到 TB 級別的集群,對於未來數據增長很大的集群我們可以預分片 1024 ,基本可以滿足所有大容量內存需求了。
原先我們的 redis 集群有四種角色, Smart Client, redis , sentinel , ZooKeeper 。為了支持動態擴容,我們增加了一個角色, redis_cluster_manager (以下簡稱 manager ),用於管理 redis 集群。主要工作是初始化集群(即預分片),增加實例後負責修改Z ooKeeper 狀態,待客戶端做好准備後遷移數據到新增實例上。為了盡量減少數據遷移期間對現性能帶來的影響,我們每次只會遷移一個分片的數據,待遷移完成,再進行下一個分片的遷移。
如圖2所示
相比原先的方案,多了 slots 、M anager Lock 、 clients 、M igrating Clients 節點。
Slots: 所有分片會把自身信息寫入到 slots 節點下面。 Manager 在初始化集群時,根據設置的分片數,以及集群下的 group 數,進行預分片操作,把所有分片均勻分配給已有 group 。分片的信息由一個 json 串組成,記錄有分片的狀態 (stats) ,當前擁有此分片的 group(src) ,需要遷移到的 group(dst) 。分片的狀態一共有三種: online 、 pre_migrate 、 migrating 。
Online 指這個分片處於正常狀態,這時 dst 是空值,客戶端根據 src 的 group 進行讀寫。
Pre_migrate 是指這個分片被 manager 標記為需要遷移,此時 dst 仍然為空, manager 在等所有 client 都已經准備就緒,因為 ZooKeeper 回掉所有客戶端有時間差,所以如果某些 client 沒有準備就緒的時候 manager 進行了數據遷移,那麼就會有數據丟失。
Migrating 是 manager 確認了所有客戶端都已經做好遷移准備後,在 dst 寫入此分片需要遷移的目標 group 。待遷移完成,會在 src 寫入目標 group_name , dst 設為空, stats 設為 online 。
Manager Lock: 因為我們是每次只允許遷移一個 slot ,所以不允許超過一個 manager 操作一個集群。所以 manager 在操作集群前,會在M anager Lock 下注冊臨時節點,代表這個集群已經有 manager 在操作了,這樣其他 manager 想要操作這個集群時就會自動退出。
Clients 和M igrating Clients 是為了讓 manager 知道客戶端是否已經准備就緒的節點。客戶端通過 uid 代表自己,格式是 客戶端語言 _ 主機名 _pid 。當集群沒有進行遷移,即所有分片都是 online 的時候,客戶端會在 clients 下創建 uid 的臨時節點。
當某個 slot 從 online 變成 pre_migrate 後,客戶端會刪除 clients 下的 uid 臨時節點,然後在M igrating Clients 創建 uid 臨時節點。注意,因為需要保證數據不丟失,從 pre_migrate 到 migrating 期間,這個 slot 是被鎖定的,即所有對這個 slot 的讀寫都會被阻塞。所以 mananger 會最多等待 10s ,確認所有客戶端都已經切換到准備就緒狀態,如果發現某個客戶端一直未准備就緒,那麼 mananger 會放棄此次遷移,把 slot 狀態由 pre_migrate 改為 online 。如果客戶端發現 slot 狀態由 pre_migrate 變成 online 了,那麼會刪除 migrating_clients 下的 uid 節點,在 clients 下重新創建 uid 節點。還需要注意的一點是,有可能一個客戶剛啟動,並且正在往 clients 下創建 uid 節點,但是因為網路延遲還沒創建完成,導致 manager 未確認到這個 client 是否准備就緒,所以 mananger 把 slot 改為 pre_migrate 後會等待 1s 再確認所有客戶端是否准備就緒。
如果 Manager 看到 clients 下已經沒有客戶端的話(都已經准備就緒),會把 slot 狀態改為 migrating 。 Slot 變成 migrating 後,鎖定也隨之解除, manager 會遍歷 src group 的數據,把對應 slot 的數據遷移到 dst group 里。客戶端在 migrating 期間如果有讀寫 migrating slot 的 key ,那麼客戶端會先把這個 key 從 src group 遷移到 dst group ,然後再做讀寫操作。即這期間客戶端性能會有所下降。這也是為什麼每次只遷移一個 slot 的原因。這樣即使只有 128 個分片的集群,在遷移期間受到性能影響的 key 也只有 1/128 ,是可以接受的。
Manager 發現已經把 slot 已經遷移完畢了,會在 src 寫入目標 group_name , dst 設為空, stats 設為 online 。客戶端也刪除 migrating_clients 下的 uid ,在 clients 下創建 uid 節點。