前言:
在高并发的场景下,Spring Cloud通过Hystrix的请求缓存和请求合并来减轻高并发时的请求线程消耗、降低请求响应时间的效果。
这里以注解的方式进行请求缓存的演示。
请求缓存:在同一次请求的多次访问中,保证只访问一次服务提供者提供的服务接口。在同一次请求中只有第一次访问去调用服务提供者提供的服务接口并将返回结果进行保存,从而保证同一次请求中同样的多次访问返回结果相同。
进行演示的前提是已经具备了注册中心eureka-server、服务提供者hello-service、服务消费者ribbon-consume。
-----------------------------------------------请求缓存-------------------------------------------------
一、我们在服务提供者添加一个获取随机数的一个接口(HelloController),供消费者调用。
//生成随机数
@RequestMapping("/hystrix/randomInt")
public Integer getRandomInteger(){
Random random = new Random();
int randomInt = random.nextInt(99999);
return randomInt;
}
二、在服务消费者中添加调用服务提供者提供的获取随机数的接口。
1、这里使用自定义缓存key的方式。
1.1、新增一个HystrixCacheService:
- 添加一个缓存方法(cacheByDefinedCacheKey),将结果进行缓存,使用@CacheResult来标记这是一个缓存方法,是请求命令返回的结果被缓存
- 添加一个生成缓存key的方法(getCacheKey),使用@CacheResult的cacheKeyMethod属性指定key的生成函数,这里要注意生成缓存key的方法的参数要和缓存方法的参数保持一致并且返回值为String类型。
/**
* @author HDN
* @date 2019/6/20 20:44
*/
@Service
public class HystrixCacheService {
private static Logger logger = Logger.getLogger(String.valueOf(HystrixCacheService.class));
@Autowired
RestTemplate restTemplate;
@CacheResult(cacheKeyMethod = "getCacheKey")
@HystrixCommand(commandKey = "commandKey1")
public Integer cacheByDefinedCacheKey(Long id){
//此次结果会被缓存
return restTemplate.getForObject("http://hello-service/hystrix/randomInt", Integer.class);
}
public String getCacheKey(Long id){
System.out.println("getCacheKey:"+id);
return String.valueOf(id);
}
}
1.2、新增一个HystrixCacheController:
- 实现一个/cacheByDefinedKey接口
测试1:在/cacheByDefinedKey接口来调用缓存方法,测试缓存
/**
* @author HDN
* @date 2019/6/20 20:47
*/
@RestController
public class HystrixCacheController {
private static Logger logger = Logger.getLogger(String.valueOf(HystrixCacheController.class));
@Autowired
HystrixCacheService hystrixCacheService;
@RequestMapping(value = "/cacheByDefinedCacheKey",method = RequestMethod.GET)
public String cacheByDefinedCacheKey(){
//初始化Hystrix请求上下文
HystrixRequestContext.initializeContext();
//访问并开启缓存,在同一请求中两次访问了服务提供者的方法,第一次访问的结果会被缓存
Integer result1 = hystrixCacheService.cacheByDefinedCacheKey(1L);
Integer result2 = hystrixCacheService.cacheByDefinedCacheKey(1L);
logger.info("first request result is:"+ result1+" and secend request result is:"+result2);
return "SUCCESS";
}
}
访问http://localhost:9000/cacheByDefinedCacheKey显示SUCCESS表示访问成功,我们来看下ribbon-consume的控制台,可以看到两次结果一样:
结论1:我们发送了一次请求访问两次,得到两次随机数的结果一样的。这是因为:我们在调用cacheByDefinedCacheKey方法的时候把两次访问的key值都设置为1,缓存key一样,在访问的时候第一次去请求服务提供者提供的服务将返回的结果保存在key为1的缓存中,第二次我们再去访问的时候,缓存发现已经有key为1的返回结果了,从而不去调用服务提供者提供的方法,直接将缓存的结果返回。
=========================================================
测试2:我们将修改调用cacheByDefinedCacheKey的key值,是两次访问的key值不一样:
/**
* @author HDN
* @date 2019/6/20 20:47
*/
@RestController
public class HystrixCacheController {
private static Logger logger = Logger.getLogger(String.valueOf(HystrixCacheController.class));
@Autowired
HystrixCacheService hystrixCacheService;
@RequestMapping(value = "/cacheByDefinedCacheKey",method = RequestMethod.GET)
public String cacheByDefinedCacheKey(){
//初始化Hystrix请求上下文
HystrixRequestContext.initializeContext();
//访问并开启缓存,在同一请求中两次访问了服务提供者的方法,第一次访问的结果会被缓存
Integer result1 = hystrixCacheService.cacheByDefinedCacheKey(1L);
Integer result2 = hystrixCacheService.cacheByDefinedCacheKey(2L);
logger.info("first request result is:"+ result1+" and secend request result is:"+result2);
return "SUCCESS";
}
}
访问http://localhost:9000/cacheByDefinedCacheKey显示SUCCESS表示访问成功,我们来看下ribbon-consume的控制台,可以看到两次结果不一样:
结论2:请求一次访问两次,访问两次的key值不一样,每一次都会去调用服务提供者提供的获取随机数的接口。
从测试1和测试2中我们可以得出:请求缓存是在同一个请求多次访问中,保证只请求一次服务提供者提供的接口,会将第一次访问的结果进行缓存,从而保证同一请求的多次访问的返回结果相同。这是因为每次请求都会看是否已经存在相同的key的缓存结果,如果已经存在当前访问相同的key,那么就直接从请求缓存中返回结果不会再次调用。
=========================================================
测试3:测试缓存清理:@CacheRemove注解来实现失效缓存的清理
- 在HystrixCacheService中添加清理缓存的方法(flushCacheByDefinedCacheKey)
/**
* @author HDN
* @date 2019/6/20 20:44
*/
@Service
public class HystrixCacheService {
private static Logger logger = Logger.getLogger(String.valueOf(HystrixCacheService.class));
@Autowired
RestTemplate restTemplate;
@CacheResult(cacheKeyMethod = "getCacheKey")
@HystrixCommand(commandKey = "commandKey1")
public Integer cacheByDefinedCacheKey(Long id){
//此次结果会被缓存
return restTemplate.getForObject("http://hello-service/hystrix/randomInt", Integer.class);
}
@CacheRemove(commandKey = "commandKey1", cacheKeyMethod = "getCacheKey")
@HystrixCommand
public void flushCacheByDefinedCacheKey(Long id){
logger.info("请求缓存已清空!");
}
public String getCacheKey(Long id){
return String.valueOf(id);
}
}
- 修改HystrixCacheController中的/cacheByDefinedKey方法,使其调用清理缓存的方法。
/**
* @author HDN
* @date 2019/6/20 20:47
*/
@RestController
public class HystrixCacheController {
private static Logger logger = Logger.getLogger(String.valueOf(HystrixCacheController.class));
@Autowired
HystrixCacheService hystrixCacheService;
@RequestMapping(value = "/cacheByDefinedCacheKey",method = RequestMethod.GET)
public String cacheByDefinedCacheKey(){
//初始化Hystrix请求上下文
HystrixRequestContext.initializeContext();
//访问并开启缓存,在同一请求中两次访问了服务提供者的方法,第一次访问的结果会被缓存
Integer result1 = hystrixCacheService.cacheByDefinedCacheKey(1L);
Integer result2 = hystrixCacheService.cacheByDefinedCacheKey(1L);
logger.info("first request result is:"+ result1+" and secend request result is:"+result2);
//清除缓存
hystrixCacheService.flushCacheByDefinedCacheKey(1L);
//再一次访问并开启缓存
Integer result3 = hystrixCacheService.cacheByDefinedCacheKey(1L);
Integer result4 = hystrixCacheService.cacheByDefinedCacheKey(1L);
logger.info("清除缓存之后再次访问:first request result is:"+ result3+" and secend request result is:"+result4);
return "SUCCESS";
}
}
访问http://localhost:9000/cacheByDefinedCacheKey显示SUCCESS表示访问成功,我们来看下ribbon-consume的控制台,可以看到结果如下:
调用清理缓存的方法之后,又会重新去调用服务提供者提供的服务:
从中我们可以看出清理缓存前后数据发生了变化。
结论3:当我们在发送一次请求多次访问的时候,如果在访问过程中进行了缓存的清理,那么就会再次去调用服务提供者提供的服务。
需要注意的是,@CacheRemove注解的commandKey属性是必须要指定的,它用来指明需要使用请求缓存的请求命令,因为只有通过该属性的配置,Hystrix才能找到正确的请求命令缓存位置。如果没有配置启动的时候会报错,如下:
2、使用@CacheKey注解
2.1、在HystrixCacheService中先添加如下的代码:
//----------------使用@CacheKey注解-------------------------
@CacheResult
@HystrixCommand(commandKey = "commandKey1")
public Integer cacheByAnnotationCacheKey(@CacheKey Long id){
//此次结果会被缓存
return restTemplate.getForObject("http://hello-service/hystrix/randomInt", Integer.class);
}
@CacheRemove(commandKey = "commandKey1")
@HystrixCommand
public void flushCacheByAnnotationCacheKey(@CacheKey Long id){
logger.info("请求缓存已清空!");
}
2.2、在HystrixCacheController中实现一个/cacheByAnnotationCacheKey接口:
@RequestMapping(value = "/cacheByAnnotationCacheKey",method = RequestMethod.GET)
public String cacheByAnnotationCacheKey(){
//初始化Hystrix请求上下文
HystrixRequestContext.initializeContext();
//访问并开启缓存,在同一请求中两次访问了服务提供者的方法,第一次访问的结果会被缓存
Integer result1 = hystrixCacheService.cacheByAnnotationCacheKey(1L);
Integer result2 = hystrixCacheService.cacheByAnnotationCacheKey(1L);
logger.info("first request result is:"+ result1+" and secend request result is:"+result2);
//清除缓存
hystrixCacheService.flushCacheByAnnotationCacheKey(1L);
//再一次访问并开启缓存
Integer result3 = hystrixCacheService.cacheByAnnotationCacheKey(1L);
Integer result4 = hystrixCacheService.cacheByAnnotationCacheKey(1L);
logger.info("清除缓存之后再次访问:first request result is:"+ result3+" and secend request result is:"+result4);
return "SUCCESS";
}
测试:访问http://localhost:9000/cacheByAnnotationCacheKey显示SUCCESS表示访问成功,我们来看下ribbon-consume的控制台,可以看到结果如下:
这里需要注意的是,使用@CacheKey注解的优先级比cacheKeyMethod的优先级要第,两者都存在@cacheKey注解不会生生效。
3、默认使用所有参数作为缓存key
3.1、在HystrixCacheService中先添加如下的代码:
//----------------默认使用所有的参数作为缓存key-------------------------
@CacheResult
@HystrixCommand(commandKey = "commandKey1")
public Integer cacheByDefaultCacheKey(Long id){
//此次结果会被缓存
return restTemplate.getForObject("http://hello-service/hystrix/randomInt", Integer.class);
}
@CacheRemove(commandKey = "commandKey1")
@HystrixCommand
public void flushCacheByDefaultCacheKey(Long id){
logger.info("请求缓存已清空!");
}
3.2、在HystrixCacheController中实现一个/cacheByDefaultCacheKey接口:
@RequestMapping(value = "/cacheByDefaultCacheKey",method = RequestMethod.GET)
public String cacheByDefaultCacheKey(){
//初始化Hystrix请求上下文
HystrixRequestContext.initializeContext();
//访问并开启缓存,在同一请求中两次访问了服务提供者的方法,第一次访问的结果会被缓存
Integer result1 = hystrixCacheService.cacheByDefaultCacheKey(1L);
Integer result2 = hystrixCacheService.cacheByDefaultCacheKey(1L);
logger.info("first request result is:"+ result1+" and secend request result is:"+result2);
//清除缓存
hystrixCacheService.flushCacheByDefaultCacheKey(1L);
//再一次访问并开启缓存
Integer result3 = hystrixCacheService.cacheByDefaultCacheKey(1L);
Integer result4 = hystrixCacheService.cacheByDefaultCacheKey(1L);
logger.info("清除缓存之后再次访问:first request result is:"+ result3+" and secend request result is:"+result4);
return "SUCCESS";
}
测试:访问http://localhost:9000/cacheByDefaultCacheKey显示SUCCESS表示访问成功,我们来看下ribbon-consume的控制台,可以看到结果如下: