spring cloud简单使用

spring cloud简单使用

1. SpringCloud简介

1.1 什么是SpringCloud

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简 化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、 熔断器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并 没有重复制造*,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框 架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给 开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。 Spring Cloud项目的官方网址:http://projects.spring.io/spring-cloud/

1.2 SpringCloud与SpringBoot的关系

Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单 个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具;Spring Boot专 注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架; Spring Boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就 不配置,Spring Cloud很大的一部分是基于Spring Boot来实现,可以不基于Spring Boot 吗?不可以。 Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开 Spring Boot,属于依赖的关系。

1.3 SpringCloud主要框架

  • 服务发现——Netflix Eureka
  • 服务调用——Netflix Feign
  • 熔断器——Netflix Hystrix
  • 服务网关——Netflix Zuul
  • 分布式配置——Spring Cloud Config
  • 消息总线 —— Spring Cloud Bus

1.4 Spring Cloud和Dubbo对比

或许很多人会说Spring Cloud和Dubbo的对比有点不公平,Dubbo只是实现了服务治 理,而Spring Cloud下面有17个子项目(可能还会新增)分别覆盖了微服务架构下的方 方面面,服务治理只是其中的一个方面,一定程度来说,Dubbo只是Spring Cloud Netflix中的一个子集。

Dubbo Spring Cloud
服务注册中心 Zookeeper Spring Cloud Netflix Eureka
服务调用方式 RPC REST API
服务网关 Spring Cloud Netflix Zuul
熔断器 不完善 Spring Cloud Netflix Hystrix
分布式配置 Spring Cloud Config
服务跟踪 Spring Cloud Sleuth
消息总线 Spring Cloud Bus
数据流 Spring Cloud Stream
批量任务 Spring Cloud Task
…… …… ……

1.5 说说SpringCloud的版本

我们目前课程采用的SpringCloud版本为Finchley.M9 。你可能会觉得这个版本怎么这么 奇怪?SpringCloud由于是一系列框架组合,为了避免与包含的自框架版本产生混淆,采 用伦敦地铁站的名称作为版本名,形式为版本名+里程碑号。 M9为第9个里程碑版本。 以下是SpringBoot与Spring Cloud版本的对照表,大家看看有没有找到什么规律呢?

Spring Boot Spring Cloud
1.2.x Angel版本
1.3.x Brixton版本
1.4.x Camden版本
1.5.x Dalston版本、Edgware版本
2.0.x Finchley版本

2. 服务发现组件 Eureka

2.1 Eureka

Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目 spring-cloud-netflix中,实现SpringCloud的服务发现功能。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通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活 性和可伸缩性。

2.2 Eureka服务端开发

(1)创建tensquare_eureka模块
(2)引入依赖 父工程pom.xml定义SpringCloud版本

<dependencyManagement> 
  <dependencies> 
    <dependency> 
      <groupId>org.springframework.cloud</groupId>  
      <artifactId>spring‐cloud‐dependencies</artifactId>  
      <version>Finchley.M9</version>  
      <type>pom</type>  
      <scope>import</scope> 
    </dependency> 
  </dependencies> 
</dependencyManagement>

tensquare_eureka模块pom.xml引入eureka-server

<dependencies> 
  <dependency> 
    <groupId>org.springframework.cloud</groupId>  
    <artifactId>spring‐cloud‐starter‐netflix‐eureka‐ server</artifactId> 
  </dependency> 
</dependencies>

(2)添加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/

(3)编写启动类
创建包com.tensquare.eureka ,包下建立类

@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer.class, args);
    }
}

(4)启动运行启动类,然后在浏览器地址栏输入 http://localhost:6868/ 运行效果如 下:
spring cloud简单使用
主界面中system status为系统信息 General Info为一般信息 Instances currently registered with Eureka为注册到的所有微服务列表

2.3 服务注册

我们现在就将所有的微服务都注册到Eureka中,这样所有的微服务之间都可以互相调用了。
(1)将其他微服务模块添加依赖

<dependency> 
  <groupId>org.springframework.cloud</groupId>  
  <artifactId>spring‐cloud‐starter‐netflix‐eureka‐ client</artifactId> 
</dependency>

(2)修改每个微服务的application.yml,添加注册eureka服务的配置

eureka:
	client:
		defaultZone:  http://localhost:6868/eureka
	instance:
		prefer‐ip‐address: true

(3)修改每个服务类的启动类,添加注解

@EnableEurekaClient

(4)启动测试:将每个微服务启动起来,会发现eureka的注册列表中可以看到这些微服 务了
spring cloud简单使用

2.4 保护模式

