关于TCP可靠性的一点思考,借此浅谈应用层协议设计
这篇讲的是,作者从网络游戏开发转向网络存储、机器学习等场景后,对TCP“可靠性”的重新审视。他提出,在需要重连重试的严肃应用中,TCP的ACK机制和操作系统的发送成功通知并不可靠——比如网络故障后,应用层无法获知哪些数据丢失,已提交的缓冲区也可能被释放,导致数据无法重发。 文章的核心,是剖析了三个基于TCP的应用层协议设计陷阱:发送方无法确认接收状态、无法区分“成功”与“未失败”、以及重试可能导致数据重复。针对这些“坑”,作者给出了具体的应对方案:必须在应用层设计确认应答(ACK);对于大文件追加,应采用带偏移量的positioned write;对于重复消息,则需在应用层进行去重。 最后,文章也讨论了优雅关闭连接的原则:应由接收最后一条消息的一方主动发起关闭。整篇文章从实际场景中的问题切入,深入浅出地阐明了在设计RPC协议时,不能盲目信任传输层,而必须在应用层构建自己的可靠性机制。
公司倒了,请让领导先走
这是一篇观点类的职场观察文章,作者从个人近期的感悟出发,提出了一个略带调侃却又现实的观点:在职业变动期,或许可以考虑“让领导先走”。 文章的核心在于对传统求职路径的一种反思。作者以求职大厂(如腾讯)为例,指出自己作为有8年经验的工程师,仍可能要面对年轻面试官对其系统架构能力的评估,这种错位有时会影响求职效率。因此,他提出了一个“曲线救国”的思路:与其投入大量精力进行不确定的常规应聘,不如等待并关注自己熟悉的领导或前辈的动向。如果他们加入了心仪的公司,通过其内推或许是一条更直接、更受认可的路径。 文中提及的“Mann咖啡生意恢复正常”,为这个略显冷峻的职场策略增添了一丝生活气息和时代背景。文章并非鼓吹投机,而是以一种轻松的方式,折射出当前环境下许多职场人对于人脉价值和求职效率的务实思考,也提醒读者,职业网络中的“弱连接”有时能提供意想不到的机会。
使用逻辑时钟重述paxos协议
Paxos协议以其晦涩难懂而闻名,但这篇技术博客提供了一个清新的视角:用逻辑时钟来重述它。作者认为,一旦从带时钟同步的RPC协议出发,复杂的共识流程就变得直观起来。 文章首先构建了一个基于逻辑时钟的通信框架。它引入一个全局计数器(globalClock)来产生全局递增的时间戳,规定所有网络消息必须携带时间戳,且接收方拒绝处理“过时”的请求。这个简单的同步机制,为后续的协议设计打下了确定性基础。 在此基础上,Paxos的两个阶段被清晰地映射为两类带时钟的请求。Proposer在Prepare阶段(设置时钟、广播提案号)和Accept阶段(发送具体提案)中,都遵循“时钟必须严格递增”的规则。Acceptor则依据收到的消息时间戳与本地时钟的比较,来决定是接受还是拒绝。这样一来,协议中复杂的冲突避免和提案推进,被转化为了对时钟值的比较和递增操作。 更进一步,文章指出可以摆脱中心化的全局时钟服务。每台机器的本地时钟可由一个二元组(轮次编号roundNumber, 服务器ID)构成。通过定义先比较轮次再比较ID的规则,保证了分布式环境下时间戳的全局唯一性和单调性,使得整个协议更加贴近实际部署场景。 总而言之,这种重述方式将分布式共识中抽象的“提案编号”竞争,转化为对逻辑时钟值的单调递增和比较操作,让Paxos协议的内在逻辑——即如何利用确定的全局顺序来避免冲突、达成一致——变得异常清晰。
排错经历:全局变量被多次析构
这篇讲的是一个C++服务程序在退出时崩溃的排查过程。作者团队发现服务每次正常退出都会触发core dump,堆栈显示崩溃发生在exit()函数析构全局变量时,提示“double free or corruption (fasttop)”,指向一个std::string全局变量。 排查过程相当硬核。由于服务器使用定制glibc,缺乏调试符号,作者只能在不完整的栈上“黑灯瞎火”地工作。核心难点在于,崩溃发生在main函数返回之后,且该std::string在exit中被析构数百次,难以直接定位。作者尝试在string的析构函数中打断点,却发现this指针已被编译器优化掉,常规条件断点也无法设置。 最终,作者绕到析构函数的底层实现(_M_dispose)下断点,并通过分析内存布局,找到了引用计数成员(_M_refcount)的偏移地址。他巧妙地设置了一个基于寄存器的条件断点(`if *((int*)$edi+4)!=-1`),成功捕获到了那个引用计数异常(为-2)的string对象。通过自定义的xxd命令打印内存,最终定位到了那个出问题的全局变量。整个过程展示了在调试信息匮乏的条件下,如何结合对内存结构和编译器行为的深刻理解,一步步从core dump反向追踪到问题根源的扎实技巧。
Linux程序链接时-lpthread对程序正确性的影响
作者线上服务频繁卡死,CPU使用率飙升。通过pstack堆栈分析,发现线程大量阻塞在pthread_cond_signal和mutex解锁操作上,但本应轻量的unlock函数却异常消耗资源。排查指向了链接时未指定-lpthread的隐患。 文章深入剖析了这一常见陷阱背后的机制。在glibc中,pthread相关函数(如mutex、条件变量)存在“空实现”以优化单线程程序性能。只有当程序显式链接libpthread.so后,其正确的多线程实现才会覆盖libc中的符号。作者通过readelf和nm工具对比发现,动态库通过versioned symbol(如pthread_cond_signal@GLIBC_2.3.2)实现这一覆盖,而非静态库使用的weak symbol。 关键在于,若主程序或最终可执行文件未链接pthread库,即使动态库本身依赖多线程,程序启动时加载的仍是libc中的空实现,导致死锁等严重问题。作者通过复现实验证明,只要最终可执行文件链接了-pthread,即便中间动态库未链接,也能通过符号版本机制正确解析到pthread库的实现,从而规避风险。
域名随机大小写导致libevent2的异步DNS解析失败
这篇文章深入剖析了libevent2内置异步DNS解析器的一个潜在陷阱。作者从实际遇到的DNS解析失败问题出发,指出根源在于libevent默认启用了“DNS-0x20 encoding”这种防投毒机制——它会将请求域名中的字母随机大小写化,但现实中的许多DNS服务器或中间网络设备(如ISP)并不严格遵守RFC规范,会在回复中将域名统一转为小写。这导致libevent在核对请求与响应域名时因大小写不匹配而判定解析失败。 作者通过抓包实验清晰地展示了这一过程,并深入libevent源码,定位到`global_randomize_case`开关及其导致严格字符串匹配的关键逻辑。文中还对比了Google Public DNS的做法:它也使用0x20 encoding,但通过引入白名单机制,仅对兼容的服务器启用,从而避免了全面故障。最后,作者向libevent社区提交了修改默认行为的补丁请求。这篇文章不仅讲清了一个技术故障的排查脉络,也揭示了理论标准与实际实现之间的常见鸿沟。
关于限制性股票和期权的一点个人看法
作者从非专业人士的视角,分享了对限制性股票(RSU)和期权这两种常见激励方式的个人观察。 他首先澄清了一个关键误解:RSU和期权并非公司“白送”的礼物,本质上是员工用部分现金薪酬换来的。文中用两个Offer选项做对比,清晰展示了薪酬包中现金与股票之间的权衡关系。 核心观点深入分析了两者的差异。RSU相当于行权价为零的期权,但其流动性受限,价值会打折扣。期权则具有“杠杆效应”,作者通过一个B司的数字案例生动说明:当股价上涨时,期权收益可能被成倍放大,反之下跌风险也同步扩大。他指出,期权本质是公司与员工共担风险的风险对冲工具。 最后,文章也提及了RSU与期权在税率上的关键区别,并建议读者通过假设可自由交易来思考其真实价值。作者的结论很实在:理解这些工具的金融本质,有助于在谈Offer时做出更明智的判断。
用牛顿迭代法求整数的平方根
这篇文章讲解了如何用牛顿迭代法解决一个经典的编程面试题:求整数的平方根。作者直接给出了一个基于 double 类型的 C++ 实现,并解释了其核心思路——利用迭代公式不断逼近平方根值,当相邻两次迭代结果差值小于1时即终止,最终取整。 文章的巧妙之处在于指出了实际实现中的几个关键细节。例如,代码对输入为0和1的情况做了预处理,并在最后通过 `floor` 和条件判断确保结果严格是小于等于原始整数的最大整数。作者也坦诚分享了一个尝试:他曾想用有理数运算避免浮点误差,但发现迭代次数增多后分子分母极易溢出,此路不通。 最后,作者点明这段代码的定位:它足以应对面试考察,但在生产环境中,面对性能要求时,可能存在更优的方案(如打表预计算)。这提醒读者,算法选择需结合具体场景,在理论正确性与工程实用性之间做好权衡。
面试总结[2014.06]
最近,一位工作7年的程序员分享了他密集面试百度、阿里、小米、美团、雅虎等多家公司的详细总结与思考。文章从一次略带遗憾的求职经历切入,深入剖析了国内技术面试的几个核心考察维度。 作者对面试环节的观察颇为犀利。他认为,当前面试对“编码能力”的实际考察不足,而对“算法”考察的侧重点(如是否追求标准答案)值得商榷。在“概念知识”与“项目经验”环节,他指出面试容易陷入“你知不知道”和“销售能力”的比拼,而非真正评估解决问题的能力。相比之下,雅虎面试新技术广度,阿里考察底层深度,小米采用类似谷歌的“基础能力优先”招聘风格,都给他留下了较好印象。 文章不仅分享了各家公司的面试风格差异与薪酬职级对比,更抛出了一个核心观点:面试官如何设计问题,才能公平且有效地甄别出候选人的真实能力与潜力?作者对面试体系的反思,或许能为同行带来一些启发。
工作与价值观
这篇文章探讨了一个看似简单实则深刻的问题:我们工作究竟是为了什么。作者以观察到的三种典型选择为起点——有人为了薪水支持自己的生活方式,有人为了证明和提升个人能力,有人则是为了实践自己信奉的价值观。文章明确指出,这三者并非递进关系,而是相互排斥,你只能选择一个作为核心驱动力。 作者着重阐述了第三种选择:在日常工作中,通过选择“做”与“不做”来体现并践行个人价值观。文中引用了Sam Altman的观点以及对代码质量、技术实践的容忍度等具体细节,说明当个人对事业的认同感足够强烈时,许多技术琐事都显得微不足道。 延伸到创业层面,文章对比了“选对人”、“选对方向”与“选对事(共同认可)”三种不同理念。作者明确倾向于后者,认为基于对事业本身的共同认可而组建的团队,其根基更为稳固。他以土豆网为例,说明推动公司前进的可能不是某个创始人,而是一个被广泛认可的价值主张。 读完此文,你或许也会开始重新审视,支撑自己日复一日工作的,究竟是什么。
2014年1月21日中国互联网DNS瘫痪事件原因分析
这篇文章讲的是2014年1月21日中国互联网大规模DNS瘫痪事件的技术剖析。作者从一个普通用户的视角出发,描述了当时所有网站都打不开的异常现象,并以通俗的“电话本”比喻,解释了DNS作为互联网基础设施的核心作用。 作者详细梳理了从用户输入网址到获得IP地址的正常DNS解析流程,并与当天故障时的实际流程进行对比:所有查询都直接返回了同一个错误IP(65.49.2.178),跳过了正常的层层解析步骤。通过分析,作者排除了全球根域名服务器自身出错的可能,将原因锁定在“DNS劫持”上——即有人伪造了根服务器的响应。 文章进一步通过追踪那个错误IP的历史关联信息,发现其与特定组织及“无界浏览器”等翻墙工具存在关联,并指出这种大规模、快速的劫持手法与GFW(防火长城)的运作机制高度一致,从而提出了事件可能由某墙导致的观点。整个分析过程层层递进,从现象描述到技术原理拆解,再到幕后推断,为读者提供了一次生动的网络故障排查案例。
chrome对代理服务器的支持情况
这篇深入探讨了Chrome浏览器对代理服务器的支持情况,清晰梳理了它支持的两大类协议:SOCKS和HTTP。作者指出,SOCKS下实际涵盖了SOCKS4、SOCKS4a和SOCKS5,但Chrome并未明确支持SOCKS4a的远程域名解析,且所有SOCKS协议都不支持身份验证。 在对比关键差异时,文章分析得非常细致。例如,在连接建立的开销上,HTTP、SOCKS4、SOCKS5三者并不相同:SOCKS4需要1次往返,SOCKS5需要2次,而HTTP代理虽然也需要1次往返,但Chrome处理带认证的HTTP代理时机制比较特别(先收到407再补发头信息),且新版本浏览器会尝试记忆认证信息,不过底层部分请求如更新程序依然不支持。 文章还揭示了两个值得开发者注意的实用细节:一是Chrome的DNS预取功能在配置代理后仍可能尝试本地解析,存在隐私泄露风险;二是用户社区长期呼吁为Chrome的SOCKS5代理增加身份验证功能,但官方尚未有实质性进展。这些内容不仅对比了协议特性,也点出了实际使用中可能遇到的坑。
在tomcat应用中获得原始IP
这篇讲的是如何在使用 Apache/Nginx 作为反向代理的 Tomcat 应用中,重新获取被代理层“吃掉”的客户端原始信息。作者从实际场景出发,指出 Apache 代理后,Tomcat 得不到客户端的真实 IP、主机名和是 HTTP 还是 HTTPS 协议,这会给生成绝对重定向 URL 和页面资源链接带来麻烦。 文章的核心方案分两步:先在 Apache 端配置,通过 `ProxyPreserveHost` 转发原始 Host 头,并添加 `X-Forwarded-Proto` 等自定义头部来传递协议等信息。然后,在 Tomcat 端配置 `RemoteIpValve`,让它能智能地“读懂”并应用这些从代理转发过来的头部,从而在 `HttpServletRequest` 中还原出真实的客户端信息。 文章还贴心地附上了测试用的 Servlet 代码和对比结果。测试表明,配置完成后,无论前端是 HTTP 还是 HTTPS 访问,Tomcat 都能正确获取到原始 IP 和协议类型。这套组合配置为解决代理环境下的应用逻辑判断提供了清晰有效的路径。
自建DNS以防止GFW干扰
这篇讲的是如何通过自建本地DNS服务器,来规避GFW对DNS查询的干扰,从而恢复部分网站的访问。文章首先解释了问题根源:GFW会拦截并污染常见的UDP协议DNS请求,导致解析结果错误。而一个有效的对策是利用TCP协议的DNS查询,因为它目前不易被干扰。 作者推荐的具体方案是,使用开源软件Unbound在本地搭建一个DNS服务器。该服务器监听本地,接收程序发出的UDP请求,并将其转换为TCP查询转发给上游公共DNS(如8.8.8.8),从而绕过污染。文章给出了在Windows系统上的详细配置步骤,包括安装、修改配置文件、重启服务,并最终将系统DNS指向本地127.0.0.1,操作性很强。 对于更进一步的安全需求,文章末尾还提到了一个升级思路:可以结合SSL加密,在境外服务器上部署Unbound作为上游,实现查询流量的端到端加密,提供了更彻底的解决方案。
为什么你写不好一个快速排序? 谈程序员的职业发展
一位资深程序员的自我拷问:为什么我写不好一个快速排序了?作者从自己的真实经历出发,讲述了工作六七年、title和薪水都提升后,却发现自己的基础编码能力(如实现快速排序)甚至不如一些应届生的困惑。他坦诚地反思,这源于过去听信“不要重复造轮子”而忽视了基础训练,以及在职业上升期,作为程序员最核心的“把想法快速变成正确代码”的能力被逐渐淡忘。 文章将程序员与医生类比——主任医师依然需要主刀,以此尖锐地指出技术岗位的核心竞争力所在。作者并非否定项目经验的价值,而是强调,若没有扎实的底层编码能力作为基石,那些“牛B的经历”可能只是平台与顺境带来的附加值。他最终将职业梦想落回原点:用扎实的编码能力赢得同行的尊重。这篇反思为所有走技术路线的人提了个醒:无论走得多远,别忘了那个让你成为程序员的起点。
用msmtp代替系统自身的sendmail
系统自带的sendmail因为漏洞多、配置复杂,常被管理员禁用,但这会导致cron任务出错时无法及时知晓。作者为了解决这个问题,放弃了之前使用的但已停止维护的ssmtp,转而寻找并采用了msmtp作为轻量级替代方案。 文章详细分享了从安装、配置到与系统深度集成的完整步骤。关键不仅在于如何配置msmtp连接邮件服务器,更在于两个精妙的实践:一是修改`/etc/mail.rc`让系统`mail`命令默认使用msmtp;二是在crond配置中为`CRONDARGS`参数正确添加了`-t`选项。 作者特别指出,这个`-t`参数至关重要,它确保msmtp从标准输入读取收件人列表。此前遗漏此参数导致了cron任务虽然输出了日志但邮件发送状态异常的诡异问题。这个解决方案是作者在实际踩坑后总结出的独家经验。通过这一套替换,既保留了系统邮件通知的能力,又极大地简化了管理负担。
用Bloom Filter的方式统计网络流量
作者从网站面临爬虫攻击和恶意访问的现实问题出发,想要高效统计每个IP的日访问量以识别机器人。传统的Map计数法可能消耗数百兆内存,而文章介绍了一种基于Bloom Filter思想的变体算法,可以在极低的内存占用(O(1)空间复杂度)下完成计数。 这个方案的核心是使用一个二维数组和多个独立的哈希函数。每次访问到来时,不是增加所有对应位置的计数器,而是只增加这m个计数器中值最小的那一个。这种方法巧妙地将Bloom Filter的“是否存在”判断,扩展为了“计数”的近似统计。当然,它继承了Bloom Filter可能存在的假阳性特点——可能误判某些低频IP为机器人,但可以通过调整数组大小和哈希函数数量来控制误差率。 文章还由此类比了《编程之美》中一个经典的微软面试题,并进一步提出了扩展问题:如果要统计的不是访问次数,而是IP的入/出流量,该如何设计算法?这为读者提供了更广阔的思考空间。
获取文件大小之前最好先读一下这个文件
这篇讲的是一个在Windows开发中容易被忽略的陷阱:使用`stat`函数获取的文件大小可能不可靠。 作者从一个具体场景出发,指出了问题所在——有时通过`stat`得到的文件大小,会与资源管理器中显示的大小或实际读取的大小存在差异。这往往会导致后续的文件读取、内存分配或网络传输逻辑出现错误。文章深入分析了根源,通常与文件系统的缓存、写入后未及时同步或某些特定文件系统的特性有关,使得元数据中的大小信息并非实时准确。 针对这个问题,作者没有停留在发现问题,而是给出了经过验证的解决方案:在依赖文件大小信息前,不妨先实际读取一下文件(哪怕只读取一部分)。这种方法虽然增加了一次I/O操作,却能确保你获得的大小信息与后续操作的真实数据完全一致,从而避免因元数据不准而引发的难以排查的bug。文章最后强调,对于需要精确文件信息的场景,这种“以读取为准”的策略是更稳健的做法。
利用node.js搭建SPDY协议的翻墙服务
这篇讲的是作者如何从翻墙的“痛点”出发,用 Node.js 与 SPDY 协议打造新方案。作者最初依赖 ssh -D,但常遭遇连接中断,即便配置 keep-alive 也难以根治。这让他思考:能否直接走 HTTP 或 SOCKS 协议?核心障碍在于通信的加密与效率。 于是,他将目光投向了 SPDY 协议。文章详细记录了如何用 Node.js 搭建基于 SPDY 的代理服务——它在 TCP 之上实现了多路复用与头部压缩,同时依托 TLS 加密,恰好解决了传统 HTTP 明文传输的安全隐患。作者从环境搭建到代码实现逐步展开,不仅剖析了 SPDY 协议相比 HTTP/1.1 在延迟与吞吐量上的优势,也坦诚对比了与 SSH 隧道在连接稳定性上的差异。最终,这套自建服务不仅运行稳定,更让客户端免于安装额外软件,实现了浏览器原生支持的便利访问。
我对开源的看法
这篇讲的是作者对“通过开源提升技术”这一常见信条的反思。他曾经坚信,程序员提高水平的最佳路径就是多读开源代码、多参与社区。但随着实践深入,他发现了这一路径的局限性。 文章坦诚地剖析了这种转变:开源项目代码质量参差不齐,社区讨论有时流于表面,而初学者可能陷入“只读不写”或“盲目模仿”的误区。作者指出,真正的技术成长需要更结构化的思考、对项目背景的深刻理解,以及将知识内化并应用到自身项目中的能力。 对于许多把开源视为“技术朝圣地”的开发者来说,这篇文章提供了一个冷静的视角。它提醒我们,开源是宝贵的资源,但如何从中有效汲取养分,需要比“多看多练”更精细的方法论。