目錄java
其實zk的應用場景都是針對zk能夠監聽某個節點,而且能夠感知到節點的修改或者節點的數據的修改,這樣就能夠利用根據節點或者節點的數據變化這一特性而應用到不少的場景中,只要抓住這一個特性就能夠了。node
這個實際上是zk很經典的一個用法,簡單來講,就比如,你A系統發送個請求到mq,而後B消息消費以後處理了。那A系統如何知道B系統的處理結果?用zk就能夠實現分佈式系統之間的協調工做。A系統發送請求以後能夠在zk上對某個節點的值註冊個監聽器,一旦B系統處理完了就修改zk那個節點的值,A立馬就能夠收到通知,完美解決。
mysql
對某一個數據連續發出兩個修改操做,兩臺機器同時收到了請求,可是隻能一臺機器先執行另一個機器再執行。那麼此時就可使用zk分佈式鎖,一個機器接收到了請求以後先獲取zk上的一把分佈式鎖,就是能夠去建立一個znode,接着執行操做;而後另一個機器也嘗試去建立那個znode,結果發現本身建立不了,由於被別人建立了。。。。那隻能等着,等第一個機器執行完了本身再執行。
web
zk能夠用做不少系統的配置信息的管理,好比kafka、storm等等不少分佈式系統都會選用zk來作一些元數據、配置信息的管理,包括dubbo註冊中心不也支持zk麼。
redis
這個應該是很常見的,好比hadoop、hdfs、yarn等不少大數據系統,都選擇基於zk來開發HA高可用機制,就是一個重要進程通常會作主備兩個,主進程掛了立馬經過zk感知到切換到備用進程。
算法
SET my:lock 隨機值 NX PX 30000,這個命令就ok,這個的NX的意思就是隻有key不存在的時候纔會設置成功,PX 30000的意思是30秒後鎖自動釋放。別人建立的時候若是發現已經有了就不能加鎖了。
PX 30000避免一直佔有鎖,會自動釋放鎖。
釋放鎖就是刪除key,可是通常能夠用lua腳本刪除,判斷value同樣才刪除:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
爲何須要判斷value的值呢?
由於若是某個客戶端獲取到了鎖,可是阻塞了很長時間才執行完,此時可能已經自動釋放鎖了,此時可能別的客戶端已經獲取到了這個鎖,要是你這個時候直接刪除key的話會有問題,因此得用隨機值加上面的lua腳原本釋放鎖。
可是此種仍是存在必定的問題,由於若是是普通的redis單實例,那就是單點故障。或者是redis普通主從,那redis主從異步複製,若是主節點掛了,key還沒同步到從節點,此時從節點切換爲主節點,別人就會拿到鎖。
spring
第二種,使用的是RedLock算法(有爭議的算法)
這個場景是假設有一個redis cluster,有5個redis master實例。而後執行以下步驟獲取一把鎖:
1)獲取當前時間戳,單位是毫秒
2)跟上面相似,輪流嘗試在每一個master節點上建立鎖,過時時間較短,通常就幾十毫秒
3)嘗試在大多數節點上創建一個鎖,好比5個節點就要求是3個節點(n / 2 +1)
4)客戶端計算創建好鎖的時間,若是創建鎖的時間小於超時時間,就算創建成功了
5)要是鎖創建失敗了,那麼就依次刪除這個鎖
6)只要別人創建了一把分佈式鎖,你就得不斷輪詢去嘗試獲取鎖
sql
其實能夠作的比較簡單,就是某個節點嘗試建立臨時znode,此時建立成功了就獲取了這個鎖;這個時候別的客戶端來建立鎖會失敗,只能註冊個監聽器監聽這個鎖。釋放鎖就是刪除這個znode,一旦釋放掉就會通知客戶端,而後有一個等待着的客戶端就能夠再次從新枷鎖。
固然也能夠基於建立有序臨時節點,這樣的話,就能夠實現公平鎖。先建立鎖的就能夠先獲取鎖。數據庫
redis分佈鎖,須要字段不斷去嘗試獲取鎖,比較消耗性能
zk分佈鎖,獲取不到鎖,註冊個監聽器便可,不須要不斷主動嘗試獲取鎖,性能開銷較小
而且,若是是redis獲取鎖的那個客戶端bug了或者掛了,那麼只能等待超時時間以後才能釋放鎖;而zk的話,由於建立的是臨時znode,只要客戶端掛了,znode就沒了,此時就自動釋放鎖瀏覽器
瀏覽器有個cookie,在一段時間內這個cookie都存在,而後每次發請求過來都帶上一個特殊的jsessionid cookie,就根據這個東西,在服務端能夠維護一個對應的session域,裏面能夠放點兒數據。
通常只要你沒關掉瀏覽器,cookie還在,那麼對應的那個session就在,可是cookie沒了,session就沒了。常見於什麼購物車之類的東西,還有登陸狀態保存之類的。
這個其實還挺方便的,就是使用session的代碼跟之前同樣,仍是基於tomcat原生的session支持便可,而後就是用一個叫作Tomcat RedisSessionManager的東西,讓全部咱們部署的tomcat都將session數據存儲到redis便可。
在tomcat的配置文件中,配置一下
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="{redis.host}" port="{redis.port}" database="{redis.dbnum}" maxInactiveInterval="60"/>
搞一個相似上面的配置便可,你看是否是就是用了RedisSessionManager,而後指定了redis的host和 port就ok了。
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="{redis.host}" port="{redis.port}" database="{redis.dbnum}" maxInactiveInterval="60"/>
還能夠用上面這種方式基於redis哨兵支持的redis高可用集羣來保存session數據,都是能夠的的
分佈式會話的這個東西重耦合在tomcat中,若是我要將web容器遷移成jetty,難道從新把jetty都配置一遍嗎?
spring基本上是一站式解決方案了,spirng cloud作微服務了,spring boot作腳手架了,因此用sping session是一個很好的選擇。
pom.xml <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.2.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency>
spring配置文件種 <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="600"/> </bean> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="100" /> <property name="maxIdle" value="10" /> </bean> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <property name="hostName" value="${redis_hostname}"/> <property name="port" value="${redis_port}"/> <property name="password" value="${redis_pwd}" /> <property name="timeout" value="3000"/> <property name="usePool" value="true"/> <property name="poolConfig" ref="jedisPoolConfig"/> </bean>
web.xml <filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
將一個系統拆分爲多個子系統,用微服務dubbo,spring cloud來搞,而後每隔系統連一個數據庫,這樣原來是一個庫,如今多個數據庫,在必定程度上提升併發能力
對於應對高併發,必定得用緩存。大部分的高併發場景,都是讀多寫少你徹底能夠在數據庫和緩存裏都寫一份,而後讀的時候大量走緩存不就得了。畢竟redis輕輕鬆鬆單機幾萬的併發啊。沒問題的。因此你能夠考慮考慮你的項目裏,那些承載主要請求的讀場景,怎麼用緩存來抗高併發。
可能仍是會出現高併發寫的場景,好比說一個業務操做裏要頻繁搞數據庫幾十次,增刪改增刪改,瘋了。那高併發絕對搞掛系統,要是用redis來承載寫那確定不行,人家是緩存,數據隨時就被LRU了,數據格式還無比簡單,沒有事務支持。因此該用mysql還得用mysql。這個時候可使用MQ,大量的寫請求灌入MQ裏,排隊慢慢玩兒,後邊系統消費後慢慢寫,控制在mysql承載範圍以內。因此你得考慮考慮你的項目裏,那些承載複雜寫業務邏輯的場景裏,如何用MQ來異步寫,提高併發性。MQ單機抗幾萬併發也是能夠的。
能到了最後數據庫層面仍是免不了抗高併發的要求,好吧,那麼就將一個數據庫拆分爲多個庫,多個庫來抗更高的併發;而後將一個表拆分爲多個表,每一個表的數據量保持少一點,提升sql跑的性能。
這個就是說大部分時候數據庫可能也是讀多寫少,不必全部請求都集中在一個庫上吧,能夠搞個主從架構,主庫寫入,從庫讀取,搞一個讀寫分離。讀流量太多的時候,還能夠加更多的從庫。
es是分佈式的,能夠隨便擴容,分佈式自然就能夠支撐高併發,由於動不動就能夠擴容加機器來抗更高的併發。那麼一些比較簡單的查詢、統計類的操做,能夠考慮用es來承載,還有一些全文搜索類的操做,也能夠考慮用es來承載。
分庫:就是一個庫通常而言,最多支撐到併發2000,若是併發超過了2000,通常須要擴容了,並且一個健康的單庫併發值最好保持在每秒1000左右,不要太大。那麼你能夠將一個庫的數據拆分到多個庫中,訪問的時候就訪問一個庫好了。
分表:是把一個表的數據放到多個表中,而後查詢的時候你就查一個表。好比按照用戶id來分表,將一個用戶的數據就放在一個表中。而後操做的時候你對一個用戶就操做那個表就行了。這樣能夠控制每一個表的數據量在可控的範圍內,好比每一個表就固定在200萬之內。
水平拆分的意思,就是把一個表的數據給弄到多個庫的多個表裏去,可是每一個庫的表結構都同樣,只不過每一個庫表放的數據是不一樣的,全部庫表的數據加起來就是所有數據。水平拆分的意義,就是將數據均勻放更多的庫裏,而後用多個庫來抗更高的併發,還有就是用多個庫的存儲容量來進行擴容。
垂直拆分的意思,就是把一個有不少字段的表給拆分紅多個表,或者是多個庫上去。每一個庫表的結構都不同,每一個庫表都包含部分字段。通常來講,會將較少的訪問頻率很高的字段放到一個表裏去,而後將較多的訪問頻率很低的字段放到另一個表裏去。由於數據庫是有緩存的,你訪問頻率高的行字段越少,就能夠在緩存裏緩存更多的行,性能就越好。這個通常在表層面作的較多一些。
能夠是按照id得hash來分(擴容起來特別困難),也能夠按照時間的range來分。
(1)停機遷移
系統停掉,而後將單庫單表的數據讀取出來,而後插入到數據庫中間件種。修改系統的配置,
(2)雙寫遷移
就是在線上系統裏面,以前全部寫庫的地方,增刪改操做,都除了對老庫增刪改,都加上對新庫的增刪改,這就是所謂雙寫,同時寫倆庫,老庫和新庫。
若是擴容了,原來擴容3個庫,每一個庫4個表,如今須要擴容成6個庫,每一個庫須要12個表。
(1)停機擴容(極度不靠譜)
由於既然分庫分表就說明數據量實在是太大了,可能多達幾億條,甚至幾十億,你這麼玩兒,可能會出問題。
(2)優化方案
擴容的時候,申請增長更多的數據庫服務器,裝好mysql,倍數擴容,4臺服務器,擴到8臺服務器,16臺服務器。這樣的話,路由規則是不須要修改的
由dba負責將原先數據庫服務器的庫,遷移到新的數據庫服務器上去,不少工具,庫遷移,比較便捷
而後修改一些配置便可,調整遷移的庫所在數據庫服務器地址
從新發布系統,上線,原先的路由規則變都不用變,直接能夠基於2倍的數據庫服務器的資源,繼續進行線上系統的提供服務
就是指在系統裏每次獲得一個id,都是往一個庫的一個表裏插入一條沒什麼業務含義的數據,而後獲取一個數據庫自增的一個id。拿到這個id以後再往對應的分庫分表裏去寫入。
這個方案的好處就是方便簡單,誰都會用;缺點就是單庫生成自增id,要是高併發的話,就會有瓶頸的;若是你硬是要改進一下,那麼就專門開一個服務出來,這個服務每次就拿到當前id最大值,而後本身遞增幾個id,一次性返回一批id,而後再把當前最大id值修改爲遞增幾個id以後的一個值;可是不管怎麼說都是基於單個數據庫。
適合的場景:你分庫分表就倆緣由,要不就是單庫併發過高,要不就是單庫數據量太大;除非是你併發不高,可是數據量太大致使的分庫分表擴容,你能夠用這個方案,由於可能每秒最高併發最多就幾百,那麼就走單獨的一個庫和表生成自增主鍵便可。
併發很低,幾百/s,可是數據量大,幾十億的數據,因此須要靠分庫分表來存放海量的數據
好處就是本地生成,不要基於數據庫來了;很差之處就是,uuid太長了,做爲主鍵性能太差了,不適合用於主鍵。
適合的場景:若是你是要隨機生成個什麼文件名了,編號之類的,你能夠用uuid,可是做爲主鍵是不能用uuid的。
獲取當前時間便可,可是問題是,併發很高的時候,好比一秒併發幾千,會有重複的狀況,這個是確定不合適的。基本就不用考慮了。
適合的場景:通常若是用這個方案,是將當前時間跟不少其餘的業務字段拼接起來,做爲一個id,若是業務上你以爲能夠接受,那麼也是能夠的。你能夠將別的業務字段值跟當前時間拼接起來,組成一個全局惟一的編號,訂單編號,時間戳 + 用戶id + 業務含義編碼
twitter開源的分佈式id生成算法,就是把一個64位的long型的id,1個bit是不用的,用其中的41 bit做爲毫秒數,用10 bit做爲工做機器id,12 bit做爲序列號
主庫將變動寫binlog日誌,而後從庫鏈接到主庫以後,從庫有一個IO線程,將主庫的binlog日誌拷貝到本身本地,寫入一箇中繼日誌中。接着從庫中有一個SQL線程會從中繼日誌讀取binlog,而後執行binlog日誌中的內容,也就是在本身本地再次執行一遍SQL,這樣就能夠保證本身跟主庫的數據是同樣的。
從庫同步主庫數據的過程是串行化(單線程)的,也就是說主庫上並行的操做,在從庫上會串行執行。因此這就是一個很是重要的點了,因爲從庫從主庫拷貝日誌以及串行執行SQL的特色,在高併發場景下,從庫的數據必定會比主庫慢一些,是有延時的。因此常常出現,剛寫入主庫的數據多是讀不到的,要過幾十毫秒,甚至幾百毫秒才能讀取到。
並且這裏還有另一個問題,就是若是主庫忽然宕機,而後剛好數據還沒同步到從庫,那麼有些數據可能在從庫上是沒有的,有些數據可能就丟失了。
半同步復:指的就是主庫寫入binlog日誌以後,就會將強制此時當即將數據同步到從庫,從庫將日誌寫入本身本地的relay log以後,接着會返回一個ack給主庫,主庫接收到至少一個從庫的ack以後纔會認爲寫操做完成了
通常來講,若是主從延遲較爲嚴重
(1)、分庫,將一個主庫拆分爲4個主庫,每一個主庫的寫併發就500/s,此時主從延遲能夠忽略不計
(2)、打開mysql支持的並行複製,多個庫並行複製,若是說某個庫的寫入併發就是特別高,單庫寫併發達到了2000/s,並行複製仍是沒意義。28法則,不少時候好比說,就是少數的幾個訂單表,寫入了2000/s,其餘幾十個表10/s。
(3)、若是確實是存在必須先插入,立馬要求就查詢到,而後立馬就要反過來執行一些操做,對這個查詢設置直連主庫。不推薦這種方法,你這麼搞致使讀寫分離的意義就喪失了