SpringCloud与微服务-第1章-基本概念简介

第 1 章 - 微服务基础知识

在进行 SpringCloud 的具体内容介绍之前,我们先通过本章学习一些关于微服务架构以及 SpringCloud 的基础知识。对 SpringCloud 能够解决的具体问题有一个大致的了解,以帮助我们更好地理解后续章节对各个组件的介绍。


学习目标

  • 了解分布式系统
  • 了解什么是微服务架构
  • 为什么选择 Spring Cloud

随着业务规模和系统复杂度的提升,很多系统会历经从单一架构到垂直架构,再到分布式架构的技术发展过程。


单体系统

在以往传统的企业系统架构中,我们针对一个复杂的业务需求通常使用对象或业务类型来构建一个单体项目。在项目中我们通常将需求分为三个主要部分:数据库、服务端处理、前端展现。在业务发展初期,由于所有的业务逻辑在一个应用中,开发、测试、部署都还比较容易且方便。


w:36em



但是由于单体系统部署在一个进程内,功能之间是紧密耦合的, 往往我们修改了一个很小的功能,部署上线会影响其他功能的运行。

并且,单体应用中的这些功能模块的使用场景、并发量、消耗的资源类型都各有不同,对于资源的利用又互相影响,这样使得我们对各个业务模块的系统容量很难给出较为准确的评估。

所以,单体系统在初期虽然可以非常方便地进行开发和使用,但是随着系统的发展,维护成本会变得越来越大,且难以控制。


面对大流量高并发的用户访问,以及随之产生的海量数据处理等诸多挑战下,如何能为用户提供稳定可靠的服务,成为目前很多互联网大公司面临的技术问题。比如,常见的高并发场景有:

  • 购物节
  • 春运购票
  • 秒杀系统
  • 抖音

购物节的支付系统要想要在高并发的场景下实现五个 9(99.999%)的高可用性,保证支付率,还要保证秒杀场景下单位时间内成交的订单数更多,仅靠单一架构是不能做到的。


如果采用集群的垂直架构,随着业务的发展和系统复杂度的提生,要会出现越来越多的子项目,并且子项目之间功能存在重叠, 维护和部署也变更复杂,很难方便的扩展。

因此越来越多的公司采用分布式架构


分布式架构

将一个系统横向分成若干子系统或服务,实现服务性能的动态扩容。
这不但大幅提高服务处理能力, 且降低单一程序的开发维护以及部署难度。

从整体来看系统复杂度和部署难度是增加了, 但是可以通过 CI/CD 即持续集成和持续部署手段将繁杂的测试部署等环节尽可能自动化.


w:28em


使用分布式服务作为软件体系结构的最早记录可追溯到二十世纪 80 年代初。


SOA 架构

SOA 首先在 90 年代中期得名,当时一家名为 Gartner Group 的公司认识到了这个软件架构的新趋势,并在全球推广。通过这样做,他们设法大大加快了这种架构模式的采用和进一步发展。

SOA(全称:Service Oriented Architecture),中文意思为 “面向服务的架构”,你可以将它理解为一个架构模型或者一种设计方法,而并不是服务解决方案。其中包含多个服务, 服务之间通过相互依赖或者通过通信机制,来完成相互通信的,最终提供一系列的功能。一个服务通常以独立的形式存在与操作系统进程中。各个服务之间通过网络调用 。


SOA 通常使用 ESB(企业总线)来连接各个服务节点。各个服务之间,只需要和 ESB 进行通信,这个时候,各个应用之间的交互就会变得更加的清晰,业务架构/逻辑等,也会变得很清楚。原本杂乱没有规划的系统,梳理成了一个有规划可治理的系统。

bg right


微服务架构

“微服务”一词源于 Martin Fowler 的名为 Microservices 的博文.

简单地说,微服务是系统架构上的一种设计风格,可以认为 SOA 是微服务的超集。微服务架构重点强调的一个是"业务需要彻底的组件化和服务化",原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用称为微服务,微服务之间通过基于 HTTP 的 RESTful API 进行通信协作。每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。


微服务和 SOA 的区别

  1. 微服务去中心化,去掉 ESB 企业总线。微服务不再强调传统 SOA 架构里面比较重的 ESB 企业服务总线,同时 SOA 的思想进入到单个业务系统内部实现真正的组件化。

  2. Docker 容器技术的出现,为微服务提供了更便利的条件,比如更小的部署单元,每个服务可以通过类似 Node 或者 SpringBoot 等技术跑在自己的进程中。

  3. SOA 注重的是系统集成方面,而微服务关注的是完全分离。


由于微服务是独立部署的,我们可以更准确地为每个服务评估性能容量,通过配合服务间的协作流程也可以更容易地发现系统的瓶颈位置,以及给出较为准确的系统级性能容量评估。


  • 使用微服务架构的厂商
    • 淘宝
    • 京东
    • 抖音
    • ... ...

bg fit right


bg fit


RPC(remote procedure call)

整个应用分散成多个服务使得整个系统变得更为复杂。我们需要在分布式开发中引入额外的技术,以解决服务之间交互和分布式部署导致的问题。

RPC(远程过程调用),即在本地调用远程机器的函数或者对象方法,使实际的体验和调用本地函数或者对象方法无异。

RPC 也是一种技术思想,HTTP 和 WebService 就是 RPC 思想的一种很好的体现方式,但 HTTP 已经满足不了企业内外部日益复杂的信息交互。因此许多优秀的 RPC 框架应运而生,比如著名的 Dubbo ,封装了一些像负载均衡、熔断降级、服务注册发现等面向对象的高级特性。


如何实施微服务

在实施微服务之前,我们必须要知道,微服务虽然有非常多吸引人的优点,但是也因为服务的拆分引发了诸多原本在单体应用中没有的问题。


  • 运维的新挑战:在微服务架构中,运维人员需要维护的进程数量会大大增加。我们需要运维人员有更多的技能来应对这样的挑战,运维过程需要更多的自动化,这就要求运维人员具备一定的开发能力来编排运维过程并让它们能自动运行起来。

  • 接口的一致性:虽然我们拆分了服务,但是业务逻辑上的依赖并不会消除,只是从单体应用中的代码依赖变为了服务间的通信依赖。我们需要更完善的接口和版本管理,或是严格地遵循开闭原则。


  • 分布式的复杂性:由于拆分后的各个微服务都是独立部署并运行在各自的进程内,它们只能通过通信来进行协作,所以分布式环境的问题都将是微服务架构系统设计时需要考虑的重要因素,比如网络延迟、分布式事务、异步消息等。

  • 尽管微服务架构有很多缺点和问题,但是其实现的敏捷开发和自动化部署等优点依然被广大优秀架构师和开发者所青睐.微服务架构并没有一个标准或正式的定义,


Martin Fowler 在 Microservices—文中,提炼出了微服务架构的九大特性,用于指导大家设计架构。

bg fit right


1. 服务组件化

在微服务架构中,需要我们对服务进行组件化分解。服务,是一种进程外的组件,它通过 HTTP 等通信协议进行协作,而不是像传统组件那样以嵌入的方式协同工作。每一个服务都独立开发、部署,可以有效避免一个服务的修改引起整个系统的重新部署。


bg fit


2. 按业务组织团队

在实施微服务架构时,需要采用不同的团队分割方法。由于每一个微服务都是针对特定业务的宽栈或是全栈实现,既要负责数据的持久化存储,又要负责用户的接口定义等各种跨专业领域的职能。因此,面对大型项目的时候,对于微服务团队的拆分更加建议按业务线的方式进行拆分,一方面可以有效减少服务内部修改所产生的内耗;另一方面,团队边界可以变得更为清晰.


w:20em

康威法则: 一个组织的结构应该反映其沟通结构。


bg fit


3. 做“产品”的态度

在实施微服务架构的团队中,每个小团队都应该以做产品的方式,对其产品的整个生命周期负责。持续关注服务的运作情况,并不断分析以帮助用户来改善业务功能。


4. 智能端点与哑管道

在微服务架构中,组件间的通信模式发生了改变,若仅仅将原本在进程内的方法调用改成 RPC 方式的调用,会导致微服务之间产生烦琐的通信,使得系统表现更为糟糕,所以,我们需要更粗粒度的通信协议。

Martin Flower 将微服务架构的服务通信理念称为“Smart endpoints and dumb pipes”, 简单翻译为“聪明的终端,愚蠢的管道”,大意是指强服务个体和弱通信。


在微服务架构中,通常会使用以下两种服务调用方式:

  • 使用 HTTP 的 RESTful API 或轻量级的消息发送协议
  • 通过在轻量级消息总线上传递消息,类似 RabbitMQ 等一些提供可靠异步交换的中间件。

在极度强调性能的情况下,有些团队会使用二进制的消息发送协议,例如 protobuf 即使是这样,这些系统仍然会呈现出“智能端点和哑管道”的特点,这是为了在易读性与高效性之间取得平衡。当然大多数 Web 应用或企业系统并不需要在这两者间做出选择,能够获得易读性已经是一个极大的胜利了。


5. 去中心化治理

在实施微服务架构时,通过采用轻量级的契约定义接口,使得我们对于服务本身的具体技术平台不再那么敏感,这样整个微服务架构系统中的各个组件就能针对其不同的业务特点选择不同的技术平台.


6. 去中心化管理数据

我们在实施微服务架构时,都希望让每一个服务来管理其自有的数据库,这就是数据管理的去中心化。

bg fit left

由于数据存储于不同的数据库实例中后,数据一致性也成为微服务架构中亟待解决的问题之一。(分布式锁,分布式事务)


7. 基础设施自动化

近年来云计算服务与容器化技术的不断成熟,在微服务架构中,务必从一开始就构建起“持续交付”平台来支撑整个实施过程:

  • 自动化测试:每次部署前的强心剂,尽可能地获得对正在运行的软件的信心。
  • 自动化部署:解放烦琐枯燥的重复操作以及对多环境的配置管理。

w:28em

Reference Link


8. 容错设计

微服务架构中,由于服务都运行在独立的进程中,所以存在部分服务出现故障,而其他服务正常运行的情况。由于服务之间存在调用链,因此在微服务架构中,快速检测出故障源并尽可能地自动恢复服务是必须被设计和考虑的。

通常,我们都希望在每个服务中实现监控和日志记录的组件,比如服务状态、断路器状态、吞吐量、网络延迟等关键数据的仪表盘等。


9. 演进式设计

在很多情况下,架构师都会以演进的方式进行系统的构建。

  • 在初期,以单体系统的方式来设计和实施,一方面系统体量初期并不会很大,构建和维护成本都不高,此时还不需要微服务架构。

  • 随着系统的发展或者业务的需要,架构师会将一些经常变动或是有一定时间效应的内容进行微服务处理,并逐渐将原来在单体系统中多变的模块逐步拆分出来,而稳定不太变化的模块就形成一个核心微服务存在于整个架构之中。


Spring Cloud

SpringCloud 的出现,可以说是对微服务架构的巨大支持和强有力的技术后盾,SpringCloud 是一个解决微服务架构实施的综合性解决框架的有序集合,利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。

w:20em


SpringCloud 核心部件:

w:28em


近几年人们对于微服务架构的热情非常高,无数的架构师和开发者在实际项目中实践该设计理念并为此付出了诸多努力,同时也分享了他们在微服务架构中针对不同应用场景出现的各种问题的各种解决方案和开源框架,其中也不乏国内互联网企业的杰出贡献。

  • 服务治理:阿里巴巴开源的 Spring Cloud Alibaba Nacos、Netflix 的 Eureka、Apache 的 Consul 等。
  • 分布式配置管理:百度的 Disconf、SpringCloud 的 Config、阿里巴巴开源的 Spring Cloud Alibaba Nacos 等。
  • 批量任务:当当网的 Elastic-Job、Linkedln 的 Azkaban、SpringCloud 的 Task 等。
  • 服务跟踪:京东的 Hydra、SpringCloud 的 Sleuth、Twitter 的 Zipkin 等。

早期 Netflix 和现在的 Alibaba 都是非常重要的 SpringCloud 的贡献者,他们都是微服务架构的先行者,他们的开源框架和解决方案都是我们在实施微服务架构时的参考。

w:28em


Spring Cloud Netflix

h:6em

Spring Cloud Alibaba

h:6em


上面列举了一些在实施微服务架构初期,就需要被我们考虑进去的问题,以及针对这些问题的开源解决方案。可以看到国内、国外的技术公司都在贡献着他们的智慧。我们搜索微服务架构的实施方案时会发现,几乎大部分的分享主要以理论或是一个粗轮廓框架为主,整合了来自不同公司或组织的诸多开源框架,并加入针对自身业务的一些优化,所以找不到一个完全相同的架构方案。


Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品,还可能会新增),挑选部分项目,如下所述。

  • Spring Cloud Config:配置管理工具,支持使用 Git 存储配置内容,可以使用它实现应用配置的外部化存储,并支持客户端配置信息刷新、加密/解密配置内容等。

  • Spring Cloud Netflix:核心组件,对多个 Netflix OSS 开源套件进行整合。

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

  • Spring Cloud Alibaba: 核心组件,对多个 Alibaba 开源套件进行整合.

    • 流量控制和服务降级: Alibaba Sentinel 的流量控制、断路和系统自适应保护
    • 服务注册与发现:实例注册到阿里巴巴的 Nacos,通过负载均衡器 Ribbon,客户端可以通过 Spring 管理的 bean 发现实例。
    • 分布式配置:使用阿里巴巴 Nacos 作为数据存储
    • 事件驱动:构建与 Spring Cloud Stream RocketMQ Binder 连接的高度可扩展的事件驱动微服务
  • Spring Cloud Bus:事件、消息总线,用于传播集群中的状态变化或事件,以触发后续的处理,比如用来动态刷新配置等

  • Spring Cloud Cluster:针对 ZooKeeper、Redis、Hazelcast、Consul 的选举算法和通用状态模式的实现。

  • Spring Cloud Stream:通过 Redis、RabbitMQ 或者 Kafka 实现的消费微服务,可以通过简单的声明式模型来发送和接收消息。


  • Spring Cloud Security:安全工具包,提供在 Zuul 代理中对 OAuth2 客户端请求的中继器。
  • Spring Cloud Sleuth: SpringCloud 应用的分布式跟踪实现,可以完美整合 Zipkin
  • Spring Cloud ZooKeeper:基于 ZooKeeper 的服务发现与配置管理组件。
  • Spring Cloud Starters:SpringCloud 的基础组件,它是基于 SpringBoot 风格项目的基础依赖模块。

image-20220711131013102


Spring Cloud 版本说明

当我们通过搜索引擎查找一些 SpringCloud 的文章或示例时,往往可以在依赖中看到很多不同的版本名字,比如 Angel.SR6、Brixton.SR5 等,为什么 SpringCloud 没有像其他 Spring 的项目使用类似 l.x.x 的版本命名规则呢?这些版本之间又有什么区别呢?在学习之初,非常有必要弄清楚这些版本的意义和内容,这样才能在我们使用 SpringCloud 时,指导我们选择更为合适的版本进行架构与开发。


版本名与版本号

由于 SpringCloud 不像 Spring 社区其他一些项目那样相对独立,它是一个拥有诸多子项目的大型综合项目,可以说是对微服务架构解决方案的综合套件组合,其包含的各个子项目也都独立进行着内容更新与迭代,各自都维护着自己的发布版本号。因此每一个 Spring Cloud 的版本都会包含多个不同版本的子项目,为了管理每个版本的子项目清单,避免 SpringCloud 的版本号与其子项目的版本号相混
淆,没有采用版本号的方式,而是通过命名的方式。


这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序,比如最早的 Release 版本为 Angel,第二个 Release 版本为 Brixton 经过上面的解释,不难猜出,之前所提到的 Angel.SR6、Brixton.SR5 中的 SR6、SR5 就是版本号了。

当一个版本的 SpringCloud 项目的发布内容积累到临界点或者一个严重 bug 解决可用后,就会发布一个“servicereleases”版本,简称 SRX 版本,其中 X 是一个递增的数字,所以 Brixton.SR5 就是 Brixton 的第 5 个 Release 版本。


SpringCloud 与 SpringBoot 的版本兼容关系如下:

Release Train Boot Version
2021.0.x aka Jubilee 2.6.x
2020.0.x aka Ilford 2.4.x, 2.5.x (Starting with 2020.0.3)
Hoxton 2.2.x, 2.3.x (Starting with SR5)
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

注意:在本教程,主要以 Hoxton.SR10 版本进行案例演示.因此对应的 SpringBoot 版本是 2.2.x ~ 2.3.x 版本.


活动手册

我们在构建 SpringBoot 应用的时候,总是需要一整套的符合 MVC 规范的简单模板文件,为了避免重复性操作,我们可以使用一些工具类帮我们完成这部分的工作.比如说 Intellij IDEA 的一款优秀插件: EasyCode。

EasyCode 是基于 IntelliJ IDEA 开发的代码生成插件,支持自定义任意模板(Java,html,js,xml)。只要是与数据库相关的代码都可以通过自定义模板来生成。支持数据库类型与 java 类型映射关系配置。支持同时生成生成多张表的代码。每张表有独立的配置信息。完全的个性化定义,规则由你设置。


活动 1.1 使用 EasyCode 插件生成辅助代码

创建项目- 推荐使用 Spring Initializr 创建项目

  • 使用https://start.aliyun.com/ 作为启动页

  • 建议使用 JDK11 或 JDK17


  • 依赖:

    • spring-boot-starter-parent 2.3.12.RELEASE

    • springboot-starter-web - 相关

    • springboot-starter-jdbc

    • spring-data-commons 2.3.9.RELEASE

    • mybatis-spring-boot-starter 2.1.4

    • mysql-connector-java (5.1.x)

    • spring-boot-starter-actuator

    • lombok optional

    • spring-boot-configuration-processor optional

    • spring-boot-devtool optional


步骤 1. 安装 EasyCode 插件

h:16em


步骤 2. 配置 EasyCode 插件

h:16em


步骤 3. 配置数据库连接

按照活动手册要求创建数据库, 然后Views -> Tool Windows -> Database

h:16em


步骤 4. 配置模板

settings -> Other Settings -> EasyCode -> templates

如果使用 lombok,需要在 entity 类模板中添加 @Data 注解

##使用全局变量实现默认包导入
$!{autoImport.vm}
import java.io.Serializable;
import lombok.Data;
##使用宏定义实现类注释信息
#tableComment("实体类")
@Data
public class $!{tableInfo.name} implements Serializable {
    private static final long serialVersionUID = $!tool.serial();
#foreach($column in $tableInfo.fullColumn)
    #if(${column.comment})/**
     * ${column.comment}
     */#end
    private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end

dao 接口类模板示例

queryAllByLimit代码需要修改才能使用

public interface $!{tableName} {

    /**
     * 通过ID查询单条数据
     * @param $!pk.name 主键
     * @return 实例对象
     */
    $!{tableInfo.name} queryById($!pk.shortType $!pk.name);

    /**
     * 查询指定行数据
     * @param $!tool.firstLowerCase($!{tableInfo.name}) 查询条件
     * @param pageable         分页对象
     * @return 对象列表
     */
    List<$!{tableInfo.name}> queryAllByLimit(
      @Param("entity") $!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name}),
      @Param("pageable") Pageable pageable
    );

mapper.xml 模板示例

mapper.xml文件保存路径需要调整与 SpringBoot 默认的一致

##引入mybatis支持
$!{mybatisSupport.vm}

##设置保存名称与保存位置
$!callback.setFileName($tool.append($!{tableInfo.name}, "Dao.xml"))
$!callback.setSavePath($tool.append($modulePath, "/src/main/resources/mappers"))

mapper.xml 模板示例

queryAllByLimit的查询需要修改才能使用

    <!--查询单个-->
    <select id="queryById" resultMap="$!{tableInfo.name}Map">
        select
          #allSqlColumn()

        from $!tableInfo.obj.name
        where $!pk.obj.name = #{$!pk.name}
    </select>

    <!--查询指定行数据-->
    <select id="queryAllByLimit" resultMap="$!{tableInfo.name}Map">
        select
          #allSqlColumn()
        from $!tableInfo.obj.name
        <where>
#foreach($column in $tableInfo.fullColumn)
            <if test="entity.$!column.name != null
  #if($column.type.equals("java.lang.String"))
                  and entity.$!column.name != ''
    #end      ">
                and $!column.obj.name = #{entity.$!column.name}
            </if>
#end
        </where>
        <if test="pageable.sort != null and pageable.sort.orders.size > 0">
            order by
            <foreach collection="pageable.sort.orders" item="order" separator=",">
                ${order.property} ${order.direction}
            </foreach>
        </if>
        limit #{pageable.offset}, #{pageable.pageSize}
    </select>

步骤 5. 生成代码

application.properties 需要修改mapper.xml的生成路径

# 应用名称
spring.application.name=EasyCodeDemo
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据源名称
spring.datasource.name=MySQLDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/course?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=niit1234
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件 - EasyCode自动生成的包为mapper, 需修改
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.example.easycodedemo.mybatis.entity
# debug
logging.level.com.example.easycodedemo=debug
spring.jpa.show-sql=true

可以选择多表, 选择生成代码


指定生成规则, 点击 OK 进行生成


指定 Mapper 的包扫描路径

package com.example.easycodedemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.easycodedemo.dao")
public class EasyCodeDemoApplication {

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

}

测试:

  • 对于 get 请求可以直接使用浏览器窗口进行测试(只支持 get)
    • 或者编写页面使用 ajax 进行测试
  • 或者 idea 的插件 (方便)
    • HTTP Client(Builtin)
    • RestfulTool(MarketPlace 免费)
    • Restful Fast Request(收费)
  • 另外还可以使用专用软件进行测试 (功能强大)
    • curl
    • postman
    • apifox
    • apipost

这里使用 RestfulTool 进行测试: body 设置{}表示无条件分页查询

bg right


分页查询报错的问题解决

测试http://localhost:8080/course/接口收到报错如下:

[Request processing failed;
nested exception is java.lang.IllegalStateException:
No primary or default constructor found for class org.springframework.data.domain.PageRequest] with root cause

java.lang.NoSuchMethodException: org.springframework.data.domain.PageRequest.<init>()

新建config包, 并创建一个自定义参数解析器PageRequestHandlerMethodArgumentResolver, 让它也能解析Pageable的子类 PageRequest

@Component
public class PageRequestHandlerMethodArgumentResolver extends PageableHandlerMethodArgumentResolver {

    /**
     * {@link PageableHandlerMethodArgumentResolver} SpringData 提供的只能解析 {@link Pageable} 类型的
     * 这里让它也能解析它的子类 {@link PageRequest}
     *
     * @param parameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Pageable.class.isAssignableFrom(parameter.getParameterType());
    }
}

接着创建WebConfig 配置类

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private PageRequestHandlerMethodArgumentResolver pageRequestHandlerMethodArgumentResolver;

    /**
     * 添加自定义参数解析器
     *
     * @param argumentResolvers
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(pageRequestHandlerMethodArgumentResolver);
    }
}

分页和排序支持:

http://localhost:8080/course?size=2&page=0&sort=cid,desc&sort=cname,asc

表示:

  1. 页容量为2, 显示第1页的数据
  2. 二次排序: 先按cid倒序, 再按cname正序

小问题:

Spring Cloud 为以下哪些操作提供了一种简单的开发方式?

  • A. 服务治理
  • B. 智能路由
  • C. 微代理
  • D. 控制总线


谢谢

Views: 47

大型网站架构演进和架构师需具备的基础能力

[架构设计] 01 大型网站架构演进

用户最初通过在浏览器地址栏输入网址来上网,打开的是静态单页网站,包含HTML、JavaScript和CSS样式。

image-20240326142713875

随着时间发展,网站进化成动态交互模式,引入了数据库,使用户能与服务器进行双向交互,如增加、删除、修改数据。

image-20240326143413170

随后出现了单体架构,用户访问服务器,而服务器内部署了应用程序、文件服务器和数据库。

image-20240326143454416

但随着流量增加,为避免服务器性能下降,将不同功能分离部署:网站数据放在应用服务器,用户上传的文件存储在文件服务器,数据库也独立部署。这种分离可以降低用户请求的延迟,提高并发能力和存储空间。

image-20240326143523416

然而,单体服务仍有瓶颈,为解决大规模用户访问问题,引入了缓存中间件,减轻数据库压力并提高用户体验。

image-20240326143606182

但单节点部署存在风险,因此进一步发展为集群负载均衡,通过部署多个相同的应用实例(集群)来分摊流量,提升整体系统性能。

image-20240326143658594

当读操作占大部分时,采用读写分离策略,设置主从数据库以优化性能。

image-20240326143725837

进一步地,当单表数据量巨大时,采用分库分表策略,将数据散列到不同数据库中,实现分布式数据库。

image-20240326143753609

海量数据检索可以引入搜索引擎技术(如 ElasticSearch)贴合用户需求,也可以保护数据库。

image-20240326143842523

最后,面对业务复杂性,采取微服务架构,将业务拆分成独立的子项目,每个子项目有自己的数据库并可独立扩展。但这增加了代码复杂性和运维难度,同时需处理分布式事务问题。

image-20240326144139535

整个演变过程根据企业业务需求灵活调整,目标是满足用户需求,保证系统稳定高效运行。

image-20240326144301774

架构师应该具备的能力和技术知识

成为Java架构师,需要驾驭大型互联网的架构与落地。主要技术包括前后端分离架构、负载均衡、微服务、数据库读写分离、缓存技术和消息队列等。

  • 前后端分离架构:用户请求首先会经过前端,然后进入后端。负载均衡器负责将请求分发到不同的服务器,以保证系统的高可用性。
  • 微服务架构:微服务是一个独立的系统,有自己的数据库,可以与其他服务进行通信。当请求量增大时,可以通过读写分离和缓存技术来减轻数据库的压力。
  • 数据库读写分离和缓存技术:为了提高效率,可以将一部分常用数据存储在缓存中,减少对数据库的访问。同时,数据库之间也可以通过读写分离来分散压力。
  • 消息队列:服务之间可以通过消息队列进行通信,也可以调用公共资源,如推送、短信和邮件等。

大型互联网系统的架构和实施过程

image-20240326151429901

  • 前端和后端交互的过程及其中涉及的关键技术

    • 当用户通过浏览器或移动设备等方式访问网站时,前端负责接收用户的请求并将它们传递给后台。
    • 为了提高可用性和可靠性,在此过程中通常会引入负载均衡器(如Nginx)将流量分配到多台服务器上。如果一台服务器出现问题,另一台备机会立即接管工作。
  • 深入解析Spring Cloud微服务体系结构及其组成部分

  • 微服务架构下,每项业务功能都被视为独立的服务单元。每个服务都有自己的数据库用于存储相关的数据信息,并支持读写分离策略以降低数据库压力。Redis缓存的作用,可以加速某些频繁使用的数据操作。

  • 分布式系统的设计原则和关键挑战

    • 分布式系统的核心概念——数据一致性和事务隔离性等问题。
    • 如何利用锁机制来解决这些问题,解决方案—分布式Session。
    • 应对高并发情况下的限流问题以及分布式日志记录的问题。
    • 其他重要的技术解决方案例如Distributed File System (DFS) 和 Elasticsearch Search Engine(ES)
    • 其中提到的Elasticsearch搜索引擎(ES)是一款开源的企业搜索平台,它可以处理大量的数据,并提供快速高效的查询结果。此外,该引擎还可以与其他工具和服务集成,如Apache Hadoop和Amazon Web Services等,从而进一步增强其性能和实用性。

Views: 66

RHEL 9 新特性及技术演示

image-20240325023617906

预览

  • RHEL 9 新特性与演示:

  • OpenSSH:新增禁止 root 的密码登录

  • Cockpit:RHEL 的 Web 控制台

  • DNF-3:软件安装方法

  • NetworkManager:网络管理的主要组件

  • Nftables:默认的用户空间防火墙

  • WireGuard:快速、安全的 VPN 隧道(技术预览

  • Podman & Skopeo:新一代容器运行时与镜像搬运工具

  • LVM-VDO:以逻辑卷形式使用 VDO

RHEL 9 新特性与演示 - OpenSSH

  • RHEL 9.0 中的 OpenSSH 套件版本:

    • openssh-8.7p1-8.el9.x86_64

    • openssh-clients-8.7p1-8.el9.x86_64

    • openssh-server-8.7p1-8.el9.x86_64

  • OpenSSH server:

    • 默认禁止 root 的密码登录

    • PermitRootLogin prohibit-password

image-20240325023639104

RHEL 9 新特性与演示 - Cockpit

  • Cockpit:RHEL 的 Web 控制台

  • Cockpit 自 RHEL 8 开始加入系统中作为 Web 图形化控制台,可帮助系统管理员快速查看系统状态,并对系统实施简易的运维工作。

  • RHEL 9 中的 Cockpit 增强:

    • 单个 Web 控制台中添加多个主机
    • KVM 虚拟机的管理:cockpit-machines 插件
    • 原生容器镜像与容器的管理:cockpit-podman 插件
    • PCP (Performance Co-Pilot) 的支持:cockpit-pcp 插件
  • 启动 cockpit 服务并设置开机自启动

$ sudo systemctl enable --now cockpit.socket
$ sudo systemctl start cockpit.service
$ sudo systemctl status cockpit.service
$ sudo ss -ntulp | grep 9090

image-20240325023721085

image-20240325023847847

$ sudo systemctl enable --now serial-getty@ttyS0.service:

虚拟机中启用串口控制台连接服务

RHEL 9 新特性与演示 - DNF-3

  • 自 RHEL 8 开始使用 dnf-3 用于安装 RPM 软件包。

  • RHEL 8 与 RHEL 9 中原有的 yum 安装工具依然可继续使用,yum 为 dnf-3 的软链接。

RHEL 9 的软件源管理:

  • 采用 BaseOS 与 AppStream 软件仓库分开管理

  • 支持模块流(module stream)可同时安装相同软件的不同版本

image-20240325024004211

RHEL 9 新特性与演示- NetworkManager

  • RHEL 7 中可使用 network.service 与 NetworkManager.service 两个服务用于网络管理。

  • RHEL 8 中默认使用 NetworkManager.service,而不再默认安装 network.service (network-scripts 软件包提供),但是可手动安装该组件。

  • 可通过 nmcli 命令行或 /etc/sysconfig/network-scripts/ifcfg-* 配置文件进行配置。

image-20240325024049070

  • RHEL 9 中不再提供 network-scripts 软件包,只能使用 NetworkManager.service。

  • RHEL 9 网络配置工具:

    • 命令行:nmcli

    • 图形化:nm-connection-editor、nmtui、nmtui-connect、nmtui-edit

  • RHEL 8 中兼容的 ifdown 与 ifup 命令在 RHEL 9 中已弃用。

  • RHEL 9 网口配置文件路径:

    • /etc/NetworkManager/system-connections/*.nmconnection

    • /etc/sysconfig/network-scripts/ifcfg-* 配置文件依然可用,但是建议使用上述配置文件!

image-20240325024122613

RHEL 9 新特性与演示 - NetworkManager

  • RHEL 8 与 RHEL 9 网口配置对比:

image-20240325024150855

image-20240325024349025

image-20240325024424400

RHEL 9 新特性与演示 - Nftables

  • RHEL 7 中使用 firewalld 服务作为用户配置防火墙规则的工具,iptables 作为实现防火墙规则的用户空间实现,因此通过 firewall-cmd 与 iptables 命令行均可实现防火墙的配置。

  • RHEL 8 中也使用 firewalld 服务,但其后端默认通过 nftables 实现防火墙规则,iptables 也可作为其后端,但为非默认配置可通过手动生效。

image-20240325024516788

  • firewalld、iptables 与 nftables 之间的联系与区别:

image-20240325024545693

image-20240325024605921

  • nftables 和 iptables 一样,由表(table)、链(chain)和规则(rule)组成,其中表包含链,链包含规则,规则是真正的 action。

  • 与 iptables 相比,nftables 主要有以下几个变化:

    • iptables 规则的布局是基于连续的大块内存的,即数组式布局;而 nftables 的规则采用链式布局(数组和链表的区别)。

    • iptables 大部分工作在内核态完成,如果要添加新功能,只能重新编译内核;而 nftables 的大部分工作是在用户态完成,添加新功能不需要改内核。

  • iptables 有内置的链,即使只需要一条链,其他的链也会跟着注册;而 nftables 不存在内置的链,可以按需注册。

  • 由于 iptables 内置了一个数据包计数器,所以即使这些内置的链是空的,也会带来性能损耗。

  • 简化了 IPv4/IPv6 双栈管理

  • 原生支持集合(IPset)、字典和映射

  • 示例:nftables 添加 SNAT 规则使 KVM 虚拟机访问外网

  • KVM 虚拟机与宿主机的网络状态:

image-20240325024702459

  • KVM 宿主机的 nft 命令示意:
$ sudo systemctl stop firewalld.service
$ sudo nft list ruleset
  • nftables 不存在内置链,关闭 firewalld 服务后,原先由 firewalld 创建的规则也将置空。
$ sudo nft add rule nat POSTROUTING ip saddr 172.25.10.0/24 oif bridge0 snat 192.168.110.200
  • 添加 nftables 的 SNAT 规则

$ sudo nft list table ip nat

  • 查看 nftables 的 NAT 表规则

image-20240325024741173

  • 若发现在 nftables 中无任何表,需从头创建 nat 表、POSTROUTING 基本链与 SNAT 规则,如下所示:

image-20240325024813953

RHEL 9 新特性与演示 - WireGuard

image-20240325024849706

RHEL 9 中 Linux kernel 为 5.14.0,kernel 已支持 WireGuard 功能,但还处于技术预览阶段,不推荐在生产环境中使用。

WireGuard 是由 Jason Donenfeld 等人用 C 语言编写的一个开源 VPN 协议,被视为下一代 VPN 协议,旨在解决许多困扰 IPSec/IKEv2、OpenVPN 或 L2TP 等其他 VPN 协议的问题。它与 Tinc 和 MeshBird 等现代 VPN 产品有一些相似之处,即加密技术先进、配置简单。从 2020 年 1 月开始,它已经并入了 Linux 内核的 5.6 版本,这意味着大多数 Linux 发行版的用户将拥有一个开箱即用的 WireGuard。

  • WireGuard 与其他 VPN 协议的性能测试对比:

image-20240325024906182

  • WireGuard 优点:

    • 配置精简,可直接使用默认值。

    • 只需最少的密钥管理工作,每个主机只需要 1 个公钥和 1 个私钥。

    • 就像普通的以太网接口一样,以 Linux 内核模块的形式运行,资源占用小。

    • 能够将部分流量或所有流量通过 VPN 传送到局域网内的任意主机。

    • 能够在网络故障恢复之后自动重连,戳到了其他 VPN 的痛处。

    • 比目前主流的 VPN 协议,连接速度要更快,延迟更低(见上图)。

    • 使用了更先进的加密技术,具有前向加密和抗降级攻击的能力。

    • 支持任何类型的二层网络通信,例如 ARP、DHCP 和 ICMP,而不仅仅是 TCP/HTTP。

    • 可以运行在主机中为容器之间提供通信,也可以运行在容器中为主机之间提供通信。

  • WireGuard 功能示例:

    • 由于 RHEL 9 中 kernel 已支持 WireGuard,因此只需安装 wireguard-tools 软件包即可。

    • 创建 client 端与 server 端的 vpn 通信。

Ubuntu 20.04.3 LTS:

WireGuard Server(Peer) 192.0.2.1/24, 192.168.110.208/24

image-20240325024941693

  • RHEL 9.0 Beta:

    • WireGuard Client(Peer) 192.0.2.2/24, 172.25.10.11/24, gateway: 192.168.110.1

image-20240325025014423

image-20240325025035557

RHEL 9 新特性与演示 - Podman & Skopeo

  • Podman 作为下一代容器运行时自 RHEL 8 开始正式在 RHEL 中替代 Docker,红帽官方不再使用 Docker,并且还提供两个强大的容器镜像操作工具 Skopeo 与 Buildah。

  • Podman 在 RHEL 9 中版本为 4.0.x,其功能得到增强且更加稳定。

  • Podman 4.0.x 在兼容 CNI 的基础上又新增原生的网络模式 Netavark 与 Aardvark-DNS 以更好的支持 IPv6、单容器接入多个容器网络与提供更好的性能。

  • Podman 的以下特性:

    • CLI 与 Docker 命令行相互兼容,可平滑过度。

    • 同样支持 Dockerfile 构建容器镜像,并且兼容 Containerfile。

    • 无守护进程运行,采用 fork/exec 模型运行容器。

    • 可运行 rootfull 与 rootless 容器

    • 可以实现容器随操作系统启动而启动

  • 虽然 Podman 可执行部分容器镜像相关操作,但在执行效率上与 Skopeo 相比相差甚远,因此,若对容器镜像执行操作可使用 Skopeo 与 Buildah 替换。

  • 有关 Podman 与 Skopeo 的详细内容可参阅最后的 Alberthua Blog 参考链接。

  • Podman & Skopeo 命令示例:

image-20240325025154265

image-20240325025220569

image-20240325025239938

  • Podman 各特征比较:

image-20240325025254980

RHEL 9 新特性与演示 - LVM-VDO

  • RHEL 9 在存储方面增加了较多特性,如已支持 exfat 文件系统、LVM-VDO 卷存储等。

  • 单纯使用 VDO 卷功能已从 RHEL 9 中移除。

  • 注:RHEL 9.0 Beta 中 kmod-kvdo 内核模块当前仍存在 bug,该模块所需的内核版本与系统当前版本无法匹配。

image-20240325025315776

参考链接

Views: 45

Linux 的 loginctl 命令详解

在使用podman创建无根用户服务时, 除了通过systemctl enable ...设置开机自启, 还需要在当前用户执行 loginctl enable-linger`, 那么这个命令到底是做什么的呢?

loginctl 并不是一个常用 Linux 命令,大部分时候都用不到它。这个命令隶属于 Systemd 的一部分,是 systemd 的登录管理器。

loginctl 命令格式

loginctl [选项...] {命令} [用户名...]

loginctl 命令示例

列出登录的用户:

loginctl list-users

列出登录的用户和会话:

loginctl list-sessions

列出当前用户的登录信息:

loginctl show-user $USER

Systemd 支持通过 --user 参数管理用户级别的后台程序,不过这存在一个问题,如果用户退出登录后,属于该用户的后台服务会被终止。如果希望用户退出后仍然保持服务的运行,可以使用下面的命令启用用户的逗留状态:

loginctl enable-linger $USER

loginctl 命令选项

缩写 完整名称 说明
--no-ask-password 在执行特权操作时不向用户索要密码。
-p --property= 在显示 session/user/seat 属性时, 仅显示此处指定的属性。 若未指定,则显示全部属性。 参数必须是属性名(例如"Sessions")。 可以多次使用此选项以指定多个属性。
--value 在使用 show 显示属性时, 仅显示属性值,而不显示属性名及等号。
-a --all 在显示 session/user/seat 属性时, 显示全部属性,无论这些属性是否已经被设置。
-l --full 在显示进程树的时候,不对超长行进行截断。
--kill-who= 与 kill-session 连用,指定杀死哪个进程。 leader 表示仅杀死会话的领导进程; all 表示杀死会话的所有进程。 默认值为 all
-s --signal= 与 kill-session 或 kill-user 连用, 指定向选中的进程发送什么信号。 必须设为众所周知的信号名称,例如 SIGTERM(默认值), SIGINT, SIGSTOP 之类
-n --lines= 与 user-status 或 session-status 连用, 控制显示多少行日志(从最新的一条日志开始计算)。 必须设为一个正整数,默认值是"10"。
-o --output= 与 user-status 或 session-status 连用, 控制日志的输出格式。 可用值参见 journalctl(1) 手册。默认为 "short"
-H --host= 操作指定的远程主机。可以仅指定一个主机名(hostname), 也可以使用 "username@hostname" 格式。 hostname 后面还可以加上容器名(以冒号分隔), 也就是形如 "hostname:container" 的格式, 以表示直接连接到指定主机的指定容器内。 操作将通过 SSH 协议进行,以确保安全。 可以通过 machinectl -H HOST 命令列出远程主机上的所有容器名称。
-M --machine= 在本地容器内执行操作。 必须明确指定容器的名称。
--no-pager 不将程序的输出内容管道(pipe)给分页程序。
--no-legend 不输出列标题, 也就是不在输出列表的头部和尾部显示字段的名称。
-h --help 显示简短的帮助信息并退出。
--version 显示简短的版本信息并退出。

loginctl 参数命令

loginctl 有三种参数命令,分别是:会话命令,用户命令和席位命令。

会话命令

名称 说明
list-sessions 列出当前所有的会话。这是默认命令。
session-status [ID...] 显示简洁的会话状态信息,后跟最近的日志。 如果指定了会话ID,那么仅显示指定的会话, 否则显示当前调用者的会话。 此命令主要用于输出人类易读的信息, 如果你想输出易于程序分析的信息, 那么应该使用 show-session 命令
show-session [ID...] 如果指定了会话ID,那么显示指定会话的各项属性值, 否则显示登陆管理器自身的各项属性值。 除非使用了 --all 选项, 否则空属性将被忽略。 还可以使用 --property= 选项指定仅显示个别属性。 此命令主要用于输出易于程序分析的信息, 如果你想输出人类易读的信息, 那么应该使用 session-status 命令。
activate [ID] 激活会话。 也就是将处于后台的会话切换到前台(如果同席位的另一个会话正处于前台)。 如果指定了会话ID, 那么将激活指定的会话, 否则将激活当前调用者的会话。
lock-session [ID...], unlock-session [ID...] 锁定/解锁会话(如果会话支持屏幕锁)。 如果指定了会话ID,那么将锁定/解锁指定的会话, 否则将锁定/解锁当前调用者的会话。
lock-sessionsunlock-sessions 锁定/解锁所有支持屏幕锁的会话。
terminate-session ID... 结束指定的会话。 也就是杀死指定会话的所有进程、释放所有与此会话相关的资源。
kill-session ID... 向指定的会话进程发送信号。 使用 --kill-who= 指定目标进程, 使用 --signal= 指定要发送的信号。

用户命令

名称 说明
list-users 列出当前登录的用户。
user-status [USER...] 显示简洁的已登录用户信息,后跟最近的日志。 如果指定了用户名或UID, 那么仅显示指定的用户, 否则显示当前调用者的用户。 此命令主要用于输出人类易读的信息, 如果你想输出易于程序分析的信息, 那么应该使用 show-user 命令。
show-user [USER...] 如果指定了用户名或UID,那么显示指定用户的各项属性值, 否则显示登陆管理器自身的各项属性值。 除非使用了 --all 选项, 否则空属性将被忽略。 还可以使用 --property= 选项来显示指定的属性。此命令主要用于输出易于程序分析的信息, 如果你想输出人类易读的信息, 那么应该使用 user-status 命令。
enable-linger [USER...], disable-linger [USER...] 启用/禁止用户逗留(相当于保持登录状态)。 如果指定了用户名或UID, 那么系统将会在启动时自动为这些用户派生出用户管理器, 并且在用户登出后继续保持运行。 这样就可以允许未登录的用户在后台运行持续时间很长的服务。 如果没有指定任何参数, 那么将作用于当前调用者的用户。
terminate-user USER... 结束指定用户的所有会话。 这将杀死该用户的所有会话中的所有进程, 同时释放与此用户有关的所有资源。
kill-user USER... 向指定用户的所有进程发送 --signal= 选项指定的信号。

席位命令

名称 说明
list-seats 列出当前本机上的所有可用席位
seat-status [NAME...] 显示简洁的席位信息,后跟最近的日志。 如果指定了席位名,那么仅显示指定的席位, 否则显示当前调用者会话所属的席位。 此命令主要用于输出人类易读的信息, 如果你想输出易于程序分析的信息, 那么应该使用 show-seat 命令。
show-seat [NAME...] 如果指定了席位名,那么显示指定席位的各项属性值, 否则显示登陆管理器自身的各项属性值。 除非使用了 --all 选项, 否则空属性将被忽略。 还可以使用 --property= 选项来显示指定的属性。 此命令主要用于输出易于程序分析的信息, 如果你想输出人类易读的信息, 那么应该使用 seat-status 命令。
attach NAME DEVICE... 将指定的设备(DEVICE)持久的连接到指定的席位(NAME)上。 设备可以用相对于 /sys 文件系统的设备路径表示。 要创建一个新席位,至少需要连接一个显卡。 席位名称必须以 "seat" 开头, 后跟 a–z, A–Z, 0–9, "-", "_" 字符。 要想从席位上删除一个设备, 可以将此设备连接到另一个席位, 或者使用 flush-devices 命令。
flush-devices 删除所有先前用 attach 命令连接的设备(同时也删除了所有先前用 attach 命令创建的席位)。 调用此命令之后,所有自动生成的席位将会被保留, 同时所有席位设备将会连接到自动生成的席位上。
terminate-seat NAME... 结束指定席位上的所有会话。 这将杀死指定席位上的所有会话进程, 同时释放与之关联的所有资源。

Views: 42

nginx配置中location匹配规则和优先级

location 介绍

location是Nginx中的块级指令(block directive),location指令的功能是用来匹配不同的url请求,进而对请求做不同的处理和响应,这其中较难理解的是多个location的匹配顺序,本文会作为重点来解释和说明。

开始之前先明确一些约定,我们输入的网址叫做请求URI,nginx用请求URI与location中配置的URI做匹配。

localtion 语法

location有两种匹配规则:

匹配URL类型,有四种参数可选,当然也可以不带参数。

location [ = | ~ | ~* | ^~ ] uri { … }

命名location,用@标识,类似于定于goto语句块。

location @name { … }

location匹配参数解释:

“=” ,精确匹配

内容要同表达式完全一致才匹配成功

location = /abc/ {
  .....
 }

# 只匹配http://abc.com/abc
#http://abc.com/abc [匹配成功]
#http://abc.com/abc/index [匹配失败]

“~”,执行正则匹配,区分大小写。

location ~ /Abc/ {
  .....
}
#http://abc.com/Abc/ [匹配成功]
#http://abc.com/abc/ [匹配失败]

“~*”,执行正则匹配,忽略大小写

location ~* /Abc/ {
  .....
}
# 则会忽略 uri 部分的大小写
#http://abc.com/Abc/ [匹配成功]
#http://abc.com/abc/ [匹配成功]

“^~”,表示普通字符串匹配上以后不再进行正则匹配。

location ^~ /index/ {
  .....
}
#以 /index/ 开头的请求,都会匹配上
#http://abc.com/index/index.page  [匹配成功]
#http://abc.com/error/error.page [匹配失败]

不加任何规则

不加任何规则时,默认是大小写敏感,前缀匹配,相当于加了“~”与“^~”

location /index/ {
  ......
}
#http://abc.com/index  [匹配成功]
#http://abc.com/index/index.page  [匹配成功]
#http://abc.com/test/index  [匹配失败]
#http://abc.com/Index  [匹配失败]

“@”,nginx内部跳转

location /index/ {
  error_page 404 @index_error;
}
location @index_error {
  .....
}

以 /index/ 开头的请求,如果链接的状态为 404。则会匹配到 @index_error 这条规则上。

location匹配顺序

= > ^~ > ~ | ~* > 最长前缀匹配 > /

序号越小优先级越高

location = # 精准匹配

location ^~ # 带参前缀匹配

location ~ # 正则匹配(区分大小写)

location ~* # 正则匹配(不区分大小写)

location /a # 普通前缀匹配,优先级低于带参数前缀匹配。

location / # 任何没有匹配成功的,都会匹配这里处理

举例

location = /  {
#规则A
}

location = /login {
#规则B
}

location ^~ /static/ {
#规则C
}

location ~ \.(gif|jpg|png|js|css)$ {
#规则D
}

location ~* \.png$ {
#规则E
}

location !~ \.xhtml$ {
#规则F
}

location !~* \.xhtml$ {
#规则G
}

location / {
#规则H
}

匹配结果:

访问根目录/, 比如http://localhost/ 将匹配规则A

访问 http://localhost/login 将匹配规则B,http://localhost/register 则匹配规则H

访问 http://localhost/static/a.html 将匹配规则C

访问 http://localhost/b.jpg 将匹配规则D和规则E,但是规则D顺序优先,规则E不起作用, 而 http://localhost/static/c.png 则优先匹配到 规则C

访问 http://localhost/a.PNG 则匹配规则E, 而不会匹配规则D,因为规则E不区分大小写。

访问 http://localhost/a.xhtml 不会匹配规则F和规则G,http://localhost/a.XHTML 不会匹配规则G,因为不区分大小写。规则F,规则G属于排除法,符合匹配规则但是不会匹配到。

访问 http://localhost/qll/id/1111 则最终匹配到规则H,因为以上规则都不匹配。

location URI结尾带不带 /

如果 URI 结构是 https://domain.com/ 的形式,尾部有没有 / 都不会造成重定向。因为浏览器在发起请求的时候,默认加上了 / 。虽然很多浏览器在地址栏里也不会显示 / 。这一点,可以访问百度验证一下。

使用curl命令访问的时候是不会默认加上/

如果 URI 的结构是 https://domain.com/some-dir/ 。尾部如果缺少 / 将导致重定向。因为约定,URL 尾部的 / 表示目录,没有 / 表示文件。所以访问 /some-dir/ 时,服务器会自动去该目录下找对应的默认文件。如果访问 /some-dir 的话,服务器会先去找 some-dir 文件,找不到的话会将 some-dir 当成目录,重定向到 /some-dir/ ,去该目录下找默认文件。

举个例子:

server {
    listen       9001;
    server_name  www.abc.com;

    location ~ /edu {
        proxy_pass http://127.0.0.1:8080;
     }
  }

我们访问www.abc.com:9001/edu,看下效果

访问 /edu 时,服务器首先去找edu文件,找不到则将edu当做目录,重定向到 /edu/,在该目录下找默认文件。

但是如果想这两种请求对应不同的处理,就要明确增加不带/结尾的location配置。例如:

location  /doc {
  proxy_pass http://www.doc123.com
}
location  /doc/ {
  proxy_pass http://www.doc456.com
}

Views: 21