springcloud是什么?
是一系列技术的组合
应该包含哪些组件?
- 服务注册与发现
- 服务调用
- 服务熔断
- 负载均衡
- 服务降级
- 服务消息队列
- 配置中心管理
- 服务网关
- 服务监控
- 全链路追踪
- 自动化构建部署
- 服务定时任务调度操作
- ....
Eureka已经停止更新了,所以现在已经使用nacos了
Ribbon停止维护,所以新版的SpringCloud已经不用它了,所以需要引入spring-cloud-starter-loadbalancer,并且在nacos依赖中排除掉ribbon
认识微服务
根据业务进行拆分,每个业务模块都是一个单独的项目,称为服务
优点:
- 降低服务耦合度
- 有利于服务升级扩展
微服务面临的问题
- 服务拆分的粒度如何?
- 服务集群的地址要怎么维护?
- 服务之间如何实现远程调用?
- 服务健康状态如何感知?
微服务架构特征
- 单一职责
- 微服务拆分的粒度要小,每一个服务对应唯一的业务能力,做到单一职责,避免重复业务开发
- 面向服务
- 微服务对外暴露业务接口
- 自治
- 团队独立、技术独立、数据独立、部署独立
- 隔离性强
- 服务调用要做好隔离、容错、降级,避免出现级联错误
Dubbo技术
主要是做服务的注册和远程调用功能,没有配置中心和服务网关。
微服务技术对比
企业需求
Dubbo+Zookeeper其实是一种很老的微服务方案
Springcloud集成了各个组件,基于springboot实现了这些组件的自动装配,从而提供了一个开箱即用的体验
怎么拆微服务?
- 微服务之间尽量不要业务交叉
- 微服务之间只能通过接口进行服务调用,而不同绕过接口直接访问对方的数据
- 高内聚,低耦合
Eureka-注册与发现
是基于AP(可用型和分区容错性)模型的,优先保证了可用型,
- 各个节点之间是平等的,不会因为几个节点挂掉而影响正常节点的工作,剩余的节点依然可以正常的提供注册和查询服务
- eureka的客户端在想某个eureka注册或者是连接失败的时候,则会自动切换到其他的节点,保证了注册服务的可用型(保证高可用),只不过查询到的信息可能不是最新的(不能保证强一致性)。
- eureka的自我保护机制,如果15分钟内,超过85%以上的节点都没有正常的心跳,那么Eureka就认为客户端和注册中心出现了网络故障,此时会有一下情况:
- Eureka不在从注册列表中移动长时间没有心跳而应该过期的服务。
- Eureka仍然能接受服务的注册和查询请求,但是不会再同步到其他节点,保证当前节点可用。
- 当网络稳定的时候,当前实例新的注册信息会被同步到其他节点中。
选型方案
- 当我们要求注册服务的可用型强于一致性的时候,则可以选择使用Eureka。
- 当我们对于注册服务的强一一致型高于可用型的时候,则可以选择Zookeeper。
作用
- 消费者该如何获取服务提供者的具体信息?
- 服务提供者启动的时候,会想Eureka注册自己的信息
- Eureka保存了服务提供者的信息
- 消费者服务根据服务者服务名称去Eureka拉取具体的提供者信息
- 如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个
- 消费者如何感知服务提供者的健康状态?
- 服务提供者每隔30秒就会向Eureka发送一次心跳请求,报告健康状态,实质是通过短连接来与保证与服务的联系。
- eureka会更新服务列表中的信息,心跳不正常就会被踢出
- 消费者就可以拉到最新的信息
使用
搭建EurekaServer
- 引入Eureka的依赖
- 添加EnableEurekaServer注解
- 在application.yml中配置eureka地址
服务注册
- 引入Eureka-client依赖
- 在application.yml中配置eureka地址
- 无论是消费的提供者还是注册者,引入了eureka的依赖,知道了eureka的服务地址,都可以完成服务的注册
服务发现
- 引入eureka依赖
- 在application.yml中配置eureka的地址
- 在RestTemplate中添加@LoadBlance注解
- 用服务提供者的服务名称进行远程调用
缺点
- 只有一个服务注册中心,如果希望高可用的话,就得增加Eureka Server的的数量,维护成本太高了。
- 时间生产中,Eureka Server不会和业务服务部署在同一台服务上,当Eureka server服务地址发生变化时,害的修改配置文件中的地址,比较麻烦。
- Eureka因为缓存设计的原因,使得注册上去的服务,最迟得需要两分钟以后才能被发现。
485308108
Ribbon-负载均衡(2022年4月停更)
并使用 Spring Cloud Loadbalancer 作为其替代品
Eureka中自带了Ribbon
工作流程
运行原理
原理
- Ribbon通过拦截器拦截请求,然后拿到请求中的服务名称
- 然后根据服务名称去Eureka中查到对应的服务服务列表
- 根据IRule中的负载均衡策略,选择到某个服务
- 修改url,发送请求
负载均衡策略
- 线性轮询策略;
- 重试策略
- 加权响应时间策略
- 随机策略
- 客户端配置启动线性轮询策略
- 最空闲策略
- 过滤性线性轮询策略
- 区域感知轮询策略
- 可用性过滤策略
使用
userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
加载策略
- 默认的是懒加载,即第一次访问的时候速度比较慢。
- 饥饿加载:项目启动的那一刻就启动加载
Nacos
有哪些功能?
- nacos属于阿里巴巴的开源项目,能够帮助用户实现服务动态发现、服务配置、服务元数据及流程管理。
- nacos提供了三个功能:
- 服务注册与发现
- 动态配置服务
- 动态DNS服务
入门使用
- 在父工程的pom中添加上依赖管理
<dependencyManagement> <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> </dependencies> </dependencyManagement>
2.在对应的微服务中添加上pom依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
3.修改微服务中的application.yml配置文件
cloud: nocos: server-addr: localhost:8848
至此,服务的注册已经完成。
服务多级存储模型
模型图
分级模型是什么?
- 一级是服务,例如UserService。
- 二级是集群,例如杭州或赏花。
- 三级是实例,例如杭州机房的某台服务器上部署了UserService的服务器。
如何设置实例的集群属性
在配置文件中添加:
spring cloud: discovery: cluster-name: SH #集群名称
负载均衡
使用
userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
NacosRule负载均衡策略
- 优先选择同集群服务实例列表。
- 本地集群找不到提供者,采取其他集群寻找,并且会报警告。
- 确定了可用实例列表后,再采用随机负载均衡挑选实例。
服务实例的权重设置
使用
说明
- Nacos控制台可以设置实例的权重值,0~1之间。
- 同集群内的多个实例,权重越高被访问的频率也越高。
- 权重为0的时候,则不会被访问。
小技巧
我们可以通过权重的大小,实现一个服务的更新升级。
Nacos环境隔离
什么是环境隔离?
为什么要搞环境隔离?
使用
- 配置隔离信息
- 在服务的配置文件中添加配置信息
spring: cloud: nacos: server-addr: localhost:8848 # nacos服务地址 discovery: cluster-name: HZ # 集群名称 namespace: fec57c9d-e0bd-4ee3-97fc-5b3add001022 # dev环境
- 重启服务以后,就可以看到dev环境下多了一个服务
总结
- namespace用来做环境隔离的。
- 每个namespace都是唯一的id。
- 不同的namespace下的服务是不可见的。
Nacos中临时实例和非临时实例
spring: cloud: nacos: server-addr: localhost:8848 # nacos服务地址 discovery: cluster-name: HZ # 集群名称 namespace: fec57c9d-e0bd-4ee3-97fc-5b3add001022 # dev环境 ephemeral: false # 是否是临时实例
配置后的效果:
Nacos与Eureka的区别
共同点
- 都支持服务注册和服务拉取
- 都提供了服务提供者心跳方式做健康检测
异同点
- Nacos支持服务端主动检测提供者状态,临时实例采用心跳模式,非临时实例采用主动检测模式。
- 临时实例如果没有心跳不正常会被剔除
- Nacos支持服务服务列表变更的消息推送模式,服务列表更新更及时。
- Nacos集群默认采用的是AP模型,当集群中存在非临时实例时,采用的CP模型。Eureka采用的AP模型。
Nacos配置管理
主要是用来做热更新的。不是将application中所有的配置都拿过来。
多环境配置共享
- [服务名]-[Spring.profile.active].yaml,多环境配置
- [服务名].yaml,默认配置,多环境共享
- 读取配置文件的优先级:
- [服务名]-[环境].yaml > [服务名].yaml > 本地配置
Nacos集群搭建
集群架构
搭建步骤
- 搭建Mysql集群并初始化数据表
- 下载解压Nacos
- 修改集群配置(节点信息)、数据库配置
- 分别启动多个节点信息
- nginx反向代理
Feign(http客户端)
基于Feign的远程调用
RestTemplate方式调用存在的问题
- 代码可读性差,编程体验不统一
- 参数复杂的url难易维护
介绍
Feign是一个声明式的http客户端,其作用主要是帮助我们优雅的实现了http请求的发送。
使用
- 引入依赖。
- 添加EnableFeignClients注解。
- 编写FeignClient接口。
- 使用FeignClient中定义的方法代替RestTemplate。
- Feign中已经将Ribbon集成进来了,自动实现了负载均衡。
Feign自定义配置
Feign性能优化
Feign的实现原理
Feign底层是就UrlConnection实现的,每次创建都是需要建立连接,这样很消耗资源,但是UrlConnection是不支持连接池。
- UrlConnection:默认使用的客户端,不支持连接池
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
性能优化
- 使用连接池替代默认的URLConnection。
- 日志级别,最好使用basic或none。
连接池配置
关于连接池的数量是多少合适?这样需要根据进行压测后的数据来确定。
Feign的最佳实践
- 继承,给消费者的FeignClient和提供者的controller定义统一标准的父接口作为标准
这种方式也存在问题:紧耦合、父接口参数列表中的映射
- 抽取
总结
- 让Controller和FeignClient继承同一个接口
- 将FeignClient、POJO、Feign的默认配置都定义到一个项目中,最好以jar依赖的方式,提供给三方来使用。
- 两种方式都有优缺点,在实际项目中,根据实际的情况再来选型。
实现最佳实践
使用
- 创建一个module,命名为feign-api,并引入feign的starter的依赖。
- 将feign中client、pojo等信息复制过来。
- 微服务项目中引入feign-api依赖。
- 重启测试
注意:
如果feign中的包名称和微服务中的名称不一致的话,则需要为微服务的启动类中添加feign的扫描路径:
Gateway(统一网关)
网关作用介绍
为什么要使用网关?
- 网关可以做身份认证和权限校验
- 服务路由、负载均衡
- 请求限流
网关如何选型?
在springcloud中网关主要有两种:
- gateway
- zuul
- Zuul是基于Servlet实现的,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中的webflux,属于响应式编程的实现,具备更好的性能。
使用
- 创建一个单独的gateway的module
- 添加依赖:
<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>
- 配置文件中添加路由规则配置
server: port: 8092 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 gateway: routes: - id: userservice uri: lb://userservice predicates: - Path=/user/** - id: orderservice uri: lb://orderservice predicates: - Path=/order/**
- 运行gateway网关服务,通过访问统一网关,路由到对应的服务上。
- 路由配置规则:
- 路由id:路由的唯一标示
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡。
- 路由断言(predicates):判断路由的规则
- 路由过滤器:对请求或响应做处理
网关运行原理
路由断言工厂
作用
我们在配置文件中写的断言规则知识字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。
总共有11中断言方式:
网关过滤器GatewayFilter
GateWayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。
过滤器工厂
使用
server: port: 8092 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 gateway: routes: - id: userservice uri: lb://userservice predicates: - Path=/user/** # filters: # - AddRequestHeader=Truth, come from gateway! - id: orderservice uri: lb://orderservice predicates: - Path=/order/** default-filters: - AddRequestHeader=Truth, come from gateway!
- 默认过滤器:通过默认过滤器可以给所有的微服务都实现添加过滤器信息。
- default-filters:
- AddRequestHeader=Truth, come from gateway!
全局过滤器GoobalFilter
- 全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
- 和默认过滤器的区别在于,GatewayFilter是通过配置定义的,处理逻辑是固定的。而GlobalFilter的逻辑是需要自己写代码实现的。
- 定义的方式就是实现GlobalFilter接口即可。
自定义过滤器
package cn.itcast.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.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; //设置order,是因为过滤器也是有顺序的, @Order(-1) @Component public class AutherizeFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1. 获取请求参数 ServerHttpRequest request = exchange.getRequest(); MultiValueMap<String,String> params = request.getQueryParams(); //2. 判断参数是否合法 String auth = params.getFirst("authorization"); //3. 合法则放行,不合法则拦截 if("admin".equals(auth)) { //4. 放行 return chain.filter(exchange); } else { //5. 拦截 //5.1设置状态码 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } } }
作用
对所有的路由都生效的过滤器,并且可以自定义处理逻辑
实现步骤
- 实现GlobalFilter接口
- 添加@Order注解或实现Ordered接口
- 编写处理逻辑
过滤器的执行顺序
执行顺序
- 每一个过滤器都必须指定一个int类型的Order值,Order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order值,由我们自己指定。
- 路由过滤器和DefaultFilter的Order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order的值一样时,会按照defaultFilter>路由过滤器>GlobalFilter的顺序执行。
关于排序的结论
- 先按照order值进行排序,order值越小,优先级越高
- 当order值一样的时候,顺序执行defaultFilter,然后执行局部的路由过滤器,最后执行全局过滤器。
网关的cors的跨域配置
什么是跨域?
浏览器禁止请求的发起者和服务端发生跨域的ajax请求,请求被浏览器拦截的问题。
解决方案
CORS
openfeign跨域问题解决
在网关中配置上面的内容就可以实现基于cors实现跨域问题的解决。
Docker
Docker原理
docker服务启动名称
service docker start
目前部署时的问题?
- 依赖关系复杂,容易出现兼容问题
- 开发、测试、生产环境有差异
如何解决不同系统环境的问题?
- Docker将用户程序和所需要调用的系统函数库一起打包
- Docker运行到不同的操作系统时,直接基于打包的库函数,借助于操作系统的Linux内核来运行。
Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容问题?
- Docker允许开发中将应用、依赖、函数库、配置一起打包,形成一个可以直接的镜像
- Docker应用允许在容器中,使用沙箱机制,相互隔离
Docker如何解决开发、测试、生产环境差异的问题?
- Docker镜像中包含了完整运行环境,包括系统函数库、仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行。
Docker与虚拟机的区别
区别
- Docker接近原生,打包后的镜像体积小,启动速度快,因为Docker是一个系统进程。
- 虚拟机性能查,一般一个镜像文件都是GB级别,启动速度都是分钟级。
Docker架构
镜像(Image)
Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。
容器(Container)
镜像中应用程序运行后形成的进程叫容器,知识Docker会给容器做隔离,对外不可见。
DockerHub
DockerHub是一个Docker镜像的托管平台,国内有很多类似DockerHub的公共服务,如阿里云等
Docker架构
docker是一个CS架构的程序,由两部分组成:
- 服务端(server):Docker守护线程,负责处理Docker指令,管理镜像等。
- 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程想服务端发送指令。
Docker命令
镜像相关命令
https://hub.docker.com/search?q=redis
- 拉取镜像
docker pull nginx
- 查看镜像是否安装好
docker images
- 保存nginx镜像并打包压缩
docker save -o nginx.tar nginx:latest
nginx:latest 这个的含义是名字:版本
- 删除nginx镜像
docker rmi nginx:latest
- 再次查询镜像是否存在
docker images
- 通过刚才备份的镜像重新加载
docker load -i nginx.tar
- 镜像安装好了
Docker容器
docker run命令的常见参数
- --name:指定容器名称
- -p:指定端口映射
- -d:让容器后台运行
查看容器日志的命令
- docker logs 例如:docker logs 容器名称
- 添加 - f 参数可以持续查看日志
查看容器状态(查看所有运行中的)
- docker ps
- docker ps -a 可以查看到所有的容器,停止的也会运行
删除容器
- docker rm 容器名称
Docker容器操作
- 进入容器,进入我们刚创建的nginx容器
docker exec -it mn bash
命令解读:
- docker exec:进入容器内部,执行一个命令
- -it:给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
- mn:要进入的容器名称
- bash:进入容器后执行的命令,bash是一个linux终端交互命令
运行redis容器
- 启动容器
docker run --name mr -p 6379:6379 -d redis redis-server --appendonly ye
- 进入容器内执行
docker exec -it mr bash
- 进入redis
redis-cli
- 这样就可以进入redis
Docker数据卷
数据卷是一个虚拟目录,指向宿主机文件系统中的某个目录。
作用
将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全。
创建数据卷
docker volume create 数据卷名称
查看数据卷
docker volume ls
查看数据卷详细信息
docker volume inspect 数据卷名称
删除数据卷
docker volume rm
数据卷挂载
创建容器并挂载数据卷到容器内的HTML目录
docker run --name mn -p 80:80 -v html:/usr/share/nginx/html -d nginx
进入html数据卷所在的位置,修改html内容
查看html数据卷的位置
docker volume inspect html
进入该目录
cd /var/lib/docker/volumes/html/_data
修改文件
vi index.html
宿主机目录挂载到容器
创建一个mysql容器,将宿主机目录挂载到容器中
docker run \ --name mysql \ -e MYSQL_ROOT_PASSWORD=123 \ -p 3307:3307 \ -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.conf \ -v /tmp/mysql/data:/var/lib/mysql \ -d \ mysql:5.7.25
Dockerfile自定义镜像
镜像结构
- 镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。
镜像分层结构。每一层都称为一个Layer
- BaseImage层:包括基本的系统函数库、环境变量、文件系统
- Entrypoint:入口,是镜像中应用启动的命令
- 其他:在BaseImage基础上添加依赖、安装程序、完成整个应用的安装和配置
制作镜像(基于jar构建)
- 在linux的tmp上创建一个docker-demo文件夹,将需要制作到镜像中的jdk、jar运行包、Dockerfile文件都放到这个目录下,如下图:
- 进入到需要导打包的目录下
cd /tmp/docker-demo
- 开始制作镜像
docker build -t javaweb1.0
- 查看镜像是否安装好
- 到此,微服务的镜像已经安装好了,那接下来就是要运行这个容器
docker run --name -p 8090:8090 -d java1.0:latest
Dockerfile内容
# 指定基础镜像 FROM java:8-alpine COPY docker-demo.jar /tmp/app.jar # 暴露端口 EXPOSE 8090 # 入口,java项目的启动命令 ENTRYPOINT java -jar /tmp/app.jar
其中,FROM java:8-alpine就是安装jdk的意思
Dockerfile
- Dockerfile本质是一个文件,通过指令描述镜像的构建过程
- Dockerfile的第一行必须是FROM,从一个基础镜像来构建
- 基础镜像可以是基本操作系统,如Ubantu.也可以是其他人制作好的镜像,例如:java:8-alpine
DockerCompose
什么是DockerCompose?
- Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。
- Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。
SpringAMQP
发布、订阅
发布订阅模式与之前的案例区别是允许将同一个消息发给多个消费者,
实现方式是加入了exchange(交换机)
常见exchange类型包括:
- Fanout:广播
- Direct:路由
- Topic:话题
注意:exchange负责消息路由,而不是消息存储,路由失败会导致消息丢失。
Fanout Exchange
会将接收到的消息路由到每一个跟其绑定的queue
DirectExchange
会将接受到的消息根据规则路由到指定的queue,因此被称为路由模式(routes)
- 每一个Queue都与Exchange设置一个BindingKey
- 发布者发布消息的时候,指定消息的RoutingKey
- Exchange将消息路由到BindingKey与RoutingKey一致的队列
- 每个Queue都可以设置多个BindigKey
Sentinel
什么是雪崩问题?
微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。
雪崩问题解决方案
- 超时处理:设置超时时间,请求超过一段时间没有响应就返回错误信息,不会无休止等待。
- 舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离
- 熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求
- 流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。
如果避免因瞬间高并发流量而导致的微服务故障
- 流量控制
如果避免因为微服务故障引起的雪崩问题
- 超时处理
- 线程隔离
- 降级熔断
Sentinel与Hystrix的差异
流控模式-链路
Hystrix
实现原理
是一个分布式容错框架
- 组织故障的连锁反应,实现熔断
- 快速失败,实现优雅降级
- 提供实施的监控和告警
资源隔离
- 线程隔离:
- Hystrix会给每个Commad分配一个单独的线程池,这样在进行单个服务调用的时候,就可以用独立的线程池里面进行,而不会影响其他线程池造成影响。
- 信号量隔离: