Spring cloud Hystrix 服務容錯保護---斷路器(3)- 請求緩存

Hystrix有兩種方式來應對高併發場景:請求緩存和請求合併。

請求緩存:當系統併發量越來越大,每個微服務都要承受很大的壓力,因爲請求依賴服務的資源需要通過通信來實現,如果每一此都去服務提供者的服務器去請求服務,獲得數據,那麼高併發的情況下,性能會低下,Hystrix提供了請求緩存功能,我們可以開啓請求緩存功能來優化系統,以便減輕請求線程消耗和降低 請求響應時間。

 

開啓請求緩存:

繼承的方式:

public class HelloCommand extends HystrixCommand<String> {

private static final HystrixCommandGroupKey GROUP_KEY=HystrixCommandGroupKey.Factory.asKey("testgroupKey");

    private static final HystrixCommandKey COMMAND_KEY=HystrixCommandKey.Factory.asKey("testCommandKey");

    private static final HystrixThreadPoolKey THREAD_POOL_KEY=HystrixThreadPoolKey.Factory.asKey("testThreadKey");

    private RestTemplate restTemplate;

    public HelloCommand( RestTemplate restTemplate) {
        super(Setter.withGroupKey(GROUP_KEY).andCommandKey(COMMAND_KEY).andThreadPoolKey(THREAD_POOL_KEY));
        this.restTemplate=restTemplate;
    }

    @Override
    protected String run() throws Exception {
        return restTemplate.getForObject("http://helloservice-1",String.class);
    }

    @Override
    protected String getFallback() {
        return "error";
    }

    @Override
    protected String getCacheKey() {
        return "test";
    }

}

只需要重載getCacheKey方式,通過在 getCacheKey 方法中返回的請求緩存 key 值,就能讓該請求命令具備緩存功能。 此時,當不同的外部請 求處理邏輯調用了同 一 個依賴服務時, Hystrix 會根據 getCacheKey 方法返回的值來區分 是否是重複的請求,如果它們的 cacheKey 相同, 那麼該依賴服務只會在第一個請求到達時 被真實地調用一 次, 另外 一 個請求則是直接從請求緩存中返回結果。

如果只是讀操作,那麼不需要考慮緩存內容是否正確,但是如果請求命令中還有更新數據的寫操作,那麼緩存中的數據就需要我們在進行寫操作是做及時的處理,以防止讀操作讀到髒數據。

我們可以通過 HystrixRequestCache.clear() 方法來進行緩存的 清理:

public class HelloCommand extends HystrixCommand<String> {

    private static final HystrixCommandGroupKey GROUP_KEY=HystrixCommandGroupKey.Factory.asKey("testgroupKey");

    private static final HystrixCommandKey COMMAND_KEY=HystrixCommandKey.Factory.asKey("testCommandKey");

    private static final HystrixThreadPoolKey THREAD_POOL_KEY=HystrixThreadPoolKey.Factory.asKey("testThreadKey");

    private RestTemplate restTemplate;

    public HelloCommand( RestTemplate restTemplate) {
        super(Setter.withGroupKey(GROUP_KEY).andCommandKey(COMMAND_KEY).andThreadPoolKey(THREAD_POOL_KEY));
        this.restTemplate=restTemplate;
    }

    @Override
    protected String run() throws Exception {
        return restTemplate.getForObject("http://helloservice-1",String.class);
    }

    @Override
    protected String getFallback() {
        return "error";
    }

    @Override
    protected String getCacheKey() {
        return "test";
    }

    public static void flushCache(){
        HystrixRequestCache.getInstance(COMMAND_KEY,HystrixConcurrencyStrategyDefault.getInstance()).clear("test");
    }
}

1.其中getInstance方法中的第一個參數的key名稱要與實際相同 *

2.clear方法中的cacheKey要與getCacheKey方法生成的key方法相同 *

3.注意我們用了commandKey是test,大家要注意之後new這個Command的時候要指定相同的commandKey,否則會清除不成功 *

 

在執行寫操作的之後,就調用這個flushCache方法對緩存進行清理。

請求緩存不是隻寫入一次結果就不再變化的,而是每次請求到達Controller的時候,我們都需要爲HystrixRequestContext進行初始化,之前的緩存也就是不存在了,我們是在同一個請求中保證結果相同,同一次請求中的第一次訪問後對結果進行緩存,緩存的生命週期只有一次請求!

需要注意的地方:

1.flushRequestCache(),其中.clear()中的cacheKey的生成方法相同,只有把正確需要清除的key清掉纔會連同value一同清掉,從而達到清除緩存的作用。

2.清除緩存時機:我們應該在同一個Controller中進行寫操作之後,如果這個操作之後還有訪問同一資源的請求,那麼必須加清除緩存,從而保證數據同步,如果後面沒有讀操作,無須清除緩存,因爲在下一次請求到來的時候HystrixRequestContext會重置,緩存自然也沒有了

 

