RocketMQ
中的实现正是死信队列。
]]> 我们从一个例子开始吧:一个订单服务 OrderService
,IP为192.168.0.110
连接了商品服务 ProductService
, ip 为192.168.0.111
,其中订单服务中的方法比较多,并且很多请求也刚好路由到192.168.0.111
的 ProductService
服务。那问题就来了:110
机器作为客户端是怎么控制连接数的?会不会无限量地和111
机器进行TCP连接?
我们先看一下Dubbo
的官方文档对“连接控制”的说明文档 : http://dubbo.apache.org/zh-cn/docs/user/demos/config-connections.html 。
在xml配置方式中xml accepts="10"
和 connections="10"
分别在服务端和客户端进行了相应的连接控制。下面我们看一下源码,追一下连接控制的原理。
我们看一下DubboProtocol.java
的创建客户端tcp连接的方法,int connectNum正是每个客户端对服务端的tcp连接数,默认是1
,当然可以修改成更大。默认是1,这样一个客户端的调用service
数最多也不会超过1000吧。这样就不会出现单机创建TCP连接数过多的问题。
1 | /** |
客户端和服务端是一对一的,建立长连接,那么如果客户端并发访问,他们是怎么和服务端交互的呢?
经过看代码:
下面咱们试图从代码中找到痕迹。一路追踪,我们来到这个类:com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel.java
,先来看看其中的request
方法,大概在第101行左右:
1 | public ResponseFuture request(Object request, int timeout) throws RemotingException { |
注意这个方法返回的ResponseFuture
对象,当前处理客户端请求的线程在经过一系列调用后,会拿到ResponseFuture
对象,最终该线程会阻塞在这个对象的下面这个方法调用上,如下:
1 | public Object get(int timeout) throws RemotingException { |
上面我已经看到请求线程已经阻塞,那么又是如何被唤醒的呢?再看一下com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.java
,其实所有实现了ChannelHandler
接口的类都被设计为装饰器模式,所以你可以看到类似这样的代码:
1 | protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) { |
现在来仔细看一下HeaderExchangeHandler
类的定义,先看一下它定义的received
方法,下面是代码片段:
1 | public void received(Channel channel, Object message) throws RemotingException { |
我们主要看中间的那个条件分支,它是用来处理响应消息的,也就是说当dubbo客户端接收到来自服务端的响应后会执行到这个分支,它简单的调用了handleResponse
方法,我们追过去看看:
1 | static void handleResponse(Channel channel, Response response) throws RemotingException { |
熟悉的身影:DefaultFuture
,它是实现了我们上面说的ResponseFuture
接口类型,实际上细心的童鞋应该可以看到,上面request
方法中其实实例化的就是这个DefaultFutrue
对象:
1 | DefaultFuture future = new DefaultFuture(channel, req, timeout); |
那么我们可以继续来看一下DefaultFuture.received
方法的实现细节:
1 | public static void received(Channel channel, Response response) { |
留一下我们之前提到的id的作用,这里可以看到它已经开始发挥作用了。通过id
,DefaultFuture.FUTURES
可以拿到具体的那个DefaultFuture
对象,它就是上面我们提到的,阻塞请求线程的那个对象。好,找到目标后,调用它的doReceived
方法,这就是标准的java多线程编程知识了:
1 | private void doReceived(Response res) { |
这样我们就可以证实上图中左边的绿色箭头所标注的两点。
参考链接:https://blog.csdn.net/joeyon1985/article/details/51046548
]]>商业产品:
普元:bps eos
在JDK1.7中的HotSpot中,已经把原本放在方法区的字符串常量池移出。
从JDK7开始永久代的移除工作,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
随着JDK8的到来:
JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。
下面来一张图看一下java7到8的内存模型吧(这个是在网上找的图,如有侵权问题请联系我删除。)
运行时常量池(Runtime Constant Pool)的所处区域一直在不断的变化,在java6时它是方法区的一部分;1.7又把他放到了堆内存中;1.8之后出现了元空间,它又回到了方法区。
Metaspace 结构是怎么样的?
参考:https://blog.csdn.net/weixin_42711325/article/details/86533192
]]>ActiveMQ | RabbitMQ | RocketMQ | Kafka | Pulsar | |
---|---|---|---|---|---|
代码地址 | apache/activemq | apache/rabbitmq-server | apache/rocketmq | apache/kafka | apache/pulsar |
PRODUCER-COMSUMER | 支持 | 支持 | 支持 | 支持 | 支持 |
PUBLISH-SUBSCRIBE | 支持 | 支持 | 支持 | 支持 | 支持 |
REQUEST-REPLY | 支持 | 支持 | |||
API完奋性 | 高 | 高 | 高 | 高 | |
多语言支持 | 支持,IAVA优先 | 语言无关 | 支持 | 支持,java优先 | |
单机吞吐量 | 万级 | 万级 | 万级 | 十万级 | 单个分区高达 1.8 M 消息/秒 |
消息延迟 | 微秒级 | 毫秒级 | 毫秒级 | 99% 的生产延迟小于5ms。 | |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 非常高(分布式) | 高 |
消息丢失 | 低 | 低 | 理论上不会丢失 | 理论上不会丢失 | |
消息重复 | 可控制 | 理论上会有重复 | |||
文挡完备性 | 高 | 高 | 高 | 高 | 高 |
提供快速入门 | 有 | 有 | 有 | 有 | 有 |
首次部署难度 | 低 | 中 | 高 | ||
社区活跃度 | 高 | 高 | 高 | 高 | 高 |
商业支持 | 无 | 无 | 阿里云 | 无 | |
成熟度 | 成熟 | 成熟 | 成熟 | 成熟日志领域 | |
支持协议 | OpenWire、STOMP、REST、 XMPP、AMQP | AMQP | 白己定义的一套,社区提供JMS,不成熟) | ||
持久化 | 内存、文件、数据库 | 内存、文件 | 磁盘文件 | PageCache ->磁盘 | Apache BookKeeper |
事务 | 支持 | 支持 | 支持 | ||
负载均衡 | 支持 | 支持 | 支持 | ||
管理界面 | 一般 | 好 | 有web console实现 | ||
部署方式 | 独立、嵌入 | 独立 | 独立 | ||
特点 | 功能齐全,被大 望开源项目使用 | 由于Erlang语言的并发能力,性能很好 | 各个环节分布式扩展设计,主从HA;支持上万个队列;多 种消费模式;性能很好 | ||
评价:优点 | 成熟的产品,已经在很多公司得到应用(非大规横场景);有较多的文档;备种协议支持较好;有多重语富的成熟的客户端; | 由于erlang语富的特 性,mq性能较好;管埋界面 较丰富,在互联网公司也有 较大规棋的应用;支持amqp协议,有多种语言且支持 amqp的客户端可用; | 模型简单,接口易用(JMS接口在很多场合并不太实用);在阿里大规棋应用;目前支付宝中的余额宝等新兴产品均使用rocketmq;集群规棋大槪在50台左右,单日处理消息上百亿;性能非常好,可以大量消息堆积在broker中;支持多种消费:包括集群消费、广播消费等;社区活跃,版本更新很快。 | 地域复制、多租户、扩展性、读写隔离等等;对 Kubernetes 的友好支持。 | |
评价:缺点 | 根据其他用户反馈,会出现莫名其妙的问题,且会丢消息。目前社区不活跃;不适合用于上千个队列的应用场景。 | erlang语言难度较大。集群不支持动态扩展。 | 多语言客户端支持需加强 | 部署相对复杂;新来者,文档较少 |
大数据行业标配组件
有事务性消息、私信队列等支持,适合交易场景
新贵,地域复制、多租户、扩展性比较好
erlang编写,性能较好。有不少互联网公司用。不过因为erlang,社区开发者较少
项目较老,不够活跃,会丢消息,不适合在互联网项目使用
一开始就是存储在PageCache上的,定期flush到磁盘上的,也就是说,不是每个消息都被存储在磁盘了,如果出现断电或者机器故障等,PageCache上的数据就丢失了。
这个是总结出的到目前为止没有发生丢失数据的情况
1 | //producer用于压缩数据的压缩类型。默认是无压缩。正确的选项值是none、gzip、snappy。压缩最好用于批量处理,批量处理消息越多,压缩性能越好 |
强行kill线程,导致消费后的数据,offset没有提交,partition就断开连接。比如,通常会遇到消费的数据,处理很耗时,导致超过了Kafka的session timeout时间(0.10.x版本默认是30秒),那么就会re-blance重平衡,此时有一定几率offset没提交,会导致重平衡后重复消费。
如果在close之前调用了consumer.unsubscribe()则有可能部分offset没提交,下次重启会重复消费
kafka数据重复 kafka设计的时候是设计了(at-least once)至少一次的逻辑,这样就决定了数据可能是重复的,kafka采用基于时间的SLA(服务水平保证),消息保存一定时间(通常为7天)后会被删除
]]>功能: 当committer合并别人提交的pull request
的时候,同时联动关闭问题issue
配置: https://help.github.com/cn/articles/closing-issues-using-keywords
功能: 当committer合并别人提交的选择将提交点进行合并,类似于指令git squash
配置: https://help.github.com/cn/articles/about-merge-methods-on-github
功能:在合作开发中,一些分支的提交者好久没有活动了,远程分支已经修改了很多东西。 committer或者pr的提交可以在github界面手动同步主分支的最新代码.
类似于指令
1 | git remote update -p |
配置方法如下:
效果:
配置: https://help.github.com/cn/articles/enabling-required-status-checks
功能: 用junit、travis-ci 和codecov等采集项目的测试覆盖率,从而进行持续集成
配置:
Travis CI + Codecov + Junit5 + jacoco + Maven + java8 above Java Example
https://github.com/lovepoem/codecov-travis-maven-junit5-example
Travis CI + Codecov + Junit4 + cobertura + java1.7 + Maven Java Example
https://github.com/lovepoem/codecov-travis-maven-junit4-example
最早时候github不支持将一个issue指派给对项目没有写权限的用户。在2019年下半年开始,github增加了“Triage”角色:没有项目的直接写权限,却能review pr的人。首先issue可以指派给“Triage”角色;一般用户只要在issue上加评论,就可以将issue指派给她。
]]> 订阅的含义是啥??用图画一下订阅模式。一般情况:订报刊。搞技术的:消息队列,异步。为什么异步的模式是未来商业模式的方向和趋势?重点
将书中各个行业的异步商业模型简要讲解,必要时候画出流程图。
自己切身感受和思考。在软件架构方面,事情本来的样子是什么?异步编程,消息驱动。开源社区的运营和商业化方面:吸引同好,
]]>单线程的, Redis6 之后多线程IO
1.redis是基于内存的,内存的读写速度非常快;
2.redis是单线程的,省去了很多上下文切换线程的时间;
3.redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。
问题二:Redis都有什么数据结构,内部存储数据结构是啥?
String——字符串 简单动态字符串(SDS[Simple Dynamic String])
Hash——字典
List——列表
Set——集合
Sorted Set——有序集合
skiplist数据结构
skiplist作为zset的存储结构,整体存储结构如下图,核心点主要是包括一个dict对象和一个skiplist对象。dict保存key/value,key为元素,value为分值;skiplist保存的有序的元素列表,每个元素包括元素和分值。两种数据结构下的元素指向相同的位置。
问题三 Redis的持久化策略有哪些?
Redis两种持久化方式(RDB&AOF)
RDB每次进行快照方式会重新记录整个数据集的所有信息。RDB在恢复数据时更快,可以最大化redis性能,子进程对父进程无任何性能影响。
AOF有序的记录了redis的命令操作。意外情况下数据丢失甚少。AOF同步也是一种把记录写到硬盘上的行为,在上述两个步骤之外,Redis额外加一步命令,Redis先把记录追加到自己维护的一个aof_buf中。所以AOF持久化分为三步:1、命令追加 2、文件写入 3.文件同步
]]> 超时机制是dubbo中的一个很重要的机制。在“快速失败”设计中,能将客户端断掉,防止服务端资源耗尽而被压挂。
我们先看一下Dubbo协议中的超时机制是怎么实现的。
超时的实现原理是什么?
我们先回顾一下dubbo的大致流程图。
在生产过程中都是客户端向服务端发送请求,在流程4。
我们可以看到:4的流程是: 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
超时是针对消费端还是服务端?
dubbo的超时是争对客户端的,由于是一种NIO模式,消费端发起请求后得到一个ResponseFuture,然后消费端一直轮询这个ResponseFuture直至超时或者收到服务端的返回结果。虽然超时了,但仅仅是消费端不再等待服务端的反馈并不代表此时服务端也停止了执行。
超时的实现原理是什么?
之前有简单提到过, dubbo默认采用了netty做为网络组件,它属于一种NIO的模式。消费端发起远程请求后,线程不会阻塞等待服务端的返回,而是马上得到一个ResponseFuture,消费端通过不断的轮询机制判断结果是否有返回。因为是通过轮询,轮询有个需要特别注要的就是避免死循环,所以为了解决这个问题就引入了超时机制,只在一定时间范围内做轮询,如果超时时间就返回超时异常。
1 | //ResponseFuture接口定义 |
超时的实现原理是什么?
之前有简单提到过, dubbo默认采用了netty做为网络组件,它属于一种NIO的模式。消费端发起远程请求后,线程不会阻塞等待服务端的返回,而是马上得到一个ResponseFuture,消费端通过不断的轮询机制判断结果是否有返回。因为是通过轮询,轮询有个需要特别注要的就是避免死循环,所以为了解决这个问题就引入了超时机制,只在一定时间范围内做轮询,如果超时时间就返回超时异常。
在客户端int DEFAULT_RETRIES = 2;
看到重试次数是2 ,就是说出了本身的一次请求,再失败后,又会再请求一次。
重试策略在集群的失败重试 FailoverClusterInvoker
的策略中:
1 | public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { |
可以看到使用的是for循环做的失败重试的。当有异常发生时候,就重试一次访问,直到达到最大重试次数为止。
参考:https://www.cnblogs.com/xuwc/p/8974709.html
]]>精英管理通常在每个Apache项目社区中都有不同的角色:
用户是指使用 apache 软件的人。他们通过以错误报告和功能建议的形式向开发人员提供反馈,为Apache项目做出贡献。用户通过在邮件列表和用户支持论坛上帮助其他用户来参与Apache社区的建设。
开发者是用代码或文档形式个 apache 项目做贡献的用户。他们采取额外步骤参与项目,积极参与开发人员邮件列表,参与讨论,提供补丁,文档,建议和批评。开发人员也被称为贡献者。
提交者是签订了贡献者许可协议(CLA)文件,并且被授予了 apache 代码库的写入权限的开发者。他们有一个apache.org邮件地址。他们不需要依赖其他人来打补丁,他们实际上正在为项目做出短期决策。PMC可以(甚至是默许)同意并批准它成为永久性的,或者他们可以拒绝它。但是请记住,是PMC做出的决定,而不是单个提交者。
PMC会员由于项目的发展和承诺的证明而当选的开发商或委员会成员。他们拥有对代码存储库的写访问权、apache.org邮件地址、对与社区相关的决策进行投票的权利以及向活动用户建议提交的权利。作为一个整体,项目管理咨询公司是控制项目的实体,没有其他人。特别是,项目管理委员会必须对其项目软件产品的任何正式发布进行投票。
项目管理委员会(PMC)的主席由董事会从PMC成员中任命。PMC作为一个整体是控制和领导项目的实体。主席是理事会与项目之间的接口。 PMC主席有特定的职责。
ASF成员是由现任成员提名并因基金会的发展和进步而当选的人。会员关心ASF本身。这通常通过与项目相关和跨项目活动的根源来证明。法律上,会员是基金会的“股东”之一。他们有权选举董事会,作为董事会选举的候选人,并提名一名委员为成员。他们也有权提出一个新的孵化项目(稍后我们将看到这意味着什么)。成员通过邮件列表和年度会议协调其活动。我们有一个完整的Apache成员列表。
]]>在使用Dubbo的过程中,在服务端压力大时候我们常常会遇到说线程池耗尽的这样一个错误日志:
1 | 17:54:34,026 WARN [New I/O server worker #1-4] - [DUBBO] Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-10.8.64.57:20880, Pool Size: 300 (active: 300, core: 300, max: 300, largest: 300), Task: 5821 (completed: 5621), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://x.x.x.x:20880!, dubbo version: 2.6.5, current host: x.x.x.x |
我们从错误堆栈可以看到,是服务端线程池溢出了。
二、源码分析
看一下服务端线程池代码是怎么写的,起到什么作用
画图 dubbo客户端调用服务端线程池的示意图
三、线程池知识
解释一下线程池的定义是用来干嘛的。
一些处理的办法:
适当加大服务端线程池,找到合理的配置。这个关联一线程池的配合准则,链接到老的文章。
四、分析原因,处理建议
为什么线程池升高呢? 主要从流量、cpu数据率、负载、jvm日志几方面分析
1)流量过大?(加缓存、对于上游上游服务)
2) 扩机器:cpu核数的增加
3) 宿主机此时负载高?
4)自己代码优化
5) 设置客户端快速失败
程序慢?io过多?(并行io)
]]>下面的日志信息trace.log
,字段分别是:时间| traceId|接口名|执行时间ms|执行结果
1 | 2018-10-01 14:00:00|1|getById|1|success| |
请实现如下需求:
1、执行结果大于200ms的记录
1 | awk -F "|" '{if($4>=200){print $1" "$2" "$3" "$4 }}' trace.log |
执行结果
1 | 2018-10-02 02:00:00 3 getById 1000 |
2、2018-10-01 日接口数量排行前3
1 | awk '/2018-10-01/' trace.log |awk -F "|" '{print $3}' |sort|uniq -c|sort -rn| head -3 |
执行结果
1 | 5 updateById |
3、对于各个接口的执行时间ms按照(0-50,50-100,100-300,300以上)范围进行数量统计
1 | awk -F "|" '{totalCnt[$3]++;if($4<=50){ms50[$3]++};if($4>50 && $4<=100){ms100[$3]++};if($4>100 && $4<=300){ms300[$3]++};if($4>300){ms300b[$3]++}}END{for(i in totalCnt)print i,int(ms50[i]),int(ms100[i]),int(ms300[i]),int(ms300b[i])}' trace.log |
执行结果
1 | getById 1 1 0 2 |
4、查询trace.log文件各个接口的失败率
1 | awk -F "|" '{totalCnt[$3]++;if($5=="fail"){failCnt[$3]++}}END{for(i in totalCnt)print i,(failCnt[i]/totalCnt[i])*100"%"}' trace.log |
执行结果
1 | getById 25% |
5、查询trace.log各个接口的平均耗时
1 | awk -F "|" '{totalCnt[$3]++;{rtSum[$3]+=$4}}END{for(i in totalCnt)print i,(rtSum[i]/totalCnt[i])}' trace.log |
执行结果
1 | getById 525.25 |
二 、简单数据处理
1、删除id.txt重复id
1 | aaaaa |
脚本
1 | cat id.txt | sort | uniq |
执行结果
1 | 111 |
一些参考:
http://techslides.com/grep-awk-and-sed-in-bash-on-osx
http://www.grapenthin.org/teaching/geop501/lectures/lecture_10_unix2.pdf
http://coewww.rutgers.edu/www1/linuxclass2005/lessons/lesson9/shell_script_tutorial.html
http://blog.chinaunix.net/uid-26736384-id-5756343.html
2、ConcurrentHashMap怎么做到线程安全的?
3、java8的,ConcurrentHashMap实现有哪些变化?
]]> 我们知道,java语言不像C++,将内存的分配和回收给程序员来处理。java用统一的垃圾回收机制来管理java进程的内存,就是通常所说的垃圾回收:GC(Garbage Collection)。
先到维基百科上看一下Garbage Collection的概念:
In computer science, garbage collection (GC) is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objectst that are no longer in use by the program. Garbage collection was invented by John McCarthy around 1959 to simplify manual memory management in Lisp.
这就面临一些问题:java进程到底是什么样的结构?java进程的内存哪些需要被回收,在什么条件下才回收,谁来回收?我们首先来看一些概念
从维基百科上看jvm的定义:
A Java virtual machine (JVM) is a virtual machine that enables a computer to run Java programs as well as programs written in other languages that are also compiled to Java bytecode.
在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的。 根据《Java虚拟机文档》 第七版的规定,Java虚拟机锁管理的内存包含下面几个运行时数据区域:
方法区、虚拟机栈、本地方法栈、队、程序计数器等。
程序计数器:
一块较小的内存空间,可以看做是当前线程所执行的字节码的行数指示器。
JAVA虚拟机栈:
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量表存放了编译器可知的:
本地方法栈:
为虚拟机使用到的Native方法服务,与虚拟机栈的作用类似。
JAVA堆:
Java堆是被所有线程共享的一块内存区域,此内存区域的唯一目的就是存放内存实例,所有的对象实例以及数组都要在堆上分配,是垃圾收集管理的主要区域。
方法区:
和Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编辑器编译后的代码等
运行时常量池
是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池来存放编译期生成的各种字面量和符号引用。
直接内存
对外内存,不是jvm的存储,所以不受jvm的堆参数控制。NIO可以直接分配对外内存,可能会导致OutOfMemoryError,导致java进程僵死。
我们Java程序需要通过栈上的reference数据来操作堆上的具体对象。 有两种方式:使用句柄访问和通过指针访问。
使用句柄
Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例与类型数据各自的具体地址信息。
通过指针
Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
参考:
最近我们上线了一个比较底层的业务服务,上线后研发也去盯着服务器看了日志,没有发现有啥问题,就将服务全量上线了。谁知道过了15分钟左右,上游业务方打电话过来了,说报调用我们的业务服务出现超时。
这时候研发赶紧登上相关的机器去看,发现日志量很少,机器很卡。再有人去登入这台机器的时候就登入不了。研发初步考虑是网络问题或者宿主机问题,于是找来运维。
运维登入进去机器通过
1 | ps -ef | wc -l |
查看运行的进程数,结果为60000多。通过脚本查看linux进程的进程限制:
1 | cat /etc/security/limits.conf |
,结果是
1 | root soft nofile 65535 |
进程总数差不多进程总数的限制。考虑到半小时前更新了新服务,去服务的日志看了看,原来tomcat有jvm内存溢出的日志:
1 | java 进程out of memeory |
通过上面的一些现象,赶紧将刚才的发布的机器回滚了,避免更大的问题。观察了半个小时,上游再也没有发现我们的服务报错的问题了。于是我们终端review了刚刚上线的一个pr,
发现线程池的代码竟然定义在方法体里面:
1 | public class BizService{ |
这样每次调用这个方法,就构建一个新的线程池,开启了新的8个线程。又查找了《Java线程与Linux内核线程的映射关系》[1] , 发现:JVM线程跟内核轻量级进程有一一对应的关系。线程数达到60000多,处级进程上限65535。 每开启一个进程。
对于应用进程发生:out of memory,是因为多线程的开销内存超过jvm的最大堆内存限制。 没开启一个进程,都有内存开销,内存超过linux内存的上限了,怪不得机器快僵死。
为了验证这个猜想,于是将有错误的代码进行压测,果然问题复现,出OutOfMemoryError和机器被拖挂。然后将代码修改为:
1 | public class BizService{ |
然后进行了压测,问题没有复现,问题解决。
复盘会议上,qa和相关人一起提出了如下方案来避免类似问题:
本次线程池超限,java进程也有报错,就是报OutOfMemoryError
,可是报警系统竟然没有第一时间收到。查了监控系统,在前一段项目进程了重构,日志路径修改了,原来的采集配置竟然没有同时修改。导致造成损失却没有收到警报。
本次程序员的线程池代码为低级失误,review认真是可以看出来的。
本次pr,有线程池的使用,所以要进行压测
添加平台监控,当linux进程大于20000个时候就就对进程数进行报警
参考:
[1] http://blog.sina.com.cn/s/blog_605f5b4f010198b5.html
修改记录:
2019-07-18 丰富了案例的描述
]]>