1.微服务的定义
随着互联网行业的发展对于服务的要求也越来越高,服务架构也从单体架构逐渐演变为微服务架构
1.1 三种架构的对比
- 单体架构
- 分布式架构
- 微服务(优化版分布式架构)
微服务的架构特征:==可以认为微服务是一种经过良好架构设计的分布式架构方案==
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
- 自治:团队独立、技术独立、数据独立,独立部署和交付
- 面向服务:服务提供统一标准的接口,与语言和技术无关
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
1.2 SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
其中常见的组件包括:
此外,要考虑很多版本兼容问题。主要是因为SpringCloud底层是依赖于Springboot实现的:
2.服务拆分和远程调用
2.1 服务拆分原则
- ==什么时候拆分?==
- ==怎么拆分?==
- ==服务拆分原则==
不同微服务,不要重复开发相同业务
微服务数据独立,不要访问其它微服务的数据库
微服务可以将自己的业务暴露为接口,供其它微服务调用
2.2 远程调用原则
==远程调用原则==
3.提供者与消费者
在服务调用关系中,会有两个不同的角色:
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言【==相对的==】
如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?
- 对于A调用B的业务而言:A是服务消费者,B是服务提供者
- 对于B调用C的业务而言:B是服务消费者,C是服务提供者
因此,服务B既可以是服务提供者,也可以是服务消费者。
可能会出现三种情况:
1.消费者该如何获取服务提供者具体信息?
2.如果有多个服务提供者,消费者该如何选择?
3.消费者如何感知服务提供者健康状态?
4.Eureka注册中心
4.1 Eureka结构
组成部分:分为eureka-server和eureka-client[服务消费者和服务提供者]两个部分
基本步骤:
1.client向注册中心server注册服务信息(告知有哪些服务和端口信息) –每隔30s就重新注册
2.服务消费者从service拉取服务提供者信息
3.服务消费者自己负载均衡配置
4.服务消费者远程调用
==解决上述可能出现的三种情况==
问题1:order-service如何得知user-service实例地址?
获取地址信息的流程如下:
- user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端)。这个叫服务注册
- eureka-server保存服务名称到服务实例地址列表的映射关系
- order-service根据服务名称,拉取实例地址列表。这个叫服务发现/服务拉取
问题2:order-service如何从多个user-service实例中选择具体的实例?
- order-service从实例列表中利用负载均衡算法选中一个实例地址
- 向该实例地址发起远程调用
问题3:order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
- user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳
- 当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除
- order-service拉取服务时,就能将故障实例排除了
注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端
==分三步进行==
4.2 搭建三步
==总体图==
4.2.1 搭建注册中心
4.2.2 服务注册
4.2.3 服务发现
分为四个步骤:
1和2步骤类似于服务注册,只有spring.application.name= orderservice
3和4步骤更新具体操作
spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡
5.Ribbon负载均衡
4部分添加@LoadBalanced注解即可实现负载均衡功能
==补充:以前SpringMVC默认是Ribbon负载均衡,后来默认是loadbalancer负载均衡==
5.1 负载均衡原理
其实就是我们发出的请求是http://userservice/user/1,根据负载均衡变成了http://localhost:8081
SpringCloud底层其实是利用了一个名字为==Ribbon组件==来实现负载均衡
5.2 源码跟踪
基本流程如下:
- 拦截我们的RestTemplate请求http://userservice/user/1
- RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-service
- DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
- eureka返回列表,localhost:8081、localhost:8082
- IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
- RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求
5.3 负载均衡策略(IRule接口)
5.3.1 负载均衡策略
负载均衡的规则都定义在==IRule接口==中,而IRule有很多不同的实现类:
不同规则的含义如下:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule[默认] | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。[集群内优先] |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
5.3.2 自定义负载均衡策略(消费者)
通过定义IRule实现可以修改负载均衡规则,有两种方式:
①代码方式:在order-service中的OrderApplication启动类上定义一个新的IRule:
1 | //启动类定义一个bean,放入ioc容器 |
②配置文件方式:在order-service的application.yml文件中,添加新的配置:
1 | userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务 |
- 注意,一般用默认的负载均衡规则,不做修改。
5.4 饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
1 | ribbon: |
6.Nacos
SpringCloudAlibaba推出了一个名为Nacos的注册中心,现在是SpringCloud中的一个组件,相比Eureka功能更加丰富,在国内受欢迎程度较高。
主要涉及nacos注册中心和nacos配置管理
6.1 Nacos结构
6.2 搭建三步
6.2.1 搭建注册中心
- 1.下载
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
- 2.解压
- 3.端口配置
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:
- 4.启动nacos
6.2.2 服务注册
6.2.3 服务发现
和服务注册类似,导入nacos依赖
6.3 服务分级存储模型
一个服务可以有多个实例,例如我们的user-service,可以有:
- user-service:127.0.0.1:8081
- user-service:127.0.0.1:8082
- user-service:127.0.0.1:8083
假如这些实例分布于全国各地的不同机房,例如:
- 127.0.0.1:8081,在上海机房
- 127.0.0.1:8082,在上海机房
- 127.0.0.1:8083,在杭州机房
Nacos就将同一机房内的实例 划分为一个集群。
也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如图:
微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如:
6.3.1user-service服务提供者配置集群
6.3.2 order-service服务消费者配置集群
6.4 Nacos负载均衡(NacosRule实现)
Ribbon默认的ZoneAvoidanceRule
并不能实现根据同集群优先来实现负载均衡。[Eureka默认是同一个zone区域轮询]
6.4.1 修改负载均衡规则
注意:==原来默认是集群间轮询,后来设定nacosrule之后是本地集群优先(本地集群又是随机的 –>可以设置权重提高访问频率),如果本地的都挂了那就会跨集群==
因此Nacos中提供了一个NacosRule
的实现,可以优先从==同集群中挑选实例==
修改order-service服务消费者的application.yml文件,修改负载均衡规则:
6.4.2 修改权重
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。
但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。
因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
- 注意:如果权重修改为0,则该实例永远不会被访问
==如果设备更新/版本升级,那我就可以将权重设置低一点然后少数用户测试,慢慢增大权重==
6.5 环境隔离
类似于rabbitMQ消息队列的virtueHost虚拟主机空间可以造成环境隔离
- nacos中可以有多个namespace
- namespace下可以有group、service等
- 不同namespace之间相互隔离,例如不同namespace的服务互相不可见
6.5.1 网页创建namespace
默认情况下,所有service、data、group都在同一个namespace,名为public:
我们可以点击页面新增按钮,添加一个namespace:
然后,填写表单:
就能在页面看到一个新的namespace:
6.5.2 服务消费者配置
给==微服务配置namespace==只能通过修改配置来实现
6.6 Nacos服务实例
Nacos的服务实例分为两种类型:
临时实例:【默认类型】如果实例宕机超过一定时间,会从服务列表剔除。
非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。
配置一个服务实例为永久实例:
6.7 Nacos和Eureka对比
Nacos与eureka的共同点
都支持服务注册和服务拉取
都支持服务提供者心跳方式做健康检测
Nacos与Eureka的区别
Eureka Nacos 客户端 ==搭建新的EurekaServer项目==<br 1.pom.xml引入xxx-client依赖
2.启动类添加@EnableEurekaServer注解
3.application.yml配置eureka地址==启动app==
1.父工程引入依赖
2.pom.xml引入依赖服务提供者 1.pom.xml引入xxx-server依赖
2.application.yml配置eureka地址在application.yml配置nacos信息[server-addr地址,cluster-name集群名] 服务消费者 1.pom.xml引入xxx-server依赖
2.application.yml配置eureka地址
3.RestTemlate引入@LoadBalanced注解
4.修改具体业务在application.yml配置nacos信息[server-addr地址和cluster-name集群名,namespace空间名,ephemeral实例类型] 临时实例 心跳模式【默认每30s进行检测,不正常的会被剔除】 心跳模式【默认每30s进行检测,不正常的会被剔除】 非临时实例 × 主动监测模式【不会被剔除】 集群方式 AP方式 【默认】AP方式
如果集群中存在非临时实例会变成CP模式服务列表变更的消息推送模式 定时拉取服务pull 主动推送变更消息push 负载均衡策略[服务消费者] 【默认】随机
【IRule】同Zone轮询【默认】集群内轮询
【NacosRule】优先本地集群内 –> 修改权重还可以提高访问概率
6.8 Nacos配置管理
当微服务部署的实例越来越多[达到数百以上],逐步修改微服务配置就会让你抓狂。 –> 所以我们需要一种==统一配置管理方案==,可以集中管理所有实例配置
Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新
6.8.1 统一配置管理
6.8.1.1 网站添加配置
注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。
6.8.1.2 微服务拉取配置
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动
但是如果未读取application.yml文件就不能得到nacos地址。【属于是卡bug呢】
–> 因此Spring引入一种新的配置文件:==bootstrap.yaml==文件【会在application.yml之前被读取】
流程如下:
进行步骤:
②引入nacos-config依赖
首先,在user-service服务中,引入nacos-config的客户端依赖:
1 | <!--nacos配置管理依赖--> |
③添加bootstrap.yaml
然后,在user-service中添加一个bootstrap.yaml文件,内容如下:
④尝试读取nacos配置
在user-service中的UserController中添加业务逻辑,读取pattern.dateformat配置:
6.8.2 配置热更新
最终目的是修改nacos中的配置后,微服务无需重启即可让配置生效,也就是配置热更新
- 方式一:在@Value注入变量所在类上添加@RefreshScope注解
- 方式二:使用@ConfigurationProperties注解
6.8.3 多环境配置共享
可能不同环境下有不同的yaml文件[像单体架构的时候properties,yml,yaml等情况],因此当出现相同属性时就有优先级:==名字越长越牛逼==
6.9 搭建Nacos集群
==日后学习到了补充==
7.HTTP客户端Feign
之前使用的RestTemplate发起远程调用的代码:
存在下面的问题:
•代码可读性差,编程体验不统一
•参数复杂URL难以维护
==Feign==是一个声明式的http客户端。其作用是帮助我们优雅地实现http请求发送,解决了上述的问题
7.1 使用操作
7.1.1 导入依赖
7.1.2 启动类添加注解
启动类上添加注解@EnableFeignClients
7.1.3 编写子服务接口
这样可以相当于http://userservice/user/id
7.1.4 实际操作
底层也有ribbon负载均衡,可以避免了访问ip地址的麻烦。Feign可以将调用步骤放在接口里面,这样使得我们看起来都是直接调用方法统一了。
7.2 自定义配置
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
【注意:一般我们需要配置的就是日志级别】
==下面以日志为例来演示如何自定义配置==
而日志的级别分为四种:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
7.2.1 配置文件yml方式–日志
7.2.2 Java代码方式–日志
7.3 Feign使用优化
==Feign底层发起http请求,依赖于其它的框架==。其底层客户端实现包括:
URLConnection:[默认]不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
7.3.1 连接池优化
==由此可见,对于默认的URLConnection可以更改为Apache HttpClient或者OKHttp方式==
以HttpClient为例:
①pom.xml文件引入依赖
1 | <!--httpClient的依赖 --> |
②yml配置文件
1 | feign: |
7.3.2 日志级别优化
==日志级别最好是用basic或者none==
7.4 最佳实践方案
像7.1使用操作里面用feign替换掉RestTemplate
这种情况下会发现usercontroller和feign客户端代码相似,因此可以提出继承方式和抽取方式
7.4.1 继承方式
一样的代码可以通过继承来共享:
1)定义一个API接口,利用定义方法,并基于SpringMVC注解做声明。
2)Feign客户端和Controller都集成该接口
优点:
- 简单
- 实现了代码共享
缺点:
服务提供方、服务消费方紧耦合
参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
7.4.2 抽取方式
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign日志配置都放到这个模块中,提供给所有消费者使用。
例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。
举例:
8.Gateway服务网关
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API 路由管理方式
Gateway网关是我们服务的守门神,==所有微服务的统一入口==
网关的核心功能特性:
- 1.请求路由和负载均衡:一切请求都必须先经过gateway,只根据某种规则把请求转发到某个微服务。如果转发的目标服务有多个时就需要负载均衡
- 2.权限控制:通过拦截器校验用户是否有请求资格,没有就进行拦截
- 3.限流:当请求流量过高时,网关中按照下流的微服务能够接受的速度放行请求,避免服务压力过大
在SpringCloud中网关的实现包括两种:
- gateway【基于Spring5提供的WebFlux,属于响应式编程】
- zuul【基于Servlet实现,属于阻塞式编程】
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能
8.1 Gateway快速入门
演示下网关的基本路由功能。基本步骤如下:
- pom.xml导入依赖
- application.yml配置基础配置和路由规则
- 启动测试
8.3.1 导入依赖
1 | <!--网关--> |
8.3.2 编写规则
1 | server: |
总结:
1 | 路由id:路由唯一标示 |
8.3.3 启动测试
8.2 路由参数3-断言工厂—–==判断是否符合规则==
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例:Path=/user/**是按照路径匹配,这个规则由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理
==要符合条件才能访问转换匹配==
8.2.1 断言工厂分类
从这张图可以看出来可以有很多判断情况来匹配转换,可以通过访问时间,访问ip,访问范围,访问参数等等
8.3 路由参数4-过滤器工厂—–==请求时添加信息==
GatewayFilter是网关中提供的一种过滤器,可以对进入==网关的请求==和==微服务返回的响应==做处理:
8.3.1 路由过滤器种类
8.3.2 请求头过滤器
以AddRequestHeader 为例来讲解:
需求:给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
8.3.3 默认过滤器
对所有的路由都生效,需要写到default下面
8.3.4 全局过滤器
需求:自定义拦截请求,前端访问时必须有一个authorization=admin才可以
- 实现GlobalFilter接口
在filter中编写自定义逻辑,可以实现下列功能:
- 登录状态判断
- 权限校验
- 请求限流等
8.3.5 执行顺序总结
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)[==适配器模式]==中,排序后依次执行每个过滤器:
==原因:可以合并是因为路由过滤器和默认过滤器在yml配置的是范围不同,但是底层都是GatewayFilter同一类。而全局过滤器实现GatewayFilter,内部是适配成GatewayFilter==
==总结图:==
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter过滤器的顺序: 实现Ordered接口/@Order注解,由我们自己指定[@order(数字)]
- 路由过滤器和defaultFilter的顺序: 由Spring指定,默认是按照声明顺序从1递增[yml文件的书写顺序]。
- 当三大类过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行
详细内容,可以查看源码:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
8.4 跨域问题
==跨域==:域名不一致就是跨域,主要包括:
1.域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
2.域名相同,端口不同:localhost:8080和localhost:8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:
8.4.1 Gateway配置
在gateway服务的application.yml文件中,添加下面的配置:
1 | spring: |
8.5 Gateway流程图
发送请求后,可以根据过滤器加一些修改,可以增加头拦截[过滤器]。然后http://localhost:10010/user/1根据断言工厂的断言路径判断/user/**就找到lb:/userservice 就去负载均衡发送给http://localhost:xxxx/user/1
如果是访问orderservice的话在上述过程之后还会使用feign发送请求,feign里面配置的去找userservice/user/id
8.6 yml配置文件详解
9.Springcloud总结
1.服务注册:将网关,userservice,orderservice这些微服务要注册到nacos注册中心
2.服务配置:nacos进行配置管理
3.网关配置和Feign配置: