第十一章 缓存之更新/穿透/雪崩/击穿

目录

一、什么是缓存

二、缓存更新策略

2.1. 缓存主动更新策略

2.1.1. Cache Aside模式(主流)‌

2.1.2. Read/Write Through模式‌

2.1‌.3. Write Behind模式‌

2.1.4. 总结

三、缓存穿透

四、缓存雪崩

五、缓存击穿

5.1. 互斥锁实现

5.1.1. 实现流程图

​编辑5.1.2. 主要实现代码

5.2. 逻辑过期实现

六、缓存穿透/击穿封装工具类


本文中的图片内容部分来源于黑马程序员教程案例

一、什么是缓存

‌缓存是一种用于临时存储数据的技术或机制,旨在提高数据访问速度和系统性能。‌ 缓存通常位于计算机系统内部或附近,可以是硬件、软件或两者的结合体。例如,Web浏览器可以将最常访问的网页内容缓存在本地,以便下次访问时无需从远程服务器重新下载。‌

缓存的作用和原理在于利用程序局部性原理,将频繁访问的数据存储在高速存储器中,如‌SRAM或‌DRAM,以便快速访问。当硬件需要读取数据时,首先在缓存中查找,如果找到则直接执行,否则从内存中查找。由于缓存的运行速度比内存快得多,因此缓存的作用是帮助硬件更快地运行。

缓存可以根据不同的分类标准进行分类。例如,根据存储位置和用途的不同,可以分为‌CPU缓存、‌硬盘缓存、‌内存缓存等。CPU缓存包括‌L1、L2和L3缓存,硬盘缓存用于提高数据传输速度。此外,还有‌HTTP缓存、‌浏览器缓存等。

虽然缓存可以提高系统性能,但也会引入一定的数据一致性问题。因为缓存中的数据可能会与后端数据源(如数据库)存在不一致的情况,所以在使用缓存时需要考虑数据的更新和缓存的过期策略,以保证数据的一致性。

二、缓存更新策略

2.1. 缓存主动更新策略

指在更新数据库中的数据时,如何同步更新缓存中的数据,以保证数据的一致性。常见的缓存更新策略包括‌Cache Aside模式、‌Read/Write Through模式和‌Write Behind模式。

2.1.1. Cache Aside模式(主流)

Cache Aside模式是最常用的缓存更新策略,其中又分为两种:

1. 先删除缓存,再操作数据库

2. 先操作数据库,再删除缓存(主流),其操作步骤如下:

  1. 先更新数据库中的数据。
  2. 然后删除缓存中的数据,或者让缓存失效。
  3. 当下次查询时,如果缓存失效,则重新从数据库中读取数据并更新缓存。

注:由于操作缓存和数据库的话存在线程安全性问题,相较于先删除缓存再操作数据库而言,先操作数据库再删除缓存对于产生线程安全性的概率较低,因为写缓存的速度比更新数据库要快很多:线程1在查询缓存未命中后继续查询数据库时,线程2进来更新数据库,绝大部分情况下线程2更新数据库期间,线程1已经完成了缓存的写入。

2.1.2. Read/Write Through模式

Read/Write Through模式将缓存和数据库整合为一个服务,由服务来维护一致性。操作步骤如下:

  1. 查询操作直接从缓存中读取数据。
  2. 如果缓存中不存在数据,则直接从数据库中读取并返回给用户,同时将数据写入缓存。

2.1‌.3. Write Behind模式

Write Behind模式由其他线程异步地将缓存数据持久化到数据库,保证最终一致性。操作步骤如下:

  1. 更新操作只更新缓存。
  2. 由其他线程异步地将缓存数据写入数据库。

2.1.4. 总结

不同策略的优缺点

Cache Aside模式的优点是简单易实现,但存在数据不一致的风险。Read/Write Through模式的优点是数据一致性高,但性能较低。Write Behind模式的优点是最终一致性,但需要额外的线程管理。

不同场景下的适用性

Cache Aside模式适用于读多写少的场景,简单高效。Read/Write Through模式适用于对数据一致性要求高的场景。Write Behind模式适用于写操作较少,可以容忍最终一致性的场景。

缓存更新策略的最佳实践方案

1. 低一致性需求:使用Redis自带的内存淘汰机制

2. 高一致性需求:主动更新,并以超时剔除作为兜底方案

读操作:

  • 缓存命中则直接返回
  • 缓存未命中则查询数据库,并写入缓存,设定超时时间

