Springcloud项目接入nacos

一、控制中心

  1. 下载
    https://github.com/alibaba/nacos/releases/download/2.0.4/nacos-server-2.0.4.zip
    2. 配置
    conf/application.properties
### 默认路径,别动:
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
  1. 启动

bin/startup.sh

访问: http://202.173.9.40:8848/nacos/

Springcloud项目接入nacos_第1张图片

二、服务端

  1. pom
  <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>配置
  1. yml
  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
  1. 配置

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;
   }
 }
  1. 启动

服务器启动后,控制台打印

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. 重复服务端1-3的步骤

  2. 配置

    启动类

@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();
    }
}
  1. 访问服务端
   @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://服务名/接口地址

  1. 临时实例与持久化实例
# 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秒的异常会被删除,就算服务端恢复正常也不能使用,需重启服务端才能恢复,建议正式环境使用持久化实例,开发者使用临时实例。

你可能感兴趣的:(笔记,技术相关,spring,cloud,java,微服务)