11 Redis之高并发问题(读+写) + 缓存预热+分布式锁

news2024/11/21 2:37:30

8. 高并发问题

Redis做缓存虽减轻了DBMS的压力,减小了RT(Response Time),但在高并发情况下也是可能会出现各种问题的。

8.1 缓存穿透

当用户访问的数据既不在数据库中也不在缓存中,如id为“-1”的数据或id为特别大不存在的数据, 这时的用户很可能是攻击者,攻击会导致数据库压力过大。就会导致每个用户查询都会“穿透”缓存“直抵”数据库。这种情况就称为缓存穿透。
当高度发的访问请求到达时,缓存穿透不仅增加了响应时间,而且还会引发对DBMS的高并发查询,这种高并发查询很可能会导致DBMS的崩溃(对DBMS做的负载均衡暂且不提)。

缓存穿透产生的主要原因有两个:

  • 一是在数据库中没有相应的查询结果,
  • 二是查询结果为空时,不对查询结果进行缓存。

所以,针对以上两点,解决方案也有两个:

  • 对非法请求进行限制,例如限制查询的范围
    a. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
    b. 使用布隆过滤器,需要安装redis组件
    c. 使用布谷鸟滤器,布谷鸟过滤器是布隆过滤器的升级版,需要安装redis组件

  • 对数据库中查询结果也为空的查询给出默认值, 并且把这个键值对的缓存有效时间可以设置短一些,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

8.2 缓存击穿

关键词:定点打击

试想如果所有请求对着一个 key 照死里搞,这是不是就是一种定点打击呢?

怎么理解呢?举个极端的例子:比如某某明星爆出一个惊天狠料,海量吃瓜群众同时访问微博去查看该八卦新闻,而微博 Redis 集群中数据在此刻正好过期了,那么无数的请求则直接打到了微博系统的物理 DB 上,DB 瞬间挂了。

缓存击穿指的就是某key长期有大量请求,但某一瞬间却过期了,那么程序在redis找不到数据,就会去数据库里查询,数据库处理大量的请求的同时导致压力瞬间增大,甚至导致崩溃.
这种情况称为缓存击穿,而该缓存数据称为热点数据。

解决方案:

  • 设置key值永不过期
  • 将key的过期时间设为随机
  • 使用布隆过滤器或者布谷鸟过滤器
  • 使用分布式锁,当多个key过期时,同一时间只有一个查询请求下发到数据库,其他的key等待一个个地轮流查,就可以避免数据库压力过大的问题;代码如下:
   // 分布式锁,为了可读性高用 ReentrantLock 代替分布式锁
    static Lock lock = new ReentrantLock();
    
    public String getData(String key ) throws InterruptedException {
        try {
            // 从redis获取值
            String data =  getRedisData(key);
            // 如果key不存在,从数据库查询
            if(null  == data){
                // 尝试获取锁
                if(!lock.tryLock()){
                   // 获取锁失败 ,100ms后在次尝试
                    TimeUnit.MILLISECONDS.sleep(100);
                    data = getData(key);
                }
                // 走到这里表示成功获取锁

                // 从myqsl中获取锁
                data = getMysqlData(key);

                // 将数据更新到redis
                setDataToRedis(key,value);
            }
            return data;
        } catch (Exception e){
            e.printStackTrace();
            throw e;
        } finally {
            // 解锁
            lock.unlock();
        }
    }
  • 双重检测锁机制

8.3.1 穿透和击穿的区别

关于穿透和击穿的区别上面已经介绍的很清楚了,这里在做个总结

  • 穿透 :大量请求了缓存和数据库中都没有的数据,每次都查询数据库,导致数据库压力过大
  • 击穿 : 热点key在同一时间过期,导致所有请求都达到数据库,导致数据库压力过大

8.3 缓存雪崩

关键词:Redis 崩了,没有数据了

