前言

每一种该语言在某些极限情况下的表现一般都不太一样,那么我常用的Java语言,在达到100万个并发连接情况下,会怎么样呢,有些好奇,更有些期盼。

这次使用经常使用的顺手的netty NIO框架(netty-3.6.5.Final),封装的很好,接口很全面,就像它现在的域名 netty.io,专注于网络IO。

整个过程没有什么技术含量,浅显分析过就更显得有些枯燥无聊,准备好,硬着头皮吧。

测试服务器配置

运行在VMWare Workstation 9中,64位Centos 6.2系统,分配14.9G内存左右,4核。

已安装有Java7版本:

  1. java version "1.7.0_21"
  2. Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
  3. Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)

在/etc/sysctl.conf中添加如下配置:

  1. fs.file-max = 1048576
  2. net.ipv4.ip_local_port_range = 1024 65535
  3. net.ipv4.tcp_mem = 786432 2097152 3145728
  4. net.ipv4.tcp_rmem = 4096 4096 16777216
  5. net.ipv4.tcp_wmem = 4096 4096 16777216
  6. net.ipv4.tcp_tw_reuse = 1
  7. net.ipv4.tcp_tw_recycle = 1

在/etc/security/limits.conf中添加如下配置:

  1. * soft nofile 1048576
  2. * hard nofile 1048576

测试端

测试端无论是配置还是程序和以前一样,翻看前几篇博客就可以看到client5.c的源码,以及相关的配置信息等。

服务器程序

这次也是很简单呐,没有业务功能,客户端HTTP请求,服务端输出chunked编码内容。

入口HttpChunkedServer.java:

  1. package com.test.server;
  2. import static org.jboss.netty.channel.Channels.pipeline;
  3. import java.net.InetSocketAddress;
  4. import java.util.concurrent.Executors;
  5. import org.jboss.netty.bootstrap.ServerBootstrap;
  6. import org.jboss.netty.channel.ChannelPipeline;
  7. import org.jboss.netty.channel.ChannelPipelineFactory;
  8. import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
  9. import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
  10. import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
  11. import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
  12. import org.jboss.netty.handler.stream.ChunkedWriteHandler;
  13. public class HttpChunkedServer {
  14. private final int port;
  15. public HttpChunkedServer(intport) {
  16. this.port = port;
  17. }
  18. public void run() {
  19. // Configure the server.
  20. ServerBootstrap bootstrap = new ServerBootstrap(
  21. new NioServerSocketChannelFactory(
  22. Executors.newCachedThreadPool(),
  23. Executors.newCachedThreadPool()));
  24. // Set up the event pipeline factory.
  25. bootstrap.setPipelineFactory(newChannelPipelineFactory() {
  26. public ChannelPipeline getPipeline ()throws Exception {
  27. ChannelPipeline pipeline = pipeline();
  28. pipeline.addLast("decoder", new HttpRequestDecoder());
  29. pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
  30. pipeline.addLast("encoder", new HttpResponseEncoder());
  31. pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
  32. pipeline.addLast("handler", new HttpChunkedServerHandler());
  33. return pipeline;
  34. }
  35. });
  36. bootstrap.setOption("child.reuseAddress", true);
  37. bootstrap.setOption("child.tcpNoDelay", true);
  38. bootstrap.setOption("child.keepAlive", true);
  39. // Bind and start to accept incoming connections.
  40. bootstrap.bind(newInetSocketAddress(port));
  41. }
  42. public static void main(String[] args) {
  43. int port;
  44. if (args.length > 0) {
  45. port = Integer.parseInt(args[0]);
  46. } else {
  47. port = 8080;
  48. }
  49. System.out.format("server start with port %d \n", port);
  50. new HttpChunkedServer(port).run();
  51. }
  52. }

唯一的自定义处理器HttpChunkedServerHandler.java:

  1. package com.test.server;
  2. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
  3. import static org.jboss.netty.handler.codec.http.HttpMethod.GET;
  4. import static org.jboss.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
  5. import static org.jboss.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
  6. import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
  7. import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
  8. import java.util.concurrent.atomic.AtomicInteger;
  9. import org.jboss.netty.buffer.ChannelBuffer;
  10. import org.jboss.netty.buffer.ChannelBuffers;
  11. import org.jboss.netty.channel.Channel;
  12. import org.jboss.netty.channel.ChannelFutureListener;
  13. import org.jboss.netty.channel.ChannelHandlerContext;
  14. import org.jboss.netty.channel.ChannelStateEvent;
  15. import org.jboss.netty.channel.ExceptionEvent;
  16. import org.jboss.netty.channel.MessageEvent;
  17. import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
  18. import org.jboss.netty.handler.codec.frame.TooLongFrameException;
  19. import org.jboss.netty.handler.codec.http.DefaultHttpChunk;
  20. import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
  21. import org.jboss.netty.handler.codec.http.HttpChunk;
  22. import org.jboss.netty.handler.codec.http.HttpHeaders;
  23. import org.jboss.netty.handler.codec.http.HttpRequest;
  24. import org.jboss.netty.handler.codec.http.HttpResponse;
  25. import org.jboss.netty.handler.codec.http.HttpResponseStatus;
  26. import org.jboss.netty.util.CharsetUtil;
  27. public class HttpChunkedServerHandlerextends SimpleChannelUpstreamHandler {
  28. private static final AtomicInteger count = new AtomicInteger(0);
  29. private void increment() {
  30. System.out.format("online user %d\n", count.incrementAndGet());
  31. }
  32. private void decrement() {
  33. if (count.get() <= 0) {
  34. System.out.format("~online user %d\n", 0);
  35. } else {
  36. System.out.format("~online user %d\n", count.decrementAndGet());
  37. }
  38. }
  39. @Override
  40. public void messageReceived(ChannelHandlerContextctx, MessageEvent e)
  41. throws Exception {
  42. HttpRequest request = (HttpRequest) e.getMessage();
  43. if (request.getMethod() != GET) {
  44. sendError(ctx, METHOD_NOT_ALLOWED);
  45. return;
  46. }
  47. sendPrepare(ctx);
  48. increment();
  49. }
  50. @Override
  51. public void channelDisconnected(ChannelHandlerContextctx,
  52. ChannelStateEvent e) throws Exception {
  53. decrement();
  54. super.channelDisconnected(ctx, e);
  55. }
  56. @Override
  57. public void exceptionCaught(ChannelHandlerContextctx, ExceptionEvent e)
  58. throws Exception {
  59. Throwable cause = e.getCause();
  60. if (cause instanceof TooLongFrameException) {
  61. sendError(ctx, BAD_REQUEST);
  62. return;
  63. }
  64. }
  65. private static void sendError(ChannelHandlerContext ctx,
  66. HttpResponseStatus status) {
  67. HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
  68. response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
  69. response.setContent(ChannelBuffers.copiedBuffer(
  70. "Failure:" + status.toString() + "\r\n", CharsetUtil.UTF_8));
  71. // Close the connection as soon as the error message is sent.
  72. ctx.getChannel().write(response)
  73. .addListener(ChannelFutureListener.CLOSE);
  74. }
  75. private void sendPrepare(ChannelHandlerContextctx) {
  76. HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
  77. response.setChunked(true);
  78. response.setHeader(HttpHeaders.Names.CONTENT_TYPE,
  79. "text/html; charset=UTF-8");
  80. response.addHeader(HttpHeaders.Names.CONNECTION,
  81. HttpHeaders.Values.KEEP_ALIVE);
  82. response.setHeader(HttpHeaders.Names.TRANSFER_ENCODING,
  83. HttpHeaders.Values.CHUNKED);
  84. Channel chan = ctx.getChannel();
  85. chan.write(response);
  86. // 缓冲必须凑够256字节,浏览器端才能够正常接收 ...
  87. StringBuilder builder = new StringBuilder();
  88. builder.append("");
  89. int leftChars = 256 - builder.length();
  90. for (int i = 0; i < leftChars; i++) {
  91. builder.append("");
  92. }
  93. writeStringChunk(chan, builder.toString());
  94. }
  95. private void writeStringChunk(Channelchannel, String data) {
  96. ChannelBuffer chunkContent = ChannelBuffers.dynamicBuffer(channel
  97. .getConfig().getBufferFactory());
  98. chunkContent.writeBytes(data.getBytes());
  99. HttpChunk chunk = new DefaultHttpChunk(chunkContent);
  100. channel.write(chunk);
  101. }
  102. }

启动脚本start.sh

  1. set CLASSPATH=.
  2. nohup java -server -Xmx6G -Xms6G -Xmn600M -XX:PermSize=50M -XX:MaxPermSize=50M -Xss256K -XX:+DisableExplicitGC -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -Djava.ext.dirs=lib com.test.server.HttpChunkedServer 8000>server.out 2>&1 &

达到100万并发连接时的一些信息

