SpringBoot 使用 Cache 集成 Redis做缓存保姆教程

1. 项目背景

Spring Cache是Spring框架提供的一个缓存抽象层,它简化了缓存的使用和管理。Spring Cache默认使用服务器内存,并无法控制缓存时长,查找缓存中的数据比较麻烦。

因此Spring Cache支持将缓存数据集成到各种缓存中间件中。本文已常用的Redis作为缓存中间件作为示例,详细讲解项目中如何使用Cache提高系统性能。

2. Spring Cache介绍

Spring Cache是Spring框架提供的一种缓存解决方案,基于AOP原理,实现了基于注解的缓存功能,只需要简单地加一个注解就能实现缓存功能,对业务代码的侵入性很小。

使用Spring Cache的方法很简单,只需要在方法上添加注解即可实现将方法返回数据存入缓存,以及清理缓存等注解的使用。

2.1 主要特点

  1. 统一的缓存抽象:Spring Cache为应用提供了一种统一的缓存抽象,可以轻松集成各种缓存提供者(如Ehcache、Redis、Caffeine等),使用统一的API。
  2. 注解驱动:Spring Cache通过简单的注解配置,如@Cacheable@CachePut@CacheEvict等,可以快速实现缓存功能,而无需处理底层缓存逻辑。
  3. 灵活性和扩展性:Spring Cache允许根据业务需求自定义缓存策略,如缓存的失效时间、缓存的淘汰策略等。同时,它也提供了CacheManager接口和Cache接口,可以实现降低对各种缓存框架的耦合。

2.2 常用注解

@EnableCaching
  • 作用:开启Spring的缓存注解支持。
  • 使用场景:在配置类上添加此注解,以启用Spring Cache的注解处理功能。
  • 注意:此注解本身并不提供缓存实现,而是允许你使用@Cacheable@CachePut@CacheEvict等注解来定义缓存行为。
@Cacheable
  • 作用:在方法执行前检查缓存,如果缓存中存在数据则直接返回,否则执行方法并将结果缓存。
  • value:指定缓存的名称(或名称数组)。缓存名称与CacheManager中配置的缓存对应。
  • key:用于生成缓存键的表达式(可选)。如果不指定,则默认使用方法的参数值作为键。
  • condition:条件表达式(可选),用于决定是否执行缓存操作。
  • unless:否定条件表达式(可选),用于在方法执行后决定是否缓存返回值。
@Cacheable注解配置参数说明
  1. value/cacheNames

    • 用于指定缓存的名称(或名称数组),缓存名称作为缓存key的前缀。这是缓存的标识符,用于在CacheManager中查找对应的缓存。
    • valuecacheNames是互斥的,即只能指定其中一个。
  2. key

    • 用于生成缓存键的表达式。这个键用于在缓存中唯一标识存储的值。
    • 如果不指定key,则默认使用方法的参数值(经过某种转换)作为键。
    • 可以使用Spring Expression Language(SpEL)来编写key表达式,以实现动态键的生成。
  3. keyGenerator

    • 指定一个自定义的键生成器(实现 org.springframework.cache.interceptor.KeyGenerator 接口的类),用于生成缓存的键。与 key 属性互斥,二者只能选其一。
    • 如果同时指定了keykeyGenerator,则会引发异常,因为它们是互斥的。
    • 开发者可以编写自己的KeyGenerator实现,并将其注册到Spring容器中,然后在@Cacheable注解中引用。
  4. cacheManager

    • CacheManager表示缓存管理器,通过缓存管理器可以设置缓存过期时间。
    • 用于指定要使用的CacheManager。这是一个可选参数,通常不需要显式指定,因为Spring会默认使用配置的CacheManager。
    • 如果系统中配置了多个CacheManager,则需要通过此参数指定使用哪一个。
  5. cacheResolver

    • 缓存解析器,用于解析缓存名称并返回相应的Cache对象。这也是一个可选参数。
    • 类似于cacheManager,如果系统中配置了多个缓存解析逻辑,可以通过此参数指定使用哪一个。
  6. condition

    • 条件表达式,用于决定是否执行缓存操作。这是一个可选参数。
    • 条件表达式使用SpEL编写,如果表达式返回true,则执行缓存操作;否则不执行。
  7. unless

    • 否定条件表达式,用于在方法执行后决定是否缓存返回值。这也是一个可选参数。
    • condition类似,unless也使用SpEL编写,但它是在方法执行后才进行评估的。
    • 如果unless表达式返回true,则不缓存返回值;否则缓存。
  8. sync

    • 是否使用异步模式进行缓存操作。这是一个可选参数,通常不需要显式指定。
    • 在多线程环境中,如果多个线程同时请求相同的数据并触发缓存操作,使用异步模式可以避免线程阻塞和重复计算。

