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 缓存部分数据
    • 3.3 缓存粒度控制方法对比
  • 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 缓存部分数据

只缓存部分重要列。

3.3 缓存粒度控制方法对比

数据类型通用性占用空间
内存空间+网络带宽+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.rhkb.cn/news/303143.html

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

相关文章

(2022级)成都工业学院软件构造实验三:面向数据的软件构造

写在前面 1、基于2022级软件工程实验指导书 2、代码仅提供参考 3、如果代码不满足你的要求&#xff0c;请寻求其他的途径 运行环境 window11家庭版 IntelliJ IDEA 2023.2.2 jdk17.0.6 实验要求 任务&#xff1a; ‍一、构造任务4&#xff1a;批量产生习题并用文件存储…

IntelliJ IDEA 2024.1安装与激活[破解]

一&#xff1a;IDEA官方下载 ①如题&#xff0c;先到IDEA官方下载&#xff0c;简简单单 ②IDEA官方&#xff1a;IntelliJ IDEA – the Leading Java and Kotlin IDE 二&#xff1a;获取脚本 &#x1f31f;网盘下载&#xff1a;jetbra (密码&#xff1a;lzh7) &#x1f31f;获取…

STC89C52学习笔记(七)

STC89C52学习笔记&#xff08;七&#xff09; 综述&#xff1a;本文介绍了串口以及讲述了串口相关寄存器如何配置并给予相关代码。 一、修改代码注意事项 在修改代码时不要一次性加入一堆代码&#xff0c;不利于定位错误。可以先注释一些代码&#xff0c;待解决完毕问题后再…

物联网农业四情在线监测系统

TH-Q2随着科技的飞速发展和信息化时代的来临&#xff0c;物联网技术在各个领域都取得了显著的应用成果。其中&#xff0c;物联网农业四情在线监测系统作为农业现代化的重要组成部分&#xff0c;正在为农业生产带来革命性的变革。 一、物联网农业四情在线监测系统的概念 物联网…

大模型笔记:Prompt tuning

1 NLP模型的几个阶段 1.1 第一阶段&#xff08;在深度学习出现之前&#xff09; 通常聚焦于特征工程&#xff08;feature engineering&#xff09;利用领域知识从数据中提取好的特征 1.2 第二阶段&#xff08;在深度学习出现之后&#xff09; 特征可以从数据中习得——>…

使用 kaggle api 实现 kaggle 数据快速下载

在下载kaggle数据集时&#xff0c;以猫狗数据集举例子&#xff0c;有两种方法&#xff1a; Dogs vs. Cats | Kaggle 1&#xff1a;直接浏览器下载&#xff0c;较慢&#xff0c;不推荐。 2&#xff1a;使用kaggle API下载&#xff0c;很快。本文重点介绍。详情可以&#xff1…

Web前端-Ajax

Ajax 概念:Asynchronous JavaScript And XML,异步的JavaScript和XML。 作用: 1.数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。 2.异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用的校验等等…

ARM嵌入式控制器带HDMI为制造业注入智能动力

工业自动化技术的飞速发展&#xff0c;IT与OT的融合已成为推动工业进步的关键力量。在这个背景下&#xff0c;ARM工业计算机凭借其强大的功能和灵活性&#xff0c;成为了边缘自动化领域的一颗新星。今天&#xff0c;我们将深入探讨这款设备如何通过其独特的特性&#xff0c;助力…

Scrapy 爬取m3u8视频

Scrapy 爬取m3u8视频 【一】效果展示 爬取ts文件样式 合成的MP4文件 【二】分析m3u8文件路径 视频地址&#xff1a;[在线播放我独自升级 第03集 - 高清资源](https://www.physkan.com/ph/175552-8-3.html) 【1】找到m3u8文件 这里任务目标很明确 就是找m3u8文件 打开浏览器…

【C语言】“vid”Microsoft Visual Studio安装及应用(检验内存泄露)

文章目录 前言安装包获取配置VLD完成 前言 我们在写代码时往往容易存在内存泄漏的情况&#xff0c;所以存在这样一个名为VLD的工具用来检验内存泄漏&#xff0c;现在我来教大家安装一下 安装包获取 vld下载网址&#xff1a;https://github.com/KindDragon/vld/releases/tag/…

