一、概述

Spring Cloud for Alibaba,它是由一些阿里巴巴的开源组件和云产品组成的。Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

Spring Cloud Alibaba GitHub

二、主要功能

  • 服务限流降级: 默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现: 适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理: 支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力: 基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 阿里云对象存储: 阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度: 提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。

三、组件

  • Sentinel: 把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
  • Nacos: 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  • RocketMQ: 一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
  • Dubbo: Apache Dubbo™ 是一款高性能 Java RPC 框架。
  • Seata: 阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
  • Alibaba Cloud ACM: 一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。
  • Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
  • Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

四、创建项目

项目结构

image-20200608210949101

基于SpringBoot创建项目spring-cloud-alibaba

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <modules>
        <module>spring-cloud-alibaba-dependencies</module>
        <module>spring-cloud-alibaba-provider</module>
        <module>spring-cloud-alibaba-consumer</module>
        <module>spring-cloud-external-skywalking</module>
        <module>gateway</module>
    </modules>

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

    <groupId>com.qiang</groupId>
    <artifactId>spring-cloud-alibaba</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.qiang</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${project.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <profiles>
        <profile>
            <id>default</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <spring-javaformat.version>0.0.12</spring-javaformat.version>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>io.spring.javaformat</groupId>
                        <artifactId>spring-javaformat-maven-plugin</artifactId>
                        <version>${spring-javaformat.version}</version>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <includes>
                                <include>**/*Tests.java</include>
                            </includes>
                            <excludes>
                                <exclude>**/Abstract*.java</exclude>
                            </excludes>
                            <systemPropertyVariables>
                                <java.security.egd>file:/dev/./urandom</java.security.egd>
                                <java.awt.headless>true</java.awt.headless>
                            </systemPropertyVariables>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-enforcer-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>enforce-rules</id>
                                <goals>
                                    <goal>enforce</goal>
                                </goals>
                                <configuration>
                                    <rules>
                                        <bannedDependencies>
                                            <excludes>
                                                <exclude>commons-logging:*:*</exclude>
                                            </excludes>
                                            <searchTransitive>true</searchTransitive>
                                        </bannedDependencies>
                                    </rules>
                                    <fail>true</fail>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-install-plugin</artifactId>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                        <inherited>true</inherited>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

    <repositories>
        <repository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

创建统一依赖管理项目spring-cloud-alibaba-dependencies

pom.xml

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


    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <groupId>com.qiang</groupId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
        <spring-cloud-alibaba.verion>2.1.0.RELEASE</spring-cloud-alibaba.verion>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.verion}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <name>Spring Milestone</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshot</id>
            <name>Spring Snapshot</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

五、服务注册中心Nacos

概述

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以 “服务” 为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

服务发现和服务健康监测

Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生 SDK、OpenAPI、或一个 独立的 Agent TODO 注册 Service 后,服务消费者可以使用 DNS TODO 或 HTTP&API 查找和发现服务。

Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP) 和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测 2 种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。

动态配置服务

动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

Nacos 提供了一个简洁易用的 UI 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

服务及其元数据管理

Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

Nacos 地图

img

  • 特性大图:要从功能特性,非功能特性,全面介绍我们要解的问题域的特性诉求。
  • 架构大图:通过清晰架构,让您快速进入 Nacos 世界。
  • 业务大图:利用当前特性可以支持的业务场景,及其最佳实践。
  • 生态大图:系统梳理 Nacos 和主流技术生态的关系。
  • 优势大图:展示 Nacos 核心竞争力。
  • 战略大图:要从战略到战术层面讲 Nacos 的宏观优势。

Nacos 架构

image.png

逻辑架构及其组件介绍

img

  • 服务管理: 实现服务 CRUD,域名 CRUD,服务健康状态检查,服务权重管理等功能
  • 配置管理: 实现配置管 CRUD,版本管理,灰度管理,监听管理,推送轨迹,聚合数据等功能
  • 元数据管理: 提供元数据 CURD 和打标能力
  • 插件机制: 实现三个模块可分可合能力,实现扩展点 SPI 机制
  • 事件机制: 实现异步化事件通知,sdk 数据变化异步通知等逻辑
  • 日志模块: 管理日志分类,日志级别,日志可移植性(尤其避免冲突),日志格式,异常码 + 帮助文档
  • 回调机制: sdk 通知数据,通过统一的模式回调用户处理。接口和数据结构需要具备可扩展性
  • 寻址模式: 解决 ip,域名,nameserver、广播等多种寻址模式,需要可扩展
  • 推送通道: 解决 server 与存储、server 间、server 与 sdk 间推送性能问题
  • 容量管理: 管理每个租户,分组下的容量,防止存储被写爆,影响服务可用性
  • 流量管理: 按照租户,分组等多个维度对请求频率,长链接个数,报文大小,请求流控进行控制
  • 缓存机制: 容灾目录,本地缓存,server 缓存机制。容灾目录使用需要工具
  • 启动模式: 按照单机模式,配置模式,服务模式,dns 模式,或者 all 模式,启动不同的程序 + UI
  • 一致性协议: 解决不同数据,不同一致性要求情况下,不同一致性机制
  • 存储模块: 解决数据持久化、非持久化存储,解决数据分片问题
  • Nameserver: 解决 namespace 到 clusterid 的路由问题,解决用户环境与 nacos 物理环境映射问题
  • CMDB: 解决元数据存储,与三方 cmdb 系统对接问题,解决应用,人,资源关系
  • Metrics: 暴露标准 metrics 数据,方便与三方监控系统打通
  • Trace: 暴露标准 trace,方便与 SLA 系统打通,日志白平化,推送轨迹等能力,并且可以和计量计费系统打通
  • 接入管理: 相当于阿里云开通服务,分配身份、容量、权限过程
  • 用户管理: 解决用户管理,登录,sso 等问题
  • 权限管理: 解决身份识别,访问控制,角色管理等问题
  • 审计系统: 扩展接口方便与不同公司审计系统打通
  • 通知系统: 核心数据变更,或者操作,方便通过 SMS 系统打通,通知到对应人数据变更
  • OpenAPI: 暴露标准 Rest 风格 HTTP 接口,简单易用,方便多语言集成
  • Console: 易用控制台,做服务管理、配置管理等操作
  • SDK: 多语言 sdk
  • Agent: dns-f 类似模式,或者与 mesh 等方案集成
  • CLI: 命令行对产品进行轻量化管理,像 git 一样好用

docker-compose部署

第一种

git clone https://github.com/nacos-group/nacos-docker.git
cd nacos-docker
docker-compose -f example/standalone-mysql-5.7.yaml up -d
docker-compose -f example/standalone-mysql-5.7.yaml logs -f

第二种

version: "3"
services:
  nacos:
    image: nacos/nacos-server:latest
    container_name: nacos
    volumes:
      - ./logs/:/home/nacos/logs
      - ./conf/custom.properties:/home/nacos/init.d/custom.properties
    ports:
      - "8848:8848"
      - "9555:9555"
    privileged: true
    restart: always
    environment:
      - "PREFER_HOST_MODE=hostname"
      - "MODE=standalone"
      - "SPRING_DATASOURCE_PLATFORM=mysql"
      - "MYSQL_SERVICE_HOST=192.168.17.199"
      - "MYSQL_SERVICE_DB_NAME=nacos"
      - "MYSQL_SERVICE_PORT=3306"
      - "MYSQL_SERVICE_USER=root"
      - "MYSQL_SERVICE_PASSWORD=123456"

http://192.168.17.101:8848/nacos

账号密码:nacos/nacos

image-20200608225848949

六、服务提供者Provider

服务提供者: 是指提供可复用和可调用服务的应用方

创建项目:spring-cloud-alibaba-provider

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-alibaba</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-alibaba-provider</artifactId>
    <packaging>jar</packaging>

    <dependencies>

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

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 分布式配置管理中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.qiang.ProviderApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

配置文件:application.yml

# 配置端口
server:
  port: 8090
spring:
  application:
    # 服务名
    name: provider
  cloud:
    nacos:
      discovery:
        # 服务注册中心
        server-addr: 192.168.17.101:8848
management:
  # 端点检查(健康检查)
  endpoints:
    web:
      exposure:
        include: "*"

ProviderApplication.java加上@EnableDiscoveryClient注解

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

编写测试类EchoController

package com.qiang.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EchoController {

    @GetMapping(value = "/echo/{string}")
    public String echo(@PathVariable String string) {
        return "Hello Nacos Provider " + string;
    }
}

验证是否成功

http://192.168.17.101:8848/nacos/

image-20200610231705308

此时发现服务已经注册上去了,访问http://localhost:8090/echo/hi

image-20200610231801051

健康检查

以指定方式检查服务下挂载的实例 (Instance) 的健康度,从而确认该实例 (Instance) 是否能提供服务。根据检查结果,实例 (Instance) 会被判断为健康或不健康。对服务发起解析请求时,不健康的实例 (Instance) 不会返回给客户端。

http://localhost:8090/actuator/nacos-discovery

image-20200610231919602

七、服务消费者Consumer

服务消费方: 是指会发起对某个服务调用的应用方

创建项目:spring-cloud-alibaba-consumer

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-alibaba</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-alibaba-consumer</artifactId>
    <packaging>jar</packaging>

    <dependencies>

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

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- feign依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- 分布式配置管理中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- Sentinel分布式系统的流量防卫兵 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.qiang.ConsumerApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

配置文件:application.yml

spring:
  application:
    # 服务名
    name: consumer
  cloud:
    nacos:
      discovery:
        # 服务注册中心
        server-addr: 192.168.17.101:8848

server:
  # 服务端口
  port: 8080

management:
  # 端点检查(健康检查)
  endpoints:
    web:
      exposure:
        include: "*"

ConsumerApplication.java加上@EnableDiscoveryClient注解

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

创建配置类注入RestTemplate

ConsumerConfiguration.java

package com.qiang.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConsumerConfiguration {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

编写测试类TestController

package com.qiang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class TestController {

    private final RestTemplate restTemplate;

    @Autowired
    public TestController (RestTemplate restTemplate){
        this.restTemplate=restTemplate;
    }

    @GetMapping(value = "/echo/{str}")
    public String echo(@PathVariable String str) {
        // 使用服务名请求服务提供者
        return restTemplate.getForObject("http://provider/echo/" + str, String.class);
    }


}

验证是否成功

http://192.168.17.101:8848/nacos/image-20200610233642655

此时发现服务已经注册上去了,访问http://localhost:8080/echo/hi

image-20200610233618898

健康检查

http://localhost:8080/actuator/nacos-discovery

image-20200610234144572

八、负载均衡Feign

Feign 是一个声明式的伪 HTTP 客户端,它使得写 HTTP 客户端变得更简单。使用 Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用 Feign 注解和 JAX-RS 注解。Feign 支持可插拔的编码器和解码器。Feign 默认集成了 Ribbon,Nacos 也很好的兼容了 Feign,默认实现了负载均衡的效果

  • Feign 采用的是基于接口的注解
  • Feign 整合了 Ribbon

在spring-cloud-alibaba-consumer项目加入依赖

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

在ConsumerApplication.java加上@EnableFeignClients注解

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

在Service层加入注解@FeignClient(“服务名”)

EchoService.java

package com.qiang.service;

import com.qiang.service.impl.EchoServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "provider", fallback = EchoServiceFallback.class)
public interface EchoService {
    @GetMapping(value = "/echo/{string}")
    String echo(@PathVariable("string") String string);

    @GetMapping(value = "/lb")
    String lb();
}

创建测试类TestEchoController

package com.qiang.controller;


import com.qiang.service.EchoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RefreshScope
@RestController
public class TestEchoController {

    @Autowired
    private EchoService echoService;

    @Value("${user.name}")
    private String username;

    @GetMapping(value = "/config")
    public String config() {
        return echoService.echo(username);
    }

    @GetMapping(value = "/feign/echo/{str}")
    public String echo(@PathVariable String str) {
        return echoService.echo(str);
    }

    @GetMapping(value = "/lb")
    public String lb() {
        return echoService.lb();
    }
}

验证是否成功

http://localhost:8080/feign/echo/hi

image-20200610235309804

修改application.yaml配置文件端口号,启动多个实例

image-20200610235745276

image-20200610235812100

服务列表consumer有两个

image-20200610235919978

在provider项目中的EchoController添加方法

@Value("${server.port}")
    private String port;

    @GetMapping(value = "/lb")
    public String lb() {
        return "Hello Nacos Provider i am from port: " + port;
    }

在consumer项目中的的EchoService添加方法

@GetMapping(value = "/lb")
    String lb();

在consumer项目中的的TestEchoController添加方法

@GetMapping(value = "/lb")
    public String lb() {
        return echoService.lb();
    }

验证是否成功

http://localhost:8080/lb

默认使用轮询负载均衡策略

image-20200611000559011

image-20200611000607472

常见负载均衡策略
负载主机可以提供很多种负载均衡方法,也就是我们常说的调度方法或算法

轮循
Round Robin: 这种方法会将收到的请求循环分配到服务器集群中的每台机器,即有效服务器。如果使用这种方式,所有的标记进入虚拟服务的服务器应该有相近的资源容量 以及负载形同的应用程序。如果所有的服务器有相同或者相近的性能那么选择这种方式会使服务器负载形同。基于这个前提,轮循调度是一个简单而有效的分配请求 的方式。然而对于服务器不同的情况,选择这种方式就意味着能力比较弱的服务器也会在下一轮循环中接受轮循,即使这个服务器已经不能再处理当前这个请求了。 这可能导致能力较弱的服务器超载。

加权轮循
Weighted Round Robin: 这种算法解决了简单轮循调度算法的缺点:传入的请求按顺序被分配到集群中服务器,但是会考虑提前为每台服务器分配的权重。管理员只是简单的通过服务 器的处理能力来定义各台服务器的权重。例如,能力最强的服务器 A 给的权重是 100,同时能力最低的服务器给的权重是 50。这意味着在服务器 B 接收到第一个 请求之前前,服务器 A 会连续的接受到 2 个请求,以此类推。

最少连接数
Least Connection: 以上两种方法都没有考虑的是系统不能识别在给定的时间里保持了多少连接。因此可能发生,服务器 B 服务器收到的连接比服务器 A 少但是它已经超载,因为 服务器 B 上的用户打开连接持续的时间更长。这就是说连接数即服务器的负载是累加的。这种潜在的问题可以通过 “最少连接数” 算法来避免:传入的请求是根据每 台服务器当前所打开的连接数来分配的。即活跃连接数最少的服务器会自动接收下一个传入的请求。接本上和简单轮询的原则相同:所有拥有虚拟服务的服务器资源 容量应该相近。值得注意的是,在流量率低的配置环境中,各服务器的流量并不是相同的,会优先考虑第一台服务器。这是因为,如果所有的服务器是相同的,那么 第一个服务器优先,直到第一台服务器有连续的活跃流量,否则总是会优先选择第一台服务器。

最少连接数慢启动时间
Least Connection Slow Start Time: 对最少连接数和带权重的最小连接数调度方法来说,当一个服务器刚加入线上环境是,可以为其配置一个时间段,在这段时间内连接数是有限制的而且是缓慢 增加的。这为服务器提供了一个‘过渡时间’以保证这个服务器不会因为刚启动后因为分配的连接数过多而超载。这个值在 L7 配置界面设置。

加权最少连接
Weighted Least Connection: 如果服务器的资源容量各不相同,那么 “加权最少连接” 方法更合适:由管理员根据服务器情况定制的权重所决定的活跃连接数一般提供了一种对服务器非常 平衡的利用,因为他它借鉴了最少连接和权重两者的优势。通常,这是一个非常公平的分配方式,因为它使用了连接数和服务器权重比例;集群中比例最低的服务器 自动接收下一个请求。但是请注意,在低流量情况中使用这种方法时,请参考 “最小连接数” 方法中的注意事项。

基于代理的自适应负载均衡
Agent Based Adaptive Balancing: 除了上述方法之外,负载主机包含一个自适用逻辑用来定时监测服务器状态和该服务器的权重。对于非常强大的 “基于代理的自适应负载均衡” 方法来说,负 载主机以这种方式来定时检测所有服务器负载情况:每台服务器都必须提供一个包含文件,这个文件包含一个 0~99 的数字用来标明改服务器的实际负载情况 (0 = 空前,99 = 超载,101 = 失败,102 = 管理员禁用),而服务器同构 http get 方法来获取这个文件;同时对集群中服务器来说,以二进制文件形式提供自身负载情况也是该服务器工作之一,然而,并没有限制服务器如何计算自身的负载 情况。根据服务器整体负载情况,有两种策略可以选择:在常规的操作中,调度算法通过收集的服务器负载值和分配给该服务器的连接数的比例计算出一个权重比 例。因此,如果一个服务器负载过大,权重会通过系统透明的作重新调整。和加权轮循调度方法一样,不正确的分配可以被记录下来使得可以有效的为不同服务器分 配不同的权重。然而,在流量非常低的环境下,服务器报上来的负载值将不能建立一个有代表性的样本;那么基于这些值来分配负载的话将导致失控以及指令震荡。 因此,在这种情况下更合理的做法是基于静态的权重比来计算负载分配。当所有服务器的负载低于管理员定义的下限时,负载主机就会自动切换为加权轮循方式来分 配请求;如果负载大于管理员定义的下限,那么负载主机又会切换回自适应方式。

固定权重
Fixed Weighted: 最高权重只有在其他服务器的权重值都很低时才使用。然而,如果最高权重的服务器下降,则下一个最高优先级的服务器将为客户端服务。这种方式中每个真实服务器的权重需要基于服务器优先级来配置。

加权响应
Weighted Response: 流量的调度是通过加权轮循方式。加权轮循中所使用的权重是根据服务器有效性检测的响应时间来计算。每个有效性检测都会被计时,用来标记它响应成功花 了多长时间。但是需要注意的是,这种方式假定服务器心跳检测是基于机器的快慢,但是这种假设也许不总是能够成立。所有服务器在虚拟服务上的响应时间的总和 加在一起,通过这个值来计算单个服务物理服务器的权重;这个权重值大约每 15 秒计算一次。

源 IP 哈希
Source IP Hash: 这种方式通过生成请求源 IP 的哈希值,并通过这个哈希值来找到正确的真实服务器。这意味着对于同一主机来说他对应的服务器总是相同。使用这种方式,你不需要保存任何源 IP。但是需要注意,这种方式可能导致服务器负载不平衡。

九、分布式配置中心Config

概述
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件

什么是 Nacos Config
Nacos 提供用于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Spring Cloud Alibaba Nacos Config,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外部属性配置。

Spring Cloud Alibaba Nacos Config 是 Spring Cloud Config Server 和 Client 的替代方案,客户端和服务器上的概念与 Spring Environment 和 PropertySource 有着一致的抽象,在特殊的 bootstrap 阶段,配置被加载到 Spring 环境中。当应用程序通过部署管道从开发到测试再到生产时,您可以管理这些环境之间的配置,并确保应用程序具有迁移时需要运行的所有内容。

接入配置中心

<!-- 分布式配置管理中心 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

在TestEchoController加入@RefreshScope打开动态刷新功能

测试动态配置更新@Value(“${user.name}”)

package com.qiang.controller;


import com.qiang.service.EchoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RefreshScope
@RestController
public class TestEchoController {

    @Autowired
    private EchoService echoService;

    @Value("${user.name}")
    private String username;

    @GetMapping(value = "/config")
    public String config() {
        return echoService.echo(username);
    }

    @GetMapping(value = "/feign/echo/{str}")
    public String echo(@PathVariable String str) {
        return echoService.echo(str);
    }

    @GetMapping(value = "/lb")
    public String lb() {
        return echoService.lb();
    }
}

在nacos创建配置文件

Data ID 的默认扩展名为 .properties ,希望使用 YAML 配置,此处必须指明是 .yaml

image-20200622102350982

spring:
  application:
    # 服务名
    name: service-consumer
  cloud:
    nacos:
      discovery:
        # 服务注册中心
        server-addr: 192.168.17.101:8848
    # 熔断限流
    sentinel:
      transport:
        # 与sentinel通信的端口
        port: 3333
        dashboard: 192.168.17.101:8081

# 开启 Feign 对 Sentinel 的支持
feign:
  sentinel:
    enabled: true

server:
  # 服务端口
  port: 8080

management:
  # 端点检查(健康检查)
  endpoints:
    web:
      exposure:
        include: "*"

user:
  name: "傻狗"

创建名为 bootstrap.properties 的配置文件并删除之前创建的 application.yml 配置文件

spring.application.name=service-consumer-config
spring.cloud.nacos.config.server-addr=192.168.17.101:8848
spring.cloud.nacos.config.file-extension=yaml

image-20200622102551133

启动服务并访问http://localhost:8080/config

image-20200622102917208

更新配置文件

image-20200622103102066

再次访问http://localhost:8080/config

image-20200622103128502

可以使用 spring.cloud.nacos.config.refresh.enabled=false 来关闭动态刷新

端点Endpoint信息查看

添加依赖

<-- 端点信息查看 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

访问 nacos-discovery:http://localhost:8080/actuator/nacos-discovery

image-20200622103542440

访问 nacos-config:http://localhost:8080/actuator/nacos-config

image-20200622103707938

Sources 表示此客户端从哪些 Nacos Config 配置项中获取了信息,RefreshHistory 表示动态刷新的历史记录,最多保存20条,NacosConfigProperties 则为 Nacos Config Starter 本身的配置。

多环境配置

我们在做项目开发的时候,生产环境和测试环境的一些配置可能会不一样,有时候一些功能也可能会不一样,所以我们可能会在上线的时候手工修改这些配置信息。Spring 为我们提供了 Spring Boot Profile 这个功能(Maven 也为我们提供了 Maven Profile)。我们只需要在启动的时候添加一个虚拟机参数,激活自己环境所要用的 Profile 就可以了。

操作起来很简单,只需要为不同的环境编写专门的配置文件,如:application-dev.yml、application-prod.yml, 启动项目时只需要增加一个命令参数 --spring.profiles.active= 环境配置即可。

java -jar 1.0.0-SNAPSHOT.jar --spring.profiles.active=prod

控制台发布配置时不要加注释,否则打成 Jar 包后运行会报无法解析配置文件的错误。

发布配置文件

service-provider-config.yaml

spring:
  application:
    # 服务名
    name: service-provider
  cloud:
    nacos:
      discovery:
        # 服务注册中心
        server-addr: 192.168.17.101:8848

server:
  # 服务端口
  port: 8070

management:
  # 端点检查(健康检查)
  endpoints:
    web:
      exposure:
        include: "*"

service-provider-config-prod.yaml

spring:
  application:
    # 服务名
    name: service-provider
  cloud:
    nacos:
      discovery:
        # 服务注册中心
        server-addr: 192.168.17.101:8848

server:
  # 修改了上面的端口号,区分配置的不同
  port: 8071

management:
  # 端点检查(健康检查)
  endpoints:
    web:
      exposure:
        include: "*"

创建名为 bootstrap.properties 的配置文件

spring.application.name=service-provider-config
spring.cloud.nacos.config.server-addr=192.168.17.101:8848
spring.cloud.nacos.config.file-extension=yaml

创建名为 bootstrap-prod.properties 的配置文件

spring.profiles.active=prod
spring.application.name=service-provider-config
spring.cloud.nacos.config.server-addr=192.168.17.101:8848
spring.cloud.nacos.config.file-extension=yaml

image-20200622105511977

启动服务

image-20200622105643317

配置成功

image-20200622105757621

十、熔断限流Sentinel

概述

Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。另外,鉴权在生产环境中也必不可少。这里,我们将会详细讲述如何通过简单的步骤就可以使用这些功能。Sentinel 控制台最少应该包含如下功能:

  1. 查看机器列表以及健康情况: 收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
  2. 监控 (单机和集群聚合): 通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
  3. 规则管理和推送: 统一管理推送规则。
    鉴权: 生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。

什么是服务雪崩

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 HTTP/RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + LoadBalanceClient 和 Feign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。为了解决这个问题,业界提出了 熔断器模型。

阿里巴巴开源了 Sentinel 组件,实现了熔断器模式,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的。较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值熔断器将会被打开。熔断器打开后,为了避免连锁故障,通过 fallback 方法可以直接返回一个固定值。

什么是 Sentinel

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 的特征

  1. 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷(对于突然到来的大量请求,您可以配置流控规则,以稳定的速度逐步处理这些请求,从而避免流量突刺造成系统负载过高)、集群流量控制、实时熔断下游不可用应用等。
  2. 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  3. 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架 / 库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel
  4. 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 的主要特性

img

Sentinel 的开源生态

img

Sentinel 的组成

  1. 核心库(Java 客户端): 不依赖任何框架 / 库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  2. 控制台(Dashboard): 基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

jar下载

https://github.com/alibaba/Sentinel/releases

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

docker-compose

version: '3.0'
services:
  sentinel:
    image: bladex/sentinel-dashboard:latest
    container_name: sentinel
    restart: always
    privileged: true
    ports:
      - '8858:8858'

账号:sentinel

密码:sentinel

访问:http://192.168.17.101:8858/

image-20200622112533608

在项目中引入sentinel

<-- 熔断限流 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  application:
    # 服务名
    name: service-consumer
  cloud:
    nacos:
      discovery:
        # 服务注册中心
        server-addr: 192.168.17.101:8848
    # 熔断限流
    sentinel:
      transport:
        # 与sentinel通信的端口
        port: 3333
        dashboard: 192.168.17.101:8081

# 开启 Feign 对 Sentinel 的支持
feign:
  sentinel:
    enabled: true

server:
  # 服务端口
  port: 8080

management:
  # 端点检查(健康检查)
  endpoints:
    web:
      exposure:
        include: "*"

user:
  name: "大傻狗"

配置熔断类

编写一个 Feign 接口的实现类并增加 @Component 注解。

package com.qiang.service.impl;

import com.qiang.service.EchoService;
import org.springframework.stereotype.Component;

@Component
public class EchoServiceFallback implements EchoService {
    @Override
    public String echo(String string) {
        return "echo请求失败,请检查你的网络!";
    }

    @Override
    public String lb() {
        return "lb请求失败,请检查你的网络!";
    }
}

修改 Feign 接口

在 @FeignClient 接口上增加 fallback 属性指定熔断类即可。

package com.qiang.service;

import com.qiang.service.impl.EchoServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "service-provider", fallback = EchoServiceFallback.class)
public interface EchoService {
    @GetMapping(value = "/echo/{string}")
    String echo(@PathVariable("string") String string);

    @GetMapping(value = "/lb")
    String lb();
}

测试熔断类

必须至少请求过一次才能在 Sentinel 控制台看到对应的服务。

http://localhost:8080/feign/echo/hi

image-20200702203050049

http://192.168.17.101:8858/#/dashboard/metric/sentinel-dashboard

image-20200702203124084

十一、链路追踪

微服务架构是通过业务来划分服务的,使用 REST 调用。对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂。随着服务的越来越多,对调用链的分析会越来越复杂。

什么是 SkyWalking

目前主要的一些 APM 工具有: Cat、Zipkin、Pinpoint、SkyWalking;Apache SkyWalking 是观察性分析平台和应用性能管理系统。提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。

img

  • Skywalking Agent: 使用 JavaAgent 做字节码植入,无侵入式的收集,并通过 HTTP 或者 gRPC 方式发送数据到 SkyWalking Collector。
  • SkyWalking Collector: 链路数据收集器,对 agent 传过来的数据进行整合分析处理并落入相关的数据存储中。
  • Storage: SkyWalking 的存储,时间更迭,SW 已经开发迭代到了 6.x 版本,在 6.x 版本中支持以 ElasticSearch (支持 6.x)、Mysql、TiDB、H2、作为存储介质进行数据存储。
  • UI: Web 可视化平台,用来展示落地的数据。

SkyWalking 功能特性

  • 多种监控手段,语言探针和服务网格 (Service Mesh)
  • 多语言自动探针,Java,.NET Core 和 Node.JS
  • 轻量高效,不需要大数据
  • 模块化,UI、存储、集群管理多种机制可选
  • 支持告警
  • 优秀的可视化方案

使用Elasticsearch存储

SkyWalking 存储方案有多种,官方推荐的方案是 ElasticSearch ,所以我们需要先安装 ElasticSearch。

mkdir data
chmod 777 data
version: '3'
services:
  elasticsearch:
    image: elasticsearch:6.4.3
    container_name: elasticsearch
    restart: always
    privileged: true
    volumes:
      - ./data:/usr/share/elasticsearch/data
    environment:
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - "cluster.name=elasticsearch"
      - "TZ=Asia/Shanghai"
    ports:
      - "9200:9200"
      - "9300:9300"

验证是否安装成功

http://192.168.17.101:9200/

image-20200809034504945

安装skywalking

version: "3.0"
services:
  skywalking:
    image: apache/skywalking-oap-server:8.0.1-es6
    ports:
      - "11800:11800"
      - "12800:12800"
    container_name: skywalking
    restart: always
    privileged: true
    environment:
      - "SW_STORAGE=elasticsearch"
      - "SW_STORAGE_ES_CLUSTER_NODES=192.168.17.101:9200"
      - "TZ=Asia/Shanghai"
  skywalking-ui:
    image: apache/skywalking-ui:8.0.1
    container_name: skywalking-ui
    restart: always
    privileged: true
    depends_on:
      - skywalking
    ports:
      - "8080:8080"
    environment:
      - "SW_OAP_ADDRESS=192.168.17.101:12800"
      - "TZ=Asia/Shanghai"

验证是否安装成功

http://192.168.17.101:8080/

默认的用户名密码为:admin/admin,登录成功后,效果如下图

image-20200809034624466

客户端接入

官方已经为我们准备好了编译过的服务端版本,下载地址为 http://skywalking.apache.org/downloads/,这里下载 8.0.1 的版本,跟服务的的对应。

image-20200809213310080

image-20200809054716827

Java Agent 服务器探针

探针为我们达到监控的目的,按照实际情况我们需要实现三种部署方式。

  • IDEA 部署探针(这里演示)
  • Java 启动方式部署探针(我们是 Spring Boot 应用程序,需要使用 java -jar 的方式启动应用)
  • Docker 启动方式部署探针

探针文件在 apache-skywalking-apm-bin-es7\agent 目录下

image-20200809042214208

新建工程spring-cloud-external-skywalking

把agent目录拷贝过来

image-20200809043502674

修改启动参数

-javaagent:D:\File\IDEAProject\SpringCloudAlibaba\spring-cloud-alibaba\spring-cloud-external-skywalking\agent\skywalking-agent.jar
-Dskywalking.agent.service_name=provider
-Dskywalking.collector.backend_service=192.168.17.101:11800
  • -javaagent:用于指定探针路径
  • -Dskywalking.agent.service_name:用于重写 agent/config/agent.config 配置文件中的服务名
  • -Dskywalking.collector.backend_service:用于重写 agent/config/agent.config 配置文件中的服务地址

Java启动方式

java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=nacos-provider -Dskywalking.collector.backend_service=localhost:11800 -jar yourApp.jar

image-20200809051520009

查看日志已经成功加载探针

image-20200809051918864

访问一次服务http://localhost:8070/echo/skywalking后刷新SkyWalking Web UI服务才会检测到

image-20200809213749052

image-20200809214330817

SkyWalking Trace 监控

SkyWalking 通过业务调用监控进行依赖分析,提供给我们了服务之间的服务调用拓扑关系、以及针对每个 Endpoint 的 Trace 记录。

点击追踪

image-20200809214733453

image-20200809214751485

上图展示了一次正常的响应,总响应时间为 2781ms 共有一个 Span(基本工作单元,表示一次完整的请求,包含响应,即请求并响应)

Span /echo/{string} 说明如下:

  • Duration:响应时间 2781毫秒
  • component:组件类型为 Feign
  • url:请求地址
  • http.method:请求类型

Maven Assembly 插件协助打包

什么是 Assembly Plugin

Assembly 插件目的是提供一个把工程依赖元素、模块、网站文档等其他文件存放到单个归档文件里。

Assembly 支持的归档文件类型

  • zip
  • tar.gz
  • tar.bz2
  • jar
  • dir
  • war

使用步骤

此处以将 SkyWalking 探针打包为 tar.gz 为例,为后期持续集成时构建 Docker 镜像做好准备

pom.xml 中增加插件配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-alibaba</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-external-skywalking</artifactId>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <finalName>skywalking</finalName>
                            <descriptors>
                                <descriptor>src/main/resources/assembly.xml</descriptor>
                            </descriptors>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

创建 src/main/resources/assembly.xml 配置文件

<assembly>
    <id>1.0.0</id>
    <formats>
        <format>tar.gz</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <useProjectArtifact>false</useProjectArtifact>
            <outputDirectory>lib</outputDirectory>
            <scope>runtime</scope>
        </dependencySet>
    </dependencySets>
    <fileSets>
        <fileSet>
            <!-- 要打包的目录 -->
            <directory>agent</directory>
            <!-- 解压后的目录 -->
            <outputDirectory>agent</outputDirectory>
        </fileSet>
    </fileSets>
</assembly>

打包

mvn clean package
mvn clean install
  • package:会在 target 目录下创建名为 skywalking-1.0.0.tar.gz 的压缩包
  • install:会在本地仓库目录下创建名为 spring-cloud-external-skywalking-1.0.0-SNAPSHOT-1.0.0.tar.gz 的压缩包

十二、网关Gateway

Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,Spring Cloud Gateway 旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式。Spring Cloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控 / 埋点,和限流等。

img

  • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
  • 动态路由
  • Predicates 和 Filters 作用于特定路由
  • 集成 Hystrix 断路器
  • 集成 Spring Cloud DiscoveryClient
  • 易于编写的 Predicates 和 Filters
  • 限流
  • 路径重写

工作流程

img

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。

创建Gateway项目

pom.xml

主要增加了 org.springframework.cloud:spring-cloud-starter-gateway 依赖

<?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>spring-cloud-alibaba</artifactId>
        <groupId>com.qiang</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway</artifactId>
    <packaging>jar</packaging>

    <dependencies>

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

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 分布式配置管理中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.qiang.GatewayApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • Spring Cloud Gateway 不使用 Web 作为服务器,而是 使用 WebFlux 作为服务器 ,Gateway 项目已经依赖了 starter-webflux,所以这里千万不要依赖 starter-web
  • 由于过滤器等功能依然需要 Servlet 支持,故这里还需要依赖 javax.servlet:javax.servlet-api

配置文件

bootstrap.properties

spring.application.name=service-gateway-config
spring.cloud.nacos.config.server-addr=192.168.17.101:8848
spring.cloud.nacos.config.file-extension=yaml
spring.main.allow-bean-definition-overriding=true

service-gateway-config.yaml

spring:
  application:
    name: service-gateway-config
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.17.101:8848
    # 路由网关配置
    gateway:
      # 设置与服务注册发现组件结合,这样可以采用服务名的路由策略
      discovery:
        locator:
          enabled: true
      # 配置路由规则
      routes:
        # 采用自定义路由 ID(有固定用法,不同的 id 有不同的功能,详见:https://cloud.spring.io/spring-cloud-gateway/2.0.x/single/spring-cloud-gateway.html#gateway-route-filters)
        - id: SERVICE-PROVIDER
          # 采用 LoadBalanceClient 方式请求,以 lb:// 开头,后面的是注册在 Nacos 上的服务名
          uri: lb://service-provider
          # Predicate 翻译过来是“谓词”的意思,必须,主要作用是匹配用户的请求,有很多种用法
          predicates:
            # 路径匹配,以 api 开头,直接配置是不生效的,看 filters 配置
            - Path=/api/provider/**
          filters:
            # 前缀过滤,默认配置下,我们的请求路径是 http://localhost:7788/service-provider/** 这时会路由到指定的服务
            # 此处配置去掉 2 个路径前缀,相当于去掉/api/provider/,再配置上面的 Path=/api/provider/**,就能按照 http://localhost:7788/api/provider/** 的方式访问了
            - StripPrefix=2

server:
  port: 7788

# 配置日志级别,方便调试
logging:
  level:
    org.springframework.cloud.gateway: debug

跨域问题

GatewayApplication.java

package com.qiang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.support.DefaultServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

    // ----------------------------- 解决跨域 Begin -----------------------------

    private static final String ALL = "*";
    private static final String MAX_AGE = "3600L";

    @Bean
    public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
        return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
    }

    @Bean
    public ServerCodecConfigurer serverCodecConfigurer() {
        return new DefaultServerCodecConfigurer();
    }

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (!CorsUtils.isCorsRequest(request)) {
                return chain.filter(ctx);
            }
            HttpHeaders requestHeaders = request.getHeaders();
            ServerHttpResponse response = ctx.getResponse();
            HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
            HttpHeaders headers = response.getHeaders();
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
            headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
            if (requestMethod != null) {
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
            }
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
            headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
            headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
            if (request.getMethod() == HttpMethod.OPTIONS) {
                response.setStatusCode(HttpStatus.OK);
                return Mono.empty();
            }
            return chain.filter(ctx);
        };
    }

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

注意: 如果 RestController 代码中使用了 @CrossOrigin(value = "*", maxAge = 3600) 注解解决跨域问题,此时我们的网关已经一并处理了跨域请将之前的注解删除,否则会报 The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. 错误,表示配置了多个跨域。

访问网关

此时访问网关地址http://localhost:7788/api/provider/echo/haha相当于访问提供服务的http://localhost:8070/echo/haha

image-20200810020101922

image-20200810020111235