codedump的网络日志

其他

周刊(第24期):sqlite并发读写的演进之路

引言:本文梳理sqlite并发读写方案的演进之路。sqlite并发读写的演进之路概论sqlite底层的存储基于B-tree,B-Tree对底层存储的基本读写单位是页面,而每个页面都由全局唯一的页面编号与之对应,一般来说页面编号从1开始递增。类B-Tree的存储引擎修改数据的流程如下图所示:从上图中,需要区分B-Tree类的存储引擎几个核心的模块:•
其他

周刊(第23期):图解Blink-Tree:B+Tree的一种并发优化结构和算法

key和link指针之后的blink-tree示意图:算法对数据结构有了解之后,来看看算法的实现,不过在具体展开之前,还要先了解几个概念。high
其他

周刊(第22期):图解一致性模型

引言:本文使用大量的图例,同时没有难懂的公式,意图解释清楚一致性模型要解决什么问题,以及三种一致性模型:顺序一致性、线性一致性、因果一致性。图解一致性模型概述解决什么问题?分布式系统要保证系统的可用性,就需要对数据提供一定的冗余度:一份数据,要存储在多个服务器上,才能认为保存成功,至于这里要保存的冗余数,有Majority和Quorum之说,可以参考之前的文章:周刊(第17期):Read-Write
2022年7月18日
其他

周刊(第21期):Lamport时钟介绍

https://martinfowler.com/articles/patterns-of-distributed-systems/lamport-clock.html[6]Lamport
其他

周刊(第20期):Rust并发安全相关的几个概念(下)

引言:本文介绍Rust并发安全相关的几个概念:Send、Sync、Arc,Mutex、RwLock等之间的联系。这是其中的下篇,主要介绍Arc,Mutex、RwLock这几个线程安全相关的类型。Rust并发安全相关的几个概念(下)在上一节[1]中,讲解了Send和Sync这两个线程安全相关的trait,在此基础上展开其它相关类型的讲解。RcRc是Reference
2022年6月27日
其他

周刊(第19期):Rust并发安全相关的几个概念(上)

引言:本文介绍Rust并发安全相关的几个概念:Send、Sync、Arc,Mutex、RwLock等之间的联系。这是其中的上篇,主要介绍Send、Sync这两个trait。Rust并发安全相关的几个概念(上)Rust的所有权概念在展开介绍并发相关的几个概念之前,有必要先了解一下Rust的所有权概念,Rust对值(value)的所有权有明确的限制:•
2022年6月20日
其他

周刊(第18期):网状的思考,线性的写作

引言:本文介绍我理解的“卡片式笔记法”,以及我的笔记实践、工具等。网状的思考,线性的写作现实世界中的思考我们的脑子,可能每时每刻都在进行着一些思考:走在路上、做饭、看书看电影时,等等。而在物理的时空上,肉身在任意时刻只能身处在一个物理意义上的空间里,在时间上也只能处于一个时间点上。可见,思考产生的念头,和时空的限制对比起来,是发散的、不确定的。如下图所示:很多时候,思考并不是凭空而生,可能是:前几天有过一次对某问题的思考。过了几天,想起来这个问题,又有了另外的考虑和补充。当需要记录下来这些想法为笔记时,单篇笔记本身是线型的,只能算是一个一维的创作,这是因为从物理角度来理解一篇“笔记”的话,它在物理上只能存储在一个地方(比如只能是一个文件)。如果按照“一维”的视角,去记录写作的想法,可能就会这样做:某天产生了一个想法,把它记录下来;隔几天有了对这个想法的补充,要么找到前几天的想法记录的地方,继续在这上面补充,也或者另开一篇再记录一次今天的想法。如果这样做,就会得到类似上图那样的效果:不同时空的思考之间,缺少了联系。从这个意义上来说,用只有一个物理维度的“笔记”,来试图存储维度不限的想法、念头,并不适合。这种做法相当于把多维度的想法、念头给降维了。什么时候更适用于这类“降维”的写作行为?我认为是在思考已经很清楚,能够把想法、念头整合在一篇完整的文章时才适合,这时候这篇输出的文章是经过整理、且有完整的观点的。在更多的时候人是在不同的时空进行不同的思考,这时候就需要另外的工具来整合这些念头,“卡片式笔记法”就适用于这种场景。卡片式笔记法“卡片式笔记法”中的“卡片”,对应的是前述场景中不同时空下产生的想法、念头。与传统意义上的“笔记”不同的是,“卡片式笔记法”中的记录粒度更细,可以任意想法就能记录在它所谓的“卡片”上。同时,在每个“卡片”赋予一个逻辑上的“地址”,这个逻辑地址类似于编程中的IP地址、超链接等概念。当有了另外的和这个想法有关联的其他想法时,可以再创建另外的卡片,不同的卡片之间通过逻辑地址进行关联。同时,为了更好的查找同类的想法,可以使用tag等方式打上标签,便于搜索、归类。下图是一个典型的“卡片笔记”组成示意图:Unique
2022年6月13日
其他

周刊(第17期):Read-Write Quorum System及在Raft中的实践

引言:在Paxos、Raft这类一致性算法的描述里,经常会看到Majority、Quorum这两个词,在以前我以为都是表达“半数以上”的含义,最近才发现两者有不小的区别。本文介绍这两者的区别,以及在Raft中实践中的问题。有了Quorum的视角,能更好得理解一致性算法。Read-Write
2022年5月30日
其他

周刊(第16期):图解ARIES论文(下)

checkpoint-begin:标记着checkpoint操作的开始,在这次checkpoint操作成功之后,这条日志的LSN将保存到MasterRecord记录中。•
2022年5月23日
其他

周刊(第15期):图解ARIES论文(上)

no-force:事务提交之前,必须所有的修改操作都写入磁盘的WAL中才能提交,但并不要求一定要落入数据库文件。由于持久化存储包括了WAL文件和数据库文件,即可以认为任意时刻的数据为:数据库文件
2022年5月16日
其他

重读Raft论文中的集群成员变更算法(二):实践篇

引言:以前阅读Raft大论文的时候,对“集群变更”这部分内容似懂非懂。于是最近又重读了大论文这部分的内容,以下是重读时做的一些记录。这部分内容打算分为两篇文章,上篇讲解成员变更流程的理论基础,下篇讲解实践中存在的问题。重读Raft论文中的集群成员变更算法(二):实践篇单步成员变更存在的问题正确性问题单步变更成员时,可能出现正确性问题。如下面的例子所示,最开始时,系统的成员是{a,b,c,d}这四个节点的集合,要将节点u和v加入集群,按照单步变更成员的做法,依次会经历:{a,b,c,d}->{a,b,c,d,u}->{a,b,c,d,u,v}的变化,每次将一个节点加入到集群里。上面的步骤看起来很美好,但是考虑下面的例子,在变更过程中leader节点发生了变化的情况:C₀
其他

周刊(第13期):重读Raft论文中的集群成员变更算法(一):理论篇

引言:以前阅读Raft大论文的时候,对“集群变更”这部分内容似懂非懂。于是最近又重读了大论文这部分的内容,以下是重读时做的一些记录。这部分内容打算分为两篇文章,上篇讲解成员变更流程的理论基础,下篇讲解实践中存在的问题。重读Raft论文中的集群成员变更算法(一):理论篇“集群成员变更(cluster
2022年4月18日
其他

周刊(第12期):Page oriented类存储引擎里可能同时存在多种结构

oriented”类存储引擎的核心模块是页面管理器和树形结构的实现,前者提供物理页面这一“原材料”的读写操作,对页面内部的结构一无所知;后者组织管理物理页面间的逻辑关系,以及物理页面内部的数据。•
2022年4月11日
自由知乎 自由微博
其他

周刊(第11期):mmap适用于存储引擎吗?

写事务完成之后,需要将修改落盘,将按照如下的顺序来进行:这个修改的顺序必须严格遵守,否则中间一个过程失败整个数据库文件就损坏了。只有当完成meta页面的修改,才认为这次修改完成。•
2022年3月28日
其他

周刊(第10期):“忘记目标,专注于体系”

引言:本期聊一聊《掌控习惯》这本书里提到的养成习惯的方法论。我读下来一个最深的感受是:越不需要“坚持”就能做下去的事情,才越能长久做下去。“忘记目标,专注于体系”“忘记目标,专注于体系(Forget
2022年3月21日
其他

周刊(第9期):Mozilla rr使用简介

replay,默认将使用最近保存的记录文件进行回放,回放时可以进入类似gdb那样的调试环境。比如前面那个多线程程序,使用rr来记录及回放就是:•record:$
2022年3月14日
其他

周刊(第8期):技术配图的一些心得

引言:写过不少技术文章,以及给不少技术思路手绘示例配图之后,在这方面有了一些心得,本文权当个人的一些的总结,抛砖引玉。技术配图的一些心得我觉得我们理工科出身的,对于可以量化的事情,总是很容易根据量化差异来做出判断,比如一个程序性能优化之后,能比优化之前快出多少,都能有一个量化的数字来说明。但是对于那些不能量化的东西,就很难说出具体好在哪里了。本文主题要讨论的“技术配图”就属于这种很难量化的领域,很难有一个标准来量化说明两幅图之间差别在哪里。我也是画了很多图,以及看了别人的很多配图之后,才慢慢有一些心得,本文权当个人的一些的总结,抛砖引玉。本文并不是一个画图工具的对比说明,尽管现在各种绘图工具已经很多,也各有自己的优缺点以及个人喜好,但是在这里并不讨论具体工具的使用,会把更多的文字放在配图的一些注意事项上。但是,也总有人问我文章的配图使用什么工具做的,在这里再回答一次:OmniGraffle,一款目前仅有Mac版本的工具软件。一图胜千言
其他

周刊(第7期):一个C系程序员的Rust初体验

引言:在工作里使用Rust已经有两个多月的时间了,谈谈我做为一名多年的C系(C、C++)程序员,对Rust的初体验。一个C系程序员的Rust初体验最近由于工作的原因,使用上了Rust语言,在此之前我有多年的C、C++编码经验(以下将C、C++简称C系语言)。使用C系语言编码时,最经常面对的问题就是内存问题,诸如:野指针(Wild
2022年2月28日
其他

周刊(第6期):《sqlite 3.36 btree实现解析》番外篇

Database[3],这是一篇解释btree工作原理的文章,这篇文章同时还列出了一个项目:madushadhanushka/simple-sqlite:
2022年2月21日
其他

周刊(第5期):从存储模型聊一聊时序数据库的应用场景

引言:本期介绍时序数据库的存储模型,只有理解了时序数据的存储模型,才能更好的了解时序数据库的优缺点以及其适用场景。从存储模型聊一聊时序数据库的应用场景想写本文,是因为看到了知乎上的一篇文章:投资数据库领域:2021年总结(NoSQL、图、时序)
2022年2月14日
其他

周刊(第4期):为什么我还在看中国足球

引言:虎年大年初一的晚上,一场脆败发生在世界杯亚洲区预选赛中国客场对越南队的比赛上。如今,“你居然还在看中国男足”,仿佛已成一句骂人的质问。本期从我角度来谈谈,我眼中的中国足球,以及说说我为什么还一直在关注这个领域。我为什么还在看中国足球我从94年开始看球,中国足球绝大部分的重要比赛都看了:94年亚运会决赛输给乌兹别克斯坦、97年大连金州被伊朗逆转、2002年世界杯出线...太多了,数不过来,算是从我开始看球之后就一直有关注中国足球。在看国足比赛二十多年之后,慢慢地从一个参与者、评论者的角色,切换到了近似于第三方视角的观察者角色。切换到这个视角之后,让我能从里面各种情绪里抽离出来,当然高兴的时候也会像个普通球迷那样欢乐,比如2017年世界杯预选赛击败韩国这样的比赛。我国虽然在奥运会上取得了看似很好的成绩,金牌数总是保持前列,但是有一说一,并不算是体育大国,更别提强国了。只看我们占优势、能取得好成绩的项目,大多有这样的特点:小众、冷门,这样的特点直接导致这样的项目,实际是商业化程度很低的领域。这样的领域,国外参与的人不会太多,也因此可以继续沿用以前我们擅长的打法:集中力量办大事,换到体育这个领域,就是所谓的“举国体制”。这样做的好处是,能用较少的资源拿到不错的效果,因为大部分人只关注金银牌这些数字,并不关心你怎么拿到的。这个策略,用知乎上一个回答的话来说叫“田忌赛马”,见:为什么中国的其他运动项目那么强,到了男足这里就不行呢?[1]在商业化、职业化很好的体育项目,比如足球、篮球、网球等等领域,我们的成绩就不这么好了,李娜、姚明、刘翔是少数在这些领域拿得出手的世界级运动员。(后面会专门谈谈女足)一言以蔽之:举国体制从目前的成绩来看,并不适合职业化、商业化很好的体育项目。“足球是体育工业化的集大成者”(见(为什么整个中国都知道中国足球的问题,为什么还是没有办法解决?-
其他

周刊(第3期):一个前游戏开发者眼中的游戏后端技术

引言:在我之前的工作里,因为各种原因,断续在游戏行业里有过总共大概四年左右的从业时间,今天想从我的视角聊聊游戏行业后端开发相关的技术,供那些想在这个行业从业,尤其是后端开发从业人员一些参考。一个前游戏开发者眼中的游戏后端技术由于我从业的方向都是后端开发,所以这里仅谈论游戏开发后端的技术。游戏本质是个内容行业,所以游戏开发时相当一部分工作内容,就是不停的更新新的内容出来给玩家消费。这些内容包括但不限于:新的英雄、技能、玩法,等等。而要支撑这些玩法,相当一部分是策划的配表数据,即:•程序员把玩法的框架、逻辑搭建好,留出读玩法数据的接口来。•玩法数据由负责该玩法的策划来配表实现。这其实就是很朴素的“表驱动”的编程方式。因为这个原因,所以游戏服务器启动时,要加载相当多的数据,主要有:•玩家的数据,包括账号、角色、帮派、金钱等数据。•玩法相关的策划配表数据。比如一个场景的坐标位置、NPC的坐标位置、任务,等等。由于需要在启动的时候要加载很多数据,游戏服务器才能完成初始化,所以“编译型”语言在游戏开发里并不适用来编写游戏玩法逻辑,试想下面的开发场景:•策划提出了新的玩法需求。•开发使用如C++这样的编译型语言编码实现玩法。•编译新修改的代码:这里面有包括了编译、修改编译不通过时候的报错,等等。•停止服务器,重启服务器来验证玩法的逻辑。可以看到,姑且不论其他因素,单是每次验证代码修改时需要停服、重启服务器的流程,而重启时又要加载一堆数据才能完成初始化,这个过程就严重影响开发效率。换言之,“编译型”语言并不适合于用来编码在游戏开发里需要经常变更的玩法逻辑。于是,一种新的开发架构出现了,见下图。在图中,将游戏服务器架构分为了两层:•引擎层:这部分由C++编码,实现了游戏开发中与具体逻辑关系不大、且不太会变更的部分,如网络数据收发、数据库访问,等等。•脚本层:这部分由Python、Lua这样的脚本语言实现,主要就是各种玩法。采用这样的架构最主要的优点,就是解决前面提到的开发效率问题。由于Python、Lua这样的脚本语言,支持热更新,即“不需要重启进程也能更新最新的代码”,这样开发模式就变成了:•策划提出了新的玩法需求。•开发使用如Python、Lua这样的脚本型语言编码实现玩法。•热更新脚本代码,调试玩法。可以看到,由于支持“热更新”,省去了停服、重启、加载数据的步骤,一下子开发效率就提升了很多。多说一句,“热更新”还有一个优点:假如线上出问题时,总不可能停服下来修复,热更新不需要重启就能更新最新代码的特点在这里又发挥了作用。从这个开发架构里,也可以看到游戏服务器人员的构成:•引擎层只有少数人能够去维护,要求稳定、高效。•绝大部分人,都在脚本层用脚本语言来写各种玩法逻辑,类似于web开发中的CRUD。这个架构已经很久没变化了,而且每个公司都有自己一套(甚至几套)维护得较为稳定的服务器引擎,这会有一个问题:•技术演进慢。十几年前的架构到现在基本没变。•由于演进慢,而且对稳定性的要求高,实际上新人也很难找到机会在引擎层发挥作用,引擎层的代码通常也不会让脚本层工作的人看到,这样就进入不到这一层的开发,而且引擎层绝大部分的问题已经被前人解决了,用现在的话来说,这部分技术“固化”了。为了证实我的观点,我找来了去年刚出版的《腾讯游戏开发精粹Ⅱ
2022年1月31日
其他

周刊(第2期):从笔记软件谈被体制化

》[10],由87版《西游记》玉兔精扮演者李玲玉出演,每一集大概5分钟,主题涉及反诈骗、广场舞、互联网等大妈日常的生活话题,轻松搞笑,在临近春节的时间点,倒是不错的能和父母一起看的剧。引用链接[1]
2022年1月24日
其他

周刊(第1期):开刊,数字化生活数据

另外,写周刊还有一个好处,之前有很多想法、念头会散落在各种社交平台这样的新媒体上,实际对整理、汇总、索引等也不好,把念头汇之于文章,用这种更老式的传播方式可以更好得记录下来。
2022年1月17日
其他

sqlite3.36版本 btree实现(零)- 起步及概述

已经在数据库领域沉浸多年,前阿里云数据库内核组早期成员、前青云数据库团队负责人。现在数据库领域创业,公司的项目是:datafuselabs/databend[13],欢迎围观。•博客地址:[
2022年1月11日
其他

etcd 3.5版本的joint consensus实现解析

概述在以前的etcd实现中,“集群节点变更”这一功能,仅支持每次变更一个节点,最新的etcd已经能支持一次变更多个节点配置的功能了。本文将就这部分的实现进行解析。原理Raft论文《CONSENSUS:
其他

为什么Raft协议不能提交之前任期的日志?

概述在Raft大论文中3.6.2中,有一个细节“不允许提交之前任期的日志”,之前看了几次都理解的不够准确,把这部分内容展开阐述一下。问题还是先从论文的图例开始解释,如下图:需要特别说明的是,图例中演示的是“如果允许提交之前任期的日志,将导致什么问题”,这是大前提,这个前提条件后面会反复强调。有了这个前提,下面展开图中的步骤讨论:•(a)
2021年10月18日
其他

Etcd Raft库的日志存储

3;}•type:记录的类型,下面解释。•crc:后面data部分数据的crc32校验值。•data:数据部分,根据类型的不同有不同格式的数据。记录数据的类型如下:const
其他

boltdb 1.3.0实现分析(一)

1.3.0对其实现进行分析。boltdb是etcd系统存储数据使用的KV嵌入式DB,使用Go编码实现,内部是一个B+树结构体。关于etcd、raft协议以及B+树,可以参考之前的文章:
其他

服务调用的演进历史

这是2019年给组内分享时整理的一篇服务调用演进历史的科普文,微信公众号上首发于”高可用架构“公众号,做了一些调整重新发在我自己的公众号,最下方的”阅读原文“可以跳转到博客原文。写作本文的时候,我自己最大的感受是:如果能清楚理解演化历史中的一些原则和思路,就会发现现在的变化并不新鲜。它们不是今天才有,也不会止于今天的演化。在技术大发展的今天,更多的关注本质才能让我们不至于在变化中失去方向。本文专注于服务调用演化过程中每一步的为什么(Why)和是什么(What)上面,尽量不在技术细节(How)上面做太多深入。服务的三要素一般而言,一个网络服务包括以下的三个要素:地址:调用方根据地址访问到网络接口。地址包括以下要素:IP地址、服务端口、服务协议(TCP、UDP,etc)。协议格式:协议格式指的是该协议都有哪些字段,由接口提供者与协议调用者协商之后确定下来。协议名称:或者叫协议类型,因为在同一个服务监听端口上面,可能同时提供多种接口服务于调用方,这时候需要协议类型(名称)来区分不同的网络接口。需要说明在服务地址中:IP地址提供了在互联网上找到这台机器的凭证。协议以及服务端口提供了在这台机器上找到提供服务的进程的凭证。这都属于TCPIP协议栈的知识点,不在这里深入详述。下图中,以最简单的一个HTTP请求,来拆解请求URL中的服务要素:http-request其中:http:指明使用的是哪种应用层协议,同类型的还有“https”、“ftp”等。www.abc.com:域名地址,最终会由DNS域名解析服务器解析成数字的IP地址。8080:前面解析成数字化的IP地址之后,就可以访问到具体提供服务的机器上,但是上面提供服务的进程可能有很多,这时候就需要端口号来告诉协议栈到底是访问哪个进程提供的服务了。hello:该服务进程中,可能提供多个接口供访问,所以需要接口名+协议(即前面的http)告诉进程访问哪个协议的哪个接口。msg=world:不同的接口,需要的参数不同,最后跟上的查询参数(query
2020年6月23日
其他

B树、B+树索引算法原理(下)

这一段时间由于在阅读boltdb代码的缘故,找机会学习了B树及B+树的算法原理,这个系列会花两个篇幅分别介绍这两种数据结构的实现,其用于数据库索引中的基本原理。
2020年6月19日
其他

B树、B+树索引算法原理(上)

在这种情况中,如果待删除数据并不存在于节点X中,那么找到必然包含该数据的X的子节点X.child[i](假设为节点Y),如果这时候Y节点数据数量不满足t这个条件,则需要用下面两种方式进行重平衡。
2020年6月12日
其他

如何阅读一份源代码?(2020年版)

比如,你如果现在刚接手某个项目,需要简单的了解一下项目,可以先阅读代码了解都有哪些核心数据结构。理解了之后,如果不清楚某些情景下的流程,可以使用情景分析法。总而言之,交替进行直到解答你的疑问为止。
其他

glog C++版本代码分析

}}可以看到,对某个级别的日志而言,会依次调用级别值更小的日志输出,比如WARN级别的日志也会输出到INFO级别的日志文件中。接着看MaybeLogToLogfile函数的实现:inline
2020年5月20日
其他

IM服务器设计-基础

DB中,这部分消息将根据消息的发送者ID水平扩展。c2g模块从cache中查询该群组的用户ID,如果查不到会到存放群组信息的DB中查询。遍历获取到的群组ID,保存消息到RecvMsg
其他

Leveldb代码阅读笔记

group),每个记录组内部又由多条记录(record)组成。在同一个记录组内部,以本组的第一条数据的键值做为公共前缀,后续的记录数据键值部分只存放与公共前缀非共享部分的数据即可。
其他

zeromq所谓的“无锁消息队列”实现

写入一个元素a,同时incomplete为true,意味着写入还未完成,所以并没有更新flush指针,_w指针也没有在这个函数中被更新,因此当incomplete为true时不会更新上面的四个指针。
2020年2月25日
其他

Etcd存储的设计

revision的数据进行了删除,此时由于generations[0]已经没有数据了,所以这一整个generation被删除,原先的generations[1]变成了generations[0]。
2020年2月14日
其他

etcd Raft库解析

进行选举的节点,它的日志是更新的,条件为:logterm比本节点最新日志的任期号大,在两者相同的情况下,消息的index大于等于当前节点最新日志的index,即总要保证该选举节点的日志比自己的大。
2020年2月10日
其他

Raft算法原理

因此在成为leader节点之后首次向follower节点同步日志数据时,将复制索引位置在10以后的日志数据,同时带上日志二元组告知follower节点当前leader保存的follower日志状态。
其他

C++11中的内存模型下篇 - C++11支持的几种内存模型

assert(z.load()!=0);}上面这个例子中,并不能保证程序最后的断言即z!=0为真,其原因在于:在不同的线程中分别针对x、y两个变量进行了同步操作并不能保证x、y变量的读取操作。
2020年1月26日
其他

C++11中的内存模型上篇 - 内存模型基础

前段时间花了些精力研究C++11引入的内存模型相关的操作,于是把相关的知识都学习了一下,将这个学习过程整理为两篇文档,这是第一篇,主要分析内存模型的一些基础概念,第二篇展开讨论C++11相关的操作。
2020年1月21日
其他

线上存储服务崩溃问题分析记录

上周我们的存储服务在某个线上项目频繁出现崩溃,花了几天的时间来查找解决该问题。在这里,将这个过程做一下记录。加入调试信息由于问题在线上发生,较难重现,首先想到的是能不能加上更多的信息,在问题出现时提供更多的解决思路。首先,我们的代码里,在捕获到进程退出的信号比如SIGABRT、SIGSEGV、SIGILL等信号时,会打印出主线程的堆栈,用于帮助我们发现问题。但是在崩溃的几次情况中,打印出来的信息并不足以帮助我们解决问题,因为打印的崩溃堆栈只有主线程,猜测是不是在辅助线程中发生的异常,于是采取了两个策略:ulimit命令打开线上一台服务器的coredump,当再次有崩溃发生时有core文件产生,能够帮助发现问题。加入了一些代码,用于在崩溃的时候同时也打印出所有辅助线程的堆栈信息。在做这两部分工作之后,再次发生崩溃的情况下,辅助线程的堆栈并无异常,core文件由于数据错乱也看不出来啥有用的信息来。复现问题由于第一步工作受挫,接下来我的思路就在考虑怎么能在开发环境下复现这个问题。我们的存储服务在其他项目上已经上线了有一段时间了,但是并没有出现类似的问题。那么,出现问题的项目,与其他已经上线的服务有啥不同,这里也许是一个突破口。经过咨询业务方,该业务的特点是:单条数据大:有的数据可能有几KB,而之前的项目都只有几百字节。读请求并发大,而其他业务是写请求远大于读请求。由于我们的存储服务兼容memcached协议,出现问题时也是以memcached协议进行访问的,所以此时我的考虑是找一个memcached压测工具,模拟前面的数据和请求特点来做模拟压测。最后选择的是twitter出品的工具twemperf,其特点是可以指定写入缓存的数据范围,同时还可以指定请求的频率。有了这个工具,首先尝试了往存储中写入大量数据量分布在4KB~10KB的数据,此时没有发现服务有core的情况出现。然后,尝试构造大量的读请求,果然出现了core情况,重试了几次,都能稳定的重现问题了。有了能稳定重现问题的办法,总算给问题的解决打开了一个口子。首次尝试此时,可以正式的在代码中查找问题的原因了。来大概说明一下该存储服务的架构:主线程负责接收客户端请求,并且进行解析。如果是读请求,将分派给读请求处理线程,由这个线程与存储引擎库进行交互,查询数据。此时该线程数量配置为2。存储引擎库负责存储落地到磁盘的数据,类似leveldb,只不过这部分是我们自己写的存储引擎。在读线程从存储引擎中查询数据返回后,将把数据返回给主线程,由主线程负责应答客户端。在这几步中,第1和第4步是在主线程中进行的,第2和第3步是在读存储引擎线程中进行的。在这个过程中,如果同一个客户端有多个读请求,那么只有按照这四步在处理完毕一个读请求之后,才会继续从该客户端中取出下一个请求进行处理。在几次重现问题的过程中,发现出错的都是在第2步和第4步中,该请求客户端的数据结构某些成员出现了错乱,即要访问的指针地址已经无效了,导致的错误。指针无效,一般来说有两种可能性:被无效地址覆盖了这个指针。指针已经被释放的情况下继续使用。当时尝试把一些错误的指针地址打印出来,发现有几次都是是字符串“pcm”的16进制表示,当时在想这个特殊的字符串到底是什么,百思不得其解的时候,一位曾经使用过mcperf工具的同事,想起来mcperf做压测时的key就是"mcp"开头的,而因为是小端方式,所以如果使用这个类型的字符串,去覆盖指针,那么就变成了"pcm"。我们很快验证了这个说法,mcperf确实是以这个为前缀来写入数据的。此时,猜测问题的原因在于:当读存储引擎线程去访问存储引擎时,某些错误导致从存储引擎读出来的数据,将客户端请求数据写乱,从而导致了崩溃。由于同时有两个读存储引擎的线程,猜测这里是不是因为多线程访问出了问题,导致的错误呢?为了验证这个问题,最简单的办法就是将线程数量改成1,重新用mcperf试了几次,确实没有再次出现问题。此时已经是周五,我们缓了一口气,打算以此修改暂时上测试环境利用周末的时间观察一下情况。柳暗花明前面提到过,猜测问题出现的原因,是多线程访问存储引擎时将某个数据写错乱了,导致其中的指针无效。clang和gcc
2017年6月14日