didi单体应用架构

将项目所有模块(功能)打成jar或者war,然后部署一个进程--医院挂号系统;

> 优点:
> 1:部署简单:由于是完整的结构体,可以直接部署在一个服务器上即可。
> 2:技术单一:项目不需要复杂的技术栈,往往一套熟悉的技术栈ssm就可以完成开发。
>
> 缺点:
> 1:系统启动慢,一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过长;
>
> 2:系统错误隔离性差、可用性差,任何一个模块的错误均可能造成整个系统的宕机;
>
> 3:可伸缩性差:系统的扩容只能只对这个应用进行扩容,无法结合业务模块的特点进行伸缩。
>
> 4: 线上问题修复周期长:任何一个线上问题修复需要对整个应用系统进行全面升级。
>
> 5: 跨语言程度差 [必须统一用一门编程一些]
>
> 6: 不利于安全管理,所有开发人员都拥有全量代码。

> 单体架构===管理类项目 比如: 仓库管理系统  OA办公系统  银行系统{内部使用的系统}
>
> 不适合电商系统---->

微服务应用70%

微服务架构论文: Microservices

译文: 微服务译文理解_微服务架构翻译-CSDN博客

In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies

简单来说,微服务架构风格[1]是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。

--把一个完整的项目--拆分为若干个小型的项目

解读微服务

1:微服务是一种==项目架构思想==(风格)

2:微服务架构是一系列小服务的组合(组件化与多服务)

3:任何一个微服务,都是一个独立的进程(独立开发、独立维护、独立部署)

4:轻量级通信http协议(跨语言,跨平台) 服务与服务之间可以通过http协议进行相互调用

5:服务粒度(围绕业务功能拆分---模块拆分【系统管理服务】【日志服务】【焦虑测试】【抑郁测试系统】)

