OSPP Add some RPC metrics for Spring-Cloud-Admin (#3836)

2023.x-2024-ospp
kwings6 4 months ago committed by GitHub
parent bd6475ff69
commit c4d550cfe3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>admin-message-common</artifactId>
<name>Spring Cloud Starter Alibaba Admin Example Message Common</name>
<description>Admin message exmaple common codes</description>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

@ -0,0 +1,40 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.common;
/**
* @author kwings6
*/
public class SimpleMsg {
private String msg;
public SimpleMsg() {
}
public SimpleMsg(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}

@ -0,0 +1,29 @@
server:
port: 28087
spring:
application:
name: rocketmq-sql-consume-example
cloud:
stream:
function:
definition: producer;consumer;
rocketmq:
binder:
name-server: localhost:9876
bindings:
producer-out-0:
producer:
group: output_1
consumer-in-0:
consumer:
# tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } .
subscription: sql:(color in ('red1', 'red2', 'red4') and price>3)
bindings:
producer-out-0:
destination: sql
consumer-in-0:
destination: sql
group: sql-group
logging:
level:
org.springframework.context.support: debug

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.alibaba.cloud</groupId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>admin-prometheus-consumer-example</artifactId>
<name>Spring Cloud Admin Prometheus Consumer Example</name>
<description>Example for Spring Cloud Alibaba Admin Consumer</description>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!--todo sentinel need to support GraalVM in future-->
<!--<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>admin-message-common</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,59 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples;
import java.util.function.Consumer;
import com.alibaba.cloud.examples.common.SimpleMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.Message;
/**
* @author kwings6
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@LoadBalancerClients({
@LoadBalancerClient("service-provider")
})
public class ConsumerApplication {
private static final Logger log = LoggerFactory
.getLogger(ConsumerApplication.class);
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public Consumer<Message<SimpleMsg>> consumer() {
return msg -> {
log.info(Thread.currentThread().getName() + " Consumer Receive New Messages: " + msg.getPayload().getMsg());
};
}
}

@ -0,0 +1,179 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.cloud.examples.common.SimpleMsg;
import com.alibaba.cloud.examples.feign.EchoClient;
import jakarta.annotation.Resource;
import org.apache.rocketmq.common.message.MessageConst;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Example of remote invocation of service fusing and load balancing.
*
* @author kwings6
*/
@RestController
public class TestController {
private static final Logger log = LoggerFactory
.getLogger(ConsumerApplication.class);
@Autowired
private RestTemplate urlCleanedRestTemplate;
@Autowired
private RestTemplate restTemplate;
@Autowired
private EchoClient echoClient;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private StreamBridge streamBridge;
private static final String SERVICE_PROVIDER_ADDRESS = "http://service-provider";
@Resource
private ReactiveDiscoveryClient reactiveDiscoveryClient;
@Resource
private WebClient.Builder webClientBuilder;
@GetMapping("/pro")
public ApplicationRunner producerDelay() {
return args -> {
for (int i = 0; i < 100; i++) {
String key = "KEY" + i;
Map<String, Object> headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_KEYS, key);
headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);
headers.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, 2);
Message<SimpleMsg> msg = new GenericMessage(new SimpleMsg("Delay RocketMQ " + i), headers);
streamBridge.send("producer-out-0", msg);
}
};
}
@GetMapping("/exp")
public String exp() {
return restTemplate.getForObject("https://httpbin.org/status/500", String.class);
}
@GetMapping("/rt")
public String rt() {
return restTemplate.getForObject("https://httpbin.org/delay/3", String.class);
}
@GetMapping("/get")
public String get() {
return restTemplate.getForObject("https://httpbin.org/get", String.class);
}
@GetMapping("/all-services")
public Flux<String> allServices() {
return reactiveDiscoveryClient.getInstances("service-provider")
.map(serviceInstance -> serviceInstance.getHost() + ":"
+ serviceInstance.getPort());
}
@GetMapping("/service-call/{name}")
public Mono<String> serviceCall(@PathVariable("name") String name) {
return webClientBuilder.build().get()
.uri("http://service-provider/echo/" + name).retrieve()
.bodyToMono(String.class);
}
@GetMapping("/echo-rest/{str}")
public String rest(@PathVariable String str) {
return urlCleanedRestTemplate
.getForObject(SERVICE_PROVIDER_ADDRESS + "/echo/" + str,
String.class);
}
@GetMapping("/index")
public String index() {
return restTemplate.getForObject(SERVICE_PROVIDER_ADDRESS, String.class);
}
@GetMapping("/test")
public String test() {
return restTemplate
.getForObject(SERVICE_PROVIDER_ADDRESS + "/test", String.class);
}
@GetMapping("/sleep")
public String sleep() {
return restTemplate
.getForObject(SERVICE_PROVIDER_ADDRESS + "/sleep", String.class);
}
@GetMapping("/notFound-feign")
public String notFound() {
return echoClient.notFound();
}
@GetMapping("/divide-feign")
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
return echoClient.divide(a, b);
}
@GetMapping("/divide-feign2")
public String divide(@RequestParam Integer a) {
return echoClient.divide(a);
}
@GetMapping("/echo-feign/{str}")
public String feign(@PathVariable String str) {
return echoClient.echo(str);
}
@GetMapping("/services/{service}")
public Object client(@PathVariable String service) {
return discoveryClient.getInstances(service);
}
@GetMapping("/services")
public Object services() {
return discoveryClient.getServices();
}
}

@ -0,0 +1,36 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.configuration;
import com.alibaba.cloud.examples.feign.EchoClient;
import com.alibaba.cloud.examples.feign.EchoClientFallback;
import org.springframework.context.annotation.Bean;
/**
* Configuration for Feign.
*
* @author kwings6
*/
public class FeignConfiguration {
@Bean
public EchoClient echoClientFallback() {
return new EchoClientFallback();
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.configuration;
import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* Load balancing and sentinel configuration for RestTemplate.
*
* @author kwings6
*/
@Configuration
public class RestTemplateConfiguration {
@LoadBalanced
@Bean
// todo sentinel need to support GraalVM in future
// @SentinelRestTemplate(urlCleanerClass = UrlCleaner.class, urlCleaner = "clean")
public RestTemplate urlCleanedRestTemplate() {
return new RestTemplate();
}
@LoadBalanced
@Bean
// todo sentinel need to support GraalVM in future
@SentinelRestTemplate
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

@ -0,0 +1,76 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.configuration;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
/**
* @author kwings6
*/
@Component
public class SentinelRulesConfiguration {
/**
* You can configure sentinel rules by referring.
* https://sca.aliyun.com/docs/2023/user-guide/sentinel/advanced-guide/#%E6%9B%B4%E5%A4%9A%E9%85%8D%E7%BD%AE%E9%A1%B9
*/
@PostConstruct
public void init() {
System.out.println("Load Sentinel Rules start");
List<FlowRule> flowRules = new ArrayList<FlowRule>();
FlowRule flowRule = new FlowRule();
flowRule.setResource("GET:https://httpbin.org/get");
flowRule.setCount(1);
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT);
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setLimitApp("default");
flowRules.add(flowRule);
FlowRuleManager.loadRules(flowRules);
List<DegradeRule> degradeRules = new ArrayList<DegradeRule>();
DegradeRule degradeRule1 = new DegradeRule();
degradeRule1.setResource("GET:https://httpbin.org/status/500");
degradeRule1.setCount(1);
degradeRule1.setMinRequestAmount(1);
degradeRule1.setTimeWindow(30);
degradeRule1.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
degradeRule1.setLimitApp("default");
degradeRules.add(degradeRule1);
DegradeRule degradeRule2 = new DegradeRule();
degradeRule2.setResource("GET:https://httpbin.org/delay/3");
degradeRule2.setCount(1);
degradeRule2.setGrade(RuleConstant.DEGRADE_GRADE_RT);
degradeRule2.setSlowRatioThreshold(0.1);
degradeRule2.setMinRequestAmount(1);
degradeRule2.setTimeWindow(30);
degradeRule2.setLimitApp("default");
degradeRules.add(degradeRule2);
DegradeRuleManager.loadRules(degradeRules);
System.out.println("Load Sentinel Rules end");
}
}

@ -0,0 +1,42 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Change the request path containing echo.
*
* @author kwings6
*/
public class UrlCleaner {
private static final Logger LOGGER = LoggerFactory.getLogger(UrlCleaner.class);
private static final String URL_CLEAN_ECHO = ".*/echo/.*";
public static String clean(String url) {
LOGGER.info("enter urlCleaner");
if (url.matches(URL_CLEAN_ECHO)) {
LOGGER.info("change url");
url = url.replaceAll("/echo/.*", "/echo/{str}");
}
return url;
}
}

@ -0,0 +1,39 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.configuration;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Configuration for web client.
*
* @author kwings6
*/
@Configuration
public class WebClientConfiguration {
@Bean
@LoadBalanced
public WebClient.Builder webClient() {
return WebClient.builder();
}
}

@ -0,0 +1,68 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Provide the external exposure interface of the service calling client.
*
* @author kwings6
*/
@FeignClient(name = "service-provider", contextId = "service-provider")
public interface EchoClient {
/**
* Call the echo method of the remote provider or roll back when the service is blown.
*
* @param str str
* @return {@link String}
*/
@GetMapping("/echo/{str}")
String echo(@PathVariable("str") String str);
/**
* Call the divide method of the remote provider or roll back when the service is blown.
*
* @param a a
* @param b b
* @return {@link String}
*/
@GetMapping("/divide")
String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b);
/**
* Test that the default method calls the remote method is still a remote call.
*
* @param a a
* @return {@link String}
*/
default String divide(Integer a) {
return divide(a, 0);
}
/**
* Call the notFound method of the remote provider or roll back when the service is blown.
*
* @return {@link String}
*/
@GetMapping("/notFound")
String notFound();
}

@ -0,0 +1,43 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.feign;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* When the service is blown, the fallback operation is performed.
*
* @author kwings6
*/
public class EchoClientFallback implements EchoClient {
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
@Override
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
return "divide fallback";
}
@Override
public String notFound() {
return "notFound fallback";
}
}

@ -0,0 +1,49 @@
management:
endpoint:
prometheus:
enabled: true
endpoints:
web:
exposure:
include: '*'
prometheus:
metrics:
export:
enabled: true
server:
port: 18083
spring:
application:
name: service-consumer
cloud:
loadbalancer:
nacos:
enabled: true
ribbon:
enabled: false
nacos:
discovery:
fail-fast: true
server-addr: 127.0.0.1:8848
password: nacos
username: nacos
refresh:
enabled: false
sentinel:
transport:
dashboard: localhost:8080
eager: true
stream:
function:
definition: consumer;
rocketmq:
binder:
name-server: localhost:9876
bindings:
consumer-in-0:
consumer:
messageModel: BROADCASTING
bindings:
consumer-in-0:
destination: broadcast
group: broadcast-consumer

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.alibaba.cloud</groupId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>admin-prometheus-provider-example</artifactId>
<name>Spring Cloud Admin Prometheus Provider Example</name>
<description>Example for Spring Cloud Alibaba Admin Provider</description>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>admin-message-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,91 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples;
import java.util.Map;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Provide interfaces to consumers.
*
* @author kwings6
*/
@RestController
public class EchoController {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@GetMapping("/sayHello")
public String sayHello() {
System.out.println("beifangwen");
return "nihao";
}
@GetMapping("/")
public ResponseEntity<String> index() {
return new ResponseEntity<>("index error", HttpStatus.INTERNAL_SERVER_ERROR);
}
@GetMapping("/test")
public ResponseEntity<String> test() {
return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR);
}
@GetMapping("/sleep")
public String sleep() {
try {
Thread.sleep(1000L);
}
catch (InterruptedException e) {
e.printStackTrace();
}
return "ok";
}
@GetMapping("/echo/{string}")
public String echo(@PathVariable String string) {
return "hello Nacos Discovery " + string;
}
@GetMapping("/divide")
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
if (b == 0) {
return String.valueOf(0);
}
else {
return String.valueOf(a / b);
}
}
@GetMapping("/zone")
public String zone() {
Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
return "provider zone " + metadata.get("zone");
}
}

@ -0,0 +1,69 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.cloud.examples.common.SimpleMsg;
import org.apache.rocketmq.common.message.MessageConst;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
/**
* @author kwings6
*/
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@Autowired
private StreamBridge streamBridge;
private static final Logger log = LoggerFactory
.getLogger(ProviderApplication.class);
@Bean
public ApplicationRunner producer() {
return args -> {
Thread.sleep(30000);
for (int i = 0; i < 100; i++) {
String key = "KEY" + i;
Map<String, Object> headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_KEYS, key);
headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);
Message<SimpleMsg> msg = new GenericMessage<SimpleMsg>(new SimpleMsg("Hello RocketMQ " + i), headers);
streamBridge.send("producer-out-0", msg);
}
};
}
}

@ -0,0 +1,31 @@
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: '*'
server:
port: 18080
spring:
application:
name: service-provider
cloud:
nacos:
discovery:
enabled: true
server-addr: 127.0.0.1:8848
password: nacos
username: nacos
stream:
rocketmq:
binder:
name-server: localhost:9876
bindings:
producer-out-0:
producer:
group: output_1
bindings:
producer-out-0:
destination: broadcast

@ -0,0 +1,49 @@
version: "3.8"
services:
grafana:
image: grafana/grafana
hostname: "otel-grafana"
container_name: "otel-grafana"
ports:
- "3000:3000"
# Jaeger
# jaeger-all-in-one:
# image: jaegertracing/all-in-one:latest
# ports:
# - "16686:16686"
# - "14268"
# - "14250:14250"
# environment:
# - COLLECTOR_OTLP_ENABLED=true
# Zipkin
zipkin-all-in-one:
image: openzipkin/zipkin:latest
hostname: zipkin
ports:
- "9411:9411"
otel-collector:
container_name: "otel-collector"
hostname: "otel-collector"
image: otel/opentelemetry-collector
volumes:
- ./otel/config.yml:/etc/otel/config.yml
ports:
# - 18880:1888 # pprof extension
# - 8888:8888 # Prometheus metrics exposed by the Collector
- "8889:8889" # Prometheus exporter metrics
# - 13133:13133 # health_check extension
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP http receiver
# - 55679:55679 # zpages extensio
prometheus:
container_name: prometheus
image: prom/prometheus:latest
volumes:
- ./prometheus/config.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"

@ -0,0 +1,42 @@
# https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/examples/demo/otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
namespace: promexample
const_labels:
label1: value1
logging:
loglevel: debug
zipkin:
endpoint: "http://zipkin:9411/api/v2/spans"
format: proto
# otlp/jaeger:
# endpoint: jaeger-all-in-one:14250
# tls:
# insecure: true
service:
# extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [logging, zipkin]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [logging, prometheus]

@ -0,0 +1,20 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['127.0.0.1:9090']
- job_name: 'admin-prometheus'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: [ '127.0.0.1:18083' ]
#
# - job_name: 'otel-collector'
# scrape_interval: 2s
# static_configs:
# - targets: ['otel-collector:8888']

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

