基於 request cache 請求緩存技術優化批量商品數據查詢接口

基於 request cache 請求緩存技術優化批量商品數據查詢接口

Hystrix command 執行時 8 大步驟第三步,就是檢查 Request cache 是否有緩存。java

首先,有一個概念,叫作 Request Context 請求上下文,通常來講,在一個 web 應用中,若是咱們用到了 Hystrix,咱們會在一個 filter 裏面,對每個請求都施加一個請求上下文。就是說,每一次請求,就是一次請求上下文。而後在此次請求上下文中,咱們會去執行 N 多代碼,調用 N 多依賴服務,有的依賴服務可能還會調用好幾回。web

在一次請求上下文中,若是有多個 command,參數都是同樣的,調用的接口也是同樣的,而結果能夠認爲也是同樣的。那麼這個時候,咱們可讓第一個 command 執行返回的結果緩存在內存中,而後這個請求上下文後續的其它對這個依賴的調用所有從內存中取出緩存結果就能夠了。緩存

這樣的話,好處在於不用在一次請求上下文中反覆屢次執行同樣的 command,避免重複執行網絡請求,提高整個請求的性能bash

舉個栗子。好比說咱們在一次請求上下文中,請求獲取 productId 爲 1 的數據,第一次緩存中沒有,那麼會從商品服務中獲取數據,返回最新數據結果,同時將數據緩存在內存中。後續同一次請求上下文中,若是還有獲取 productId 爲 1 的數據的請求,直接從緩存中取就行了。網絡

HystrixCommand 和 HystrixObservableCommand 均可以指定一個緩存 key,而後 Hystrix 會自動進行緩存,接着在同一個 request context 內,再次訪問的話,就會直接取用緩存。app

下面,咱們結合一個具體的業務場景,來看一下如何使用 request cache 請求緩存技術。固然,如下代碼只做爲一個基本的 Demo 而已。ide

如今,假設咱們要作一個批量查詢商品數據的接口,在這個裏面,咱們是用 HystrixCommand 一次性批量查詢多個商品 id 的數據。可是這裏有個問題,若是說 Nginx 在本地緩存失效了,從新獲取一批緩存,傳遞過來的 productIds 都沒有進行去重,好比 productIds=1,1,1,2,2,那麼可能說,商品 id 出現了重複,若是按照咱們以前的業務邏輯,可能就會重複對 productId=1 的商品查詢三次,productId=2 的商品查詢兩次。性能

咱們對批量查詢商品數據的接口,能夠用 request cache 作一個優化,就是說一次請求,就是一次 request context,對相同的商品查詢只執行一次,其他重複的都走 request cache。優化

實現 Hystrix 請求上下文過濾器並註冊

定義 HystrixRequestContextFilter 類,實現 Filter 接口。this

/** * Hystrix 請求上下文過濾器 */
public class HystrixRequestContextFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (IOException | ServletException e) {
            e.printStackTrace();
        } finally {
            context.shutdown();
        }
    }

    @Override
    public void destroy() {

    }
}
複製代碼

而後將該 filter 對象註冊到 SpringBoot Application 中。

@SpringBootApplication
public class EshopApplication {

    public static void main(String[] args) {
        SpringApplication.run(EshopApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new HystrixRequestContextFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
}
複製代碼

command 重寫 getCacheKey() 方法

在 GetProductInfoCommand 中,重寫 getCacheKey() 方法,這樣的話,每一次請求的結果,都會放在 Hystrix 請求上下文中。下一次同一個 productId 的數據請求,直接取緩存,無須再調用 run() 方法。

public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {

    private Long productId;

    private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand");

    public GetProductInfoCommand(Long productId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
                .andCommandKey(KEY));
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() {
        String url = "http://localhost:8081/getProductInfo?productId=" + productId;
        String response = HttpClientUtils.sendGetRequest(url);
        System.out.println("調用接口查詢商品數據,productId=" + productId);
        return JSONObject.parseObject(response, ProductInfo.class);
    }

    /** * 每次請求的結果,都會放在Hystrix綁定的請求上下文上 * * @return cacheKey 緩存key */
    @Override
    public String getCacheKey() {
        return "product_info_" + productId;
    }

    /** * 將某個商品id的緩存清空 * * @param productId 商品id */
    public static void flushCache(Long productId) {
        HystrixRequestCache.getInstance(KEY,
                HystrixConcurrencyStrategyDefault.getInstance()).clear("product_info_" + productId);
    }
}
複製代碼

這裏寫了一個 flushCache() 方法,用於咱們開發手動刪除緩存。

controller 調用 command 查詢商品信息

在一次 web 請求上下文中,傳入商品 id 列表,查詢多條商品數據信息。對於每一個 productId,都建立一個 command。

若是 id 列表沒有去重,那麼重複的 id,第二次查詢的時候就會直接走緩存。

@Controller
public class CacheController {

    /** * 一次性批量查詢多條商品數據的請求 * * @param productIds 以,分隔的商品id列表 * @return 響應狀態 */
    @RequestMapping("/getProductInfos")
    @ResponseBody
    public String getProductInfos(String productIds) {
        for (String productId : productIds.split(",")) {
            // 對每一個productId,都建立一個command
            GetProductInfoCommand getProductInfoCommand = new GetProductInfoCommand(Long.valueOf(productId));
            ProductInfo productInfo = getProductInfoCommand.execute();
            System.out.println("是不是從緩存中取的結果:" + getProductInfoCommand.isResponseFromCache());
        }

        return "success";
    }
}
複製代碼

發起請求

調用接口,查詢多個商品的信息。

http://localhost:8080/getProductInfos?productIds=1,1,1,2,2,5
複製代碼

在控制檯,咱們能夠看到如下結果。

調用接口查詢商品數據,productId=1
是不是從緩存中取的結果:false
是不是從緩存中取的結果:true
是不是從緩存中取的結果:true
調用接口查詢商品數據,productId=2
是不是從緩存中取的結果:false
是不是從緩存中取的結果:true
調用接口查詢商品數據,productId=5
是不是從緩存中取的結果:false
複製代碼

第一次查詢 productId=1 的數據,會調用接口進行查詢,不是從緩存中取結果。而隨後再出現查詢 productId=1 的請求,就直接取緩存了,這樣的話,效率明顯高不少。

刪除緩存

咱們寫一個 UpdateProductInfoCommand,在更新商品信息以後,手動調用以前寫的 flushCache(),手動將緩存刪除。

public class UpdateProductInfoCommand extends HystrixCommand<Boolean> {

    private Long productId;

    public UpdateProductInfoCommand(Long productId) {
        super(HystrixCommandGroupKey.Factory.asKey("UpdateProductInfoGroup"));
        this.productId = productId;
    }

    @Override
    protected Boolean run() throws Exception {
        // 這裏執行一次商品信息的更新
        // ...

        // 而後清空緩存
        GetProductInfoCommand.flushCache(productId);
        return true;
    }
}
複製代碼

這樣,之後查詢該商品的請求,第一次就會走接口調用去查詢最新的商品信息。

相關文章
相關標籤/搜索