Redis 缓存穿透、击穿、雪崩:防御与解决方案大全

🛡️ Redis 缓存穿透、击穿、雪崩:防御与解决方案大全

文章目录

  • 🛡️ Redis 缓存穿透、击穿、雪崩:防御与解决方案大全
  • 🧠 一、缓存穿透:防御不存在数据的攻击
    • 💡 问题本质与危害
    • 🛡️ 解决方案
    • 📊 布隆过滤器 vs 空值缓存
  • ⚡ 二、缓存击穿:保护热点数据瞬间失效
    • 💡 问题本质与危害
    • 🛡️ 解决方案
    • 📊 缓存击穿解决方案对比
  • ❄️ 三、缓存雪崩:预防大规模缓存失效
    • 💡 问题本质与危害
    • 🛡️ 解决方案
    • 📊 缓存雪崩解决方案对比
  • 🚀 四、实战案例:电商与秒杀场景
    • 🛒 电商商品详情页防护
    • ⚡ 秒杀系统缓存防护
    • 📊 电商场景防护策略对比
  • 💡 五、总结与最佳实践
    • 📋 防护策略 Checklist
    • 🏗️ 架构设计建议
    • 🔧 应急响应方案
    • 🚀 性能优化建议

🧠 一、缓存穿透:防御不存在数据的攻击

💡 问题本质与危害

​​缓存穿透​​​​是指查询​​​​根本不存在的数据​​​​,导致请求直接穿透缓存到达数据库:

不存在
存在
恶意请求
查询缓存
数据是否存在?
查询数据库
返回缓存数据
数据库压力剧增
系统崩溃风险

​​攻击场景​​:

  1. 恶意请求随机ID或不存在的关键词
  2. 爬虫遍历所有可能的ID 业
  3. 务逻辑缺陷导致查询无效数据

🛡️ 解决方案

  1. 布隆过滤器(Bloom Filter)
    ​​原理​​:使用概率型数据结构快速判断元素是否存在
public class BloomFilterProtection {private BloomFilter<String> bloomFilter;private Jedis jedis;public BloomFilterProtection() {// 初始化布隆过滤器this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, // 预期元素数量0.01     // 误判率);// 预热数据:将已有数据加入过滤器loadExistingData();}public Object getData(String key) {// 1. 首先检查布隆过滤器if (!bloomFilter.mightContain(key)) {// 肯定不存在,直接返回return null;}// 2. 检查缓存Object value = jedis.get(key);if (value != null) {return value;}// 3. 检查数据库value = database.get(key);if (value != null) {// 缓存并返回jedis.setex(key, 300, serialize(value));return value;} else {// 记录不存在的Key,避免重复查询jedis.setex(key, 60, "NULL"); // 缓存空值return null;}}private void loadExistingData() {// 从数据库加载所有存在的KeyList<String> allKeys = database.getAllKeys();for (String key : allKeys) {bloomFilter.put(key);}}
}
  1. 空值缓存与恶意请求识别
public class NullCacheProtection {private static final String NULL_VALUE = "NULL";private static final int NULL_CACHE_TIME = 60; // 空值缓存60秒public Object getDataWithNullCache(String key) {// 1. 检查缓存Object value = jedis.get(key);if (NULL_VALUE.equals(value)) {// 之前已确认为空值return null;}if (value != null) {return value;}// 2. 检查数据库value = database.get(key);if (value != null) {jedis.setex(key, 300, serialize(value));return value;} else {// 缓存空值,设置较短过期时间jedis.setex(key, NULL_CACHE_TIME, NULL_VALUE);// 记录访问频率,识别恶意请求recordAccessPattern(key);return null;}}private void recordAccessPattern(String key) {String counterKey = "access:counter:" + key;long count = jedis.incr(counterKey);jedis.expire(counterKey, 60);if (count > 100) { // 60秒内超过100次访问// 识别为恶意请求,加入黑名单jedis.sadd("blacklist:keys", key);jedis.expire(key, 3600); // 黑名单1小时}}
}

📊 布隆过滤器 vs 空值缓存

方案优点缺点适用场景
布隆过滤器内存占用小,判断快有误判率,需要预热海量数据存在性判断
空值缓存实现简单,无额外依赖可能缓存大量无效Key数据量不大,恶意请求较少
组合方案综合优势,防护全面实现复杂度较高高安全要求场景

⚡ 二、缓存击穿:保护热点数据瞬间失效

💡 问题本质与危害

​​缓存击穿​​​​是指​​​​热点Key在过期瞬间​​​​,大量并发请求直接访问数据库:

客户端1客户端2客户端3Redis缓存数据库热点Key同时过期获取热点Key返回null(已过期)获取热点Key返回null(已过期)获取热点Key返回null(已过期)查询数据查询数据查询数据瞬间高并发压力返回数据返回数据返回数据客户端1客户端2客户端3Redis缓存数据库

🛡️ 解决方案