@ -0,0 +1,321 @@
# Spring Cloud Alibaba Admin Example
## 项目说明
本项目演示如何使用 Spring Cloud Alibaba Admin 相关 Starter 获得对 Spring Cloud Alibaba实例的监控数据。并展示到Prometheus和Grafana中。
Admin中数据来自于使用MicroMeter在Starter中的原生埋点。
此项目只包括NacosRocketMQSentinel指标如果想查看Seata相关指标可以启动Seata Example按照下列步骤进行查看。
## 正确配置并启动 Nacos Server 2.4.2
在 Nacos 2.4.2 中,加入了用户鉴权相关的功能,在首次启动 Nacos Server 时,需要正确配置,避免出现启动失败的问题。
### 下载 Nacos Server
> 本示例中使用 Nacos Server 版本为 2.4.2
Nacos 支持直接下载和源码构建两种方式。**推荐在 Spring Cloud Alibaba 2023.x 中使用 Nacos Server 2.4.2 版本。**
1. 直接下载:[Nacos Server 下载页](https://github.com/alibaba/nacos/releases)
2. 源码构建:进入 Nacos [Github 项目页面](https://github.com/alibaba/nacos),将代码 git clone 到本地自行编译打包,[参考文档](https://nacos.io/zh-cn/docs/quick-start.html)。
### 配置 Nacos Server
打开 `\nacos-server-2.4.2\conf\application.properties` 配置文件,修改以下配置项:
#### 配置数据源
此处以 MySQL 数据库为例,使用 `nacos-server-2.4.2\conf\mysql-schema.sql` 初始化数据库表文件。同时修改以下配置
```properties
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
db.user.0=root
db.password.0=root
### Connection pool configuration: hikariCP
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20
db.pool.config.minimumIdle=2
```
#### 开启鉴权
**注意:不开启在 2.4.2 中会出现登陆失败异常!**
```properties
### The auth system to use, currently only 'nacos' and 'ldap' is supported:
nacos.core.auth.system.type=nacos
### If turn on auth system:
nacos.core.auth.enabled=true
```
#### 设置服务端验证 key
```properties
nacos.core.auth.server.identity.key=test
nacos.core.auth.server.identity.value=test
```
#### 设置默认 token
```properties
### The default token (Base64 String):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
```
**在使用 Nacos 服务发现和配置功能时,一定要配置 `username``password` 属性,否则会出现用户未找到异常!**
#### Open API 鉴权
在 nacos server 2.4.2 中使用 Open api 接口时需要鉴权:更多细节请参考:[Nacos api 鉴权](https://nacos.io/zh-cn/docs/auth.html)
1. 获取 accessToken使用用户名和密码登陆 nacos server
`curl -X POST '127.0.0.1:8848/nacos/v1/auth/login' -d 'username=nacos&password=nacos'`
若用户名和密码正确,返回信息如下:
`{"accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyOTE2Nn0.2TogGhhr11_vLEjqKko1HJHUJEmsPuCxkur-CfNojDo","tokenTtl":18000,"globalAdmin":true}`
2. 使用 accessToken 请求 nacos api 接口:
`curl -X GET '127.0.0.1:8848/nacos/v1/cs/configs?accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyMzkyM30.O-s2yWfDSUZ7Svd3Vs7jy9tsfDNHs1SuebJB4KlNY8Q&dataId=nacos.example.1&group=nacos_group'`
### 单机启动 Nacos Server
1. 启动 Nacos Server进入下载到本地并解压完成后的文件夹(使用源码构建的方式则进入编译打包好的文件夹),再进去其相对文件夹 `nacos/bin`,并对照操作系统实际情况执行如下命令。[详情参考此文档](https://nacos.io/zh-cn/docs/quick-start.html)。
1. Linux/Unix/Mac 操作系统,执行命令
`sh startup.sh -m standalone`
2. Windows 操作系统,执行命令
`cmd startup.cmd -m standalone`
2. 访问 Nacos Server Console
浏览器输入地址 http://127.0.0.1:8848/nacos **首次登陆需要绑定 nacos 用户,因为新版本增加了鉴权,需要应用注册和配置绑定时配置用户名和密码。**
## 正确配置RocketMQ并启动
### Spring Cloud Alibaba RocketMQ
**首先需要启动 RocketMQ 的 Name Server 和 Broker。**
1. 下载[RocketMQ最新的二进制文件](https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.3.2/rocketmq-all-4.3.2-bin-release.zip),并解压
2. 启动 Name Server
```bash
sh bin/mqnamesrv
```
3. 启动 Broker
```bash
sh bin/mqbroker -n localhost:9876
```
Windows操作系统直接启动 `mqnamesrv.cmd`
然后 `start mqbroker.cmd -n localhost:9876 autoCreateTopicEnable=true`
## Admin 应用示例
#### 完成前置组件配置后按照顺序启动admin-prometheus-consumer-example和admin-prometheus-provider-example
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@LoadBalancerClients({
@LoadBalancerClient("service-provider")
})
public class ConsumerApplication {
private static final Logger log = LoggerFactory
.getLogger(ConsumerApplication.class);
@Autowired
private StreamBridge streamBridge;
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public Consumer<Message<SimpleMsg>> consumer() {
return msg -> {
log.info(Thread.currentThread().getName() + " Consumer Receive New Messages: " + msg.getPayload().getMsg());
};
}
}
```
```java
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@Autowired
private StreamBridge streamBridge;
private static final Logger log = LoggerFactory
.getLogger(ProviderApplication.class);
@Bean
public ApplicationRunner producer() {
return args -> {
Thread.sleep(30000);
for (int i = 0; i < 100; i++) {
String key = "KEY" + i;
Map<String, Object> headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_KEYS, key);
headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);
Message<SimpleMsg> msg = new GenericMessage<SimpleMsg>(new SimpleMsg("Hello RocketMQ " + i), headers);
streamBridge.send("producer-out-0", msg);
}
};
}
}
```
#### 分别使用Nacos RestTemplateOpenFeignReactive
1. 地址栏分别输入
`http://localhost:18083/echo-rest/test`
`http://localhost:18083/echo-feign/test`
`http://localhost:18083/service-call/test`
2. 输入`http://localhost:18083/actuator/metrics`查看指标
```text
"spring.cloud.rpc.openfeign.qps"
"spring.cloud.rpc.reactive.qps"
"spring.cloud.rpc.restTemplate.qps"
```
3. 输入`http://localhost:18083/actuator/metrics/spring-cloud.rpc.reactive.qps`可查看详细数据
```json
{
"name": "spring-cloud.rpc.reactive.qps",
"description": "Spring Cloud Alibaba QPS metrics when use Reactive RPC Call.",
"baseUnit": "SECONDS",
"measurements": [{
"statistic": "COUNT",
"value": 17
}],
"availableTags": [{
"tag": "sca.reactive.rpc.method",
"values": ["GET"]
},
{
"tag": "sca.reactive.rpc",
"values": ["url: http://10.2.64.89:18080/echo/11 method: GET status: 200 OK"]
}]
}
```
#### 使用Sentinel RestTemplate的degrade和flow
1. 在地址栏输入
`http://localhost:18083/rt`
`http://localhost:18083/get`
2. 输入`http://localhost:18083/actuator/metrics`查看指标
```text
"spring.cloud.alibaba.sentinel.degrade.sum"
"spring.cloud.alibaba.sentinel.flow.sum"
```
3. 输入`http://localhost:18083/actuator/metrics/spring.cloud.alibaba.sentinel.degrade.sum`查看具体指标
```json
{
"name": "spring.cloud.alibaba.sentinel.degrade.sum",
"measurements": [{
"statistic": "COUNT",
"value": 16
}],
"availableTags": []
}
```
## 接入Prometheus和Grafana
#### 可以先通过地址`http://localhost:18083/actuator/prometheus`查看传输给Prometheus的数据
<img src="./images/image-20241025103000343.png" alt="image-20241025103000343.png" style="zoom: 50%;" />
**通过docker启动Prometheus和Grafana启动前修改prometheus文件夹下的config.yml 的targets位置的ip地址请修改为个人电脑的ip**
```yaml
- job_name: 'admin-prometheus'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: [ '127.0.0.1:18083' ]
```
**输入`http://localhost:9090/targets?search=`有两个刮取指标的地址**
<img src="./images/image-20241025103641209.png" alt="image-20241025103641209.png" style="zoom: 50%;" />
<img src="./images/image-20241025103649642.png" alt="image-20241025103649642.png" style="zoom: 50%;" />
**然后再搜索框进行搜索能看到指标**
<img src="./images/image-20241024225418675.png" alt="image-20241024225418675.png" style="zoom: 50%;" />
<img src="./images/image-20241024225435691.png" alt="image-20241024225435691.png" style="zoom: 50%;" />
#### 使用docker启动grafana
**账户密码均输入admin然后点击skip**
<img src="./images/image-20241024225527267.png" alt="image-20241024225527267.png" style="zoom: 50%;" />
**添加数据源**
<img src="./images/image-20241024225604518.png" alt="image-20241024225604518.png" style="zoom: 50%;" />
**此处输入个人电脑ip+9090**
<img src="./images/image-20241024225633698.png" alt="image-20241024225633698.png" style="zoom: 50%;" />
**测试是否成功**
<img src="./images/image-20241024225708457.png" alt="image-20241024225708457.png" style="zoom: 50%;" />
**返回到dashboard中import导入文件夹中的json文件作为grafana面板**
[此面板基于SLS JVM监控大盘进行修改](https://grafana.com/grafana/dashboards/12856-jvm-micrometer/)
<img src="./images/image-20241024225744092.png" alt="image-20241024225744092.png" style="zoom: 50%;" />
<img src="./images/image-20241024225835039.png" alt="image-20241024225835039.png" style="zoom: 50%;" />
<img src="./images/image-20241025001135834.png" alt="image-20241025001135834.png" style="zoom: 50%;" />
<img src="./images/image-20241025001146055.png" alt="image-20241025001146055.png" style="zoom: 50%;" />

@ -0,0 +1,321 @@
# Spring Cloud Alibaba Admin Example
## Project description
This project demonstrates how to use Spring Cloud Alibaba Admin related Starter to obtain monitoring data for Spring Cloud Alibaba instances. And displayed on Prometheus and Grafana.
The data in Admin comes from the native embedding points in Starter using MicroMeter.
This project only includes Nacos, RocketMQ, Sentinel indicators. If you want to view Seata related indicators, you can start Seata Example and follow the steps below to view them.
## Nacos Server 2.4.2 is properly configured and started
In Nacos 2.4.2, functions related to user authentication are added. When starting Nacos Server for the first time, it needs to be configured correctly to avoid the problem of startup failure.
### Download Nacos Server
> The Nacos serv version used in this example is 2.2.3!
Nacos supports both direct download and source code construction. **Nacos Server version 2.2.3 is recommended for Spring Cloud Alibaba 2022.x.**
1. Direct download: [Nacos Server download page](https://github.com/alibaba/nacos/releases)
2. Source code construction: Enter Nacos [Github project page](https://github.com/alibaba/nacos), git clone the code to the local compilation and packaging [参考文档](https://nacos.io/zh-cn/docs/quick-start.html).
### Configure the Nacos Server
Open the `\nacos-server-2.2.3\conf\application.properties` configuration file and modify the following configuration items:
#### Configure the data source
Take the MySQL database as an example here, and use the `nacos-server-2.2.3\conf\mysql-schema.sql` initialization database table file. Modify the following configuration as well
```properties
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
db.user.0=root
db.password.0=root
### Connection pool configuration: hikariCP
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20
db.pool.config.minimumIdle=2
```
#### Turn on authentication
**Note: If it is not enabled, login failure exception will occur in 2.4.2!**
```properties
### The auth system to use, currently only 'nacos' and 'ldap' is supported:
nacos.core.auth.system.type=nacos
### If turn on auth system:
nacos.core.auth.enabled=true
```
#### Set the server authentication key
```properties
nacos.core.auth.server.identity.key=test
nacos.core.auth.server.identity.value=test
```
#### Set the default token
```properties
### The default token (Base64 String):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
```
** When using the Nacos service discovery and configuration function, be sure to configure `username` and `password` attribute, otherwise the user will not be found! **
#### Open API authentication
Authentication is required when using the Open api interface in nacos server 2.4.2: For more details, please refer to: [Nacos api authentication](https://nacos.io/zh-cn/docs/auth.html)
1. Obtain accessToken: Use username and password to log in to the nacos server:
`curl -X POST '127.0.0.1:8848/nacos/v1/auth/login' -d 'username=nacos&password=nacos'`
If the username and password are correct, the returned information is as follows:
`{"accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyOTE2Nn0.2TogGhhr11_vLEjqKko1HJHUJEmsPuCxkur-CfNojDo", "tokenTtl": 18000, "globalAdmin": true}`
2. Use accessToken to request the nacos api interface:
`curl -X GET '127.0.0.1:8848/nacos/v1/cs/configs?accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyMzkyM30.O-s2yWfDSUZ7Svd3Vs7jy9tsfDNHs1SuebJB4KlNY8Q&dataId=nacos.example.1&group=nacos_group'`
### Start the Nacos Server
1. Start Nacos Server, enter the folder after downloading to the local and decompressing (enter the folder after compiling and packaging by using the source code construction method), then enter its relative folder `nacos/bin`, and execute the following command according to the actual situation of the operating system. [详情参考此文档](https://nacos.io/zh-cn/docs/quick-start.html)。
1. Linux/Unix/Mac operating system, execute the command
`sh startup.sh -m standalone`
2. Windows operating system, executing command
`cmd startup.cmd`
2. Access Nacos Server Console.
The browser enters the address http://127.0.0.1:8848/nacos , **The first login needs to bind the nacos user, because the new version adds authentication, and the user name and password need to be configured during application registration and configuration binding.**
## Configure RocketMQ and start it
### Spring Cloud Alibaba RocketMQ
**Firstly, it is necessary to start the Name Server and Broker of RocketMQ**
### Download and Startup RocketMQ
You should startup Name Server and Broker before using RocketMQ Binder.
1. Download [RocketMQ](https://archive.apache.org/dist/rocketmq/4.3.2/rocketmq-all-4.3.2-bin-release.zip) and unzip it.
2. Startup Name Server
```
sh bin/mqnamesrv
```
3. Startup Broker
```
sh bin/mqbroker -n localhost:9876
```
## Admin application example
#### After completing the configuration of the front-end components, start admin-prometheus-consumer-example和admin-prometheus-provider-example in order
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@LoadBalancerClients({
@LoadBalancerClient("service-provider")
})
public class ConsumerApplication {
private static final Logger log = LoggerFactory
.getLogger(ConsumerApplication.class);
@Autowired
private StreamBridge streamBridge;
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public Consumer<Message<SimpleMsg>> consumer() {
return msg -> {
log.info(Thread.currentThread().getName() + " Consumer Receive New Messages: " + msg.getPayload().getMsg());
};
}
}
```
```java
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@Autowired
private StreamBridge streamBridge;
private static final Logger log = LoggerFactory
.getLogger(ProviderApplication.class);
@Bean
public ApplicationRunner producer() {
return args -> {
Thread.sleep(30000);
for (int i = 0; i < 100; i++) {
String key = "KEY" + i;
Map<String, Object> headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_KEYS, key);
headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i);
Message<SimpleMsg> msg = new GenericMessage<SimpleMsg>(new SimpleMsg("Hello RocketMQ " + i), headers);
streamBridge.send("producer-out-0", msg);
}
};
}
}
```
#### Use Nacos RestTemplate, OpenFeign, Reactive respectively
1. Address bar input
`http://localhost:18083/echo-rest/test`
`http://localhost:18083/echo-feign/test`
`http://localhost:18083/service-call/test`
2. Input` http://localhost:18083/actuator/metrics `view metrics
```text
"spring.cloud.rpc.openfeign.qps"
"spring.cloud.rpc.reactive.qps"
"spring.cloud.rpc.restTemplate.qps"
```
3. Input` http://localhost:18083/actuator/metrics/spring -Cloud.rpc.reactive. qps ` can view detailed data
```json
{
"name": "spring-cloud.rpc.reactive.qps",
"description": "Spring Cloud Alibaba QPS metrics when use Reactive RPC Call.",
"baseUnit": "SECONDS",
"measurements": [{
"statistic": "COUNT",
"value": 17
}],
"availableTags": [{
"tag": "sca.reactive.rpc.method",
"values": ["GET"]
},
{
"tag": "sca.reactive.rpc",
"values": ["url: http://10.2.64.89:18080/echo/11 method: GET status: 200 OK"]
}]
}
```
#### Using Sentinel RestTemplate for Grading and Flow
1. Enter in the address bar
`http://localhost:18083/rt`
`http://localhost:18083/get`
2. Input`http://localhost:18083/actuator/metrics`View metrics
```text
"spring.cloud.alibaba.sentinel.degrade.sum"
"spring.cloud.alibaba.sentinel.flow.sum"
```
3. input`http://localhost:18083/actuator/metrics/spring.cloud.alibaba.sentinel.degrade.sum`can view detailed data
```json
{
"name": "spring.cloud.alibaba.sentinel.degrade.sum",
"measurements": [{
"statistic": "COUNT",
"value": 16
}],
"availableTags": []
}
```
## Integrate Prometheus and Grafana
#### First provide the address` http://localhost:18083/actuator/prometheus `View data transmitted to Prometheus
<img src="./images/image-20241025103000343.png" alt="image-20241025103000343.png" style="zoom: 50%;" />
**Start Prometheus and Grafana through Docker. Before starting, modify the IP address of the targets location in the config. yml folder of Prometheus to the IP address of your personal computer**
```yaml
- job_name: 'admin-prometheus'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: [ '127.0.0.1:18083' ]
```
**Input` http://localhost:9090/targets?search= `There are two addresses for scraping metrics**
<img src="./images/image-20241025103641209.png" alt="image-20241025103641209.png" style="zoom: 50%;" />
<img src="./images/image-20241025103649642.png" alt="image-20241025103649642.png" style="zoom: 50%;" />
**Then search in the search box to see the metrics**
<img src="./images/image-20241024225418675.png" alt="image-20241024225418675.png" style="zoom: 50%;" />
<img src="./images/image-20241024225435691.png" alt="image-20241024225435691.png" style="zoom: 50%;" />
#### Starting Grafana with Docker
**Enter admin for both account and password, then click on 'skip'**
<img src="./images/image-20241024225527267.png" alt="image-20241024225527267.png" style="zoom: 50%;" />
**Add data source**
<img src="./images/image-20241024225604518.png" alt="image-20241024225604518.png" style="zoom: 50%;" />
**Enter personal computer IP+9090 here**
<img src="./images/image-20241024225633698.png" alt="image-20241024225633698.png" style="zoom: 50%;" />
**Is the test successful**
<img src="./images/image-20241024225708457.png" alt="image-20241024225708457.png" style="zoom: 50%;" />
**Return to dashboard and import the JSON file from the import folder as the Grafana panel**
[This panel is modified based on the SLS JVM monitoring dashboard](https://grafana.com/grafana/dashboards/12856-jvm-micrometer/)
<img src="./images/image-20241024225744092.png" alt="image-20241024225744092.png" style="zoom: 50%;" />
<img src="./images/image-20241024225835039.png" alt="image-20241024225835039.png" style="zoom: 50%;" />
<img src="./images/image-20241025001135834.png" alt="image-20241025001135834.png" style="zoom: 50%;" />
<img src="./images/image-20241025001146055.png" alt="image-20241025001146055.png" style="zoom: 50%;" />

@ -21,6 +21,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
@ -36,6 +37,11 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!--todo sentinel need to support GraalVM in future-->
<!--<dependency>
<groupId>com.alibaba.cloud</groupId>
@ -46,6 +52,11 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
<build>

@ -1,6 +1,8 @@
spring.application.name=service-consumer
server.port=18083
management.endpoints.web.exposure.include=*
management.endpoint.prometheus.enabled=true
management.prometheus.metrics.export.enabled=true
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.fail-fast=true

@ -21,6 +21,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

@ -18,7 +18,6 @@ package com.alibaba.cloud.examples;
import java.util.Map;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import jakarta.annotation.Resource;

@ -55,6 +55,9 @@
<module>ai-example/spring-cloud-ai-rag-example</module>
<module>ai-example/spring-cloud-ai-chat-msg-context-example</module>
<module>spring-cloud-scheduling-example</module>
<module>admin-example/admin-prometheus-provider-example</module>
<module>admin-example/admin-prometheus-consumer-example</module>
<module>admin-example/admin-message-common</module>
</modules>

@ -27,6 +27,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
@ -36,6 +40,11 @@
<artifactId>rocketmq-example-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>

@ -21,3 +21,12 @@ spring:
logging:
level:
org.springframework.context.support: debug
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -26,6 +26,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>

@ -21,3 +21,12 @@ spring:
logging:
level:
org.springframework.context.support: debug
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -26,6 +26,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
@ -35,6 +44,11 @@
<artifactId>rocketmq-example-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>4.2.27</version>
</dependency>
</dependencies>
<build>

@ -18,3 +18,12 @@ spring:
logging:
level:
org.springframework.context.support: debug
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -26,6 +26,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>

@ -31,3 +31,12 @@ spring:
logging:
level:
org.springframework.context.support: debug
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -54,3 +54,12 @@ seata:
server-addr: 127.0.0.1:8848
username: 'nacos'
password: 'nacos'
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -45,3 +45,11 @@ logging:
io:
seata: debug
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -51,3 +51,12 @@ seata:
server-addr: 127.0.0.1:8848
username: 'nacos'
password: 'nacos'
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -53,3 +53,11 @@ seata:
username: 'nacos'
password: 'nacos'
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -45,6 +45,10 @@
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<build>

@ -67,14 +67,23 @@ server:
port: 18083
management:
endpoint:
endpoints:
web:
exposure:
include: "*"
exposure:
include: "*"
endpoint:
health:
show-details: always
diskSpace:
# we can disable health check, default is enable
enabled: false
# sentinel:
# enabled: false
#management:
# endpoint:
# web:
# exposure:
# include: "*"
# health:
# show-details: always
# diskSpace:
# # we can disable health check, default is enable
# enabled: false
# # sentinel:
# # enabled: false

@ -22,6 +22,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
@ -32,6 +36,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<build>

@ -39,3 +39,12 @@ spring:
# file: "classpath: flowrule.json"
# ruleType: "flow"
# dataType: "json"
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always

@ -19,6 +19,12 @@
<artifactId>spring-cloud-alibaba-commons</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
@ -43,6 +49,12 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
@ -95,6 +107,11 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

@ -20,6 +20,7 @@ import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration;
import com.alibaba.cloud.nacos.metrics.NacosMetricsAutoConfiguration;
import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -37,9 +38,14 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled",
matchIfMissing = false)
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration({ NacosDiscoveryAutoConfiguration.class,
NacosServiceAutoConfiguration.class, NacosDiscoveryClientConfiguration.class,
NacosReactiveDiscoveryClientConfiguration.class, UtilIPv6AutoConfiguration.class })
@ImportAutoConfiguration({
NacosDiscoveryAutoConfiguration.class,
NacosServiceAutoConfiguration.class,
NacosDiscoveryClientConfiguration.class,
NacosReactiveDiscoveryClientConfiguration.class,
UtilIPv6AutoConfiguration.class,
NacosMetricsAutoConfiguration.class
})
public class NacosDiscoveryClientConfigServiceBootstrapConfiguration {
}

@ -0,0 +1,91 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.metrics;
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.metrics.aop.NacosDiscoveryMetricsReactiveBeanPostProcessor;
import com.alibaba.cloud.nacos.metrics.aop.NacosDiscoveryMetricsRestBeanPostProcessor;
import com.alibaba.cloud.nacos.metrics.aop.interceptor.NacosDiscoveryMetricsOpenFeignInterceptor;
import com.alibaba.cloud.nacos.metrics.aop.interceptor.NacosDiscoveryMetricsReactiveInterceptor;
import com.alibaba.cloud.nacos.metrics.aop.interceptor.NacosDiscoveryMetricsRestTemplateInterceptor;
import feign.Client;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosMetricsAutoConfiguration {
@Bean
InitializingBean forcePrometheusPostProcessor(BeanPostProcessor meterRegistryPostProcessor, PrometheusMeterRegistry registry) {
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(registry, "");
}
@ConditionalOnClass(WebClient.class)
@ConditionalOnBean(WebClient.Builder.class)
protected static class NacosMetricsReactiveConfiguration {
@Bean
@ConditionalOnMissingBean
public NacosDiscoveryMetricsReactiveInterceptor nacosDiscoveryMetricsReactiveInterceptor() {
return new NacosDiscoveryMetricsReactiveInterceptor();
}
@Bean
public NacosDiscoveryMetricsReactiveBeanPostProcessor nacosDiscoveryMetricsReactiveBeanPostProcessor() {
return new NacosDiscoveryMetricsReactiveBeanPostProcessor();
}
}
@ConditionalOnClass(RestTemplate.class)
protected static class NacosMetricsRestConfiguration {
@Bean
@ConditionalOnMissingBean
public NacosDiscoveryMetricsRestTemplateInterceptor nacosDiscoveryMetricsRestTemplateInterceptor() {
return new NacosDiscoveryMetricsRestTemplateInterceptor();
}
@Bean
public NacosDiscoveryMetricsRestBeanPostProcessor nacosDiscoveryMetricsRestBeanPostProcessor() {
return new NacosDiscoveryMetricsRestBeanPostProcessor();
}
}
@ConditionalOnClass(Client.class)
protected static class NacosMetricsOpenFeignConfiguration {
@Bean(name = "nacosDiscoveryMetricsOpenFeignInterceptor")
@ConditionalOnMissingBean
public NacosDiscoveryMetricsOpenFeignInterceptor nacosDiscoveryMetricsOpenFeignInterceptor() {
return new NacosDiscoveryMetricsOpenFeignInterceptor();
}
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.metrics.aop;
import com.alibaba.cloud.nacos.metrics.aop.interceptor.NacosDiscoveryMetricsReactiveInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.web.reactive.function.client.WebClient;
public class NacosDiscoveryMetricsReactiveBeanPostProcessor implements BeanPostProcessor {
@Autowired
private NacosDiscoveryMetricsReactiveInterceptor reactiveInterceptor;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebClient.Builder) {
WebClient.Builder builder = (WebClient.Builder) bean;
builder.filter(reactiveInterceptor);
}
return bean;
}
}

@ -0,0 +1,46 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.metrics.aop;
import java.util.Arrays;
import com.alibaba.cloud.nacos.metrics.aop.interceptor.NacosDiscoveryMetricsRestTemplateInterceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.web.client.RestTemplate;
public class NacosDiscoveryMetricsRestBeanPostProcessor implements BeanPostProcessor {
@Autowired
private NacosDiscoveryMetricsRestTemplateInterceptor restTemplateInterceptor;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RestTemplate) {
RestTemplate restTemplate = (RestTemplate) bean;
restTemplate.setInterceptors(Arrays.asList(restTemplateInterceptor));
}
return bean;
}
}

@ -0,0 +1,70 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.metrics.aop.interceptor;
import java.util.concurrent.TimeUnit;
import feign.InvocationContext;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Response;
import feign.ResponseInterceptor;
import io.micrometer.core.instrument.Counter;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
public class NacosDiscoveryMetricsOpenFeignInterceptor implements ResponseInterceptor, RequestInterceptor {
@Autowired
private PrometheusMeterRegistry prometheusMeterRegistry;
RequestTemplate request;
@Override
public Object intercept(InvocationContext invocationContext, Chain chain) throws Exception {
Response response = invocationContext.response();
Counter qpsCounter = Counter.builder("spring.cloud.rpc.openfeign.qps")
.description("Spring Cloud Alibaba QPS metrics when use OpenFeign RPC Call.")
.baseUnit(TimeUnit.SECONDS.name())
.tag("sca.openfeign.rpc", "url: " + request.url()
+ " method: " + request.method()
+ " status: " + response.status())
.register(prometheusMeterRegistry);
qpsCounter.increment();
return null;
}
@Override
public ResponseInterceptor andThen(ResponseInterceptor nextInterceptor) {
return ResponseInterceptor.super.andThen(nextInterceptor);
}
@Override
public Chain apply(Chain chain) {
return ResponseInterceptor.super.apply(chain);
}
@Override
public void apply(RequestTemplate requestTemplate) {
this.request = requestTemplate;
}
}

@ -0,0 +1,70 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.metrics.aop.interceptor;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.Counter;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
public class NacosDiscoveryMetricsReactiveInterceptor implements ExchangeFilterFunction {
@Autowired
private PrometheusMeterRegistry prometheusMeterRegistry;
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
HttpMethod method = request.method();
URI url = request.url();
return next.exchange(request).doOnSuccess(response -> {
Counter qpsCounter = Counter.builder("spring.cloud.rpc.reactive.qps")
.description("Spring Cloud Alibaba QPS metrics when use Reactive RPC Call.")
.baseUnit(TimeUnit.SECONDS.name()).tag("sca.reactive.rpc.method", method.name())
.tag("sca.reactive.rpc", "url: " + url + " method: " + method.name() + " status: " + response.statusCode())
.register(prometheusMeterRegistry);
qpsCounter.increment();
response.bodyToMono(String.class).doOnNext(System.out::println).subscribe();
}).doOnError(error -> {
});
}
@Override
public ExchangeFilterFunction andThen(ExchangeFilterFunction afterFilter) {
return ExchangeFilterFunction.super.andThen(afterFilter);
}
@Override
public ExchangeFunction apply(ExchangeFunction exchange) {
return ExchangeFilterFunction.super.apply(exchange);
}
}

@ -0,0 +1,60 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.metrics.aop.interceptor;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.Counter;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
public class NacosDiscoveryMetricsRestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Autowired
private PrometheusMeterRegistry prometheusMeterRegistry;
private Counter qpsCounter;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
qpsCounter = Counter.builder("spring.cloud.rpc.restTemplate.qps")
.description("Spring Cloud Alibaba QPS metrics when use resTemplate RPC Call.")
.baseUnit(TimeUnit.SECONDS.name())
.tag("sca.resTemplate.rpc", "url: " + request.getURI()
+ " method: " + request.getMethod()
+ " status: " + response.getStatusCode())
.register(prometheusMeterRegistry);
qpsCounter.increment();
return response;
}
}

