[阶段4 企业开发进阶] 7. 微服务--SpringCloud Alibaba

2023-11-02

文章目录

SpringCloud Alibaba

中文文档
开源代码

服务限流降级:默认支持Servlet、Feign、RestTemplate、Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics监控

服务注册与发现:适配Spring Cloud服务注册与发现标准,默认集成Ribbon的支持

分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新

消息驱动能力:基于SpringCloudStream为微服务应用构建新消息驱动能力

阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据

分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有Worker(schedulerx-client)上执行

1 服务注册和配置中心Nacos

1.1 Nacos简介

Nacos:Dynamic Naming and Configuration Service

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos就是注册中心+配置中心的组合(Eureka+Config+Bus)

快速入门

安装运行成功后访问http://localhost:8848/nacos(默认账号密码为nacos)

1.2 Nacos作为服务注册中心

服务提供者注册

1.新建模块cloudalibaba-provider-payment-9001

2.pom

<dependencies>

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

    <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.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

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

</dependencies>

3.yml

server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'

4.主启动

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {

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

5.业务

@RestController
@RequestMapping("/payment")
public class PaymentController {

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

    @GetMapping("/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id) {
        return "Nacos registry, server port: " + serverPort + "\t id" + id;
    }
}

6.测试

  1. 启动nacos和9001
  2. 访问http://localhost:9001/payment/nacos/1

在这里插入图片描述

服务消费者注册和负载

1.新建cloudalibaba-consumer-nacos-order-83

2.pom

3.yml

server:
  port: 83


spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848


#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

4.主启动

5.配置

由于nacos自带负载均衡,其包引入ribbon,故配置resttemplate

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

5.业务

@Slf4j
@RestController
@RequestMapping("/consumer")
public class OrderNacosController {
    @Resource
    private RestTemplate restTemplate;

    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @GetMapping(value = "/payment/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id)
    {
        return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
    }

}

6.测试

  1. 启动nacos、9001、9002、83
  2. 访问http://localhost:83/consumer/payment/nacos/1

服务注册中心对比

在这里插入图片描述

Nacos支持AP和CP模式的切换

C是所有节点在同一时间看到的数据是一致的,而A的定义是所有的请求都会收到响应

一般来说,

如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么可以选择AP模式。当前主流的服务如SpringCloud和Dubbo服务,都适用于AP模式。AP模式为服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例

如果需要在服务级别编辑或者存储配置信息,那么CP是必须,K8S服务和DNS服务则适用于CP模式。CP模式下则支持注册持久化实例,此时则以Raft协议为集群运行模式,该模式下注册实例之前必须先注册微服务,如果服务不存在,则返回错误

# 模式切换
curl -X POST '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
服务注册与发现框架 CAP模型 控制台管理 社区活跃度
Eureka AP 支持 低(2.X版本闭源)
Zookeeper CP 不支持
Consul CP 支持
Nacos AP 支持

1.3 Nacos作为服务配置中心

基础配置

1.新建cloudalibaba-config-nacos-client-3377

2.pom

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

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

3.yml

Nacos与SpringCloud Config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置后,才能保证项目的正常启动,SpringBoot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application

bootstrap.yml

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

application.yml

spring:
  profiles:
    active: dev # 表示开发环境
    #active: test # 表示测试环境
    #active: info

4.主启动

5.业务

@RefreshScope:通过SpringCloud原生注解@RefreshScope实现配置的自动更新

@RestController
@RefreshScope //支持Nacos的动态刷新功能。
public class ConfigClientController {
    
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
}

Nacos中匹配规则

Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则

在 Nacos Spring Cloud 中,dataId 的完整格式如下:

${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。

公式

# nacos-config-client-dev.yaml
${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

在这里插入图片描述

在这里插入图片描述

测试

  1. 启动需要在nacos客户端-配置管理-配置管理模块下有对应的yaml文件
  2. 运行cloud-config-nacos-client-3377
  3. 调用接口查看配置信息 http://localhost:3377/config/info

自带动态刷新

修改Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新

分类配置

多环境多项目管理的问题

问题1

实际开发中,一个系统会准备

  • dev开发环境
  • test测试环境
  • prod生产环境

如何保证指定环境启动时服务能正确读取到Nacos相应环境的配置文件?

问题2

一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又会有相应的开发环境、测试环境、预发环境、正式环境等,

如何对这些微服务配置进行管理?

配置管理

在这里插入图片描述

命名空间

在这里插入图片描述

Namespace+Group+DataID

1.是什么

类似Java里面的包名和类名,最外层的namespace可以用于区分部署环境,Group和DataID逻辑上区分两个目标对象

2.三种情况

在这里插入图片描述

默认情况

Namespace=public, Group=DEFAULT_GROUP,默认Cluster是DEFAULT

  • Nacos默认的命名空间是public,Namespace主要用于实现隔离

  • Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

  • Service就是微服务,一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分

  • Instance就是微服务实例

DataID方案配置

指定spring.profiile.active和配置文件的DataID来使不同环境下读取不同的配置

默认空间+默认分组+新建dev和test两个DataID

通过spring.profiile.active属性进行多环境下配置文件的读取

spring:
  profiles:
    active: dev # 表示开发环境
    #active: test # 表示测试环境
    #active: info

Group方案配置

通过Group实现环境区分,新建Group,在Nacos图形界面控制台新建配置文件

在这里插入图片描述

bootstrap+application

在config下增加一条group的配置,可以配置为DEV_GROUP或者TEST_GROUP

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        group: DEV_GROUP

Namespace方案配置

新建dev/test的Namespace

在这里插入图片描述

在这里插入图片描述

bootstrap.yml

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        group: DEV_GROUP
        namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4

application.yml

spring:
  profiles:
    active: dev # 表示开发环境
    #active: test # 表示测试环境
    #active: info

1.4 Nacos集群和持久化配置

集群部署说明

默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决该问题,Nacos采用集中式存储的方式来支持集群化部署,目前仅支持MYSQL的存储

[待补充]

2 服务熔断和限流Sentinel

参考文档

Sentinel特性

在这里插入图片描述

Sentinel分两个部分

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

Sentinel控制台安装步骤

  1. 下载地址

  2. 运行命令(前提Java8环境;8080端口)

    java -jar sentinel-dashboard-1.8.6.jar
    
  3. 访问sentinel管理界面(账号密码均为sentinel)

2.1 初始化监控

启动Nacos http://localhost:8848/nacos

1.新建cloudalibaba-sentinel-service-8401

2.pom

3.yml

4.主启动

5.业务类

6.测试

启动8401,由于Sentinel采用懒加载

执行一次访问http://localhost:8401/testA和http://localhost:8401/testB

2.2 流控规则

在这里插入图片描述

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阈值:
    • QPS(每秒钟的请求数量):当调用该api的QPS达到阈值时,进行限流
    • 线程数:当调用该api的线程数达到阈值时,进行限流
  • 是否集群,不需要集群
  • 流控模式:
    • 直接:api到达限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来时的流量,如果达到阈值,就进行限流)【api级别的针对来源】
  • 流控效果:
    • 快速失败::直接失败,抛异常
    • Warm Up:根据coldFactor(冷加载因子,默认3)的值,从阈值/coldFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效

2.3 降级规则

在这里插入图片描述

基本介绍

RT(平均响应时间,秒级)

平均响应时间 超出阈值且在时间窗内通过的请求>=5,两个条件同时满足后触发降级,窗口期过后关闭断路器,RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

异常比例(秒级)

QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

Sentinel熔断器降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)

Sentinel断路器是没有半开状态

半开的状态系统自动检测是否有请求异常,没有异常就去关闭断路器恢复使用,有异常则继续打开断路器不可用

RT

在这里插入图片描述

异常比例 (ERROR_RATIO)

当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

在这里插入图片描述

异常数 (ERROR_COUNT)

当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

在这里插入图片描述

2.4 热点规则

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

在这里插入图片描述

代码

@RestController
public class FlowLimitController {
    @GetMapping("/testA")
    public String testA() {
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB() {
        log.info(Thread.currentThread().getName()+"\t"+"...testB");
        return "------testB";
    }

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                             @RequestParam(value = "p2", required = false) String p2) {
        return "------testHotKey";
    }

    public String deal_testHotKey(String p1, String p2, BlockException e) {
        return "-------deal_testhotkey";
    }
}

配置

在这里插入图片描述

@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")

方法testHotKey里面的第一个参数只要QPS超过每秒一次,马上降级处理

参数例外项

特例情况

  • 普通: 超过1s后阈值马上被限流
  • 特殊:期望p1参数为某特殊值时,它的限流值和平时不一样

配置

在这里插入图片描述


@SentinelResource处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理,主管配置出错,运行时异常不管的

2.5 系统规则

参考文档

Sentinel 系统自适应过载保护从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

2.6 SentinelResource配置

参考文档

按资源名称限流+后续处理

1.启动Nacos

2.启动Sentinel

3.修改cloudalibaba-sentinel-service-8401

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource() {
        return new CommonResult(200, "按资源名限流测试成功", new Payment(2023L, "serial001"));
    }

    public CommonResult handleException(BlockException ex) {
        return new CommonResult(444, ex.getClass().getCanonicalName() + "\t服务不可用");
    }
}

4.配置流控规则

在这里插入图片描述

测试流控后,将8401关闭,发现流控规则消失,无法持久化

按URL地址限流+后续处理

@GetMapping("/byUrl")
@SentinelResource(value = "byUrl", blockHandler = "handleException")
public CommonResult byUrl() {
    return new CommonResult(200, "按url限流测试成功", new Payment(2023L, "serial001"));
}

上述两种兜底方案存在问题

  1. 系统默认的,没有体现自己的业务要求
  2. 依照现有条件,自定义的处理方法和业务代码耦合在一起,不直观
  3. 每个业务方法都添加一个兜底的,造成代码膨胀
  4. 全局统一的异常处理方法没有体现

客户自定义限流处理逻辑

1.创建CustomerBlockHandler类用于自定义限流处理逻辑

public class CustomerBlockHandler {

    public static CommonResult handlerException1(BlockException ex) {
        return new CommonResult(444, "按客户自定义,全局异常处理器----1");
    }

    public static CommonResult handlerException2(BlockException ex) {
        return new CommonResult(444, "按客户自定义,全局异常处理器----2");
    }
}

2.controller

@GetMapping("/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException1")
public CommonResult customerBlockHandler() {
    return new CommonResult(200, "按客户自定义", new Payment(2023L, "serial003"));
}

@SentinelResource 注解

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为 EntryType.OUT

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallback/fallbackClassblockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

1.8.0 版本开始,defaultFallback 支持在类级别进行配置。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

sentinel主要有三个核心API

  • SphU定义资源
  • Tracer定义统计
  • ContextUtil定义上下文

2.7 服务熔断

Sentinel整合Ribbon+OpenFeign+fallback

Ribbon系列

提供者9003/9004

1.新建cloudalibaba-provider-payment-9003/9004

2.pom

3.yml

4.主启动

5.业务

消费者84

1.新建cloudalibaba-consumer-nacos-order-84

2.pom

3.yml

4.主启动

5.业务s

目的

fallback负责运行时异常

blockHandler负责配置违规

若blockHandler和fallback都进配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑

public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    //@SentinelResource(value = "fallback") //没有配置
    //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
    //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

        if (id == 4) {
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }
    //本例是fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }
    //本例是blockHandler
    public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }
   
}

