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

Java Spring HATEOAS ControllerLinkBuilder方法显著增加响应时间,java,spring,spring-hateoas,Java,Spring,Spring Hateoas,设置:因此我有一个用java编写的RESTfull API,使用spring boot和spring Haves添加到资源的链接(超媒体驱动的RESTful Web服务)。我所有的东西都是标准的,没有额外的设置或更改 问题 案例:资源上无链接-Chrome TTFB平均(10次)400毫秒,1000个项目 案例:1资源上的自参考链接-Chrome TTFB平均(10次)1500毫秒,用于1000个项目 我正在使用 问题 为什么只向我的资源添加一个链接会为处理请求增加额外的1秒。我将需要大约5-7

设置:因此我有一个用java编写的RESTfull API,使用
spring boot
spring Haves
添加到资源的链接(超媒体驱动的RESTful Web服务)。我所有的东西都是标准的,没有额外的设置或更改

问题

  • 案例:资源上无链接-Chrome TTFB平均(10次)400毫秒,1000个项目
  • 案例:1资源上的自参考链接-Chrome TTFB平均(10次)1500毫秒,用于1000个项目
  • 我正在使用

    问题

    为什么只向我的资源添加一个链接会为处理请求增加额外的1秒。我将需要大约5-7对每个资源的链接,每个资源都有额外的嵌入式的

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

    另外,附加的代码是不相关的,因为我只是添加了教程中的一个代码,它会显著影响性能

    编辑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<Item> process(Resource<Item> resource) {
      resource.add(linkTo(method, resource.getContent().getId()).withSelfRel());
      return resource;
    }
    
    编辑: 更新帖子以使用改进版本-和

    答复: 这很有趣。我看了
    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;
        }
    }
    
    此变量的结果如下所示:

        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<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;
        }
    }
    
    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;
        }
    }
    
    摘要

    当然,methodOn()有一个开销。测试表明,与
    BasicLinkBuilder
    相比,100个链接的平均成本不到2ms

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


    免责声明:我知道我的wrk测试不是合适的基准测试-但是结果可以重复,并且在比较备选方案时显示相同的结果-因此它们至少可以提供性能差异维度的提示)

    评测你的应用程序。@Kayaman即使我的问题是基于特定的应用程序-这种情况发生在我所拥有的每个spring应用程序上——我刚刚在响应大小爆炸时检测到了它,但这不是问题所在,不是吗……添加链接不会导致问题。减速更可能是由于使用了
    methodOn
    。但只要你相信不需要显示你的代码,我们就帮不了你。@zeroflagL我编辑了postTry
    linkTo(TestController.class).withRel(“testLink”)
    。如有必要,您可以使用
    linkTo(TestController.class).slash(ID)
    添加ID(或另一个路径段)<代码>方法非常昂贵。我会尽快尝试:)谢谢你的建议。很抱歉,我还不能测试它,但你的结果似乎很有希望。我会尽量在周末抽出时间来做这件事,并根据我提出问题时使用的相同设置分享我的结果。再次感谢您的帮助:)我进行了一些测试,结果与您的非常相似,非常令人振奋。但是,如果我的方法中有@PathVariable,它就不会像methodTo()中那样显示为url参数。你知道为什么会这样吗?请确保这些问题不仅被记录为SO答案,而且还引起项目维护人员的注意以进行修复。同时,这似乎是在和中归档和修复的。@OliverGierke感谢您的提示-实际上我没有意识到ControllerLinkBuilder的性能是由于一个bug-我认为这是由于无法避免的代理创建开销。
        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
    
    @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;
        }
    }
    
    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
    
    @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;
        }
    }
    
    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