@ -8,3 +8,4 @@ com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguratio
com.alibaba.cloud.nacos.loadbalancer.LoadBalancerNacosAutoConfiguration
com.alibaba.cloud.nacos.NacosServiceAutoConfiguration
com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration
com.alibaba.cloud.nacos.metrics.NacosMetricsAutoConfiguration

@ -18,13 +18,17 @@ package com.alibaba.cloud.nacos;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static com.alibaba.cloud.nacos.NacosDiscoveryPropertiesServerAddressBothLevelTests.TestConfig;
@ -42,6 +46,8 @@ public class NacosDiscoveryPropertiesServerAddressBothLevelTests {
@Autowired
private NacosDiscoveryProperties properties;
@Test
public void testGetServerAddr() {
assertThat(properties.getServerAddr()).isEqualTo("321.321.321.321:8848");
@ -53,7 +59,11 @@ public class NacosDiscoveryPropertiesServerAddressBothLevelTests {
NacosDiscoveryClientConfiguration.class,
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
}

@ -18,13 +18,17 @@ package com.alibaba.cloud.nacos;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static com.alibaba.cloud.nacos.NacosDiscoveryPropertiesServerAddressTopLevelTests.TestConfig;
@ -54,6 +58,11 @@ public class NacosDiscoveryPropertiesServerAddressTopLevelTests {
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
}

@ -26,6 +26,8 @@ import java.util.Properties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.nacos.api.NacosFactory;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
@ -34,9 +36,11 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -121,6 +125,11 @@ public class NacosAutoServiceRegistrationIpNetworkInterfaceTests {
NacosDiscoveryClientConfiguration.class,
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
static boolean hasValidNetworkInterface = false;
static String netWorkInterfaceName;

@ -21,6 +21,8 @@ import java.util.Properties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.nacos.api.NacosFactory;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
@ -29,8 +31,10 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -42,9 +46,9 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
*/
@SpringBootTest(classes = NacosAutoServiceRegistrationIpTests.TestConfig.class,
properties = { "spring.application.name=myTestService1",
properties = {"spring.application.name=myTestService1",
"spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848",
"spring.cloud.nacos.discovery.ip=123.123.123.123" },
"spring.cloud.nacos.discovery.ip=123.123.123.123"},
webEnvironment = RANDOM_PORT)
public class NacosAutoServiceRegistrationIpTests {
@ -58,11 +62,13 @@ public class NacosAutoServiceRegistrationIpTests {
private NacosDiscoveryProperties properties;
private static MockedStatic<NacosFactory> nacosFactoryMockedStatic;
static {
nacosFactoryMockedStatic = Mockito.mockStatic(NacosFactory.class);
nacosFactoryMockedStatic.when(() -> NacosFactory.createNamingService((Properties) any()))
.thenReturn(new MockNamingService());
}
@AfterAll
public static void finished() {
if (nacosFactoryMockedStatic != null) {
@ -85,11 +91,15 @@ public class NacosAutoServiceRegistrationIpTests {
@Configuration
@EnableAutoConfiguration
@ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class,
@ImportAutoConfiguration({AutoServiceRegistrationConfiguration.class,
NacosDiscoveryClientConfiguration.class,
NacosServiceRegistryAutoConfiguration.class })
NacosServiceRegistryAutoConfiguration.class})
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
}

@ -21,6 +21,8 @@ import java.util.Properties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.nacos.api.NacosFactory;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
@ -29,8 +31,10 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -87,7 +91,7 @@ public class NacosAutoServiceRegistrationManagementPortTests {
.isEqualTo("8888");
assertThat(
properties.getMetadata().get(NacosRegistration.MANAGEMENT_CONTEXT_PATH))
.isEqualTo("/test-context-path");
.isEqualTo("/test-context-path");
}
@Configuration
@ -97,6 +101,11 @@ public class NacosAutoServiceRegistrationManagementPortTests {
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
}

@ -21,6 +21,8 @@ import java.util.Properties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.nacos.api.NacosFactory;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
@ -29,8 +31,10 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -90,6 +94,11 @@ public class NacosAutoServiceRegistrationPortTests {
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
}

@ -24,6 +24,8 @@ import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpoint;
import com.alibaba.nacos.api.NacosFactory;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
@ -32,10 +34,12 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -212,6 +216,11 @@ public class NacosAutoServiceRegistrationTests {
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
}

@ -21,6 +21,8 @@ import java.util.Properties;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.nacos.api.NacosFactory;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@ -30,6 +32,7 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.context.annotation.Bean;
@ -78,6 +81,12 @@ public class NacosRegistrationCustomizerTest {
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
public NacosRegistrationCustomizer nacosRegistrationCustomizer() {
return registration -> {

@ -27,6 +27,18 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>

@ -16,6 +16,9 @@
package com.alibaba.cloud.seata.web;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.seata.common.util.StringUtils;
import io.seata.core.context.RootContext;
import jakarta.servlet.http.HttpServletRequest;
@ -23,6 +26,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
/**
@ -34,7 +38,14 @@ import org.springframework.web.servlet.HandlerInterceptor;
* And clean up Seata information after servlet method invocation in
* {@link org.springframework.web.servlet.HandlerInterceptor#afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)}
*/
public class SeataHandlerInterceptor implements HandlerInterceptor {
@Autowired
private PrometheusMeterRegistry prometheusMeterRegistry;
private Counter transSumCounter;
private Timer.Sample trancsTimerSample;
private static final Logger log = LoggerFactory
.getLogger(SeataHandlerInterceptor.class);
@ -42,6 +53,9 @@ public class SeataHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
transSumCounter = Counter.builder("spring-cloud.seata.transaction.counter")
.description("Spring Cloud Alibaba Seata global transaction sum.")
.register(prometheusMeterRegistry);
String xid = RootContext.getXID();
String rpcXid = request.getHeader(RootContext.KEY_XID);
if (log.isDebugEnabled()) {
@ -54,13 +68,16 @@ public class SeataHandlerInterceptor implements HandlerInterceptor {
log.debug("bind {} to RootContext", rpcXid);
}
}
transSumCounter.increment();
trancsTimerSample = Timer.start(prometheusMeterRegistry);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception e) {
trancsTimerSample.stop(prometheusMeterRegistry.timer("spring-cloud.seata.transaction.time"));
if (StringUtils.isNotBlank(RootContext.getXID())) {
String rpcXid = request.getHeader(RootContext.KEY_XID);

@ -0,0 +1,45 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.seata.web;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author kwings6
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
public class SeataHandlerInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
InitializingBean forcePrometheusPostProcessor(BeanPostProcessor meterRegistryPostProcessor, PrometheusMeterRegistry registry) {
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(registry, "");
}
@Bean
public SeataHandlerInterceptor seataHandlerInterceptor() {
return new SeataHandlerInterceptor();
}
}

@ -16,6 +16,7 @@
package com.alibaba.cloud.seata.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -26,9 +27,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ConditionalOnWebApplication
public class SeataHandlerInterceptorConfiguration implements WebMvcConfigurer {
@Autowired
SeataHandlerInterceptor seataHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**");
registry.addInterceptor(seataHandlerInterceptor).addPathPatterns("/**");
}
}

@ -1,3 +1,4 @@
com.alibaba.cloud.seata.rest.SeataRestTemplateAutoConfiguration
com.alibaba.cloud.seata.web.SeataHandlerInterceptorConfiguration
com.alibaba.cloud.seata.feign.SeataFeignClientAutoConfiguration
com.alibaba.cloud.seata.web.SeataHandlerInterceptorAutoConfiguration

@ -33,6 +33,12 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>

@ -29,7 +29,12 @@ import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import io.micrometer.core.instrument.Counter;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
@ -42,11 +47,16 @@ import org.springframework.web.client.RestTemplate;
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor {
private static final Logger log = LoggerFactory
.getLogger(SentinelProtectInterceptor.class);
private final SentinelRestTemplate sentinelRestTemplate;
private final RestTemplate restTemplate;
@Autowired
private PrometheusMeterRegistry prometheusMeterRegistry;
public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate,
RestTemplate restTemplate) {
this.sentinelRestTemplate = sentinelRestTemplate;
@ -122,6 +132,9 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor
if (isDegradeFailure(ex)) {
Method fallbackMethod = extractFallbackMethod(sentinelRestTemplate.fallback(),
sentinelRestTemplate.fallbackClass());
Counter counter = Counter.builder("spring.cloud.alibaba.sentinel.degrade.sum")
.register(prometheusMeterRegistry);
counter.increment();
if (fallbackMethod != null) {
return (ClientHttpResponse) methodInvoke(fallbackMethod, args);
}
@ -133,6 +146,9 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor
Method blockHandler = extractBlockHandlerMethod(
sentinelRestTemplate.blockHandler(),
sentinelRestTemplate.blockHandlerClass());
Counter counter = Counter.builder("spring.cloud.alibaba.sentinel.flow.sum")
.register(prometheusMeterRegistry);
counter.increment();
if (blockHandler != null) {
return (ClientHttpResponse) methodInvoke(blockHandler, args);
}

@ -30,6 +30,12 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>

@ -28,6 +28,7 @@ import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindi
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner;
import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.apache.rocketmq.remoting.protocol.NamespaceUtil;
import org.springframework.cloud.stream.binder.AbstractMessageChannelBinder;
@ -65,13 +66,16 @@ public class RocketMQMessageChannelBinder extends
private final RocketMQBinderConfigurationProperties binderConfigurationProperties;
private PrometheusMeterRegistry meterRegistry;
public RocketMQMessageChannelBinder(
RocketMQBinderConfigurationProperties binderConfigurationProperties,
RocketMQExtendedBindingProperties extendedBindingProperties,
RocketMQTopicProvisioner provisioningProvider) {
RocketMQTopicProvisioner provisioningProvider,
PrometheusMeterRegistry meterRegistry) {
super(new String[0], provisioningProvider);
this.extendedBindingProperties = extendedBindingProperties;
this.binderConfigurationProperties = binderConfigurationProperties;
this.meterRegistry = meterRegistry;
}
@Override
@ -86,7 +90,7 @@ public class RocketMQMessageChannelBinder extends
.mergeRocketMQProperties(binderConfigurationProperties,
extendedProducerProperties.getExtension());
RocketMQProducerMessageHandler messageHandler = new RocketMQProducerMessageHandler(
destination, extendedProducerProperties, mqProducerProperties);
destination, extendedProducerProperties, mqProducerProperties, meterRegistry);
messageHandler.setApplicationContext(this.getApplicationContext());
if (errorChannel != null) {
messageHandler.setSendFailureChannel(errorChannel);
@ -131,7 +135,7 @@ public class RocketMQMessageChannelBinder extends
extendedConsumerProperties.getExtension().setGroup(group);
RocketMQInboundChannelAdapter inboundChannelAdapter = new RocketMQInboundChannelAdapter(
destination.getName(), extendedConsumerProperties);
destination.getName(), extendedConsumerProperties, meterRegistry);
ErrorInfrastructure errorInfrastructure = registerErrorInfrastructure(destination,
group, extendedConsumerProperties);
if (extendedConsumerProperties.getMaxAttempts() > 1) {

@ -21,11 +21,16 @@ import com.alibaba.cloud.stream.binder.rocketmq.actuator.RocketMQBinderHealthInd
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -55,9 +60,21 @@ public class RocketMQBinderAutoConfiguration {
@Bean
public RocketMQMessageChannelBinder rocketMQMessageChannelBinder(
RocketMQTopicProvisioner provisioningProvider) {
RocketMQTopicProvisioner provisioningProvider, PrometheusMeterRegistry meterRegistry) {
return new RocketMQMessageChannelBinder(rocketBinderConfigurationProperties,
extendedBindingProperties, provisioningProvider);
extendedBindingProperties, provisioningProvider, meterRegistry);
}
@Bean
@ConditionalOnMissingBean
InitializingBean forcePrometheusPostProcessor(BeanPostProcessor meterRegistryPostProcessor, PrometheusMeterRegistry registry) {
return () -> meterRegistryPostProcessor.postProcessAfterInitialization(registry, "");
}
@Bean
@ConditionalOnMissingBean
public PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Configuration(proxyBeanMethods = false)

@ -21,9 +21,12 @@ import java.util.function.Supplier;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.MessageMeter;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport;
import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils;
import io.micrometer.core.instrument.Counter;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
@ -66,10 +69,13 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport
private final ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties;
private PrometheusMeterRegistry prometheusMeterRegistry;
public RocketMQInboundChannelAdapter(String topic,
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties) {
ExtendedConsumerProperties<RocketMQConsumerProperties> extendedConsumerProperties,
PrometheusMeterRegistry prometheusMeterRegistry) {
this.topic = topic;
this.extendedConsumerProperties = extendedConsumerProperties;
this.prometheusMeterRegistry = prometheusMeterRegistry;
}
@Override
@ -110,30 +116,30 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport
if (extendedConsumerProperties.getExtension().getPush().getOrderly()) {
pushConsumer.registerMessageListener((MessageListenerOrderly) (msgs,
context) -> RocketMQInboundChannelAdapter.this
.consumeMessage(msgs, () -> {
context.setSuspendCurrentQueueTimeMillis(
extendedConsumerProperties.getExtension()
.getPush()
.getSuspendCurrentQueueTimeMillis());
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}, () -> ConsumeOrderlyStatus.SUCCESS));
.consumeMessage(msgs, () -> {
context.setSuspendCurrentQueueTimeMillis(
extendedConsumerProperties.getExtension()
.getPush()
.getSuspendCurrentQueueTimeMillis());
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}, () -> ConsumeOrderlyStatus.SUCCESS));
}
else {
pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs,
context) -> RocketMQInboundChannelAdapter.this
.consumeMessage(msgs, () -> {
context.setDelayLevelWhenNextConsume(
extendedConsumerProperties.getExtension()
.getPush()
.getDelayLevelWhenNextConsume());
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}, () -> ConsumeConcurrentlyStatus.CONSUME_SUCCESS));
.consumeMessage(msgs, () -> {
context.setDelayLevelWhenNextConsume(
extendedConsumerProperties.getExtension()
.getPush()
.getDelayLevelWhenNextConsume());
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}, () -> ConsumeConcurrentlyStatus.CONSUME_SUCCESS));
}
}
catch (Exception e) {
log.error("DefaultMQPushConsumer init failed, Caused by " + e.getMessage());
throw new MessagingException(MessageBuilder.withPayload(
"DefaultMQPushConsumer init failed, Caused by " + e.getMessage())
"DefaultMQPushConsumer init failed, Caused by " + e.getMessage())
.build(), e);
}
}
@ -155,6 +161,8 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport
"DefaultMQPushConsumer consuming failed, Caused by messageExtList is empty");
}
for (MessageExt messageExt : messageExtList) {
Counter counter = MessageMeter.getMQConsumerSumCounter(topic, prometheusMeterRegistry);
counter.increment();
try {
Message<?> message = RocketMQMessageConverterSupport
.convertMessage2Spring(messageExt);
@ -193,7 +201,7 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport
instrumentation.markStartFailed(e);
log.error("DefaultMQPushConsumer init failed, Caused by " + e.getMessage());
throw new MessagingException(MessageBuilder.withPayload(
"DefaultMQPushConsumer init failed, Caused by " + e.getMessage())
"DefaultMQPushConsumer init failed, Caused by " + e.getMessage())
.build(), e);
}
finally {

@ -22,9 +22,12 @@ import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst;
import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager;
import com.alibaba.cloud.stream.binder.rocketmq.metrics.MessageMeter;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties;
import com.alibaba.cloud.stream.binder.rocketmq.provisioning.selector.PartitionMessageQueueSelector;
import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
@ -80,12 +83,15 @@ public class RocketMQProducerMessageHandler extends AbstractMessageHandler
private final RocketMQProducerProperties mqProducerProperties;
private PrometheusMeterRegistry meterRegistry;
public RocketMQProducerMessageHandler(ProducerDestination destination,
ExtendedProducerProperties<RocketMQProducerProperties> extendedProducerProperties,
RocketMQProducerProperties mqProducerProperties) {
RocketMQProducerProperties mqProducerProperties,
PrometheusMeterRegistry meterRegistry) {
this.destination = destination;
this.extendedProducerProperties = extendedProducerProperties;
this.mqProducerProperties = mqProducerProperties;
this.meterRegistry = meterRegistry;
}
@Override
@ -175,6 +181,8 @@ public class RocketMQProducerMessageHandler extends AbstractMessageHandler
}
sendResult = defaultMQProducer.sendMessageInTransaction(mqMessage,
message.getHeaders().get(RocketMQConst.USER_TRANSACTIONAL_ARGS));
DistributionSummary distributionSummary = MessageMeter.getMQProducerSumCounter(destination.getName(), mqProducerProperties, meterRegistry);
distributionSummary.record(1);
}
else {
if (log.isDebugEnabled()) {
@ -182,6 +190,8 @@ public class RocketMQProducerMessageHandler extends AbstractMessageHandler
}
sendResult = this.send(mqMessage, this.messageQueueSelector,
message.getHeaders(), message);
DistributionSummary distributionSummary = MessageMeter.getMQProducerSumCounter(destination.getName(), mqProducerProperties, meterRegistry);
distributionSummary.record(1);
}
if (log.isDebugEnabled()) {
log.debug("the message has sent,message={},sendResult={}", mqMessage,
@ -264,6 +274,8 @@ public class RocketMQProducerMessageHandler extends AbstractMessageHandler
}
private void doFail(Message<?> message, Throwable e) {
DistributionSummary distributionSummary = MessageMeter.getMQProducerFailCounter(mqProducerProperties, meterRegistry);
distributionSummary.record(1);
if (getSendFailureChannel() != null) {
getSendFailureChannel().send(getErrorMessageStrategy().buildErrorMessage(e,
ErrorMessageUtils.getAttributeAccessor(message, message)));

@ -0,0 +1,64 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.stream.binder.rocketmq.metrics;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.prometheus.PrometheusMeterRegistry;
/**
* @author kwings6
*/
public final class MessageMeter {
private MessageMeter() {
}
public static DistributionSummary getMQProducerSumCounter(String destination, RocketMQProducerProperties producerProperties,
PrometheusMeterRegistry prometheusMeterRegistry) {
return DistributionSummary.builder("spring.cloud.alibaba.rocketmq.producer.message.sum")
.description("Spring Cloud Alibaba RocketMQ Producer Message Sum")
.tag("destination", destination)
.tag("nameServer", producerProperties.getGroup())
.tag("group", producerProperties.getGroup())
.tag("producerType", producerProperties.getProducerType())
.tag("sendType", producerProperties.getSendType())
.tag("accessChannel", producerProperties.getAccessChannel())
.register(prometheusMeterRegistry);
}
public static DistributionSummary getMQProducerFailCounter(RocketMQProducerProperties producerProperties,
PrometheusMeterRegistry prometheusMeterRegistry) {
return DistributionSummary.builder("spring.cloud.alibaba.rocketmq.producer.message.fail.sum")
.description("Spring Cloud Alibaba RocketMQ Producer Message Fail Sum")
.tag("nameServer", producerProperties.getGroup())
.tag("group", producerProperties.getGroup())
.tag("producerType", producerProperties.getProducerType())
.tag("sendType", producerProperties.getSendType())
.tag("accessChannel", producerProperties.getAccessChannel())
.register(prometheusMeterRegistry);
}
public static Counter getMQConsumerSumCounter(String topic, PrometheusMeterRegistry prometheusMeterRegistry) {
return Counter.builder("spring.cloud.alibaba.rocketmq.consumer.message.success.sum")
.description("Spring Cloud Alibaba RocketMQ Message Consumer Success Sum")
.tag("topic", topic)
.register(prometheusMeterRegistry);
}
}

@ -21,14 +21,18 @@ import com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.ExtendedBindingH
import com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.RocketMQBinderAutoConfiguration;
import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst;
import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import jakarta.annotation.Resource;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.core.MessageProducer;
@ -94,5 +98,10 @@ public class RocketMQMessageChannelBinderTest {
RocketMQBinderAutoConfiguration.class})
public static class TestConfig {
@Bean
@ConditionalOnMissingBean
PrometheusMeterRegistry prometheusMeterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
}

Loading…
Cancel
Save