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

Dan*_*nev 3 java spring spring-hateoas

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

问题

  1. 案例:没有关于资源的链接 - Chrome TTFB avg.(10次运行)400个项目,1000个项目
  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"));
Run Code Online (Sandbox Code Playgroud)

编辑2

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

private Method method = ReflectionUtils.findMethod(TestController.class, "getOne", Integer.class);

@Override
public Resource<Item> process(Resource<Item> resource) {
  resource.add(linkTo(method, resource.getContent().getId()).withSelfRel());
  return resource;
}
Run Code Online (Sandbox Code Playgroud)

新问题

控制器:

@RestController
@RequestMapping("items")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {

    private final ItemResourceProcessor resourceProcessor;

    @RequestMapping(method = GET)
    public ResponseEntity<List<Resource<Item>>> getAll() {
        List<Resource<Item>> 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<Item>> getOne(@PathVariable Integer id, @RequestParam boolean test1, @RequestParam boolean test2) {
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

private Method method = ReflectionUtils.findMethod(TestController.class, "getOne", Integer.class);    

@Override
public Resource<Item> process(Resource<Item> resource) {
     resource.add(linkTo(method, resource.getContent().getId(), true, true).withSelfRel());
     return resource;
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*nkt 11

编辑:

更新了使用改进版本的帖子 - Spring HATEOAS 0.22Spring Framework 4.3.5/5.0 M4.

回答:

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

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

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

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

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

@RestController
@RequestMapping("items")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {

    private final ItemResourceProcessor resourceProcessor;

    @RequestMapping(method = GET)
    public ResponseEntity<List<Resource<Item>>> getAll() {
        List<Resource<Item>> 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<Item>> getOne(@PathVariable Integer id) {
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

ItemResourceProcessor增加了一个简单的自我链接,我试图和测量的三种不同的选择:

1.带linkTo的ControllerLinkBuilder(methodOn)

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

@Component
public class ItemResourceProcessor implements ResourceProcessor<Resource<Item>> {

    @Override
    public Resource<Item> process(Resource<Item> resource) {
        resource.add(linkTo(methodOn(TestController.class).getOne(resource.getContent().getId())).withSelfRel());
        return resource;
    }
}
Run Code Online (Sandbox Code Playgroud)

这个变体的结果如下:

    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
Run Code Online (Sandbox Code Playgroud)

2.没有methodOn()的ControllerLinkBuilder

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

@Component
public class ItemResourceProcessor implements ResourceProcessor<Resource<Item>> {

    private Method method = ReflectionUtils.findMethod(TestController.class, "getOne", Integer.class);

    @Override
    public Resource<Item> process(Resource<Item> resource) {
    resource.add(linkTo(method, resource.getContent().getId()).withSelfRel());
    return resource;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

3.使用BasicLinkBuilder生成链接

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

@Component
public class ItemResourceProcessor implements ResourceProcessor<Resource<Item>> {

    private ControllerLinkBuilder baseLink;

    @Override
    public Resource<Item> process(Resource<Item> resource) {
      resource.add(BasicLinkBuilder.linkToCurrentMapping()
            .slash("items")
            .slash(resource.getContent().getId()).withSelfRel());
      return resource;
    }
}
Run Code Online (Sandbox Code Playgroud)

结果再次好于前一个

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
Run Code Online (Sandbox Code Playgroud)

摘要

当然,methodOn()会产生开销.测试表明,100个链接平均花费不到2毫秒BasicLinkBuilder.

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

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