一、背景及目的
springBoot要做成微服务,有很多解决方案,这里采用nacos与restTemplate方式,nacos作为springBoot的注册与发现中心,restTemplate作为具体的调用代理,这样可以快速的组合一个微服务系统。
二、实验环境及设计思路
springBoot 2.1.1 + jdk1.8 + nacos2.3.2
Nacos Server 下载 | Nacos 官网,安装也请看官网,非常简单。
nacos集成springBoot参考:
Nacos 融合 Spring Cloud,成为注册配置中心 | Nacos
设计思路简图:
本地启动两个echoService服务提供者 ,端口号分别是8081和8082,向nacos注册。
本地启用1个服务调用者springBoot1,端口号为8080,向nacos注册,采用RestTemplate方式,并实现负载均衡的效果。
三、相关配置及代码
基本工程结构;
1、服务提供方echoService配置及代码
echoService 工程 pom.xml
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.spell</groupId>
<artifactId>echoServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>echoServer</name>
<description>echoServer</description>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<skipTests>true</skipTests>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
</project>
echoService的application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application.properties配置
server.port=8081
spring.application.name=echoService
server.servlet.context-path=/
server.tomcat.uri-encoding=UTF-8
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
logging.level.root=WARN
logging.level.org.springframework.web=DEBUG
logging.level.com.spell=INFO
#logging.level.com.example.yourpackage=INFO
logging.file=./log/echoService.log
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
echoService的EchoController
package com.spell.echoServer.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EchoController {
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) {
System.out.println("--receive request,name=" + string);
return "Hello " + string;
}
}
echoService的Appliation.java, 这里加入了@EnableDiscoveryClient 注解
package com.spell;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = { "com.spell.*" })
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
System.out.println("====start up====");
}
}
2、调用方springBoot的配置
pom.xml与echoService类似,新增加一个httpclient的依赖
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.spell</groupId>
<artifactId>echoServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>echoServer</name>
<description>echoServer</description>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<skipTests>true</skipTests>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>
</project>
application.properties配置
server.port=8080
spring.application.name=sprintBoot1
server.servlet.context-path=/
server.tomcat.uri-encoding=UTF-8
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
logging.level.root=WARN
logging.level.org.springframework.web=DEBUG
logging.level.com.spell=INFO
#logging.level.com.example.yourpackage=INFO
logging.file=./log/springboot.log
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
application.yaml配置
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
http:
maxTotal: 50 #最大连接数
defaultMaxPerRoute: 5 #并发数
connectTimeout: 1000 #创建连接的最长时间
connectionRequestTimeout: 500 #从连接池中获取到连接的最长时间
socketTimeout: 10000 #数据传输的最长时间
staleConnectionCheckEnabled: true #提交请求前测试连接是否可用
validateAfterInactivity: 3000000 #可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过,释放socket重新建立
config/RestTemplatetConfig.java 用于配置httpClient线程池及LoadBlanced能力。
package com.spell.config;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplatetConfig {
@Value("${http.maxTotal}")
private Integer maxTotal;
@Value("${http.defaultMaxPerRoute}")
private Integer defaultMaxPerRoute;
@Value("${http.connectTimeout}")
private Integer connectTimeout;
@Value("${http.connectionRequestTimeout}")
private Integer connectionRequestTimeout;
@Value("${http.socketTimeout}")
private Integer socketTimeout;
@Value("${http.staleConnectionCheckEnabled}")
private boolean staleConnectionCheckEnabled;
@Value("${http.validateAfterInactivity}")
private Integer validateAfterInactivity;
@Bean
@LoadBalanced //增加负载均衡的能力
public RestTemplate restTemplate() {
return new RestTemplate(httpRequestFactory());
}
@Bean
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
@Bean
public HttpClient httpClient() {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory()).build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(maxTotal); // 最大连接数
connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); // 单个路由最大连接数
connectionManager.setValidateAfterInactivity(validateAfterInactivity); // 最大空间时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout) // 服务器返回数据(response)的时间,超过抛出read
// timeout
.setConnectTimeout(connectTimeout) // 连接上服务器(握手成功)的时间,超出抛出connect timeout
.setStaleConnectionCheckEnabled(staleConnectionCheckEnabled) // 提交前检测是否可用
.setConnectionRequestTimeout(connectionRequestTimeout)// 从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException:
// Timeout waiting for connection from pool
.build();
// headers
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));
return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager)
.setDefaultHeaders(headers)
// 保持长连接配置,需要在头添加Keep-Alive
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
// 重试次数,默认是3次,没有开启
.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true)).build();
}
}
EchoController.java,采用restTemplate来调用远程服务,注意访问的URL地址为服务注册名http://echoService/echo,这个具体在底层执行访问的时候,会替换为轮询的具体服务提供方的ip端口与地址。
package com.spell.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class EchoController {
private final RestTemplate restTemplate;
@Autowired
public EchoController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
public String echo(@PathVariable String str) {
return restTemplate.getForObject("http://echoService/echo/" + str, String.class);
}
}
Application.java
package com.spell;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = { "com.spell.*" })
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
System.out.println("====start up====");
}
}
四、测试结果
echoService的server.port分别为8081和8082各启动一次。
springBoot1工程server.port为8080启动一次,可以看到nacos的注册的服务:
访问:http://localhost:8080/echo/jack1,可以得到预期输出
可以看到echoService的具体输出,这样就完成了微服务基本的RPC调用及负载均衡能力了。
五、结束语
SpringBoot这套微服务集成框架,还是非常方便的,如果性能要求不是特别高,可以采用这个框架,如果对性能的要求很高,建议用springboot + dubbo也是个不错的选择,不过最终还是要以压测对比为准。
欢迎大家点赞、收藏、订阅,你们的反馈是我持续输出的动力。