黄山奇石
vibe coding 派还在 code 你说什么? 哦,老爷子,你看那:vibe-coding-派-还-在-code 老王啊……我说张主管啊,怎么这种标语现在还留着?……八股余孽嘛,赶紧找人清理掉 算了,算了,留着也好,以示警世。
分布式系统一致性
在分布式系统中,一致性模型定义了多节点间数据更新的可见顺序与实时性,是权衡性能、可用性和正确性的核心。四个经典模型从强到弱依次为: 线性一致性(Linearizability) 顺序一致性(Sequential Consistency) 因果一致性(Causal Consistency) 最终一致性(Eventual Consistency) 越强的一致性模型对系统的性能和可用性要求越高,实际工程需要根据业务场景取舍。 线性一致性 每个操作在现实中需要一段时间才能完成,从逻辑上的“调用(call)开始”到“返回(return)结束”。但在这期间某个瞬间,该操作原子性地生效了,并被其他参与者(线程、节点等)立刻感知到这个变化。 线性一致性保证了系统对外表现得像只有单一副本,无论 client 访问哪个节点,读取到的数据都是一致的。在 Raft 这样的共识协议中,一个操作必须经过多数节点确认,也就是达成共识才能生效,也就对应了操作原子性生效的那个瞬间。 在 CAP 理论中,线性一致对应 $C$(一致性)。当网络分区($P$)发生时,系统必须拒绝请求而不是返回可能过期的数据,因此牺牲了可用性($A$)。Raft、ZooKeeper 等走的就是 CP 优先的道路。 举个例子,用户 A 转账给用户 B 100元,B 立刻查询账户余额必须多出100元而不能是旧值。如果银行网络故障,必须拒绝这个请求,防止写入脏数据。 顺序一致性 顺序一致性比线性一致性稍弱。它要求存在一个操作的全局总序,在这个总序里: 一个参与者内的操作保持程序顺序 所有参与者看到的内存操作顺序是一致的,但不要求这个顺序与真实时钟一致。 也就是说,操作可以看起来“乱序”发生,但只要所有参与者看到的顺序一致即可。 这点区别其实影响很大。线性一致中,节点 A 在 $T_1$ 完成了写入,节点 B 在 $T_2$ 读取,那么节点 B 必须能读到这个写入。但是顺序一致中不保证,只保证所有参与者看到相同的结果。 举个例子,初始 x=0, y=0 Thread 1: x=1 r1=y Thread 2: y=1 r2=x 最终结果应该为:r1=1, r2=1 所以可能的合法的执行顺序只有两种: T1 执行完两条后,T2 执行两条,T2 看到 x=1, r2=1 T2 执行完两条后,T1 执行两条,T1 看到 y=1, r1=1 不符合最终结果一致的执行顺序则不符合顺序一致性。 ...
Linux C/C++ IO多路复用
在linux服务器编写中,为每个连接单独开一个进程/线程不现实, 我们采用io多路复用。 基本TCP socket模型 select/poll select内部有一个定长(1024)的bitsmap,以存储要监听特定事件的fd。fd从用户空间拷贝到内核,内核里遍历修改一遍后,再把就绪的fd拷贝到用户空间,用户再遍历一遍。 poll不再用bitsmap,改用动态数组(链表),不再受限于特定个数的fd。 但二者其实区别不大,本质都是遍历了两遍,查找有事件的fd时间复杂度为O(n),也都需要来回拷贝fd集合。这些操作都很消耗性能。 epoll int sockfd = socket(AF_NET, SOCK_STREAM, 0); bind( ... ); listen( ... ); int epoll_fd = epoll_create1(0); // 添加fd epoll_ctl(epoll_fd, ...); while (1) { int n = epoll_wait( ... ); for ( ... ) { // 有事件的fd } } epoll不再使用线性的存储结构,改用红黑树(一种自平衡二叉树),增删改的时间复杂度一般为O(log n)。 epoll内部维护了一个就绪fd链表,每次epoll_wait只需要处理链表,然后把所有就绪fd拷贝到用户空间。 epoll不适用轮询,而是事件驱动。 epoll有两种触发模式,ET(边缘触发,select/poll不支持)和LT(水平触发),区别主要体现在读上。 LT模式下,只要可读(socket读缓冲区中有数据),就会触发事件,直到缓冲区被读完。 ET模式下,仅当数据到来时(缓冲区数据从无到有)触发一次事件,后续将不再触发,即使缓冲区还有数据。 LT一般搭配阻塞读写,ET一般搭配非阻塞读写。
零拷贝技术
什么是零拷贝? 利用直接内存访问(Direct Memory Access,DMA),消除数据在内存中不必要的拷贝,即零拷贝技术(Zero-Copy)。 零拷贝不是没有任何复制,而是消除了消耗CPU和内存带宽的不必要的冗余复制。 为什么要有零拷贝? 传统IO过程: 用户进程–read()—>CPU(转化为内核态)–请求读取—>磁盘–多次缓冲区拷贝后转化为用户态—>read()返回—->用户进程 CPU被大量文件数据占用,多次切换上下文,期间干不了其他事情。并且,多次缓冲区的拷贝极大限制了文件传输性能。 于是Zero-Copy出现了。 优化思路 减少上下文切换的次数。传统文件IO(read()+write())有4次上下文切换(read用户->内核->用户,write用户->内核->用户)。 减少各种缓冲区的拷贝次数。传统读取:磁盘控制器缓冲区->内核缓冲区->用户缓冲区,每次拷贝都在消耗时间。 mmap()+write() read()会把内核缓冲区的数据拷贝到用户缓冲区,为了减少拷贝,换成mmap()。 mmap(Memory Map)核心思想是利用虚拟内存管理机制,在进程虚拟地址空间与物理资源(比如文件)之间建立直接的映射关系。 #include <sys/mman.h> void *mmap(void addr[.length], size_t length, int prot, int flags, int fd, off_t offset); 使用 mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0); 把内核缓冲区的数据直接映射到用户缓冲区,这样就避免了一次拷贝。 然后write(),把数据直接拷贝到socket缓冲区。由于数据还在内核缓冲区,这些都在内核态发生。 不过系统调用上下文切换的次数没变。 sendfile() #include <sys/socket.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); read from in_fd,然后write to out_fd。offset是in_fd内部的偏移量,count是最大处理长度。返回实际处理长度。 直接减少了一次系统调用,减少上下文切换开销 直接把数据从内核缓冲区拷贝到socket缓冲区,不再经过用户态 这时有磁盘->内核缓冲区->socket缓冲区->网卡,共3次拷贝(CPU一次,从内核读缓冲区到socket写缓冲区)。 如果网卡还支持SD-DMA技术,数据可以绕过sockert缓冲区,从内核缓冲区直接发送到网卡。内核空间缓冲区中对应的数据描述信息(内存地址、地址偏移量)记录到socket缓冲区中,由 DMA 根据内存地址、地址偏移量将数据批量地从内核缓冲区拷贝到网卡设备中(DMA gather copy),这样就又省去了内核空间中仅剩的 1 次 CPU 拷贝操作。 splice() sendfile的局限性是,只能从本地文件的fd发送到socket fd,且在一定程度上依赖硬件支持。 ...
SQL和数据库管理系统(MySQL)
为什么需要数据库? 数据太多,文件搞不定。 查询复杂 数据结构混乱难以维护 文件难以并发处理 数据库, 结构化数据管理 高效查询 并发控制 数据安全 数据库和SQL 数据库 : 关系型数据库 非关系型数据库 SQL是一种管理关系型数据库的语言。 SQL (Structured Query Language):结构化查询语言 用于操作关系型数据库:定义数据表,插入/查询/修改/删除数据 MySQL?SQLite?PostgreSQL? Database Management System. 是一种软件,不是数据库。 MySQL -> 主流服务器端 SQLite -> 嵌入式 PostgreSQL -> 支持复杂查询 MySQL安装 Ubuntu sudo apt install mysql-common mysql-client mysql-server sudo systemctl start mysql-server sudo systemctl enable mysql-server # 开机自启 Arch sudo pacman -S mariadb sudo mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql sudo systemctl start mariadb sudo systemctl enable mariadb 开始使用(Ubuntu) sudo mysql CREATE USER 'new_user'@'host_name' IDENTIFIED BY 'password115414'; host_name="localhost" 只能从本地访问 ...