• [技术干货] 【Rollbacked】华为云TaurusDB性能挑战赛参赛总结
    缘起TaurusDB是华为云自主研发的最新一代云原生分布式数据库,完全兼容mysql8.0,采用计算与存储分离、日志即数据的架构设计,支持1写15读,性能达到原生Mysql的7倍。Taurus构建在共享分布式存储上,存储空间最高达128T,能跨AZ部署,具有可靠、高性能、易伸缩等特性。华为云TaurusDB性能挑战赛是由华为云主办的数据库领域顶级赛事,大赛将综合科研教学成果及商业领域需求,探索数据库领域的技术问题的可行性,为需求方和开发者提供联接的桥梁;并联合合作伙伴,搭建一个技术交流、人才培养、机遇共创数据库开发者平台和生态。听起来很激动人心吧,关键是奖金也很多, 同时还能刷脸! 最早是听一个同事说起的,恰好本人的工作也是“新一代数据库”开发,看着这么多人工智能的比赛不能参加,只能后悔自己选错了专业,但是机会来了,终于有自己擅长的领域了,心想不能错过这次机会,一边默默加了收藏。比赛内容一句话概括,就是实现一个kvstore,初赛是单机引擎,复赛要求存储计算分离,并且kv都是定长的,如果通用性做得好,可以直接用作块存储。题目初赛和复赛稍有区别,这里只说复赛的题目。存储引擎K,V都是定长的,key 8bytes, value 4K bytes。 测试分为三轮,第一轮, 16个线程写,每个线程写400万次;第二轮,16个线程读,每个线程按顺序读取400万次;第三轮,16个线程读,每个线程逆序读取400万次。第三轮的逆序是局部随机,总体逆序,也即在10M的数据范围内随机读,然后以10M为单位逆序推进。 成绩就是看总时间,时间越短越好。前面已经说过,初赛要实现的是单机引擎,复赛要实现的是存储计算分离的引擎,最大的限制是计算节点不能持久化任何数据。预选赛不得不说,华为可真会玩,这次预选赛竟然还要做题,并且是必须要学习他们的资料才能通过的题目,题目包括数据库的基本常识,但是也有产品介绍,经过深夜两个小时的学习,我算是第一次知道了这么多种不同的产品究竟是干嘛的。第一次答题还错了一道,以防万一,又做了一遍,这次错了两道,算了不玩了,95分也够了,睡觉去。但是心里总是不舒服,究竟是哪错了,又看了一遍,原来是幻读的理解这道题错了。初赛不出意外,预选赛顺利通过,初赛就开始写代码了,初赛只要写对差不多就可以晋级复赛。 当然任何一个程序员都不会满足刚好够用的状态,因此正常能想到的优化都加上了: 比如Direct IO, 多文件做数据分区,引入写buffer,比较频繁修改元数据,读取使用自己实现的page cache,以免4K读不能打满磁盘带宽。复赛复赛要求存储计算分离, 我认真做了一些分析,同时也对他们的硬件做了测试,分析下来,这个比赛比拼的点和我平时在工作中要追求的还是很不一样的。 平时无论做什么系统,都是在延迟差不多的情况下,把吞吐量做高,这次比赛最关键的一个点是:延迟第一,延迟是最重要的,特别是写的阶段,因为并发只有16,想通过聚合换吞吐量都不好使了。首先考虑持久化方法:4K恰好能对齐IO,所以key和value要分开存储,      想使用rocksdb之类的存储引擎即使不被禁止也是没有竞争力的。value非常随机,基本不用考虑压缩,就算有一点点好处,实现起来也太复杂了。再次考虑如何优化IO:SSD的IO吞吐量高于4K * iops, 不管是读还是写,IO聚合是必须的。单文件会遇到文件系统瓶颈,需要多文件, 也即要对数据做partition。关于同步IO还是异步IO的选择, 因为延迟优先,所以应该选同步IO。最后考虑网络框架:首先,我直接放弃考虑任何已有的网络框架,因为这是benchmark, 再小的开销也是开销,都会让程序变慢。其次,实际上任何IO      multiplexing的框架都会造成额外的延迟,不仅复杂,也是得不尝失的。综上,我决定就使用最简单的阻塞式IO来做socket通信。总体设计计算节点和存储节点分工, 关键就是索引维护在哪?经过思考, 决定索引维护在计算节点。 索引的内容是: key -> <file_id, pos>, 索引在计算节点的内存中维护为hash表。因为build索引大概需要400ms,写入再读取index比重新构建一遍index时间还长, 所以索引不需要持久化, 这其实是一个反直觉的决定。 计算节点在发送第一个读请求之前,会从存储节点把所有写入的key和pos发送过来, 然后由计算节点构造索引。存储节点起16个线程,listen在16个不同的端口。计算节点也会有16个线程, 每个计算节点的线程只会连接一个存储节点的端口,从而和一个存储节点线程通信。写入请求:写入过程不同的线程完全独立,每个线程负责一部分数据。请求发到存储节点后由接受请求的线程写入。读取过程:读取过程每个存储线程会读取任意一个线程写入的数据。由计算节点指定要读取那个分区的数据。也即写请求发到哪个存储线程,就由那个存储线程写入,每个存储线程实际上就对应了一个数据分区。 读请求发到一个存储线程,它可能要跨线程读取其它分区的数据。存储文件存储节点把数据分为16个partition,每个partition由一个线程负责写入。 每个partition共三个文件: 之所以有三个文件,是因为首先key和value要分开存储,这就要用两个文件;其次为了优化写,额外引入了文件做写入buffer。文件命名规则如下, 以第一个分区为例:00.k: 保存写入的key。 fallocate 4M * 8B, mmap到内存。00.v:      保存写入的value。fallocate 4M *      4K, DIO读写。00.b:      用作写入buffer。fallocate 16K      buffer + 4K 元数据,mmap到内存。写入原子性先写key和value,再更新key count。 key count改成功,则写成功;否则写失败,下次重启进程当作这次写没发生过。换句话说,key count改成功实际上表示这次写入commit成功。key count记录在00.k的第一个8字节, 如前所诉, 00.k是mmap到内存的,所以更新key count是没有什么代价的。value先记录到写buffer里,然后批量刷到00.v文件。key file内容如下:key countk1k2k3k4k5k6……value file内容如下:value countv1v2v3v4v5v6……buffer file内容如下:b0b1b2b3…………b15flushed poswrite buffer也是mmap到内存的, 前64K记录数据。 紧跟64K的8个字节记录flushed pos。 因为mmap必须以page为单位,所以实际内存占用64K + 4K。 它实际上是一个mmap持久化的ring buffer。 Ring Buffer的元数据包括filled pos和flushed pos。 filled pos由key count可以算出来, 所以不需要单独再记。build indexbuild index实际上是构造一个key -> offset的hash表。 要在400ms内完成build index,难点是如何并行:基本思路是把key做partition,每个partition内独立构造hash。这本质上是一个MapReduce的过程。map阶段: 并行划分range, 16个线程,每个线程负责处理一个key文件。reduce阶段: 并行构造hash, 16个线程,每个线程处理一个range。线程同步: 第二阶段开始之前要等第一全部完成,也即两个Stage之间是个Barrier, 这个同步过程和map reduce的shuffle是类似的。读取cache的实现cache是value的镜像,value文件分成了16个,cache也对应分成16组。 因为顺序和热点访问模式对cache都很友好,cache不需要特别大,每一组cache大小为64M。为了应对热点读,cache的最小单元设为16M,借用CPU cache的术语,我把它叫cache line,这是一个很大的值。 cache的内存因为是定长的,所以通过mmap一次申请好,不需要动态分配。 cache的索引本质是一个hash,但是不用解决冲突, 用file offset直接计算得到索引的下标。对16组cache中的每一组来说,有4个cache line, 每个cache line有三种状态,用一个uint32_t表示:Invalid: UINT32_MAXLocked:      UINT32_MAX - 1Valid:      file_offset »12cache line的状态被叫做indexStat,它被单独记在另外一个小数组里。读取不用加锁,使用double check即可:读取之前检查IndexStat有效,并且offset和我要读取的内容匹配。拷贝cache line的内容到私有buffer。拷贝完cache line的内容后再检查IndexStat是否发生过变化, 如果indexStat没有变化过,则说明我们拷贝的cache      line内容是对的。为了让上述double check生效, 更新cache的线程需要在写入之前把indexStat改为Locked,更新完成后再把indexStat改为有效值。关于网络通信关于网络框架还有一个问题,就是是否有必要用UDP,犹豫之下,觉得UDP还是会更复杂,稳妥起见,选择了TCP。后来得知在测试环境里UDP是不同的,也就释然了。但是TCP看起来开箱即用,用起来却暗藏机关,至少要调整以下三个参数:tcp_nodelaytcp_quickacksend/recv      buf最后为了规避TCP的流控,我们要避免另外两个坑:避免发送太快,超过交换机的队列长度,从而导致丢包,因为TCP的工作方式就是,只要你给它数据,它就会不停的加大,发送窗口,直到发生丢包,然后触发限流。要避免这种情况,本质是要限制TCP的连接数, 因为只要连接数限制住了,发送窗口总长度也就限制住了。避免TCP”冷却”之后重新进入慢启动状态,如果有root权限,是可以通过sysctl关闭slow start的,但是我们没法控制系统参数,这就要求我们一个连接最好要不停的发包,不要停下来。有人定义了packet格式,而我遵循一切从简的原则,没有实现通常RPC要有的功能:没有定义pcode,因为整个通信过程就只有读和写两种request,每一个socket只用来发送一种类型的request,如果要发送不同的request,只需要切换socket即可。没有实现序列化,因为request很简单,只需要把结构体直接发送到socket即可。细节决定性能细节也就是很多关于性能的小点:首先关于文件IO: 同步IO比异步IO延迟要小; open的时候加入O_NOATIME避免读取操作也修改元数据其次关于内存分配: 分配完内后提前触发page fault至少可以让性能更稳定,如果不能提高性能的话;      分配内存可以尝试hugepage, 失败之后再使用4K page; mmap的内存可以不用清零最好关于CPU, 绑核,通用可以让延迟更稳定, 理论上对性能是有提升的。正确性测试如前所属,我基本上是一切从简,但即使如此,提交了好多次,都通不过正确性测试。为了排查问题,专门写了一个随机的验证程序,这个比官方的测试强度高多了。确实发现了一个readv之后更新iovec的bug,还有一处cache的bug。 事后看来,在这个测试上花的时间非常值得,否则我可能到最后都没有成绩。总结这次比赛感觉有点像马拉松,听完大家的分享,感觉每个人的时间都比较有限,大家都是争分夺秒,每个人都有一些优化没来得及试。本人最大的一个遗憾是读预取没有实现。另外一个是写的过程中网络和IO并行化做的不好。最后听了第一名的方案,深有体会,要想拿到最好的成绩,需要把写和读分开考虑,同时把网络传输和本地IO分开考虑。单纯从工程上说,我的代码有一些特有的风格, 简单来说就是总爱重复造轮子,不愿意引入依赖:没有用pthread mutex/cond,全部用atomic      ops + futex没有spin,等待的地方都有睡眠和唤醒机制另外,本人虽然工作中用C++, 但是我情愿用plain old C, 所以我很少用高级C++特性。如果未来还做这种系统实现的比赛,希望有可以利用RDMA的和NVM的比赛, 毕竟,新硬件总是有更多的可能。当然比赛的设置要考虑引入更多自由度,这样会更加有趣。 
  • [赛事公告] 【Kirito】华为云 TaurusDB 性能挑战赛赛题总结
    1 前言回顾第一次参加性能挑战赛—第四届阿里中间件性能挑战赛,那时候真的是什么都不会,只有一腔热情,借着比赛学会了 Netty、学会了文件 IO 的最佳实践,到了这次华为云举办的 TaurusDB 性能挑战赛,已经是第三次参加比赛了,同时也是最“坎坷”的一次比赛。经过我和某位不愿意透露姓名的 96 年小迷妹的不懈努力,最终跑分排名为第 3 名。如果要挑选一个词来概括这次比赛的核心内容,那非”计算存储分离“莫属了,通过这次比赛,自己也对计算存储分离架构有了比较直观的感受。为了比较直观的体现计算存储分离的优势,以看电影来举个例子:若干年前,我总是常备一块大容量的硬盘存储小电影,但自从家里带宽升级到 100mpbs 之后,我从来不保存电影了,要看直接下载/缓冲,基本几分钟就好了。这在几年前还不可想象,如今是触手可及的事实,归根到底是随着互联网的发展,网络 IO 已经不再是瓶颈了。计算存储分离架构相比传统本地存储架构而言,具有更加灵活、成本更低等特性,但架构的复杂性也会更高,也会更加考验选手的综合能力。计算存储分离架构的含义:存储端有状态,只存储数据,不处理业务逻辑。计算端无状态,只处理逻辑,不持久化存储数据。2 赛题概览比赛整体分成了初赛和复赛两个部分,初赛要求实现一个简化、高效的本地 kv 存储引擎,复赛在初赛的基础上增加了计算存储分离的架构,计算节点需要通过网络传输将数据递交给存储节点存储。计算节点和存储节点共用上述的接口,评测程序分为 2 个阶段:正确性评测此阶段评测程序会并发写入随机数据(key 8B、value 4KB),写入数据过程中进行任意次进程意外退出测试,引擎需要保证异常中止不影响已经写入的数据正确性。异常中止后,重启引擎,验证已经写入数据正确性和完整性,并继续写入数据,重复此过程直至数据写入完毕。只有通过此阶段测试才会进入下一阶段测试。性能评测随机写入:16 个线程并发随机写入,每个线程使用 Set 各写 400 万次随机数据(key 8B、value 4KB)顺序读取:16 个线程并发按照写入顺序逐一读取,每个线程各使用 Get 读取 400 万次随机数据 热点读取:16 个线程并发读取,每个线程按照写入顺序热点分区,随机读取 400 万次数据,读取范围覆盖全部写入数据。热点的逻辑为:按照数据的写入顺序按 10MB 数据粒度分区,分区逆序推进,在每个 10MB 数据分区内随机读取。随机读取次数会增加约 10%。语言限定CPP & Java,一起排名3 赛题剖析初赛主要是文件 IO 和存储架构的设计,实现一个简单的本地KV存储引擎,如果对文件 IO 常识不太了解,可以先行阅读 《文件IO操作的一些最佳实践》。大赛的重头戏基本是在复赛网络通信的比拼上,在引入计算/存储分离架构后,计算节点无状态,计算节点和存储节点需要通过网络交互,网络将引入大于服务器内部的时延,因此,结合应用场景的良好架构和缓存设计,将是此类引擎设计的一个重点。 3.1 架构设计计算节点只负责生成数据,在实际生产中计算节点还承担额外的计算开销,由于计算节点是无状态的,所以不能够聚合数据写入、落盘等操作,但可以在 Get 触发网络 IO 时一次读取大块数据用作缓存,减少网络 IO 次数。存储节点负责存储数据,考验了选手对磁盘 IO 和缓存的设计,可以一次使用缓存写入/读取大块数据,减少磁盘 IO 次数。所以选手们将会围绕网络 IO、磁盘 IO 和缓存设计来设计整体架构。3.2 正确性检测赛题明确表示会进行 kill -9 并验证数据的一致性,正确性检测主要影响的是写入阶段。存储节点负责存储数据,需要保证 kill -9 不丢失数据,但并不要求断电不丢失,这间接地阐释了一点:我们可以使用 PageCache 来做写入缓存;正确性检测对于计算节点与存储节点之间通信影响便是:每次写入操作都必须 ack,所以选手必须保证同步通信,类似于 ping/pong 模型。3.3 性能评测性能评测由随机写、顺序读、热点读(随机读取热点数据)三部分构成。随机写阶段与 PolarDB 的评测不同,TaurusDB 随机写入 key 的 16 个线程是隔离的,即 A 线程写入的数据只会由 A 线程读出,可以认为是彼此独立的 16 个实例在执行评测,这大大简化了我们的架构。顺序读阶段的描述也很容易理解,需要注意的是这里的顺序是按照写入顺序,而不是 Key 的字典序,所以随机写可以转化为顺序写,也方便了选手去设计顺序读的架构。热点读阶段有点故弄玄虚了,其实就是按照 10M 数据为一个分区进行逆序读,同时在 10M 数据范围内掺杂一些随机读,由于操作系统的预读机制只会顺序预读,无法逆序预读,PageCache 将会在这个环节会失效,考验了选手自己设计磁盘 IO 缓存的能力。4 架构详解4.1 全局架构计算存储分离架构自然会分成计算节点和存储节点两部分来介绍。计算节点会在内存维护数据的索引表;存储节点负责存储持久化数据,包括索引文件和数据文件;计算节点与存储节点之间的读写都会经过网络 IO。4.2 随机写架构随机写阶段,评测程序调用计算节点的 set 接口,发起网络 IO,存储节点接收到数据后不会立刻落盘,针对 data 和 index 的处理也会不同。针对 data 部分,会使用一块缓冲区(如图:Mmap Merge IO)承接数据,由于 Mmap 的特性,会形成 Merge File 文件,一个数据缓冲区可以聚合 16 个数据,当缓冲区满后,将缓冲区的数据追加到数据文件后,并清空 Merge File;针对 index 部分,使用 Mmap 直接追加到索引文件中。F: 1. data 部分为什么搞这么复杂,需要聚合 16 个数据再刷盘?Q: 针对此次比赛的数据盘,实测下来 16 个数据刷盘可以打满 IO。F: 2. 为什么使用 Mmap Merge IO 而不直接使用内存 Merge IO?Q: 正确性检测阶段,存储节点可能会被随机 kill,Mmap 做缓存的好处是操作系统会帮我们落盘,不会丢失数据F: 3. 为什么 index 部分直接使用 Mmap,而不和 data 部分一样处理?Q: 这需要追溯到 Mmap 的特点,Mmap 适合直接写索引这种小数据,所以不需要聚合。4.3 热点读&顺序读架构热点读取阶段 & 顺序读取阶段 ,这两个阶段其实可以认为是一种策略,只不过一个正序,一个逆序,这里以热点读为例介绍。我们采取了贪心的思想,一次读取操作本应该只会返回 4kb 的数据,但为了做预读缓存,我们决定会存储节点返回 10M 的数据,并缓存在计算节点中,模拟了一个操作系统预读的机制,同时为了能够让计算节点精确知道缓存是否命中,会同时返回索引数据,并在计算节点的内存中维护索引表,这样便减少了成吨的网络 IO 次数。4.4 存储设计站在每个线程的视角,可以发现在我们的架构中,每个线程都是独立的。评测程序会对每个线程写入 400w 数据,最终形成 16 * 16G 的数据文件和 16 * 32M 左右的索引文件。数据文件不停追加 MergeFile,相当于一次落盘单位是 64K(16 个数据),由于自行聚合了数据,所以可以采用 Direct IO,减少操作系统的 overhead。索引文件由小数据构成,所以采用 Mmap 方式直接追加写计算节点由于无状态的特性,只能在内存中维护索引结构。4.5 网络通信设我们都知道 Java 中有 BIO(阻塞 IO)和 NIO(非阻塞 IO)之分,并且大多数人可能会下意识觉得:NIO 就是比 BIO 快。而这次比赛恰恰是要告诉大家,这两种 IO 方式没有绝对的快慢之分,只有在合适的场景中选择合适的 IO 方式才能发挥出最佳性能。稍微分析下这次比赛的通信模型,写入阶段由于需要保证每次 set 不受 kill 的影响,所以需要等到同步返回后才能进行下一次 set,而 get 本身依赖于返回值进行数据校验,所以从通信模型上看只能是同步 ping/pong 模型;从线程数上来看,只有固定的 16 个线程进行收发消息。以上两个因素暗示了 BIO 将会非常契合这次比赛。在很多人的刻板印象中,阻塞就意味着慢,非阻塞就意味着快,这种理解是完全错误的,快慢取决于通信模型、系统架构、带宽、网卡等因素。我测试了 NIO + CountDownLatch 和 BIO 的差距,前者会比后者整体慢 100s ~ 130s。5 细节优化点5.1 最大化磁盘吞吐量但凡是涉及到磁盘 IO 的比赛,首先需要测试便是在 Direct IO 下,一次读写多大的块能够打满 IO,在此基础上,才能进行写入缓冲设计和读取缓存设计,否则在这种争分夺秒的性能挑战赛中不可能取得较好的名次。测试方法也很简单,如果能够买到对应的机器,直接使用 iostat 观察不同刷盘大小下的 iops 即可,如果比赛没有机器,只能祭出调参法,不停提交了,这次 TaurusDB 的盘实测下来 64k、128K 都可以获得最大的吞吐量。5.2 批量回传数据计算节点设计缓存是一个比较容易想到的优化点,按照常规的思路,索引应该是维护在存储节点,但这样做的话,计算节点在 get 数据时就无法判断是否命中缓存,所以在前文的架构介绍中,我们将索引维护在了计算节点之上,在第一次 get 时,顺便恢复索引。批量返回数据的优势在于增加了缓存命中率、降低总网络 IO 次数、减少上行网络 IO 数据量,是整个比赛中分量较重的一个优化点。5.3 流控在比赛中容易出现的一个问题,在批量返回 10M 数据时经常会出现网络卡死的情况,一时间无法定位到问题,以为是代码 BUG,但有时候又能跑出分数,不得以尝试过一次返回较少的数据量,就不会报错。最后还是机智的小迷妹定位到问题是 CPU 和 IO 速率不均等导致的,解决方案便是在一次 pong 共计返回 10M 的基础上,将报文拆分成 64k 的小块,中间代入额外的 CPU 操作,最终保证了程序稳定性的同时,也保障了最佳性能。额外的 CPU 操作例如:for(int i=0;i<700;i++),不要小看这个微不足道的一个 for 循环哦。流控其实也是计算存储分离架构一个常见设计点,存储节点与计算节点的写入速度需要做一个平衡,避免直接打垮存储节点,也有一种”滑动窗口“机制专门应对这种问题,不在此赘述了。5.4 预分配文件在 Cpp 中可以使用 fallocate 预先分配好文件大小,会使得写入速度提升 2s。在 Java 中没有 fallocate 机制,但是可以利用评测程序的漏洞,在 static 块中事先写好 16 * 16G 的文件,同样可以获得 fallocate 的效果。5.5 合理设计索引结构get 时需要根据 key 查询到文件偏移量,这显示是一个 Map 结构,在这个 Map 上也有几个点需要注意。以 Java 为例,使用 HashMap 是否可行呢?当然可以,但是缺点也很明显,其会占用比较大的内存,而且存取性能不好,可以使用 LongIntHashMap 来代替,看过我之前文章的朋友应该不会对这个数据结构感到陌生,它是专门为基础数据类型设计的 Map 容器。每个线程 400w 数据,每个线程独享一个索引 Map,为了避免出现扩容,需要合理的设置扩容引子和初始化容量:new LongIntHashMap(410_0000, 0.99)。5.6 Direct IO最终进入决赛的,有三支 Java 队伍,相比较 Cpp 得天独厚的对操作系统的灵活控制性,Java 选手更像是带着镣铐在舞蹈,因为有过参赛经验,我提前封装好了 Java 的 Direct IO 类库:https://github.com/lexburner/kdio,相比 FileChannel,它能够使得磁盘 IO 效率更高。得知有 Java 选手真的在比赛中使用了我的 Direct IO 类库,也是比赛中实实切切的乐趣之一。6 失败的优化点6.1 预读线程先行考虑到网络 IO 还是比本地磁盘 IO 要慢的,一个本以为可行的方案是单独使用预读线程进行存储节点的磁盘 IO,设计一个 RingBuffer,不断往前预读,直到环满,计算阶段 get 时会消费 RingBuffer 的一格缓存,从而使得网络 IO 和磁盘 IO 不会相互等待。实际测试下来,发现瓶颈主要还是在于网络 IO,这样的优化徒增了不少代码,不利于进行其他的优化尝试,最终放弃。6.2 计算节点聚合写入缓冲既然在 get 阶段时存储节点批量返回数据给计算节点可以提升性能,那 set 阶段聚合批量的数据再发送给存储节点按理来说也能提升性能吧?的确如此,如果不考虑正确性检测,这的确是一个不错的优化点,但由于 kill 的特性使得我们不得不每一次 set 都进行 ACK。但是可以将 4/8/16 个线程编为一组进行聚合,通过调整参数来确定该方案是否可行。然而事与愿违,该方案并没有取得成效。7 聊聊比赛吧之前此类工程性质的性能挑战赛只有阿里一家互联网公司承办过,作为热衷于中间件性能优化的参赛选手而言,非常高兴华为也能够举办这样性质的比赛。
  • [技术干货] 【ADSL】华为云TaurusDB性能挑战赛参赛总结
    赛题分析赛题要求一个无状态的计算节点和一个存储节点,并要求保证程序崩溃时的数据完整性,所以当计算节点写入时必须等待存储节点的 ack,而存储节点需快快确保数据写入到 page cache。赛题说明 16 个线程,每个线程写入 4 百万条记录,按写入顺序读取及热点区间读取,所以数据按写入线程分离存储。此外 Key 大小固定 8B,value 大小固定 4KB,所以需要批量写入才能打满带宽,并且索引只需要保存位置序号就可以,减小了索引的大小。整体架构针对赛题的要求,可以把问题分解成三个模块来看,分别是计算节点,存储节点和网络通信,因此我们设计了如下图所示的总体结构。在存储节点上,数据分为 16 组存储,按照写入线程分组,保证同一线程的数据写入到同一分组,这样在读取阶段不论是按写入顺序读取还是热点区间读取都可以一次从一个分组中读取一整块 cache,降低网络开销,另外这样并发管理也比较简单,因为一个线程只会对应到一个文件,所以读写的时候是不需要对分组加锁的。具体到到每个分组内部,key 和 value 是分离存储的,因为 key 和 value都是定长的,kv 分离以后恢复索引时只需要读取 key 文件就够了。最后存储节点会保证一旦写入成功数据就不会再丢失,也就是可以容忍 kill -9 退出。我们把索引放在计算节点,并且使用 TCP 进行节点间通信。由于并发数目不高,所以计算节点初始化时创建 16 条 tcp 连接,每个前台线程对应一条连接,在存储节点为每个 tcp 连接创建一个新线程。数据在写入时首先由计算节点发送写请求到存储节点,存储节点收到请求并确认数据成功写入不会丢失后,会把存储的位置回复给计算节点,然后计算节点把位置代入内存索引后返回。读取时计算节点首先根据内存索引判断数据在不在 cache pool 里,如果不在就去向存储节点读取一块新的 cache,然后从 cache 中读数据。存储节点之前我们也提到存储节点按写入线程分组,每个分组里包含四类文件,value data 文件保存 value,key data文件保存 key,两者各有一个 mmap buffer 文件,用来将写入 batch 到一起再刷盘。然后有一个全局的 mmap meta file,记录各个分组当前的数据长度,也就是各分组分别有多少个 kv 对,主要用于数据恢复和重建索引。数据写入时直接将key和value写入对应分组的mmap buffer,两者都写入成功后更新 meta file。只有当 mmap buffer 写满了以后才会 flush 到磁盘中,并且 flush 操作都是用 directIO 进行的。采用这种设计使得写入均是内存操作,将数据写入到 mmap 管理的 page cache 中即可返回,并且我们对mmap使用了mmap_lock标记使其一直锁定在内存中,只有当程序退出时才会刷盘。每次向data文件刷盘都是以一个 buffer 为单位,这样可以最大化利用 nvme 磁盘带宽,采用 directIO 的方式刷盘可以跳过 page cache,一方面减少了一次内存复制,另一方面降低了阶段切换时清空 page cache 的时间。而 mmap 的机制保障了即使进程意外退出,操作系统也会让 mmap buffer 中的数据安全落盘,不会引起数据丢失。我们实际为每个 key 和 data 文件维护了多个 mmap buffer,当一个 buffer 写满时转入后台 flush,接下来的写入会写进另一个 buffer。这是因为写入操作的开销是由网络传输开销和磁盘 IO 开销两部分组成的,单个buffer的情况下两者完全串行执行,在发生刷盘时写入操作需要消耗网络传输加上磁盘IO的时间。采用多个 buffer 以后相当于将磁盘 IO 与网络开销并行化了,产生的阻塞会减少很多。对于数据读取操作,读请求会将 offset 对齐到 cache 长度,也就是 10MB,然后存储节点直接以 directIO 方式从对齐后的 offset 处读出这一块数据返回即可。当存储节点意外关闭时,因为我们已经确保了所有数据都能安全落盘,唯一需要恢复的就是 mmap buffer 文件当前的写入位置,所以只需要从 meta file 中读取个分组的长度,对 buffer 长度取余便可以计算出各 buffer 的当前写入位置了。计算节点计算节点中主要包含了索引和 cache 两部分。索引部分,索引存储在 hash 表中,每个 entry 的格式为 (key, 分组编号+分组内偏移量)。因为 key 和 value 都是大小固定的,因此偏移量只需要保存它是分组内的第几个 kv 对,所以只需要 4 字节。因此每个 entry 的空间占用为 key 8字节加分组编号 4 字节加偏移量 4 字节,总共 16 字节,那么 6400 万个 entry 一共需要大约 1G 左右空间,因此可以完全保存在内存里。我们实现了一个原子的 hash 表。hash 表底层是一个大小为 8000 万左右的数组,以线性探测法处理 hash 冲突。说hash表的每一个 entry 由两个 64 位无符号整数组成,分别代表 key 与索引,并初始化为0。因为 key 本身可能是0,所以我们让分组编号从1开始,然后对索引部分的原子变量做 cas 操作来实现原子代入。代码如下:因为计算节点本身是无状态的,所以重启时需要恢复索引。我们把索引恢复的时机放在第一次 get 操作的时候,这样可以避免写入阶段计算节点请求恢复索引。索引恢复由 16 个线程并行执行,每一个线程向存储节点请求一整个 key 文件,因为 key 在 key 文件中的位置与 value 在 value 文件中的位置是一样的,所以遍历 key 文件就可以重建索引了。如果每次 get 都去存储节点读取,那么网络延迟开销太大,无法打满带宽。所以我们每次向存储节点请求数据时都读取一整块数据保存在内存 cache 里,大小为 10MB。由于我们不确定是不是完全按照 10MB 对齐的,因此我们为每个分组准备了两个 cache,这样当 cache miss 时,可以保证前一块 cache 的数据一定已经被读完了,可以被新的 cache 置换掉。此外我们还实现了 cache 预取机制。由于赛题要求顺序读取及热点区间读取,热点区间读取是按照数据的写入顺序以 10MB 数据粒度分区,分区逆序推进,在每个 10MB 数据分区内随机读取,所以当向存储节点请求一个新 cache 完成时,另一个线程会根据当前两个 cache 的 offset 之差向存储节点预取下一块 cache。在顺序读取阶段差为整数,因此会顺序预取,在热点区间读取阶段差为负数,因此会逆序预取。总结我们的最好成绩是 786 秒,其中写用了 505 秒,顺序读取用了 134 秒,热点读取用了 147 秒,最终排名第 5 名。此外各阶段分别跑出的最佳成绩为505/132/142秒,可惜我们对读操作的优化是最后一天完成的,但是最后一天的评测环境不太好,写性能一直上不去(我们的写入时间基本稳定在520秒以内,但是最后一天一直在560秒+)。在热点读阶段实际消耗在读取上的时间为133秒,但是恢复索引使用了9 秒,这里还有很大的改进空间。考虑到评测系统的带宽大约15Gbps,我们的读取性能已经基本跑满网络带宽了。第一次参加这种类型的比赛,我们学习和积累了很多经验,希望来年还有机会参加,取得更好的成绩。 作者:ADSL
  • [调优工具] 性能监控Nmon工具:可以生成excel图表,很方便
    一、Nmon介绍(这句不用看——Nmon得名于 Nigel 的监控器,是IBM的员工 Nigel Griffiths 为 AIX 和 Linux 系统开发的,使用 Nmon 可以很轻松的监控系统的 CPU、内存、网络、硬盘、文件系统、NFS、高耗进程、资源和 IBM Power 系统的微分区的信息)。Nmon是一款计算机性能系统监控工具,因为它免费,体积小,安装简单,耗费资源低,广泛应用于AIX和Linux系统。二,Nmon下载首先查看Linux系统内核版本(两种方式):1,cat /proc/version [qgc@localhost nmon16d]$ cat /proc/versionLinux version 2.6.32-431.el6.x86_64 (mockbuild@c6b8.bsys.dev.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) ) #1 SMP Fri Nov 22 03:15:09 UTC 20132,uname -a[qgc@localhost nmon16d]$ uname -aLinux localhost.localdomain 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux对照上图,我是VMware虚拟机安装的64位centos6.5。接着下载nmon软件包:1,wget方式下载,地址:  https://nchc.dl.sourceforge.net/project/nmon/nmon16d_x86.tar.gz[qgc@localhost nmon16d]$ wget https://nchc.dl.sourceforge.net/project/nmon/nmon16d_x86.tar.gz--2018-08-27 11:26:11--  https://nchc.dl.sourceforge.net/project/nmon/nmon16d_x86.tar.gzResolving nchc.dl.sourceforge.net... 211.79.60.17, 2001:e10:ffff:1f02::17Connecting to nchc.dl.sourceforge.net|211.79.60.17|:443... connected.HTTP request sent, awaiting response... 200 OKLength: 9252938 (8.8M) [application/x-gzip]Saving to: “nmon16d_x86.tar.gz” 100%[===========================================================>] 9,252,938   1.77M/s   in 5.0s     2018-08-27 11:26:17 (1.77 MB/s) - “nmon16d_x86.tar.gz” saved [9252938/9252938] [qgc@localhost nmon16d]$ lsnmon16d_x86.tar.gz 2,官网手动下载,地址:http://nmon.sourceforge.net/pmwiki.php?n=Site.Downlo ,翻译了下,如下图因为CentOS就是 RedHat 的免费版本(上面内核版本 2.6.32-431.el6.x86_64),所以对应系统是RHEL 6.5,选择右边包含RedHat 6.5的下载。 三,解压安装下载完成后,可以新建一个目录,作为解压后存放的目录,这里为nmon16d,如果是手工下载的,需要拷贝到虚拟机。[qgc@localhost Desktop]$ mkdir nmon16d输入解压命令:tar -zxvf nmon16d_x86.tar.gz -C nmon16d ,-C 是指定解压目录[qgc@localhost Desktop]$ tar -zxvf nmon16d_x86.tar.gz  -C nmon16dnmon_power_32_linuxnmon_power_32_rhel6nmon_power_32_sles11nmon_power_64_kvm2nmon_power_64_linux...在nmon6d目录中,可以找到nmon_x86_64_centos6这个文件,并对它添加执行权限[qgc@localhost nmon16d]$ chmod +x nmon_x86_64_centos6再把该程序文件加入环境变量运行目录,如果不是管理员,需要加上sudo命令[qgc@localhost nmon16d]$ mv nmon_x86_64_centos6 /usr/local/bin/nmonmv: cannot move `nmon_x86_64_centos6' to `/usr/local/bin/nmon': Permission denied[qgc@localhost nmon16d]$ sudo mv nmon_x86_64_centos6 /usr/local/bin/nmon[sudo] password for qgc: [qgc@localhost nmon16d]$ 四,运行Nmon输入nmon命令启动Nmon,如下图在上面的交互式窗口中,可以使用nmon 快捷键来显示不同的系统资源统计数据:·        q : 停止并退出 Nmon·        h : 查看帮助·        c : 查看 CPU 统计数据·        m : 查看内存统计数据·        d : 查看硬盘统计数据·        k : 查看内核统计数据·        n : 查看网络统计数据·        N : 查看 NFS 统计数据·        j : 查看文件系统统计数据·        t : 查看高耗进程·        V : 查看虚拟内存统计数据·        v : 详细模式按下c,m,d后如下图,CPU、内存、磁盘的消耗情况都直观的展现出来了,按q键可以退出。五,数据采集性能测试时,需要根据测试场景的执行情况,分析一段时间内系统资源的变化,这时需要nmon采集数据并保存下来,以下是常用的参数:      -f 参数:生成文件,文件名=主机名+当前时间.nmon     -T 参数:显示资源占有率较高的进程     -s 参数:-s 10表示每隔10秒采集一次数据     -c 参数:-s 10表示总共采集十次数据     -m 参数:指定文件保存目录具体的参数学习可参考博客:https://www.cnblogs.com/iclk/p/4456311.html如每隔5秒采集一次,一共采集12次,就是1分钟的数据(生成的文件已标红):[qgc@localhost nmon16d]$ nmon -f -s 5 -c 12 -m /home/qgc/Desktop/[qgc@localhost nmon16d]$ nmon -f -T -s 5 -c 12 -m /home/qgc/Desktop/[qgc@localhost Desktop]$ lslmon16g.c                   localhost_180827_2318.nmon  nmon16d_x86.tar.gzlocalhost_180827_2317.nmon  nmon16d                     Test数据采集完毕后,如需关闭nmon进程,需要获取nmon的pid(已标红)[qgc@localhost Desktop]$ ps -ef | grep nmonqgc        4455(pid)   4349(ppid)  0 23:40 pts/0    00:00:00 nmonqgc        4491   4429  0 23:40 pts/1    00:00:00 grep nmon再安全杀掉该进程:kill -9 pid[qgc@localhost Desktop]$ kill -9 4455[qgc@localhost Desktop]$ ps -ef | grep nmonqgc        4493   4429  0 23:40 pts/1    00:00:00 grep nmon六,数据分析1,下载nmon analyser借助nmon analyser可以把nmon采集的数据生成直观的Excel表,nmon analyser可以在IBM的官网下载,https://www.ibm.com/developerworks/community/wikis/home?lang=en#!/wiki/Power+Systems/page/nmon_analyser 在windows上下载后解压,有word和exce两个文档,Word是说明文档,包括更新日志,详细参数等,其中的Excel就是nmon analyser工具了。2,打开nmon analyser双击打开nmon analyser v54.xlsm,点击Analyze nmon data按钮: 注:因为我用的个人免费版WPS(10.1),没有包含宏,需要安装宏插件(VBA for WPS),Excel是自带宏插件的,如果宏不能运行,需要做以下操作:工具 -> 宏 -> 安全性 -> 中,然后再打开文件并允许运行宏。 3,下载VBA for WPS地址:https://pan.baidu.com/s/1QzW4ebQxYQtxgVfkTmxVJw,下载VBA7.0.1590_For WPS(中文).exe后,先退出WPS,再直接安装就行,再次打开nmon analyser,启用宏4,使用nmon analyser生成图表成功打开nmon analyser后,点击Analyze nmon data按钮,选择nmon数据文件,会再次提示另存为,选择地址保存即可。  下图就是nmon生成的数据报告截图,底部可以切换其他资源信息。    References:1.       https://www.cnblogs.com/qgc1995/p/7523786.html2.       http://nmon.sourceforge.net/pmwiki.php
  • 如何定制服务消费者的发送线程池?
    A服务向B服务发消息,采用CSE同步restTemplate方式,使用jmeter压测,一分钟500次,故意让B服务延时几秒后返回响应,A服务会报大量的超时错误(设置了30秒超时),看默认的发送线程貌似只有几个。如何定制发送的线程池大小呢?
  • [热门活动] 进程造成容器dump
    Jul 30 14:54:31 huawei kernel: Pid 21310(fcst_cal) over core_pipe_limit Jul 30 14:54:31 huawei kernel: Skipping core dump Jul 30 14:54:31 huawei kernel: Pid 21311(clean_db) over core_pipe_limit Jul 30 14:54:31 huawei kernel: Skipping core dump,容器在运行过程中发生如上问题,请问如何引起?如何处理?
  • [问题求助] CSE provider线程配置
    provider侧microservice.yaml中有线程配置,容器(tomcat)自身也有线程配置,最终线程数的上限是由什么决定的?
  • 【华为云TaurusDB性能挑战赛】初赛FAQ
    本帖对参加华为云TaurusDB性能挑战赛初赛选手的问题进行解答    Q1:  是否可以使用aio             可以使用       Q2:class KVIntf {public:    virtual ~KVIntf() {};     virtual bool Init(const char * dir, int id) = 0;     virtual void Close() = 0;     virtual int Set(KVString &key, KVString & val) = 0;     virtual int Get(KVString &key, KVString & val) = 0;};   1. 接口Init的参数id是什么含义,怎样约定的?       测试程序运行时,每个线程会调用Init,init中的dir相同,id不同。参赛者可以使用,也可以不使用这个id来识别不同的线程。这个id仅体现线程的不同,没有其他含义。另外每个线程都会调用 GetKVIntf, Init, Close;  只有init会传线程标记。   2. 接口Get和Set的返回值是怎样约定的?       在一个实际的使用场景中,返回值可以表征错误码/或者分区信息等,第一阶段的比赛中,没有检验这个返回值;   3. kv_string.h是否可以修改?如果不能修改,主办方如何保证判题的时候、所有人的kv_string.h是原始没有被修改?       判题为自动化程序,判题时不会检查kv_string文件本身是否被修改,但会判题程序会使用原始kv_string的定义来检验返回的k/v是否和原始值相同。  4. 性能评测性能评测随机写入:16个线程并发随机写入,每个线程使用Set各写400万次随机数据(key 8B、value 4KB)顺序读取:16个线程并发按照写入顺序读取,每个线程各使用Get读取400万次随机数据随机读取:16个线程并发读取,每个线程随机读取400万次数据,读取范围覆盖全部写入数据 注:1. 共2个维度测试性能,每一个维度测试结束后会保留DB数据,关闭实例,重启进程,清空PageCache,下一个维度开始后重新打开新的实例      这里是2个维度还是3个维度?如果是2个维度,哪两个阶段是在一起的?      随机写入和顺序读取是一个阶段(即写入完成后立即读取),随机读取是另一个阶段(经过Close和Init后再读取)Q3:关于 Key的问题1)不同线程的key不会重复Q4: GCC 版本gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36)Copyright (C) 2015 Free Software Foundation, Inc.     
  • [问题求助] Liteos 集中式任务调度方式如何理解?
    官方资料中讲到为了降低功耗,liteos 采用集中式任务调度方式,这个集中式怎么理解呢?假如有两个任务,每个任务执行时间和阻塞时间都不同,而liteos也是个实时内核,要怎么个集中调度法 ,同时还保证实时性呢?
  • [已解决问题归档] 【入驻式求助】动态库coredump
    【问题简要】动态库coredump 的问题【问题类别】vxml2.0 【IPCC解决方案版本】IPCC V200R001C80【问题现象描述】您好!我们的测试流程大概如下:语音识别获取结果 id 后,使用结果 id 去调用动态库,测试现象为 ivr trace 日志卡死崩溃,我们参考文档初步排查为动态库coredump 的问题。关键日志如下:附件 mds.log 的 137 行「进程未启动或者进程意外退出」。附件 ivr301.log 的 12 行「callinginstance[100] accesscode:777 flow:/home/icd/icddir/bin/Y:/flow/vxml/temp.jsp get sig[11] in dlg:use_dll 」。ivr 服务器的「/home/icd/icddir/corebak」目录下有 core 文件生成。在 gdb 提示符环境下,输入 where 回车看到Core was generated by `/home/icd/icddir/bin/ivr -p 301'.Program terminated with signal 6, Aborted.#0  0xffffe430 in __kernel_vsyscall ()(gdb) where#0  0xffffe430 in __kernel_vsyscall ()#1  0xf6347e30 in raise () from /lib/libc.so.6#2  0xf6349765 in abort () from /lib/libc.so.6#3  0x082c3efa in ProgramExitSIGDeal (sig=11) at ../scp/ntscp.cpp:285#4 #5  0x00000000 in ?? ()#6  0xf71f1dba in CObj::Obj_UseDll (this=0xd813ed8) at ../vxml/Obj.cpp:9982#7  0xf7214137 in CObj::Visit (this=0xd813ed8) at ../vxml/Obj.cpp:1883#8  0xf70ce634 in CForm::Collect (this=0xd82c9a8, pItem=0xd813edc) at ../vxml/Form.cpp:700#9  0xf70f486f in CInterpreter::Collect (this=0xa0b1e70) at ../vxml/Interpreter.cpp:474#10 0xf70ff1a6 in CInterpreter::Go (this=0xa0b1e70) at ../vxml/Interpreter.cpp:1136#11 0xf71040d4 in CInterpreter::Run (this=0xa0b1e70, sUrl=0xd58fc11 "/home/icd/icddir/bin/Y:/flow/vxml/temp.jsp", usRetCode=@0xdc0db302: 65535) at ../vxml/Interpreter.cpp:2541#12 0xf71042c7 in CInterpreter::VXMLRun (this=0xa0b1e70, pUrl=0xd58fc11 "/home/icd/icddir/bin/Y:/flow/vxml/temp.jsp", usRetCode=@0xdc0db302: 65535,    dwGetFileTime=@0xdc0db2dc: 0, bGetFileSuccessFalg=@0xdc0db2d8: 0) at ../vxml/Interpreter.cpp:2389#13 0x0812988d in CCallingInstance::ThreadProc (lpParam=0xa0c01e8) at ../icd_files/icd_callapp.cpp:1478#14 0xf6491809 in start_thread () from /lib/libpthread.so.0#15 0xf63f63be in clone () from /lib/libc.so.6问题:如何继续排查和解决这个问题?源码和日志已发附件。
  • [中间件] 【鲲鹏翱翔】消息中间件04-ELK6.2.3环境搭建指南
    1.   简介ELK是集中日志解决方案的一种,具有收集、传输、存储、分析日志的功能。ELK分别是Elasticsearch、Logstash和Kibana三个开源软件的首字母缩写。Elasticsearch 是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。它是一个建立在全文搜索引擎 Apache Lucene 基础上的搜索引擎,使用 Java 语言编写。Logstash 的作用就是一个数据收集器,将各种格式各种渠道的数据通过它收集解析之后格式化输出到 Elasticsearch。Kibana 是一款基于 Apache 开源协议,使用 JavaScript 语言编写,为 Elasticsearch 提供分析和可视化的 Web 平台。它可以在 Elasticsearch 的索引中查找,交互数据,并生成各种维度的表图。类别:中间件2.   部署环境Packet NameVersion备注EulerEuler 2.8与Centos二选一CentOSCentOS 7.5 with ARM与Euler二选一Elasticsearch6.2.3Logstash6.2.3Kibana6.2.3整个环境在一台服务器部署    3.   安装部署    3.1 约束说明 & 配置华为官方镜像源Kibana 的版本需要和 Elasticsearch 的版本一致,这是官网的配置要求,可参考网址:https://www.elastic.co/guide/cn/kibana/current/setup.html 配置华为官方镜像源Centos7.5 mkdir -p /etc/yum.repos.d/repo_bak/ mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/repo_bak/ curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.myhuaweicloud.com/repo/Centos-altarch-7.repo yum makecacheEuler2.8 wget http://mirrors.myhuaweicloud.com/repo/mirrors_source.sh && sh mirrors_source.sh     3.2  JDK安装Centos7.5 yum -y install java-1.8.0-openjdk java-1.8.0-openjdk-develEuler2.8#Euler2.8 没有找到java-1.8.0-openjdk-devel, yum search openjdk yum -y install java-1.8.0-openjdk源码安装java(option)#下载tar包wget http://openjdk.linaro.org/releases/jdk8u-server-release-1803.tar.xz#解压tar -jxvf jdk8u-server-release-1803.tar.xz -C /usr/lib/jvm#创建软连接ln -sf /usr/lib/jvm/jdk8u-server-release-1803/bin/java /etc/alternative/java #配置环境变量vi /etc/profile.d/java.sh-------------------java.sh---------------#JAVA_HOMEexport JAVA_HOME=/usr/lib/jvm/jdk8u-server-release-1803export JRE_HOME=$JAVA_HOME/jreexport JAVA=$JAVA_HOME/binexport CLASSPATH=.:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jarexport PATH=$PATH:${JAVA_HOME}/bin--------------------------------------------source /etc/profile.d/java.sh#查看java版本java -version      3.3 安装部署Elasticsearch6.2.3 wget  https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.2.3.tar.gz tar xvf elasticsearch-6.2.3.tar.gz -C /opt && cd /opt# 添加用户,elasticsearch不允许使用root执行 groupadd elastic && useradd elastic -g elastic -p elastic chown -R elastic:elastic elasticsearch-6.2.3 su elastic cd elasticsearch-6.2.3# 后台启动elasticsearch ./bin/elasticsearch &# 验证 curl 127.0.0.1:9200{ "name" : "Surge", "cluster_name" : "elasticsearch", "version" : { "number" : "2.1.0", "build_hash" : "72cd1f1a3eee09505e036106146dc1949dc5dc87", "build_timestamp" : "2015-11-18T22:40:03Z", "build_snapshot" : false, "lucene_version" : "5.3.1" }, "tagline" : "You Know, for Search"}备注:如果遇到如下错误ERROR: [2] bootstrap checks failed[2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]# 切换至root用户,进行操作# 临时生效# elasticsearch需要在内存中保存索引,max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量. 防止elasticsearch抛出内存不足的异常。 sudo sysctl vm.max_map_count=262144 [1]: max file descriptors [65535] for elasticsearch process is too low, increase to at least [65536]# centos7.5 执行如下命令,退出ssh或者重启生效 vi /etc/security/limits.conf# 在文件末尾添加* soft nofile 65536      #限制(软)进程数量* hard nofile 131072     #限制(硬)进程数量# Euler2.8执行如下命令,退出ssh或者重启生效# 限制资源使用的配置文件是 /etc/security/limits.conf,和/etc/security/limits.d/目录,/etc/security/limits.d/里面配置会覆盖/etc/security/limits.conf的配置 vi /etc/security/limits.d/huawei-nofile.conf# 在文件末尾添加* soft nofile 65536* hard nofile 65536部署Kibana6.2.3 wget https://artifacts.elastic.co/downloads/kibana/kibana-6.2.3-linux-x86_64.tar.gz tar xvf kibana-6.2.3-linux-x86_64.tar.gz -C /opt rm -rf /opt/kibana-6.2.3-linux-x86_64/node# 直接下载的Kibana是x86的,需要将node组件换成arm版的运行 wget https://nodejs.org/dist/v8.11.3/node-v8.11.3-linux-arm64.tar.xz tar xvf /opt/node-v8.11.3-linux-arm64.tar.xz -C /opt/kibana-6.2.3-linux-x86_64 cd /opt/kibana-6.2.3-linux-x86_64 && mv node-v8.11.3-linux-arm64 node# 修改配置文件 vi config/kibana.yml# 配置成任意IP可以访问,生产环境建议为特定IPserver.host: "0.0.0.0"# 后台启动kibana ./bin/kibana &访问http://IP:5601部署Logstash6.2.3 wget https://artifacts.elastic.co/downloads/logstash/logstash-6.2.3.tar.gz tar xvf logstash-6.2.3.tar.gz && cd logstash-6.2.3# 解压完成,早config目录下新建一个logstash.conf# 然后做好input ,filter,output三大块, 其中input是吸取logs文件下的所有log后缀的日志文件,filter是一个过滤函数,这里不用配置,output配置了导入到# hosts为127.0.0.1:9200的elasticsearch中,每天一个索引。 vi  config/logstash.confinput {     file {        type => "log"        path => "/logs/*.log"        start_position => "beginning"    }} output {  stdout {   codec => rubydebug { }  }   elasticsearch {    hosts => "127.0.0.1"    index => "log-%{+YYYY.MM.dd}"  }}# 启动logstash ./bin/logstash -f ./config/logstash.conf开启另一个ssh终端,往日志文件目录中写入数据 mkdir -p /logs cd /logs echo 'hello world' > 1.log      3.4  验证在浏览器中访问http://IP:/5601,可以打开kibana页面,                                                选择Management标签,在index-name-*框中输入log*,选择Time-field name 为@timestamp 点击Discover,就可以看见内容了3.5 参考信息[1] 通过ELK快速搭建一个你可能需要的集中化日志平台:https://www.cnblogs.com/huangxincheng/p/7918722.html4.   FAQ4.1 点击Management标签时显示如下界面说明数据源不存在,在logs目录下增加相应日志 mkdir -p /logs cd /logs echo 'hello world' > 1.log
  • [安装经验] win10+anaconda3(64位) 安装带并行多线程的projectQ
    在windows平台上,官方给的projectQ安装C++的Simulator库的办法有两个,要么是安装msvc(体积大且没多线程加速,因为msvc只支持OpenMP2.0),要么是装mingw编译器,后者可以支持正常的OpenMP,编译出来的C++库是是支持多线程的。显然装mingw更好,不过projectQ没说清楚具体怎么玩,只好自己查命令去编译C++库。下面是具体的过程(每台机器上提前装的库都有差别,不保证一定没问题):1. 提前要装的软件Anaconda3 64位mingw-w64vcruntime140.dll 64位说明:mingw-w64是最新版的mingw编译器(还有个旧版的叫mingw32,只有32位版的,早就停止更新了),安装的时候可以选装32位的还是64位的,由于要用64位的Python,肯定装64位的(如果必须要用32位的Python,那就装32位的mingw,但是坑很多,最后会说)装完之后,把安装目录下的bin文件夹比如C:\MinGW\mingw64\bin加到Path环境变量中。然后把下载的vcruntime140.dll放到mingw安装目录下的lib文件夹中,比如C:\MinGW\mingw64\lib。可能需要代理才能装这个编译器,这有离线版的。2. 开装projectQ!右键管理员身份打开Anaconda Prompt(环境变量都加好了的话直接cmd也行)输入命令:  pip install --global-option build_ext --global-option --compiler=mingw32 projectq一般来说,一通各种包安装之后会开始编译C++库,然后会直接出个Error,说缺lmsvcr140 :问题的原因是微软把这个库给移除了,导致编译程序找不着这个东西。这个时候之前下载的dll就要派上用场了,直接修改Anaconda安装目录下的C:\Application\Anaconda3\Lib\distutils\cygwinccompiler.py把87行改成这样(可能需要带管理员权限的编辑器)再跑一遍pip命令应该就能装好C++版的Simulator库了3. 并行效果在导入projectQ的库之前加上以下几行代码以启用多线程:  import os   os.environ["OMP_NUM_THREADS"] = '8'   os.environ["OMP_PROC_BIND"] = "spread"线程数设置就看各自的电脑了。Linux貌似是用projectQ官方给的export命令,没用过就不多说了。运行官方示例shor算法,记得把high_level_gates里面的BasicMathGate置为False,强制模拟器去分解模幂运算对应的酉门,然后就可以开始愉快的数框框了!实际上它不是一开始就用上多核并行,而是代码跑到途中才开始,所以可能要等一会儿才能看到CPU占用率上去。官方说的是可以用当代笔记本电脑在3分钟内分解4028033这么大的数,其中的前提是把high_level_gates里面的BasicMathGate置为True,即不分解那个最耗时的模幂运算酉门,有点作弊的感觉,我实际这么跑了一下,发现程序根本就没用多核去跑,还是单线程运行,结果大概跑了10分钟才分解完,没搞清楚为什么会这样,群里哪位大佬知道的话劳烦告知。其实开了并行感觉shor算法并没有变快多少,实实在在去分解个529(貌似只用了十几个qubit)这种数都慢得要死,有点失望QAQ。4. 注意事项我的Anaconda是最新版64位的(2019.03),不知道低版本的会不会有问题。而且我之前电脑上就有全套Visual Studio 2017,不知道这个会不会对安装结果有影响(理论上有mingw了就不需要msvc)。32位的安装方法和以上步骤差不多(先把所有东西都换成32位的),但是更麻烦,32位的Anaconda的根目录下的include文件夹下的pyconfig.py里面如果有#define hypot _hypot这一行的话,要先注释掉再装projectQ,否则会报错。补一句,就算32位的装好了,实际跑比特数比较多的projectQ程序的时候也会有很大概率出MemoryError,所以不建议装32位的。貌似Linux或Mac上装带并行的projectQ没这么多幺蛾子。5. 最后一开始我是装的32位的projectQ,看到一堆报错信息一脸懵逼,折腾了好久才把这些安装问题给解决,希望能对大家有所帮助。可能大家按这个安装教程弄到最后还是会出新的Bug,到时候欢迎大家跟帖交流!
  • [分享交流] TensorFlow 队列与线程
    TensorFlow 队列与线程深度学习的模型训练过程往往需要大量的数据,而将这些数据一次性的读入和预处理需要大量的时间开销,所以通常采用队列与多线程的思想解决这个问题,而且TensorFlow为我们提供了完善的函数。本文介绍了TensorFlow的线程和队列。在使用TensorFlow进行异步计算时,队列是一种强大的机制。正如TensorFlow中的其他组件一样,队列就是TensorFlow图中的节点。这是一种有状态的节点,就像变量一样:其他节点可以修改它的内容。具体来说,其他节点可以把新元素**到队列后端(rear),也可以把队列前端(front)的元素删除。在Python中是没有提供直接实现队列的函数的,所以通常会使用列表模拟队列。而TensorFlow提供了整套实现队列的函数和方法,在TensorFlow中,队列和变量类似,都是计算图上有状态的节点。操作队列的函数主要有:l  FIFOQueue():创建一个先入先出(FIFO)的队列 l  RandomShuffleQueue():创建一个随机出队的队列 l  enqueue_many():初始化队列中的元素 l  dequeue():出队 enqueue():入队与队列Queue有关的有以下三个概念:l  Queue是TF队列和缓存机制的实现l  QueueRunner是TF中对操作Queue的线程的封装l  Coordinator是TF中用来协调线程运行的工具虽然它们经常同时出现,但这三样东西在TensorFlow里面是可以单独使用的,不妨先分开来看待。1.    Queue,队列根据实现的方式不同,分成具体的几种类型,例如:l  tf.FIFOQueue 按入列顺序出列的队列l  tf.RandomShuffleQueue 随机顺序出列的队列l  tf.PaddingFIFOQueue 以固定长度批量出列的队列l  tf.PriorityQueue 带优先级出列的队列l  ...这些类型的Queue除了自身的性质不太一样外,创建、使用的方法基本是相同的,以下介绍两个最常用的1)  tf.FIFOQueue(capacity, dtypes, name='fifo_queue') 创建一个以先进先出的顺序对元素进行排队的队列参数:capacity:整数。可能存储在此队列中的元素数量的上限dtypes:DType对象列表。长度dtypes必须等于每个队列元 素中的张量数,dtype的类型形状,决定了后面进队列元素形状方法:q.dequeue()获取队列的数据q.enqueue(值)将一个数据添加进队列q.enqueue_many(列表或者元组)将多个数据添加进队列q.size() 返回队列的大小2)、tf.RandomShuffleQueue() 随机出的队列Queue主要包含入列(enqueue)和出列(dequeue)两个操作。enqueue操作返回计算图中的一个Operation节点,dequeue操作返回一个Tensor值。Tensor在创建时同样只是一个定义(或称为“声明”),需要放在Session中运行才能获得真正的数值。下面是一个单独使用Queue的例子:#模拟同步操作   ''' 1.创建一个空的队列 2.向队列设置几个初始的值 3.从队列中取出一个数据,再将数据加1 4.数据加1之后的结果进队列 5.打印出队列中的数据 '''   def sync_test():     #创建一个空的队列     q = tf.FIFOQueue(3,tf.float32)     #向队列设置几个初始的值     enq_many = q.enqueue_many(([0.1,0.2,0.3],))       #从队列中取出一个数据,再将数据加1     out_q = q.dequeue()     data = out_q+1     #数据加1之后的结果进队列     en_q = q.enqueue(data)     with tf.Session() as sess:         sess.run(enq_many)           #执行en_q op 99次         for i in range(99):             sess.run(en_q)           #打印出队列中的数据         for i in range(q.size().eval()):             print(q.dequeue().eval())     return None  2.     QueueRunner,队列管理器tf.train.QueueRunner(queue, enqueue_ops=None)参数:l  queue:A Queuel  enqueue_ops:添加线程的队列操作列表,[]*2,指定两个线程l  create_threads(sess, coord=None,start=False) 创建线程来运行给定会话的入队操作l  start:布尔值,如果True启动线程;如果为False调用者 必须调用start()启动线程l  coord:线程协调器  用于线程的管理Tensorflow的计算主要在使用CPU/GPU和内存,而数据读取涉及磁盘操作,速度远低于前者操作。因此通常会使用多个线程读取数据,然后使用一个线程消费数据。QueueRunner就是来管理这些读写队列的线程的。QueueRunner需要与Queue一起使用(这名字已经注定了它和Queue脱不开干系),但并不一定必须使用Coordinator。看下面这个例子:import tensorflow as tf   import sys q = tf.FIFOQueue(10, "float") counter = tf.Variable(0.0)  #计数器 # 给计数器加一   increment_op = tf.assign_add(counter, 1.0)   # 将计数器加入队列   enqueue_op = q.enqueue(counter)     # 创建QueueRunner # 用多个线程向队列添加数据 # 这里实际创建了4个线程,两个增加计数,两个执行入队   qr = tf.train.QueueRunner(q, enqueue_ops=[increment_op, enqueue_op] * 2)     # 主线程   sess = tf.InteractiveSession() tf.global_variables_initializer().run()   # 启动入队线程   qr.create_threads(sess, start=True)   for i in range(20):     print (sess.run(q.dequeue()))增加计数的进程会不停的后台运行,执行入队的进程会先执行10次(因为队列长度只有10),然后主线程开始消费数据,当一部分数据消费被后,入队的进程又会开始执行。最终主线程消费完20个数据后停止,但其他线程继续运行,程序不会结束。3.    Coordinator,线程协调器tf.train.Coordinator() 线程协调员,实现一个简单的机制来协调一 组线程的终止方法: 返回的是线程协调实例            request_stop()  请求停止            join(threads=None, stop_grace_period_secs=120) 等待线程终止Coordinator是个用来保存线程组运行状态的协调器对象,它和TensorFlow的Queue没有必然关系,是可以单独和Python线程使用的。例如:#模拟异步操作   '''   通过队列管理器来实现变量加1,入队,主线程出队列的操作,观察效果? (异步操作) '''   def async_test():     # # 1、定义一个队列,1000     q = tf.FIFOQueue(300,tf.float32)       # 2、定义要做的事情 循环值,+1, 放入队列当中     var = tf.Variable(0.0)     # 实现一个自增  tf.assign_add     data = tf.assign_add(var,tf.constant(1.0))     en_q = q.enqueue(data)       # 3、定义队列管理器op, 指定多少个子线程,子线程该干什么事情     # 1,2,3,4,5,6,7,8,9,10     qr = tf.train.QueueRunner(q, enqueue_ops=[en_q] * 2)       # 初始化变量的OP     init_op = tf.global_variables_initializer()       with tf.Session() as sess:         #运行初始化op         sess.run(init_op)         # 开启线程管理器,coord线程协调器         coord = tf.train.Coordinator()         # 真正开启子线程,读数据入队列,t1,t2  0.0001  10         threads = qr.create_threads(sess, coord=coord, start=True)           # 主线程,不断读取数据训练  t0         for i in range(300):             #             print(sess.run(q.dequeue()))         print("size==>",sess.run(q.size()))         # 停止子线程,回收         coord.request_stop()         coord.join(threads)       return None 将这个程序运行起来,主线程会等待所有子线程都停止后结束,从而使整个程序结束。由此可见,只要有任何一个线程调用了Coordinator的request_stop方法,所有的线程都可以通过should_stop方法感知并停止当前线程。将QueueRunner和Coordinator一起使用,实际上就是封装了这个判断操作,从而使任何一个现成出现异常时,能够正常结束整个程序,同时主线程也可以直接调用request_stop方法来停止所有子线程的执行。 更多的技术内容,请访问腾科it教育集团网站 www.togogo.net
  • [大咖交流] idou老师教你学Istio 29:Envoy启动流程
    1. 功能概述Envoy启动时,会启动一个进程,并在这个进程中启动很多线程,这样,可以启动很多worker线程,一般worker线程数与核心数相同,每个worker线程处理所有已配置的listener上的请求,管理连接并处理filterchain,非阻塞;同时,在这个进程中会启动一个主线程,它负责启动和停止envoy,也是通过API提供配置管理的线程,同时它收集不同的指标,管理其它线程,也是非阻塞的。2. 重要数据结构定义2.1 Filter过滤器,包括listener filter、network filter和http filter。Listener filter可以用于操作连接元数据,在新接收的套接字上进行操作,例如获取原始目的地址,重定向连接等;network filter主要负责数据读写;http filter主要负责数据处理。2.2 Listener监听器,envoy中支持在每个线程中配置任意数量的监听器,每个监听器独立配置一定数量的network filter,也可以选择性的配置listener filter,listener filter在连接建立之前处理,network filter在连接建立之后处理。2.3 Worker一个worker对应一个envoy的执行线程,将listener绑定在worker上,worker负责监听、过滤和转发,每个连接的生命周期会绑定在一个单独的worker上,通常情况下,envoy实现了100%的非阻塞。3. 代码流程3.1 流程概述Envoy启动时,首先启动主线程,在主线程中对listener和filter进行初始化操作,然后将listener绑定到worker上,并由主线程拉起worker线程,由worker线程负责监听新连接。3.2 初始化3.2.1 main入口main函数是envoy启动的总入口,首先生成main_common,用于后面的初始化。3.2.2 初始化main_common在main_common里面会生成maincommonbase,它会做server instance的初始化,一个instance是一个服务的实例.3.2.3 Instance初始化在maincommonbase里调用InstanceImpl函数后,首先对启动携带的配置信息进行注册,然后执行instance的初始化。Instance的初始化包括两部分:① 将当前instance注册到ListenerManager,来管理更新;② 创建并初始化MainImpl,MainImpl用来初始化监听listener;MainImpl根据配置文件获取静态监听listener列表,将它们实例化并注册到ListenerManager。3.2.4 初始化listener对于每个静态listener,根据配置文件为它创建ListenerFilterFactoryList,并根据配置为它添加ListenerFilterFactory。listener filter有三个:original dst filter,proxy protocol filter, TLS inspector filter,一一按照配置判断是否加入ListenerFilterFactoryList。配置ListenerFilterFactoryList的同时,也会根据配置为这个listener创建NetworkerFilterFactoryList,供后续建立在这个listener上的连接使用。3.3 启动3.3.1 启动入口在main_common初始化正常完成后,执行main_common→run()启动,从而后续执行instance的run()方法,在instance的run()方法,会执行网络级别上的listener初始化。3.3.2 启动worker,将listener绑定到worker上此处,会将从配置文件读取的所有listener绑定到所有的worker上,worker是服务的并发线程,数目一般和核心数相同,将listener绑定到worker上后会通过connectionhandler模块将其初始化。3.3.3 Listener初始化Listener的初始化过程首先生成ActiveListener,通过ActiveListener调用network包内的创建函数来对listener进行网络级别的初始化。3.3.5 启动worker线程,进入监听Listener绑定在worker上,当listener初始化完成后,需要启动worker服务才能真正进入监听流程。此处,为每个worker启动新线程,并调用libevent的event_base_loop进入监听,等待连接事件到达触发后,回调onAccept进入处理流程。4. 总结本文从程序入口main函数开始,分析了envoy如何启动,以及如何对listener、worker这些核心数据结构进行初始化,并详细阐述了从envoy主线程启动到worker线程进入监听行为的全过程。相关服务请访问https://support.huaweicloud.com/cce/index.html?cce_helpcenter_2019
  • [技术干货] 一个关于优雅停机关闭线程池的代码问题
    代码最后https://github.com/apache/servicecomb-java-chassis/blob/master/core/src/main/java/org/apache/servicecomb/core/executor/GroupExecutor.java为什么只是shutdown了,而没有awaitTermination,这样不就无法保证线程池里面的任务一定能执行完成了吗?@Override     public void close() {        for (ExecutorService executorService : executorList) {           executorService.shutdown();        }        executorList.clear();     }