分布式架构
分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务
优点:
- 降低服务耦合
- 有利于服务升级拓展
微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
- 面向服务:微服务对外暴露业务接口
- 自治:团队独立、技术独立、数据独立、部署独立
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
服务远程调用
/**
*创建 RestTemplate 并注入Spring容器
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.发起http请求
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
//封装user到order
order.setUser(user);
// 4.返回
return order;
}
提供者与消费者
- 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
- 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
一个服务可以既是消费者,也是提供者。
Eureka注册中心
消费者获取服务提供者具体信息的方式:
- 服务提供者启动时向eureka注册自己的信息
- eureka保存这些信息
- 消费者根据服务名称向eureka拉取提供者信息
如果有多个服务提供者,消费者利用负载均衡算法,从服务列表中挑选一个
消费者感知服务提供者健康状态:
- 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
- eureka会更新记录服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新的信息
在Eureka架构中,微服务角色有两类:
- EurekaServer:服务端,注册中心
记录服务信息
心跳监控
EurekaClient:客户端
- Provider:服务提供者,例如案例中的 user-service
注册自己的信息到EurekaServer
每隔30秒向EurekaServer发送心跳
- consumer:服务消费者,例如案例中的order-service
根据服务名称从Eurekaserver拉取服务列表
基于服务列表做负载均衡,选中一个微服务后发起远程调用
搭建eureka服务
引入依赖,写文件
<dependencies>
<!-- eureka服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
server:
port: 10086
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: #eureka的地址信息
dafaulZone: http://localhost:10086/eureka
package com.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
启动成功之后,会出现下面这个页面
服务注册
1.在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
<!--引入eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.在application.yml文件,编写下面的配置:
eureka:
client:
service-url: #eureka的地址信息
defaultZone: http://localhost:10086/eureka
配置完成之后,在eureka客户端就能看到载入的两个服务
服务发现
由于之前远程调用使用的固定的ip,耦合度太高,所有在引入eureka之后,可以使用它的技术来实现低耦合的服务发现。
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.发起http请求
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
//封装user到order
order.setUser(user);
// 4.返回
return order;
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
Nacos
nacos注册中心
基础配置
分别在父工程和子工程中加载nacos的xml文件
<!--nacos管理依赖-->
<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>
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在完成以上配置后,打开nacos就能看到一下信息
服务多级存储
集群负载均衡
集群配置需要再yml文件中追加一下的信息
discovery:
cluster-name: HZ
#负载均衡规则
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
之后的所有请求都会朝着本地服务进行发送,但当本地服务挂掉以后,nacos还是会选择进行夸集群访问。
08-20 11:45:10:888 WARN 25336 --- [nio-8080-exec-3] c.alibaba.cloud.nacos.ribbon.NacosRule : A cross-cluster call occurs,name = userservice, clusterName = HZ, instance = [Instance{instanceId='192.168.211.1#8081#SH#DEFAULT_GROUP@@userservice', ip='192.168.211.1', port=8081, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='SH', serviceName='DEFAULT_GROUP@@userservice', metadata={preserved.register.source=SPRING_CLOUD}}]
NacosRule载均衡策略
- 优先选择同集群服务实例列表
- 本地集群找不到提供者,才去其它集群寻找,并且会报警告
- 确定了可用实例列表后,再采用随机负载均衡挑选实例
权重负载均衡
实际部署中会出现这样的场景:
- 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
- 在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
- 将权重设置为0.1,测试可以发现8081被访问到的频率大大降低
实例的权重控制
- Nacos控制台可以设置实例的权重值,0~1之间
- 同集群内的多个实例,权重越高被访问的频率越高
- 权重设置为0则完全不会被访问
环境隔离-namespace
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离。
命名空间需要再nacos中进行设置
将服务分配到哪一个命名空间是需要在yml文件中进行配置,配置完成之后,就可以在dev命名空间中发现服务。
namespace: 8bffbcda-7ef6-40f1-ab4a-51043ae9a880
当他们不在一个空间内时,服务之间无法进行访问。
Nacos环境隔离
- namespace用来做环境隔离
- 每个namespace都有唯一id
- 不同namespace下的服务不可见
Nacos和Eureka的共同点和区别
Nacos与eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
Vacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
Nacos配置管理
微服务配置拉取![](https://i-blog.csdnimg.cn/direct/e2703611e2c5401b8f93f01065e38f3b.png)
在yml文件中对信息填写完全后,就可以通过浏览器对数据进行读取
spring:
application:
name: userservice
profiles:
active: dev
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml
配置的热更新
但是普通模式下无法实现对数据的热更新,实现热更新有两种方式。
方式一:
添加RefreshScope注解
@RefreshScope
方式二:使用@ConfigurationProperties注解(推荐)
@Component
@Data
@ConfigurationProperties(prefix="pattern")
public class PatternProperties {
private String dateformat;
}
Nacos配置更改后,微服务可以实现热更新,方式:
- 通过@Value注解注入,结合@RefreshScope来刷新
- 通过@ConfigurationProperties注自动刷新
注意事项:
- 不是所有的配置都适合放到配置中心,维护起来比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
多环境配置共享
微服务启动时会从nacos读取多个配置文件
微服务会从nacos读取的配置文件
- [服务名]-[spring.profile.active].yaml,环境配置
- [服务名].yaml,默认配置,多环境共享
优先级:
- [服务名]-[环境].yaml>[服务名].yaml>本地配置
Nacos集群搭建
搭建集群的基本步骤
- 搭建数据库,初始化数据库表结构
- 下载nacos安装包
- 配置nacos
- 启动nacos集群
- nginx反向代理
http客户端Feign
远程调用
Feign是一个声明式的http客户端,官方地址:Feign 其作用就是帮助我们优雅的实现http请求的发送,解决发送请求的代码过于难解读的问题。
下面是使用的方法
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在启动类中添加注解开启Feign的功能
@EnableFeignClients
3. 编写Feign客户端
主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id)}
- 请求参数:Long id
- 返回值类型:User
@FeignClient("userservice")
public interface UserClient {
@GetMapping("user/{id}")
User findById(@PathVariable("id") Long id);
}
4.使用Feign方法
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.用Feign远程调用
User user = userClient.findById(order.getUserId());
//3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
Feign的日志配置
方式一是配置文件,feign.client.config.xxx.loggerLevel
- 如果xxx是default则代表全局
- 如果xxx是服务名称,例如userservice则代表某服务
方式二是java代码配置Logger.Level这个Bean
- 如果在@EnableFeignclients注解声明则代表全局
- 如果在@FeignClient注解中声明则代表某服务
Feign的性能优化
Feign底层的客户端实现:
- URLConnection:默认实现,不支持连接池
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
因此优化Feign的性能主要包括:
- 使用连接池代替默认的URLConnection
- 日志级别,最好用basic或none
feign:
httpclient:
enabled: true #支持HttpClient的开关
max-connections: 200
max-connections-per-route: 50
Feign的优化:
1.日志级别尽量用basic
2.使用HttpClient或OKHttp代替URLConnection
引入feign-httpClient依赖
配置文件开启httpClient功能,设置连接池参数
Feign的最佳实践
为了避免在日常开发中,过多服务都需要去调用Contorller的情况,使用以下情况来避免重复开发。
将FeignCclient抽取为独立模块,并且把接口有关的POJ0、默认的Feign配置都放到这个模块中,提供给所有消费者使用。
Feign的最佳实践:
- 让controller和FeignClient继承同一接口
- 将FeignClient、POj0、Feign的默认配置都定义到一个项目中,供所有消费者使用
不同包的Feignclient的导入有两种方式
- 在@EnableFeignClients注解中添加basePackages,指定Feignclient所在的包
- 指定具体在@EnableFeignClients注解中添加clients,:FeignClient的字节码
统一网关Gateway
网关功能
- :身份认证和权限
- 校验服务路由、负载均衡
- 请求限流
在SpringCloud中网关的实现包括两种:
- gateway
- Zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的Webflux,属于响应式编程的实现,具备更好的性能。
搭建网关服务
1.创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖。
<dependencies>
<!--服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
2.编写路由配置及nacos地址
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848 #nacos地
gateway:
routes:
- id: user-service #路由标示,必须唯一
uri: lb://userservice #路由的目标地址
predicates: #路由断言,判断请求是否符合规则
- Path=/user/** #路径断言,判断路径是否是以/user开头,如果是则符合
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
现在通过访问gateway晚安的端口,也能对我们的目标数据进行访问了。
网关搭建步骤
- 创建项目,引入nacos服务发现和gateway依赖
- 配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括
- 路由id:路由的唯一标示
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
- 路由断言(predicates):判断路由的规则
- 路由过滤器(filters):对请求或响应做处理
路由断言工厂
网关路由可以配置的内容包括:
- 路由id:路由唯一标示
- uri:路由目的地,支持lb和http两种
- predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
- filters:路由过滤器,处理请求或响应
路由过滤器
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
过滤器的作用是什么?
- 对路由的请求或响应做加工处理,比如添加请求头配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
- 对所有路由都生效的过滤器
全局过滤器(GloalFilter)
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFiter的逻辑需要自己写代码实现。
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@0verride
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterchain chain){
//1.获取请求参数
ServerHttpRequest request=exchange.getequest();
MultiValueMap<String,String>params=request.getQueryParams();
//2.获取参数中的 authorization 参数
String auth=params.getFirst( key:"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();
}
全局过滤器的作用
- 对所有路由都生效的过滤器,并且可以自定义处理逻辑
实现全局过滤器的步骤
- 实现GlobalFilter接口
- 添加@Order注解或实现Ordered接
- 编写处理逻辑
过滤器执行顺序
- 每一个过滤器都必须指定一个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和localhost:8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
spring:
cloud:
gateway:
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#这次跨域检测的有效期
Docker
什么是Docker
Docker实际上就相当于一个封闭的沙盒或者是集装箱,它可以把不同的应用全都放在它的集装箱里面,并且以后有需要的时候,可以直接把集装箱搬到其他平台或者服务器上,实现容器虚拟化技术,随用随搬。
一句话,Docker解决了运行环境和配置问题软件容器,方便做持续集成并有助于整体发布的容器虚拟化技术。
Docker解决大型项目依赖关系复杂,不同组件依赖的兼容性问题
- Docker允许开发中将应用、依赖、函数库、配置一起打包,形成可移植镜像
- Docker应用运行在容器中,使用沙箱机制,相互隔离
Docker解决开发、测试、生产环境有差异的问题
- Docker镜像中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行
Docker和虚拟机
虚拟机(virtualmachine)是在操作系统中模拟硬件设备,然后运行另一个操作系统,比如在 Windows 系统里面运行Ubuntu 系统,这样就可以运行任意的Ubuntu应用了。
Docker和虚拟机的差异
- docker是一个系统进程;虚拟机是在操作系统中的操作系统
- docker体积小、启动速度快、性能好:虚拟机体积大、启动速度慢、性能一般
Docker架构
镜像和容器
- 镜像(lmage):Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像
- 容器(Container):镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器做隔离,对外不可见。
Docker和DockerHub
- DockerHub:DockerHub是一个Docker镜像的托管平台。这样的平台称为Docker Registry。
- 国内也有类似于DockerHub 的公开服务,比如 网易云镜像服务、阿里云镜像库。
Docker是一个CS架构的程序,由两部分组成:
- 服务端(server):Docker守护进程,负责处理Docker指令,管理镜像、容器等
- 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令