【系统架构理论】一篇文章搞掂:微服务架构

本文篇幅较长,建议合理利用右上角目录进行查看(若是没有目录请刷新)。git

本文基于《Spring 微服务实战》一书进行总结和扩展,你们也能够自行研读此书。web

1、Spring、云计算、微服务简介

1.一、什么是微服务

单体架构:

    • 在微服务概念逐步造成前,大部分Web应用都是基于单体架构风格进行构建。
    • 单体架构风格每每是几个团队维护一份共同代码,生成一个单一程序进行运行。

    

 

    • 问题:当应用规模增加,各个团队间的沟通和合做成本也会增大;而当其中一个团队修改代码时,整个应用程序须要从新构建、测试、部署。

微服务:

    • 微服务的概念在2014年左右在软件开发社区中蔓延
    • 一个小的、松耦合的分布式服务;分解和分离应用程序的功能,并使他们彼此独立

    

微服务架构特征

    • 逻辑业务分解为明肯定义了职责范围的细粒度组件,组件互相协调提供解决方案
    • 每一个组件有其职责领域,并能彻底独立部署
    • 微服务应该对业务领域的单个部件负责
    • 一个微服务能够跨多个应用程序复用
    • 微服务通讯基于一些基本原则,采用HTTP或JSON等轻量级通讯协议做,在服务的消费者和提供者间进行数据交换
    • 微服务小、独立、分布式的特性,使组织拥有明确责任领域的小型开发团队;一样是开发一个应用程序,这些团队各自只须要负责本身作的服务。

1.二、Spring和微服务的关系

  • Spring的地位:Java应用的标准开发框架。由于Spring提供了依赖注入的功能,使程序中类之间的关系能够外部管理,为开发企业级应用提供了一个轻量级的解决方案,使其成为了Java程序的一种必然使用的开发框架。
  • Spring的更新:Spring团队是一个与时俱进的团队,不断地改造本身的代码迎合时代需求;为相应微服务架构的需求,Spring团队开发出了2个项目:Spring Boot和Spring Cloud
    • Spring Boot:对Spring架构理念从新思考的结果:以默认配置的方式简化了原来Spring大量的手工配置。
    • Spring Cloud:一个封装了多个流行的云管理微服务框架的公共框架。

1.三、为何要改变应用架构和选择微服务架构

当前局势:

  互联网链接了社会各个部分,链接了全球客户群,带来了全球竞争算法

    • 复杂度上升:现在的应用程序不只要访问本身的数据,更要和互联网和外部服务提供商进行交互和通讯。
    • 客户要求更快的交付:为了应对瞬息万变的市场,如今的客户追求快速的变化,但愿能在几周或者几天发布新功能,而无需等待整个产品发布。
    • 性能和伸缩性:全球性的应用程序没法预测事务量的变化,但愿能使应用程序跨多个服务器在事务高峰时进行扩大,在高峰后又能进行收缩。
    • 高可用性:客户但愿不能由于某个部分的故障致使整个系统的崩溃

微服务架构提供的解决方案:

  将单体的应用程序,分解为互相独立构建和部署的小型服务,带来的好处是:spring

    • 高灵活性:将结构的服务进行组合和从新安排,可快速交付新功能;一个代码单元越小,更容易修改代码,也更容易测试。
    • 高弹性:解耦的服务使故障不会蔓延到整个应用程序,致使程序中断。
    • 可伸缩性:解耦的服务能够轻松地跨多个服务器进行水平分布,从而适当地对功能/服务进行伸缩。若是是单体应用程序,即便只有小部分遇到瓶颈,都要整个程序进行扩展;而微服务的扩展是局部的,实现成本更低。

1.四、什么是云

如今“云”的概念已经被过分使用。数据库

云:

  云是网络、互联网的一种比喻说法。apache

云计算:

  将数据的运算,放到互联网中去。编程

云计算的3种基本模式:

    • IaaS(Infrastructure as a Service)基础设施即服务:直接使用互联网中的硬件,如租用硬盘,租用内存,租用服务器;例如我在阿里云上租用了一个服务器,在里面安装本身的数据库,IIS,而后搭建Web应用程序。
    • Paas(Platform as a Service)平台即服务:互联网商提供一个完整的软件研发和部署平台,我无需购买硬件和软件,直接在该平台上开发和部署个人应用。
    • SaaS(Software as a Service)软件即服务:互联网商提供一套已经研发好,部署好的软件,我直接经过Web来使用他的软件。

  新型的模式json

    • Faas(Functions as a Service)函数即服务:将代码块交给互联网商,该代码会在互联网商的计算设施上计算,对外暴露成一种服务,咱们直接调用该服务便可。
    • Caas(Container as a Service)容器即服务:互联网商提供一些虚拟容器(如Docker),咱们把程序放到容器中启动便可。

1.五、为何选择云+微服务

微服务架构核心概念是:把每个服务打包和部署为独立的制品。后端

部署方式的可选项:

    • 物理服务器:虽然能够,可是物理服务器没法快速提高容量;且在多个物理服务器间水平伸缩微服务成本很是高。
    • 虚拟机镜像:基于IaaS,能够在租用的云虚拟机中快速部署和启动服务的多个实例。
    • 虚拟容器:基于IaaS,不是直接把服务部署到云虚拟机,而是将Docker容器(或其余容器)部署到虚拟机,再把服务放到虚拟容器中运行。这样能够把单个虚拟机隔离成共享相同虚拟机镜像的一系列独立进程。

云的优点:

    • 快速:开发人员能够在几分钟内快速启动新的虚拟机和容器。
    • 高水平伸缩性:服务容量需求降低,能够关闭虚拟服务器,减小费用;需求上升,能够快速添加服务器和服务实例。
    • 高弹性:例如一台微服务遇到问题,能够启动新的服务实例,让程序继续运行,给时间开发团队解决问题。

IaaS+Docker:

  咱们采用IaaS+Docker做为部署环境api

  为何不是基于PaaS:IaaS与供应商无关,PaaS要考虑基于供应商提供的微服务抽象,因此考虑IaaS有更大灵活性,能够跨供应商移植。

1.六、微服务架构的内容

编写单个微服务的代码很简单,可是要开发、运行、支持一个健壮的微服务应用

须要考虑几个主题:

  

    • 大小适当:如何正确划分微服务大小,容许快速更改应用程序,下降整个应用程序中断的风险?
    • 位置透明:微服务应用程序中,多个服务实例能够快速启动和关闭,如何管理服务调用的物理细节?
    • 有弹性:如何绕过失败的服务,确保采用“快速失败”来保护微服务消费者和应用程序的总体完整性?
    • 可重复:如何确保提供的每一个新服务实例与生产环境中的全部其余服务实例具备相同的配置和代码库?
    • 可伸缩:如何使用一部处理和事件,最小化服务之间的直接依赖关系,确保能够优雅地扩展微服务?

从基于模式的方法来考虑解决这些主题(详细内容不是过重点,暂时不讲解这些模式):

    • 核心微服务开发模式
    • 微服务路由模式
    • 微服务客户端弹性模式
    • 微服务安全模式
    • 微服务日志记录和跟踪模式
    • 微服务构建和部署模式

1.七、总结与实例

互联网的发展与变化,要求如今的应用程序在高复杂性下仍然能保持快速交付、高弹性、高可用、高伸缩性,微服务架构就是为了实现这个要求所出现的系统架构。

微服务架构,把系统的需求,分割成若干个能够独立运行的应用,并做为服务暴露出来;而后经过服务的注册与发现,使这些服务之间能够互相协做,完成系统任务。

一个微服务框架的例子:Cloud-Admin,开源框架,https://gitee.com/minull/ace-security

架构图:

这里须要补充这个架构图的意义

2、第一步:实现服务注册与发现

服务发现:在分布式计算架构中,咱们要调用某台计算机提供的服务时,必须知道这台机器的物理地址,这个称为服务发现。

微服务架构与服务发现:

  • 因为应用被拆分为多个微服务,咱们能够将这些微服务实例数量进行水平伸缩,并经过服务发现让这些服务参与运算;服务消费者不须要知道服务的真正地址,因此咱们能够在服务池中添加和移除服务实例。而单体架构,只能考虑购买更大型、更好的硬件来扩大服务。
  • 当某些服务变得不健康或者不可用时,咱们能够移除这些服务,服务发现引擎能够绕过这些服务继续运行,使移除服务形成的影响最小化。

咱们下面将经过Spring Cloud和Netflix技术,来实现这一个功能。

2.一、一种传统的方式服务位置解析模型

DNS和负载均衡器的传统服务位置解析模型:

缺点:

  • 单点故障——虽然负载均衡器能够实现高可用,但这是整个基础设施的单点故障。若是负载均衡器出现故障,那么依赖它的每一个应用程序都会出现故障。尽管能够使负载平衡器高度可用,但负载均衡器每每是应用程序基础设施中的集中式阻塞点。
  • 有限的水平可伸缩性——在服务集中到单个负载均衡器集群的状况下,跨多个服务器水平伸缩负载均衡基础设施的能力有限。许多商业负载均衡器受两件事情的限制:冗余模型和许可证成本。第一,大多数商业负载均衡器使用热插拔模型实现冗余,所以只能使用单个服务器来处理负载,而辅助负载均衡器仅在主负载均衡器中断的状况下,才能进行故障切换。这种架构本质上受到硬件的限制。第二,商业负载均衡器具备有限数量的许可证,它面向固定容量模型而不是更可变的模型。
  • 静态管理——大多数传统的负载均衡器不是为快速注册和注销服务设计的。它们使用集中式数据库来存储规则的路由,添加新路由的惟一方法一般是经过供应商的专有API(Application Programming Interface,应用程序编程接口)来进行添加。
  • 复杂——因为负载均衡器充当服务的代理,它必须将服务消费者的请求映射到物理服务。这个翻译层一般会为服务基础设施增长一层复杂度,由于开发人员必须手动定义和部署服务的映射规则。在传统的负载均衡器方案中,新服务实例的注册是手动完成的,而不是在新服务实例启动时完成的。

2.二、适合云的服务发现架构

基于云的微服务环境但愿实现的服务发现架构有以下特色:

  • 高可用——服务发现须要可以支持“热”集群环境,在服务发现集群中能够跨多个节点共享服务查找。若是一个节点变得不可用,集群中的其余节点应该可以接管工做。
  • 点对点——服务发现集群中的每一个节点共享服务实例的状态。
  • 负载均衡——服务发现须要在全部服务实例之间动态地对请求进行负载均衡,以确保服务调用分布在由它管理的全部服务实例上。在许多方面,服务发现取代了许多早期Web应用程序实现中使用的更静态的、手动管理的负载均衡器。
  • 有弹性——服务发现的客户端应该在本地“缓存”服务信息。本地缓存容许服务发现功能逐步降级,这样,若是服务发现服务变得不可用,应用程序仍然能够基于本地缓存中维护的信息来运行和定位服务。
  • 容错——服务发现须要检测出服务实例何时是不健康的,并从能够接收客户端请求的可用服务列表中移除该实例。服务发现应该在没有人为干预的状况下,对这些故障进行检测,并采起行动。

2.2.一、服务发现架构的共同流程

  

  • 服务注册——当服务实例启动时,它们将经过一个或多个服务发现实例来注册它们能够访问的物理位置、路径和端口。虽然每一个服务实例都具备惟一的IP地址和端口,可是每一个服务实例都将以相同的服务ID进行注册。服务ID是惟一标识一组相同服务实例的键。
  • 服务地址的客户端查找——客户端经过服务发现代理获取服务IP地址。
  • 信息共享——服务发现节点之间共享服务实例的健康信息。
  • 健康监测——服务向服务发现代理发送心跳包。

2.2.二、增长客户端负载均衡

上面的模型中,每次调用注册的微服务实例时,服务发现引擎就会被调用。这种方法很脆弱,由于服务客户端彻底依赖于服务发现引擎来查找和调用服务。

增长客户端负载均衡的服务发现模型:

在这个模型中,当服务消费者须要调用一个服务时:

  • 它将联系服务发现服务,获取它请求的全部服务实例,而后在服务消费者的机器上本地缓存数据。
  • 每当客户端须要调用该服务时,服务消费者将从缓存中查找该服务的位置信息。一般,客户端缓存将使用简单的负载均衡算法,如“轮询”负载均衡算法,以确保服务调用分布在多个服务实例之间。
  • 而后,客户端将按期与服务发现服务进行联系,并刷新服务实例的缓存。客户端缓存最终是一致的,可是始终存在这样的风险:在客户端联系服务发现实例以进行刷新和调用时,调用可能会被定向到不健康的服务实例上。
  • 若是在调用服务的过程当中,服务调用失败,那么本地的服务发现缓存失效,服务发现客户端将尝试从服务发现代理刷新数据。

2.三、使用Spring Cloud和Netflix实现服务发现架构

使用Spring Cloud和Netflix实现的服务发现架构模型:

    • 随着服务的启动,许可证和组织服务将经过Eureka服务进行注册。这个注册过程将告诉Eureka每一个服务实例的物理位置和端口号,以及正在启动的服务的服务ID。
    • 当许可证服务调用组织服务时,许可证服务将使用Netflix Ribbon库来提供客户端负载均衡。Ribbon将联系Eureka服务去检索服务位置信息,而后在本地进行缓存。
    • Netflix Ribbon库将按期对Eureka服务进行ping操做,并刷新服务位置的本地缓存。
    • 任何新的组织服务实例如今都将在本地对许可证服务可见,而任何不健康实例都将从本地缓存中移除。

2.四、代码实现服务发现架构

2.4.一、构建Eureka服务

Eureka服务的做用是:

提供服务的注册中心。

主POM文件:

添加Spring Cloud的版本管理,添加Spring Boot的Starter依赖和Test依赖。

<?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>xxx</groupId>
    <artifactId>xxx</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>base-eureka</module>
    </modules>

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

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
</project>
主POM文件代码

Eureka模块POM文件:

Eureka服务做为项目的一个模块进行添加,并在POM文件中添加Eureka服务器依赖以及Spring Cloud Starter依赖

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-eureka</artifactId>
    <packaging>jar</packaging>

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

</project>
Eureka模块POM文件

建立Eureka服务程序引导类:

经过@EnableEurekaServer注解,启动Eureka服务器

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}
主程序引导类

添加配置文件:

经过application.yml文件对Eureka服务器进行配置

spring:
  application:
    name: eureka
    
server:
  port: 8761 #启动端口

eureka:
  client:
    registerWithEureka: false #false:不做为一个客户端注册到注册中心
    fetchRegistry: false #为true时,能够启动,但报异常:Cannot execute request on any known server
配置Eureka服务器

 启动服务器:

根据配置的端口,经过引导类启动程序后,访问如http://localhost:8761/,便可看到以下画面,说明Eureka服务器启动成功

2.4.二、把RESTful服务注册到Eureka注册服务中去

模块名为:testrest

testrest模块POM文件:

添加Spring Boot Web依赖和Eureka客户端依赖

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-testrest</artifactId>

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

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

</project>
REST模块POM文件

testrest服务程序引导类:

@SpringBootApplication
public class TestRestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRestApplication.class, args);
    }
}
主程序引导类

testrest控制器:

@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String selectById(@PathVariable String id)  {
        return id;
    }
}
控制器

配置文件:

server:
  port: 8801 #启动端口

spring:
  application:
    name: base-testrest #将使用Eureka注册的服务的逻辑名称

eureka:
  instance:
    preferIpAddress: true #注册服务的IP,而不是服务器名称
  client:
    registerWithEureka: true #向Eureka注册服务
    fetchRegistry: true
    serviceUrl: 
      defaultZone: http://localhost:8761/eureka/ #Eureka服务的位置
配置文件

启动客户端:

先启动Eureka服务器,而后再启动testrest程序引导类,再访问Eureka服务器界面,如http://localhost:8761/,便可看见此服务成功注册到Eureka服务器中了

 2.4.三、建立另一个RESTful服务,对上面2中已注册的服务进行发现和消费

对注册到Eureka的服务进行发现和消费,有几种方式:

  • Spring DiscoveryClient
  • 启用了RestTemplate的Spring DiscoveryClient
  • Netflix Feign客户端

这里仅介绍Feign客户端

模块名为:testrestconsume

testrestconsume模块POM文件:

添加Spring Boot Web依赖、Eureka客户端依赖及Feign依赖

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-testrestconsume</artifactId>

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

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

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

</project>
POM文件

testrestconsume服务程序引到类:

添加注解@EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class TestRestConsumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRestConsumeApplication.class, args);
    }
}
程序引导类

建立接口发现和消费微服务:

一个微服务的多个实例,使用其spring.application.name进行标识;

Feign客户端利用接口和@FeignClient客户端,使用name标识,能够直接调用注册到Eureka中的服务。

@FeignClient("base-testrest")
public interface ITestConsumeService {
  @RequestMapping(value="/test/{id}",method = RequestMethod.GET)
  public String selectById(@PathVariable("id") String id);
}
FeignClient接口

控制器调用接口:

Feign客户端接口,直接经过@Autowired自动装配便可使用

@RestController
@RequestMapping("/testconsume")
public class TestConsumeController {
    @Autowired
    private ITestConsumeService testConsumeService;

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String selectById(@PathVariable String id)  {
        return testConsumeService.selectById(id);
    }
}
控制器

配置文件:

主要须要指明Eureka服务的地址

server:
  port: 8802 #启动端口

spring:
  application:
    name: base-testrestconsume #将使用Eureka注册的服务的逻辑名称

eureka:
  instance:
    preferIpAddress: true #注册服务的IP,而不是服务器名称
  client:
    serviceUrl: #拉取注册表的本地副本
      defaultZone: http://${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka/ #Eureka服务的位置
配置文件

3、第二步:为微服务提供认证和受权

一般一个系统的服务不会彻底暴露给全部人使用,而是根据用户的身份、权力来决定是否容许其使用。

咱们下面将经过Spring Cloud Security、Spring Security Oauth2和Spring Security Jwt技术,来实现这一个功能。

3.一、OAuth2框架

OAuth2是一个基于令牌的安全验证和受权框架,它将安全性分解为4个部分:

  • 受保护资源——开发人员想要保护的资源(如一个微服务),确保只有已经过验证而且具备适当受权的用户才能访问它。
  • 资源全部者——资源全部者定义哪些应用程序能够调用其服务,哪些用户能够访问该服务,以及他们能够使用该服务完成哪些事情。资源全部者注册的每一个应用程序都将得到一个应用程序名称,该应用程序名称与应用程序密钥一块儿标识应用程序。应用程序名称和密钥的组合是在验证OAuth2令牌时传递的凭据的一部分。
  • 应用程序——直接和用户解除的应用程序。一般用户访问应用程序,而后应用程序再调用各个微服务。
  • OAuth2验证服务器——OAuth2验证服务器是应用程序和正在使用的服务之间的中间人。OAuth2验证服务器容许用户对本身进行验证,而没必要将用户凭据传递给由应用程序表明用户调用的每一个服务。

 

OAuth2运做流程:

  • 一、用户访问应用程序,并提交其身份凭证(如账号密码);
  • 二、应用程序把身份凭证传给OAuth2验证服务器;
  • 三、OAuth2验证服务器对其身份进行认证,认证经过则返回一个令牌给应用程序;
  • 四、应用程序调用服务时,把令牌传给受保护服务;
  • 五、受保护服务把令牌传给OAuth2验证服务器,验证令牌对应的用户,是否拥有将要访问资源的权力;
  • 六、验证经过,则能够使用对应资源。

OAuth2规范具备如下4种类型的受权,这里仅讨论密码的方式:

  • 密码(password);
  • 客户端凭据(client credential);
  • 受权码(authorization code);
  • 隐式(implicit)。

3.二、创建OAuth2验证服务器

主POM文件:

添加Spring Boot Web依赖、Spring Cloud Security依赖及Spring Security Oauth2依赖

<?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>XXX</artifactId>
        <groupId>XXX</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>base-authentication</artifactId>
    <packaging>jar</packaging>

    <dependencies>

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

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

        <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

    </dependencies>

</project>
主POM文件

程序引导类:

添加验证服务器注解@EnableAuthorizationServer代表这个是验证服务器,添加资源服务器注解@EnableResourceServer代表这个程序提供的资源受到OAuth2保护

@SpringBootApplication
@EnableResourceServer
@EnableAuthorizationServer
public class AuthenticationApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthenticationApplication.class, args);
    }
}
程序引导类

OAuch2配置类:

配置一个AuthorizationServerConfigurerAdapter类,用于配置验证服务注册哪些应用程序

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    // 由于当前security版本,密码须要以{0}XXXX的方式,增长密码的编码方式在花括号内进行传输
    // 因此若是想直接传XXXX的密码,须要加这段代码
    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("eagleeye")// 容许访问的客户端
                .secret("thisissecret")// 密码
                .authorizedGrantTypes(// 容许的受权类型
                        "refresh_token",
                        "password",
                        "client_credentials")
                .scopes("webclient", "mobileclient");// 引用程序做用域
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
}
OAuth2配置类

WebSecurity配置类:

配置一个WebSecurityConfigurerAdapter类,用于配置系统有哪些用户,分别是什么角色

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("john.carnell")
                .password("password1")
                .roles("USER")
                .and()
                .withUser("william.woodward")
                .password("password2")
                .roles("USER", "ADMIN");
    }
}
WebSecurity配置类

配置文件:

主要设置端口号和增长了一个url路径前缀

spring:
  application:
    name: authenticationservice

server:
  servlet:
    context-path: /auth
  port: 8901
配置文件

令牌验证端点:

建立一个REST端点,用于令牌验证

@RestController
public class TokenController {
    @RequestMapping(value = {"/user"}, produces = "application/json")
    public Map<String, Object> user(OAuth2Authentication user) {
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put(
                "user",
                user.getUserAuthentication().getPrincipal());
        userInfo.put(
                "authorities",
                AuthorityUtils.authorityListToSet(
                        user.getUserAuthentication().getAuthorities()));
        return userInfo;
    }
}
令牌验证端点控制器

运行:

启动程序后,使用POST方式,能够获取到用户对应令牌

而后使用令牌访问令牌验证端点,获取令牌信息

3.三、设置获取微服务资源须要认证和受权

要对其它微服务进行保护,只须要在微服务上进行一些设置便可;而后访问微服务资源的时候就会根据要求进行令牌验证。

主POM文件:

添加Spring Cloud Security依赖及Spring Security Oauth2依赖

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

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>
POM文件增长的依赖

 主程序引导类:

添加资源服务器注解@EnableResourceServer代表这个程序提供的资源受到OAuth2保护

@EnableResourceServer
@SpringBootApplication
public class TestRestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRestApplication.class, args);
    }
}
主程序引导类

配置文件:

配置OAuth2验证服务器令牌验证服务的地址

security:
  oauth2:
    resource:
       userInfoUri: http://localhost:8901/auth/user
配置文件

配置ResourceServer配置类:

配置一个ResourceServerConfigurerAdapter配置类,用于定义哪些资源须要什么角色、什么权限才能访问

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception{
        // 只要用户经过认证便可访问
        http.authorizeRequests().anyRequest().authenticated();

        //用户须要有相关角色和权限才能访问
//        http
//        .authorizeRequests()
//          .antMatchers(HttpMethod.DELETE, "/v1/organizations/**")
//          .hasRole("ADMIN")
//          .anyRequest()
//          .authenticated();
    }
}
ResourceServerConfigurerAdapter配置类

运行:

把测试的微服务以及OAuth2验证服务器都运行起来

访问测试的微服务

当没有令牌时,会提示:

当令牌不正确的时候,会提示:

咱们访问验证服务器,传入相关数据,获取一个新的token,并填入上面的Headers-Authorization中,才能正确访问资源

3.四、使用JSON Web Token

OAuth2是一个基于令牌的验证框架,但它并无为如何定义其规范中的令牌提供任何标准。

为了矫正OAuth2令牌标准的缺陷,一个名为JSON Web Token(JWT)的新标准脱颖而出。

JWT是因特网工程任务组(Internet Engineering Task Force,IETF)提出的开放标准(RFC-7519),旨在为OAuth2令牌提供标准结构。

JWT令牌具备以下特色:

  • 小巧——JWT令牌编码为Base64,能够经过URL、HTTP首部或HTTP POST参数轻松传递。
  • 密码签名——JWT令牌由颁发它的验证服务器签名。这意味着能够保证令牌没有被篡改。
  • 自包含——因为JWT令牌是密码签名的,接收该服务的微服务能够保证令牌的内容是有效的,所以,不须要调用验证服务来确认令牌的内容,由于令牌的签名能够被接收微服务确认,而且内容(如令牌和用户信息的过时时间)能够被接收微服务检查。
  • 可扩展——当验证服务生成一个令牌时,它能够在令牌被密封以前在令牌中放置额外的信息。接收服务能够解密令牌净荷,并从它里面检索额外的上下文。

3.4.一、修改配置验证服务器使用JWT

主POM文件:

原来已添加Spring Cloud Security依赖、Spring Security Oauth2依赖,再添加Spring Security JTW依赖

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

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>
主POM文件依赖

JWTOAuch2配置类:

去掉原来的OAuch2Config配置类,配置一个新的AuthorizationServerConfigurerAdapter类,里面设置使用JWT做为令牌标准,用于配置验证服务注册哪些应用程序

@Component
@Configuration
public class ServiceConfig {
  @Value("${signing.key}")
  private String jwtSigningKey="";

  public String getJwtSigningKey() {
    return jwtSigningKey;
  }

}
一、经过配置文件读取JWT密钥
@Configuration
public class JWTTokenStoreConfig {

    @Autowired
    private ServiceConfig serviceConfig;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    @Primary // @Primary注解用于告诉Spring,若是有多个特定类型的bean(在本例中是DefaultTokenService),那么就使用被@Primary标注的bean类型进行自动注入
    public DefaultTokenServices tokenServices() { // 用于从出示给服务的令牌中读取数据
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }


    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {// 在JWT和OAuth2服务器之间充当翻译
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(serviceConfig.getJwtSigningKey());// 定义将用于签署令牌的签名密钥
        return converter;
    }

    @Bean
    public TokenEnhancer jwtTokenEnhancer() {
        return new JWTTokenEnhancer();
    }
}
二、须要建立JWT令牌存储配置类,用于定义Spring将如何管理JWT令牌的建立、签名和翻译
public class JWTTokenEnhancer implements TokenEnhancer {
//    @Autowired
//    private OrgUserRepository orgUserRepo;
//
//    private String getOrgId(String userName){
//        UserOrganization orgUser = orgUserRepo.findByUserName( userName );
//        return orgUser.getOrganizationId();
//    }

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
//        String orgId =  getOrgId(authentication.getName());

        String orgId = "id";

        additionalInfo.put("organizationId", orgId);

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}
(可选)三、配置Token扩展器,用于在token中扩展自定义字段
@Configuration
public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DefaultTokenServices tokenServices;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenEnhancer jwtTokenEnhancer;

    // 由于当前security版本,密码须要以{0}XXXX的方式,增长密码的编码方式在花括号内进行传输
    // 因此若是想直接传XXXX的密码,须要加这段代码
    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));

        endpoints.tokenStore(tokenStore)                             // 注入令牌存储
                .accessTokenConverter(jwtAccessTokenConverter)       // 这是钩子,用于告诉Spring Security OAuth2代码使用JWT
                .tokenEnhancer(tokenEnhancerChain)                   // 注入Token扩展器
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }



    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
                .withClient("eagleeye")
                .secret("thisissecret")
                .authorizedGrantTypes("refresh_token", "password", "client_credentials")
                .scopes("webclient", "mobileclient");
    }
}
四、配置JWTOAuch2配置类

其它地方无需变动。

运行:

再次运行获取token的服务,会获得JWT形式的token:

 

3.4.二、配置微服务使用JWT进行验证

此时,即便不修改微服务,也能够经过验证;由于验证服务器的JWT是基于OAuth2的,因此支持客户端使用OAuth2进行验证。

固然,为了使用一些JWT的特性,例如自包含等,咱们须要配置微服务使用JWT。

主POM文件:

原来已添加Spring Cloud Security依赖、Spring Security Oauth2依赖,再添加Spring Security JTW依赖

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

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>
主POM文件依赖

建立令牌存储配置类:

告诉微服务使用JWT做为令牌,并须要设置密钥和验证服务端对应

@Configuration
public class JWTTokenStoreConfig {
    //JWT
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    //JWT
    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    //JWT
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123456");
        return converter;
    }
}
JWT令牌存储配置类

建立JWTRestTemplate Bean:

由于许可证服务调用组织服务,因此须要确保OAuth2令牌被传播。这项工做一般是经过OAuth2RestTemplate类完成的,可是OAuth2RestTemplate类并不传播基于JWT的令牌。为了确保许可证服务可以作到这一点,须要添加一个自定义的RestTemplate bean来完成这个注入。 

@Component
public class UserContext {
    public static final String CORRELATION_ID = "tmx-correlation-id";
    public static final String AUTH_TOKEN     = "Authorization";
    public static final String USER_ID        = "tmx-user-id";
    public static final String ORG_ID         = "tmx-org-id";

    private static final ThreadLocal<String> correlationId= new ThreadLocal<String>();
    private static final ThreadLocal<String> authToken= new ThreadLocal<String>();
    private static final ThreadLocal<String> userId = new ThreadLocal<String>();
    private static final ThreadLocal<String> orgId = new ThreadLocal<String>();


    public static String getCorrelationId() { return correlationId.get(); }
    public static void setCorrelationId(String cid) {correlationId.set(cid);}

    public static String getAuthToken() { return authToken.get(); }
    public static void setAuthToken(String aToken) {authToken.set(aToken);}

    public static String getUserId() { return userId.get(); }
    public static void setUserId(String aUser) {userId.set(aUser);}

    public static String getOrgId() { return orgId.get(); }
    public static void setOrgId(String aOrg) {orgId.set(aOrg);}
}
辅助类UserContext
@Component
public class UserContextFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(UserContextFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {


        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

        logger.debug("I am entering the licensing service id with auth token: ", httpServletRequest.getHeader("Authorization"));


        UserContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(UserContext.CORRELATION_ID));
        UserContextHolder.getContext().setUserId(httpServletRequest.getHeader(UserContext.USER_ID));
        UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(UserContext.AUTH_TOKEN));
        UserContextHolder.getContext().setOrgId(httpServletRequest.getHeader(UserContext.ORG_ID));

        filterChain.doFilter(httpServletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}
}
辅助类UserContextFilter
public class UserContextHolder {
    private static final ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>();

    public static final UserContext getContext(){
        UserContext context = userContext.get();

        if (context == null) {
            context = createEmptyContext();
            userContext.set(context);

        }
        return userContext.get();
    }

    public static final void setContext(UserContext context) {
        Assert.notNull(context, "Only non-null UserContext instances are permitted");
        userContext.set(context);
    }

    public static final UserContext createEmptyContext(){
        return new UserContext();
    }
}
辅助类UserContextHolder
public class UserContextInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(
            HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {

        HttpHeaders headers = request.getHeaders();
        headers.add(UserContext.CORRELATION_ID, UserContextHolder.getContext().getCorrelationId());
        headers.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken());


        return execution.execute(request, body);
    }
}
辅助类UserContextInterceptor
@Configuration
public class JWTRestTemplateConfig {
    @Primary
    @Bean
    public RestTemplate getCustomRestTemplate() {
        RestTemplate template = new RestTemplate();
        List interceptors = template.getInterceptors();
        if (interceptors == null) {
            template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
        } else {
            interceptors.add(new UserContextInterceptor());
            template.setInterceptors(interceptors);
        }

        return template;
    }
}
JWTRestTemplate配置类

 运行:

运行方式和OAuth2没有区别,有一个有意思的区别是:

获取到token后,把验证服务器关掉,再使用token去访问微服务,仍然能经过,这是由于JWT是自包含的,并不须要在每一个服务联系验证服务器再验证。

4、第三步:使用Spring Cloud和Zuul进行服务路由

需求:在微服务架构这种分布式架构中,须要确保多个服务调用的关键行为正常运做,如安全、日志记录、用户跟踪等。

问题:若是把这些工做分布在各个微服务中实现,有时会忘记,有时须要修改则全部微服务都要修改,显然不现实。

方案:

  将服务的这些横切关注点抽象成一个独立的,做为全部微服务调用的过滤器和路由器的服务。这个横切关注点称为服务网关(service gateway)。

  客户端不在直接调用服务,而是由服务网管做为单个策略执行点(Policy Enforcement Point,PEP),全部调用经过服务网关进行路由,而后被路由到目的地。

4.一、什么是服务网关

使用服务网关前:难以实现安全性、日志等横切关注点

使用服务网关后:客户端调用服务网关,全部服务的调用交给服务网关进行

这样,微服务架构的横切关注点能够放在服务网关实现,如:

    • 静态路由——服务网关将全部的服务调用放置在单个URL和API路由的后面。这简化了开发,由于开发人员只须要知道全部服务的一个服务端点就能够了。
    • 动态路由——服务网关能够检查传入的服务请求,根据来自传入请求的数据和服务调用者的身份执行智能路由。例如,可能会将参与测试版程序的客户的全部调用路由到特定服务集群的服务,这些服务运行的是不一样版本的代码,而不是其余人使用的非测试版程序的代码。
    • 验证和受权——因为全部服务调用都通过服务网关进行路由,因此服务网关是检查服务调用者是否已经进行了验证并被受权进行服务调用的天然场所。
    • 度量数据收集和日志记录——当服务调用经过服务网关时,能够使用服务网关来收集数据和日志信息,还能够使用服务网关确保在用户请求上提供关键信息以确保日志统一。这并不意味着不该该从单个服务中收集度量数据,而是经过服务网关能够集中收集许多基本度量数据,如服务调用次数和服务响应时间。

4.二、Spring Cloud和Netflix Zuul简介

Spring Cloud集成了Netflix开源项目Zuul。

Zuul提供了许多功能,具体包括如下几个:

  • 将应用程序中的全部服务的路由映射到一个URL——Zuul不局限于一个URL。在Zuul中,开发人员能够定义多个路由条目,使路由映射很是细粒度(每一个服务端点都有本身的路由映射)。然而,Zuul最多见的用例是构建一个单一的入口点,全部服务客户端调用都将通过这个入口点。
  • 构建能够对经过网关的请求进行检查和操做的过滤器——这些过滤器容许开发人员在代码中注入策略执行点,以一致的方式对全部服务调用执行大量操做。

使用Zuul的步骤:

  • 一、创建一个Spring Boot项目,并配置Zuul的Maven依赖项:
注意!在Spring Cloud的新版本中,依赖名改为了spring-cloud-starter-netflix-zuul
<
dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
  • 二、使用Spring Cloud注解修改这个Spring Boot项目,将其声明为Zuul服务:
@SpringBootApplication
@EnableZuulProxy  ⇽--- 使服务成为一个Zuul服务器
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
}

  @EnableZuulProxy与@EnableZuulServer

    @EnableZuulServer:使用此注解将建立一个Zuul服务器,它不会加载任何Zuul反向代理过滤器,也不会使用Netflix Eureka进行服务发现(咱们将很快进入Zuul和Eureka集成的主题)。

    本文只会使用@EnableZuulProxy注解。

  • 三、配置Zuul以便Eureka进行通讯(可选):

  Zuul将自动使用Eureka来经过服务ID查找服务,而后使用Netflix Ribbon对来自Zuul的请求进行客户端负载均衡。

  配置src/main/resources/application.yml文件,与Eureka通讯

eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

4.三、在Zuul中配置路由

Zuul的核心是一个反向代理中间服务器,负责捕获客户端的请求,而后表明客户端调用远程资源。

在微服务架构的状况下,Zuul(反向代理)从客户端接收微服务调用并将其转发给下游服务。服务客户端认为它只与Zuul通讯。

Zuul要与下游服务进行沟通,Zuul必须知道如何将进来的调用映射到下游路由。Zuul有几种机制来作到这一点,包括:

  • 4.3.一、经过服务发现自动映射路由;

  Zuul不须要配置,默认自动使用正在调用的服务的Eureka服务ID,并将其映射到下游服务实例。例如,若是要调用organizationservice并经过Zuul使用自动路由,则能够使用如下URL做为端点,让客户端调用Zuul服务实例:

http://localhost:5555/organizationservice/v1/organizations/e254f8c-c442-4ebe-

 

使用带有Eureka的Zuul的优势在于,开发人员不只能够拥有一个能够发出调用的单个端点,有了Eureka,开发人员还能够添加和删除服务的实例,而无须修改Zuul。例如,能够向Eureka添加新服务,Zuul将自动路由到该服务,由于Zuul会与Eureka进行通讯,了解实际服务端点的位置。

  Zuul服务器上的/routes端点能够查看服务中全部映射的列表。如http://localhost:5555/routes

  

经过zuul注册的服务的映射展现在从/route调用返回的JSON体的左边,路由映射到的实际Eureka服务ID展现在其右边。

  • 4.3.二、使用服务发现手动映射路由;

Zuul容许开发人员更细粒度地明肯定义路由映射,而不是单纯依赖服务的Eureka服务ID建立的自动路由。

能够经过在zuulsvr/src/main/resources/application.yml中手动定义路由映射。

zuul:
  routes:
    organizationservice: /organization/**

经过添加上述配置,如今咱们就能够经过访问/organization/v1/organizations/ {organization-id}路由来访问组织服务了。

查看route的结果

此时出现2条服务条目,一条是根据Eureka自动映射的,一条是咱们在配置文件中手动映射的。

能够经过application.yml文件添加一个额外的Zuul参数ignored-services来排除Eureka自动映射的服务.

如下代码片断展现了如何使用ignored-services属性从Zuul完成的自动映射中排除Eureka服务ID organizationservice。

zuul:
  ignored-services: 'organizationservice'
  routes:
    organizationservice: /organization/**

ignored-services属性容许开发人员定义想要从注册中排除的Eureka服务ID的列表,该列表以逗号进行分隔。

添加前缀标记

zuul:
  ignored-services: '*'  ⇽--- ignored-services被设置为*,以排除全部基于Eureka服务ID的路由的注册
  prefix: /api  ⇽--- 全部已定义的服务都将添加前缀/api
  routes:
    organizationservice: /organization/**  ⇽--- organizationservice和licensingservice分别映射到organization和licensing
    licensingservice: /licensing/**

如今,则须要经过/api/organization/v1/organization/ {organization-id}来访问网关接口了

  • 4.3.三、使用静态URL手动映射路由。

Zuul能够用来路由那些不受Eureka管理的服务。在这种状况下,能够创建Zuul直接路由到一个静态定义的URL。

zuul:
 routes:
   licensestatic:  ⇽--- Zuul用于在内部识别服务的关键字
     path: /licensestatic/**  ⇽--- 许可证服务的静态路由
     url: http://licenseservice-static:8081  ⇽--- 已创建许可证服务的静态实例,它将被直接调用,而不是由Zuul经过Eureka调用

如今,licensestatic端点再也不使用Eureka,而是直接将请求路由到http://licenseservice-static:8081端点。

这里存在一个问题,那就是经过绕过Eureka,只有一条路径能够用来指向请求。

幸运的是,开发人员能够手动配置Zuul来禁用Ribbon与Eureka集成,而后列出Ribbon将进行负载均衡的各个服务实例。

zuul:
  routes:
    licensestatic:
      path: /licensestatic/**
      serviceId: licensestatic  ⇽--- 定义一个服务ID,该服务ID将用于在Ribbon中查找服务
ribbon:
  eureka:
    enabled: false  ⇽--- 在Ribbon中禁用Eureka支持
licensestatic:
  ribbon:
    listOfServers: http://licenseservice-static1:8081,
      http://licenseservice-static2:8082  ⇽--- 指定请求会路由到的服务器列表

可是若是禁用了Ribbon与Eureka集成,Zuul没法经过Ribbon来缓存服务的查找,那么每次调用都会调用Eureka。

解决这些问题,能够经过对非JVM应用程序创建单独的Zuul服务器来处理这些路由。例如经过Spring Cloud Sidecar使用Eureka实例注册非JVM服务,而后经过Zuul进行代理。(这里不介绍Spring Cloud Sidecar)

  • 4.3.四、动态从新加载路由配置

动态从新加载路由的功能容许在不回收Zuul服务器的状况下更改路由的映射。

Zuul公开了基于POST的端点路由/refresh,其做用是让Zuul从新加载路由配置。在访问完refresh端点以后,若是访问/routes端点,就会看到路由被刷新了。

  • 4.3.五、Zuul和服务超时

Zuul使用Netflix的Hystrix和Ribbon库,来帮助防止长时间运行的服务调用影响服务网关的性能。

在默认状况下,对于任何须要用超过1 s的时间(这是Hystrix默认值)来处理请求的调用,Zuul将终止并返回一个HTTP 500错误。

能够使用hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds属性来为全部经过Zuul运行的服务设置Hystrix超时。

zuul.prefix:  /api
zuul.routes.organizationservice: /organization/**
zuul.routes.licensingservice: /licensing/**
zuul.debug.request: true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 2500

为特定服务设置Hystrix超时,能够使用须要覆盖超时的服务的Eureka服务ID名称来替换属性的default部分。

hystrix.command.licensingservice.execution.isolation.thread.timeoutInMilliseconds:3000

最后,读者须要知晓另一个超时属性。虽然已经覆盖了Hystrix的超时,Netflix Ribbon一样会超时任何超过5 s的调用。尽管我强烈建议读者从新审视调用时间超过5 s的调用的设计,但读者能够经过设置属性servicename.ribbon.ReadTimeout来覆盖Ribbon超时。

hystrix.command.licensingservice.execution.isolation.thread.timeoutInMilliseconds: 7000
licensingservice.ribbon.ReadTimeout: 7000

 4.四、Zuul的真正威力:过滤器

经过Zuul网关代理确实简化了服务调用,可是Zuul的真正威力在于能够为全部流经网关的服务调用编写自定义逻辑。

例如:安全性、日志记录和对全部服务的跟踪。

Zuul实现这个功能的方式是:过滤器。

Zuul支持这3种类型过滤器:

  • 前置过滤器——前置过滤器在Zuul将实际请求发送到目的地以前被调用。前置过滤器一般执行确保服务具备一致的消息格式(例如,关键的HTTP首部是否设置稳当)的任务,或者充当看门人,确保调用该服务的用户已经过验证(他们的身份与他们声称的一致)和受权(他们能够作他们请求作的)。
  • 后置过滤器——后置过滤器在目标服务被调用并将响应发送回客户端后被调用。一般后置过滤器会用来记录从目标服务返回的响应、处理错误或审核对敏感信息的响应。
  • 路由过滤器——路由过滤器用于在调用目标服务以前拦截调用。一般使用路由过滤器来肯定是否须要进行某些级别的动态路由。例如,本章的后面将使用路由级别的过滤器,该过滤器将在同一服务的两个不一样版本之间进行路由,以便将一小部分的服务调用路由到服务的新版本,而不是路由到现有的服务。这样就可以在不让每一个人都使用新服务的状况下,让少许的用户体验新功能。

Zuul的运做过程:

  • (1)在请求进入Zuul网关时,Zuul调用全部在Zuul网关中定义的前置过滤器。前置过滤器能够在HTTP请求到达实际服务以前对HTTP请求进行检查和修改。前置过滤器不能将用户重定向到不一样的端点或服务。
  • (2)在针对Zuul的传入请求执行前置过滤器以后,Zuul将执行已定义的路由过滤器。路由过滤器能够更改服务所指向的目的地。
  • (3)路由过滤器能够将服务调用重定向到Zuul服务器被配置的发送路由之外的位置。但Zuul路由过滤器不会执行HTTP重定向,而是会终止传入的HTTP请求,而后表明原始调用者调用路由。这意味着路由过滤器必须彻底负责动态路由的调用,而且不能执行HTTP重定向。
  • (4)若是路由过滤器没有动态地将调用者重定向到新路由,Zuul服务器将发送到最初的目标服务的路由。
  • (5)目标服务被调用后,Zuul后置过滤器将被调用。后置过滤器能够检查和修改来自被调用服务的响应。

 5、第四步:客户端弹性模式

分布式系统的传统弹性实现:集群关键服务器、服务间的负载均衡以及将基础设施分离到多个位置

传统弹性实现的缺点:只能解决致命的问题,当服务器奔溃,服务没法调用时,应用程序才会绕过它;而当服务变得缓慢或者性能不佳时,传统方式则没法绕过它

传统弹性实现的危害:可能只是一个服务的问题,如执行缓慢等,致使线程池被占用完,或数据库链接池被占用完,结果最终致使整个服务器资源被耗尽,致使服务器崩溃;甚至可能从一个服务器,蔓延往上游服务器去蔓延,致使整个生态系统崩溃。

解决方案:客户端弹性模式

5.一、什么是客户端弹性模式

客户端:是指调用远程服务或远程资源的应用程序

客户端弹性模式:当调用的远程服务或远程资源,出现缓慢或其余问题时,客户端执行“快速失败”,保证本身的线程池和数据库链接不被占用,防止远程服务或资源的问题向上游传播

四种模式:

  • 客户端负载均衡模式

在前面的服务发现中介绍到,客户端从服务发现代理(如Netflix Eureka)查找服务的全部实例,而后缓存服务实例的物理位置。

每当服务消费者须要调用该服务实例时,客户端负载均衡器将从它维护的服务位置池返回一个位置。

由于客户端负载均衡器位于服务客户端和服务消费者之间,因此负载均衡器能够检测服务实例是否抛出错误或表现不佳。

若是客户端负载均衡器检测到问题,它能够从可用服务位置池中移除该服务实例,并防止未来的服务调用访问该服务实例。

使用Netflix的Ribbon库提供的开箱即用的功能,不须要额外的配置便可实现。

  • 断路器模式

断路器模式是模仿电路断路器的客户端弹性模式。

当远程服务被调用时,断路器将监视这个调用。

若是调用时间太长,断路器将会介入并中断调用。

此外,断路器将监视全部对远程资源的调用,若是对某一个远程资源的调用失败次数足够多,那么断路器实现就会出现并采起快速失败,阻止未来调用失败的远程资源。

  • 后备模式

后备模式中,当远程服务调用失败时,服务消费者将执行替代代码路径,并尝试经过其余方式执行操做,而不是生成一个异常。

这一般涉及从另外一数据源查找数据或将用户的请求进行排队以供未来处理。

用户的调用结果不会显示为提示问题的异常,但用户可能会被告知,他们的请求要在晚些时候被知足。

例如,假设咱们有一个电子商务网站,它能够监控用户的行为,并尝试向用户推荐其余能够购买的产品。

一般来讲,能够调用微服务来对用户过去的行为进行分析,并返回针对特定用户的推荐列表。

可是,若是这个偏好服务失败,那么后备策略多是检索一个更通用的偏好列表,该列表基于全部用户的购买记录分析得出,而且更为广泛。

这些更通用的偏好列表数据可能来自彻底不一样的服务和数据源。

  • 舱壁模式

舱壁模式是创建在造船的概念基础上的。

经过使用舱壁模式,能够把远程资源的调用分到线程池中,并下降一个缓慢的远程资源调用拖垮整个应用程序的风险。

线程池充当服务的“舱壁”。

每一个远程资源都是隔离的,并分配给线程池。

若是一个服务响应缓慢,那么这种服务调用的线程池就会饱和并中止处理请求,而对其余服务的服务调用则不会变得饱和,由于它们被分配给了其余线程池。

5.二、客户端弹性的重要性

一个实现客户端弹性的例子

第一种场景:

  • 愉快路径,断路器将维护一个定时器,若是在定时器的时间用完以前完成对远程服务的调用,那么一切都很是顺利,服务B能够继续工做。

第二种场景:

  • 在部分降级的场景中,服务B将经过断路器调用服务C。
  • 这一次服务C运行缓慢,在断路器维护的线程上的定时器超时以前没法完成对远程服务的调用,断路器就会切断对远程服务的链接。
  • 而后,服务B将从发出的调用中获得一个错误,可是服务B不会占用资源(也就是本身的线程池或链接池)来等待服务C完成调用。

第三种场景:

  • 若是对服务C的调用被断路器超时中断,断路器将开始跟踪已发生故障的数量。
  • 若是在必定时间内在服务C上发生了足够多的错误,那么断路器就会电路“跳闸”,而且在不调用服务C的状况下,就断定全部对服务C的调用将会失败。
  • 电路跳闸将会致使以下3种结果。
    1. 服务B如今当即知道服务C有问题,而没必要等待断路器超时。
    2. 服务B如今能够选择要么完全失败,要么执行替代代码(后备)来采起行动。
    3. 服务C将得到一个恢复的机会,由于在断路器跳闸后,服务B不会调用它。这使得服务C有了喘息的空间,并有助于防止出现服务降级时发生的级联死亡。
  • 最后,断路器会让少许的请求调用直达一个降级的服务,若是这些调用连续屡次成功,断路器就会自动复位。

总结断路器模式提供的关键能力:

  • 快速失败:

当远程服务处于降级状态时,应用程序将会快速失败,并防止一般会拖垮整个应用程序的资源耗尽问题的出现。在大多数中断状况下,最好是部分服务关闭而不是彻底关闭。

  • 优雅地失败

经过超时和快速失败,断路器模式使应用程序开发人员有能力优雅地失败,或寻求替代机制来执行用户的意图。

例如,若是用户尝试从一个数据源检索数据,而且该数据源正在经历服务降级,那么应用程序开发人员能够尝试从其余地方检索该数据。

  • 无缝恢复

有了断路器模式做为中介,断路器能够按期检查所请求的资源是否从新上线,并在没有人为干预的状况下从新容许对该资源进行访问。

对于有几百个服务的系统,这种自恢复能力很重要,而不是靠人为手工去恢复这些服务的状态。

 5.三、Hystrix

构建断路器模式、后备模式和舱壁模式的实现须要对线程和线程管理有深刻的理解,正确地作到这一点很困难。

咱们如今能够借助Spring Cloud和Netflix的Hystrix库,轻松实现这些模式。

 一、引入依赖和使用注解开启断路器模式

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
@EnableCircuitBreaker
public class Application {
} 

断路器咱们介绍2种类型:包装数据库调用、包装服务调用

6、使用Spring Cloud Steam的事件驱动架构

基于消息,实现微服务间异步通讯

6.一、为何使用消息传递、EDA和微服务

假设许可证服务须要调用组织服务获取组织信息,而组织信息是较少修改的。

若是每次获取组织信息,都调取组织服务对应的API,那么网络开销较大。

一个可行方案是对组织信息进行缓存。

缓存方案实施时有如下3个核心要求:

(1)缓存的组织数据应该在许可证服务全部实例之间保持一致——表明不能在许可证服务本地缓存数据。

(2)不能将组织数据缓存在许可证服务的容器的内存中——许可证服务的运行时容器一般受到大小限制,而且能够使用不一样的访问模式来对数据进行访问。本地缓存可能会带来复杂性,由于必须保证本地缓存与集群中的全部其余服务同步。

(3)在更新或删除一个组织记录时,开发人员但愿许可证服务可以识别出组织服务中出现了状态更改——许可证服务应该使该组织的全部缓存数据失效,并将它从缓存中删除。

 

2种实现方法

6.1.一、使用同步请求——响应方式来传递状态变化

一、用户调用许可证服务,查询许可证数据

二、许可证服务中须要组织信息,先检查Redis缓存中是否有

三、若是没有,则调用组织服务获取,并写入缓存中保存

四、用户调用组织服务能够更新组织数据

五、组织数据更新后,组织服务应该更新缓存中数据,能够经过调用许可证端点或直接与缓存联系。

问题:

一、服务之间紧耦合

许可证服务始终依赖于组织服务来检索数据。

若是经过调用许可证端点来更新缓存,则令组织服务又依赖于许可证服务。

若是组织服务直接联系Redis,也不合理,由于你直接与另一个服务的数据库进行通讯,是不正确的,一个是权限问题,另外一个不清楚规则会破坏许可证服务的数据格式。

二、服务之间脆弱性

若是许可证服务出现问题,会影响甚至拖垮组织服务;而Redis出现问题,则会影响2个服务。

三、缺少灵活性

若是有新的服务对组织服务的变化感兴趣,则须要修改组织服务,从而须要从新生成构建代码部署代码;并且整个网络互相依赖,容易出现一个故障点拖垮整个网络。

 

6.1.二、使用消息传递在服务之间传达状态更改

 

加入消息传递方式与上面一种方法的差异在于组织变动时如何

使用消息传递方式将会在许可证服务和组织服务之间注入队列。该队列不会用于从组织服务中读取数据,而是由组织服务用于在组织服务管理的组织数据内发生状态更改时发布消息。图8-2演示了这种方法。

在图8-2所示的模型中,每次组织数据发生变化,组织服务都发布一条消息到队列中。许可证服务正在监视消息队列,并在消息进入时将相应的组织记录从Redis缓存中清除。当涉及传达状态时,消息队列充当许可证服务和组织服务之间的中介。这种方法提供了如下4个好处:

松耦合;
耐久性;
可伸缩性;
灵活性。
1.松耦合
微服务应用程序能够由数十个小型的分布式服务组成,这些服务彼此交互,并对彼此管理的数据感兴趣。正如在前面提到的同步设计中所看到的,同步HTTP响应在许可证服务和组织服务之间产生一个强依赖关系。尽管咱们不能彻底消除这些依赖关系,可是经过仅公开直接管理服务所拥有的数据的端点,咱们能够尝试最小化依赖关系。消息传递的方法容许开发人员解耦两个服务,由于在涉及传达状态更改时,两个服务都不知道彼此。当组织服务须要发布状态更改时,它会将消息写入队列,而许可证服务只知道它获得一条消息,殊不知道谁发布了这条消息。

2.耐久性
队列的存在让开发人员能够保证,即便服务的消费者已经关闭,也能够发送消息。即便许可证服务不可用,组织服务也能够继续发布消息。消息将存储在队列中,并将一直保存到许可证服务可用。另外一方面,经过将缓存和队列方法结合在一块儿,若是组织服务关闭,许可证服务能够优雅地降级,由于至少有部分组织数据将位于其缓存中。有时候,旧数据比没有数据好。

3.可伸缩性
由于消息存储在队列中,因此消息发送者没必要等待来自消息消费者的响应,它们能够继续工做。一样地,若是一个消息消费者没有足够的能力处理从消息队列中读取的消息,那么启动更多消息消费者,并让它们处理从队列中读取的消息则是一项很是简单的任务。这种可伸缩性方法适用于微服务模型,由于我经过本书强调的其中一件事情就是,启动微服务的新实例应该是很简单的,让这些追加的微服务处理持有消息的消息队列亦是如此。这就是水平伸缩的一个示例。从队列中读取消息的传统伸缩机制涉及增长消息消费者能够同时处理的线程数。遗憾的是,这种方法最终会受消息消费者可用的CPU数量的限制。微服务模型则没有这样的限制,由于它是经过增长托管消费消息的服务的机器数量来进行扩大的。

4.灵活性
消息的发送者不知道谁将会消费它。这意味着开发人员能够轻松添加新的消息消费者(和新功能),而不影响原始发送服务。这是一个很是强大的概念,由于能够在没必要触及现有服务的状况下,将新功能添加到应用程序。新的代码能够监听正在发布的事件,并相应地对它们作出反应。

 

6.1.三、消息传递架构的缺点

与任何架构模型同样,基于消息传递的架构也有折中。基于消息传递的架构多是复杂的,须要开发团队密切关注一些关键的事情,包括:

消息处理语义;
消息可见性;
消息编排。
1.消息处理语义

开发人员不只须要了解如何发布和消费消息

面对有序消息,开发人员须要考虑如何处理,没有按顺序处理会出现什么状况,而不是每条消息独立地使用

还要考虑消息抛出异常或错误时,对当前消息的处理(重试or失败?)以及对将来消息的处理

2.消息可见性

考虑使用关联ID等,跟踪Web服务调用以及消息的发布,实现对用户事务的跟踪

3.消息编排

基于消息传递的应用程序很难按照顺序进行业务逻辑推理,用户事务可能也在不一样时间不按顺序执行,调试基于消息的应用程序会设计多个不一样服务的日志。

 

6.二、Spring Cloud Stream

简介

Spring Cloud经过Spring Cloud Stream项目,轻松地将消息传递集成到基于Spring的微服务中。

Spring Cloud Stream是一个由注解驱动的框架,它容许开发人员在Spring应用程序中轻松地构建消息发布者和消费者。

Spring Cloud Stream对消息传递平台进行了抽象,实现消息发布和消费是经过平台无关的Spring接口实现的,而平台的具体实现细节(如使用Kafka仍是RabbitMQ),则排除在应用程序以外。

架构

随着Spring Cloud中消息的发布和消费,有4个组件涉及发布消息和消费消息,它们是:

发射器(source);
通道(channel);
绑定器(binder);
接收器(sink)。
1.发射器
当一个服务准备发布消息时,它将使用一个发射器发布消息。发射器是一个Spring注解接口,它接收一个普通Java对象(POJO),该对象表明要发布的消息。发射器接收消息,而后序列化它(默认的序列化是JSON)并将消息发布到通道。

2.通道
通道是对队列的一个抽象,它将在消息生产者发布消息或消息消费者消费消息后保留该消息。通道名称始终与目标队列名称相关联。然而,队列名称永远不会直接公开给代码,相反,通道名称会在代码中使用。这意味着开发人员能够经过更改应用程序的配置而不是应用程序的代码来切换通道读取或写入的队列。

3.绑定器
绑定器是Spring Cloud Stream框架的一部分,它是与特定消息平台对话的Spring代码。Spring Cloud Stream框架的绑定器部分容许开发人员处理消息,而没必要依赖于特定于平台的库和API来发布和消费消息。

4.接收器
在Spring Cloud Stream中,服务经过一个接收器从队列中接收消息。接收器监听传入消息的通道,并将消息反序列化为POJO。从这里开始,消息就能够按照Spring服务的业务逻辑来进行处理。

 

6.三、编写简单的消息发布者和消费者

目的:A服务发布消息,B服务打印到窗口

6.3.一、编写消息发布者

咱们首先修改组织服务,以便每次添加、更新或删除组织数据时,组织服务将向Kafka主题(topic)发布一条消息,指示组织更改事件已经发生。

 

 

 

 

 

 

2、使用Spring Cloud构建微服务

从3个关键角色的视角解析:

  • 架构师:负责大局,了解应用程序如何分解为单个微服务,以及微服务如何交互以交付解决方案。
  • 软件开发人员:编写代码并交付微服务。
  • DevOps工程师:负责服务部署和管理,保障每一个环境一致性和可重复性。

2.一、架构师:设计微服务架构

架构师负责提供解决问题的工做模型,提供脚手架供开发人员构建代码,使应用程序全部部件组合在一块儿。

关键任务:

  • 分解业务问题
  • 创建服务粒度
  • 定义服务接口

2.1.一、分解业务问题

!!!暂时不是重点,这里须要补充

3、使用Spring Cloud配置服务器控制配置

由于微服务架构中,各个服务是独立开发独立部署的,配置文件不可能像单体架构同样,给每个服务代码中放置一份配置文件来控制。

这里提出了一种方案:经过Spring Cloud配置服务器,将配置做为一个微服务部署起来,其它微服务经过访问这个配置服务器来获取配置,从而实现统一管理。

3.一、构建一个基于文件系统的Spring Cloud配置服务器

目的:

  编写对应的配置文件,放置在Spring Cloud配置服务的代码下;其它微服务访问获取配置时,读取这些文件,并返回给服务的消费者。

一、建立Maven工程

  

  为项目加上resources文件夹

  

  下一步,添加排除的文件类型

  

二、加入POM文件

<?xml version="1.0"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

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

    <groupId>com.ltmicro</groupId>
    <artifactId>test01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>test01</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

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

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
View Code

主要代码解析:

  • parent:spring-boot-starter-parent,使用Spring Boot
  • dependencyManagement-spring-cloud-dependencies:设定使用的Spring Cloud的版本
  • dependency-spring-cloud-config-server、spring-cloud-starter-config:引入Spring Cloud的服务器和配置组件,版本自动根据上面的来
  • 注意,Spring Boot和Spring Cloud是相对独立的2个项目,他们之间的版本兼容性请查看官方文档

三、添加几个配置文件

  

四、配置Spring Cloud Config启动类

使用注解:

  @SpringBootApplication:代表Spring Boot应用程序(Spring Cloud是Spring Boot应用)

  @EnableConfigServer:代表是Spring Cloud Config服务

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

五、 配置程序

在src/main/resources文件夹下,添加一个application.yml文件(也能够使用properties文件,语法不一样)

server:
  port: 8888
spring:
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          searchLocations: classpath:config/,classpath:config/licensingservice
#         searchLocations: c:/Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/licensingservice,
#                          c:/Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/organizationservice
#         searchLocations: file:///Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/licensingservice,
#                          file:///Louis/Projects/JavaDefaultWorkPlace/test01/src/main/resources/config/organizationservice

主要设置了这个程序的端口,以native(本地)的方式运行,而后配置了对应查找配置的目录(包含了3种写法)

六、运行程序

我这里使用的是Maven 的spring-boot:run命令运行,也能够直接项目右键运行

 

访问如下地址都可以获得对应配置

  • http://localhost:8888/licensingservice/default
  • http://localhost:8888/licensingservice/dev
  • http://localhost:8888/licensingservice/prod

3.二、将配置服务改造为基于Git

3.1中,若是部署这个配置服务,要求部署环境必须有对应文件系统,这个有时候很不方便,例如须要用容器来部署。

因此Spring Cloud Config支持其余后端存储库,如Git等,咱们这里介绍如何基于Git来构建这个配置服务。

一、Git的使用

Git是一个开源分布式版本管理系统,咱们能够使用一些厂商提供的Git服务,如GitHub、Gitee(码云)

下面咱们使用码云来实现这个配置服务。

在码云中建立一个项目,而后建立文件夹,放入咱们在3.1中设计好的配置文件

 

二、如今咱们能够删掉项目中resources文件夹下的配置文件

三、修改application.yml文件

注意访问码云地址的时候,uri应该填项目地址下面这个

server:
  port: 8888
spring:
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/Louisyzh/LTMicro.git
          searchPaths: /**
          username: 你的码云账号
          password: 你的码云密码

四、同3.1运行程序,能获得相同的结果

3.三、客户端(其它微服务)获取配置

咱们的配置服务器通过上面已经搭建好了,是一个Spring Boot程序,打包成jar或者war包便可放到生产环境中运行。

下面介绍客户端(其它微服务)若是获取这些配置

一、从新建立一个Maven项目,并添加resources文件夹

(步骤同上,略)

二、修改POM文件

<?xml version="1.0"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

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

    <groupId>com.ltmicro</groupId>
    <artifactId>test01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>test01</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
View Code

主要代码解析:

  • dependency-spring-boot-starter-web:使用Spring Boot的Web依赖
  • dependency-spring-cloud-config-client:使用Spring Cloud Config的客户端依赖

三、配置application.yml文件

spring:
  application:
    name: licensingservice
  profiles:
    active: default
  cloud:
    config:
      uri: http://localhost:8888
  • spring.application.name:应用程序名称,必须直接映射到Spring Cloud配置服务器中的目录名称,即服务器上必须有此名的一个目录。
  • spring.profiles.active:代表程序运行哪一个profile,对应就会读取配置服务器中对应的profile的配置。
  • spring.cloud.config.uri:Spring Cloud Config服务器的地址。

四、构建一些测试代码

配置好后,程序运行时,至关于把从服务器获得的配置,做为本身的配置文件进行初始化。

咱们这里经过一个控制器+@Value注解读取配置属性来进行测试

建立一个组件用来读取配置属性

@Component
public class ServiceConfig {
    @Value("${example.property}")
    private String exampleProperty;

    public String getExampleProperty() {
        return exampleProperty;
    }
}

建立一个控制器用来显示属性

@RestController
public class LicenseServiceController {
    @Autowired
    private ServiceConfig serviceConfig;
    @RequestMapping(value = "/config", method = RequestMethod.GET)
    public String getLicenses() {
        return serviceConfig.getExampleProperty();
    }
}

五、运行程序

运行程序,访问对应地址http://localhost:8080/config,便可显示出获取到的属性。

3.四、配置文件的加密解密(待补充)

若是咱们使用像Gitee、GitHub这样的第三方存储库存放咱们的配置文件,若是敏感的信息都明文存放,会容易暴露。

因此这里提供一个对敏感信息进行加密解密的方案。

 

 

 

 

 

 

 

 

 

 

 

布式调用架构、SOA架构上面演进的一种架构,也不是一种彻底创新的架构。

是为了适应互联网行业需求而出现的一种架构。

包括词汇、工做方式、开发方式的演进。

目前尚未很标准的一种定义,这里介绍只是一种实现方式。

架构演进:服务的时代背景:

  • 互联网行业的快速发展:为快不破,快速套现,占领用户
  • 敏捷方法论的深刻人心:短迭代,仍是为了实现快
  • 容器虚拟化等DevOps技术的成熟化:持续交付,为快提供支持
  • 传统分布式技术面临的挑战:大规模服务化需求,服务粒度较小,数量较多

讨论集中式架构:

  集中式单块(Monolithic)架构:如MVC

  

  优点

    • 易于开发/测试/部署
    • 具备必定程度的水平伸缩性,如放多块上去,作一些分流、负载均衡等,都是能够的

  劣势

    • 维护成本与业务复杂度:当业务变复杂,东西都放在单块里面,就不方便了,维护增长了成本
    • 团队规模与结构
    • 交互周期与管理
    • 可扩展性

讨论SOA架构:

  主要问题:

    • SOA只是一个思想,只提供指导思想和原则,难以达成共识
    • 服务的粒度/依赖关系/通讯协议等如何肯定:除非使用第三方框架
    • 服务实例数量相对有限,难以应对大规模服务化场景:这个只是相对微服务而言
    • 企业级实施方案,须要自顶向下推行
    • 交互方式偏重于功能性团队模式:须要不一样功能的部门都齐备且合做和谐

互联网公司的需求:

  应对大规模服务化需求:(具体的含义,或者实际的示例是什么?须要补充)

    • 如何肯定服务拆分粒度
    • 如何下降服务之间依赖
    • 若是尽可能下降技术/架构之间差别化
    • 若是让团队Owning服务
    • 如何进行快速的服务开发和交付

 

微服务概述:

定义:(来自Martin Fowler)

  • 微服务架构是一种架构模式,它提倡将单一应用程序划分红一组小的服务,服务之间互相协调和配合,为用户提供最终价值
  • 每一个服务运行在其独立的进程中,服务于服务器间采用轻量级通讯机制相互沟通(一般是基于HTTP的RESTful API)
  • 每一个服务都围绕着业务进行构建,而且可以被独立部署到生产/类生产环境
  • 尽可能避免同一的、集中式的服务管理机制,对具体的一个服务而已,应该根据业务上下文,选择合适的语言、工具进行构建

特性:(“微”的含义)

  • 业务独立
  • 团队自主
  • 技术无关轻量级通讯
  • 交付独立性:

    

  • 进程隔离

    服务间并不须要组合成组建去发布,而是每一个服务都是独立的;同时下降服务之间的耦合。

    

微服务与SOA的区别和联系:

  

 

架构师演化:

快速演进过程当中的架构师:

  • 关注服务之间的交互,而不是服务内部实现细节
  • 作出符合团队目标的技术选择,提供代码模版
  • 考虑系统容许多少可变性,并可以快速适应变化
  • 完成>完美
  • 建设团队

 

咱们的思路:

  • 从核心原理到基本实现
  • 从开发到运维
  • 从技术体系到案例
    • 技术体系:Spring Boot、Spring Cloud
    • 案例:建模、开发、测试、部署
  • 穿插相关模式和架构风格

 

微服务架构

实现策略:

  • 采用松散服务体系
  • 围绕业务组件团队
  • 融合技术多样性
  • 确保业务数据独立
  • 基本设施自动化

采用松散服务体系

  • 独立开发/部署/维护
  • 技术无关的集成接口

  

围绕业务组件团队

  创建Feature Team

  

融合技术多样性

  由于面向服务,可能遇到不少系统

  使用http这些技术无关的技术进行链接

  

确保业务数据独立

  使用服务集成而不是数据集成(微服务中一个颇有用的特色)

  

基础设施自动化(DevOps)

  • 服务部署
  • 健康诊断
  • 错误回滚
  • 日志分析
  • 服务治理

 

 

实施问题:

  • 分布式系统固有的复杂性
    • 性能
    • 可靠性
    • 异步
    • 数据一致性
  • 部署自动化与运维成本
    • 配置
    • 监控和报警
    • 日志收集
    • 部署流水线
  • 组织架构
    • 康威定律与企业文化
    • 开发/运维角色变换
      • 开发承担整个生命周期
      • 运维尽早开来服务部署
  • 服务间的依赖管理
    • 开发进程同步和管理
    • 服务通讯与集成
    • 服务依赖测试

切入点:

一切从零开始:

  • 明确服务建模和服务集成的方法
  • 寻找一个构建微服务的基本框架
  • 探索面向发布的全流程微服务实现方案
    • 技术体系
    • 工具
    • 策略

 

 

第三节:

 

服务建模:

微服务中的如何实现松耦合

  独立部署单个服务并不须要修改其它服务

  尽量使用轻量级通讯方式进行服务集成

微服务中的高内聚

  只改一个地方就能够发布

  尽量明确领域边界

  

不该该是技术驱动建模,应该业务驱动建模

利用上下文划分界限

 

 

服务集成:

 

 

RPC,Messaging分别表明了2中风格的通讯方式

API网关,一个统一入口

 

 

Spring Boot入门

Spring 的一个子项目

简介:

 

 

 

基本示例:

 

Spring Boot消息传递

点对点,发布订阅

这2种方式都比较常见,因此基本的第三方消息中间件都支持这2种模型

消息传递的协议、规范

一、JMS(Java Message Service)协议

实现:ActiveMQ

Spring组件:spring-jms 只要实现了JMS的组件,均可以经过Spring的spring-jms抽象来操做

二、AMQP(Advanced Message Queuing Protocol)协议

实现:RabbitMQ

Spring组件:同上,有spring-rabbit

三、以上2个协议是业界标准;其它协议,如kafka,自定的规范,自为体系

 

JMS规范(选修课里面讲到)

AMQP规范:

 

2个示例

讲解了2个组件在Sping BOot中的简单实用

 

Spring Boot部署与Docker

SPring Boot部署

 

Docker云部署

 

部署建模:把部署提高到建模等级

有点相似Maven的仓库,例如把一个jar包做为镜像放到注册中心中,那么其余镜像均可以访问到他

 

群关键服务器、服务间的负载均衡以及将基础设施分离到多个位置的

Hystrix

相关文章
相关标签/搜索