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

深入理解分布式缓存 以及Redis 实现缓存更新通知方案

一、分布式缓存简介

1. 什么是分布式缓存

分布式缓存:指将应用系统和缓存组件进行分离的缓存机制,这样多个应用系统就可以共享一套缓存数据了,它的特点是共享缓存服务和可集群部署,为缓存系统提供了高可用的运行环境,以及缓存共享的程序运行机制。

2、本地缓存VS分布式缓存

本地缓存:是应用系统中的缓存组件,其最大的优点是应用和cache是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持的场景下使用本地缓存较合适;但是,它的缺点也是应为缓存跟应用程序耦合,多个应用程序无法共享缓存数据,各应用或集群的各节点都需要维护自己的单独缓存。很显然,这是对内存是一种浪费。

分布式缓存:与应用分离的缓存组件或服务,分布式缓存系统是一个独立的缓存服务,与本地应用隔离,这使得多个应用系统之间可直接的共享缓存数据。目前分布式缓存系统已经成为微服务架构的重要组成部分,活跃在成千上万的应用服务中。但是,目前还没有一种缓存方案可以解决一切的业务场景或数据类型,我们需要根据自身的特殊场景和背景,选择最适合的缓存方案。

3、分布式缓存的特性

相对于本地应用缓存,分布式缓存具有如下特性:  

(1) 高性能:当传统数据库面临大规模数据访问时,磁盘I/O 往往成为性能瓶颈,从而导致过高的响应延迟。分布式缓存将高速内存作为数据对象的存储介质,数据以key/value 形式存储。

(2) 动态扩展性:支持弹性扩展,通过动态增加或减少节点应对变化的数据访问负载,提供可预测的性能与扩展性;同时,最大限度地提高资源利用率;

(3) 高可用性:高可用性包含数据可用性与服务可用性两方面,故障的自动发现,自动转义。确保不会因服务器故障而导致缓存服务中断或数据丢失。

( 4) 易用性:提供单一的数据与管理视图;API 接口简单,且与拓扑结构无关;动态扩展或失效恢复时无需人工配置;自动选取备份节点;多数缓存系统提供了图形化的管理控制台,便于统一维护;

4、分布式缓存的应用场景  

分布式缓存的典型应用场景可分为以下几类:

(1) 页面缓存:用来缓存Web 页面的内容片段,包括HTML、CSS 和图片等,多应用于社交网站等;

(2) 应用对象缓存:缓存系统作为ORM 框架的二级缓存对外提供服务,目的是减轻数据库的负载压力,加速应用访问;

(3) 状态缓存:缓存包括Session 会话状态及应用横向扩展时的状态数据等,这类数据一般是难以恢复的,对可用性要求较高,多应用于高可用集群;

( 4) 并行处理:通常涉及大量中间计算结果需要共享;

(5) 事件处理:分布式缓存提供了针对事件流的连续查询(continuous query)处理技术,满足实时性需求;

( 6) 极限事务处理:分布式缓存为事务型应用提供高吞吐率、低延时的解决方案,支持高并发事务请求处理,多应用于铁路、金融服务和电信等领域;

二、 为什么要用分布式缓存?

在传统的后端架构中,由于请求量以及响应时间要求不高,我们经常采用单一的数据库结构。这种架构虽然简单,但随着请求量的增加,这种架构存在性能瓶颈导致无法继续稳定提供服务。

通过在应用服务与DB中间引入缓存层,我们可以得到如下三个好处:

(1)读取速度得到提升。

(2)系统扩展能力得到大幅增强。我们可以通过加缓存,来让系统的承载能力提升。

(3)总成本下降,单台缓存即可承担原来的多台DB的请求量,大大节省了机器成本。

通过在应用服务与DB中间引入缓存层,我们可以得到如下三个好处:

(1)读取速度得到提升。

(2)系统扩展能力得到大幅增强。我们可以通过加缓存,来让系统的承载能力提升。

(3)总成本下降,单台缓存即可承担原来的多台DB的请求量,大大节省了机器成本。

三、常用的缓存技术

目前最流行的分布式缓存技术有redis和memcached两种,

1. Memcache

Memcached 是一个高性能,分布式内存对象缓存系统,通过在内存里维护一个统一的巨大的 Hash 表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是:将数据缓存到内存中,然后从内存中读取,从而大大提高读取速度。老旧不推荐

2. Redis

Redis 是一个开源(BSD 许可),基于内存的,支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。可以用作数据库、缓存和消息中间件。

Redis 支持多种数据类型 - string、Hash、list、set、sorted set。提供两种持久化方式 - RDB 和 AOF。通过 Redis cluster 提供集群模式。

Redis的优势:

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。(事务)
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

3. 分布式缓存技术对比

不同的分布式缓存功能特性和实现原理方面有很大的差异,因此他们所适应的场景也有所不同。

四、分布式缓存实现方案

