SpringCloud个人总结

一下内容就是个人学习sc微服务架构中的学习总结,整个架构的东西很多,大家可以在需要某个组件时再去学习。

一、为什么需要微服务

我么那首先思考下面这些问题,为什么需要微服务,微服务能够解决什么痛点,它有什么优缺点?微服务和微服务架构是什么关系?什么是分布式?什么是集群?为了解决这些问题我们得从实现一个系统的架构的到底发生了那些演变说起。

1.1分布式的演化

1.1.1单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

优点:

适用于小型网站,小型管理系统,将所有功能都部署到一个工程里,简单易用,易于开发

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

4、 只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块;

5、系统耦合性强,一旦其中一个模块有问题,整个系统就瘫痪了;一旦升级其中一个模块,整个系统就停机了;

6、 集群只能是复制整个系统,即使只是其中一个模块压力大。(可能整个订单处理,仅仅是支付模块压力过大,按道理只需要升级支付模块,但是在单一场景里面是不能的)

那么这个时候我么那肯定产生了想法,就是把所有功能模块切开,分而治之,那么就演变成了下面的架构。

1.1.2 垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用**拆成互不相干**的几个应用,以提升效率,**这样就可以单独修改某个模块而不用重启或者影响其他模块,同时也可以给某个访问量剧增的模块,单独添加服务器部署集群**。此时,用于加速前端页面开发的Web框架(MVC)是关键。

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点:

公用模块无法重复利用,开发性的浪费(存在重复开发的问题)

面对突变的应用场景,可能某个模块对于web界面会频繁修改,但是模块业务功能没有变化,这样会造成单个应用频繁修改。所以需要界面+业务逻辑的实现分离。

没有处理好应用之间的交互问题,系统之间相互独立,例如订单模块可能会需要查询商品模块的信息。

这个时候,虽然切分了各个模块,但是没有很好地考虑到服务之间的引用等等问题。

1.1.3 分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

例如我们常见的springcloud和dubbo就是属于分布式服务架构,但是严格上来讲dubbo并不是属于分布式架构,因为他并不具备分布式架构的某些特性,例如服务的分布式配置,服务网关,数据流,批量任务等等。一般认为Dubbo只是相当于SpringCloud中的Eureka模块(服务注册中心)

分布式服务框架很好的解决了垂直应用架构的缺点,实现界面和服务的分离,实现界面和服务,以及服务与服务之间的调度。

但是存在问题,那就是没有一个**统一管理服务的机制和基于访问压力的调度中心(服务注册中心,负载均衡)**,容易造成资源浪费,什么意思呢?假设用户服务部署了200台服务器,但是在某个时间段,他的访问压力很小,订单服务的访问压力剧增,服务器不够用。那么就会造成资源浪费和倾斜,存在服务器闲置或者请求量少的情况。

1.1.4 流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)Service Oriented Architecture]是关键

1525530804753

以前出现了什么问题?

  • 服务越来越多,需要管理每个服务的地址
  • 调用关系错综复杂,难以理清依赖关系
  • 服务过多,服务状态难以管理,无法根据服务情况动态管理

服务治理要做什么?

  • 服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
  • 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
  • 动态监控服务状态监控报告,人为控制服务状态

缺点:

  • 服务间会有依赖关系,一旦某个环节出错会影响较大
  • 服务关系复杂,运维、测试部署困难,不符合DevOps思想



1.1.5 微服务架构

前面说的SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实却有一些差别:

微服务的特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
  • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
  • 面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  • 自治:自治是说服务间互相独立,互不干扰
    • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
    • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
    • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
    • 数据库分离:每个服务都使用自己的数据源
    • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护

微服务结构图:

1526860071166

1.2 基本概念梳理

分布式:一个业务分拆多个子业务,部署在不同的服务器上

集群: 同一个业务,部署在多个服务器上

微服务: 微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毁,可以拥有自己独立的数据库。(我们之前使用springboot开发的项目就是属于一个微服务,他是单一进程,处理单一服务。-他关注的是单一业务的实现细节

微服务架构:微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值.每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API).每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等.另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建.(springcloud就是一个微服务架构,通过一系列措施,管理微服务,实现系统整体的功能-他关注的是整体项目的实现和架构

微服务提出者:马丁.福勒(Martin Fowler)

论文网址:https://martinfowler.com/articles/microservices.html

1.3 微服务优缺点

优点:

每个服务足够内聚,足够小,代码容易理解这样能聚焦一个指定的业务功能或业务需求

开发简单、开发效率提高,一个服务可能就是专一的只干一件事.

微服务能够被小团队单独开服,这个小团队是2到5人的开发人员组成

微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的.

微服务能试用不同的语言开发

易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如Jenkins,Hudson,bamboo.

微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果.无需通过合作才能体现价值

微服务允许你利用融合最新技术.

微服务只是业务逻辑的代码,不会和HTML,CSS或其他界面组件混合.

每个微服务都有自己的存储能力,可以有自己的数据库.也可以有统一的数据库

缺点:

开发人员要处理分布式系统的复杂性

多服务运维难度,随着服务的增加,运维的压力也在增大

系统部署依赖

服务间通信成本

数据一致性

系统集成测试

性能监控

1.4 常见微服务架构

微服务条目 落地技术
服务开发 SpringBoot,Spring,SpringMVC
服务配置与管理 Netflix公司的Archaius、阿里的Diamond等
服务注册与发现 Eureka、Consul、Zookeeper等
服务调用 Rest、RPC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Feign等
消息队列 Kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Specatator等
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpenStack、Kubernetes等
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息)
事件消息总线 SpringCloud Bus

二.springcloud和dubbo

为什么现在流行的微服务架构是springcloud而不是dubbo,最主要的是dubbo在这之前停止更新过几年的时间,这个时候springcloud异军突起,很好地抢占了先机,整体解决方案和框架成熟度,社区热度,可维护性,学习曲线也是它更加火爆的原因:

最主要的是,Dubbo 的定位始终是一款 RPC 框架,目的是提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案

然而:Spring Cloud 的目标是微服务架构下的一站式解决方案,换句话说,dubbo更像是springcloud的Eureka模块。

接下来我么你看一段关于Dubbo目前负责人刘军的一段采访

https://www.oschina.net/question/2896879_2272652?sort=time

当前各大IT公司用的微服务架构有哪些

阿里Dubbo/HSF
京东JSF
新浪微博Motan
当当网DubboX

1.各微服务的框架对比

功能点/服务框架 Netflix/SpringCloud Motan gRPC Thrift Dubbo/DubboX
功能定位 完整的微服务架构 RPC框架,但整合了ZK或Consul,实现集群环境的基本服务注册/发现 RPC框架 RPC框架 服务框架
支持Rest 是,Ribbon支持多种可插拔的序列化选择
支持RPC
支持多语言 是(Rest形式)
服务注册/发现 是(Eureka) Eureka服务注册表,Karyon服务端框架支持服务自注册和健康检查 是(zookeeper/consul)
负载均衡 是(服务端zuul+客户端Ribbon) zuul-服务,动态路由 云端负载均衡 Eureka(针对中间层服务器) 是(客户端) 是(客户端)
配置服务 Netflix Archaius SpringCloud Config Server集中配置 是(zookeeper提供)
服务调用链监控 是(zuul) Zuul提供边缘服务,API网关
高可用/容错 是(服务端Hystrix+客户端Ribbon) 是(客户端) 是(客户端)
典型应用案例 Netflix Sina Google Facebook
社区活跃度 一般 一般 2017年7月才重启
学习难度 中等 一般 一般
文档丰富度 一般 一般 一般
其他 Spring Cloud Bus为我们应用程序带来了更多管理端点 支持降级 Netflix内部在开发集成gRPC IDL定义 实践公司比较多

2. springcloud VS Dubbo

社区活跃度

https://github.com/dubbo
https://github.com/springcloud

功能对比

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

最大区别:

  • Spring Cloud抛弃了RPC通讯,采用基于HTTP的REST方式。Spring Cloud牺牲了服务调用的性能,但是同时也避免了原生RPC带来的问题。REST比RPC更为灵活,不存在代码级别的强依赖,在强调快速演化的微服务环境下,显然更合适。
  • ==一句话:Dubbo像组装机,Spring Cloud像一体机==
  • 社区的支持与力度:Dubbo曾经停运了5年,虽然重启了,但是对于技术发展的新需求,还是需要开发者自行去拓展,对于中小型公司,显然显得比较费时费力,也不一定有强大的实力去修改源码

总结

  1. 解决的问题域不一样:Dubbo的定位是一款RPC框架,Spring Cloud的目标是微服务架构下的一站式解决方案

三.服务调用方式

1.RPC和HTTP

无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?

常见的远程调用方式有以下2种:

  • RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表

  • Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。

    现在热门的Rest风格,就可以通过http协议来实现。

如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。

相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么SpringCloud搭建微服务是不二之选。在我们的项目中,我们会选择SpringCloud套件,因此我们会使用Http方式来实现服务间调用。

2.Http客户端工具

既然微服务选择了Http,那么我们就需要考虑自己来实现对请求和响应的处理。不过开源世界已经有很多的http客户端工具,能够帮助我们做这些事情,例如:

  • HttpClient
  • OKHttp
  • URLConnection

接下来,不过这些不同的客户端,API各不相同

3.Spring的RestTemplate

Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:

  • HttpClient
  • OkHttp
  • JDK原生的URLConnection(默认的)

RestTemplate简单使用

首先在项目中注册一个RestTemplate对象,可以在启动类位置注册:

@SpringBootApplication
public class HttpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HttpDemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

在测试类中直接@Autowired注入:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {
@Autowired
private RestTemplate restTemplate;
@Test
public void httpGet() {
// 调用springboot案例中的rest接口
User user = this.restTemplate.getForObject("http://localhost/user/1", User.class);
System.out.println(user);
}
}
  • 通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。

1525573702492

学习完了Http客户端工具,接下来就可以正式学习微服务了。

四.初识SpringCloud

微服务是一种架构方式,最终肯定需要技术架构去实施。

微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?

  • 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
  • 技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了
  • 群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
  • 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建

1.简介

SpringCloud是Spring旗下的项目之一,官网地址:http://projects.spring.io/spring-cloud/

官网介绍:https://spring.io/

SpringCloud,基于springboot提供了一套为服务解决方案.

Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。

SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:

  • Eureka:服务治理组件,包含服务注册中心,服务注册与发现机制的实现。(服务治理,服务注册/发现)
  • Zuul:网关组件,提供智能路由,访问过滤功能
  • Ribbon:客户端负载均衡的服务调用组件(客户端负载)
  • Feign:服务调用,给予Ribbon和Hystrix的声明式服务调用组件 (声明式服务调用)
  • Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。(熔断、断路器,容错)

架构图:

1525575656796

以上只是其中一部分。

SpringCloud,基于springboot提供了一套为服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件.

SpringCloud利用springboot的开发便利性巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等,它们都可以利用Springboot的开发风格做到一键启动和部署.

SpringBoot并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Springboot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包.

2.SpringCloud和springboot是什么关系

Springboot专注于快速方便的开发单个个体微服务.

SpringCloud是关注全局的微服务协调整理治理框架,它将Springboot开发的一个个单体微服务整合并管理起来,为各个微服务质检提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务

Springboot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开Springboot,属于依赖的关系.
Springboot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架.

3.springcloud的版本

因为Spring Cloud不同其他独立项目,它拥有很多子项目的大项目。所以它的版本是版本名+版本号 (如Angel.SR6)。

版本名:是伦敦的地铁名

版本号:SR(Service Releases)是固定的 ,大概意思是稳定版本。后面会有一个递增的数字。

所以 Edgware.SR3就是Edgware的第3个Release版本。

1528263985902

我们在项目中,会是以Finchley的版本。

其中包含的组件,也都有各自的版本,如下表:

Component Edgware.SR3 Finchley.RC1 Finchley.BUILD-SNAPSHOT
spring-cloud-aws 1.2.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-bus 1.3.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-cli 1.4.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-commons 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-contract 1.2.4.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-config 1.4.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-netflix 1.4.4.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-security 1.2.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-cloudfoundry 1.1.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-consul 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-sleuth 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-stream Ditmars.SR3 Elmhurst.RELEASE Elmhurst.BUILD-SNAPSHOT
spring-cloud-zookeeper 1.2.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-boot 1.5.10.RELEASE 2.0.1.RELEASE 2.0.0.BUILD-SNAPSHOT
spring-cloud-task 1.2.2.RELEASE 2.0.0.RC1 2.0.0.RELEASE
spring-cloud-vault 1.1.0.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-gateway 1.0.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-openfeign 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT

1528263942152

4.SpringCloud的参考资料

五.springcloud的实现准备

为了下面使用springcloud微服务架构各个优秀的组件,我们先搭建一个基本的分布式项目工程。

案例使用的springcloud和springboot版本分别是:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

1 新建父工程-microservicecloud

microservicecloud

img

img

img

打包方式设置为pom

设置pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kingge.springcloud</groupId>
<artifactId>microservicecloud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

查看项目

img

新建父工程目的:定义pom文件,统一各个子模块的jar依赖版本,方面管理,避免每个子模块使用相同组件不同版本,造成项目测试运行出现问题。

2 根据父工程,新建api公共模块-microservicecloud-api

目的:抽取出所有子项目公共的bean或者方法。例如在下面的创建的服务提供者和服务消费者都是用到了Person这个javabean,那么我们就需要把这个bean放在这里。然后服务提供者和消费者就可以依赖这个api公共模块,从而实用Person实体类。

img

img

img

创建完毕

2.1 查看父工程pom

img

发现多了这一行,因为我们是在父工程microserviceproject下新建的,表示microservicecloud-api工程为父工程子模块。

2.2 修改microservicecloud-api的pom文件

修改内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>microservicecloud</artifactId>
<groupId>com.kingge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>microservicecloud-api</artifactId>
<dependencies><!-- 当前Module需要用到的jar包,按自己需求添加,如果父类已经包含了,可以不用写版本号 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>//这个组件可用可不用
</dependencies>
</project>

Lombok组件在真实项目中不建议使用,虽然他简化了javabean的开发,但是代码的可读性也产生了严重的影响。

2.3 新建公共bean-Person

img

生成get/set方法

2.4 项目结构

img

3 根据父工程,新建微服务提供者-provider

microservicecloud-provider-person-8001 –> 8001表示服务暴露的端口号

新建方法同新建-microservicecloud-api 模块

3.1 修改pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>microservicecloud</artifactId>
<groupId>com.kingge.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>microservicecloud-provider-person-8001</artifactId>
<dependencies>
<!-- 引入自己定义的api通用包,可以使用Person用户Entity -->
<dependency>
<groupId>com.kingge.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>

3.2 新建application.yml文件

img

内容是:详细的参见代码

server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.kingge.entity # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件
spring:
application:
name: microservicecloud-person #很重要,对外暴露的微服务的名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://127.0.0.1:3306/test # 数据库名称
username: root
password: 123
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200

注意每个属性后面必须是有空格-yml文件的格式

3.3 新建person数据表

DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
`deptno` int(255) NOT NULL AUTO_INCREMENT,
`dname` varchar(255) DEFAULT NULL,
`db_source` varchar(255) DEFAULT NULL, //这和字段标识当前数据来源于那个数据库,后面讲解Eureka集群时会使用到
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('1', '开发部', 'test');
INSERT INTO `person` VALUES ('2', '人事部', 'test');
INSERT INTO `person` VALUES ('3', '集成部', 'test');
INSERT INTO `person` VALUES ('4', '市场部', 'test');

3.4 新建dao和mapper

img

内容

import com.kingge.entity.Person;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface PersonDao {
public boolean addDept(Person dept);
public Person findById(Long id);
public List<Person> findAll();
}

3.4.1 新建mybatis.cfg.xml

实际上我们不需要这个xml文件,因为我们的配置一般都是已经放置在了application.yml文件中,但是为了整体架构的扩展性,这里也新建了改文件

img

img

3.5 新建PersonMapper.xml

img

存放mapper.xml文件的位置我们在上面application.yml中已经声明

内容

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kingge.dao.PersonDao">
<select id="findById" resultType="Person" parameterType="Long">
select deptno,dname,db_source from person where deptno=#{deptno};
</select>
<select id="findAll" resultType="Person ">
select deptno,dname,db_source from person;
</select>
<insert id="addDept" parameterType="Person ">
INSERT INTO person(dname,db_source) VALUES(#{dname},DATABASE());
</insert>
</mapper>

3.6 新建PersonService接口和接口实现类impl

img

PersonService 接口

public interface PersonService{
publicboolean add(Person dept);
public Personget(Long id);
publicList<Person> list();
}

PersonServiceImpl 实现类

@Service
public class PersonServiceImpl implements PersonService{
@Autowired
private PersonDao dao;
@Override
public boolean add(Person person){
return dao.addDept(person);
}
@Override
public Person get(Long id){
return dao.findById(id);
}
@Override
public List<Person> list(){
return dao.findAll();
}
}

3.7 controller控制层-PersonController

代码实现:

@RestController
public class PersonController
{
@Autowired
private PersonService service;
//全部使用restful风格,返回json字符串
@RequestMapping(value = "/person/add", method = RequestMethod.POST)
public boolean add(@RequestBody Person person)
{
return service.add(person);
}
@RequestMapping(value = "/person/get/{id}", method = RequestMethod.GET)
public Person get(@PathVariable("id") Long id)
{
return service.get(id);
}
@RequestMapping(value = "/person/list", method = RequestMethod.GET)
public List<Person> list()
{
return service.list();
}
}

3.8 springboot启动类

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

启动服务并测试访问。

img

img

3.9 项目整体结构

img

4 根据父工程,新建微服务消费者-consumer

microservicecloud-consumer-person-80 方法同上

4.1 修改pom文件

<dependencies>
<dependency><!-- 自己定义的api -->
<groupId>com.kingge.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.7.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>

4.2 添加application.yml文件

添加端口配置

server:

port: 80

4.3 新建@Configuration注解的配置类

@Configuration
public class ConfigBean
{
@Bean
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}

它的作用就是我们整合SSM的时候,对应的applicationContext.xml

img

4.4 新建消费者控制器,访问服务提供者获取数据

通过RestTemplate获取消息提供者暴露的服务信息。

@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://localhost:8001";//这里通过书写固定的服务提供者的地址,后面我们学习到了Eureka服务注册中心,那么注重修改这里
@RequestMapping(value = "/consumer/person/add")
public boolean add(Person person)
{
return restTemplate.postForObject(REST_URL_PREFIX + "/person/add", person, Boolean.class);
}
@RequestMapping(value = "/consumer/person/get/{id}")
public Person get(@PathVariable("id") Long id)
{
return restTemplate.getForObject(REST_URL_PREFIX + "/person/get/" + id, Person.class);
}
@SuppressWarnings("unchecked")
@RequestMapping(value = "/consumer/person/list")
public List<Person> list()
{
return restTemplate.getForObject(REST_URL_PREFIX + "/person/list", List.class);
}
}

4.5 新建启动类

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

4.6 完整项目结构

1565962178357

5 同时启动服务提供者和服务消费者

也就是运行ApplicationBootStart8001和ApplicationBootStart80启动类即可

测试服务是否可以消费,访问消费者

img

img

img

测试成功,以上就是成功的搭建了一个简单的微服务架构,下面我们会逐步的加入springcloud的其他组件,完善这个架构

5.上面的项目存在什么问题

存在什么问题?

  • 在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
  • consumer需要记忆provider的地址,如果出现变更,可能得不到通知,地址将失效
  • consumer不清楚provider的状态,服务宕机也不知道
  • provider只有1台服务,不具备高可用性
  • 即便provider形成集群,consumer还需自己实现负载均衡

其实上面说的问题,概括一下就是分布式服务必然要面临的问题:

  • 服务管理
    • 如何自动注册和发现
    • 如何实现状态监管
    • 如何实现动态路由
  • 服务如何实现负载均衡
  • 服务如何解决容灾问题
  • 服务如何实现统一配置

以上的问题,我们都将在SpringCloud中得到答案。

六.Eureka注册中心

**首先我们来解决第一问题,服务的管理 - 利用Eureka解决服务治理问题**

1.Eureka概念

Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。**服务注册与发现对于微服务架构来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务(解决上诉案例在consumer中,我们把服务提供者的url地址硬编码到了代码中)**,而不需要修改服务调用的配置文件了。功能类似于dubbo的注册中心,比如Zookeeper。



Netflix在设计Eureka时遵守的就是CAP规则中的AP原则。

CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得

特别提示:同样作为dubbo服务注册中心的zookeeper遵守的是CP原则。

2.Eureka架构和原理

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现(请对比Zookeeper)。

Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心

而系统中的其他微服务,使用 Eureka 的客户端(上诉案例中的,服务提供者和服务消费者相对于EurekaServer都是属于客户端,前者是向EurekaServer注册服务,后者是向EurekaServer获取服务)连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。SpringCloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。

基本架构:

1525597885059

Eureka包含两个组件:Eureka Server和Eureka Client

Eureka Server提供服务注册服务
各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到

EurekaClient是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)

  • Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
  • 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
  • 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
  • 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态

3. 实现EurekaServer

接下来我们通过加入springcloud的Eureka服务治理组件,改造之前的例子,解决动态路由(上个例子中我们是把服务提供者的url硬编码到消费者中)、注册发现,动态监管的问题

3.1 根据父工程实现单节点Eureka服务注册中心

根据父工程microservicecloud 创建 microservicecloud-eureka-7001模块

3.1.1 修改pom文件导入EurekaServer依赖

<dependencies>
<!--eureka-server服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies

3.1.2 修改application.yml配置文件

server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
#等同于 http://localhost:7001/eureka/

3.1.3 创建启动类并添加@EnableEurekaServer注解

package com.kingge.entity;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer ////EurekaServer服务器端启动类,接受其它微服务注册进来
public class ApplicationBootStart7001 {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart7001.class, args);
}
}

3.1.4 完整项目结构

1565961802586

3.1.5 运行启动类,启动EurekaServer

访问网址:http://localhost:7001/

No application available 没有服务被发现 —- 因为没有注册服务进来当然不可能有服务被发现

接下来我门把8001模块服务注册进来

3.2 修改 服务提供者

也就是修改上面我们实现的:microservicecloud-provider-person-8001 模块,将人员服务注册进EurekaServer中

3.2.1 修改pom文件

修改内容:

<!-- 将微服务provider端注册进eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

3.2.2 修改application.yml配置文件

eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://localhost:7001/eureka #这个地址就是我们在3.1.2定义的EurekaServer对外暴露的连接地址。

3.2.3 修改启动类添加@EnableEurekaClient注解

@SpringBootApplication
@EnableEurekaClient////本服务启动后会自动注册进 eureka服务中
public class ApplicationBootStart8001 {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart8001.class, args);
}
}

3.3 修改服务消费者

通过访问服务名称的方式消费服务,解决硬编码服务提供者url的问题

也就是修改上面我们实现的:microservicecloud-consumer-person-80 模块

3.3.1 修改pom文件

修改内容:

<!-- 将微服务provider端注册进eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

3.3.2 修改application.yml配置文件

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka/

3.3.3 修改启动类添加@EnableEurekaClient注解

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

3.3.4修改ConsumerController代码

修改内容如下

// private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-PERSON";

把url更改为服务名称。

3.4 启动EurekaServer和服务提供者,以及服务消费者

运行ApplicationBootStart7001和ApplicationBootStart8001、ApplicationBootStart80

查看地址:http://localhost:7001/

1565963220408

1565966479080

注册成功!!!

服务名称就是我们在服务提供者的application.yml配置文件中配置的:

spring:
application:
name: microservicecloud-person #很重要,对外暴露的微服务的名称

我们发现生成的实例名称是没有任何意义的,而且实例的介绍地址不是ip的形式

1565963618268

下面就讲解修改这些小细节

4.actuator与注册微服务信息完善

4.1 修改服务实例名和实例名访问路径显示ip

修改microservicecloud-provider-person-8001 模块的配置文件,添加以下内容

instance:
instance-id: microservicecloud-person8001
prefer-ip-address: true #访问路径可以显示IP地址

注意instance属性是eureka的子属性

1565964019636

重启服务提供者ApplicationBootStart8001

查看EurekaServer

1565964513626

这里涉及到了Eureka 的自我保护机制,下一章节我们会讲到。

4.2 修改服务实例的详情页info

默认我么你点击服务实例名,跳转到的界面是:

1565964617801

接下来我们要定制一下这界面,显示一下当前服务实例的一些说明信息。

(1)修改microservicecloud-provider-person-8001 模块的pom文件,添加以下内容

<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

(2)修改microservicecloud-provider-person-8001 模块的配置文件,添加以下内容

info:
app.name: ${spring.application.name}
company.name: kingge.top
build.artifactId: ${project.artifactId}
build.version: ${project.version}
app.desc: 这是一个提供查询部门人员信息的服务

(3)修改父工程的pom文件,添加以下内容

<build>
<finalName>microservicecloud</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimit>$</delimit>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>

如果不添加那么就无法解析像这样的动态赋值${spring.application.name}

重启EurekaServer和服务提供者

再次查看服务实例名的info界面

1565965118437

5.Eureka详解

5.1.基础架构

Eureka架构中的三个核心角色:

  • 服务注册中心

    Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的7001模块。

  • 服务提供者

    提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的8001模块。

  • 服务消费者

    消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的80模块。

服务提供和服务消费者相对于服务注册中心,他们都是客户端。所以他们访问EurekaServer导入的依赖是相同的都是spring-cloud-starter-netflix-eureka-client

5.2.服务提供者

服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。

服务注册

服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-eureka=true参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。

  • 第一层Map的Key就是服务id,一般是配置中的spring.application.name属性
  • 第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:locahost:service-provider:8081
  • 值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。

服务续约

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

有两个重要参数可以修改服务续约的行为:

eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
  • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
  • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。

eureka:
instance:
lease-expiration-duration-in-seconds: 10 # 10秒即过期
lease-renewal-interval-in-seconds: 5 # 5秒一次心跳

5.3.服务消费者

获取服务列表

当服务消费者启动时,会检测eureka.client.fetch-registry=true参数的值,如果为true,则会拉取Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒会重新获取并更新数据。我们可以通过下面的参数来修改:

eureka:
client:
registry-fetch-interval-seconds: 5

生产环境中,我们不需要修改这个值。

但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

5.4.失效剔除和自我保护

服务下线

当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。

失效剔除

有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生产环境不要修改。

这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如:10秒

1528696142799

自我保护

我们关停一个服务,就会在Eureka面板看到一条警告:

1525618396076

这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

也就是好死不如赖活着,这个就是用EurekaServer的AP原则,保证可用性

但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:(itcast-eureka)

eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

一句话:某时刻某一个微服务不可用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存

6.消费者获取服务信息

如果我们想要在消费者端获取服务者提供的服务实例列表,那么应该怎么做?对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息

既然是消费者端想查看服务端暴露的服务信息,那么就需要在服务提供者实现一个查询暴露服务实例的列表的接口

1.修改服务端PersonController

添加如下代码,查询服务名称为MICROSERVICECLOUD-PERSON的服务实例列表信息

@Autowired
private DiscoveryClient client;
@RequestMapping(value = "/person/discovery", method = RequestMethod.GET)
public Object discovery()
{
List<String> list = client.getServices();
System.out.println("**********" + list);
List<ServiceInstance> srvList = client.getInstances("MICROSERVICECLOUD-PERSON");
for (ServiceInstance element : srvList) {
System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
+ element.getUri());
}
return this.client;
}

2.服务提供者启动类添加注解@EnableDiscoveryClient (后来测试发现其实这一步是多余的)

1565967990809

因为@EnableEurekaClient注解已经包含了@EnableDiscoveryClient 注解

1565968096288

也就是说当服务注册中心是Eureka的时候那么官方已经为了包装了一个注解替代了@EnableDiscoveryClient,但是如果注册中心不是Eureka的话,那么建议使用@EnableDiscoveryClient注解实现服务发现,因为这里注册中心是Eureka那么就是用官方推荐的@EnableEurekaClient

3.修改ConsumerController代码,访问服务提供者提供的接口:

// 测试@EnableDiscoveryClient,消费端可以调用服务发现
@RequestMapping(value = "/consumer/person/discovery")
public Object discovery()
{
return restTemplate.getForObject(REST_URL_PREFIX + "/person/discovery", Object.class);
}

4.启动服务提供者和服务消费者

消费者访问接口

1565968242592

7.EurekaServer集群

单个的EurekaServer很明显是不符合HA,高可用原则,所以下面再加两台EurekaServer-8002和8003构成EurekaServer集群

1565970421791

基本原理

上图是来自eureka的官方架构图,这是基于集群配置的eureka;

- 处于不同节点的eureka通过Replicate进行数据同步

- Application Service为服务提供者

- Application Client为服务消费者

- Make Remote Call完成一次服务调用

服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。

当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服务注册中心将服务置为DOWN状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。

服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。

7.1 根据microservicecloud-eureka-7001创建两个相同的工程

分别是microservicecloud-eureka-7002和microservicecloud-eureka-7003

按照7001为模板粘贴POM

修改7002和7003的主启动类

完整工程如下

1565970941327

7.2 修改映射配置-实现唯一的eureka服务端的实例名称

为了模拟EurekaServer集群,不同的EurekaServer在不同的机器,而且拥有不同的实例名称

找到C:\Windows\System32\drivers\etc路径下的hosts文件

修改映射配置添加进hosts文件
127.0.0.1 peer1
127.0.0.1 peer2
127.0.0.1 peer3

7.3 修改7001-7003三台EurekaServer的配置文件

7001 修改内容如下:

server:
port: 7001
eureka:
instance:
hostname: peer1 #peer1 #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://peer2:7002/eureka/,http://peer3:7003/eureka/ #注册7002和7003 自己不用声明

7002 修改内容如下:

server:
port: 7002
eureka:
instance:
hostname: peer2 #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://peer1:7001/eureka/,http://peer3:7003/eureka/ #注册7001和7003 自己不用声明
#等同于http://localhost:7001/eureka/

7003修改内容如下

server:
port: 7003
eureka:
instance:
hostname: peer3 #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/ #注册7001和7002 自己不用声明
#等同于http://localhost:7001/eureka/

需要注意的是service-url:defaultZone的值,都是包含其他EurekaServer的值,不用书写自己的。

7.4 修改服务提供者的配置文件

也就是修改microservicecloud-provider-person-8001模块修改内容如下

server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.kingge.entity # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件
spring:
application:
name: microservicecloud-person #很重要,对外暴露的微服务的名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://127.0.0.1:3306/test # 数据库名称
username: root
password: 123
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
#
eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/
# http://localhost:7001/eureka #单机版本使用
# defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/
instance:
instance-id: microservicecloud-person8001 #自定义服务实例名
prefer-ip-address: true #访问路径可以显示IP地址
#
info:
app.name: ${spring.application.name}
company.name: kingge.top
build.artifactId: ${project.artifactId}
build.version: ${project.version}
app.desc: 这是一个提供查询部门人员信息的服务

实际上就是修改了service-url:defaultZone的值,修改为了EurekaServer集群的地址,其他配置没有改变

7.5重启7001-7003服务器

也就是分别运行ApplicationBootStart7001、ApplicationBootStart7002、ApplicationBootStart7003

访问查看

1565971841015

1565972117167

1565972150084

部署成功!!!!!!!

8.Eureka和Zookeeper-服务注册中心比较

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性P在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。

那么分布式系统,必然要求分区容错性,也就是P原则,那么Zookeeper选择了C,Eureka选择了A

因此Zookeeper保证的是CP,Eureka则是AP。

8.1 Zookeeper保证CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

8.2 Eureka保证AP

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的 ,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况: 
Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务 

Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)

当网络稳定时,当前实例新的注册信息会被同步到其它节点中

因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。

那么既然保证了保证了可用性,那么数据的一致性肯定是不能够保证了,所以这个就是自我保护的机制。所以到底是AP还是CP,又或者是AC(数据库),要看业务场景来定。

七.Ribbon负载均衡

接下来解决第二个问题,那就是假设在多个服务提供者提供服务的情况下,怎么做到负载均衡,解决需要把服务提供者url硬编码到消费者端的问题。

7.1 Ribbon概念

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供**客户端**的**软件负载**均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon**实现自定义的负载均衡算法**。

7.2 什么叫LB(负载均衡)

LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。

负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。

常见的负载均衡有软件Nginx,LVS,硬件 F5等。

相应的在中间件,例如:dubbo和SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。

两种负载均衡:

  1. 集中式LB:偏硬件,服务的消费方和提供方之间使用独立的LB设施,由该设施负责把访问请求以某种策略转发至服务的提供方。
  2. 进程内LB:偏软件, 将LB逻辑集成到消费方,消费方从服务注册中心指导哪些地址可用,再自己选择一个合适的服务器。

    Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

7.3 Ribbon负载均衡实现

因为在上面的例子中只存在一个8001模块在提供服务,那么为了能够演示负载均衡的例子,这里需要再增加两个服务提供者8002和8003

7.3.1 根据8001模块复制新建两份分别命名为8002和8003

请看完整项目结构图

8002模块

1566008410173

8003模块

1566008463016

7.3.2 新建数据库test2、test3,让各自微服务分别连各自的数据库

我们知道一个微服务可能是一套完整的系统,那么也就意味着他可能拥有自己的数据库。而且为了方便测试负载均衡,我们让8001-8003这三个服务提供者各自连接自己的数据库,也方便验证负载均衡是否实现。

给test2数据库导入数据:

DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
`deptno` int(255) NOT NULL AUTO_INCREMENT,
`db_source` varchar(255) DEFAULT NULL,
`dname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('5', 'test2', '开发部');
INSERT INTO `person` VALUES ('6', 'test2', '人事部');
INSERT INTO `person` VALUES ('7', 'test2', '集成部');
INSERT INTO `person` VALUES ('8', 'test2', '市场部');
INSERT INTO `person` VALUES ('9', 'test2', 'hr');

db_source字段标识,数据来源那个数据库

给test3数据库导入数据:

DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
`deptno` int(255) NOT NULL AUTO_INCREMENT,
`db_source` varchar(255) DEFAULT NULL,
`dname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('5', 'test3', '开发部');
INSERT INTO `person` VALUES ('6', 'test3', '人事部');
INSERT INTO `person` VALUES ('7', 'test3', '集成部');
INSERT INTO `person` VALUES ('8', 'test3', '市场部');
INSERT INTO `person` VALUES ('9', 'test3', 'hr');

1566009325282

7.3.3 修改8002/8003各自application.yml配置文件

实际上只需要修改三个地方:服务暴露的端口号,服务连接的数据库,服务的实例名

注意:服务名称不能够修改

1566008980542

因为这三个服务都是提供同样的业务,那么就不会归属到一个服务组下,也就是说我们想要的是:microservicecloud-person 这个服务名称(服务组)下面有三个服务实例(8001-8003)提供服务,这样负载均衡才能够演示

8002模块修改如下

1566009196970

8003模块修改如下

1566009240467

7.3.4 启动EurekaServer集群和8001-8003服务模块

1566009501569

查看EurekaServer

http://peer1:7001/

1566009599956

http://peer2:7002/

1566009617535

http://peer3:7003/

1566009632534

服务提供者集群创建成功

7.3.5 自测启动的服务是否可用

1566009826135

1566009838479

1566009865015

7.3.6 修改服务消费者-采取负载均衡方式访问服务

也就是修改microservicecloud-consumer-person-80模块

(1)修改pom文件添加Robbin依赖

<!-- Ribbon相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

(2)修改application.yml文件

server:
port: 80
#
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/

也就是修改服务注册中心地址为集群地址

(3)修改ConfigBean配置类添加@LoadBalanced注解,获取rest服务的时候添加ribbon

@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}

(4)主启动类ApplicationBootStart80添加@EnableEurekaClient

@SpringBootApplication
@EnableEurekaClient//这里建议使用@EnableDiscoveryClient 替换@EnableEurekaClient
public class ApplicationBootStart80 {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart80.class, args);
}
}

7.3.7 修改服务消费者

修改ConsumerController代码

修改内容如下

// private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-PERSON";

把url更改为服务名称

Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号

7.3.8 启动7001-7003,8001-8003,80

1566010881201

启动完成后测试:

第一次访问

1566011120741

第二次访问:

1566011157293

第三次访问:

1566011173416

第四次访问:

1566011196418

我们注意db_source的值是变换的,说明负载均衡成功。但是当我们访问一轮后,发现他又从头开始:

test2->test3->test->test2->test3->test 说明Ribbon默认采用的是轮询的负载均衡策略

7.3.9 总结

1566011028903

Ribbon在工作时分成两步

第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.

第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

Ribbon其实就是一个软负载均衡的客户端组件, 他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

7.4 Ribbon负载均衡实现核心接口IRule

7.4.1.源码跟踪

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。

显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor

在consumer的ConsumerController如下代码打断点:

1566012847807

一路源码跟踪:RestTemplate.getForObject –> RestTemplate.execute –> RestTemplate.doExecute:

1566013203247

点击进入AbstractClientHttpRequest.execute –> AbstractBufferingClientHttpRequest.executeInternal –> InterceptingClientHttpRequest.executeInternal –> InterceptingClientHttpRequest.execute:

1528776489965

继续跟入:LoadBalancerInterceptor.intercept方法

1566013476262

获取请求的服务名称,我们发现执行this.loadBalancer.execute()方法的loadBalancer是一个接口LoadBalancerClient,那么很明显执行execute()方法的只能是LoadBalancerClient的实现类。

继续跟入execute方法发现执行该方法的类是:RibbonLoadBalancerClient负载均衡客户端类:

发现获取了8003端口的服务

1566013958604

我们查看一下获取的负载均衡器的信息:

1566014122746

7.4.2.负载均衡策略

Ribbon默认的负载均衡策略是简单的轮询,我们可以测试一下:

编写测试类,在刚才的源码中我们看到拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,找到choose方法的接口方法,是这样介绍的:

1525622320277

现在这个就是负载均衡获取实例的方法。

我们注入这个类的对象,然后对其测试:

1566014544201

测试内容:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationBootStart80.class)
public class LoadBalanceTest {
@Autowired
private RibbonLoadBalancerClient client;
@Test
public void testLoadBalance(){
for (int i = 0; i < 100; i++) {
ServiceInstance instance = this.client.choose("MICROSERVICECLOUD-PERSON");
System.out.println(instance.getHost() + ":" +instance.getPort());
}
}
}

结果:

1566015460435

符合了我们的预期推测,确实是轮询方式。

我们是否可以修改负载均衡的策略呢?

继续跟踪源码,发现这么一段代码:

1525622652849

我们看看这个rule是谁:

1525622699666

这里的rule默认值是一个RoundRobinRule,看类的介绍:

1525622754316

这不就是轮询的意思嘛。

我们注意到,这个类其实是实现了接口IRule的,查看一下:

1525622817451

定义负载均衡的规则接口。

它有以下实现:

1528782624098

七大方法

IRule是一个接口,七大方法是其实现类

  • RoundRobinRule:轮询(默认方法)
  • RandomRule:随机
  • AvailabilityFilteringRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务进行轮询
  • WeightedResponseTimeRule:根据平均响应时间计算服务的权重。统计信息不足时会按照轮询,统计信息足够会按照响应的时间选择服务
  • RetryRule:正常时按照轮询选择服务,若过程中有服务出现故障,在轮询一定次数后依然故障,则会跳过故障的服务继续轮询。
  • BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • ZoneAvoidanceRule:默认规则,符合判断server所在的区域的性能和server的可用性选择服务

1566022083352

7.4.3 负载均衡自定义

1.修改某个服务的负载均衡策略

例如我们只想修改 MICROSERVICECLOUD-PERSON服务的负载均衡策略

这里一共有两种方法实现一种是使用yml配置的方式声明-一种是使用注解的方式声明

第一种使用配置方式(建议使用)

(1)修改消费者端(microservicecloud-consumer-person-80)的application.yml配置文件

修改内容如下:

MICROSERVICECLOUD-PERSON:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是:{服务名称}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的实现类。

(2)运行上面的LoadBalanceTest

1566019186732

第二种使用配置方式

前提:注释掉第一种方式实现的 配置信息(不注释掉也可以,因为第一种方式跟第二种方式同时存在时,以第二种方式为主)

(1)修改消费者端启动类

@SpringBootApplication
#@EnableEurekaClient
@EnableDiscoveryClient //服务发现
@RibbonClient(name = "MICROSERVICECLOUD-PERSON",configuration = OwnRule.class)
public class ApplicationBootStart80 {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart80.class, args);
}
}

添加一下注解

@RibbonClient(name = "MICROSERVICECLOUD-PERSON",configuration = OwnRule.class)

(2)新建OwnRule自定义侧略类

首先新建包com.myrule

@Configuration
public class OwnRule {
@Bean
public IRule getIuIRule(){
System.out.println("进入了自定义负载均衡策略");
return new RandomRule();
}
}

1566019594262

注意:

官方文档明确给出了警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,

否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说

我们达不到特殊化定制的目的了。

所以上面的OwnRule类是不在启动类同级包或者子包下的。

1566019672480

所以我们也可以利用这个特性,修改全局的所有服务的获取策略为某个策略

(3)运行上面的LoadBalanceTest

1566019186732

2. 修改全局的服务访问策略(替换默认的轮询策略)

很简单,直接在IOC容器注入想要替换成的负载均衡策略即可

@Configuration
public class ConfigBean
applicationContext.xml
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
@Bean
public IRule getIuIRule(){
return new RandomRule();
}
}

3.自定义负载均衡策略

上上面的两个章节实际上使用的都是Ribbon提供的负载均衡策略,所以接下来我们要实现一个负载均衡策略

提出需求:

问题:修改MICROSERVICECLOUD-PERSON服务的负载均衡策略:依旧使用轮询策略,但是加上新需求,每个服务器(现在有三台8001-8003)要求被调用5次。也即 以前是每台机器一次,现在是每台机器5次

在实现之前拜读一下官方的RandomRule 源代码,然后再修改出符合我们需求的策略类

https://github.com/Netflix/ribbon/blob/master/ribbon-loadbalancer/src/main/java/com/netflix/loadbalancer/RandomRule.java

public class RandomRule extends AbstractLoadBalancerRule {
/**
* Randomly choose from all living servers
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;//需要返回的服务
while (server == null) {//使用while循环知道获取服务
if (Thread.interrupted()) {//如果当前线程已经中断,那么直接返回null
return null;
}
List<Server> upList = lb.getReachableServers();//获取所有可达的服务列表
List<Server> allList = lb.getAllServers();//获取所有服务列表
int serverCount = allList.size();//得到服务列表里服务实例的数量
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = chooseRandomInt(serverCount);//根据服务数量所及获取服务下标
等同于Random.rand.nextInt(serverCount);
server = upList.get(index);//根据随机获取到的下标,从可用服务列表实例中获取服务
if (server == null) {//获取不到时,暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程
//然后继续while循环获取
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
}

代码其实很简单

(1)新建RandomRuleModify类
package com.myrule;
import java.util.List;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
public class RandomRuleModify extends AbstractLoadBalancerRule
{
// total = 0 // 当total==5以后,我们指针才能往下走,
// index = 0 // 当前对外提供服务的服务器地址,
// total需要重新置为零,但是已经达到过一个5次,我们的index = 1
// 分析:我们5次,但是微服务只有8001 8002 8003 三台,OK?
//
private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
private int currentIndex = 0; // 当前提供服务的机器号
public Server choose(ILoadBalancer lb, Object key)
{
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
if(total < 5)
{
server = upList.get(currentIndex);
total++;
}else {
total = 0;
currentIndex++;
if(currentIndex >= upList.size())
{
currentIndex = 0;
}
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig)
{
// TODO Auto-generated method stub
}
}

这个类不能够放在启动类的同级或者子包下,否则将被设置为全局的负载均衡策略,起不到为MICROSERVICECLOUD-PERSON服务定制策略的作用

1566021323120

(2)OwnRule返回我们实现的RandomRuleModify
@Configuration
public class OwnRule {
@Bean
public IRule getIuIRule(){
System.out.println("进入了自定义负载均衡策略");
return new RandomRuleModify();//RandomRule();
}
}
(3)修改启动类
@SpringBootApplication
//@EnableEurekaClient
@EnableDiscoveryClient //服务发现
@RibbonClient(name = "MICROSERVICECLOUD-PERSON",configuration = OwnRule.class)
public class ApplicationBootStart80 {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart80.class, args);
}
}
(4)运行测试类LoadBalanceTest

1566021621272

轮询的同时每个服务器调用五次!!!

(5)总结

我们也可以使用配置类的方式为某个服务配置特定的负载均衡策略实现类(参考7.4.3.1 章节的第一种配置方式),这样就可以省略上面的第二和第三步骤

通过配置的方式更加,灵活

1566021993675

然后运行测试类LoadBalanceTest,结果一模一样。

八. Feign负载均衡

8.1.简介

Feign出现的原因是什么,既然他也是提供负载均衡的功能,那么他跟Ribbon有什么区别?

项目主页:

https://github.com/OpenFeign/feign

官网解释:

http://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-feign

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

1566106445678

1566106480369

Feign的出现的目的是:为了迎合我们平时面向接口编程和调用的习惯。例如我们在Controller通过注入Service层的接口调用相关的业务。但是在上面的Ribbon例子中我们是通过RestTemplate和URL的方式调用某个服务:

1566106719992

Feign的实现其实也很简单:只需要创建一个接口,然后在上面添加注解即可。

有道词典的英文解释:

img

为什么叫伪装?

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

img

总的来说:Feign通过封装Ribbon实现了我们常用的面向接口编程

8.2 实现Feign

其实就是复制microservicecloud-consumer-person-80工程代码,在做一些修改。

(1)根据父工程新建microservicecloud-consumer-person-80-feign模块

(2)复制80模块代码到80-feign模块

1566107268418

修改启动类的名字。

(3)修改80-feign模块的pom文件,添加Feign依赖

添加如下内容:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

(4)修改api公共模块

也就是修改microservicecloud-api,因为我们知道我们抽象出来的服务,有可能其他模块也会调用,不仅仅是80-feign模块。所以公共的东西我们都放在api模块

1.修改pom文件,添加feign依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

2.新建PersonClientService接口并新增注解@FeignClient

@FeignClient(value = "MICROSERVICECLOUD-PERSON")
public interface PersonClientService
{
@RequestMapping(value = "/person/get/{id}", method = RequestMethod.GET)
public Person get(@PathVariable("id") long id);
@RequestMapping(value = "/person/list", method = RequestMethod.GET)
public List<Person> list();
@RequestMapping(value = "/person/add", method = RequestMethod.POST)
public boolean add(Person person);
}

• 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像

• @FeignClient,声明这是一个Feign客户端,类似@Mapper注解。同时通过value属性指定服务名称

• 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果

(5)feign工程修改Controller,添加上一步新建的PersonClientService接口

@RestController
public class ConsumerController {
// @Autowired
// private RestTemplate restTemplate;
// private static final String REST_URL_PREFIX = "http://localhost:8001";
// private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-PERSON";
@Autowired
private PersonClientService personClientService;
@RequestMapping(value = "/consumer/person/add")
public boolean add(Person person)
{
// return restTemplate.postForObject(REST_URL_PREFIX + "/person/add", person, Boolean.class);
return personClientService.add(person);
}
@RequestMapping(value = "/consumer/person/list")
public List<Person> list()
{
// return restTemplate.getForObject(REST_URL_PREFIX + "/person/list", List.class);
return personClientService.list();
}
@RequestMapping(value = "/consumer/person/get/{id}")
public Person get(@PathVariable("id") Long id)
{
return this.personClientService.get(id);
}
}

可以看到我们注释掉了以往的RestTemplate+URL请求服务的方式,通过注入接口调用的方式,实现了面向借口编程。

(6)feign工程修改主启动类

添加@EnableFeignClients // 开启feign客户端

@SpringBootApplication
@EnableDiscoveryClient //服务发现
@EnableFeignClients // 开启feign客户端
public class ApplicationBootStart80feign {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart80feign.class, args);
}
}

(7)启动EurekaServer7001-7003和服务提供集群8001-8003,启动feign工程进行测试

1566108827974

请求

1566108849361

默认使用轮询的负载均衡方式

8.3 总结

Feign通过接口的方法调用Rest服务(之前是Ribbon+RestTemplate) ,

该请求发送给Eureka服务器(http://MICROSERVICECLOUD-PERSON/person/list),

通过Feign直接找到服务接口,由于在进行服务调用的时候融合了Ribbon技术,所以也支持负载均衡作用。

也就是说Feign只是封装了Ribbon,改造成了我们习惯的面向接口编程的方式。

自定义负载均衡的方式跟之前使用Ribbon 的一样,也就是支持注解和配置两种方式实现某个服务或者全局服务的负载均衡策略自定义

例如:

@SpringBootApplication
@EnableDiscoveryClient //服务发现
@EnableFeignClients // 开启feign客户端
@RibbonClient(name = "MICROSERVICECLOUD-PERSON",configuration = OwnRule.class)
public class ApplicationBootStart80feign {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart80feign.class, args);
}
}

8.4.请求压缩(了解)

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩

同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限

注:上面的数据类型、压缩大小下限均为默认值。

8.5.日志级别(了解)

前面讲过,通过logging.level.xx=debug来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为@FeignClient注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。

1)设置com.kingge包下的日志级别都为debug

logging:
level:
com.kingge: debug

2)新建FeignLogConfiguration配置类,定义日志级别

内容:

@Configuration
public class FeignLogConfiguration {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

这里指定的Level级别是FULL,Feign支持4种级别:

• NONE:不记录任何日志信息,这是默认值。

• BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

• HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

• FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

3)在FeignClient中指定配置类:

@FeignClient(value = “MICROSERVICECLOUD-PERSON” configuration = FeignLogConfiguration.class)
public interface UserFeignClient {
@GetMapping(“/user/{id}”)
User queryUserById(@PathVariable(“id”) Long id);
}

4)重启项目,即可看到每次访问的日志:

8.6.Hystrix支持

参加下面服务降级的案例

九.Hystrix断路器

**首先我们来解决第三问题,服务的容灾处理**

跟Ribbon和Feign是客户端技术不同的是Hystrix是服务端的技术,也就是他是作用在服务提供端

1.分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败

1.雪崩问题

微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:

标题: fig:

如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务(这个就是所谓的扇出)。

如果此时,某个服务出现异常:

标题: fig:

例如微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:

标题: fig:

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应所以我们需要阻断故障的传播,这个就是断路器

这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。

备注:一般情况对于服务依赖的保护主要有3中解决方案:

(1)熔断模式:这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

(2)(降级)隔离模式:这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。

(3)限流模式:上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。

springcloud的Hystrix就是提供了前两种解决方式:

Hystix解决雪崩问题的手段有两个:

• 线程隔离(降级)

• 服务熔断

2.Hystrix简介

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

 Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,**通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack)(解决思路)** ,**而不是长时间的等待或者抛出调用方无法处理的异常** ,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

官网资料

https://github.com/Netflix/Hystrix/wiki/How-To-Use

3.服务端的服务熔断

熔断机制是应对雪崩效应的一种微服务链路保护机制。他是在服务端实现。

**当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息**。 当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。**熔断机制的注解是@HystrixCommand。**

3.1 熔断原理

熔断器,也叫断路器,其英文单词为:Circuit Breaker

img

熔断状态机3个状态:

• Closed:关闭状态,所有请求都正常访问。

• Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。

• Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时

当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息。 当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。

3.2 案例演示

3.2.1 参考microservicecloud-provider-person-8001 新建microservicecloud-provider-person-hystrix-8001

赋值pom文件和代码已经application.yml文件

3.2.2 修改新建hystrix-8001模块的pom文件和启动类

(1)pom文件添加hystrix依赖

<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

(2)修改启动类名称并添加@EnableCircuitBreaker注解//对hystrixR熔断机制的支持

@SpringBootApplication
//@EnableEurekaClient
@EnableDiscoveryClient //服务发现
@EnableCircuitBreaker//对hystrixR熔断机制的支持
public class ApplicationBootStart8001hystrix {
public static void main(String[] args)
{
SpringApplication.run(ApplicationBootStart8001hystrix.class, args);
}
}

3.2.3 修改配置文件

其实就是修改服务实例名字,避免跟8001模块冲突

1566145616539

3.2.4 修改PersonController,添加熔断处理

在某个方法使用@HystrixCommand注解,模拟报异常后如何处理

一旦调用服务方法失败并抛出了错误信息/请求超时后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法

下面以get方法为例,代码内容(下面模拟的是出现异常)

@RestController
public class PersonController
{
@Autowired
private PersonService service;
//全部使用restful风格,返回json字符串
@RequestMapping(value = "/person/add", method = RequestMethod.POST)
public boolean add(@RequestBody Person person)
{
return service.add(person);
}
@RequestMapping(value = "/person/get/{id}", method = RequestMethod.GET)
@HystrixCommand(fallbackMethod = "processHystrix_Get") //关键代码
public Person get(@PathVariable("id") Long id)
{
Person person = service.get(id);
if(null == person)
{
throw new RuntimeException("该ID:"+id+"没有没有对应的信息");
}
return person;
}
public Person processHystrix_Get(@PathVariable("id") Long id)//关键代码
{
Person person = new Person();
person.setDeptno(id);person.setDname("该ID:"+id+"没有没有对应的信息,null--@HystrixCommand");
person.setDb_source("no this database in MySQL");
return person;
}
@RequestMapping(value = "/person/list", method = RequestMethod.GET)
public List<Person> list()
{
return service.list();
}
}

注意,配置的fallbackMethod方法必须与被@HystrixCommand注解的方法有相同的入参和返回值

3.2.5 启动EurekaServer集群和8001-hystrix模块、80模块

1566146282120

消费者访问

http://localhost/consumer/person/get/5

输出:{“deptno”:5,”dname”:”开发部”,”db_source”:”test”}

我们假设获取某个不存在的用户

http://localhost/consumer/person/get/55

输出:{“deptno”:55,”dname”:”该ID:55没有没有对应的信息,null–@HystrixCommand”,”db_source”:”no this database in MySQL”}

4.客户端的服务降级

既然有了服务熔断,为什么还需要服务降级?

服务降级处理是在客户端实现完成的,与服务端没有关系,客户端自带一个异常处理机制。上面的服务熔断是在服务端实现

客户端的服务降级,能够快速的响应用户的请求,当服务不可达,那么立即返回定制的错误信息。

整体资源快不够了,忍痛将某些服务先关掉,待渡过难关,再开启回来

优先保证核心服务,而非核心服务不可用或弱可用。

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息 。

服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

而且上面的服务端熔断案例存在一些缺点:控制层每实现一个方法,就要实现对应的fallBack方法处理相关的异常逻辑,那么代码量会越来越大,而且跟业务逻辑偶合在一起。所以我们需要解耦,我们把熔断的的fallback方法都放在一个类中,去除控制层的@HystrixCommand,这样能够保证业务的纯粹。

4.1 案例演示

4.1.1 修改microservicecloud-api的工程, 根据已有的PersonClientService接口新建一个实现类 FallbackFactory接口的类PersonClientServiceFallbackFactory

@Component//不要忘记添加
public class PersonClientServiceFallbackFactory implements FallbackFactory<PersonClientService> {
@Override
public PersonClientService create(Throwable throwable) {
return new PersonClientService() {
@Override
public Person get(long id) {
Person person = new Person();
person.setDeptno(id);person.setDname("该ID"+id+"没有对应的信息,Consumer客户端提供的降级信息,此服务Provider已经关闭");
person.setDb_source("no this database in MySQL");
return person;
}
@Override
public List<Person> list() {
return null;
}
@Override
public boolean add(Person person) {
return false;
}
};
}
}

4.1.2 修改microservicecloud-api工程,PersonClientService接口在注解@FeignCLient中添加fallbackFactory属性值

@FeignClient(value = "MICROSERVICECLOUD-PERSON",fallbackFactory = PersonClientServiceFallbackFactory.class)
public interface PersonClientService
{
@RequestMapping(value = "/person/get/{id}", method = RequestMethod.GET)
public Person get(@PathVariable("id") long id);
@RequestMapping(value = "/person/list", method = RequestMethod.GET)
public List<Person> list();
@RequestMapping(value = "/person/add", method = RequestMethod.POST)
public boolean add(Person person);
}

4.1.3 修改80-feign消费者端配置文件,开启服务熔断

microservicecloud-consumer-person-80-feign工程修改YML 
feign:
hystrix:
enabled: true

4.1.4 测试

启动3个eureka先启动和微服务microservicecloud-provider-person-8001启动                                    microservicecloud-consumer-person-80-feign启动      

正常访问测试 http://localhost/consumer/dept/get/5

输出:{“deptno”:5,”dname”:”hr”,”db_source”:”test”}

故意关闭微服务microservicecloud-provider-person-8001

访问测试 http://localhost/consumer/dept/get/5

输出:”deptno”:5,”dname”:”该ID5没有对应的信息,Consumer客户端提供的降级信息,此服务Provider已经关闭”,”db_source”:”no this database in MySQL”}

成功!!!

**此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器**

也就是说,如果服务端能够正常调用那么就返回值,如果不能够调用那么就返回由fallbackFactory定义的值

5. 服务监控HystrixDashboard

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix      Dashboard),Hystrix      会持续地**记录所有通过Hystrix发起的请求的执行信息**,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等.Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控.Spring Cloud也提供了Hystrix Dashboard的整合.对监控内容转化成可视化界面.     

也就是说,服务端必须是集成了Hystrix组件,才能够被监控,也即是:启动类添加@EnableCircuitBreaker//对hystrixR熔断机制的支持。例如我们的服务8002和8003是监控不到的

记下来看案例实现

5.1 案例实现

5.1.1 新建 microservicecloud-consumer-hystrix-dashboard-9001 模块

根据父工程 microservicecloud新建服务监控模块( 参考microservicecloud-consumer-person-80-feign )

(1)复制80-feign模块pom文件并添加dashboard依赖
<!-- hystrix和 hystrix-dashboard相关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
(2)修改application.yml配置文件          
server:
port: 9001
(3)启动类ApplicationBootStart9001dashboard添加@EnableHystrixDashboard         

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

(4)所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置

<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

(5)完整工程

![](SpringCloud个人总结/Snipaste_2019-08-19_16-34-32.png)

5.1.2 启动dashboard-9001模块

http://localhost:9001/hystrix 

部署成功

5.1.3 启动3个eureka集群

5.1.4启动microservicecloud-provider-person-hystrix-8001和microservicecloud-consumer-person-80-feign 服务端和消费者

5.1.4dashboard填写需要监控的服务地址

1:Delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗。

2:Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。

点击Monitor Stream 开始监控

怎么查看这张图:关键的部位我已经用红色方框,框住。核心就是:7色(服务的状态),1圈,1线。

   1圈                   

      实心圆:共有两种含义.它通过颜色的变化代表了 实例健康程度,它的健康度从绿色<黄色<橙色<红色递减.该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大.所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例.              

1线                   

     曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势.

5.1.5 多次刷新http://localhost/consumer/person/get/1

也即是多次请求8001服务,然后查看监控的状态

显示请求的get方法的情况。圆圈变大,曲线上升。(如果请求多个方法会增加图形说明

十. zuul路由网关

通过前面的学习,使用Spring Cloud实现微服务的架构基本成型,大致是这样的:

1566221714611

我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。

在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过均衡负载公开至服务调用方。我们把焦点聚集在对外服务这块,直接暴露我们的服务地址,这样的实现是否合理,或者是否有更好的实现方式呢?

先来说说这样架构需要做的一些事儿以及存在的不足:

• 破坏了服务无状态特点。

为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。

  从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外考虑对接口访问的控制处理。

• 无法直接复用既有接口。

当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

面对类似上面的问题,我们要如何解决呢?答案是:服务网关!

为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器的 服务网关。

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

Zuul包含了对请求的路由和过滤两个最主要的功能:

其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。

**注意:Zuul服务最终还是会注册进Eureka**

提供=代理+路由+过滤三大功能

1.简介

官网:https://github.com/Netflix/zuul

标题: fig:

Zuul:维基百科

电影《捉鬼敢死队》中的怪兽,Zuul,在纽约引发了巨大骚乱。

事实上,在微服务架构中,Zuul就是守门的大Boss!一夫当关,万夫莫开!

img

2.Zuul加入后的架构

标题: fig:

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。

3.路由基本配置(例子)

3.1新建Module模块microservicecloud-zuul-gateway-9999

3.2 修改pom文件

主要添加:

<!-- zuul路由网关 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

完整内容

<dependencies>
<!-- zuul路由网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- actuator监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix容错-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 日常标配 -->
<dependency>
<groupId>com.kingge.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 热部署插件 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>

3.3 修改application.yml

server:
port: 9999
spring:
application:
name: microservicecloud-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/
instance:
instance-id: microservicecloud-zuul-gateway9999
prefer-ip-address: true
info:
app.name: ${spring.application.name}
company.name: kingge.top
build.artifactId: ${project.artifactId}
build.version: ${project.version}
app.desc: 这是一个zuul

3.4 修改主启动类ApplicationBootStart9999zuul

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

3.4.1完整工程展示

1566226205578

3.5启动三个eureka集群 、一个服务提供类microservicecloud-provider-person-8001 、9999模块路由

1566222721701

1566222752232

3.6 测试

不用路由访问:http://localhost:8001/person/get/2
启用路由访问 http://localhost:9999/microservicecloud-person/person/get/2

输出都是:{“deptno”:5,”dname”:”开发部”,”db_source”:”test”}

4.路由访问映射规则

上面的案例存在一个问题,那就是使用路由访问服务的时候

http://localhost:9999/microservicecloud-person/person/get/2

我们暴露了 服务名称:microservicecloud-person ,所以我们想隐藏他。

4.1 修改9999模块配置文件

添加如下内容

zuul:
routes:
myperson.serviceId: microservicecloud-person
myperson.path: /myperson/**

重要

规则说明

• zuul.routes..path=/xxx/**: 来指定映射路径。是自定义的路由名(在上面是myperson)

• zuul.routes..serviceId=service-provider:来指定服务名。

而大多数情况下,我们的路由名称往往和服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:zuul.routes.=

比方说上面我们关于microservicecloud-person的配置可以简化为一条:

zuul:
routes:
microservicecloud-person: /myperson/** # 这里是映射路径
# myperson.serviceId: microservicecloud-person
# myperson.path: /myperson/**

省去了对服务名称的配置。

4.2 重启9999模块

访问如下两个网址

(1)服务名称映射后路由访问OK
http://localhost:9999/myperson/person/get/5

(2)未映射服务名称原路径访问OK - 默认的路由规则
http://localhost:9999/microservicecloud-person/person/get/5

如果在保留第一种方式的情况下,禁止第二种方式的访问。

4.3 原真实服务名忽略-关闭默认的路由规则

增你家该属性即可:ignored-services:

zuul:
ignored-services: microservicecloud-person #单个具体,多个可以用"*" ignored-services: *
routes:
myperson.serviceId: microservicecloud-person
myperson.path: /myperson/**

重启9999zuul模块:

访问:http://localhost:9999/microservicecloud-person/person/get/5

访问失败

1566223608884

4.4 设置统一公共前缀

增加prefix属性即可

zuul:
ignored-services: microservicecloud-person #单个具体,多个可以用"*" ignored-services: *
routes:
myperson.serviceId: microservicecloud-person
myperson.path: /myperson/**
prefix: /sb #必须有反斜杠

http://localhost:9999/sb/myperson/person/get/5 //访问成功

http://localhost:9999/myperson/person/get/5 //不加前缀访问失败

5 .默认的路由规则

在使用Zuul的过程中,上面讲述的规则(也就是上面4.1小节的规则)已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:

• 默认情况下,一切服务的映射路径就是服务名本身。例如服务名为:service-provider,则默认的映射路径就 是:/service-provider/**

也就是说,刚才的映射规则我们完全不配置也是OK的,不信就试试看。

终极简化版本

zuul:
# routes:
# microservicecloud-person: /myperson/** # 这里是映射路径
# myperson.serviceId: microservicecloud-person
# myperson.path: /myperson/**

那么默认microservicecloud-person服务的映射路径是:/microservicecloud-person/**

为了不暴露服务名称,那么我么你需要关闭默认的路由规则:见4.3小节

6.过滤器

Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。

6.1.ZuulFilter

ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:

public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 来自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}

• shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。

• run:过滤器的具体业务逻辑。

• filterType:返回字符串,代表过滤器的类型。包含以下4种:

– pre:请求在被路由之前执行

– route:在路由请求时调用

– post:在route和errror过滤器之后调用

– error:处理请求时发生错误调用

• filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

6.2.过滤器执行生命周期

这张是Zuul官网提供的请求生命周期图,清晰的表现了一个请求在各个过滤器的执行顺序。

img

正常流程:

• 请求到达首先会经过pre类型过滤器,而后到达route类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。

异常流程:

• 整个过程中,pre或者route过滤器出现异常,都会直接进入error过滤器,在error处理完毕后,会将请求交给POST过滤器,最后返回给用户。

• 如果是error过滤器自己出现异常,最终也会进入POST过滤器,将最终结果返回给请求客户端。

• 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和route不同的是,请求不会再到达POST过滤器了。

所有内置过滤器列表:

标题: fig:

6.3.使用场景

场景非常多:

• 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了

• 异常处理:一般会在error类型和post类型过滤器中结合来处理。

• 服务调用时长统计:pre和post结合使用。

7.自定义过滤器

接下来我们来自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。

7.1.定义过滤器类

1566226606544

内容:

package com.kingge.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class LoginFilter extends ZuulFilter {
/**
* 过滤器类型,前置过滤器
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 该过滤器是否生效
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 登陆校验逻辑
* @return
* @throws ZuulException
*/
@Override
public Object run() {
// 获取zuul提供的上下文对象
RequestContext context = RequestContext.getCurrentContext();
// 从上下文对象中获取请求对象
HttpServletRequest request = context.getRequest();
// 获取token信息
String token = request.getParameter("access-token");
// 判断
if (StringUtils.isBlank(token)) {
// 过滤该请求,不对其进行路由
context.setSendZuulResponse(false);
// 设置响应状态码,401
context.setResponseStatusCode(401);
// 设置响应信息
context.setResponseBody("{\"status\":\"401\", \"text\":\"request error!\"}");
}
// 校验通过,把登陆信息放入上下文信息,继续向后执行
context.set("token", token);
return null;
}
}

7.2.测试

没有token参数时,访问失败:

1566226821375

添加token参数后:

1566226885002

8.负载均衡和熔断

Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 # 设置hystrix的超时时间为6000ms

十一.SpringCloud Config 分布式配置中心

**解决第四个问题,统一配置的问题**

1.为什么需要配置中心

举个简单的例子,我们想要修改8001模块的服务名称的值(spring.application.name),那么我们首先得找到8001模块,然后找到配置文件,然后再进去修改。如果修改修改8003模块的配置信息,重复之前的步骤。

所以我们需要一个集中管理所有微服务配置信息的地方。

微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务.由于每个服务都需要必要的配置信息才能运行(application.yml),所以一套集中式的、动态的配置管理设施是必不可少的.SpringCloud提供了ConfigServer来解决这个问题(我们每一个微服务自己带着一个application.yml,上百个配置文件的管理的问题)

**总而言之:是为了更加方便的帮助我们集中式的管理微服务架构里面微服务的配置信息。**

2. config配置中心

SpringCloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置,方便我们集中式的修改微服务的配置

SpringCloud Config分为服务端和客户端两部分.

服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口

客户端则是通过制定的配置中心来管理应用资源,以及业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容.(客户端可以是我们的8001,8003模块,也即是需要获取配置信息的微服务都是客户端)

功能:

  • 1.集中管理配置文件
  • 2.不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
  • 3.运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  • 4.当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
  • 5.将配置信息以REST接口的形式暴露

配置中心配置文件放置的位置:与GitHub整合配置

由于SpringCloudConfig默认使用Git来存储配置文件(也有其他方式,比如支持SVN和本地文件),
但最推荐的还是使用Git,而且使用的是http/https访问形式

3.SpringCloud Config服务端配置

3.1 在GitHub上新建一个名为springcloud-config-server的新Repository

git@github.com:JeremyKinge/springcloud-config-server.git

3.2 本地磁盘获取上述创建的仓库

git命令:
git clone git@github.com:JeremyKinge/springcloud-config-server.git

3.3 在上述磁盘新建配置文件并上传到git仓库

(1)添加配置文件application.yml

spring:
profiles:
active:
- dev
---
spring:
profiles: dev
application: #开发环境
name: microservicecloud-config-kingge-dev
---
spring:
profiles: test #测试环境
application:
name: microservicecloud-config-kingge-test
#请保存为UTF-8格式

(2)git bash执行上传命令:

git add .
git commit -m “新建配置文件”
git push origin master

(3)查看github

3.4 新建Module模块microservicecloud-config-10000它即为Cloud的配置中心模块

3.5 修改10000模块pom文件和yml配置文件、启动类

(1)修改pom文件

<dependencies>
<!-- springCloud Config 关键代码-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- 避免Config的Git插件报错:org/eclipse/jgit/api/TransportConfigCallback -->
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>4.10.0.201712302008-r</version>
</dependency>
<!-- 图形化监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 熔断 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 热部署插件 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>

关键代码

<!-- springCloud Config 关键代码-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

(2)修改yml文件

server:
port: 10000
spring:
application:
name: microservicecloud-config
cloud:
config:
server:
git:
uri: git@github.com:JeremyKinge/springcloud-config-server.git #GitHub上面的git仓库名字
#username: xxxxxx #如果访问github需要密码那么填写下面三项
#password: xxxxxxx
#force-pull: true

(3)启动类

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

3.6 完整项目结构

3.7 测试通过Config微服务是否可以从GitHub上获取配置内容

启动微服务10000
http://localhost:10000/application-dev.yml

输出:

http://localhost:10000/application-test.yml

输出:

http://localhost:10000/application-xxx.yml(不存在的配置)

成功实现了用SpringCloud Config通过GitHub获取配置信息

3.8 配置文件读取规则

上面3.8测试中,我们采用的是第二种方式,通过10000配置中心去github获取配置文件

请求例子:

/{application}-{profile}.yml
http://localhost:10000/application-dev.yml
http://localhost:10000/application-test.yml
http://localhost:10000/application-xxx.yml(不存在的配置)
/{application}/{profile}[/{label}]
http://localhost:10000/application/dev/master
http://localhost:10000/application/test/master
http://localhost:10000/application/xxx/master
/{label}/{application}-{profile}.yml
http://localhost:10000/master/application-dev.yml
http://localhost:10000/master/application-test.yml

4.SpringCloud Config客户端配置与测试

在上面中我们已经搭建好了,配置中心的服务端。那么接下来演示一下客户端怎么取获取服务端的配置信息。

新建8004服务提供者,他的配置信息我们取自配置中心(而不是在application.yml中配置,动态获取)

4.1 新建8004 配置文件并上传到github

新建microservicecloud-provider-person-config-client-8004.yml

spring:
profiles:
active:
- dev
---
server:
port: 8004
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.kingge.entity # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件
spring:
profiles: dev
application:
name: microservicecloud-person #很重要,对外暴露的微服务的名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://127.0.0.1:3306/test # 数据库名称
username: root
password: 123
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
#
eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/
# http://localhost:7001/eureka #单机版本使用
# defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/
instance:
instance-id: microservicecloud-person8001 #自定义服务实例名
prefer-ip-address: true #访问路径可以显示IP地址
lease-expiration-duration-in-seconds: 10 # 10秒即过期
lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
#
info:
app.name: ${spring.application.name}
company.name: kingge.top
build.artifactId: ${project.artifactId}
build.version: ${project.version}
app.desc: 这是一个提供查询部门人员信息的服务
---
server:
port: 8005
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.kingge.entity # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件
spring:
profiles: test
application:
name: microservicecloud-person #很重要,对外暴露的微服务的名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://127.0.0.1:3306/test2 # 数据库名称
username: root
password: 123
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
#
eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/
# http://localhost:7001/eureka #单机版本使用
# defaultZone: http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/
instance:
instance-id: microservicecloud-person8001 #自定义服务实例名
prefer-ip-address: true #访问路径可以显示IP地址
lease-expiration-duration-in-seconds: 10 # 10秒即过期
lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
#
info:
app.name: ${spring.application.name}
company.name: kingge.top
build.artifactId: ${project.artifactId}
build.version: ${project.version}
app.desc: 这是一个提供查询部门人员信息的服务

dev和test环境的不同在于,他们的服务端口不同和访问的数据库不同

4.2 microservicecloud-provider-person-config-client-8004模块

(1)修改pom文件

<dependencies>
<!-- 引入自己定义的api通用包,可以使用Person用户Entity -->
<dependency>
<groupId>com.kingge.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringCloud Config客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 将微服务provider端注册进eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>

关键依赖

<!-- SpringCloud Config客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

4.3 新建bootstrap.yml 系统级别配置文件

applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高

Spring Cloud会创建一个Bootstrap Context,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的EnvironmentBootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap contextApplication Context有着不同的约定,
所以新增了一个bootstrap.yml文件,保证Bootstrap ContextApplication Context配置的分离。

换句话说,如果将下面内容放到application.yml中,那么项目启动会报错,因为是找不到控制层ConfigClientRest导入的属性

@Value(“${spring.application.name}”)

(1)增加内容如下:

spring:
cloud:
config:
name: microservicecloud-provider-person-config-client-8004 #需要从github上读取的资源名称,注意没有yml后缀名
profile: dev #本次访问的配置项-profile值是什么,决定从github上读取什么
label: master
uri: http://localhost:10000 #本微服务启动后先去找config配置中心地址,通过SpringCloudConfig获取GitHub的服务地址

(2)为了配置文件的完整性我们新建一个空的application.yml

4.4 新建一个控制层,获取配置文件的某些属性(测试)

实际上如果8004服务端能够正常启动和访问,也能够说明客户端获取config配置中心配置文件成功。不过为了证实一下,所以这里通过控制层输出某些属性

(1)新建ConfigClientRest

@RestController
public class ConfigClientRest {
@Value("${spring.application.name}")
private String applicationName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServers;
@Value("${server.port}")
private String port;
@Value("${spring.datasource.url}")
private String datasourceurl;
@RequestMapping("/config")
public String getConfig()
{
String str = "applicationName: "+applicationName+"\t eurekaServers:"+eurekaServers+"\t port: "+port;
System.out.println("******str: "+ str);
return "applicationName: "+applicationName+"\t eurekaServers:"+eurekaServers+"\t port: "+port + "\t datasourceurl:"+datasourceurl;
}
}

后面可以通过更改 bootstrap.yml的profile属性的值,访问/config,查看服务端口号和数据库url是否改变。

4.5 新建启动类

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

4.5.1 完整目录机构

4.6 启动10000配置中心和8004服务

(1) 启动Config配置中心10000微服务并自测

(2)启动8004作为Client准备访问

  bootstrap.yml里面的profile值是什么,决定从github上读取什么

2.1 启动成功,说明配置文件获取成功



   2.2 额外验证
假如目前是 profile: dev
dev默认在github上对应的端口就是8004
http://localhost:8004/config
输出:applicationName: microservicecloud-person eurekaServers:http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/ port: 8004 datasourceurl:jdbc:mysql://127.0.0.1:3306/test


​ 假如目前是 profile: test
​ test默认在github上对应的端口就是8005
http://localhost:8005/config
​ 输出:applicationName: microservicecloud-person eurekaServers:http://peer1:7001/eureka/,http://peer2:7002/eureka/,http://peer3:7003/eureka/ port: 8005 datasourceurl:jdbc:mysql://127.0.0.1:3306/test2

5.手动刷新配置

上面的案例,有个缺点,那就是我们修改了github上面的配置文件(例如修改了连接数据库的地址),对应的8004模块没有刷新,也就是,他获取的连接数据库的地址还是未修改前的。

在真实的案例中,我们不可能手动关闭服务器,然后重启。所以就需要8004它能够自动刷新

1566377247710

1566377271635

需要依赖actuator组件和通过psot方式访问请求配置变动的服务器(也即是@RefreshScope注解所在的bean)

(1)8004模块引入actuator依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

(2)8004启动类添加@EnableDiscoveryClient服务发现注解

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

(3)8004模块修改application.yml文件,暴露所有端点

management:
security:
enabled: false

(4)8004添加controller

@RestController
@RefreshScope
public class ConfigClientRest {
@Value("${spring.application.name}")
private String applicationName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServers;
@Value("${server.port}")
private String port;
@Value("${spring.datasource.url}")
private String datasourceurl;
@RequestMapping("/config")
public String getConfig()
{
String str = "applicationName: "+applicationName+"\t eurekaServers:"+eurekaServers+"\t port: "+port;
System.out.println("******str: "+ str);
return "applicationName: "+applicationName+"\t eurekaServers:"+eurekaServers+"\t port: "+port + "\t datasourceurl:"+datasourceurl;
}
}

(5)启动configserver10000和8004模块

打印通过configserver从github上面获取的配置信息

1566379397446

此时数据库的url是:jdbc:mysql://127.0.0.1:3306/test1

(5)这个时候我们修改8004模块在github中引入的配置文件-

也就是修改microservicecloud-provider-person-config-client-8004.yml 文件。

1.修改访问数据库的url为,原先是test1

url: jdbc:mysql://127.0.0.1:3306/test2

2.修改8004的服务端口为8005,原先是8008 - 这个修改我们预测不会成功,因为服务器没有重启

server.port:8005

(6)请求:curl -X POST http://localhost:8008/refresh 手动更新8004模块的配置

我们只需要请求这个地址就可以实现配置文件的更新,而不用重启8004模块,他默认会帮我们刷新配置文件。

1566379561052

1566379675277

我们发现数据库访问地址修改成功,但是服务端口号没有成功(很明显这种做法是错误的,因为服务端口号不会轻易的变动)

注意

如果执行curl -X POST http://localhost:8008/refresh 包如下错误

{"timestamp":1513070580796,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource.","path":"/refresh"}

那就说明默认开启访问端口验证了。需要关闭

一、在Spring Boot1.5.x版本中通过management.security.enabled=false来暴露所有端点

img

二、切换SpringBoot版本为2.x 使用IDE的搜索功能,找到类ManagementServerProperties,发现Security内部类已经被删除,通过去官网查看2.0暴露端点的方式得知:

方式1:

# 启用端点 env
management.endpoint.env.enabled=true
# 暴露端点 env 配置多个,隔开
management.endpoints.web.exposure.include=env

方式2:

方式1中的暴露方式需要一个一个去开启需要暴露的端点,方式2直接开启和暴露所有端点

management.endpoints.web.exposure.include=*

注意在使用Http访问端点时,需要加上默认/actuator 前缀

三、如果这三种还不行,可以尝试在8004添加security验证依赖,然后给8004模块设置访问账号和密码, 然后通过 curl -X POST http://账号:密码@localhost:8008/refresh 这种方式访问

6.自动刷新配置

上面是通过手动刷新方式,缺点就是,如果我们在github上面修改了20个服务器的配置,那么我们需要手动执行20次 curl -X POST —— ,那么我们想能够缩减执行命令的次数或者说自动刷新配置。

额外知识补充

1.spring cloud服务发现注解之@EnableDiscoveryClient与@EnableEurekaClient区别

在使用服务发现的时候有两种注解,

一种为@EnableDiscoveryClient,

一种为@EnableEurekaClient,

用法上基本一致,下文是从stackoverflow上面找到的对这两者的解释

There are multiple implementations of "Discovery Service" (eureka, consul, zookeeper).
@EnableDiscoveryClient lives in spring-cloud-commons and picks the implementation on the classpath.
@EnableEurekaClient lives in spring-cloud-netflix and only works for eureka. If eureka is on your classpath, they are effectively the same.

意思也就是spring cloud中discovery service有许多种实现(eureka、consul、zookeeper等等)

@EnableDiscoveryClient基于spring-cloud-commons;而 @EnableEurekaClient基于spring-cloud-netflix

对@EnableEurekaClient的源码如下:

/**
* Convenience annotation for clients to enable Eureka discovery configuration
* (specifically). Use this (optionally) in case you want discovery and know for sure that
* it is Eureka you want. All it does is turn on discovery and let the autoconfiguration
* find the eureka classes if they are available (i.e. you need Eureka on the classpath as
* well).
*
* @author Dave Syer
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
public @interface EnableEurekaClient {
}

注解@EnableEurekaClient上有@EnableDiscoveryClient注解,可以说基本就是EnableEurekaClient有@EnableDiscoveryClient的功能,另外上面的注释中提到,其实@EnableEurekaClient注解就是一种方便使用eureka的注解而已,可以说使用其他的注册中心后,都可以使用@EnableDiscoveryClient注解,

但是使用@EnableEurekaClient的情景,就是在服务采用eureka作为注册中心的时候,使用场景较为单一。

所以还是比较建议使用@EnableDiscoveryClient。

所以上面的还是建议使用@EnableDiscoveryClient替换@EnableEurekaClient

2.Ribbon和Feign

这两种都是SPringcloud提供的负载均衡技术,都是客户端的软负载均衡技术。feign优化的Ribbon的服务调用方式,实现了面向接口编程的封装。

如果你感觉文章对你又些许感悟,你可以支持我!!
-------------本文结束感谢您的阅读-------------