SpringCloud是一个微服务框架,集成阞各种微服务功能组件,并基于SpringBoot实现了几大组件的自动装配,从而提供了良好的开箱即用。
从之前的单体项目,根据功能和业务拆分成过个多个微服务。
服务拆分要注意几个事项
1.不同微服务,不可重复开发相同业务
2.微服务数据独立,不要访问其他微服务的数据库
3.微服务可以将自己的业务暴露为接口,供其他微服务调用
服务调用关系:
服务提供者:暴露接口给其他微服务调用
服务消费者:调用其他微服务提供的接口
提供者与消费者的角色是相对的,提供者可以是消费者,消费者也可以是提供者
EureKa的作用
1.记录微服务的信息,并提供给eureka客户端(微服务)要拉取的提供者信息。
2.结合负载均衡,给消费者提供一个合适的服务者
3.感知服务者的状态,检查监控状态
EureKa角色关系
在EureKa架构中,微服务角色有两类:
一、搭建EureKaServer
1.创建独立的EureKaServer项目,引入spring-cloud-start-netflix-eureka-server依赖
2.编写启动类,添加@EnableEureKaServer注解
二、服务注册
步骤如下:
1.在user-service项目中引入spring-cloud-start-netflix-eureka-client的依赖
三、服务发现(拉取)
服务器拉取是基于服务器名称获取服务器列表
然后在对服务器列表做负载均衡
步骤:
1.修改OrderService的代码,修改访问路径,用服务名代替ip、端口
2.在order-server项目的启动类OrderApplication中的RestTemplate添加负载均衡注解@LoadBalanced
对于之前的配置,我们只做了EureKa的服务器注册和客户端注册,之后就自动进行了拉取和负载均衡等操作,那么这一些列动作是谁完成的呢?是怎么完成呢?流程是什么呢?接下来我们一起了解SpringCloud核心组件之一Ribbon
1.当微服务发起请求时,会被LoadBalancerInterceptor
负载均衡拦截器拦截,并获取请求名称
2.把获取到的请求名称交给RibbonLoadBanlancerClient
,接着这个RibbonLoadBanlancerClient
会把获取的url中的服务ID交给DynamicServerListLoadBalancer
3.EureKa从DynamicServerListLoadBalancer
中拉取userservice,然后得到多个服务。
4.DynamicServerListLoadBalancer
会使用IRule做负载均衡(轮询),选择一个服务器返回给LoadBalancerInterceptor
5.接着 RibbonLoadBanlancerClient
修改url,发起向服务器请求
Ribbon的负载均衡规则是是一个叫IRule的接口来定义的,每一个子接口都是有着不同的规则
负载均衡常见策略
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobbinRule | 简单轮询列表来选择服务器。它是Ribbon默认的负载均衡规则 |
AvailableilityFilteringRule | 对一下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间会几何级的增加 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置AvailablityFilteringRule规则的客户端也会将其忽略。并发连接数的上线。可以由客户端的..ActiveConnetctionsLimit属性进行配置 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重会印象服务器的选择 |
ZoneAviodanceRule | 以区域(Zone)可用的服务器为基础的选择,使用Zone对服务器进行分类,这个Zone可以理解为一个机房,一个机架等。而后再对Zone内多个服务做轮询 |
BestAvailableRule | 忽略哪些短路的服务器,并进选择并发数较低的服务器 |
RandomRule | 随机选择一个可用的服务器 |
RetryRule | 重试机制的选择逻辑 |
调整负载均衡策略
通过定义IRule实现可以修改负载均衡规则,有两种:
(1)代码方式:在order-servce中的OrderApplication类中,定义一个新的IRule
这种是针对于全体的。即order-servce访问任何服务都是随机的
上面这个轮询机制就被修改成了随机选择了。
(2)配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
这种是针对一个的,指定哪一个微服务的访问方式
Ribbon默认是采用的懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问时的耗时,通过下面配置开启饥饿加载:
Nacos是阿里巴巴的产品。现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内比较受欢迎
(1) Windows安装
下载
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
如图:
解压
下载好后进行解压(在一个没有中文的目录下解压)
启动
单机启动
startup.cmd -m standalone
访问
1.在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2.注释掉order-service和user-service中原有的eureka依赖
3.添加nacos的客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
4.修改user-service&order-service的application.yml文件,注释eurka地址,添加nacos地址:
可以理解为将不同的实例(微服务)化分为一个区域(集群)
服务跨集群调用问题
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其他集群
服务集群属性
1.修改application.yml,设置集群为HZ,添加如下内容
2.然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:
NacosRule
一般会优先访问本地集群服务
总结:
**实例的权重越大,就越会被优先访问到**
总结:
1.namespace
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
1.nacos的命名空间可以在nacos控制台上面进行设置和修改
2.修改order-service的application.yml,添加namespace:
3.回到nacos控制中心,我们可以看到orderservice的命名空间已经变化了
在一些大型的项目,一个项目可能有很多微服务,如果我们需要修改配置微服务的话,可能需要逐个修改,并再重启。可想而知,这样的操作是繁琐的,不利于生产的。
那么接下来我们可以进行统一配置
步骤:
1.引入nacos的配置管理客户端依赖
<!--nacos的配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是一个引导文件,优先级高于application.yml:
spring:
application:
name: userservice
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
discovery:
cluster-name: HZ
config:
file-extension: yaml # 文件后缀名
总结:
Nacos中的配置文件变更后,微服务无需重启就可以感知。不需要通过下面两种配置实现:
总结:
配置的优先级
总结:
搭建集群的基本步骤:
1.搭建数据库
这里由于是个人电脑,所以就不搭建MySQL集群了
SQL,nacos有提供,可以直接使用nacos提供的SQL
2.下载nacos安装包,略
3.配置nacos
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
然后添加内容:
127.0.0.1:8846
127.0.0.1.8847
127.0.0.1.8848
然后修改application.properties文件,添加数据库配置
spring.datasource.platform=mysql
db.num=1
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
db.user.0=root
db.password.0=xxxxxx
修改每个nacos端口
4.启动nacos集群
分别启动三个并访问
5.Nginx反向代理
在Nginx服务器中配置如下信息
#nacos集群
upstream nacos-cluster {
server 127.0.0.1:8846;
server 127.0.0.1:8847;
server 127.0.0.1:8848;
}
server {
listen 90;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
接着修改服务中的之前的端口号,统一修改成90
cloud:
nacos:
server-addr: localhost:90 # nacos服务地址
discovery:
cluster-name: SH
namespace: 4bcd9908-987a-4d32-aa39-b60f9b0c08ef # dev环境
ephemeral: false # 是否是临时实例
然后启动Nginx,访问http://localhost:90/nacos
在nacos中,有临时实例和非临时实例,对于这两个实例,nacos有不同的对待策略,对于临时实例,nacos会采用心跳检测来查看服务是否健康,对于非临时实例,nacos则会主动询问服务是否健康。同时,对于临时服务实例产生宕机超过一定时间,则会把该宕机的服务从服务列表中剔除,而非临时实例则不会
配置临时实例和非临时实例
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: SH
namespace: 4bcd9908-987a-4d32-aa39-b60f9b0c08ef # dev环境
ephemeral: false # 是否是临时实例
以前我们通过RestTemplate调用远程服务,但是调用方式和写法不太优雅,维护性比较差
因此,我们使用Feign来进行远程调用
2.在order-service的启动类添加注解开启Feign的功能
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {
@FeignClient(value = "userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
修改后的远程调用代码:
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
//1.查询订单
Order order = orderMapper.findById(orderId);
// //2.利用RestTemplate发起http请求,查询用户
// //2.1 url路径
// String url = "http://localhost:8081/user/"+order.getUserId();
// //2.2 发送http请求,实现远程调用:get请求
// User user = restTemplate.getForObject(url, User.class);
User user = userClient.findById(order.getUserId());
//3.封装user信息
order.setUser(user);
//4.返回
// 根据id查询订单并返回
return order;
}
Feign主要是基于SpringMVC的注解来声明远程调用的信息,比如:
服务名称:userservice
请求方式:GET
请求路径:/user/{id}
请求参数:Long id
返回值类型:User
配置Feign日志有两种方式
方式一:配置文件
方式二:代码配置
URLConnection:性能较差,每次连接都会造成大量资源浪费
性能优化的主要方向:
略
方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准
方法二(抽取):将FeignClient抽取为独立模块,并把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
步骤:
1.创建一个module,命名为feign-api,然后引入feign的start依赖
2.将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
3.在order-service中引入feign-api的依赖
4.修改order-service中所有与上述三个组件有关的import部分,改成导入feign-api中的包
1.创建一个module,命名为feign-api,然后引入feign的start依赖
2.将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
3.在order-service中引入feign-api的依赖
4.修改order-service中所有与上述三个组件有关的import部分,改成导入feign-api中的包
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
方式一:指定FeignClient所在包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:指定FeignClients字节码
@EnableFeignClients(clients={UserClient.class})
网关功能
在springcloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway者是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能
1.新建module。引入SpringCloudGateway的依赖和nacos的服务发现依赖
<!--nacos服务注册发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2.编写路由配置以及nacos地址
server:
port: 10010
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:90 # nacos地址
gateway:
routes:
- id: user-service # 路由标示,必须唯一
uri: lb://userservice # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awesome!
网关路由可以配置的内容包括:
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/** 是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
案例,给进入所有微服务添加一个请求头
gateway:
routes:
- id: user-service # 路由标示,必须唯一
uri: lb://userservice # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awesome!
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。二GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GolbalFilter接口。
实现代码:
// @Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2.获取参数中的 authorization 参数
String auth = params.getFirst("authorization");
// 3.判断参数值是否等于 admin
if ("admin".equals(auth)) {
// 4.是,放行
return chain.filter(exchange);
}
// 5.否,拦截
// 5.1.设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2.拦截请求
return exchange.getResponse().setComplete();
}
//执行顺序,值越小越先执行 ,等同于@Order(-1)注解
@Override
public int getOrder() {
return -1;
}
}
注:
在上面代码中、@Override
public int getOrder() {
return -1;
}
和注解@Order(-1)的作用是相同的,代表着该过滤器执行的先后顺序,数值越小,越先执行
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFiter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后一次执行每个过滤器
1.在过滤器中,每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行的顺序越靠前。
2.GlobalFilter通过实现Ordered接口,或添加@Order注解来指定order的值,由我们自己指定
3.路由过滤器和defaultFilter的order由spring指定,默认是按照声明顺序从1递增
4.当过滤器的order值一样时,会按照defultFilter>路由过滤器>GlibalFilter的顺序执行
跨域:域名不一致就是跨域,主要包括
域名不同:www.taobao.com和www.taobao.org
域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器进制请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
可以通过在网关中配置跨域以达到跨域的目的
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期