天天看點

9.Spring-Cloud-Hystrix之請求緩存(踩坑)

          當系統的使用者不斷增長時, 每個微服務需要承受的并發壓力越來越大。在分布式環境下,通常壓力來自于對依賴服務的調用,因為請求依賴服務的資源需要通過通信來實作,這樣的依賴方式比起程序内的調用方式引起一部分的性能損失,同時HTTP相比于其他高性能的通信協定在速度上沒有任何優勢,是以它有些類似于對資料庫這樣的外部資源進行讀寫操作,在高并發的情況下可能會成為系統的瓶頸。

       在高并發的場景之下,Hystrix中提供了請求緩存的功能,可以很友善的開啟和使用請求緩存來優化系統,達到減輕高并發時的請求線程消耗、降低請求響應時間的效果。

具體如下:

一:服務消費者

1.pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>6.spring-cloud-hystrix-consumer</groupId>
<artifactId>hystrix-consumer</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud Maven Webapp</name>
<url>http://maven.apache.org</url>
<!--springboot采用1.5.x 對應springcloud版本為 Dalston -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.RELEASE</spring-cloud.version>
</properties>

     <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

<!--引入ribbon 負載均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<!-- 搞了好久踩的坑,就是在複制上面代碼的時候 順手把scope test複制了,其意思為緊參與junit類似的測試工作,是以一直報找不到服務或者或者拒絕連接配接 -->
<!-- <scope>test</scope> -->
</dependency>
<!--引入hystrix熔斷器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!--引入hystrix dashboard(儀表盤)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
        <!--fastjson  -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.15</version>
</dependency>

</dependencies>

        <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 這樣變成可執行的jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
           

2.啟動類

package com.niugang;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;
/**
 * 負責服務的發現與消費
 * 
 * @author niugang
 *
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
/*
 * @SpringCloudApplication
 * 包含了
 * @SpringBootApplication
 * @EnableDiscoveryClient
 * @EnableCircuitBreaker
 * 着三個注解
 */

@ComponentScan
public class Application {
    //負載均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {

SpringApplication.run(Application.class, args);
}
}
           

3.controller

@RequestMapping(value = "/queryUser/{id}")
public Map queryUser(@PathVariable("id")String id) {
    
String queryUser1 = helloService.queryUser(id);
logger.info("第一次調用資料:{}",queryUser1);
String queryUser2 = helloService.queryUser(id);
logger.info("第二次調用資料:{}",queryUser2);
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("one", queryUser1);
hashMap.put("two", queryUser2);
return hashMap;

}
@RequestMapping(value = "/updateUser")
public String updateUser(Integer id,String username,String phone) {
return helloService.updateUser(id,username,phone);
}
           

4.service

/**
* 查詢使用者資訊
* 1.execution.isolation.thread.timeoutInMilliseconds:調用目前依賴服務,響應超過2000ms将執行降級服務處理,
* 
* 在配置檔案中配置全局的逾時時間:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 
* 
* [email protected] 預設方法參數将作為緩存的key執行
* 也可通過String queryUser(@CacheKey("id") String id) 這樣設定,而且用@CacheKey會報錯,問題還未解決
* 也可通過@CacheResult(cacheKeyMethod="getKeyMethod")優先級比@CacheKey("id")高
*/
@CacheResult
@HystrixCommand(fallbackMethod = "queryUserBackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
@HystrixProperty(name = "requestCache.enabled", value = "true") 
})
public String queryUser( String id) {


String body = restTemplate
.postForEntity("http://service-provide/queryUser/{1}", String.class, String.class,id)
.getBody();
return  body;


}

     /**
* commandKey用于找到适當的Hystrix指令,緩存應該被清除
*/
@CacheRemove(cacheKeyMethod = "getKeyMethod", commandKey = "queryUser")
@HystrixCommand(fallbackMethod = "queryUserBackMethod")
public String updateUser(Integer id, String username, String phone) {
User user = new User(id, username, phone);
return restTemplate.postForEntity("http://service-provide/updateUser", user, String.class).getBody();
}


/**
* key隻支援String類型的
* 
* @param id
* @return
*/
public String getKeyMethod(String id) {
return id;

}

     /**
* 回退函數必須和他正常調用函數必須具有相同的
*/
public String queryUserBackMethod(String id, Throwable e) {
return "error1:" + e.getMessage();
}

5.filter   存放filter的包
           
package com.niugang.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

