点赞功能真的有必要上 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,一经查实,立即删除!

相关文章

【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 抽象类的概念 什么是抽象类?在面向对象中,如果一…

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;本文中结合企业真…

开源向量数据库介绍

在开源矢量数据库的世界里&#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…

力扣同类题:重排链表

很明显做过一次 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值,一列时间。代码可以扩展到遥感时间序列突变检测(突变年份、突变幅度等)中。 结果如下图所示, 文…

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

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

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

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

关于安卓ZXing条码识别(一)引入源码

背景 从0-1引入安卓zxing&#xff0c;实现条码识别 环境 win10 as4 jdk8 引入 首先&#xff0c;官方网站&#xff0c;就是源码。链接 选择你要引入的分支&#xff0c;这里博主选择的是最近更新的分支&#xff0c;如下图&#xff1a; 上图中&#xff0c;1和2都需要引入&am…

鸿蒙开发学习:【ets_frontend组件】

简介 ets_frontend组件是方舟运行时子系统的前端工具&#xff0c;结合ace-ets2bundle组件&#xff0c;支持将ets文件转换为方舟字节码文件。 ets_frontend组件架构图 目录 /arkcompiler/ets_frontend/ ├── test262 # test262测试配置和运行脚本 ├── testTs…

《TCP/IP详解 卷一》第13章 TCP连接管理

目录 13.1 引言 13.2 TCP连接的建立与终止 13.2.1 TCP半关闭 13.2.2 同时打开与关闭 13.2.3 初始序列号 13.2.4 例子 13.2.5 连接建立超时 13.2.6 连接与转换器 13.3 TCP 选项 13.3.1 最大段大小选项 13.3.2 选择确认选项 13.3.3 窗口缩放选项 13.3.4 时间戳选项与…

AWS 入门实践-远程访问AWS EC2 Linux虚拟机

远程访问AWS EC2 Linux虚拟机是AWS云计算服务中的一个基本且重要的技能。本指南旨在为初学者提供一系列步骤&#xff0c;以便成功地设置并远程访问他们的EC2 Linux实例。包括如何上传下载文件、如何ssh远程登录EC2虚拟机。 一、创建一个AWS EC2 Linux 虚拟机 创建一个Amazon…

LeetCode_25_困难_K个一组翻转链表

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 模拟 1. 题目 给你链表的头节点 h e a d head head &#xff0c;每 k k k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k k k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节…

全面对比Amazon DocumentDB 与 MongoDB

在云中部署 MongoDB 似乎有多种选择。例如&#xff0c;Amazon DocumentDB自称是完全支持 MongoDB API 的 AWS 原生数据库。虽然它支持一些 MongoDB 功能&#xff0c;但需要注意的是 DocumentDB 并不完全兼容 MongoDB。要在 AWS 上访问功能齐全的“MongoDB 即服务”&#xff0c;…

单链表(下)

我们在单链表&#xff08;上&#xff09;中了解了一些需要实现的函数&#xff0c;这一篇就让我们一起来实现。 1.创建新节点 2.打印 3.尾插 4.头插 5.尾删 6.头删 7.查找 8.计算节点个数 9.在指定位置之前插入数据 10.在指定位置之前插入数据 11.删除指定位置的节点 12.删除指…