Redis 篇-深入了解查询缓存与缓存所带来的问题(读写不一致、缓存穿透、缓存雪崩、缓存击穿)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

本章目录

        1.0 什么是缓存

        2.0 项目中具体如何添加缓存

        3.0 添加缓存后所带来的问题

        3.1 读写不一致问题

        3.1.1 缓存更新策略

        3.1.2 具体实现缓存与数据库的双写一致

        3.2 缓存穿透问题

        3.2.1 具体解决缓存穿透问题

        3.3 缓存雪崩问题

        3.4 缓存击穿问题

        3.4.1 利用互斥锁解决缓存击穿问题

        3.4.2 利用逻辑过期解决缓存击穿问题

        4.0 封装 Redis 工具类


        1.0 什么是缓存

        缓存就是数据交换的缓冲器,称作为 Cache,是存放数据的零时地方,一般读写性能较高。缓存的作用可以降低后端负载,提高读写效率、降低响应时间。缓存的成本包括数据一致性成本、代码维护成本、运维成本等。

        

        2.0 项目中具体如何添加缓存

        举例子,在实现根据用户 id 来查询用户信息的功能中,添加缓存的步骤:

        首先,提交用户 id ,先从缓存中查找是否命中目标,就是是否有相同的 id 关键字 key 。如果命中,直接返回该 key 对应的 value 即可;如果没有命中,就需要来到数据库中查询用户信息,继续判断数据库中是否存在该用户 id ,如果不存在,那么返回报错信息;如果存在,那么返回该用户信息的同时,将用户信息写回到 Redis 缓存中。

缓存作用模型图:

代码实现:

    @AutowiredStringRedisTemplate stringRedisTemplate;@Overridepublic String getUserNameById(Integer userId) throws Exception {//先判断userId是否为空if (userId == null){throw new Exception("userId is null");}//先从缓存中查看是否存在该keyString s = stringRedisTemplate.opsForValue().get("user:" + userId);if (s != null){//如果缓存中不为null,则成功从缓存中获取值return s;}//如果从缓存中获取不到,则需要到数据库中获取数据String userName = adminMapper.getUserNameById(userId);//如果数据返回为null,那么数据库中查找不到数据if (userName == null){//直接抛出异常throw new Exception("根据该用户id查找不到用户信息");}//判断数据不为null之后,则需要将该用户信息写到redis中stringRedisTemplate.opsForValue().set("user:"+userId,userName);//最后返回值即可return userName;}

运行结果:

        在第一次查询的时候,redis 第一次时找不到该用户信息,那么就会到数据库中查询,查询完毕之后,将数据写回到 redis 中,再到第二次查询的时候,就可以直接到 redis 中获取数据了。

发送的请求:

第一次获取数据:

        到数据库中获取了

此时 redis 中:

        已经存在该用户信息了

        3.0 添加缓存后所带来的问题

        添加缓存之后,会带来一些问题,比如说:数据库更新之后,缓存还没来得及更新所带来的缓存与数据库数据不一致问题,还有缓存穿透、缓存雪崩、缓存击穿等问题给数据库带来的沉重的“打击”。

        3.1 读写不一致问题

        顾名思义,数据库与缓存中的数据两者不一致,为了解决这个问题,就有了缓存更新策略,可以极大可能维护缓存中的数据和数据库中的数据一致性。

        3.1.1 缓存更新策略

通常的方法有三种:

        1)内存淘汰:不用自己维护,利用 redis 的内存淘汰机制,当内存不足自动淘汰部分数据,下次查询时更新缓存。该方法一致性比较差,无维护成本。

        2)超时剔除:给缓存数据添加 TTL 时间,到期后自动删除缓存,下次查询时更新缓存。该方法一致性一般,维护成本低。

        3)主动更新:

        编写业务逻辑,在修改数据库的同时,更新缓存。该方法一致性比较好,维护成本高。主动更新包含三种常见的策略:

        第一种:Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存。

        第二种:Read/Write Through Pattern:缓存与数据库整合一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。

        第三种:Write Behind Caching Pattern:调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致。

在主动更新中,第一种方式比较常见,实现比较简单。但是在操作缓存和数据库时有三个问题需要考虑:

        第一个问题:删除缓存还是更新缓存?

                更新缓存:每次更新数据库都更新缓存,无效写操作较多。

                删除缓存:更新数据库时让缓存失效,查询时在更新缓存。

                因此,一般来说,选择删除缓存。

        第二个问题:如何保证缓存与数据库的操作的同时成功或失败?

                将缓存与数据库操作放在同一个事务即可,保证其原子性。

        第三个问题:先操作缓存还是先操作数据库?

                先写数据库,然后删除缓存。

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

        1)低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存。

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

                读操作:

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

                写操作:

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

        3.1.2 具体实现缓存与数据库的双写一致

实现高一致性需求:主动更新策略代码:

        1)读操作:缓存命中则直接返回,缓存未命中则查询数据库,并写入缓存,设定超时时间。

    @AutowiredStringRedisTemplate stringRedisTemplate;@Overridepublic String getUserNameById(Integer userId) throws Exception {//先判断userId是否为空if (userId == null){throw new Exception("userId is null");}//先从缓存中查看是否存在该keyString s = stringRedisTemplate.opsForValue().get("user:" + userId);if (s != null){//如果缓存中不为null,则成功从缓存中获取值return s;}//如果从缓存中获取不到,则需要到数据库中获取数据String userName = adminMapper.getUserNameById(userId);//如果数据返回为null,那么数据库中查找不到数据if (userName == null){//直接抛出异常throw new Exception("根据该用户id查找不到用户信息");}//判断数据不为null之后,则需要将该用户信息写到redis中,且设定超时时间stringRedisTemplate.opsForValue().set("user:"+userId,userName,100, TimeUnit.SECONDS);//最后返回值即可return userName;}

        这里的重点是:设置超时时间。

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

        为了保证原子性,需要加上 @Transactional 注解

    @Override@Transactionalpublic void modifyUser(UserDTO userDTO) throws Exception {//先判断userDTO是否为nullif (userDTO == null){throw new Exception("userDTO is null");}//先更新数据库adminMapper.modifyUser(userDTO);//再删除redis缓存Integer userId = userDTO.getUserId();stringRedisTemplate.delete("user"+userId);}

运行结果:

        先查询用户信息,因为第一次 redis 不存在该用户信息,因此需要到数据库中获取该用户信息。

        从数据库中查询信息:

        redis 缓存情况:

        接着去更新用户信息:

        此时,redis 中的用户信息就被删除掉了:

                下一次查询就需要到数据库中查询了。

        再一次查询:

                会到数据库中查询用户信息。

        3.2 缓存穿透问题

        是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远都不会生效,这些请求都会打到数据库。则会给数据库的压力非常大,因此需要解决这种情况发生。

        常见的解决方案有四种:

        1)增强 id 的复杂度,避免被猜测 id 规律。

        2)做好数据的基础格式校验。

        3)缓存空对象:实现简单,维护方便。

        该方法的缺点:额外的内存消耗,因为设置 key 对应的 value 为 null ,占用了一定的缓存空间,因此为了减少内存浪费,会设置缓存时间 TTL ;还可能造成短期的不一致,当数据库中 key 有对应的 value 了,当前的 key 还在缓存中,value 还是为 null ,所以造成一定的不一致性。

        4)布隆过滤:内存占用较少,没有多余 key ,该方法的缺点为实现复杂,存在误判的可能。

        3.2.1 具体解决缓存穿透问题

使用缓存空对象来解决缓存穿透问题步骤:

        首先,从缓存中查询用户,判断缓存是否命中,如果命中,则直接返回用户信息;如果没有命中,根据用户 id 到数据库中查询用户信息,如果用户信息不为 null ,则说明用户信息是存在的,那么将用户信息写回到缓存中,方便下一次查询可以直接从缓存中获取用户信息;如果用户信息为 null ,则说明数据库中也不存在该用户信息,那么下一次就不需要继续查询该用户信息了,让其在缓存中查询,再抛出异常即可。

具体的流程图:

