Redis缓存设计

文章目录

  • 1 缓存的收益与成本分析
    • 1.1 收益
    • 1.2 成本
  • 2 缓存更新策略的选择和使用场景
    • 2.1 LRU/LFU/FIFO算法剔除
    • 2.2 超时剔除
    • 2.3 主动更新
      • 2.4 缓存更新策略对比
    • 2.5 最佳实践
  • 3 缓存粒度控制方法
    • 3.1 缓存全部数据
    • 3.2 缓存部分数据
    • 缓存粒度控制方法对比
  • 4 缓存穿透问题优化
    • 4.1 什么是缓存穿透?
    • 4.2 缓存空对象
    • 4.3 布隆过滤器拦截
    • 4.4 两种方案对比
  • 5 无底洞问题优化
    • 5.1 什么是无底洞问题?
    • 5.2 串行命令
    • 5.3 串行IO
    • 5.4 并行IO
    • 5.5 hash_tag
    • 5.5 几种批量操作方案对比
  • 6 缓存雪崩问题优化
    • 6.1 什么是缓存雪崩问题?
    • 6.2 保证缓存层服务高可用性
    • 6.3 依赖隔离组件为后端限流并降级
    • 6.4 提前演练
  • 7 热点key重建问题优化
    • 7.1 什么是热点key重建问题?
    • 7.2 互斥锁
    • 7.3 永远不过期
    • 7.4 两种方案比较

为什么要进行缓存设计?
缓存设计能够有效地加速应用读写速度,降低后端负载,对日常应用开发至关重要。但是将缓存加入应用架构后也会带来一些问题,所以针对不同的问题或场景,需要进行相应的缓存设计

1 缓存的收益与成本分析

如图所示,左边是客户端直接调用存储层的架构,右边是典型的缓冲层+存储层架构
在这里插入图片描述

1.1 收益

  • 加速读写:因为缓存通常都是全内存的,而存储层通常读写性能不够强悍(因为需要磁盘IO操作),通过缓存可以有效地加速度下,优化用户体验。
  • 降低后端负载:对于一些很复杂的SQL语句,如果在缓冲层就命中了结果,可以有效减少后端访问量和复杂计算,在很大程度上降低了后端负载。

1.2 成本

  • 数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,这个时间窗口的大小根更新策略有关。
  • 代码维护成本:加入缓存后,要同时处理缓存层和存储层逻辑,增大了开发者维护代码的成本。
  • 运维成本:项目上线后,mysql,redis都需要进行运维。

2 缓存更新策略的选择和使用场景

为什么我们在用Redis进行缓存是通常会加上键的生命周期?
给缓存中的数据通加上生命周期,需要在指定的时间后被删除或更新,这样可以保证缓存空间在一个可控范围内。

既然缓存有生命周期,那为什么要更新缓存?
因为前面成本分析中介绍到,缓存中的数据和存储层中的数据有一段时间窗口的不一致,需要利用某些策略进行更新。

2.1 LRU/LFU/FIFO算法剔除

  • 使用场景:剔除算法通常用于缓存使用量超过了预设最大值时候,如何对现有数据进行提出。例如Redis使用maxmemory-policy这个配置作为内存最大值后对数据的提出策略。
  • 一致性:要清理哪些数据是由具体算法决定,开发人员只能决定使用哪些算法,所以一致性是最差的
  • 维护成本:算法不需要开发人员自己来实现,通常只需要配置最大maxmemory和对应的策略即可。开发人员只需要知道每种算法的含义,选择自己合适的算法即可。

redis maxmemory-policy有哪些?
1、noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。这是默认策略。
2、allkeys-lru:尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
3、volatile-lru:在设置了过期时间的键空间中,尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
4、allkeys-random:回收随机的键,使得新添加的数据有空间存放。
5、volatile-random:在设置了过期时间的键空间中,回收随机的键,使得新添加的数据有空间存放。
6、volatile-ttl:在设置了过期时间的键空间中,有更早过期时间的键将优先被回收。

2.2 超时剔除

  • 使用场景:超时剔除通过给缓存设置过期时间,让其在过期时间后自动删除。例如Redis提供的expire命令。如果业务可以容忍一段时间以内,缓存层和存储层的数据不一致,那么可以为其设置过期时间。在数据过期后,再从真实数据源获取数据,重新放到缓存并设置过期时间。
  • 一致性:一段时间窗口内(取决于过期时间的长短)存在一致性问题,即缓存数据和真实数据源不一致。
  • 维护成本:维护成本不是很高,只需要设置expire过期时间即可。

2.3 主动更新

  • 使用场景:应用方对数据一致性要求很高,需要在真实数据更新后,立即更新缓存数据。例如可以用过消息系统或其他方式通知缓存更新。
  • 一致性:一致性最高,但如果主动更新发生了问题,那么这条数据可能长时间不会更新,所以将主动更新策略与超时剔除策略结合起来比较好。
  • 维护成本:维护成本较高,开发者需要自己来完成更新,并保证更新操作的正确性。

2.4 缓存更新策略对比

策略一致性维护成本
LRU/LFU/FIFO算法剔除最差
超时剔除较差较低
主动更新

2.5 最佳实践

  • 低一致性业务:配置最大内存和淘汰策略方式使用。
  • 高一致性业务:结合超时剔除和主动更新。

为什么对高一致性业务,建议结合超时剔除和主动更新?
因为主动更新代码是由开发人员编写的,如果发生问题,缓存可能长时间得不到更新,这是缓存层的数据和存储层的数据就长时间不一致,对于高一致性业务是无法容忍的,所以为了避免这个问题,将主动更新策略和超时剔除策略结合起来,如果主动更新发生问题,超时剔除还可以保证缓存数据过期后剔除脏数据。

3 缓存粒度控制方法

什么是缓存粒度?
缓存粒度是缓存系统中存储数据的最小单位。
假设现在有一个用户的信息需要缓存在Redis中
Key, user:id
Value,用户信息
假设用户表有100个列,需要缓存到什么程度呢?可以选择缓存全部列(缓存粒度较大), 也可以选择缓存部分重要列(缓存粒度较小),这就是缓存粒度问题。

3.1 缓存全部数据

将所有列缓存到Redis中。

3.2 缓存部分数据

只缓存部分重要列。

缓存粒度控制方法对比

数据类型通用性占用空间
内存空间+网络带宽+CPU开销
代码维护
全部数据简单
部分数据较为复杂

4 缓存穿透问题优化

4.1 什么是缓存穿透?

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。
在这里插入图片描述
如图所示,整个缓存穿透的过程分为三步:

  1. 缓存层不命中。
  2. 存储层不命中,不将空结果写回缓存
  3. 返回空结果。

缓存穿透将导致对不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。

缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。可以在程序中分别统计总调用数,缓存命中数、存储层命中数,如果发现大量存储层空命中,可能出现了缓存穿透问题。

造成缓存穿透的基本原因有两个:

  1. 自身业务代码或数据出现问题。
  2. 一些恶意攻击、爬虫等造成大量空命中。

4.2 缓存空对象

在这里插入图片描述
缓存空对象的解决办法如上图所示,在前面第二步,缓存层发现空命中时,将空对象写入缓存中,之后再访问这个不存在的数据就直接从缓存中获取,这样就保护了后端数据源。

缓存空对象解决办法简单、高效,但是存在两个问题:

  1. 缓存中有了个更多的键,需要更多的内存空间。如果是攻击造成的缓存穿透,使用该方法会使得问题更严重。比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动删除。
  2. 缓存层和存储层会有一段时间窗口的不一致,可能会对业务造成一定的影响。例如设置过期时间为5分钟,在这5分钟内,存储层添加了这个不存在的数据,那么就会造成缓存层和存储层数据不一致的问题,此时可以利用消息系统或其他方式主动更新缓存层里面的空对象。