6:去中心化管理(去中心化"地治理技术、去中心化地管理数据);

微服务架构的优势

1.易于开发和维护

一个微服务只关注一个特定的业务功能,所以它的业务清晰、代码量较少。开发和维护单个微服务相对比较简单,整个应用是由若干个微服务构建而成,所以整个应用也会维持在可控状态;

⒉.单个微服务启动较快

单个微服务代码量较少,所以启动会比较快;

3.局部修改容易部署

单体应用只要有修改,就要重新部署整个应用,微服务解决了这样的问题。一般来说,对某个微服务进行修改,只需要重新部署这个服务即可;

4.技术栈不受限

在微服务中,我们可以结合项目业务及团队的特点,合理地选择技术栈;

5.按需伸缩

患者系统访问量大,只需要对患者系统进行扩展;

微服务架构的缺点--(挑战)

l 这么多小服务,如何管理他们?

l 这么多小服务,他们之间如何通讯?

l 这么多小服务,客户端怎么访问他们?

l 这么多小服务,一旦出现问题了,应该如何自处理?

l 这么多小服务,一旦出现问题了,应该如何排错?

SpringCloud与微服务的关系

  • Springcloud为微服务思想提供了完美的解决方案;

  • Springcloud是一些列框架的集合体(服务的注册与发现【注册中心】、服务间远程调用、服务降级、服务熔断、服务限流、分布式事务等);

  • 微服务:它是一种架构思想;
    springcloud:就是解决微服务架构思想面临的挑战;

SpringBoot与SpringCloud的关系

  • SpringBoot专注于快速方便的开发单个个体微服务。

  • SpringCloud是关注全局的微服务协调、整理、治理的框架,它将SpringBoot开发的单体整合并管理起来。

  • SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系。

电商案例:(搭建的是微服务架构项目服务

第一步:准备/创建数据库 ;(商品数据库  ,订单数据库)

商品数据库;  数据库名:springcloud

创建表结构

DROP TABLE IF EXISTS `shop_product`;
CREATE TABLE `shop_product`  (
  `pid` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '商品id',
  `pname` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '商品名',
  `pprice` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品价格',
  `stock` varchar(255) CHARACTER SET utf8mb4  NULL DEFAULT NULL COMMENT '商品库存',
  PRIMARY KEY (`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4  ROW_FORMAT = Dynamic;

插入数据

INSERT INTO `shop_product` VALUES (1, '华为手机', 1999.00, '100');
INSERT INTO `shop_product` VALUES (2, 'vivo手机', 2999.00, '100');
INSERT INTO `shop_product` VALUES (3, '小米', 2222.00, '1000');

SET FOREIGN_KEY_CHECKS = 1;

订单数据库;数据库名:springcloud-order

#订单数据库#
DROP TABLE IF EXISTS `shop_order`;
CREATE TABLE `shop_order`  (
  `oid` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `uid` int(0) NULL DEFAULT NULL COMMENT '用户id',
  `username` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '用户名',
  `pid` bigint(0) NULL DEFAULT NULL COMMENT '商品id',
  `pname` varchar(255) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '商品名称',
  `pprice` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品价格',
  `number` int(0) NULL DEFAULT NULL COMMENT '购买数量',
  PRIMARY KEY (`oid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 61741 CHARACTER SET = utf8mb4 ROW_FORMAT = Dynamic;

第二步:创建一个父工程;

创建一个工程

注意: 把src目录删除,并在pom.xml中添加打包方式为pom打包。

引入相关依赖

<!--引入父依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
    </parent>
    <!--定义版本号-->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!--springcloud的版本-->
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <!--springcloudalibaba的版本号-->
        <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <!--dependencyManagement:只用于jar的管理 不负责jar的下载。 后期子工程使用jar时无需指定版本号-->
    <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.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

注意: springboot和springcloud以及springcloud alibaba他们版本必须匹配https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

第三步:创建一个公共模块 

模块名字:spring-common

所有微服务都需要用到的内容,可以放入到公共模块中。未来:放置实体类 工具类等;

引入依赖

<dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
    </dependencies>

创建实体类entity 

 product类
package com.aaa.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

/**/
@Data
@TableName(value = "shop_product")
public class Product {
    @TableId(type = IdType.AUTO)
    private Integer pid;
    private String pname;
    //double  存在精度丢失  金额不要使用double  使用类型bigdecimal

    private BigDecimal pprice;
    private Integer stock;

}
Order类
package com.aaa.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@Data
@TableName(value = "shop_order")
public class Order {
    @TableId(type = IdType.AUTO)
    private Integer oid;
    private Integer uid;
    private String username;

    //并没有只写商品id

    private Integer pid;
    private String pname;
    private BigDecimal pprice;
    private Integer number;

}

第四步:创建商品微服务;

模块名字:spring-product

  

   引入依赖

<!--引入公共模块-->
        <dependency>
            <groupId>com.aaa</groupId>
            <artifactId>spring-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--springbootweb依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>




 <!--引入nacos的依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

创建配置文件

application.properties

# 商品微服务的端口号[8001~8009]
server.port=8001
# 数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# mybatis sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl


#指定nacos服务器地址
spring.cloud.nacos.discovery.server-addr=localhost:8848
#指定服务名
spring.application.name=qy172-product

创建dao层接口

ProductDao

package com.aaa.dao;

import com.aaa.entity.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface ProductDao  extends BaseMapper<Product> {
}

创建service层

ProductService接口

package com.aaa.service;

import com.aaa.entity.Product;

public interface ProductService {
    /**
     * 根据商品id查询商品信息
     * @param id
     * @return
     */
    public Product getBiId(Integer id);
}

创建service实现层

  ProductServiceImpl 

package com.aaa.service.impl;

import com.aaa.dao.ProductDao;
import com.aaa.entity.Product;
import com.aaa.service.ProductService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class ProductServiceImpl implements ProductService {
    @Resource
    private ProductDao productDao;
    @Override
    public Product getBiId(Integer id) {
        return productDao.selectById(id);
    }
}

创建controller

ProductController

package com.aaa.controller;

import com.aaa.entity.Product;
import com.aaa.service.ProductService;
import com.baomidou.mybatisplus.annotation.TableId;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/selectById/{id}")
    public Product selecId(@PathVariable Integer id){
        Product product = productService.getBiId(id);
        return  product;
    }


}

创建主启动类

package com.aaa;

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

@SpringBootApplication
@MapperScan(basePackages = "com.aaa.dao")   //dao接口扫描
public class ProductApp {
    public static void main(String[] args) {
        SpringApplication.run(ProductApp.class,args);
    }
}

访问

第五步:创建订单微服务 

模块名字   spring-order

引入依赖

<!--引入公共模块-->
        <dependency>
            <groupId>com.aaa</groupId>
            <artifactId>spring-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--springbootweb依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>



 <!--引入nacos的依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

创建配置文件

application.properties     

注:订单微服务和商品微服务配置文件唯一不同的就是数据库名字不同

# 订单微服务的端口号[8001~8009]
server.port=8001
# 数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud-order?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# mybatis sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl


#指定nacos服务器地址
spring.cloud.nacos.discovery.server-addr=localhost:8848
#指定服务名
spring.application.name=qy172-order

创建dao层接口

OrderDao

package com.aaa.dao;

import com.aaa.entity.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface Orderdao extends BaseMapper<Order> {
}

创建service层

OrderService

package com.aaa.service;

import com.aaa.entity.Order;

public interface OrderService {

    public int inserorder(Order order);
}

创建service实现层

OrderServiceimpl

package com.aaa.service.impl;

import com.aaa.dao.Orderdao;
import com.aaa.entity.Order;
import com.aaa.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class OrderServiceimpl implements OrderService {
    
    @Resource
    private Orderdao orderdao;
    
    @Override
    public int inserorder(Order order) {
        int insert = orderdao.insert(order);
        return insert;
    }
}

创建controller

OrderController

package com.aaa.controller;


import com.aaa.entity.Order;
import com.aaa.entity.Product;
import com.aaa.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
import java.util.Random;

@RestController
@RequestMapping("order")
public class OrderController {

    @Resource
    private OrderService orderService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("save")

    //传参  商品编号+数量

    public String save(Integer pid,Integer num){
            Order order = new Order();
        //订单购买数量
            order.setNumber(num);
        //获取商品ID+用户信息
        order.setUid(1);
        order.setUsername("小明");

        //获取注册中心实例表
        List<ServiceInstance> instances = discoveryClient.getInstances("qy172-product");
        //获取列表的第一个实列
//        ServiceInstance serviceInstance = instances.get(0);
//        String scheme = serviceInstance.getScheme();
//        System.out.println("协议类型" + scheme);
//        String host = serviceInstance.getHost();
//        System.out.println("主机地址" + host);
//        int port = serviceInstance.getPort();
//        System.out.println("实列的端口号" + port);
//        URI uri = serviceInstance.getUri();
//        System.out.println("uri:"+uri);


        //随机生成一个实例下标
        int index = new Random().nextInt(instances.size());

        ServiceInstance serviceInstance = instances.get(index);
       URI uri = serviceInstance.getUri();


      Product product = restTemplate.getForObject(uri+"/product/selectById/"+pid,Product.class);
    // Product product = restTemplate.getForObject("http://qy172-product/product/selectById/"+pid,Product.class);
       // Product product = restTemplate.getForObject("http://localhost:8001/product/selectById/"+pid,Product.class);
        if(product == null){
            return "下单失败,商品不存在";
        }

        order.setPid(product.getPid());
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());

        int i = orderService.inserorder(order);
        return "下单成功";
    }
}

注:上面RestTemplate在spring容器中默认不存在的,需要创建该对象并交于spring容器管理。

创建启动类

OrderApp

package com.aaa;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@MapperScan(basePackages = "com.aaa.dao")   //dao接口扫描
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }

//告诉RestTemplate远程调用时需要使用负载均衡ribbon
    
    @LoadBalanced
    @Bean
     public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

访问  

服务治理

什么是服务治理?

服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现

服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服

务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳30s 90s的方式去监测清单中 的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。

服务发现:服务调用方向服务注册中心咨询服务,并获取所有服务的实例清单,实现对具体服务实;

通过上面的调用图会发现,除了微服务,还有一个组件是服务注册中心,它是微服务架构非常重要

的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:

        1. 服务发现:

服务注册:保存服务提供者和服务调用者的信息;

服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息;

        2. 服务配置:

配置订阅:服务提供者和服务调用者订阅微服务相关的配置;

配置下发:主动将配置推送给服务提供者和服务调用者;

        3. 服务健康检测

检测服务提供者的健康情况,如果发现异常,执行服务剔除;

常见的注册中心

Zookeeper

zookeeper是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式

应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用

配置项的管理等。

Eureka

springcloud很多组件都是使用的是netflix公司。--停更了---springcloud alibaba

Eureka是Springcloud Netflix中的重要组件,主要作用就是做服务注册和发现。但是现在已经闭

源 ,停更不停用。

Consul

Consul是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现

和配置管理的功能。Consul的功能都很实用,其中包括:服务注册/发现、健康检查、Key/Value

存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以

安装和部署都非常简单,只需要从官网下载后,在执行对应的启动脚本即可。

Nacos (服务治理 配置中心)

Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring

Cloud Alibaba 组件之一,负责服务注册发现和服务配置,可以这样认为nacos=eureka+config。

nacos简介

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

从上面的介绍就可以看出,nacos的作用就是一个注册中心,用来管理注册上来的各个微服务。

nacos实战入门

接下来,我们就在现有的环境中加入nacos,并将我们的两个微服务注册上去。

Releases · alibaba/nacos · GitHub nacos安装网站

安装nacos服务治理组件

解压即可

 默认nacos启动模式为集群模式。 修改模式为单机模式。

修改文件目录:nacos/bin/startup.cmd文件

修改完成以后双击启动nacos组件(startup.cmd

访问nacos服务

注:无法启动nacosl常见的两种情况:

1.nacos放在中文目录;

2.jdk环境设置有问题;

把微服务注册到注册中心nacos

引入依赖

分别把该依赖分别添加在商品微服务架构订单微服务架构

 <!--引入nacos的依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

修改配置文件

分别修改商品微服务和订单微服务的application.properties配置文件

商品微服务

订单微服务

启动微服务

修改订单服务和商品服务的耦合问题

通过以上修改订单服务和商品服务的耦合问题解决了 一旦服务提供者地址变化,就需要手工修改代码;我们修改完地址以后,只需要重新运行一下修改地址的微服务即可;

实现服务调用的负载均衡

什么是负载均衡?

通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。

根据负载均衡发生位置的不同,一般分为服务端负载均衡客户端负载均衡

服务端负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡

而客户端负载均衡指的是发生在服务请求的一方,也就是在发送请求之前已经选好了由哪个实例处理请求;

我们在微服务调用关系中一般会选择*客户端负载均衡*,也就是在服务调用的一方来决定服务由哪个提供者执行.

搭建商品微服务的集群

自定义负载均衡

上面是自己手写了一个随机负载均衡策略代码,如果后期我想使用其他的负载均衡策略,手动改上面的代码。--使用第三方提供的负载均衡组件来实现。---ribbon

什么是ribbon?

专门实现微服务客户端负载均衡的。目前阿里巴巴没有提供负载均衡器。

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

注:使用ribbon无需引入任何依赖。因为nacos依赖中自带了ribbon;

修改controller类

测试结果为:ribbon的负载均衡策略为轮询。  

ribbon内部提供了哪些负载均衡策略

你可以自己定义策略【很少】---你可以仿造上面的策略写自己的策略类。

如何改变相应的策略
#指定ribbon的负载均衡策略
qy172-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
#服务提供者的名称.ribbon.NFLoadBalancerRuleClassName=负载均衡策略类全路径

openfeign

openfeign组件,完成我们的远程调用的。 我们的编程习惯controller---->service--->dao

需要在controller注入一个service对象并调用service钟相应的方法传递相应的参数,根据方法返回的结果 使用相应的类型接受;

什么是openfeign

OpenFeign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得(调用远程服务就像调用本地服务一样简单), 只需要创建一个接口并添加一个注解即可。 
Nacos很好的兼容了OpenFeign, OpenFeign负载均衡默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。

如何使用openfeign

第一步:引入openfeign依赖;

<!--引入openfeign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

第二步:创建一个接口;

 ProductFeign接口

package com.aaa.feign;

import com.aaa.entity.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value ="qy172-product" )
public interface ProductFeign {

    @GetMapping("/product/selectById/{id}")
    public Product selectById(@PathVariable Integer id);
}

第三步:开启openfeign注解;

第四步:修改controller代码;

Gateway服务网关

思考问题:

大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端(pc android ios 平板)要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。

这样的架构,会存在着诸多的问题:

l 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性

l 认证复杂,每个服务都需要独立认证。

l 存在跨域请求,在一定场景下处理相对复杂。

上面的这些问题可以借助API网关来解决。

所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控(黑白名单)、路由转发等等。 添加上API网关之后,系统的架构图变成了如下所示:

在业界比较流行的网关,有下面这些:

Ngnix+lua

使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用

lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本

Kong

基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:

只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。

 Zuul 1.0(慢 servlet 2.0 ) zuul2.0 没出来。

Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配

置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx

 Spring Cloud Gateway

Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。

注意:**SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关**

Gateway简介

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

优点:

l 性能强劲:是第一代网关Zuul的1.6倍;

l 功能强大:内置了很多实用的功能,例如转发、监控、限流等;

l 设计优雅,容易扩展.;

缺点:

l 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高;

l 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行 web.Jar;

l 需要Spring Boot 2.0及以上的版本,才支持;

gateway内置了服务器 netty服务器。千万不要在使用tomcat作为服务器。---千万不要引入;springboot-web依赖;

Gateway快速入门

第一步:创建网关微服务;

第二步:引入依赖;

    <!--加入网关依赖-->
        <!--引入网关以后,不要在引入web依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

第三步:修改配置文件;

server:
  port: 7777
spring:
  application:
    name: shopping-gateway

  cloud:
    gateway:
      routes:
        - id: qy172-product
          uri: http://localhost:8001
          predicates:
            - Path=/product/**

        - id: qy172-order
          uri: http://localhost:9001
          predicates:
            - Path=/order/**

 

第四步:创建主启动类;

第五步:访问;

增强版

现在在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从注册中心获取此地址。

gateway本身也是一个微服务,那么它也可以从nacos注册中心拉取其他服务信息,调用其他微服务。

第一步:从Gateway网关微服务引入nacos依赖;

    <!--加入网关依赖-->
        <!--引入网关以后,不要在引入web依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--加入nacos依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

第二步:拉去微服务信息从而完成转发功能;

server:
  port: 7777
spring:
  application:
    name: shopping-gateway

  cloud:
    gateway:
      routes:
        - id: qy172-product
          uri: lb://qy172-product
          predicates:
            - Path=/product/**

        - id: qy172-order
          uri: lb://qy172-order
          predicates:
            - Path=/order/**

  nacos:
    discovery:
      server-addr: localhost:8848
      register-enabled: false
      enabled: true

第三步:测试访问;

简写版

如果未来增加了一个微服务,那么网关这里还需要修改配置文件。

可以使用网关简写版--->可能不太使用。--开启网关的路由自动定位功能;

第一步:修改配置文件

server:
  port: 7777
spring:
  application:
    name: qy172-gateway

  nacos:
    discovery:
      server-addr: localhost:8848
      register-enabled: false
      enabled: true

  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

第二步:访问

Gateway核心架构

基本概念

路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:

l id,路由标识符,区别于其他 Route。默认生成一个 UUID

l uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。

l order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。

l predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。

l filter,过滤器用于修改请求和响应信息。

执行流程

执行流程大体如下:

1. Gateway Client向Gateway Server发送请求;

2. 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文;

3. 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给 RoutePredicateHandlerMapping;

4. RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用;

5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用;

6. 请求会一次经过PreFilter--微服务--PostFilter的方法,最终返回响应;

断言

Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。

断言就是说: 在 什么条件下 才能进行路由转发;

内置路由断言工厂

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配体如下:

l *基于**Datetime**类型的断言工厂*

此类型的断言根据时间做判断,主要有三个:

AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期

BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期

BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]

l *基于远程地址的断言工厂* *RemoteAddrRoutePredicateFactory**:*

接收一个IP地址段,判断请求主机地址是否在地址段中

-RemoteAddr=192.168.1.1/24

l *基于**Cookie**的断言工厂*

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求

cookie是否具有给定名称且值与正则表达式匹配。

-Cookie=chocolate, ch.

l *基于**Header**的断言工厂*

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否

具有给定名称且值与正则表达式匹配。 key value

-Header=X-Request-Id, \d+

l *基于**Host**的断言工厂*

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.testhost.org

l *基于**Method**请求方法的断言工厂*

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

-Method=GET

l *基于**Path**请求路径的断言工厂*

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

-Path=/foo/{segment}基于Query请求参数的断言工厂

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具

有给定名称且值与正则表达式匹配。

-Query=baz, ba.

l *基于路由权重的断言工厂*

WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发

routes:

-id: weight_route1 uri: host1 predicates:

-Path=/product/**

-Weight=group3, 1

-id: weight_route2 uri: host2 predicates:

-Path=/product/**

-Weight= group3, 9

如果上面的断言无法满足要求 可以自定义断言。 仿造人家提供的断言。

第一步:创建 AgeRoutePredicateFactory类

package com.aaa.predicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {


    public AgeRoutePredicateFactory() {
        super(AgeRoutePredicateFactory.Config.class);
    }

    public List<String> shortcutFieldOrder() {
        return Arrays.asList("min", "max");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //获取请求携带年龄--请求地址中或请求头
                ServerHttpRequest request = serverWebExchange.getRequest();
                String age = request.getHeaders().getFirst("age");
                if(age==null){
                    return false;
                }
                int i = Integer.parseInt(age);
            if(i>=config.getMin()&&i<=config.getMax()){
                    return true;
                }
                return false;
            }
        };
    }

    @Validated
    public static class Config {
        //最大年龄和最小年龄
        @NotNull
        private Integer min;
        @NotNull
        private Integer max;

        public Config() {
        }

        public Integer getMin() {
            return min;
        }

        public void setMin(Integer min) {
            this.min = min;
        }

        public Integer getMax() {
            return max;
        }

        public void setMax(Integer max) {
            this.max = max;
        }
    }
}

第二步:修改配置文件

server:
  port: 7777
spring:
  application:
    name: qy172-gateway

  cloud:
    gateway:
      routes:
        - id: qy172-product
          uri: lb://qy172-product
          predicates:
            - Path=/product/**
            - Age=18,50

        - id: qy172-order
          uri: lb://qy172-order
          predicates:
            - Path=/order/**

  nacos:
    discovery:
      server-addr: localhost:8848
      register-enabled: false
      enabled: true

第三步:使用第三方测试软件-------Postman测试年龄是否在min和max区间,在区间可以跳转,不在区间显示404;

gateway网关完成统一认证

认证判断

当客户端第一次请求服务时,服务端对用户进行信息认证(登录);

认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证;

以后每次请求,客户端都携带认证的token;

服务端对token进行解密,判断是否有效。

1. 在网关中需要写的业务代码:
   [1]获取访问的资源路径
   [2]判断该资源路径是否为白名单--放行的资源的路径 比如登录 注册 发生验证码
   [3]判断是否携带token令牌以及令牌是否有效。
   [4]如果携带了token令牌--放行
   [5]如果没有携带token令牌--返回一个json数据

网关中如何实现认证的校验  

第一步:创建filter层    LoginFilter类

package com.aaa.filter;

import com.aaa.vo.Result;
import com.aaa.white.WhiteList;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
//表示该类的对象交于spring容器管理
@Component
public class LoginFilter implements GlobalFilter, Ordered {

    @Autowired
    private WhiteList whiteList;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取request对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //2.获取访问资源路径
        String path = request.getPath().toString();
        //3.获取属于白名单 无需认证即可访问的资源
        List<String> list = whiteList.getList();
        if(list.contains(path)){
          return  chain.filter(exchange);           //放行
        }
        //4.获取携带token--请求头
        String token = request.getHeaders().getFirst("token");
        if(token != null && "admin".equals(token)){
           return chain.filter(exchange);          //放行
        }
        //5.返回一个json数据
        Result result = new Result(402,"登录无效",null);
        //json转换---没有引入fastjson依赖
        byte[] bytes = JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        //调用bufferFactory方法,生成DataBuffer对象
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        //调用Mono中的just方法,返回要写给前端的JSON数据
        return response.writeWith(Mono.just(buffer));
    }

    //值越小优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

第二步:创建 vo 和 white层       

   

Result类
package com.aaa.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Integer code;
    private String msg;
    private Object data;
}
WhiteList类
package com.aaa.white;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

@Data
@Component
@ConfigurationProperties(prefix = "white")
public class WhiteList {
        private List<String> list;
}

第三步:配置application.yml文件 

server:
  port: 7777
spring:
  application:
    name: qy172-gateway

  cloud:
    gateway:
      routes:
        - id: qy172-product
          uri: lb://qy172-product
          predicates:
            - Path=/product/**


        - id: qy172-order
          uri: lb://qy172-order
          predicates:
            - Path=/order/**

  nacos:
    discovery:
      server-addr: localhost:8848
      register-enabled: false
      enabled: true


white:
  list:
    - /sso/login
    - /user/register
    - /msg/sendMsg
    - /order/save

第四步:使用第三方测试软件Postman测试数据;

不带token

带token   密码必须是admin

可以正常获取数据

带token   密码是admin

Gateway解决跨域

Gateway解决跨域的两种方式

第一种方式:基于跨域配置类;

1.创建config层  CorConfig类
package com.aaa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class CorConfig {
    // 当前跨域请求最大有效时长。这里默认1天
    private static final long MAX_AGE = 24 * 60 * 60;
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*"); //允许任意域跨域访问该服务
        config.addAllowedHeader("*");
        // springboot升级成2.4.0以上时对AllowedOrigin设置发生了改变,不能有”*“,可以替换成AllowedOriginPattern
        config.addAllowedMethod("*"); //允许任意的请求方式 GET  POST
        config.setAllowCredentials(true);

        // 必须是reactive包下的UrlBasedCorsConfigurationSource
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}
 2.使用WebStorm运行前端项目

3.在这里输入npm run serve运行前端项目

 下面这种情况就是出现的跨域问题

第二种方式:使用配置文件的方式解决;(企业常用解决跨域的方案

配置application.yml文件

注:不添加允许访问的路径的话,允许控制台会出现”跨域问题“ ;

server:
  port: 7777
spring:
  application:
    name: qy172-gateway

  cloud:
    gateway:
      routes:
        - id: qy172-product
          uri: lb://qy172-product
          predicates:
            - Path=/product/**



        - id: qy172-order
          uri: lb://qy172-order
          predicates:
            - Path=/order/**
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins:
              - "http://localhost:8001"
              - "http://localhost:9001"
              - "http://localhost:8080"
            allowedMethods:
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*"
            allowedCredentials: true
            maxAge: 36000
        add-to-simple-url-handler-mapping: true




  nacos:
    discovery:
      server-addr: localhost:8848
      register-enabled: false
      enabled: true


white:
  list:
    - /sso/login
    - /user/register
    - /msg/sendMsg
    - /order/save

链路追踪

在大型系统的微服务化构建中,一个系统被拆分成了许多微服务。这些微服务负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心【区域】,也就意味着这种架构形式也会存在一些问题:

l 如何快速发现问题?

l 如何判断故障影响范围?

l 如何梳理服务依赖?

l 如何分析链路性能问题以及实时容量规划?

分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上IP、每个服务节点的请求状态200 500等等。

常见链路追踪技术栈有哪些

cat 由大众点评开源,基于Java开发的实时应用监控平台,包括实时应用监控,业务监控 。 集成

方案是通过代码埋点【代码】的方式来实现监控,比如: 拦截器,过滤器等。 对代码的侵入性很大,集成成本较高。风险较大。

zipkin 由Twitter公司开源,开放源代码分布式的跟踪系统,用于==收集服务的定时数据==,以解决微

服务架构中的延迟问题,包括:数据的收集、存储、查找和展现《图形化》。该产品结合spring-cloud-sleuth 使用较为简单, 集成很方便, 但是功能较简单。

pinpoint Pinpoint是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点

是支持多种插件,UI功能强大,接入端无代码侵入。

skywalking 【未来企业会使用的多】

SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多

种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。 ---火。

Sleuth (日志记录每一条链路上的所有节点,以及这些节点所在的机器,和耗时。)log4j

SpringCloud 提供的分布式系统中链路追踪解决方案。

注:在微服务架构使用的是sleuth+zipkin结合使用;

链路追踪就是为了快速定位微服务故障所在地。

sleuth就是为了生成微服务日志。---查看起来比较麻烦。

zipkin搜集sleuth生成的日志,并以图像化展示。

sleuth介绍

SpringCloud Sleuth主要功能就是在分布式系统中提供追踪解决方案。它大量借用了Google Dapper的设计, 先来了解一下Sleuth中的术语和相关概念。

*Trace* *(一条完整链路--包含很多span(微服务接口))*

由一组Trace Id(贯穿整个链路)相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId),同时在分布式系统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯一标识将所有的请求串联起来,形成一条完整的请求链路。

*Span*

代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时候,也通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等元数据。

*Annotation*

用它记录一段时间内的事件,内部使用的重要注释:

l cs(Client Send)客户端发出请求,开始一个请求的命令

l sr(Server Received)服务端接受到请求开始进行处理, sr-cs = 网络延迟(服务调用的时间)

l ss(Server Send)服务端处理完毕准备发送到客户端,ss - sr = 服务器上的请求处理时间

l cr(Client Reveived)客户端接受到服务端的响应,请求结束。 cr - cs = 请求的总时间

sleuth使用

注:在各个微服务引入sleuth的依赖即可,为了方便使用我这里把依赖就加在父工程pom文件里;

在父工程中加入sleuth依赖

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

从上面sleuth产生的日志可以看出,他们的traceId是相同,每个微服务都有自己的spanid,把这些traceId相同的span串联在一起形成了一个分布式链路。可以每个span的时间。我也可以看出该span消化的时间。如果这样来看的话,非常麻烦。在实际开发中是绝对不允许。 我们应该使用zipkin来搜集上面这样的日志,然后形成一个图像化界面,通过查看界面则可看出那个微服务出现故障;

zipkin原理图

根据上面的原理; 我们需要安装一个zipkin的服务端。

Central Repository: io/zipkin/zipkin-server

访问网址  

在父工程中添加zipkin依赖

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

把搜集的日志存在数据库中

上面我们如果重新启动zipkin 我们可以看到链路丢失 默认存在内存。 我们可以指定存放在数据库

CREATE TABLE IF NOT EXISTS zipkin_spans (
 `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', 
`trace_id` BIGINT NOT NULL, 
`id` BIGINT NOT NULL, 
`name` VARCHAR(255) NOT NULL, 
`parent_id` BIGINT, 
`debug` BIT(1), 
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query' )
 ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate'; 
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations'; 
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
 ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
 ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';
CREATE TABLE IF NOT EXISTS zipkin_annotations (
 `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', 
`trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id', 
`span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
 `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1', 
`a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB', 
`a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation', 
`a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
 `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null', 
`endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address', 
`endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null' )
 ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate'; 
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans'; 
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds'; 
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';

ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
 ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces'; 
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';
CREATE TABLE IF NOT EXISTS zipkin_dependencies ( 
`day` DATE NOT NULL, 
`parent` VARCHAR(255) NOT NULL, 
`child` VARCHAR(255) NOT NULL,
 `call_count` BIGINT ) 
ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; 
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);

运行zipkin

java -jar zipkin-server-2.25.2-exec.jar --STORAGE_TYPE=mysql -- 
MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=root

 

微服务保护

初始Sentinel

雪崩问题

微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩;

解决雪崩问题的常见四种方式:

超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待;

舱壁模式:限定每个业务就使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离;

熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问业务的一切请求;

流量控制:限制业务访问的QPS,避免服务因流量的突增而故障;

如何避免因瞬间高并发流量而导致服务故障?  流量控制

如何避免因服务故障引起的雪崩问题?超时处理,舱壁模式(线程隔离),熔断降级;

服务保护技术对比

 

认识Sentinel

官方网址:http://sentinelguard.io/zh-cn/index.html

要想Sentinel结合微服务一起使用,项目结构Springcloud;

第一步:我们也可以在父工程或者指定的模块pom文件添加Sentinel依赖;

我这里使用的在指定的模块添加Sentienl依赖   spring-product商品服务模块

<!--添加Sentienl依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

第二步:在配置文件中添加访问路径

spring.cloud.sentinel.transport.dashboard=localhost:8090

注:要是在父工程添加依赖的话,所有模块的配置文件都需要添加访问路径;

配置文件yml结构

第三步:访问spring-product商品服务模块

第四步:在Sentienl组件中查看spring-product商品服务模块的信息;

限流规则

簇点链路     

簇点链路:就是项目内的调用链路,链路中被监控的每个接口就是一个资源,默认情况下  sentienl会监控SpringMVC的每一个端点,因此SpringMVC的每一个端点就是调用链路中的一个资源;

流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则;

快速入门

流控模式

在添加限流规则时,点击高级选项,可以选择三种流控模式;

直接:统计当前资源的请求,触发阈值时对资源直接限流,也就是默认的模式;

谁触发阈值,谁限流;

关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流;

链路:统计从指定链路访问到本资源请求,触发阈值时,对指定的链路限流;

谁使用链路,谁限流;

 

线程数限流

​​​​​​​

 

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