代码如下:

    @AutowiredStringRedisTemplate stringRedisTemplate;@Overridepublic String getUserNameById(Integer userId) throws Exception {//先判断userId是否为空if (userId == null){throw new Exception("userId is null");}//先从缓存中查看是否存在该keyString s = stringRedisTemplate.opsForValue().get("user:" + userId);if (StrUtil.isNotBlank(s)){//如果缓存中不为null,则成功从缓存中获取值return s;}if (s != null){//直接抛出异常throw new Exception("该用户信息不存在!");}//如果从缓存中获取不到,则需要到数据库中获取数据String userName = adminMapper.getUserNameById(userId);//如果数据返回为null,那么数据库中查找不到数据if (userName == null){//如果在数据库中找不到该信息,则将该 key 值对应的 value 为 "" 写到缓存中stringRedisTemplate.opsForValue().set("user:"+userId,"",100,TimeUnit.SECONDS);}//判断数据不为null之后,则需要将该用户信息写到redis中,且设定超时时间stringRedisTemplate.opsForValue().set("user:"+userId,userName,100, TimeUnit.SECONDS);//最后返回值即可return userName;}

 运行结果:

        查询数据库不存在的用户信息:

                第一次会到数据库查询该用户信息,当该用户信息不存在时,则会在 redis 中设置空值,这样的好处,下一次的查询该用户,就不会打到数据库中了,减少了数据库的压力。

        3.3 缓存雪崩问题

        是指在同一时间段大量的缓存 key 同时失效或者 Redis 服务宕机,导致大量请求到达数据库,带来巨大压力。

        3.3.1 解决缓存雪崩方案

        1)给不同的 key 的 TTL 添加随机值。

        2)利用 Redis 集群提高服务的可用性。

        3)给缓存业务添加降级限流策略。

        4)给业务添加多级缓存。

        3.4 缓存击穿问题

        缓存击穿问题也叫热点 Key 问题,就是一个被高并发访问并且缓存重建业务交复杂的 Key 突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

如图:

        常见的解决方法:

        1)利用互斥锁解决击穿问题

        没有额外的内存消耗,保证一致性,实现简单。该方法的缺点:线程需要等待,性能受影响,可能有死锁的风险。

        2)利用逻辑过期解决缓存击穿问题 

        线程无需等待,性能较好。该方法的缺点,不保证一致性,有额外的内存消耗,实现复杂。

        3.4.1 利用互斥锁解决缓存击穿问题

        利用互斥锁解决的步骤:

        首先,查询缓存是否命中,如果命中,直接返回;如果没有命中,则需要判断是否能获取互斥锁,如果获取到了互斥锁,则查询数据库重建缓冲数据,最后释放锁,再返回数据;如果没有获取互斥锁,则休眠一段时间,再重试,直到从缓冲中获取到数据返回。

流程图:

代码如下:

        解决缓存穿透与缓存击穿:

    //解决缓存穿透与缓存击穿public String getUserNameById2(Integer userId) throws Exception {//先判断userId是否为空if (userId == null){throw new Exception("userId is null");}//先从缓存中查看是否存在该keyString s = stringRedisTemplate.opsForValue().get("user:" + userId);if (StrUtil.isNotBlank(s)){//如果缓存中不为null,则成功从缓存中获取值return s;}if (s != null){//直接抛出异常throw new Exception("该用户信息不存在!");}//如果从缓存中获取不到,则需要到数据库中获取数据//判断释放可以获取到锁String lock = "getLock";String userName = null;try {boolean b = tryLock(lock);if (!b) {//如果没有获取到锁,休眠一会,再重新从缓存中获取数据Thread.sleep(50);return getUserNameById2(userId);}userName = adminMapper.getUserNameById(userId);//如果数据返回为null,那么数据库中查找不到数据if (userName == null){//如果在数据库中找不到该信息,则将该 key 值对应的 value 为 "" 写到缓存中stringRedisTemplate.opsForValue().set("user:"+userId,"",100,TimeUnit.SECONDS);}//判断数据不为null之后,则需要将该用户信息写到redis中,且设定超时时间stringRedisTemplate.opsForValue().set("user:"+userId,userName,100, TimeUnit.SECONDS);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unlock(lock);}//返回值即可return userName;}//获取锁private boolean tryLock(String key){Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 200, TimeUnit.SECONDS);return BooleanUtil.isTrue(aBoolean);}//释放锁private void unlock(String key){stringRedisTemplate.delete(key);}

        3.4.2 利用逻辑过期解决缓存击穿问题

        设置缓存中 key 的逻辑过期,顾名思义:在实际上,缓存中的 key 是设置永远不过期,将其添加过期字段,通过查看该字段,来判断该 key 在缓存中是否已经过期了。

        利用逻辑过期解决缓存击穿问题步骤:

        首先,判断缓存是否命中,如果没有命中,则返回空;如果命中,继续判断该字段是否过期,如果没有过期,则直接获取并且返回该值;如果已经过期,再继续判断能否获取锁,如果获取锁失败,则直接返回已经过期的值;如果获取锁成功,创建一个线程来做查询数据库,并且写入到缓存中,对于主线程来说,仍然返回旧的数据。

