1.什么是缓存穿透怎么解决

:缓存穿透是指用户请求的数据在缓存(如 Redis)和数据库(如 MySQL)中都不存在,导致每次请求都必须绕过缓存直接查询数据库,最终大量无效请求集中冲击数据库的现象。

其核心问题在于:缓存的 “拦截” 作用失效(因为缓存中没有该数据),而数据库也无法返回有效结果,导致所有请求都直接打到数据库,可能引发数据库过载、响应延迟甚至宕机。

解决方案:针对缓存穿透的核心矛盾(“缓存和数据库都无数据,导致请求直达数据库”),解决方案的本质是在请求到达数据库前,提前拦截无效请求,或减少无效请求对数据库的直接冲击

1. 布隆过滤器(Bloom Filter):提前拦截 “绝对不存在” 的请求

原理
布隆过滤器是一种空间效率极高的概率性数据结构,它可以提前将数据库中 “已存在的所有有效 key”(如所有合法的用户 ID、商品 ID)存入其中。当有新请求来时,先通过布隆过滤器判断该 key 是否 “可能存在”:

  • 若布隆过滤器判断 “不存在”,则直接返回空结果(无需查询缓存和数据库);
  • 若判断 “可能存在”,再继续查询缓存和数据库(因为布隆过滤器有极小的误判率,即 “不存在的 key 可能被误判为存在”)。

优势

  • 内存占用小(相比缓存全量 key),查询速度快(O (1)),适合拦截大量无效请求;
  • 能从源头过滤掉 “绝对不存在” 的 key,大幅减少数据库压力。

注意点

  • 存在误判率(可通过调整哈希函数数量和位数组大小降低,但无法完全消除),可能导致少量不存在的 key 被误判为 “可能存在”,仍需查询数据库;
  • 数据更新时需同步更新布隆过滤器(如新增数据时添加 key,删除数据时需谨慎,因为布隆过滤器不支持高效删除)。
2. 缓存空值(Null Value):避免重复查询不存在的数据

原理
当数据库查询结果为 “空”(即数据不存在)时,不直接返回空结果,而是将这个 “空值” 作为缓存值存入缓存,并设置一个较短的过期时间(如 1-5 分钟)。后续相同的请求会直接从缓存获取 “空值”,无需再查询数据库。

优势

  • 实现简单,无需额外组件,能快速拦截重复的无效请求;
  • 适合应对短期集中的无效请求(如用户输入错误参数的场景)。

注意点

  • 需设置合理的过期时间:时间过长会导致缓存中积累大量空值,浪费内存;时间过短则无法有效拦截重复请求;
  • 可能被恶意攻击利用(如伪造大量不同的不存在 key,导致缓存中存入大量空值,占用内存),需配合其他策略(如限流)使用。
3. 业务层校验与过滤:从源头减少无效请求

原理
在请求到达缓存或数据库前,通过业务逻辑对请求参数进行合法性校验,直接过滤掉明显无效的请求。

常见手段

  • 参数格式校验:比如用户 ID 必须为正整数,过滤负数、字符串等非法格式;
  • 范围校验:比如商品 ID 的有效范围是 1-100 万,直接拦截超出范围的请求;
  • 白名单机制:对于核心业务(如支付、用户信息),仅允许白名单内的 key 通过查询。

优势

  • 成本低,无需依赖缓存或数据库,直接在应用层拦截,效率高;
  • 能针对性过滤业务场景中的无效请求。
4. 接口限流与熔断:控制请求总量

原理
通过限流算法(如令牌桶、漏桶)限制单位时间内的请求数量,或通过熔断机制(如 Sentinel、Hystrix)在数据库压力过大时,暂时停止对无效请求的处理,避免数据库被压垮。

适用场景

  • 应对突发的恶意攻击(如短时间内大量不同的无效请求);
  • 作为兜底策略,防止其他措施失效时数据库过载。
5. 数据预热:减少缓存未命中的概率

原理
在系统启动或低峰期,提前将数据库中 “高频访问的有效数据” 加载到缓存中,减少缓存未命中的情况。虽然不能直接解决缓存穿透,但能降低无效请求的相对比例。

缓存穿透

定义:指查询一个「不存在的数据」时,由于缓存和数据库中都没有该数据,导致每次请求都会直接穿透缓存,全部打到数据库上。如果这类请求量很大,可能会压垮数据库。

缓存击穿

定义:指一个「热点 key」(被高频访问的 key)在缓存中突然失效(比如过期),此时大量请求同时访问该 key,缓存未命中,导致所有请求瞬间打到数据库,造成数据库短期内压力骤增。

缓存雪崩

定义:指「大量缓存 key 在同一时间集中过期」,或 Redis 服务本身宕机,导致缓存层整体失效,此时所有请求全部涌向数据库,数据库因无法承载高并发而崩溃。

维度缓存穿透缓存击穿缓存雪崩
针对的 key不存在的 key存在的热点 key大量 key(或 Redis 集群)
触发原因数据本身不存在热点 key 突然失效大量 key 集中过期 / Redis 宕机
影响范围单个无效 key 的高频请求单个热点 key 的突发请求整体缓存层失效,全量请求

缓存穿透保护: 

// 布隆过滤器实现
public class BloomFilter {private BitSet bitSet;private int size;private HashFunction[] hashFunctions;public BloomFilter(int size, int hashCount) {this.bitSet = new BitSet(size);this.size = size;this.hashFunctions = new HashFunction[hashCount];for (int i = 0; i < hashCount; i++) {hashFunctions[i] = new HashFunction(size, i);}}public void add(String key) {for (HashFunction f : hashFunctions) {bitSet.set(f.hash(key), true);}}public boolean contains(String key) {for (HashFunction f : hashFunctions) {if (!bitSet.get(f.hash(key))) {return false;}}return true;}private static class HashFunction {private int size;private int seed;public HashFunction(int size, int seed) {this.size = size;this.seed = seed;}public int hash(String key) {int result = 1;for (char c : key.toCharArray()) {result = seed * result + c;}return (size - 1) & result;}}
}

缓存击穿保护:

// 互斥锁实现
public class CacheBreakdownProtection {private RedisTemplate<String, Object> redisTemplate;private Map<String, Lock> lockMap = new ConcurrentHashMap<>();public Object getWithLock(String key, Callable<Object> loader) {Object value = redisTemplate.opsForValue().get(key);if (value != null) {return value;}Lock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());try {lock.lock();// 双重检查value = redisTemplate.opsForValue().get(key);if (value == null) {value = loader.call();redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);}return value;} catch (Exception e) {throw new RuntimeException(e);} finally {lock.unlock();lockMap.remove(key);}}
}

缓存雪崩保护:

// 随机过期时间实现
public class CacheAvalancheProtection {private RedisTemplate<String, Object> redisTemplate;private ThreadLocalRandom random = ThreadLocalRandom.current();public void setWithRandomExpire(String key, Object value, long baseExpire, long delta) {long expireTime = baseExpire + random.nextLong(delta);redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);}// 集群模式下使用多级缓存public Object getWithMultiLevelCache(String key, Callable<Object> loader) {Object value = redisTemplate.opsForValue().get(key);if (value != null) {return value;}value = localCache.get(key);if (value != null) {return value;}try {value = loader.call();setWithRandomExpire(key, value, 1800, 600); // 30分钟±10分钟localCache.put(key, value);return value;} catch (Exception e) {throw new RuntimeException(e);}}
}

综合保护: 

// 综合防护策略
public class CacheProtectionService {private BloomFilter bloomFilter;private CacheBreakdownProtection breakdownProtection;private CacheAvalancheProtection avalancheProtection;public Object safeGet(String key, Callable<Object> loader) {// 1. 检查布隆过滤器if (!bloomFilter.contains(key)) {return null;}// 2. 尝试获取缓存Object value = breakdownProtection.getWithLock(key, () -> {// 3. 数据加载逻辑Object loadedValue = loader.call();// 4. 设置随机过期时间avalancheProtection.setWithRandomExpire(key, loadedValue, 1800, 600);// 5. 更新布隆过滤器bloomFilter.add(key);return loadedValue;});return value;}
}

2.redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性

强一致性:需要让数据库与redis高度保持一致,因为要求时效性比较高。采用读写锁保证的强一致性。使用Redisson实现的读写锁。在读的时候添加共享锁,可以保证读读不互斥、读写互斥。当更新数据的时候,添加排他锁。它是读写、读读都互斥,这样就能保证在写数据的同时,是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是,读方法和写方法上需要使用同一把锁才行。排他锁底层使用的也是SETNX,它保证了同时只能有一个线程操作锁住的方法。

最终一致性:数据同步可以有一定的延时(这符合大部分业务需求)。采用的阿里的Canal组件实现数据同步:不需要更改业务代码,只需部署一个Canal服务。Canal服务把自己伪装成mysql的一个从节点。当mysql数据更新以后,Canal会读取binlog数据,然后再通过Canal的客户端获取到数据,并更新缓存即可。

3.为什么要优先保证数据库一致性,先更新缓存会怎么样?

在处理数据同步时优先保证数据库一致性,核心是因为数据库是数据的最终持久化存储,是数据真实性的权威来源。从实际项目场景来看,若先更新缓存,可能出现 “缓存更新成功但数据库更新失败” 的情况:比如xxxx项目更新中,若先修改了 Redis 中的模板缓存,却因网络波动等导致 MySQL 中模板数据更新失败,会使缓存中留存错误数据,后续所有依赖该缓存的请求都会获取到不一致信息,且难以快速发现和修正。

而先更新数据库再处理缓存,即使缓存更新失败,后续请求查询时会因缓存未命中而从数据库加载最新数据并重建缓存,能通过 “数据库的正确性” 兜底,保证数据最终一致。这也与项目中对 Redis 缓存的使用逻辑一致 —— 缓存本质是提升查询效率的辅助存储,其一致性需依赖数据库这一核心载体来保障。

4.你听说过延时双删吗?为什么不用它呢?

延时双删是一种在高并发场景下解决数据库与缓存双写一致性问题的策略,其核心思想是通过两次删除缓存操作,配合一定的延迟时间,尽量保证在数据库更新完成后,旧数据不会因并发请求被错误地重新写入缓存。以下是其实现原理和关键步骤:

基本流程

  1. 先删除缓存:在更新数据库前,先删除 Redis 中的对应缓存,防止后续请求读取到旧数据。
  2. 更新数据库:执行 MySQL 等数据库的更新操作。
  3. 延时后再次删除缓存:更新数据库完成后,等待一段时间(如 1-3 秒),再次删除 Redis 缓存。
    • 目的:确保在数据库更新期间,若有请求读取到旧数据并写入缓存,通过第二次删除操作清除该脏数据。

为什么需要延时?

在高并发场景下,可能存在以下时序问题:

  • 步骤 1 删除缓存后,若有新请求在数据库更新前读取数据,会从数据库获取旧值并重新写入缓存。
  • 步骤 2 更新数据库,此时缓存中仍为旧值,导致后续请求读取到不一致的数据。

通过延时,可以等待数据库更新完成后,再执行第二次删除,确保旧数据被彻底清除。

延时时间如何确定?

通常需要根据业务场景的数据库更新耗时请求处理耗时来估算,一般设置为1-3 秒。例如:

  • 若数据库更新操作平均耗时 200ms,可设置延时为 1 秒,确保大部分更新操作已完成。
  • 对于更复杂的业务,可通过压测或监控数据动态调整延时时间。

优缺点

  • 优点:实现简单,成本低,能解决大部分并发场景下的数据不一致问题。
  • 缺点
    • 无法保证强一致性:极端情况下(如第二次删除失败)仍可能存在短暂不一致。
    • 性能损耗:延时操作会增加请求响应时间,影响吞吐量。
    • 延时时间难精准控制:不同业务场景的最优延时时间差异较大。

适用场景

  • 适用于读多写少、对一致性要求不是极致严格的场景(如商品价格、用户信息等)。
  • 不适用于金融交易等需要强一致性的场景(通常需借助分布式事务或中间件)。

实际项目中的替代方案

在实际项目中,更倾向于使用:

  1. 异步消息队列:通过 MQ 异步更新缓存,利用重试机制保证最终一致性。
  2. 订阅数据库 binlog:如通过 Canal 监听 MySQL 变更,自动同步到 Redis,减少人工干预。
  3. 设置合理的缓存过期时间:作为兜底策略,即使出现不一致,过期后会自动刷新。

5.redis做为缓存,数据的持久化是怎么做的?这两种持久化方式有什么区别呢,优缺点?分别介绍一下这两种持久化方式那个恢复得更快?

Redis 的数据持久化主要通过两种方式实现:RDB(Redis Database)和 AOF(Append Only File)。

RDB 是通过生成数据集的时间点快照来实现持久化的。它会在指定的时间间隔内,将内存中的所有数据以二进制的形式写入磁盘的 RDB 文件中,比如可以配置 “每 5 分钟内有 1000 次写操作就触发一次快照”。

AOF 则是通过记录所有写操作命令来实现持久化的。服务器在执行完一个写命令后,会将该命令追加到 AOF 文件的末尾,当 Redis 重启时,会通过重新执行 AOF 文件中的所有命令来恢复数据。

两者的区别主要体现在以下方面:
从数据完整性来看,RDB 可能会丢失最后一次快照后的所有数据,因为它是定时快照;而 AOF 可以通过配置 “everysec”(每秒同步一次)等策略,最多丢失 1 秒内的数据,完整性更好。
从文件大小来看,RDB 是二进制压缩存储,文件体积较小;AOF 记录的是命令文本,相同数据下文件体积更大,即使有重写机制优化,通常也比 RDB 大。
从性能影响来看,RDB 在触发快照时会通过 fork 子进程处理,主进程不阻塞,但 fork 操作在数据量大时可能有短暂阻塞;AOF 的追加操作是异步的,对主进程影响小,但 AOF 重写时也可能有一定性能消耗。

在恢复速度上,RDB 更快。因为 RDB 是二进制文件,加载时直接解析还原数据即可;而 AOF 需要逐条执行命令,尤其是当 AOF 文件较大时,恢复速度会明显慢于 RDB。

6.Redis的数据过期策略有哪些? Redis的数据淘汰策略有哪些? 

一、数据过期策略(处理已过期的 Key)

Redis 采用 「定期删除 + 惰性删除」组合策略 ,平衡 CPU 性能与内存利用率。

  1. 惰性删除(Lazy Deletion)

    • 触发时机:仅当访问(读 / 写)已过期的 Key 时,才会检查并删除。
    • 优点:避免主动扫描带来的 CPU 开销,适合低频访问的过期 Key。
    • 缺点:若过期 Key 长期未被访问,会导致内存泄漏(如用户会话缓存长期未失效)。
  2. 定期删除(Periodic Deletion)

