SpringCloud-1-基础组件(Eureka,Ribbon,Consul)

发布于:2023-01-05 ⋅ 阅读:(1244) ⋅ 点赞:(0)

目录

1 微服务基础知识

1.1 系统架构的演变

1.1.1 单体应用架构

1.1.2 垂直应用架构

1.1.3 分布式SOA架构

1.1.4 微服务架构

1.1.5 SOA与微服务的关系

1.2 分布式核心知识

1.2.1 分布式中的远程调用

1.2.2 分布式中的CAP原理

1.3 常见微服务框架

1.3.1 SpringCloud

1.3.2 ServiceComb

1.3.3 ZeroC ICE

2 SpringCloud概述

2.1 微服务中的相关概念

2.1.1 服务注册与发现

2.1.2 负载均衡

2.1.3 熔断

2.1.4 链路追踪

2.1.5 API网关

2.2 SpringCloud的介绍

2.3 SpringCloud的架构

2.3.1 SpringCloud中的核心组件

2.3.2 SpringCloud的体系结构

 2.3 SpringCloud的架构

2.3.1 SpringCloud中的核心组件

3 案例搭建

3.1 数据库表

3.2 搭建环境

3.3 搭建商品微服务

3.3.1 编写实体类

3.3.2 编写dao接口

3.3.3 编写service层

实现类

3.3.4 编写web层

3.3.5 配置启动类

3.3.6 配置yml文件

3.4 其他微服务

​编辑

3.5 服务调用

3.5.1 RestTemplate介绍

3.5.2 RestTemplate方法介绍

3.5.3 通过RestTemplate调用微服务

3.5.4 硬编码存在的问题

3 服务注册Eureka基础

3.1 微服务的注册中心

3.1.1 注册中心的主要作用

3.2 Eureka的概述

3.2.1 Eureka的基础知识

3.2.2 Eureka的交互流程与原理

3.3 搭建Eureka注册中心

3.3.1 搭建Eureka服务中心

3.3.2 服务注册中心管理后台

3.4 服务注册到Eureka注册中心

3.4.1 商品服务注册

3.4.2 订单服务注册

3.4.3 用户服务注册

3.5 Eureka中的自我保护

3.6 Eureka中的元数据

4 服务注册Eureka高级

4.1 Eureka Server 高可用集群

4.1.1 搭建 Eureka Server高可用集群

4.2 Eureka中的常见问题

4.2.4 自我保护

4.3 Eureka源码解析

4.3.1 SpringBoot中的自动装载

5 Ereka替换方案Consul

5.1 Eureka闭源的影响

5.1.1 Eureka闭源影响

5.1.2 Eureka的替换方案

5.2 什么是consul

5.2.1 consul 概述

5.2.2 consul与Eureka的区别

5.2.3 consul的下载与安装

5.3 consul的基本使用

5.3.1 服务注册与发现

5.3.2 Consul的KV存储

5.4 基于consul的服务注册

5.4.1 案例准备

5.4.2 配置服务注册

5.4.3 在控制台中查看服务列表

5.5 基于consul的服务发现

5.6 consul高可用集群

5.6.1 Consul的核心知识

5.6.2 Consul 集群搭建

5.6.3 Consul 常见问题

6 服务调用Ribbon入门

6.2 Ribbon概述

6.2.1 什么是Ribbon

6.2.2 Ribbon的主要作用

6.3 基于Ribbon实现服务调用

6.3.1 坐标依赖

6.3.2 工程改造

6.4 基于Ribbon实现负载均衡

6.4.1 什么是负载均衡

6.4.2 客户端负载均衡与服务端负载均衡

6.4.3 搭建多服务实例

6.4.4 负载均衡策略

6.5 基于Ribbon实现超时与重试

6.5.1 引入spring的重试坐标

6.5.2 在消费者appllication.yal中配置

6.6 Ribbon中负载均衡的源码解析


1 微服务基础知识

1.1 系统架构的演变

随着互联网的发展,网站应用的规模不断扩大,常规的应用架构已无法应对,分布式服务架构以及微服务架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

1.1.1 单体应用架构

Web 应用程序发展的早期,大部分 web 工程 ( 包含前端页面 ,web 层代码 ,service 层代码 ,dao 层代码 ) 是所有的功能模块, 打包到一起并放在一个 web 容器中运行。 比如搭建一个电商系统:客户下订单,商品展示,用户管理。这种将所有功能都部署在一个web 容器中运行的系统就叫做单体架构。
优点:
所有的功能集成在一个项目工程中。项目架构简单,前期开发成本低,周期短,小型项目的首选。
缺点:
全部功能集成在一个工程中,对于大型项目不易开发、扩展及维护。系统性能扩展只能通过扩展集群结点,成本高、有瓶颈。技术栈受限。

1.1.2 垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
优点
项目架构简单,前期开发成本低,周期短,小型项目的首选。通过垂直拆分,原来的单体项目不至于无限扩大。不同的项目可采用不同的技术。
缺点
全部功能集成在一个工程中,对于大型项目不易开发、扩展及维护。系统性能扩展只能通过扩展集群结点,成本高、有瓶颈。

1.1.3 分布式SOA架构

1.1.3.1 什么是 SOA
SOA 全称为 Service-Oriented Architecture ,即面向服务的架构。它可以根据需求通过网络对松散耦合的粗粒度应用组件( 服务 ) 进行分布式部署、组合和使用。一个服务通常以独立的形式存在于操作系统进程中。站在功能的角度,把业务逻辑抽象成可复用、可组装的服务,通过服务的编排实现业务的快速再生,目的:把原先固有的业务功能转变为通用的业务服务,实现业务逻辑的快速复用。
通过上面的描述可以发现 SOA 有如下几个特点:分布式、可重用、扩展灵活、松耦合。
1.1.3.2 SOA 架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定 的服务 中心,使前端应用能更快速的响应多变的市场需求
优点 :抽取公共的功能为服务, 提高开发效率。对不同的服务进行集群化部署解决系统压力。基于ESB/DUBBO 减少系统耦合。
缺点 :抽取服务的粒度较大,服务提供方与调用方接口耦合度较高。

1.1.4 微服务架构

优点
通过服务的原子化拆分,以及微服务的独立打包、部署和升级,小团队的交付周期将缩短,运维成本也将大幅度下降。
微服务遵循单一原则。微服务之间采用Restful 等轻量协议传输。
缺点
微服务过多,服务治理成本高,不利于系统维护。
分布式系统开发的技术成本高(容错、分布式事务等)。

1.1.5 SOA与微服务的关系

SOA Service Oriented Architecture ) 面向服务的架构”: 他是一种设计方法,其中包含多个服务,服务之间通过相互依赖最终提供一系列的功能。一个服务通常以独立的形式存在与操作系统进程中。各个服务之间通过网络调用。
微服务架构 : 其实和 SOA 架构类似 , 微服务是在 SOA 上做的升华,微服务架构强调的一个重点是 业务需要彻底的组件化和服务化” ,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。

1.2 分布式核心知识

1.2.1 分布式中的远程调用

在微服务架构中,通常存在多个服务之间的远程调用的需求。远程调用通常包含两个部分:序列化和通信协议。常见的序列化协议包括json xml hession protobuf thrift text bytes 等,目前主流的远程调用技术有基于HTTP RESTful 接口以及基于 TCP RPC 协议。
1 RESTful 接口
REST ,即 Representational State Transfer 的缩写,如果一个架构符合 REST 原则,就称它为 RESTful 架构。
总结一下什么是 RESTful 架构:
  1. 每一个URI代表一种资源;
  2. 客户端和服务器之间,传递这种资源的某种表现层;
  3. 客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"

2 RPC 协议
RPC Remote Procedure Call )一种进程间通信方式。允许像调用本地服务一样调用远程服务。RPC框架的主要目标就是让远程服务调用更简单、透明。RPC 框架负责屏蔽底层的传输方式( TCP 或者UDP)、序列化方式( XML/JSON/ 二进制)和通信细节。开发人员在使用的时候只需要了解谁在什么位置提供了什么样的远程服务接口即可,并不需要关心底层通信细节和调用过程。

3 )区别与联系
1 HTTP 相对更规范,更标准,更通用,无论哪种语言都支持 http 协议。如果你是对外开放 API ,例如开放平台,外部的编程语言多种多样,你无法拒绝对每种语言的支持,现在开源中间件,基本最先支持的几个协议都包含RESTful
2 RPC 框架作为架构微服务化的基础组件,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率,屏蔽跨进程调用函数(服务)的各类复杂细节。让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现服务。

1.2.2 分布式中的CAP原理

现如今,对于多数大型互联网应用,分布式系统( distributed system )正变得越来越重要。分布式系统的最大难点,就是各个节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。CAP理论由 Eric Brewer ACM 研讨会上提出,而后 CAP 被奉为分布式领域的重要理论。分布式系统的CAP理论,首先把分布式系统中的三个特性进行了如下归纳:
  1. Consistency(一致性):数据一致更新,所有数据的变化都是同步的。
  2. Availability(可用性):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
  3. Partition tolerance(分区容忍性):某个节点的故障,并不影响整个系统的运行。
通过学习 CAP 理论,我们得知任何分布式系统只可同时满足二点,没法三者兼顾,既然一个分布式系统无法同时满足一致性、可用性、分区容错性三个特点,所以我们就需要抛弃一样:
  • CA :放弃分区容错性,加强一致性和可用性,其实就是传统的关系型数据库的选择。
  • AP :放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,例如很多NoSQL系统就是如此。
  • CP :放弃可用性,追求一致性和分区容错性,基本不会选择,网络问题会直接让整个系统不可用。

需要明确一点的是,在一个分布式系统当中,分区容忍性和可用性是最基本的需求,所以在分布是系统中,我们的系统最当关注的就是A (可用性) P (容忍性),通过补偿的机制寻求数据的一致性。

1.3 常见微服务框架

1.3.1 SpringCloud

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

1.3.2 ServiceComb

Apache ServiceComb 是业界第一个Apache微服务顶级项目,是一个开源微服务解决方案,致力于帮助企业、用户和开发者将企业应用轻松微服务化上云,并实现对微服务应用的高效运维管理。其提供一站 式开源微服务解决方案,融合SDK框架级、0侵入ServiceMesh场景并支持多语言。

1.3.3 ZeroC ICE

ZeroC IceGrid ZeroC 公司的杰作,继承了 CORBA 的血统,是新一代的面向对象的分布式系统中间件。作为一种微服务架构,它基于RPC 框架发展而来,具有良好的性能与分布式能力。

2 SpringCloud概述

2.1 微服务中的相关概念

2.1.1 服务注册与发现

服务注册 :服务实例将自身服务信息注册到注册中心。这部分服务信息包括服务所在主机 IP 和提供服务的Port ,以及暴露服务自身状态以及访问协议等信息。
服务发现 :服务实例请求注册中心获取所依赖服务信息。服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。

2.1.2 负载均衡

负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性和可靠性。

2.1.3 熔断

熔断这一概念来源于电子工程中的断路器(Circuit Breaker )。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

2.1.4 链路追踪

随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要对一次请求涉及的多个服务链路进行日志记录,性能监控即链路追踪

2.1.5 API网关

随着微服务的不断增多,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信可能出现:客户端需要调用不同的url 地址,增加难度再一定的场景下,存在跨域请求的问题每个微服务都需要进行单独的身份认证针对这些问题,API 网关顺势而生。
API 网关 直面意思是将所有 API 调用统一接入到 API 网关层,由网关层统一接入和输出。一个网关的基本功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后,各个API服务提供团队可以专注于自己的的业务逻辑处理,而 API 网关更专注于安全、流量、路由等问题。

2.2 SpringCloud的介绍

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

2.3 SpringCloud的架构

2.3.1 SpringCloud中的核心组件

Spring Cloud 的本质是在 Spring Boot 的基础上,增加了一堆微服务相关的规范,并对应用上下文 (Application Context )进行了功能增强。既然 Spring Cloud 是规范,那么就需要去实现,目前 Spring Cloud 规范已有 Spring 官方, Spring Cloud Netflflix Spring Cloud Alibaba 等实现。通过组件化的方式,Spring Cloud 将这些实现整合到一起构成全家桶式的微服务技术栈。

2.3.2 SpringCloud的体系结构

从上图可以看出 Spring Cloud 各个组件相互配合,合作支持了一套完整的微服务架构。
  • 注册中心负责服务的注册与发现,很好将各服务连接起来
  • 断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护。
  • API网关负责转发所有对外的请求和服务
  • 配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
  • 链路追踪技术可以将所有的请求数据记录下来,方便我们进行后续分析
  • 各个组件又提供了功能完善的dashboard监控平台,可以方便的监控各组件的运行状况

 2.3 SpringCloud的架构

2.3.1 SpringCloud中的核心组件

Spring Cloud 的本质是在 Spring Boot 的基础上,增加了一堆微服务相关的规范,并对应用上下文(Application Context )进行了功能增强。既然 Spring Cloud 是规范,那么就需要去实现,目前Spring Cloud 规范已有 Spring 官方, Spring Cloud Netflflix Spring Cloud Alibaba 等实现。通过组件化的方式,Spring Cloud 将这些实现整合到一起构成全家桶式的微服务技术栈。
Spring Cloud Netflflix 组件

Spring Cloud Alibaba组件  

Spring Cloud原生及其他组件

3 案例搭建

使用微服务架构的分布式系统 , 微服务之间通过网络通信。我们通过服务提供者与服务消费者来描述微服务间的调用关系。
服务提供者:服务的被调用方,提供调用接口的一方。
服务消费者:服务的调用方,依赖于其他服务的一方。
我们以电商系统中常见的用户下单为例,用户向订单微服务发起一个购买的请求。在进行保存订单之前需要调用商品微服务查询当前商品库存,单价等信息。在这种场景下,订单微服务就是一个服务消费者,商品微服务就是一个服务提供者。

3.1 数据库表

资料中提供了案例中所需要的数据库表与实体类。
用户表
CREATE TABLE `tb_user` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `username` varchar(40) DEFAULT NULL COMMENT '用户名',
 `password` varchar(40) DEFAULT NULL COMMENT '密码',
 `age` int(3) DEFAULT NULL COMMENT '年龄',
 `balance` decimal(10,2) DEFAULT NULL COMMENT '余额',
 `address` varchar(80) DEFAULT NULL COMMENT '地址',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
商品表
CREATE TABLE `tb_product` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `product_name` varchar(40) DEFAULT NULL COMMENT '名称',
 `status` int(2) DEFAULT NULL COMMENT '状态',
 `price` decimal(10,2) DEFAULT NULL COMMENT '单价',
 `product_desc` varchar(255) DEFAULT NULL COMMENT '描述',
 `caption` varchar(255) DEFAULT NULL COMMENT '标题',
 `inventory` int(11) DEFAULT NULL COMMENT '库存',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
订单表
CREATE TABLE `tb_order` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `user_id` int(11) DEFAULT NULL COMMENT '用户id',
 `product_id` int(11) DEFAULT NULL COMMENT '商品id',
 `number` int(11) DEFAULT NULL COMMENT '数量',
 `price` decimal(10,2) DEFAULT NULL COMMENT '单价',
 `amount` decimal(10,2) DEFAULT NULL COMMENT '总额',
 `product_name` varchar(40) DEFAULT NULL COMMENT '商品名',
 `username` varchar(40) DEFAULT NULL COMMENT '用户名',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

注意:mysql创建数据库时设置字符集编码为utf8mb4,排序规则为utf8mb4_unicode_ci

create database test default character set utf8mb4 collate utf8mb4_unicode_ci;

3.2 搭建环境

1 )创建父工程 shop_parent在IDEA 中创建父工程 shop_parent 并引入坐标。
    <groupId>cn.itcast</groupId>
    <artifactId>spring_cloud_demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>product_service</module>
        <module>order_service</module>
        <module>eureka_server</module>
        <module>import_test</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

<!--        lombok省略get和set方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2 )创建微服务工程模块
创建公共模块 shop_service_common , 用于存放公共的实体类和工具类。
创建订单微服务模块 shop_service_order。
创建商品微服务模块 shop_service_product。
创建用户微服务模块 shop_service_user。
3 )设置依赖
工程的依赖结构如图

3.3 搭建商品微服务

3.3.1 编写实体类

shop_service_common中创建 cn.awen.entity.Product 实体类,并配置。

@Data
@Entity
@Table(name="tb_product")
public class Product {
 @Id
 private Long id;
 private String productName;
 private Integer status;
 private BigDecimal price;
 private String productDesc;
 private String caption;
 }
这里使用了 lombok 简化实体类的开发。Lombok能以简单的注解形式来简化 java 代码,提高开发人员的开发效率。

3.3.2 编写dao接口

/**
 * 接口继承
 */
public interface ProductDao extends JpaRepository<Product,Long>, JpaSpecificationExecutor<Product> {
}

JpaRepository:JpaRepository 接口是我们开发时使用的最多的接口。其特点是可以帮助我们将其他接口的方法的返回值做适配处理。可以使得我们在开发时更方便的使用这些方法。

JpaSpecificationExecutor:完成多条件查询,并且支持分页与排序。

3.3.3 编写service

接口


public interface ProductService {

	/**
	 * 根据id查询
	 */
	Product findById(Long id);

	/**
	 * 保存
	 */
	void save(Product product);
	/**
	 * 更新
	 */
	void update(Product product);
	/**
	 * 删除
	 */
	void delete(Long id);
}

实现类

@Service
public class ProductServiceImpl implements ProductService {

	@Autowired
	private ProductDao productDao;

	@Override
	public Product findById(Long id) {
		return productDao.findById(id).get();
	}

	@Override
	public void save(Product product) {
		productDao.save(product);
	}

	@Override
	public void update(Product product) {
		productDao.save(product);
	}

	@Override
	public void delete(Long id) {
		productDao.deleteById(id);
	}
}

3.3.4 编写web

shop_service_product 中创建 cn.itcast.product.controller.ProductController、 controller中使用的 @GetMapping 是一个组合注解 , 相当与 @RequestMapping(method="get") 。类似的注解还有@PostMapping @PutMapping @DeleteMapping
@RestController
@RequestMapping("/product")
public class ProductController {
 @Autowired
 private ProductService productService;
 @GetMapping
 public List findAll() {
 return productService.findAll();
 }
 @GetMapping("/{id}")
 public Product findById(@PathVariable Long id) {
 return productService.findById(id);
 }
 @PostMapping
 public String save(@RequestBody Product product) {
 productService.save(product);
 return "保存成功";
 }
 @PutMapping("/{id}")
 public String update(@RequestBody Product product) {
 productService.update(product);
 return "修改成功";
 }
 @DeleteMapping("/{id}")
 public String delete(Long id) {
 productService.delete(id);
 return "删除成功";
 }
}

3.3.5 配置启动类

@SpringBootApplication
@EntityScan("cn.itcast.product.entity")

public class ProductApplication {

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

3.3.6 配置yml文件

server:
  port: 9011 #端口
spring:
  application:
    name: service-product #服务名称
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
    username: root
    password: 111111
  jpa:
    database: MySQL
    show-sql: true
    open-in-view: true

3.4 其他微服务

3.5 服务调用

前文已经编写了三个基础的微服务,在用户下单时需要调用商品微服务获取商品数据。那应该怎么做呢?总人皆知商品微服务提供了供人调用的HTTP 接口。所以可以再下定单的时候使用 http 请求的相关工具类完成,如常见的HttpClient OkHttp ,当然也可以使用 Spring 提供的 RestTemplate

3.5.1 RestTemplate介绍

Spring 框架提供的 RestTemplate 类可用于在应用中调用 rest 服务,它简化了与 http 服务的通信方式,统一了RESTful 的标准,封装了 http 链接,我们只需要传入url 及返回值类型即可。相较于之前常用的HttpClient, RestTemplate 是一种更优雅的调用 RESTful 服务的方式。
在Spring 应用程序中访问第三方 REST 服务与使用 Spring RestTemplate 类有关。 RestTemplate 类的设计原则与许多其他Spring 模板类 ( 例如 JdbcTemplate JmsTemplate) 相同,为执行复杂任务提供了一种具有默认行为的简化方法。RestTemplate默认依赖 JDK 提供 http 连接的能力( HttpURLConnection ),如果有需要的话也可以通过setRequestFactory方法替换为例如 Apache HttpComponents Netty OkHttp 等其它 HTTP library 。考虑到RestTemplate 类是为调用 REST 服务而设计的,因此它的主要方法与 REST 的基础紧密相连就不足为奇了,后者是HTTP 协议的方法 :HEAD GET POST PUT DELETE OPTIONS 。例如, RestTemplate类具有 headForHeaders() getForObject() postForObject() put() delete() 等方法。

3.5.2 RestTemplate方法介绍

3.5.3 通过RestTemplate调用微服务

1 )在 shop_service_order 工程中 ProductApplication 启动类 中配置 RestTemplate
@SpringBootApplication
@EntityScan("cn.itcast.order.entity")
public class OrderApplication {

	/**
	 * 使用spring提供的RestTemplate发送http请求到商品服务
	 *      1.创建RestTemplate对象交给容器管理
	 *      2.在使用的时候,调用其方法完成操作 (getXX,postxxx)
	 * * @LoadBalanced : 是ribbon提供的负载均衡的注解
	 */
	@LoadBalanced
	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}

	public static void main(String[] args) {
		SpringApplication.run(OrderApplication.class,args);
	}
}
2) 编写下订单方法
@RestController
@RequestMapping("/order")
public class OrderController {

	//注入restTemplate对象
	@Autowired
	private RestTemplate restTemplate;
	
	/**
	 * 参数:商品id
	 *  通过订单系统,调用商品服务根据id查询商品信息
	 *      1.需要配置商品对象
	 *      2.需要调用商品服务
	 *  使用java中的urlconnection,httpclient,okhttp
	 */
	@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
		Product product = null;
		//如何调用商品服务?
		product = restTemplate.getForObject("http://127.0.0.1:9001/product/1",Product.class);
		return product;
	}


}

3.5.4 硬编码存在的问题

至此已经可以通过 RestTemplate 调用商品微服务的 RESTFul API 接口。但是我们把提供者的网络地址(ip ,端口)等硬编码到了代码中,这种做法存在许多问题: 应用场景有局限。无法动态调整。 那么应该怎么解决呢,就需要通过注册中心动态的对服务注册和服务发现。

3 服务注册Eureka基础

3.1 微服务的注册中心

注册中心可以说是微服务架构中的 通讯录 ,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用。

3.1.1 注册中心的主要作用

服务注册中心(下称注册中心)是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:
1. 服务发现:
  • 服务注册/反注册:保存服务提供者和服务调用者的信息。
  • 服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能。
  • 服务路由(可选):具有筛选整合服务提供者的能力。
2. 服务配置:
  • 配置订阅:服务提供者和服务调用者订阅微服务相关的配置。
  • 配置下发:主动将配置推送给服务提供者和服务调用者。
3. 服务健康检测
  • 检测服务提供者的健康情况。
3.1.2 常见的注册中心
Zookeeper
zookeeper 它是一个分布式服务框架,是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。简单来说zookeeper= 文件系统 + 监听通知机制。
Eureka
Eureka 是在 Java 语言上,基于 Restful Api 开发的服务注册与发现组件, Springcloud Netflflix 中的重要组件。
Consul
Consul 是由 HashiCorp 基于 Go 语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,采用Raft 算法保证服务的一致性,且支持健康检查。
Nacos
Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说 Nacos 就是注册中心 + 配置中心的组合,提供简单易用的特性集,帮助我们解决微服务开发必会涉及到的服务注册与发现,服务配置,服务管理等问题。Nacos 还是 Spring Cloud Alibaba 组件之一,负责 服务注册与发现。
最后我们通过一张表格大致了解Eureka Consul Zookeeper 的异同点。选择什么类型的服务注册与发现组件可以根据自身项目要求决定。

3.2 Eureka的概述

3.2.1 Eureka的基础知识

Eureka Netflflix 开发的服务发现框架, SpringCloud 将它集成在自己的子项目 spring-cloud-netflflix 中,实现SpringCloud 的服务发现功能。
上图简要描述了 Eureka 的基本架构,由 3 个角色组成:
1 Eureka Server
提供服务注册和发现
2 Service Provider
服务提供方
将自身服务注册到 Eureka ,从而使服务消费方能够找到
3 Service Consumer
服务消费方
Eureka 获取注册服务列表,从而能够消费服务

3.2.2 Eureka的交互流程与原理

  • Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;
  • Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka ClientEureka Server进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒)以续约自己的信息。如果Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒);
  • 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册表的同步;
  • Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者。
  • 综上,Eureka通过心跳检测、健康检查和客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用性。

3.3 搭建Eureka注册中心

3.3.1 搭建Eureka服务中心

(1) 创建shop_eureka_server子模块
shop_parent 下创建子模块 shop_eureka_server
(2) 引入 maven 坐标

<!--引入euraka坐标-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
(3) 配置 application.yml

server:
  port: 9000 #端口
#配置eureka server
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false #是否将自己注册到注册中心
    fetch-registry: false #是否从eureka中获取注册信息
    service-url: #配置暴露给Eureka Client的请求地址
      defaultZone: http://127.0.0.1:8000/eureka/
(4) 配置启动类
@SpringBootApplication
//激活eureakaserver
@EnableEurekaServer
public class EurekaServerApplication {

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

3.3.2 服务注册中心管理后台

打开浏览器访问 http://localhost:9000 即可进入 EurekaServer 内置的管理控制台 , 显示效果如下

3.4 服务注册到Eureka注册中心

3.4.1 商品服务注册

(1) 商品模块中引入坐标
shop_service_product pom 文件中添加 eureka client 的相关坐标
        <!--引入EurekaClient-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
(2) 配置 application.yml 文件
在工程的 application.yml 中添加 Eureka Server 的主机地址
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/ #多个eurekaserver之间用,隔开
  instance:
    prefer-ip-address: true #使用ip地址注册
(3) 修改启动类添加服务注册注解
Spring Cloud Edgware 版本开始, @EnableDiscoveryClient @EnableEurekaClient可以省略 。只需加上相关依赖,并进行相应配置,即可将微服务注册到服务发现组件上。
@SpringBootApplication
//激活eureakaserver
@EnableEurekaServer
public class EurekaServerApplication {

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

3.4.2 订单服务注册

和商品微服务一样 , 只需要引入坐标依赖 , 在工程的 application.yml 中添加 Eureka Server 的主机地址即可

3.4.3 用户服务注册

和商品微服务一样 , 只需要引入坐标依赖 , 在工程的 application.yml 中添加 Eureka Server 的主机地址
即可

3.5 Eureka中的自我保护

微服务第一次注册成功之后,每 30 秒会发送一次心跳将服务的实例信息注册到注册中心。通知 EurekaServer 该实例仍然存在。如果超过 90 秒没有发送更新,则服务器将从注册信息中将此服务移除。Eureka Server在运行期间,会统计心跳失败的比例在 15 分钟之内是否低于 85% ,如果出现低于的情况(在单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),Eureka Server 会将当前的实例注册信息保护起来,同时提示这个警告。保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server 将会尝试保护其服务注册表中的 信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)验证完自我保护机制开启后,并不会马上呈现到web 上,而是默认需等待 5 分钟(可以通过 eureka.server.wait- time - in - ms - when - sync - empty 配置),即 5 分钟后你会看到提示信息。
如果关闭自我保护通过设置 eureka.enableSelfPreservation=false 来关闭自我保护功能。

3.6 Eureka中的元数据

Eureka 的元数据有两种:标准元数据和自定义元数据。
标准元数据:主机名、 IP 地址、端口号、状态页和健康检查等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
自定义元数据:可以使用 eureka.instance.metadata-map 配置,符合 KEY/VALUE 的存储格式。这些元数据可以在远程客户端中访问。
在程序中可以使用 DiscoveryClient 获取指定微服务的所有元数据信息。
	@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
	/**
	 * 注入DiscoveryClient :
	 *  springcloud提供的获取原数组的工具类
	 *      调用方法获取服务的元数据信息
	 *
	 */
		//调用discoveryClient方法
		//已调用服务名称获取所有的元数据
		List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
		//获取唯一的一个元数据
		ServiceInstance instance = instances.get(0);
		//根据元数据中的主机地址和端口号拼接请求微服务的URL
		Product product = null;
		//如何调用商品服务?
		product = restTemplate.getForObject("http://"+instance.getHost()+":"+instance.getPort()+"/product/1",Product.class);
		return product;

4 服务注册Eureka高级

4.1 Eureka Server 高可用集群

在上一个章节,实现了单节点的 Eureka Server 的服务注册与服务发现功能。 Eureka Client 会定时连接Eureka Server,获取注册表中的信息并缓存到本地。微服务在消费远程 API 时总是使用本地缓存中的数据。因此一般来说,即使Eureka Server 发生宕机,也不会影响到服务之间的调用。但如果 Eureka Server宕机时,某些微服务也出现了不可用的情况, Eureka Server 中的缓存若不被刷新,就可能会影响到微服务的调用,甚至影响到整个用系统的高可用。因此,在生成环境中,通常会部署一个高可用的Eureka Server 集Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署, Eureka Server 实例会彼此增量地同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server 的默认行为。

4.1.1 搭建 Eureka Server高可用集群

1 )Earuka相互注册
端口9000的Eureka注册到端口为8000的Eureka上, 端口8000的Eureka注册到端口为9000的Eureka上
#模拟两个EurekaServer
#端口9000 8000 
#两个server相互注册
#配置eureka server
spring:
  application:
    name: eureka-server
server:
  port: 9000 #端口
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: true#是否将自己注册到注册中心
    fetch-registry: true#是否从eureka中获取注册信息
    service-url: #配置暴露给Eureka Client的请求地址
      defaultZone: http://127.0.0.1:8000/eureka/
#模拟两个EurekaServer
#端口9000 8000 
#两个server相互注册
#配置eureka server
spring:
  application:
    name: eureka-server
server:
  port: 8000 #端口
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: true#是否将自己注册到注册中心
    fetch-registry: true#是否从eureka中获取注册信息
    service-url: #配置暴露给Eureka Client的请求地址
      defaultZone: http://127.0.0.1:9000/eureka/
4.1.2 服务注册到 Eureka Server 集群
如果需要将微服务注册到 Eureka Server 集群只需要修改 yml 配置文件即可
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
修改 defaultZone 配置添加多个 Eureka Server 的地址,中间已逗号隔开。

4.2 Eureka中的常见问题

4.2.1 服务注册慢
默认情况下,服务注册到 Eureka Server 的过程较慢。 SpringCloud 官方文档中给出了详细的原因大致含义:服务的注册涉及到心跳,默认心跳间隔为30s 。在实例、服务器、客户端都在本地缓存中具有相同的元数据之前,服务不可用于客户端发现(所以可能需要3 次心跳)。可以通过配置 eureka.instance.leaseRenewalIntervalInSeconds ( 心跳频率 ) 加快客户端连接到其他服务的过程。在生产中,最好坚持使用默认值,因为在服务器内部有一些计算,他们对续约做出假设。
解决方案如下:
Eureka Server :配置关闭自我保护,设置剔除无效节点的时间间隔。
#配置Eureka
eureka:
  server:
   enable-self-preservation: false  #关闭自我保护
   eviction-interval-timer-in-ms: 4000 #剔除时间间隔,单位:毫秒

4.2.2 服务节点剔除问题

默认情况下,由于 Eureka Server 剔除失效服务间隔时间为 90s 且存在自我保护的机制。所以不能有效而迅速的剔除失效节点,这对开发或测试会造成困扰。
解决方案如下:
Eureka Client :配置开启健康检查,并设置续约时间。
#配置Eureka
eureka:
  instance:
   preferIpAddress: true
   lease-expiration-duration-in-seconds: 10 #eureka client发送心跳给server端后,续
约到期时间(默认90秒)
   lease-renewal-interval-in-seconds: 5 #发送心跳续约间隔

4.2.3 监控页面显示ip

Eureka Server的管控台中,显示的服务实例名称默认情况下是微服务定义的名称和端口。为了更好的对所有服务进行定位,微服务注册到Eureka Server的时候可以手动配置示例ID。配置方式如下:

#配置Eureka
eureka:
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id

4.2.4 自我保护

统计十五分钟内心跳比例是否低于85%,如果是则开启自我保护模式,不在对宕机的服务进行剔除。

#配置eureka server
eureka:
  server:
    enable-self-preservation: false #关闭自我保护
    eviction-interval-timer-in-ms: 4000 #剔除服务间隔

4.3 Eureka源码解析

4.3.1 SpringBoot中的自动装载

1 ImportSelector
ImportSelector 接口是 Spring 导入外部配置的核心接口,在 SpringBoot 的自动化配置和 @EnableXXX( 功能性注解) 中起到了决定性的作用。当在 @Confifiguration 标注的 Class 上使用 @Import 引入了一个ImportSelector实现类后,会把实现类中返回的 Class 名称都定义为 bean 。DeferredImportSelector接口继承 ImportSelector, 他和 ImportSelector 的区别在于装载 bean 的时机上,DeferredImportSelector 需要等所有的 @Confifiguration 都执行完毕后才会进行装载接下来我们写一个小例子, 看下 ImportSelector 的用法。

1 )定义 Bean 对象
2 )定义配置类 Configuration
3 ) 定义 ImportSelector
4 定义 EnableXXX 注解
5 ) 测试
在这里我们可以看到 ImportSelector 接口的返回值会递归进行解析,把解析到的类全名按照
@Confifiguration 进行处理
2 springBoot 自动装载

5 Ereka替换方案Consul

5.1 Eureka闭源的影响

5.1.1 Eureka闭源影响

Euraka GitHub 上,宣布 Eureka 2.x 闭源。近这意味着如果开发者继续使用作为 2.x 分支上现有工作repo 一部分发布的代码库和工件,则将自负风险。

5.1.2 Eureka的替换方案

Zookeeper
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google Chubby 一个开源的实现,是Hadoop Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
Consul
consul 是近几年比较流行的服务发现工具,工作中用到,简单了解一下。 consul 的三个主要应用场景:服务发现、服务隔离、服务配置。
Nacos
Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos 致力于帮助您发现、配置和管理微服务。 Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以 服务 为中心的现代应用架构 ( 例如微服务范式、云原
生范式 ) 的服务基础设施。

5.2 什么是consul

5.2.1 consul 概述

Consul HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更 一站式 ,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性 ( 支持 Linux windows 和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。
Consul 的优势:
使用 Raft 算法来保证一致性 , 比复杂的 Paxos 算法更直接 . 相比较而言 , zookeeper 采用的是Paxos, 而 etcd 使用的则是 Raft 。支持多数据中心,内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障, 而其部署则需要考虑网络延迟 , 分片等情况等。 zookeeper etcd 均不提供多数据中心功能的支持。支持健康检查。 etcd 不提供此功能。支持 http dns 协议接口。 zookeeper 的集成较为复杂 , etcd 只支持 http 协议。官方提供 web 管理界面 , etcd 无此功能。综合比较, Consul 作为服务注册和配置管理的新星 , 比较值得关注和研究。
特性:
  • 服务发现
  • 健康检查
  • Key/Value 存储
  • 多数据中心

5.2.2 consulEureka的区别

1 )一致性
Consul 强一致性( CP ):服务注册相比Eureka 会稍慢一些。因为 Consul raft 协议要求必须过半数的节点都写入成功才认为注册成功。Leader挂掉时,重新选举期间整个 consul 不可用。保证了强一致性但牺牲了可用性。
Eureka 保证高可用和最终一致性( AP ):服务注册相对要快,因为不需要等注册信息replicate 到其他节点,也不保证注册信息是否replicate成功。当数据出现不一致时,虽然A, B 上的注册信息不完全相同,但每个 Eureka 节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A 查不到,但请求 B 就能查到。如此保证了可用性但牺牲了一致性。
2 )开发语言和使用
eureka 就是个 servlet 程序,跑在 servlet 容器中。Consul则是 go 编写而成,安装启动即可。

5.2.3 consul的下载与安装

Consul 不同于 Eureka 需要单独安装,访问 Consul 官网 下载 Consul 的最新版本。在linux 虚拟中下载 consul 服务 启动 consul 服务:启动成功之后访问: http://IP:8500 ,可以看到 Consul 的管理界面。
1)进入consul安装目录
consul agent -dev -client=0.0.0.0
2)进入到管理后台页面
http://127.0.0.1:8500

5.3 consul的基本使用

Consul 支持健康检查 , 并提供了 HTTP DNS 调用的 API 接口 完成服务注册,服务发现,以及 K/V 存储这些功能。接下来通过发送HTTP请求的形式来了解一下 Consul

5.3.1 服务注册与发现

1 )注册服务
通过 postman 发送put请求到http://192.168.137.1 :8500/v1/catalog/register 地址可以完成服务注册
2 )服务查询
通过 postman 发送 get请求到 http://192.168.137.1:8500/v1/catalog/services 查看所有的服务列表
通过 postman 发送 get请求到http://192.168.137.1 :8500/v1/catalog/service/ 服务名查看具体的服务详
3 )服务删除
通过 postman 发送 put请求到http://192.168.137.1 :8500/v1/catalog/deregister 删除服务

5.3.2 ConsulKV存储

5.4 基于consul的服务注册

5.4.1 案例准备

1 )复制一份新的工程进行配置
拷贝一份新的工程,起名为 spring_cloud_consul_demo,并导入相关的子模块
2 )修改微服务的相关 pom 文件
修改每个微服务的 pom 文件,添加 SpringCloud 提供的基于 Consul 的依赖。其中 spring - cloud - starter - consul - discovery SpringCloud 提供的对 consul 支持的相关依赖。spring- boot - starter - actuator 适用于完成心跳检测响应的相关依赖。
        <!--springcloud 提供的对基于consul的服务发现-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!--actuator的健康检查-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

5.4.2 配置服务注册

  ###开始配置consul的服务注册
  cloud:
    consul:
      host: 127.0.0.1 #consul服务器的主机地址
      port: 8500 #consul服务器的ip地址
      discovery:
        #是否需要注册
        register: true
        #注册的实例ID (唯一标志)
        instance-id: ${spring.application.name}-1
        #服务的名称
        service-name: ${spring.application.name}
        #服务的请求端口
        port: ${server.port}
        #指定开启ip地址注册
        prefer-ip-address: true
        #当前服务的请求ip
        ip-address: ${spring.cloud.client.ip-address}

5.4.3 在控制台中查看服务列表

打开 ConsulServer 的管理控制台,可以发现两个个微服务已经全部注册到 Consul 中了。

5.5 基于consul的服务发现

由于 SpringCloud Consul 进行了封装。对于在消费者端获取服务提供者信息和 Eureka 是一致的。同样使用 DiscoveryClient 完成调用获取微服务实例信息。

5.6 consul高可用集群

5.6.1 Consul的核心知识

Gossip 协议(流言协议)
传统的监控,如 ceilometer ,由于每个节点都会向 server 报告状态,随着节点数量的增加 server 的压力随之增大。在所有的Agent 之间(包括服务器模式和普通模式)运行着 Gossip 协议。服务器节点和普通Agent都会加入这个 Gossip 集群,收发 Gossip 消息。每隔一段时间,每个节点都会随机选择几个节点发送Gossip 消息,其他节点会再次随机选择其他几个节点接力发送消息。这样一段时间过后,整个集群都 能收到这条消息。示意图如下。

 RAFT一致性算法

为了实现集群中多个ConsulServer中的数据保持一致性,consul使用了基于强一致性的RAFT算法。

Raft 中,任何时候一个服务器可以扮演下面角色之一:
  • 1. Leader: 处理所有客户端交互,日志复制等,一般一次只有一个Leader.
  • 2. Follower: 类似选民,完全被动
  • 3. Candidate(候选人): 可以被选为一个新的领导人。
Leader 全权负责所有客户端的请求,以及将数据同步到 Follower 中(同一时刻系统中只存在一个Leader)。 Follower 被动响应请求 RPC ,从不主动发起请求 RPC Candidate Follower Leader转换的中间状态。

5.6.2 Consul 集群搭建

 首先需要有一个正常的Consul集群,有Server,有Leader。这里在服务器Server1Server2Server3上分别部署了Consul Server。(这些服务器上最好只部署Consul程序,以尽量维护Consul Server的稳定

1准备环境

  • Agent client 模式启动的节点。在该模式下,该节点会采集相关信息,通过 RPC 的方式向server 发送。Client模式节点有无数个,官方建议搭配微服务配置
  • Agent server 模式启动的节点。一个数据中心中至少包含 1 server 节点。不过官方建议使用3 或 5 server 节点组建成集群,以保证高可用且不失效率。server 节点参与 Raft、维护会员信息、注册服务、健康检查等功能。
2 安装 consul 并启动
1)在每个 consul 节点上安装 consul 服务,下载安装过程和单节点一致。
##从官网下载最新版本的Consul服务
wget https://releases.hashicorp.com/consul/1.5.3/consul_1.5.3_linux_amd64.zip
##使用unzip命令解压
unzip consul_1.5.3_linux_amd64.zip
##将解压好的consul可执行命令拷贝到/usr/local/bin目录下
cp consul /usr/local/bin
##测试一下
consul
2)启动每个consul server 节点。
##登录s1虚拟机,以server形式运行
consul agent -server -bootstrap-expect 3 -data-dir /etc/consul.d -node=server-1 
-bind=192.168.74.101 -ui -client 0.0.0.0 &
##登录s2 虚拟机,以server形式运行
consul agent -server -bootstrap-expect 2 -data-dir /etc/consul.d -node=server-2 
-bind=192.168.74.102 -ui -client 0.0.0.0 & 
##登录s3 虚拟机,以server形式运行
consul agent -server -bootstrap-expect 2 -data-dir /etc/consul.d -node=server-3 
-bind=192.168.74.103 -ui -client 0.0.0.0 &
##在本地电脑中使用client形式启动consul
consul agent -client=0.0.0.0  -data-dir /etc/consul.d -node=client-1

​​​server:以server身份启动。

  • -bootstrap-expect:集群要求的最少server数量,当低于这个数量,集群即失效。
  • -data-dirdata存放的目录,更多信息请参阅consul数据同步机制
  • -node:节点id,在同一集群不能重复。
  • -client:客户端的ip地址(0.0.0.0表示不限制)
  • & :在后台运行,此为linux脚本语法

3每个节点加入集群

在s2s3s4 服务其上通过consul join 命令加入 s1中的consul集群中。

##加入consul集群
consul join 192.168.74.101

4测试

在任意一台服务器中输入 consul members 查看集群中的所有节点信息。

5.6.3 Consul 常见问题

1 )节点和服务注销
当服务或者节点失效, Consul 不会对注册的信息进行剔除处理,仅仅标记已状态进行标记(并且不可使用)。如果担心失效节点和失效服务过多影响监控。可以通过调用HTTP API 的形式进行处理节点和服务的注销可以使用HTTP API:
  • 注销任意节点和服务:/catalog/deregister
  • 注销当前节点的服务:/agent/service/deregister/:service_id
如果某个节点不继续使用了,也可以在本机使用 consul leave 命令,或者在其它节点使用 consul forceleave 节点 Id
2 )健康检查与故障转移
  • 在集群环境下,健康检查是由服务注册到的Agent来处理的,那么如果这个Agent挂掉了,那么此节点的健康检查就处于无人管理的状态。
  • consul members从实际应用看,节点上的服务可能既要被发现,又要发现别的服务,如果节点挂掉了,仅提供被发现的功能实际上服务还是不可用的。当然发现别的服务也可以不使用本机节点,可以通过访问一个Nginx实现的若干Consul节点的负载均衡来实现。

6 服务调用Ribbon入门

经过以上的学习,已经实现了服务的注册和服务发现。当启动某个服务的时候,可以通过 HTTP 的形式将信息注册到注册中心,并且可以通过SpringCloud 提供的工具获取注册中心的服务列表。但是服务之间的调用还存在很多的问题,如何更加方便的调用微服务,多个微服务的提供者如何选择,如何负载均衡等。

6.2 Ribbon概述

6.2.1 什么是Ribbon

Netflixfa 发布的一个负载均衡器,有助于控制 HTTP TCP 客户端行为。在 SpringCloud 中,Eureka一般配合 Ribbon 进行使用, Ribbon 提供了客户端负载均衡的功能, Ribbon 利用从 Eureka 中读取到的服务信息,在调用服务节点提供的服务时,会合理的进行负载。在SpringCloud 中可以将注册中心和 Ribbon 配合使用, Ribbon 自动的从注册中心中获取服务提供者的列表信息,并基于内置的负载均衡算法,请求服务。

6.2.2 Ribbon的主要作用

(1)服务调用

基于Ribbon实现服务调用,是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助RestTemplate 最终进行调用。

2 )负载均衡
当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址。

(3)超时与​​​​​​​​​​​​​​重试

当多个服务之前某个服务发生错误时,会去掉另一个相同的服务。

6.3 基于Ribbon实现服务调用

不论是基于 Eureka 的注册中心还是基于 Consul 的注册中心, SpringCloudRibbon 统一进行了封装,所以对于服务调用,两者的方式是一样的。

6.3.1 坐标依赖

springcloud 提供的服务发现的 jar 中以及包含了 Ribbon 的依赖。所以这里不需要导入任何额外的坐标。

6.3.2 工程改造

1 服务消费者
修改 shop_service_order模块中ProductController中findById() 方法如下:
	/**
	 * 基于ribbon的形式调用远程微服务
	 *  1.使用@LoadBalanced声明RestTemplate
	 *  2.使用服务名称替换ip地址
	 */
	@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
	public Product findById(@PathVariable Long id) {
		Product product = null;
		product = restTemplate.getForObject("http://service-product/product/1",Product.class);
		return product;
	}

(2服务消费者

修改服务消费者 shop_service_order 模块中的启动类 OrderApplication ,在创建 RestTemplate 方法上添加 @LoadBalanced 注解在 shop_service_order OrderController 中添加下单方法,并使用 RestTemplate 完成服务调用。
@SpringBootApplication
@EntityScan("cn.itcast.order.entity")
public class OrderApplication {

	/**
	 * 使用spring提供的RestTemplate发送http请求到商品服务
	 *      1.创建RestTemplate对象交给容器管理
	 *      2.在使用的时候,调用其方法完成操作 (getXX,postxxx)
	 * * @LoadBalanced : 是ribbon提供的负载均衡的注解
	 */
	@LoadBalanced
	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}

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

6.3.3 代码测试

浏览器中请求 http://localhost:9001/order/buy/1 查看展示效果如下,已经可以在订单微服务中已服务名称的形式调用商品微服务获取数据

6.4 基于Ribbon实现负载均衡

6.4.1 什么是负载均衡

在搭建网站时,如果单节点的 web 服务性能和可靠性都无法达到要求;或者是在使用外网服务时,经常担心被人攻破,一不小心就会有打开外网端口的情况,通常这个时候加入负载均衡就能有效解决服务问题。负载均衡是一种基础的网络服务,其原理是通过运行在前面的负载均衡服务,按照指定的负载均衡算法,将流量分配到后端服务集群上,从而为系统提供并行扩展的能力。负载均衡的应用场景包括流量包、转发规则以及后端服务,由于该服务有内外网个例、健康检查等功能,能够有效提供系统的安全性和可用性。

6.4.2 客户端负载均衡与服务端负载均衡

服务端负载均衡:先发送请求到负载均衡服务器或者软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配
客户端负载均衡:客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。
​​​​​​​

6.4.3 搭建多服务实例

修改 shop_service_product application.yml 配置文件,已 profifiles 的形式配置多个实例 。​​​​​​ 分别启动两次服务器通过不同端口验证效果,并查看两个控制台发现已轮询的方式调用了商品服务。
server:
  port: 9011 #端口
spring:
  application:
    name: service-product #服务名称
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
  jpa:
    database: MySQL
    show-sql: true
    open-in-view: true
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/ #多个eurekaserver之间用,隔开
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id
server:
  port: 9001 #端口
spring:
  application:
    name: service-product #服务名称
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
  jpa:
    database: MySQL
    show-sql: true
    open-in-view: true
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/ #多个eurekaserver之间用,隔开
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id: ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id

6.4.4 负载均衡策略

Ribbon 内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为com.netflix.loadbalancer.IRule ,实现方式如下
  • com.netflix.loadbalancer.RoundRobinRule :以轮询的方式进行负载均衡。
  • com.netflix.loadbalancer.RandomRule :随机策略
  • com.netflix.loadbalancer.RetryRule :重试策略。
  • com.netflix.loadbalancer.WeightedResponseTimeRule :权重策略。会计算每个服务的权重,越高的被调用的可能性越大。
  • com.netflix.loadbalancer.BestAvailableRule :最佳策略。遍历所有的服务实例,过滤掉故障实例,并返回请求数最小的实例返回。
  • com.netflix.loadbalancer.AvailabilityFilteringRule :可用过滤策略。过滤掉故障和请求数超过阈值的服务实例,再从剩下的实力中轮询调用。
在服务消费者的 application.yml 配置文件中修改负载均衡策略
#修改ribbon的负载均衡策略   服务名 -  ribbon - NFLoadBalancerRuleClassName : 策略
service-product:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
策略选​​​​​​​择:
1 、如果每个机器配置一样,则建议不修改策略 ( 推荐 )
2 、如果部分机器配置强,则可以改为 WeightedResponseTimeRule

6.5 基于Ribbon实现超时与重试

6.5.1 引入spring的重试坐标

        <!--        引入重试的坐标-->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

6.5.2 在消费者appllication.yal中配置

#修改ribbon的负载均衡策略   服务名 -  ribbon - NFLoadBalancerRuleClassName : 策略
service-product:
  ribbon:
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

6.6 Ribbon中负载均衡的源码解析

本文含有隐藏内容,请 开通VIP 后查看