流程图:

代码实现:

        利用逻辑过期实现解决缓存击穿问题:

    //获取锁private boolean tryLock(String key){Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 200, TimeUnit.SECONDS);return BooleanUtil.isTrue(aBoolean);}//释放锁private void unlock(String key){stringRedisTemplate.delete(key);}//解决缓存穿透public String getUserNameById(Integer userId) throws Exception {//先判断userId是否为空if (userId == null){throw new Exception("userId is null");}//先从缓存中查看是否存在该keyString s = stringRedisTemplate.opsForValue().get("user:" + userId);//如果从缓存中没有获取到数据,则直接抛出异常if (s == null){throw new Exception("该用户不存在!!!");}//反序列化RedisData redisData = JSON.parseObject(s, RedisData.class);String data = (String) redisData.getData();LocalDateTime localDateTime = redisData.getLocalDateTime();//判断是否过期if (localDateTime.isAfter(LocalDateTime.now())){//如果没有过期,则直接返回数据return data;}//创建线程池ExecutorService pool = Executors.newFixedThreadPool(10);//如果过期了//判断能否获取到互斥锁String lock = "getLock";boolean b = tryLock(lock);if (b) {//获取到锁,从线程池中获取一个线程来从数据库获取信息,再将信息写入到缓存中pool.submit(() -> {try {//先从数据库中获取到数据String userName = adminMapper.getUserNameById(userId);//再将数据写入到缓存中RedisData red = new RedisData();//设置过期时间red.setLocalDateTime(LocalDateTime.now().plusSeconds(100L));red.setData(userName);//将其序列化String jsonString = JSON.toJSONString(red);stringRedisTemplate.opsForValue().set("user:"+userId,jsonString);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unlock(lock);}});}//最后返回return data;}

        4.0 封装 Redis 工具类

        基于 StringRedisTemplate 封装一个缓存工具类,满足下列需要:

        1)方法1:将任意 Java 对象序列化为 json 并存储在 string 类型的 key 中,并且可以设置 TTL 过期时间。

代码如下:

    public void set(String key, Object value, Long time, TimeUnit timeUnit){stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value),time,timeUnit);}

        2)方法2:将任意 Java 对象序列化为 json 并存储在 string 类型的 key 中,并且可以设置逻辑过期时间,用于处理缓存击穿问题。

