Author:haoransun
WeChat:SHR—97
参考:
https://www.springcloud.cc/
https://www.zhihu.com/question/283286745/answer/763040709
http://www.ityouknow.com/spring-cloud.html
1. 什么是微服务
The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services , which may be written in different programming languages and use different data storage technologies. – James Lewis and Martin Fowler
官⽅⽹站: https://www.martinfowler.com/articles/microservices.html
微服务就是由⼀系列服务功能组成,能单独跑在⾃⼰的进程⾥,每个服务独⽴开发, 独⽴部署,分布式的管理。
2. 微服务的起源
3. 为什么会出现微服务
1. 单体架构
优点:
- 容易测试
- 容易部署
缺点:
- 开发效率低
- 代码难维护
- 部署不灵活
- 稳定性不⾼
- 扩展性不⾼
2.架构的演变
好的架构不是设计出来的,是(进化演变ing)出来的
4. 微服务的解决方案
5. 什么是Spring Cloud
Spring Cloud是⼀个含概多个⼦项⽬的开发⼯具集,集合了众多的开源框架,他利⽤了Spring Boot开发的便利性 实现了很多功能,如服务注册,服务注册发现,负载均衡等.Spring Cloud在整合过程中主要是针对Netflix(耐⾮)开源组件的封装。
NetFlix 是美国的⼀个在线视频⽹站,微服务业的翘楚,他是公认的⼤规模⽣产级微服务的杰出实践者,NetFlix的 开源组件已经在他⼤规模分布式微服务环境中经过多年的⽣产实战验证,因此spring cloud中很多组件都是基于 NetFlix组件的封装。
Spring Cloud的出现真正的简化了分布式架构的开发
6. Spring Cloud 的特性
服务注册和发现
路由
service - to - service调⽤
负载均衡
断路器
7. Spring Cloud 的服务架构图
*⼀个服务注册中心,eureka server,端⼝为8080 *
*多个服务的提供者 端⼝为8989….. *
⼀个服务的消费者 端⼝为9090
1 | @SpringCloudApplication |
8. 开发注册中⼼
9.创建聚合项目
1. 创建一个父项目(spring Initializr)
2. 删除无关包(src、mvn等)
3. 指定父项目的pom打包方式为 pom
4. 各个依赖关系
组件基本使用2.2.2发行版本
10. 开发Eureka-Server注册中心
1. 创建 eureka客户端(spring Initializr方式创建)
2. 添加到 springcloud_parent 的 modules中
3. 将 父项目中的 eureka-server依赖放在当前项目中,如果以后有很多项目依赖它,再提到父项目中
4. 在application.properties中配置eureka
1 | # defaultZone 默认域(service-url是一个Map结构),eureka-server默认是8761,要求与服务器启动端口一致。(借助tomcat启动了eureka-server) |
添加 @EnableEurekaServer 注解
5. 启动,访问 eureka-server 管理界面
访问:http://localhost:8761 (这个是管理界面),配置文件中是 http://localhost:8761/eureka 是eureka-server注册中心的地址,供实例注册用 )
6. 启动过程中报错处理:(但仍能访问管理界面)
即 既将当前 eureka-server 当成一个注册中心,又将它 当成一个实例注册进这个注册中心中,
通过 spring.application.name 配置实例名称
再次启动:
此时 实例名称 即为 eureka
7. 一般将当前应用纯粹做一个注册中心,不想将当前应用实例再注册进以自己为注册中心的容器中
再次启动,不再报错,且不会以实例方式注册进当前注册中心中来。
8.为了方便使用服务注册中心,一般以Maven 打jar包 的方式部署启动
1 | # 测试 |
11. 开发Eureka的客户端(服务提供方)
实例名称不建议有下划线,可能会找不到该实例。
1. 以同样方式开发eureka_hello_service 作为第一个案例
删除springcloud_eureka_hello_service POM文件中不必要的文件,指定父类是 springcloud_parent,并添加依赖。
在父类 modules 中添加子项目
2. 开发一个controller,并注册到eureka-server
3. 服务配置文件
4. 启动类上配置eureka客户端
5. eureka-server的自我保护模式
eureka-server会与当前注册的实例做心跳监测,没有一个实例时会报警告。(有时也会出现一些问题,服务已经宕机了,但心跳监测还没有到来,仍会显示在这里。)
6. 启动实例
7. 小结
对原有项目没有过多的入侵,只要在入口类上添加对应注解,配置文件配置注册中心地址即可。
11. HTTP VS RPC
应⽤间通信⽅式主要是HTTP和RPC,在微服务架构中两⼤配⽅的主⻆分别是:
- Dubbo RPC框架
基于dubbo开发的应⽤还是要依赖周边的平台⽣态, 相⽐其它的RPC框架, dubbo在服务治理与服务集成上可谓 是⾮常完善, 不仅提供了服务注册,发现还提供了负载均衡,集群容错等基础能⼒同时,还提供了⾯向开发测试节 点的Mock和泛化调⽤等机制。 在spring cloud 出现之前dubbo在国内应⽤⼗分⼴泛,但dubbo定位始终是⼀ 个RPC框架。
- SpringCloud 微服务框架(HTTP通信)
Spring Cloud 的⽬标是微服务架构下的⼀栈式解决⽅案,⾃dubbo复活后dubbo官⽅表示要积极适配到spring cloud的⽣态⽅式,⽐如作为springcloud的⼆进制通信⽅案来发挥dubbo的性能优势,或者通过dubbo的模块 化以及对http的⽀持适配到Spring Cloud,但是到⽬前为⽌dubbo与spring cloud 还是不怎么兼容,spring cloud 微服务架构下微服务之间使⽤http的RestFul⽅式进⾏通信,Http RestFul 本身轻量易⽤适⽤性强,可以很容易跨语⾔,跨平台,或者与已有的系统集成。
12. 服务消费者(restTemplate+ribbon)
在微服务架构中,业务都会被拆分成⼀个独⽴的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调⽤⽅式,一种是ribbon+restTemplate,另⼀种是feign。这⾥优先使⽤第⼀个种⽅式,后续讲解第⼆种方式。
1. ribbon简介
Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Feign already uses Ribbon, so if you are using @FeignClient then this section also applies.—–摘⾃官⽹
ribbon是⼀个负载均衡客户端,可以很好的控制htt和tcp的⼀些⾏为。Feign默认集成了ribbon。
2. 开发服务消费者(service customer)
启动注册中⼼和服务两个⼯程,这⾥注册中⼼的端⼝号是8080,服务的端⼝号8989,启动之后创建服务消费者并引⼊以下依赖 spring-cloud-starter-netflix-eureka-client、spring-cloud-starter-netflix-ribbon、spring-boot-starter-web。
1. 新建module(springcloud_ribbon_resttemplate_client,删除不必要的依赖,并加入父项目mudules中
2. 配置client的配置文件 + 入口类
3. 开发 controller
第一种方式调用:
第二种方式调用:
第三种方式调用:(推荐):
第三种方式 调用时可能会出现问题:java.net.UnknownHostException: HELLO-SERVICE
用 HELLO-SERVICE 找到的实际上是计算机的名称,不知道具体返回哪一个实例,在Beans类中必须加一个注解 @LoadBalanced
,才会返回一个具体的实例(即使有一个,也必须加)。
第三种方式 启动+验证
4. 启动、验证
13. Eureka Server 集群(高可用)
8761 注册到 8762
8762 注册到 8761
1. 启动Eureka-server,更改有关配置
更改Name:EurekaServer001
更改VM options:-Dserver.port=8761
选中EurekaServer001,点击复制按钮,出现下图:
更改名称为 EurekaServer002
更改VM options:-Dserver.port=8762
因为设置了 -Dserver.port=xxxx
,所以会覆盖 application.properties
中的 server.port=xxxx
配置。
2. 启动 Eureka-server(同一个配置文件,启动时指定不同的端口及注册中心)
启动 Eureka-server001,指定注册中心为 8762
日志中:添加副本节点8762
启动 Eureka-server002,指定注册中心为 8761
日志中:添加副本节点8761
此时 eureka-server001/002 都已经启动
我们的 springcloud_eureka_hello_service
服务只注册进了 eureka-server001,即8761端口。
刷新后,发现001 与 002 都有了该 service服务。(集群,副本节点会拉取当前节点的注册实例)
3. 验证,启动 springcloud_ribbon_resttemplate_client客户端
服务消费者也只注册进了8761,但8762副本节点仍会拉取该实例。
访问 http://localhost:9090/show/hello?name=haoran
4. 宕掉eureka-server001(8761),再次访问
因为有 心跳监测机制,心跳监测之前仍能访问,为了效果,重启消费者服务(即 resttemplate_client )。
启动报错:
也不能被访问:
5. service(服务提供方)应该向两个 eureka-server 都进行注册,client同理。
服务提供方:
1 | eureka.client.service-url.defaultZone=http://localhost:8761/eureka,http://localhost:8762/eureka |
服务消费方:
1 | eureka.client.service-url.defaultZone=http://localhost:8761/eureka,http://localhost:8762/eureka |
分别启动 001 002 服务提供方、消费方验证:(001启动时更改配置文件,向8762注册,002启动时更改配置文件,向8761注册)
宕掉001注册中心,再次访问:
再次宕掉002注册中心,此时二者都宕掉,再次访问,因为有心跳检测机制,当前仍可以访问
虽然 服务提供方 与 服务消费方 已经不能连接到注册中心,但仍能访问。
原因:服务消费方 从 注册中心 取到 服务提供方的 IP+Port后,缓存到自己本地,导致注册中心即使宕机,只要服务没有挂掉(即通过 IP+Port 仍然可以调用服务),消费方仍然可以调用,重启服务消费方,使缓存失效。
服务消费方每次不用都要走网络传输从注册中心取得服务消费方的IP+Port。
将 eureka-server001、002再次启动起来
再次访问
14. Spring Cloud 服务集群(高可用与负载均衡)
1. eureka_server配置文件恢复,重新package
因为主要是做服务的高可用,为了演示效果,将eureka_server恢复为单机版,且使用 Maven 重新 package.
运行 java -jar xxx.jar
2. 复制几份 hello_service
选中 spring_cloud_eureka_hello_service
, ctrl + c
选中 spring_parent
ctrl + v
。
复制出来的service 都是灰色的,并不是一个Maven项目。
在每一个复制出来的service的POM文件中,右键,选择 Add as a Maven Project
选项。
更改每一个复制service的 artifactId
更改项目名称
选中 001右键 refactor, 选择rename,选择 rename module。
002、003 同理
技巧:先更改 artifactId,再右键,选择 Add as a Maven Project
选,名字会自动更改(2020版Idea好像不适合)。
聚合到父项目的modules中
服务集群化要保证 spring.application.name 名字一致,因为在一台机器上跑,所以端口号也不能一样。
同时因为只有一个 eureka_server,所以只需要注册进8761即可。
1 | 001 改为 8989 |
类似于下图
3. 启动前的准备
原则上,3台相同的service中的服务代码应该保持一致,此处为了演示,人为更改3台服务的部分代码,只为演示负载均衡效果。
只修改打印部分代码,001-service1、002-service2、003-service3。
002、003 类比上图做修改。
分别启动即可。
4. 启动验证
hello-service有3个实例
至此,服务的高可用构建完成。
5. 复制1份 resettemplate_client
步骤与2一致。
更改配置文件的注册中心,因为现在只有8761启动
启动resettemplate_client001,看下是访问001、002、003哪一个服务。
经测试,默认为 轮询。
因为采用RestTemplate方式调用,而RestTemplate被 @LoadBalanced 修饰。它帮我们做了负载均衡,默认为轮询策略。
虽然仅仅加了 @LoadBalanced 注解,但这依赖于 spring-cloud-starter-netflix-ribbon
依赖,
6. Dubbo 与 Spring Cloud Ribbon区别
Dubbo
在服务提供方就设置了一系列负载均衡策略,消费方可以覆盖服务方的负载均衡策略。
SpringCloud Ribbon
SpringCloud Ribbon 的负载均衡是一个软负载均衡,即基于客户端的软负载均衡。消费方调用服务时,会向 eureka-server 请求该服务实例,然后 eureka-server 会将该服务实例的服务列表全部推送到消费方的客户端(还有的说是客户端自己拉取服务列表到自己本地),缓存起来,消费方会与服务方进行心跳检测,当某个服务宕机时,通过心跳检测,删除该列表项,即高效的自动剔除。缓存到本地后,自然在客户端可以用@LoadBalanced 进行负载均衡(底层用的还是LoadBalancerClient),根据默认的负载均衡策略,从服务列表中拿一个来用。
7. Ribbon 三大组件
- ServerList(根据服务实例名称去注册中心拉取服务列表)
- IRules(指定负载均衡规则)
- ServerListFilter(通过心跳机制进行服务过滤)
怎样改规则呢?
在 服务消费方(客户端)application.properties中加入配置
1 | # **HELLO-SERVICE** 服务名 |
此处更改轮询策略的服务名要与调用方式的服务名大小写保持一致,否则策略不生效,即 代码调用时服务名大写,此处也需大写,小写同理。
重启验证
15. 服务消费者(Feigin)
服务的消费的两种⽅式:⼀种是使⽤:rest+ribbon、⼀种是Feign
1. Fegin简介
Feign是⼀个声明式的伪Http客户端,它使得写Http客户端变得更简单。使⽤Feign,只需要创建⼀个接口并注入注解即可。它具有可插拔的注解特性,可使⽤Feign 注解和JAX-RS(基于restful风格的WebService)注解。Feign⽀持可插拔的编码器和解码器。Feign 默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
简而言之
- Feign 采⽤的是基于接口+注解
- Feign 整合了ribbon
2. Fegin依赖引入
1 | # 根据仓库提示,上图的依赖包已过时,推荐使用下图依赖 |
3. 新建 springcloud_fegin_client模块
过程同上。
4. 编写调用方
Controller
创建一个interfaces包,存放fegin调用接口
如果服务方写了应用名称 server.context-path=/hello
,则上述接口中抽象方法注解改为@RequestMapping("/hello/hello/hello")
在controller中注入即可
入口类添加注解
编写配置文件
5. 启动验证
6. name 为 null
RestTemplate 与 HttpClient 相像,相当于浏览器,所以基于RestTemplate发起的请求,服务方自然认为 方法名称?name=haoran后拼接的就是请求参数。
Feign :对于消费方而言,在浏览器访问时,请求是先到我自己服务的控制器,又通过 Feign 的方式调用了跑在别的机器上的hello路径下的服务(以Feign的方式又发了一次请求),但这次请求的过程中,仅仅是自定义了一个接口,自定义了返回值,自定义了参数,需要调用方明确告知基于Feign发起的这次请求,String name
参数是服务方那边要用到的请求参数。
需要将请求参数明确告知。 @RequestParam
,如果调用方调用参数名称不一致,可以使用注解提供的别名机制,与服务方提供的参数名称保持一致。
16. 断路器(Hystrix)
在微服务架构中,根据业务来拆分成⼀个个的服务,服务与服务之间可以相互调⽤(RPC),在Spring Cloud可 以⽤RestTemplate+Ribbon和Feign来调⽤。为了保证其⾼可⽤,单个服务通常会集群部署。由于⽹络原因或者 ⾃身的原因,服务并不能保证100%可⽤,如果单个服务出现问题,调⽤这个服务就会出现线程阻塞,此时若有 ⼤量的请求涌⼊,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传 播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
1. 断路器简介
Netflix has created a library called Hystrix that implements the circuit breaker pattern. In a microservice architecture it is common to have multiple layers of service calls.—-摘⾃官⽹
Netflix开源了Hystrix组件,实现了断路器模式,SpringCloud对这⼀组件进行了整合。 在微服务架构中,⼀个请求需要调⽤多个服务是⾮常常⻅的,如下架构所展示⼀样:
如果较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调⽤的不可⽤达到⼀个阀值(Hystric 是5秒 20次) 断路器将会被打开。如下图所示:
注意:此时断路器打开后,可用于避免连锁故障,fallback⽅法可以直接返回⼀个固定值。
2. 使⽤Rest+Ribbon的断路器实现
基于 springcloud_ribbon_resttemplate_client进行开发
1. 引入依赖
1 | spring-cloud-starter-netflix-eureka-client |
2. 确保注册中心+服务+消费方可正常访问
宕掉一台后,根据负载均衡原理,只在另外两台轮询。
再宕掉一台后,只在一台机器上提供服务。
3台都宕掉,这个不用多数了 0.0。
断路器的作用相当于访问到空白页或404,总之服务不可用时,快速返回一个固定的值。
3. 编写断路器有关代码
@HystrixCommand
注解在服务调用类的调用方法上,代表当前方法调用外部服务时,外部服务如果出现阻塞、宕机,当前方法的应对方案。
@HystrixCommand(fallbackMethod=”errorHello”),并且书写一个名称为 errorHello的方法。
fallbackMethod触发时机:只有当服务器集群的节点都阻塞或宕机时才会触发。
启动类启用Hystrix @EnableHystrix
4. 验证
*5. 如果RestTemplate中有好多方法都需要断路器的支持,每个方法上都要写 @HystrixCommand(fallbackMethod="xxx")
,太过繁琐,可以在 controller上添加 @DefaultProperties(defaultFallback="defaultError")
,再写一个默认的 名称为 defaultError 方法即可。这个可以被公用(如果当前方法没有提供自己的断路器) ,但是 方法上仍然要加 @HystrixCommand
注解 *
6. 验证
3. Feign中使⽤断路器
Feign是⾃带断路器的,在低版本的Spring Cloud中,它没有默认打开。需要在配置⽂件中配置打开它,在配置文件加以下代码:
feign.hystrix.enabled=true
基于 springcloud_feign_cllient进行二次开发。
1. 配置文件开启断路器(当前版本要加上)
2. 在FeignClient的服务的接口加入实现类,即只需要编写一个HelloServiceInterface
的实现类即可
3. 在方法调用接口中指明断路器实现的类
4. 验证
17. 小结
18. 统⼀配置中心
微服务都跑在自己的进程里,当后期因为开发需要修改某一台服务的配置时,该服务的集群中每一台都要修改。维护代价太大。
在生产环境中,一般配置文件中的敏感信息不会明文写出,为了保密,都是写在系统中。
SpringCloud 提供了一个 配置服务器,统一配置中心,当一个服务实例的配置文件变化时,其他实例的配置会自动变更。SpringCloud 默认基于 Git方式管理配置文件,将配置文件都放在远程的Git上,由 config server这个服务从远端Git抓取配置文件,之后统一配置中心会将拿到的远端配置文件存放在本地系统一份(防止远端被和谐,不能访问)。
每一个微服务配置时只需要指明配置中心,配置中心在服务启动时会从远端Git拉取配置到本地。一旦日后有改动,只需要改动远端Git中的配置文件即可。
统一配置中心如此重要,所以也需要做成集群(类比服务集群),要保证他们的名字是一致的,彼此做心跳检测。
统一配置中心也需要注册进Eureka-Server注册中心。它本身就是一个服务
1.创建项目
与上述方法一致。
1. 引入依赖
1 | spring-cloud-starter-netflix-eureka-client |
2. 配置启动类
1 | @SpringBoolApplication |
3. 配置配置文件
4. 启动验证:报错
因为统一配置中心要在远端的Git才能拿到配置文件;因此必须部署Git。
*在配置文件中指定Git 仓库 *
git不行,则换成 HTTPS开头
2. 准备 + 验证
1. 远程仓库存放mybatisgenerator.yml文件 测试用
2. 启动项目,通过端口访问
访问成功,文件名称后必须跟 -xx
,实际上可以随便写-sdfsdf
怎么写都行,就是不能只写原文件名称。
同时,也能更换文件的后缀名,如 我们远端是 yml结尾,此处写为 properties结尾,照样可以转换,需要自己加等号。
支持 json格式
不管远端是什么格式的,只要写出支持的格式,都可以做转换。
远程添加两个文件 mybatisgenerator.yml 与 mybatisgenerator-dev.yml,后者第一行有 env: dev
的区分。
如果浏览器访问 mybatisgenerator-dev.yml,会返回该文件的内容
如果浏览器访问 mybatisgenerator-prod.yml,即不存在的文件名,会返回mybatisgenerator.yml 内容
远程配置中心还可以校验格式,如果格式不对,访问出错。
如果想访问不同分支下的文件呢?
只需要在文件前添加分支名即可,如下图:
http://localhost:8799/release/order-dev.yml
release:分支名 order-dev.yml 文件名(上面默认走的是master分支)
明明已经删除了 order-dev 中的内容,为什么还能访问到?
原因:整个配置文件可能有很多个环境,可以将公共配置放在 order.yml 文件中,在访问子环境时,如order-dev.yml,会默认将 公共配置添加到 子环境文件中。
远程文件拉取到了哪里呢?
每访问1次,就会拉取1次,存放位置如下图
更改文件存放路径,如在 F:/Git
文件中(每次都会将本地文件夹中的动气清空,再拉取),所以要慎重考虑存放位置。
再次拉取
3. 客户端使用统一配置中心
如果要搭建统一配置中心的集群,类比服务集群,取多个端口,应用名一致即可。
在 order项目中 引入依赖
1 | <dependency> |
更改配置文件读取方式:从远端读
只保留以下内容
1 | # 开启客户端的远端读取设置 |
启动会报异常,因为是在 application.yml中配置的,当加载到这个文件时才刚知道数据原来是要去远端获取的。当前启动线程不会等着你去拉取数据所以在使用了统一配置中心后,要求在项目启动之前,明确告知启动时应该去远程拉取配置,拉取到本地后再去加载我的配置。客户端不能再命名为 application.yml
,改名字为 bootstrap.yml
,固定写死,启动时优先载入,优先拉取远端配置。
19. 路由网关(zuul)
在微服务架构中,需要⼏个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能 路由、配置管理等,由这⼏个基础组件相互协作,共同组建了⼀个简单的微服务系统。⼀个简答的微服务系统如下图:
注意:A服务和B服务是可以相互调⽤的,并且配置服务也是注册到服务注册中心的。
总结: 在Spring Cloud微服务系统中,⼀种常见的负载均衡⽅式是,客户端的请求⾸先经过负载均衡(zuul、Ngnix),再到达服务⽹关(zuul集群),然后再到具体的服务,服务统⼀注册到⾼可⽤的服务注册中心集群,服务的所有的配置文件由配置服务中心统一管理,配置服务中心的配置⽂件放在git仓库,方便开发⼈员随时改配置。
1. Zuul简介
Zuul的主要功能是路由转发和过滤器。路由功能是微服务的⼀部分,⽐如/api/user转发到到user服 务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能
2. 搭建Zuul
1. 新建Module
步骤同上
2. 引入依赖
1 | # zuul 转发服务是从 eureka中找注册的服务 然后进行转发 |
3. 基本配置
入口类添加 @EnableDiscoveryClient
、@EnableZuulProxy
注册进eureka-server中
原来访问某个消费者进而访问服务时,要用到该消费者的IP+端口,Zuul作为统一网关入口,只需访问 http://localhost:8760/config/release/order-dev.yml 。Zuul在启动时,默认回将Eureka中注册的所有服务统一管理。
原来经过 http://localhost:8799/release/order-dev.yml 访问的
现在经过 http://localhost:8760/config/release/order-dev.yml 访问。
但为什么访问不到呢?访问超时了?
Zuul默认开启了断路器,默认时间为1秒,需要更改。
源码中也可以看到。
1 | # 更改 Zuul的 断路器时间为5s |