Feign系列

Feign组件一般是消费侧

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

@Component
public class PaymentFallbackService implements PaymentService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}
@Resource
private PaymentService paymentService;

@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
    return paymentService.paymentSQL(id);
}

测试84调用9003,此时9003微服务宕机,消费者84会被降级

熔断框架比较

Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件形式 接口形式
基于注解的支持 支持 支持 支持
限流 基于QPS、支持基于调用关系的限流 有限的支持 Rate Limiter

2.8 规则持久化

一旦重启应用,Sentinel规则将消失,生产环境需要将配置进行持久化

将限流规则持久化进Nacos进行保存,只要刷新8401某个rest地址,Sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上的Sentinel上的流控规则持续有效

步骤

修改cloudalibaba-sentinel-service-8401

pom

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

yml(添加Nacos数据源配置)

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

添加Nacos业务规则配置

在这里插入图片描述

{
    “resource”:"/rateLimit/byUrl",
    "limitApp":"default",
    "grade":1,
    "count":1,
    "strategy":0,
    "controllerBehavior":0,
    "clusterMode":false
}
属性 说明
resource 资源名称
limitApp 来源应用
grade 阈值类型,0表示线程数,1表示QPS
count 单机阈值
strategy 流控模式,0表示直接,1表示关联,2表示链路
controllerBehavior 流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
clusterMode 是否集群

