Java技术栈 —— Redis的雪崩、穿透与击穿

Java技术栈 —— Redis的雪崩、穿透与击穿

  • 〇、实验的先导条件(Nginx+Jmeter)
  • 一、Redis缓存雪崩、缓存穿透、缓存击穿
    • 1.1 雪崩
    • 1.2 穿透
    • 1.3 击穿
  • 二、Redis应用场景——高并发
    • 2.1 单机部署的高并发问题与解决(JVM级别锁)
    • 2.2 集群部署的高并发问题与解决(分布式锁)
      • 2.2.1 代码1(存在非原子操作与释放问题)
      • 2.2.2 代码2(finally块中,存在释放其它线程锁的可能性)
      • 2.2.3 代码3(redisson)
        • 2.2.3.1 Java中嵌入Lua脚本
      • 2.2.4 对代码3的性能优化、redis主从架构锁失效问题的解决方案
        • 2.2.4.1 性能优化的解决(分段锁,重要)
        • 2.2.4.2 主从架构锁失效问题的解决
            • 2.2.4.2.1 zookeeper
            • 2.2.4.2.2 redis的RedLock
  • 三、Redis与数据库的数据一致性

〇、实验的先导条件(Nginx+Jmeter)

首先你需要掌握Nginx负载均衡与Jmeter压测工具,搭建过程与使用方式,见参考文章。

参考文章或视频链接
[1] 《Java技术栈 —— Nginx的使用》
[2] 2 ways to install Apache JMeter on Ubuntu 22.04 LTS Linux

一、Redis缓存雪崩、缓存穿透、缓存击穿

关于雪崩、穿透与击穿的原理,可以先看本节的参考文章[1],代码以后再写到文章中。

1.1 雪崩

1.2 穿透

1.3 击穿

一、参考文章或视频链接
[1] 【什么是Redis缓存雪崩、穿透、击穿,十分钟给你讲的明明白白】- bilibili

二、Redis应用场景——高并发

高并发导致的问题,本质就是资源争抢。 在操作系统中,这类问题的雏形有哲学家用餐问题、进程争夺计算资源,相关解决机制有信号量机制,所以道理都是相通的,高并发在计算机领域并不是什么新鲜事,只是落地到应用场景,会有一些其它考量。就像古代兵符印信,或是倚天屠龙记中说的“武林至尊,宝刀屠龙,号令天下,莫敢不从!倚天不出,谁与争锋?”,听谁的问题的解决方法啊,就是象征物在谁手上就听谁的,包括抢职位争权力,也可以理解为一种并发,谁坐到了那个位置,才有号令的权力,但是权力是致命毒药,要小心哦!

首先导入jedis依赖,从而可以用java程序包操纵redis,以下是完整依赖。

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jdbc</artifactId>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>8.0.28</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</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-data-redis</artifactId>
	</dependency>
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>3.0.3</version>
	</dependency>
	<!--实现分布式锁redisson-->
	<dependency>
		<groupId>org.redisson</groupId>
		<artifactId>redisson</artifactId>
		<version>3.6.5</version>
	</dependency>
<!-- 也可以手动引入Jedis,不用SpringBoot提供的spring-boot-starter-data-redis-->
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>5.1.0</version>
	</dependency>

<!--如果你导入了下面的SpringBoot父依赖,会自带Jedis,不过版本不一定最新而已,并且有些-->
<!--	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.1</version>
		<relativePath/> 
	</parent>
	用SpringBoot提供的Jedis版本
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
	</dependency> -->

然后,我们开始复现高并发问题。首先是假设你已经搭建了一个简单的SpringBoot项目架构,并且相关的Nginx配置也已配置好,可以看 参考文章[5] 《Java技术栈 —— Nginx的使用》第3.1节,那正是我为本文而写,项目demo搭好了,port号也初步定为9998