这里的 Redis 崩了指的并不是 Redis 集群宕机了。而是说在某个时刻 Redis 集群中的热点 key 都失效了。
如果集群中的热点 key 在某一时刻同时失效了的话,试想海量的请求都将直接打到 DB 上,DB 可能在瞬间就被打爆了,一旦DB崩了,它所带来的连锁反应是可怕的,数据库不可用的情况下你的服务器也无法使用;这就是雪崩效应。

对于缓存雪崩没有很直接的解决方案,最好的解决方案就是预防,即提前规划好缓存的过期时间。要么就是让缓存永久有效,当 DB 中数据发生变化时清除相应的缓存。

如果 DBMS采用的是分布式部署,则将热点数据均匀分布在不同数据库节点中,将可能到来的访问负载均衡开来。

8.4 数据库缓存双写不一致

以上三种情况都是针对高并发读场景中可能会出现的问题,

而在高并发写场景下 , 则可能出现数据库缓存双写不一致的问题

对于数据库缓存双写不一致问题,又分为两种

8.4.1 “修改 DB 并更新缓存”场景

若多个请求要对 DBMS 中同一个数据进行修改,修改后还需要更新缓存中相关数据,
那么程序的异步执行可能会导致缓存与数据库中数据不一致的情况
在这里插入图片描述

8.4.2 “修改 DB 并删除缓存”场景

若两个请求对 DBMS 中同一个数据的操作既包含写也包含读,
且修改后还要删除缓存中相关数据,那么程序的异步执行就可能导致缓存与数据库中数据不一致的情况。

在很多系统中是没有缓存预热 warmup 功能的,为了保持缓存与数据库数据的一致性,一般都是在对数据库执行了写操作后,就会删除相应缓存。

在这里插入图片描述

8.4.3 解决方案

8.4.3.1 延迟双删

延迟双删方案是专门针对于“修改 DB 并删除缓存”场景的解决方案。但该方案并不能彻底解决数据不一致的状况,其只可能降低发生数据不一致的概率。

延迟双删方案是指,在写操作完毕后会立即执行一次缓存的删除操作,然后再停上一段时间(一般为几秒)后再进行一次删除。而两次删除中间的间隔时长,要大于一次缓存写操作
在这里插入图片描述

8.4.3.2 队列

以上两种场景中,只所以会出现数据库与缓存中数据不一致,主要是因为对请求的处理出现了并行。

只要将请求写入到一个统一的队列,只有处理完一个请求后才可处理下一个请求,即让系统对用户请求的处理串行化,就可以完全解决数据不一致的问题。

例如使用ZooKeeper或分布式消息队列MQ

8.4.3.3 分布式锁

使用队列的串行化虽然可以解决数据库与缓存中数据不一致,但系统失去了并发性,降低了性能。

使用分布式锁可以在不影响并发性的前提下,协调各处理线程间的关系,使数据库与缓存中的数据达成一致性。

只需要对数据库中的这个共享数据的访问通过分布式锁来协调对其的操作访问即可


9. 分布式锁

在分布式环境下, 分布式锁大部分是由Lua实现的

9.1 分布式锁的工作原理

当有多个线程要访问某一个共享资源(DBMS 中的数据或 Redis 中的数据,或共享文件等)时,为了达到协调多个线程的同步访问,此时就需要使用分布式锁了。

为了达到同步访问的目的,规定,让这些线程在访问共享资源之前先要获取到一个令牌token,只有具有令牌的线程才可以访问共享资源。这个令牌就是通过各种技术实现的分布式锁。而这个分布锁是一种“互斥资源”,即只有一个。只要有线程抢到了锁,那么其它线程只能等待,直到锁被释放或等待超时。

9.2 问题引入

某电商平台要对商品 sk:0008 进行秒杀销售。假设参与秒杀的商品数量amount 为 1000 台,每个账户只允许抢购一台,即每个请求只会减少一台库存

9.2.1 SB实现

9.2.1.1 准备
  1. 添加spring-boot-starter-redis/web依赖
  2. 编写配置文件设置Redis的主机地址和端口号
    总之,在过去一年中,虽然我在各方面都取得了一些进步,但是离一名优秀共产党员的标准和要求还有一定差距,还存在一些缺点需要克服,主要体现在工作的主动性还不够、服务一线员工的意识还有待加强、思想认识还有待提高等。我相信,在以后的工作学习中,我一定会在党组织的关怀下,在各位党员及同事的帮助日下,通过自己的努力、采取有效措施克服缺点,不断积累经验,提高自身素质、增强工作能力,使自己真正成为一名能经受任何考验的共产党员。
    以上是自己一年来基本情况的小结,不妥之处,恳请党组织批评指正,作为一名预备党员,我渴望按期转为中共正式党员,请党组织考虑我的申请,我将虚心接受党组织对我的审查和考验!如果党组织批准我成为正式党员,我一定在党组织和广大群众的监督之下,牢记入党誓言,勤奋工作、刻苦学习,处处以党员标准严格要求自己,做一名合格的共产党员,如果党组织没有批准我成为正式党员,我也不会泄气,继续努力,争取早日成为一名中国共产党正式党员。
9.2.1.1 有问题的示例

这里仅编写一个controller

@RestController
public class Seckillcontroller {
	@Autowired
	StringRedisTemplate srt;

	@GetMapping("/sk")
	public string seckillHandler(){
		String stock =srt.opsForValue().get("sk:0008");
		int amount =stock == null ?0 : Integer.parseInt(stock);
		if(amount>0){
			srt.opsForValue().set("sk:0008",String.value0f(--amount));
			return "库存剩余"+ amount +"台";
		}
		return"抱歉,您没抢到";
}

上述代码是有问题的。既然是秒杀,那么一定是高并发场景,且生产环境下,该应用一定是部署在一个集群中的。如果参与秒杀的用户数量特别巨大,那么一定会存在很多用户同时读取 Redis 缓存中的 sk:0008 这个 key,那么大家读取到的 value 很可能是相同的,均大于零,均可购买。此时就会出现“超卖”。
即,以上代码存在并发问题。

9.2.1.2 SETNX修改

为了解决上述代码中的并发问题,可以使用 Redis 实现的分布式锁。

该实现方式主要是通过 SETNX 命令完成的。其基本原理是,SETNX 只有在指定 key 不存在时才能执行成功,分布式系统中的哪个节点抢先成功执行了 SETNX ,谁就抢到了锁,谁就拥有了对共享资源的操作权限。
与此同时,其它节点只能等待锁的释放。一旦拥有锁的节点对共享资源操作完毕,其就可以主动删除该 key,即释放锁。然后其它节点就可重新使用 SETNX 命令抢注该 key,即抢注锁

新建一个SeckillController类:

@RestController
public class SeckillController {
	// 分布式锁的key
	public static final String REDIS_LOCK = "redis_lock";
	
	@Autowired
	private stringRedisTemplate srt;
	
	@Value("${server.port}")
	private String serverPort;

	@GetMapping(©~"/sk2")
	public string seckillHandler2(){

	String result ="抱歉,您没抢到";
	
	//仅当
	try {
				//setIfAbsent实质就是SETNX, 仅当原键不存在时才能设置
				Boolean lockOK = srt.opsForValve().setIfAbsent(REDIS_LOCK, "I'm a Lock");
				if(!lockOK){
				return "没抢到锁哟";
				}
				
	String stock =srt.opsForValue().get("sk:0008");
	//如果stock为null, 即缓存中没获取到, 就将amount设为0, 宣告购买失败
	int amount = stock == null ? 0 : Integer.parseInt(stock);
	//因为每个人只买一件, 如果库存大于0, 则肯定能买到, 故将amount-1后写回缓存 
	if (amount >0){
			srt.opsForValue().set("sk:0008",String.value0f(--amount));
			result = "库存剩余"+ amount +"台";
			System.out.println(result);
	} 
} finally {
		srt.delete(REDIS_LOCK);
	}
	return result +"。server is "+ serverPort;
}

10. 缓存预热warmup

缓存预热指的是提前将热点数据加载到缓存中,这样当用户或系统开始请求这些数据时,它们已经可用,无需等待数据从慢速存储(如数据库)中检索。这有助于避免冷启动问题,提高系统的响应速度和吞吐量。

对于具有缓存 warmup 功能的系统,DBMS 中常用数据的变更,都会引发缓存中相关数据的更新。

Redis缓存预热的场景:

  • 系统重启或部署: 重新部署应用程序后,缓存可能会被清空,预热可以迅速恢复缓存状态。
  • 数据更新: 当缓存中的数据定期更新时,预热可以确保最新数据的快速可用性。
  • 流量高峰: 在预期流量高峰之前预热缓存,可以帮助系统更好地应对负载。

10.1 实现方案

在 Spring Boot 启动之后,可以通过以下四种方案实现缓存预热:

  • 使用启动监听事件实现缓存预热。
  • 使用 @PostConstruct 注解实现缓存预热。
  • 使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。
  • 通过实现 InitializingBean 接口,并重写 afterPropertiesSet 方法实现缓存预热。

10.1.1 使用启动监听事件实现缓存预热

① 启动监听事件

可以使用 ApplicationListener 监听 ContextRefreshedEvent 或 ApplicationReadyEvent 等应用上下文初始化完成事件,在这些事件触发后执行数据加载到缓存的操作,具体实现如下:

@Component
public class CacheWarmer implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 执行缓存预热业务...
        cacheManager.put("key", dataList);
    }
}

或监听 ApplicationReadyEvent 事件,如下代码所示:

@Component
public class CacheWarmer implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 执行缓存预热业务...
        cacheManager.put("key", dataList);
    }
}

② @PostConstruct 注解

在需要进行缓存预热的类上添加 @Component 注解,并在其方法中添加 @PostConstruct 注解和缓存预热的业务逻辑,具体实现代码如下:

@Component
public class CachePreloader {
    
    @Autowired
    private YourCacheManager cacheManager;

    @PostConstruct
    public void preloadCache() {
        // 执行缓存预热业务...
        cacheManager.put("key", dataList);
    }
}

③ CommandLineRunner或ApplicationRunner

CommandLineRunner 和 ApplicationRunner 都是 Spring Boot 应用程序启动后要执行的接口,它们都允许我们在应用启动后执行一些自定义的初始化逻辑,例如缓存预热。
CommandLineRunner 实现示例如下:

@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // 执行缓存预热业务...
        cacheManager.put("key", dataList);
    }
}

ApplicationRunner 实现示例如下:

@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 执行缓存预热业务...
        cacheManager.put("key", dataList);
    }
}

CommandLineRunner 和 ApplicationRunner 区别如下:

方法签名不同: 
    CommandLineRunner 接口有一个 run(String... args) 方法,它接收命令行参数作为可变长度字符串数组。
    ApplicationRunner 接口则提供了一个 run(ApplicationArguments args) 方法,它接收一个 ApplicationArguments 对象作为参数,这个对象提供了对传入的所有命令行参数(包括选项和非选项参数)的访问。
参数解析方式不同: 
    CommandLineRunner 接口更简单直接,适合处理简单的命令行参数。
    ApplicationRunner 接口提供了一种更强大的参数解析能力,可以通过 ApplicationArguments 获取详细的参数信息,比如获取选项参数及其值、非选项参数列表以及查询是否存在特定参数等。
使用场景不同: 
    当只需要处理一组简单的命令行参数时,可以使用 CommandLineRunner。
    对于需要精细控制和解析命令行参数的复杂场景,推荐使用 ApplicationRunner。

