记一次web服务的调优

来源:互联网 发布:项目管理流程软件 编辑:程序博客网 时间:2024/06/03 16:54

首先,描述一下环境,简单的web服务,关键日志写入kafka,要求qps达到单机10K即可。

后面将遇到的问题、解决方案和原理记录如下:


1、内存占用过大,虽然jvm的堆内存设为1G,但进程实际内存使用量达到了12G

     解决方案程序中使用了kafkanewkafka producer来向kafka中写日志,调整kafka参数解决,调大了batch.sizepartition

     原理原因还是batch.sizepartition设置过小,导致要发送的数据包过多,都堵在了producer的机器上,而kafka producer又使用了zero copy技术,使得占用了大量的内核态内存无法释放,而内核态内存又不是堆内存,不由用户控制,所以出现了进程占用内存过大的情况,kafka版本为0.8.2.2(其实这里还存在一点疑问,网上说kafka通信时使用了zerocopy,但我实际跟进代码时,producer使用的是ByteBuffer.allocate,也就是说使用的是堆内存,而没有调用ByteBuffer.allocateDirect创建非堆内存,与网上说的有冲突。如果哪位朋友知道这个问题的原理,还请赐教)


2、长时间负载高的情况下,程序性能下降明显,查看GC情况,发现频繁进行full gc

       解决方案通过使用jmap -dump将内存中的对象信息导出到文件,使用jhat命令分析,查出是一个kafka callback对象过多,而这个Callback对象包含了一些内容较长的字符串类型的私有变量,导致字符串占了较多内存,而kafkaproducer有缓存机制,就会导致Callback对象占据内存无法释放,后来通过删除了Callback的私有变量,将Callback变为单例,来减少了内存占用

 

3、多线程丢数据

 解决方案:由于有实时性要求,所以不能阻塞,只能通过加大队列长度和子线程的数量的方式 

     原理主线程向子线程分配任务时,未自己管理BlockingQueue,而是交由操作系统自己管理,向子线程分配任务时,直接分配Callable对象

// 创建线程池ExecutorService pool = newThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS,          newLinkedBlockingQueue<>(queueCapacity));// 向子线程分配任务pool.submit(() ->producer.send(new ProducerRecord<>(topic, null, record), kafkaCallback));

由于submit方法调用了BlockingQueueoffer方法来添加,若队列已满,则返回false,而不是阻塞,且会抛出RejectedExecutionExceptionRuntimeException的子类),导致抛出异常和数据丢失

若考虑使用阻塞的方式,则可修改为自己控制BlockingQueue,若不想使用阻塞的方式,则可加大queueCapacitythreadPoolSize

 

4、日志使用log4j记录和写kafka性能差别过大,写日志文件达到3000qps,写kafka能达到11Kqps

     解决方案:改为使用log4j 2.x,或使用kafka

      原理:通过跟进log4j的代码,发现log4j 1.x中使用了很多sychronized代码,导致log4j多线程下性能问题严重,改为log4j 2.x可使qps提高到10K

0 0