启动8401,刷新Sentinel

3 分布式事务处理 Seata

分布式事务存在问题

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务保证,但是全局的数据一致性无法保证

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

  • 仓储服务:对给定的商品扣除仓储数量。
  • 订单服务:根据采购需求创建订单。
  • 帐户服务:从用户帐户中扣除余额。

在这里插入图片描述

3.1 Seata简介

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

官网文档

发布说明

典型的分布式事务过程

分布式事务处理过程的ID+三组件模型

ID 说明
Transaction ID XID 全局唯一的事务ID
术语 说明
TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态,驱动全局事务提交或回滚
TM (Transaction Manager) - 事务管理器 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程

  1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
  2. XID在微服务调用链路的上下文中传播
  3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
  4. TM向TC发起针对XID的全局提交或回滚
  5. TC调度XID下管辖的全部分支事务完成提交或回滚请求

在这里插入图片描述

3.2 快速入门

下载地址

在这里插入图片描述

seata-server-0.9.0解压到指定目录并修改conf目录下的file.com配置文件

  • 先备份原始file.conf文件

  • 主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息

  • file.conf

    # service模块
    service {
      #vgroup->rgroup
      vgroup_mapping.my_test_tx_group = "fsp-tx_group"
      #only support single node
      default.grouplist = "127.0.0.1:8091"
      #degrade current not support
      enableDegrade = false
      #disable
      disable = false
      #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
      max.commit.retry.timeout = "-1"
      max.rollback.retry.timeout = "-1"
    }
    
    # store模块
    store {
      ## store mode: file、db
      mode = "db"
      ....
    

新建数据量seata(表在文件里提供)

修改…\seata\conf里面的registry.conf(指定注册中心为nacos)

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }
  ....