@Cacheable注解的这些参数是互斥或相互关联的,例如valuecacheNames不能同时指定,keykeyGenerator也不能同时指定。此外,cacheManagercacheResolver也是互斥的,因为它们都用于指定缓存的解析和管理方式。

对于前两个注解的应用:

    @Cacheable(cacheNames = "cache:cacheByKey", key = "#id")public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByKey方法" + id);return id;}

看注释掉的那行,取缓存名称为cache:cacheByKey,参数id的值作为key,最终缓存key为:缓存名称+“::”+key,例如:上述代码id为123,最终的key为:cache:cacheByKey::123

SpEL(Spring Expression Language)是一种在 Spring 框架中用于处理字符串表达式的强大工具,它可以实现获取对象的属性,调用对象的方法操作。

  • 单个缓存名称@Cacheable(value = "myCache") 表示使用名为myCache的缓存。
  • 多个缓存名称@Cacheable(value = {"cache1", "cache2"}) 表示方法的结果将同时缓存到cache1cache2中。
  • @CacheConfig结合使用:如果类上使用了@CacheConfig注解,并且指定了cacheNames属性,那么类中的方法在使用@Cacheable时可以省略value属性,直接使用类级别的缓存配置。
@CacheEvict
  • 作用:从缓存中删除数据。
  • value:指定要删除的缓存的名称(或名称数组)。
  • key:用于指定要删除的缓存键(可选)。如果不指定,则默认使用方法的参数值作为键。
  • allEntries:布尔值,指定是否删除缓存中的所有条目(而不是仅删除与指定键匹配的条目)。
  • beforeInvocation:布尔值,指定是否在方法执行之前删除缓存(默认为false,即在方法执行之后删除)。
@CachePut
  • 作用:更新缓存中的数据,无论方法是否成功执行,都会将结果放入缓存。
  • valuekeyconditionunless:与@Cacheable中的这些属性相同。
@Caching
  • 作用:允许在同一个方法上组合使用多个缓存注解(如@Cacheable@CachePut@CacheEvict)。
  • 属性:包含一个或多个缓存注解。
@CacheConfig
  • 作用:为类级别提供缓存相关的默认配置。
  • cacheNames:指定该类中所有方法使用的默认缓存名称(或名称数组)。
  • keyGenerator:指定自定义的键生成器(可选)。
  • cacheManager:指定要使用的CacheManager(可选)。

3. 示例代码

项目依赖于Redis配置,这里就不多赘述了。

缓存管理器配置:

定义了两个缓存管理器,默认cacheManager(使用@Primary标注),一个缓存返回值为null的管理器cacheNullManager,详情看下面代码。

package com.maple.redis.config;import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.lang.reflect.Method;
import java.time.Duration;/*** @author 笑小枫* @date 2025/1/7*/
@Slf4j
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {/*** 默认缓存管理器* 只有CacheManger才能扫描到cacheable注解* spring提供了缓存支持Cache接口,实现了很多个缓存类,其中包括RedisCache。但是我们需要对其进行配置,这里就是配置RedisCache*/@Bean@Primarypublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {return RedisCacheManager.RedisCacheManagerBuilder//Redis链接工厂.fromConnectionFactory(redisConnectionFactory)//缓存配置 通用配置  默认存储一小时.cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))//配置同步修改或删除  put/evict.transactionAware()//对于不同的cacheName我们可以设置不同的过期时间.withCacheConfiguration("cache2:cacheByUser", getCacheConfigurationWithTtl(Duration.ofHours(2))).build();}/*** 创建并返回一个CacheManager Bean,用于管理Redis缓存。* 主要返回结果为null时使用,会缓存null值,缓存时间为10分钟,防止缓存穿透。* 使用时通过 cacheManager = "cacheNullManager" 指定使用该缓存管理器。*/@Beanpublic CacheManager cacheNullManager(RedisConnectionFactory redisConnectionFactory) {return RedisCacheManager.RedisCacheManagerBuilder//Redis链接工厂.fromConnectionFactory(redisConnectionFactory)//缓存配置 通用配置  默认存储一小时.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()// 设置key为String.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))// 设置value 为自动转Json的Object.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())).entryTtl(Duration.ofMinutes(10)))//配置同步修改或删除  put/evict.transactionAware().build();}/*** 缓存的基本配置对象*/private RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {return RedisCacheConfiguration.defaultCacheConfig()//设置key value的序列化方式// 设置key为String.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))// 设置value 为自动转Json的Object.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()))// 不缓存null.disableCachingNullValues()// 设置缓存的过期时间.entryTtl(duration);}/*** 缓存的异常处理*/@Bean@Overridepublic CacheErrorHandler errorHandler() {// 异常处理,当Redis发生异常时,打印日志,但是程序正常走log.info("初始化 -> [{}]", "Redis CacheErrorHandler");return new CacheErrorHandler() {@Overridepublic void handleCacheGetError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);}@Overridepublic void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);}@Overridepublic void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);}@Overridepublic void handleCacheClearError(RuntimeException e, Cache cache) {log.error("Redis occur handleCacheClearError:", e);}};}@Override@Bean("myKeyGenerator")public KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuffer sb = new StringBuffer();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};}
}

使用案例:

User对象就idname两个字段,大家随意~

package com.maple.redis.controller;import com.maple.redis.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.web.bind.annotation.*;/*** @author 笑小枫* @date 2025/1/7*/
@Slf4j
@RestController
@RequestMapping("/cache")
public class TestCacheController {/*** 获取简单缓存数据。** <p>通过@Cacheable注解,该方法的结果会被缓存到名为"cache:simpleCache"的缓存中。* 如果在缓存中找到相同请求的结果,将直接返回缓存的值,避免重复执行方法体中的逻辑。** <p>方法内部,使用Thread.sleep(5000)模拟了一个耗时操作,*/@GetMapping("/simpleCache")@Cacheable(cacheNames = "cache:simpleCache")public String simpleCache() throws InterruptedException {Thread.sleep(5000);log.info("执行了simpleCache方法");return "test";}/*** 如果缓存中存在对应的ID,则直接从缓存中获取结果,避免重复执行耗时操作。* 如果缓存中不存在,则执行方法体中的逻辑,将结果存入缓存并返回。* 方法执行过程中,通过Thread.sleep模拟了一个耗时操作。*/@GetMapping("/{id}")@Cacheable(cacheNames = "cache:cacheByKey", key = "#id")public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByKey方法" + id);return id;}/*** <p>该方法使用@Caching注解集成了多个缓存策略:</p>* <ul>*     <li>*         当方法返回值为null时(即缓存穿透情况),使用名为"cacheNullManager"的CacheManager进行缓存处理,*         缓存名称为"cache2:cacheByKey",缓存键为传入的用户ID,并设置缓存过期时间为10分钟。*         这通过@Cacheable注解的cacheManager属性指定缓存管理器,unless属性设置缓存条件(当结果为null时缓存)。*     </li>*     <li>*         当方法返回值不为null时,使用默认的CacheManager进行缓存处理,*         缓存名称和键的设置与上述相同,但此时缓存管理器为默认配置。*         这通过另一个@Cacheable注解实现,其unless属性设置为当结果为null时不缓存。*     </li>* </ul>** <p>在方法执行过程中,通过Thread.sleep模拟了一个耗时操作。</p>*/@Caching(cacheable = {//result为null时,属于缓存穿透情况,使用cacheNullManager缓存管理器进行缓存,并且设置过期时间为10分钟。@Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result != null", cacheManager = "cacheNullManager"),//result不为null时,使用默认缓存管理器进行缓存。@Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result == null")})@GetMapping("/cacheMore/{id}")public User cacheMore(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(5000);if (id > 100) {return null;} else {return new User(id, "zhangsan");}}@PostMapping("/cacheByUser")@Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id")public User cacheByUser(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByUser方法" + user.getId());return user;}@PostMapping("/cacheByIdAndName")@Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id")public User cacheByIdAndName(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByUser方法" + user.getId());return user;}/*** 根据用户ID大于100的条件进行缓存处理。** @param user 用户对象,包含用户ID等信息。* @return 返回传入的用户对象。* @throws InterruptedException 如果线程被中断,则抛出此异常。**                              通过@Cacheable注解实现了缓存功能,当请求的用户ID大于100时,会触发缓存机制。*                              缓存的名称设置为"cache2:cacheByUser",缓存的键为传入的用户对象的ID。*                              如果缓存中已存在对应的用户数据,则直接从缓存中获取并返回,避免重复执行耗时操作。*                              如果缓存中不存在,则执行方法体中的逻辑,将结果存入缓存并返回。*                              在方法执行过程中,通过Thread.sleep模拟了一个耗时操作。*/@PostMapping("/cacheByUserIdGt100")@Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id", condition = "#user.id > 100")public User cacheByUserIdGt100(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了cacheByUser方法" + user.getId());return user;}/*** 更新用户信息。* <p>* 使用@CachePut注解将更新后的用户信息存入缓存中。* 缓存的名称设置为"cache2:cacheByUser",缓存的键为传入的User对象的ID。* 如果缓存中已存在对应的用户数据,则更新缓存中的值;如果不存在,则创建新的缓存条目。* 在方法执行过程中,通过Thread.sleep模拟了一个耗时操作。*/@PostMapping("/updateUser")@CachePut(cacheNames = "cache2:cacheByUser", key = "#user.id")public User updateUser(@RequestBody User user) throws InterruptedException {Thread.sleep(5000);log.info("执行了saveUser方法" + user.getId());return user;}/*** 删除指定ID的用户,并从缓存中移除对应的数据。* <p>* 使用@CacheEvict注解用于从缓存中移除指定ID的用户数据。* 缓存的名称设置为"cache2:cacheByUser",缓存的键为传入的用户ID。* 在执行删除操作前,方法通过Thread.sleep模拟了一个耗时操作。*/@DeleteMapping("/{id}")@CacheEvict(cacheNames = "cache2:cacheByUser", key = "#id")public void deleteUser(@PathVariable("id") Integer id) throws InterruptedException {Thread.sleep(10000);log.info("执行了deleteUser方法" + id);}
}

