小說軟體原始碼的快取設計,保證服務的正常執行
小說軟體原始碼快取設計原理
小說軟體原始碼對快取是隻刪除,不做更新,一旦DB裡資料出現修改,我們就會直接刪除對應的快取,而不是去更新。
我們看看刪除快取的順序怎樣才是正確的。
-
先刪除快取,再更新DB
我們看小說軟體原始碼中兩個併發請求的情況,A請求需要更新資料,先刪除了快取,然後B請求來讀取資料,此時快取沒有資料,就會從DB載入資料並寫回快取,然後A更新了DB,那麼此時快取內的資料就會一直是髒資料,知道快取過期或者有新的更新資料的請求。如圖
-
先更新DB,再刪除快取
A請求先更新DB,然後B請求來讀取資料,此時小說軟體原始碼返回的是老資料,此時可以認為是A請求還沒更新完,最終一致性,可以接受,然後A刪除了快取,後續請求都會拿到最新資料,如圖
讓我們再來看一下正常的請求流程:
1、第一個請求更新DB,並刪除了快取
2、第二個請求讀取快取,沒有資料,就從DB讀取資料,並回寫到快取裡
3、後續讀請求都可以直接從小說軟體原始碼快取讀取
我們再看一下DB查詢有哪些情況,假設行記錄裡有ABCDEFG七列資料:
只查詢小說軟體原始碼部分列資料的請求,比如請求其中的ABC,CDE或者EFG等,如圖
查詢單條完整行記錄,如圖
查詢多條行記錄的部分或全部列,如圖
小說軟體原始碼快取程式碼解讀
1. 基於主鍵的快取邏輯
具體實現程式碼如下:
func (cc CachedConn) QueryRow(v interface{}, key string, query QueryFn) error { return cc.cache.Take(v, key, func(v interface{}) error { return query(cc.db, v) }) }
這裡的 Take 方法是先從小說軟體原始碼快取裡去通過 key 拿資料,如果拿到就直接返回,如果拿不到,那麼就通過 query 方法去 DB 讀取完整行記錄並寫回快取,然後再返回資料。整個邏輯還是比較簡單易懂的。
我們詳細看看 Take 的實現:
func (c cacheNode) Take(v interface{}, key string, query func(v interface{}) error) error { return c.doTake(v, key, query, func(v interface{}) error { return c.SetCache(key, v) }) }
Take 的邏輯如下:
-
用 key 從小說軟體原始碼快取裡查詢資料
-
如果找到,則返回資料
-
如果找不到,用 query 方法去讀取資料
-
讀到後呼叫 c.SetCache(key, v) 設定快取
其中的 doTake 程式碼和解釋如下:
// v - 需要讀取的資料物件 // key - 快取key // query - 用來從DB讀取完整資料的方法 // cacheVal - 用來寫快取的方法 func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) error, cacheVal func(v interface{}) error) error { // 用barrier來防止快取擊穿,確保一個程式內只有一個請求去載入key對應的資料 val, fresh, err := c.barrier.DoEx(key, func() (interface{}, error) { // 從cache裡讀取資料 if err := c.doGetCache(key, v); err != nil { // 如果是預先放進來的placeholder(用來防止快取穿透)的,那麼就返回預設的errNotFound // 如果是未知錯誤,那麼就直接返回,因為我們不能放棄快取出錯而直接把所有請求去請求DB, // 這樣在高併發的場景下會把DB打掛掉的 if err == errPlaceholder { return nil, c.errNotFound } else if err != c.errNotFound { // why we just return the error instead of query from db, // because we don't allow the disaster pass to the DBs. // fail fast, in case we bring down the dbs. return nil, err } // 請求DB // 如果返回的error是errNotFound,那麼我們就需要在快取裡設定placeholder,防止快取穿透 if err = query(v); err == c.errNotFound { if err = c.setCacheWithNotFound(key); err != nil { logx.Error(err) } return nil, c.errNotFound } else if err != nil { // 統計DB失敗 c.stat.IncrementDbFails() return nil, err } // 把資料寫入快取 if err = cacheVal(v); err != nil { logx.Error(err) } } // 返回json序列化的資料 return jsonx.Marshal(v) }) if err != nil { return err } if fresh { return nil } // got the result from previous ongoing query c.stat.IncrementTotal() c.stat.IncrementHit() // 把資料寫入到傳入的v物件裡 return jsonx.Unmarshal(val.([]byte), v) }
2. 基於唯一索引的快取邏輯
因為這塊比較複雜,所以我用不同顏色標識出來了小說軟體原始碼響應的程式碼塊和邏輯,block 2 其實跟基於主鍵的快取是一樣的,這裡主要講 block 1 的邏輯。
程式碼塊的 block 1 部分分為兩種情況:
1、通過索引能夠從快取裡找到主鍵
-
此時就直接用主鍵走 block 2 的邏輯了,後續同上面基於主鍵的快取邏輯
2、通過索引無法從快取裡找到主鍵
-
通過小說軟體原始碼索引從DB裡查詢完整行記錄,如有 error,返回
-
查到完整行記錄後,會把主鍵到完整行記錄的快取和索引到主鍵的快取同時寫到 redis 裡
-
返回所需的行記錄資料
// v - 需要讀取的資料物件 // key - 通過索引生成的快取key // keyer - 用主鍵生成基於主鍵快取的key的方法 // indexQuery - 用索引從DB讀取完整資料的方法,需要返回主鍵 // primaryQuery - 用主鍵從DB獲取完整資料的方法 func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary interface{}) string, indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error { var primaryKey interface{} var found bool // 先通過索引查詢快取,看是否有索引到主鍵的快取 if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) { // 如果沒有索引到主鍵的快取,那麼就通過索引查詢完整資料 primaryKey, err = indexQuery(cc.db, v) if err != nil { return } // 通過索引查詢到了完整資料,設定found,後面直接使用,不需要再從快取讀取資料了 found = true // 將主鍵到完整資料的對映儲存到快取裡,TakeWithExpire方法已經將索引到主鍵的對映儲存到快取了 return cc.cache.SetCacheWithExpire(keyer(primaryKey), v, expire+cacheSafeGapBetweenIndexAndPrimary) }); err != nil { return err } // 已經通過索引找到了資料,直接返回即可 if found { return nil } // 通過主鍵從快取讀取資料,如果快取沒有,通過primaryQuery方法從DB讀取並回寫快取再返回資料 return cc.cache.Take(v, keyer(primaryKey), func(v interface{}) error { return primaryQuery(cc.db, v, primaryKey) }) }
我們來看一個實際的例子
func (m *defaultUserModel) FindOneByUser(user string) (*User, error) { var resp User // 生成基於索引的key indexKey := fmt.Sprintf("%s%v", cacheUserPrefix, user) err := m.QueryRowIndex(&resp, indexKey, // 基於主鍵生成完整資料快取的key func(primary interface{}) string { return fmt.Sprintf("user#%v", primary) }, // 基於索引的DB查詢方法 func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { query := fmt.Sprintf("select %s from %s where user = ? limit 1", userRows, m.table) if err := conn.QueryRow(&resp, query, user); err != nil { return nil, err } return resp.Id, nil }, // 基於主鍵的DB查詢方法 func(conn sqlx.SqlConn, v, primary interface{}) error { query := fmt.Sprintf("select %s from %s where id = ?", userRows, m.table) return conn.QueryRow(&resp, query, primary) }) // 錯誤處理,需要判斷是否返回的是sqlc.ErrNotFound,如果是,我們用本package定義的ErrNotFound返回 // 避免使用者感知到有沒有使用快取,同時也是對底層依賴的隔離 switch err { case nil: return &resp, nil case sqlc.ErrNotFound: return nil, ErrNotFound default: return nil, err } }
以上就是“小說軟體原始碼的快取設計,保證服務的正常執行”的全部內容,希望對大家有幫助。
本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理
原文連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2847031/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 快取設計的好,服務基本不會倒快取
- 教你編寫 Node.js 中介軟體,實現服務端快取(附demo原始碼)Node.js服務端快取原始碼
- 從原始碼層面談談mybatis的快取設計原始碼MyBatis快取
- 聊聊保證執行緒安全的10個小技巧執行緒
- 曹工說JDK原始碼(2)--ConcurrentHashMap的多執行緒擴容,說白了,就是分段取任務JDK原始碼HashMap執行緒
- 系統架構設計:程式快取和快取服務,如何抉擇?架構快取
- 服務重啟了,如何保證執行緒池中的資料不丟失?執行緒
- springboot的服務不需要連線資料庫,如何保證正常啟動Spring Boot資料庫
- YYWebImage 原始碼剖析:執行緒處理與快取策略Web原始碼執行緒快取
- Java併發程式設計—synchronized保證執行緒安全的原理分析Java程式設計synchronized執行緒
- 小說軟體開發,java獲取文字檔案的編碼格式Java
- 快取架構中的服務詳解!SpringBoot中二級快取服務的實現快取架構Spring Boot
- ThinkPHP6 原始碼閱讀(七):中介軟體的執行PHP原始碼
- Koa2.0原始碼解析-中介軟體的設計原始碼
- Mybatis的快取——一級快取和原始碼分析MyBatis快取原始碼
- Android 記憶體快取框架 LruCache 的原始碼分析Android記憶體快取框架原始碼
- Java併發程式設計(二)如何保證執行緒同時/交替執行Java程式設計執行緒
- 【高併發】通過原始碼深度解析ThreadPoolExecutor類是如何保證執行緒池正確執行的原始碼thread執行緒
- 直播系統原始碼,利用重試機制保證服務穩定性原始碼
- 保證執行緒安全的技術執行緒
- SaaS(軟體即服務)架構設計架構
- 軟體測試——軟體安全質量的保證
- Java 併發程式設計(四):如何保證物件的執行緒安全性Java程式設計物件執行緒
- 說說ERP軟體的系統設計--開源軟體誕生8
- 確保租用的香港伺服器執行正常的措施有哪些伺服器
- 趣說 | 資料庫和快取如何保證一致性?資料庫快取
- 從快取角度入手實現聊天室軟體原始碼的前端效能優化快取原始碼前端優化
- 保證執行緒在主執行緒執行執行緒
- 恆訊科技分析:如何保證網站伺服器的正常執行時間達到99.99%?網站伺服器
- [記]SAF 中快取服務的實現快取
- 說說 Vue 中元件的快取Vue元件快取
- 為什麼說軟體服務的未來必然是WebAssembly?Web
- 執行緒安全的 iOS 通用快取-SwiftlyCache執行緒iOS快取Swift
- Dubbo原始碼分析(六)服務引用的具體流程原始碼
- 程式設計中快取的使用程式設計快取
- 固定容量的本地快取設計快取
- go-zero微服務實戰系列(六、快取的一致性如何保證)Go微服務快取
- 阿里雲簡訊服務的使用-----獲取簡訊驗證碼阿里