当前位置: 首页 > news >正文

利用Redis实现数据缓存

目录

1 为啥要缓存捏?

2 基本流程(以查询商铺信息为例)

3 实现数据库与缓存双写一致

3.1 内存淘汰

3.2 超时剔除(半自动)

3.3 主动更新(手动)

3.3.1 双写方案

3.3.2 读写穿透方案

3.3.3 写回方案

4 缓存穿透的解决方案

1)缓存空对象

2)布隆过滤

(缓存空对象)解决缓存穿透问题流程

 5 缓存雪崩解决方案

 6 缓存击穿的解决方案

6.1 基于互斥锁解决缓存击穿问题

6.2 基于逻辑过期解决缓存击穿问题

 7 缓存工具封装


1 为啥要缓存捏?

速度快,好用

缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力

实际开发过程中,企业的数据量,少则几十万,多则几千万,这么大数据量,如果没有缓存来作为"避震器",系统是几乎撑不住的,所以企业会大量运用到缓存技术

 2 基本流程(以查询商铺信息为例)

3 实现数据库与缓存双写一致

首先我们需要明确数据一致性问题的主要原因是什么,从主要原因入手才是解决问题的关键!数据一致性的根本原因是 缓存和数据库中的数据不同步,那么我们该如何让 缓存 和 数据库 中的数据尽可能的即时同步?这就需要选择一个比较好的缓存更新策略

常见的缓存更新策略:

3.1 内存淘汰

利用Redis的内存淘汰机制实现缓存更新,Redis的内存淘汰机制是当Redis发现内存不足时,会根据一定的策略自动淘汰部分数据

这种策略模型优点在于没有维护成本,但是内存不足这种无法预定的情况就导致了缓存中会有很多旧的数据,数据一致性差

Redis中常见的淘汰策略:

1 noeviction(默认):当达到内存限制并且客户端尝试执行写入操作时,Redis 会返回错误信息,拒绝新数据的写入,保证数据完整性和一致性
2 allkeys-lru:从所有的键中选择最近最少使用(Least Recently Used,LRU)的数据进行淘汰。即优先淘汰最长时间未被访问的数据
3 allkeys-random:从所有的键中随机选择数据进行淘汰
4 volatile-lru:从设置了过期时间的键中选择最近最少使用的数据进行淘汰
5 volatile-random:从设置了过期时间的键中随机选择数据进行淘汰
6 volatile-ttl:从设置了过期时间的键中选择剩余生存时间(Time To Live,TTL)最短的数据进行淘汰

3.2 超时剔除(半自动)

手动给缓存数据添加TTL,到期后Redis自动删除缓存

这种策略数据一致性一般,维护成本有但是较低,一般用于兜底方案~

3.3 主动更新(手动)

手动编码实现缓存更新,在修改数据库的同时更新缓存

这种策略数据一致性就是最高的(毕竟自己动手,丰衣足食),但同时维护成本也是最高的

3.3.1 双写方案

1)读取(Read):当需要读取数据时,首先检查缓存是否存在该数据。如果缓存中存在,直接返回缓存中的数据。如果缓存中不存在,则从底层数据存储(如数据库)中获取数据,并将数据存储到缓存中,以便以后的读取操作可以更快地访问该数据。

2)写入(Write):当进行数据写入操作时,首先更新底层数据存储中的数据。然后,根据具体情况,可以选择直接更新缓存中的数据(使缓存与底层数据存储保持同步),或者是简单地将缓存中与修改数据相关的条目标记为无效状态(缓存失效),以便下一次读取时重新加载最新数据

!在更新数据的情况下 优先选择删除缓存模式 其次是更新缓存模式

问题:操作时,先操作数据库还是先操作缓存捏?

答案:先操作数据库,再删缓存

如果先操作缓存:先删缓存,再更新数据库

当线程1删除缓存到更新数据库之间的时间段,会有其它线程进来查询数据,由于没有加锁,且前面的线程将缓存删除了,这就导致请求会直接打到数据库上,给数据库带来巨大压力。这个事件发生的概率很大,因为缓存的读写速度块,而数据库的读写较慢。