缓存的目的是为了在高并发系统中有效降低DB的压力,高效的数据缓存可以极大地提高系统的访问速度和并发性能。分布式缓存有很多实现方案,下面将讲一讲分布式缓存实现方案。

1、缓存实现方案

如上图所示,系统会自动根据调用的方法缓存请求的数据。当再次调用该方法时,系统会首先从缓存中查找是否有相应的数据,如果命中缓存,则从缓存中读取数据并返回;如果没有命中,则请求数据库查询相应的数据并再次缓存。

如上图所示,每一个用户请求都会先查询缓存中的数据,如果缓存命中,则会返回缓存中的数据。这样能减少数据库查询,提高系统的响应速度。

2、使用Spring Boot+Redis实现分布式缓存解决方案

接下来,以用户信息管理模块为例演示使用Redis实现数据缓存框架。

1.添加Redis Cache的配置类

RedisConfig类为Redis设置了一些全局配置,比如配置主键的生产策略KeyGenerator()方法,此类继承CachingConfigurerSupport类,并重写方法keyGenerator(),如果不配置,就默认使用参数名作为主键。

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {/ *** 采用RedisCacheManager作为缓存管理器* 为了处理高可用Redis,可以使用RedisSentinelConfiguration来支持Redis Sentinel*/@Beanpublic CacheManager cacheManager(RedisConnectionFactory connectionFactory) {RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory).build();return redisCacheManager;}/ *** 自定义生成key的规则*/@Overridepublic KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object o, Method method, Object...objects) {// 格式化缓存key字符串StringBuilder stringBuilder = new StringBuilder();// 追加类名stringBuilder.append(o.getClass().getName());// 追加方法名stringBuilder.append(method.getName());// 遍历参数并且追加for (Object obj :objects) {stringBuilder.append(obj.toString());}System.out.println("调用Redis缓存Key: " + stringBuilder.toString());return stringBuilder.toString();}};}
}

在上面的示例中,主要是自定义配置RedisKey的生成规则,使用@EnableCaching注解和@Configuration注解。

  • @EnableCaching:开启基于注解的缓存,也可以写在启动类上。
  • @Configuration:标识它是配置类的注解。

2.添加@Cacheable注解

在读取数据的方法上添加@Cacheable注解,这样就会自动将该方法获取的数据结果放入缓存。

@Repository
public class UserRepository {/ *** @Cacheable应用到读取数据的方法上,先从缓存中读取,如果没有,再从DB获取数据,然后把数据添加到缓存中* unless表示条件表达式成立的话不放入缓存* @param username* @return*/@Cacheable(value = "user")public User getUserByName(String username) {User user = new User();user.setName(username);user.setAge(30);user.setPassword("123456");System.out.println("user info from database");return user;}
}

在上面的实例中,使用@Cacheable注解标注该方法要使用缓存。@Cacheable注解主要针对方法进行配置,能够根据方法的请求对参数及其结果进行缓存。

1)这里缓存key的规则为简单的字符串组合,如果不指定key参数,则自动通过keyGenerator生成对应的key。

2)Spring Cache提供了一些可以使用的SpEL上下文数据,通过#进行引用。

3.测试数据缓存

创建单元测试方法调用getUserByName()方法,测试代码如下:

@Test
public void testGetUserByName() {User user = userRepository.getUserByName("weiz");System.out.println("name: "+ user.getName()+",age:"+user.getAge());user = userRepository.getUserByName("weiz");System.out.println("name: "+ user.getName()+",age:"+user.getAge());
}

上面的实例分别调用了两次getUserByName()方法,输出获取到的User信息。

最后,单击Run Test或在方法上右击,选择Run 'testGetUserByName',运行单元测试方法,结果如下图所示。

通过上面的日志输出可以看到,首次调用getPersonByName()方法请求User数据时,由于缓存中未保存该数据,因此从数据库中获取User信息并存入Redis缓存,再次调用会命中此缓存并直接返回。

五、常见问题及解决方案

1.缓存预热

缓存预热指在用户请求数据前先将数据加载到缓存系统中,用户查询 事先被预热的缓存数据,以提高系统查询效率。缓存预热一般有系统启动 加载、定时加载等方式。

5.热key问题

所谓热key问题就是,突然有大量的请求去访问redis上的某个特定key,导致请求过于集中,达到网络IO的上限,从而导致这台redis的服务器宕机引发雪崩。

针对热key的解决方案:

1. 提前把热key打散到不同的服务器,降低压力

2. 加二级缓存,提前加载热key数据到内存中,如果redis宕机,则内存查询

2.缓存击穿

缓存击穿是指大量请求缓存中过期的key,由于并发用户特别多,同时新的缓存还没读到数据,导致大量的请求数据库,引起数据库压力瞬间增大,造成过大压力。缓存击穿和热key的问题比较类似,只是说的点在于过期导致请求全部打到DB上而已。

解决方案:

1. 加锁更新,假设请求查询数据A,若发现缓存中没有,对A这个key加锁,同时去数据库查询数据,然后写入缓存,再返回给用户,这样后面的请求就可以从缓存中拿到数据了。