如果在Eureka Server的首页看到以下这段提示,则说明Eureka已经进入了保护模式:
spring cloud简单使用
Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出 现低于的情况(在单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳 定导致),Eureka Server会将当前的实例注册信息保护起来,同时提示这个警告。保护 模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保 护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的 数据(也就是不会注销任何微服务)。
在Spring Cloud中,可以使用eureka.server.enable-self-preservation=false来禁用自我保护模式

2.5 Eureka的健康检查

先看下图:
spring cloud简单使用
说明:在Status栏显示着UP,表示应用程序状态正常。其它取值DOWN、OUT_OF_SERVICE、UNKNOWN等,只有UP的微服务会被请求。

由于Eureka Server与Eureka Client之间使用心跳机制来确定Eureka Client的状态,默认情况下,服务器端与客户端的心跳保持正常,应用程序就会始终保持“UP”状态,所以微服务的UP并不能完全反应应用程序的状态。

Spring Boot Actuator提供了/health端点,该端点可展示应用程序的健康信息,只有将该端点中的健康状态传播到Eureka Server就可以了,实现这点很简单,只需为微服务配置如下内容:
eureka.client.healthcheck.enabled = true

如果需要更细粒度健康检查,可实现com.netflix.appinfo.HealthCheckHandler接口 。 EurekaHealthCheckHandler 已实现了该接口

3. Feign实现服务间的调用

3.1 Feign简介

Feign是简化Java HTTP客户端开发的工具(java-to-httpclient-binder),它的灵感 来自于Retrofit、JAXRS-2.0和WebSocket。Feign的初衷是降低统一绑定Denominator到 HTTP API的复杂度,不区分是否为restful。

3.2 快速体验

我们现在在问答微服务调用基础微服务的方法(根据ID查询标签)
(1)在tensquare_qa模块添加依赖

<dependency> 
  <groupId>org.springframework.cloud</groupId>  
  <artifactId>spring‐cloud‐starter‐openfeign</artifactId> 
</dependency>

(2)修改tensquare_qa模块的启动类,添加注解

@EnableDiscoveryClient 
@EnableFeignClients

(3)在tensquare_qa模块创建 com.tensquare.qa.client包,包下创建接口

@FeignClient("tensquare‐base")
public interface LabelClient {
    @RequestMapping(value = "/label/{id}", method = RequestMethod.GET)
    public Result findById(@PathVariable("id") String id);
}

@FeignClient注解用于指定从哪个服务中调用功能 ,注意 里面的名称与被调用的服务 名保持一致,并且不能包含下划线。
@RequestMapping注解用于对被调用的微服务进行地址映射。注意 @PathVariable注 解一定要指定参数名称,否则出错

(5)修改tensquare_qa模块的 ProblemController

@Autowired 
private LabelClient labelClient;

@RequestMapping(value = "/label/{labelid}")
public Result findLabelById(@PathVariable String labelid){
		Result result = labelClient.findById(labelid);
		return result;
}

3.3 负载均衡

测试:同时开启多个基础微服务,看是否是轮流调用。

修改tensquare_base工程LabelController的findById方法

@RequestMapping(value="/{id}", method = RequestMethod.GET)
public Result findById(@PathVariable String id){
		System.out.println("No.1");
		return new Result(true,StatusCode.OK,"查询成功",labelService.findById(id) )
}

启动基础微服务后,修改端口和输出信息,再次启动基础微服务

启动问答微服务,浏览器执行http://localhost:9003/problem/label/1 看是否轮流启动。

4. 熔断器Hystrix

4.1 为什么要使用熔断器

在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障, 进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种 因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A 不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成 了。
spring cloud简单使用
如何避免产生这种雪崩效应呢?我们可以使用Hystrix来实现熔断器。

4.2 什么是Hystrix

Hystrix [hɪst’rɪks]的中文含义是豪猪, 因其背上长满了刺,而拥有自我保护能力
spring cloud简单使用
Hystrix 能使你的系统在出现依赖服务失效的时候,通过隔离系统所依赖的服务,防 止服务级联失败,同时提供失败回退机制,更优雅地应对失效,并使你的系统能更快地 从异常中恢复。
了解熔断器模式请看下图:
spring cloud简单使用

4.3 快速体验

Feign 本身支持Hystrix,不需要额外引入依赖。
(1)修改tensquare_qa模块的application.yml ,开启hystrix

feign:
	hystrix:
		enabled: true

(2)在com.tensquare.qa.client包下创建impl包,包下创建熔断实现类,实现自接口 LabelClient

@Component
public class LabelClientImpl implements LabelClient {
    @Override
    public Result findById(String id) {
        return new Result(false, StatusCode.ERROR, "熔断器启动了");
    }
}

(3)修改LabelClient的注解

@FeignClient(value="tensquare‐base",fallback = LabelClientImpl.class)

(4)测试运行
重新启动问答微服务,测试看熔断器是否运行。

5. 微服务网关Zuul