这种方式的不足之处:存在缓存击穿问题,且概率较大

如果先操作数据库:先更新数据库,再删缓存

当线程1在查询缓存且未命中,此时线程1查询数据,查询完准备写入缓存时,由于没有加锁线程2乘虚而入,线程2在这期间对数据库进行了更新,此时线程1将旧数据返回了,出现了脏读,这个事件发生的概率很低,因为先是需要满足缓存未命中,且在写入缓存的那段时间内有一个线程进行更新操作,缓存的读写和查询很快,这段空隙时间很小,所以出现脏读现象的概率也很低

这种方式的不足之处:存在脏读现象,但概率较小

要保证两个操作同时操作 在单体项目中可以放在同一个事务中

3.3.2 读写穿透方案

将读取和写入操作首先在缓存中执行,然后再传播到数据存储

1)读取穿透(Read Through):当进行读取请求时,首先检查缓存。如果所请求的数据在缓存中找到,直接返回数据。如果缓存中没有找到数据,则将请求转发给数据存储以获取数据。获取到的数据随后存储在缓存中,然后返回给调用者。

2)写入穿透(Write Through):当进行写入请求时,首先将数据写入缓存。缓存立即将写操作传播到数据存储,确保缓存和数据存储之间的数据保持一致。这样保证了后续的读取请求从缓存中返回更新后的数据。

3.3.3 写回方案

调用者只操作缓存,其他线程去异步处理数据库,实现最终一致

1)读取(Read):先检查缓存中是否存在数据,如果不存在,则从底层数据存储中获取数据,并将数据存储到缓存中。

2)写入(Write):先更新底层数据存储,然后将待写入的数据放入一个缓存队列中。在适当的时机,通过批量操作或异步处理,将缓存队列中的数据写入底层数据存储

  • 主动更新策略中三种方案的应用场景

    • 双写方案 较适用于读多写少的场景,数据的一致性由应用程序主动管理
    • 读写穿透方案 适用于数据实时性要求较高、对一致性要求严格的场景
    • 写回方案 适用于追求写入性能的场景,对数据的实时性要求相对较低、可靠性也相对低
  • 更新策略的应用场景

    • 对于低一致性需求,可以使用内存淘汰机制。例如店铺类型数据的查询缓存
    • 对于高一致性需求,可以采用主动更新策略,并以超时剔除作为兜底方案。例如店铺详情数据查询的缓存

4 缓存穿透的解决方案

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

常见解决缓存穿透的解决方案:

1)缓存空对象

就是给redis缓存一个空对象并设置TTL存活时间

优点:实现简单,维护方便

缺点:额外的内存消耗,可能造成短期的不一致

2)布隆过滤

通俗的说,就是中间件~

优点:内存占用较少,没有多余key

缺点:实现复杂,存在误判可能(有穿透的风险),无法删除数据


上面两种方式都是被动的解决缓存穿透方案,此外我们还可以采用主动的方案预防缓存穿透,比如:增强id的复杂度避免被猜测id规律、做好数据的基础格式校验、加强用户权限校验、做好热点参数的限流

(缓存空对象)解决缓存穿透问题流程

 

 5 缓存雪崩解决方案

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

就是说,一群设置了有效期的key同时消失了,或者说redis罢工了,导致所有的或者说大量的请求会给数据库带来巨大压力叫做缓存雪崩~

  • 缓存雪崩的常见解决方案
    • 给不同的Key的TTL添加随机值
    • 利用Redis集群提高服务的可用性
    • 给缓存业务添加降级限流策略,比如快速失败机制,让请求尽可能打不到数据库上
    • 给业务添加多级缓存

 6 缓存击穿的解决方案

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

大概击穿流程:

第一个线程,查询redis发现未命中,然后去数据库查询并重建缓存,这个时候因为在缓存重建业务较为复杂的情况下,重建时间较久,又因为高并发的环境下,在线程1重建缓存的时间内,会有其他的大量的其他线程进来,发现查找缓存仍未命中,导致继续重建,如此死循环。

