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

谷粒商城の缓存篇

文章目录

  • 前言
  • 一、本地缓存和分布式缓存
    • 1.本地缓存
    • 2.分布式缓存
  • 二、项目实战
    • 1.配置Redis
    • 2.整合业务代码
      • 2.1 缓存击穿
      • 2.2 缓存雪崩
      • 2.3 缓存穿透
      • 2.4 业务代码1.0版
      • 2.5 分布式锁1.0版
      • 2.6 分布式锁2.0版
      • 2.7 Spring Cache及缓存一致性问题
        • 2.7.1 Spring Cache
        • 2.7.2 缓存一致性问题
        • 2.7.3 Spring Cache的弊端

前言

  本篇重点介绍谷粒商城首页整合缓存技术,从本地缓存(Map)到分布式缓存(Redis),描述常见的缓存三大问题(缓存穿透,缓存雪崩,缓存击穿)及解决方案,并且在解决的过程中引用成熟的Redisson方案。最后到缓存一致性的问题及解决,整合Spring Cache

  对应视频P151-P172

一、本地缓存和分布式缓存

1.本地缓存

  本地缓存存储在单个应用服务器的内存中,属于该服务器的进程空间。仅在当前服务器节点内有效,不会在多个服务器之间共享。
本地缓存最简单的实现方式:通过Map

    private HashMap<String,Object> map = new HashMap<>();@Testpublic Object testMapCache(){Object key = map.get("key");if (key !=null){return key;}//查询数据库相关逻辑...假设查询到的值为valuemap.put("key","value");return "value";}

  不考虑缓存一致性,穿透,击穿等问题,上面的案例就是通过Map做本地缓存最简单的实现。

2.分布式缓存

  目前市面上大多数的项目都是采用微服务的架构,同一个服务也可能部署多个实例。而如上面所说,本地缓存仅在当前服务器节点内有效。假设现在有三台服务器:
Alt
  初始状态下三台服务器都没有缓存,第一次用户访问了服务器1,查询数据库后将结果存入了缓存。下一次由于负载均衡,访问到了服务器2:
Alt
  由于缓存此时只存在于服务器1,这次用户又需要去数据库中查询,然后放入服务器2的缓存中。
  为了解决这样的问题,在微服务的架构中,引入了缓存中间件对不同服务间的缓存进行统一管理。常用的是Redis

二、项目实战

1.配置Redis

  		 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
spring:redis:host: xxxport: 6379

  Redis为我们封装了两个模版,分别是redisTemplatestringRedisTemplatestringRedisTemplate的key和value默认都是String类型的,在项目中使用时,只需要注入对应的模版即可。
Alt

2.整合业务代码

  在项目中,需要加入缓存的业务场景是,首页渲染三级分类菜单。
  缓存这一块的坑点很多,在整合业务代码前,有必要先介绍一下缓存常见的三大问题及解决方案:

2.1 缓存击穿

  假设数据库中的某张A表,数据的主键ID是从1-1000,如果使用1001的ID去查询数据,是无论如何都查询不到的,查询到的会是空值。如果没有将这个空值存入缓存,那么通过伪造请求等方式不断地使用不存在的ID作为条件去查询数据库,也会导致数据库崩溃的情况。
  解决方式:如果根据查询条件查询到的结果不存在,就缓存一个空值或进行约定,缓存一个特定的值。也可以通过布隆过滤器,或加强参数校验的方式解决。

2.2 缓存雪崩

  这种情况主要是出现在大并发量的场景下,大量的热点key同时失效,导致这一刻的所有请求都打到数据库上。
  解决方式:给不同的key设置随机的过期时间,或者设置永不过期。

2.3 缓存穿透

  区别于缓存雪崩,击穿主要是体现在某个热点key失效,导致大量的请求在查询缓存无果的情况下,都去数据库中查询。
  解决方式:加锁,让同一时刻只有一个线程能查询到数据库。但是涉及到多线程锁的问题时,一般就不会有那么简单了。我们知道锁有本地锁和分布式锁,也有乐观锁和悲观锁。
  如果直接使用synchronized关键字进行加锁,在单体应用下是没问题的。synchronized关键字是锁当前的JVM。在微服务架构下,每个服务都有自己的JVM,假设我的product服务部署在了8台服务器上,每个服务器锁自己的JVM,最后还是有可能8个请求同时打在数据库上。所以需要一个全局的锁去统一管理这些服务。通过Redis也可以自己实现分布式锁,但是其中有很多坑点。

2.4 业务代码1.0版

  加入缓存后的业务流程图:
Alt
  我们先不考虑分布式锁的实现,完成第一版加入缓存的业务代码:
  这里有几点需要注意下:

  1. 存入缓存的key必须唯一,可以加上当前用户或者业务的前缀。例如我将商品列表放入缓存,商品列表可以被不同的用户访问,又带有查询条件,可以这样设计key:用户标识:查询条件1_查询条件2_查询条件3
  2. 某个线程获取到了锁,在查询数据库前,需要先再次查询缓存中是否有值。
  3. 将数据库查询结果,放入缓存必须在锁的范围内,否则可能存在,A线程查到了数据然后释放了锁,准备放入缓存,在放入缓存的过程中,B线程获取到了锁,又去查了一遍数据库的问题。
  4. 向Redis中存储的数据,一般约定使用JSON字符串的方式进行存储,在读取时进行反序列化。
@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Map<String, List<CategoryJsonVO>> getCategoryJson() {//从缓存中获取String category = stringRedisTemplate.opsForValue().get(RedisConstants.CATEGORY_KEY);//缓存中不为空if (StringUtils.isNotBlank(category)) {log.info("查询到了结果");return JSON.parseObject(category, new TypeReference<Map<String, List<CategoryJsonVO>>>() {});}/*缓存空值解决缓存穿透设置过期时间(随机值)解决缓存雪崩加锁解决缓存击穿*///查询pms_category表的全量数据Map<String, List<CategoryJsonVO>> map;map = this.getCateGoryFromDB();return map;}/*** 从数据库查询三级分类* @return 查询结果*/private Map<String, List<CategoryJsonVO>> getCateGoryFromDB() {synchronized (this) {log.info("获取到了锁");//再看下缓存中有没有//从缓存中获取String category = stringRedisTemplate.opsForValue().get(RedisConstants.CATEGORY_KEY);//缓存中不为空if (StringUtils.isNotBlank(category)) {log.info("查询到了结果");return JSON.parseObject(category, new TypeReference<Map<String, List<CategoryJsonVO>>>() {});}log.info("开始查询数据库");List<CategoryEntity> list = list();Map<String, List<CategoryJsonVO>> map = list.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {//查出某个一级分类下的所有二级分类//            List<CategoryEntity> entityList = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));List<CategoryEntity> entityList = list.stream().filter(categoryEntity -> categoryEntity.getParentCid().equals(v.getCatId())).collect(Collectors.toList());List<CategoryJsonVO> categoryJsonVOS = entityList.stream().map(categoryEntity -> {CategoryJsonVO jsonVO = new CategoryJsonVO();jsonVO.setCatalog1Id(String.valueOf(categoryEntity.getParentCid()));jsonVO.setId(String.valueOf(categoryEntity.getCatId()));jsonVO.setName(categoryEntity.getName());//查出某个二级分类下的所有三级分类//                List<CategoryEntity> entityListThree = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", categoryEntity.getCatId()));List<CategoryEntity> entityListThree = list.stream().filter(categoryEntity1 -> categoryEntity1.getParentCid().equals(categoryEntity.getCatId())).collect(Collectors.toList());List<CategoryJsonVO.CatalogJsonThree> catalogJsonThrees = entityListThree.stream().map(categoryEntity1 -> {CategoryJsonVO.CatalogJsonThree catalogJsonThree = new CategoryJsonVO.CatalogJsonThree();catalogJsonThree.setId(String.valueOf(categoryEntity1.getCatId()));catalogJsonThree.setName(categoryEntity1.getName());catalogJsonThree.setCatalog2Id(String.valueOf(categoryEntity1.getParentCid()));return catalogJsonThree;}).collect(Collectors.toList());jsonVO.setCatalog3List(catalogJsonThrees);return jsonVO;}).collect(Collectors.toList());return categoryJsonVOS;}));//向缓存中存一份(序列化)stringRedisTemplate.opsForValue().set(RedisConstants.CATEGORY_KEY, CollectionUtils.isEmpty(map) ? "0" : JSON.toJSONString(map), 1, TimeUnit.DAYS);return map;}}}

2.5 分布式锁1.0版

  下面我们自己先手动实现一个分布式锁:

 	  @Testpublic void testLock(){String uuid = UUID.randomUUID().toString();//获取锁Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid);//获取到了锁if (lock){try {//设置过期时间stringRedisTemplate.expire("lock",300, TimeUnit.SECONDS);//执行业务代码}catch (Exception e){//日志记录异常}finally {stringRedisTemplate.delete("lock");}}else {//未获取到锁就自旋继续尝试获取testLock();}}

  上面的代码有什么问题?可谓漏洞百出。

  1. 获取锁和设置过期时间分为了两个步骤去实现。:会导致一个什么样的问题?既然是两步,没有写在一条命令里,说明是非原子性的操作。如果两行代码之间出现了异常,那么过期时间就没有设置成功。那么能不能将设置过期时间写在finally块中?答案也是不行的,因为出现异常不仅仅可能是程序方面的异常,假设极端情况下机房停电了…所以为了解决这个问题,需要做如下的改动:
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,300, TimeUnit.SECONDS);
  1. 解锁时没有进行判断:会导致将其他线程的锁误删的问题。例如线程A拿到了锁,由于业务执行的时间较长,线程A的锁超时了,线程B拿到了锁,B在执行自己业务的时候,线程A执行完了业务,释放了B线程的锁…不是那么靠谱的解决方案:
 			if (stringRedisTemplate.opsForValue().get("lock").equals(uuid)){stringRedisTemplate.delete("lock");}

为什么说这个解决方案不是那么靠谱?引出了第三个问题

  1. 解锁时的条件判断非原子性操作:因为判断+解锁之间也是存在间隔时间的,必须要保证原子性。例如锁设置的key的value是1,设置的过期时间是10S,但是前面的操作花费了9.5S,判断的时间花费了0.6S,相当于key对应的value已经过期了。下一个线程进来又设置key的value是2(实际上lock对应的值变了,但是在判断的时候,获取到的lock的值还是之前的1),然后原来的线程解锁就把下一个线程的锁给解了。解决方案是使用lua脚本,包括后面引入的Redisson的底层很多也是通过lua脚本实现的
			String script = "if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1]) else return 0 end";//删除锁Long lock1 = redisTemplate.execute(newDefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);

  通过上述问题的发现与解决,看似我们自己实现的分布式锁没有问题了,其实不然,仔细深究还是会存在锁重入,重试等相关问题。

2.6 分布式锁2.0版

  引入Redisson:

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><!-- 请使用最新版本 --><version>3.16.3</version>
</dependency>

  进行配置:

@Configuration
public class RedissonConfig {@Bean(destroyMethod = "shutdown")public RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://自己的虚拟机地址:6379");return Redisson.create(config);}}

  Redisson的基本使用及原理:

@Test
public void testRedisson() {RLock lock = redissonClient.getLock("lock");//默认过期时间30S,业务在执行完成之前每隔10S续期一次//如果设置了过期时间,就按照过期时间来,不会自动续期lock.lock();try {}finally {lock.unlock();}
}

  通过RLock lock = redissonClient.getLock("lock");可以获取一把锁,只要名称相同就代表是同一把锁。
  除了上面获取锁的方式,还有其他关于锁的操作,在官方文档中都有说明:
在这里插入图片描述Redisson官方文档中文版

  lock.lock();方法,如果没有设置过期时间,它有一个默认的30S过期时间,同时会每隔1/3默认时间自动续期,设置了过期时间,则按照实际的过期时间,即使业务没有执行完成也不会自动续期。
  项目实战篇以应用为主,限于篇幅不翻源码,源码解析会放在源码分析专栏后续更新。
  改造业务代码:

@Autowired
private RedissonClient redissonClient;/*** 从数据库查询三级分类* 分布式锁解决缓存击穿* @return 查询结果*/
private Map<String, List<CategoryJsonVO>> getCateGoryFromDB() {//category_lockRLock lock = this.redissonClient.getLock(RedisConstants.CATEGORY_LOCK_KEY);lock.lock(10, TimeUnit.SECONDS);try {-- 业务代码} finally {lock.unlock();}
}

2.7 Spring Cache及缓存一致性问题

2.7.1 Spring Cache

  简单来说,Spring Cache是基于声明式注解的缓存,对于缓存声明,Spring的缓存抽象提供了一组Java注解:

  • @Cacheable: 触发缓存的填充。
  • @CacheEvict: 触发缓存删除。
  • @CachePut: 更新缓存而不干扰方法的执行。
  • @Caching: 将多个缓存操作重新分组,应用在一个方法上。
  • @CacheConfig: 分享一些常见的类级别的缓存相关设置。

  详见Spring官方文档中文版

  在项目中使用,只需要引入依赖,并在配置文件中进行配置:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
# 配置spring cache 为redis
spring.cache.type=redis
spring.cache.redis.time-to-live=360000

  在方法上加入注解:

@Override
@Cacheable(value = {"category"},key = "'getLevelOneCateGory'") //放入缓存 如果缓存中有方法就不调用
public List<CategoryEntity> getLevelOneCateGory() {return list(new QueryWrapper<CategoryEntity>().eq("parent_cid", "0"));
}

  启动项目,通过redis客户端查看对应的缓存数据:
在这里插入图片描述  需要注意,默认的序列化方式不是JSON,而是JDK序列化。需要自定义配置:

@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class MyRedisCacheConfig {@Beanpublic RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();//自定义键值的序列化config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));//自定义键和值的过期时间,从配置文件中读取CacheProperties.Redis redisProperties = cacheProperties.getRedis();if (redisProperties.getTimeToLive() != null) {config = config.entryTtl(redisProperties.getTimeToLive());}if (redisProperties.getKeyPrefix() != null) {config = config.prefixKeysWith(redisProperties.getKeyPrefix());}if (!redisProperties.isCacheNullValues()) {config = config.disableCachingNullValues();}if (!redisProperties.isUseKeyPrefix()) {config = config.disableKeyPrefix();}return config;}
}
2.7.2 缓存一致性问题

  缓存一致性问题简单来说,就是缓存中的数据和数据库最新的数据不一致,导致用户看到的数据非实时而是旧的缓存中的。
  解决缓存一致性问题,对于数据库写入方,一般有如下几种方案:

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

  上述两种方案都是有弊端的:
在这里插入图片描述
   先删除缓存再更新数据库对应上图的情况,用户读取到的数据还是未更新数据库前旧的数据。
在这里插入图片描述
  如果先更新数据库再删除缓存 也可能存在上图的情况,即如果B线程更新数据库的时间较长,并且此时C线程进行查询,C线程查询到的还是A线程更新数据库的结果,并且将A的操作结果写入缓存,获取到的依旧不是B最新操作的数据。
  既然两者都有弊端,那么就引入了第三种方式:延迟双删在这里插入图片描述  其实无论是何种方式,保证的都是缓存的最终一致性,如果对数据实时性的要求高,且数据更新频繁,应该去查数据库,而不是使用缓存。
  在项目中,采用先更新数据库再删除缓存 的策略,结合注解:

/*** 修改* 修改时删除缓存*/
@CacheEvict(value = {"category"},key = "'getLevelOneCateGory'")
@RequestMapping("/update")
public R update(@RequestBody CategoryEntity category){categoryService.updateById(category);return R.ok();
}
2.7.3 Spring Cache的弊端

  主要体现在解决缓存击穿问题上,在手动编写逻辑时,是通过Redisson分布式锁的方式解决的,而Spring Cache的注解默认是不加锁的,如果加锁,需要在注解中设置sync为true,并且这里的锁是本地锁,非分布式锁。

相关文章:

谷粒商城の缓存篇

文章目录 前言一、本地缓存和分布式缓存1.本地缓存2.分布式缓存 二、项目实战1.配置Redis2.整合业务代码2.1 缓存击穿2.2 缓存雪崩2.3 缓存穿透2.4 业务代码1.0版2.5 分布式锁1.0版2.6 分布式锁2.0版2.7 Spring Cache及缓存一致性问题2.7.1 Spring Cache2.7.2 缓存一致性问题2.…...

永远学习:为什么人工智能难以适应新挑战

理解深度学习的局限性并追求真正的持续适应 欢迎来到雲闪世界。 “智者适应环境&#xff0c;正如水适应水瓶。”——中国谚语 “适应或灭亡&#xff0c;现在和以往一样&#xff0c;是大自然的必然法则。”——赫伯特乔治威尔斯 近年来&#xff0c;人工智能取得了长足的进步。所…...

【spring】 Jackson :@JsonIgnore 注解

@JsonIgnore 是 Jackson 库中的一个注解,用于在序列化和反序列化过程中忽略某个字段。也就是说,当对象被转换为 JSON 或从 JSON 转换为对象时,带有 @JsonIgnore 注解的字段将不会被包含在内在这个示例中,ignoredField 字段将不会出现在生成的 JSON 字符串中。 import com.…...

Dependencies与DependencyManagement的区别

现在Maven项目管理&#xff0c;在开发中时比较常用的&#xff0c;在一些项目汇总遇到依赖冲突的问题之后&#xff0c;还是没有能有一个很好的解决办法&#xff0c;这次就来看看在使用Maven管理依赖的过程中dependencies与dependencyManagement的区别。 DepencyManagement应用场…...

git svn 日记

1. git log -p -1 --name-only 该命令用于查看最新的一次提交记录的详细信息&#xff0c;包括文件更改情况。 git log&#xff1a;显示 Git 仓库的提交历史。-p&#xff1a;显示每次提交的差异 (diff)&#xff0c;也就是文件内容的修改部分。-1&#xff1a;表示只显示最近的一…...

FSMC

RAM ROM RAM和ROM相比&#xff0c;两者的最大区别是RAM在断电以后保存在上面的数据会自动消失&#xff0c;而ROM不会自动消失&#xff0c;可以长时间断电保存。 并且RAM的速度要远远高于ROM的速度。 SRAM SRAM 的存储单元以锁存器来存储数据&#xff0c;种电路结构不需要定时…...

NAT技术+代理服务器+内网穿透

NAT技术 IPv4协议中&#xff0c;会存在IP地址数量不充足的问题&#xff0c;所以不同的子网中会存在相同IP地址的主机。那么就可以理解为私有网络的IP地址并不是唯一对应的&#xff0c;而公网中的IP地址都是唯一的&#xff0c;所以NAT&#xff08;Network Address Translation&…...

【ABAP】ole2 excel多sheet导入导出

原理就不分享了 原来是用了动态表格&#xff0c;但是要导出不方便&#xff0c;所以就写死了&#xff0c;excel多sheet导入的类放在另一篇文章里 REPORT zcdemo17. INCLUDE ole2incl.DATA: excel TYPE ole2_object,workbooks TYPE ole2_object,workbook TYPE ole2_object…...

图像配准-小结

图像配准&#xff1a;找到一对图像间的几何变换关系&#xff0c;并且将待配准图像根据几何变换关系对齐到参考图像上&#xff0c;从而为图像融合、变化检测/监测提供基础。图像匹配&#xff0c;在某些语境中可能与上面的图像配准指的是一个东西&#xff0c;而在某些语境中可能指…...

【2025】基于Python的空气质量综合分析系统的设计与实现(源码+文档+调试+答疑)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

计算机基础知识-2

x86架构的寄存器 AT&T汇编和Intel汇编的区别 每取出完一条指令&#xff0c;PC会自动&#xff0b;"1"&#xff0c;指向下一条要被执行的指令。这里的1是指下一条指令&#xff0c;但是指令本身可能占用多个字节&#xff0c;所以地址可能不是以1叠加 当前执行的是10…...

Ubuntu2204配置连续失败后账户锁定

配置启用pam_faillock sudo nano /etc/pam.d/common-auth在最上面添加以下内容 auth required pam_faillock.so preauth silent audit auth sufficient pam_unix.so nullok try_first_pass auth [defaultdie] pam_faillock.so authfail auditsudo nano /etc/pam.d/…...

windows下安装elasticSearch和kibana

下载es 下载地址官网 下载后是个压缩包(elasticsearch-8.15.0-windows-x86_64)&#xff0c;解压即可 启动 配置 改一下 /conf/jvm.options文件&#xff0c;最后加一行编码配置&#xff0c;这个是为了启动后防止控制台乱码 -Dfile.encodingGBK启动es 依赖jdk8环境&#xf…...

Java-IDEA模拟一个Redis服务器,与Redis客户端进行一次简单的交互。默认端口号:6379

首先要了解Redis的交互协议。 摘抄&#xff1a; 简单字符串&#xff08;Simple Strings&#xff09;: 以 “” 开头&#xff0c;例如 “OK\r\n” 表示一个成功的响应。错误&#xff08;Errors&#xff09;: 以 “-” 开头&#xff0c;例如 “-ERR unknown command\r\n” 表示一…...

WEB服务与虚拟主机/IIS中间件部署

WWW&#xff08;庞大的信息系统&#xff09;是基于客户机/服务器⽅式的信息发现技术和超⽂本技术的综合。网页浏览器//网页服务器 WWW的构建基于三项核⼼技术&#xff1a; HTTP&#xff1a;超文本传输协议&#xff0c;⽤于在Web服务器和客户端之间传输数据。HTML&#xff1a;⽤…...

JAVA开源项目 图书个性化推荐系统 计算机毕业设计

本文项目编号 T 015 &#xff0c;文末自助获取源码 \color{red}{T015&#xff0c;文末自助获取源码} T015&#xff0c;文末自助获取源码 目录 一、系统介绍1.1 业务分析1.2 用例设计1.3 时序设计 二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究…...

Spring Boot 注解探秘:HTTP 请求的魅力之旅

在SpringBoot应用开发中&#xff0c;处理Http请求是一项基础且重要的任务。Spring Boot通过提供一系列丰富的注解极大地简化了这一过程&#xff0c;使得定义请求处理器和路由变得更加直观与便捷。这些注解不仅帮助开发者清晰地定义不同类型的HTTP请求如何被处理&#xff0c;同时…...

TYPE-C USB设计

目录 摘要 TYPE-C电路 握手过程 USB电路 摘要 TYPE-C,是USB的一种接口&#xff0c;USB的第一种接口为常见的USB接口&#xff0c;U盘即为这种接口&#xff1b;第二种接口的形状类似一个凸字&#xff0c;常应用在打印机中&#xff0c;第三种接口即为TYPE-C&#xff0c;支持正…...

Python炒股自动化,怎样理解股票交易性质

炒股自动化&#xff1a;申请官方API接口&#xff0c;散户也可以 python炒股自动化&#xff08;0&#xff09;&#xff0c;申请券商API接口 python炒股自动化&#xff08;1&#xff09;&#xff0c;量化交易接口区别 Python炒股自动化&#xff08;2&#xff09;&#xff1a;获取…...

Vue2 day-02

目录 一. Vue脚手架(Vue CLI) 1.1 安装新版本的Vue脚手架vue/cli 1.2 用命令创建Vue项目 1.2.1 命令创建vue项目 1.2.2 默认创建 1.2.3 自定义创建 1.2.4 基于ui界面创建Vue项目 1.3 分析Vue脚手架生成的项目结构及代码执行 1.3.1 默认创建文件结构 1.3.2 分开放置文…...

什么?!新版 Node.js V22.5 自带 SQLite 模块啦

前言 2024年7月&#xff0c;Node.js V22.5.0 版本发布&#xff0c;自带了 SQLite 模块&#xff0c;意味着开发者可以直接在程序中使用 SQLite 数据库&#xff0c;而无需引入第三方库&#x1f44d;。 话不多说&#xff0c;感觉来体验一波✈。 安装/升级 我现在用的是21.4.0版…...

Maven持续集成(Continuous integration,简称CI)版本友好管理

从Maven 3.5.0-beta-1 版本开始可以在pom文件中使用 r e v i s i o n 、 {revision}、 revision、{sha1}、${changelist}做为版本的占位符。 一、单module简单使用${revision}的场景 <project><modelVersion>4.0.0</modelVersion><parent><groupId…...

EvoSuite使用总结

1.安装EvoSuite插件 以IDEA为例&#xff0c;在Plugins栏搜索EvoSuite后点击install&#xff0c;安装完成后重启IDEA 2.使用EvoSuite 选中文件右键选择Run EvoSuite 生成成功可以看到如下提示&#xff1a; 注意事项&#xff1a; 生成路径&#xff1a;src/test/java 使用juni…...

Cortex-A7:简单中断处理(不可嵌套中断)机制

0 参考资料 ARM Cortex-A(armV7)编程手册V4.0.pdf ARM体系结构与编程第2版1 前言 Cortex-M系列内核MCU中断硬件原生支持嵌套中断&#xff0c;开发者不需要为了实现嵌套中断而进行额外的工作。但在Cortex-A7中&#xff0c;硬件原生是不支持嵌套中断的&#xff0c;这从Cortex-A…...

k8s HPA

水平自动扩容和缩容HPA HPA全称Horizontal Pod Autoscaler&#xff0c;即pod水平自动伸缩。HPA可以基于CPU利用率对replication controller、deployment和replicaset中的pod数量进行自动扩缩容&#xff08;除了CPU利用率&#xff0c;也可以基于其他应用程序提供的度量指标cust…...

5G移动网络运维实验(训)室解决方案

随着第五代移动通信技术&#xff08;5G&#xff09;的快速普及和工业互联网的迅猛发展&#xff0c;全球制造业正面临着前所未有的深刻变革。5G技术凭借其超高的传输速率、极低的延迟以及大规模的连接能力&#xff0c;为工业自动化、智能制造等领域带来了革命性的技术支持。为了…...

单片机学习笔记

一、单片机帝国的诞生与发展 1.1 单片机的基本概念 单片机是一种集成电路芯片&#xff0c;采用超大规模的集成电路把具有数据处理功能的中央处理器存储器、输入输出端口、外围电路和相关外设集成在一块硅片上构成一个小而完整的微型计算机系统。 一般而言&#xff0c;单片机也…...

SpringBoot中@Value获取值和@ConfigurationProperties获取值用法及比较

SpringBoot中Value获取值和ConfigurationProperties获取值用法及比较 更新时间&#xff1a;2024年08月08日 09:41:48 作者&#xff1a;岳轩子 在Spring Boot中,Value注解是一个非常有用的特性,它允许我们将外部的配置注入到我们的Bean中,ConfigurationProperties用于将配置文件…...

执行任务赚积分

题目描述 现有N个任务需要处理&#xff0c;同一时间只能处理一个任务&#xff0c;处理每个任务所需要的时间固定为1。 每个任务都有最晚处理时间限制和积分值&#xff0c;在最晚处理时间点之前处理完成任务才可获得对应的积分奖励。 可用于处理任务的时间有限&#xff0c;请问…...

使用TLS解决Docker API暴露2375端口的问题

问题起因 由于本人开发环境是在 Windows&#xff0c;开发完成后需要使用 Dockerfile 打包镜像&#xff0c;这个过程需要有一个 Docker 服务完成&#xff0c;Windows 安装 Docker 会影响到很多环境&#xff0c;我又不想本地开虚拟机使用 Docker&#xff0c;于是我就索性使用服务…...