二、参考文章或视频链接
[1] Java guide(Jedis) - Redis Offical Website
[2] Intro to Jedis – the Java Redis Client Library
[3] Redis可视化工具 RedisInsight | The best Redis GUI
[4] 示例代码来源,图灵诸葛老师,讲的确实很好: 【这可能是目前讲的最好的Redis高并发架构教程,堪称Redis架构实战的天花板!】
[5] 《Java技术栈 —— Nginx的使用》

2.1 单机部署的高并发问题与解决(JVM级别锁)

(1)先在redis中设置缓存好一个键值对,键的名字为store,这是我们要高并发的对象。

$ redis-cli
127.0.0.1:6379>	SETNX store 2000
127.0.0.1:6379>	get store
"2000"

(2)写一段操作redis获取store值的代码,完整项目代码最后会附上开源地址。

@RestController
public class demoController {
    public static int count = 0;

    @RequestMapping("deduct_stock_then_get_stock")
    public Integer deductStock(){
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        int currentStock = Integer.parseInt(jedis.get("stock"));
        if (currentStock > 0){
            currentStock--;
            jedis.set("stock", String.valueOf(currentStock));
            System.out.println("扣减成功,剩余库存"+currentStock);
        }else{
            System.out.println("扣减失败,库存不足");
        }

        return currentStock;
    }
}

启动项目并访问http://127.0.0.1:9998/deduct_stock_then_get_stock,让我们先看看效果,慢慢迭代,好的,现在浏览器上已经返回了当前库存数量,显示是199不要在意,这个数字随时可以在redis中修改。

然后我们用Jmeter,模拟多个用户同时访问 http://127.0.0.1:9998/deduct_stock_then_get_stock,上面这段Java代码会出什么问题呢?简单来说,就是会出现超卖问题。按下面的过程配置,并点击绿色的启动箭头在这里插入图片描述,就开启了压测。

在这里插入图片描述在这里插入图片描述在这里插入图片描述
这是控制台输出的结果,果然,出现了超卖问题,这说明会有多个用户都看到了相同的1999库存,很明显是有问题的,这是因为多个用户同时进入了相同段代码的执行过程,并且都拿到了一个currentStock变量作为副本,而这个变量在获取的时候出现了值相同的情况。

@RestController
public class demoController { //方法(2)以函数为单位上锁,写成 public synchronized Integer deductStock(){

    @RequestMapping("deduct_stock_then_get_stock")
    public Integer deductStock(){
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        synchronized (this){ //方法(1)以对象为单位上锁
            int currentStock = Integer.parseInt(jedis.get("stock")); //上一段未加synchronized的代码,问题出在这里,都获取到了一样的值,那么再进行currentStock--,就是1999了
            if (currentStock > 0){
                currentStock--;
                jedis.set("stock", String.valueOf(currentStock));
                System.out.println("扣减成功,剩余库存"+currentStock);
            }else{
                System.out.println("扣减失败,库存不足");
            }

            return currentStock;
        }
    }
    
}

只加了一个锁,问题解决,那么到目前为止,单机部署的高并发问题,可以算解决了,如果集群部署的话,上面这段代码还有用吗?

2.2 集群部署的高并发问题与解决(分布式锁)

根据参考视频[4]所说,上面的代码也只是解决了单机部署下的高并发问题,如果是集群部署,启动了多个服务分别部署在不同机器上呢?这个时候Nginx会分发请求到不同服务实例上,还会出现上面的超卖现象吗?答案是会的,这相当于线程A在服务A上执行扣库存,线程B在服务B上执行扣库存,这两个线程压根不归同一个JVM虚拟机进程管,是没办法用上面的加synchronized关键字去限制的,具体可以看视频讲解。但是,只要思想不滑坡,办法总比困难多,请看。PS:你能想象,其实12306是全世界最能抗高并发的软件吗?总有些东西在微不足道的角落里熠熠生辉,独自发热。
还是刚刚那段,在单机部署上解决了高并发问题的代码,我们来多启动一个服务,只是端口不同。