写操作:

  • 先写数据库,然后再删除缓存
  • 要确保数据库与缓存操作的原子性

整个写操作的逻辑,我们要确保事务性,如在单体的Spring的JavaWeb项目业务代码的Service实现层添加@Transactional注解,分布式的项目中可以通过TTC模式来控制,当比如删除Redis缓存出现异常,直接回滚数据库的写操作。

三、缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会到数据库。

常见的解决方案有两种:

3.1. 缓存空对象(项目中主流用法):

  • 优点:实现简单,维护方便
  • 缺点:额外的内存消耗以及可能造成短期的不一致

2. 布隆过滤器

  • 优点:内存占用较少,没有多余key
  • 缺点:实现复杂、存在误判可能

3. 参数校验过滤不合法请求

通过对用户输入的参数进行校验,例如检查ID的格式是否合法,如果不合法则直接拒绝请求。这种方法可以过滤掉一部分恶意伪造的用户请求,减少对数据库的压力。

4. 使用分布式锁

在缓存未命中时,通过分布式锁控制只有一个请求去查询数据库,其他请求等待锁释放。这样可以防止多个请求同时查询数据库,减轻数据库的压力。

5. 服务降级和限流

在面对大量的恶意请求时,可以通过服务降级或限流的方式来保护后端服务。限流可以限制到达数据库的请求数量,避免数据库压力过大。

四、缓存雪崩

缓存雪崩是指由于缓存中大量数据同时失效或缓存服务器故障,导致大量请求直接打到数据库上,引发数据库压力激增,可能导致整个系统崩溃的现象。‌ 缓存雪崩通常由于缓存的过期策略不当或缓存服务器故障导致。例如,如果大量的缓存数据设置为在同一时间点过期,或者缓存服务器出现问题无法提供服务,所有的请求将直接访问后端存储系统,导致后端系统瞬时承受巨大的负载压力‌

解决方案:

  • 给不同的key的TTL失效时间设置随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

五、缓存击穿

缓存击穿是指当一个缓存中的热点数据过期或被删除后,大量并发请求同时到达,导致这些请求直接穿透缓存访问数据库,从而增加数据库的负载。‌ 这种情况通常发生在热点数据过期或删除的瞬间,导致短时间内大量请求无法通过缓存获取数据,直接访问数据库,造成数据库负载急剧增加‌。

缓存击穿的具体场景包括:

  • 热点数据过期‌:当缓存中的热点数据过期时,大量请求会同时查询后端数据库,导致数据库负载增加‌。
  • 第一次请求‌:对于一个之前从未被请求过的数据,当它第一次被请求时,缓存中没有该数据,导致请求穿透到后端存储‌。

解决缓存击穿的方法包括:

  • 设置热点数据永不过期‌:将热点数据的缓存过期时间设置为较长的时间,甚至是永不过期。这种方法可以避免缓存击穿,但可能导致数据不够及时和准确‌。
  • 使用互斥锁‌:在数据失效时,通过设置互斥锁来保护数据库访问过程。如果某个请求已经获取到了锁,其他请求需要等待,直到获取到锁为止。这种方法可以避免大量并发请求同时访问数据库,但可能导致并发性能下降和请求等待时间增加‌

注意:这两种方案没有孰优孰劣,在实际项目中,我们要针对业务场景和需求,权衡自身是更注重可用性还是一致性来做选择。

5.1. 互斥锁实现

5.1.1. 实现流程图

互斥锁的实现主要基于Redis的命令setnx再附加一个失效时间,key不存在时可以往Redis中写入(返回值1),否则写入失败(返回值0),同一时期如果有多人写入同一个key,只有一人能成功,以此达到互斥锁的效果:


5.1.2. 主要实现代码

