ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ChatRoom 项目复盘

ChatRoom 项目复盘 文章目录ChatRoom 项目复盘1. TCP 和 UDPTCP传输控制协议面向连接UDP用户数据报协议无连接TCP vs UDP2. 锁1互斥锁 mutex2读写锁 rwlock3自旋锁 spinlock死锁产生死锁的 4 种条件四个条件**同时满足**才会发生死锁3. 智能指针**unique_ptr****shared_ptr****weak_ptr**4. RAII 资源获取即初始化守护5. Redis存信息常用数据结构业务场景持久化6. 百万数据写MySQL6.1 批量插入1多条合并 INSERT批量 values2LOAD DATA INFILE最快导入外部文件6.2 事务优化6.3 索引影响6.4 分表分库6.5 线上实时海量写入中间件缓冲MySQL vs Redis7. 别的IO8. Reactor模式为什么需要 epoll没有 epoll 会发生什么为什么需要 epoll如果没有 epoll9. HTTPSHTTPTLS/SSL**TLS工作流程****中间人攻击****HTTP1.1 / HTTP2 / HTTP3**10. protocol主流自定义协议格式**长度头 消息体**11. sendfile 零拷贝Linux系统调用其他零拷贝技术ChatRoom 项目复盘1. TCP 和 UDPTCP传输控制协议面向连接面向连接通信前必须三次握手建立连接通信结束四次挥手断开连接。可靠传输序号、确认应答、超时重传、流量控制、拥塞控制保证数据完整、有序、不丢失到达对方。字节流没有报文边界数据是一串字节流会出现粘包问题。缺点开销大握手、重传、控制逻辑速度相对慢。适用场景文件传输、HTTP/HTTPS、聊天、网页浏览要求数据不能错不能丢。三次握手客户端发SYN服务端回SYNACK客户端发ACK连接建立。目的确认双方收发能力正常协商初始序列号。四次挥手客户端FIN服务端ACK服务端FIN客户端ACK。TIME‑WAIT状态客户端等待2MSLS确保对方收到ACK处理残留报文。为什么握手是三次挥手是四次握手时服务端可以把SYN和ACK合并成一个报文所以握手 3 次。挥手时收到客户端 FIN 之后服务端不能立刻发 FIN。服务端可能还有残余数据需要传输ACK 和 FIN 不能合并必须分成两个包因此必须四次。流量控制滑动窗口接收方告诉发送方自己还能收多少数据防止发送太快把接收缓冲区打满。拥塞控制慢启动、拥塞避免、快重传、快恢复防止把网络压垮。UDP用户数据报协议无连接无连接不用建立连接直接发包。不可靠没有重传确认数据包可能丢、乱序上层应用自己做可靠性。数据报保留报文边界一个UDP包就是一条完整消息没有粘包。优点头部小开销极低速度快。适用场景直播、语音通话、游戏、DNS允许少量丢包追求低延迟。TCP vs UDP特性TCPUDP连接面向连接无连接可靠性可靠不可靠控制开销大极小报文形式字节流数据报粘包存在不存在粘包TCP连续发送多段数据接收方缓冲区把数据合并分不清原始消息边界。解决方案定长、分隔符、消息头携带长度。2. 锁多线程同时访问共享资源会产生竞态条件数据错乱锁用来保证同一时间只有一个线程操作临界资源。1互斥锁 mutex作用排他访问加锁之后其他线程阻塞等待锁释放。2读写锁 rwlock读共享写独占。多个线程可以同时读写的时候所有读、写全部阻塞。适合读多写少场景。3自旋锁 spinlock不会休眠循环空转不停查询锁是否可用。优点没有线程切换开销缺点占用CPU适合锁持有时间很短的场景。死锁定义多个线程互相占有对方需要的资源又不肯释放自己手里资源全部互相等待全部卡住谁都无法继续执行。产生死锁的 4 种条件四个条件同时满足才会发生死锁互斥条件资源是互斥锁同一时刻只能一个线程占用别的线程要使用必须等待。mutex 互斥锁就满足这个条件。占有且等待线程已经拿到一部分锁 / 资源不释放已经占有的资源再去申请别的被其他线程占有的资源。线程 A 拿到锁 1不去释放还想去拿锁 2。不可剥夺条件已经被线程占有的资源不能被其他线程强行抢走只能持有资源的线程自己主动释放。锁不会被系统强制抢过来只能持有者 unlock。循环等待条件线程之间形成环形等待链线程 A 等 B 占有的资源线程 B 等 C 占有的资源线程 C 又等 A 占有的资源闭环。3. 智能指针C裸指针容易内存泄漏、野指针、重复释放智能指针用RAII自动管理堆内存生命周期unique_ptr独占所有权同一个对象只能有一个unique_ptr指向它禁止拷贝只可以移动std::move。离开作用域自动delete内存。std::unique_ptrint p std::make_uniqueint(10);适合对象明确只归一个管理者开销几乎为0。shared_ptr共享所有权引用计数。多个shared_ptr指向同一块堆内存每拷贝一次引用计数1每析构计数‑1计数变为0释放内存。std::shared_ptrint p1 std::make_sharedint(20); std::shared_ptrint p2 p1; // 引用计数1循环引用问题A持有shared_ptrB持有shared_ptr引用计数永远不为0内存泄漏。→ 用weak_ptr解决。weak_ptr弱引用不增加shared_ptr的引用计数。专门解决循环引用。不能直接解引用需要.lock()得到临时shared_ptr。4. RAII 资源获取即初始化守护核心思想资源的获取放在构造函数资源释放放在析构函数对象生命周期绑定资源。对象出作用域自动调用析构自动释放资源。资源不限于堆内存锁、文件句柄、socket、数据库连接。例子智能指针就是RAII构造接管堆内存析构释放内存。手写简易RAII锁class MyLockGuard{ mutex m; public: MyLockGuard(mutex mm):m(mm){m.lock();} ~MyLockGuard(){m.unlock();} // 禁止拷贝 MyLockGuard(const MyLockGuard)delete; };优点不会漏释放资源异常安全不用手动写释放语句。RAII本质利用C栈对象自动析构机制管理资源。5. Redis存信息Redis是内存型NoSQL数据库key‑value数据放在内存速度极高支持持久化RDB/AOF。常用数据结构String字符串缓存用户信息、计数器key‑value。Hash哈希存储对象适合存用户信息hset user:100 name zhang age 20。List列表双向链表消息队列。Set集合去重交集并集。ZSet有序集合排行榜带score权重。业务场景缓存热点数据减轻MySQL压力设置expire过期时间。会话信息存储登录token、session用户登录信息放Redis。计数器、限流、分布式锁、排行榜、消息队列。持久化RDB定时全量快照把内存全部保存磁盘恢复速度快但可能丢最后一段时间数据。AOF记录每一条写命令追加日志数据安全性高文件体积大。线上一般两者同时开启。6. 百万数据写MySQL6.1 批量插入1多条合并 INSERT批量 valuesINSERT INTO table(col1,col2) VALUES (v1,v2),(v3,v4),(v5,v6)...;不要一次性拼 100 万条,控制每批次大小一般 500‑2000 条一批。太大SQL 超长、数据包超限、锁表、回滚日志过大。太小等于逐条插入性能上不去。2LOAD DATA INFILE最快导入外部文件MySQL 原生导入比 insert 批量还要快几倍适合离线大批量导入。把数据整理成 csv 文本文件直接加载进表。LOAD DATA INFILE /xxx/data.csv INTO TABLE mytable FIELDS TERMINATED BY ,;6.2 事务优化1. 默认 MySQL 每条 insert 自动提交事务autocommit1每写一条刷磁盘。 2. **批量一批外面包一个事务**一批结束再 commit。begin; insert ... 1000条 commit;不要百万条全部放在同一个大事务因为大事务会让undo log、redo log 暴涨回滚代价巨大锁持有时间长阻塞其他业务主从复制延迟变大。6.3 索引影响写数据的时候索引越多写入越慢每插入一行所有索引都要更新。大批量导入阶段可以临时删除 / 禁用非必要索引导入完成后再重建索引。主键索引不能删。不要建多余索引。6.4 分表分库如果不止一次性导入后续业务常态持续达到百万、千万级别水平分表 按 id、时间拆分多张表每张表控制几百万以内。分库拆到多个 MySQL 实例分摊 IO 压力。6.5 线上实时海量写入中间件缓冲业务源源不断产生大量数据不是一次性导入消息队列RabbitMQ/Kafka生产者把数据丢消息队列消费端慢慢批量写 MySQL。防止流量瞬间压垮数据库。Redis 做缓冲合并先暂存一批攒够数量再批量落库。MySQL vs Redis对比维度MySQLRedis数据存储位置主要存磁盘部分数据放内存缓冲池全部数据放内存磁盘只做持久化备份数据类型关系型表格行、列支持int/varchar等支持复杂SQLKV非关系型String、Hash、List、Set、ZSet不支持SQL查询能力强大SQL多表联查、聚合、事务、复杂条件筛选简单key查询不支持复杂查询读写性能读写较慢受磁盘IO限制适合写、复杂查询读写极快内存操作适合高并发读写持久化默认持久化数据不会丢失RDB/AOF持久化宕机可能丢失少量数据事务完整ACID事务支持回滚强一致性单命令原子性不支持复杂事务弱一致性数据容量支持TB级大量数据存储受物理内存限制不适合存超大规模冷数据锁机制行锁、表锁支持并发写控制单线程执行命令无锁竞争并发靠多路复用典型使用场景持久化业务数据订单、用户基础信息需要复杂查询、事务的业务缓存、计数器、分布式锁、排行榜、会话存储、限流、临时数据数据可靠性高保证数据不丢可以丢少量数据适合热数据7. 别的IOIO 等待数据就绪 将数据从内核拷贝到用户空间。阻塞IOblocking IO调用recv/read没有数据线程直接阻塞休眠简单但是一个线程只能处理一个连接多连接需要大量线程开销巨大。非阻塞IO non‑blockingsocket设置O_NONBLOCK调用read没有数据直接返回‑1(EAGAIN)。需要循环轮询CPU空转消耗CPU很少单独使用。IO多路复用select/poll/epoll单线程监听大量文件描述符内核帮我监控哪些socket就绪是Reactor高并发网络库核心。selectfd有数量上限拷贝fd数组用户内核来回遍历查找就绪。poll没有数量上限但是同样遍历。epoll(Linux独有)epoll_ctl注册fd内核回调就绪事件epoll_wait拿就绪列表。水平触发LT边缘触发ET。性能高百万连接Reactor模型用epoll。8. Reactor模式主线程epoll_wait等待事件事件就绪分发回调函数处理业务。为什么需要 epoll没有 epoll 会发生什么为什么需要 epoll传统阻塞 IO 一个线程只能处理一个连接select/poll 多路复用存在性能缺陷需要把 fd 集合在用户态与内核态来回拷贝内核遍历全部文件描述符查找就绪事件连接数量大的时候效率急剧下降。epoll 是 Linux 高性能 IO 多路复用技术内核维护事件就绪列表只返回就绪的连接不需要遍历全部 fd采用回调机制大量并发连接下性能几乎不受连接数影响。实现单线程可以监听成千上万 socket 连接支撑高并发 Reactor 网络服务器。如果没有 epoll使用阻塞 IO每一条连接就要开启一个线程百万连接需要百万线程内存、线程切换开销巨大系统扛不住。使用 select/poll并发连接多的时候拷贝 fd、遍历 fd 带来巨大 CPU 开销并发性能差无法支撑大量连接。无法高效实现单线程高并发服务器难以开发高并发网络程序服务端处理大量客户端连接的能力大幅下降。9. HTTPSHTTPTLS/SSLHTTP明文传输容易被窃听篡改HTTPS HTTP over TLS443端口TLS工作流程握手阶段协商加密套件交换证书协商会话密钥。传输阶段使用对称密钥加密http报文。证书CA颁发数字证书服务器携带公钥客户端验证证书合法性确认服务器身份防止中间人冒充。非对称加密握手交换密钥计算慢。对称加密真正传输业务数据速度快。TLS握手流程客户端发送ClientHello给出支持加密套件、随机数。服务端ServerHello选定加密套件返回随机数、服务器证书。客户端校验证书生成预主密钥用服务器公钥加密发给服务端。两边各自计算会话密钥握手完成之后全部用对称加密通信。中间人攻击没有证书校验中间人拦截替换公钥证书就是用来证明公钥真正属于目标服务器。HTTP1.1 / HTTP2 / HTTP3HTTP1.1文本队头阻塞长连接keep‑alive。HTTP2二进制帧多路复用头部压缩。HTTP3跑在UDP之上QUIC协议解决TCP握手、队头阻塞。10. protocol是自定义通信协议 TCP是字节流没有消息边界。网络程序要自己定义应用层协议解决粘包区分一条条消息主流自定义协议格式长度头 消息体[4字节表示消息总长度][消息体二进制数据]接收方先读4字节拿到消息长度再读取对应长度的字节得到一条完整消息。其他方案分隔符特殊字符\r\n分割消息二进制数据不能用内容不能出现分隔符。定长消息每条消息固定大小简单但是浪费流量。序列化方案把结构体转为字节流放到消息体Protobuf(谷歌二进制小快网络程序首选)json文本协议可读性好体积大。 自定义协议要点大小端网络传输统一大端本机可能小端数字要做字节序转换htonl/ntohl。写网络程序接收缓冲区循环读取解析协议切出一条条完整报文多余字节留在缓冲区下次继续解析。11. sendfile 零拷贝Linux系统调用传统读取文件发送给socket流程 磁盘→内核缓冲区→read拷贝到用户缓冲区→write拷贝到socket内核缓冲区。两次CPU拷贝用户空间来回切换。sendfile(int out_fd, int in_fd, off_t* offset, size_t count)零拷贝系统调用数据直接在内核空间流转不经过用户态缓冲区。磁盘文件 → page cache内核缓冲区 → socket缓冲区直接发给网卡。减少两次CPU拷贝极大提升大文件发送性能。局限只能从文件描述符输出到socket老版本sendfile不能修改数据没有经过用户态无法修改内容。其他零拷贝技术sendfile基础零拷贝mmap writemmap把文件内核缓存映射到用户虚拟地址减少拷贝splice管道零拷贝不需要文件。
返回列表