Spring HATEOAS ControllerLinkBuilder方法显着增加响应时间

设置:所以我有一个用java编写的RESTfull API,使用spring-bootspring-hates hates添加资源链接(超媒体驱动的RESTful Web服务)。 我拥有的一切都是标准的,没有进行任何额外的设置或更改

问题

  1. 案例: 没有关于资源的链接 – Chrome TTFB avg。 (10次运行)1000个项目400毫秒
  2. 案例:资源上的1个自我参考链接 – Chrome TTFB avg。 (10次运行)1000个项目1500毫秒

我正在使用这个官方指南

这个问题

为什么只添加1个链接到我的资源,为处理请求增加了1秒。 每个资源需要大约5-7个链接,每个资源都有其他嵌入的链接?

对于9000个项目,每个项目只有1个链接(包括嵌套的项目),我必须等待30秒才能获得响应,并且没有链接~400毫秒。

PS附加代码是无关紧要的,因为我只是添加了教程中的代码,它会显着影响性能。

编辑1

正如我所建议的那样,我将从我的TextItem构造函数中添加示例代码

 add(linkTo(methodOn(TestController.class).getTestItems()).withRel("testLink")); 

编辑2

所以@Mathias Dpunkt提出的以下示例非常完美

 private Method method = ReflectionUtils.findMethod(TestController.class, "getOne", Integer.class); @Override public Resource process(Resource resource) { resource.add(linkTo(method, resource.getContent().getId()).withSelfRel()); return resource; } 

新问题

控制器:

 @RestController @RequestMapping("items") @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class TestController { private final ItemResourceProcessor resourceProcessor; @RequestMapping(method = GET) public ResponseEntity<List<Resource>> getAll() { List<Resource> items = new ArrayList(100); for (int i = 0; i < 100; i++) { items.add(resourceProcessor.process( new Resource(new Item(i, UUID.randomUUID().toString())))); } return ResponseEntity.ok(items); } @RequestMapping(method = GET, path = "/{id}") public ResponseEntity<Resource> getOne(@PathVariable Integer id, @RequestParam boolean test1, @RequestParam boolean test2) { return null; } } 

如果控制器方法采用@RequestParam则发布的解决方案不会将其附加到链接。 我打电话的时候

 private Method method = ReflectionUtils.findMethod(TestController.class, "getOne", Integer.class); @Override public Resource process(Resource resource) { resource.add(linkTo(method, resource.getContent().getId(), true, true).withSelfRel()); return resource; } 

编辑:

更新了post以使用改进版本 – Spring HATEOAS 0.22和Spring Framework 4.3.5 / 5.0 M4 。

回答:

这很有趣。 我看了一下ControllerLinkBuilder方法linkTolinkTo的源代码,并且有一些简单的链接:

  • 为控制器构建一个aop propxy,用于记录交互并获取方法和参数以构建链接
  • 它发现了这种方法构建链接的映射

ControllerLinkBuilder非常方便,因为它避免了重复映射中已包含的逻辑。

我想出了一个简单的示例应用程序和一个非常基本的基准测试来衡量和比较链接构建器的性能

它基于一个简单的控制器 – 它只返回100个简单的对象 – 每个对象都带有一个自我链接。

 @RestController @RequestMapping("items") @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class TestController { private final ItemResourceProcessor resourceProcessor; @RequestMapping(method = GET) public ResponseEntity>> getAll() { List> items = new ArrayList<>(100); for (int i = 0; i < 100; i++) { items.add(resourceProcessor.process( new Resource<>(new Item(i, UUID.randomUUID().toString())))); } return ResponseEntity.ok(items); } @RequestMapping(method = GET, path = "/{id}") public ResponseEntity> getOne(@PathVariable Integer id) { return null; } } 

ItemResourceProcessor添加了一个简单的自我链接,我尝试并测量了三种不同的选择:

1.带linkTo的ControllerLinkBuilder(methodOn)

这里ControllerLinkBuilder用于检查控制器和方法上的映射 – 它需要为生成的每个链接使用aop代理。

 @Component public class ItemResourceProcessor implements ResourceProcessor> { @Override public Resource process(Resource resource) { resource.add(linkTo(methodOn(TestController.class).getOne(resource.getContent().getId())).withSelfRel()); return resource; } } 

这个变体的结果如下:

  wrk -t2 -c5 -d30s http://localhost:8080/items Running 30s test @ http://localhost:8080/items 2 threads and 5 connections Thread Stats Avg Stdev Max +/- Stdev Latency 4.77ms 0.93ms 25.57ms 83.97% Req/Sec 420.87 48.63 500.00 71.33% 25180 requests in 30.06s, 305.70MB read Requests/sec: 837.63 

2.没有methodOn()的ControllerLinkBuilder

这里避免了对methodOn()的调用,并且在创建资源处理器时确定方法引用一次并重新使用以生成链接。 此版本避免了methodOn的开销,但仍然发现了生成链接的方法上的映射。

 @Component public class ItemResourceProcessor implements ResourceProcessor> { private Method method = ReflectionUtils.findMethod(TestController.class, "getOne", Integer.class); @Override public Resource process(Resource resource) { resource.add(linkTo(method, resource.getContent().getId()).withSelfRel()); return resource; } } 

结果略好于第一个版本。 优化给我们带来的好处很小。

 wrk -t2 -c5 -d30s http://localhost:8080/items Running 30s test @ http://localhost:8080/items 2 threads and 5 connections Thread Stats Avg Stdev Max +/- Stdev Latency 4.02ms 477.64us 13.80ms 84.01% Req/Sec 499.42 18.24 540.00 65.50% 29871 requests in 30.05s, 365.50MB read Requests/sec: 994.03 

3.使用BasicLinkBuilder生成链接

在这里,我们离开ControllerLinkBuilder并使用BasicLinkBuilder 。 此实现不执行任何控制器映射的内省,因此是参考基准的良好候选。

 @Component public class ItemResourceProcessor implements ResourceProcessor> { private ControllerLinkBuilder baseLink; @Override public Resource process(Resource resource) { resource.add(BasicLinkBuilder.linkToCurrentMapping() .slash("items") .slash(resource.getContent().getId()).withSelfRel()); return resource; } } 

结果再次好于前一个

 wrk -t2 -c5 -d30s http://localhost:8080/items Running 30s test @ http://localhost:8080/items 2 threads and 5 connections Thread Stats Avg Stdev Max +/- Stdev Latency 3.05ms 683.71us 12.84ms 72.12% Req/Sec 658.31 87.79 828.00 66.67% 39349 requests in 30.03s, 458.91MB read Requests/sec: 1310.14 

概要

当然,methodOn()会产生开销。 测试显示,与BasicLinkBuilder相比,100个链接平均花费不到2毫秒。

因此,当渲染链接的数量不大时, ControllerLinkBuilder的便利性使其成为链接生成的良好选择。

免责声明 :我知道我的wrk测试不是合适的基准 – 但结果可以重复并显示相同的结果比较替代品 – 所以他们至少可以提供性能差异的维度提示)