缓存击穿的常见解决方案:

互斥锁(时间换空间)
        优点:内存占用小,一致性高,实现简单
        缺点:性能较低,容易出现死锁
逻辑过期(空间换时间)
        优点:性能高
        缺点:内存占用较大,容易出现脏读
两者相比较,互斥锁更加易于实现,但是容易发生死锁,且锁导致并行变成串行,导致系统性能下降,逻辑过期实现起来相较复杂,且需要耗费额外的内存,但是通过开启子线程重建缓存,使原来的同步阻塞变成异步,提高系统的响应速度,但是容易出现脏读

6.1 基于互斥锁解决缓存击穿问题

就是当线程查询缓存未命中时,尝试去获取互斥锁,然后在重建缓存数据,在这段时间里,其他线程也会去尝试获取互斥锁,如果失败就休眠一段时间,并继续,不断重试,等到数据重建成功,其他线程就可以命中数据了。这样就不会导致缓存击穿。这个方案数据一致性是绝对的,但是相对来说会牺牲性能。

这里我们获取互斥锁可以使用redis中string类型中的setnx方法 ,因为setnx方法是在key不存在的情况下才可以创建成功的,所以我们重建缓存时,使用setnx来将锁的数据加入到redis中,并且通过判断这个锁的key是否存在,如果存在就是获取锁成功,失败就是获取失败,这样刚好可以实现互斥锁的效果。

释放锁就更简单了,直接删除我们存入的锁的key来释放锁。

 //获取锁public Boolean tryLock(String key){Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}//释放锁方法public void unlock(String key){stringRedisTemplate.delete(key);}

6.2 基于逻辑过期解决缓存击穿问题

给redis缓存字段中添加一个过期时间,然后当线程查询缓存的时候,先判断是否已经过期,如果过期,就获取互斥锁,并开启一个子线程进行缓存重建任务,直到子线程完成任务后,释放锁。在这段时间内,其他线程获取互斥锁失败后,并不是继续等待重试,而是直接返回旧数据。这个方法虽然性能较好,但也牺牲了数据一致性。

所谓的逻辑过期,类似于逻辑删除,并不是真正意义上的过期,而是新增一个字段,用来标记key的过期时间,这样能能够避免key过期而被自动删除,这样数据就永不过期了,从根本上解决因为热点key过期导致的缓存击穿。一般搞活动时,比如抢优惠券,秒杀等场景,请求量比较大就可以使用逻辑过期,等活动一过就手动删除逻辑过期的数据

逻辑过期一定要先进行数据预热,将我们热点数据加载到缓存中

逻辑过期时间根据具体业务而定,逻辑过期过长,会造成缓存数据的堆积,浪费内存,过短造成频繁缓存重建,降低性能,所以设置逻辑过期时间时需要实际测试和评估不同参数下的性能和资源消耗情况,可以通过观察系统的表现,在业务需求和性能要求之间找到一个平衡点

 

 7 缓存工具封装