下面是缓存空对象这个方法的流程
在这里插入图片描述

4.3 布隆过滤器拦截

在这里插入图片描述
在用户访问缓存层和存储层之前,布隆过滤器会将存在的key提前保存起来,做第一层拦截。如果布隆过滤器认为此次请求的数据不存在,就不会访问存储层,在一定程度上保护了存储层。

4.4 两种方案对比

决缓存穿透适用场景维护成本
缓存空对象●数据命中不高
●数据频繁变化,实时性高
●代码维护简单
●需要过多的缓存空间
●数据不一致
布隆过滤器●数据命中不高
●数据相对固定,实时性低
●代码维护复杂
●缓存空间占用少

5 无底洞问题优化

5.1 什么是无底洞问题?

当数据量和访问量增大时,一个Redis节点根本扛不住,所以需要分布式Redis,部署多个Redis节点来缓存数据。
在这里插入图片描述
使用分布式技术,部署多个Redis节点可以有效解决单节点Redis压力过大的问题,但是使用批量操作从多个分布式Redis节点获取值时,由于键值对的分配是按照哈希函数,而不是按照业务,所以某个业务的一次批量操作可能需要从多个节点去获取key,这会造成多次网络时间。
在这里插入图片描述
使用单节点Redis,批量操作时只需要一次网络操作。
在这里插入图片描述

无底洞问题分析:

  1. 客户端一次批量操作会涉及多次网络时间,所以一次批量操作会随着节点的增多,耗时会不断增大。
  2. 网络连接数变多,对节点性能也有一定影响

在这里插入图片描述
总结:更多的节点不代表更多的性能,所谓无底洞问题就是说投入越多不一定产出越多。但分布式又是不可避免的,因为访问量和数据量越来越大,一个节点根本扛不住。所以如何高效地在分布式缓存中批量操作是一个难点。

5.2 串行命令

由于n个key是比较均匀地分布在Redis集群的各个节点上,因此无法使用mget命令一次性获取,所以通常来讲,要获取n个key的值,最简单的方法就是逐次执行n个get命令,这种操作时间复杂度较高。

操作时间 = n次网络时间 + n次命令时间

在这里插入图片描述
串行命令执行的客户端代码实例如下:

public List<String> serialMGet(List<String> keys) {// 创建一个用于存储结果的列表List<String> values = new ArrayList<>();// 对于输入列表中的每个键,执行get操作,并将结果添加到结果列表中for (String key : keys) {String value = jedisCluster.get(key);values.add(value);}// 返回结果列表return values;
}

5.3 串行IO

前面介绍到,分布式部署多个Redis节点时,是将Key输入一个散列函数然后得到这个键值对该存放在哪个Redis节点,而不是根据业务。根据这个原理,我们可以在执行mget keys批量获取多个key时,先主动根据相同的散列函数将keys进行归档,然后同一个node的key放在一起用mget或Pipeline获取,只需要一次网络时间,如果有多个node,总的网路时间就是node的个数次网络时间。

操作时间 = node次网络时间 + n次命令时间

在这里插入图片描述
以下是客户端代码示例:

public Map<String, String> serialIOMget(List<String> keys) {// 创建一个用于存储结果的键值对映射Map<String, String> keyValueMap = new HashMap<>();// 创建一个映射,用于存储每个节点的键列表Map<JedisPool, List<String>> nodeKeyListMap = new HashMap<>();// 遍历所有的键for (String key : keys) {// 使用CRC16本地计算每个键的slotint slot = JedisClusterCRC16.getSlot(key);// 通过JedisCluster本地slot->node映射获取slot对应的nodeJedisPool jedisPool = jedisCluster.getConnectionHandler().getJedisPoolFromSlot(slot);// 将键归档到对应的节点if (nodeKeyListMap.containsKey(jedisPool)) {nodeKeyListMap.get(jedisPool).add(key);} else {List<String> list = new ArrayList<>();list.add(key);nodeKeyListMap.put(jedisPool, list);}}// 从每个节点上批量获取值for (Map.Entry<JedisPool, List<String>> entry : nodeKeyListMap.entrySet()) {JedisPool jedisPool = entry.getKey();List<String> nodeKeyList = entry.getValue();// 将键列表转换为数组String[] nodeKeyArray = nodeKeyList.toArray(new String[0]);// 批量获取值List<String> nodeValueList = jedisPool.getResource().mget(nodeKeyArray);// 将获取到的值归档到结果映射中for (int i = 0; i < nodeKeyList.size(); i++) {keyValueMap.put(nodeKeyList.get(i), nodeValueList.get(i));}}// 返回结果映射return keyValueMap;
}