代码如下:

    public void setWithLogicalExpire(String key,Object value,Long time,TimeUnit timeUnit){RedisData redisData = new RedisData();redisData.setData(value);redisData.setLocalDateTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));String jsonString = JSON.toJSONString(redisData);stringRedisTemplate.opsForValue().set(key,jsonString);}

        3)方法3:根据指定的 key 查询缓存,并反序列化为指定类型,利用缓存空值的方式解决穿透问题。

    //利用缓存空值解决缓存穿透public <R,ID> R queryWithPassThrough(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit ){String key = prefix + id;//判断在缓存中是否能命中String jsonString = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(jsonString)){//反序列化return JSON.parseObject(jsonString, type);}if (jsonString != null){return null;}//查询数据库,且将数据信息写入到缓存中R apply = function.apply(id);//判断是否为空值if (apply == null){//如果为空//将其写进缓存中stringRedisTemplate.opsForValue().set(key,"",50,TimeUnit.SECONDS);return null;}//序列化String json = JSON.toJSONString(apply);//如果不为空stringRedisTemplate.opsForValue().set(key,json,time,unit);return apply;}

        4)方法4:根据指定的 key 查询缓存,并反序列为指定类型,需要利用逻辑过期解决缓存击穿问题。

    //利用逻辑过期解决缓存击穿public <R,ID> R queryWithLogicalExpire(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit){String key = prefix + id;//判断在缓存中是否命中String s = stringRedisTemplate.opsForValue().get(key);//如果不存在,直接返回nullif (s == null){return null;}//如果存在,还得判断是否过期//反序列化RedisData redisData = JSONUtil.toBean(s, RedisData.class);JSONObject d = (JSONObject) redisData.getData();R data = JSONUtil.toBean(d, type);LocalDateTime localDateTime = redisData.getLocalDateTime();if (localDateTime.isAfter(LocalDateTime.now())){//如果没有过期//直接返回数据return data;}//创建线程池ExecutorService pool = Executors.newFixedThreadPool(10);//过期了,判断是否可以获取锁String lock = "getLock";boolean b = tryLock(lock);if (b){//如果获取锁成功,pool.submit(() -> {//从数据库中获取数据,再将数据写回缓存中try {R apply = function.apply(id);setWithLogicalExpire(key,apply,time,unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unlock(lock);}});}return data;}

        5)完整 Redis 的工具类

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.example.bookproject20.pojo.RedisData;
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;@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit timeUnit){stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value),time,timeUnit);}public void setWithLogicalExpire(String key,Object value,Long time,TimeUnit timeUnit){RedisData redisData = new RedisData();redisData.setData(value);redisData.setLocalDateTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));String jsonString = JSON.toJSONString(redisData);stringRedisTemplate.opsForValue().set(key,jsonString);}//利用缓存空值解决缓存穿透public <R,ID> R queryWithPassThrough(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit ){String key = prefix + id;//判断在缓存中是否能命中String jsonString = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(jsonString)){//反序列化return JSON.parseObject(jsonString, type);}if (jsonString != null){return null;}//查询数据库,且将数据信息写入到缓存中R apply = function.apply(id);//判断是否为空值if (apply == null){//如果为空//将其写进缓存中stringRedisTemplate.opsForValue().set(key,"",50,TimeUnit.SECONDS);return null;}//序列化String json = JSON.toJSONString(apply);//如果不为空stringRedisTemplate.opsForValue().set(key,json,time,unit);return apply;}//利用逻辑过期解决缓存击穿public <R,ID> R queryWithLogicalExpire(String prefix,ID id,Class<R> type,Function<ID,R> function,Long time,TimeUnit unit){String key = prefix + id;//判断在缓存中是否命中String s = stringRedisTemplate.opsForValue().get(key);//如果不存在,直接返回nullif (s == null){return null;}//如果存在,还得判断是否过期//反序列化RedisData redisData = JSONUtil.toBean(s, RedisData.class);JSONObject d = (JSONObject) redisData.getData();R data = JSONUtil.toBean(d, type);LocalDateTime localDateTime = redisData.getLocalDateTime();if (localDateTime.isAfter(LocalDateTime.now())){//如果没有过期//直接返回数据return data;}//创建线程池ExecutorService pool = Executors.newFixedThreadPool(10);//过期了,判断是否可以获取锁String lock = "getLock";boolean b = tryLock(lock);if (b){//如果获取锁成功,pool.submit(() -> {//从数据库中获取数据,再将数据写回缓存中try {R apply = function.apply(id);setWithLogicalExpire(key,apply,time,unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unlock(lock);}});}return data;}//获取锁private boolean tryLock(String key){Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 200, TimeUnit.SECONDS);return BooleanUtil.isTrue(aBoolean);}//释放锁private void unlock(String key){stringRedisTemplate.delete(key);}}

        6)依赖:

        <!--fastJSON--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><!--redis、redis连接池依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency>

        7)Redis 配置:

  data:redis:password: 你的redis密码host: 你的redis主机号,IP地址lettuce:pool:max-active: 10max-idle: 10min-idle: 1time-between-eviction-runs: 10sdatabase: 0

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

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

