Spring Security | Note-4

Spring Security Note-4


使用多线程提高REST服务性能

Q:为什么需要异步处理服务?

A:当HTTP请求服务之后,中间件的主线程调用一个副线程,当副线程处理完成之后,再由主线程返回结果;

在此过程中,主线程是可以空闲出来,去处理其它请求;

服务器的吞吐量可以达到优于同步处理的效果;

1
2
3
4
5
6
7
8
9
10
11
12
13
// 同步处理
@RestController
public class AsyncController {
private Logger logger = LoggerFactory.getLogger(getClass());

@RequestMapping("/order")
public String order() throws InterruptedException {
logger.info("MAIN THREAD START");
Thread.sleep(1000);
logger.info("MAIN THREAD END");
return "success";
}
}
使用Runnable异步处理REST服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
public class AsyncController {
private Logger logger = LoggerFactory.getLogger(getClass());

@RequestMapping("/order")
public Callable<String> order() throws InterruptedException {
logger.info("MAIN THREAD START");
Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception {
logger.info("VICE THREAD START");
Thread.sleep(1000);
logger.info("VICE THREAD END");
return "success";
}
};
logger.info("MAIN THREAD END");
return result;
}
}

控制器返回:

2018-08-31 10:34:47.285 Controller : MAIN THREAD START
2018-08-31 10:34:47.285 Controller : MAIN THREAD END
2018-08-31 10:34:47.292 Controller : VICE THREAD START
2018-08-31 10:34:48.292 Controller : VICE THREAD END

从时间可以看出,主线程和副线程是几乎同时开始,没有停顿,不等待副线程的一秒钟;

副线程处理的一秒钟,主线程可以继续处理其他的HTTP请求;

为什么我通过Runnable已经可以异步处理服务请求,还需要DeferredResult的处理方式?

因为Runnable不能处理所有的情况,它的副线程是必须通过主线程来调用的;

使用DeferredResult异步处理REST服务

在模拟场景下,接受HTTP请求的服务器与处理服务的应用,并不是同一台服务器的情况下;

则需要由

1.HTTP请求,由应用1接收请求;

2.应用1将 请求发送消息到消息队列中;

3.应用2则实时监听消息队列,并且处理发送的消息;

4.在应用2处理完成的请求,发送处理的结果返回到消息队列中;

5.应用1也将实时监听消息队列,监听应用2返回的处理结果;

6.应用1收到处理结果后,再返回HTTP相应;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 模拟应用1接收请求并且监听,返回
@RestController
public class AsyncController {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;

private Logger logger = LoggerFactory.getLogger(getClass());

@RequestMapping("/order")
public DeferredResult<String> order() throws InterruptedException {
logger.info("MAIN THREAD START");
String orderNumber = RandomStringUtils.randomNumeric(8);
mockQueue.setPlaceOrder(orderNumber);

DeferredResult<String> result = new DeferredResult<>();
deferredResultHolder.getMap().put(orderNumber,result);

logger.info("MAIN THREAD END");
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 模拟监听器
@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
private Logger logger = LoggerFactory.getLogger(getClass());

@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
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(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 模拟消息队列处理请求
@Component
public class MockQueue {
// 订单消息
private String placeOrder;
// 完成订单消息
private String completeOrder;
private Logger logger = LoggerFactory.getLogger(getClass());

public void setPlaceOrder(String placeOrder) throws InterruptedException {
new Thread(()->{
logger.info("接到下单请求," + placeOrder);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.completeOrder = placeOrder;
logger.info("下单请求处理完毕," + placeOrder);
}).start();
}
}
1
2
3
4
@Component
public class DeferredResultHolder {
private Map<String,DeferredResult<String>> map = new HashMap<>();
}

控制器返回结果:

2018-08-31 10:58:33.234 INFO 15020 — [nio-8081-exec-1] Controller : MAIN THREAD START
2018-08-31 10:58:33.236 INFO 15020 — [nio-8081-exec-1] Controller : MAIN THREAD END
2018-08-31 10:58:33.236 INFO 15020 — [ Thread-34] MockQueue : 接到下单请求,51722757
2018-08-31 10:58:34.236 INFO 15020 — [ Thread-34] MockQueue : 下单请求处理完毕,51722757
2018-08-31 10:58:34.344 INFO 15020 — [ Thread-23] QueueListener : 返回订单处理结果,51722757

通过控制器返回的结果发现,几乎在主线程完成返回的同时,副线程(Thread-34)同时接收到请求,并且在一秒后完成服务的请求,并且返回处理结果,由模拟应用1的线程2接收返回结果(Thread-23);

异步处理配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
private TimeInterceptor timeInterceptor;

@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 需要通过以下两个注册异步请求的拦截器
configurer.registerCallableInterceptors();
configurer.registerDeferredResultInterceptors();
// 设置异步请求的超时时间
configurer.setDefaultTimeout(1000);
// 设置自定义的线程池
configurer.setTaskExecutor();
}
}

与前端开发并行工作工具

使用Swagger自动生成API文档

首先需要在pom中加入依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>

在主应用中开启Swagger服务

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
@RestController
@EnableSwagger2
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}

@GetMapping("/hello")
public String hello(){
return "Hello Spring Security";
}
}

常用注解

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/user")
public class UserController {
// Swagger注解
@ApiOperation(value = "用户列表查询服务")
public List<User> query(UserQueryCondition condition, @PageableDefault(size = 17, page = 2, sort = "username,asc") Pageable pageable) {
}
...
public User getInfo(@ApiParam(value = "用户ID") @PathVariable(name = "id", required = false) String id) {
}

}

1
2
3
4
5
6
public class UserQueryCondition {
@ApiModelProperty(value = "用户年龄起始值")
private int age;
@ApiModelProperty(value = "用户年龄终止值")
private int ageTo;
}

使用WireMock伪造REST服务

前后端分离的情况下,不止于HTML的请求,还有IOS,ANDROID的请求服务;

自我编写假数据是不现实,并且效率很低;

提供一个统一的伪造REST服务是有必要的;

WireMock是一个独立的服务器,收到怎么样的请求,返回怎么样的相应;

WireMock不需要重启,不需要模拟数据,只需要去连接WireMock服务器即可;

WireMock独立运行

1
java -jar wiremock-standalone-2.18.0.jar --port 8082
1
2
3
4
5
6
7
8
9
// 添加WireMock的依赖
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MockServer {
public static void main(String[] args) throws IOException {
// 指定连接
WireMock.configureFor(8082);
// 清空所有配置
WireMock.removeAllMappings();
// 处理请求
mock("/order/1", "01");
mock("/order/2", "02");
}

private static void mock(String url, String file) throws IOException {
ClassPathResource resource = new ClassPathResource("mock/response/" + file + ".txt");
String content = StringUtils.join(FileUtils.readLines(resource.getFile(), "UTF-8").toArray(), "\n");
WireMock.stubFor(WireMock.get(urlPathEqualTo(url)).willReturn(aResponse().withBody(content).withStatus(200)));
}
}