目标
- 运行examples下面的 http服务
- 学习文档,结合divde插件,发起http请求soul网关,体验http代理
- 记录心得
soul-examples运行
在soul-example下有soul-examples-http项目,这个是基于Spring Boot的Web应用。
我们通过IDEA直接运行SoulTestHttpApplication
类的main方法,直接启动Spring Boot应用。
发起http请求
我们先不通过API网关,请求http://127.0.0.1:8188/order/findById?id=1,如下:
GET http://127.0.0.1:8188/order/findById?id=1
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 40
{
"id": "1",
"name": "hello world findById"
}
Response code: 200 (OK); Time: 175ms; Content length: 40 bytes
接着,我们在通过API网关,请求http://127.0.0.1:9195/http/order/findById?id=1
GET http://127.0.0.1:9195/http/order/findById?id=1
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 40
{
"id": "1",
"name": "hello world findById"
}
Response code: 200 (OK); Time: 401ms; Content length: 40 bytes
对API网关进行压测
首先是不通过AIP网关,直接请求应用服务
$ wrk http://127.0.0.1:8188/order/findById\?id\=1
Running 10s test @ http://127.0.0.1:8188/order/findById?id=1
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.17ms 5.49ms 70.70ms 96.86%
Req/Sec 16.04k 4.22k 20.93k 77.00%
319363 requests in 10.01s, 33.81MB read
Requests/sec: 31919.99
Transfer/sec: 3.38MB
通过API网关请求应用服务
$ wrk http://127.0.0.1:9195/http/order/findById\?id\=1
Running 10s test @ http://127.0.0.1:9195/http/order/findById?id=1
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 9.23ms 34.79ms 363.93ms 97.24%
Req/Sec 1.57k 1.07k 3.44k 65.82%
30740 requests in 10.05s, 3.25MB read
Requests/sec: 3059.90
Transfer/sec: 331.69KB
把日志输出级别调整为ERROR
$ wrk http://127.0.0.1:9195/http/order/findById\?id\=1
Running 10s test @ http://127.0.0.1:9195/http/order/findById?id=1
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.19ms 6.40ms 94.56ms 97.15%
Req/Sec 3.83k 659.86 4.59k 91.50%
76155 requests in 10.01s, 8.06MB read
Requests/sec: 7611.12
Transfer/sec: 825.05KB
性能差距比较大,估计是两个应用都在本机运行,估计有CPU、内存等资源的竞争吧。
divde插件配置LoadBalance
同样运行soul-examples-http的Spring Booot两个应用,端口分别为:8188和8189
server:
port: 8188
soul:
http:
port: 8188
在IDEA下配置如下:
在Soul Admin下查看:
我们可以修改Weight的值,观察API网关日志输出的情况
Weight都是50的情况:
2021-01-15 17:40:40.979 INFO 23062 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:40:40.988 INFO 23062 --- [-work-threads-1] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8188/order/findById?id=1, retryTimes is 0
2021-01-15 17:40:41.101 WARN 23062 --- [-work-threads-1] io.netty.bootstrap.Bootstrap : Unknown channel option 'SO_TIMEOUT' for channel '[id: 0xff1a6c7c]'
2021-01-15 17:40:42.882 INFO 23062 --- [-work-threads-3] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:40:42.882 INFO 23062 --- [-work-threads-3] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8189/order/findById?id=1, retryTimes is 0
2021-01-15 17:40:42.883 WARN 23062 --- [-work-threads-3] io.netty.bootstrap.Bootstrap : Unknown channel option 'SO_TIMEOUT' for channel '[id: 0x4ac43817]'
2021-01-15 17:40:44.746 INFO 23062 --- [-work-threads-5] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:40:44.747 INFO 23062 --- [-work-threads-5] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8189/order/findById?id=1, retryTimes is 0
2021-01-15 17:41:06.089 INFO 23062 --- [-work-threads-7] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:41:06.089 INFO 23062 --- [-work-threads-7] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8188/order/findById?id=1, retryTimes is 0
8188端口的weight的值是99,8189的weight的值是1的情况:
2021-01-15 17:42:46.959 INFO 23062 --- [-work-threads-9] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:42:46.959 INFO 23062 --- [-work-threads-9] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8188/order/findById?id=1, retryTimes is 0
2021-01-15 17:42:49.434 INFO 23062 --- [work-threads-11] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:42:49.435 INFO 23062 --- [work-threads-11] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8188/order/findById?id=1, retryTimes is 0
2021-01-15 17:42:51.096 INFO 23062 --- [work-threads-13] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:42:51.096 INFO 23062 --- [work-threads-13] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8188/order/findById?id=1, retryTimes is 0
2021-01-15 17:42:52.482 INFO 23062 --- [work-threads-15] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:42:52.483 INFO 23062 --- [work-threads-15] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8188/order/findById?id=1, retryTimes is 0
2021-01-15 17:42:54.309 INFO 23062 --- [work-threads-17] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
2021-01-15 17:42:54.309 INFO 23062 --- [work-threads-17] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://127.0.0.1:8188/order/findById?id=1, retryTimes is 0
部分源码解读
Connected to the target VM, address: '127.0.0.1:59655', transport: 'socket'
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.2.RELEASE)
2021-01-15 14:55:39.607 INFO 21683 --- [ main] o.d.s.e.http.SoulTestHttpApplication : Starting SoulTestHttpApplication on BindeMacBook-Pro.local with PID 21683 (/Users/howard/github/howard/soul/soul-examples/soul-examples-http/target/classes started by howard in /Users/howard/github/howard/soul/soul-examples)
2021-01-15 14:55:39.621 INFO 21683 --- [ main] o.d.s.e.http.SoulTestHttpApplication : No active profile set, falling back to default profiles: default
2021-01-15 14:55:40.123 INFO 21683 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.dromara.soul.springboot.starter.client.springmvc.SoulSpringMvcClientConfiguration' of type [org.dromara.soul.springboot.starter.client.springmvc.SoulSpringMvcClientConfiguration$$EnhancerBySpringCGLIB$$f02f8e4b] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-01-15 14:55:40.144 INFO 21683 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'soulHttpConfig' of type [org.dromara.soul.client.springmvc.config.SoulSpringMvcConfig] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-01-15 14:55:40.701 INFO 21683 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils : http client register success: {"appName":"http","context":"/http","path":"/http/test/**","pathDesc":"","rpcType":"http","host":"127.0.0.1","port":8188,"ruleName":"/http/test/**","enabled":true,"registerMetaData":false}
2021-01-15 14:55:40.721 INFO 21683 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8188
2021-01-15 14:55:40.725 INFO 21683 --- [ main] o.d.s.e.http.SoulTestHttpApplication : Started SoulTestHttpApplication in 1.506 seconds (JVM running for 2.109)
2021-01-15 14:55:40.740 INFO 21683 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils : http client register success: {"appName":"http","context":"/http","path":"/http/order/save","pathDesc":"Save order","rpcType":"http","host":"127.0.0.1","port":8188,"ruleName":"/http/order/save","enabled":true,"registerMetaData":false}
2021-01-15 14:55:40.768 INFO 21683 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils : http client register success: {"appName":"http","context":"/http","path":"/http/order/findById","pathDesc":"Find by id","rpcType":"http","host":"127.0.0.1","port":8188,"ruleName":"/http/order/findById","enabled":true,"registerMetaData":false}
2021-01-15 14:55:40.796 INFO 21683 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils : http client register success: {"appName":"http","context":"/http","path":"/http/order/path/**","pathDesc":"","rpcType":"http","host":"127.0.0.1","port":8188,"ruleName":"/http/order/path/**","enabled":true,"registerMetaData":false}
2021-01-15 14:55:40.829 INFO 21683 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils : http client register success: {"appName":"http","context":"/http","path":"/http/order/path/**/name","pathDesc":"","rpcType":"http","host":"127.0.0.1","port":8188,"ruleName":"/http/order/path/**/name","enabled":true,"registerMetaData":false}
在应用启动的时候,我们通过输出的日志信息,看到了该应用提供的HTTP API的元数据信息注册到Soul Admin控制台。元数据的数据格式如下:
{
"appName": "http",
"context": "/http",
"path": "/http/order/save",
"pathDesc": "Save order",
"rpcType": "http",
"host": "127.0.0.1",
"port": 8188,
"ruleName": "/http/order/save",
"enabled": true,
"registerMetaData": false
}
JSON属性说明:
属性 | 描述 |
---|---|
appName | 应用名称 |
context | context |
path | controller request mapping path |
pathDesc | description |
rpcType | RPC类型 |
host | 该应用的IP address |
port | 该应用的端口号 |
ruleName | rule name,存储在rule表 |
enabled | 是否开启,默认开启 |
registerMetaData | 是否有MetaData(通常dubbo rpc会用到) |
我们看一下org.dromara.soul.examples.http.controller.OrderController
这个类的代码,如下:
@PostMapping("/save")
@SoulSpringMvcClient(path = "/save" , desc = "Save order")
public OrderDTO save(@RequestBody final OrderDTO orderDTO) {
orderDTO.setName("hello world save order");
return orderDTO;
}
@SoulSpringMvcClient
这个Annotation是用来配置信元数据信息。通过SpringMvcClientBeanPostProcessor
这个类在Spring Bean容器初始化时,将读取所有包含@Controller
、@RestController
或@RequestMapping
的Spring Bean的元信息,并将这些数据异步的方式注册到Soul Admin。核心代码如下:
public SpringMvcClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
ValidateUtils.validate(soulSpringMvcConfig);
this.soulSpringMvcConfig = soulSpringMvcConfig;
// Soul Admin URL 负责注册元数据信息
url = soulSpringMvcConfig.getAdminUrl() + "/soul-client/springmvc-register";
// 启动只有一个线程的线程池,负责异步将元数据注册到Soul Admin
executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
}
@Override
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
if (soulSpringMvcConfig.isFull()) {
return bean;
}
Controller controller = AnnotationUtils.findAnnotation(bean.getClass(), Controller.class);
RestController restController = AnnotationUtils.findAnnotation(bean.getClass(), RestController.class);
RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
// 判断该bean 是否有Controller、RestController或RequestMapping Annotation.
if (controller != null || restController != null || requestMapping != null) {
// 在Bean Class上是否有@SoulSpringMvcClient Annotation
SoulSpringMvcClient clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringMvcClient.class);
String prePath = "";
if (Objects.nonNull(clazzAnnotation)) {
if (clazzAnnotation.path().indexOf("*") > 1) {
String finalPrePath = prePath;
// 元数据注册
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(clazzAnnotation, finalPrePath), url,
RpcTypeEnum.HTTP));
return bean;
}
prePath = clazzAnnotation.path();
}
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
for (Method method : methods) {
// 在该Bean中的所有Methods上是否有@SoulSpringMvcClient Annotation
SoulSpringMvcClient soulSpringMvcClient = AnnotationUtils.findAnnotation(method, SoulSpringMvcClient.class);
if (Objects.nonNull(soulSpringMvcClient)) {
String finalPrePath = prePath;
// 元数据注册
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(soulSpringMvcClient, finalPrePath), url,
RpcTypeEnum.HTTP));
}
}
}
return bean;
}
总结
- divide插件是进行http正向代理的插件,所有http类型的请求,都是由该插件进行负载均衡的调用
- http请求是如何接入API网关的