相关文章

【日记】想见珍一面怎么就这么难(985 字)

正文 想见珍一面怎么就这么难…… 事故频发。昨天说考试时间跟机票时间冲突了&#xff0c;最后结果出来了&#xff0c;改签了&#xff0c;并且差价不补。我不干&#xff0c;他们也不干。因为上级行给我们行长施压&#xff0c;于是我们行长给我施压。最后要到了国庆之前拔智齿的…

华为 HCIP-Datacom H12-821 题库 (6)

有需要题库的可以看主页置顶 V群仅进行学习交流 1.转发表中 FLAG 字段中B 的含义是&#xff1f; A、可用路由 B、静态路由 C、黑洞路由 D、网关路由 答案&#xff1a;C 解析&#xff1a; 可用路由用U 表示&#xff0c;静态路由用 S 表示&#xff0c;黑洞路由用 B 表示&#x…

笔试,牛客.kotori和n皇后​,牛客.AOE还是单体

目录 牛客.kotori和n皇后​编辑 牛客.AOE还是单体 牛客.kotori和n皇后 想起来&#xff0c;我之前还写过n皇后的题&#xff0c;但是这个我开始只能想到暴力解法 判断是不是斜对角线&#xff0c;联想yxb和y-xb,假如在一条线上&#xff0c;那么他们的x和y会对应成比例&#xff0c…

【弱监督时间动作定位】Probabilistic Vision-Language Representation for WSTAL 论文阅读

Probabilistic Vision-Language Representation for Weakly Supervised Temporal Action Localization 论文阅读 Abstract1 Introduction2 RELATEDWORK2.1 Weakly Supervised Temporal Action Localization2.2 Vision Language Pre-training2.3 Probabilistic Representation 3…

RocketMQ高级特性四-消息过滤

目录 前言 Broker端过滤 定义与概述 消息过滤分类 原理机制 使用场景 优缺点 Java代码示例 - Tag过滤 Java代码示例 - SQL92过滤 客户端过滤 定义与概述 原理机制 使用场景 优缺点 Java代码示例 总结 前言 消息过滤是RocketMQ的一项高级特性&#xff0c;它允许…

常见HTTP状态码、APUD响应状态字及含义

目录 一、HTTP状态码 二、APDU指令码 一、HTTP状态码 HTTP状态&#xff08;HTTP Status Code&#xff09;是用以表示网页服务器超文本传输协议响应状态的3位数字代码。 关于HTTP状态码更加详细介绍推荐阅读&#xff1a; http://t.csdnimg.cn/qSJv6http://t.csdnimg.cn/qSJv…

光敏电阻传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.光敏电阻传感器介绍 2.原理图 三、程序设计 main.c文件 ldr.h文件 ldr.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 光敏电阻器是利用半导体的光电导效应制成的一种电阻值随入射光的强弱而改变的电阻器&#xff0c;又称为光…

基于树莓派的儿童音频播发器—Yoto

Raspberry Pi 的开发可能性使吸引人的、以儿童为中心的音频播放器得以成型 Yoto Player 为孩子们提供了拥有和控制的绝佳体验&#xff0c;同时不会增加屏幕时间。得益于 Raspberry Pi 以及我们认可的经销商提供的支持和专业知识&#xff0c;Yoto Player 在英国取得了成功。 Yo…

七款最佳的渗透测试工具(非常详细)零基础入门到精通,收藏这一篇就够了

渗透测试工具是模拟对计算机系统、网络或 Web 应用程序的网络攻击的软件应用程序&#xff0c;它们的作用是在实际攻击者之前发现安全漏洞。它们可以作为系统的压力测试&#xff0c;揭示哪些区域可能会受到真正的威胁。 本文我将介绍七款最佳的渗透测试工具。 1 Kali Linux K…

Maven入门:自动化构建工具的基本概念与配置

