一、控制中心
### 默认路径,别动:
server.servlet.contextPath=/nacos
### 端口,看你高兴:
server.port=8848
### Count of DB:
db.num=1
### 数据库
db.url.0=jdbc:mysql://202.173.9.28:3306/nacos2?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=abcd1234
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20
db.pool.config.minimumIdle=2
###开启登录认证,默认密码nacos/nacos,登录进去自己改
nacos.core.auth.enabled=true
conf/cluster.conf
#集群配置 你想搞几个搞几个
202.173.14.69:8848
202.173.14.94:8848
202.173.9.40:8848
bin/startup.sh
访问: http://202.173.9.40:8848/nacos/
二、服务端
<properties>
<spring-cloud.version>2020.0.0spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASEspring-cloud-alibaba.version>
properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<exclusions>
<exclusion>
<artifactId>spring-cloud-starter-netflix-archaiusartifactId>
<groupId>org.springframework.cloudgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>配置
server:
port: 15060
application:
#服务名
name: knet-cloud-domain
cloud:
nacos:
username: nacos
password: abcd1234
discovery:
server-addr: 202.173.14.69:8848,202.173.9.40:8848,202.173.14.94:8848
jar启动方式直接增加注解
@SpringBootApplication
@EnableDiscoveryClient
public class KnetBusinessTaskApplication{}
tomcat需要配置类,否则找不到端口
package cn.knet.wz.conf;
import com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import java.lang.management.ManagementFactory;
import java.util.Set;
@Component
@Slf4j
public class NacosConfig implements ApplicationRunner {
@Autowired(required = false)
private NacosAutoServiceRegistration registration;
@Value("${server.port}")
Integer port;
@Override
public void run(ApplicationArguments args) {
if (registration != null && port != null) {
Integer tomcatPort = port;
try {
tomcatPort = new Integer(getTomcatPort());
} catch (Exception e) {
//e.printStackTrace();
log.info("运行在非WAR环境");
}
registration.setPort(tomcatPort);
registration.start();
}
}
public String getTomcatPort() throws Exception {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
return port;
}
}
服务器启动后,控制台打印
2022-03-31 15:15:57.135 INFO 8464 --- [ main] c.a.c.n.registry.NacosServiceRegistry : nacos registry, DEFAULT_GROUP knet-domain-boss 192.168.90.21:10710 register finished
服务注册成功。
在nacos的服务管理-服务列表中显示该服务
三、客户端
重复服务端1-3的步骤
配置
启动类
@SpringBootApplication
@EnableDiscoveryClient
public class KnetDomainBossApplication {
public static void main(String[] args) {
SpringApplication.run(KnetDomainBossApplication.class, args);
}
}
RestTemplateConfig配置
package cn.knet.boss;
import cn.knet.domain.filter.LoggingClientHttpRequestInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpRequest;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Configuration
public class RestTemplateConfig {
private static final int HTTP_CLIENT_RETRY_COUNT = 0;
private static final int MAXIMUM_TOTAL_CONNECTION = 10;
private static final int MAXIMUM_CONNECTION_PER_ROUTE = 5;
private static final int CONNECTION_VALIDATE_AFTER_INACTIVITY_MS = 10 * 1000;
/**
* @param connectionTimeoutMs milliseconds/毫秒
* @param readTimeoutMs milliseconds/毫秒
* @return
*/
public static RestTemplate createRestTemplate(int connectionTimeoutMs, int readTimeoutMs, ObjectMapper objectMapper) {
HttpClientBuilder clientBuilder = HttpClients.custom();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
// 整个连接池最大连接数
connectionManager.setMaxTotal(MAXIMUM_TOTAL_CONNECTION);
// 设置每个路由的最大并发连接数,默认为2.
connectionManager.setDefaultMaxPerRoute(MAXIMUM_CONNECTION_PER_ROUTE);
// 官方推荐使用检查永久链接的可用性,而不推荐每次请求的时候才去检查 (milliseconds 毫秒)
connectionManager.setValidateAfterInactivity(CONNECTION_VALIDATE_AFTER_INACTIVITY_MS);
clientBuilder.setConnectionManager(connectionManager);
//设置重连操作次数,这里设置了3次
clientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(HTTP_CLIENT_RETRY_COUNT, true, new ArrayList<>()) {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
HttpRequestWrapper httpRequestWrapper = (HttpRequestWrapper) context.getAttribute("http.request");
HttpRequest original = httpRequestWrapper.getOriginal();
log.info("Retry request, execution count:{}, exception:{}, request URL:{}", executionCount, exception.getMessage(), original);
return super.retryRequest(exception, executionCount, context);
}
});
//
// //使用httpClient创建一个ClientHttpRequestFactory的实现
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(clientBuilder.build());
httpRequestFactory.setConnectTimeout(connectionTimeoutMs);
httpRequestFactory.setConnectionRequestTimeout(readTimeoutMs);
httpRequestFactory.setReadTimeout(readTimeoutMs);
RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
// 添加自定义拦截器
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new LoggingClientHttpRequestInterceptor());
restTemplate.setInterceptors(interceptors);
//提供对传出/传入流的缓冲,可以让响应body多次读取(如果不配置,拦截器读取了Response流,再响应数据时会返回body=null)
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory));
//防止响应中文乱码
restTemplate.getMessageConverters().stream().filter(StringHttpMessageConverter.class::isInstance).map(StringHttpMessageConverter.class::cast).forEach(a -> {
a.setWriteAcceptCharset(false);
a.setDefaultCharset(StandardCharsets.UTF_8);
});
return restTemplate;
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = RestTemplateConfig.createRestTemplate(120000, 120000, new ObjectMapper());
//配置自定义的interceptor拦截器
//使用restTemplate远程调用防止400和401导致报错而获取不到正确反馈信息
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
super.handleError(response);
}
}
});
return restTemplate;
}
}
主要是对RestTemplate进行配置和增加日志监控,也可以简化为在启动类中增加:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
日志监控拦截器:
package cn.knet.domain.filter;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
@Slf4j
public class LoggingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
int i = 0;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
long l = System.currentTimeMillis();
ClientHttpResponse response = execution.execute(request, body);
long time = System.currentTimeMillis() - l;
try {
String className = Thread.currentThread().getStackTrace()[9].getClassName();//调用的类名
String methodName = Thread.currentThread().getStackTrace()[9].getMethodName();//调用的方法名
int lineNumber = Thread.currentThread().getStackTrace()[9].getLineNumber();
String bodystr = new String(body, StandardCharsets.UTF_8);
StringBuilder inputStringBuilder = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
inputStringBuilder.append('\n');
line = bufferedReader.readLine();
}
}
String logstr = "\n\n===========================请求开始:================================================" +
"\n= Service : {}" +
"\n= URI : {}" +
"\n= Request body : {}" +
"\n= Method : {}" +
"\n= Status code : {}" +
"\n= Times : {}" +
"\n= Response body: {}" +
"===========================请求结束:================================================\n\n";
log.info(logstr, className + "." + methodName + ":" + lineNumber, request.getURI(), getJsonStrByQueryUrl(bodystr),
request.getMethod(), response.getStatusCode(), time, inputStringBuilder.toString());
} catch (Exception e) {
log.info("请求参数处理异常:" + e.getMessage() + ",URI:" + request.getURI());
}
return response;
}
public String getJsonStrByQueryUrl(String paramStr) {
String[] params = paramStr.split("&");
JSONObject obj = new JSONObject();
for (int i = 0; i < params.length; i++) {
String[] param = params[i].split("=");
if (param.length >= 2) {
String key = param[0];
String value = param[1];
for (int j = 2; j < param.length; j++) {
value += "=" + param[j];
}
try {
obj.put(key, URLDecoder.decode(value,"UTF-8"));
} catch (JSONException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
return obj.toString();
}
}
@Autowired
private RestTemplate restTemplate;
MultiValueMap<String, Object> parm = new LinkedMultiValueMap<>();
parm.add("from","[email protected]");
parm.add("to", mail);
parm.add("subject", ".网址-商标通知");
parm.add("ftl", "tmch.ftl");
parm.add("jsonMap", map);
Map<String, Object> result = restTemplate.postForObject("http://knet-cloud-mail/mail/send/ftlMap", parm, Map.class);
请求地址为:http://服务名/接口地址
# false为永久实例,true表示临时实例
spring.cloud.nacos.discovery.ephemeral=false
临时实例向Nacos注册,Nacos不会对其进行持久化存储,只能通过心跳方式保活。默认模式是:客户端心跳上报Nacos实例健康状态,默认间隔5秒,Nacos在15秒内未收到该实例的心跳,则会设置为不健康状态,超过30秒则将实例删除。
持久化实例向Nacos注册,Nacos会对其进行持久化处理。当该实例不存在时,Nacos只会将其健康状态设置为不健康,但并不会对将其从服务端删除。
另外,可以使用实例的ephemeral来判断健康检查模式,ephemeral为true对应的是client模式(客户端心跳),为false对应的是server模式(服务端检查)。
临时实例的问题
一旦服务端出现超过30秒的异常会被删除,就算服务端恢复正常也不能使用,需重启服务端才能恢复,建议正式环境使用持久化实例,开发者使用临时实例。