每次服务器端达到一百万个并发持久连接之后,然后关掉测试端程序,断开所有的连接,等到服务器端日志输出在线用户为0时,再次重复以上步骤。在这反反复复的情况下,观察内存等信息的一些情况。以某次断开所有测试端为例后,当前系统占用为(设置为list_free_1):

  1. total used free shared buffers cached
  2. Mem: 15189 7736 7453 0 18 120
  3. -/+ buffers/cache: 7597 7592
  4. Swap: 4095 948 3147

通过top观察,其进程相关信息

  1. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  2. 4925 root 20 0 8206m 4.3g 2776 S 0.3 28.8 50:18.66 java

在启动脚本start.sh中,我们设置堆内存为6G。

ps aux|grep java命令获得信息:

  1. root 4925 38.0 28.8 8403444 4484764 ? Sl 15:26 50:18 java -server...HttpChunkedServer 8000

RSS占用内存为4484764K/1024K=4379M

然后再次启动测试端,在服务器接收到online user 1023749时,ps aux|grep java内容为:

  1. root 4925 43.6 28.4 8403444 4422824 ? Sl 15:26 62:53 java -server...

查看当前网络信息统计

  1. ss -s
  2. Total: 1024050 (kernel 1024084)
  3. TCP: 1023769 (estab 1023754, closed 2, orphaned 0, synrecv 0, timewait 0/0), ports 12
  4. Transport Total IP IPv6
  5. * 1024084 - -
  6. RAW 0 0 0
  7. UDP 7 6 1
  8. TCP 1023767 12 1023755
  9. INET 1023774 18 1023756
  10. FRAG 0 0 0

通过top查看一下

  1. top -p 4925
  2. top - 17:51:30 up 3:02, 4 users, load average: 1.03, 1.80, 1.19
  3. Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
  4. Cpu0 : 0.9%us, 2.6%sy, 0.0%ni, 52.9%id, 1.0%wa, 13.6%hi, 29.0%si, 0.0%st
  5. Cpu1 : 1.4%us, 4.5%sy, 0.0%ni, 80.1%id, 1.9%wa, 0.0%hi, 12.0%si, 0.0%st
  6. Cpu2 : 1.5%us, 4.4%sy, 0.0%ni, 80.5%id, 4.3%wa, 0.0%hi, 9.3%si, 0.0%st
  7. Cpu3 : 1.9%us, 4.4%sy, 0.0%ni, 84.4%id, 3.2%wa, 0.0%hi, 6.2%si, 0.0%st
  8. Mem: 15554336k total, 15268728k used, 285608k free, 3904k buffers
  9. Swap: 4194296k total, 1082592k used, 3111704k free, 37968k cached
  10. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  11. 4925 root 20 0 8206m 4.2g 2220 S 3.3 28.4 62:53.66 java

四核都被占用了,每一个核心不太平均。这是在虚拟机中得到结果,可能真实服务器会更好一些。 因为不是CPU密集型应用,CPU不是问题,无须多加关注。

系统内存状况

  1. free -m
  2. total used free shared buffers cached
  3. Mem: 15189 14926 263 0 5 56
  4. -/+ buffers/cache: 14864 324
  5. Swap: 4095 1057 3038

物理内存已经无法满足要求了,占用了1057M虚拟内存。

查看一下堆内存情况

  1. jmap -heap 4925
  2. Attaching to process ID 4925, please wait...
  3. Debugger attached successfully.
  4. Server compiler detected.
  5. JVM version is 23.21-b01
  6. using parallel threads in the new generation.
  7. using thread-local object allocation.
  8. Concurrent Mark-Sweep GC
  9. Heap Configuration:
  10. MinHeapFreeRatio = 40
  11. MaxHeapFreeRatio = 70
  12. MaxHeapSize = 6442450944 (6144.0MB)
  13. NewSize = 629145600 (600.0MB)
  14. MaxNewSize = 629145600 (600.0MB)
  15. OldSize = 5439488 (5.1875MB)
  16. NewRatio = 2
  17. SurvivorRatio = 1
  18. PermSize = 52428800 (50.0MB)
  19. MaxPermSize = 52428800 (50.0MB)
  20. G1HeapRegionSize = 0 (0.0MB)
  21. Heap Usage:
  22. New Generation (Eden + 1 Survivor Space):
  23. capacity = 419430400 (400.0MB)
  24. used = 308798864 (294.49354553222656MB)
  25. free = 110631536 (105.50645446777344MB)
  26. 73.62338638305664% used
  27. Eden Space:
  28. capacity = 209715200 (200.0MB)
  29. used = 103375232 (98.5863037109375MB)
  30. free = 106339968 (101.4136962890625MB)
  31. 49.29315185546875% used
  32. From Space:
  33. capacity = 209715200 (200.0MB)
  34. used = 205423632 (195.90724182128906MB)
  35. free = 4291568 (4.0927581787109375MB)
  36. 97.95362091064453% used
  37. To Space:
  38. capacity = 209715200 (200.0MB)
  39. used = 0 (0.0MB)
  40. free = 209715200 (200.0MB)
  41. 0.0% used
  42. concurrent mark-sweep generation:
  43. capacity = 5813305344 (5544.0MB)
  44. used = 4213515472 (4018.321487426758MB)
  45. free = 1599789872 (1525.6785125732422MB)
  46. 72.48054631000646% used
  47. Perm Generation:
  48. capacity = 52428800 (50.0MB)
  49. used = 5505696 (5.250640869140625MB)
  50. free = 46923104 (44.749359130859375MB)
  51. 10.50128173828125% used
  52. 1439 interned Strings occupying 110936 bytes.