    • 执行逻辑:默认每 100ms 随机抽取 20 个设置过期时间的 Key,删除其中已过期的;若过期比例>25%,重复抽取,单次扫描耗时不超过 25ms。
    • 优点:主动清理部分过期 Key,防止内存膨胀。
    • 缺点:随机抽样可能遗漏大量过期 Key(如集中过期的热点数据)。

二、数据淘汰策略(内存不足时的兜底方案)

当 Redis 内存达到maxmemory阈值时,根据以下 8 种策略淘汰数据(区分「是否设置过期时间」):

策略分类策略名称淘汰逻辑适用场景
仅过期 Keyvolatile-lru淘汰最近最少使用(LRU)的过期 Key缓存临时数据(如验证码、短期会话),保留永久数据
volatile-lfu淘汰最不频繁使用(LFU)的过期 Key(Redis 4.0+)区分短期高频访问的临时数据(如活动促销页缓存)
volatile-ttl优先淘汰剩余 TTL 最短的过期 Key需精准控制过期顺序的场景(如倒计时活动)
volatile-random随机淘汰过期 Key无明显冷热数据区分的临时缓存
所有 Keyallkeys-lru淘汰全体 Key 中最近最少使用的通用缓存场景(如商品详情页),不区分是否过期
allkeys-lfu淘汰全体 Key 中最不频繁使用的(Redis 4.0+)长期冷热数据分明(如用户行为日志缓存)
allkeys-random随机淘汰全体 Key数据访问频率均匀的场景(如测试环境)
不淘汰noeviction(默认)拒绝写入新数据(读正常),防止数据丢失不允许丢失数据的场景(如持久化配置中心)

三、核心区别与选择建议

维度过期策略(已过期 Key)淘汰策略(内存不足)
触发条件Key 已过期内存超过阈值
目标释放过期内存腾出空间写入新数据
典型场景会话超时、验证码失效突发流量导致内存不足

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

aspnetcore Mvc配置选项中的ModelMetadataDetailsProviders

在ASP.NET Core 中&#xff0c;ModelMetadataDetailsProviders 是用于配置模型元数据提供程序的核心组件&#xff0c;它决定了如何解析和提供模型属性的元数据&#xff08;如数据类型、验证规则、显示名称等&#xff09;。以下是其详细解析&#xff1a; 一、核心概念与作用 模…

分区表设计:历史数据归档与查询加速

以下为分区表设计的核心实现方案与技术要点&#xff0c;综合最新技术实践整理&#xff1a;一、分区表核心机制与价值‌物理存储与逻辑分离‌分区表通过预定义规则&#xff08;如时间戳、ID范围&#xff09;将大表物理拆分为多个子表&#xff08;分区&#xff09;&#xff0c;对…

下班倒计时

下班倒计时#include <stdio.h> #include <time.h> #include <unistd.h>void print_remaining_time(time_t now, time_t tar_time) {double diff difftime(tar_time, now);int hours (int)diff / 3600;int minutes ((int)diff % 3600) / 60;int seconds (…

Vue配置特性(ref、props、混入、插件与作用域样式)

前言Vue提供了许多高级特性来增强组件开发的能力。本文将深入解析Vue中的ref属性、props配置、混入(mixin)、插件开发以及scoped样式等核心特性&#xff0c;通过实例演示它们的用法&#xff0c;并给出最佳实践建议。一、ref属性详解1. ref基本用法ref用于给元素或子组件注册引用…

解析力和清晰度区别

在视觉成像、光学设备或数字信号处理领域&#xff0c;清晰度和解析力是两个相关但侧重点不同的概念。它们都与“细节呈现”有关&#xff0c;但核心定义、影响因素和应用场景存在显著区别。以下从定义、核心差异、联系三个方面详细说明&#xff1a; 一、核心定义清晰度&#xff…

Java网络通信:UDP和TCP

一、UDP特点&#xff1a; 无连接不可靠&#xff1a;通信双方不事先建立连接&#xff0c;直接发送数据。数据封装&#xff1a;将数据封装在64KB的数据包中&#xff0c;包含接收端的IP和端口。UDP通信模型&#xff1a; 模型比喻&#xff1a;以抛韭菜为例&#xff0c;发送端像抛韭…

Java行为型模式(状态模式)实现方式与测试方法

一、状态模式实现方式 核心结构 状态接口&#xff08;State&#xff09;&#xff1a;定义状态相关的行为方法。具体状态类&#xff08;ConcreteState&#xff09;&#xff1a;实现状态接口&#xff0c;封装特定状态下的逻辑。上下文类&#xff08;Context&#xff09;&#xff…

MISRA C-2012准则之标准C环境准则

目录 1.标准C环境准则 错误示例1&#xff1a;未定义行为&#xff08;整数溢出&#xff09; 错误示例2&#xff1a;未指定行为&#xff08;函数调用顺序&#xff09; 错误示例3&#xff1a;语言扩展&#xff08;GCC内置函数&#xff09; 错误示例4&#xff1a;关键未指定行…

26、鸿蒙Harmony Next开发:ArkTS并发(Promise和async/await和多线程并发TaskPool和Worker的使用)

目录 异步并发 (Promise和async/await) Promise async/await 多线程并发 多线程并发模型 内存共享模型 Actor模型 TaskPool TaskPool运作机制 TaskPool注意事项 Concurrent装饰器 装饰器说明 装饰器使用示例 TaskPool扩缩容机制 扩容机制 缩容机制 Worker Wo…

[IRF/Stack]华为/新华三交换机堆叠配置

堆叠的三大优势 提高资源利用率&#xff0c;获得更高的转发性能、链路带宽降低网络规划的复杂度、方便网络的管理降低故障对业务的影响时间 堆叠的两个需求 设备型号必须统一系统版本必须统一 华三堆叠案例&#xff1a;#### S6850_1 <H3C>sy [H3C]undo in en [H3C]sy SW…

融智兴科技: RFID超高频洗涤标签解析

在纺织品租赁与管理领域&#xff0c;布草、工服、医护织物等物品的流转追踪一直是运营管理的核心挑战。传统管理方式依赖人工计数与条码扫描&#xff0c;存在效率低下、差错率高、损耗严重等问题&#xff0c;尤其在工业洗涤环境下&#xff0c;纸质标签易损坏、识别率低。融智兴…

从平面到时空:地图故事的时空叙事与沉浸式阅读

朋友们&#xff0c;在工作中你是否也遇到过这些令人头疼的挑战&#xff1f;当项目汇报时总觉得表达不够精彩&#xff0c;方案讲解时听众总是一头雾水&#xff0c;制作应急预案时更是无从下手&#xff1f;别担心&#xff01;今天我要向大家介绍一个超级实用的解决方案——地图故…

自动控制原理知识地图:舵轮、路径与导航图

掌握自控原理的关键&#xff0c;在于看清那棵枝繁叶茂的“知识树”——从根部的数学模型&#xff0c;到主干的分析方法&#xff0c;直至顶端的系统设计。作为一名自动化专业学生&#xff0c;你是否曾在深夜里面对劳斯判据和奈奎斯特图感到深深的恐惧&#xff1f;作为初入行的工…

Flutter在Android studio运行出现Error: Entrypoint is not a Dart file

Flutter在Android studio运行出现Error: Entrypoint is not a Dart file

NE综合实验2:RIP 与 OSPF 动态路由精细配置及ACL访问控制列表 电脑

NE综合实验2&#xff1a;RIP 与 OSPF 动态路由精细配置及ACL访问控制列表 实验拓扑图实验需求 1.按照图示配置IP地址 2.按照图示区域划分配置对应的动态路由协议 3.在R7上配置dhcp服务器&#xff0c;能够让pc可以获取IP地址 4.将所有环回⼝宣告进ospf中&#xff0c;将环回⼝7宣…

Kafka 控制器(Controller)详解:架构、原理与实战

目录Kafka 控制器&#xff08;Controller&#xff09;详解&#xff1a;架构、原理与实战一、控制器的核心职责1. 元数据管理2. 分区状态机3. 故障恢复4. 集群操作协调二、传统 ZooKeeper 模式下的控制器1. 控制器选举机制2. 控制器与 ZooKeeper 的交互3. 潜在问题三、KRaft 模式…

【C++基础】#define vs constexpr:C++ 编译期常量的双雄对决(面试高频考点 + 真题解析)

​在 C++ 面试中,#define与constexpr的对比堪称 “元老级” 考点 —— 据统计,在 2023-2024 年的 C++ 工程师面试中,该知识点的出现频率高达 72%,尤其是在字节跳动、腾讯、华为等企业的校招 / 社招中,几乎是必问内容。​ 这两个语法元素都与 “编译期常量” 相关,但背后却…

k8s环境使用Operator部署Seaweedfs集群(上)

作者&#xff1a;闫乾苓 文章目录前言4.1 前置条件4.2 部署seaweedfs-operator4.3 准备operator镜像4.4 使用operator部署Seaweedfs集群4.4.1 部署StorageClass4.4.2 使用StorageClass预先创建PV前言 SeaweedFS Operator是一个Kubernetes Operator&#xff0c;用于自动化部署和…

Git CLI高危任意文件写入漏洞(CVE-2025-48384)PoC已公开

Git CLI&#xff08;命令行界面&#xff09;中存在一个高危漏洞&#xff0c;攻击者可利用该漏洞在Linux和macOS系统上实现任意文件写入。目前该漏洞的概念验证&#xff08;PoC&#xff09;利用代码已公开。该漏洞编号为CVE-2025-48384&#xff0c;CVSS严重性评分为8.1分&#x…

前端开发中关于表单内容的使用和基础知识

在前边&#xff0c;我们已经写过Web前端开发&#xff0c;Web前端开发&#xff0c;万字详细博文带你HTML&#xff0c;CSS快速入门&#xff08;上篇&#xff09;和Web前端开发&#xff0c;一文带你HTML&#xff0c;CSS快速入门&#xff08;下篇&#xff09;&#xff0c;使用近两万…