文章目录
十三、异步并发进修——请求缓存与请求合并实战
1. 请求缓存
在高并发系统中,针对同一请求的重复调用会造成资源浪费,尤其是在查询操作中。如果对同一商品或数据进行多次查询,我们可以通过请求缓存机制来避免重复调用,从而提升系统的性能。以下是使用 Hystrix 和 CompletableFuture
实现请求缓存的具体方法。
1.1 使用 Hystrix 请求级别缓存
Hystrix 提供了请求级别的缓存功能,这意味着在一个请求的上下文中,可以通过共享缓存来避免重复调用。这种机制在对同一数据进行多次查询时非常有用。
代码实现
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixRequestContext;
public class GetProductServiceCommand extends HystrixCommand<String> {
private ProductService productService;
private Long id;
public GetProductServiceCommand(ProductService productService, Long id) {
super(Setter.withGroupKey(() -> "ProductServiceGroup")
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
.withRequestCacheEnabled(true))); // 启用请求缓存
this.productService = productService;
this.id = id;
}
@Override
protected String run() {
// 调用真实的服务来获取产品信息
return productService.getProduct(id);
}
@Override
protected String getCacheKey() {
// 返回用于缓存的唯一键
return "product-" + id;
}
}
// 使用 Hystrix 请求缓存
public void executeCommands() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
ProductService productService = new ProductService();
GetProductServiceCommand command1 = new GetProductServiceCommand(productService, 1L);
GetProductServiceCommand command2 = new GetProductServiceCommand(productService, 1L);
String result1 = command1.execute();
String result2 = command2.execute();
// 第一个请求不使用缓存,第二个请求命中缓存
assert !command1.isResponseFromCache(); // 第一次请求,不使用缓存
assert command2.isResponseFromCache(); // 第二次请求,使用缓存
} finally {
context.shutdown();
}
}
关键点解析
-
缓存键配置:通过实现
getCacheKey
方法,定义用于缓存的唯一键。在此示例中,"product-" + id
是缓存的键。 -
启用缓存:通过配置
withRequestCacheEnabled(true)
启用请求级别的缓存功能。这样在同一个请求上下文中,多次调用相同的命令会直接从缓存中获取结果。 -
缓存使用验证:通过
isResponseFromCache
方法,可以验证某个请求是否命中了缓存。第一次调用通常不会命中缓存,而后续相同的调用将会从缓存中读取数据。
场景应用
这种请求缓存的机制非常适合在查询类服务中使用,特别是在电商、库存查询等场景中。如果系统需要频繁查询同一商品的库存状态,通过请求级别的缓存可以避免重复调用,从而提升响应效率并减少对后端服务的压力。
1.1.1 Hystrix 请求级别缓存实现
在 Spring 项目中,可以通过整合 Hystrix 来实现请求级别的缓存功能。Hystrix 提供了强大的请求缓存机制,可以在一个请求的上下文中对相同的服务调用进行缓存,从而避免重复调用,提高系统性能。
1. 依赖与配置
在 Spring 项目中使用 Hystrix 请求缓存,需要添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
确保 Spring Boot 项目已经启用了 Hystrix 支持,通常在主类上加上 @EnableCircuitBreaker
或 @EnableHystrix
注解:
@SpringBootApplication
@EnableCircuitBreaker
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. Hystrix 请求缓存实现
通过继承 HystrixCommand
来实现带有请求级别缓存的服务调用。
Step 1: 实现 HystrixCommand 并配置缓存
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandProperties;
public class GetProductCommand extends HystrixCommand<String> {
private final ProductService productService;
private final Long productId;
public GetProductCommand(ProductService productService, Long productId) {
super(Setter.withGroupKey(() -> "ProductServiceGroup")
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withRequestCacheEnabled(true))); // 启用请求级别缓存
this.productService = productService;
this.productId = productId;
}
@Override
protected String run() throws Exception {
return productService.getProductById(productId);
}
@Override
protected String getCacheKey() {
return "product_" + productId;
}
}
Step 2: 在请求中使用 Hystrix 缓存
public class ProductService {
public String getProductById(Long id) {
// 模拟服务调用,可能是一个远程服务或数据库查询
return "Product_" + id;
}
public void executeCommands() {
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
GetProductCommand command1 = new GetProductCommand(this, 1L);
GetProductCommand command2 = new GetProductCommand(this, 1L);
String result1 = command1.execute(); // 第一次请求
String result2 = command2.execute(); // 第二次请求
assert !command1.isResponseFromCache(); // 第一次请求不使用缓存
assert command2.isResponseFromCache(); // 第二次请求命中缓存
} finally {
context.shutdown();
}
}
}
3. 关键点解析
- 缓存键配置:通过重写
getCacheKey
方法为每个请求生成唯一的缓存键,Hystrix 会根据这个键来存储和检索缓存数据。 - 启用缓存:通过
withRequestCacheEnabled(true)
配置启用请求缓存。 - 请求上下文:使用
HystrixRequestContext
来初始化请求上下文,确保同一请求中的缓存数据能够被复用。
4. Hystrix 缓存的工作原理
Hystrix 的请求缓存是通过 HystrixRequestContext
实现的。在同一个请求上下文中,所有使用相同缓存键的请求会共享缓存数据。这种方式适用于在一个请求中需要多次访问同一服务的场景,可以有效减少重复调用,提高系统性能。
通过这种方式,Spring 项目可以实现类似 Hystrix 的请求级别缓存功能,简化了复杂的请求缓存管理逻辑。
1.1.2 Spring 项目中的应用
以下是一个更符合实际开发环境的完整示例,展示如何在 Spring Boot 项目中应用 Hystrix 请求级别缓存,并结合实际的 Spring Bean 管理。
1. 项目结构
项目结构如下:
src
├── main
│ ├── java
│ │ ├── com
│ │ │ └── example
│ │ │ ├── HystrixApplication.java
│ │ │ ├── controller
│ │ │ │ └── ProductController.java
│ │ │ ├── service
│ │ │ │ ├── ProductService.java
│ │ │ │ ├── GetProductServiceCommand.java
│ │ │ │ └── impl
│ │ │ │ ├── ProductServiceImpl.java
│ │ │ │ └── GetProductServiceCommand.java
│ │ └── resources
│ └── application.yml
2. 配置依赖
在 pom.xml
中添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3. 创建主类
在 HystrixApplication
中启用 Hystrix:
package com.yyl.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableHystrix
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
4. 创建 ProductService
接口和实现类
package com.yyl.study.service;
/**
* 产品信息获取接口
*
* @author yangjian
* @date 2024-08-20 20:16:38
*/
public interface ProductService {
String getProductById(Long id);
}
package com.yyl.study.service.impl;
import com.yyl.study.service.ProductService;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
@Override
public String getProductById(Long id) {
// 模拟从数据库或远程服务获取产品信息
return "Product Info for ID: " + id;
}
}
5. 创建 GetProductServiceCommand
类
package com.yyl.study.service.impl;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.yyl.study.service.ProductService;
public class GetProductServiceCommand extends HystrixCommand<String> {
private final ProductService productService;
private final Long productId;
public GetProductServiceCommand(ProductService productService, Long productId) {
super