分布式IM架构
id=”embed_dom” name=”embed_dom” style=”border:1px solid #000;display:block;width:800px; height:600px;” src=”https://www.processon.com/embed/56ab6119e4b0ed3b6490f436″ frameborder=”0″>
IM服务器开发,从功能抽象的角度看可能非常简单,可以认为是管理大量的客户端连接和在不同的客户端之间传递消息,但具体到实现细节就比较复杂了。打个不恰当的比喻,OS的功能抽象也非常简单,无非是进程间的调度和硬件资源的管理,但要是自己去实现一个,一般人也就只能呵呵了。
由于IM服务器里面的内容比较多,这个可以是一个系列的内容,所以这里只介绍服务器的架构以及为什么选择这样的架构
我们的设计原则是保持简单,可以做一定的扩容,无单点故障。很多时候开发人员常犯的错误是过度设计和提前优化,”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信息。
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地址