三支冲突分析介绍

Pawlak最早通过观察一组智能体对一组问题的意见&#xff0c;提出了冲突分析模型。U表示对象集&#xff0c;V表示属性集&#xff0c;R表示对象集和属性集之间的二元关系&#xff0c;这样一个刻画冲突分析的信息系统通过三元组&#xff08;U&#xff0c;V&#xff0c;R&#xff0…

物理服务器与云服务器的租用对比

​ 物理服务器&#xff1a;每个基于 Web 的应用程序都依赖于一个服务器&#xff0c;该服务器提供网络中的数据存储&#xff0c;并可根据请求提供给客户端。例如&#xff0c;用户使用浏览器访问 Web 应用程序。服务器可确保托管客户端可以使用该硬件组件。与其他托管可能性相比&…

LeetCode 2529. 正整数和负整数的最大计数——每日一题

上一篇博客&#xff1a;LeetCode 993. 二叉树的堂兄弟节点——每日一题 写在前面&#xff1a;大家好&#xff01;我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正&#xff0c;感谢大家的不吝赐教。我的唯一博客更新地址是&#xff1a;https://ac-fun.…

【黑马头条】-day07APP端文章搜索-ES-mongoDB

文章目录 今日内容1 搭建es环境1.1 拉取es镜像1.2 创建容器1.3 配置中文分词器ik1.4 测试 2 app文章搜索2.1 需求说明2.2 思路分析2.3 创建索引和映射2.3.1 PUT请求添加映射2.3.2 其他操作 2.4 初始化索引库数据2.4.1 导入es-init2.4.2 es-init配置2.4.3 导入数据2.4.4 查询已导…

Elastisearch、Kibana安装

Elastisearch简介 全文搜索属于最常见的需求&#xff0c;开源的 Elasticsearch &#xff08;以下简称 Elastic&#xff09;是目前全文搜索引擎的首选。它可以快速地储存、搜索和分析海量数据。 Elastic 的底层是开源库 Lucene。但是&#xff0c;你没法直接用 Lucene&#xff0…

[网鼎杯 2020 玄武组]SSRFMe

[网鼎杯 2020 玄武组]SSRFMe 源码 <?php function check_inner_ip($url) {$match_resultpreg_match(/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/,$url);if (!$match_result){die(url fomat error);}try{$url_parseparse_url($url);}catch(Exception $e){die(url foma…

真实世界的映照-DDD实体

什么是实体&#xff1f; 实体&#xff0c;官方的解释是&#xff1a;实体&#xff08;Entity&#xff0c;又称为Reference Object&#xff09;很多对象不是通过他们的属性定义的&#xff0c;而是通过一连串的连续事件和标识定义的。主要由标识定义的对象被称为ENTITY。 但&…

RTThread studio 驱动开发

rtthread 驱动使用的两种情况 rtthread studio 自动生成 由 RT Thread Studio 自动生成&#xff0c;无需修改任何文件或者简单定义几个宏即可直接使用的驱动&#xff0c;如 GPIO&#xff0c;UART&#xff0c;I2C&#xff0c;SPI&#xff0c;SDIO 和 ETH 等。 使用 RT-Thread S…

嵌入式硬件中常见的面试问题与实现

1 01 请列举您知道的电阻、电容、电感品牌(最好包括国内、国外品牌) ▶电阻 美国:AVX、VISHAY威世 日本:KOA兴亚、Kyocera京瓷、muRata村田、Panasonic松下、ROHM罗姆、susumu、TDK 台湾:LIZ丽智、PHYCOM飞元、RALEC旺诠、ROYALOHM厚生、SUPEROHM美隆、TA-I大毅、TMT…

STM32学习和实践笔记(6):自己进行时钟配置的思路

在《STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (d)-CSDN博客》 中&#xff0c;我了解到&#xff0c;在程序执行我们写的main函数之前&#xff0c;实际上先执行了一个汇编语言所写的启动文件&#xff0c;以完成相应的初始…