④ 实现InitializingBean接口

实现 InitializingBean 接口并重写 afterPropertiesSet 方法,可以在 Spring Bean 初始化完成后执行缓存预热,具体实现代码如下:

@Component
public class CachePreloader implements InitializingBean {
    @Autowired
    private YourCacheManager cacheManager;
    @Override
    public void afterPropertiesSet() throws Exception {
        // 执行缓存预热业务...
        cacheManager.put("key", dataList);
    }
}

小结

缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。它可以通过监听 ContextRefreshedEvent 或 ApplicationReadyEvent 启动事件,或使用 @PostConstruct 注解,或实现 CommandLineRunner 接口、ApplicationRunner 接口,和 InitializingBean 接口的方式来完成。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单晶银粉在光伏发电和电子电气领域需求旺盛 我国市场国产化进程有望加快

单晶银粉在光伏发电和电子电气领域需求旺盛 我国市场国产化进程有望加快 单晶银粉指以单晶形式存在的银材料。与普通银粉相比&#xff0c;单晶银粉具有化学稳定性好、光学透过率高、导电性佳、导热性好、易于加工、纯度高等优势&#xff0c;在光伏发电、电子电气等领域拥有广阔…

【Python笔记-设计模式】中介者模式

一、说明 中介者模式是一种行为设计模式&#xff0c;减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互&#xff0c;迫使它们通过一个中介者对象进行合作。 (一) 解决问题 降低系统中对象之间的直接通信&#xff0c;将复杂的交互转化为通过中介者进行的间接交…

Django配置静态文件

Django配置静态文件 目录 Django配置静态文件静态文件配置调用方法 一般我们将html文件都放在默认templates目录下 静态文件放在static目录下 static目录大致分为 js文件夹css文件夹img文件夹plugins文件夹 在浏览器输入url能够看到对应的静态资源&#xff0c;如果看不到说明…

4核8G服务器选阿里云还是腾讯云?价格性能对比

4核8G云服务器多少钱一年&#xff1f;阿里云ECS服务器u1价格955.58元一年&#xff0c;腾讯云轻量4核8G12M带宽价格是646元15个月&#xff0c;阿腾云atengyun.com整理4核8G云服务器价格表&#xff0c;包括一年费用和1个月收费明细&#xff1a; 云服务器4核8G配置收费价格 阿里…

Ubuntu上Jenkins自动化部署Gitee上SpringBoot项目

文章目录 安装安装JDK安装Maven安装GitNodeJS安装&#xff08;可选&#xff09;安装Jenkins 配置Jenkins为Jenkins更换插件源设置jenkins时区安装插件全局工具配置添加Gitee凭证Gitee项目配置 部署后端1.新建任务2.配置源码管理3.构建触发器4.到Gitee中添加WebHook5.构建环境6.…

nodejs 实现pdf与图片互转

PDF转图片 效果图 代码 const path require(path); const pdf require(pdf-poppler); const fs require(fs); // PDF文件路径 const pdfFilePath ./path/test.pdf; // 转换选项 const opts { format: png, // 输出图片格式&#xff0c;可以是 jpeg, png, ppm…

绿色蔬菜销售管理系统

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一 、设计说明 1.1 研究…

Java毕业设计-基于springboot开发的漫画之家系统-毕业论文+PPT(有源代码)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1.开发说明2.需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、后台模块3、用户功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开发的漫画之家系统-毕业论…

day03_登录注销(前端接入登录,异常处理, 图片验证码,获取用户信息接口,退出功能)

文章目录 1. 前端接入登录1.1 修改前端代码1.2 跨域请求1.2.1 跨域请求简介1.2.2 COSR概述CORS简介CORS原理 1.2.3 CORS解决跨域 2. 异常处理2.1 提示空消息分析2.2 系统异常分类2.3 异常处理2.2.1 方案一2.2.2 方案二 3. 图片验证码3.1 图片验证码意义3.2 实现思路3.3 后端接口…