老生代占用内存为72%,较为合理,毕竟系统已经处理100万个连接。

再次断开所有测试端,看看系统内存(free -m)

  1. total used free shared buffers cached
  2. Mem: 15189 7723 7466 0 13 120
  3. -/+ buffers/cache: 7589 7599
  4. Swap: 4095 950 3145

记为list_free_2

list_free_1list_free_2两次都释放后的内存比较结果,系统可用物理已经内存已经降到7589M,先前可是7597M物理内存。

总之,我们的JAVA测试程序在内存占用方面已经,最低需要7589 + 950 = 8.6G内存为最低需求内存吧。

GC日志

我们在启动脚本处设置的一大串参数,到底是否达到目标,还得从gc日志处获得具体效果,推荐使用GCViewer

GC事件概览:

其它:

总之:

  • 只进行了一次Full GC,代价太高,停顿了12秒。
  • PartNew成为了停顿大户,导致整个系统停顿了41秒之久,不可接受。
  • 当前JVM调优喜忧参半,还得继续努力等

小结

Java与与Erlang、C相比,比较麻烦的事情,需要在程序一开始就得准备好它的堆栈到底需要多大空间,换个说法就是JVM启动参数设置堆内存大小,设置合适的垃圾回收机制,若以后程序需要更多内存,需停止程序,编辑启动参数,然后再次启动。总之一句话,就是麻烦。单单JVM的调优,就得持续不断的根据检测、信息、日志等进行适当微调。

  • JVM需要提前指定堆大小,相比Erlang/C,这可能是个麻烦
  • GC(垃圾回收),相对比麻烦,需要持续不断的根据日志、JVM堆栈信息、运行时情况进行JVM参数微调
  • 设置一个最大连接目标,多次测试达到顶峰,然后释放所有连接,反复观察内存占用,获得一个较为合适的系统运行内存值
  • Eclipse Memory Analyzer结合jmap导出堆栈DUMP文件,分析内存泄漏,还是很方便的
  • 想修改运行时内容,或者称之为热加载,默认不可能
  • 真实机器上会有更好的反映

吐槽一下:

JAVA OSGI,相对比Erlang来说,需要人转换思路,不是那么原生的东西,总是有些别扭,社区或商业公司对此的修修补补,不过是实现一些面向对象所不具备的热加载的企业特性。

测试源代码,下载just_test


无编程不创客,无案例不学习。疯狂创客圈,一大波高手正在交流、学习中!

疯狂创客圈 Netty 死磕系列 10多篇深度文章: 【博客园 总入口】 QQ群:104131248