调用者

    /*** 根据id查询商铺数据** @param id* @return*/@Overridepublic Result queryById(Long id) {// 调用解决缓存穿透的方法
//        Shop shop = cacheClient.handleCachePenetration(CACHE_SHOP_KEY, id, Shop.class,
//                this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
//        if (Objects.isNull(shop)){
//            return Result.fail("店铺不存在");
//        }// 调用解决缓存击穿的方法Shop shop = cacheClient.handleCacheBreakdown(CACHE_SHOP_KEY, id, Shop.class,this::getById, CACHE_SHOP_TTL, TimeUnit.SECONDS);if (Objects.isNull(shop)) {return Result.fail("店铺不存在");}return Result.ok(shop);}

工具类

@Component
@Slf4j
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 将数据加入Redis,并设置有效期** @param key* @param value* @param timeout* @param unit*/public void set(String key, Object value, Long timeout, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), timeout, unit);}/*** 将数据加入Redis,并设置逻辑过期时间** @param key* @param value* @param timeout* @param unit*/public void setWithLogicalExpire(String key, Object value, Long timeout, TimeUnit unit) {RedisData redisData = new RedisData();redisData.setData(value);// unit.toSeconds()是为了确保计时单位是秒redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(timeout)));stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), timeout, unit);}/*** 根据id查询数据(处理缓存穿透)** @param keyPrefix  key前缀* @param id         查询id* @param type       查询的数据类型* @param dbFallback 根据id查询数据的函数* @param timeout    有效期* @param unit       有效期的时间单位* @param <T>* @param <ID>* @return*/public <T, ID> T handleCachePenetration(String keyPrefix, ID id, Class<T> type,Function<ID, T> dbFallback, Long timeout, TimeUnit unit) {String key = keyPrefix + id;// 1、从Redis中查询店铺数据String jsonStr = stringRedisTemplate.opsForValue().get(key);T t = null;// 2、判断缓存是否命中if (StrUtil.isNotBlank(jsonStr)) {// 2.1 缓存命中,直接返回店铺数据t = JSONUtil.toBean(jsonStr, type);return t;}// 2.2 缓存未命中,判断缓存中查询的数据是否是空字符串(isNotBlank把null和空字符串给排除了)if (Objects.nonNull(jsonStr)) {// 2.2.1 当前数据是空字符串(说明该数据是之前缓存的空对象),直接返回失败信息return null;}// 2.2.2 当前数据是null,则从数据库中查询店铺数据t = dbFallback.apply(id);// 4、判断数据库是否存在店铺数据if (Objects.isNull(t)) {// 4.1 数据库中不存在,缓存空对象(解决缓存穿透),返回失败信息this.set(key, "", CACHE_NULL_TTL, TimeUnit.SECONDS);return null;}// 4.2 数据库中存在,重建缓存,并返回店铺数据this.set(key, t, timeout, unit);return t;}/*** 缓存重建线程池*/public static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*** 根据id查询数据(处理缓存击穿)** @param keyPrefix  key前缀* @param id         查询id* @param type       查询的数据类型* @param dbFallback 根据id查询数据的函数* @param timeout    有效期* @param unit       有效期的时间单位* @param <T>* @param <ID>* @return*/public <T, ID> T handleCacheBreakdown(String keyPrefix, ID id, Class<T> type,Function<ID, T> dbFallback, Long timeout, TimeUnit unit) {String key = keyPrefix + id;// 1、从Redis中查询店铺数据,并判断缓存是否命中String jsonStr = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isBlank(jsonStr)) {// 1.1 缓存未命中,直接返回失败信息return null;}// 1.2 缓存命中,将JSON字符串反序列化未对象,并判断缓存数据是否逻辑过期RedisData redisData = JSONUtil.toBean(jsonStr, RedisData.class);// 这里需要先转成JSONObject再转成反序列化,否则可能无法正确映射Shop的字段JSONObject data = (JSONObject) redisData.getData();T t = JSONUtil.toBean(data, type);LocalDateTime expireTime = redisData.getExpireTime();if (expireTime.isAfter(LocalDateTime.now())) {// 当前缓存数据未过期,直接返回return t;}// 2、缓存数据已过期,获取互斥锁,并且重建缓存String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);if (isLock) {// 获取锁成功,开启一个子线程去重建缓存CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 查询数据库T t1 = dbFallback.apply(id);// 将查询到的数据保存到Redisthis.setWithLogicalExpire(key, t1, timeout, unit);} finally {unlock(lockKey);}});}// 3、获取锁失败,再次查询缓存,判断缓存是否重建(这里双检是有必要的)jsonStr = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isBlank(jsonStr)) {// 3.1 缓存未命中,直接返回失败信息return null;}// 3.2 缓存命中,将JSON字符串反序列化未对象,并判断缓存数据是否逻辑过期redisData = JSONUtil.toBean(jsonStr, RedisData.class);// 这里需要先转成JSONObject再转成反序列化,否则可能无法正确映射Shop的字段data = (JSONObject) redisData.getData();t = JSONUtil.toBean(data, type);expireTime = redisData.getExpireTime();if (expireTime.isAfter(LocalDateTime.now())) {// 当前缓存数据未过期,直接返回return t;}// 4、返回过期数据return t;}/*** 获取锁** @param key* @return*/private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);// 拆箱要判空,防止NPEreturn BooleanUtil.isTrue(flag);}/*** 释放锁** @param key*/private void unlock(String key) {stringRedisTemplate.delete(key);}
}

