当前位置: 首页 > 编程日记 > 正文

金蝶中间件部署报栈溢出_京东618压测时自研中间件暴露出的问题,压测级别数十万/秒...

618大促演练进行了全链路压测,在此之前刚好我的热key探测框架也已经上线灰度一周了,小范围上线了几千台服务器,每秒大概接收几千个key探测,每天大概几亿左右,因为量很小,所以框架表现稳定。

借着这次压测,刚好可以检验一下热key框架在大流量时的表现。毕竟作为一个新的中间件,里面很多东西还是第一次用,免不得会出一些问题。

压测期,我没有去扩容热key的worker集群,还是平时用的3个16C+1个4C8G的组合,3个16核是是主力,4核的是看上限能到什么样。

由于之前那一周的平稳表现,导致我有点大意了,没再去好好检查代码。导致实际压测期间表现有点惨淡。

框架的架构如下:

512b2c9187f2838be47c096d075d3d29.png

大概0点多压测开始,初始量比较小,从10w/s开始压,当然都是压的APP的后台,我的框架只是被动的接收后台发来的热key探测请求而已。我主要检测的就是worker集群,也就是那4台机器的情况。

从压测开始的一瞬间,那台4核8G的机器就cpu100%,16核的cpu在90%以上,4核的100%即便在压测暂停的间隙也没有恢复,一直都是100%,无论是10w/s,还是后期到大几十万/s。

16核的在20w/s以上时也开始cpu100%,整体卡到不行了已经,连10秒一次的定时任务都卡的不走了,导致定时注册自己到etcd的任务都停了,再导致etcd里把自己注册信息过期删除,大量和client断连。

然后dashboard控制台监听etcd热key信息的监听器也出了大问题,热key产生非常密集,导致dashboard将热key入库卡顿,甚至于入库时,都已经过期1分钟多了,导致插入数据库的时间全部是错的。

虽然worker问题蛮多,也蛮严重,但好在etcd集群稳如老狗,除了1分钟一次的热key密集过期导致cpu有个小尖峰,别的都非常稳定,接收、推送都很稳,client端表现也可以,没有什么异常表现。

其中etcd真的很不错,比想象中的更好,有图为证:

bc1469c2b10215dca93c7cc262ced4d2.png

worker呢就是这样子

fcc08ac17619f0fa615c1ccf7cbb94d4.png

后来经过一系列操作,我还乐观的修改上线了一版,然后没什么用,在100%上稳的一匹。

后来经过我一天的研究分析,发现当时没找到关键点,改的效果不明显。当然后来我自我感觉找到问题点了,又修改了一些,有待下次压测检验。

这一篇就是针对各个发现的问题进行总结,包括压测期间的和之前灰度期间发现的一些。总的来说,无论书上写的、博客写的,各路这个说的那个说的虽然在本地跑的时候各种正常,但真正在大流量面前,未必能对。还有一些知名框架,参数配不好,效果未必达到预期。

平时发现的问题列表

先说压测前小流量时的问题

1 、在worker端,会密集收到client发来的请求。其中有代码逻辑为先后取系统时间戳,居然有后取的时间戳小于前面的时间戳的情况(罕见、不能复现),猜测为docker时间对齐问题。造成时间戳相减为负值,代码数组越界,cpu瞬间达到100%。注意,这可是单线程的!

解决:问题虽然很奇葩,但很好解决,为负时,按0处理。

2 、使用网上找的的netty自定义协议(我前几天还转过那篇问题),在本地测试以及线上灰度测试时,表现稳健。但在全量上线后,2千台client情况下,出现过单worker关闭一段时间并重启后,瞬间收到高达数GB的流量包,直接打爆内存,导致worker停机,后续无法启动的情况。

解决:书上及网上均未找到相关解决方案,类似场景别人极难遇到。后通过使用netty官方自带协议StringDecoder加分隔符后,未复现突传大包的情况,目前线上表现稳定。

3、 Netty client是可以反复连接同一个server的,造成单个client对单个server产生多个长连接的情况,使得server的tcp连接数远远大于client的总数量。此前书上、网络教程等各个地方均未提及该情况。使得误认为,client对server仅会保持一个长连接。

解决:对client的连接进行排重、加锁,避免client反复连接同一个server。

4、 Netty server在推送信息到大量client时,会造成cpu瞬间飙升至60-100%,推送完毕后cpu下降至正常值

解决:在推送时,避免做循环体内json序列化操作,应在循环体外进行

