| <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Apache Dubbo – 提案</title><link>https://dubbo.apache.org/zh-cn/overview/reference/proposals/</link><description>Recent content in 提案 on Apache Dubbo</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><atom:link href="https://dubbo.apache.org/zh-cn/overview/reference/proposals/index.xml" rel="self" type="application/rss+xml"/><item><title>Overview: Rest 协议</title><link>https://dubbo.apache.org/zh-cn/overview/reference/proposals/protocol-http/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/overview/reference/proposals/protocol-http/</guid><description> |
| <p>本文将介绍 Dubbo 的 REST/HTTP 协议设计。</p> |
| <h2 id="restprotocol设计">RestProtocol 设计</h2> |
| <h3 id="原版本dubborest">原版本dubbo rest</h3> |
| <p><strong>consumer</strong></p> |
| <p>restClient支持 依赖resteasy 不支持spring mvc </p> |
| <p><strong>provider(较重)</strong></p> |
| <p>依赖web container (tomcat,jetty,)servlet 模式,jaxrs netty server</p> |
| <h3 id="新版本dubborest">新版本dubbo rest </h3> |
| <p>更加轻量,具有dubbo风格的rest,微服务体系互通(Springcloud Alibaba)</p> |
| <p><strong>1.注解解析</strong></p> |
| <p><strong>2.报文编解码</strong></p> |
| <p><strong>3.restClient</strong></p> |
| <p><strong>4.restServer(netty)</strong></p> |
| <p>支持程度:</p> |
| <p>content-type text json xml form(后续会扩展)</p> |
| <p>注解</p> |
| <p>param,header,body,pathvaribale (spring mvc &amp; resteasy)</p> |
| <h2 id="http协议报文">Http 协议报文</h2> |
| <pre><code>POST /test/path? HTTP/1.1 |
| Host: localhost:8080 |
| Connection: keep-alive |
| Content-type: application/json |
| {&quot;name&quot;:&quot;dubbo&quot;,&quot;age&quot;:10,&quot;address&quot;:&quot;hangzhou&quot;} |
| </code></pre> |
| <h3 id="dubbohttpheader">dubbo http(header)</h3> |
| <pre><code>// service key header |
| path: com.demo.TestInterface |
| group: demo |
| port: 80 |
| version: 1.0.0 |
| // 保证长连接 |
| Keep-Alive,Connection: keep-alive |
| Keep-alive: 60 |
| // RPCContext Attachment |
| userId: 123456 |
| </code></pre> |
| <h2 id="支持粒度">支持粒度</h2> |
| <table> |
| <thead> |
| <tr> |
| <th>数据位置</th> |
| <th>content-type</th> |
| <th>spring注解</th> |
| <th>resteasy注解</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>body</td> |
| <td>无要求</td> |
| <td>ReuqestBody</td> |
| <td> 无注解即为body</td> |
| </tr> |
| <tr> |
| <td>querystring(?test=demo)</td> |
| <td>无要求</td> |
| <td>RequestParam</td> |
| <td>QueryParam</td> |
| </tr> |
| <tr> |
| <td>header</td> |
| <td>无要求</td> |
| <td>RequestHeader</td> |
| <td>PathParam</td> |
| </tr> |
| <tr> |
| <td>form</td> |
| <td>application/x-www-form-urlencoded</td> |
| <td>RequestParam ReuqestBody</td> |
| <td>FormParam</td> |
| </tr> |
| <tr> |
| <td>path</td> |
| <td>无要求</td> |
| <td>PathVariable</td> |
| <td>PathParam</td> |
| </tr> |
| <tr> |
| <td>method</td> |
| <td>无要求</td> |
| <td>PostMapping GetMapping</td> |
| <td>GET POST</td> |
| </tr> |
| <tr> |
| <td>url</td> |
| <td></td> |
| <td>PostMapping GetMapping path属性</td> |
| <td>Path</td> |
| </tr> |
| <tr> |
| <td>content-type</td> |
| <td></td> |
| <td>PostMapping GetMapping consumers属性</td> |
| <td>Consumers</td> |
| </tr> |
| <tr> |
| <td>Accept</td> |
| <td></td> |
| <td>PostMapping GetMapping produces属性</td> |
| <td>Produces</td> |
| </tr> |
| </tbody> |
| </table> |
| <h2 id="rest注解解析">rest注解解析</h2> |
| <p>ServiceRestMetadataResolver</p> |
| <pre><code>JAXRSServiceRestMetadataResolver |
| SpringMvcServiceRestMetadataResolver |
| </code></pre> |
| <p>ServiceRestMetadata</p> |
| <pre><code>public class ServiceRestMetadata implements Serializable { |
| private String serviceInterface; // com.demo.TestInterface |
| private String version;// 1.0.0 |
| private String group;// demo |
| private Set&lt;RestMethodMetadata&gt; meta;// method 元信息 |
| private int port;// 端口 for provider service key |
| private boolean consumer;// consumer 标志 |
| /** |
| * make a distinction between mvc &amp; resteasy |
| */ |
| private Class codeStyle;// |
| /** |
| * for provider |
| */ |
| private Map&lt;PathMatcher, RestMethodMetadata&gt; pathToServiceMap; |
| /** |
| * for consumer |
| */ |
| private Map&lt;String, Map&lt;ParameterTypesComparator, RestMethodMetadata&gt;&gt; methodToServiceMa |
| </code></pre> |
| <p>RestMethodMetadata</p> |
| <pre><code>public class RestMethodMetadata implements Serializable { |
| private MethodDefinition method; // method 定义信息(name ,pramType,returnType) |
| private RequestMetadata request;// 请求元信息 |
| private Integer urlIndex; |
| private Integer bodyIndex; |
| private Integer headerMapIndex; |
| private String bodyType; |
| private Map&lt;Integer, Collection&lt;String&gt;&gt; indexToName; |
| private List&lt;String&gt; formParams; |
| private Map&lt;Integer, Boolean&gt; indexToEncoded; |
| private ServiceRestMetadata serviceRestMetadata; |
| private List&lt;ArgInfo&gt; argInfos; |
| private Method reflectMethod; |
| /** |
| * make a distinction between mvc &amp; resteasy |
| */ |
| private Class codeStyle; |
| </code></pre> |
| <p>ArgInfo</p> |
| <pre><code>public class ArgInfo { |
| /** |
| * method arg index 0,1,2,3 |
| */ |
| private int index; |
| /** |
| * method annotation name or name |
| */ |
| private String annotationNameAttribute; |
| /** |
| * param annotation type |
| */ |
| private Class paramAnnotationType; |
| /** |
| * param Type |
| */ |
| private Class paramType; |
| /** |
| * param name |
| */ |
| private String paramName; |
| /** |
| * url split(&quot;/&quot;) String[n] index |
| */ |
| private int urlSplitIndex; |
| private Object defaultValue; |
| private boolean formContentType; |
| </code></pre> |
| <p>RequestMeatadata</p> |
| <pre><code>public class RequestMetadata implements Serializable { |
| private static final long serialVersionUID = -240099840085329958L; |
| private String method;// 请求method |
| private String path;// 请求url |
| private Map&lt;String, List&lt;String&gt;&gt; params // param参数?拼接 |
| private Map&lt;String, List&lt;String&gt;&gt; headers// header; |
| private Set&lt;String&gt; consumes // content-type; |
| private Set&lt;String&gt; produces // Accept; |
| </code></pre> |
| <h3 id="consumer代码">Consumer 代码</h3> |
| <p>refer</p> |
| <pre><code> @Override |
| protected &lt;T&gt; Invoker&lt;T&gt; protocolBindingRefer(final Class&lt;T&gt; type, final URL url) throws RpcException { |
| // restClient spi创建 |
| ReferenceCountedClient&lt;? extends RestClient&gt; refClient = |
| clients.computeIfAbsent(url.getAddress(), key -&gt; createReferenceCountedClient(url, clients)); |
| refClient.retain(); |
| // resolve metadata |
| Map&lt;String, Map&lt;ParameterTypesComparator, RestMethodMetadata&gt;&gt; metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url); |
| ReferenceCountedClient&lt;? extends RestClient&gt; finalRefClient = refClient; |
| Invoker&lt;T&gt; invoker = new AbstractInvoker&lt;T&gt;(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) { |
| @Override |
| protected Result doInvoke(Invocation invocation) { |
| try { |
| // 获取 method的元信息 |
| RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes())); |
| RequestTemplate requestTemplate = new RequestTemplate(invocation, restMethodMetadata.getRequest().getMethod(), url.getAddress(), getContextPath(url)); |
| HttpConnectionCreateContext httpConnectionCreateContext = new HttpConnectionCreateContext(); |
| // TODO dynamic load config |
| httpConnectionCreateContext.setConnectionConfig(new HttpConnectionConfig()); |
| httpConnectionCreateContext.setRequestTemplate(requestTemplate); |
| httpConnectionCreateContext.setRestMethodMetadata(restMethodMetadata); |
| httpConnectionCreateContext.setInvocation(invocation); |
| httpConnectionCreateContext.setUrl(url); |
| // http 信息构建拦截器 |
| for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) { |
| intercept.intercept(httpConnectionCreateContext); |
| } |
| CompletableFuture&lt;RestResult&gt; future = finalRefClient.getClient().send(requestTemplate); |
| CompletableFuture&lt;AppResponse&gt; responseFuture = new CompletableFuture&lt;&gt;(); |
| AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation); |
| // response 处理 |
| future.whenComplete((r, t) -&gt; { |
| if (t != null) { |
| responseFuture.completeExceptionally(t); |
| } else { |
| AppResponse appResponse = new AppResponse(); |
| try { |
| int responseCode = r.getResponseCode(); |
| MediaType mediaType = MediaType.TEXT_PLAIN; |
| if (400 &lt; responseCode &amp;&amp; responseCode &lt; 500) { |
| throw new HttpClientException(r.getMessage()); |
| } else if (responseCode &gt;= 500) { |
| throw new RemoteServerInternalException(r.getMessage()); |
| } else if (responseCode &lt; 400) { |
| mediaType = MediaTypeUtil.convertMediaType(r.getContentType()); |
| } |
| Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(), |
| restMethodMetadata.getReflectMethod().getReturnType(), mediaType); |
| appResponse.setValue(value); |
| Map&lt;String, String&gt; headers = r.headers() |
| .entrySet() |
| .stream() |
| .collect(Collectors.toMap(Map.Entry::getKey, e -&gt; e.getValue().get(0))); |
| appResponse.setAttachments(headers); |
| responseFuture.complete(appResponse); |
| } catch (Exception e) { |
| responseFuture.completeExceptionally(e); |
| } |
| } |
| }); |
| return asyncRpcResult; |
| } catch (RpcException e) { |
| if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) { |
| e.setCode(getErrorCode(e.getCause())); |
| } |
| throw e; |
| } |
| } |
| @Override |
| public void destroy() { |
| super.destroy(); |
| invokers.remove(this); |
| destroyInternal(url); |
| } |
| }; |
| invokers.add(invoker); |
| return invoker; |
| </code></pre> |
| <h3 id="provider代码">provider 代码</h3> |
| <p>export</p> |
| <pre><code> public &lt;T&gt; Exporter&lt;T&gt; export(final Invoker&lt;T&gt; invoker) throws RpcException { |
| URL url = invoker.getUrl(); |
| final String uri = serviceKey(url); |
| Exporter&lt;T&gt; exporter = (Exporter&lt;T&gt;) exporterMap.get(uri); |
| if (exporter != null) { |
| // When modifying the configuration through override, you need to re-expose the newly modified service. |
| if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) { |
| return exporter; |
| } |
| } |
| // TODO addAll metadataMap to RPCInvocationBuilder metadataMap |
| Map&lt;PathMatcher, RestMethodMetadata&gt; metadataMap = MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(),url); |
| PathAndInvokerMapper.addPathAndInvoker(metadataMap, invoker); |
| final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl()); |
| exporter = new AbstractExporter&lt;T&gt;(invoker) { |
| @Override |
| public void afterUnExport() { |
| exporterMap.remove(uri); |
| if (runnable != null) { |
| try { |
| runnable.run(); |
| } catch (Throwable t) { |
| logger.warn(PROTOCOL_UNSUPPORTED, &quot;&quot;, &quot;&quot;, t.getMessage(), t); |
| } |
| } |
| } |
| }; |
| exporterMap.put(uri, exporter); |
| return exporter; |
| } |
| </code></pre> |
| <p>RestHandler</p> |
| <pre><code> private class RestHandler implements HttpHandler&lt;HttpServletRequest, HttpServletResponse&gt; { |
| @Override |
| public void handle(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException { |
| // 有servlet reuqest 和nettyRequest |
| RequestFacade request = RequestFacadeFactory.createRequestFacade(servletRequest); |
| RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); |
| // dispatcher.service(request, servletResponse); |
| Pair&lt;RpcInvocation, Invoker&gt; build = null; |
| try { |
| // 根据请求信息创建 RPCInvocation |
| build = RPCInvocationBuilder.build(request, servletRequest, servletResponse); |
| } catch (PathNoFoundException e) { |
| servletResponse.setStatus(404); |
| } |
| Invoker invoker = build.getSecond(); |
| Result invoke = invoker.invoke(build.getFirst()); |
| // TODO handling exceptions |
| if (invoke.hasException()) { |
| servletResponse.setStatus(500); |
| } else { |
| try { |
| Object value = invoke.getValue(); |
| String accept = request.getHeader(RestConstant.ACCEPT); |
| MediaType mediaType = MediaTypeUtil.convertMediaType(accept); |
| // TODO write response |
| HttpMessageCodecManager.httpMessageEncode(servletResponse.getOutputStream(), value, invoker.getUrl(), mediaType); |
| servletResponse.setStatus(200); |
| } catch (Exception e) { |
| servletResponse.setStatus(500); |
| } |
| } |
| // TODO add Attachment header |
| } |
| } |
| </code></pre> |
| <p>RPCInvocationBuilder</p> |
| <pre><code>{ |
| private static final ParamParserManager paramParser = new ParamParserManager(); |
| public static Pair&lt;RpcInvocation, Invoker&gt; build(RequestFacade request, Object servletRequest, Object servletResponse) { |
| // 获取invoker |
| Pair&lt;Invoker, RestMethodMetadata&gt; invokerRestMethodMetadataPair = getRestMethodMetadata(request); |
| RpcInvocation rpcInvocation = createBaseRpcInvocation(request, invokerRestMethodMetadataPair.getSecond()); |
| ProviderParseContext parseContext = createParseContext(request, servletRequest, servletResponse, invokerRestMethodMetadataPair.getSecond()); |
| // 参数构建 |
| Object[] args = paramParser.providerParamParse(parseContext); |
| rpcInvocation.setArguments(args); |
| return Pair.make(rpcInvocation, invokerRestMethodMetadataPair.getFirst()); |
| } |
| private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) { |
| ProviderParseContext parseContext = new ProviderParseContext(request); |
| parseContext.setResponse(servletResponse); |
| parseContext.setRequest(servletRequest); |
| Object[] objects = new Object[restMethodMetadata.getArgInfos().size()]; |
| parseContext.setArgs(Arrays.asList(objects)); |
| parseContext.setArgInfos(restMethodMetadata.getArgInfos()); |
| return parseContext; |
| } |
| private static RpcInvocation createBaseRpcInvocation(RequestFacade request, RestMethodMetadata restMethodMetadata) { |
| RpcInvocation rpcInvocation = new RpcInvocation(); |
| int localPort = request.getLocalPort(); |
| String localAddr = request.getLocalAddr(); |
| int remotePort = request.getRemotePort(); |
| String remoteAddr = request.getRemoteAddr(); |
| String HOST = request.getHeader(RestConstant.HOST); |
| String GROUP = request.getHeader(RestConstant.GROUP); |
| String PATH = request.getHeader(RestConstant.PATH); |
| String VERSION = request.getHeader(RestConstant.VERSION); |
| String METHOD = restMethodMetadata.getMethod().getName(); |
| String[] PARAMETER_TYPES_DESC = restMethodMetadata.getMethod().getParameterTypes(); |
| rpcInvocation.setParameterTypes(restMethodMetadata.getReflectMethod().getParameterTypes()); |
| rpcInvocation.setMethodName(METHOD); |
| rpcInvocation.setAttachment(RestConstant.GROUP, GROUP); |
| rpcInvocation.setAttachment(RestConstant.METHOD, METHOD); |
| rpcInvocation.setAttachment(RestConstant.PARAMETER_TYPES_DESC, PARAMETER_TYPES_DESC); |
| rpcInvocation.setAttachment(RestConstant.PATH, PATH); |
| rpcInvocation.setAttachment(RestConstant.VERSION, VERSION); |
| rpcInvocation.setAttachment(RestConstant.HOST, HOST); |
| rpcInvocation.setAttachment(RestConstant.REMOTE_ADDR, remoteAddr); |
| rpcInvocation.setAttachment(RestConstant.LOCAL_ADDR, localAddr); |
| rpcInvocation.setAttachment(RestConstant.REMOTE_PORT, remotePort); |
| rpcInvocation.setAttachment(RestConstant.LOCAL_PORT, localPort); |
| Enumeration&lt;String&gt; attachments = request.getHeaders(RestConstant.DUBBO_ATTACHMENT_HEADER); |
| while (attachments != null &amp;&amp; attachments.hasMoreElements()) { |
| String s = attachments.nextElement(); |
| String[] split = s.split(&quot;=&quot;); |
| rpcInvocation.setAttachment(split[0], split[1]); |
| } |
| // TODO set path,version,group and so on |
| return rpcInvocation; |
| } |
| private static Pair&lt;Invoker, RestMethodMetadata&gt; getRestMethodMetadata(RequestFacade request) { |
| String path = request.getRequestURI(); |
| String version = request.getHeader(RestConstant.VERSION); |
| String group = request.getHeader(RestConstant.GROUP); |
| int port = request.getIntHeader(RestConstant.REST_PORT); |
| return PathAndInvokerMapper.getRestMethodMetadata(path, version, group, port); |
| } |
| } |
| </code></pre> |
| <h2 id="编码示例">编码示例</h2> |
| <p><strong>API</strong></p> |
| <p>mvc</p> |
| <pre><code>@RestController() |
| @RequestMapping(&quot;/demoService&quot;) |
| public interface DemoService { |
| @RequestMapping(value = &quot;/hello&quot;, method = RequestMethod.GET) |
| Integer hello(@RequestParam Integer a, @RequestParam Integer b); |
| @RequestMapping(value = &quot;/error&quot;, method = RequestMethod.GET) |
| String error(); |
| @RequestMapping(value = &quot;/say&quot;, method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE) |
| String sayHello(@RequestBody String name); |
| } |
| </code></pre> |
| <p>resteasy:</p> |
| <pre><code>@Path(&quot;/demoService&quot;) |
| public interface RestDemoService { |
| @GET |
| @Path(&quot;/hello&quot;) |
| Integer hello(@QueryParam(&quot;a&quot;)Integer a,@QueryParam(&quot;b&quot;) Integer b); |
| @GET |
| @Path(&quot;/error&quot;) |
| String error(); |
| @POST |
| @Path(&quot;/say&quot;) |
| @Consumes({MediaType.TEXT_PLAIN}) |
| String sayHello(String name); |
| boolean isCalled(); |
| } |
| </code></pre> |
| <p>impl(service)</p> |
| <pre><code>@DubboService() |
| public class RestDemoServiceImpl implements RestDemoService { |
| private static Map&lt;String, Object&gt; context; |
| private boolean called; |
| @Override |
| public String sayHello(String name) { |
| called = true; |
| return &quot;Hello, &quot; + name; |
| } |
| public boolean isCalled() { |
| return called; |
| } |
| @Override |
| public Integer hello(Integer a, Integer b) { |
| context = RpcContext.getServerAttachment().getObjectAttachments(); |
| return a + b; |
| } |
| @Override |
| public String error() { |
| throw new RuntimeException(); |
| } |
| public static Map&lt;String, Object&gt; getAttachments() { |
| return context; |
| } |
| } |
| </code></pre> |
| <h2 id="流程图">流程图</h2> |
| <p><strong>Consumer</strong> </p> |
| <p><img src="https://static.dingtalk.com/media/lQLPJxLOtqTxs9TNA5rNBQCwci8F2QYiGAYD5sSyd4BVAA_1280_922.png" alt="image"></p> |
| <p><strong>Provider(RestServer)</strong></p> |
| <p><img src="https://static.dingtalk.com/media/lQLPJxZcNUm4M9TNA1_NBMuwZUu6IC3FeYAD5sSydYADAA_1227_863.png" alt="image"></p> |
| <h2 id="场景">场景 </h2> |
| <h3 id="1体系互通">1.体系互通</h3> |
| <p><strong>非dubbo体系互通(Springcloud alibaba 互通)</strong></p> |
| <p>互通条件:</p> |
| <table> |
| <thead> |
| <tr> |
| <th></th> |
| <th>协议</th> |
| <th>Dubbo</th> |
| <th>SpringCloud Alibaba</th> |
| <th>互通</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>通信协议</td> |
| <td>rest</td> |
| <td>spring web/resteasy 编码风格</td> |
| <td>集成feignclient,ribbon (spring web 编码风格)</td> |
| <td>是</td> |
| </tr> |
| <tr> |
| <td></td> |
| <td>triple</td> |
| <td></td> |
| <td></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td></td> |
| <td>dubbo</td> |
| <td></td> |
| <td></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td></td> |
| <td>grpc</td> |
| <td></td> |
| <td></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td></td> |
| <td>hessian</td> |
| <td></td> |
| <td></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>注册中心</td> |
| <td>zookeeper</td> |
| <td></td> |
| <td></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td></td> |
| <td>nacos</td> |
| <td>支持</td> |
| <td>支持</td> |
| <td>应用级别注册</td> |
| </tr> |
| </tbody> |
| </table> |
| <h3 id="2dubbo双注册">2.dubbo 双注册 </h3> |
| <p> 完成应用级别注册,(dubo2-dubbo3 过度),dubbo版本升级</p> |
| <p><img src="https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/LvBPlNAjAmw3OdG8/img/0ceca951-f467-4ab3-9b71-8e7d52e5e7d1.png" alt="image"></p> |
| <p><img src="https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/LvBPlNAjAmw3OdG8/img/6bcc7aed-1d22-470f-b185-efbab32df1e5.png" alt="image"></p> |
| <h3 id="3多协议发布">3.多协议发布</h3> |
| <p>配置:</p> |
| <pre><code>&lt;dubbo:service interface=&quot;org.apache.dubbo.samples.DemoService&quot; protocol=&quot;dubbo, grpc,rest&quot;/&gt; |
| </code></pre> |
| <h3 id="4跨语言">4.跨语言</h3> |
| <p><img src="https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/LvBPlNAjAmw3OdG8/img/1bdf8f91-9666-4c20-9aea-8396c745f554.png" alt="image"></p> |
| <h3 id="5多协议交互">5.多协议交互</h3> |
| <p><img src="https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/LvBPlNAjAmw3OdG8/img/af72e3df-05d5-42a2-a333-618be7ec6cb8.png" alt="image"></p> |
| <h3 id="6协议迁移">6.协议迁移</h3> |
| <p><img src="https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/LvBPlNAjAmw3OdG8/img/36d30183-8d5f-494c-8ebb-b57403c88661.png" alt="image"></p> |
| <p>rest编码风格</p> |
| <p>Http协议更通用跨语言调用</p> |
| <p>dubbo rest 对其他http服务 进行调用</p> |
| <p>其他httpclient 对dubbo rest进行调用</p> |
| <p>dubbo restServer 可以与其他web服务,浏览器等客户端直接进行http交互</p> |
| <h2 id="consumertodolist">consumer TODOLIST</h2> |
| <blockquote> |
| <p>功能已经初步实现,可以调通解析response</p> |
| </blockquote> |
| <p>1. org/apache/dubbo/rpc/protocol/rest/RestProtocol.java:157 dynamic load config</p> |
| <p>2.org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java:50 load config HttpClientConfig</p> |
| <p>3.org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java:52 support Dubbo style service</p> |
| <p>4.org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:120 TODO config</p> |
| <p>5.org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:140 TODO close judge</p> |
| <p>6.org/apache/dubbo/rpc/protocol/rest/message/decode/MultiValueCodec.java:35 TODO java bean get set convert</p> |
| <h2 id="providertodolist">provider TODOLIST</h2> |
| <blockquote> |
| <p>待实现</p> |
| </blockquote> |
| <p>基于netty实现支持http协议的NettyServer</p> |
| <p>无注解协议定义</p> |
| <p>官网场景补充</p> |
| <h2 id="rest使用说明文档及demo">Rest使用说明文档及demo</h2></description></item><item><title>Overview: 注册中心、配置中心和元数据中心</title><link>https://dubbo.apache.org/zh-cn/overview/reference/proposals/registry-config-meta/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/overview/reference/proposals/registry-config-meta/</guid><description> |
| <h2 id="三中心逻辑架构">三中心逻辑架构</h2> |
| <blockquote> |
| <p>本节侧重描述传统模式下的 Dubbo 部署架构,在云原生背景下的部署架构会有些变化,主要体现在基础设施(Kubernetes、Service Mesh等)会承担更多的职责, |
| 中心化组件如注册中心、元数据中心、配置中心等的职责被集成、运维变得更加简单,但通过强调这些中心化的组件能让我们更容易理解 Dubbo 的工作原理。</p> |
| </blockquote> |
| <p>作为一个微服务框架,Dubbo sdk 跟随着微服务组件被部署在分布式集群各个位置,为了在分布式环境下实现各个微服务组件间的协作, |
| Dubbo 定义了一些中心化组件,这包括:</p> |
| <ul> |
| <li>注册中心。协调 Consumer 与 Provider 之间的地址注册与发现</li> |
| <li>配置中心。 |
| <ul> |
| <li>存储 Dubbo 启动阶段的全局配置,保证配置的跨环境共享与全局一致性</li> |
| <li>负责服务治理规则(路由规则、动态配置等)的存储与推送。</li> |
| </ul> |
| </li> |
| <li>元数据中心。 |
| <ul> |
| <li>接收 Provider 上报的服务接口元数据,为 Admin 等控制台提供运维能力(如服务测试、接口文档等)</li> |
| <li>作为服务发现机制的补充,提供额外的接口/方法级别配置信息的同步能力,相当于注册中心的额外扩展</li> |
| </ul> |
| </li> |
| </ul> |
| <p><img src="https://dubbo.apache.org/imgs/v3/concepts/threecenters.png" alt="threecenters"></p> |
| <p>上图完整的描述了 Dubbo 微服务组件与各个中心的交互过程。</p> |
| <p>以上三个中心并不是运行 Dubbo 的必要条件,用户完全可以根据自身业务情况决定只启用其中一个或多个,以达到简化部署的目的。通常情况下,所有用户都会以独立的注册中心 |
| 以开始 Dubbo 服务开发,而配置中心、元数据中心则会在微服务演进的过程中逐步的按需被引入进来。</p> |
| <h3 id="注册中心">注册中心</h3> |
| <p>注册中心扮演着非常重要的角色,它承载着服务注册和服务发现的职责。目前Dubbo支持以下两种粒度的服务发现和服务注册,分别是接口级别和应用级别,注册中心可以按需进行部署:</p> |
| <ul> |
| <li> |
| <p>在传统的Dubbo SDK使用姿势中,如果仅仅提供直连模式的RPC服务,不需要部署注册中心。</p> |
| </li> |
| <li> |
| <p>无论是接口级别还是应用级别,如果需要Dubbo SDK自身来做服务注册和服务发现,则可以选择部署注册中心,在Dubbo中集成对应的注册中心。</p> |
| </li> |
| <li> |
| <p>在Dubbo + Mesh 的场景下,随着 Dubbo 服务注册能力的弱化,Dubbo内的注册中心也不再是必选项,其职责开始被控制面取代,如果采用了Dubbo + Mesh的部署方式,无论是ThinSDK的mesh方式还是Proxyless的mesh方式,都不再需要独立部署注册中心。</p> |
| </li> |
| </ul> |
| <p>而注册中心并不依赖于配置中心和元数据中心,如下图所示:</p> |
| <p><img src="https://dubbo.apache.org/imgs/v3/concepts/centers-registry.png" alt="centers-registry"></p> |
| <p>该图中没有部署配置中心和元数据中心,在Dubbo中会默认将注册中心的实例同时作为配置中心和元数据中心,这是Dubbo的默认行为,如果确实不需要配置中心或者元数据中心的能力,可在配置中关闭,在注册中心的配置中有两个配置分别为use-as-config-center和use-as-metadata-center,将配置置为false即可。</p> |
| <h3 id="元数据中心">元数据中心</h3> |
| <p>元数据中心在2.7.x版本开始支持,随着应用级别的服务注册和服务发现在Dubbo中落地,元数据中心也变的越来越重要。在以下几种情况下会需要部署元数据中心:</p> |
| <ol> |
| <li>对于一个原先采用老版本Dubbo搭建的应用服务,在迁移到Dubbo 3时,Dubbo 3 会需要一个元数据中心来维护RPC服务与应用的映射关系(即接口与应用的映射关系),因为如果采用了应用级别的服务发现和服务注册,在注册中心中将采用“应用 —— 实例列表”结构的数据组织形式,不再是以往的“接口 —— 实例列表”结构的数据组织形式,而以往用接口级别的服务注册和服务发现的应用服务在迁移到应用级别时,得不到接口与应用之间的对应关系,从而无法从注册中心得到实例列表信息,所以Dubbo为了兼容这种场景,在Provider端启动时,会往元数据中心存储接口与应用的映射关系。</li> |
| <li>为了让注册中心更加聚焦于地址的发现和推送能力,减轻注册中心的负担,元数据中心承载了所有的服务元数据、大量接口/方法级别配置信息等,无论是接口粒度还是应用粒度的服务发现和注册,元数据中心都起到了重要的作用。</li> |
| </ol> |
| <p>如果有以上两种需求,都可以选择部署元数据中心,并通过Dubbo的配置来集成该元数据中心。</p> |
| <p>元数据中心并不依赖于注册中心和配置中心,用户可以自由选择是否集成和部署元数据中心,如下图所示:</p> |
| <p><img src="https://dubbo.apache.org/imgs/v3/concepts/centers-metadata.png" alt="centers-metadata"></p> |
| <p>该图中不配备配置中心,意味着可以不需要全局管理配置的能力。该图中不配备注册中心,意味着可能采用了Dubbo mesh的方案,也可能不需要进行服务注册,仅仅接收直连模式的服务调用。</p> |
| <h3 id="配置中心">配置中心</h3> |
| <p>配置中心与其他两大中心不同,它无关于接口级还是应用级,它与接口并没有对应关系,它仅仅与配置数据有关,即使没有部署注册中心和元数据中心,配置中心也能直接被接入到Dubbo应用服务中。在整个部署架构中,整个集群内的实例(无论是Provider还是Consumer)都将会共享该配置中心集群中的配置,如下图所示: |
| <img src="https://dubbo.apache.org/imgs/v3/concepts/centers-config.png" alt="centers-config"></p> |
| <p>该图中不配备注册中心,意味着可能采用了Dubbo mesh的方案,也可能不需要进行服务注册,仅仅接收直连模式的服务调用。</p> |
| <p>该图中不配备元数据中心,意味着Consumer可以从Provider暴露的MetadataService获取服务元数据,从而实现RPC调用</p> |
| <h3 id="保证三大中心高可用的部署架构">保证三大中心高可用的部署架构</h3> |
| <p>虽然三大中心已不再是Dubbo应用服务所必须的,但是在真实的生产环境中,一旦已经集成并且部署了该三大中心,三大中心还是会面临可用性问题,Dubbo需要支持三大中心的高可用方案。在Dubbo中就支持多注册中心、多元数据中心、多配置中心,来满足同城多活、两地三中心、异地多活等部署架构模式的需求。</p> |
| <p>Dubbo SDK对三大中心都支持了Multiple模式。</p> |
| <ul> |
| <li> |
| <p>多注册中心:Dubbo 支持多注册中心,即一个接口或者一个应用可以被注册到多个注册中心中,比如可以注册到ZK集群和Nacos集群中,Consumer也能够从多个注册中心中进行订阅相关服务的地址信息,从而进行服务发现。通过支持多注册中心的方式来保证其中一个注册中心集群出现不可用时能够切换到另一个注册中心集群,保证能够正常提供服务以及发起服务调用。这也能够满足注册中心在部署上适应各类高可用的部署架构模式。</p> |
| </li> |
| <li> |
| <p>多配置中心:Dubbo支持多配置中心,来保证其中一个配置中心集群出现不可用时能够切换到另一个配置中心集群,保证能够正常从配置中心获取全局的配置、路由规则等信息。这也能够满足配置中心在部署上适应各类高可用的部署架构模式。</p> |
| </li> |
| <li> |
| <p>多元数据中心:Dubbo 支持多元数据中心:用于应对容灾等情况导致某个元数据中心集群不可用,此时可以切换到另一个元数据中心集群,保证元数据中心能够正常提供有关服务元数据的管理能力。</p> |
| </li> |
| </ul> |
| <p>拿注册中心举例,下面是一个多活场景的部署架构示意图:</p> |
| <p><img src="https://dubbo.apache.org/imgs/v3/concepts/multiple-registry-deployment-architecture.png" alt="multiple-registry-deployment-architecture"></p> |
| <h2 id="三中心物理部署架构">三中心物理部署架构</h2> |
| <p><img src="#" alt="同一集群,承担三个中心职责"></p> |
| <h2 id="不同场景下的推荐使用方式">不同场景下的推荐使用方式</h2> |
| <ul> |
| <li>只配置 registry,默认作为 metadata、config-center</li> |
| <li>registry、metadata、config-center 使用不同的集群甚至是不同的扩展实现,此时需要独立配置 metadata 或 config-center</li> |
| </ul></description></item><item><title>Overview: Dubbo Admin 控制面总体架构设计</title><link>https://dubbo.apache.org/zh-cn/overview/reference/proposals/admin/</link><pubDate>Tue, 28 Feb 2023 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/overview/reference/proposals/admin/</guid><description> |
| <h2 id="1-dubbo-整体架构">1 Dubbo 整体架构</h2> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/architecture.png" alt="DubboAdmin架构图.png"></p> |
| <p>架构上分为:<strong>服务治理抽象控制面</strong> 和 <strong>Dubbo 数据面</strong> 。</p> |
| <ul> |
| <li><strong>服务治理控制面</strong>。控制面包含注册中心、流量管控策略、Admin 控制台、Istio、OpenSergo 等组件。</li> |
| <li><strong>Dubbo 数据面</strong>。数据面代表集群部署的所有 Dubbo 进程,进程之间通过 RPC 协议实现数据交换,并与控制面进行治理策略交互。</li> |
| </ul> |
| <p>**进一步解释:**<a href="https://cn.dubbo.apache.org/zh-cn/overview/what/overview/">https://cn.dubbo.apache.org/zh-cn/overview/what/overview/</a></p> |
| <h2 id="dubbo-admin-的整体定位与解释">Dubbo Admin 的整体定位与解释</h2> |
| <p><strong>Dubbo Admin 是对微服务治理体系的统一定义与抽象,通过自定义核心组件与一系列配套工具,为不同部署架构和基础设施环境下部署的微服务集群带来统一的开发与运维差异。</strong></p> |
| <h2 id="2-面向用户的开发步骤">2 面向用户的开发步骤</h2> |
| <h3 id="第一步安装-dubbo-stackadmin">第一步:安装 Dubbo Stack/Admin</h3> |
| <blockquote> |
| <p>核心思路是,屏蔽架构差异,通过统一入口将治理组件的安装和配置纳入成为 Dubbo 体系中的前置步骤</p> |
| </blockquote> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>dubboctl install dubbo-stack |
| </span></span></code></pre></div><p>安装请参见: <a href="../../setup/install/">Dubbo Admin 安装指南</a></p> |
| <h3 id="第二步服务框架开发">第二步:服务框架开发</h3> |
| <ul> |
| <li><a href="https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/">Java</a></li> |
| <li><a href="https://cn.dubbo.apache.org/zh-cn/overview/quickstart/go/">Go</a></li> |
| <li><a href="https://github.com/apache/dubbo-js">Node.js</a></li> |
| <li><a href="https://cn.dubbo.apache.org/zh-cn/overview/quickstart/rust/">Rust</a></li> |
| </ul> |
| <h2 id="3-控制面方案">3 控制面方案</h2> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/architecture-draft.png" alt="Dubbo架构草图.jpeg"></p> |
| <h3 id="31-确定-dubbo-微服务治理体系的核心能力">3.1 确定 Dubbo 微服务治理体系的核心能力</h3> |
| <ul> |
| <li>服务发现</li> |
| <li>配置管理</li> |
| <li>流量治理规则</li> |
| <li>安全基础设施</li> |
| <li>可视化控制台</li> |
| </ul> |
| <h3 id="32-统一服务治理层接入方式">3.2 统一服务治理层接入方式</h3> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/address-discovery.png" alt="address-discovery.png"></p> |
| <p>**对于任何微服务部署模式,Dubbo 数据面统一面向 **<code>**dubbo://hopst:ip**</code><strong>抽象服务治理控制面编程。</strong></p> |
| <p>具体工作流程:</p> |
| <ol> |
| <li>数据面通过配置先与 admin 组件进行交互,admin 返回当前部署架构下的实际注册中心、配置中心等组件地址,如图中的 <code>nacos://host:port</code>。</li> |
| <li>数据面组件接收到新的组件地址后,直接与 Nacos 建立通信,此后依赖 Nacos 完成服务发现等功能。</li> |
| </ol> |
| <h3 id="33-在不同场景下如何兑现这些核心能力">3.3 在不同场景下如何兑现这些核心能力?</h3> |
| <h4 id="场景一传统微服务体系-vm--kubernetes">场景一:传统微服务体系 (VM &amp; Kubernetes)</h4> |
| <ul> |
| <li>控制面治理体系一键安装 (Admin &amp; Nacos)</li> |
| <li>传统 Nacos 服务发现与治理模式</li> |
| <li>控制面可按需拉起更多的的组件,如 prometheus 等</li> |
| </ul> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/traditional.png" alt="traditional.png"></p> |
| <h4 id="场景二kubernetes-service">场景二:Kubernetes Service</h4> |
| <ol> |
| <li><strong>Istio 模式</strong></li> |
| </ol> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/kubernetes-service.png" alt="kubernetes-service.png"></p> |
| <ol start="2"> |
| <li><strong>其他对等模式 Nacos/OpenSergo</strong></li> |
| </ol> |
| <h4 id="场景三migration-or-multi-cluster">场景三:Migration or Multi-cluster</h4> |
| <p>集群处于隔离的子网络空间</p> |
| <ul> |
| <li>1</li> |
| <li>2</li> |
| </ul> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/multi-cluster-ingress.png" alt="multi-cluster-ingress.png"></p> |
| <p>集群处于同一网络空间</p> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/multi-cluster.png" alt="multi-cluster.png"></p> |
| <h3 id="34-admin-控制面">3.4 Admin 控制面</h3> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/admin-core-components.png" alt="admin-core-components.png"></p> |
| <h3 id="35-其他配套基础设施与工具">3.5 其他配套基础设施与工具</h3> |
| <h4 id="用户控制台-console">用户控制台 Console</h4> |
| <p>交互地址:<a href="https://qedzyx.axshare.com/#id=2pqh0k&amp;p=admin__&amp;g=1">https://qedzyx.axshare.com/#id=2pqh0k&amp;p=admin__&amp;g=1</a></p> |
| <p><img src="https://dubbo.apache.org/imgs/v3/reference/admin/console-ui.png" alt="console-ui.png"></p> |
| <h4 id="dubboctl--helm">Dubboctl &amp; Helm</h4></description></item><item><title>Overview: 指标埋点</title><link>https://dubbo.apache.org/zh-cn/overview/reference/proposals/metrics/</link><pubDate>Mon, 20 Feb 2023 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/overview/reference/proposals/metrics/</guid><description> |
| <h1 id="概述">概述</h1> |
| <h2 id="1-指标接入说明">1. 指标接入说明</h2> |
| <h2 id="2-指标体系设计">2. 指标体系设计</h2> |
| <p>Dubbo的指标体系,总共涉及三块,指标收集、本地聚合、指标推送</p> |
| <ul> |
| <li>指标收集:将Dubbo内部需要监控的指标推送至统一的Collector中进行存储</li> |
| <li>本地聚合:指标收集获取的均为基础指标,而一些分位数指标则需通过本地聚合计算得出</li> |
| <li>指标推送:收集和聚合后的指标通过一定的方式推送至第三方服务器,目前只涉及Prometheus</li> |
| </ul> |
| <h2 id="3-结构设计">3. 结构设计</h2> |
| <ul> |
| <li>移除原来与 Metrics 相关的类</li> |
| <li>创建新模块 dubbo-metrics/dubbo-metrics-api、dubbo-metrics/dubbo-metrics-prometheus,MetricsConfig 作为该模块的配置类</li> |
| <li>使用micrometer,在Collector中使用基本类型代表指标,如Long、Double等,并在dubbo-metrics-api中引入micrometer,由micrometer对内部指标进行转换</li> |
| </ul> |
| <h2 id="4-数据流转">4. 数据流转</h2> |
| <p><img src="https://dubbo.apache.org/imgs/docs3-v2/java-sdk/observability/dataflow.png" alt="img.png"></p> |
| <h2 id="5-目标">5. 目标</h2> |
| <p>指标接口将提供一个 MetricsService,该 Service 不仅提供柔性服务所的接口级数据,也提供所有指标的查询方式,其中方法级指标的查询的接口可按如下方式声明</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">MetricsService</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * Default {@link MetricsService} extension name. |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> */</span> |
| </span></span><span style="display:flex;"><span> String DEFAULT_EXTENSION_NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;default&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * The contract version of {@link MetricsService}, the future update must make sure compatible. |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> */</span> |
| </span></span><span style="display:flex;"><span> String VERSION <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;1.0.0&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * Get metrics by prefixes |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @param categories categories |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @return metrics - key=MetricCategory value=MetricsEntityList |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> */</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>MetricsCategory, List<span style="color:#719e07">&lt;</span>MetricsEntity<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">getMetricsByCategories</span>(List<span style="color:#719e07">&lt;</span>MetricsCategory<span style="color:#719e07">&gt;</span> categories); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * Get metrics by interface and prefixes |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @param serviceUniqueName serviceUniqueName (eg.group/interfaceName:version) |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @param categories categories |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @return metrics - key=MetricCategory value=MetricsEntityList |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> */</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>MetricsCategory, List<span style="color:#719e07">&lt;</span>MetricsEntity<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">getMetricsByCategories</span>(String serviceUniqueName, List<span style="color:#719e07">&lt;</span>MetricsCategory<span style="color:#719e07">&gt;</span> categories); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * Get metrics by interface、method and prefixes |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @param serviceUniqueName serviceUniqueName (eg.group/interfaceName:version) |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @param methodName methodName |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @param parameterTypes method parameter types |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @param categories categories |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * @return metrics - key=MetricCategory value=MetricsEntityList |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> */</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>MetricsCategory, List<span style="color:#719e07">&lt;</span>MetricsEntity<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">getMetricsByCategories</span>(String serviceUniqueName, String methodName, Class<span style="color:#719e07">&lt;?&gt;[]</span> parameterTypes, List<span style="color:#719e07">&lt;</span>MetricsCategory<span style="color:#719e07">&gt;</span> categories); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>其中 MetricsCategory 设计如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">enum</span> MetricsCategory { |
| </span></span><span style="display:flex;"><span> RT, |
| </span></span><span style="display:flex;"><span> QPS, |
| </span></span><span style="display:flex;"><span> REQUESTS, |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>MetricsEntity 设计如下</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">MetricsEntity</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String name; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> tags; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> MetricsCategory category; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> Object value; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h1 id="指标收集">指标收集</h1> |
| <h2 id="1-嵌入位置">1. 嵌入位置</h2> |
| <p>Dubbo 架构图如下 |
| <img src="https://dubbo.apache.org/imgs/docs3-v2/java-sdk/observability/dubbo.png" alt="img.png"></p> |
| <p>在 provider 中添加一层 MetricsFilter 重写 invoke 方法嵌入调用链路用于收集指标,用 try-catch-finally 处理,核心代码如下</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">@Activate</span>(group <span style="color:#719e07">=</span> PROVIDER, order <span style="color:#719e07">=</span> <span style="color:#719e07">-</span>1) |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">MetricsFilter</span> <span style="color:#268bd2">implements</span> Filter, ScopeModelAware { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">invoke</span>(Invoker<span style="color:#719e07">&lt;?&gt;</span> invoker, Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> collector.increaseTotalRequests(interfaceName, methodName, group, version); |
| </span></span><span style="display:flex;"><span> collector.increaseProcessingRequests(interfaceName, methodName, group, version); |
| </span></span><span style="display:flex;"><span> Long startTime <span style="color:#719e07">=</span> System.currentTimeMillis(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> Result invoke <span style="color:#719e07">=</span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> collector.increaseSucceedRequests(interfaceName, methodName, group, version); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoke; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RpcException e) { |
| </span></span><span style="display:flex;"><span> collector.increaseFailedRequests(interfaceName, methodName, group, version); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> e; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> Long endTime <span style="color:#719e07">=</span> System.currentTimeMillis(); |
| </span></span><span style="display:flex;"><span> Long rt <span style="color:#719e07">=</span> endTime <span style="color:#719e07">-</span> startTime; |
| </span></span><span style="display:flex;"><span> collector.addRT(interfaceName, methodName, group, version, rt); |
| </span></span><span style="display:flex;"><span> collector.decreaseProcessingRequests(interfaceName, methodName, group, version); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h2 id="2-指标标识">2. 指标标识</h2> |
| <p>用以下五个属性作为隔离级别区分标识不同方法,也是各个 ConcurrentHashMap 的 key</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">MethodMetric</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String applicationName; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String interfaceName; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String methodName; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String group; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String version; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h2 id="3-基础指标">3. 基础指标</h2> |
| <p>指标通过 common 模块下的 MetricsCollector 存储所有指标数据</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DefaultMetricsCollector</span> <span style="color:#268bd2">implements</span> MetricsCollector { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> Boolean collectEnabled <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> List<span style="color:#719e07">&lt;</span>MetricsListener<span style="color:#719e07">&gt;</span> listeners <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ApplicationModel applicationModel; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> String applicationName; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> totalRequests <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> succeedRequests <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> failedRequests <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> processingRequests <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> lastRT <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, LongAccumulator<span style="color:#719e07">&gt;</span> minRT <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, LongAccumulator<span style="color:#719e07">&gt;</span> maxRT <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> avgRT <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> totalRT <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, AtomicLong<span style="color:#719e07">&gt;</span> rtCount <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span></code></pre></div><h1 id="本地聚合">本地聚合</h1> |
| <p>本地聚合指将一些简单的指标通过计算获取各分位数指标的过程</p> |
| <h2 id="1-参数设计">1. 参数设计</h2> |
| <p>收集指标时,默认只收集基础指标,而一些单机聚合指标则需要开启服务柔性或者本地聚合后另起线程计算。此处若开启服务柔性,则本地聚合默认开启</p> |
| <h3 id="11-本地聚合开启方式">1.1 本地聚合开启方式</h3> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:metrics&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:aggregation</span> enable=<span style="color:#2aa198">&#34;true&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/dubbo:metrics&gt;</span> |
| </span></span></code></pre></div><h3 id="12-指标聚合参数">1.2 指标聚合参数</h3> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:metrics&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:aggregation</span> enable=<span style="color:#2aa198">&#34;true&#34;</span> bucket-num=<span style="color:#2aa198">&#34;5&#34;</span> time-window-seconds=<span style="color:#2aa198">&#34;10&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/dubbo:metrics&gt;</span> |
| </span></span></code></pre></div><h2 id="2-具体指标">2. 具体指标</h2> |
| <p>Dubbo的指标模块帮助用户从外部观察正在运行的系统的内部服务状况 ,Dubbo参考 <a href="https://sre.google/sre-book/monitoring-distributed-systems/">&ldquo;四大黄金信号&rdquo;</a>、<em>RED方法</em>、<em>USE方法</em>等理论并结合实际企业应用场景从不同维度统计了丰富的关键指标,关注这些核心指标对于提供可用性的服务是至关重要的。</p> |
| <p>Dubbo的关键指标包含:<strong>延迟(Latency)</strong>、<strong>流量(Traffic)</strong>、 <strong>错误(Errors)</strong> 和 <strong>饱和度(Saturation)</strong> 等内容 。同时,为了更好的监测服务运行状态,Dubbo 还提供了对核心组件状态的监控,如Dubbo应用信息、线程池信息、三大中心交互的指标数据等。</p> |
| <p>在Dubbo中主要包含如下监控指标:</p> |
| <table> |
| <thead> |
| <tr> |
| <th style="text-align:left"></th> |
| <th style="text-align:left">基础设施</th> |
| <th style="text-align:left">业务监控</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td style="text-align:left">延迟类</td> |
| <td style="text-align:left">IO 等待; 网络延迟;</td> |
| <td style="text-align:left">接口、服务的平均耗时、TP90、TP99、TP999 等</td> |
| </tr> |
| <tr> |
| <td style="text-align:left">流量类</td> |
| <td style="text-align:left">网络和磁盘 IO;</td> |
| <td style="text-align:left">服务层面的 QPS、</td> |
| </tr> |
| <tr> |
| <td style="text-align:left">错误类</td> |
| <td style="text-align:left">宕机; 磁盘(坏盘或文件系统错误); 进程或端口挂掉; 网络丢包;</td> |
| <td style="text-align:left">错误日志;业务状态码、错误码走势;</td> |
| </tr> |
| <tr> |
| <td style="text-align:left">饱和度类</td> |
| <td style="text-align:left">系统资源利用率: CPU、内存、磁盘、网络等; 饱和度:等待线程数,队列积压长度;</td> |
| <td style="text-align:left">这里主要包含JVM、线程池等</td> |
| </tr> |
| </tbody> |
| </table> |
| <ul> |
| <li>qps: 基于滑动窗口获取动态qps</li> |
| <li>rt: 基于滑动窗口获取动态rt</li> |
| <li>失败请求数: 基于滑动窗口获取最近时间内的失败请求数</li> |
| <li>成功请求数: 基于滑动窗口获取最近时间内的成功请求数</li> |
| <li>处理中请求数: 前后增加Filter简单统计</li> |
| <li>具体指标依赖滑动窗口,额外使用 AggregateMetricsCollector 收集</li> |
| </ul> |
| <p>输出到普罗米修斯的相关指标可以参考的内容如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_gc_live_data_size_bytes Size of long-lived heap memory pool after reclamation</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_gc_live_data_size_bytes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_live_data_size_bytes <span style="color:#2aa198">1.6086528E7</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP requests_succeed_aggregate Aggregated Succeed Requests</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE requests_succeed_aggregate gauge</span> |
| </span></span><span style="display:flex;"><span>requests_succeed_aggregate{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">39.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_buffer_memory_used_bytes An estimate of the memory that the Java virtual machine is using for this buffer pool</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_buffer_memory_used_bytes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_buffer_memory_used_bytes{id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;direct&#34;</span>,} <span style="color:#2aa198">1.679975E7</span> |
| </span></span><span style="display:flex;"><span>jvm_buffer_memory_used_bytes{id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;mapped&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_gc_memory_allocated_bytes_total Incremented for an increase in the size of the (young) heap memory pool after one GC to before the next</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_gc_memory_allocated_bytes_total counter</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_memory_allocated_bytes_total <span style="color:#2aa198">2.9884416E9</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP requests_total_aggregate Aggregated Total Requests</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE requests_total_aggregate gauge</span> |
| </span></span><span style="display:flex;"><span>requests_total_aggregate{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">39.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE system_load_average_1m gauge</span> |
| </span></span><span style="display:flex;"><span>system_load_average_1m <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP system_cpu_usage The &#34;recent cpu usage&#34; for the whole system</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE system_cpu_usage gauge</span> |
| </span></span><span style="display:flex;"><span>system_cpu_usage <span style="color:#2aa198">0.015802269043760128</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_threads_peak_threads The peak live thread count since the Java virtual machine started or peak was reset</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_threads_peak_threads gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_peak_threads <span style="color:#2aa198">40.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP requests_processing Processing Requests</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE requests_processing gauge</span> |
| </span></span><span style="display:flex;"><span>requests_processing{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_memory_max_bytes The maximum amount of memory in bytes that can be used for memory management</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_memory_max_bytes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;profiled nmethods&#39;&#34;</span>,} <span style="color:#2aa198">1.22912768E8</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Survivor Space&#34;</span>,} <span style="color:#719e07">-</span><span style="color:#2aa198">1.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Old Gen&#34;</span>,} <span style="color:#2aa198">9.52107008E8</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Metaspace&#34;</span>,} <span style="color:#719e07">-</span><span style="color:#2aa198">1.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Eden Space&#34;</span>,} <span style="color:#719e07">-</span><span style="color:#2aa198">1.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;non-nmethods&#39;&#34;</span>,} <span style="color:#2aa198">5828608.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Compressed Class Space&#34;</span>,} <span style="color:#2aa198">1.073741824E9</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_max_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;non-profiled nmethods&#39;&#34;</span>,} <span style="color:#2aa198">1.22916864E8</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_threads_states_threads The current number of threads having BLOCKED state</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_threads_states_threads gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_states_threads{state<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;blocked&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_states_threads{state<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;runnable&#34;</span>,} <span style="color:#2aa198">10.0</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_states_threads{state<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;waiting&#34;</span>,} <span style="color:#2aa198">16.0</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_states_threads{state<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;timed-waiting&#34;</span>,} <span style="color:#2aa198">13.0</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_states_threads{state<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;new&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_states_threads{state<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;terminated&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_buffer_total_capacity_bytes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_buffer_total_capacity_bytes{id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;direct&#34;</span>,} <span style="color:#2aa198">1.6799749E7</span> |
| </span></span><span style="display:flex;"><span>jvm_buffer_total_capacity_bytes{id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;mapped&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP rt_p99 Response Time P99</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE rt_p99 gauge</span> |
| </span></span><span style="display:flex;"><span>rt_p99{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">1.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_memory_used_bytes The amount of used memory</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_memory_used_bytes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Survivor Space&#34;</span>,} <span style="color:#2aa198">1048576.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;profiled nmethods&#39;&#34;</span>,} <span style="color:#2aa198">1.462464E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Old Gen&#34;</span>,} <span style="color:#2aa198">1.6098728E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Metaspace&#34;</span>,} <span style="color:#2aa198">4.0126952E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Eden Space&#34;</span>,} <span style="color:#2aa198">8.2837504E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;non-nmethods&#39;&#34;</span>,} <span style="color:#2aa198">1372032.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Compressed Class Space&#34;</span>,} <span style="color:#2aa198">4519248.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_used_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;non-profiled nmethods&#39;&#34;</span>,} <span style="color:#2aa198">5697408.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP qps Query Per Seconds</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE qps gauge</span> |
| </span></span><span style="display:flex;"><span>qps{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">0.3333333333333333</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP rt_min Min Response Time</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE rt_min gauge</span> |
| </span></span><span style="display:flex;"><span>rt_min{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_buffer_count_buffers gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_buffer_count_buffers{id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;mapped&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span>jvm_buffer_count_buffers{id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;direct&#34;</span>,} <span style="color:#2aa198">10.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP system_cpu_count The number of processors available to the Java virtual machine</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE system_cpu_count gauge</span> |
| </span></span><span style="display:flex;"><span>system_cpu_count <span style="color:#2aa198">2.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_classes_loaded_classes The number of classes that are currently loaded in the Java virtual machine</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_classes_loaded_classes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_classes_loaded_classes <span style="color:#2aa198">7325.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP rt_total Total Response Time</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE rt_total gauge</span> |
| </span></span><span style="display:flex;"><span>rt_total{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">2783.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP rt_last Last Response Time</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE rt_last gauge</span> |
| </span></span><span style="display:flex;"><span>rt_last{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_gc_memory_promoted_bytes_total Count of positive increases in the size of the old generation memory pool before GC to after GC</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_gc_memory_promoted_bytes_total counter</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_memory_promoted_bytes_total <span style="color:#2aa198">1.4450952E7</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_gc_pause_seconds Time spent in GC pause</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_gc_pause_seconds summary</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_pause_seconds_count{action<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;end of minor GC&#34;</span>,cause<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Metadata GC Threshold&#34;</span>,} <span style="color:#2aa198">2.0</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_pause_seconds_sum{action<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;end of minor GC&#34;</span>,cause<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Metadata GC Threshold&#34;</span>,} <span style="color:#2aa198">0.026</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_pause_seconds_count{action<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;end of minor GC&#34;</span>,cause<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Evacuation Pause&#34;</span>,} <span style="color:#2aa198">37.0</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_pause_seconds_sum{action<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;end of minor GC&#34;</span>,cause<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Evacuation Pause&#34;</span>,} <span style="color:#2aa198">0.156</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_gc_pause_seconds_max Time spent in GC pause</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_gc_pause_seconds_max gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_pause_seconds_max{action<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;end of minor GC&#34;</span>,cause<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Metadata GC Threshold&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_pause_seconds_max{action<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;end of minor GC&#34;</span>,cause<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Evacuation Pause&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP rt_p95 Response Time P95</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE rt_p95 gauge</span> |
| </span></span><span style="display:flex;"><span>rt_p95{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP requests_total Total Requests</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE requests_total gauge</span> |
| </span></span><span style="display:flex;"><span>requests_total{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">27738.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP process_cpu_usage The &#34;recent cpu usage&#34; for the Java Virtual Machine process</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE process_cpu_usage gauge</span> |
| </span></span><span style="display:flex;"><span>process_cpu_usage <span style="color:#2aa198">8.103727714748784E-4</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP rt_max Max Response Time</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE rt_max gauge</span> |
| </span></span><span style="display:flex;"><span>rt_max{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">4.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_gc_max_data_size_bytes Max size of long-lived heap memory pool</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_gc_max_data_size_bytes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_gc_max_data_size_bytes <span style="color:#2aa198">9.52107008E8</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_threads_live_threads The current number of live threads including both daemon and non-daemon threads</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_threads_live_threads gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_live_threads <span style="color:#2aa198">39.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_threads_daemon_threads The current number of live daemon threads</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_threads_daemon_threads gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_threads_daemon_threads <span style="color:#2aa198">36.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_classes_unloaded_classes_total counter</span> |
| </span></span><span style="display:flex;"><span>jvm_classes_unloaded_classes_total <span style="color:#2aa198">0.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE jvm_memory_committed_bytes gauge</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;profiled nmethods&#39;&#34;</span>,} <span style="color:#2aa198">1.4680064E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Survivor Space&#34;</span>,} <span style="color:#2aa198">1048576.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Old Gen&#34;</span>,} <span style="color:#2aa198">5.24288E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Metaspace&#34;</span>,} <span style="color:#2aa198">4.1623552E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;heap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;G1 Eden Space&#34;</span>,} <span style="color:#2aa198">9.0177536E7</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;non-nmethods&#39;&#34;</span>,} <span style="color:#2aa198">2555904.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;Compressed Class Space&#34;</span>,} <span style="color:#2aa198">5111808.0</span> |
| </span></span><span style="display:flex;"><span>jvm_memory_committed_bytes{area<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;nonheap&#34;</span>,id<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;CodeHeap &#39;non-profiled nmethods&#39;&#34;</span>,} <span style="color:#2aa198">5701632.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP requests_succeed Succeed Requests</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE requests_succeed gauge</span> |
| </span></span><span style="display:flex;"><span>requests_succeed{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">27738.0</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># HELP rt_avg Average Response Time</span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75"># TYPE rt_avg gauge</span> |
| </span></span><span style="display:flex;"><span>rt_avg{application_name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;metrics-provider&#34;</span>,group<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,hostname<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;iZ8lgm9icspkthZ&#34;</span>,interface<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;org.apache.dubbo.samples.metrics.prometheus.api.DemoService&#34;</span>,ip<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;172.28.236.104&#34;</span>,method<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;sayHello&#34;</span>,version<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;&#34;</span>,} <span style="color:#2aa198">0.0</span> |
| </span></span></code></pre></div><h2 id="聚合收集器">聚合收集器</h2> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AggregateMetricsCollector</span> <span style="color:#268bd2">implements</span> MetricsCollector, MetricsListener { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">int</span> bucketNum; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">int</span> timeWindowSeconds; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, TimeWindowCounter<span style="color:#719e07">&gt;</span> totalRequests <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, TimeWindowCounter<span style="color:#719e07">&gt;</span> succeedRequests <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, TimeWindowCounter<span style="color:#719e07">&gt;</span> failedRequests <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, TimeWindowCounter<span style="color:#719e07">&gt;</span> qps <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>MethodMetric, TimeWindowQuantile<span style="color:#719e07">&gt;</span> rt <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ApplicationModel applicationModel; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> Integer DEFAULT_COMPRESSION <span style="color:#719e07">=</span> 100; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> Integer DEFAULT_BUCKET_NUM <span style="color:#719e07">=</span> 10; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> Integer DEFAULT_TIME_WINDOW_SECONDS <span style="color:#719e07">=</span> 120; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">//在构造函数中解析配置信息</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">AggregateMetricsCollector</span>(ApplicationModel applicationModel) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.applicationModel <span style="color:#719e07">=</span> applicationModel; |
| </span></span><span style="display:flex;"><span> ConfigManager configManager <span style="color:#719e07">=</span> applicationModel.getApplicationConfigManager(); |
| </span></span><span style="display:flex;"><span> MetricsConfig config <span style="color:#719e07">=</span> configManager.getMetrics().orElse(<span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (config <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> config.getAggregation() <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> Boolean.TRUE.equals(config.getAggregation().getEnabled())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// only registered when aggregation is enabled.</span> |
| </span></span><span style="display:flex;"><span> registerListener(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> AggregationConfig aggregation <span style="color:#719e07">=</span> config.getAggregation(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.bucketNum <span style="color:#719e07">=</span> aggregation.getBucketNum() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">?</span> DEFAULT_BUCKET_NUM : aggregation.getBucketNum(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.timeWindowSeconds <span style="color:#719e07">=</span> aggregation.getTimeWindowSeconds() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">?</span> DEFAULT_TIME_WINDOW_SECONDS : aggregation.getTimeWindowSeconds(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如果开启了本地聚合,则通过 spring 的 BeanFactory 添加监听,将 AggregateMetricsCollector 与 DefaultMetricsCollector 绑定,实现一种生存者消费者的模式,DefaultMetricsCollector 中使用监听器列表,方便扩展</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">registerListener</span>() { |
| </span></span><span style="display:flex;"><span> applicationModel.getBeanFactory().getBean(DefaultMetricsCollector.class).addListener(<span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h2 id="3-指标聚合">3. 指标聚合</h2> |
| <p>滑动窗口 |
| 假设我们初始有6个bucket,每个窗口时间设置为2分钟 |
| 每次写入指标数据时,会将数据分别写入6个bucket内,每隔两分钟移动一个bucket并且清除原来bucket内的数据 |
| 读取指标时,读取当前current指向的bucket,以达到滑动窗口的效果 |
| 具体如下图所示,实现了当前 bucket 内存储了配置中设置的 bucket 生命周期内的数据,即近期数据 |
| <img src="https://dubbo.apache.org/imgs/docs3-v2/java-sdk/observability/aggre.png" alt="img_1.png"></p> |
| <p>在每个bucket内,使用<strong>TDigest 算法</strong>计算分位数指标</p> |
| <blockquote> |
| <p><strong>TDigest 算法</strong>(极端分位精确度高,如p1 p99,中间分位精确度低,如p50),相关资料如下</p> |
| <ul> |
| <li><a href="https://op8867555.github.io/posts/2018-04-09-tdigest.html">https://op8867555.github.io/posts/2018-04-09-tdigest.html</a></li> |
| <li><a href="https://blog.csdn.net/csdnnews/article/details/116246540">https://blog.csdn.net/csdnnews/article/details/116246540</a></li> |
| <li>开源实现:https://github.com/tdunning/t-digest</li> |
| </ul> |
| </blockquote> |
| <p>代码实现如下,除了 TimeWindowQuantile 用来计算分位数指标外,另外提供了 TimeWindowCounter 来收集时间区间内的指标数量</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">TimeWindowQuantile</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">double</span> compression; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> TDigest<span style="color:#719e07">[]</span> ringBuffer; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">int</span> currentBucket; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">long</span> lastRotateTimestampMillis; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">long</span> durationBetweenRotatesMillis; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">TimeWindowQuantile</span>(<span style="color:#dc322f">double</span> compression, <span style="color:#dc322f">int</span> bucketNum, <span style="color:#dc322f">int</span> timeWindowSeconds) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.compression <span style="color:#719e07">=</span> compression; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.ringBuffer <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> TDigest<span style="color:#719e07">[</span>bucketNum<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> bucketNum; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.ringBuffer<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> TDigest.createDigest(compression); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.currentBucket <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.lastRotateTimestampMillis <span style="color:#719e07">=</span> System.currentTimeMillis(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.durationBetweenRotatesMillis <span style="color:#719e07">=</span> TimeUnit.SECONDS.toMillis(timeWindowSeconds) <span style="color:#719e07">/</span> bucketNum; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">synchronized</span> <span style="color:#dc322f">double</span> <span style="color:#268bd2">quantile</span>(<span style="color:#dc322f">double</span> q) { |
| </span></span><span style="display:flex;"><span> TDigest currentBucket <span style="color:#719e07">=</span> rotate(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> currentBucket.quantile(q); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">synchronized</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">add</span>(<span style="color:#dc322f">double</span> value) { |
| </span></span><span style="display:flex;"><span> rotate(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (TDigest bucket : ringBuffer) { |
| </span></span><span style="display:flex;"><span> bucket.add(value); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> TDigest <span style="color:#268bd2">rotate</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> timeSinceLastRotateMillis <span style="color:#719e07">=</span> System.currentTimeMillis() <span style="color:#719e07">-</span> lastRotateTimestampMillis; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (timeSinceLastRotateMillis <span style="color:#719e07">&gt;</span> durationBetweenRotatesMillis) { |
| </span></span><span style="display:flex;"><span> ringBuffer<span style="color:#719e07">[</span>currentBucket<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> TDigest.createDigest(compression); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">++</span>currentBucket <span style="color:#719e07">&gt;=</span> ringBuffer.length) { |
| </span></span><span style="display:flex;"><span> currentBucket <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> timeSinceLastRotateMillis <span style="color:#719e07">-=</span> durationBetweenRotatesMillis; |
| </span></span><span style="display:flex;"><span> lastRotateTimestampMillis <span style="color:#719e07">+=</span> durationBetweenRotatesMillis; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> ringBuffer<span style="color:#719e07">[</span>currentBucket<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h1 id="指标推送">指标推送</h1> |
| <p>指标推送只有用户在设置了&lt;dubbo:metrics /&gt;配置且配置protocol参数后才开启,若只开启指标聚合,则默认不推送指标。</p> |
| <h2 id="1-promehteus-pull-servicediscovery">1. Promehteus Pull ServiceDiscovery</h2> |
| <p>使用dubbo-admin等类似的中间层,启动时根据配置将本机 IP、Port、MetricsURL 推送地址信息至dubbo-admin(或任意中间层)的方式,暴露HTTP ServiceDiscovery供prometheus读取,配置方式如&lt;dubbo:metrics protocol=&ldquo;prometheus&rdquo; mode=&ldquo;pull&rdquo; address=&quot;${dubbo-admin.address}&quot; port=&ldquo;20888&rdquo; url=&quot;/metrics&quot;/&gt;,其中在pull模式下address为可选参数,若不填则需用户手动在Prometheus配置文件中配置地址</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">exportHttpServer</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> exporterEnabled <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_EXPORTER_ENABLED_KEY, <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (exporterEnabled) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> port <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_EXPORTER_METRICS_PORT_KEY, PROMETHEUS_DEFAULT_METRICS_PORT); |
| </span></span><span style="display:flex;"><span> String path <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_EXPORTER_METRICS_PATH_KEY, PROMETHEUS_DEFAULT_METRICS_PATH); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>path.startsWith(<span style="color:#2aa198">&#34;/&#34;</span>)) { |
| </span></span><span style="display:flex;"><span> path <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;/&#34;</span> <span style="color:#719e07">+</span> path; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> prometheusExporterHttpServer <span style="color:#719e07">=</span> HttpServer.create(<span style="color:#719e07">new</span> InetSocketAddress(port), 0); |
| </span></span><span style="display:flex;"><span> prometheusExporterHttpServer.createContext(path, httpExchange <span style="color:#719e07">-&gt;</span> { |
| </span></span><span style="display:flex;"><span> String response <span style="color:#719e07">=</span> prometheusRegistry.scrape(); |
| </span></span><span style="display:flex;"><span> httpExchange.sendResponseHeaders(200, response.getBytes().length); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> (OutputStream os <span style="color:#719e07">=</span> httpExchange.getResponseBody()) { |
| </span></span><span style="display:flex;"><span> os.write(response.getBytes()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> httpServerThread <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Thread(prometheusExporterHttpServer::start); |
| </span></span><span style="display:flex;"><span> httpServerThread.start(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (IOException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RuntimeException(e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h2 id="2-prometheus-push-pushgateway">2. Prometheus Push Pushgateway</h2> |
| <p>用户直接在Dubbo配置文件中配置Prometheus Pushgateway的地址即可,如&lt;dubbo:metrics protocol=&ldquo;prometheus&rdquo; mode=&ldquo;push&rdquo; address=&quot;${prometheus.pushgateway-url}&quot; interval=&ldquo;5&rdquo; /&gt;,其中interval代表推送间隔</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">schedulePushJob</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> pushEnabled <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_PUSHGATEWAY_ENABLED_KEY, <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (pushEnabled) { |
| </span></span><span style="display:flex;"><span> String baseUrl <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_PUSHGATEWAY_BASE_URL_KEY); |
| </span></span><span style="display:flex;"><span> String job <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_PUSHGATEWAY_JOB_KEY, PROMETHEUS_DEFAULT_JOB_NAME); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> pushInterval <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_PUSHGATEWAY_PUSH_INTERVAL_KEY, PROMETHEUS_DEFAULT_PUSH_INTERVAL); |
| </span></span><span style="display:flex;"><span> String username <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_PUSHGATEWAY_USERNAME_KEY); |
| </span></span><span style="display:flex;"><span> String password <span style="color:#719e07">=</span> url.getParameter(PROMETHEUS_PUSHGATEWAY_PASSWORD_KEY); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> NamedThreadFactory threadFactory <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> NamedThreadFactory(<span style="color:#2aa198">&#34;prometheus-push-job&#34;</span>, <span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> pushJobExecutor <span style="color:#719e07">=</span> Executors.newScheduledThreadPool(1, threadFactory); |
| </span></span><span style="display:flex;"><span> PushGateway pushGateway <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> PushGateway(baseUrl); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>StringUtils.isBlank(username)) { |
| </span></span><span style="display:flex;"><span> pushGateway.setConnectionFactory(<span style="color:#719e07">new</span> BasicAuthHttpConnectionFactory(username, password)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> pushJobExecutor.scheduleWithFixedDelay(() <span style="color:#719e07">-&gt;</span> push(pushGateway, job), pushInterval, pushInterval, TimeUnit.SECONDS); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">push</span>(PushGateway pushGateway, String job) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> pushGateway.pushAdd(prometheusRegistry.getPrometheusRegistry(), job); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (IOException e) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Error occurred when pushing metrics to prometheus: &#34;</span>, e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h2 id="可视化展示">可视化展示</h2> |
| <p>目前推荐使用 Prometheus 来进行服务监控,Grafana 来展示指标数据。可以通过案例来快速入门 <a href="../../../tasks/observability/grafana/">Dubbo 可视化监控</a>。</p></description></item><item><title>Overview: 自适应负载均衡与限流</title><link>https://dubbo.apache.org/zh-cn/overview/reference/proposals/heuristic-flow-control/</link><pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/overview/reference/proposals/heuristic-flow-control/</guid><description> |
| <h1 id="整体介绍">整体介绍</h1> |
| <p>本文所说的柔性服务主要是指<strong>consumer端的负载均衡</strong>和<strong>provider端的限流</strong>两个功能。在之前的dubbo版本中,</p> |
| <ul> |
| <li>负载均衡部分更多的考虑的是公平性原则,即consumer端尽可能平等的从provider中作出选择,在某些情况下表现并不够理想。</li> |
| <li>限流部分只提供了静态的限流方案,需要用户对provider端设置静态的最大并发值,然而该值的合理选取对用户来讲并不容易。</li> |
| </ul> |
| <p>我们针对这些存在的问题进行了改进。</p> |
| <h2 id="负载均衡">负载均衡</h2> |
| <h3 id="使用介绍">使用介绍</h3> |
| <p>在原本的dubbo版本中,有五种负载均衡的方案供选择,他们分别是 <code>Random</code>、<code>ShortestResponse</code>、<code>RoundRobin</code>、<code>LeastActive</code> 和 <code>ConsistentHash</code>。其中除 <code>ShortestResponse</code> 和 <code>LeastActive</code> 外,其他的几种方案主要是考虑选择时的公平性和稳定性。</p> |
| <p>对于 <code>ShortestResponse</code> 来说,其设计目的是从所有备选的 provider 中选择 response 时间最短的以提高系统整体的吞吐量。然而存在两个问题:</p> |
| <ol> |
| <li>在大多数的场景下,不同provider的response时长没有非常明显的区别,此时该算法会退化为随机选择。</li> |
| <li>response的时间长短有时也并不能代表机器的吞吐能力。对于 <code>LeastActive</code> 来说,其认为应该将流量尽可能分配到当前并发处理任务较少的机器上。但是其同样存在和 <code>ShortestResponse</code> 类似的问题,即这并不能单独代表机器的吞吐能力。</li> |
| </ol> |
| <p>基于以上分析,我们提出了两种新的负载均衡算法。一种是同样基于公平性考虑的单纯 <code>P2C</code> 算法,另一种是基于自适应的方法 <code>adaptive</code>,其试图自适应的衡量 provider 端机器的吞吐能力,然后将流量尽可能分配到吞吐能力高的机器上,以提高系统整体的性能。</p> |
| <h4 id="总体效果">总体效果</h4> |
| <p>对于负载均衡部分的有效性实验在两个不同的情况下进行的,分别是提供端机器配置比较均衡和提供端机器配置差距较大的情况。</p> |
| <p><img src="https://intranetproxy.alipay.com/skylark/lark/0/2023/png/67956773/1675265258687-c3df68a8-80e0-4311-816c-63480494850c.png#clientId=u54f9e7eb-38a1-4&amp;from=paste&amp;height=226&amp;id=ud2d81be9&amp;name=image.png&amp;originHeight=890&amp;originWidth=1798&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=63793&amp;status=done&amp;style=none&amp;taskId=u9adb8df7-315a-4800-ac9f-888ba0d1c11&amp;title=&amp;width=457" alt="image.png"></p> |
| <p><img src="https://intranetproxy.alipay.com/skylark/lark/0/2023/png/67956773/1675265271198-5b045ced-8524-42a2-8b34-d7edbbd1f232.png#clientId=u54f9e7eb-38a1-4&amp;from=paste&amp;height=246&amp;id=u4652443c&amp;name=image.png&amp;originHeight=890&amp;originWidth=1798&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=57908&amp;status=done&amp;style=none&amp;taskId=u624f980f-f1de-43ed-a068-9f1c38ab171&amp;title=&amp;width=497" alt="image.png"></p> |
| <h4 id="使用方法">使用方法</h4> |
| <p><a href="https://dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/performance/loadbalance">Dubbo Java 实现的使用方法</a> 与原本的负载均衡方法相同。只需要在consumer端将&quot;loadbalance&quot;设置为&quot;p2c&quot;或者&quot;adaptive&quot;即可。</p> |
| <h4 id="代码结构">代码结构</h4> |
| <p>负载均衡部分的算法实现只需要在原本负载均衡框架内继承 LoadBalance接口即可。</p> |
| <h3 id="原理介绍">原理介绍</h3> |
| <h4 id="p2c算法">P2C算法</h4> |
| <p>Power of Two Choice 算法简单但是经典,主要思路如下:</p> |
| <ol> |
| <li>对于每次调用,从可用的provider列表中做两次随机选择,选出两个节点providerA和providerB。</li> |
| <li>比较providerA和providerB两个节点,选择其“当前正在处理的连接数”较小的那个节点。</li> |
| </ol> |
| <h4 id="adaptive算法">adaptive算法</h4> |
| <p><a href="https://github.com/apache/dubbo/pull/10745">代码的github地址</a></p> |
| <h5 id="相关指标">相关指标</h5> |
| <ol> |
| <li> |
| <p>cpuLoad |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/26808016bc7f1ee83ab425e308074f17.svg#card=math&amp;code=cpuLoad%20%3D%20cpu%E4%B8%80%E5%88%86%E9%92%9F%E5%B9%B3%E5%9D%87%E8%B4%9F%E8%BD%BD%20%2A%20100%20%2F%20%E5%8F%AF%E7%94%A8cpu%E6%95%B0%E9%87%8F&amp;id=DLuwW" alt="img">。该指标在provider端机器获得,并通过invocation的attachment传递给consumer端。</p> |
| </li> |
| <li> |
| <p>rt |
| rt为一次rpc调用所用的时间,单位为毫秒。</p> |
| </li> |
| <li> |
| <p>timeout |
| timeout为本次rpc调用超时剩余的时间,单位为毫秒。</p> |
| </li> |
| <li> |
| <p>weight |
| weight是设置的服务权重。</p> |
| </li> |
| <li> |
| <p>currentProviderTime |
| provider端在计算cpuLoad时的时间,单位是毫秒</p> |
| </li> |
| <li> |
| <p>currentTime |
| currentTime为最后一次计算load时的时间,初始化为currentProviderTime,单位是毫秒。</p> |
| </li> |
| <li> |
| <p>multiple |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/b60f036bd026b92129df8a6476922cc8.svg#card=math&amp;code=multiple%3D%28%E5%BD%93%E5%89%8D%E6%97%B6%E9%97%B4%20-%20currentTime%29%2Ftimeout%20%2B%201&amp;id=VpE3k" alt="img"></p> |
| </li> |
| <li> |
| <p>lastLatency |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/f2abbc771049cf4f3e492e93a258d699.svg#card=math&amp;code=%5Cbegin%7Balign%2A%7D%0A%5Cend%7Balign%2A%7D%0A%0A&amp;id=ynJBf" alt="img"><img src="https://intranetproxy.alipay.com/skylark/lark/__latex/8fb1af970b995232ebed2764a5706aab.svg#card=math&amp;code=%5Cbegin%7Balign%2A%7D%0A%5Cbegin%7Bsplit%7D%0A%20%0AlastLatency%3D%20%5Cleft%20%5C%7B%0A%20%0A%5Cbegin%7Barray%7D%7Bll%7D%0A%20%0A%20%20%20%202%2Atimeout%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20currentTime%3D%3DcurrentProviderTime%5C%5C%0A%20%0A%20%20%20%20lastLatency%20%3E%3E%20multiple%2C%20%20%20%20%20%26%20otherwise%5C%5C%0A%20%0A%0A%5Cend%7Barray%7D%0A%20%0A%5Cright.%0A%20%0A%5Cend%7Bsplit%7D%0A%20%0A%5Cend%7Balign%2A%7D&amp;id=xAu3F" alt="img"></p> |
| </li> |
| <li> |
| <p>beta |
| 平滑参数,默认为0.5</p> |
| </li> |
| <li> |
| <p>ewma |
| lastLatency的平滑值<img src="https://intranetproxy.alipay.com/skylark/lark/__latex/c26fdbae56f3a06c46434ae91185a3d6.svg#card=math&amp;code=lastLatency%3Dbeta%20%2A%20lastLatency%20%2B%20%281%20-%20beta%29%20%2A%20lastLatency&amp;id=absgN" alt="img"></p> |
| </li> |
| <li> |
| <p>inflight |
| inflight为consumer端还未返回的请求的数量。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/f429c4726dec484e70ee73e6a37c88dd.svg#card=math&amp;code=inflight%3DconsumerReq%20-%20consumerSuccess%20-%20errorReq&amp;id=UZIcf" alt="img"></p> |
| </li> |
| <li> |
| <p>load |
| 对于备选后端机器x来说,若距离上次被调用的时间大于2*timeout,则其load值为0。 |
| 否则,</p> |
| </li> |
| </ol> |
| <p><img src="https://intranetproxy.alipay.com/skylark/lark/__latex/0f56746b3643dc3ed0e019c24ad5f377.svg#card=math&amp;code=load%20%3D%20CpuLoad%20%2A%20%28sqrt%28ewma%29%20%2B%201%29%20%2A%20%28inflight%20%2B%201%29%2F%28%28%28consumerSuccess%20%2F%20%28consumerReq%20%2B1%29%20%29%20%2A%20weight%29%2B1%29&amp;id=TCYWX" alt="img"></p> |
| <h5 id="算法实现">算法实现</h5> |
| <p>依然是基于P2C算法。</p> |
| <ol> |
| <li>从备选列表中做两次随机选择,得到providerA和providerB</li> |
| <li>比较providerA和providerB的load值,选择较小的那个。</li> |
| </ol> |
| <h2 id="自适应限流">自适应限流</h2> |
| <p>与负载均衡运行在consumer端不同的是,限流功能运行在provider端。其作用是限制provider端处理并发任务时的最大数量。从理论上讲,服务端机器的处理能力是存在上限的,对于一台服务端机器,当短时间内出现大量的请求调用时,会导致处理不及时的请求积压,使机器过载。在这种情况下可能导致两个问题:</p> |
| <ol> |
| <li>由于请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务瘫痪。</li> |
| <li>服务端机器长时间的过载可能有宕机的风险。</li> |
| </ol> |
| <p>因此,在可能存在过载风险时,拒绝掉一部分请求反而是更好的选择。在之前的 Dubbo 版本中,限流是通过在 provider 端设置静态的最大并发值实现的。但是在服务数量多,拓扑复杂且处理能力会动态变化的局面下,该值难以通过计算静态设置。</p> |
| <p>基于以上原因,我们需要一种自适应的算法,其可以动态调整服务端机器的最大并发值,使其可以在保证机器不过载的前提下,尽可能多的处理接收到的请求。因此,我们参考相关理论与算法实践基础上,在 Dubbo 框架内实现了两种自适应限流算法,分别是基于启发式平滑的<code>HeuristicSmoothingFlowControl</code> 和基于窗口的 <code>AutoConcurrencyLimier</code>。</p> |
| <p><a href="https://github.com/apache/dubbo/pull/10642">代码的github地址</a></p> |
| <h3 id="使用介绍-1">使用介绍</h3> |
| <h4 id="总体效果-1">总体效果</h4> |
| <p>自适应限流部分的有效性实验我们在提供端机器配置尽可能大的情况下进行,并且为了凸显效果,在实验中我们将单次请求的复杂度提高,将超时时间尽可能设置的大,并且开启消费端的重试功能。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/0/2023/png/67956773/1675267798831-3da99681-577f-4e5a-b122-b87c8aba7299.png#clientId=u54f9e7eb-38a1-4&amp;from=paste&amp;height=239&amp;id=u4de83107&amp;name=image.png&amp;originHeight=800&amp;originWidth=1680&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=53641&amp;status=done&amp;style=none&amp;taskId=u948ff148-1ec8-42ec-9712-0655b1e0336&amp;title=&amp;width=502" alt="image.png"></p> |
| <h4 id="使用方法-1">使用方法</h4> |
| <p>要确保服务端存在多个节点,并且消费端开启重试策略的前提下,限流功能才能更好的发挥作用。</p> |
| <p><a href="https://dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/performance/loadbalance">Dubbo Java 实现的自适应限流开启方法</a> 与静态的最大并发值设置类似,只需在provider端将&quot;flowcontrol&quot;设置为&quot;autoConcurrencyLimier&quot;或者&quot;heuristicSmoothingFlowControl&quot;即可。</p> |
| <h4 id="代码结构-1">代码结构</h4> |
| <ol> |
| <li>FlowControlFilter:在provider端的filter负责根据限流算法的结果来对provider端进行限流功能。</li> |
| <li>FlowControl:根据dubbo的spi实现的限流算法的接口。限流的具体实现算法需要继承自该接口并可以通过dubbo的spi方式使用。</li> |
| <li>CpuUsage:周期性获取cpu的相关指标</li> |
| <li>HardwareMetricsCollector:获取硬件指标的相关方法</li> |
| <li>ServerMetricsCollector:基于滑动窗口的获取限流需要的指标的相关方法。比如qps等。</li> |
| <li>AutoConcurrencyLimier:自适应限流的具体实现算法。</li> |
| <li>HeuristicSmoothingFlowControl:自适应限流的具体实现方法。</li> |
| </ol> |
| <h3 id="原理介绍-1">原理介绍</h3> |
| <h4 id="heuristicsmoothingflowcontrol">HeuristicSmoothingFlowControl</h4> |
| <h5 id="相关指标-1">相关指标</h5> |
| <ol> |
| <li> |
| <p>alpha |
| alpha为可接受的延时的上升幅度,默认为0.3</p> |
| </li> |
| <li> |
| <p>minLatency |
| 在一个时间窗口内的最小的Latency值。</p> |
| </li> |
| <li> |
| <p>noLoadLatency |
| noLoadLatency是单纯处理任务的延时,不包括排队时间。这是服务端机器的固有属性,但是并不是一成不变的。在HeuristicSmoothingFlowControl算法中,我们根据机器CPU的使用率来确定机器当前的noLoadLatency。当机器的CPU使用率较低时,我们认为minLatency便是noLoadLatency。当CPU使用率适中时,我们平滑的用minLatency来更新noLoadLatency的值。当CPU使用率较高时,noLoadLatency的值不再改变。</p> |
| </li> |
| <li> |
| <p>maxQPS |
| 一个时间窗口周期内的QPS的最大值。</p> |
| </li> |
| <li> |
| <p>avgLatency |
| 一个时间窗口周期内的Latency的平均值,单位为毫秒。</p> |
| </li> |
| <li> |
| <p>maxConcurrency |
| 计算得到的当前服务提供端的最大并发值。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/f40e48ebdb49648cf942714609808c52.svg#card=math&amp;code=maxConcurrency%3Dceil%28maxQPS%20%2A%20%28%282%20%2B%20alpha%29%20%2A%20noLoadLatency%20-%20avgLatency%29%29&amp;id=xO1h8" alt="img"></p> |
| </li> |
| </ol> |
| <h5 id="算法实现-1">算法实现</h5> |
| <p>当服务端收到一个请求时,首先判断CPU的使用率是否超过50%。如果没有超过50%,则接受这个请求进行处理。如果超过50%,说明当前的负载较高,便从HeuristicSmoothingFlowControl算法中获得当前的maxConcurrency值。如果当前正在处理的请求数量超过了maxConcurrency,则拒绝该请求。</p> |
| <h4 id="autoconcurrencylimier">AutoConcurrencyLimier</h4> |
| <h5 id="相关指标-2">相关指标</h5> |
| <ol> |
| <li> |
| <p>MaxExploreRatio |
| 默认设置为0.3</p> |
| </li> |
| <li> |
| <p>MinExploreRatio |
| 默认设置为0.06</p> |
| </li> |
| <li> |
| <p>SampleWindowSizeMs |
| 采样窗口的时长。默认为1000毫秒。</p> |
| </li> |
| <li> |
| <p>MinSampleCount |
| 采样窗口的最小请求数量。默认为40。</p> |
| </li> |
| <li> |
| <p>MaxSampleCount |
| 采样窗口的最大请求数量。默认为500。</p> |
| </li> |
| <li> |
| <p>emaFactor |
| 平滑处理参数。默认为0.1。</p> |
| </li> |
| <li> |
| <p>exploreRatio |
| 探索率。初始设置为MaxExploreRatio。 |
| 若avgLatency&lt;=noLoadLatency*(1.0 + MinExploreRatio)或者qps&gt;=maxQPS*(1.0 + MinExploreRatio) |
| 则exploreRatio=min(MaxExploreRatio,exploreRatio+0.02) |
| 否则 |
| exploreRatio=max(MinExploreRatio,exploreRatio-0.02)</p> |
| </li> |
| <li> |
| <p>maxQPS |
| 窗口周期内QPS的最大值。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/d5cf045bc17267befc176f3d76273267.svg#card=math&amp;code=%5Cbegin%7Balign%2A%7D%0A%5Cbegin%7Bsplit%7D%0A%20%0AmaxQPS%3D%20%5Cleft%20%5C%7B%0A%20%0A%5Cbegin%7Barray%7D%7Bll%7D%0A%20%0A%20%20%20%20qps%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20qps%20%3E%20maxQPS%5C%5C%0A%20%0A%20%20%20%20qps%2AemaFactor%20%2B%20maxQPS%2A%281-emaFactor%29%2C%20%20%20%20%20%26%20otherwise%5C%5C%0A%20%0A%0A%5Cend%7Barray%7D%0A%20%0A%5Cright.%0A%20%0A%5Cend%7Bsplit%7D%0A%20%0A%5Cend%7Balign%2A%7D&amp;id=VbdUd" alt="img"></p> |
| </li> |
| <li> |
| <p>noLoadLatency |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/8c700211f5c7a13403e3088df9cd9f43.svg#card=math&amp;code=%5Cbegin%7Balign%2A%7D%0A%5Cbegin%7Bsplit%7D%0A%20%0AnoLoadLatency%3D%20%5Cleft%20%5C%7B%0A%20%0A%5Cbegin%7Barray%7D%7Bll%7D%0A%20%0A%20%20%20%20avgLatency%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20noLoadLatency%20%3C%3D%200%5C%5C%0A%20%0A%20%20%20%20avgLatency%2AemaFactor%20%2B%20noLoadLatency%2A%281%20-%20emaFactor%29%2C%20%20%20%20%20%26%20otherwise%5C%5C%0A%20%0A%0A%5Cend%7Barray%7D%0A%20%0A%5Cright.%0A%20%0A%5Cend%7Bsplit%7D%0A%20%0A%5Cend%7Balign%2A%7D&amp;id=hB7ED" alt="img"></p> |
| </li> |
| <li> |
| <p>halfSampleIntervalMs |
| 半采样区间。默认为25000毫秒。</p> |
| </li> |
| <li> |
| <p>resetLatencyUs |
| 下一次重置所有值的时间戳,这里的重置包括窗口内值和noLoadLatency。单位是微秒。初始为0. |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/1af4a6134ede96985302ee8a27f93df7.svg#card=math&amp;code=resetLatencyUs%3DsamplingTimeUs%2B2%2AavgLatency%2C%0Aif%28remeasureStartUs%3C%3DsamplingTimeUs%29&amp;id=vJHLa" alt="img"></p> |
| </li> |
| <li> |
| <p>remeasureStartUs |
| 下一次重置窗口的开始时间。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/c7da904b9a4c890456499b09d01938d3.svg#card=math&amp;code=remeasureStartUs%3DsamplingTimeUs%2B%28halfSampleIntervalMS%20%2B%20%E9%9A%8F%E6%9C%BA%E5%80%BC%29%2A1000&amp;id=ket08" alt="img"></p> |
| </li> |
| <li> |
| <p>startSampleTimeUs |
| 开始采样的时间。单位为微秒。</p> |
| </li> |
| <li> |
| <p>sampleCount |
| 当前采样窗口内请求的数量。</p> |
| </li> |
| <li> |
| <p>totalSampleUs |
| 采样窗口内所有请求的latency的和。单位为微秒。</p> |
| </li> |
| <li> |
| <p>totalReqCount |
| 采样窗口时间内所有请求的数量和。注意区别sampleCount。</p> |
| </li> |
| <li> |
| <p>samplingTimeUs |
| 采样当前请求的时间戳。单位为微秒。</p> |
| </li> |
| <li> |
| <p>latency |
| 当前请求的latency。</p> |
| </li> |
| <li> |
| <p>qps |
| 在该时间窗口内的qps值。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/c0e8b30fc1ecf9438bc2d574fb3da8b6.svg#card=math&amp;code=qps%3DtotalReqCount%2A1000000%2F%28samplingTimeUs%20-%20startSampleTimeUs%29&amp;id=tzGm6" alt="img"></p> |
| </li> |
| <li> |
| <p>avgLatency |
| 窗口内的平均latency。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/3a3acfdb05be7d3985835d43e492d3b9.svg#card=math&amp;code=avgLatency%3DtotalSampleUs%20%2F%20sampleCount&amp;id=gTnsb" alt="img"></p> |
| </li> |
| <li> |
| <p>maxConcurrency |
| 上一个窗口计算得到当前周期的最大并发值。</p> |
| </li> |
| <li> |
| <p>nextMaxConcurrency |
| 当前窗口计算出的下一个周期的最大并发值。 |
| <img src="https://intranetproxy.alipay.com/skylark/lark/__latex/09852cc0ef125b43a37719796cb8baae.svg#card=math&amp;code=%5Cbegin%7Balign%2A%7D%0A%5Cbegin%7Bsplit%7D%0A%20%0AnextMaxConcurrency%3D%20%5Cleft%20%5C%7B%0A%20%0A%5Cbegin%7Barray%7D%7Bll%7D%0A%20%0A%20%20%20%20ceil%28maxQPS%2AnoLoadLatency%2A0.9%2F1000000%29%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20remeasureStartUs%20%3C%3D%20samplingTimeUs%5C%5C%0A%20%0A%20%20%20%20ceil%28noLoadLatency%2AmaxQPS%2A%281%2BexploreRatio%29%2F1000000%29%2C%20%20%20%20%20%20otherwise%5C%5C%0A%20%0A%0A%5Cend%7Barray%7D%0A%20%0A%5Cright.%0A%20%0A%5Cend%7Bsplit%7D%0A%20%0A%5Cend%7Balign%2A%7D&amp;id=kLKle" alt="img"></p> |
| </li> |
| </ol> |
| <h5 id="littles-law">Little&rsquo;s Law</h5> |
| <ul> |
| <li>当服务处于稳定状态时:concurrency=latency*qps。这是自适应限流理论的基础。</li> |
| <li>当请求没有导致机器超载时,latency基本稳定,qps和concurrency处于线性关系。</li> |
| <li>当短时间内请求数量过多,导致服务超载的时候,concurrency会和latency一起上升,qps则会趋于稳定。</li> |
| </ul> |
| <h5 id="算法实现-2">算法实现</h5> |
| <p>AutoConcurrencyLimier的算法使用过程和HeuristicSmoothingFlowControl类似。与HeuristicSmoothingFlowControl的最大区别是:</p> |
| <p>AutoConcurrencyLimier是基于窗口的。每当窗口内积累了一定量的采样数据时,才利用窗口内的数据来更新得到maxConcurrency。 |
| 其次,利用exploreRatio来对剩余的容量进行探索。</p> |
| <p>另外,每隔一段时间都会自动缩小max_concurrency并持续一段时间,以处理noLoadLatency上涨的情况。因为估计noLoadLatency时必须先让服务处于低负载的状态,因此对maxConcurrency的缩小是难以避免的。</p> |
| <p>由于 max_concurrency &lt; concurrency 时,服务会拒绝掉所有的请求,限流算法将 &ldquo;排空所有的经历过排队的等待请求的时间&rdquo; 设置为 2*latency,以确保 minLatency 的样本绝大部分时没有经过排队等待的。</p></description></item><item><title>Overview: Dubbo3 应用级服务发现设计</title><link>https://dubbo.apache.org/zh-cn/overview/reference/proposals/service-discovery/</link><pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/overview/reference/proposals/service-discovery/</guid><description> |
| <h2 id="objective">Objective</h2> |
| <ul> |
| <li>显著降低服务发现过程的资源消耗,包括提升注册中心容量上限、降低消费端地址解析资源占用等,使得 Dubbo3 框架能够支持更大规模集群的服务治理,实现无限水平扩容。</li> |
| <li>适配底层基础设施服务发现模型,如 Kubernetes、Service Mesh 等。</li> |
| </ul> |
| <h2 id="background">Background</h2> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/arc.png" alt="interface-arc"></p> |
| <p>我们从 Dubbo 最经典的工作原理图说起,Dubbo 从设计之初就内置了服务地址发现的能力,Provider 注册地址到注册中心,Consumer 通过订阅实时获取注册中心的地址更新,在收到地址列表后,consumer 基于特定的负载均衡策略发起对 provider 的 RPC 调用。</p> |
| <p>在这个过程中:</p> |
| <ul> |
| <li>每个 Provider 通过特定的 key 向注册中心注册本机可访问地址;</li> |
| <li>注册中心通过这个 key 对 provider 实例地址进行聚合;</li> |
| <li>Consumer 通过同样的 key 从注册中心订阅,以便及时收到聚合后的地址列表;</li> |
| </ul> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/interface-data1.png" alt="interface-data1"></p> |
| <p>这里,我们对接口级地址发现的内部数据结构进行详细分析。</p> |
| <p>首先,看右下角 provider 实例内部的数据与行为。Provider 部署的应用中通常会有多个 Service,也就是 Dubbo2 中的服务,每个 service 都可能会有其独有的配置,我们所讲的 service 服务发布的过程,其实就是基于这个服务配置生成地址 URL 的过程,生成的地址数据如图所示;同样的,其他服务也都会生成地址。</p> |
| <p>然后,看一下注册中心的地址数据存储结构,注册中心以 service 服务名为数据划分依据,将一个服务下的所有地址数据都作为子节点进行聚合,子节点的内容就是实际可访问的ip地址,也就是我们 Dubbo 中 URL,格式就是刚才 provider 实例生成的。</p> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/interface-data2.png" alt="interface-data2"></p> |
| <p>这里把 URL 地址数据划分成了几份:</p> |
| <ul> |
| <li>首先是实例可访问地址,主要信息包含 ip port,是消费端将基于这条数据生成 tcp 网络链接,作为后续 RPC 数据的传输载体</li> |
| <li>其次是 RPC 元数据,元数据用于定义和描述一次 RPC 请求,一方面表明这条地址数据是与某条具体的 RPC 服务有关的,它的版本号、分组以及方法相关信息,另一方面表明</li> |
| <li>下一部分是 RPC 配置数据,部分配置用于控制 RPC 调用的行为,还有一部分配置用于同步 Provider 进程实例的状态,典型的如超时时间、数据编码的序列化方式等。</li> |
| <li>最后一部分是自定义的元数据,这部分内容区别于以上框架预定义的各项配置,给了用户更大的灵活性,用户可任意扩展并添加自定义元数据,以进一步丰富实例状态。</li> |
| </ul> |
| <p>结合以上两页对于 Dubbo2 接口级地址模型的分析,以及最开始的 Dubbo 基本原理图,我们可以得出这么几条结论:</p> |
| <ul> |
| <li>第一,地址发现聚合的 key 就是 RPC 粒度的服务</li> |
| <li>第二,注册中心同步的数据不止包含地址,还包含了各种元数据以及配置</li> |
| <li>得益于 1 与 2,Dubbo 实现了支持应用、RPC 服务、方法粒度的服务治理能力</li> |
| </ul> |
| <p>这就是一直以来 Dubbo2 在易用性、服务治理功能性、可扩展性上强于很多服务框架的真正原因。</p> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/interface-defect.png" alt="interface-defect"></p> |
| <p>一个事物总是有其两面性,Dubbo2 地址模型带来易用性和强大功能的同时,也给整个架构的水平可扩展性带来了一些限制。这个问题在普通规模的微服务集群下是完全感知不到的,而随着集群规模的增长,当整个集群内应用、机器达到一定数量时,整个集群内的各个组件才开始遇到规模瓶颈。在总结包括阿里巴巴、工商银行等多个典型的用户在生产环境特点后,我们总结出以下两点突出问题(如图中红色所示):</p> |
| <ul> |
| <li>首先,注册中心集群容量达到上限阈值。由于所有的 URL 地址数据都被发送到注册中心,注册中心的存储容量达到上限,推送效率也随之下降。</li> |
| <li>而在消费端这一侧,Dubbo2 框架常驻内存已超 40%,每次地址推送带来的 cpu 等资源消耗率也非常高,影响正常的业务调用。</li> |
| </ul> |
| <p>为什么会出现这个问题?我们以一个具体 provider 示例进行展开,来尝试说明为何应用在接口级地址模型下容易遇到容量问题。 |
| 青蓝色部分,假设这里有一个普通的 Dubbo Provider 应用,该应用内部定义有 10 个 RPC Service,应用被部署在 100 个机器实例上。这个应用在集群中产生的数据量将会是 “Service 数 * 机器实例数”,也就是 10 * 100 = 1000 条。数据被从两个维度放大:</p> |
| <ul> |
| <li>从地址角度。100 条唯一的实例地址,被放大 10 倍</li> |
| <li>从服务角度。10 条唯一的服务元数据,被放大 100 倍</li> |
| </ul> |
| <h2 id="proposal">Proposal</h2> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/app-principle.png" alt="app-principle"></p> |
| <p>面对这个问题,在 Dubbo3 架构下,我们不得不重新思考两个问题:</p> |
| <ul> |
| <li>如何在保留易用性、功能性的同时,重新组织 URL 地址数据,避免冗余数据的出现,让 Dubbo3 能支撑更大规模集群水平扩容?</li> |
| <li>如何在地址发现层面与其他的微服务体系如 Kubernetes、Spring Cloud 打通?</li> |
| </ul> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/app-data1.png" alt="app-data1"></p> |
| <p>Dubbo3 的应用级服务发现方案设计本质上就是围绕以上两个问题展开。其基本思路是:地址发现链路上的聚合元素也就是我们之前提到的 Key 由服务调整为应用,这也是其名称叫做应用级服务发现的由来;另外,通过注册中心同步的数据内容上做了大幅精简,只保留最核心的 ip、port 地址数据。</p> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/app-data2.png" alt="app-data2"></p> |
| <p>这是升级之后应用级地址发现的内部数据结构进行详细分析。 |
| 对比之前接口级的地址发现模型,我们主要关注橙色部分的变化。首先,在 provider 实例这一侧,相比于之前每个 RPC Service 注册一条地址数据,一个 provider 实例只会注册一条地址到注册中心;而在注册中心这一侧,地址以应用名为粒度做聚合,应用名节点下是精简过后的 provider 实例地址;</p> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/app-metadataservice.png" alt="app-metadataservice"></p> |
| <p>应用级服务发现的上述调整,同时实现了地址单条数据大小和总数量的下降,但同时也带来了新的挑战:我们之前 Dubbo2 强调的易用性和功能性的基础损失了,因为元数据的传输被精简掉了,如何精细的控制单个服务的行为变得无法实现。</p> |
| <p>针对这个问题,Dubbo3 的解法是引入一个内置的 MetadataService 元数据服务,由中心化推送转为 Consumer 到 Provider 的点对点拉取,在这个模式下,元数据传输的数据量将不在是一个问题,因此可以在元数据中扩展出更多的参数、暴露更多的治理数据。</p> |
| <p><img src="https://dubbo.apache.org/imgs/blog/proposals/discovery/app-workflow.png" alt="app-metadataservice"></p> |
| <p>这里我们个重点看消费端 Consumer 的地址订阅行为,消费端从分两步读取地址数据,首先是从注册中心收到精简后的地址,随后通过调用 MetadataService 元数据服务,读取对端的元数据信息。在收到这两部分数据之后,消费端会完成地址数据的聚合,最终在运行态还原出类似 Dubbo2 的 URL 地址格式。因此从最终结果而言,应用级地址模型同时兼顾了地址传输层面的性能与运行层面的功能性。</p> |
| <p>以上就是的应用级服务发现背景、工作原理部分的所有内容,接下来我们看一下饿了么升级到 Dubbo3 尤其是应用级服务发现的过程。</p></description></item></channel></rss> |