Java Spring HATEOAS ControllerLinkBuilder方法显著增加响应时间
设置:因此我有一个用java编写的RESTfull API,使用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
spring boot
和spring Haves
添加到资源的链接(超媒体驱动的RESTful Web服务)。我所有的东西都是标准的,没有额外的设置或更改
问题
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我编辑了postTrylinkTo(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