模拟了多种缓存使用的方式

  • updateUser使用@CachePut对数据进行缓存或更新。
  • deleteUser使用@CacheEvict删除缓存。
  • cacheMore根据条件选择不同的缓存管理器进行缓存数据。

简单附几张测试截图吧

第一次查询,没有缓存截图:

image-20250108110651822

后续查询走缓存的截图

image-20250108110707747

redis缓存数据格式:

image-20250108110505496

redis缓存数据详情:

image-20250108110447105

4. SpEL在Spring Cache中的应用

4.1 SpEL概述

SpEL是Spring框架提供的一种功能强大的表达式语言,它能够在运行时查询和操作对象图。SpEL的语法简洁,支持方法调用、字符串模板、集合操作、逻辑运算等复杂功能,使得在Spring配置和代码中能够更轻松地处理复杂的逻辑和数据结构。

4.2 SpEL应用

  1. 动态生成缓存键

    • 在Spring Cache中,缓存键(Key)用于在缓存中唯一标识数据。通过使用SpEL表达式,可以根据方法参数、返回值等动态生成缓存键。
    • 例如,在@Cacheable注解中,可以使用key属性配合SpEL表达式来指定缓存键的生成规则。
  2. 条件缓存

    • Spring Cache允许通过condition属性来指定缓存的条件。当条件满足时,才会执行缓存操作(如缓存数据或移除缓存)。
  3. 除非条件

    • unless属性与condition属性类似,但它用于指定不执行缓存操作的条件。
    • 当unless条件满足时,即使方法被调用,其结果也不会被缓存。
    • unless属性同样支持SpEL表达式。

4.3 SpEL表达式在Spring Cache中的常用变量

  1. #参数名

    • 表示方法参数。可以通过参数名来引用方法参数的值。
    • 例如,#param1表示第一个参数的值。
  2. #result

    • 表示方法的返回值。在@CachePut和@CacheEvict注解中,可以使用#result来引用方法的返回值。
  3. #root

    • 表示缓存表达式根对象(CacheExpressionRootObject)。它提供了对缓存操作上下文的访问。
    • 通过#root,可以获取到缓存的详细信息,如缓存名称、方法参数等。

注意:

condition属性在Spring Cache中用于在方法执行前判断是否执行缓存操作,并且不能引用方法的返回值;而unless属性则用于在方法执行后根据返回值或其他条件来决定是否缓存数据。

5. 工作原理

Spring Cache是基于AOP原理,对添加注解@Cacheable的类生成代理对象,在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用源方法获取数据返回,并缓存起来,下边跟踪Spring Cache的切面类CacheAspectSupport.java中的private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)方法。

