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.rhkb.cn/news/416227.html

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

相关文章

vue2———组件

一个简单的组件 组件进行注册并使用 结果&#xff1a; 在进行对组件的学习时遇见一些问题&#xff1a; 1、组件的命名 解决方法&#xff1a; 组件的命名 Vue.js 组件的命名遵循一些最佳实践&#xff0c;这些实践有助于保持代码的清晰和一致性。 多单词命名&#xff1a;Vue 官…

Robotics: computational motion planning 部分笔记—— week 2 Configuration Space 构型空间

基本概念 构型(Configuration)&#xff1a;构型是机器人上所有点的完整描述。它提供了机器人在特定时刻状态的简洁表示。 构型空间(Configuration Space)&#xff1a;也称为C-Space&#xff0c;指的是机器人可以到达的所有可能构型的集合。它考虑了空间限制范围和机器人的物理…

期权交易方式和基本策略有哪几种?期权交易要注意什么?

今天带你了解期权交易方式和基本策略有哪几种&#xff1f;期权交易要注意什么&#xff1f;期权&#xff0c;作为一种金融衍生品&#xff0c;它赋予了持有人在未来某个时间内购买或出售特定资产的权利&#xff0c;近年来在全球范围内得到了广泛的关注和应用。 期权交易方式 期…

Latex安装--新手教程、遇到的问题

第一个LaTeX文件的编写 1.tex文件&#xff1a;自己创建后缀为.tex的文件 2.在VScode中打开1.tex文件&#xff08;图1&#xff09;&#xff0c;然后双击打开1.tex文件&#xff08;图2&#xff09;&#xff0c;VScode左侧工具栏出现TEX插件&#xff0c;点击TEX即可 3.写第一个1.t…

SpringBoot-读取配置文件方式

目录 前言 一. 使用 ConfigurationProperties 注解读取 二. 使用 Value 注解读取配置文件 三. 使用 Environment 类获取配置属性 前言 Spring Boot提供了多种灵活的方式来读取配置文件&#xff0c;以适应不同的开发和部署需求&#xff0c;SpringBoot启动的时候&#xff0c;…

[Linux] 项目自动化构建工具-make/Makefile

标题&#xff1a;[Linux] 项目自动化构建工具-make/Makefile 水墨不写bug 目录 一、什么是make/makefile 二、make/makefile语法 补充&#xff08;多文件标识&#xff09;&#xff1a; 三、make/makefile原理 四、make/makefile根据时间对文件选择操作 正文开始&#xff…

在安卓和Windows下使用Vizario H264 RTSP

Unity2021.3.35f1&#xff0c;运行模式为ENGINE_SERVER 1.环境设置 Windows设置 安卓设置 2.代码修改 ConnectionProperties中的server必须与真实IP一样&#xff0c;所以需要新增一个获取IP的函数 public string GetLocalIPAddress(){IPHostEntry host;string localIP &quo…

codesys进行控制虚拟轴运动时出现的一些奇怪bug的解释

codesys进行控制虚拟轴运动时出现的一些奇怪bug的解释 问题描述第一个奇怪的bug&#xff1a;新建的工程没有SoftMotion General Axis Pool选项第二个奇怪的bug&#xff1a;在新建工程SoftMotion General Axis Pool选项时&#xff0c;无法手动添加第三个奇怪的bug&#xff1a;虚…

Postgresql碎片整理

创建pgstattuple 扩展 CREATE EXTENSION pgstattuple 获取表的元组&#xff08;行&#xff09;信息&#xff0c;包括空闲空间的比例和行的平均宽度 SELECT * FROM pgstattuple(表名); 查看表和索引大小 SELECT pg_relation_size(表名), pg_relation_size(索引名称); 清理碎片方…

如何进行 AWS 云监控

什么是 AWS&#xff1f; Amazon Web Services&#xff08;AWS&#xff09;是 Amazon 提供的一个全面、广泛使用的云计算平台。它提供广泛的云服务&#xff0c;包括计算能力、存储选项、网络功能、数据库、分析、机器学习、人工智能、物联网和安全。 使用 AWS 有哪些好处&…