5.1 为什么需要微服务网关

不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才 能完成一个业务需求。比如一个电影购票的收集APP,可能回调用电影分类微服务,用户 微服务,支付微服务等。如果客户端直接和微服务进行通信,会存在一下问题:

  • 客户端会多次请求不同微服务,增加客户端的复杂性
  • 存在跨域请求,在一定场景下处理相对复杂
  • 认证复杂,每一个服务都需要独立认证
  • 难以重构,随着项目的迭代,可能需要重新划分微服务,如果客户端直接和微服务通 信,那么重构会难以实施
  • 某些微服务可能使用了其他协议,直接访问有一定困难

上述问题,都可以借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中 间层,所有的外部请求都会先经过微服务网关。

5.2 什么是Zuul

Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使 用。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求
  • 审查与监控:
  • 动态路由:动态将请求路由到不同后端集群
  • 压力测试:逐渐增加指向集群的流量,以了解性能
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  • 静态响应处理:边缘位置进行响应,避免转发到内部集群
  • 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使 用多样化

Spring Cloud对Zuul进行了整合和增强。
使用Zuul后,架构图演变为以下形式
spring cloud简单使用

5.3 Zuul路由转发

5.3.1 管理后台微服务网关

(1)创建子模块tensquare_manager,pom.xml引入eureka-client 和zuul的依赖

<dependencies> 
  <dependency> 
    <groupId>org.springframework.cloud</groupId>  
    <artifactId>spring‐cloud‐starter‐netflix‐eureka‐ client</artifactId> 
  </dependency>  
  <dependency> 
    <groupId>org.springframework.cloud</groupId>  
    <artifactId>spring‐cloud‐starter‐netflix‐zuul</artifactId> 
  </dependency> 
</dependencies>

(2)创建application.yml

server:
	port: 9011
spring:
	application:
		name:  tensquare‐manager #指定服务名
euraka:
	client:
		serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
			defaultZone: http://127.0.0.1:6868/eureka/
	instance: 
		prefer-ip-address: true
zuul:
	routes:
		tensquare‐gathering: #活动
			path: /gathering/** #配置请求URL的请求规则
			serviceId: tensquare‐gathering #指定Eureka注册中心中的服务id
		tensquare‐article: #文章
			path: /article/** #配置请求URL的请求规则
			serviceId: tensquare‐article #指定Eureka注册中心中的服务id

(3)编写启动类

@EnableZuulProxy
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

5.3 Zuul路由转发

5.3.1 Zuul过滤器快速体验

我们现在在tensquare_web 创建一个简单的zuul过滤器

@Component 
public class WebFilter extends ZuulFilter {

@Override public String filterType() { return "pre";// 前置过滤器 }
@Override public int filterOrder() { return 0;// 优先级为0,数字越大,优先级越低 }
@Override public boolean shouldFilter() { return true;// 是否执行该过滤器,此处为true,说明需要过滤 }
@Override public Object run() throws ZuulException { System.out.println("zuul过滤器..."); return null; }
}

启动tensquare_web会发现过滤器已经执行
filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:

  • pre :可以在请求被路由之前调用
  • route :在路由请求时候被调用
  • post :在route和error过滤器之后被调用
  • error :处理请求时发生错误时被调用

filterOrder :通过int值来定义过滤器的执行顺序

shouldFilter :返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可 实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效

run :过滤器的具体逻辑。

6. 集中配置组件SpringCloudConfig

6.1 Spring Cloud Config简介

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所 以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库 中。
在spring cloud config 组件中,分两个角色:
一是config server
二是config client。

Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个 环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件 存储。

Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。 微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器。

详细内容看在线文档: https://springcloud.cc/spring-cloud-config.html

6.2 配置服务端

6.2.1 将配置文件提交到码云

使用GitHub时,国内的用户经常遇到的问题是访问速度太慢,有时候还会出现无法连接 的情况。如果我们希望体验Git飞一般的速度,可以使用国内的Git托管服务——码云 (gitee.com)。
和GitHub相比,码云也提供免费的Git仓库。此外,还集成了代码质量检测、项目演示等 功能。对于团队协作开发,码云还提供了项目管理、代码托管、文档管理的服务。
步骤:
(1)浏览器打开gitee.com,注册用户 ,注册后登陆码云管理控制台
spring cloud简单使用
(2)创建项目 tensquare-config (点击右上角的加号 ,下拉菜单选择创建项目)
(3)上传配置文件,将tensquare_base工程的application.yml改名为base-dev.yml后 上传
spring cloud简单使用
可以通过拖拽的方式将文件上传上去
文件命名规则:
{application}-{profile}.yml或{application}-{profile}.properties
application为应用名称 profile指的开发环境(用于区分开发环境,测试环境、生产环境 等)
(4)复制git地址 ,备用

6.2.2 配置中心微服务

(1)创建工程模块 配置中心微服务 tensquare_config ,pom.xml引入依赖

<dependencies> 
  <dependency> 
    <groupId>org.springframework.cloud</groupId>  
    <artifactId>spring‐cloud‐config‐server</artifactId> 
  </dependency> 
</dependencies>

(2)创建启动类ConfigServerApplication

@EnableConfigServer //开启配置服务
@SpringBootApplication
public class ConfigServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConfigServerApplication.class, args);
		}
	}

(3)编写配置文件application.yml

spring:
	application:
		name: tensquare‐config
	cloud: 
		config:
			server:
				git: 
					url: https://gitee.com/chuanzhiliubei/tensquare‐config.git
server: 
	port: 12000

(4)浏览器测试:http://localhost:12000/base-dev.yml 可以看到配置内容

6.3 配置客户端

(1)在tensquare_base工程添加依赖

<dependency> 
  <groupId>org.springframework.cloud</groupId>  
  <artifactId>spring‐cloud‐starter‐config</artifactId> 
</dependency>

(2)添加bootstrap.yml ,删除application.yml

spring: 
	cloud: 
		config:
			name: base
			profile: dev 
			label: master
			uri:  http://127.0.0.1:12000

(3)测试: 启动工程tensquare_eureka tensquare_config tensquare_base,看是 否可以正常运行
http://localhost:9001/label

7. 消息总线组件SpringCloudBus

7.1 SpringCloudBus简介

如果我们更新码云中的配置文件,那客户端工程是否可以及时接受新的配置信息 呢?我们现在来做有一个测试,修改一下码云中的配置文件中mysql的端口 ,然后测试 http://localhost:9001/label 数据依然可以查询出来,证明修改服务器中的配置并没有更 新立刻到工程,只有重新启动程序才会读取配置。 那我们如果想在不重启微服务的情况 下更新配置如何来实现呢? 我们使用SpringCloudBus来实现配置的自动更新。

7.2 代码实现

7.2.1 配置服务端

(1)修改tensquare_config工程的pom.xml,引用依赖

<dependency> 
  <groupId>org.springframework.cloud</groupId>  
  <artifactId>spring‐cloud‐bus</artifactId> 
</dependency>
<dependency> 
  <groupId>org.springframework.cloud</groupId>  
  <artifactId>spring‐cloud‐stream‐binder‐rabbit</artifactId> 
</dependency>

(2)修改application.yml ,添加配置

	rabbitmq:
		host:  192.168.184.135
management: #暴露触发消息总线的地址
	endpoints:
		web:
			exposure:
				include: bus‐refresh

7.2.2 配置客户端

我们还是以基础模块为例,加入消息总线
(1)修改tensquare_base工程 ,引入依赖

<dependency> 
  <groupId>org.springframework.cloud</groupId>  
  <artifactId>spring‐cloud‐bus</artifactId> 
</dependency>
<dependency> 
  <groupId>org.springframework.cloud</groupId>  
  <artifactId>spring‐cloud‐stream‐binder‐rabbit</artifactId> 
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring‐boot‐starter‐actuator</artifactId>
</dependency>

(2)在码云的配置文件中配置rabbitMQ的地址:

rabbitmq: 
	host: 192.168.184.135

(3)启动tensquare_eureka 、tensquare_config和tensquare_base 看是否正常运行
(4)修改码云上的配置文件 ,将数据库连接IP 改为127.0.0.1 ,在本地部署一份数据 库。
(5)postman测试 Url: http://127.0.0.1:12000/actuator/bus-refresh Method: post
(6)再次观察输出的数据是否是读取了本地的mysql数据。

7.2.3 自定义配置的读取

(1)修改码云上的配置文件,增加自定义配置

sms:
	ip: 127.0.0.1

(2)在tensquare_base工程中新建controller

@RestController
public class TestController {
    @Value("${sms.ip}")
    private String ip;

    @RequestMapping(value = "/ip", method = RequestMethod.GET)
    public String ip() {
        return ip;
    }
}

(3)运行测试看是否能够读取配置信息 ,OK.
(4)修改码云上的配置文件中的自定义配置

sms:
	ip: 192.168.184.134

(5)通过postman测试 Url: http://127.0.0.1:12000/actuator/bus-refresh Method: post
测试后观察,发现并没有更新信息。
这是因为我们的 controller少了一个注解@RefreshScope 此注解用于刷新配置

@RefreshScope
@RestController
public class TestController {
    @Value("${sms.ip}")
    private String ip;

    @RequestMapping(value = "/ip", method = RequestMethod.GET)
    public String ip() {
        return ip;
    }
}

添加后再次进行测试。

上一篇:WCF初探-9:WCF服务承载 (下)


下一篇:url中#号的作用