相关文章:

利用Redis实现数据缓存

目录 1 为啥要缓存捏&#xff1f; 2 基本流程&#xff08;以查询商铺信息为例&#xff09; 3 实现数据库与缓存双写一致 3.1 内存淘汰 3.2 超时剔除&#xff08;半自动&#xff09; 3.3 主动更新&#xff08;手动&#xff09; 3.3.1 双写方案 3.3.2 读写穿透方案 3.3.…...

jQuery小游戏(二)

jQuery小游戏&#xff08;二&#xff09; 今天是新年的第二天&#xff0c;本人在这里祝大家&#xff0c;新年快乐&#xff0c;万事胜意&#x1f495; 紧接jQuery小游戏&#xff08;一&#xff09;的内容&#xff0c;我们开始继续往下咯&#x1f61c; 游戏中使用到的方法 key…...

农产品价格报告爬虫使用说明

农产品价格报告爬虫使用说明 # ************************************************************************** # * * # * 农产品价格报告爬虫 …...

xceed PropertyGrid 如何做成Visual Studio 的属性窗口样子

类似这样的&#xff0c;我百度了一下&#xff0c;发现使用Xceed 不错。使用PropertyGrid 前台代码为 <Windowx:Class"WpfApp.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.co…...

Fork/Join框架_任务分解与并行执行

1 概述 Fork/Join框架是Java 7引入的一个用于并行执行任务的框架。它特别适用于可以递归分解为多个子任务的工作,每个子任务可以独立执行,并且结果可以合并以获得最终结果。Fork/Join框架通过工作窃取(work-stealing)算法提高了多核处理器上的任务执行效率。 2 核心组件 …...

智能家居监控系统数据收集积压优化

亮点&#xff1a;RocketMQ 消息大量积压问题的解决 假设我们正在开发一个智能家居监控系统。该系统从数百万个智能设备&#xff08;如温度传感器、安全摄像头、烟雾探测器等&#xff09;收集数据&#xff0c;并通过 RocketMQ 将这些数据传输到后端进行处理和分析。 在某些情况下…...

详解python的单例模式

单例模式是一种设计模式&#xff0c;它确保一个类只有一个实例&#xff0c;并提供一个全局访问点来获取这个实例。在Python中实现单例模式有多种方法&#xff0c;下面我将详细介绍几种常见的实现方式。 1. 使用模块 Python的模块天然就是单例的&#xff0c;因为模块在第一次导…...

momask-codes 部署踩坑笔记

目录 依赖项 t2m_nlayer8_nhead6_ld384_ff1024_cdp0.1_rvq6ns 推理代码完善&#xff1a; 代码地址&#xff1a; https://github.com/EricGuo5513/momask-codes 依赖项 pip install numpy1.23 matplotlib 必须指定版本&#xff1a;pip install matplotlib3.3.4 t2m_nlayer…...

H3CNE-31-BFD

Bidirectional Forwarding Dection&#xff0c;双向转发检查 作用&#xff1a;毫秒级故障检查&#xff0c;通常结合三层协议&#xff08;静态路由、vrrp、ospf、BGP等&#xff09;&#xff0c;实现链路故障快速检查。 BFD配置示例 没有中间的SW&#xff0c;接口down&#xff…...

蓝桥备赛指南(5)

queue队列 queue是一种先进先出的数据结构。它提供了一组函数来操作和访问元素&#xff0c;但它的功能相对较简单&#xff0c;queue函数的内部实现了底层容器来存储元素&#xff0c;并且只能通过特定的函数来访问和操作元素。 queue函数的常用函数 1.push()函数&#xff1a;…...

讯飞智作 AI 配音技术浅析(一)

一、核心技术 讯飞智作 AI 配音技术作为科大讯飞在人工智能领域的重要成果&#xff0c;融合了多项前沿技术&#xff0c;为用户提供了高质量的语音合成服务。其核心技术主要涵盖以下几个方面&#xff1a; 1. 深度学习与神经网络 讯飞智作 AI 配音技术以深度学习为核心驱动力&…...

MySQL(高级特性篇) 14 章——MySQL事务日志

事务有4种特性&#xff1a;原子性、一致性、隔离性和持久性 事务的隔离性由锁机制实现事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证&#xff08;1&#xff09;REDO LOG称为重做日志&#xff0c;用来保证事务的持久性&#xff08;2&#xff09;UNDO LOG称为回…...

openRv1126 AI算法部署实战之——TensorFlow TFLite Pytorch ONNX等模型转换实战

Conda简介 查看当前系统的环境列表 conda env list base为基础环境 py3.6-rknn-1.7.3为模型转换环境&#xff0c;rknn-toolkit版本V1.7.3&#xff0c;python版本3.6 py3.6-tensorflow-2.5.0为tensorflow模型训练环境&#xff0c;tensorflow版本2.5.0&#xff0c;python版本…...

【Redis】常见面试题

什么是Redis&#xff1f; Redis 和 Memcached 有什么区别&#xff1f; 为什么用 Redis 作为 MySQL 的缓存&#xff1f; 主要是因为Redis具备高性能和高并发两种特性。 高性能&#xff1a;MySQL中数据是从磁盘读取的&#xff0c;而Redis是直接操作内存&#xff0c;速度相当快…...

每日 Java 面试题分享【第 17 天】

欢迎来到每日 Java 面试题分享栏目&#xff01; 订阅专栏&#xff0c;不错过每一天的练习 今日分享 3 道面试题目&#xff01; 评论区复述一遍印象更深刻噢~ 目录 问题一&#xff1a;Java 中的访问修饰符有哪些&#xff1f;问题二&#xff1a;Java 中静态方法和实例方法的区…...

「全网最细 + 实战源码案例」设计模式——桥接模式

核心思想 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;将抽象部分与其实现部分分离&#xff0c;使它们可以独立变化。降低代码耦合度&#xff0c;避免类爆炸&#xff0c;提高代码的可扩展性。 结构 1. Implementation&#xff08;实现类…...

JavaScript 进阶(上)

作用域 局部作用域 局部作用域分为函数作用域和块作用域。 函数作用域&#xff1a; 在函数内部声明的变量只能在函数内部被访问&#xff0c;外部无法直接访问。 总结&#xff1a; 函数内部声明的变量&#xff0c;在函数外部无法被访问 函数的参数也是函数内部的局部变量 …...

【编译原理实验二】——自动机实验:NFA转DFA并最小化

本篇适用于ZZU的编译原理课程实验二——自动机实验&#xff1a;NFA转DFA并最小化&#xff0c;包含了实验代码和实验报告的内容&#xff0c;读者可根据需要参考完成自己的程序设计。 如果是ZZU的学弟学妹看到这篇&#xff0c;那么恭喜你&#xff0c;你来对地方啦&#xff01; 如…...

深入探讨:服务器如何响应前端请求及后端如何查看前端提交的数据

深入探讨&#xff1a;服务器如何响应前端请求及后端如何查看前端提交的数据 一、服务器如何响应前端请求 前端与后端的交互主要通过 HTTP 协议实现。以下是详细步骤&#xff1a; 1. 前端发起 HTTP 请求 GET 请求&#xff1a;用于从服务器获取数据。POST 请求&#xff1a;用…...

如何利用Docker和.NET Core实现环境一致性、简化依赖管理、快速部署与扩展,同时提高资源利用率、确保安全性和生态系统支持

目录 1. 环境一致性 2. 简化依赖管理 3. 快速部署与扩展 4. 提高资源利用率 5. 确保安全性 6. 生态系统支持 总结 使用 Docker 和 .NET Core 结合&#xff0c;可以有效地实现环境一致性、简化依赖管理、快速部署与扩展&#xff0c;同时提高资源利用率、确保安全性和生态…...

