多級緩存架構(六)


在信息暴炸的時代,為了在項目中提高數據加載效率,緩存技術是必不可以少的,緩存技術存在於應用場景的方方面面。從瀏覽器請求,到反向代理服務器,從進程內緩存到分布式緩存。其中緩存策略,算法也是層出不窮,下面要說的就是一套如何實現一套可以對后端服務器形成最小壓力的架構。

一、緩存的解析

 

 

 借一下上一篇文章中的圖,其實現在問很多人怎么提高數據的訪問速度和效率,很多人都能回答出做緩存處理,這句話可以說對也可以說不對,對是因為沒錯,緩存是可以提高效率,但並不是做了緩存處理他的並發量就一定能提升的上來,就以上圖為例,如果說前端請求很高,達到了2000K,如果所有的數據加載在后端redis中做了緩存,如果這時所有用戶請求全部從前端傳到后端,服務器照樣承受不了這么高並發,所以說只在后端提升了加載速度是沒有用的,正確的做法是把緩存在到Nginx中而不是存到后台服務器,原因就是Nginx能承受的並發比tomcat要高。

 

 

 有了這個思路后,架構就要換一下了,如上圖,當第一次請求過來時走后台去后台Redis中查詢,並存在Nginx下面的Redis,之后的所有請求就不用走后台查詢,這樣性能就提升上來了,這時還要考慮一個問題,那就是如果Nginx下的Redis本身沒有緩存數據又怎么搞,那就要去Nginx緩存中去找,因為Nginx本身是帶有緩存功能的,如果Nginx中存在要的緩存就直接從Nginx中返回給用戶,這樣一設計后可以幾十萬個請求就只有幾十個落在了后台服務器上了。這種設計除了可以有效提高加載速度、降低后端服務負載之外,還可以防止緩存雪崩,這也就是電商中用的多級緩存架構體系。

二、應用

我們在看購物平台時有很多商品優先推薦展示,這些其實都是推廣商品,並非真正意義上的熱門商品,首頁展示這些商品數據需要加載效率極高,並且商城首頁訪問頻率也是極高,所以需要對首頁數據做緩存處理,首先想到的就是Redis緩存。

 

 接下來要作的東西就是設計推廣商品的表結構及邏輯設計,其實這推廣產品不一定只在首頁出現,可能在別的地方也有,所以可以設計一張表存放這玩意,表結構如下