5 、在复用netty创建出来的ByteBuf对象时,反复的使用它,会出现大量的报错。原因是对ByteBuf对象了解不深,该对象和普通的Java对象不一样,Java对象是可以传递下去反复使用的,不存在使用后销毁的情况,而ByteBuf在被netty传出去后,就销毁了,里面携带的字节组就没了

解决:每次都创建新的ByteBuf对象,不要复用它。

6 、2千台client在监听到worker发生变化后,会同时瞬间去连接它,和平时上线时,每次几百台缓慢连接server的场景不同,突发瞬间数千连接时,可能发生server丢失一部分连接,导致部分client连接失败。

解决:不再采用监听的方式,而采用定时轮训的方式,错开连接的时机,对连不上的worker进行本地保存,后加一个定时任务,定时去连接那些没连上的server。

7 、worker机器占用的内存持续增长,超过给docker分配的内存后,被系统杀死进程

解决:worker全部是部署在docker里的,刚开始我是没有给它配JVM参数的,譬如那个4核8G的,我只是将它部署上去,就没有管它了。随后,它的内存在持续稳定上涨,从未下降。直到内存爆满。

后来经进入到容器内部,执行查看内存命令,发现虽然docker是4核8G的,但是宿主机是250G的。JVM采用默认的内存分配策略,初始分配1/64的内存,最大分配1/4的内存。但是是按250G进行分配的,导致jvm不断扩容再扩容,直到1/4 * 250G,在到达docker分配的8G时就被杀死了。

后来给容器配置了JVM参数后,内存平稳。这块带来的经验教训就是,一定要给自己的程序配JVM,不然JVM按默认的执行,后果就不可控了。

压测发现的问题列表

前面发现的多是代码逻辑和配置问题,压测期间主要是cpu100%的问题,也列一下。

1 、netty线程数巨多、disruptor线程数也巨多,导致cpu100%

问题描述:worker部署的jdk版本是1.8.20,注意,这个版本是在1.8里算比较老的版本。worker里面作为netty server启动,我是没有给它配线程池的(如图,之前boss和worker我都没有指定线程数量),所以它走的就是默认Runtime.getRuntime().availableProcessors() * 2。

这个是系统获取核数的代码,在jdk1.8.31之前,docker容器内的这段代码获取到的是宿主机的核数,而非给容器分配的核数!!!譬如我的程序取到的就是32核,而非分配的4核。再乘以2后,变成了64个线程。

导致netty boss和worker线程数高达64,另外我还用了disruptor,disruptor的consumer数量也是64!导致压测一开始,瞬间cpu切换及其繁忙,大量的空转。大家都知道,cpu密集型的应用,线程数最好比较小,等于核数是比较合适的,而我的程序线程数高达180,cpu全部用于轮转了。

之后我增加了判断jdk版本的逻辑,jdk1.8.31后的获取到的availableProcessors就是对的了,并且我限制了bossGroup的线程数为1.再次上线后,cpu明显有下降!

带来的经验教训是,用docker时,需要注意jdk版本,尤其是有获取系统核数的代码作为逻辑时。cpu密集型的,切勿搞很多线程。

ac9fcccfff3c4ba72e524224dd8d5bf4.png

2 、cpu持续100%,导致定时任务都不执行了

和第一个问题是连锁的,因为worker接收到的请求非常密集,每秒达10万以上,而cpu已经全部用于N个线程的轮转了,真正工作的都没了,我的一个很轻的定时任务5s上传一次worker自己的ip信息到配置中心etcd,连这个定时任务都工作不ok了,通过jstack查看,一直处于wait状态。

之后导致etcd里该worker信息过期被删除,再导致2千多个client从etcd没取到该worker注册信息,就把它给删掉了,发生了大量client没有和worker进行连接。

可见,cpu满时,什么都不靠谱了,核心功能都会阻塞。

3、 caffeine密集扩容,耗费cpu大

因为worker里是用caffeine来存储各client发来的key信息的,之后读取caffeine进行存取。caffeine底层是用ConcurrentHashMap来进行的数据存储,大家都知道HashMap扩容的事,扩容2倍,就要进行一次copy,里面动辄几十万个key,扩容resize时,cpu会占用比较大。

尤其是cpu本身负荷很重时,这一步也会卡住。

我的worker给caffeine分配的最大500万容量,虽然不是很大,但卡顿时,resize这一步执行很慢。不过这个不是什么大问题,也没有什么好修复的,就保持这样就行。