4核8g服务器能支持多少人访问?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

MQTT协议解析:揭秘固定报头、可变报头与有效载荷的奥秘

MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;是一种轻量级的通讯协议&#xff0c;常用于远程传感器和控制设备的通讯。MQTT协议基于发布/订阅模式&#xff0c;为大量计算能力有限且工作在低带宽、不可靠网络环境中的设备…

跨境支付介绍

1、跨境电商定义和分类&#xff1b; 2、国际贸易清结算&#xff1b; 3、跨境支付&#xff1b; 1、跨境电商定义和分类 跨境电商业务简单说就是指不同国家地域的主体通过电子商务进行交易的一种业务模式。同传统的电商不同&#xff0c;交易双方属于不同的国家。因此&#xff0…

【计算机】本科考研还是就业?

其实现在很多计算机专业的学生考研&#xff0c;也是无奈的选择 技术发展日新月异&#xff0c;而在本科阶段&#xff0c;大家学着落后的技术&#xff0c;出来找工作自然会碰壁。而且现在用人单位的门槛越来越高&#xff0c;学历默认研究生起步&#xff0c;面试一般都是三轮起步…

机器视觉3D材料整理

3D视觉/机器视觉 1.3D重建 主要包括基于单目图像重建、结构光、双目重建、多目重建、多视图几何、深度相机、光场、SFM等内容 链接&#xff1a;Tom-Hardy-3D-Vision-Workshop/aweosme-3D-restruction 2、3D检测与识别 主要包括基于单目图像的3D检测、基于双目的3D检测、基于…

K8S(kubernetes) 部署运用方式汇总

k8s 部署运用这边汇总两类&#xff0c;第一种是命令版本。第二种是文本版本&#xff0c;通过创建yaml文件方式。 此次目标&#xff1a;通过k8s创建nginx,端口80并且可以被外网访问。 kubectl get namespaces 一、创建命名空间 首先创建一个命名空间&#xff0c;有了命名空间后…

c++委托构造函数与类的类型自动转换

前言 记录看书复习到的2个知识点 委托构造函数类类型自动转换 c11标准之后&#xff0c;类允许初始化数据成员&#xff0c;但是根据抽象派&#xff08;老派&#xff09;人员的观点&#xff0c;类一个抽象的东西&#xff0c;怎么能有具体的数值呢&#xff0c;就算要有默认数据…

Apache POl

介绍 Apache POl是一个处理Miscrosoft Ofice各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作,一般情况下&#xff0c;POI都是用于操作 Excel 文件。 Apache POl 的应用场景 1.银行网银系统导出交易…

python自动化管理和zabbix监控网络设备(无线AC控制瘦ap配置部分)

目录 前言 拓扑 一、AC-SW1 二、AC1 三、SW1-6 前言 具体原理和操作可以访问我的主页视频 白帽小丑的个人空间-白帽小丑个人主页-哔哩哔哩视频 拓扑 一、AC-SW1 sys sysname AC-SW1 vlan batch 100 200 210 220 230 240 250stp region-configuration region-name huawe…

R语言数学建模(一)—— 基础知识

R语言数学建模&#xff08;一&#xff09;—— 基础知识 文章目录 R语言数学建模&#xff08;一&#xff09;—— 基础知识前言一、建模软件1.1 软件建模的基础1.2 模型的分类1.3 不同类型模型间的联系1.4 一些术语1.5 建模如何适应数据分析过程 二、Tidyverse基础2.1 tidyvers…

4. client-go 编程式交互

Kubernetes 系统使用 client-go 作为 Go 语言的官方编程式交互客户端库&#xff0c;提供对 Kubernetes API Server 服务的交互访问。Kubernetes 的源码中已经集成了 client-go 的源码&#xff0c;无须单独下载。client-go 源码路径为 vendor/k8s.io/client-go。 开发者经常使用…