先启动Nacos(8848),再启动seata-server

3.3 订单/库存/账户业务数据库准备

使用教程

创建三个微服务(订单、库存、账户)

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成

该操作跨越三个数据库,两次远程调用,明显存在分布式事务问题

创建业务数据库

  • seata_order:存储订单的数据库

    CREATE DATABASE seata_order;
    USE seata_order;
    
    CREATE TABLE t_order(
        id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        user_id BIGINT(11) DEFAULT NULL COMMENT '用户id',
        product_id BIGINT(11) DEFAULT NULL COMMENT '产品id',
        count INT(11) DEFAULT NULL COMMENT '数量',
        money DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
        status INT(1) DEFAULT NULL COMMENT '订单状态:0创建中,1已完结'
    )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
    
  • seata_storage:存储库存的数据库

    CREATE DATABASE seata_storage;
    
    USE seata_storage;
    CREATE TABLE t_storage(
        id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        product_id BIGINT(11) DEFAULT NULL COMMENT '产品id',
        total INT(11) DEFAULT NULL COMMENT '总库存',
        used INT(11) DEFAULT NULL COMMENT '已用库存',
        residue INT(11) DEFAULT NULL COMMENT '剩余库存'
    )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
    INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);
    
  • seata_account:存储账户信息的数据库

    CREATE DATABASE seata_account;
    
    USE seata_account;
    CREATE TABLE t_account(
        id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        user_id BIGINT(11) DEFAULT NULL COMMENT '用户id',
        total DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
        used DECIMAL(10,0) DEFAULT NULL COMMENT '已用额度',
        residue DECIMAL(10,0) DEFAULT 0 COMMENT '剩余可用额度'
    )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
    INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);
    

对上述数据库表创建对应的回滚日志表

  • 订单-库存-账户三个库各建各自的回滚日志表
  • …\environment\seata\conf目录下db_undo_log.sql
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

