第28章_mysql缓存策略

文章目录

  • MySQL缓存方案目的分析
    • 缓存层作用
      • 举例
    • 缓存方案选择
    • 场景分析
  • 提升MySQL访问性能的方式
    • MySQL主从复制
    • 读写分离
    • 连接池
    • 异步连接
  • 缓存方案
    • 缓存和MySQL一致性状态分析
    • 制定读写策略
  • 同步方案
    • canal
    • go-mysql-transfer
  • 缓存方案的故障问题及解决
    • 缓存穿透
    • 缓存击穿
    • 缓存雪崩
    • 缓存方案的弊端
  • 总结

前提:读多写少,单个主节点能支撑项目数据量;数据的主要依据是mysql。

MySQL缓存方案目的分析

mysql 有自己缓冲层,它的作用也是用来缓存热点数据,这些数据包括索引、记录等。mysql 缓冲层是从自身出发,跟具体的业务无关。这里的缓冲策略主要是 lru。

mysql 数据主要存储在磁盘当中,适合大量重要数据的存储;磁盘当中的数据一般是远大于内存当中的数据。

一般业务场景的关系型数据库(mysql)是作为主要数据库的。

缓存层作用

MySQL缓存方案用来缓存用户定义的热点数据,用户直接从缓存获取热点数据,降低数据库的读写压力。

举例

比如说12点有一个促销活动,用户可以在12点之前预约,则12点之前预约的用户可称为热点数据,到12点的时候会有大量用户登录,这将对mysql造成很大压力,所以需要提前缓存预约的用户数据,因为mysql自带的缓存不适合用户定义的热点数据。此时可以考虑使用redis来缓存预约的用户数据,并且需要设置过期时间,因为12点之后可能过1小时活动就结束了,所以考虑过期时长为13:00 - 预约的时间。

缓存方案选择

缓存数据库可以选用 redis,memcached;它们所有数据都存储在内存当中,当然也可以将内存当中的数据持久化到磁盘当中。

场景分析

(1)内存访问速度是磁盘访问速度10W倍,访问磁盘的速度比较慢,尽量使获取数据是从内存中获取。

(2)读的需求远远大于写的需求。主要解决读的性能;因为写没必要优化,必须让数据正确的落盘。如果写性能出现问题,那么请使用横向扩展集群方式来解决

(3)MySQL自身缓冲层跟业务无关。由于 mysql 的缓冲层不由用户来控制,也就是不能由用户来控制缓存具体数据

(4)MySQL作为项目主要数据库,便于统计分析。项目中需要存储的数据应该远大于内存的容量,同时需要进行数据统计分析,所以数据存储获取的依据应该是关系型数据库。

(5)缓存数据库作为辅助数据库,存放热点数据。缓存数据库可以存储用户自定义的热点数据。

提升MySQL访问性能的方式

(1)读写分离

(2)连接池

(3)异步连接

(4)预处理。

(5)更换存储引擎。

(6)分库分表。(淘汰的技术)

(7)mycat。(淘汰的技术)

(8)tidb。

MySQL主从复制

在这里插入图片描述

  1. 主库更新事件 ( update、insert、delete ) 通过 io-thread写到 binlog。

  2. 从库请求读取 binlog,通过 io-thread 写入从库本地 relay log(中继日志)。

  3. 从库通过 sql-thread 读取 relay-log,并把更新事件在从库中重放(replay)一遍。

复制流程:

  1. Slave 上面的 IO 线程连接上 Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。
  2. Master 接收到来自 Slave 的 IO 线程的请求后,负责复制的IO 线程会根据请求信息读取日志指定位置之后的日志信息,返回给 Slave 的 IO 线程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到 Master 端的 binlog 文件的名称以及 binlog 的位置。
  3. Slave 的 IO 线程接收到信息后,将接收到的日志内容依次添加到 Slave 端的 relay-log 文件的最末端,并将读取到的Master 端的 binlog 的文件名和位置记录到master-info 文件中,以便在下一次读取的时候能够清楚的告诉 Master 从何处开始读取日志。
  4. Slave 的 sql 进程检测到 relay-log 中新增加了内容后,会马上解析 relay-log 的内容成为在 Master 端真实执行时候的那些可执行的内容,并在自身执行。

