点赞功能真的有必要上 Redis 吗?(Mongo、MySQL、Redis、MQ 实测性能对比)

目录

一、你会怎么设计一个点赞功能?

1.1、点赞实现思路

1.2、点赞功能设计

1.2.1、MySQL 单表

1.2.2、单表 + MySQL 关联表

1.2.3、MySQL 关联表 + mq

1.2.4、redis + mq

1.2.5、mongodb 关联文档

二、性能测试

2.1、前置说明 

2.2、10 万数据准备


一、你会怎么设计一个点赞功能?


1.1、点赞实现思路

我们先来想一想一个基本的点赞功能都需要哪些服务(这里以小红书系统为例):

读操作:当用户刷到一个专辑的时候,需要做以下几个操作

  1. 去查询当前用户是否有点赞.
  2. 查询当前点赞的数量.

写操作:当用户点击点赞按钮时候,需要进行以下几个操作

  1. 查询当前用户是否已经点赞.
  2. 如果点赞,就删除点赞信息,如果没有点赞,就添加点赞信息.
  3. 更新点赞数量(根据不同设计,可能会有这一步).
  4. 发布点赞消息.

Ps:之后的代码由于篇幅原因,没有任何封装

1.2、点赞功能设计

1.2.1、MySQL 单表

只设计一张 点赞信息表 来实现点赞功能.  

表中记录了哪个用户对哪篇专辑进行点赞.

读操作:

    public AlbumStatVO MySQLOne(@RequestParam @NotBlank String albumId) {//为了简单,只设计了点赞表,因此这里通过四次查询点赞表来模拟Long pageView = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getTargetId, albumId).count();Long likeCnt = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getTargetId, albumId).count();Long collectCnt = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getTargetId, albumId).count();Long commentCnt = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getTargetId, albumId).count();if(pageView == null || likeCnt == null || commentCnt == null || collectCnt == null) {return null;}return AlbumStatVO.builder().albumId(Long.valueOf(albumId)).pageView(pageView).likeCnt(likeCnt).collectCnt(collectCnt).commentCnt(commentCnt).build();}

写操作:

    public String MySQLOne(@RequestBody @Valid ActDTO dto) {synchronized (locker1) {boolean exists = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getPostId, dto.getPostId()).eq(AlbumLike::getTargetId, dto.getTargetId()).exists();if(!exists) {Db.save(AlbumLike.builder().postId(Long.valueOf(dto.getPostId())).targetId(Long.valueOf(dto.getTargetId())).ctTime(new Date()).utTime(new Date()).build());} else {Db.lambdaUpdate(AlbumLike.class).eq(AlbumLike::getPostId, dto.getPostId()).eq(AlbumLike::getTargetId, dto.getTargetId()).remove();}}return "ok";}

好处:

实现起来简单:一张表不仅记录了当前专辑有谁点赞,还可以通过 count(*) 查询出当前专辑的点赞量.

缺点:

速度低:如果这样设计点赞表,那么你肯定也会设计出差不多的 收藏表、评论表...(如果需要,可能还有记录访问量的表).  那么当用户看到这个专辑的时候,光是查看点赞量、访问量... 都需要至少 3 次 count(*) 操作(频繁的和数据库建立连接和断开连接都有一定的开销).  

1.2.2、单表 + MySQL 关联表

为了解决刚刚提到的问题,可以再设计一个关联表——专辑信息统计表

这个表中就记录了访问量、点赞量、收藏量... 这些信息.  同时使用 专辑id 保证和 专辑表的关联关系.

读操作:

    public AlbumStatVO MySQLTwo(@RequestParam @NotBlank String albumId) {AlbumStat stat = Db.lambdaQuery(AlbumStat.class).eq(AlbumStat::getAlbumId, albumId).one();if(stat == null) {return null;}return AlbumStatVO.builder().albumId(Long.valueOf(albumId)).pageView(stat.getPageView()).likeCnt(stat.getLikeCnt()).collectCnt(stat.getCollectCnt()).commentCnt(stat.getCommentCnt()).build();}

写操作:

    public String MySQLTwo(@RequestBody @Valid ActDTO dto) {synchronized (locker2) {boolean exists = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getPostId, dto.getPostId()).eq(AlbumLike::getTargetId, dto.getTargetId()).exists();if(!exists) {Db.save(AlbumLike.builder().postId(Long.valueOf(dto.getPostId())).targetId(Long.valueOf(dto.getTargetId())).ctTime(new Date()).utTime(new Date()).build());Db.lambdaUpdate(AlbumStat.class).setSql("like_cnt = like_cnt + 1").eq(AlbumStat::getAlbumId, dto.getTargetId()).update();} else {Db.lambdaUpdate(AlbumLike.class).eq(AlbumLike::getPostId, dto.getPostId()).eq(AlbumLike::getTargetId, dto.getTargetId()).remove();Db.lambdaUpdate(AlbumStat.class).setSql("like_cnt = like_cnt - 1").eq(AlbumStat::getAlbumId, dto.getTargetId()).update();}}return "ok";}

 好处:

读操作方便,效率相对较高:解决了频繁和数据库建立连接和断开连接的问题.  查询点赞量、收藏量...这些信息,只需要通过 专辑id 对 专辑信息统计表 进行一次查询就可以得到所有的统计信息.

缺点:

写操作麻烦,效率相对较低:每次保存了用户点赞信息,还需要更新 专辑信息统计表 中的点赞量信息.

在一致性的问题上,引入了额外开销和系统复杂度.

1.2.3、MySQL 关联表 + mq

那么此时遇到的问题就是,点赞效率低,并且 点赞的统计 和 点赞消息添加 耦合在了一起.

那么此时使用 mq 就能很好的解决上述问题,因为点赞量这种数据并不需要很强的时效性,只需要保证最终一致性即可.

读操作和 MySQL 关联表没有区别.

写操作:

    public String MqOne(@RequestBody @Valid ActDTO dto) {long postId = Long.parseLong(dto.getPostId());long targetId = Long.parseLong(dto.getTargetId());synchronized (locker3) {boolean exists = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getPostId, postId).eq(AlbumLike::getTargetId, targetId).exists();if(!exists) {Db.save(AlbumLike.builder().postId(postId).targetId(targetId).ctTime(new Date()).utTime(new Date()).build());} else {Db.lambdaUpdate(AlbumLike.class).eq(AlbumLike::getPostId, postId).eq(AlbumLike::getTargetId, targetId).remove();}rabbitTemplate.convertAndSend(MqConst.STAT_DIRECT,MqConst.LIKE_MySQL_QUEUE,objectMapper.writeValueAsString(LikeMsg.builder().targetId(targetId).isLike(!exists).build()));}return "ok";}

好处:

  • 效率相对较高,达到了削峰填谷的作用,避免大量请求同一时间打入数据库导致崩溃
  • 实现了点赞量统计和点赞消息的解耦合.

缺点:

需要引入额外的表来统计数据.

单表的读写性能在大量数据的情况下,还是会达到瓶颈.

为了保证一致性,增加系统复杂度.

1.2.4、redis + mq

Ps:此处先不考虑 雪崩、击穿、穿透 问题.

这里我只考虑使用 redis 作为缓存,不考虑作为内存数据库的情况.

原因:1. 贵  2. Redis 实例突然崩溃或遭遇其他故障,RDB和AOF机制可能无法完全保证数据的完整性,这里为了保证数据的强一致性,还需要 mysql.

mq 用来解决数据同步问题.

实现思路:

a)redis 缓存更新策略:在 redis 的配置文件中设置 maxmemory (内存使用上限). 读的时候先从 redis 中读数据,如果没有读到数据,就从 mysql 中读取数据,然后再将数据通过 mq 同步到 redis 上. 经过一段时间的 “动态平衡”, redis 中的剩余数据就是 “热点数据” 了.

redis 实现点赞功能有很多种方式,这里我讲保证强一致性的方案.

b)具体实现:点赞功能使用 set 类型是非常合适的,这样不仅表示了当前专辑有哪些用户进行点赞,还可以通过 set 的 scard 获取点赞量.

读操作:

  1. 先查 redis 上有没有点赞信息.
  2. redis 中存在:直接返回
  3. redis 中不存在:去 MySQL 查数据,同步到 redis 上.
    public AlbumStatVO redisOne(@RequestParam @NotBlank String albumId) {//1.redis 是否有List<Object> statList = redisTemplate.executePipelined(new SessionCallback<String>() {@Overridepublic <K, V> String execute(RedisOperations<K, V> operations) throws DataAccessException {RedisOperations<String, Object> template = (RedisOperations<String, Object>) operations;template.opsForValue().get(RedisKeyConst.ALBUM_PAGE_VIEW + albumId);template.opsForSet().size(RedisKeyConst.ALBUM_LIKE + albumId);template.opsForValue().get(RedisKeyConst.ALBUM_COLLECT + albumId);template.opsForValue().get(RedisKeyConst.ALBUM_COMMENT + albumId);return null;}});if(statList.get(0) != null && statList.get(1) != null&& statList.get(2) != null && statList.get(3) != null) {return AlbumStatVO.builder().albumId(Long.valueOf(albumId)).pageView((Long) statList.get(0)).likeCnt((Long) statList.get(1)).collectCnt((Long) statList.get(2)).commentCnt((Long) statList.get(3)).build();}//2.redis上没有,去查数据库AlbumStat dbStat = Db.lambdaQuery(AlbumStat.class).eq(AlbumStat::getAlbumId, albumId).one();if(dbStat == null) {return null;}List<Long> userIds = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getTargetId, albumId).list().stream().map(AlbumLike::getPostId).toList();//3.将数据保存到 redis 上redisTemplate.executePipelined(new SessionCallback<Object>() {@Overridepublic <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {RedisOperations<String, Object> temp = (RedisOperations<String, Object>) operations;temp.opsForValue().set(RedisKeyConst.ALBUM_PAGE_VIEW + albumId, dbStat.getPageView());temp.opsForSet().add(RedisKeyConst.ALBUM_LIKE + albumId, userIds.toArray()); //第二个参数是数组temp.opsForValue().set(RedisKeyConst.ALBUM_COLLECT + albumId, dbStat.getCollectCnt());temp.opsForValue().set(RedisKeyConst.ALBUM_COMMENT + albumId, dbStat.getCommentCnt());return null;}});return AlbumStatVO.builder().albumId(Long.valueOf(albumId)).pageView(dbStat.getPageView()).likeCnt(dbStat.getLikeCnt()).commentCnt(dbStat.getCommentCnt()).collectCnt(dbStat.getCollectCnt()).build();}

写操作:

  1. 首先去判断 redis 上判断是否有当前用户的点赞信息
  2. redis 中存在:删除 redis 上的点赞信息. 
  3. redis 中不存在:又有两种情况
    1. 当前用户确实没有对此专辑进行过点赞.
    2. 点赞数据过期了.
  4. 无论是以上哪种情况,我为了保证强一致性,都会去 mysql 中查一下,点赞数据是否存在.(如果你不想保证强一致性,就是愿意只通过 redis 来判断,就是不怕某些特殊情况下 redis 突然崩溃,持久化文件损坏,你确实可以写个定期更新任务,定期同步 redis)
  5. mysql 中存在:redis 上啥都不用做,反正查到点赞数据存在,也是删除.
  6. mysql 中不存在:在 redis 上添加点赞数据.
  7. 根据上述所有情况,通过 mq 更新 mysql 中的 点赞量 和 点赞信息.
    public String redisOne(@RequestBody @Valid ActDTO dto) {synchronized (locker4) {boolean isExists = Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),dto.getPostId()));Boolean isLike = null;if(isExists) {redisTemplate.opsForSet().remove(RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),dto.getPostId());isLike = false;} else {isExists = Db.lambdaQuery(AlbumLike.class).eq(AlbumLike::getPostId, dto.getPostId()).eq(AlbumLike::getTargetId, dto.getTargetId()).exists();if(!isExists) {redisTemplate.opsForSet().add(RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),dto.getPostId());isLike = true;} else {isLike = false;}}rabbitTemplate.convertAndSend(MqConst.STAT_DIRECT,MqConst.SYNC_LIKE_MYSQL_QUEUE,objectMapper.writeValueAsString(LikeMsg.builder().isLike(isLike).postId(Long.valueOf(dto.getPostId())).targetId(Long.valueOf(dto.getTargetId())).build()));}return "ok";}

好处:

redis 是在内存中操作数据,速度大大提升.

坏处:

  1. 使用 redis 真的一定快么?某些情况不一定快,因为 redis 作为客户端服务器系统,也会存在一定的网络开销.
  2. 贵!内存资源是十分珍贵的,不适合用来存储大量数据.
  3. 大大增加了系统复杂度,需要考虑数据一致性,时效性问题.

