项目代码: https://gitee.com/xuhx615/MyWFWDemo.git
在国内最知名的微服务技术框架就是SpringCloud
和阿里巴巴的Dubbo
。
微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
SpringCloudAlibaba
兼容前两种框架技术,通常我们使用SpringCloudAlibaba
SpringCloud
是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。SpringCloud
集成了各种微服务功能组件,并基于SpringBoot
实现了这些组件的自动装配,从而提供了良好的开箱即用体验:eureka
注册自己的信息eureka
保存这些信息eureka
拉取提供者信息EurekaServer
发送心跳请求,报告健康状态eureka
会更新记录服务列表信息,心跳不正常会被剔除在Eureka架构中,微服务角色有两类:
EurekaServer
:服务端,注册中心
EurekaClient
:客户端
Provider
:服务提供者
EurekaServer
30
秒向EurekaServer
发送心跳consumer
:服务消费者
EurekaServer
拉取服务列表 <parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.9.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.SR10spring-cloud.version>
<mysql.version>8.0.16mysql.version>
<mybatis.version>2.1.1mybatis.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.version}version>
dependency>
dependencies>
dependencyManagement>
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
配置文件
server.port=10086
#服务注册需要的信息(注意eureka本身也是需要注册的,所以他也需要配置下面的注册信息)
#eureka服务名称(便于注册到eureka注册中心)
spring.application.name=eurekaserver
#需要注册到eureka服务集群的地址(eureka集群地址,多个用逗号隔开,除了ip和端口是自己配置的,其他是死的)
eureka.client.service-url.defaultZone=http://127.0.0.1:10086/eureka
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer //开启eureka注册服务
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
配置文件
server.port=8082
server.servlet.context-path=/orderService
#服务注册需要的信息
#eureka客户端的服务名称
spring.application.name=orderservice
#需要注册到eureka服务集群的地址
eureka.client.service-url.defaultZone=http://127.0.0.1:10086/eureka
启动类
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
访问demo
import cn.wfw.xuhx.util.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
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
@CrossOrigin(origins = "*",maxAge = 3600)
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/queryUser" ,produces = "application/json;charset=utf-8",method = RequestMethod.GET)
public Result queryUser(){
//注意:通过eureka注册中心去访问另外一个服务器,这里就不能写另外一个服务器的ip和端口,需要写另外一个服务器在eureka注册中心注册的服务名称
return restTemplate.getForObject("http://userservice/userService/user/queryUser", Result.class);
}
}
Nacos
是阿里巴巴的产品,现在是SpringCloud
中的一个组件。相比Eureka
功能更加丰富,在国内受欢迎程度较高
nacos下载地址,注意当前1版本是稳定版,2版本的还处于测试阶段
nacos
开箱即用,前往bin
目录输入命令./startup.cmd -m standalone
启动nacos
在浏览器输入:http://127.0.0.1:8848/nacos/index.html
即可,默认用户密码是nacos/nacos
naos
与eureka
都遵循Spring cloud common
的接口规范,所以只需引入对应的依赖和修改对应的配置即可,其他代码都不需要动。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.5.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
配置文件只需修改一处,注册中心换成nacos
即可
spring.cloud.nacos.server-addr=http://127.0.0.1:8848
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
Ribbon
的负载均衡规则是一个叫做IRule
的接口来定义的,每一个子接口都是一种规则:
Ribbon
的负载均衡策略大概分两种:轮循(RoundRobinRule
)和随机(RandomRule
)。
默认策略是ZoneAvoidanceRule
:按照zone
对服务器进行分组,按组进行轮循。
具体策略释义如下:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule |
简单轮询服务列表来选择服务器。它是Ribbon 默认的负载均衡规则。 |
AvailabilityFilteringRule |
对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule 规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的 属性进行配置。 |
WeightedResponseTimeRule |
为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule |
以区域可用的服务器为基础进行服务器的选择。使用Zone 对服务器进行分类,这个Zone 可以理解为一个机房、一个机架等。而后再对Zone 内的多个服务做轮询。 |
BestAvailableRule |
忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule |
随机选择一个可用的服务器。 |
RetryRule |
重试机制的选择逻辑 |
@Bean
public IRule randomRule(){
return new RandomRule();
}
application.properties
中)userservice.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
Ribbon
默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient
,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon.eager-load.enabled=true
ribbon.eager-load.clients=userservice
# 配置服务集群分组属性
spring.cloud.nacos.discovery.cluster-name=HZ
# 配置服务集群分组属性
userservice.ribbon.NFLoadBalancerRuleClassName=com.alibaba.cloud.nacos.ribbon.NacosRule
nacosRule负载均衡的策略:
根据部署的不同服务器的优劣,可以使用加权负载均衡,来控制它的访问权重,这个是nacos
可视化配置的,非常方便,权重大小介于0~1
之间,如下图所示:
命名空间的作用就是隔离代码环境,即处于不同命名空间之间的服务是不能借助注册中心通信的。
将生成的命名空间ID配置到需要放置到该命名空间下的服务中去
spring.cloud.nacos.discovery.namespace=2728a8a7-f915-45bc-8196-2c527679ecc8
服务注册到nacos
中的实例默认都是临时实例。
临时实例和非临时实例的差别:
nacos
的服务列表中剔除,而非临时实例则不会。nacos
发送心跳,当nacos
一段时间收不到心跳,就会认为该服务实例不健康,将其剔除出服务列表nacos
发送心跳,而是由nacos
注册中心定时向非临时实例发送请求询问其是否健康,当nacos
认为该服务实例不健康时,nacos
不会剔除该实例,而是等待它重新启动起来配置服务实例为非临时实例:
spring.cloud.nacos.discovery.ephemeral=false
注意: 一般我们不会选择非临时实例,因为这样对于nacos
注册中心的服务压力就变大了。
Nacos
与eureka
的共同点
Nacos
与eureka
的区别
Nacos
支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式Nacos
支持服务列表变更的消息推送模式,服务列表更新更及时Naco
s集群默认采用AP
方式,当集群中存在非临时实例时,采用CP
模式;eureka
采用AP
方式在nacos
可视化页面添加动态配置文件,文件名称格式:服务名称-名称空间.properties
或者服务名称-名称空间.yaml
在需要引入动态配置的项目中引入nacos
动态配置的依赖,并添加bootstrap.properties
配置文件
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
#这五个决定了去哪里读取动态配置文件
#1. 服务注册需要的信息
#eureka客户端的服务名称
spring.application.name=orderservice
#2. 动态配置文件的名称空间名称
spring.profiles.active=dev
#3. 动态配置文件的后缀
spring.cloud.nacos.config.file-extension=properties
#4. 动态配置文件的配置文件所在的名称空间ID(public可以省略,可以与项目不在一个名称空间)
spring.cloud.nacos.config.namespace=2728a8a7-f915-45bc-8196-2c527679ecc8
#5. 需要注册到nacos服务集群的地址
spring.cloud.nacos.server-addr=http://127.0.0.1:8848
注意: bootstrap.properties
只是比application.properties
加载的优先级更高,所以有些配置在bootstrap.properties
里面配置完成后就不需要在application.properties
重复配置
测试
//创建与之对应的配置类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
//ConfigurationProperties注解可以注入配置文件属性值,只需前缀与该类下的属性值相结合是配置文件的key即可,并且可以实现实时感知,比@Value注入要强大
@ConfigurationProperties(prefix = "message")
public class MessageConfig {
private Boolean switchStatus;
public Boolean getSwitchStatus() {
return switchStatus;
}
public void setSwitchStatus(Boolean switchStatus) {
this.switchStatus = switchStatus;
}
}
使用feignClient
代替restTemplate
原因: restTemplate
存在下列问题:
URL
难以维护引入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
在启动类使用注解@EnableFeignClients
开启feignClient
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients //开启feignClient
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
创建client
接口映射客户端服务接口
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("userservice") //服务实例名称
public interface UserServiceClient {
//路径,请求方式
@RequestMapping(value = "/userService/user/queryUser",produces = "application/json;charset=utf-8",method = RequestMethod.GET)
Result queryUser ();
}
注意: feignClient
已经自动装配了Ribbon
负载均衡策略,所以关于负载均衡方面的配置无需修改。
URLConnection
:默认实现,不支持连接池Apache HttpClient
:支持连接池OKHttp
:支持连接池Feign
的日志级别主要有四种:NONE、BASIC、HEADERS、FULL
URLConnection
basic
或none
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
# 设置feign的日志级别
feign.client.config.default.loggerLevel=BASIC
# 设置feign客户端底层实现是支持连接池的httpclient
feign.httpclient.enabled=true
# 设置连接池的最大连接数
feign.httpclient.max-connections=200
# 设置一个连接的最大连接数
feign.httpclient.max-connections-per-route=50
网关的作用:
注入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
配置文件
server.port=10010
spring.application.name=gateway
spring.cloud.nacos.server-addr=http://127.0.0.1:8848
#路由ID,必须唯一
spring.cloud.gateway.routes[0].id=userservice
#路由目标地址,可以是真实的ip + port(ttp://ip:port),也可以是注册在注册中心的实例名称(lb://实例名称),lb就是负载均衡
spring.cloud.gateway.routes[0].uri=lb://userservice
#路由规则,设置路由规则
spring.cloud.gateway.routes[0].predicates[0]= Path=/userService/**
注意: 网关服务的实例也需要注册到注册中心
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
GatewayFilter
是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
过滤器工厂 | 功能 |
---|---|
RemoveRequestHeader |
移除请求中的一个请求头 |
AddResponseHeader |
给响应结果中添加一个响应头 |
RemoveResponseHeader |
从响应结果中移除有一个响应头 |
RequestRateLimiter |
限制请求的流量 |
AddRequestHeader |
添加请求头 |
AddRequestParameter |
添加请求参数 |
#局部过滤器
spring.cloud.gateway.routes[0].filters[0]= AddRequestHeader=Truth,Hello Spring Cloud!
#添加全局过滤器
spring.cloud.gateway.default-filters[0]= AddRequestHeader=Truth,Hello Spring Cloud!
有些过滤业务并不简单,需要一定的逻辑代码才能实现,则gateway
提供的过滤工厂已不满足要求,可以使用自定义过滤器实现。
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定义过滤器,需要实现接口GlobalFilter
*/
@Order(-1) //过滤器执行优先级,值越小优先级越高
@Component
public class LoginFileter implements GlobalFilter {
/**
*
* @param exchange 可以获取到所有请求信息
* @param chain 放行用的
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
String user = queryParams.getFirst("user");
if("admin".equals(user)){
//放行
return chain.filter(exchange);
}
//拦截
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
注意: 自定义过滤器就是全局过滤器
int
类型的order
值,order
值越小,优先级越高,执行顺序越靠前。GlobalFilter
通过实现Ordered
接口,或者添加@Order
注解来指定order
值,由我们自己指定defaultFilter
的order
由Spring
指定,默认是按照声明顺序从1递增。order
值一样时,会按照 defaultFilter
> 路由过滤器 > GlobalFilter
的顺序执行。跨域:域名不一致就是跨域,主要包括:
www.taobao.com
和 www.taobao.org
和 www.jd.com
和 miaosha.jd.com
localhost:8080
和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
gateway解决跨域的配置:
#跨域解决
#解决options请求被拦截的问题 (options请求就是浏览器向服务器询问该请求是否可以跨域)
spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true
#允许哪些网站的跨域请求
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-origins[0]=http://localhost:8849
#允许的跨域ajax的请求方式
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-methods[0]=GET
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-methods[1]=POST
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-methods[2]=DELETE
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-methods[3]=PUT
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-methods[4]=OPTIONS
#允许在请求中携带的头信息
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allowed-headers[0]=*
#是否允许携带cookie
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.allow-credentials=true
#这次跨域检测的有效期(这个值设置的越长,对系统的性能提高越好)
spring.cloud.gateway.globalcors.cors-configurations.'[/**]'.max-age=360000
Docker具体使用参考文档
这里我们采用Docker-Compose
去部署微服务集群
步骤分析:
docker-compose.yml
文件#版本
version: "3.2"
#服务
services:
nacos: #nacos服务
image: nacos/nacos-server
environment:
MODE: standalone #启动模式
ports:
- "8848:8848" #暴露端口
userservice: #userservice服务
build: ./user-service
orderservice: #orderservice服务
build: ./order-service
gateway: #网关
build: ./gateway
ports:
- "10010:10010" #暴露端口
Dockerfile
文件 FROM java:8-alpine
COPY ./app.jar /tmp/app.jar
ENTRYPOINT java -jar /tmp/app.jar
#我们只需把ip改成集群部署对应的服务名即可,Docker-Compose会帮我们找到对应的容器
spring.cloud.nacos.server-addr=http://nacos:8848
<build>
<finalName>appfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
docker-compose.yml
所在目录下输入以下命令完成集群部署#1. 部署集群
[root@centos100 cloud-demo]# docker-compose up -d
#2. 查看集群是否启动
[root@centos100 cloud-demo]# docker ps
#3. 查看集群日志
[root@centos100 cloud-demo]# docker-compose logs -f
#4. 如若发现有集群连不上nacos,查看集群启动顺序,如果nacos不是第一个启动,则会有问题,那么我们把剩下集群重新restart一下
[root@centos100 cloud-demo]# docker-compose restart gateway userservice orderservice