由于在参考文章[5]中,我已经配置了Nginx,所以我们的Jmeter测试地址,应该改为http://127.0.0.1:8011/deduct_stock_then_get_stock,看下面的两张截图,和视频[5]里说的一样,确实在集群部署时会出现超卖问题。

下面是加上分布式锁的解决方法, 但是仍然存在问题。

2.2.1 代码1(存在非原子操作与释放问题)

@RestController
public class demoController {

    @RequestMapping("deduct_stock_then_get_stock")
    public Integer deductStock(){

        String lockKey = "product_100";
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        long result = jedis.setnx(lockKey,"xxx"); // 获取分布式锁
        if(result == 0){
            System.out.println("争抢分布式锁失败"); 
            /*
            注意,这里实际使用会有问题,不应该return,只是作为示例
            争抢分布式锁失败的话也应该程门立雪,三顾茅庐,不可半途而返,
            半途而返会导致许多业务请求被扼杀
            */ 
            return 500;                          
        }
        
        //*****重要思维*****
        //业务逻辑,可能出异常,导致分布式锁无法释放,永远要考虑系统的业务逻辑被某种不可抗力因素停止,不管是运维还是什么,程序要具备健壮性。
        int currentStock = Integer.parseInt(jedis.get("stock"));
        if (currentStock > 0) {
            currentStock--;
            jedis.set("stock", String.valueOf(currentStock));
            System.out.println("扣减成功,剩余库存" + currentStock);
        } else {
            System.out.println("扣减失败,库存不足");
        }

        jedis.del(lockKey);  //释放分布式锁
        return currentStock;
    }
    
}

2.2.2 代码2(finally块中,存在释放其它线程锁的可能性)

下面的代码对上面的代码做了两处改进:
(1)将获取与设置超时时间这两步,组合成原子操作,不可分离。
(2)增加clientID,保证释放的是自己加的锁,但在释放仍旧可能存在问题,视频中提到用redisson进行解决,见 redisson - github wiki,redisson与jedis区别在于,jedis只是提供一些原生命令的实现,redisson可以提供分布式锁的实现能力。

@RequestMapping("deduct_stock_then_get_stock")
public Integer deductStock(){ //集群版
    //(1)获得分布式锁
    String lockKey = "product_100";
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    String clientID = UUID.randomUUID().toString(); //唯一ID,加锁人的身份

//        String result = jedis.setex(lockKey, 10, clientID); //该命令是原子命令,将获取与设置超时时间这两步,组合成原子操作,不可分离,但还是存在问题,如业务逻辑执行较慢,锁已经超时释放了业务逻辑还没执行完,又导致了并发
    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientID, 10, TimeUnit.SECONDS);  //该命令是原子命令,将获取与设置超时时间这两步,组合成原子操作,不可分离,但还是存在问题,如业务逻辑执行较慢,锁已经超时释放了业务逻辑还没执行完,又导致了并发
    stringRedisTemplate.opsForValue().get(lockKey);
    if(result == Boolean.FALSE){
        System.out.println("争抢分布式锁失败");  // 分布式锁争抢失败应该等待,而不应该直接return
        return 500;
    }

    try{
        //*****重要思维*****
        //(2)执行业务逻辑,可能出异常,导致分布式锁无法释放,永远要考虑系统的业务逻辑被某种不可抗力因素停止,不管是运维还是什么,要具备健壮性。
        //此处可能存在的异常有:
        // (2.1)业务逻辑执行失败,但finally可以正常释放分布式锁
        // (2.2)应用被重启,连finally都无法执行,那么就需要令分布式锁自动过期
        int currentStock = Integer.parseInt(jedis.get("stock"));
        if (currentStock > 0) {
            currentStock--;
            jedis.set("stock", String.valueOf(currentStock));
            System.out.println("扣减成功,剩余库存" + currentStock);
        } else {
            System.out.println("扣减失败,库存不足");
        }
        return currentStock;
    }finally{
        //(3)出异常时释放分布式锁,这里释放分布式锁可能存在问题
        if (clientID.equals(jedis.get(lockKey))){

            //自己加的锁才能释放,中间还可能存在执行时间的间隔,开一个分线程,将分布式锁加时,检测这把分布式锁还是否加载在该主线程中,加时到直到业务逻辑执行完成为止

            jedis.del(lockKey);
        }
    }

}