  1. 互斥锁(Mutex Lock)
    ​​原理​​:只允许一个线程重建缓存,其他线程等待
public class MutexLockSolution {private static final String LOCK_PREFIX = "lock:";private static final int LOCK_TIMEOUT = 3000; // 锁超时3秒public Object getDataWithLock(String key) {// 1. 尝试从缓存获取Object value = jedis.get(key);if (value != null) {return value;}// 2. 获取分布式锁String lockKey = LOCK_PREFIX + key;boolean locked = tryLock(lockKey);if (locked) {try {// 3. 再次检查缓存(双重检查锁)value = jedis.get(key);if (value != null) {return value;}// 4. 查询数据库value = database.get(key);if (value != null) {// 5. 写入缓存jedis.setex(key, 300, serialize(value));}return value;} finally {// 6. 释放锁releaseLock(lockKey);}} else {// 未获取到锁,短暂等待后重试try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}return getDataWithLock(key); // 重试}}private boolean tryLock(String lockKey) {// 使用SETNX实现分布式锁String result = jedis.set(lockKey, "locked", "NX", "PX", LOCK_TIMEOUT);return "OK".equals(result);}private void releaseLock(String lockKey) {jedis.del(lockKey);}
}
  1. 逻辑过期与永不过期策略
public class LogicalExpirationSolution {private static class CacheData {Object data;long expireTime; // 逻辑过期时间public boolean isExpired() {return System.currentTimeMillis() > expireTime;}}public Object getDataWithLogicalExpire(String key) {// 1. 从缓存获取数据String cacheValue = jedis.get(key);if (cacheValue == null) {// 缓存不存在,正常加载return loadDataFromDb(key);}// 2. 反序列化CacheData cacheData = deserialize(cacheValue);// 3. 检查是否逻辑过期if (!cacheData.isExpired()) {return cacheData.data;}// 4. 已过期,获取锁重建缓存String lockKey = "rebuild:" + key;if (tryLock(lockKey)) {try {// 再次检查是否已被其他线程更新String latestValue = jedis.get(key);CacheData latestData = deserialize(latestValue);if (latestData.isExpired()) {// 重建缓存Object newData = database.get(key);CacheData newCacheData = new CacheData();newCacheData.data = newData;newCacheData.expireTime = System.currentTimeMillis() + 300000; // 5分钟jedis.set(key, serialize(newCacheData));return newData;} else {return latestData.data;}} finally {releaseLock(lockKey);}} else {// 未获取到锁,返回旧数据return cacheData.data;}}
}
  1. 热点数据预热与监控
public class HotKeyMonitor {private static final double HOT_THRESHOLD = 1000; // QPS阈值public void monitorHotKeys() {// 定时分析热点KeyScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);scheduler.scheduleAtFixedRate(() -> {Map<String, Long> accessStats = getAccessStatistics();for (Map.Entry<String, Long> entry : accessStats.entrySet()) {if (entry.getValue() > HOT_THRESHOLD) {// 发现热点Key,提前刷新preloadHotKey(entry.getKey());}}}, 0, 30, TimeUnit.SECONDS); // 每30秒检查一次}private void preloadHotKey(String key) {// 1. 获取数据Object data = database.get(key);// 2. 异步刷新缓存(使用更长的过期时间)CompletableFuture.runAsync(() -> {jedis.setex(key, 3600, serialize(data)); // 1小时过期log.info("热点Key {} 已预热", key);});}
}

📊 缓存击穿解决方案对比

方案优点缺点适用场景
互斥锁保证数据一致性,实现简单有等待时间,可能阻塞数据一致性要求高的场景
逻辑过期无等待时间,用户体验好可能返回旧数据,实现复杂可接受短暂数据不一致的场景
永不过期+异步更新完全避免击穿,性能好数据更新延迟,复杂度高极少变更的热点数据

❄️ 三、缓存雪崩:预防大规模缓存失效

💡 问题本质与危害

​​缓存雪崩​​​​是指​​​​大量Key同时失效​​​​,导致所有请求直接访问数据库:

大量缓存Key同时失效
海量请求直达数据库
数据库连接池占满
数据库响应变慢
应用线程阻塞
系统全面崩溃

典型场景​​:

  1. 缓存服务器重启
  2. 大量Key设置相同过期时间
  3. 缓存服务故障

🛡️ 解决方案

  1. 随机过期时间策略
public class RandomExpirationSolution {private static final int BASE_EXPIRE = 3600; // 基础过期时间1小时private static final int RANDOM_RANGE = 600; // 随机范围10分钟public void setWithRandomExpire(String key, Object value) {// 生成随机过期时间int randomExpire = BASE_EXPIRE + ThreadLocalRandom.current().nextInt(RANDOM_RANGE);jedis.setex(key, randomExpire, serialize(value));}public void batchSetWithRandomExpire(Map<String, Object> dataMap) {for (Map.Entry<String, Object> entry : dataMap.entrySet()) {setWithRandomExpire(entry.getKey(), entry.getValue());}}
}
  1. 缓存永不过期 + 异步更新
public class NeverExpireSolution {private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);public void setWithBackgroundRefresh(String key, Object value) {// 1. 设置永不过期的缓存jedis.set(key, serialize(value));// 2. 启动后台刷新任务scheduler.scheduleAtFixedRate(() -> {try {Object freshData = database.get(key);if (freshData != null) {jedis.set(key, serialize(freshData));}} catch (Exception e) {log.error("后台刷新缓存失败: {}", key, e);}}, 30, 30, TimeUnit.MINUTES); // 每30分钟刷新一次}
}
  1. 多级缓存架构
public class MultiLevelCache {private LoadingCache<String, Object> localCache;private Jedis redis;public MultiLevelCache() {// 初始化本地缓存(Guava Cache)this.localCache = Caffeine.newBuilder().maximumSize(10000).expireAfterWrite(5, TimeUnit.MINUTES).refreshAfterWrite(1, TimeUnit.MINUTES).build(this::loadFromRedis);}public Object get(String key) {try {// 1. 首先尝试本地缓存return localCache.get(key);} catch (Exception e) {// 2. 降级到Redisreturn loadFromRedis(key);}}private Object loadFromRedis(String key) {Object value = redis.get(key);if (value == null) {// 3. 最终降级到数据库value = database.get(key);if (value != null) {redis.setex(key, 3600, serialize(value));}}return value;}
}
  1. 熔断降级与限流保护
public class CircuitBreakerProtection {private final CircuitBreaker circuitBreaker;private static final int MAX_QPS = 1000;public Object getDataWithProtection(String key) {// 1. 检查熔断器状态if (circuitBreaker.isOpen()) {return getFallbackData(key);}try {// 2. 限流保护if (!rateLimiter.tryAcquire()) {return getFallbackData(key);}// 3. 正常业务逻辑Object value = jedis.get(key);if (value == null) {value = database.get(key);if (value != null) {jedis.setex(key, 300, serialize(value));}}// 4. 记录成功,重置熔断器circuitBreaker.recordSuccess();return value;} catch (Exception e) {// 5. 记录失败,可能触发熔断circuitBreaker.recordFailure();return getFallbackData(key);}}private Object getFallbackData(String key) {// 降级策略:返回默认值或缓存旧数据return Collections.emptyMap();}
}

📊 缓存雪崩解决方案对比

方案优点缺点适用场景
随机过期时间实现简单,效果明显不能完全避免雪崩预防性措施
永不过期+异步更新完全避免雪崩数据更新有延迟数据变更不频繁的场景
多级缓存提供额外保护层增加系统复杂度高可用要求场景
熔断降级保护数据库免于崩溃影响用户体验极端情况下的保护措施

🚀 四、实战案例:电商与秒杀场景

🛒 电商商品详情页防护

public class ProductDetailService {private static final String PRODUCT_PREFIX = "product:";private BloomFilter<String> bloomFilter;private RateLimiter rateLimiter;public ProductDetail getProductDetail(Long productId) {String key = PRODUCT_PREFIX + productId;// 1. 布隆过滤器防护if (!bloomFilter.mightContain(key)) {return null; // 肯定不存在}// 2. 限流防护if (!rateLimiter.tryAcquire()) {throw new RateLimitException("访问过于频繁");}// 3. 缓存查询ProductDetail detail = jedis.get(key);if (detail != null) {return detail;}// 4. 互斥锁重建缓存String lockKey = "lock:" + key;if (tryLock(lockKey)) {try {// 双重检查detail = jedis.get(key);if (detail != null) {return detail;}// 数据库查询detail = productDao.getById(productId);if (detail != null) {// 设置随机过期时间int expireTime = 3600 + ThreadLocalRandom.current().nextInt(600);jedis.setex(key, expireTime, serialize(detail));} else {// 缓存空值jedis.setex(key, 300, "NULL");}return detail;} finally {releaseLock(lockKey);}} else {// 等待后重试try {Thread.sleep(100);return getProductDetail(productId);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("获取商品详情失败");}}}
}

⚡ 秒杀系统缓存防护

public class SeckillService {private static final String STOCK_PREFIX = "seckill:stock:";private static final String ITEM_PREFIX = "seckill:item:";public SeckillResult seckill(Long userId, Long itemId) {String stockKey = STOCK_PREFIX + itemId;String itemKey = ITEM_PREFIX + itemId;// 1. 校验商品是否存在if (!jedis.exists(itemKey)) {return SeckillResult.error("商品不存在");}// 2. Lua脚本保证原子性操作String script = """local stockKey = KEYS[1]local stock = tonumber(redis.call('GET', stockKey))if stock <= 0 thenreturn 0endredis.call('DECR', stockKey)return 1""";Long result = (Long) jedis.eval(script, 1, stockKey);if (result == 1) {// 3. 扣减成功,创建订单String orderId = createOrder(userId, itemId);return SeckillResult.success(orderId);} else {return SeckillResult.error("库存不足");}}public void preheatSeckillData(Long itemId, Integer stock) {// 预热秒杀数据String stockKey = STOCK_PREFIX + itemId;String itemKey = ITEM_PREFIX + itemId;// 1. 设置库存(永不过期)jedis.set(stockKey, stock.toString());// 2. 设置商品信息(逻辑过期)SeckillItem item = seckillDao.getItem(itemId);jedis.set(itemKey, serialize(item));// 3. 启动后台刷新任务startBackgroundRefresh(itemId);}
}

📊 电商场景防护策略对比

场景主要风险防护策略关键技术
商品详情页缓存穿透、击穿布隆过滤器+互斥锁存在性判断、分布式锁
秒杀活动缓存雪崩、超卖原子操作+库存预热Lua脚本、库存隔离
购物车数据一致性多级缓存+异步更新本地缓存、数据同步
订单查询热点数据逻辑过期+限流熔断器、限流器

💡 五、总结与最佳实践

📋 防护策略 Checklist

​​预防缓存穿透​​:

  • ✅ 布隆过滤器校验数据存在性
  • ✅ 缓存空值并设置较短过期时间
  • ✅ 接口层参数校验和限流
  • ✅ 恶意请求识别和黑名单机制

​​预防缓存击穿​​:

  • ✅ 互斥锁重建缓存
  • ✅ 逻辑过期时间策略
  • ✅ 热点数据预加载和监控
  • ✅ 永不过期策略+后台刷新

​​预防缓存雪崩​​:

  • ✅ 随机过期时间分散失效
  • ✅ 多级缓存架构
  • ✅ 熔断降级机制
  • ✅ 数据库限流保护

🏗️ 架构设计建议

​​多级缓存架构​​:

缓存命中
缓存命中
缓存未命中
限流熔断
恶意拦截
客户端
本地缓存
分布式缓存
数据库
监控系统
防护系统

​​监控指标体系​​:

# 关键监控指标
metrics:- name: cache_penetration_ratedescription: 缓存穿透率threshold: < 0.1%- name: cache_breakdown_countdescription: 缓存击穿次数threshold: < 10次/分钟- name: cache_avalanche_riskdescription: 缓存雪崩风险threshold: 同时失效Key < 1%- name: database_qpsdescription: 数据库查询QPSthreshold: < 最大承载能力的60%- name: cache_hit_ratedescription: 缓存命中率threshold: > 90%

🔧 应急响应方案

​​故障处理流程​​:

监控告警
确定问题类型
问题分类
缓存穿透
缓存击穿
缓存雪崩
启用布隆过滤器
设置空值缓存
添加互斥锁
调整过期策略
分散过期时间
启用熔断降级
验证解决效果
恢复监控
总结优化

🚀 性能优化建议

​​Redis 配置优化​​:

# redis.conf 优化配置
maxmemory 16gb
maxmemory-policy allkeys-lru
timeout 300
tcp-keepalive 60# 持久化配置
appendonly yes
appendfsync everysec
aof-rewrite-incremental-fsync yes# 慢查询配置
slowlog-log-slower-than 10000
slowlog-max-len 128

​​客户端优化​​:

// 连接池配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(1000);
config.setMaxIdle(500);
config.setMinIdle(100);
config.setMaxWaitMillis(2000);
config.setTestOnBorrow(true);// Pipeline批量操作
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 100; i++) {pipeline.get("key:" + i);
}
List<Object> results = pipeline.syncAndReturnAll();

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

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

相关文章

量子计算机的发展对传统密码学的打击

量子计算机的发展对传统密码学的核心威胁&#xff0c;源于其能高效解决传统计算机“计算不可行”的数学问题——而这些问题正是当前主流密码算法保障安全的基石。这种影响并非“全面摧毁”&#xff0c;而是针对传统密码学的不同分支&#xff08;非对称密码、对称密码、哈希函数…

《var, let, const:现代JS声明指南》

文章目录JavaScript 中 var、let、const 的差异1. 作用域&#xff08;Scope&#xff09;2. 变量提升&#xff08;Hoisting&#xff09;3. 重复声明4. 变量值是否可变对比表5. 示例代码总结JavaScript 中 var、let、const 的差异 1. 作用域&#xff08;Scope&#xff09; var 函…

在 Docker 中安装 MySQL 教程

拉取 MySQL 镜像docker pull mysql:8.0创建并启动 MySQL 容器docker run -d \--name mysql8 \-p 3306:3306 \-e MYSQL_ROOT_PASSWORD123456 \-v mysql_data:/var/lib/mysql \mysql:8.0命令说明&#xff1a;-d&#xff1a;后台运行容器 --name mysql8&#xff1a;给容器起个名字…

C#线程理解

目录 一.线程类 1.基础线程类&#xff08;Thread&#xff09; 2.线程池类&#xff08;Threadpool&#xff09; 3.任务并行库&#xff08;Task&#xff09; 4.并行循环&#xff08;Parallel&#xff09; 二.线程池(threadPool)和Thread/Task之间的联系 1.ThreadPool和Thr…

Java入门级教程16——JUC的安全并发包机制

目录 1.JUC的安全并发包机制 1.1 包含 1.2 Barrier(栅栏)机制——CyclicBarrier&#xff08;循环屏障&#xff09; 1.2.1 定义 1.2.2 特性 1.2.1 模拟包车 1.2.2 模拟学生到齐上课 1.2.3 计算任务总耗时 1.3 CountDownLatch(闭锁)机制 1.3.1 定义 1.3.2 特性 1.3.3…

【网络通信】全面解析MAC地址:网络设备的唯一标识

【网络通信】全面解析MAC地址&#xff1a;网络设备的唯一标识 文章目录【网络通信】全面解析MAC地址&#xff1a;网络设备的唯一标识前言一、MAC 地址的定义&#xff1a;设备的 “网络身份证”​二、MAC 地址的格式与组成&#xff1a;48 位的 “数字编码”​三、MAC 地址的工作…

Perforce Klocwork 2025.2版本更新:默认启用现代分析引擎、支持 MISRA C:2025 新规、CI构建性能提升等

Perforce Klocwork 现已更新至2025.2版本&#xff01;该版本增强了对 C/C的分析能力&#xff0c;提升了现代 C 分析的准确性&#xff0c;并改进了对源文件编码的支持。该版本还为 MISRA C:2025 标准引入了新的分类体系&#xff0c;并增强了 Visual Studio Code 插件的可用性。 …

机器人驭风而行:低空经济如何开启智能新纪元【科普类】

新晋码农一枚&#xff0c;小编会定期整理一些写的比较好的代码和知识点&#xff0c;作为自己的学习笔记&#xff0c;试着做一下批注和补充&#xff0c;转载或者参考他人文献会标明出处&#xff0c;非商用&#xff0c;如有侵权会删改&#xff01;欢迎大家斧正和讨论&#xff01;…

Java学习笔记四(继承)

1 继承继承的实现&#xff1a;public class 子类 extends 父类 {… }注释&#xff1a;子类可直接使用&#xff0c;父类&#xff08;保护&#xff0c;公开&#xff09;的属性和方法优点&#xff1a;减少重复代码&#xff0c;缺点&#xff1a;只能单继承// 父类 public class Tes…

NAT技术:SNAT与DNAT区别详解

1. 什么是NAT&#xff1f; 定义&#xff1a;NAT 是一种网络技术&#xff0c;用于在私有网络&#xff08;如家庭或企业局域网&#xff09; 与 公共网络&#xff08;如互联网&#xff09; 之间转换IP地址。它允许使用私有IP地址的设备通过一个&#xff08;或多个&#xff09;公共…

java语言中,list<String>转成字符串,逗号分割;List<Integer>转字符串,逗号分割

java语言中&#xff0c;list<String 转成字符串&#xff0c;逗号分割 在 Java 中&#xff0c;将 List<String> 转成逗号分割的字符串有多种方法&#xff1a; 使用 String.join 方法 String.join 是 Java 8 引入的一个静态方法&#xff0c;它可以方便地将集合中的元素用…

NineData云原生智能数据管理平台新功能发布|2025年8月版

本月发布 11 项更新&#xff0c;其中重点发布 5项、功能优化 6 项。重点发布数据库 DevOps - SQL 窗口支持 PolarDB 系列SQL 窗口新增支持 PolarDB PostgreSQL 与 PolarDB Oracle 数据源&#xff0c;扩展云原生数据库管理能力。新增 AWS 数据源支持新增支持 AWS Aurora Postgre…

【ARDUINO】通过ESP8266连接WIFI,启动TCP,接受TCP客户端指令【测试中】

通过ESP8266连接WIFI&#xff0c;启动TCP&#xff0c;接受TCP客户端指令**记录**2025年9月8日11:20:372025年9月9日08:45:342025年9月11日21:40:22**代码**记录 2025年9月8日11:20:37 【测试情况】 代码可以跑到正确连接WIFI&#xff0c;也能获得IP&#xff0c;但是启动TCP服…

(网络原理)核心知识回顾 网络核心原理 get和post的理解 解析http 加密+请求和响应的一些关键字 Cookie和session 对密钥的理解

目录 核心知识回顾 网络核心原理 get和post的理解 解析http 加密请求和响应的一些关键字 Cookie和session 对密钥的理解 核心知识回顾 网络编程---socket api UDP DatagramSocket DatagramPacket TCP ServerSocket Socket 1.读写数据通过Socket,通过Socket内置的 lnpu…

前端框架对比分析:离线PWA + Cloudflare Workers部署

目录 概述 框架对比表格 详细分析 1. Astro ⭐⭐⭐⭐⭐ **强烈推荐** 2. Next.js ⭐⭐⭐⭐ **推荐** 3. Remix (现React Router) ⭐⭐⭐⭐⭐ **强烈推荐** 4. SvelteKit ⭐⭐⭐⭐ **推荐** 5. Nuxt.js ⭐⭐⭐ **一般推荐** 6. Vite + React ⭐⭐⭐ **基础选择** 推荐方案 🏆 …

9-10关于JS初学产生的问题

1.页面添加加载完成事件监听&#xff0c;页面加载完成后&#xff0c;执行页面初始化方法/函数; 这是什么意思 这句话描述的是前端开发中一种常见的操作&#xff1a;等待页面完全加载完成后&#xff0c;再执行特定的初始化代码。 简单来说&#xff0c;就是要确保页面上的所有元素…

项目中遇到pom文件里使用systemPath的例子记录

项目中遇到pom文件里使用systemPath&#xff0c;很少见&#xff0c;问了下豆包&#xff0c;记录下结果。在 Maven 的 pom.xml 中&#xff0c;<systemPath> 是 <dependency> 标签内的一个可选配置&#xff0c;用于手动指定本地系统中某个依赖包&#xff08;通常是 J…

10、向量与矩阵基础 - 深度学习的数学语言

学习目标:建立向量和矩阵的几何直观理解,掌握线性代数的核心概念,培养空间思维能力,为手搓大模型奠定扎实的数学基础 想象一下,当你使用GPT进行对话时,每个词汇都被转换成高维向量,整个对话历史变成一个巨大的矩阵。模型的"理解"过程,本质上就是在这个高维空…

【Python Tkinter】图形用户界面(GUI)开发及打包EXE指南

【Python Tkinter】图形用户界面&#xff08;GUI&#xff09;开发及打包EXE指南一、关于 Python Tkinter二、密码生成器示例2.1 使用Python添加图形用户界面&#xff08;GUI&#xff09;2.2 使用工具PyInstaller将应用打包成exe文件三、总结一、关于 Python Tkinter Python Tk…

【设计模式】【观察者模式】实例

一对多的统一监听 —— 这就是 观察者模式&#xff08;Observer Pattern&#xff09; 的经典应用场景。也就是说&#xff1a;一个事件源&#xff08;Subject&#xff09; → 可以注册多个监听器&#xff08;Observers&#xff09;&#xff1b;当事件发生时&#xff0c;一次性通…