3.4 订单/库存/账户业务微服务准备

业务需求:

下订单----减库存----扣余额----改(订单)状态

新建订单Order-Module

1.新建seata-order-service-2001

2.pom

    <dependencies>

        <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-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>

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

        <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>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
    </dependencies>

3.yml

server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中的对应
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order
    username: *****
    password: *****

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

4.file.conf

5.registry.conf

6.domain

CommonResult

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {

    private Integer code;
    private String message;
    private T data;

    public CommonResult(Integer code, String message) {
      this(code, message, null);
    }
}

Order

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    private Integer status; //订单状态:0:创建中;1:已完结
}

7.dao

@Mapper
public interface OrderDao {

    // 1. 新建订单
    void create(Order order);

    // 修改订单状态
    void update(@Param("userId") Long userId, @Param("status") Integer status);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cyan.springcloud.alibaba.dao.OrderDao">
    <resultMap id="BaseResultMap" type="com.cyan.springcloud.alibaba.domain.Order">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="count" property="count" jdbcType="INTEGER"/>
        <result column="money" property="money" jdbcType="DECIMAL"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
    </resultMap>

    <insert id="create" parameterType="com.cyan.springcloud.alibaba.domain.Order">
        INSERT INTO t_order(user_id, product_id, "count", money, status)
        VALUES (null, #{userId}, #{productId}, #{count}, #{money}, 0);
    </insert>

    <update id="update" parameterType="com.cyan.springcloud.alibaba.domain.Order">
        UPDATE t_order SET status = 1 WHERE user_id = #{userId} AND status = #{status};
    </update>
</mapper>

8.service

public interface OrderService {

    void create(Order order);
}

@FeignClient(value = "seata-storage-service")
public interface StorageService {

    @PostMapping("/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

@FeignClient(value = "seata-account-service")
public interface AccountService {

    @PostMapping("/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDao orderDao;

    @Resource
    private StorageService storageService;

    @Resource
    private AccountService accountService;

    @Override
    public void create(Order order) {
        // 1. 新建订单
        log.info("*****************开始新建订单");
        orderDao.create(order);

        // 2. 扣减库存
        log.info("*****************订单微服务开始调用库存扣减");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("*****************订单微服务调用库存扣减结束");

        // 3.扣减账户
        log.info("*****************订单微服务开始调用账户扣减");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("*****************订单微服务开始调用账户结束");

        // 4. 修改状态
        log.info("*****************开始修改订单状态");
        orderDao.update(order.getUserId(), 0);
        log.info("*****************修改订单状态结束");

        log.info("*****************成功下单!!!");

    }
}

9.controller

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

    @Resource
    private OrderService orderService;

    @GetMapping("/create")
    public CommonResult create(Order order) {
        orderService.create(order);
        return new CommonResult(200, "订单创建成功");
    }
}

10.MyBatis和数据源代理配置

@Configuration
@MapperScan({"com.cyan.springcloud.alibaba.dao"})
public class MyBatisConfig {
}
@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

新建库存Storage-Module

新建账户Account-Module

1.新建seata-storage-service-2002

…一模一样

3.5 验证

正常下单

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

手动制造超时异常,当库存和账户金额扣减后,订单状态没有设置为已完成(没有从零变为一),由于feign的重试机制,账户余额还有可能被多次扣减

@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDao orderDao;

    @Resource
    private StorageService storageService;

    @Resource
    private AccountService accountService;

    @Override
    @GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
    public void create(Order order) {
        // 1. 新建订单
        log.info("*****************开始新建订单");
        orderDao.create(order);

        // 2. 扣减库存
        log.info("*****************订单微服务开始调用库存扣减");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("*****************订单微服务调用库存扣减结束");

        // 3.扣减账户
        log.info("*****************订单微服务开始调用账户扣减");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("*****************订单微服务开始调用账户结束");

        // 4. 修改状态
        log.info("*****************开始修改订单状态");
        orderDao.update(order.getUserId(), 0);
        log.info("*****************修改订单状态结束");

        log.info("*****************成功下单!!!");

    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[阶段4 企业开发进阶] 7. 微服务--SpringCloud Alibaba 的相关文章

  • 什么是RAID存储技术

    RAID是廉价磁盘冗余阵列的缩写 但随着世界变得数字化 效率和成功达到顶峰 它被重命名为独立磁盘冗余阵列 负责以完整的协议存储您的宝贵数据 并为您提供具有一般或传统存储设备可能无法实现的容错功能 为什么要使用RAID 当 RAID 领域的工
  • 如何检查您的业务网络是否防御不力

    Summary 您最后一次检查业务网络是否存在缺陷和漏洞是什么时候 如果时间太长 现在可能就有危险了 请继续阅读以了解执行快速测试的方法 Details 如今 任何组织 无论大小 都无法逃脱黑客的攻击 如果您拥有一家企业 就不能没有万无一失
  • Java程序检查回文数

    A 回文数是一个即使数字顺序颠倒也保持不变的数字 例如 121 12321 1001等都是回文数的例子 在本文中 我们将探讨如何使用 Java 程序检查数字是否为回文数 概念概述 为了确定一个数字是否是回文 我们将原始数字与其相反的版本进行
  • 了解 Terraform 提供商

    Terraform 是 HashiCorp 开发的开源基础设施即代码软件 使用户能够使用声明性配置语言定义和提供数据中心基础设施 其运作机制的关键是概念 提供者 它们是 Terraform 基于插件的架构的组成部分 在本文中 我们将探讨 T

随机推荐

  • 什么是本地主机?

    在计算机网络中 术语 本地主机 指当前用于访问它的设备 它用于通过环回网络接口访问主机上运行的网络服务 使用环回接口绕过任何本地网络接口硬件 期限 本地主机 是由单词的组合衍生而来 local and host 这个单词 local 指的是
  • 如何通过 SSH 使用 Nagios 监控远程 Linux 系统

    NRPE是使用 Nagios 服务器监控远程 Linux 系统的最流行方法 但在某些情况下 我们不想在远程系统上安装 NRPE 或者无法安装它 在这种情况下 几乎没有其他方法可以监视远程系统 在本教程中 我们将描述通过 ssh 检查 met
  • 如何在 Ubuntu 22.04 上安装 Anaconda

    Anaconda 是一个用于 R 编程和 Python 的开源平台 其中包含各种各样的包和存储库 它的功能很重要 因为它提供大规模的处理和计算数据 并且还可以用Python语言进行编程 Anaconda 为 python 应用程序提供了一个
  • 如何在 Ubuntu 20.04 上安装 Docker Compose

    Docker compose 是管理多个 docker 容器的有用工具 它帮助我们使用单个命令启动 更新和构建一组 docker 容器 在多容器应用程序的情况下 docker compose帮助我们更轻松地管理它 本教程帮助您在 Ubunt
  • 构建第一个 RPM 包的初学者指南

    如果您正在开始 Linux 世界的旅程并选择了红帽生态系统 那么您需要掌握的基本技能之一就是创建 RPM 软件包 本指南将逐步引导您完成整个过程 帮助您了解 RPM 打包以及如何构建自己的 RPM 包 即使作为初学者也是如此 什么是 RPM
  • Linux 中的 Pstree 命令

    在 Linux 计算机上工作时 有时您可能需要了解当前正在运行哪些进程 您可以使用许多命令来查找有关正在运行的进程的信息 其中ps and top是最常用的 在这篇文章中 我们将讨论pstree命令 它类似于ps 但它不是列出正在运行的进程
  • 如何在 Ubuntu 18.04 上安装 Asterisk

    Asterisk 是最流行和广泛采用的开源 PBX 平台 为 IP PBX 系统 会议服务器和 VoIP 网关提供支持 它被世界各地的个人 小型企业 大型企业和政府使用 Asterisk 功能包括语音邮件 等待音乐 电话会议 呼叫排队 通话
  • 如何在 Ubuntu 20.04 上安装 PHP 8

    PHP 是最广泛使用的服务器端编程语言之一 许多流行的 CMS 和框架 例如 WordPress Magento 和 Laravel 都是用 PHP 编写的 PHP 8 0 是 PHP 语言的最新主要版本 它引入了一些重大变化 性能改进以及
  • 如何在 Debian 9 上安装 MariaDB

    MariaDB 是一个开源 多线程关系数据库管理系统 向后兼容 MySQL 的替代品 它由以下机构维护和开发MariaDB基金会包括MySQL的一些原始开发人员 随着 Debian 9 的发布 MySQL 被 MariaDB 取代作为默认数
  • 如何在 Ubuntu 18.04 上安装 R

    R 是一种快速发展的开源编程语言和免费环境 专门从事统计计算和图形表示 它由 R 统计计算基金会支持 主要供统计学家和数据挖掘人员用于开发统计软件和执行数据分析 本教程将指导您完成在 Ubuntu 18 04 计算机上安装 R 的步骤 先决
  • 如何在 Linux 中复制文件和目录

    复制文件和目录是使用命令行时最常见的任务之一 Linux 中有多种用于复制文件的命令 其中cp and rsync是使用最广泛的工具 通常的做法是使用cp复制文件的命令和rsync复制目录 为了能够复制文件和目录 您必须至少具有源文件的读取
  • 如何在 Ubuntu 18.04 上安装和使用 Docker Compose

    Docker 组合是一个允许您定义和管理多容器 Docker 应用程序的工具 它使用 YAML 文件来配置应用程序的服务 网络和卷 Compose 可用于不同的目的 单主机应用程序部署 自动化测试和本地开发是 Docker Compose
  • 如何设置无密码 SSH 登录

    Secure Shell SSH 是一种加密网络协议 用于客户端和服务器之间的安全连接 支持多种身份验证机制 两种最流行的机制是基于密码的身份验证和基于公钥的身份验证 在本教程中 我们将向您展示如何设置基于 SSH 密钥的身份验证以及如何在
  • 如何在 Ubuntu 20.04 上安装 Visual Studio Code

    视觉工作室代码是微软开发的一款功能强大的开源代码编辑器 它具有内置的调试支持 嵌入式Git控制 语法突出显示 代码完成 集成终端 代码重构和片段 Visual Studio Code 是跨平台的 可在 Windows Linux 和 mac
  • 如何在 Debian 10 Linux 上安装 Webmin

    Webmin是一个用于管理 Linux 服务器的开源 Web 控制面板 它允许您管理系统用户 组 磁盘配额以及安装和配置 Web ssh ftp 电子邮件和数据库服务器 使用 Webmin 您几乎可以配置系统的每个方面 在本教程中 我们将向
  • 如何在 Ubuntu 18.04 上安装 Kvm

    KVM 基于内核的虚拟机 是内置于 Linux 内核中的开源虚拟化技术 它允许您运行多个基于 Linux 或 Windows 的隔离来宾虚拟机 每个来宾都有自己的操作系统和专用虚拟硬件 例如 CPU 内存 网络接口和存储 本指南介绍如何在
  • 如何在 Debian 10 上安装 Elasticsearch

    Elasticsearch 是一个开源分布式全文搜索和分析引擎 它支持 RESTful 操作 允许您实时存储 搜索和分析大量数据 Elasticsearch 是最流行的搜索引擎之一 为具有复杂搜索要求的应用程序 例如大型电子商务商店和分析应
  • (see DUPEFILTER_DEBUG to show all duplicates)

    出现 see DUPEFILTER DEBUG to show all duplicates 的原因 在爬虫出现了重复的链接 重复的请求 解决方法 在request添加dont filter True
  • 常见反爬虫方法以及怎样突破

    大家可能不知道 互联网中超过一半的流量是网络爬虫贡献的 若是网站不设置反爬虫机制 可能根本没法运营 于是都设置了各种各样的反爬虫机制 即使如此 网络爬虫还是有办法去突破 今天小编为大家介绍一些常见的反网络爬虫以及突破方法 1 动态页面限制
  • [阶段4 企业开发进阶] 7. 微服务--SpringCloud Alibaba

    文章目录 1 服务注册和配置中心Nacos 1 1 Nacos简介 1 2 Nacos作为服务注册中心 服务提供者注册 服务消费者注册和负载 服务注册中心对比 1 3 Nacos作为服务配置中心 基础配置 分类配置 DataID方案配置 G