2.2.3 代码3(redisson)

@RequestMapping("deduct_stock_then_get_stock_cluster_redisson")
public Integer deductStock3(){ //集群+redisson版
    //(1)获得分布式锁
    String lockKey = "product_100";
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    RLock redissonLock = redisson.getLock(lockKey); //获取RLock对象

    try{
        redissonLock.lock(); //(2)上锁,底层调用redis命令时用到了lua脚本
        //(3)业务逻辑
        int currentStock = Integer.parseInt(jedis.get("stock"));
        if (currentStock > 0) {
            currentStock--;
            jedis.set("stock", String.valueOf(currentStock));
            System.out.println("扣减成功,剩余库存" + currentStock);
        } else {
            System.out.println("扣减失败,库存不足");
        }
        return currentStock;
    }finally{
        //(4)释放锁
        redissonLock.unlock();
    }
}

redisson是一种Redis Java client,上述redisson的使用方法,也是大厂在生产环境会用到的,但上面的代码还有两个问题:
(1)性能问题,虽然没有超卖,但会导致系统性能问题,需要开始性能优化。
(2)redis主从架构下,锁失效问题。比如Master同步给Slave分布式锁时,Master正好挂掉,然后重新选举的Master正好没有同步到这把锁,就失效了。

2.2.3 参考文章或视频链接
[1] 1. Overview of Redisson - GitHub
2.2.3.1 Java中嵌入Lua脚本

什么是Lua脚本?我第一次听说Lua,是在敖丙解说B站出事那次,最后定位到一段Lua写的gcd()代码,久闻大名却未上手实操过。请看本节参考文章[1]。

2.2.3.1 参考文章或视频链接
[1] Lua:about - Offical Website

2.2.4 对代码3的性能优化、redis主从架构锁失效问题的解决方案

2.2.4.1 性能优化的解决(分段锁,重要)

先了解下并发编程集合类ConcurrentHashMap,这是一个高并发的Java集合类且线程安全,其保证线程安全的原理是,使用分段锁。受此启发,性能优化也可以用分段加锁,每个线程去不同的段位请求锁即可。

2.2.4.1 参考文章或视频链接
[1] 《详解ConcurrentHashMap》- CSDN
2.2.4.2 主从架构锁失效问题的解决

CAP原则:zookeeper是CP架构,重在维持数据一致性;redis是AP架构,重在可用性。

2.2.4.2.1 zookeeper

使用zookeeper,zookeeper解决主从架构锁失效问题更合适,但会牺牲一点性能。

2.2.4.2.1 参考文章或视频链接
[1] What is Apache ZooKeeper?
[2] Welcome to Apache ZooKeeper
[3] 《2.0 Zookeeper 安装配置》- 菜鸟
[4] 《zookeeper快速入门一:zookeeper安装与启动》
2.2.4.2.2 redis的RedLock

要超过半数redis节点加锁成功才算成功,这样的原理又回到了zookeeper,还是会损失加锁的性能,所以RedLock实现的是否完善依旧存在争议。
在这里插入图片描述

三、Redis与数据库的数据一致性

(1)要保证的是数据的最终一致性,而不是强一致性,若要保证数据强一致性会损失性能,这违背了使用Redis的初衷。
(2)删除Redis缓存,而不是更新Redis缓存。
(3)先更新数据库数据。

三、 参考文章或视频链接
[1] 《字节二面:redis如何保证缓存和数据库的一致性》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/284116.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构:基于数组的环形队列(循环队列)实现

1 前言 队列是一种先进先出的线性表&#xff0c;简称为FIFO。它只允许在队尾插入成员&#xff0c;在队头删除成员&#xff0c;就像现实生活中排队上车一样。 队列的实现可以通过链表或数组完成&#xff0c;一般来说都推荐使用链表来实现队列&#xff0c;使用数组实现队列时每次…