package com.hmdp.service;import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.baomidou.mybatisplus.extension.service.IService;public interface IShopService extends IService<Shop> {Result queryById(Long id);Result update(Shop shop);
}
package com.hmdp.service.impl;import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.hmdp.utils.CacheClient;
import com.hmdp.utils.SystemConstants;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.domain.geo.GeoReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.*;@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 互斥锁解决缓存击穿Shop shop = queryWithMutex(id);if (shop == null) {return Result.fail("店铺不存在");}return Result.ok(shop);}@Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if (id == null) {return Result.fail("id不能为空");}// 1.更新数据库updateById(shop);// 2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + id);return Result.ok();}public Shop queryWithMutex(Long id) {String key = CACHE_SHOP_KEY + id;// 1. 从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2. 判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3. 存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}// 判断命中的是否为null空值,如果是除了null以外的""和" \t\n"这类空串,则直接返回错误信息if (shopJson != null) {// 返回一个错误信息return null;}// 4. 实现缓存重建// 4.1. 获取互斥锁String lockKey = "lock:shop" + id;Shop shop = null;try {boolean isLock = trylock(lockKey);// 4.2. 判断是否获取成功if (!isLock) {// 4.3. 失败,则休眠并重试Thread.sleep(50);return queryWithMutex(id);}// 4.4 获取成功,则根据id查询数据库shop = getById(id);// 5. 不存在,则返回错误if (shop == null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}//  6. 存在,写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException();} finally {// 释放互斥锁unlock(lockKey);}// 7. 返回return shop;}private boolean trylock(String key) {// 不要直接返回flag,因这个值可能会为空,直接拆箱会可能报空指针Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void unlock(String key) {stringRedisTemplate.delete(key);}
}

5.2. 逻辑过期实现

缓存击穿往往发生在高并发访问的热点数据上,因此实际项目中,在我们的预期里,缓存击穿的热点数据是能够命中,即存在的。对于未命中的key,我们认定为非热点数据,因此直接返回空,不做逻辑过期处理。

package com.hmdp.utils;import lombok.Data;
import java.time.LocalDateTime;/*** 作为封装的Redis缓存对象*/
@Data
public class RedisData {/*** 逻辑过期时间*/private LocalDateTime expireTime;/*** 缓存的业务实体Bean,如用户、商家等*/private Object data;
}
package com.hmdp.service;import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.baomidou.mybatisplus.extension.service.IService;public interface IShopService extends IService<Shop> {Result queryById(Long id);Result update(Shop shop);
}
package com.hmdp.service.impl;import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.hmdp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 逻辑过期Shop shop = queryWithLogicalExpire(id);if (shop == null) {return Result.fail("店铺不存在!");}// 7.返回return Result.ok(shop);}@Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if (id == null) {return Result.fail("id不能为空");}// 1.更新数据库updateById(shop);// 2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + id);return Result.ok();}/*** 逻辑过期代码实现* @param id* @return*/public Shop queryWithLogicalExpire(Long id) {String key = CACHE_SHOP_KEY + id;// 1. 从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2. 判断缓存是否命中,未命中表明不是我们的热点数据,直接返回空(实际项目中,在我们的预期里,缓存击穿的热点数据是能够命中的)if (StrUtil.isBlank(shopJson)) {// 3. 未命中,直接返回空return null;}// 4. 命中,需要把json反序列化为对象RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 5. 判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {// 5.1. 未过期则直接返回店铺信息return shop;}// 5.2. 已过期需要缓存重建// 6. 缓存重建// 6.1. 获取互斥锁String lockKey = LOCK_SHOP_KEY +id;boolean isLock = trylock(lockKey);// 6.2. 判断是否获取锁成功if (isLock) {// 6.3. 成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {this.saveShop2Redis(id, 20L);} catch (Exception e) {throw new RuntimeException(e);} finally {unlock(lockKey);}});}// 6.4. 返回过期的商铺信息return shop;}private void saveShop2Redis(Long id, Long expireSeconds) {// 1. 查询店铺数据Shop shop = getById(id);// 2. 封装逻辑过期时间RedisData redisData = new RedisData();redisData.setData(shop);// 当前时间加上传入的指定秒数(即多少秒后过期)redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));// 3. 写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));}private boolean trylock(String key) {// 不要直接返回flag,因这个值可能会为空,直接拆箱会可能报空指针Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void unlock(String key) {stringRedisTemplate.delete(key);}
}

六、缓存穿透/击穿封装工具类

