<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用Redis來做緩存,你該知道的!

          共 6511字,需瀏覽 14分鐘

           ·

          2021-08-19 12:00

          一、緩存一些知識

          1.1、緩存擊穿、緩存穿透、緩存雪崩是什么?

          • 緩存擊穿

          用戶請求的某個key在DB或者緩存中存在,但是可能正好在當這個key在緩存中到達了失效時間而過期,而此時大量訪問該數(shù)據請求過來,相當于在緩存中鑿開一個缺口,一下子全部打在DB上,造成DB壓力壓垮DB。

          • 緩存穿透

          用戶通過請求一些緩存和DB中壓根都不存在的數(shù)據,致使每次請求都會繞過緩存,請求DB,給DB帶來壓力

          • 緩存雪崩

          當緩存服務器重啟或者大量緩存的keys過期失效,導致客戶端過來的請求全部直接請求到DB中,造成DB壓力大而崩潰(注意這里和緩存擊穿不同的是緩存擊穿是單個key失效引起,而雪崩是大量key失效

          1.2 應對緩存擊穿

          • 1)互斥鎖訪問數(shù)據

          針對某一熱點數(shù)據,在獲取時可通過加互斥鎖使得只有一個請求進行處理訪問DB,并將數(shù)據放置到緩存中,后續(xù)阻塞的請求將直接命中緩存。

          • 2)設置熱點數(shù)據永不過期

          1.3 應對緩存穿透

          • 加強接口入參校驗。將不合法入參抹殺在搖籃中

          • 如果未查出數(shù)據,可賦予緩存中對應key一個null值,并設置一個過期時間,防止單位時間內大量請求訪問DB。因為設置了過期時間,所以可以保證后續(xù)該key有值了,可以獲取到。

          1.4 應對緩存雪崩

          • 1)設置緩存過期時間時,加上隨機時間戳

          這樣做的好處就是盡量使得緩存key們的過期時間均勻分散,不至于在同一個時間點大面積緩存過期失效引起雪崩

          • 2)不設置緩存過期時間

          • 3)分布式緩存服務器部署的情況下,可以將熱點數(shù)據分散在不同的緩存服務器中

          二、redisLRU緩存機制

          2.1 Redis內存淘汰機制

          LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。該算法賦予每個頁面一個訪問字段,用來記錄一個頁面自上次被訪問以來所經歷的時間 t,當須淘汰一個頁面時,選擇現(xiàn)有頁面中其 t 值最大的,即最近最少使用的頁面予以淘汰。----- 摘自百度百科

          LRU緩存那就是將最近最少訪問的緩存剔除。redis針對LRU機制提供了實現(xiàn)支持。redis在redis.conf中提供了配置選項maxmemory 來配置執(zhí)行大小的內存數(shù)據集。或者在服務器運行時,可在客戶端通過CONFIG SET進行設置。

          maxmemory 100mb
          復制代碼

          如果設置為0,那么則表示內存不受限制。當往redis中存放值時,達到我們指定的maxmemory值時,redis提供了多種不同的剔除值策略。可以通過在redis.conf中配置策略

          noeviction xxx
          復制代碼
          • noeviction(default)

          即當達到maxmemory內存限制時,不做數(shù)據剔除,直接返回錯誤

          • allkeys-lru

          即當達到maxmemory內存限制時,刪除最近最少使用的key以保證新的值可以添加進來

          • volatile-lru

          即當達到maxmemory內存限制時,刪除最近最少使用并且通過expire設置了過期時間的key,以保證新的值可以添加進來

          • allkeys-random

          即當達到maxmemory內存限制時,隨機刪除一些key以保證新的值可以添加進來

          • volatile-random

          即當達到maxmemory內存限制時,隨機刪除一些設置了過期時間的key以保證新的值可以添加進來

          • volatile-ttl

          即當達到maxmemory內存限制時,刪除一些設置了過期時間并且TTK所剩時間最短的keys以保證新的值可以添加進來

          其中volatile-lru、volatile-random、volatile-ttl如果沒有滿足條件的key可以進行刪除,那么它們的行為就和noeviction策略一樣。剔除策略也可以在運行時進行設置。

          2.2 Redis LRU淘汰機制精確度

          在redis3.0之后,redis針對之前近似LRU算法進行了性能上的改進提升,以及使得LRU算法更加接近準確。通過配置參數(shù)maxmemory-samples可以調整樣本的數(shù)量來為LRU算法獲取精度。注意redis的LRU算法并不是LRU真正具體的實現(xiàn),因為那很耗費內存。但是其實redis計算出的近似值于真實算法值是很相近的。

          maxmemory-samples 5
          復制代碼

          從上述Redis官方給的測試數(shù)據可以看出,同樣樣本數(shù)未5,redis3.0要比之前好一點,當提高樣本數(shù)之后,更加的好一點,但是與真實LRU算法實現(xiàn)還是差點火候。

          我們可以以額外的CPU使用為代價將樣本大小增加到10,以接近真實的LRU,并檢查這是否會對緩存漏報率產生影響。

          在生產環(huán)境中,很容易通過CONFIG SET maxmemory-samples 命令來設置不同的樣本值進行實驗。

          三、Redis結合Spring Cache Abstraction實現(xiàn)緩存

          spring cache abstraction的更多使用spring知識之Spring Cache Abstraction的使用

          實現(xiàn)緩存的思想其實并不復雜,簡單點說,就是查詢先從緩存服務器中查詢,如果查詢到則立即返回,否則再去DB中查詢返回然后再放至到緩存當中;;在對數(shù)據進行更新操作時,操作DB成功以后,在決定緩存的更新策略(更新緩存or刪除緩存)。

          3.1 配置RedisCacheManager

          spring-data-redis提供了spring抽象緩存的實現(xiàn),為了借助于spring cache實現(xiàn)緩存,我們需要加載一個RedisCacheManager。

          @Bean
          public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
          return RedisCacheManager.create(connectionFactory);
          }
          復制代碼

          通過RedisCacheManager.create創(chuàng)建出的是默認配置的RedisCacheManager,如果我們想設置一些事務或者預定義緩存則可通過builder進行構建。

          3.1.1 自定義緩存配置RedisCacheConfiguration

          RedisCacheConfiguration配置類,可以用來配置比如設置緩存key的過期時間、是否緩存null值、是否啟用key前綴、以及key和value緩存過程中二進制數(shù)據 的序列化策略。

          • 基本配置

                  // 默認緩存配置
          RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();

          // 設置緩存key失效時間為2分鐘
          redisCacheConfiguration.entryTtl(Duration.ofMinutes(2));

          // 禁用緩存null值(默認為啟用)
          redisCacheConfiguration.disableCachingNullValues();

          // 禁用設置key前綴(默認為啟用)
          redisCacheConfiguration.disableKeyPrefix();

          // 設置key前綴
          redisCacheConfiguration.prefixCacheNameWith("miaomiao:");
          復制代碼
          • key,value序列化策略

          RedisCacheConfiguration默認key的序列化器是StringRedisSerializer,value的序列化器是JdkSerializationRedisSerializer

          3.2 代碼實操Srping Cache Abstranction

          大致貼下各層demo。

          entity(注意需要序列化)

          @Entity
          @Table(name = "user")
          public class UserInfo implements Serializable {

          @Id
          @Column(name = "user_id")
          private int userId;

          @Column(name = "user_name")
          private String userName;

          get set 省略。。。
          }
          復制代碼

          Dao

          @Repository
          public interface UserInfoRepository extends JpaRepository<UserInfo,Integer> {
          }
          復制代碼

          Service

          public interface UserInfoService {
          Optional<UserInfo> getUserInfo(int userId);
          }

          復制代碼
          @Service
          public class UserInfoServiceImpl implements UserInfoService {
          @Autowired
          private UserInfoRepository userInfoRepository;

          @Override

          @Cacheable("user")
          public Optional<UserInfo> getUserInfo(int userId) {
          System.out.println("發(fā)生了真實調用!!!");
          return userInfoRepository.findById(userId);
          }
          }
          復制代碼

          config(注意@EnableCaching啟用緩存)

          @Configuration
          @EnableCaching
          public class RedisCacheConfig {
          @Bean
          public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
          // 默認緩存配置
          RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();

          // 設置緩存key失效時間為2分鐘
          redisCacheConfiguration.entryTtl(Duration.ofMinutes(2));

          // 禁用緩存null值(默認為啟用)
          redisCacheConfiguration.disableCachingNullValues();


          return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration).build();
          }
          }
          復制代碼

          Test 以及輸入

          @SpringBootTest
          public class UserInfoServiceImplTest {

          @Autowired
          private UserInfoService userInfoService;

          @Test
          public void getUserInfo() {
          System.out.println("=========start1===========");
          System.out.println(userInfoService.getUserInfo(1));
          System.out.println("==========end1==========");

          System.out.println("=========start2===========");
          System.out.println(userInfoService.getUserInfo(1));
          System.out.println("==========end2==========");

          }
          }
          復制代碼
          =========start1===========
          發(fā)生了真實調用!!!
          Hibernate: select userinfo0_.user_id as user_id1_0_0_, userinfo0_.user_name as user_nam2_0_0_ from user userinfo0_ where userinfo0_.user_id=?
          Optional[UserInfo{userId=1, userName='cf'}]
          ==========end1==========


          =========start2===========
          Optional[UserInfo{userId=1, userName='cf'}]
          ==========end2==========
          復制代碼

          3.3 小結

          從上述測試輸出我們可以看出,在第一次進行調用(緩存中未存放相關值時,發(fā)生了真實調用,查庫,然后返回結果),而第二次因為在第一次調用的基礎上,緩存中已經有了值,所以直接將緩存結果返回,完全未觸發(fā)真實的方法邏輯,所以這里也是很需要注意的地方,如果不太了解,只是跟著網上demo去使用,則可能發(fā)生將其他業(yè)務邏輯包雜在上述方法中,造成有緩存清空未調用導致bug等。

          四、如何保障緩存DB一致性以及分布式數(shù)據一致性?

          4.1 緩存和DB數(shù)據不一致場景(或者說誘因)

          1)緩存和DB的操作不在一個事務中進行,很可能緩存的操作和DB的操作其中一個成功一個失敗,導致數(shù)據不一致。2)多線程對緩存進行更新操作時,可能導致舊數(shù)據被更新至緩存中。

          4.2 Cache Aside Pattern

          • 當緩存失效時

          即讀緩存,未命中,則從數(shù)據庫中取,成功之后將數(shù)據放到緩存中

          • 當緩存命中時

          即讀緩存,若命中,則成功返回

          • 當數(shù)據更新時

          先更新數(shù)據庫,然后刪除緩存

          著重與最后一條,當數(shù)據發(fā)生變化時,針對緩存如何進行處理?可能有以下幾種情況:

          • 先更新緩存,再更新數(shù)據庫

          此方案如果說更新數(shù)據庫的時候,數(shù)據庫宕機導致DB更新失敗,造成了數(shù)據不一致

          • 先更新數(shù)據庫,再更新緩存

          此方案主要存在兩個問題:1).假如線程A先更新了數(shù)據,然后準備做更新數(shù)據庫,更新緩存的操作,此時線程B也更新了相同的數(shù)據,更新數(shù)據庫更新緩存。按道理線程B的數(shù)據應該是最新的,但是因為多線程的時序問題,很可能最終緩存被更新成了舊值。2).并不是所有緩存是熱點數(shù)據,有可能此緩存不會被經常讀取,而如果每次更新數(shù)據的時候,都要去更新一下緩存,就造成了不必要的性能和內存消耗。

          • 先刪除緩存,再更新數(shù)據庫

          developer.aliyun.com/article/712… 強推!!!!

          參考文檔

          [1].docs.spring.io/spring-data…

          [2].docs.spring.io/spring/docs…

          [3].juejin.cn/post/684490…


          作者:我有一只喵喵
          鏈接:https://juejin.cn/post/6996820141405274142
          來源:掘金
          著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。



          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级做A爱片久久毛片 | 少妇后入在线观看 | 中国黄色毛片 | av无码精品一区二区三区宅噜噜 | 亚洲黄片免费看 |