SpreadJS 集成使用案例

SpreadJS 集成案例 介绍&#xff1a; SpreadJS 基于 HTML5 标准&#xff0c;支持跨平台开发和集成&#xff0c;支持所有主流浏览器&#xff0c;无需预装任何插件或第三方组件&#xff0c;以原生的方式嵌入各类应用&#xff0c;可以与各类后端技术框架相结合。SpreadJS 以 纯前…

Java日期和时间(二)

新增的日期和时间 为什么要学习新增的日期和时间 1、代替Calendar LocalDate&#xff1a;年、月、日 LocalTime&#xff1a;时、分、秒 LocalDateTime&#xff1a;年、月、日、时、分、秒 ZoneId&#xff1a;时区 ZoneldDatetime&#xff1a;带时区的时间 2、代替Date Instan…

使用flutter开发一个简单的轮播图带指示器的组件

使用PageView开发一个带指示器的轮播图组件&#xff0c;当轮播图切换的时候&#xff0c;指示器也会跟着切换&#xff0c;切换到当前轮播图所在的索引时&#xff0c;指示器的背景色会变成蓝色&#xff0c;否则是灰色。使用了一个curIndex变量来记录当前激活的轮播图索引。并使用…

HarmonyOS资源分类与访问

资源分类与访问 应用开发过程中&#xff0c;经常需要用到颜色、字体、间距、图片等资源&#xff0c;在不同的设备或配置中&#xff0c;这些资源的值可能不同。 应用资源&#xff1a;借助资源文件能力&#xff0c;开发者在应用中自定义资源&#xff0c;自行管理这些资源在不同…

【INTEL(ALTERA)】如何使用quartus设计助理Design Assistant提高结果质量,很好的资料一定要分享!!!

大家在用quartus的时候一定遇到过超级多的警告 warning&#xff0c;甚至异常 error&#xff0c;还有无从下手的timing 。 多扇出&#xff0c;布线拥堵&#xff0c;时序违例是不是让你头疼不已&#xff1f;那你一定要看看这篇文章分享的文档和资料。 优化设计的源代码通常是提高…

CMake入门教程【基础篇】什么是CMakeLists.txt

文章目录 什么是CMakeLists.txtCMakeLists.txt的核心作用CMakeLists.txt的基本结构总结 什么是CMakeLists.txt CMakeLists.txt是一个由CMake&#xff08;一个跨平台的自动化构建系统&#xff09;使用的配置文件。这个文件用于定义软件构建的过程&#xff0c;包括编译源代码、链…

wait 和 notify 这个为什么要在synchronized 代码块中

文章目录 wait 和 notify 这个为什么要在synchronized 代码块中&#xff1f; wait 和 notify 这个为什么要在synchronized 代码块中&#xff1f; wait 和 notify 用来实现多线程之间的协调&#xff0c;wait 表示让线程进入到阻塞状态&#xff0c;notify 表示让阻塞的线程唤醒。…

Vue3+Echarts(柱状图):点击不同的按钮可以切换不同年份的数据

一、需求 在Vue3项目中&#xff0c;绘制一个柱状图&#xff1a; 柱状图会展示某一年里四个季度的销售额提供2个按钮选项&#xff0c;点击不同的按钮可以切换到不同年份的销售额&#xff0c;这里的年份指2022年以及2023年目标效果如下&#xff1a; 默认展示的是2023年的数据&a…

spring 之 事务

1、JdbcTemplate Spring 框架对 JDBC 进行封装&#xff0c;使用 JdbcTemplate 方便实现对数据库操作 1.1 准备工作 ①搭建子模块 搭建子模块&#xff1a;spring-jdbc-tx ②加入依赖 <dependencies><!--spring jdbc Spring 持久化层支持jar包--><dependency&…

病情聊天机器人,利用Neo4j图数据库和Elasticsearch全文搜索引擎相结合