註解的方式:

註解 描述 屬性
@CacheResult 該註解用來標記請求命令返回的結果應該被緩存,它必須與@HystrixCommand註解結合使用 cacheKeyMethod
@CacheRemove 該註解用來讓請求命令的緩存失效,失效的緩存根據commandKey進行查找。 commandKey,cacheKeyMethod
@CacheKey 該註解用來在請求命令的參數上標記,使其作爲cacheKey,如果沒有使用此註解則會使用所有參數列表中的參數作爲cacheKey value
@CacheResult
@HystrixCommand
public User getUserByid(Long id) {
return restTemplale.getForObject("http://USER-SERVICE/users/{l}", User. class, id);
}

上面的方式會用所有的參數作爲cacheKey,也就是上面的id

 

當使用註解來定義請求緩存時,若要爲請求命令指定具體的緩存 Key 生 成規則, 我們可以使用@CacheResut和@CacheRemove 註解的cacheKeyMethod方法來指定具體的生成函數;也可以通過使用@CacheKey 註解 在方法參數中指定用於組裝緩存 Key 的元素.

@CacheResult(cacheKeyMethod = "getUserByidCacheKey") 
@HystrixCommand
public User getUserByid{Long id) {
    return restTemplale.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
}

private Long getUserByidCacheKey(Long id) { 
    return id;
}

通過@CacheKey 註解實現的方式更加簡單, 具體示例如下所示。 但是在使用 @CacheKey 註解的時候需要注意,它的優先級比 cacheKeyMethod 的優先級低,如果已 經使用了 cacheKeyMethod 指定緩存 Key 的生成函數, 那麼@CacheKey 註解不會生效。

@CacheResult
@HystrixCommand
public User getUserByid(@CacheKey("id") Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{l}",User.class,id);
}

@CacheKey 註解除了可以指定方法參數作爲緩存 Key 之外, 它還允許訪問參數對象 的內部屬性作爲緩存 Key。比如下面的例子,它指定了 User 對象的 id 屬性作爲緩存 Key。

@CacheResult
@HystrixCommand
public User getUserByid(@CacheKey("id") User user) {
    return restTemplate.getForObject("http://USER-SERVICE/users/ { 1} ", User.class, user.getid());
}

若該內容調用了 update 操作進行了更新,那麼此時請求緩 存中的結果與實際結果就會產生不一 致(緩存中的結果實際上已經失效了),所以我 們需要在 update 類型的操作上對失效的緩存進行清理。 在 Hystrix 的註解配置中, 可以通過@CacheRemove 註解來實現失效緩存的清理.

@CacheResult
@HystrixCommand(commandkey="getUserByid")
public User getUserByid(@CacheKey("id") Long id) {
return restTemplate.getForObject("http://USER-SERVICE/users/ { 1} ",id);

@CacheRemove(commandKey = "getUserByid") 
@HystrixCommand
public void update(@CacheKey("id") User user) {
    return restTemplate.postForObject("http://USER-SERVICE/users", user, User.class);
}

還可以直接在一個空方法中清理緩存:

@HystrixCommand
    @CacheRemove(commandKey = "hello" ,cacheKeyMethod = "getCacheKey")
    public void flushCache(){
        System.out.println("緩存清理。。。。。。。");
    }

controller:

@Autowired
    private HelloService helloService;
    @RequestMapping(value = "/consumer")
    public String getvalue(){
        HystrixRequestContext.initializeContext();
        //初始化HystrixRequestContext上下文
        String s1=helloService.helloServiceAysc();
        String s2=helloService.helloServiceAysc();
        helloService.flushCache();
        String s3=helloService.helloServiceAysc();
        String s4=helloService.helloServiceAysc();
        return helloService.helloService();
    }

前面提到過,下一次請求到來的時候HystrixRequestContext會重置,緩存自然也沒有了。

初始化HystrixRequestContext方法:

1、在每個用到請求緩存的Controller方法的第一行加上如下代碼:

HystrixRequestContext.initializeContext();

2、使用filter:

在啓動類加入@ServletComponentScan註解,

@WebFilter(filterName = "hystrixRequestContextServletFilter",urlPatterns = "/*",asyncSupported = true)
public class HystrixEquestContextInitFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //初始化Hystrix請求上下文
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            //請求正常通過
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            //關閉Hystrix請求上下文
            context.shutdown();
        }
    }
    @Override
    public void destroy() {

    }
}

結束語:只看書來學習,很有可能因爲版本不對,導致出現一些問題,還是要動手實踐,、

參考:https://www.cnblogs.com/hellxz/p/9056806.html

https://github.com/HellxZ/SpringCloudLearn

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章