由于MySQL的主从复制是异步的,所以同一时刻主数据库和从数据库的数据可能存在不一致的现象,这就造成可能从数据库中读取的数据不是最新的。

读写分离

在这里插入图片描述
读写分离会设置多个从数据库,从数据库可能会在多个机器中。

写操作依然在主数据库中,主数据库提供数据的主要依据。

读写分离通过设置多个从数据库解决读压力。

读写分离主要依据MySQL的主从复制原理,因为MySQL的主从复制是异步复制的,所以读写分离只能保证数据的最终一致性,不能保证实时一致性。

如果读操作有强一致性要求,那么需要读操作去读主数据库。

连接池

连接池的定义:在服务端当中创建多个与数据库的连接线程。

解决的问题:并发提升数据库访问性能;同时复用连接,避免连接建立、断开依据安全验证的开销。

原理:利用MySQL的网络模型创建多个连接,每个连接复用去处理SQL语句。值得注意的是,如果发送一个事务(多条SQL语句),这个事务必须要在一个连接里面完成。

连接池具体内容请见mysql连接池

异步连接

在服务端创建一个连接,针对这个连接采用非阻塞IO。这种方式可以节省网络传输时间。

缓存方案

缓存和MySQL一致性状态分析

没有缓冲层之前,对数据的读写都是基于 mysql;所以不存在同步问题;这句话也不是必然,比如读写分离就存在同步问题(数据一致性问题)。

引入缓冲层后,对数据的获取需要分别操作缓存数据库和mysql,那么这个时候数据可能存在以下状态:
在这里插入图片描述
4 和 5显然是没问题的,现在需要考虑1、2以及3。

首先明确一点:获取数据的主要依据是 mysql,只需要将mysql 的数据正确同步到缓存数据库就可以了。

同理,缓存有,mysql 没有,这比较危险,此时可以认为该数据为脏数据;所以需要在同步策略中避免该情况发生同时可能存在mysql 和缓存都有数据,但是数据不一致,这种也需要在同步策略中避免

注意:以MySQL为主,保证缓存不可用,整个系统依然要保持正常工作;mysql 不可用的话,系统停摆,停止对外提供服务。

制定读写策略

在这里插入图片描述
读策略:先读缓存,若缓存有,直接返回;若缓存没有,读mysql;若 mysql 有,同步到缓存,并返回;若 mysql 没有,则返回没有。

写策略:从安全优先方面考虑;先删除缓存,再写 mysql,后面数据同步交由 go-mysql-transfer 等中间件处理(将问题 3 转化成 1)。

先删除缓存,为了避免其他服务读取旧的数据;也是告知系统这个数据已经不是最新,建议从 mysql 获取数据。但是对于服务 A 而言,写入 mysql 后,接着读操作必须要能读到最新的数据。
在这里插入图片描述
写策略:从效率优先方面考虑;先写缓存,并设置过期时间(如 200ms),再写mysql,后面数据同步交由其他中间件处理。

这里设置的过期时间是预估时间,大致上是 mysql 到缓存同步的时间。在写的过程中如果 mysql 停止服务,或数据没写入 mysql,则200 ms 内提供了脏数据服务;但仅仅只有 200ms 的数据错乱,即效率优先的写策略也有安全性的问题,但只会影响200ms。
在这里插入图片描述

同步方案

同步方案可以有:

(1)伪装从数据库。比如阿里开源的canal方案、kafka、go-mysql-transfer等。

(2)MySQL的触发器+udf。udf全称User-defined function,是MySQL提供的一种可扩展代码。UDF不具备事务,不能回滚;而且效率较低。

canal

canal会考虑分布式问题,如果一个canal宕机了,会有从canal顶替上来,保证服务正常提供。
在这里插入图片描述

go-mysql-transfer