1.2.5、mongodb 关联文档

MongoDB 使用内存映射技术,将数据暂时存储在内存中,而不是直接持久化到存储设备中。由于内存读取速度远快于磁盘,因此 MongoDB 在 IO 操作上相比于 MySQL 更加高效.

MongoDB 相比于 MySQL 主要的缺点就是事务支持相对较弱,但是对于点赞这种数据,也不需要太强的事务支持,因此使用 MongoDB 来存储点赞数据是非常合适的.

读操作:

    public AlbumStatVO mongoOne(@RequestParam String albumId) {AlbumStatGO statGO = mongoTemplate.findOne(Query.query(Criteria.where("_id").is(Long.valueOf(albumId))), AlbumStatGO.class);if(statGO == null) {return null;}return AlbumStatVO.builder().albumId(Long.valueOf(albumId)).pageView(statGO.getPageView()).likeCnt(statGO.getLikeCnt()).commentCnt(statGO.getCommentCnt()).collectCnt(statGO.getCollectCnt()).build();}

写操作:

    public String mongoOne(@RequestBody @Valid ActDTO dto) {long postId = Long.parseLong(dto.getPostId());long targetId  = Long.parseLong(dto.getTargetId());synchronized (locker5) {boolean exists = mongoTemplate.exists(Query.query(Criteria.where("post_id").is(postId).and("target_id").is(targetId)),AlbumLikeGO.class);if(!exists) {mongoTemplate.insert(AlbumLikeGO.builder().postId(postId).targetId(targetId).ctTime(new Date()).utTime(new Date()).build());mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(targetId)),new Update().inc("like_cnt", -1),AlbumStatGO.class);} else {mongoTemplate.remove(Query.query(Criteria.where("post_id").is(postId).and("target_id").is(targetId)),AlbumLikeGO.class);mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(targetId)),new Update().inc("like_cnt", -1),AlbumStatGO.class);}}return "ok";}

二、性能测试


2.1、前置说明 

a)业务说明

由于我们一般不会只查询点赞数量,而是后端将点赞量、评论量、收藏量一并返回,因此这里在读操作上为了实际场景,我们会将这些数据一并返回.

写操作上,只关注点赞功能即可.

b)服务器配置

4C 6G

部署了 redis、mongo、mysql、rabbitmq......

当前项目也已部署.

c)测试环境

并发用户量:100

持续时间:2min

爬坡:1min

2.2、10 万数据准备

为了减少网络通讯次数,这里使用批处理,每次添加 1000 条数据.

a)mysql 数据准备

@SpringBootTest
@Slf4j
class MySQLDataTests {private static final int albumNum = 1000;private static final int userNum = 100;private static int count = 0;public void addLike() {for(int userId = 1; userId <= userNum; userId++) {//批量添加List<AlbumLike> list = new ArrayList<>();for (int albumId = 1; albumId <= albumNum; albumId++) {list.add(AlbumLike.builder().postId((long) userId).targetId((long) albumId).ctTime(new Date()).utTime(new Date()).build());log.info("插入 {} 条", ++count);}Db.saveBatch(list);}}public void addStat() {for(int albumId = 1; albumId <= albumNum; albumId++) {Db.save(AlbumStat.builder().albumId((long) albumId).likeCnt((long) userNum).build());log.info("当前执行 {} 条", albumId);}}@Testpublic void run() throws InterruptedException {addLike();addStat();}}

执行时间是 1min 12s

b)mongo 数据准备

@Slf4j
@SpringBootTest
public class MongoDataTests {@Autowiredprivate MongoTemplate mongoTemplate;private static final int albumNum = 1000;private static final int userNum = 100;private static int count = 0;private void addLikeData() {//分批次插入for(int userId = 1; userId <= userNum; userId++) {List<AlbumLikeGO> list = new ArrayList<>();for(int albumId = 1; albumId <= albumNum; albumId++) {list.add(AlbumLikeGO.builder().postId((long) userId).targetId((long) albumId).ctTime(new Date()).utTime(new Date()).build());log.info("插入 {} 条", ++count);}mongoTemplate.insertAll(list);}}private void addStat() {for(int albumId = 1; albumId <= albumNum; albumId++) {mongoTemplate.insert(AlbumStatGO.builder().albumId((long) albumId).pageView(0L).likeCnt((long) userNum).collectCnt(0L).commentCnt(0L).build());}}@Testpublic void run() throws InterruptedException {addLikeData();addStat();}}

执行时间大约是 3s (和 mysql 恐怖的差距)

2.3、实际结果

a)读操作

 

b)写操作

c)结论:除了单表操作比较耗时外,对于中小型项目而言,频繁的读写操作场景,使用 mongo 就够用了.  甚至都不用上 mq,更甚至有的场景下 redis 性能还不如 mongo.

对于中小型项目,能有那些项目达到每秒钟同时有 100 个用户连续请求 1 ~ 3 次接口,持续 2min 不停???

服务器能崩???平均 11ms 的响应时间你等不了???

怕是你的服务器不行吧~  

为什么我这么说,我这还有一个 2C 2G 的服务器(应用部署,环境在 4C 6G 的服务器上),来给你看看效果

同样是 100用户并发,持续 2min,爬坡 1min

a)读操作

 

b)写操作

很多请求甚至还没有来得及处理,就崩了~

Ps:本文禁止转载!!!不然 si 妈!!!

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

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

相关文章

kubectl基础命令详解

管理名称空间资源 查看名称空间 [rootceshi-130 conf]# kubectl get ns [rootceshi-130 conf]# kubectl get namespace NAME STATUS AGE default Active 7d17h kube-node-lease Active 7d17h kube-public Active 7d17h kube-system …

【C++】三大特性之继承

1 继承的概念及定义 1.1 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展、增加功能&#xff0c;这样产生新的类&#xff0c;称派生类&#xff08;或子类&#xff09;。而被继承的…

Java宝典-抽象类和接口

目录 1. 抽象类1.1 抽象类的概念1.2 抽象类的语法1.3 抽象类的特点 2. 接口2.1 接口的概念2.2 接口的语法2.3 接口的特点2.4 实现多个接口2.5 接口的继承 3. 接口使用案例 铁汁们好,今天我们学习抽象类和接口~ 1. 抽象类 1.1 抽象类的概念 什么是抽象类?在面向对象中,如果一…

FIT介绍-1

1、编译 FIT image 文件的编译过程很简单&#xff0c;根据实际情况&#xff0c;编写image source file之后&#xff08;假设名称为u-boot.its&#xff09;&#xff0c;在命令行使用mkimage工具编译即可&#xff1a; ./tools/mkimage [-D dtc_options] [-f fit-image.its|-f a…

OpenFeign的学习总结

1.Fegin调用只有在微服务之间才会使用&#xff0c;以前的单体项目用不到&#xff0c;因为单体项目不会涉及到分库的概念&#xff0c;并且单体项目就一个应用&#xff0c;想用的功能都可以直接方法调用&#xff0c;但是微服务就不行&#xff0c;跨服务的调用只能通过http的操作&…

Google浏览器122.下载驱动

打开网址&#xff1a;Chrome for Testing availability (googlechromelabs.github.io) 选择stable 选择对应的驱动就行

flink重温笔记(十四): flink 高级特性和新特性(3)——数据类型及 Avro 序列化

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 14 天啦&#xff01;学习了 flink 高级特性和新特性之数据类型及 avro 序列化&#xff0c;主要是解决大数据领域数据规范化写入和规范化读取的问题&#xff0c;avro 数据结构可以节约存储空间&#xff0c;本文中结合企业真…

JVM运行时数据区概述以及分别存放的内容

JVM的运行时数据区是JVM在执行Java程序时用于存储数据和状态信息的内存区域。它分为多个部分&#xff0c;每个部分都有其特定的作用和存放的内容。 1. 方法区&#xff08;Method Area&#xff09; 作用&#xff1a;方法区是所有线程共享的内存区域&#xff0c;用于存放已被虚…

Java面试题总结9:mybatis

mybatis的优缺点 优点&#xff1a; 基于SQL语句编程&#xff0c;不会对应用程序或者数据库的现有设计造成任何影响&#xff0c;SQL写在XML里&#xff0c;解除sql与程序代码的耦合&#xff0c;便于统一管理&#xff0c;提供XML标签&#xff0c;支持编写动态SQL语句&#xff0c…

开源向量数据库介绍

在开源矢量数据库的世界里&#xff0c;有些名字因其性能、灵活性和健壮性而脱颖而出。 1. Milvus Milvus 由 Zilliz 推出&#xff0c;是一款高度可定制的开源矢量数据库&#xff0c;在处理大规模数据方面大放异彩。由于其出色的可扩展性&#xff0c;当你需要处理大量数据时&a…

判断链表回文

题目&#xff1a; //方法一&#xff0c;空间复杂度O(n) class Solution { public:bool isPalindrome(ListNode* head) {vector<int> nums; //放进数组后用双指针判断ListNode* cur head;while(cur){nums.emplace_back(cur->val);cur cur->next;}for(int i0…

如何在非spring环境中调用service中的方法

什么是非spring环境 非Spring环境指的是不使用Spring框架来管理和配置应用程序的运行时环境。在Spring框架中&#xff0c;开发者可以利用依赖注入、面向切面编程&#xff08;AOP&#xff09;、事务管理、安全性、数据访问等特性来简化企业级应用程序的开发。当应用程序不依赖于…

力扣同类题:重排链表

很明显做过一次 class Solution { public:void reorderList(ListNode* head) {if(!head||!head->next)return;ListNode *fasthead,*lowhead;ListNode *prenullptr,*curnullptr,*nextnullptr;while(fast->next!nullptr){fastfast->next;if(fast->next)fastfast->…

深入理解操作系统Operator System(2)

目录 操作系统对上的管理 系统调用接口 用户操作接口&#xff08;库函数&#xff09; 系统调用和库函数的概念 结构层次示意图 总结 为什么要有操作系统❓ 上次主要介绍了操作系统的"管理"和操作系统对下的管理。本篇主要是对上的管理。 操作系统对上的管理 …

04-ESP32S3-GPIO

ESP32S3-IDF GPIO GPIO简介 ESP32S3提供了多达45个物理GPIO管脚&#xff0c;这些管脚不仅可以作为通用的输入输出接口&#xff0c;还可以连接到内部外设信号。通过GPIO交换矩阵、IO MUX和RTC IO MUX&#xff0c;可以灵活地配置外设模块的输入信号来源于任何GPIO管脚&#xff0…

k8s存储

目录 前瞻 emptyDir存储卷 hostPath存储卷 nfs共享存储卷 PVC 和 PV NFS使用PV和PVC 配置nfs存储 定义PV 定义PVC 测试访问 搭建 StorageClass nfs-client-provisioner &#xff0c;实现 NFS 的动态 PV 创建 在192.168.75.40节点上安装nfs&#xff0c;并配置nfs服务 …

python:标准正态同质性检验(Standard Normal Homogeneity Test,SNHT) 突变点检测(以NDVI时间序列为例)

作者:CSDN @ _养乐多_ 本文将介绍标准正态同质性检验(Standard Normal Homogeneity Test,SNHT) 突变点检测代码。以 NDVI 时间序列为例。输入数据可以是csv,一列NDVI值,一列时间。代码可以扩展到遥感时间序列突变检测(突变年份、突变幅度等)中。 结果如下图所示, 文…

k8s中的service组件

背景 service组件是k8s中类似于nginx网关的入口&#xff0c;通过它可以访问到背后的pod提供的服务&#xff0c;并且自带负载均衡的功能&#xff0c;本文就简单看下service组件 k8s中的service组件 假设我们要配置一个tomcat的service集群&#xff0c;配置步骤如下 1.首先我们…

【UE5】映射方式:动作映射(Action Mappings) 与 轴映射(Axis Mappings)

在游戏中有很多功能需要键盘按键触发&#xff0c;接下来我们会制作键盘输入设置 项目资源文末百度网盘自取 打开项目设置面板 选择Input 按键的映射方式有两种&#xff1a; 一种是动作映射 / 操作映射(Action Mappings)&#xff0c;这种方式可以响应按键的按下和抬起。当键盘…

AI安全白皮书 | “深度伪造”产业链调查以及四类防御措施

以下内容&#xff0c;摘编自顶象防御云业务安全情报中心正在制作的《“深度伪造”视频识别与防御白皮书》&#xff0c;对“深度伪造”感兴趣的网友&#xff0c;可在文章留言中写下邮箱&#xff0c;在该白皮书完成后&#xff0c;会为您免费寄送一份电子版。 “深度伪造”就是创建…