4 、caffeine在密集失效时,老版本jdk下,caffeine默认的forkJoinPool有bug

caffeine我是设置的写入后一分钟过期,因为是密集写入,自然也会密集失效。caffeine采用线程池进行过期删除,不指定线程池时采用默认的forkJoinPool。问题是什么呢,大家自己也能试出来。搞个死循环往caffeine里写值,然后看它的失效。

在jdk1.8.20之前,这个forkJoinPool存在不提交任务的bug,导致key过期后未被删除。进而caffeine容量爆满超过阈值,发生内存溢出。架构师针对该问题给caffeine官方提了issue,对方回复,请勿过于密集写入caffeine,写入过快时,删除跟不上。还需要升级jdk,至少要超过1.8.20.不然forkJoinPool也有问题。

5 、disruptor消费慢

大名鼎鼎的disruptor实际表现并不如名气那么好,很多文章都是在讲disruptor怎么怎么牛x,一秒几百万。在worker里的用法是这样的,netty的worker线程池接收到请求后,统一全部发到disruptor里,然后我搞cpu核数个线程来消费这些请求,做计算热key数量的操作。

而压测期间,cpu100%时,几乎所有的线程都卡在了disruptor生产上。即N个线程在这个生产者获取next序列号时卡住,原因很简单,就是没消费完,生产者阻塞。我设置的disruptor的队列长度为100万,实际应该写不满这个队列,但不知道为什么还是大量卡在了这个地方。该问题有待下次压测时检验。

5aefa4fd4567f174245d9e19531574cc.png

6 、有个定时任务里面有耗大量cpu的方法

之前为了统计caffeine的容量和占用的内存,我搞了个定时任务10秒一次上传caffeine的内存占用。就是被注释掉的那行,上线后坑到我了,那一句特别耗cpu。赶紧删掉,避免这种测试性质的代码误上线,占用大量资源。

2a5e796f6b1dd962919ae39db72dbcec.png

7 、数据库写入速度跟不上热key产生的速度

我是有个地方在监听etcd里热key的,每当有新key产生时,就会往数据库里插值。结果由于key瞬间来了好几千个,数据库处理不过来,导致大量的阻塞,等轮到这条key信息插入时,早就已经过期了,造成数据库里的数据全是错的。

这个问题比较好解决,可以做批量入库,加个队列缓冲就好了。

初步总结

其实里面有很多本地永远无法出现的问题,譬如时间戳的那个,还有一些问题是jdk版本的,还有是docker的。但最终都可以归纳为,代码不够严谨,没有充分考虑到这些可能会带来问题的地方,譬如不配JVM参数。

但是不上线又怎么都测试不出来这些问题,或者上线了量级不够时也发现不了。这就说明一个稳定健壮的中间件,是需要打磨的,不是说书上抄了一段netty的代码,上线了它就能正常运行了。

当然进步的过程其实就是踩坑的过程,有了相应的场景,实实在在的并发量,踩过足够的坑,才能打磨好一个框架。

也希望有相关应用场景的同学,关注京东热key探测框架。

作者:tianyaleixiaowu

原文链接请点击文末“了解更多”

相关文章:

利用box-shadow绘图

上篇博客提到过&#xff0c;box-shadow属性的本质是对形状的复制&#xff0c;那么如果我设置一个1*1px的i标签&#xff0c;利用box-shadow可以叠加的特性&#xff0c;给每一个1*1px的阴影赋上颜色&#xff0c;那么最后不就是一幅图片了么。 html代码很简单&#xff1a; <!do…

为什么要使用Go语言?Go语言的优势在哪里?

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 Go语言之所有越来越受到开发者的欢迎&#xff0c;我认为与其超高的实用价值密不可分。要知道Go语言是为了解决现实问题而设计的&#xff0c;而不是为…

BI之SSAS完整实战教程3 -- 创建第一个多维数据集

上一篇我们已经完成了数据源的准备工作&#xff0c;现在我们就开始动手&#xff0c;创建第一个多维数据集(Cube)。 文章提纲 使用多维数据集向导创建多维数据集 总结Cube设计器简介 维度细化 总结 一、使用向导创建多维数据集 在Analysis Services中&#xff0c;可以通过3种…

python opencv local_threshold_Python-OpenCV中的cv2.threshold

主要记录Python-OpenCV中的cv2,threshold()方法&#xff1b;官方文档 cv2.threshold() def threshold(src, thresh, maxval, type, dstNone): """ 设置固定级别的阈值应用于多通道矩阵 例如&#xff0c;将灰度图像变换二值图像&#xff0c;或去除指定级别的噪声…