go-mysql-transfer是一个基于Go语言开发的数据库变更数据传输工具,它可以实时捕获MySQL数据库中的数据变更,并将变更事件传输到给redis等缓存数据库。go-mysql-transfer只有一个节点,相对canal简单些,没有解决分布式问题。

缺点是需要引入etcd、zk等实现高可用。

具体流程是:
1)安装 go-mysql-transfer

# 安装 Golang 1.14 及以上版本
wget https://golang.google.cn/dl/go1.17.8.linux-amd64.tar.gz
tar -zxvf go1.17.8.linux-amd64.tar.gz
# 配置
vim /etc/profile
export PATH=$PATH:/opt/go/bin  # 配置 go 环境变量

# 安装 go-mysql-transfer
git clone https://gitee.com/mirrors/go-mysql-transfer.git
GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go build

2)修改 mysql 配置文件为主从模式,位置:/etc/mysql/my.cnf

log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=1 # 配置 MySQL replaction 需要定义,不要和slave_id重复

3)修改 app.yml,配置 mysql 和 redis,配置热点数据

#规则配置
rule:
  -
    schema: eseap #数据库名称
    table: t_user #表名称
    #order_by_column: id #排序字段,存量数据同步时不能为空
    #column_lower_case:false #列名称转为小写,默认为false
    #column_upper_case:false#列名称转为大写,默认为false
    column_underscore_to_camel: true #列名称下划线转驼峰,默认为false
    # 包含的列,多值逗号分隔,如:id,name,age,area_id  为空时表示包含全部列
    #include_columns: ID,USER_NAME,PASSWORD
    #exclude_columns: BIRTHDAY,MOBIE # 排除掉的列,多值逗号分隔,如:id,name,age,area_id  默认为空
    #column_mappings: USER_NAME=account    #列名称映射,多个映射关系用逗号分隔,如:USER_NAME=account 表示将字段名USER_NAME映射为account
    #default_column_values: area_name=合肥  #默认的列-值,多个用逗号分隔,如:source=binlog,area_name=合肥
    #date_formatter: yyyy-MM-dd #date类型格式化, 不填写默认yyyy-MM-dd
    #datetime_formatter: yyyy-MM-dd HH:mm:ss #datetime、timestamp类型格式化,不填写默认yyyy-MM-dd HH:mm:ss
    #lua_file_path: lua/t_user.lua   #lua脚本文件
    #lua_script:   #lua 脚本
    value_encoder: json  #值编码,支持json、kv-commas、v-commas;默认为json
    #value_formatter: '{{.ID}}|{{.USER_NAME}}' # 值格式化表达式,如:{{.ID}}|{{.USER_NAME}},{{.ID}}表示ID字段的值、{{.USER_NAME}}表示USER_NAME字段的值

    #redis相关
    redis_structure: string # 数据类型。 支持string、hash、list、set、sortedset类型(与redis的数据类型一致)
    #redis_key_prefix: USER_ #key的前缀
    #redis_key_column: USER_NAME #使用哪个列的值作为key,不填写默认使用主键
    #redis_key_formatter: '{{.ID}}|{{.USER_NAME}}'
    #redis_key_value: user #KEY的值(固定值);当redis_structure为hash、list、set、sortedset此值不能为空
    #redis_hash_field_prefix: _CARD_ #hash的field前缀,仅redis_structure为hash时起作用
    #redis_hash_field_column: Cert_No #使用哪个列的值作为hash的field,仅redis_structure为hash时起作用,不填写默认使用主键
    #redis_sorted_set_score_column: id #sortedset的score,当数据类型为sortedset时,此项不能为空,此项的值应为数字类型

    #mongodb相关
    #mongodb_database: transfer #mongodb database不能为空
    #mongodb_collection: transfer_test_topic #mongodb collection,可以为空,默认使用表名称

    #elasticsearch相关
    #es_index: user_index #Index名称,可以为空,默认使用表(Table)名称
    #es_mappings: #索引映射,可以为空,为空时根据数据类型自行推导ES推导
    #  -
    #    column: REMARK #数据库列名称
    #    field: remark #映射后的ES字段名称
    #    type: text #ES字段类型
    #    analyzer: ik_smart #ES分词器,type为text此项有意义
    #    #format: #日期格式,type为date此项有意义
    #  -
    #    column: USER_NAME #数据库列名称
    #    field: account #映射后的ES字段名称
    #    type: keyword #ES字段类型

    #rocketmq相关
    #rocketmq_topic: transfer_test_topic #rocketmq topic,可以为空,默认使用表名称

    #kafka相关
    #kafka_topic: user_topic #rocketmq topic,可以为空,默认使用表名称

    #rabbitmq相关
    #rabbitmq_queue: user_topic #queue名称,可以为空,默认使用表(Table)名称

    #reserve_raw_data: true #保留update之前的数据,针对rocketmq、kafka、rabbitmq有用;默认为false