5.4 并行IO

此方案是将串行IO方案的最后一步改为多线程执行,网络次数虽然还是节点个数,但是多线程并行执行,所以网络时间变成O(1)

操作时间 = max_slow(node网络时间) + n次命令时间

在这里插入图片描述

下面是客户端操作的示例:

public Map<String, String> serialIOMget(List<String> keys) {// 创建一个用于存储结果的键值对映射Map<String, String> keyValueMap = new ConcurrentHashMap<>();// 创建一个映射,用于存储每个节点的键列表Map<JedisPool, List<String>> nodeKeyListMap = new HashMap<>();// ... 和前面一样// 多线程mget,最终汇总结果。for (Map.Entry<JedisPool, List<String>> entry : nodeKeyListMap.entrySet()) {// 多线程实现}return keyValueMap;
}

5.5 hash_tag

Redis Cluster默认使用CRC16算法对key进行哈希,然后对16834取模,得出一个哈希槽,每个节点负责一部分哈希槽,这样就可以将数据分散到各个节点上。

但是这种方式业务无关的,也就是同一个业务的key可能会被分散到不同的Redis节点上,为了解决这个问题,可以使用Redis Cluster的{hash_tag}功能。

{hash_tag}的工作原理是,如果一个key中包含{…},那么基于哈希槽的计算只将基于大括号内的字符串。例如,对于key user:{1001}:name和user:{1001}:age,因为1001是相同的,所以这两个key会被映射到同一个节点上。
在这里插入图片描述

在这里插入图片描述

使用hash_tag功能,可以将多个业务相关的key强制分配到一个节点上。

操作时间 = 1次网络时间 + n次命令时间

在这里插入图片描述
客户端代码示例如下:

List<String> hashTagMget (String[ ] hashTagKeys) {return jedisCluster.mget (hashTagKeys) ;
}

5.5 几种批量操作方案对比

方案优点缺点网路IO
串行命令1)编程简单
2)如果少量keys,性能可以满足要求
大量keys请求延迟严重O(keys)
串行IO1)编程简单
2)少量节点,性能满足要求
大量node延迟严重O(nodes)
并行IO利用并行特性,延迟取决于最慢的节点1)编程复杂
2)由于多线程,问题定位可能较难
O(max_slow(nodes))
hash_tag性能最高1)业务维护成本较高
2)容易出现数据倾斜
O(1)

6 缓存雪崩问题优化

6.1 什么是缓存雪崩问题?

由于缓存层承载了大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是,所有请求都会到达存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。

缓存雪崩就是指缓存层宕掉后,流量像雪崩一样,流向存储层。

在这里插入图片描述

6.2 保证缓存层服务高可用性

将缓存层设计成高可用的,即使个别节点、个别机器、甚至机房宕掉,依然可以提供服务。

6.3 依赖隔离组件为后端限流并降级

无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。

对于并发量较大的系统,假如有一个资源不可用,可能会造成线程全部阻塞在这个资源上,造成整个系统不可用。

降级机制:例如推荐服务中,如果个性化推荐服务不可用,可以降级为补充热点数据,不至于造成前端页面直接开天窗。

隔离:对重要资源,如Redis,MySQL等都进行隔离,让每种资源单独运行在自己的线程池中,即使个别资源出现了问题,对其它服务没有影响。

6.4 提前演练

在项目上线前,演练缓存层宕掉后,应用以及后端负载情况以及可能出现的问题,在此基础上做一些预案设定。

7 热点key重建问题优化

7.1 什么是热点key重建问题?

开发人员使用 缓存 + 过期时间 的策略可以加速数据读写,又保证数据定期更新,这种模式基本能满足绝大多部分需求。但是两个问题如果同时存在就会带来热点Key重建问题:

  • 当前key是一个热点key,并发量非常大。
  • 重建缓存不能在短时间完成,可能是一个复杂的计算。

当这个key失效瞬间,由于其是一个热点key,会有大量线程涌入到缓存层获取该key的value,但是key已经失效,所以需要重建,此时会有大量的线程来重建缓存,造成后端负载加大,甚至可能让整个应用崩掉。
在这里插入图片描述
要解决热点key重建问题也不是很复杂,但是不能为了解决这个问题给系统带来更多麻烦,所以需要制定以下目标:

  1. 减少重建缓存的次数
  2. 数据尽可能一致
  3. 较少的潜在危险

7.2 互斥锁

此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,然后重新从缓存中获取数据即可。
在这里插入图片描述
客户端代码示例:

String get(String key) {// 从 Redis 中获取数据String value = redis.get(key);// 如果 value 为空,则开始重构缓存if (value == null) {// 只允许一个线程重构缓存,使用 nx 并设置过期时间 exString mutexKey = "mutex:key:" + key;if (redis.set(mutexKey, "1", "ex 180", "nx")) {// 从数据源获取数据value = db.get(key);// 回写 Redis,并设置过期时间redis.setex(key, timeout, value);// 删除 key mutexredis.delete(mutexKey);} else {// 其他线程休息 50 毫秒后重试Thread.sleep(50);get(key);}}return value;
}

为什么 nx 参数可以当作互斥锁?
有两点原因,首先是Redis是单线程架构,意味着任意一个时刻,只有一个命令在执行,如果有多个命令同时要求执行,最终一定是以队列方式排队执行的,其次nx的参数的含义是如果这个key不存在才可以设置值并返回ok,如果key已经存在,就会返回null,当多个线程同时执行set key value nx 时,最终只有一个线程的命令会执行并返回ok,其余线程都会返回null,这就实现了互斥。

7.3 永远不过期

永远不过期包含两层含义:

  1. 从缓存层来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是物理上的不过期。
  2. 从功能上看,为每一个value设置一个逻辑过期的时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。

该方法有效地杜绝了热点key产生的问题,唯一的缺点就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。

在这里插入图片描述
客户端代码示例如下:

String get(final String key) {V v = redis.get(key);String value = v.getValue();// 逻辑过期时间long logicTimeout = v.getLogicTimeout();// 如果逻辑过期时间小于当前时间,开始后台构建if (logicTimeout <= System.currentTimeMillis()) {String mutexKey = "mutex:key:" + key;if (redis.set(mutexKey, "1", "ex 180", "nx")) {// 重构缓存threadPool.execute(new Runnable() {public void run() {String dbValue = db.get(key);redis.set(key, dbValue, newLogicTimeout);redis.delete(mutexKey);}});}}return value;
}

7.4 两种方案比较

作为一个并发量较大的应用,在使用缓存时有三个目标:

  1. 加快用户访问速度,提高用户体验。
  2. 降低后端负载,减少潜在的风险,保证系统的平稳。
  3. 保证数据“进可能”及时更新。

下面就这三个维度来比较互斥锁方案与永远不过期方案:

解决方案优点缺点
简单分布式锁●思路简单
●保证一致性
●代码复杂性增大
●存在死锁的风险
●存在线程池阻塞的风险
永远不过期基本杜绝热点key问题●不保证一致性
●逻辑过期时间增加代码维护成本和内存成本

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

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

相关文章

所有网站都需要使用SSL证书?

SSL证书对于网站的重要性&#xff0c;简单来说就是&#xff1a; 保护隐私&#xff1a;就像给你的信封加了密码锁&#xff0c;SSL证书让网站和用户之间的所有交流都变得保密。当你在网站上输入密码、银行卡号等敏感信息时&#xff0c;有了SSL证书&#xff0c;这些信息就会被加密…

分表?分库?分库分表?实践详谈 ShardingSphere-JDBC

如果有不是很了解ShardingSphere的可以先看一下这个文章&#xff1a; 《ShardingSphere JDBC?Sharding JDBC&#xff1f;》基本小白脱坑问题 阿丹&#xff1a; 在很多开发场景下面&#xff0c;很多的技术难题都是出自于&#xff0c;大数据量级或者并发的场景下面的。这里就出…

算法打卡day37|动态规划篇05| Leetcode1049.最后一块石头的重量II、494.目标和、474.一和零

算法题 Leetcode 1049.最后一块石头的重量II 题目链接:1049.最后一块石头的重量II 大佬视频讲解&#xff1a;最后一块石头的重量II视频讲解 个人思路 和昨天的分割等和子集有些相像&#xff0c;这道题也是尽量让石头分成重量相同的两堆&#xff0c;相撞之后剩下的石头最小&am…

Discord注册教程:Discord刚注册就被封怎么办?附申诉教程!

Discord如今在海外社交媒体平台中迅速崛起&#xff0c;许多社交媒体营销人员也纷纷利用其社群特性进行推广&#xff0c;Discord注册也就成为社媒营销人员必经之路。然而&#xff0c;很多人注册Discord账号时常常会想&#xff1a;“在国内使用Discord会封号吗&#xff1f;”事实…

3d模型怎么取消光标轴定位---模大狮模型网

取消光标轴定位可以帮助您将3D模型的旋转、缩放和移动操作重置为全局坐标系。不同的3D建模软件可能有不同的方法来取消光标轴定位。以下是一般情况下在常见的3D建模软件(例如Blender、Maya、3ds Max等)中取消光标轴定位的方法&#xff1a; Blender中取消光标轴定位&#xff1a;…

【Canvas技法】图解绘制圆弧的重要函数 arc(x,y,r,startAngle,endAngle,clockWise)

【一图释疑】 【绘制上图用代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>Html5/Canvas中绘制圆弧的重要函数 arc(x,y,r,startA…

2024.4.1-day06-认识 CSS(三大特性、引入方式)

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; day06-认识 CSS(三大特性、引入方式) 文章目录 day06-认识 CSS(三大特性、引入方式)作业…

Chatgpt掘金之旅—有爱AI商业实战篇|内容策展业务|(八)

演示站点&#xff1a; https://ai.uaai.cn 对话模块 官方论坛&#xff1a; www.jingyuai.com 京娱AI 一、AI技术创业内容策展业务有哪些机会&#xff1f; 人工智能&#xff08;AI&#xff09;技术作为当今科技创新的前沿领域&#xff0c;为创业者提供了广阔的机会和挑战。随着…

JavaScript - 你知道Ajax的原理吗?如何封装一个Ajax

难度级别:中高级及以上 提问概率:75% 想要实现Ajax,就需要创建它的核心通信对象XMLHttpRequest,通过核心对象的open方法与服务端建立连接,核心对象的send方法可以将请求所需数据发送给服务端,服务端接收到请求并做出响应,我们通过核心对象…

Linux:冯·诺依曼结构 OS管理机制

Linux&#xff1a;冯诺依曼结构 & OS管理机制 冯诺依曼结构OS管理机制OS对下层硬件的管理OS对上层用户的服务 冯诺依曼结构 我们常见的计算机&#xff0c;比如笔记本&#xff0c;台式电脑。以及一下不常见的计算机&#xff0c;比如服务器&#xff0c;几乎都遵循冯诺依曼体…

积木-蓝桥每日真题

0积木 - 蓝桥云课 (lanqiao.cn) 题目描述 小明用积木搭了一个城堡。 为了方便&#xff0c;小明在搭的时候用的是一样大小的正方体积木&#xff0c;搭在了一个n行m列的方格图上&#xff0c;每个积木正好占据方格图的一个小方格。 当然&#xff0c;小明的城堡并不是平面的&#x…

说说对WebSocket的理解?应用场景?

一、是什么 WebSocket&#xff0c;是一种网络传输协议&#xff0c;位于OSI模型的应用层。可在单个TCP连接上进行全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通迅 客户端和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c…

学习嵌入式可以胜任哪一些行业?

嵌入式技术之应用范围甚广&#xff0c;其多见于机器人、无人机、医疗器械以及军工等领域&#xff0c;为学习者带来诸多广泛之职业机遇。嵌入式工程师于此诸领域中扮演关键之角色&#xff0c;负责解决硬件平台适配等诸问题&#xff0c;以为创新提供支撑之力。 虽嵌入式技术与日…

2024年中国金融科技(FinTech)行业发展洞察报告

核心摘要&#xff1a; 金融监管体系的改革推动金融科技行业进入超级监管时代&#xff0c;数据要素应用与金融场景建设成为如今行业关注的重要领域&#xff0c;为金融机构提供以业务需求为导向的技术服务成为“厚积成势”阶段行业发展的新目标&#xff0c;市场参与者的“业技融…

管易云和金蝶云星空接口打通对接实战

管易云和金蝶云星空接口打通对接实战 对接系统&#xff1a;管易云 管易云是金蝶旗下专注提供电商企业管理软件服务的子品牌&#xff0c;先后开发了C-ERP、EC-OMS、EC-WMS、E店管家、BBC、B2B、B2C商城网站建设等产品和服务&#xff0c;涵盖电商业务全流程。 接入系统&#xff1…

idea导入eclipse项目记录

eclipse虽然是免费的但是着实难用(可能是自身不熟悉&#xff09;&#xff0c;就连看class的源代码还得去市场下个插件&#xff0c;结果还搜索不到。对于习惯 IntelliJ 全家桶的的用户来说&#xff0c;用eclipse真的太痛苦。所以迁移eclipse到idea。 本来想着会有困难&#xff…

加州大学欧文分校英语基础语法专项课程02:Questions, Present Progressive and Future Tenses 学习笔记

Questions, Present Progressive and Future Tenses Course Certificate 本文是学习 Questions, Present Progressive and Future Tenses 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 Questions, Present Progressive and Future TensesWeek 01: …

017——DS18B20驱动开发(基于I.MX6uLL)

目录 一、 模块介绍 1.1 简介 1.2 主要特点 1.3 存储器介绍 1.4 时序 1.5 命令 1.5.1 命令大全 1.5.2 命令使用 1.5.3 使用示例 1.6 原理图 二、 驱动程序 三、 应用程序 四、 测试 一、 模块介绍 1.1 简介 DS18B20 温度传感器具有线路简单、体积小的特点&…

我去,PMP原来不是所有人都能报!

很多人可能觉得PMP的报名条件很复杂&#xff0c;又是经验要求&#xff0c;又是学历要求的&#xff0c;网络上关于PMP报名条件说的层出不穷&#xff0c;今天给大家统一一下&#xff0c;报名PMP究竟需要什么条件&#xff1a; 官方报考条件&#xff1a; 一、报名考生必须具备35小…

大语言模型 vs 大模型

前言 有时候我们经常说行业大模型&#xff0c;医疗大模型&#xff0c;开源大模型&#xff0c;甚至用「产品大模型」的固定结构去称呼一个模型&#xff0c;例如百度的文心一言大模型&#xff0c;但是文心一言其实是大语言模型&#xff0c;大模型和大语言模型&#xff0c;差别就…