一、什么是Maven 目前无论使用IDEA还是Eclipse等其他IDE&#xff0c;使用里面 ANT 工具帮助我们进行编译&#xff0c;打包运行等工作。Apache基于ANT进行了升级&#xff0c;研发出了全新的自动化构建工具Maven。 Maven使用项目对象模型&#xff08;POM-Project Object Model&…

视频合并在线工具哪个好?好用的视频合并工具推荐

当我们手握一堆零散却各有千秋的视频片段时&#xff0c;是否曾幻想过它们能像魔法般合并成一部完整、流畅的故事&#xff1f; 别担心&#xff0c;今天咱们就来一场“视频合并大冒险”&#xff0c;揭秘几款视频合并软件手机免费工具&#xff0c;帮助你在指尖上实现创意无限的视…

四、配置三层交换实验组网

一、实验拓扑 二、实验目的 通过配置交换机&#xff0c;令不同vlan间的主机能够互相通信 三、实验步骤 SW12 <Huawei>undo terminal monitor Info: Current terminal monitor is off. <Huawei>system-view Enter system view, return user view with CtrlZ. [H…

EDIUS X 10.34.9631 视频剪辑软件 下载 包含安装说明

下载地址(资源制作整理不易&#xff0c;下载使用需付费&#xff0c;不能接受请勿浪费时间下载) 链接&#xff1a;https://pan.baidu.com/s/1P2wKxVcSx5WzAtHXCaAp5A?pwd227i 提取码&#xff1a;227i

【Linux网络】应用层协议HTTP(1)

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; Linux网络 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好&#xff0c;本片文章将会讲解 应用层协议HTTP 的相关内容。 如果看到最后您觉得这篇文章写得…

「深入理解」HTML Meta标签:网页元信息的配置

「深入理解」HTML Meta标签&#xff1a;网页元信息的配置 HTML的<meta>元素用于提供关于HTML文档的元数据&#xff08;metadata&#xff09;&#xff0c;这些信息对于浏览器和其他处理HTML文档的应用程序来说是非常有用的&#xff0c;如&#xff1a;<base>、<li…

【网络安全】服务基础第一阶段——第九节:Windows系统管理基础---- Windows_AD域

目录 一、域与活动目录 1.1 工作组 1.2 域 1.2.1 域&#xff08;Domain&#xff09; 1.2.2 域控制器&#xff08;Domain Controller&#xff0c;DC&#xff09; 1.2.3 功能和角色 1.2.4 管理和监控 1.2 5 域结构 1.3 组织单元&#xff08;Organizational Unit&#xff…

集成电路学习:什么是IP知识产权

一、IP&#xff1a;知识产权 IP是Intellectual Property的缩写&#xff0c;即知识产权。知识产权是一种无形的财产权&#xff0c;也称智力成果权&#xff0c;它指的是通过智力创造性劳动所获得的成果&#xff0c;并且是由智力劳动者对成果依法享有的专有权利。这种权利包括人身…

性能优化:自动化处理系统设计

性能优化&#xff1a;自动化处理系统设计 前言需求分析系统设计1. 调度中心2. 任务执行器3. 错误处理机制4. 通知系统5. 报表生成器6. 日志记录器 技术实现结语 前言 在当今这个信息爆炸、技术日新月异的时代&#xff0c;企业面临着前所未有的挑战和机遇。随着业务量的不断增长…

基于Yolov5_6.1、LPRNet、PySide6开发的车牌识别系统

项目概述 项目背景 随着车辆数量的不断增加&#xff0c;车牌识别系统在交通管理、停车场自动化等领域变得越来越重要。本项目利用先进的深度学习技术和现代图形用户界面框架来实现高效的车牌识别功能。 项目特点 高效识别&#xff1a;采用 YOLOv5_6.1 进行车牌定位&#xff…

差分传输与单端传输

差分与单端传输 本页讨论模拟信号传输中的两个概念&#xff1a;“单端”和“差分”。模拟信号用于将模拟仪器的输出传送到数字转换器。虽然数字信号对干扰的容忍度相对较高&#xff0c;但模拟信号却可能受到环境中电磁波的干扰和改变。本文档将解释这一问题&#xff0c;并描述…