4)编写 Lua 同步逻辑

local ops = require("redisOps") --加载redis操作模块

local row = ops.rawRow()  --当前数据库的一行数据,table类型,key为列名称
local action = ops.rawAction()  --当前数据库事件,包括:insert、update、delete

-- 同步方法
if action == "insert" or action == "update" then -- 只监听insert事件
    local id = row["id"] --获取ID列的值
    local key = "user:" .. id
    local name = row["nick"] --获取USER_NAME列的值
    local sex = row["sex"]
    local height = row["height"] --获取PASSWORD列的值
    local age = row["age"]
    ops.HSET(key, "id", id) -- 对应Redis的HSET命令
    ops.HSET(key, "nick", name) -- 对应Redis的HSET命令
    ops.HSET(key, "sex", sex) -- 对应Redis的HSET命令
    ops.HSET(key, "height", height) -- 对应Redis的HSET命令
    ops.HSET(key, "age", age) -- 对应Redis的HSET命令
elseif action == "delete" then
    local id = row['id']
    local key = "user:" .. id
    ops.DEL(key)
end

5)启动 mysql, redis, go-mysql-transfer

# 全量数据同步,初次启动
./go-mysql-transfer -stock
# 启动
nohup go-mysql-transfer &

缓存方案的故障问题及解决

缓存穿透

如果某个数据在redis缓存和MySQL中都不存在,但此时一直尝试读这个不存在的数据,最后数据压力堆积在MySQL,可能会造成MySQL崩溃。

例如恶意攻击者可以通过构造大量不存在的查询请求来压垮数据库。

解决办法:
1)缓存设置<key,nil>:当发现MySQL不存在某个数据,将redis中对应的key设置为<key,nil>并设置过期时间。通过这样的标识,使得下次访问key的时候不要再去访问MySQL,并且到期自动删除这个key。但是这种方法会造成redis缓存数据库缓存很多无效数据,浪费内存。

2)部署布隆过滤器:将 MySQL当中已经存在的 key,写入布隆过滤器,不存在的直接 pass 掉。即使发生了缓存穿透,通过布隆过滤器在缓存层(即布隆过滤器部署在redis层,这样就不用在多个服务端都部署了)拦截无效的请求,避免无效查询到达MySQL。最好在缓存数据库上部署布隆过滤器。

缓存击穿

如果某个频繁访问的热点数据在redis缓存不存在(过期或被淘汰),但在MySQL中存在。此时有大量的并发连接请求该热点数据,会直接访问数据库,导致MySQL数据库压力骤增,可能造成MySQL数据库崩溃

解决方案:
1)过热数据不过期,即不要对频繁访问的热点数据设置过期时间。
2)分布式锁。请求数据的时候获取锁,若获取成功,则操作后释放锁;若获取失败,则休眠一段时间(200ms)再去获取,当获取成功,操作后释放锁。

缓存雪崩

redis缓存中的大量数据同时过期或失效,但是在MySQL中存在,导致大量请求直接访问MySQL数据库,造成系统性能下降甚至崩溃。

缓存数据库在整个系统不是必须的,也就是缓存宕机不会影响整个系统提供服务。

解决办法:

1)如果因为缓存数据库宕机,造成所有数据涌向 MySQL。采用高可用的集群方案,如哨兵模式、cluster模式。

