如果要「保證」數據的安全性,那麼會帶來開銷的進一步提升,以至於使用redis帶來的性能優勢都會喪失。正確的做法是區分不同的業務,使得並不需要「保證」數據一致性的場合,可以使用redis優化。而敏感的場合依然使用mysql。
⑵ SQLServer資料庫中如何保持數據一致性
根據實現策略的不同,主要有快照復制、事務復制、合並復制等三種類型。這三種復制類型,各有各的特點,分別適用於不同的場合。一般來說,在考慮採用哪種復制類型比較合適的時候,主要考慮的是性能與數據同步的時間間復制是SQLServer資料庫中保持數據一致性的一種手段。根據實現策略的不同,主要有快照復制、事務復制、合並復制等三種類型。這三種復制類型,各有各的特點,分別適用於不同的場合。一般來說,在考慮採用哪種復制類型比較合適的時候,主要考慮的是性能與數據同步的時間間隔。那麼在什麼情形下比較適用快照復制呢?筆者就跟大家來討論一下這個話題。 為了在恰當的時候採用快照復制,資料庫管理員首先需要知道快照復制的特點。快照復制是指將數據以特定時刻的瞬時狀態轉發,而不堅實對數據的更新。在發生同步時,將生成完整的快照並將其發送到訂閱伺服器。簡單的說,快照復制就是每隔一段時間發生數據同步操作。而不是發布伺服器的數據一有更新就出發這個快照復制。顯然這種快照復制的數據同步性稍微差一點。在訂閱伺服器與發布伺服器之間有一段時間會存在數據不一致的情況。但是這可以在很大程度上提高訂閱伺服器與發布伺服器的性能。這就好像汽車運輸。採用快照復制的話可以將一個集裝箱裝滿後在送貨,而不是有多少送多少。掌握這個資料庫復快照復制的具體特點之後,資料庫管理員就可以來考慮在什麼情況下,採用快照復制更加的合理。 一、數據更改比較少的系統中。 快照復制與其他復制相比最主要的缺陷就是資料庫中的數據無法及時同發布伺服器一致。為此如果發布伺服器中的內容很少更改的話,顯然此時採用快照復制是比較合理的。此時採用快照復制的話,不僅數據一致性延遲的負面效應會越來越不明顯,同時可以提高發布伺服器與訂閱伺服器的性能。如在實際工作中,經常會遇到這樣的客戶。如一家企業在各地都有辦事處或者銷售機構,就像肯德基一樣,各地的產品價格基本上都是相同的,不怎麼會更改。即使更改的話,各地也是統一調整。由於此時產品價格表更改的比較少,那麼在企業總部的資料庫服務與各地的訂閱伺服器之間,採用快照復制的形式就會比較合適。其實類似的情況有很多。如不少的服裝企業,像李寧、耐克等等,他們不僅自己生產,而且在各地又有自己的銷售辦事處。在價格方面也是統一的。在這種情況下,採用快照復制往往能夠提高資料庫復制的性能,同時又不影響其使用。 二、在某個時段內會出現數據大量的更改。 需要補充說明的一點是,上面說到的數據不怎麼發生更改,指的是數據的延續性更改。如在一年中,每天或者每個小時更改的數據都比較平均。此時採用快照復制不怎麼合適。但是如果數據的更改集中在一個時段內。而其他時間中資料庫的內容不會有多大的更改。此時採用快照復制是可行的。如一些決策性系統,往往在起初導入數據的時候,需要進行大量的更改。而等到數據導入完畢,在大家對數據進行分析時,則資料庫中的內容基本上保持不變。在這種情況下,筆者認為只要數據的更新集中在一個固定的時段,此時採用快照復制仍然是可行的。 再如上面這個KFC或者服裝企業的案例中,如果市場部門維護一個產品的價格,而且這些價格往往在一個固定的時間進行幾次更新。如在換季的時候會進行一些促銷。此時資料庫管理員可以在數據更新完畢後立即執行復制完成的數據快照。所以,以數據更新來判斷是否適合採用快照復制,標准並不是數據的更新量。像上面提到的分析決策系統,其起初的數據更新量可能比有些資料庫系統幾年的數據更新量都要大。筆者認為,主要是根據數據更新的頻率來進行判斷。如果數據更新的比較頻繁,那麼即使數據更新的數據不多,像那種細水長流似的更新,則不適合採用快照復制。而那些井噴似的數據更新,所有的更新都集中在一個固定的時刻,那麼此時採用快照復制是比較合理的。 三、在一段時間內是否允許具有相對發布伺服器已過時的數據副本? 現在不少超市也已經連鎖了,如世紀聯華等等。為了提高利潤,增加市場的份額,這些超市紛紛推出了沖值卡,即消費者先將一定金額的人民幣打入到沖值卡中。然後每次消費完成後從卡中扣費。但前些天經常有新聞報道,說一個客戶的消費卡在一家聯華超市掛失了。但是撿到這張卡的人仍然可以在其他的聯華超市中消費。為此消費者就想不明白了,為什麼掛失了的消費卡仍然可以在其他超市中消費?掛失後的損失該由誰來承擔呢?其實這就使超市在不適當的時候採用了快照復制所造成的。由於採用快照復制,在各個聯華超市的資料庫之間數據無法在短時間內取得一致。如有些商戶說掛失當日之內的損失他們不承擔,這就說明他們可能是每天下班後進行一次快照復制。一般情況下這不會有問題。但是像遇到消費卡被偷了等情況,就會遇到類似的問題了。 所以,在考慮是否適合採用快照復制的時候,還需要考慮在一段時間內是否允許具有相對發布伺服器來說已過時的數據副本。如果不允許的話,那麼就不允許採用這個快照復制。如果允許的話,那麼資料庫管理員就需要評估這段時間最長是多少。如果是24個小時,那麼就需要每隔24小時進行一次快照復制。但是需要注意的是,如果時間的間隔比較短,如才允許十分鍾的數據延遲,那麼採用快照復制就沒有必要了。此時採用事務復制或則和合並復制可能更加的合適。 四、復制少量的數據。 快照復制跟其他復制類型相比,還有一個比較顯著的特點,即當發生數據同步時,將生成完整的快照並將其從發布伺服器傳送到訂閱伺服器。這是一個什麼概念呢?如訂閱伺服器中有10G的數據,而在一個快照復制的周期內,只有1M的數據發生了更改。此時發生快照復制的話,資料庫系統會將10G的數據都傳送到訂閱伺服器上。此時更改的數據只有1M,卻需要在網路上傳送10G的數據流量,顯然會對企業的網路產生比較大的壓力。由於在發布伺服器上快照復制的連續開銷低於事務復制的開銷,一次資料庫系統不會啟用跟蹤增量更改。但是像這種情況,如果要復制的數據量非常的大,而平時的更新又不多。此時資料庫系統要生成和應用快照,就將耗用大量的資源,包括網路資源和伺服器資源。所以說,當發布伺服器中的數據比較多時,採用快照復制不怎麼合適。因為此時網路傳輸反而會成為其最重大的瓶頸資源。相反若能夠採取細水長流的事務復制策略,那麼對於企業網路性能的影響就會小的多,甚至可以忽略不計。 所以在採用快照復制的時候,資料庫管理員一定要明白,快照復制會傳送整個資料庫對象。從而在快照復制傳輸過程中會侵蝕大量的網路帶寬,從而明顯的降低企業網路的性能,甚至導致網路擁塞。有時候為了保障快照能夠准確、迅速的傳遞到其他的訂閱伺服器,還不得不採用VPN等技術來保障傳輸的准確性。為此,筆者認為只有發布伺服器的資料庫並不是很大的情況下,才適合採用快照復制。否則的話,採用快照復制是得不償失。 從以上的分析中,可以得到一個結論。在考慮採用快照復制是否合適時,往往不能夠採用一個指標來判斷。而需要考慮多個因素,如資料庫的大小、數據更新的頻率、允許數據延遲的時間等等因素來進行判斷。最後在數據的一致性與資料庫的性能之間取得一個均衡。說實話,對於大部分資料庫管理員來說,要做出一個抉擇,確實有困難。因為這沒有固定的指標可以拿來參考。如資料庫容量小於多少時該採用快照復制。任何一個資料庫管理專家都不能夠下這個結論。所以在掌握影響其選擇的相關因素外,就要依靠資料庫管理員的經驗了。在遇到類似的選擇題時,往往經驗可以幫助管理員迅速解決問題。最後需要提醒的是,無論最終採取了什麼方案,最好能夠持續跟蹤一段時間,看看自己的選擇是否合理。
⑶ 如何保證資料庫緩存的最終一致性
對於互聯網業務來說,傳統的直接訪問資料庫方式,主要通過數據分片、一主多從等方式來扛住讀寫流量,但隨著數據量的積累和流量的激增,僅依賴資料庫來承接所有流量,不僅成本高、效率低、而且還伴隨著穩定性降低的風險。
鑒於大部分業務通常是讀多寫少(讀取頻率遠遠高於更新頻率),甚至存在讀操作數量高出寫操作多個數量級的情況。因此, 在架構設計中,常採用增加緩存層來提高系統的響應能力 ,提升數據讀寫性能、減少資料庫訪問壓力,從而提升業務的穩定性和訪問體驗。
根據 CAP 原理,分布式系統在可用性、一致性和分區容錯性上無法兼得,通常由於分區容錯無法避免,所以一致性和可用性難以同時成立。對於緩存系統來說, 如何保證其數據一致性是一個在應用緩存的同時不得不解決的問題 。
需要明確的是,緩存系統的數據一致性通常包括持久化層和緩存層的一致性、以及多級緩存之間的一致性,這里我們僅討論前者。持久化層和緩存層的一致性問題也通常被稱為雙寫一致性問題,「雙寫」意為數據既在資料庫中保存一份,也在緩存中保存一份。
對於一致性來說,包含強一致性和弱一致性 ,強一致性保證寫入後立即可以讀取,弱一致性則不保證立即可以讀取寫入後的值,而是盡可能的保證在經過一定時間後可以讀取到,在弱一致性中應用最為廣泛的模型則是最終一致性模型,即保證在一定時間之後寫入和讀取達到一致的狀態。對於應用緩存的大部分場景來說,追求的則是最終一致性,少部分對數據一致性要求極高的場景則會追求強一致性。
為了達到最終一致性,針對不同的場景,業界逐步形成了下面這幾種應用緩存的策略。
— 1 —
Cache-Aside
Cache-Aside 意為旁路緩存模式,是應用最為廣泛的一種緩存策略。下面的圖示展示了它的讀寫流程,來看看它是如何保證最終一致性的。在讀請求中,首先請求緩存,若緩存命中(cache hit),則直接返回緩存中的數據;若緩存未命中(cache miss),則查詢資料庫並將查詢結果更新至緩存,然後返回查詢出的數據(demand-filled look-aside )。在寫請求中,先更新資料庫,再刪除緩存(write-invalidate)。
1、為什麼刪除緩存,而不是更新緩存?
在 Cache-Aside 中,對於讀請求的處理比較容易理解,但在寫請求中,可能會有讀者提出疑問,為什麼要刪除緩存,而不是更新緩存?站在符合直覺的角度來看,更新緩存是一個容易被理解的方案,但站在性能和安全的角度,更新緩存則可能會導致一些不好的後果。
首先是性能 ,當該緩存對應的結果需要消耗大量的計算過程才能得到時,比如需要訪問多張資料庫表並聯合計算,那麼在寫操作中更新緩存的動作將會是一筆不小的開銷。同時,當寫操作較多時,可能也會存在剛更新的緩存還沒有被讀取到,又再次被更新的情況(這常被稱為緩存擾動),顯然,這樣的更新是白白消耗機器性能的,會導致緩存利用率不高。
而等到讀請求未命中緩存時再去更新,也符合懶載入的思路,需要時再進行計算。刪除緩存的操作不僅是冪等的,可以在發生異常時重試,而且寫-刪除和讀-更新在語義上更加對稱。
其次是安全 ,在並發場景下,在寫請求中更新緩存可能會引發數據的不一致問題。參考下面的圖示,若存在兩個來自不同線程的寫請求,首先來自線程 1 的寫請求更新了資料庫(step 1),接著來自線程 2 的寫請求再次更新了資料庫(step 3),但由於網路延遲等原因,線程 1 可能會晚於線程 2 更新緩存(step 4 晚於 step 3),那麼這樣便會導致最終寫入資料庫的結果是來自線程 2 的新值,寫入緩存的結果是來自線程 1 的舊值,即緩存落後於資料庫,此時再有讀請求命中緩存(step 5),讀取到的便是舊值。
2、為什麼先更新資料庫,而不是先刪除緩存?
另外,有讀者也會對更新資料庫和刪除緩存的時序產生疑問,那麼為什麼不先刪除緩存,再更新資料庫呢?在單線程下,這種方案看似具有一定合理性,這種合理性體現在刪除緩存成功。
但更新資料庫失敗的場景下,盡管緩存被刪除了,下次讀操作時,仍能將正確的數據寫回緩存,相對於 Cache-Aside 中更新資料庫成功,刪除緩存失敗的場景來說,先刪除緩存的方案似乎更合理一些。那麼,先刪除緩存有什麼問題呢?
問題仍然出現在並發場景下,首先來自線程 1 的寫請求刪除了緩存(step 1),接著來自線程 2 的讀請求由於緩存的刪除導致緩存未命中,根據 Cache-Aside 模式,線程 2 繼而查詢資料庫(step 2),但由於寫請求通常慢於讀請求,線程 1 更新資料庫的操作可能會晚於線程 2 查詢資料庫後更新緩存的操作(step 4 晚於 step 3),那麼這樣便會導致最終寫入緩存的結果是來自線程 2 中查詢到的舊值,而寫入資料庫的結果是來自線程 1 的新值,即緩存落後於資料庫,此時再有讀請求命中緩存( step 5 ),讀取到的便是舊值。
另外,先刪除緩存,由於緩存中數據缺失,加劇資料庫的請求壓力,可能會增大緩存穿透出現的概率。
3、如果選擇先刪除緩存,再更新資料庫,那如何解決一致性問題呢?
為了避免「先刪除緩存,再更新資料庫」這一方案在讀寫並發時可能帶來的緩存臟數據,業界又提出了延時雙刪的策略,即在更新資料庫之後,延遲一段時間再次刪除緩存,為了保證第二次刪除緩存的時間點在讀請求更新緩存之後,這個延遲時間的經驗值通常應稍大於業務中讀請求的耗時。
延遲的實現可以在代碼中 sleep 或採用延遲隊列。顯而易見的是,無論這個值如何預估,都很難和讀請求的完成時間點准確銜接,這也是延時雙刪被詬病的主要原因。
4、那麼 Cache-Aside 存在數據不一致的可能嗎?
在 Cache-Aside 中,也存在數據不一致的可能性。在下面的讀寫並發場景下,首先來自線程 1 的讀請求在未命中緩存的情況下查詢資料庫(step 1),接著來自線程 2 的寫請求更新資料庫(step 2),但由於一些極端原因,線程 1 中讀請求的更新緩存操作晚於線程 2 中寫請求的刪除緩存的操作(step 4 晚於 step 3),那麼這樣便會導致最終寫入緩存中的是來自線程 1 的舊值,而寫入資料庫中的是來自線程 2 的新值,即緩存落後於資料庫,此時再有讀請求命中緩存(step 5),讀取到的便是舊值。
這種場景的出現,不僅需要緩存失效且讀寫並發執行,而且還需要讀請求查詢資料庫的執行早於寫請求更新資料庫,同時讀請求的執行完成晚於寫請求。足以見得,這種 不一致場景產生的條件非常嚴格,在實際的生產中出現的可能性較小 。
除此之外,在並發環境下,Cache-Aside 中也存在讀請求命中緩存的時間點在寫請求更新資料庫之後,刪除緩存之前,這樣也會導致讀請求查詢到的緩存落後於資料庫的情況。
雖然在下一次讀請求中,緩存會被更新,但如果業務層面對這種情況的容忍度較低,那麼可以採用加鎖在寫請求中保證「更新資料庫&刪除緩存」的串列執行為原子性操作(同理也可對讀請求中緩存的更新加鎖)。 加鎖勢必會導致吞吐量的下降,故採取加鎖的方案應該對性能的損耗有所預期。
— 2 —
補償機制
我們在上面提到了,在 Cache-Aside 中可能存在更新資料庫成功,但刪除緩存失敗的場景,如果發生這種情況,那麼便會導致緩存中的數據落後於資料庫,產生數據的不一致的問題。
其實,不僅 Cache-Aside 存在這樣的問題,在延時雙刪等策略中也存在這樣的問題。針對可能出現的刪除失敗問題,目前業界主要有以下幾種補償機制。
1、刪除重試機制
由於同步重試刪除在性能上會影響吞吐量,所以常通過引入消息隊列,將刪除失敗的緩存對應的 key 放入消息隊列中,在對應的消費者中獲取刪除失敗的 key ,非同步重試刪除。這種方法在實現上相對簡單,但由於刪除失敗後的邏輯需要基於業務代碼的 trigger 來觸發 ,對業務代碼具有一定入侵性。
鑒於上述方案對業務代碼具有一定入侵性,所以需要一種更加優雅的解決方案,讓緩存刪除失敗的補償機制運行在背後,盡量少的耦合於業務代碼。一個簡單的思路是通過後台任務使用更新時間戳或者版本作為對比獲取資料庫的增量數據更新至緩存中,這種方式在小規模數據的場景可以起到一定作用,但其擴展性、穩定性都有所欠缺。
一個相對成熟的方案是基於 MySQL 資料庫增量日誌進行解析和消費,這里較為流行的是阿里巴巴開源的作為 MySQL binlog 增量獲取和解析的組件 canal(類似的開源組件還有 Maxwell、Databus 等)。
canal sever 模擬 MySQL slave 的交互協議,偽裝為 MySQL slave,向 MySQL master 發送 mp 協議,MySQL master 收到 mp 請求,開始推送 binary log 給 slave (即 canal sever ),canal sever 解析 binary log 對象(原始為 byte 流),可由 canal client 拉取進行消費,同時 canal server 也默認支持將變更記錄投遞到 MQ 系統中,主動推送給其他系統進行消費。
在 ack 機制的加持下,不管是推送還是拉取,都可以有效的保證數據按照預期被消費。當前版本的 canal 支持的 MQ 有 Kafka 或者 RocketMQ。另外, canal 依賴 ZooKeeper 作為分布式協調組件來實現 HA ,canal 的 HA 分為兩個部分:
那麼,針對緩存的刪除操作便可以在 canal client 或 consumer 中編寫相關業務代碼來完成。這樣,結合資料庫日誌增量解析消費的方案以及 Cache-Aside 模型,在讀請求中未命中緩存時更新緩存(通常這里會涉及到復雜的業務邏輯),在寫請求更新資料庫後刪除緩存,並基於日誌增量解析來補償資料庫更新時可能的緩存刪除失敗問題,在絕大多數場景下,可以有效的保證緩存的最終一致性。
另外需要注意的是,還應該隔離事務與緩存,確保資料庫入庫後再進行緩存的刪除操作。 比如考慮到資料庫的主從架構,主從同步及讀從寫主的場景下,可能會造成讀取到從庫的舊數據後便更新了緩存,導致緩存落後於資料庫的問題,這就要求對緩存的刪除應該確保在資料庫操作完成之後。所以,基於 binlog 增量日誌進行數據同步的方案,可以通過選擇解析從節點的 binlog,來避免主從同步下刪除緩存過早的問題。
3、數據傳輸服務 DTS
— 3 —
Read-Through
Read-Through 意為讀穿透模式,它的流程和 Cache-Aside 類似,不同點在於 Read-Through 中多了一個訪問控制層,讀請求只和該訪問控制層進行交互,而背後緩存命中與否的邏輯則由訪問控制層與數據源進行交互,業務層的實現會更加簡潔,並且對於緩存層及持久化層交互的封裝程度更高,更易於移植。
— 4 —
Write-Through
Write-Through 意為直寫模式,對於 Write-Through 直寫模式來說,它也增加了訪問控制層來提供更高程度的封裝。不同於 Cache-Aside 的是,Write-Through 直寫模式在寫請求更新資料庫之後,並不會刪除緩存,而是更新緩存。
這種方式的 優勢在於讀請求過程簡單 ,不需要查詢資料庫更新緩存等操作。但其劣勢也非常明顯,除了上面我們提到的更新資料庫再更新緩存的弊端之外,這種方案還會造成更新效率低,並且兩個寫操作任何一次寫失敗都會造成數據不一致。
如果要使用這種方案, 最好可以將這兩個操作作為事務處理,可以同時失敗或者同時成功,支持回滾,並且防止並發環境下的不一致 。另外,為了防止緩存擾動的頻發,也可以給緩存增加 TTL 來緩解。
站在可行性的角度,不管是 Write-Through 模式還是 Cache-Aside 模式,理想狀況下都可以通過分布式事務保證緩存層數據與持久化層數據的一致性,但在實際項目中,大多都對一致性的要求存在一些寬容度,所以在方案上往往有所折衷。
Write-Through 直寫模式適合寫操作較多,並且對一致性要求較高的場景,在應用 Write-Through 模式時,也需要通過一定的補償機制來解決它的問題。首先,在並發環境下,我們前面提到了先更新資料庫,再更新緩存會導致緩存和資料庫的不一致,那麼先更新緩存,再更新資料庫呢?
這樣的操作時序仍然會導致下面這樣線程 1 先更新緩存,最後更新資料庫的情況,即由於線程 1 和 線程 2 的執行不確定性導致資料庫和緩存的不一致。這種由於線程競爭導致的緩存不一致,可以通過分布式鎖解決,保證對緩存和資料庫的操作僅能由同一個線程完成。對於沒有拿到鎖的線程,一是通過鎖的 timeout 時間進行控制,二是將請求暫存在消息隊列中順序消費。
在下面這種並發執行場景下,來自線程 1 的寫請求更新了資料庫,接著來自線程 2 的讀請求命中緩存,接著線程 1 才更新緩存,這樣便會導致線程 2 讀取到的緩存落後於資料庫。同理,先更新緩存後更新資料庫在寫請求和讀請求並發時,也會出現類似的問題。面對這種場景,我們也可以加鎖解決。
另在,在 Write-Through 模式下,不管是先更新緩存還是先更新資料庫,都存在更新緩存或者更新資料庫失敗的情況,上面提到的重試機制和補償機制在這里也是奏效的。
— 5 —
Write-Behind
Write behind 意為非同步回寫模式,它也具有類似 Read-Through/Write-Through 的訪問控制層,不同的是,Write behind 在處理寫請求時,只更新緩存而不更新資料庫,對於資料庫的更新,則是通過批量非同步更新的方式進行的,批量寫入的時間點可以選在資料庫負載較低的時間進行。
在 Write-Behind 模式下,寫請求延遲較低,減輕了資料庫的壓力,具有較好的吞吐性。但資料庫和緩存的一致性較弱,比如當更新的數據還未被寫入資料庫時,直接從資料庫中查詢數據是落後於緩存的。同時,緩存的負載較大,如果緩存宕機會導致數據丟失,所以需要做好緩存的高可用。顯然,Write behind 模式下適合大量寫操作的場景,常用於電商秒殺場景中庫存的扣減。
— 6 —
Write-Around
如果一些非核心業務,對一致性的要求較弱,可以選擇在 cache aside 讀模式下增加一個緩存過期時間,在寫請求中僅僅更新資料庫,不做任何刪除或更新緩存的操作,這樣,緩存僅能通過過期時間失效。這種方案實現簡單,但緩存中的數據和資料庫數據一致性較差,往往會造成用戶的體驗較差,應慎重選擇。
— 7 —
總結
在解決緩存一致性的過程中,有多種途徑可以保證緩存的最終一致性,應該根據場景來設計合適的方案,讀多寫少的場景下,可以選擇採用「Cache-Aside 結合消費資料庫日誌做補償」的方案,寫多的場景下,可以選擇採用「Write-Through 結合分布式鎖」的方案 ,寫多的極端場景下,可以選擇採用「Write-Behind」的方案。
⑷ 如何面試後端程序員
計算機網路常見面試點總結
計算機網路常見問題回顧
2.1 TCP、UDP 協議的區別
2.2 在瀏覽器中輸入url地址 ->> 顯示主頁的過程
2.3 各種協議與HTTP協議之間的關系
2.4 HTTP長連接、短連接
2.5 TCP 三次握手和四次揮手
三 Linux
3.1-簡單介紹一下-linux-文件系統?
3.2 一些常見的 Linux 命令了解嗎?
四 MySQL
4.1 說說自己對於 MySQL 常見的兩種存儲引擎:MyISAM與InnoDB的理解
4.2 資料庫索引了解嗎?
4.3 對於大表的常見優化手段說一下
五 Redis
5.1 redis 簡介
5.2 為什麼要用 redis /為什麼要用緩存
5.3 為什麼要用 redis 而不用 map/guava 做緩存?
5.4 redis 和 memcached 的區別
5.5 redis 常見數據結構以及使用場景分析
5.6 redis 設置過期時間
5.7 redis 內存淘汰機制
5.8 redis 持久化機制(怎麼保證 redis 掛掉之後再重啟數據可以進行恢復)
5.9 緩存雪崩和緩存穿透問題解決方案
5.10 如何解決 Redis 的並發競爭 Key 問題
5.11 如何保證緩存與資料庫雙寫時的數據一致性?
六 Java
6.1 Java 基礎知識
6.2 Java 集合框架
6.3 Java多線程
6.4 Java虛擬機
6.5 設計模式
七 數據結構
八 演算法
8.1 舉個栗子(手寫快排)
九 Spring
9.1 Spring Bean 的作用域
9.2 Spring 事務中的隔離級別
9.3 Spring 事務中的事務傳播行為
9.4 AOP
9.5 IOC
不需要寫代碼就能衡量候選人的方法可能有一萬種。我常用的三個主要方法可以覆蓋許多不同的技能。在面試過程中,我們會談論候選人的經驗,要求他們做一些代碼審查,並與別人合作設計一個系統。
下面我會詳細解釋這個過程。
我試圖通過這些方法找到真正能夠勝任技術工作的候選人,並且他們必須能在單純的編程技能之外給團隊帶來價值。通常在一次面試中我能在大約一個小時內覆蓋所有三個部分。我有信心這些信息能讓我找到好的候選人。
1、深入挖掘他們的經驗
許多團隊已經這樣做了。他們會在面試一開始花幾分鍾,詢問候選人之前的工作,他們對工作的態度,等等。大多時候這就像隨意談話一樣。
但這是不對的。
記住這是面試。你需要盡可能地理解他們構建系統時使用的技術。
為了做好這一點,你需要在面試開始之前仔細閱讀他們的簡歷。這不是開玩笑,在面試開始之前至少花上10分鍾仔細閱讀(不是略讀)簡歷,如果花30分鍾時間則最好。要從簡歷中盡可能多了解些他們之前的項目,Google一下看看能否找到他們項目的公開信息。面試時挖掘背景信息所花的時間越少,就越能獲得好的效果。
在面試中,要求候選人談談他最近最感興趣的項目。要練習主動的傾聽,要學會參與。假裝你是他團隊中的一員,或者假裝你們是在做架構審查。你要努力了解他們構建的東西以及構建的方法。這樣做的好處和壞處是什麼?要讓候選人知道,不知道答案無所謂,但重要的是能勾起你的好奇心。
下面是我認為能獲得好的答案的問題:
你在項目中的職責是什麼?這個問題本身並不是決定性的。即使在項目中承擔的職責很小,他們也可能很適合你們的團隊。你的候選人也許正是因為沒能獲得重要的職責而在尋找新的機會。因此,知道他們過去的職責會很有幫助。
你從他人那裡獲得了什麼幫助?無法感受他人的幫助是個極其危險的信號。即使是個人項目,也一定需要別人的幫忙。你肯定不想要一個以自我為中心的同事。
給我介紹下那個功能的工作原理。解釋下數據的來源和去向、存儲方式以及這一切能帶給最終用戶的好處。這個問題的答案足以吸引你的好奇心。
這個項目中最糟糕的技術債務是什麼?好的工程師必須理解他們做出決定時需要付出的代價。問完這個問題,可以繼續詢問他們怎樣改正這些問題,或者尚未改正的理由。
有沒有出過生產環境下的bug或服務中斷?測試下他們是否理解bug的原因,以及團隊解決bug的方法。他們是否提前預期到了bug?下次怎樣才能避免同樣的問題發生?
他們怎樣與代碼的「作者」交流?交流是否有用?是否高效?是否友善?
他們會著重哪些問題?是否能明確表達出他們的疑問?他們是否會立即指出哪些無關緊要的問題?
他們是否善於閱讀自己不熟悉的代碼?
過於糟糕的拉取請求的描述或提交信息;
能用但無法自洽的代碼;
過於復雜的代碼(需要重構的代碼);
混亂的變數或方法名;
過度設計的代碼(即實際上永遠不會用到的功能)。
這一部分面試能讓你直接了解候選人的經驗。做好這一部分還能讓你了解他們如何感謝別人或責備別人。你將會了解到他們如何在兩難的工程問題上做出抉擇,他們會與你分享最近的教訓,他們與別人溝通技術的能力應該也很明顯。
如果他們選擇了不太適合的項目,可以考慮談論其他項目。所謂不太適合的意思是項目不夠復雜或他們記不清的情況。
注意,這一步要避免詢問類似於「告訴我你解決過的最難的bug」之類的問題。要求別人回憶系統的某一部分的具體原理會帶來大量的虛假負面判斷。人們不可能擁有他們修復的bug相關的一切知識,這種問題會給面試過程帶來很大壓力。
2、讓他們審查你們的代碼
這項活動一半是代碼審查一半是角色扮演。你可以藉此篩選出那些能夠提升團隊整體代碼質量並促進辦公室氛圍的人。
下面是代碼審查過程中需要關注的一些方面:
這個方法需要提前准備很多東西。你需要找到或編寫一段代碼供候選人審查。你還需要為你希望候選人找出的問題創建一個優先順序列表。不要讓面試管當場出題,一定要事先准備好。
在選擇需要審查的代碼時,不要選擇產品代碼。你的候選人沒有你所擁有的背景知識,這樣做實際上是將候選人與你的同事比較,而不是與其他候選人比較。
努力降低代碼示例中的復雜度。面試的時候,候選人沒有太多時間閱讀代碼,而且很可能他們並沒有想到會做代碼審查。熱身就要花很長時間。
在代碼中加入一兩個真實的bug,但不要強調找bug。一般來說,代碼審查並不是個好的找bug方法,特別是審查者從來沒有見過代碼的情況下。能自證的bug(如給需要數組的函數傳遞字元串)最好。在你的優先順序列表中,bug的優先順序應該是最低的,bug應該是給極其優秀的人的加分項。
最後,代碼應該做一些實際的事情。如果你的公司很出名,那可以選擇你的產品簡化版本。但如果你需要花大量時間為候選人提供背景信息的話還是算了。
最好的選擇要麼是虛構的代碼(也許可以選擇本文竭力避免的代碼面試中用到的代碼),要麼是開源代碼中的一個拉取請求。
一旦決定了要審查的代碼,你應該期待候選人找出下面這些東西:
如果代碼中沒有足夠的問題,就多加一些。
這里有個潛在的問題,我還沒有確定的答案。這個問題是:你是否應該提前將代碼發給候選人?
如果你這樣做,就又給那些有空閑時間的人以巨大的優勢。如果不這樣做,就要面臨增加面試壓力的風險。
我傾向於後者。好的面試官可以減輕壓力,方法之一就是讓面試者提前知道他們將做代碼審查,你也可以在審查開始之前介紹你的期望。
⑸ 如何保證緩存與資料庫雙寫時的數據一致性
一般來說,就是如果系統不是嚴格要求緩存+資料庫必須一致性的話,緩存可以稍微的跟資料庫偶爾有不一致的情況,最好不要做這個方案,讀請求和寫請求串列化,串到一個內存隊列里去,這樣就可以保證一定不會出現不一致的情況
串列化之後,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。
⑹ Redis 如何保持和 MySQL 數據一致
redis在啟動之後,從資料庫載入數據。
讀請求:
不要求強一致性的讀請求,走redis,要求強一致性的直接從mysql讀取
寫請求:
數據首先都寫到資料庫,之後更新redis(先寫redis再寫mysql,如果寫入失敗事務回滾會造成redis中存在臟數據)
在並發不高的情況下,讀操作優先讀取redis,不存在的話就去訪問MySQL,並把讀到的數據寫回Redis中;寫操作的話,直接寫MySQL,成功後再寫入Redis(可以在MySQL端定義CRUD觸發器,在觸發CRUD操作後寫數據到Redis,也可以在Redis端解析binlog,再做相應的操作)
在並發高的情況下,讀操作和上面一樣,寫操作是非同步寫,寫入Redis後直接返回,然後定期寫入MySQL
1.當更新數據時,如更新某商品的庫存,當前商品的庫存是100,現在要更新為99,先更新資料庫更改成99,然後刪除緩存,發現刪除緩存失敗了,這意味著資料庫存的是99,而緩存是100,這導致資料庫和緩存不一致。
解決方法:
這種情況應該是先刪除緩存,然後在更新資料庫,如果刪除緩存失敗,那就不要更新資料庫,如果說刪除緩存成功,而更新資料庫失敗,那查詢的時候只是從資料庫里查了舊的數據而已,這樣就能保持資料庫與緩存的一致性。
2.在高並發的情況下,如果當刪除完緩存的時候,這時去更新資料庫,但還沒有更新完,另外一個請求來查詢數據,發現緩存里沒有,就去資料庫里查,還是以上面商品庫存為例,如果資料庫中產品的庫存是100,那麼查詢到的庫存是100,然後插入緩存,插入完緩存後,原來那個更新資料庫的線程把資料庫更新為了99,導致資料庫與緩存不一致的情況
解決方法:
遇到這種情況,可以用隊列的去解決這個問,創建幾個隊列,如20個,根據商品的ID去做hash值,然後對隊列個數取摸,當有數據更新請求時,先把它丟到隊列里去,當更新完後在從隊列里去除,如果在更新的過程中,遇到以上場景,先去緩存里看下有沒有數據,如果沒有,可以先去隊列里看是否有相同商品ID在做更新,如果有也把查詢的請求發送到隊列里去,然後同步等待緩存更新完成。
這里有一個優化點,如果發現隊列里有一個查詢請求了,那麼就不要放新的查詢操作進去了,用一個while(true)循環去查詢緩存,循環個200MS左右,如果緩存里還沒有則直接取資料庫的舊數據,一般情況下是可以取到的。
1、讀請求時長阻塞
由於讀請求進行了非常輕度的非同步化,所以一定要注意讀超時的問題,每個讀請求必須在超時間內返回,該解決方案最大的風險在於可能數據更新很頻繁,導致隊列中擠壓了大量的更新操作在裡面,然後讀請求會發生大量的超時,最後導致大量的請求直接走資料庫,像遇到這種情況,一般要做好足夠的壓力測試,如果壓力過大,需要根據實際情況添加機器。
2、請求並發量過高
這里還是要做好壓力測試,多模擬真實場景,並發量在最高的時候QPS多少,扛不住就要多加機器,還有就是做好讀寫比例是多少
3、多服務實例部署的請求路由
可能這個服務部署了多個實例,那麼必須保證說,執行數據更新操作,以及執行緩存更新操作的請求,都通過nginx伺服器路由到相同的服務實例上
4、熱點商品的路由問題,導致請求的傾斜
某些商品的讀請求特別高,全部打到了相同的機器的相同丟列里了,可能造成某台伺服器壓力過大,因為只有在商品數據更新的時候才會清空緩存,然後才會導致讀寫並發,所以更新頻率不是太高的話,這個問題的影響並不是很大,但是確實有可能某些伺服器的負載會高一些。
img
搜索微信號(ID:芋道源碼),可以獲得各種 Java 源碼解析。
並且,回復【書籍】後,可以領取筆者推薦的各種 Java 從入門到架構的書籍。
⑺ Cache緩存,怎麼實現與資料庫同步
使用SQLDependency緩存依賴,以下是一個推SQL緩存依賴的例子,當資料庫更新後緩存會自動更新
void Page_Load()
{
DataTable movies=(DataTable)Cache["Movie"];
if(movie=null)
{
SqlDataAdapter adpter=new SqlDataAdatper("Select * From Movie",sqlConnection);
SqlCacheDependency sqlDepend=new SqlCacheDependency(adapter.SelectCommand);
movies=new DataTable();
//注意必須在adpter.Fill()前先建立SqlCacheDependency,否則無效
adpter.Fill(movies);
Cache.Insert("Movie",movies,sqlDepend);
}
}
⑻ Java中高並發下怎麼保證數據一致性
以mysql來說,可能出現臟讀、不可重復讀以及幻讀,mysql默認設置是可重復讀,即一次事務中不會讀取到不同的數據。
可以做如下操作:
1)打開兩個客戶端,均設置為RR;
2)在一個事務中,查詢某個操作查到某份數據;比如是某個欄位version=1存在數據;
3)在另一個事務中,刪除這份version=1的數據;刪除後,在2所屬的事務中查詢數據是沒有變化的,還是存在version=1的數據;
4)當我們在2所屬的事務中繼續更新數據,那麼會發現更新不了,明明我們就看到了這份version=1的數據;
緩存一致性:
緩存一致,與什麼一致?是與資料庫一致,對外查詢每個時刻一致;所以在針對於緩存與資料庫之間該先更新哪一個呢?可能有人覺得我先更新資料庫,再更新緩存不就行了嗎?但是有想過個問題嗎?
當用戶已經支付成功了,更新到資料庫,但是呢?你還在緩存中顯示未支付,在用戶點擊頻率很高並且資料庫壓力過大,來不及同步到緩存時,那你是不是很尷尬,這就是典型的不一致了。此時用戶再支付,那你又告訴他已經支付了,那他會把你罵死的
那該怎麼來做呢?我們可以這樣,先更新緩存再更新資料庫,那麼存在什麼問題呢?
1)緩存更新成功,但是資料庫更新失敗,而被其它的並發線程訪問到
2)緩存淘汰成功,但是資料庫更新失敗,這也會引發後期數據不一致
⑼ java多線程下如何保證數據的一致性
以mysql來說,可能出現臟讀、不可重復讀以及幻讀,mysql默認設置是可重復讀,即一次事務中不會讀取到不同的數據。
可以做如下操作:
1)打開兩個客戶端,均設置為RR;
2)在一個事務中,查詢某個操作查到某份數據;比如是某個欄位version=1存在數據;
3)在另一個事務中,刪除這份version=1的數據;刪除後,在2所屬的事務中查詢數據是沒有變化的,還是存在version=1的數據;
4)當我們在2所屬的事務中繼續更新數據,那麼會發現更新不了,明明我們就看到了這份version=1的數據;
緩存一致性:
緩存一致,與什麼一致?是與資料庫一致,對外查詢每個時刻一致;所以在針對於緩存與資料庫之間該先更新哪一個呢?可能有人覺得我先更新資料庫,再更新緩存不就行了嗎?但是有想過個問題嗎?
當用戶已經支付成功了,更新到資料庫,但是呢?你還在緩存中顯示未支付,在用戶點擊頻率很高並且資料庫壓力過大,來不及同步到緩存時,那你是不是很尷尬,這就是典型的不一致了。此時用戶再支付,那你又告訴他已經支付了,那他會把你罵死的
那該怎麼來做呢?我們可以這樣,先更新緩存再更新資料庫,那麼存在什麼問題呢?
1)緩存更新成功,但是資料庫更新失敗,而被其它的並發線程訪問到
2)緩存淘汰成功,但是資料庫更新失敗,這也會引發後期數據不一致