成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

面對緩存,有哪些問題需要思考?

移動開發
緩存是用于解決高并發場景下系統的性能及穩定性問題的銀彈。本文主要是討論我們經常使用的分布式緩存Redis在開發過程中需要考慮的問題。

緩存可以說是無處不在,比如:PC電腦中的內存、CPU中有二級緩存、http協議中的緩存控制、CDN加速技術 無不都是使用了緩存的思想來解決性能問題。

緩存是用于解決高并發場景下系統的性能及穩定性問題的銀彈。

本文主要是討論我們經常使用的分布式緩存Redis在開發過程中需要考慮的問題。

面對緩存,有哪些問題需要思考?

1. 如何將業務邏輯與緩存之間進行解耦?

大部分情況,大家都是把緩存操作和業務邏輯之間的代碼交織在一起的,比如(代碼一):

 

  1. public UserServiceImpl implements UserService { 
  2.     @Autowired 
  3.     private RedisTemplate<String, User> redisTemplate; 
  4.      
  5.     @Autowired 
  6.     private UserMapper userMapper; 
  7.      
  8.     public User getUserById(Long userId) { 
  9.         String cacheKey = "user_" + userId; 
  10.         User user = redisTemplate.opsForValue().get(cacheKey); 
  11.         if(null != user) { 
  12.             return user
  13.         } 
  14.         user = userMapper.getUserById(userId); 
  15.         redisTemplate.opsForValue().set(cacheKey, user); // 如果user 為null時,緩存就沒有意義了 
  16.         return user
  17.     } 
  18.      
  19.     public void deleteUserById(Long userId) { 
  20.         userMapper.deleteUserById(userId); 
  21.         String cacheKey = "user_" + userId; 
  22.         redisTemplate.opsForValue().del(cacheKey); 
  23.     } 

從上面的代碼可以看出以下幾個問題:

  1. 緩存操作非常繁瑣,產生非常多的重復代碼;
  2. 緩存操作與業務邏輯耦合度非常高,不利于后期的維護;
  3. 當業務數據為null時,無法確定是否已經緩存,會造成緩存無法***;
  4. 開發階段,為了排查問題,經常需要來回開關緩存功能,使用上面的代碼是無法做到很方便地開關緩存功能;
  5. 當業務越來越復雜時,使用緩存的地方越來越多時,很難定位哪些數據要進行主動刪除;
  6. 如果不想用Redis,換用別的緩存技術的話,那是多么痛苦的一件事。

因為高耦合帶來的問題還很多,就不一一列舉了。接下來介紹筆者開源的一個緩存管理框架:AutoLoadCache是如何幫助我們來解決上述問題的。

借鑒于Spring cache的思想使用AOP + Annotation 等技術實現緩存與業務邏輯的解耦。我們再用AutoLoadCache 來重構上面的代碼,進行對比(代碼二):

 

  1. public interface UserMapper { 
  2.     @Cache(expire = 120, key = "'user_' + #args[0]"
  3.     User getUserById(Long userId); 
  4.      
  5.     @CacheDelete({ @CacheDeleteKey(value = "'user' + #args[0].id") }) 
  6.     void updateUser(User user); 
  7.  
  8. public UserServiceImpl implements UserService { 
  9.      
  10.     @Autowired 
  11.     private UserMapper userMapper; 
  12.      
  13.     public User getUserById(Long userId) { 
  14.         return userMapper.getUserById(userId); 
  15.     } 
  16.     @Transactional(rollbackFor=Throwable.class) 
  17.     public void updateUser(User user) { 
  18.         userMapper.updateUser(user); 
  19.     } 
  20. }

AutoloadCache 在AOP攔截到請求后,大概的流程如下:

  1. 獲取到攔截方法的@Cache注解,并生成緩存key;
  2. 通過緩存key,去緩存中獲取數據;

如果緩存***,執行如下流程:

  1. 如果需要自動加載,則把相關信息保存到自動加載隊列中;
  2. 否則判斷緩存是否即將過期,如果即將過期,則會發起異步刷新;
  3. ***把數據返回給用戶;

如果緩存沒有***,執行如下流程:

  1. 選舉出一個leader回到數據源中去加載數據,加載到數據后通知其它請求從內存中獲取數據(拿來主義機制);
  2. leader負責把數據寫入緩存;如果需要自動加載,則把相關信息保存到自動加載隊列中;
  3. ***把數據返回給用戶;

這里提到的異步刷新、自動加載、拿來主義機制,我們會在后面再說明。

2. 對緩存進行“包裝”

上面代碼一的例子中,當從數據源獲取的數據為null時,緩存就沒有意義了,所獲取這個數據的請求,都會回到數據源去獲取數據。當請求量非常大的話,會造成數據源負載過高而宕機。所以對于null的數據,需要做特殊處理,比如使用特殊字符串進行替換。而在AutoloadCache中使用了一個包裝器對所有緩存數據進行包裝(代碼三):

 

  1. public class CacheWrapper<T> implements Serializable, Cloneable { 
  2.  
  3.     private T cacheObject; // 緩存數據 
  4.  
  5.     private long lastLoadTime; // 加載時間 
  6.  
  7.     private int expire; // 緩存時長 
  8.      
  9.     /** 
  10.      * 判斷緩存是否已經過期 
  11.      * @return boolean 
  12.      */ 
  13.     public boolean isExpired() { 
  14.         if(expire > 0) { 
  15.             return (System.currentTimeMillis() - lastLoadTime) > expire * 1000; 
  16.         } 
  17.         return false
  18.     } 

在這上面的代碼中,除了封裝了緩存數據外,還封裝了數據加載時間和緩存時長,通過這兩項數據,很容易判斷緩存是否即將過期或者已經過期。

3. 如何提升緩存key生成表達式性能?

使用Annotation解決緩存與業務之間的耦合后,我們最主要的工作就是如何來設計緩存KEY了,緩存KEY設計的粒度越小,緩存的復用性也就越好。

上面例子中我們是使用Spring EL表達式來生成緩存KEY,有些人估計會擔心Spring EL表達式的性能不好,或者不想用Spring的情況該怎么辦?

框架中為了滿足這些需求,支持擴展表達式解析器:繼承com.jarvis.cache.script. AbstractScriptParser后就可以任你擴展。

框架現在除了支持Spring EL表達式外,還支持Ognl,javascript表達式。對于性能要求非常高的人,可以使用Ognl,它的性能非常接近原生代碼。

4. 如何解決緩存Key沖突問題?

在實際情況中,可能有多個模塊共用一個Redis服務器或是一個Redis集群的情況,那么有可能造成緩存key沖突了。

為了解決這個問題AutoLoadCache,增加了namespace。如果設置了namespace就會在每個緩存Key最前面增加namespace(代碼四):

 

  1. public final class CacheKeyTO implements Serializable { 
  2.  
  3.     private final String namespace; 
  4.  
  5.     private final String key;// 緩存Key 
  6.  
  7.     private final String hfield;// 設置哈希表中的字段,如果設置此項,則用哈希表進行存儲 
  8.  
  9.     public String getCacheKey() { // 生成緩存Key方法 
  10.         if(null != this.namespace && this.namespace.length() > 0) { 
  11.             return new StringBuilder(this.namespace).append(":").append(this.key).toString(); 
  12.         } 
  13.         return this.key
  14.     } 

5. 壓縮緩存數據及提升序列化與反序列化性能

我們希望緩存數據包越小越好,能減少內存占用,以及減輕帶寬壓力;同時也要考慮序列化與反序列化的性能。

AutoLoadCache為了滿足不同用戶的需要,已經實現了基于JDK、Hessian、JacksonJson、Fastjson、JacksonMsgpack等技術序列化及反序列工具。也可以通過實現com.jarvis.cache.serializer.ISerializer 接口自行擴展。

JDK自帶的序列化與反序列化工具產生的數據包非常大,而且性能也非常差,不建議大家使用;JacksonJson 和 Fastjson 是基于JSON的,所有用到緩存的函數的參數及返回值都必須是具體類型的,不能是不確定類型的(不能是Object, List等),另外有些數據轉成Json是其一些屬性是會被忽略,存在這種情況時,也不能使用Json;

而Hessian 則是非常不錯的選擇,非常成熟和穩定性。阿里的dubbo和HSF兩個RPC框架都是使用了Hessian進行序列化和返序列化。

6. 如何減少回源并發數?

當緩存未***時,都需要回到數據源去取數據,如果這時有多個并發來請求相同一個數據(即相同緩存key請求),都回到數據源加載數據,并寫緩存,造成資源極大的浪費,也可能造成數據源負載過高而無法服務。

AutoLoadCache 使用拿來主義機制和自動加載機制來解決這個問題:

拿來主義機制

拿來主交機制,指的是當有多個用戶請求同一個數據時,會選舉出一個leader去數據源加載數據,其它用戶則等待其拿到的數據。并由leader將數據寫入緩存。

自動加載機制

自動加載機制,將用戶請求及緩存時間等信息放到一個隊列中,后臺使用線程池定期掃這個隊列,發現緩存即將過期,則去數據源加載***的數據放到緩存中。達到將數據長駐內存的效果。從而將這些數據的請求,全部引向了緩存,而不會回到數據源去獲取數據。非常適合用于緩存使用非常頻繁的數據,以及非常耗時的數據。

為了防止自動加載隊列過大,設置了容量限制;同時會將超過一定時間沒有用戶請求的也會從自動加載隊列中移除,把服務器資源釋放出來,給真正需要的請求。

往緩存里寫數據的性能相比讀的性能差非常多,通過上面兩種機制,可以減少寫緩存的并發,提升緩存服務能力。

7. 異步刷新

AutoLoadCache 從緩存中獲取到數據后,借助于上面提到的CacheWrapper,能很方便判斷緩存是否即將過期, 如果即將過期,則會把發起異步刷新請求。

使用異步刷新的目的,提前將數據緩存起來,避免緩存失效后,大量請求穿透到數據源。

8. 支持多種緩存操作

大部分情況下,我們都是對緩存進行讀與寫操作,可有時,我們只需要從緩存中讀取數據,或者只寫數據,那么可以通過 @Cache 的 opType 指定緩存操作類型。現支持以下幾種操作類型:

  1. READ_WRITE:讀寫緩存操:如果緩存中有數據,則使用緩存中的數據,如果緩存中沒有數據,則加載數據,并寫入緩存。默認是READ_WRITE;
  2. WRITE:從數據源中加載***的數據,并寫入緩存。對數據源和緩存數據進行同步;
  3. READ_ONLY: 只從緩存中讀取,并不會去數據源加載數據。用于異地讀寫緩存的場景;
  4. LOAD :只從數據源加載數據,不讀取緩存中的數據,也不寫入緩存。

另外在@Cache中只能靜態指寫緩存操作類型,如果想在運行時調整操作類型,需要通過CacheHelper.setCacheOpType()方法來進行調整。

9. 批量刪除緩存

在很多時候,數據查詢條件是比較復雜,我們無法獲取或還原要刪除的緩存key。

AutoLoadCache 為了解決這個問題,使用Redis的hash表來管理這部分的緩存。把需要批量刪除的緩存放在同一個hash表中,如果需要需要批量刪除這些緩存時,直接把這個hash表刪除即可。這時只要設計合理粒度的緩存key即可。

通過@Cache的hfield設置hash表的key。

我們舉個商品評論的場景(代碼五):

 

  1. public interface ProuductCommentMapper { 
  2.     @Cache(expire=600, key="'prouduct_comment_list_'+#args[0]", hfield = "#args[1]+'_'+#args[2]"
  3.     // 例如:prouductId=1, pageNo=2, pageSize=3 時相當于Redis命令:HSET prouduct_comment_list_1 2_3  List<Long> 
  4.     public List<Long> getCommentListByProuductId(Long prouductId, int pageNo, int pageSize); 
  5.          
  6.     @CacheDelete({@CacheDeleteKey(value="'prouduct_comment_list_'+#args[0].prouductId")})  
  7.     // 例如:#args[0].prouductId = 1時,相當于Redis命令: DEL prouduct_comment_list_1 
  8.     public void addComment(ProuductComment comment) ; 
  9.      

如果添加評論時,我們只需要主動刪除前3頁的評論(代碼六):

 

  1. public interface ProuductCommentMapper { 
  2.     @Cache(expire=600, key="'prouduct_comment_list_'+#args[0]+'_'+#args[1]", hfield = "#args[2]"
  3.     public List<Long> getCommentListByProuductId(Long prouductId, int pageNo, int pageSize); 
  4.          
  5.     @CacheDelete({ 
  6.         @CacheDeleteKey(value="'prouduct_comment_list_'+#args[0].prouductId+'_1'"), 
  7.         @CacheDeleteKey(value="'prouduct_comment_list_'+#args[0].prouductId+'_2'"), 
  8.         @CacheDeleteKey(value="'prouduct_comment_list_'+#args[0].prouductId+'_3'"
  9.     })  
  10.     public void addComment(ProuductComment comment) ; 
  11.      

10. 雙寫不一致問題

在“代碼二”中使用updateUser方法更新用戶信息時, 同時會主動刪除緩存中的數據。 如果在事務還沒提交之前又有一個請求去加載用戶數據,這時就會把數據庫中舊數據緩存起來,在下次主動刪除緩存或緩存過期之前的這一段時間內,緩存中的數據與數據庫中的數據是不一致的。AutoloadCache框架為了解決這個問題,引入了一個新的注解:@CacheDeleteTransactional (代碼七):

 

  1. public UserServiceImpl implements UserService { 
  2.      
  3.     @Autowired 
  4.     private UserMapper userMapper; 
  5.      
  6.     public User getUserById(Long userId) { 
  7.         return userMapper.getUserById(userId); 
  8.     } 
  9.     @Transactional(rollbackFor=Throwable.class) 
  10.     @CacheDeleteTransactional 
  11.     public void updateUser(User user) { 
  12.         userMapper.updateUser(user);  
  13.     } 
  14.  

使用@CacheDeleteTransactional注解后,AutoloadCache 會先使用ThreadLocal緩存要刪除緩存KEY,等事務提交后再去執行緩存刪除操作。其實不能說是“解決不一致問題”,而是緩解而已。

緩存數據雙寫不一致的問題是很難解決的,即使我們只用數據庫(單寫的情況)也會存在數據不一致的情況(當從數據庫中取數據時,同時又被更新了),我們只能是減少不一致情況的發生。對于一些比較重要的數據,我們不能直接使用緩存中的數據進行計算并回寫的數據庫中,比如扣庫存,需要對數據增加版本信息,并通過樂觀鎖等技術來避免數據不一致問題。

11. 與Spring Cache的比較

AutoLoadCache 的思想其實是源自 Spring Cache,都是使用 AOP + Annotation ,將緩存與業務邏輯進行解耦。區別在于:

  1. AutoLoadCache 的AOP不限于Spring 中的AOP技術,即可以脫離Spring 生態使用,比如成功案例nutz;
  2. Spring Cache不支持命名空間;
  3. Spring Cache沒有自動加載、異步刷新、拿來主義機制;
  4. Spring Cache使用name 和 key的來管理緩存(即通過name和key就可以操作具體緩存了),而AutoLoadCache 使用的是namespace + key + hfield 來管理緩存,同時每個緩存都可以指定緩存時間(expire)。也就是說Spring Cache 比較適合用來管理Ehcache的緩存,而AutoLoadCache 更加適合管理Redis, Memcache,尤其是Redis,hfield 相關的功能都是針對它們進行開發的(因為Memcache不支持hash表,所以沒辦法使用hfield相關的功能)。
  5. Spring Cache不能針對每個緩存Key,進行設置緩存過期時間。而在緩存管理應用中,不同的緩存其緩存時間要盡量設置為不同的。如果都相同的,那緩存同時失效的可能性會比較大些,這樣穿透到數據庫的可能性也就更大了,對系統的穩定性是沒有好處的;
  6. Spring Cache ***的缺點就是無法使用Spring EL表達式來動態生成Cache name,而且Cache name是的必須在Spring 配置時指定幾個,非常不方便使用。尤其想在Redis中想精確清除一批緩存,是無法實現的,可能會誤刪除我們不希望被刪除的緩存;
  7. Spring Cache只能基于Spring 中的AOP及Spring EL表達式來使用,而AutoloadCache 可以根據使用者的實際情況進行擴展;
  8. AutoLoadCache中使用@CacheDeleteTransactional 來減少雙寫不一致問題,而Spring Cache沒有相應的解決方案;作者:家榆_77cd鏈接:http://www.jianshu.com/p/4f52d046c3d2來源:簡書著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
責任編輯:未麗燕 來源: 簡書
相關推薦

2018-03-13 09:20:05

數字化轉型

2009-09-16 13:29:30

BSM

2010-04-21 10:04:33

Oracle移植

2019-06-05 15:23:09

Redis緩存存儲

2013-04-03 15:42:46

2018-09-18 14:03:57

OpenStack知識難點

2020-09-17 06:47:23

緩存類型算法

2019-10-23 06:09:18

DDos攻擊清洗服務網絡攻擊

2024-03-14 09:07:05

刷數任務維度后端

2021-12-30 06:59:28

方法重寫面試

2019-05-07 18:17:26

Redis服務器數據

2020-07-01 11:29:52

云計算邊緣計算疫情

2020-12-28 11:11:26

前端開發語言

2022-09-26 10:03:02

低代碼開發

2011-07-05 09:28:57

vmware

2019-04-11 08:17:36

2017-02-09 11:10:51

2019-03-28 09:14:22

人工智能AI

2019-07-10 12:19:31

網絡安全信息安全網絡威脅

2012-07-19 15:16:45

移動營銷
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲视频一区在线观看 | 亚洲精品久久久久久国产精华液 | 成人在线电影在线观看 | 亚洲 日本 欧美 中文幕 | 国产ts人妖系列高潮 | 毛片网在线观看 | www.亚洲精品 | 久久精品国产久精国产 | 国产日韩免费视频 | 亚洲欧美日本在线 | 粉嫩一区二区三区国产精品 | 欧洲成人午夜免费大片 | 欧美一二三 | 亚洲欧洲精品一区 | 亚洲三区在线观看 | 激情 婷婷| 一级毛片成人免费看a | 五月激情婷婷网 | 手机在线不卡av | 国产精品欧美一区二区三区不卡 | 在线免费观看毛片 | 久久久夜色精品亚洲 | 成人欧美一区二区三区1314 | 日本黄色片免费在线观看 | 国产精品揄拍一区二区 | 成人久久18免费网站 | 玖操| 久久亚洲视频网 | 亚洲精品乱码久久久久久9色 | 日韩一区二区在线播放 | 91久久视频 | 超碰av免费 | 午夜精品久久久久久久久久久久 | 国产999精品久久久久久 | 国产美女视频 | 成人做爰999 | 欧美xxxx色视频在线观看免费 | www四虎影视 | 国产精品一区一区三区 | 亚洲第一视频网站 | 免费一区二区三区在线视频 |