2)如果因为设置了相同的过期时间,造成缓存集中失效。设置随机过期值或者其他机制错开失效时间。

3) 如果因为系统重启的时候,造成缓存数据消失。重启时间短,redis 开启持久化(过期信息也会持久化)就行了; 重启时间长,提前将热数据导入 redis 当中。

缓存方案的弊端

不能处理多语句事务。这是因为redis缓存数据库不支持回滚,造成redis 缓存数据库 与MySQL存储数据库数据不一致。

总结

  1. binlog的作用是数据备份和主从复制;确保主从数据的一致。
  2. redolog的作用是确保事务持久化,确保本地数据一致。
  3. 缓存方案读策略:先读缓存,存在则直接返回;不存在则去访问MySQL,再写redis。
  4. 缓存方案写策略,从安全为主;先删除缓存层中对应数据,再写MySQL,最后将MySQL数据同步到缓存层。添加缓存层的目的是为了提升效率,这种方式为了安全降低了效率。
  5. 缓存方案写策略,从效率为主;先写缓存层并设置过期时间,再写MySQL,等待MySQL同步到缓存层中。过期时间=MySQL网络传输时间+MySQL处理时间。
  6. 缓存穿透的解决方法有:缓存设置 <key,nil>,告诉服务器mysql也没有数据,不用去访问mysql了;部署布隆过滤器。

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

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

相关文章

nodejs express vue uniapp电影购票系统源码

开发技术&#xff1a; node.js&#xff0c;vscode&#xff0c;HBuilder X express vue elementui uniapp 功能介绍&#xff1a; 用户端&#xff1a; 登录注册 首页显示搜索电影&#xff0c;轮播图&#xff0c;电影分类&#xff0c;最近上架电影 点击电影进入电影详情&am…

MySQL(15):存储过程与函数

存储过程概述 含义&#xff1a; 存储过程的英文是 Stored Procedure 。它的思想很简单&#xff0c;就是一组经过 预先编译 的 SQL 语句的封装。 执行过程&#xff1a; 存储过程预先存储在 MySQL 服务器上&#xff0c;需要执行的时候&#xff0c;客户端只需要向服务器端发出调用…

Obsidian同步技巧

Obsidian介绍 Obsidian支持Markdown语法&#xff0c;所见即所得。 软件支持多仓库功能&#xff0c;支持笔记文件夹和分层文件夹&#xff0c;等功能。 值得一提的是&#xff0c;软件的笔记同步功能需要付费。 同步技巧 官方同步方法 若资金充足&#xff0c;则可在Obsidian官网…

Django(四、路由层)

文章目录 一、路由层1.路由匹配url方法第一个是参数 的正则表达式 二、正则无名分组与有名分组无名分组有名分组 三、反向解析1.概念无名分组动态路由解析有名分组动态路由解析 四、路由分发为什么要用路由分发&#xff1f; 1.总路由分发配置名称空间 五、伪静态的概念六、虚拟…

使用Jmeter进行http接口性能测试

在进行网页或应用程序后台接口开发时&#xff0c;一般要及时测试开发的接口能否正确接收和返回数据&#xff0c;对于单次测试&#xff0c;Postman插件是个不错的Http请求模拟工具。 但是Postman只能模拟单客户端的单次请求&#xff0c;而对于模拟多用户并发等性能测试&#xf…

【Java】集合(二)Set

1.Set接口基本介绍 无序:存取顺序不一致不重复:可以去除重复无索引:没有带索引的方法&#xff0c;所以不能使用普通for循环遍历&#xff0c;也不能通过索引来获取元素 2.Set集合的实现类 HashSet:无序、不重复、无索引LinkedHashSet: 有序、不重复、无索引TreeSet: 可排序、不…

二维码智慧门牌管理系统升级解决方案:数据可视化助力运营精准决策

文章目录 前言一、升级版二维码智慧门牌管理系统的特点二、数据可视化助力运营精准决策 前言 随着科技的不断进步&#xff0c;传统的门牌管理系统已经无法满足现代社会的需求。为了提高管理效率&#xff0c;减少人力成本&#xff0c;我们引入了升级版的二维码智慧门牌管理系统…