package com.hmdp.utils;import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL;
import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 设置逻辑过期RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}/*** 缓存穿透* @param keyPrefix* @param id* @param type* @param dbFallback* @param time* @param unit* @param <R>* @param <ID>* @return*/public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){String key = keyPrefix + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(json)) {// 3.存在,直接返回return JSONUtil.toBean(json, type);}// 判断命中的是否是空值if (json != null) {// 返回一个错误信息return null;}// 4.不存在,根据id查询数据库R r = dbFallback.apply(id);// 5.不存在,返回错误if (r == null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisthis.set(key, r, time, unit);return r;}/*** 逻辑过期解决缓存击穿* @param keyPrefix* @param id* @param type* @param dbFallback* @param time* @param unit* @param <R>* @param <ID>* @return*/public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isBlank(json)) {// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期,直接返回店铺信息return r;}// 5.2.已过期,需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){// 6.3.成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 查询数据库R newR = dbFallback.apply(id);// 重建缓存this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {// 释放锁unlock(lockKey);}});}// 6.4.返回过期的商铺信息return r;}/*** 互斥锁解决缓存击穿* @param keyPrefix* @param id* @param type* @param dbFallback* @param time* @param unit* @param <R>* @param <ID>* @return*/public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回return JSONUtil.toBean(shopJson, type);}// 判断命中的是否是空值if (shopJson != null) {// 返回一个错误信息return null;}// 4.实现缓存重建// 4.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;R r = null;try {boolean isLock = tryLock(lockKey);// 4.2.判断是否获取成功if (!isLock) {// 4.3.获取锁失败,休眠并重试Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.获取锁成功,根据id查询数据库r = dbFallback.apply(id);// 5.不存在,返回错误if (r == null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisthis.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.释放锁unlock(lockKey);}// 8.返回return r;}/*** 加锁* @param key* @return*/private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}/*** 解锁* @param key*/private void unlock(String key) {stringRedisTemplate.delete(key);}
}

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

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

相关文章

Elasticsearch学习笔记(五)Elastic stack安全配置二

一、手动配置http层SSL 通过前面的配置&#xff0c;我们为集群传输层手动配置了TLS&#xff0c;集群内部节点之间的通信使用手动配置的证书进行加密&#xff0c;但是集群与外部客户端的http层目前还是使用的自动配置&#xff0c;集群中HTTP的通信目前仍然使用自动生成的证书ht…

SQL Injection | MySQL 数据库概述

关注这个漏洞的其他相关笔记&#xff1a;SQL 注入漏洞 - 学习手册-CSDN博客 0x01&#xff1a;MySQL 数据库简介 MySQL 是一个流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它基于 SQL &#xff08;Structured Query Language&#xff09;进行操作。My…

Python库matplotlib之六

Python库matplotlib之六 动画FuncAnimation构造器成员函数应用例子 动画 Matplotlib基于其绘图功能&#xff0c;还提供了一个使用动画模块&#xff0c;生成动画的接口。动画是一系列帧&#xff0c;其中每个帧对应于图形上的一个图。 Matplotlib使用两个类来实现动画&#xff…

Backend - MySQL Server、HeidiSQL

目录 一、MySQL Server &#xff08;一&#xff09;官网下载 &#xff08;二&#xff09;安装与配置 二、HeidiSQL软件 &#xff08;一&#xff09;安装 1. 官网下载 2. 打开 3. 使用 &#xff08;1&#xff09;打开服务 &#xff08;2&#xff09;新增数据库 ​&#xff…

python networkx 计算路径A*

import matplotlib.pyplot as plt # 导入 Matplotlib 工具包 import networkx as nx # 导入 NetworkX 工具包 from typing import List# 初始化空的无向图 graph nx.Graph() # 向图中添加多条赋权边: (node1,node2,weight) graph.add_weighted_edges_from([(1, 2, 50),(1, 3…

集合框架05:List接口使用、List实现类、ArrayList使用

视频链接&#xff1a;13.11 ArrayList使用_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1zD4y1Q7Fw?p11&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.List接口使用代码举例 package com.yundait.Demo01;import java.util.ArrayList; import java.util.List;pu…

dowhy中反驳实验怎么做?

首先&#xff0c;我们打开最新的dowhy版本网站。 https://www.pywhy.org/dowhy/v0.11.1/index.html 我们主要看标题栏的User Guide和Examples就可以了&#xff0c;如果在User Guide 里找不到使用方法&#xff0c;就去Examples里找例子&#xff0c;里面的数据读取修改为自己的数…

Copilot Coaching新功能铸就Word更强

Copilot 的意思是副驾驶。 现在&#xff0c;您的副驾驶教练来了&#xff1a;Copilot Coaching Copilot Coaching 是 Word 中的一项新 Copilot 功能&#xff0c;可在您查看内容时为您提供支持&#xff0c;以实现语法和拼写之外的改进 - 帮助您澄清想法&#xff0c;并为您提供有…

前端反馈弹框组件封装