@WebFilter(urlPatterns = "/*")
public class RequestCacheFilter implements javax.servlet.Filter {
private static Logger logger = LoggerFactory.getLogger(RequestCacheFilter.class);
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("初始化init filter...");
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("執行filter");
/**
* 不初始化,會報如下錯誤
* Request caching is not available. Maybe you need to
* initialize the HystrixRequestContext
* 初始化是在filter中進行(官方建議),但是每一次初始化之前的緩存就失效了,是以要測緩存,就隻能在controller中調用兩次,
* 才能看到緩存的結果是否相同
*  在同一使用者請求的上下文中,相同依賴服務的傳回資料始終保持一緻  ---《spring cloud 微服務實戰》有争論
*/
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
chain.doFilter(request, response);
} finally {
context.shutdown();
}
}
public void destroy() {
// TODO Auto-generated method stub
}


}
           

5.config  存放配置檔案的包

package com.niugang.config;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * 
 * @author niugang 掃描過濾器

 *
 */
@ServletComponentScan(basePackages="com.niugang.filter")
@Configuration
public class FilterCompentConfig {
}
           

6.entity

package com.niugang.entity;
import java.io.Serializable;
/**
 * 使用者實體
 * 
 * @author niugang
 *
 */
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String username;
private String phone;
private int  randNum;
//
public User() {
super();
}


public User(Integer id, String username, String phone) {
super();
this.id = id;
this.username = username;
this.phone = phone;
}


public Integer getId() {
return id;
}


public void setId(Integer id) {
this.id = id;
}


public String getUsername() {
return username;
}


public void setUsername(String username) {
this.username = username;
}


public String getPhone() {
return phone;
}


public void setPhone(String phone) {
this.phone = phone;
}


public int getRandNum() {
return randNum;
}


public void setRandNum(int randNum) {
this.randNum = randNum;
}
           

二.服務提供者改動controller

package com.niugang.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.niugang.controller.entity.User;
@RestController
public class ComputeController {

private final Logger logger = LoggerFactory.getLogger(ComputeController.class);
public static List<User> list = new ArrayList<User>();
static {
User user1 = new User(1, "張三", "134565676776");
list.add(user1);
User user2 = new User(2, "李四", "134565676776");
list.add(user2);
User user3 = new User(3, "王五", "134565676776");
list.add(user3);
User user4 = new User(4, "趙六", "134565676776");
list.add(user4);
User user5 = new User(5, "田七", "134565676776");
list.add(user5);
}
@Autowired

private DiscoveryClient client;

       @RequestMapping(value = "/hello", method = RequestMethod.GET)
public String add() {
ServiceInstance serviceInstance = null;
// 方法已過時,不建議使用
// ServiceInstance instance = client.getLocalServiceInstance();
List<String> services = client.getServices();
if (!services.isEmpty() && services.size() > 0) {
String serviceId = services.get(0);
List<ServiceInstance> instances = client.getInstances(serviceId);
if (!instances.isEmpty() && instances.size() > 0) {
serviceInstance = instances.get(0);
logger.info(
"/hello, host:" + serviceInstance.getHost() + ", service_id:" + serviceInstance.getServiceId());
}
}


return "hello spring cloud" + serviceInstance.getHost();

}

/**
* 根據id擷取使用者資訊
* 
* @param id
* @return
* @throws InterruptedException 
*/
@RequestMapping(value = "/queryUser/{id}", method = RequestMethod.POST)
public String queryUser(@PathVariable("id") Integer id) throws InterruptedException {
logger.info("query user info id:{}", id);
//------------start----------------------------//
//示範請求逾時,當産生的随機數大于2000,消費者就會進行服務降級處理,因為請求逾時了
int nextInt = new Random().nextInt(3000);
Thread.sleep(nextInt);
logger.info("query user info id:{},sleep:{}", id,nextInt);
//------------end----------------------------//
if (id == null) {
throw new RuntimeException("使用者id不能為空");
}
User user = list.get(id - 1);

if (user == null) {
throw new RuntimeException("使用者不存在");
}
user.setRandNum(new Random().nextInt(100));
return JSONObject.toJSONString(user);

}

     /**
     *更新使用者資訊
     * @param user
     * @return
     */
@RequestMapping(value = "/updateUser", method = RequestMethod.POST)
public String updateUser(User user) {
logger.info("update user info id:{}", user.getId());
try {
Integer id = user.getId();
User set = list.set(id - 1, user);
return JSONObject.toJSONString(set);
} catch (Exception e) {
throw new RuntimeException("更新失敗");
}
}
}
           

啟動高可能注冊中心(之前部落格中有),啟動服務提供者(之前部落格中有),啟動消費者。

一次浏覽器請求,在controller中調用兩次服務,randNum資料一樣,說明第二次是從緩存中拿的資料。

微信公衆号

9.Spring-Cloud-Hystrix之請求緩存(踩坑)

繼續閱讀