使用多线程提高REST服务性能
异步处理REST服务,提高服务器吞吐量
使用Runnable异步处理Rest服务
AsyncController.java
@RestController
@GetMapping("/async")
public class AsyncController {
private Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping("/order")
public Callable order() throws Exception {
logger.info("主线程开始");
Callable result = new Callable() {
@Override
public String call() throws Exception {
logger.info("副线程开始");
Thread.sleep(2000); // 模拟处理下单消耗的时间
logger.info("副线程结束");
return "success";
}
};
logger.info("主线程结束");
return result;
}
}
使用DeferredResult异步处理Rest服务
应用1/线程1:接收下单请求,放到消息队列
应用1/线程2:监听器,监听消息队列是否有下单处理结果,返回HTTP响应
应用2:处理下单逻辑
AsyncController.java
@GetMapping("/order2")
public DeferredResult order2() throws Exception {
logger.info("主线程开始");
// 主线程,相当于图中应用1/线程1,接收HTTP请求
// 收到下单请求,生成一个随机订单号,放到消息队列里
String orderNumber = RandomStringUtils.randomNumeric(8);
mockQueue.setPlaceOrder(orderNumber);
// 用于接收处理结果
DeferredResult result = new DeferredResult<>();
deferredResultHolder.getMap().put(orderNumber, result);
logger.info("主线程结束");
return result;
}
MockQueue.java,模拟队列
@Component
public class MockQueue {
private String placeOrder; // 下单消息
private String completeOrder; // 订单完成订单完成
private Logger logger = LoggerFactory.getLogger(getClass());
public String getPlaceOrder() {
return placeOrder;
}
public void setPlaceOrder(String placeOrder) {
// 此线程是模拟应用2,处理下单逻辑
new Thread(() -> {
logger.info("接到下单请求:" + placeOrder);
try {
Thread.sleep(1000); // 模拟处理下单过程
} catch (InterruptedException e) {
e.printStackTrace();
}
this.completeOrder = placeOrder;
logger.info("下单请求处理完毕:" + placeOrder);
}).start();
}
public String getCompleteOrder() {
return completeOrder;
}
public void setCompleteOrder(String completeOrder) {
this.completeOrder = completeOrder;
}
}
DeferredResultHolder.java ,用于在线程1与线程2之间传递传递DeferredResult对象
@Component
public class DeferredResultHolder {
// 订单号,订单处理结果
private Map> map = new HashMap<>();
public Map> getMap() {
return map;
}
public void setMap(Map> map) {
this.map = map;
}
}
QueueListener.java,监听器
@Component
public class QueueListener implements ApplicationListener {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// 相当于图中应用1/线程2,模拟监听器
new Thread(() -> {
while (true) {
if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
String orderNumber = mockQueue.getCompleteOrder();
logger.info("返回订单处理结果:" + orderNumber);
deferredResultHolder.getMap().get(orderNumber)
.setResult("place order success");
mockQueue.setCompleteOrder(null);
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}).start();
}
}
异步处理配置
用拦截器拦截异步处理的请求以有线程池的配置
// 用拦截器拦截异步处理的请求,有如下两个方法注册拦截器,分别对应异步处理的两种方式
// 区别是有超时时间
// configurer.registerCallableInterceptors()
// configurer.registerDeferredResultInterceptors()
// Runnable使用的简单的异步线程池来处理,线程不可重用
使用Swagger自动生成文档
引入Swagger
引入相关依赖,immoc-security-demo/pom.xml
io.springfox
springfox-swagger2
2.7.0
io.springfox
springfox-swagger-ui
2.7.0
加注解,DemoApplication.java
@EnableSwagger2 // 启用Swagger2
详细描述
方法的描述
@ApiOperation(value = "用户查询服务")
参数的描述
// 参数被封装到对象里
@ApiModelProperty("用户名")
// 参数直接写在方法里
@ApiParam("用户ID")
使用WireMock伪造REST服务
与前端开发并行工作,开发阶段,前端包括app和页面开发时都需要测试数据,这时WireMock就派上用场了。这与你再写个web应用提供测试数据有什么不同呢。因为WireMock不用重启,定义url和返回数据都很方便。
下载并启动
指定端口启动:
java -jar wiremock-standalone-2.18.0.jar --port 9999
# --port 9999 指定端口,默认端口8080, --port 0 随机端口
模拟请求和响应
引入依赖
com.github.tomakehurst
wiremock
org.apache.httpcomponents
httpclient
编写代码,MockServer.java
public class MockServer {
public static void main(String[] args) throws IOException {
configureFor("192.168.5.210", 9999);
// configureFor(9999);
removeAllMappings();
mock("/order/1", "01.txt");
mock("/order/2", "02.txt");
}
private static void mock(String url, String fileName) throws IOException {
ClassPathResource resource =
new ClassPathResource("mock/response/" + fileName);
String content =
StringUtils.join(FileUtils.readLines(resource.getFile(), "UTF-8"), "\n");
stubFor(get(urlPathEqualTo(url))
.willReturn(aResponse().withBody(content).withStatus(200)));
}
}