博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Cloud:软件架构发展流程&Spring Cloud&Eureka服务注册中心&Ribbon负载均衡&Hystrix熔断器
阅读量:2056 次
发布时间:2019-04-28

本文共 26051 字,大约阅读时间需要 86 分钟。

1. 系统架构演变概述

1.1 集中式架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。

在这里插入图片描述

优点:

  • 系统开发速度快
  • 维护成本低
  • 适用于并发要求较低的系统

缺点:

  • 代码耦合度高,后期维护困难
  • 无法针对不同模块进行针对性优化
  • 无法水平扩展
  • 单点容错率低,并发能力差

1.2 垂直拆分

当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分

在这里插入图片描述

优点:

  • 系统拆分实现了流量分担,解决了并发问题
  • 可以针对不同模块进行优化
  • 方便水平扩展,负载均衡,容错率提高

缺点:

  • 系统间相互独立,会有很多重复开发工作,影响开发效率

1.3 分布式服务

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。

在这里插入图片描述

优点:

  • 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率

缺点:

  • 系统间耦合度变高,调用关系错综复杂,难以维护

1.4 面向服务架构(SOA)

SOA(Service Oriented Architecture)面向服务的架构:它是一种设计方法,其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务 通常以独立的形式存在与操作系统进程中。各个服务之间 通过网络调用。

SOA结构图:

在这里插入图片描述

ESB(企业服务总线),简单 来说 ESB 就是一根管道,用来连接各个服务节点。为了集 成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通。

SOA缺点:每个供应商提供的ESB产品有偏差,自身实现较为复杂;应用服务粒度较大,ESB集成整合所有服务和协议、数据转换使得运维、测试部署困难。所有服务都通过一个通路通信,直接降低了通信速度。

学习目标:了解项目架构的演变历程

总结

2. 微服务架构说明

微服务架构是使用一套小服务来开发单个应用的方式或途径,每个服务基于单一业务能力构建,运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,并能够通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。

微服务结构图:

在这里插入图片描述

API Gateway网关是一个服务器,是系统的唯一入口。为每个客户端提供一个定制的API。API网关核心是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。如它还可以具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。通常,网关提供RESTful/HTTP的方式访问服务。而服务端通过服务注册中心进行服务注册和管理。

微服务的特点:

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

微服务架构与SOA都是对系统进行拆分;微服务架构基于SOA思想,可以把微服务当做去除了ESB的SOA。ESB是SOA架构中的中心总线,设计图形应该是星形的,而微服务是去中心化的分布式软件架构。两者比较类似,但其实也有一些差别:

功能 SOA 微服务
组件大小 大块业务逻辑 单独任务或小块业务逻辑
耦合 通常松耦合 总是松耦合
管理 着重中央管理 着重分散管理
目标 确保应用能够交互操作 易维护、易扩展、更轻量级的交互

学习目标:了解SOA与微服务架构的区别以及说出微服务架构的特点

解析

SOA使用了ESB组件的面向服务架构:ESB自身实现复杂;应用服务粒度较大,所有服务之间的通信都经过ESB会降低通信速度;部署、测试ESB比较麻烦。

总结

微服务架构:是一套使用小服务或者单一业务来开发单个应用的方式或途径。

微服务架构特点:

  • 单一职责
  • 服务粒度小
  • 面向服务(对外暴露REST api)
  • 服务之间相互独立

与使用ESB的SOA架构的区别:微服务架构没有使用ESB,有服务治理注册中心;业务粒度小。

3. 服务调用方式说明

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

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

  • RPC:Remote Produce Call远程过程调用,RPC基于Socket,工作在会话层。自定义数据格式,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表

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

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

区别:RPC的机制是根据语言的API(language API)来定义的,而不是根据基于网络的应用来定义的。如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么Spring Cloud搭建微服务是不二之选。

学习目标:能够说出服务调用方式种类

总结

  • RPC:基于socket,速度快,效率高;webservice、dubbo
  • HTTP:基于TCP,封装比较臃肿;对服务和调用方没有任何技术、语言的限定,自由灵活,支持RESTful,Spring Cloud

区别:

rpc存在服务的依赖,通过NIO进行远程调用
而HTTP是基于http协议进行远程调用,不存在依赖关系.

4. Spring RestTemplate示例工程导入

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

  • HttpClient
  • OKHttp
  • JDK原生的URLConnection(RestTemplate默认的)

不过这些不同的客户端,API各不相同。而Spring也有对http的客户端进行封装,提供了工具类叫RestTemplate。

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

例如:

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

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

启动springboot项目,在项目中的测试类中直接 @Autowired 注入:

@RunWith(SpringRunner.class)@SpringBootTestpublic class RestTemplateTest {
@Autowired private RestTemplate restTemplate; @Test public void test(){
String url = "http://localhost/user/8"; //restTemplate可以对json格式字符串进行反序列化 User user = restTemplate.getForObject(url, User.class); System.out.println(user); }}

将会转换连接中获取到的json格式的对象为User对象并打印

通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。
在这里插入图片描述

学习目标:了解Spring RestTemplate的应用

解析

一般情况下有如下三种http客户端工具类包都可以方便的进行http服务调用:

  • httpClient
  • okHttp
  • JDK原生URLConnection

spring 提供了RestTemplate的工具类对上述的3种http客户端工具类进行了封装,可在spring项目中使用RestTemplate进行服务调用

5. Spring Cloud概述

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

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

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

5.1 简介

Spring Cloud是Spring旗下的项目之一,官网地址:

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

Spring Cloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等功能;协调分布式环境中各个系统,为各类服务提供模板性配置。其主要涉及的组件包括:

  • Eureka:注册中心
  • Zuul、Gateway:服务网关
  • Ribbon:负载均衡
  • Feign:服务调用
  • Hystrix或Resilience4j:熔断器

以上只是其中一部分,架构图:

在这里插入图片描述

5.2 版本

Spring Cloud不是一个组件,而是许多组件的集合;它的版本命名比较特殊,是以A到Z的为首字母的一些单词(其实是伦敦地铁站的名字)组成:

在这里插入图片描述

在项目中,使用最新稳定的Greenwich版本。

学习目标:Spring Cloud整合的组件和版本特征

总结

  • 整合的组件可以有很多组件;常见的组件有:eureka注册中心,Gateway网关,Ribbon负载均衡,Feign服务调用,Hystrix熔断器。在有需要的时候项目添加对于的启动器依赖即可。
  • 版本特征:以英文单词命名(伦敦地铁站名)

6. 创建微服务工程

微服务中需要同时创建多个项目,为了方便课堂演示,先创建一个父工程,然后后续的工程都以这个工程为父,实现maven的聚合。这样可以在一个窗口看到所有工程,方便讲解。在实际开发中,每个微服务可独立一个工程。

6.1 创建父项目

  1. 编写项目信息

  2. 编写保存位置

  3. 然后将 pom.xml 中添加如下

    org.springframework.boot
    spring-boot-starter-parent
    2.1.5.RELEASE
    1.8
    Greenwich.SR1
    2.1.5
    5.1.46
    org.springframework.cloud
    spring-cloud-dependencies
    ${spring-cloud.version}
    pom
    import
    tk.mybatis
    mapper-spring-boot-starter
    ${mapper.starter.version}
    mysql
    mysql-connector-java
    ${mysql.version}
    org.projectlombok
    lombok
    org.springframework.boot
    spring-boot-maven-plugin

    这里已经对大部分要用到的依赖的版本进行了 管理,方便后续使用

    子项目在下面会有讲解搭建

学习目标:创建微服务父工程heiheihei-springcloud、用户服务工程user-service、服务消费工程consumer-demo

解析

需求:查询数据库中的用户数据并输出到浏览器

  • 父工程heiheihei-springcloud:添加spring boot父坐标和管理其它组件的依赖
  • 用户服务工程user-service:整合mybatis查询数据库中用户数据;提供查询用户服务
  • 服务消费工程consumer-demo:利用查询用户服务获取用户数据并输出到浏览器

总结

org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import

通过 scope 的import可以继承 spring-cloud-dependencies 工程中的依赖

7. 搭建配置user-service工程

7.1 创建服务提供者

新建一个项目user-service,对外提供查询用户的服务。

  1. 选中父工程, 创建module

  2. 填写module信息,注意:子模块要在父工程的下级目录:

  3. 添加依赖

    org.springframework.boot
    spring-boot-starter-web
    tk.mybatis
    mapper-spring-boot-starter
    mysql
    mysql-connector-java
  4. 编写配置文件,采用yaml语法,而不是properties

    server:  port: 9091spring:  datasource:    driver-class-name: com.mysql.jdbc.Driver    url: jdbc:mysql://localhost:3306/springcloud    username: root    password: rootmybatis:  type-aliases-package: com.itheiheihei.user.pojo
  5. 编写代码

    1. 编写启动类

      @SpringBootApplication@MapperScan("com.itheiheihei.user.mapper")public class userApplication {
      public static void main(String[] args) {
      SpringApplication.run(userApplication.class, args); }}
    2. 编写实体类

      /** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/30 20:51 */@Data@Table(name = "tb_user")public class User{
      // id @Id //开启主键自动回填 @KeySql(useGeneratedKeys = true) private Long id; // 用户名 private String userName; // 密码 private String password; // 姓名 private String name; // 年龄 private Integer age; // 性别,1男性,2女性 private Integer sex; // 出生日期 private Date birthday; // 创建时间 private Date created; // 更新时间 private Date updated; // 备注 private String note;}
    3. 编写Mapper接口

      /** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/30 20:54 */public interface UserMapper extends Mapper
      {
      }
    4. 编写Service

      /** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/30 21:01 */@Servicepublic class UserService {
      @Autowired private UserMapper userMapper; /** * 根据主键查询用户 * @param id 用户id * @return 用户 */ public User queryById(long id) {
      return userMapper.selectByPrimaryKey(id); }}
    5. 编写Controller,并添加一个对外查询的接口处理器

      /** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/30 21:04 */@RestController@RequestMapping("/user")public class UserController {
      @Autowired private UserService userService; @RequestMapping("/{id}") public User queryById(@PathVariable Long id) {
      return userService.queryById(id); }}
  6. 启动并测试

    在这里插入图片描述

学习目标:配置user-service工程并能够根据用户id查询数据库中用户

解析

需求:可以访问http://localhost:9091/user/8输出用户数据

实现步骤:

  1. 添加启动器依赖(web、通用Mapper);
  2. 创建启动引导类和配置文件;
  3. 修改配置文件中的参数;
  4. 编写测试代码(UserMapper,UserService,UserController);
  5. 测试

8. 搭建配置consumer-demo工程

  1. 选中父工程, 创建module

  2. 填写module信息,注意:子模块要在父工程的下级目录:

  3. 添加依赖

    org.springframework.boot
    spring-boot-starter-web
  4. 编写代码

    1. 编写启动类

      /*** @author 嘿嘿嘿1212* @version 1.0* @date 2019/11/1 9:16*/@SpringBootApplicationpublic class ConsumerApplication {
      public static void main(String[] args) {
      SpringApplication.run(ConsumerApplication.class, args); } @Bean public RestTemplate restTemplate() {
      return new RestTemplate(); }}
    2. 编写实体类

      /** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/30 20:51 */@Datapublic class User{
      // id private Long id; // 用户名 private String userName; // 密码 private String password; // 姓名 private String name; // 年龄 private Integer age; // 性别,1男性,2女性 private Integer sex; // 出生日期 private Date birthday; // 创建时间 private Date created; // 更新时间 private Date updated; // 备注 private String note;}
    3. 编写Controller,并添加一个对外查询的接口处理器

      /*** @author 嘿嘿嘿1212* @version 1.0* @date 2019/11/1 9:16*/@RestController@RequestMapping("/consumer")public class ConsumerController {
      @Autowired public RestTemplate restTemplate; @RequestMapping("/{id}") public User queryById(@PathVariable Long id) {
      User user = restTemplate.getForObject("http://localhost:9091/user/10", User.class); return user; }}
  5. 启动并测试

    在这里插入图片描述

学习目标:编写测试类使用restTemplate访问user-service的路径根据id查询用户

解析

需求:访问http://localhost:8080/consumer/8 使用RestTemplate获取http://localhost:9091/user/10的数据

实现步骤:

  1. 添加启动器依赖;
  2. 创建启动引导类(注册RestTemplate)和配置文件;
  3. 编写测试代码(ConsumerController中使用restTemplate访问服务获取数据)
  4. 测试

存在问题

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

上述的问题都可以通过Spring Cloud的各种组件解决。

9. Eureka注册中心说明

Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你

同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。这就实现了服务的自动注册、发现、状态监控。

9.1 原理图

在这里插入图片描述

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

学习目标:说出Eureka的主要功能

总结

Eureka的主要功能是进行服务管理,定期检查服务状态,返回服务地址列表。

10. 搭建eureka-server工程

创建一个项目 eureka-server ,启动一个Eureka Server Application服务注册中心

项目中的 pom.xml 文件如下:

itheiheihei-springcloud
com.itheiheihei
1.0-SNAPSHOT
4.0.0
enreka-service
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
  1. 编写启动类

    package com.itheiheihei;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer@SpringBootApplicationpublic class EnrekaApplication {
    public static void main(String[] args) {
    SpringApplication.run(EnrekaApplication.class, args); }}
  2. 编写配置文件application.yml

    server:  port: 10086spring:  application:    name: enreka-serviceeureka:  client:    service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。      defaultZone: http://127.0.0.1:10086/enreka    fetch-registry: false #自身不被拉取    register-with-eureka: false # 不注册自己
  3. 启动服务

    在这里插入图片描述

学习目标:添加eureka对应依赖和编写引导类搭建eureka服务并可访问eureka服务界面

解析

Eureka是服务注册中心,只做服务注册;自身并不提供服务也不消费服务。可以搭建web工程使用Eureka,可以使用Spring Boot方式搭建。

搭建步骤:

  1. 创建工程;
  2. 添加启动器依赖;
  3. 编写启动引导类(添加Eureka的服务注解)和配置文件;
  4. 修改配置文件(端口,应用名称…);
  5. 启动测试

11. 服务注册与发现

学习目标:将user-service的服务注册到eureka并在consumer-demo中可以根据服务名称调用

解析

  • 服务注册:在服务提供工程user-service上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表。

    • 添加依赖;

      org.springframework.cloud
      spring-cloud-starter-netflix-eureka-client
    • 改造启动引导类;添加开启Eureka客户端发现的注解;

      @SpringBootApplication@MapperScan("com.itheiheihei.user.mapper")@EnableDiscoveryClient //开启Eureka客户发现功能public class userApplication {
      public static void main(String[] args) {
      SpringApplication.run(userApplication.class, args); }}
    • 修改配置文件;设置Eureka 服务地址

      eureka:  client:    service-url: #eureka注册中心地址      defaultZone:  http://127.0.0.1:10086/eureka
  • 服务发现:在服务消费工程consumer-demo上添加Eureka客户端依赖;可以使用工具类根据服务名称获取对应的服务地址列表。

    • 添加依赖;

      org.springframework.cloud
      spring-cloud-starter-netflix-eureka-client
    • 改造启动引导类;添加开启Eureka客户端发现的注解;

      @SpringBootApplication@MapperScan("com.itheiheihei.user.mapper")@EnableDiscoveryClient //开启Eureka客户发现功能public class userApplication {
      public static void main(String[] args) {
      SpringApplication.run(userApplication.class, args); }}
    • 修改配置文件;设置Eureka 服务地址

      spring:  application:     name: consumer-demoeureka:  client:    service-url: #eureka注册中心地址      defaultZone:  http://127.0.0.1:10086/eureka
    • 改造处理器类ConsumerController,可以使用工具类DiscoveryClient根据服务名称获取对应服务地址列表。

      /** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/11/1 9:16 */@RestController@RequestMapping("/consumer")public class ConsumerController {
      @Autowired private DiscoveryClient discoveryClient; @Autowired public RestTemplate restTemplate; @RequestMapping("/{id}") public User queryById(@PathVariable Long id) {
      String url = "http://localhost:9091/user/10"; List
      serviceInstances = discoveryClient.getInstances("user-service"); ServiceInstance serviceInstance = serviceInstances.get(0); //拼接URL url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id; User user = restTemplate.getForObject(url, User.class); return user; }}

总结

  • 添加Eureka客户端依赖;

    org.springframework.cloud
    spring-cloud-starter-netflix-eureka-client
  • 添加启动引导类注解;

    在这里插入图片描述

  • 修改配置(服务名称与eureka注册中心地址)

    spring:  application:    name: consumer-demoeureka:  client:    service-url:      defaultZone: http://127.0.0.1:10086/eureka

12. Eureka Server高可用配置

12.1 Eureka 基础架构

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

  • 服务注册中心

    Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server

  • 服务提供者

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

  • 服务消费者

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

实际提供者与消费者是一样的配置的,微服务不区分提供者与消费者

12.2 高可用的Eureka Server

Eureka Server即服务的注册中心,EurekaServer也可以是一个集群,形成高可用的Eureka中心。

服务同步

多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

而作为客户端,需要把信息注册到每个Eureka中:

在这里插入图片描述如果有三个Eureka,则每一个EurekaServer都需要注册到其它几个Eureka服务中,例如:有三个分别为10086、10087、10088,则:

  • 10086要注册到10087和10088上
  • 10087要注册到10086和10088上
  • 10088要注册到10086和10087上

动手搭建高可用的EurekaServer

我们假设要搭建两台EurekaServer的集群,端口分别为:10086和10087

实际上公司使用时是在不同服务器进行集群搭建,而且eureke牺牲了数据一致性(可能存在服务注册失败,而造成注册数据不一致)

  1. 修改原来的EurekaServer配置

    server:  port: ${
    port:10086}spring: application: name: enreka-serviceeureka: client: service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。 defaultZone: ${
    defaultZone:http://127.0.0.1:10086/eureka} #fetch-registry: false #自身不被拉取 #register-with-eureka: false # 不注册自己

    service-url:是指定注册的eureka地址(在eureka项目自己启动了就是一个eureka注册中心,而这里需要注册到其他的eureka注册中心,实现高可用)单机的时候进行配置是为了进行对默认注册eureka的服务配置进行修改而不将本身进行服务注册

    所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务,注册到其它EurekaServer上,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

    注意把register-with-eureka和fetch-registry修改为true或者注释掉

    在上述配置文件中的${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用,若无则使用后面的默认值
    把service-url的值改成了另外一台EurekaServer的地址,而不是自己

  2. 另外一台在启动的时候可以指定端口port和defaultZone配置:

  3. 启动测试;同时启动两台eureka server

    在这里插入图片描述

  4. 客户端注册服务到集群

    因为EurekaServer不止一个,因此 user-service 项目注册服务或者 consumer-demo 获取服务的时候,service-url参数需要修改为如下:

    eureka:  client:    service-url: #eureka注册中心地址      defaultZone:  http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

学习目标:可以启动两台eureka-server实例;在eureka管理界面看到两个实例

解析

Eureka Server是一个web应用,可以启动多个实例(配置不同端口)保证Eureka Server的高可用。

高可用配置:将Eureka Server作为一个服务注册到其它Eureka Server,这样多个Eureka Server之间就能够互相发现对方,同步服务,实现Eureka Server集群。

13. Eureka客户端与服务端配置

目标:配置eureka客户端user-service的注册、续约等配置项,配置eureka客户端consumer-demo的获取服务间隔时间;了解失效剔除和自我保护

分析

  • Eureka客户端工程
    • user-service 服务提供
      • 服务地址使用ip方式
      • 续约
    • consumer-demo 服务消费
      • 获取服务地址的频率
  • Eureka服务端工程 eureka-server
    • 失效剔除
    • 自我保护

小结

  • user-service

    服务续约

    eureka:  client:    service-url:      defaultZone: http://127.0.0.1:10086/eureka  instance:    # 更倾向使用ip地址,而不是host名    prefer-ip-address: true    # ip地址    ip-address: 127.0.0.1    # 续约间隔,默认30秒    lease-renewal-interval-in-seconds: 5    # 服务失效时间,默认90秒    lease-expiration-duration-in-seconds: 5

    也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,

    EurekaServer就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer-in-ms设定的时间)从服务列表中移除,这两个值在生产环境不要修改,默认即可。

  • consumer-demo

    获取服务列表

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

    eureka:  client:    service-url:      defaultZone: http://127.0.0.1:10086/eureka    # 获取服务地址列表间隔时间,默认30秒    registry-fetch-interval-seconds: 10
  • eureka-server

    失效剔除和自我保护

    服务下线

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

    失效剔除

    有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。

    自我保护

    当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的

    比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。

    可以通过下面的配置来关停自我保护:

    eureka:  server:    # 服务失效剔除时间间隔,默认60秒    eviction-interval-timer-in-ms: 60000    # 关闭自我保护模式(默认是打开的)    enable-self-preservation: false

14. 负载均衡Ribbon简介

但是实际环境中,往往会开启很多个 user-service 的集群。此时获取的服务列表中就会有多个,到底该访问哪一个呢?

一般这种情况下就需要编写负载均衡算法,在多个实例列表中进行选择。
不过Eureka中已经集成了负载均衡组件:Ribbon,简单修改代码即可使用。

什么是Ribbon?

Ribbn是Netfix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为.为Ribbon配置服务提供者地址列表,Ribbon就可以基于某种负载均衡算法,自动地帮助服务消费者去请求.Ribbon默认为我们提供了很多的负载均衡算法,例如轮询,随机等,当然,我们也可为Ribbon实现自定义的负载均衡算法

学习目标:描述负载均衡和ribbon的作用

解析

负载均衡是一个算法,可以通过该算法实现从地址列表中获取一个地址进行服务调用。

在Spring Cloud中提供了负载均衡器:Ribbon

总结

Ribbon提供了轮询、随机两种负载均衡算法(默认是轮询)可以实现从地址列表中使用负载均衡算法获取地址进行服务调用。

15. Ribbon负载均衡应用

因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。

  1. 在RestTemplate的配置方法上添加 @LoadBalanced注解

    /** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/11/1 9:16 */@SpringBootApplication@EnableDiscoveryClientpublic class ConsumerApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConsumerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() {
    return new RestTemplate(); }}
  2. 修改Controller 调用方式,不再手动获取ip和端口,而是直接通过服务名称调用

    @RestController@RequestMapping("/consumer")public class ConsumerController {
    @Autowired private DiscoveryClient discoveryClient; @Autowired public RestTemplate restTemplate; @RequestMapping("/{id}") public User queryById(@PathVariable Long id) {
    serviceInstance.getPort() + "/user/" + id; String url = "http://user-service/user/" + id; User user = restTemplate.getForObject(url, User.class); return user; }}
  3. 访问页面,查看结果;并可以在9091和9092的控制台查看执行情况:

了解:Ribbon默认的负载均衡策略是轮询。SpringBoot也帮提供了修改负载均衡规则的配置入口在consumerdemo的配置文件中添加如下,就变成随机的了:

user-service:  ribbon:    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

学习目标:配置启动两个用户服务,在consumer-demo中使用服务名实现根据用户id获取用户

解析

需求:可以使用RestTemplate访问http://user-service/user/8获取服务数据。

可以使用Ribbon负载均衡:在执行RestTemplate发送服务地址请求的时候,使用负载均衡拦截器拦截根据服务名获取服务地址列表,使用Ribbon负载均衡算法从服务地址列表中选择一个服务地址,访问该地址获取服务数据。

实现步骤:

  1. 启动多个user-service实例(9091,9092);
  2. 修改RestTemplate实例化方法,添加负载均衡注解;
  3. 修改ConsumerController;
  4. 测试

总结

在实例化RestTemplate的时候使用@LoadBalanced,服务地址直接可以使用服务名。例如:http://user-service/user/{id}

16. 熔断器Hystrix简介

Hystrix 在英文里面的意思是 豪猪,它的logo 看下面的图是一头豪猪,它在微服务系统中是一款提供保护机制的组件,和eureka一样也是由netflix公司开发。

主页:

那么Hystrix的作用是什么呢?具体要保护什么呢?

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

雪崩问题

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

在这里插入图片描述如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。如果此时,某个服务出现异常:
在这里插入图片描述例如: 微服务I 发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
在这里插入图片描述

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

Hystrix解决雪崩问题的手段主要是服务降级,包括:

  • 线程隔离
  • 服务熔断

学习目标:了解熔断器Hystrix的作用

总结结

Hystrix是一个延迟和容错库,用于隔离访问远程服务,防止出现级联失败。(避免雪崩)

17. 线程隔离&服务降级

线程隔离示意图:

在这里插入图片描述
解读

  • Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速 失败判定时间。
  • 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理,什么是服务降级?

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

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

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

  • 线程池已满
  • 请求超时

目标:了解什么是线程隔离和服务降级

解析

Hystrix解决雪崩效应:

  • 线程隔离:用户请求不直接访问服务,而是使用线程池中空闲的线程访问服务,加速失败判断时间。
  • 服务降级:及时返回服务调用失败的结果,让线程不因为等待服务而阻塞。

总结

  • consumer-demo中添加依赖

    org.springframework.cloud
    spring-cloud-starter-netflix-hystrix
  • 开启熔断

    在这里插入图片描述
    可以看到,我们类上的注解越来越多,在微服务中,经常会引入上面的三个注解,于是Spring就提供了一个组合注 解:@SpringCloudApplication

  • 降级逻辑

    当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。因此需要提前编写好失败时的降级处理逻辑,要使用HystrixCommand来完成。

    默认的Fallback :把fallback写在了某个业务方法上,如果这样的方法很多,那岂不是要写很多。所以可以把Fallback配置加在类 上,实现默认fallback;@DefaultProperties(defaultFallback = “defaultFallBack”)

    @RestController@RequestMapping("/consumer")@Slf4j@DefaultProperties(defaultFallback = "defaultFallback")public class ConsumerController {
    @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @GetMapping("/{id}") //@HystrixCommand(fallbackMethod = "queryByIdFallback") @HystrixCommand public String queryById(@PathVariable Long id){
    /*String url = "http://localhost:9091/user/"+id; //获取eureka中注册的user-service的实例 List
    serviceInstances = discoveryClient.getInstances("user-service"); ServiceInstance serviceInstance = serviceInstances.get(0); url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;*/ String url = "http://user-service/user/" + id; return restTemplate.getForObject(url, String.class); } public String queryByIdFallback(Long id){
    log.error("查询用户信息失败。id:{}", id); return "对不起,网络太拥挤了!"; } public String defaultFallback(){
    return "默认提示:对不起,网络太拥挤了!"; }}

    在实际开发中@HystrixCommand是加在Feign的接口上

    要注意;因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。

    失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以把queryById的方法改造为返回String, 反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。

    主要1:@HystrixCommand(fallbackMethod = "queryByIdFallBack"):用来声明一个降级逻辑的方法

    主要2:@DefaultProperties(defaultFallback = "defaultFallBack"):在类上指明统一的失败降级方法;该类中所有方法 返回类型要与处理失败的方法的返回类型一致。

  • 修改超时配置

    请求在超过1秒后都会返回错误信息,这是因为Hystrix的默认超时时长为1,我们可以通过配置修改这个值;修改消费者 application.yml 添加如下配置:(毫秒)

    hystrix:  command:    default:      execution:        isolation:          thread:            timeoutInMilliseconds: 2000

18. 服务熔断演示

在服务熔断中,使用的熔断器,也叫断路器,其英文单词为:Circuit Breaker

熔断机制与家里使用的电路熔断原理类似;当如果电路发生短路的时候能立刻熔断电路,避免发生灾难。在分布式系统中应用服务熔断后;服务调用方可以自己进行判断哪些服务反应慢或存在大量超时,可以针对这些服务进行主动熔断防止整个系统被拖垮。

Hystrix的服务熔断机制可以实现弹性容错;当服务请求情况好转之后,可以自动重连。通过断路的方式,将后续请求直接拒绝,一段时间(默认5秒)之后允许部分请求通过,如果调用成功则回到断路器关闭状态,否则继续打开,拒绝请求的服务。

Hystrix的熔断状态机模型:

在这里插入图片描述状态机有3个状态:

  • Closed:关闭状态(断路器关闭),所有请求都正常访问。
  • Open:打开状态(断路器打开),所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百 分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数少不低于20次。
  • Half Open:半开状态,不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断路器,否则继续保持打开,再次进行休眠计时

学习目标:了解熔断器工作原理

解析

可以通过配置服务熔断参数修改默认:

hystrix:  command:    default:      execution:        isolation:          thread:            timeoutInMilliseconds: 2000      circuitBreaker:        errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%        sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒        requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20       isolation:          thread:            timeoutInMilliseconds: 2000

上述的配置项可以参考 HystrixCommandProperties 类中。

测试方法:

编写Controller

@GetMapping("{id}") @HystrixCommand public String queryById(@PathVariable("id") Long id){
if(id == 1){
throw new RuntimeException("太忙了"); } String url = "http://user-service/user/" + id; String user = restTemplate.getForObject(url, String.class); return user; }

注意:配置了默认的Fallback

这样如果参数是id为1,一定失败,其它情况都成功。(不要忘了清空user-service中的休眠逻辑)
我们准备两个请求窗口:

  • 一个请求:http://localhost:8080/consumer/1,
  • 注定失败 一个请求:http://localhost:8080/consumer/2,

肯定成功 当我们疯狂访问id为1的请求时(超过20次),就会触发熔断。断路器会打开,一切请求都会被降级处理。

此时你访问id为2的请求,会发现返回的也是失败,而且失败时间很短,只有20毫秒左右;因进入半开状态之后2是可以的。

在这里插入图片描述

转载地址:http://nnilf.baihongyu.com/

你可能感兴趣的文章
Redis学习笔记(四)—— redis的常用命令和五大数据类型的简单使用
查看>>
深入分析JavaWeb技术内幕(一)—— 深入Web请求过程
查看>>
深入分析JavaWeb技术内幕(二)—— 深入分析Java I/O的工作机制
查看>>
使用Java将PDF解析成HTML页面进行展示并从页面中提取Json数据设置到Table中
查看>>
Redis学习笔记(五)—— 在Linux下搭建Redis集群
查看>>
Redis学习笔记(六)—— 解决安装ruby出现的问题:redis requires Ruby version >= 2.2.2.
查看>>
从原理上搞懂编码——究竟什么是编码?什么是解码?什么是字节流?
查看>>
前端上传文件组件Plupload使用指南
查看>>
单点登录原理与简单实现
查看>>
使用zxing生成彩色或带图片的二维码
查看>>
在Linux下安装JDK8
查看>>
面试题 —— HTTP请求中get请求和post请求的区别以及底层原理
查看>>
面试题 —— HashMap、HashTable、HashSet的实现原理和底层数据结构
查看>>
C语言学习笔记(三)—— 数据和C
查看>>
Java编程思想(三)—— 操作符
查看>>
梦飞 —— 述:我只是一个普通农民家的孩子,但我有一个梦想
查看>>
图解HTTP(二)—— 简单的HTTP协议
查看>>
程序员的数学(四)—— 数学归纳法,如何征服无穷数列
查看>>
不是技术人员也能看懂云计算、大数据、人工智能
查看>>
图解HTTP(三)—— HTTP报文内的HTTP信息
查看>>