一、需求背景 需要针对某个功能进行用户调查反馈&#xff0c;设计一个弹框&#xff0c;进行后端入表记录&#xff0c;以便后期进行数据分析。 二、实现UI 三、代码留存 以vue为例 <template><div class"advice-container"><van-dialogv-model"…

【父子线程传值TransmittableThreadLocal使用踩坑-及相关知识拓展】

文章目录 一.业务背景二.TransmittableThreadLocal是什么&#xff1f;三.问题复现1.定义注解DigitalAngel2.定义切面3.TransmittableThreadLocal相关4.线程池配置信息5.Controller6.Service7.测试结果8.问题分析9 解决办法及代码改造10.最终测试&#xff1a; 四.与 ThreadLocal…

02复写零

复写零 我们先进行异地复写&#xff1a;代码如下 public class Test {public static void main(String[] args) {int []array {1,0,2,3,0,4};duplicateZeros(array);}public static void duplicateZeros(int[] arr) {int [] elemnew int[arr.length];for(int cur0,dest0;des…

QD1-P17 HTML 下拉框标签(select、option)

本节学习 HTML 常用标签&#xff1a;select和option ‍ 本节视频 www.bilibili.com/video/BV1n64y1U7oj?p17 ‍ 知识点1&#xff1a;select标签用法 演示 ​​ HTML <select name"city"><option>北京</option><option>上海</opti…

新版Win32高级编程教程-学习笔记01:应用程序分类

互联网行业 算法研发工程师 目录 新版Win32高级编程教程-学习笔记01&#xff1a;应用程序分类 控制台程序 强烈注意 窗口程序 启动项 程序入口函数 库程序 静态库 动态库程序 几种应用程序的区别 控制台程序 本身没有窗口&#xff0c;其中的doc窗口&#xff0c;是管…

【通信协议讲解】单片机基础重点通信协议解析与总结(IIC,CAN,MODBUS...)

目录 一.IIC总线 基础特性&#xff1a; 配置特性&#xff1a; 时序特性&#xff1a; 二.SPI总线 基础特性&#xff1a; 配置特性&#xff1a; 时序特性&#xff1a; 三.串口通信 基础特性&#xff1a; 配置特性&#xff1a; 时序特性&#xff1a; 四.CAN总线 基础特性…

三款GIS工具多角度对比:免费的倾斜摄影OSGB/3Dtiles编辑转换发布平台

GIS数据处理工具在现代技术与应用中扮演着至关重要的角色&#xff0c;它们不仅是连接原始地理信息与可分析、可视化数据的桥梁&#xff0c;更是推动地理信息系统&#xff08;GIS&#xff09;在各个行业领域深入发展与应用不可或缺的关键工具。选择一款合适的工具直接关系到数据…

HttpURLConnection学习

介绍 HttpURLConnection类是位于java.net包下继承了URLConnection类的一个抽象类&#xff0c;每个 HttpURLConnection 实例都用于发出单个请求。 URL HttpURLConnection的使用需要依赖URL类&#xff0c;URL类位于java.net包下&#xff0c;有很多种构造方法。 HttpURLConnect…

AI引起用人格局变动,个人如何应对这一趋势

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 人工智能的发展带来的就业结构变革&#xf…

论文笔记:D-vlog 用于抑郁症检测的多模态数据集

整理了AAAI2022 D-vlog: Multimodal Vlog Dataset for Depression Detection 论文的阅读笔记 背景方法特征提取模型 实验数据集主实验不同模态的性能性别的影响 背景 以往关于抑郁症检测的工作大多集中在实验室环境下对抑郁症个体的检测&#xff0c;难以在实践中推广。本文提出…

图解C#高级教程(五):枚举器和迭代器

本章主要介绍 C# 当中枚举器、可枚举类型以及迭代器相关的知识。 文章目录 1. 枚举器和可枚举类型2. IEnumerator 和 IEnumerable 接口2.1 IEnumerator 接口2.2 IEnumerable 接口 3. 泛型枚举接口4. 迭代器4.1 使用迭代器创建枚举器4.2 使用迭代器创建可枚举类4.3 迭代器作为属…

消峰限流有哪几种方式?

消峰限流的方式 业务视角 验证码回答问题环节 技术视角 消息队列异步化用户请求 限流&#xff0c;对流量进行层层过滤 nginx 层限流&#xff0c; 一是控制速率 limit_req 漏桶算法 limit_req_zone $binary_remote_addr zonemylimit:10m rate2r/s; server { location / { lim…