分佈式服務之分佈式緩存(layering-cache)

很多高性能高併發的優化最有效果的優化就是做緩存,

緩存又分本地緩存和分佈式緩存,分佈式緩存大多數用redis,

但是高併發下的redis有時候網絡消耗多的時候也扛不住,

於是redis又可以做分佈式redis,增加redis的服務器節點和配置,

但是這個成本也比較高,其實完全可以用本地緩存+redis緩存結合的方式,

保證高併發下的響應速度。

下面是git上一個本地緩存(一級緩存)+redis(二級緩存)的開發模式

git地址:https://github.com/xiaolyuh/layering-cache

  • 一級緩存:Caffeine是一個一個高性能的 Java 緩存庫;使用 Window TinyLfu 回收策略,提供了一個近乎最佳的命中率(Caffeine 緩存詳解)。優點數據就在應用內存所以速度快。缺點受應用內存的限制,所以容量有限;沒有持久化,重啓服務後緩存數據會丟失;在分佈式環境下緩存數據數據無法同步;
  • 二級緩存:redis是一高性能、高可用的key-value數據庫,支持多種數據類型,支持集羣,和應用服務器分開部署易於橫向擴展。優點支持多種數據類型,擴容方便;有持久化,重啓應用服務器緩存數據不會丟失;他是一個集中式緩存,不存在在應用服務器之間同步數據的問題。缺點每次都需要訪問redis存在IO浪費的情況。

我們可以發現Caffeine和Redis的優缺點正好相反,所以他們可以有效的互補

數據的讀取流程:

數據讀取流程.jpg

數據的刪除流程

數據刪除流程.jpg

緩存的同步更新:

基於redis pub/sub 實現一級緩存的更新同步。主要原因有兩點:

  1. 使用緩存本來就允許髒讀,所以有一定的延遲是允許的 。
  2. redis本身是一個高可用的數據庫,並且刪除動作不是一個非常頻繁的動作所以使用redis原生的發佈訂閱在性能上是沒有問題的

演示步驟:

1,Pom文件引入一些包,關鍵的包(首先保證你的代碼增刪改查正常),注意springboot版本最好2.1+

      

 <!-- 分佈式緩存 開始 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaolyuh</groupId>
            <artifactId>layering-cache-starter</artifactId>
            <version>2.0.7</version>
        </dependency>
        <!-- 分佈式緩存 結束 -->

2,配置文件,自己配jdbc和redis配置,如果需要監控緩存情況可加上如下:

spring:
  layering-cache:
    layering-cache-servlet-enabled: true
    stats: true
    url-pattern: /layering-cache/*
    login-username: admin
    login-password: admin
    enable-update: true
    allow: 127.0.0.1

3,在啓動類或者是config配置類加上註解  @EnableCaching 啓動緩存

4,在需要緩存的方法前加註解

@Override
    @CacheEvict(value = "good", key = "#jsonObject['id']")
    public JSONObject update(JSONObject jsonObject){
        shrekGoodsDao.update(jsonObject);
        return CommonUtil.successJson();
    }

@Override
    @Cacheable(value = "good", key = "#jsonObject['id']", depict = "商品信息緩存",
            firstCache = @FirstCache(expireTime = 1),
            secondaryCache = @SecondaryCache(expireTime = 15, preloadTime = 8, forceRefresh = true))
    public JSONObject detail(JSONObject jsonObject) {
        return shrekGoodsDao.detail(jsonObject).get(0);
    }

    @Override
    @Cacheable(value = "goodImg", key = "#jsonObject['id']", depict = "商品圖片信息緩存",
            firstCache = @FirstCache(expireTime = 1),
            secondaryCache = @SecondaryCache(expireTime = 15, preloadTime = 8, forceRefresh = true))
    public List<JSONObject> swiper(JSONObject jsonObject) {
        return shrekGoodsDao.swiper(jsonObject);
    }

  我這裏緩存的規則是以第一個爲例   緩存表別名 good,key取json的id值,一級緩存失效時間爲1,單位分鐘,

二級緩存失效時間 15,主動刷新時間 8,單位小時。

測試結果:

    當第一次查詢:redis和本地Caffeine都無緩存,將從數據庫查詢出並緩存至二級緩存(redis)和一級緩存(Caffeine)

    當第二次查詢:日誌:

2020-04-07 17:12:17.751 DEBUG 15532 --- [nio-8090-exec-5] c.g.x.cache.caffeine.CaffeineCache       : caffeine緩存 key="1" 獲取緩存
2020-04-07 17:12:17.753 DEBUG 15532 --- [nio-8090-exec-5] com.github.xiaolyuh.cache.LayeringCache  : 查詢一級緩存。 key=1,返回值是:{"price":200.00,"intro":"這個商品是測試用的商品","chose":[{"col":"紅色","size":"100","id":1},{"col":"白色","size":"200","id":2}],"id":1,"title":"商品1"}
2020-04-07 17:12:17.753 DEBUG 15532 --- [nio-8090-exec-5] c.g.x.cache.caffeine.CaffeineCache       : caffeine緩存 key="1" 獲取緩存
2020-04-07 17:12:17.754 DEBUG 15532 --- [nio-8090-exec-5] com.github.xiaolyuh.cache.LayeringCache  : 查詢一級緩存。 key=1,返回值是:[{"id":1,"imgSrc":"api/images/timg.jpg"},{"id":2,"imgSrc":"api/images/timg.jpg"},{"id":3,"imgSrc":"api/images/timg.jpg"},{"id":7,"imgSrc":"api/images/u=2957957338,3157930748&fm=15&gp=0.jpg"},{"id":8,"imgSrc":"api/images/u=2957957338,3157930748&fm=15&gp=0.jpg"}]

第三次查詢:間隔1分鐘以後(我前面是指的一級緩存失效時間爲1分鐘)

2020-04-07 17:14:11.776 DEBUG 15532 --- [nio-8090-exec-6] c.g.x.cache.caffeine.CaffeineCache       : caffeine緩存 key="1" putIfAbsent 緩存,緩存值:{"price":200.00,"intro":"這個商品是測試用的商品","chose":[{"col":"紅色","size":"100","id":1},{"col":"白色","size":"200","id":2}],"id":1,"title":"商品1"}
2020-04-07 17:14:11.777 DEBUG 15532 --- [nio-8090-exec-6] com.github.xiaolyuh.cache.LayeringCache  : 查詢二級緩存,並將數據放到一級緩存。 key=1,返回值是:{"price":200.00,"intro":"這個商品是測試用的商品","chose":[{"col":"紅色","size":"100","id":1},{"col":"白色","size":"200","id":2}],"id":1,"title":"商品1"}
2020-04-07 17:14:11.777 DEBUG 15532 --- [nio-8090-exec-6] c.g.x.cache.caffeine.CaffeineCache       : caffeine緩存 key="1" 獲取緩存
2020-04-07 17:14:11.777 DEBUG 15532 --- [nio-8090-exec-6] com.github.xiaolyuh.cache.LayeringCache  : 查詢一級緩存。 key=1,返回值是:null
2020-04-07 17:14:11.777 DEBUG 15532 --- [nio-8090-exec-6] c.g.xiaolyuh.cache.redis.RedisCache      : redis緩存 key= goodImg:1 查詢redis緩存如果沒有命中,從數據庫獲取數據

第四次更新或者刪除:將會根據註解的 value和id清楚緩存,同時更新數據庫

2020-04-07 17:25:25.128 DEBUG 18044 --- [nio-8090-exec-7] c.g.xiaolyuh.listener.RedisPublisher     : redis消息發佈者向頻道【good】發佈了【com.github.xiaolyuh.listener.RedisPubSubMessage@3611a348】消息
2020-04-07 17:25:25.128 DEBUG 18044 --- [enerContainer-4] c.g.xiaolyuh.listener.RedisPublisher     : redis消息訂閱者接收到頻道【good】發佈的消息。消息內容:{"cacheName":"good","key":1,"messageType":"EVICT"}
2020-04-07 17:25:25.128 DEBUG 18044 --- [enerContainer-4] c.g.x.cache.caffeine.CaffeineCache       : caffeine緩存 key=1 清除緩存
2020-04-07 17:25:25.128  INFO 18044 --- [enerContainer-4] c.g.xiaolyuh.listener.RedisPublisher     : 刪除一級緩存good數據,key=1
2020-04-07 17:25:25.128 DEBUG 18044 --- [enerContainer-4] c.g.x.cache.caffeine.CaffeineCache       : caffeine緩存 key=1 清除緩存
2020-04-07 17:25:25.128 DEBUG 18044 --- [nio-8090-exec-7] c.s.e.dao.mysql.ShrekGoodsDao.update     : ==>  Preparing: UPDATE shrek_goods SET title=?, intro=?, price=?, categoryId=? ,sctionId=?,imgPath=? , delete_status=? WHERE id = ?; 
2020-04-07 17:25:25.128  INFO 18044 --- [enerContainer-4] c.g.xiaolyuh.listener.RedisPublisher     : 刪除一級緩存good數據,key=1
2020-04-07 17:25:25.129 DEBUG 18044 --- [nio-8090-exec-7] c.s.e.dao.mysql.ShrekGoodsDao.update     : ==> Parameters: 商品1(String), 這個商品是測試用的商11品(String), 200.0(Double), null, null, null, 1(Integer), 1(Integer)
2020-04-07 17:25:25.130 DEBUG 18044 --- [nio-8090-exec-7] c.s.e.dao.mysql.ShrekGoodsDao.update     : <==    Updates: 1

註解說明

@Cacheable

表示用的方法的結果是可以被緩存的,當該方法被調用時先檢查緩存是否命中,如果沒有命中再調用被緩存的方法,並將其返回值放到緩存中。

名稱 默認值 說明
value 空字符串數組 緩存名稱,cacheNames的別名
cacheNames 空字符串數組 緩存名稱
key 空字符串 緩存key,支持SpEL表達式
depict 空字符串 緩存描述(在緩存統計頁面會用到)
ignoreException true 是否忽略在操作緩存中遇到的異常,如反序列化異常
firstCache   一級緩存配置
secondaryCache   二級緩存配置
keyGenerator   key生成器,暫時不支持配置

@FirstCache

一級緩存配置項

名稱 默認值 說明
initialCapacity 10 緩存初始Size
maximumSize 5000 緩存最大Size
expireTime 9 緩存有效時間
timeUnit TimeUnit.MINUTES 時間單位,默認分鐘
expireMode ExpireMode.WRITE 緩存失效模式,ExpireMode.WRITE:最後一次寫入後到期失效,ExpireMode.ACCESS:最後一次訪問後到期失效

@SecondaryCache

二級緩存配置項

名稱 默認值 說明
expireTime 5 緩存有效時間
preloadTime 1 緩存主動在失效前強制刷新緩存的時間,建議是 expireTime * 0.2
timeUnit TimeUnit.HOURS 時間單位,默認小時
forceRefresh false 是否強制刷新(直接執行被緩存方法)
isAllowNullValue false 是否允許緩存NULL值
magnification 1 非空值和null值之間的時間倍率,默認是1。isAllowNullValue=true纔有效

@CachePut

將數據放到緩存中

名稱 默認值 說明
value 空字符串數組 緩存名稱,cacheNames的別名
cacheNames 空字符串數組 緩存名稱
key 空字符串 緩存key,支持SpEL表達式
depict 空字符串 緩存描述(在緩存統計頁面會用到)
ignoreException true 是否忽略在操作緩存中遇到的異常,如反序列化異常
firstCache   一級緩存配置
secondaryCache   二級緩存配置
keyGenerator   key生成器,暫時不支持配置

@CacheEvict

刪除緩存

名稱 默認值 說明
value 空字符串數組 緩存名稱,cacheNames的別名
cacheNames 空字符串數組 緩存名稱
key 空字符串 緩存key,支持SpEL表達式
allEntries false 是否刪除緩存中所有數據,默認情況下是隻刪除關聯key的緩存數據,當該參數設置成 true 時 key 參數將無效
ignoreException true 是否忽略在操作緩存中遇到的異常,如反序列化異常
keyGenerator   key生成器,暫時不支持配置
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章