同城服务如何引流和推广 同城小程序制作

客观原因线下实体店经营变得很艰难&#xff0c;而抖音推出的同城号功能&#xff0c;为许多商家带来了新的生机。抖音同城号的操作很简单&#xff0c;只需在短视频发布时打开同城号&#xff0c;短视频将被投入到同城流量池中&#xff0c;可以让位置附近的用户看到&#xff0c;线…

AttributeError: module ‘matplotlib‘ has no attribute ‘get_data_path‘

【报错】使用 AutoDL 下 Notebook 调用 matplotlib 时遇到 AttributeError: module matplotlib has no attribute get_data_path 报错&#xff1a; --------------------------------------------------------------------------- AttributeError …

C语言ZZULIOJ1148:组合三位数之一

题目描述 把1、2、3、4、5、6、7、8、9组合成3个3位数&#xff0c;要求每个数字仅使用一次&#xff0c;使每个3位数均为完全平方数。按从小到大的顺序输出这三个三位数。 输入:无 输出:按从小到大的顺序输出这三个三位数&#xff0c;由空格隔开。输出占一行。 提示 若一个数能表…

No192.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

2017年计网408

第33题 假设 OSI 参考模型的应用层欲发送 400B 的数据 (无拆分), 除物理层和应用层之外, 其他各层在封装 PDU 时均引入 20 B 的额外开销, 则应用层数据传输效率约为( )A. 80%B. 83%C. 87%D. 91% 本题考察有关数据包逐层封装的相关概念。我们来一起分析一下。 这是要求大家必须…

Egg.js 中 Service 的使用

Service 服务 Service是用来编写和数据库直接交互的业务逻辑代码。Service就是在复杂业务场景下用于做业务逻辑封装的一个抽象层。 简单来说&#xff0c;就是把业务逻辑代码进一步细化和分类&#xff0c;所以和数据库交互的代码都放到Service中。这样作有三个明显的好处。 保…

【leetcode】8.字符串转换整数

题目 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格 检查下一个字符&#xff08;假设还未…

同城小程序怎么运作 本地化生活小程序开发

同城小程序可以采取公域加私域的运营方式&#xff0c;进行运作。 在社交媒体平台上分享有趣的本地生活内容、社区动态&#xff0c;可以通过举办本地活动、合作推广等方式进行线下宣传&#xff0c;可以通过抖音本地化生活服务进行线下门店推广。 本地化生活小程序开发需要结合自…

基于RK3568新零售智能售货柜解决方案

I 方案简介 新零售智能售货柜解决方案&#xff1a; 无人零售除了无人货架外&#xff0c;自动售货机仍是亮点。但仍有很多人认为自动售货机已经过时&#xff0c;不会成为新零售领域的新星。 随着手机支付、人脸支付不断普及&#xff0c;智能售卖不断的推陈出新&#xff0c;无人…

Netty入门指南之Reactor模型

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言单线程…

基因检测技术的发展与创新:安全文件数据传输的重要作用

基因是生命的密码&#xff0c;它决定了我们的身体特征、健康状况、疾病风险等。随着基因检测技术的高速发展&#xff0c;我们可以通过对基因进行测序、分析和解读&#xff0c;更深入地认识自己&#xff0c;预防和治疗各种遗传性疾病&#xff0c;甚至实现个性化医疗和精准健康管…

IDEA 2022创建Spring Boot项目

首先点击New Project 接下来&#xff1a; (1). 我们点击Spring Initializr来创建。 (2). 填写项目名称 (3). 选择路径 (4). 选择JDK------这里笔者选用jdk17。 (5). java选择对应版本即可。 (6). 其余选项如无特殊需求保持默认即可。 然后点击Next。 稍等一会&#xff0c…

堆排序(大根堆、小根堆)

参考视频&#xff1a; 1、数据结构&#xff0c;小根堆的调整&#xff01;必须熟练掌握&#xff01; 2、数据结构建堆筛选输出最小值 | 计算机软件考研期末知识点2
最新文章