AI预测体彩排3采取888=3策略+和值012路或胆码测试9月4日升级新模型预测第72弹

经过70多期的测试&#xff0c;当然有很多彩友也一直在观察我每天发的预测结果&#xff0c;得到了一个非常有价值的信息&#xff0c;那就是9码定位的命中率非常高&#xff0c;已到达90%的命中率&#xff0c;这给喜欢打私菜的朋友提供了极高价值的预测结果~当然了&#xff0c;大部…

亚信安全信立方安全大模型荣获“磐石·Y”大模型安全评定

2024年4月&#xff0c;在中国软件评测中心&#xff08;工业和信息化部软件与集成电路促进中心&#xff09;联合数据安全关键技术与产业应用评价工业和信息化部重点实验室、中国计算机行业协会数据安全专业委员会开展的大模型安全性测评“磐石X”榜单计划中&#xff0c;亚信安全…

PTA L1-041 寻找250

L1-041 寻找250&#xff08;10分&#xff09; 对方不想和你说话&#xff0c;并向你扔了一串数…… 而你必须从这一串数字中找到“250”这个高大上的感人数字。 输入格式&#xff1a; 输入在一行中给出不知道多少个绝对值不超过1000的整数&#xff0c;其中保证至少存在一个“2…

Java_jdk安装配置~java入门

目录 0.总体介绍 1.入门介绍 2.官网 3.关于版本的问题 4.环境变量配置 5.编码修改 6.创建项目 0.总体介绍 下面的这个情况比较复杂&#xff0c;因为遇到了一些别人没有遇到的问题&#xff0c;我会把自己遇到这个问题&#xff0c;到解决这个问题的过程尽可能详细的展示出…

Qt 字符串的编码方式,以及反斜杠加3个数字是什么编码\344\275\240,如何生成

Qt 字符串的编码方式 问题 总所周知&#xff0c;Qt的ui文件在编译时&#xff0c;会自动生成一个ui_xxxxx.h的头文件&#xff0c;打开一看&#xff0c;其实就是将摆放的控件new出来以及布局的代码。 只要用Qt提供的uic.exe工具&#xff0c;自己也可以将ui文件输出为代码文件…

设计模式 —— 单例模式

文章目录 一、单例模式1.1 单例模式定义1.2 单例模式的优点1.3 单例模式的缺点1.4 单例模式的使用场景 二、普通案例2.1 饿汉式单例模式(Eager Initialization Singleton)2.2 懒汉式单例模式(Lazy Initialization Singleton) 参考资料 本文源代码地址为 java-demos/singeleton-…

Leetcode236经典题目二叉树的最近公共祖先

本次为大家带来的题目是leetcode236二叉树的最近公共祖先 本道题的直观思路是自底向上进行寻找&#xff0c;如果存在的话那么向上返回&#xff0c;如何能够自底向上遍历呢&#xff1f;我们可以利用回溯进行处理&#xff0c;那么需要注意的是进行回溯的时候一定要使用后序遍历来…

too many blocks in cooperative launch at cudaLaunchCooperativeKernel

在使用cudaLaunchCooperativeKernel时出现&#xff1a; cudaErrorCooperativeLaunchTooLarge (error 82) due to “too many blocks in cooperative launch” on CUDA API call to cudaLaunchCooperativeKernel. 问题&#xff1a; 在使用cudaLaunchCooperativeKernel时&…

总线操作与定时

目录 一. 总线定时1.1 同步通信&#xff08;同步定时方式&#xff09;1.2 异步通信&#xff08;异步定时方式&#xff09;1.3 半同步与分离式通信&#xff08;了解即可&#xff09; 二. 总线标准&#xff08;了解即可&#xff09;2.1 总线标准的概念2.2 系统总线标准2.3 局部总…

新电脑Win11系统想要降级为Win10怎么操作?

前言 现在的电脑大部分都是Windows 11系统&#xff0c;组装机还好一些&#xff0c;如果想要使用Windows 10&#xff0c;只需要在安装系统的时候选择Windows 10镜像即可。 但是对于新笔记本、厂商的成品机、一体机来说&#xff0c;只要是全新的电脑&#xff0c;基本上都是Wind…