@Nullableprivate Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {if (contexts.isSynchronized()) {CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {return this.invokeOperation(invoker);}Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);Cache cache = (Cache)context.getCaches().iterator().next();try {return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));} catch (Cache.ValueRetrievalException var10) {Cache.ValueRetrievalException ex = var10;ReflectionUtils.rethrowRuntimeException(ex.getCause());}}this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));List<CachePutRequest> cachePutRequests = new ArrayList();if (cacheHit == null) {this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);}Object cacheValue;Object returnValue;if (cacheHit != null && !this.hasCachePut(contexts)) {//如果缓存有,则从缓存取cacheValue = cacheHit.get();returnValue = this.wrapCacheValue(method, cacheValue);} else {//缓存没有,执行原始方法returnValue = this.invokeOperation(invoker);cacheValue = this.unwrapReturnValue(returnValue);//再存缓存}this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);Iterator var8 = cachePutRequests.iterator();while(var8.hasNext()) {CachePutRequest cachePutRequest = (CachePutRequest)var8.next();cachePutRequest.apply(cacheValue);}this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;}

6. 本文源码

使用Redis的过程中还会有很多问题,比如缓存数据一致性,缓存数据持久化,内存淘汰机制,缓存雪崩等等等,在面试的时候也经常会用到,博主整理了一份Redis常见的面试,感兴趣的朋友可以看下:

【面试1v1实景模拟】Redis面试官会怎么提问?

本文源码:https://github.com/hack-feng/maple-product/

其中maple-redis模块即为本文的Demo源码。需要的朋友可以看下。

感兴趣的朋友可以帮忙点个star⭐⭐⭐⭐⭐后续会有更多Java相关的集成Demo,让我来做你的百宝袋吧。

🐾我是笑小枫,全网皆可搜的【笑小枫】

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

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

相关文章

MySQL —— 在CentOS9下安装MySQL

MySQL —— 在CentOS9下安装MySQL 1.查看自己操作系统的版本2.找到对应的安装源3.上传我们在windows下&#xff0c;下载的文件&#xff0c;解压4.执行rpm命令&#xff0c;启用MySQL8仓库5.执行dnf install -y mysql-community-server6.设置开机自启动7.获得初始密码8.登录MySQL…

Center Loss 和 ArcFace Loss 笔记

一、Center Loss 1. 定义 Center Loss 旨在最小化类内特征的离散程度&#xff0c;通过约束样本特征与其类别中心之间的距离&#xff0c;提高类内特征的聚合性。 2. 公式 对于样本 xi​ 和其类别yi​&#xff0c;Center Loss 的公式为&#xff1a; xi​: 当前样本的特征向量&…

AI在软件工程教育中的应用与前景展望

引言 随着科技的快速发展&#xff0c;软件工程教育面临着前所未有的挑战与机遇。传统的教学模式逐渐无法满足快速变化的行业需求&#xff0c;学生们需要更多的实践经验和个性化的学习方式。而在这样的背景下&#xff0c;人工智能&#xff08;AI&#xff09;作为一项创新技术&a…

【微服务】面试 7、幂等性

幂等性概念及场景 概念&#xff1a;多次调用方法或接口不改变业务状态&#xff0c;重复调用结果与单次调用一致。例如在京东下单&#xff0c;多次点击提交订单只能成功一次。场景&#xff1a;包括用户重复点击、网络波动导致多次请求、mq 消息重复消费、代码中设置失败或超时重…

Redis 为什么要引入 Pipeline机制?

在 Redis 中有一种 Pipeline&#xff08;管道&#xff09;机制&#xff0c;其目的是提高数据传输效率和吞吐量。那么&#xff0c;Pipeline是如何工作的&#xff1f;它又是如何提高性能的&#xff1f;Pipeline有什么优缺点&#xff1f;我们该如何使用 Pipeline&#xff1f; 1、…

游戏引擎学习第78天

Blackboard: Position ! Collision “网格” 昨天想到的一个点&#xff0c;可能本来就应该想到&#xff0c;但有时反而不立即思考这些问题也能带来一些好处。节目是周期性的&#xff0c;每天不需要全程关注&#xff0c;通常只是在晚上思考&#xff0c;因此有时我们可能不能那么…

使用 C# 制作图像的特写窗口

许多网站都会显示一个特写窗口&#xff0c;其中显示放大的图像部分&#xff0c;以便您可以看到更多细节。您在主图像上移动鼠标&#xff0c;它会在单独的图片中显示特写。此示例执行的操作类似。&#xff08;示例使用的一些数学运算非常棘手&#xff0c;因此您可能需要仔细查看…

Python学习(三)基础入门(数据类型、变量、条件判断、模式匹配、循环)

目录 一、第一个 Python 程序1.1 命令行模式、Python 交互模式1.2 Python的执行方式1.3 SyntaxError 语法错误1.4 输入和输出 二、Python 基础2.1 Python 语法2.2 数据类型1&#xff09;Number 数字2&#xff09;String 字符串3&#xff09;List 列表4&#xff09;Tuple 元组5&…

【MySQL】SQL菜鸟教程(一)

1.常见命令 1.1 总览 命令作用SELECT从数据库中提取数据UPDATE更新数据库中的数据DELETE从数据库中删除数据INSERT INTO向数据库中插入新数据CREATE DATABASE创建新数据库ALTER DATABASE修改数据库CREATE TABLE创建新表ALTER TABLE变更数据表DROP TABLE删除表CREATE INDEX创建…

力扣257(关于回溯算法)二叉树的所有路径

257. 二叉树的所有路径 一.问题描述 已解答 简单 相关标签 相关企业 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5…

Redis有哪些常用应用场景?

大家好&#xff0c;我是锋哥。今天分享关于【Redis有哪些常用应用场景&#xff1f;】面试题。希望对大家有帮助&#xff1b; Redis有哪些常用应用场景&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Redis 是一个高性能的开源键值对&#xff08;Key-Va…

【2024年华为OD机试】(A卷,100分)- 处理器问题(Java JS PythonC/C++)

一、问题描述 题目描述 某公司研发了一款高性能AI处理器。每台物理设备具备8颗AI处理器&#xff0c;编号分别为0、1、2、3、4、5、6、7。 编号0-3的处理器处于同一个链路中&#xff0c;编号4-7的处理器处于另外一个链路中&#xff0c;不通链路中的处理器不能通信。 如下图所…

设计模式-结构型-组合模式

1. 什么是组合模式&#xff1f; 组合模式&#xff08;Composite Pattern&#xff09; 是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。换句话说&#xff0c;组合模式允…

HQChart使用教程30-K线图如何对接第3方数据44-DRAWPIE数据结构

HQChart使用教程30-K线图如何对接第3方数据44-DRAWPIE数据结构 效果图DRAWPIEHQChart代码地址后台数据对接说明示例数据数据结构说明效果图 DRAWPIE DRAWPIE是hqchart插件独有的绘制饼图函数,可以通过麦语法脚本来绘制一个简单的饼图数据。 饼图显示的位置固定在右上角。 下…

Proser:升级为简易的通讯调试助手软件

我本来打算将Proser定位为一个直观的协议编辑、发送端模拟软件&#xff0c;像下面这样。 但是按耐不住升级的心理&#xff0c;硬生生的把即时收发整合了进去&#xff0c;就像这样&#xff01; 不过&#xff0c;目前针对即时收发还没有发送历史、批量发送等功能&#xff0c;…

PyTorch环境配置常见报错的解决办法

目标 小白在最基础的环境配置里一般都会出现许多问题。 这里把一些常见的问题分享出来。希望可以节省大家一些时间。 最终目标是可以在cmd虚拟环境里进入jupyter notebook&#xff0c;new的时候有对应的环境&#xff0c;并且可以跑通所有的import code。 第一步&#xff1a;…

【Linux系列】Curl 参数详解与实践应用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Web基础-分层解耦

思考&#xff1a;什么是耦合&#xff1f;什么是内聚&#xff1f;软件设计原则是什么&#xff1f; 耦合&#xff1a;衡量软件中各个层 / 各个模块的依赖关联程度。 内聚&#xff1a;软件中各个功能模块内部的功能联系。 软件设计原则&#xff1a;高内聚低耦合。 那我们该如何实现…

算法题(33):长度最小的子数组

审题: 需要我们找到满足元素之和大于等于target的最小子数组的元素个数&#xff0c;并返回 思路&#xff1a; 核心&#xff1a;子数组共有n种起点&#xff0c;nums数组的每个元素都可以充当子数组的首元素&#xff0c;我们只需要先确定子数组的首元素&#xff0c;然后往后查找满…

网络数据通信基本流程

1.基本概念 网络通信就是发送数据、接收数据、处理数据的过程&#xff0c;发送数据时要读数据进行处理&#xff08;封装&#xff09;&#xff0c;接收数据时也要对数据进行处理&#xff08;分用&#xff09;&#xff0c; 1&#xff09;封装 对数据进行加工处理&#xff0c;如…