java中decimalFormat格式化数值

介绍 我们经常要对数字进行格式化&#xff0c;比如取小数点后两位小数&#xff0c;或者加个百分比符号等&#xff0c;Java提供了DecimalFormat这个类0 和 # 的区别 "#"可以理解为在正常的数字显示中&#xff0c;如果前缀与后缀出现不必要的多余的0&#xff0c;则将其…

GO语言有哪些优势?怎样入门?

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 1、学习曲线 它包含了类C语法、GC内置和工程工具。这一点非常重要&#xff0c;因为Go语言容易学习&#xff0c;所以一个普通的大学生花一个星期就能…

POJ-2955 Brackets

题目大意&#xff1a; 给你一个只由(、)、[、]组成的字符串&#xff0c;问你这个字符串的子串能够匹配的最长长度是多少。 能够匹配的意思是这样的&#xff1a; 1.如果s是个空串&#xff0c;那么它是匹配的。 2.如果子串是(s)或者[s]&#xff0c;那么它也是匹配的&#xff0c;其…

CentOS7.4-btrfs管理及使用

btrfs, B-tree File System, GPL开源文件系统, 支持CoW即读时写入. 核心特性: 多物理卷支持;btrfs可由多个底层磁盘组成支持RAID mkfs.btrfs 命令的man文档支持: raid0, raid1, raid5, raid6,raid10, single or dup联机"添加, 移除, 修改" CoW写时复制更新机制 对文件…

取消对 null 指针“l”的引用。_C++中的引用

当变量声明为引用时&#xff0c;它将成为现有变量的替代名称。通过在声明中添加“&#xff06;”&#xff0c;可以将变量声明为引用。#include using namespace std; int main() { int x 10; // ref is a reference to x. int& ref x; // Value of x is no…

你没听说过的Go语言惊人优点

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 在这篇文章中&#xff0c;我将讨论为什么你需要尝试一下 Go 语言&#xff0c;以及应该从哪里学起。 Go 语言是可能是最近几年里你经常听人说起的编…

JS document

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><div id"one">今天下雨</div><table border"1" cellspacing"0" cellpadding"…

python 流写入文件_python文件流操作

博主在学习python时对文件进行操作时经常踩一下坑。所以专门梳理了一下。有问题麻烦指出哈。 python对于文件的操作我们一般是用open&#xff08;&#xff09;。我们根据python的源码可以看出。我们必须要传的参是file即打开文件的URL。同时open方法默认是是r的打开方式即只读。…

使用 Python 从零开始开发区块链应用程序

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 “区块链”是什么&#xff1f; 区块链是一种存储数字数据的方式。数据可以是任何内容。对于比特币&#xff0c;它是事务&#xff08;在帐户之间转移…

Mybatis学习记录-使用问题总结之一DISTINCT

问题1&#xff1a;手动修改的查询语句&#xff0c;放入到项目中后显示结果和实际查询结果不一致 由于实际情况中用的了分页功能&#xff0c;导致最终的语句在查询完成后&#xff0c;添加了分页项&#xff0c;即如下代码。 ROW_NUMBER() OVER ( ORDER BY COLUMNS) PAGE_ROW_NUMB…

python xlrd读取excel所有数据_python读取excel进行遍历/xlrd模块操作

我就废话不多说了&#xff0c;大家还是直接看代码吧~ #!/usr/bin/env python # -*- coding: utf-8 -*- import csv import xlrd import xlwt def handler_excel(filenamer/Users/zongyang.yu/horizon/ops_platform/assets/upload/1.xlsl): # 打开文件 workbook xlrd.open_work…

【NOIP2015提高组Day1】 神奇的幻方

【问题描述】 幻方是一种很神奇的 N*N矩阵&#xff1a;它由数字1,2,3, … … ,N*N 构成&#xff0c;且每行、每列及两条对角线上的数字之和都相同。 当N为奇数时&#xff0c;我们可以通过以下方法构建一个幻方&#xff1a; 首先将1写在第一行的中间。 之后&#xff0c;按如下…

40行python开发一个区块链

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 尽管有人认为区块链目前还是个不成熟的解决方案&#xff0c;但它无疑称得上是计算机发展历史上的一个奇迹。但是&#xff0c;到底区块链是什么呢?…

网络实验的背景流

在最近做的网络实验中&#xff0c;发现背景流必须要先于实验流开始&#xff0c;并且要长于实验流的时间&#xff0c;这样才能看出实验流的规律。如果背景流后发于实验流&#xff0c;就会变成竞争模式&#xff0c;实验流就会被抢占或者挤压。转载于:https://www.cnblogs.com/fen…

python捕获异常后处理_python异常捕获处理

一、异常处理 在程序运行过程中&#xff0c;总会遇到各种各样的错误。程序一旦出错就停止运行了&#xff0c;此时就需要捕捉异常&#xff0c;通过捕捉到的异常&#xff0c;我们再去做对应的处理 写一个函数&#xff0c;实现除法运算 def calc(a,b): return a/b print(calc(5,1)…

《JS权威指南学习总结--第十一章子集和扩展》

js子集和扩展&#xff1a;http://www.cnblogs.com/ahthw/p/4298449.html ES6新增let和const关键字&#xff1a;http://www.cnblogs.com/telnetzhang/p/5639949.html JS中 var 和 let 关键字的区别&#xff1a;http://www.w3cfuns.com/notes/21400/891cac0f6bff2d7f25d3084618e8…

最常见的 35 个 Python 面试题及答案

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 作为一个 Python 新手&#xff0c;你必须熟悉基础知识。在本文中我们将讨论一些 Python 面试的基础问题和高级问题以及答案&#xff0c;以帮助你完…

PHP 中日期时间函数 date() 用法总结

[导读] date()是我们常用的一个日期时间函数&#xff0c;下面我来总结一下关于date()函数的各种形式的用法&#xff0c;有需要学习的朋友可参考。格式化日期date() 函数的第一个参数规定了如何格式化日期 时间。它使用字母来表示日期和时间 格式化日期date() 函数的第一个参数规…

mac mysql的安装

mac是重装的系统&#xff0c;很干净&#xff0c;没有xmpp等组合的服务器。 1. 安装mysql server https://dev.mysql.com/downloads/mysql/ 这里是官网地址&#xff0c;选择需要的版本下载&#xff0c;我下载的是第一个dmg的&#xff0c;进入后&#xff0c;会让登陆或注册&#…

windows系统和linux系统可以使用相同的js代码吗_「React 手册 」在 Windows 下使用 React , 你需要注意这些问题...

大家好&#xff0c;本篇内容&#xff0c;我要和大家聊聊使用 Windows 开发 React &#xff0c;你需要注意的一些问题。首先说明下&#xff0c;我不是使用 windows 进行开发&#xff0c;因为其配置开发环境来说不是特别方便&#xff0c;我更喜欢 苹果mac 或者乌班图这样的系统&a…

以太坊:比特币 + 无限可能

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 还记你得刚学编程时&#xff0c;第一次使用“对象”的感觉吗&#xff1f;还记你第一次尝试函数式编程的样子吗&#xff1f;这些编程范式&#xff0…

thinkphp5内置标签

thinkphp5内置标签 知道内置标签怎么用&#xff0c;查手册的时候好查 却功能的时候在里面找着来用 内置标签一览 1 内置标签2 3 变量输出使用普通标签就足够了&#xff0c;但是要完成其他的控制、循环和判断功能&#xff0c;就需要借助模板引擎的标签库4 功能了&#xff0c;系统…

python可视化窗口制作一个摇骰子游戏_使用python制作一个抽奖小游戏——骰子游戏...

1.模拟真实环境掷骰子 从Python标准库中调用模块&#xff1a;random——random中包含以各种方式生成随机数的函数 从random中引用randint这一函数——骰子都是有固定面数 from random import randint **2. **创建Die类**** 骰子属性sides&#xff08;面数&#xff09;默认为6面…

C#拾遗(一、基本类型)

1. C#是一种块结构语言&#xff0c;用花括号{}分块&#xff0c;但是用#region和#endregion来定义可以展开和折叠的代码区域 #region 这是引用区 using System; ...... #endregion 2. C#简单类型都是小写&#xff0c;bool,string类型要区别于Java的写法&#xff1b;float、decim…

我不喜欢Go语言的十个理由

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 Go 语言有多火爆&#xff1f;国外如 Google、AWS、Cloudflare、CoreOS 等&#xff0c;国内如七牛、阿里、知乎等都已经开始大规模使用 Go 语言开发…

写扩展性好的代码:函数

http://blog.jobbole.com/107442/转载于:https://www.cnblogs.com/answercard/p/8862006.html