项目设计目的&#xff1a; 本项目旨在开发一个病情聊天机器人&#xff0c;利用Neo4j图数据库和Elasticsearch全文搜索引擎相结合&#xff0c;实现对病情相关数据的存储、查询和自动回答。通过与用户的交互&#xff0c;机器人可以根据用户提供的症状描述&#xff0c;给出初步的可…

论虚继承的作用

虚继承 实验介绍 在上一小节中学习了多继承与多重继承,实际在开发的时候可能会遇到一种情况,既用到了多继承又用到了多重继承,这种继承方式通常又称为菱形继承。但这样一来就会产生新的问题,过多消耗空间。希望通过本小节学习能知道菱形继承以及产生的问题和解决方式。 …

【网络面试(3)】浏览器委托协议栈完成消息的收发

前面的博客中&#xff0c;提到过很多次&#xff0c;浏览器作为应用程序&#xff0c;本身是不具备向网络中发送网络请求的能力&#xff0c;要委托操作系统的内核协议栈来完成。协议栈再调用网卡驱动&#xff0c;通过网卡将请求消息发送出去&#xff0c;本篇博客就来探讨一下这个…

给零基础朋友的编程课09 上集 - 代码

给零基础朋友的编程课09 上 - 矩形、曲线、文字、案例5讲解 上_哔哩哔哩_bilibili 上半Code: / // 彩色案例 艺术仿制品4 // /// 色表 // // 238,150,43 橙 // 229,207,192 暖灰 // 204,50,47 暗红// 项目设定 size(825, 984); // 设置画布(窗口)尺寸 background(…

计算机网络——基础知识汇总(八)

前言&#xff1a; 前面我们已经将计算机网络的基础知识和基础框架有了一个简单的学习与了解&#xff0c;也对它可能考的一些计算机网络计算大题有了一个详细的解答与记录&#xff0c;现在我们将计算机网络中的一些基础知识点进行一个总结与记录&#xff0c;这些基础知识大多会出…

C#编程-编写和执行C#程序

编写和执行C#程序 可以使用Windows记事本应用程序来编写C#程序。在记事本应用程序中创建C#程序后,您需要编译并执行该程序以获得所需的输出。编译器将程序的源代码转换为机器代码,这样计算机就能理解程序中的指令了。 注释 除了记事本,您还可以使用任何其他文本编辑器来编写…

托管在亚马逊云科技的向量数据库MyScale如何借助AWS基础设施构建稳定高效的云数据库

MyScale是一款完全托管于亚马逊云科技&#xff0c;支持SQL的高效向量数据库。MyScale的优势在于&#xff0c;它在提供与专用向量数据库相匹敌甚至优于的性能的同时&#xff0c;还支持完整的SQL语法。以下内容&#xff0c;将阐述MyScale是如何借助亚马逊云科技的基础设施&#x…

ubuntu 20.04 自由切换 python 的版本

问题描述 当前 ubuntu 20.04 默认安装了多个 python 的版本&#xff0c;执行 python 时&#xff0c;默认版本是 Python 2.7.18 zhangszzhangsz:~$ python Python 2.7.18 (default, Jul 1 2022, 12:27:04) [GCC 9.4.0] on linux2 Type "help", "copyright&quo…

AI时代系列丛书(由北京大学出版社出版)

前言 在AI时代&#xff0c;程序员面临着新的机遇和挑战。为了适应这个快速发展的时代&#xff0c;掌握新技能并采取相应的应对策略是至关重要的。 对于办公人员或程序员来说&#xff0c;利用AI可以提高工作效率。例如&#xff0c;使用AI助手可以帮助自动化日常的重复性工作&a…

【flink番外篇】9、Flink Table API 支持的操作示例(8)- 时态表的join(scala版本)

Flink 系列文章 一、Flink 专栏 Flink 专栏系统介绍某一知识点&#xff0c;并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分&#xff0c;比如术语、架构、编程模型、编程指南、基本的…