DROP TABLE IF EXISTS `ad_items`;
CREATE TABLE `ad_items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `type` int(3) DEFAULT NULL COMMENT '分類,1首頁推廣,2列表頁推廣',
  `sku_id` varchar(60) DEFAULT NULL COMMENT '展示的產品',
  `sort` int(11) DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=utf8;

建立實體類:

@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "ad_items")
public class AdItems implements Serializable{

    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer type;
    private String skuId;
    private Integer sort;
}
public interface AdItemsMapper extends BaseMapper<AdItems> {
}
public interface SkuService extends IService<Sku> {
    /**
     * 根據推廣產品分類ID查詢Sku列表
     * @param id
     * @return
     */
    List<Sku> typeSkuItems(Integer id);
}
@Service
public class SKuServiceImpl extends ServiceImpl<SkuMapper, Sku> implements SkuService {

    @Resource
    private AdItemsMapper adItemsMapper;

    @Resource
    private SkuMapper skuMapper;


    @Override
    public List<Sku> typeSkuItems(Integer id) {
        //1.查詢當前分類下的所有列表信息
        QueryWrapper<AdItems> adItemsQueryWrapper = new QueryWrapper<AdItems>();
        adItemsQueryWrapper.eq("type",id);
        List<AdItems> adItems = adItemsMapper.selectList(adItemsQueryWrapper);

        //2.根據推廣列表查詢產品列表信息
        List<String> skuids = adItems.stream().map(adItem->adItem.getSkuId()).collect(Collectors.toList());
        return skuids==null || skuids.size()<=0? null : skuMapper.selectBatchIds(skuids);
    }
}
@RestController
@RequestMapping(value = "/sku")
public class SkuController {
    @Autowired
    private SkuService skuService;

    /****
     * 根據推廣分類查詢推廣產品列表
     *
     */
    @GetMappingpublic List<Sku> typeItems(@RequestParam(value = "id")Integer id){
        //查詢
        List<Sku> skus = skuService.typeSkuItems(id);
        return skus;
    }
}

經過上面的代碼就可以實現熱門商品推廣操作,但是有個問題,因為前面說過熱點數據訪問量大,是要放在緩存里面進行緩存處理的,下面就來完成這最后一步

三、緩存處理

在改代碼前先要說明幾個常用的注解:

@EnableCaching :開關性注解,在項目啟動類或某個配置類上使用此注解后,則表示允許使用注解的方式進行緩存操作。
@Cacheable :可用於類或方法上;在目標方法執行前,會根據key先去緩存中查詢看是否有數據,有就直接返回緩存中的key對應的value值。不再執行目標方法;無則執行目標方法,並將方法的返回值作為value,並以鍵值對的形式存入緩存。
@CacheEvict :可用於類或方法上;在執行完目標方法后,清除緩存中對應key的數據(如果緩存中有對應key的數據緩存的話)。
@CachePut :可用於類或方法上;在執行完目標方法后,並將方法的返回值作為value,並以鍵值對的形式存入緩存中。
@Caching :此注解即可作為@Cacheable、@CacheEvict、@CachePut三種注解中的的任何一種或幾種來使用。
@CacheConfig :可以用於配置@Cacheable、@CacheEvict、@CachePut這三個注解的一些公共屬性,例如cacheNames、keyGenerator。
說明完注解接下來就是來實現緩存功能了,首先在配置文件中配置Redis緩存
  #Redis配置
  redis:
    host: 192.168.32.135
    port: 6379

然后在啟動類上開啟緩存

 

 然后修改SKuServiceImpl類,加上紅框內內容

 

 完成這一步緩存就做好了,但是有個問題,前面說過熱點商品可能在多個地方都存被調用的情況,針對這個情況就要寫feigin接口,在寫feigin接口同時我也順便把修改和刪除操作給寫了,后面就懶着改這個類了

在spring-cloud-api的pom文件中先引用以下包

        <!--工具包-->
        <dependency>
            <groupId>com.ghy</groupId>
            <artifactId>spring-cloud-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

然后在spring-cloud-goods-api服務器寫feigin接口

@FeignClient(value = "spring-cloud-goods-service")   
public interface SkuFeign {


/****
* 根據推廣分類查詢推廣產品列表
*/
@GetMapping(value = "/sku")
List<Sku> typeItems(@RequestParam(value = "id")Integer id);

/****
* 根據推廣分類刪除推廣產品列表
*
*/
@DeleteMapping(value = "/sku")
RespResult delTypeItems(@RequestParam(value = "id")Integer id);

/****
* 根據推廣分類修改推廣產品列表
*/
@PutMapping(value = "/sku")
RespResult updateTypeItems(@RequestParam(value = "id")Integer id);

}

四、Lua+Redis實現多級緩存

 

 前面說過Lua和多級緩存,接下來就把這兩個東西結合起來實現;前面說過前端請求過來后第一次會通過Nginx會分發到tomact進行查詢,然后將查詢的數據放到nginx中去;現在要做的操作就是通過Lua腳本完善后面的操作,操作流程是這樣,當請求過來

nginx會先執行lua腳本數據先從redis中去查詢先看下redis有沒有對應的數據,如果有則直接把數據響應給用戶,沒有再經過tomact查詢;這時有的人會問,如果一不小心把Redis服務器中的數據全清空了怎么辦,其實問題也不大,前面也提過Nginx本身也帶有緩存,當Redis沒查找數據時,他會去Nginx自身查下,看能不能找到數據;

下面先寫一個lua腳本;

進入/usr/local/openresty/nginx/lua,在里面創建一個aditem.lua腳本

--數據響應類型JSON 
ngx.header.content_type="application/json;charset=utf8"
--Redis庫依賴 
local redis = require("resty.redis");
local cjson = require("cjson"); 
--獲取id參數(type) 
local id = ngx.req.get_uri_args()["id"]; 
--key組裝 
local key = "ad-items-skus::"..id 
--創建鏈接對象 
local red = redis:new() 
--設置超時時間 red:set_timeout(2000) 
--設置服務器鏈接信息 
red:connect("192.168.32.135", 6379) 
--查詢指定key的數據 
local result=red:get(key); 
--關閉Redis鏈接 
red:close()
if result==nil or result==null or result==ngx.null then 
return true 
else
--輸出數據 
ngx.say(result) 
end
修改 nginx.conf 添加如下配置:
#推廣產品查詢 
location /sku{ 
content_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua;
 }

 

 

 

然后保存修改,打入nginx -s reload命令重啟下再訪問 http://www.ljx.com/sku?id=1 就可以拿到數據,這個請求跟你后台服務器開沒開都沒有關系了。

 

 

五、nginx代理緩存

前面有提過,當Redis緩存沒有數據時,請求會去nginx緩存找數據;在這時就要提到一個東西,那就是proxy_cache ;proxy_cache 是用於 proxy 模式的緩存功能,proxy_cache 在 Nginx 配置的 http 段、server 段中分別寫入不同的配置。http 段中的配置用於定義 proxy_cache 空間,server 段中的配置用於調用 http 段中的定義,啟用對server 的緩存功能。要想使用它就要完成兩個步驟:第一個步驟是定義緩存空間;第二個步驟是在指定地方使用定義的緩存 。

1、開啟Proxy_Cache緩存,這個需要在nginx.conf中配置才能開啟緩存:

proxy_cache_path /usr/local/openresty/nginx/cache levels=1:2 keys_zone=proxy_cache:10m max_size=1g inactive=60m use_temp_path=off;
參數說明:
【proxy_cache_path】指定緩存存儲的路徑,緩存存儲在/usr/local/openresty/nginx/cache目錄 
【levels=1:2】設置一個兩級目錄層次結構存儲緩存,在單個目錄中包含大量文件會降低文件訪問速度,因此我們建議對大多數部署使用兩級目錄層次結構。如果 levels 未包含該參數,Nginx 會將所有文件放在同一目錄中。 
【keys_zone=proxy_cache:10m】設置共享內存區域,用於存儲緩存鍵和元數據,例如使用計時器。擁有內存中的密鑰副本,Nginx 可以快速確定請求是否是一個 HIT 或 MISS 不必轉到磁盤,從而大大加快了檢查速度。1 MB 區域可以存儲大約 8,000 個密鑰的數據,因此示例中配置的 10 MB 區域可以存儲大約80,000 個密鑰的數據。
【max_size=1g】設置緩存大小的上限。它是可選的; 不指定值允許緩存增長以使用所有可用磁盤空間。當緩存大小達到限制時,一個稱為緩存管理器的進程將刪除最近最少使用的緩存,將大小恢復到限制之下的文件。
【inactive=60m】指定項目在未被訪問的情況下可以保留在緩存中的時間長度。在此示例中,緩存管理器進程會自動從緩存中刪除 60 分鍾未請求的文件,無論其是否已過期。默認值為 10 分鍾(10m)。非活動內容與過期內容不同。Nginx 不會自動刪除緩存 header 定義為已過期內容(例如 Cache-Control:max-age=120)。過期(陳舊)內容僅在指定時間內未被訪問時被刪除。訪問過期內容時,Nginx 會從原始服務器刷新它並重置 inactive 計時器。
【use_temp_path=off】表示NGINX會將臨時文件保存在緩存數據的同一目錄中。這是為了避免在更新緩存時,磁盤之間互相復制響應數據,我們一般關閉該功能。
Proxy_Cache屬性:
proxy_cache:設置是否開啟對后端響應的緩存,如果開啟的話,參數值就是zone的名稱,比如:proxy_cache。
proxy_cache_valid:針對不同的response code設定不同的緩存時間,如果不設置code,默認為200,301,302,也可以用any指定所有code。
proxy_cache_min_uses:指定在多少次請求之后才緩存響應內容,這里表示將緩存內容寫入到磁盤。
proxy_cache_lock:默認不開啟,開啟的話則每次只能有一個請求更新相同的緩存,其他請求要么等待緩存有數據要么限時等待鎖釋放;nginx 1.1.12才開始有。配套着proxy_cache_lock_timeout一起使用。
proxy_cache_key:緩存文件的唯一key,可以根據它實現對緩存文件的清理操作。 
2、開啟Proxy_Cache緩存后修改 nginx.conf;在location /sku中加入以下配置
 #先找Nginx緩存 
 rewrite_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua; 
 #啟用緩存openresty_cache 
 proxy_cache proxy_cache; 
 #針對指定請求緩存 
 #proxy_cache_methods GET; 
 #設置指定請求會緩存 
 proxy_cache_valid 200 304 60s; 
 #最少請求1次才會緩存 
 proxy_cache_min_uses 1; 
 #如果並發請求,只有第1個請求會去服務器獲取數據 
 #proxy_cache_lock on; 
 #唯一的key 
 proxy_cache_key $host$uri$is_args$args; 
 #動態代理,這是在緩存中都沒查到的情況 
proxy_pass http:
//192.168.32.32:8081;

 

 

重啟nginx或者重新加載配置文件 nginx -s reload ,再次測試,可以發現下面個規律:
 
1:先查找Redis緩存 
2:Redis緩存沒數據,直接找Nginx緩存 
3:Nginx緩存沒數據,則找真實服務器

這時還可以發現在cache目錄下多了目錄個文件,這個東西就是Nginx緩存;這里補充下,如果你的linux是布在雲上而你的項目是內外,那么動態代理配置的就是項目的公網地址;

六、Cache_Purge代理緩存清理

上面講了Nginx緩存的保存,但是在很多時候我們如果不想等待緩存的過期,想要主動清除緩存,可以采用第三方的緩存清除模塊清除緩存nginx_ngx_cache_purge 。安裝nginx的時候,需要添加 purge 模塊purge 模塊我們已經下載了,這一個步驟我在安裝 OpenRestry 的時候已經實現了。在OpenRestry文章中也有說明,安裝好了后,我們配置一個清理緩存的地址:http://192.168.32.32/purge/sku?id=1 
 
#清理緩存 
location ~ /purge(/.*) {
#清理緩存
proxy_cache_purge proxy_cache $host$1$is_args$args;
}

 

 

此時訪問http://www.ljx.com/purge/sku?id=1,表示清除緩存,如果出現如下
效果表示清理成功: 

 

 


 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM