背景
网关选用的是Spring-boot-gateway,网关配置使用nacos持久化。
Spring的网关源码分析
通过网关的源码分析,StripPrefixGatewayFilterFactory 通过parts值来截断请求的前缀。正常的是1,在执行过程中被刷新成了5。导致截断请求错误。
1 |
|
路由配置刷新机制
Spring gateway 启动后会缓存路由配置,并且,每隔30秒会从缓存刷新配置到具体的路由执行类。
在刷新路由配置的方法里加入了监控日志:
RouteDefinitionRouteLocator.loadGatewayFilters
1 | ConfigurationUtils.bind(configuration, properties, |
properties 是个Map,存放的是待刷新的part值,configuration 是被刷新的配置类,有一个属性 int parts。通过网关通过ConfigurationUtils.bind 来注入 configuration中的parts值。
以上代码修改后的异常日志:
1 | 2022-07-15 13:46:38,233 - parts 异常:5definition:FilterDefinition{name='StripPrefix', args={parts=1}}properties:{parts=1}id:pay-api-web |
可以看到在绑定后的 值是5. 但是properties里的是 {parts=1}。 说明bug发生在
1 | ConfigurationUtils.bind(configuration, properties, |
这一行ConfigurationUtils.bind 调用的是Spring 底层JavaBeanBinder的bind方法
进一步分析JavaBeanBinder.bind
1 | private <T> boolean bind(BeanSupplier<T> beanSupplier, BeanPropertyBinder propertyBinder, BeanProperty property) { |
1 | Object bound = propertyBinder.bindProperty(propertyName, |
JavaBeanBinder 是spring boot底层bean属性处理类。
bind过程分析
propertyBinder.bindProperty的后续调用链路
->Binder.bind
->BindConverter.cover
->TypeConverterSupport.convertIfNecessary
->TypeConverterSupport.doConvertValue
->TypeConverterSupport.doConvertTextValue
doConvertTextValue方法有两行代码
1 | editor.setAsText(newTextValue); |
editor是通过PropertyEditorRegistrySupport.createDefaultEditors()初始化,一个类型一个对象。在执行的时候通过propertyEditorRegistry.getDefaultEditor(requiredType)获取。 如果requiredType相同,获取的就是同一个对象。
所以当有2次调用获取的editor相同,就可能有并发问题。时序如下:
A:editor.setAsText(1)
B:editor.setAsText(5)
A: return editor.getValue();
此时A获取到的就是5。与期望的值不同。造成执行错误。
错误分析总结
- spring cloud gateway和nacos30秒一次心跳,每次心跳会从内存中刷新路由规则到执行对象。
刷新过程调用的是ConfigurationUtils.bind方法,此方法依赖的PropertyEditor。对象对于每个类型是单例的,如果同时有2个相同类型的值进行bind,可能产生并发问题。 - 压测时gateway的负载高。RouteDefinitionRouteLocator.getRoutes()方法并发调用RouteDefinitionRouteLocator.loadGatewayFilters。
- RouteDefinitionRouteLocator.loadGatewayFilters依赖的方法ConfigurationUtils.bind有并发问题 导致路由配置错乱。
附件
重现方法
1 | import com.google.common.collect.Lists; |