Spring Cloud学习教程1【面试+工作】
JDK:1.8
Eclipse:4.4.1 luna
Maven:3.2.3
安装文件在课前资料中。
目前微服务是非常火的架构或者说概念,也是在构建大型互联网项目时采用的架构方式。
单体架构,是指将开发好的项目打成war包,然后发布到tomcat等容器中的应用。
假设你正准备开发一款与Uber和Hailo竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于Spring Boot、Play或者Maven的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下:
如何解决以上问题呢? -- 使用微服务架构。
作者:Martin Fowler
每一个应用功能区都使用微服务完成。
Spring Cloud项目的官方网址:
http://projects.spring.io/spring-cloud/
Component | Camden.SR7 | Dalston.SR3 | Edgware.M1 | Finchley.M2 | Finchley.BUILD-SNAPSHOT | 备注 |
---|---|---|---|---|---|---|
spring-cloud-aws | 1.1.4.RELEASE | 1.2.1.RELEASE | 1.2.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 用于简化整合Amazon Web Service的组件 |
spring-cloud-bus | 1.2.2.RELEASE | 1.3.1.RELEASE | 1.3.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 事件、消息总线,用于传播集群中的状态变化或事件。 |
spring-cloud-cli | 1.2.4.RELEASE | 1.3.4.RELEASE | 1.4.0.M1 | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 用于在Groovy平台创建Spring Cloud应用。 |
spring-cloud-commons | 1.1.9.RELEASE | 1.2.3.RELEASE | 1.3.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | 服务发现、负载均衡、熔断机制这种模式为Spring Cloud客户端提供了一个通用的抽象层。 |
spring-cloud-contract | 1.0.5.RELEASE | 1.1.3.RELEASE | 1.2.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | |
spring-cloud-config | 1.2.3.RELEASE | 1.3.2.RELEASE | 1.4.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | 配置管理工具,支持使用git、svn等存储配置文件。并在支持客户端配置信息的刷新,加密解密配置内容等。 |
spring-cloud-netflix | 1.2.7.RELEASE | 1.3.4.RELEASE | 1.4.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | 核心组件,对多个Netflix OSS开源套件进行整合。 |
spring-cloud-security | 1.1.4.RELEASE | 1.2.1.RELEASE | 1.2.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 安全工具包。 |
spring-cloud-cloudfoundry | 1.0.1.RELEASE | 1.1.0.RELEASE | 1.1.0.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 整合Pivotal Cloudfoundry(Vmware推出的业界第一个开源PaaS云平台)支持。 |
spring-cloud-consul | 1.1.4.RELEASE | 1.2.1.RELEASE | 1.2.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 服务发现与配置管理工具 |
spring-cloud-sleuth | 1.1.3.RELEASE | 1.2.4.RELEASE | 1.3.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | Spring Cloud应用的分布式跟踪实现。 |
spring-cloud-stream | Brooklyn.SR3 | Chelsea.SR2 | Ditmars.M2 | Elmhurst.M1 | Elmhurst.BUILD-SNAPSHOT | 通过Redis、RabbitMQ、Kafka实现的消息微服务。 |
spring-cloud-zookeeper | 1.0.4.RELEASE | 1.1.2.RELEASE | 1.2.0.M1 | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 基于ZooKeeper的服务发现与配置管理组件。 |
spring-boot | 1.4.5.RELEASE | 1.5.4.RELEASE | 1.5.6.RELEASE | 2.0.0.M3 | 2.0.0.M3 | |
spring-cloud-task | 1.0.3.RELEASE | 1.1.2.RELEASE | 1.2.0.RELEASE | 2.0.0.M1 | 2.0.0.RELEASE | 用于快速构建数据处理的应用。 |
spring-cloud-vault | 1.0.2.RELEASE | 1.1.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | ||
spring-cloud-gateway | 1.0.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | Spring Cloud网关相关的整合实现。 |
官方版本:
可见,目前Dalston SR3版本是最新的稳定版,所以我们学习的过程中,就是使用的这个版本。
在正式学习Spring Cloud之前我们先使用Spring Boot实现一个微服务。
业务非常简单:
1、 商品微服务:通过商品id查询商品的服务;
2、 订单微服务:创建订单时通时,通过调用商品的微服务进行查询商品数据;
图示:
说明:
1、 对于商品微服务而言,商品微服务是服务的提供者,订单微服务是服务的消费者;
2、 对于订单微服务而言,订单微服务是服务的提供者,人是服务的消费者。
重点是导入Spring Boot的依赖:
编写ItemService用于实现具体的商品查询逻辑,为了演示方便,我们并不真正的连接数据库,而是做模拟实现。
@RestController注解的说明:
从源码可以看出,这是一个组合注解,组合了@Controller和@Response注解。相当于我们同时写了这2个注解。
@GetMapping注解的说明:
@GetMapping注解是 @RequestMapping(method = RequestMethod.GET) 简写方式。其功能都是一样的。
同理还有其它注解:
Spring Boot以及Spring Cloud项目支持yml和properties格式的配置文件。
yml格式是YAML(Yet Another Markup Language)编写的格式,YAML和properties格式的文件是可以相互转化的。如:
等价于:
配置文件的示例:
server:
port: 8081 #服务端口
可以看到已经通过微服务查询到数据。
订单与订单详情是一对多的关系。
该Service实现的根据订单Id查询订单的服务,为了方便测试,我们将构造数据实现,不采用查询数据库的方式。
server:
port: 8082 #服务端口
测试结果可见,查询订单时,同时也将商品数据查询到。
okhttp是一个封装URL,比HttpClient更友好易用的工具。目前似乎okhttp更流行一些。
官网:http://square.github.io/okhttp/
RestTemplate底层默认使用的jdk的标准实现,如果我们想让RestTemplate的底层使用okhttp,非常简单:
1、 添加okhttp依赖
2、 设置requestFactory
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
测试:
结果:
测试结果是一样的。
通过以上的测试我们发现,在订单系统中要调用商品微服务中的查询接口来获取数据,在订单微服务中将url硬编码到代码中,这样显然不好,因为,运行环境一旦发生变化这个url地址将不可用。
如何解决呢?
解决方案:将url地址写入到application.yml配置文件中。
实现:
修改application.yml文件:
server:
port: 8082 #服务端口
itcast:
item:
url: http://127.0.0.1:8081/item/
修改ItemService中的实现:
测试:
在SpringBoot中使用@ConfigurationProperties注解可以非常简单的将配置文件中的值映射成对象。
第一步,创建ItemProperties类:
第二步,创建OrderProperties类:
第三步,在Itemservice中注入该对象:
可以看出,这种解决方案比第一种好很多。更加的方便的。
思考:这样是否还存在问题?如果商品的微服务有多个怎么办?
通过前面5.4、5.5的实现,我们视乎已经解决了url硬编码的问题,但是我们想想:
1、 如果商品微服务的ip地址发生了变更,订单微服务中的配置文件也需要跟着修改
2、 如果商品微服务有多个,那么在订单微服务中又该如何写地址?
那应该怎么解决呢? -- 通过服务注册、发现的机制来完成。
原理示意图:
由上图可以看出:
1、 服务提供者将服务注册到注册中心
2、 服务消费者通过注册中心查找服务
3、 查找到服务后进行调用(这里就是无需硬编码url的解决方案)
4、 服务的消费者与服务注册中心保持心跳连接,一旦服务提供者的地址发生变更时,注册中心会通知服务消费者
Spring Cloud提供了多种注册中心的支持,如:Eureka、ZooKeeper等。推荐使用Eureka。
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
第一步:创建Maven工程:
第二步,导入依赖:
这里需要导入Spring Cloud的管理依赖。
第三步,编写程序启动类:
第四步,编写application.yml配置文件:
server:
port: 6868 #服务端口
eureka:
client:
registerWithEureka: false #是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry: false #是否从Eureka中获取注册信息
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:${server.port}/eureka/
第五步,启动程序做测试:
接下来,我们需要将商品的微服务注册到Eureka服务中。
第一步:修改pom文件,引入Spring Cloud的管理依赖以及eureka服务依赖。
第二步,修改application.yml配置文件:
server:
port: 8081 #服务端口
spring:
application:
name: itcast-microservice-item #指定服务名
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
第三步,修改启动类,增加@EnableDiscoveryClient注解:
第四步,启动测试:
至此,我们已经将自己的微服务注册到Eureka server中了。
之前我们在订单系统中是将商品微服务的地址进行了硬编码,现在,由于已经将商品服务注册到Eureka中,所以,只需要从Eureka中发现服务即可。
第一步,在订单系统中添加依赖:
第二步,修改application.yml配置文件:
server:
port: 8082 #服务端口
itcast:
item:
url: http://127.0.0.1:8081/item/
spring:
application:
name: itcasst-microservice-order #指定服务名
eureka:
client:
registerWithEureka: false #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:6868/eureka/
第三步,修改ItemService的实现逻辑:
第四步,在启动类中添加@EnableDiscoveryClient注解
第五步,启动测试
可以看到以及获取到数据,但是,我们发现响应的数据变成了xml结构。
由于我们引入了eureka server的依赖,导致破坏了之前SpringMVC默认的配置,从而导致了响应成了xml。
解决方法:排除eureka server中的xml依赖,如下:
测试结果:
在前面的示例中,我们可以看到我们需要登录即可访问到Eureka服务,这样其实是不安全的。
接下来,我们为Eureka添加用户认证。
第一步,为itcast-microservice-eureka添加安全认证依赖:
第二步,增加application.yml配置文件:
security:
basic:
enable: true #开启基于HTTP basic的认证
user: #配置用户的账号信息
name: itcast
password: itcast123
第三步,重新启动Eureka服务进行测试:
输入正确的用户名密码即可登录。
这时,服务提供者注册到Eureka时会报错:
所以,需要在服务注册时也需要设置用户名和密码。
服务注册到有认证需求的注册中心时,需要设置如下信息:
http://USER:PASSWORD@127.0.0.1:6868/eureka/
配置:
重新启动测试:
可以看到已经注册到了Eureka服务注册中心。
如图,当前Eureka进入了自我保护模式。
所以,一般进入自我保护模式,无需处理。如果,需要禁用自我保护模式,只需要在配置文件中添加配置即可:
eureka:
client:
registerWithEureka: false #是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry: false #是否从Eureka中获取注册信息
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:${server.port}/eureka/
server:
enable-self-preservation: false #禁用自我保护模式
重新启动服务查看效果:
提示,如果禁用自我保护模式,在网络通信故障下会出现问题。
前面的测试,我们会发现,Eureka服务是一个单点服务,在生产环境就会出现单点故障,为了确保Eureka服务的高可用,我需要搭建Eureka服务的集群。
搭建Eureka集群非常简单,只要启动多个Eureka服务并且让这些服务之间彼此进行注册即可实现。
第一步,修改itcast-microservice-eureka的application.yml文件:
server:
port: 6868 #服务端口
spring:
application:
name: itcasst-microservice-eureka #指定服务名
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry: true #是否从Eureka中获取注册信息
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6869/eureka/
server:
enable-self-preservation: true #禁用自我保护模式
security:
basic:
enable: true #开启基于HTTP basic的认证
user: #配置用户的账号信息
name: itcast
password: itcast123
第二步,修改配置文件,再启动一个Eureka服务,进行重启测试:
server:
port: 6869 #服务端口
spring:
application:
name: itcast-microservice-eureka #指定服务名
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry: true #是否从Eureka中获取注册信息
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6868/eureka/
server:
enable-self-preservation: true #禁用自我保护模式
security:
basic:
enable: true #开启基于HTTP basic的认证
user: #配置用户的账号信息
name: itcast
password: itcast123
测试结果:
可以看到,2个Eureka服务进行了彼此注册。
服务注册到Eureka集群时,可以指定多个,也可以指定一个Eureka服务(因为Eureka服务集群间彼此互联)。
修改itcast-microservice-item的application.yml配置文件:
server:
port: 8081 #服务端口
spring:
application:
name: itcast-microservice-item #指定服务名
logging:
level:
org.springframework: DEBUG
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6868/eureka/,http://itcast:itcast123@127.0.0.1:6869/eureka/
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
重启启动,测试:
可以通过停止Eureka服务进行测试,结果会发现集群是高可用。
在服务的提供者配置文件中可以指定ip地址,如下:
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
通过instance-id 参数指定服务注册到Eureka中的服务实例id:
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}:${server.port} #指定实例id
首先,我们思考一个问题,如果为同一个的提供者在Eureka中注册了多个服务,那么客户端该如何选择服务呢?
这时,就需要在客户端实现服务的负载均衡。
在Spring Cloud中推荐使用Ribbon来实现负载均衡。
其实,该依赖是可以省略的,因为>spring-cloud-starter-eureka-server中已经包含了spring-cloud-starter-ribbon:
这样,RestTemplate就具备了负载均衡的功能。
之前的实现:
改造后的实现:
可以发现,实现更加简化了。
测试结果:
结果显示,可以正常获取到商品数据。
内部原理:
在执行请求前会经过org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor这个拦截器,并且通过org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient中,通过serverId查找服务地址,然后在去做真正的请求。
测试方法:
第一步,启动2个itcast-microservice-item服务(多个也可以)
第二步,编写测试单元测试用例,导入测试依赖:
第三步,编写测试用例:
测试结果:
配置:
itcast-microservice-item:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
测试:
接口:com.netflix.loadbalancer.IRule,其实现类:
默认策略:
其它策略:
建议:采用默认策略即可。
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
主页:https://github.com/Netflix/Hystrix/
正常情况:
当对特定服务的呼叫达到一定阈值时(Hystrix中的默认值为5秒内的20次故障),电路打开,不进行通讯。并且是一个隔离的线程中进行的。
在itcast-microservice-order系统中增加Hystrix实现容错。
测试一切正常。
接下来,我们把商品服务停止进行测试:
可以看到,订单服务正常,但是查询商品服务已停止服务,查询到的是错误信息。
由此可见,商品服务的宕机并没有影响订单服务的正常工作,起到的容错效果。