@Inject @Qualifier @Named

Inject Qualifier Named 在依赖注入&#xff08;DI&#xff09;中&#xff0c;Inject、Qualifier 和 Named 是用于管理对象创建和绑定的关键注解。以下是它们的用途、依赖配置和代码示例的详细说明&#xff1a; 1. 注解的作用 Inject&#xff1a;标记需要注入的构造函数、字段…...

创建 priority_queue - 进阶(内置类型)c++

内置类型就是 C 提供的数据类型&#xff0c;⽐如 int 、 double 、 long long 等。以 int 类型为例&#xff0c;分 别创建⼤根堆和⼩根堆。 这种写法意思是&#xff0c;我要告诉这个优先级队列要建一个什么样的堆&#xff0c;第一个int是要存什么数据类型&#xff0c;vecto…...

2. Java-MarkDown文件解析-工具类

2. Java-MarkDown文件解析-工具类 1. 思路 读取markdown文件的内容&#xff0c;根据markdown的语法进行各个类型语法的解析。引入工具类 commonmark 和 commonmark-ext-gfm-tables进行markdown语法解析。 2. 工具类 pom.xml <!-- commonmark 解析markdown --> <d…...

动态规划DP 最长上升子序列模型 登山(题目分析+C++完整代码)

概览检索 动态规划DP 最长上升子序列模型 登山 原题链接 AcWing 1014. 登山 题目描述 五一到了&#xff0c;ACM队组织大家去登山观光&#xff0c;队员们发现山上一共有N个景点&#xff0c;并且决定按照顺序来浏览这些景点&#xff0c;即每次所浏览景点的编号都要大于前一个…...

css-设置元素的溢出行为为可见overflow: visible;

1.前言 overflow 属性用于设置当元素的内容溢出其框时如何处理。 2. overflow overflow 属性的一些常见值&#xff1a; 1 visible&#xff1a;默认值。内容不会被剪裁&#xff0c;会溢出元素的框。 2 hidden&#xff1a;内容会被剪裁&#xff0c;不会显示溢出的部分。 3 sc…...

家居EDI:Hom Furniture EDI需求分析

HOM Furniture 是一家成立于1977年的美国家具零售商&#xff0c;总部位于明尼苏达州。公司致力于提供高品质、时尚的家具和家居用品&#xff0c;满足各种家庭和办公需求。HOM Furniture 以广泛的产品线和优质的客户服务在市场上赢得了良好的口碑。公司经营的产品包括卧室、客厅…...

1、开始简单使用rag

文章目录 前言数据存放申请api开始代码安装依赖从文件夹中读取文档文档切块将分割嵌入并存储在向量库中检索部分代码构造用户接口演示提示 整体代码 前言 本章只是简单使用rag的一个示例&#xff0c;为了引出以后的学习&#xff0c;将整个rag的流程串起来 数据存放 一个示例…...

Linux Samba 低版本漏洞(远程控制)复现与剖析

目录 前言 漏洞介绍 漏洞原理 产生条件 漏洞影响 防御措施 复现过程 结语 前言 在网络安全的复杂生态中&#xff0c;系统漏洞的探索与防范始终是保障数字世界安全稳定运行的关键所在。Linux Samba 作为一款在网络共享服务领域应用极为广泛的软件&#xff0c;其低版本中…...

安卓(android)实现注册界面【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握LinearLayout、RelativeLayout、FrameLayout等布局的综合使用。 2.掌握ImageView、TextView、EditText、CheckBox、Button、RadioGroup、RadioButton、ListView、RecyclerView等控件在项目中的…...

【 AI agents】letta:2024年代理堆栈演进(中英文翻译)

The AI agents stack AI 代理堆栈 November 14, 2024 11月 14, 2024原文: The AI agents stack官方教程教程学习笔记: 【memgpt】letta 课程1/2:从头实现一个自我编辑、记忆和多步骤推理的代理Understanding the AI agents landscape 了解 AI 代理环境 Although we see a …...