netty 高并发实战

分布式IM架构


id="embed_dom" style="border:1px solid #000;display:block;" src="https://www.processon.com/embed/56ab6119e4b0ed3b6490f436" frameborder="0">

IM服务器开发,从功能抽象的角度看可能很是简单,能够认为是管理大量的客户端链接和在不一样的客户端之间传递消息,但具体到实现细节就比较复杂了。打个不恰当的比喻,OS的功能抽象也很是简单,无非是进程间的调度和硬件资源的管理,但要是本身去实现一个,通常人也就只能呵呵了。java

因为IM服务器里面的内容比较多,这个能够是一个系列的内容,因此这里只介绍服务器的架构以及为何选择这样的架构linux

咱们的设计原则是保持简单,能够作必定的扩容,无单点故障。不少时候开发人员常犯的错误是过分设计和提早优化,"When in doubt, use brute force", 有时简单的方案才是最好的方案。算法

架构设计须要考虑到服务器的业务逻辑和预计的用户量,好比一样的IM服务器设计,最高并发在线人数百万和千万的确定有很大的不一样, 若是按千万级别来设计一个只有百万级别的系统,增长的复杂度和工做量是很是可观的.安全

目前咱们的IM服务器架构设计的单机并发链接10万用户,总并发用户量能够达百万级,对于这样规模的服务器后台,能够采用很简单的架构来处理。有一个DispatchServer来分配客户端到一个消息服务器,消息服务器之间的数据交互经过一个中心的RouteServer来转发,和数据持久化层之间的交互由DBProxy服务器来处理.服务器

因为IM消息服务器和客户端之间交互很是频繁,但处理单个数据包的逻辑比较简单,没有IO或CPU密集型的操做,因此消息服务器采用单线程来处理,这样比较简单,没有死锁,竞争条件,出现问题很是好定位。多线程

分离出一个DBProxy服务器的的理由是,因为要操做DB和Redis,一个请求的回复会比较耗时,可是交互很是简单,一个请求对应一个回复,与消息服务器之间的长链接通常很稳定,不太须要处理太多的断连,因此这个能够用多线程的来处理大的IO等待。并且因为DBProxy服务器之间不须要交互,这样能够随着业务量的增长,添加不少实例来分散每一个DBProxy的压力。架构

DispatchServer和RouteServer的逻辑都很是简单,并且能够启动多个实例,这样就不存在单点故障。两个DispatchServer或两个RouteServer之间的状态同步是经过消息服务器来实现的,好比用户上下线时,须要把这个状态通知全部的DispatchServer和RouteServer。并发

消息服务器支持TCP长链接和HTTP长轮询两种接入方式,目前的消息服务器承担了接入和业务逻辑处理两种角色,通常业界的作法是把这两种功能分解开,有一个接入服务器来处理客户端的接入,而后客户端的请求被分配到不一样的业务逻辑处理服务器来处理,好比登录服务器,状态通知服务器,好友管理服务器等。但这样运维起来比较麻烦,对于百万量级的IM来讲,这样有点过分设计的感受,因此咱们把这些功能融合在一个消息服务器里面实现,但这样的缺点是,更新一个业务功能时,须要把重启消息服务器,这样客户端会有一个从新链接服务器的过程。之后能够调研是否是能够把业务逻辑写成动态加载库,这样修改业务逻辑时,只要Reload动态库就能够了运维

搭建这些只是开了一个头,之后能够作不少有挑战性的技术,好比用分布式的消息存储方案代替如今的MySQL,用分布式的小文件系统存储系统代替如今依赖蘑菇街主站的图片存储方案,用一些Unsupervised Learning的算法对用户消息进行分析,来获取一些用户的profile信息。dom


linux 内核参数修改


“Cannot assign requested address.”是因为linux分配的客户端链接端口用尽,没法创建socket链接所致,虽然socket正常关闭,可是端口不是当即释放,而是处于TIME_WAIT状态,默认等待60s后才释放。

vi /etc/sysctl.conf

#fs.file-max:表示文件句柄的最大数量。文件句柄表示在Linux系统中能够打开的文件数量。
fs.file-max = 1048576

#增长可用端口:

net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216

#调低端口释放后的等待时间,默认为60s,修改成15~30s
net.ipv4.tcp_fin_timeout=20

#修改tcp/ip协议配置, 经过配置/proc/sys/net/ipv4/tcp_tw_resue, 默认为0,修改成1,释放TIME_WAIT端口给新链接使用
net.ipv4.tcp_tw_reuse = 1

#修改tcp/ip协议配置,快速回收socket资源,默认为0,修改成1

#net.ipv4.tcp_timestamps开启,tw_recycle才会生效

net.ipv4.tcp_timestamps=1

net.ipv4.tcp_tw_recycle = 1


经过vi /etc/security/limits.conf 添加以下配置参数:修改以后保存,注销当前用户,从新登陆,经过

ulimit -a 查看修改的状态是否生效。

*  soft  nofile  1000000
*  hard  nofile  1000000

经过 ulimit -a 查看最大句柄数

ulimit -n 10000000

改完后,执行命令“sysctl -p”使参数生效,不须要reboot。


netty设置

ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new SimpleChatServerInitializer())  //(4)
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .option(ChannelOption.TCP_NODELAY, true)
             .option(ChannelOption.SO_KEEPALIVE, true)
             .option(ChannelOption.SO_REUSEADDR, true)
             .option(ChannelOption.SO_RCVBUF, 10 * 1024)
             .option(ChannelOption.SO_SNDBUF, 10 * 1024)
             .option(EpollChannelOption.SO_REUSEPORT, true)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
            System.out.println("SimpleChatServer 启动了");


JVM监控

1. 经过jstatd启动RMI服务
        配置java安全访问,将以下的代码存为文件 jstatd.all.policy,放到JAVA_HOME/bin中,其内容以下,
        grant codebase "file:${java.home}/../lib/tools.jar" {

               permission java.security.AllPermission;

          };
            
          执行命令jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.8 &

           (192.168.1.8  为你服务器的ip地址,&表示用守护线程的方式运行)
          jstatd命令详解 :http://hzl7652.iteye.com/blog/1183182 
         
          打开jvisualvm, 右键Remort,选择 "Add Remort Host...",在弹出框中输入你的远端IP,好比192.168.1.8. 链接成功.


1.远程主机

(1)修改JMX服务的配置文件:
  在JDK的根目录/jre/lib/management中,将jmxremote.password.template另存为jmxremote.password。
用文件编辑软件按编辑jmxremote.password去掉
  # monitorRole QED
  # controlRole R&D
  前面的#注释,保存。
  若是当前系统属于AIX、Linux或者Solaris系统还须要更改jmxremote.access和jmxremote.password的权限
为只读写,命令以下
  chmod 600 jmxremote.access jmxremote.password


(2)修改JVM的启动配置信息:

 

Windows系统
set JAVA_OPTS=-Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=<hostname>
-Dcom.sun.management.jmxremote.ssl=false

 

AIX、Linux或者Solaris
export JAVA_OPTS="-Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=<hostname>  
-Dcom.sun.management.jmxremote.ssl=false"

例如:
set JAVA_OPTS=-Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.24
-Dcom.sun.management.jmxremote.ssl=false

 

配置的说明以下:-Dcom.sun.management.jmxremote.port                           远程主机端口号的 -Dcom.sun.management.jmxremote.ssl=false                   是否使用SSL链接 -Dcom.sun.management.jmxremote.authenticate=false   是否开启远程服务权限 -Djava.rmi.server.hostname                                              远程主机名,使用IP地址