2. 将过期时间组合写在value中,通过异步的方式不断的刷新过期时间,防止此类现象发生。

3.缓存穿透

缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在一样。

解决方案:

  • 接口层增加参数校验,如用户鉴权校验,请求参数校验等,对 id<=0的请求直接拦截,一定不存在请求数据的不去查询数据库。
  • 布隆过滤器:指将所有可能存在的Key通过Hash散列函数将它映射为一个位数组,在用户发起请求时首先经过布隆过滤器的拦截,一个一定不存在的数据会被这个布隆过滤器拦截,从而避免对底层存储系统带来查询上 的压力。
  • cache null策略:指如果一个查询返回的结果为null(可能是数据不存在,也可能是系统故障),我们仍然缓存这个null结果,但它的过期 时间会很短,通常不超过 5 分钟;在用户再次请求该数据时直接返回 null,而不会继续访问数据库,从而有效保障数据库的安全。其实cache null策略的核心原理是:在缓存中记录一个短暂的(数据过期时间内)数据在系统中是否存在的状态,如果不存在,则直接返回null,不再查询数据库,从而避免缓存穿透到数据库上。

布隆过滤器

布隆过滤器的原理是在保存数据的时候,会通过Hash散列函数将它映射为一个位数组中的K个点,同时把他的值置为1。这样当用户再次来查询A时,而A在布隆过滤器值为0,直接返回,就不会产生击穿请求打到DB了。

4.缓存雪崩

缓存雪崩指在同一时刻由于大量缓存失效,导致大量原本应该访问缓存的请求都去查询数据库,而对数据库的CPU和内存造成巨大压力,严重的话会导致数据库宕机,从而形成一系列连锁反应,使得整个系统崩溃。雪崩和击穿、热key的问题不太一样的是,缓存雪崩是指大规模的缓存都过期失效了。

针对雪崩的解决方案:

1. 针对不同key设置不同的过期时间,避免同时过期

2. 限流,如果redis宕机,可以限流,避免同时刻大量请求打崩DB

3. 二级缓存,同热key的方案。

六、缓存与数据库数据一致性

缓存与数据库的一致性问题分为两种情况,一是缓存中有数据,则必须与数据库中的数据一致;二是缓存中没数据,则数据库中的数据必须是最新的。

3.1删除和修改数据

第一种情况:我们先删除缓存,在更新数据库,潜在的问题:数据库更新失败了,get请求进来发现没缓存则请求数据库,导致缓存又刷入了旧的值。

第二种情况:我们先更新数据库,再删除缓存,潜在的问题:缓存删除失败,get请求进来缓存命中,导致读到的是旧值。

3.2先删除缓存再更新数据库

假设有2个线程A和B,A删除缓存之后,由于网络延迟,在更新数据库之前,B来访问了,发现缓存未命中,则去请求数据库然后把旧值刷入了缓存,这时候姗姗来迟的A,才把最新数据刷入数据库,导致了数据的不一致性。

解决方案

针对多线程的场景,可以采用延迟双删的解决方案,我们可以在A更新完数据库之后,线程A先休眠一段时间,再删除缓存。

需要注意的是:具体休眠的时间,需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。当然这种策略还要考虑redis和数据库主从同步的耗时。

3.3先更新数据库再删除缓存

这种场景潜在的风险就是更新完数据库,删缓存之前,会有部分并发请求读到旧值,这种情况对业务影响较小,可以通过重试机制,保证缓存能得到删除。

七、 分布式系统中使用 Redis 实现缓存更新通知

在分布式系统中使用 Redis 实现缓存更新通知的 Java 具体方案可以通过 发布订阅(Pub/Sub)Redis Streams 实现。以下是详细的分步实现说明(基于 Jedis 或 Lettuce 客户端)。


环境准备

1. 添加 Maven 依赖

xml

复制

<!-- Jedis 客户端 -->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version>
</dependency><!-- 或者 Lettuce 客户端 -->
<dependency><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId><version>6.2.6.RELEASE</version>
</dependency>
2. Redis 连接配置

java

复制

// 使用 Jedis
JedisPool jedisPool = new JedisPool("redis-host", 6379);// 使用 Lettuce
RedisClient redisClient = RedisClient.create("redis://redis-host:6379");
StatefulRedisConnection<String, String> connection = redisClient.connect();

方案 1:Redis 发布订阅(Pub/Sub)

核心流程
  1. 发布者(数据变更时发送通知)
  2. 订阅者(所有节点监听频道并更新缓存)
代码实现
发布者(数据变更时触发)

jav

public class CacheUpdatePublisher {private final Jedis jedis;public CacheUpdatePublisher(JedisPool jedisPool) {this.jedis = jedisPool.getResource();}// 数据更新后发送通知public void updateDataAndNotify(String dataKey, String newValue) {// 1. 更新数据库(伪代码)// db.update(dataKey, newValue);// 2. 发布缓存更新通知到频道jedis.publish("cache-update-channel", dataKey);System.out.println("已发布缓存更新通知: " + dataKey);}
}
订阅者(每个节点运行)

java

public class CacheUpdateSubscriber {private final JedisPool jedisPool;public CacheUpdateSubscriber(JedisPool jedisPool) {this.jedisPool = jedisPool;}public void startListening() {new Thread(() -> {try (Jedis jedis = jedisPool.getResource()) {// 订阅频道jedis.subscribe(new JedisPubSub() {@Overridepublic void onMessage(String channel, String message) {// 收到通知后更新缓存String dataKey = message;System.out.println("收到缓存更新通知: " + dataKey);// 删除旧缓存(或重新加载)// cache.delete(dataKey);// 从数据库加载最新数据(伪代码)// String newValue = db.get(dataKey);// cache.set(dataKey, newValue);}}, "cache-update-channel");}}).start();}
}
运行示例

java

public class Main {public static void main(String[] args) {JedisPool jedisPool = new JedisPool("localhost", 6379);// 启动订阅者CacheUpdateSubscriber subscriber = new CacheUpdateSubscriber(jedisPool);subscriber.startListening();// 模拟发布者更新数据CacheUpdatePublisher publisher = new CacheUpdatePublisher(jedisPool);publisher.updateDataAndNotify("user:1001", "new data");}
}

方案 2:Redis Streams(可靠消息队列)

核心流程
  1. 生产者(数据变更时写入 Stream)
  2. 消费者(节点消费消息并确认)
代码实现
生产者(写入 Stream)

java

public class CacheStreamProducer {private final Jedis jedis;public CacheStreamProducer(JedisPool jedisPool) {this.jedis = jedisPool.getResource();}public void sendCacheUpdateEvent(String dataKey) {// 写入 Stream,指定字段Map<String, String> fields = new HashMap<>();fields.put("key", dataKey);jedis.xadd("cache-stream", StreamEntryID.NEW_ENTRY, fields);System.out.println("已发送 Stream 消息: " + dataKey);}
}
消费者(节点消费消息)

java

public class CacheStreamConsumer {private final JedisPool jedisPool;private static final String GROUP_NAME = "cache-group";private static final String CONSUMER_NAME = "node-1";public CacheStreamConsumer(JedisPool jedisPool) {this.jedisPool = jedisPool;initializeStreamGroup();}// 初始化消费者组private void initializeStreamGroup() {try (Jedis jedis = jedisPool.getResource()) {try {jedis.xgroupCreate("cache-stream", GROUP_NAME, new StreamEntryID(), true);} catch (Exception e) {// 组已存在时忽略异常}}}public void startConsuming() {new Thread(() -> {try (Jedis jedis = jedisPool.getResource()) {while (true) {// 从 Stream 读取消息(阻塞式)List<Entry<String, List<StreamEntry>>> messages = jedis.xreadGroup(GROUP_NAME, CONSUMER_NAME, 1, 0, false,Collections.singletonMap("cache-stream", ">"));if (messages != null && !messages.isEmpty()) {for (StreamEntry entry : messages.get(0).getValue()) {String dataKey = entry.getFields().get("key");System.out.println("处理缓存更新: " + dataKey);// 删除或更新缓存(伪代码)// cache.delete(dataKey);// 确认消息处理完成jedis.xack("cache-stream", GROUP_NAME, entry.getID());}}}}}).start();}
}
运行示例

java

public class Main {public static void main(String[] args) {JedisPool jedisPool = new JedisPool("localhost", 6379);// 启动消费者CacheStreamConsumer consumer = new CacheStreamConsumer(jedisPool);consumer.startConsuming();// 模拟生产者发送消息CacheStreamProducer producer = new CacheStreamProducer(jedisPool);producer.sendCacheUpdateEvent("user:1001");}
}

注意事项

  1. 幂等性设计

消费者可能重复收到同一条消息(如网络重试),需确保多次处理同一消息的结果一致。例如:

    • j
if (!cache.isKeyLatest(dataKey, newValue)) {cache.update(dataKey, newValue);
}
  1. 消息丢失处理
    • Pub/Sub:节点宕机期间的消息会丢失,需结合数据库版本号或定时全量同步。
    • Streams:通过 XPENDING 检查未确认消息,设计重试机制。
  1. 性能优化
    • 批量处理:合并多个缓存更新事件(如攒批 100ms 内的消息)。
    • 本地缓存:使用 Caffeine 或 Ehcache 作为一级缓存,减少 Redis 压力。
  1. 消费者负载均衡
    • 在 Redis Streams 中,多个消费者可通过不同 Consumer 名称共享同一 Group,实现负载均衡。

方案对比

特性

Pub/Sub

Redis Streams

可靠性

低(消息无持久化)

高(消息持久化,支持 ACK)

实时性

高(即时推送)

高(支持阻塞读取)

复杂度

低(无需维护消费状态)

中(需管理消费者组和消息 ID)

适用场景

允许短暂消息丢失的实时通知

高可靠性要求的任务分发


总结

  • 轻量级实时通知:选择 Pub/Sub,代码简单,适合对可靠性要求不高的场景(如社交动态更新)。
  • 高可靠消息队列:选择 Redis Streams,适合订单状态同步、库存扣减等关键业务。
  • 扩展方案:若需严格保证缓存与数据库一致性,可结合 MySQL Binlog 监听(如 Canal)同步到 Redis。

相关文章:

深入理解分布式缓存 以及Redis 实现缓存更新通知方案

一、分布式缓存简介 1. 什么是分布式缓存 分布式缓存&#xff1a;指将应用系统和缓存组件进行分离的缓存机制&#xff0c;这样多个应用系统就可以共享一套缓存数据了&#xff0c;它的特点是共享缓存服务和可集群部署&#xff0c;为缓存系统提供了高可用的运行环境&#xff0c…...

C#核心笔记——(六)框架基础

我们在编程时所需的许多核心功能并不是由C#语言提供的,而是由.NET Framework中的类型提供的。本节我们将介绍Framework在基础编程任务(例如虚的等值比较、顺序比较以及类型转换)中的作用。我们还会介绍Framework中的基本类型,例如String、DateTime和Enum. 本章中的绝大部分…...

C# 点击导入,将需要的参数传递到弹窗的页面

点击导入按钮&#xff0c;获取本页面的datagridview标题的结构&#xff0c;并传递到导入界面。 新增一个datatable用于存储datagridview的caption和name&#xff0c;这里用的是devexpress组件中的gridview。 DataTable dt new DataTable(); DataColumn CAPTION …...

java面向对象编程【基础篇】之基础概念

目录 &#x1f680;前言&#x1f914;面向过程VS面向对象&#x1f4af;面向过程编程&#xff08;POP&#xff09;&#x1f4af;面向对象编程&#xff08;OOP&#xff09;&#x1f4af;两者对比 &#x1f31f;三大特性&#x1f4af;封装性&#x1f4af;继承性&#x1f4af;多态性…...

Oceanbase单机版上手示例

本月初Oceanbase单机版发布&#xff0c;作为一个以分布式起家的数据库&#xff0c;原来一个集群动辄小十台机器&#xff0c;多着十几台几十台甚至更多&#xff0c;Oceanbase单机版的发布确实大大降低了硬件部署的门槛。 1.下载安装介质 https://www.oceanbase.com/softwarece…...

深度学习基础--CNN经典网络之InceptionV3详解与复现(pytorch)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 InceptionV3是InceptionV1的升级版&#xff0c;虽然加大了计算量&#xff0c;但是当时效果是比VGG效果要好的。本次任务是探究InceptionV3结构并进行复…...

VOIP通信中的错误码

cancle报文 Reason: SIP;cause200;text"Call completed elsewhere" Reason: Q.850;cause26表示取消的原因是呼叫在其他地方已经完成表示Q.850标准中的原因码26&#xff0c;通常对应于“呼叫被取消”&#xff08;Call Cancelled&#xff09;487 Request Terminated Re…...

C++ STL编程-vector概念、对象创建

vector 概念&#xff1a;是常见的一种容器&#xff0c;被称为“柔性数组”。 在vector中&#xff0c;front()是数组中的第一个元素&#xff0c;back()是数组的最后一个元素。begin()是是指向第一个元素&#xff0c;end()是指向back()的后一个元素 vector的对象创建&#xff0…...

easyexcel使用模板填充excel坑点总结

1.单层map设置值是{属性}&#xff0c;那使用两层map进行设置值&#xff0c;是不是可以使用{属性.属性}&#xff0c;以为取出map里字段只用{属性}就可以设置值&#xff0c;那再加个.就可以从里边map取出对应属性&#xff0c;没有两层map写法 填充得到的文件打开报错 was empty (…...

C#学习第16天:聊聊反射

什么是反射&#xff1f; 定义&#xff1a;反射是一种机制&#xff0c;允许程序在运行时获取关于自身的信息&#xff0c;并且可以动态调用方法、访问属性或创建实例。用途&#xff1a;常用于框架设计、工具开发、序列化、代码分析和测试等场景 反射的核心概念 1. 获取类型信息…...

【Unity】使用Cinemachine+CharacterController实现第三人称视角下的角色视角、移动和跳跃控制

1.初始配置 安装Cinemachine插件给角色添加CharacterConroller创建Cinemachine-->Free Look Camera在Free Look Camera中调整参数&#xff0c;Y Axis勾选Inver&#xff0c;X Axis取消勾选InverFree Look Camera要看向角色 跟随角色&#xff08;自行设置&#xff0c;我就不…...

如何通俗的理解transformer架构编码器和解码器干的活

我们可以用生活中的比喻来理解Transformer的编码器和解码器&#xff0c;以及解码器中两种注意力的作用&#xff1a; 一、编码器&#xff08;Encoder&#xff09;&#xff1a;理解信息的「分析师团队」 想象你要翻译一句话&#xff0c;比如把中文“今天天气很好”翻译成英文。编…...

React 受控表单绑定基础

React 中最常见的几个需求是&#xff1a; 渲染一组列表绑定点击事件表单数据与组件状态之间的绑定 受控表单绑定是理解表单交互的关键之一。 &#x1f4cd;什么是受控组件&#xff1f; 在 React 中&#xff0c;所谓“受控组件”&#xff0c;指的是表单元素&#xff08;如 &l…...

UMG:ListView

1.创建WEB_ListView,添加Border和ListView。 2.创建Object,命名为Item(数据载体&#xff0c;可以是其他类型)。新增变量name。 3.创建User Widget&#xff0c;命名为Entry(循环使用的UI载体).添加Border和Text。 4.设置Entry继承UserObjectListEntry接口。 5.Entry中对象生成时…...

实验五 内存管理实验

实验五 内存管理实验 一、实验目的 1、了解操作系统动态分区存储管理过程和方法。 2、掌握动态分区存储管理的主要数据结构--空闲表区。 3、加深理解动态分区存储管理中内存的分配和回收。 4、掌握空闲区表中空闲区3种不同放置策略的基本思想和实现过程。 5、通过模拟程…...

初识 Firebase 与 FPM

Firebase 是什么 ? Firebase 是 Google 旗下面向 iOS、Android、Web 与多端框架&#xff08;Flutter、Unity 等&#xff09;的应用开发平台&#xff0c;提供从「构建 → 发布与运维 → 增长」全生命周期的一站式后端即服务&#xff08;BaaS&#xff09;。它把实时数据库、托管…...

探索C++中的数据结构:栈(Stack)的奥秘

引言 栈是计算机科学中最基础且重要的数据结构之一&#xff0c;它像一摞盘子一样遵循"后进先出"&#xff08;LIFO&#xff09;的原则。无论是函数调用、表达式求值&#xff0c;还是浏览器前进后退功能&#xff0c;栈都扮演着关键角色。本文将深入解析栈的C实现及其应…...

vue3 nprogress 使用

nprogress 介绍与作用 1.nprogress 是一个轻量级的进度条组件&#xff0c;主要用于在页面加载或路由切换时显示一个进度条&#xff0c;提升用户体验。它的原理是通过在页面顶部创建一个 div&#xff0c;并使用 fixed 定位来实现进度条的效果 2.在 Vite Vue 3 项目中&#xf…...

MCP(Model Context Protocol 模型上下文协议)科普

MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是由人工智能公司 Anthropic 于 2024年11月 推出的开放标准协议&#xff0c;旨在为大型语言模型&#xff08;LLM&#xff09;与外部数据源、工具及服务提供标准化连接&#xff0c;从而提升AI在实际…...

韩媒专访CertiK创始人顾荣辉:黑客攻击激增300%,安全优先的破局之路

4月17日&#xff0c;韩国知名科技媒体《韩国IT时报》(Korea IT Times)发布了对CertiK联合创始人兼CEO顾荣辉教授的专访。双方围绕CertiK一季度《HACK3D》安全报告&#xff0c;就黑客攻击手法的迭代和安全防御技术的创新路径等&#xff0c;展开深度对话。 顾荣辉认为&#xff0…...

华为openEuler操作系统全解析:起源、特性与生态对比

华为openEuler操作系统全解析&#xff1a;起源、特性与生态对比 一、起源与发展历程 openEuler&#xff08;欧拉操作系统&#xff09;是华为于2019年开源的Linux发行版&#xff0c;其前身为华为内部研发的服务器操作系统EulerOS。EulerOS自2010年起逐步发展&#xff0c;支持华…...

从零实现Git安装、使用

一、git安装 Git官方下载 1.下载exe程序 2.双击安装&#xff0c;一直点击next&#xff0c;默认安装 安装完成后&#xff0c;在任意文件夹右键&#xff0c;出现下图所示&#xff0c;即为安装成功。 3.【Git Bash Here】调出命令窗口&#xff0c;设置用户名和 email 地址。 gi…...

leetcode刷题日记——单词规律

[ 题目描述 ]&#xff1a; [ 思路 ]&#xff1a; 题目要求判断字符串 s 中的单词是否按照 pattern 这种模式排列具体思路和 205. 同构字符串基本一致&#xff0c;可以通过 hash 存储来实现思路二&#xff0c;通过字符串反推 pattern&#xff0c;如果一致&#xff0c;则遵循相…...

Ubuntu 修改语言报错Failed to download repository information

1.进入文件(ps:vim可能出现无法修改sources.list文件的问题&#xff09; sudo gedit /etc/apt/sources.list2.修改(我是直接增添以下内容在其原始源前面&#xff0c;没有删原始内容)文件并保存&#xff0c;这里会替换原文件 deb http://mirrors.aliyun.com/ubuntu/ focal mai…...

烹饪与餐饮管理实训室数字课程开发方案

烹饪与餐饮管理专业需要具有餐饮产品设计、研发的能力&#xff1b; 具有饮食美学、科学配餐与高端宴席设计的能力&#xff1b; 具有餐饮企业、中央厨房运营管理的能力&#xff1b; 具有餐饮信息化系统应用、数字化运营的能力&#xff0c;这些能力的培养&#xff0c;需要烹饪与餐…...

关于模拟噪声分析的11个误区

目录 1. 降低电路中的电阻值总是能改善噪声性能 2. 所有噪声源的噪声频谱密度可以相加&#xff0c;带宽可以在最后计算时加以考虑 3. 手工计算时必须包括每一个噪声源 4. 应挑选噪声为ADC 1/10的ADC驱动器 5. 直流耦合电路中必须始终考虑1/f噪声 6. 因为1/f噪声随着频率降…...

基于 S2SH 架构的企业车辆管理系统:设计、实现与应用

在企业运营中&#xff0c;车辆管理是一项重要工作。随着企业规模的扩大&#xff0c;车辆数量增多&#xff0c;传统管理方式效率低下&#xff0c;难以满足企业需求。本文介绍的基于 S2SH 的企业车辆管理系统&#xff0c;借助现代化计算机技术&#xff0c;实现车辆、驾驶员和出车…...

51单片机实验七:EEPROM AT24C02 与单片机的通信实例

目录 一、实验环境与实验器材 二、实验内容及实验步骤 三、proteus复位电路 1.改电阻的阻值&#xff08;方法一&#xff09; 2.改电阻的属性&#xff08;方法2&#xff09; 一、实验环境与实验器材 环境&#xff1a;Keli&#xff0c;STC-ISP烧写软件,Proteus. …...

【TeamFlow】 1 TeamFlow 去中心化生产协同系统架构

总体架构设计 采用四层混合架构&#xff0c;结合分层设计与去中心化网络&#xff1a; #mermaid-svg-qBgw9wMd8Gi0gOci {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-qBgw9wMd8Gi0gOci .error-icon{fill:#552222;}…...

第 8 期:条件生成 DDPM:让模型“听话”地画图!

本期关键词&#xff1a;Conditional DDPM、Class Embedding、Label Control、CIFAR-10 条件生成 什么是条件生成&#xff08;Conditional Generation&#xff09;&#xff1f; 在标准的 DDPM 中&#xff0c;我们只是“随机生成”图像。 如果我想让模型生成「小狗」怎么办&…...

树莓派超全系列教程文档--(32)config.txt常用音频配置

config.txt常用音频配置 板载模拟音频&#xff08;3.5mm耳机插孔&#xff09;audio_pwm_modedisable_audio_ditherenable_audio_ditherpwm_sample_bits HDMI音频 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 板载模拟音频&#xff08;3.5mm耳机…...

Perf学习

重要的能解决的问题是这些&#xff1a; perf_events is an event-oriented observability tool, which can help you solve advanced performance and troubleshooting functions. Questions that can be answered include: Why is the kernel on-CPU so much? What code-pa…...

量子神经网络编译器开发指南:从理论突破到产业落地全景解析

本文深度剖析IBM Qiskit 5.0量子经典混合编译器的技术架构&#xff0c;详解如何基于含噪量子处理器实现MNIST手写数字分类任务&#xff08;准确率达89%&#xff09;。结合本源量子云、百度量子等国内平台免费配额政策&#xff0c;系统性阐述量子神经网络开发的技术路线与资源获…...

守护者进程小练习

守护者进程含义 定义&#xff1a;守护进程&#xff08;Daemon&#xff09;是运行在后台的特殊进程&#xff0c;独立于控制终端&#xff0c;周期性执行任务或等待事件触发。它通常以 root 权限运行&#xff0c;名称常以 d 结尾&#xff08;如 sshd, crond&#xff09;。 特性&a…...

研究生面试常见问题

研究生面试是考研复试的重要环节&#xff0c;面试表现直接关系到录取结果。以下从面试流程、常见问题分类及回答技巧等方面为您整理了相关内容&#xff0c;帮助您更好地准备面试。 一、研究生面试的基本流程 自我介绍&#xff1a;通常需要准备1分钟左右的中文或英文自我介绍&a…...

极狐GitLab 登录限制如何设置?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 登录限制 (BASIC SELF) 您可以使用登录限制自定义 Web 界面以及基于 HTTP(S) 的 Git 的身份验证限制。 设置 要访问登录限…...

AI驱动商业变革:零售行业的智能化跃迁

引言&#xff1a;AI技术迈入黄金时代 2024年成为生成式AI&#xff08;Gen AI&#xff09;全面落地的关键年。据麦肯锡《技术趋势展望》报告&#xff0c;生成式AI相关投资同比增长​7倍​​&#xff0c;其经济价值预计达​​2.6-4.4万亿美元​​[1]。在零售领域&#xff0c;该技…...

初始图像学(6)

Camera类 我们之前学了很多的图形学知识和相关的程序&#xff0c;现在我们停下脚步&#xff0c;来好好整理一下我们学习的内容&#xff0c;我们将之前的视口代码和渲染代码合并到一个新的单类camera.h&#xff0c;这个类主要负责两项任务&#xff1a; 构建并发射光线到世界中 …...

【React】通过 fetch 发起请求,设置 proxy 处理跨域

fetch 基本使用跨域处理 fetch 基本使用 在node使用原生ajax发请求&#xff1a;XMLHttpRequest()1.获取xhr对象 2.注册回调函数 3.设置参数&#xff0c;请求头 4.发起连接原生ajax没有带异步处理 promise&#xff1b;原生ajax封装一下&#xff0c;以便重复调用jQuery&#…...

好数对的数目

题目描述 给你一个整数数组 nums。 如果一组数字 (i, j) 满足 nums[i] nums[j] 且 i < j&#xff0c;就可以认为这是一组 好数对。 返回 好数对 的数目。 示例 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,1,1,3] 输出&#xff1a;4 解释&#xff1a; 有 4 组好…...

Animated Raindrop Ripples In HLSL

这节课是利用材质做雨滴i效果 首先是创建一个圆环&#xff0c;实际上他就是为了创建一个圆&#xff0c;但是是空心的&#xff0c;可以看之前我的做法&#xff0c;这里以他的为准 创建圆环 就是当uv的点在max_radius和min_radius之间的时候绘制。 他这里写了ringThickness&a…...

Linux学习——守护进程编程

一、守护进程含义及实现过程 1、含义 守护进程&#xff08;Daemon Process&#xff09; 是操作系统中一种在后台长期运行的特殊进程&#xff0c;通常不与用户直接交互。它独立于控制终端&#xff0c;用于执行周期性任务或系统服务&#xff08;如日志管理、网络服务等&#xff…...

【C++】 —— 笔试刷题day_19

一、小易的升级之路 题目解析 小易现在要打游戏&#xff0c;现在游戏角色的初始能力值为a&#xff0c;我们会遇到n个怪&#xff0c;这些怪物的防御值为b1、b2、b3...&#xff0c;如果我们的能力值要高于或者等于怪物的防御值&#xff0c;那我们的能力值就会加bi&#xff1b;如…...

gitee提交大文件夹

# 安装 Git LFS&#xff08;如果未安装&#xff09; git lfs install# 跟踪整个大文件夹&#xff08;或特定大文件类型&#xff09; git lfs track "big_folder/**" # 替换为你的文件夹名# 提交并推送 git add .gitattributes big_folder/ git commit -m "add …...

纯CSS实现自动滚动到底部

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>自动滚动到底部</title><style>*…...

WInform当今技术特性分析

Windows Forms (WinForms) 技术特性分析 引言 Windows Forms (WinForms) 作为微软最早推出的基于.NET的图形用户界面开发框架&#xff0c;已经存在了20多年。在如今充满了各种现代UI框架的软件开发生态系统中&#xff0c;WinForms仍然保持着其独特的地位。本文将深入分析WinF…...

6.5 GitHub监控系统实战:双通道采集+动态调度打造高效运维体系

GitHub Sentinel Agent 定期更新功能设计与实现 关键词:GitHub API 集成、定时任务调度、Python 爬虫开发、SMTP 邮件通知、系统稳定性保障 1. GitHub 项目数据获取功能 1.1 双通道数据采集架构设计 #mermaid-svg-ZHJIMXcMAyDHVhmV {font-family:"trebuchet ms",v…...

自动驾驶安全模型研究

自动驾驶安全模型研究 自动驾驶安全模型研究 自动驾驶安全模型研究1.自动驾驶安全模型概述2. 自动驾驶安全模型应用3. 自动驾驶安全模型介绍3.1 Last Point to Steer3.2 Safety Zone3.3 RSS (Responsibility-Sensitive Safety)3.4 SFF (Safety Force Field)3.5 FSM (Fuzzy Safe…...

4090租用,各云GPU平台价格对比清单及建议

根据各平台的价格和型号配置&#xff0c;以下是通过DeepSeek-R1进行分析后&#xff0c;给出的建议。 一、按显存需求推荐 1.24G显存需求&#xff08;适合常规AI训练/推理、中小模型&#xff09; 性价比首选&#xff1a;智星云&#xff08;1.35元/小时&#xff09; &#xff0…...

Linux:简单指令(二)

文章目录 man ~~echocatcp指令which ~~mvless时间相关的指令find 要么对内容要么对属性操作 决定路径开始定位文件&#xff0c;也可以相对路径 家目录/ man ~~ 1查询具体命令 我们可以man man 可以看man 的描述 我们可以man 数字 ~~ 可以从上到下查询 2查询仿命令 3查询具体接口…...