Netty 100万级高并发服务器配置的更多相关文章

  1. Netty 100万级到亿级流量 高并发 仿微信 IM后台 开源项目实战

    目录 写在前面 亿级流量IM的应用场景 十万级 单体IM 系统 高并发分布式IM系统架构 疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之 -10[ 博客园 总入口 ] 写在前面 ​ 大家好 ...

  2. 用Netty开发中间件:高并发性能优化

    用Netty开发中间件:高并发性能优化 最近在写一个后台中间件的原型,主要是做消息的分发和透传.因为要用Java实现,所以网络通信框架的第一选择当然就是Netty了,使用的是Netty 4版本.Net ...

  3. 用Netty开发中间件:高并发性能优化(转)

    用Netty开发中间件:高并发性能优化 最近在写一个后台中间件的原型,主要是做消息的分发和透传.因为要用Java实现,所以网络通信框架的第一选择当然就是Netty了,使用的是Netty 4版本.Net ...

  4. [转]10分钟梳理MySQL知识点:揭秘亿级高并发数据库调优与最佳实践法则

    转:https://mp.weixin.qq.com/s/RYIiHAHHStIMftQT6lQSgA 做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离 ...

  5. 百万级高并发mongodb集群性能数十倍提升优化实践

    背景 线上某集群峰值TPS超过100万/秒左右(主要为写流量,读流量很低),峰值tps几乎已经到达集群上限,同时平均时延也超过100ms,随着读写流量的进一步增加,时延抖动严重影响业务可用性.该集群采 ...

  6. MySQL面试必考知识点:揭秘亿级高并发数据库调优与最佳实践法则

    做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离... 数据库的使用,是开发人员的基本功,对它掌握越清晰越深入,你能做的事情就越多. 今天我们用10分钟 ...

  7. 千万级高并发负载均衡软件HAproxy

    1负载均衡产品介绍 基于硬件的负载均衡设备例如F5,Big-IP,基于软件的负载均衡产品HAproxy,LVS,nginx在这些软件产品中,又分为基于操作系统的软负载实现和基于第三方应用的软负载实现. ...

  8. 千万级高并发负载均衡软件haproxy配置文件详解

    balance roundrobin         #轮询方式 balance source               #将用户IP经过hash计算后,使同一IP地址的所有请求都发送到同一固定的后 ...

  9. MySQL 高级性能优化架构 千万级高并发交易一致性系统基础

    一.MySQL体系架构 由图,可以看出MySQL最上层是连接组件.下面服务器是由连接池.管理服务和工具组件.SQL接口.查询解析器.查询优化器.缓存.存储引擎.文件系统组成. 1.连接池 管理.缓冲用 ...

随机推荐

  1. “百度杯”CTF比赛 十月场_GetFlag(验证码爆破+注入+绝对路径文件下载)

    题目在i春秋ctf大本营 页面给出了验证码经过md5加密后前6位的值,依照之前做题的套路,首先肯定是要爆破出验证码,这里直接给我写的爆破代码 #coding:utf-8 import hashlib ...

  2. Linux 虚拟内存和物理内存的理解【转】

    转自:http://www.cnblogs.com/dyllove98/archive/2013/06/12/3132940.html 首先,让我们看下虚拟内存: 第一层理解 1.         每 ...

  3. unix网络编程第四章----基于TCP套接字编程

    为了执行网络I/O操作.进程必须做的第一件事情就是调用Socket函数.指定期待的通信协议 #include<sys/socket.h> int socket(int family,int ...

  4. Office文件上传自动生成缩略图-C#开发

    原文: http://www.knowsky.com/898407.html 上传office文件的时候需要将首页自动截图,用于显示文件列表的时候将文件第一页缩略图展示给用户.实现的方式有多种,这里给 ...

  5. rsync数据同步工具应用指南

    Rsync (Remote synchonization)  rsync是Unix下的一款应用软件,它能同步更新两处计算机的文件与目录,并适当利用差分编码以减少数据传输.rsync中一项与其他大部分类 ...

  6. Django简单粗暴快速发送邮件!

    >>尽管Python已经提供了相对易用的邮件发送模块 smtplib ,但Django仍对其做了轻度的封装.封装后的模块不仅发送邮件速度快,而且在开发环境下也很容易对邮件发送进行测试, 并 ...

  7. 洛谷—— P1875 佳佳的魔法药水

    https://www.luogu.org/problemnew/show/1875 题目背景 发完了 k 张照片,佳佳却得到了一个坏消息:他的 MM 得病了!佳佳和大家一样焦急 万分!治好 MM 的 ...

  8. 邁向IT專家成功之路的三十則鐵律 鐵律二:IT專家專業之道–專精

    在IT技術的領域當中有許多的類別,若要細分那可真是難以一一列舉,但常見的大致有軟體研發工程師.韌體研發工程師.系統分析師.網路工程師.系統工程師.維護工程師.動畫設計師.製圖工程師.以及各類別的專業電 ...

  9. OpenGL step to step(2)

    这是一个类似于地球绕太阳旋转的demo 原有的例子是用键盘接受事件,我做了修改,使用了timer把他变成一个动态旋转的 #import <Foundation/Foundation.h> ...

  10. Scut游戏服务器免费开源框架--快速开发(1)

    Scut快速开发(1) 1        开发环境 需要安装的软件 a)        VS2010开发工具(.Net Framework 4.0以上) 2        HelloWorld 2.1 ...