1、引言
目前正在维护一个单体的JavaWeb系统,并且系统只使用了Emqx与硬件设备通讯,Tomcat部署,MySQL数据库。并且系统都在使用自己的服务器部署这些服务的,由于我们使用的服务器集群较大,并且会定期对服务器进行停机维护处理(可以用着一台机器维护另一台机器,维护好之后启动完相关服务再维护另一台)。这就相当于我们部署的应用、组件、数据库都会存在停机处理,但又要求我们的系统和设备通讯是不可以中断。因此,我就考虑对这个系统的所有服务进行分布集群部署。
MySQL集群高可用部署可以使用Innodb Cluster实现,本文章只对应用和组件做集群演示,Innodb Cluster可以自行查阅相关资料。
2、相关技术
1.Nginx
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强(它的能力不做过多描述,互联网系统上基本都在使用)。此外,Nginx支持热部署,启动简单,可以做到7*24不间断运行,几个月都不需要重新启动。
2.Keepalived
Keepalived一个基于VRRP 协议来实现的 LVS 服务高可用方案,可以利用其来解决单点故障。一个LVS服务会有2台服务器运行Keepalived,一台为主服务器(MASTER),一台为备份服务器(BACKUP),但是对外表现为一个虚拟IP,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候, 备份服务器就会接管虚拟IP,继续提供服务,从而保证了高可用性。为保证证Nginx和Emqx高可用,并且对外提供访问的IP不可变,即Keepalived主要承担的功能有两个:
①健康检测
基于类似交换机制OSI七层模型:3(网络层:IP)、4(传输层:IP+PORT)、7层(应用层:FTP、HTTP APP)来实现健康检查;
②VRRP漂移
基于VRRP(虚拟路由冗余协议)路由协议来实现两台主机之间高可用,其中包括:MASTER和BACKUP,当MASTER宕机,能够自动化切换至BACKUP,从而让用户持续的访问.
因测试环境的资源有限,现通过下面的机器和服务部署情况进行演练,以下所有服务器都将在离线Linux环境部署
3、部署和验证
1.准备环境和版本
IP | 服务 |
---|---|
192.168.110.57 | EMQX-5.0.26 |
192.168.110.58 | Tomcat-8.5.100、Nginx-1.23.4、Keepalived-2.1.5 |
192.168.110.60 | Tomcat-8.5.100、EMQX-5.0.26、Nginx-1.23.4、Keepalived-2.1.5 |
考虑到有些环境是无网的,所以下面部署都是通过离线部署的,部署相关的文件和脚本我都准备好了:
链接:https://pan.baidu.com/s/1a3ui773EBY1ytHuXQAgE5w
提取码:cctv
2.Tomcat集群
这个没啥好讲的,直接安装JDK配置环境变量,然后把Tomcat压缩文件上传,解压,修改server.xml,把web项目的war包丢到webapp目录下面,回到主目录启动catalina脚本,然后查看是否正常即可
3.Nginx集群
①在/usr/local下创建nginx目录并将nginx-1.23.4.tar.gz包上传并解压
②安装编译nginx相关模块在,/opt下分别创建zlib和pcre文件,并将准备好的pcre-8.45.tar.gz和zlib-1.3.1.tar.gz上传到对应目录下(configure文件所在位置),然后解压,并在对应的主目录下都进行编译和安装
> ./configure
> make
> make install
③接着到Nginx安装目录下面下面开始编译和安装Nginx
> ./configure --prefix=/usr/local/nginx-1.23.4 --with-http_stub_status_module --with-http_ssl_module --with-stream --with-stream_ssl_module
> make
> make install
④然后检查Nginx是否安装成功
需要注意的是,如果第二步那两个插件没有编译成功的话,第三步make的时候就会报错。而一般安装nginx的时候是不用加–with-stream模块的,这是因为Nginx配置Emqx相关的时候会需要用到,如果你不使用Emqx的话这里不需要加stream模块编译。
如果make出现错误的话,找到对应的问题(比如nginx.conf配置错误啥的)执行make clean然后再make重新编译即可。
⑤接着在找到Nginx的配置文件nginx.conf根据实际情况按照准备好的模板配置好(举例关键配置)
events {
//...
}
http {
#...
upstream acs {
server 127.0.0.1:18080;
server 192.168.110.58:18081;
ip_hash; #因为Tomcat集群之后会话是不共享的,前后请求落点服务器不一样的话会导致有些请求报302错误,但是ip_hash会导致负载倾斜,有兴趣的可以了解一下sticky模块,这里不做赘述
}
server {
listen 8880;
server_name localhost;
location /ACS {
root html;
index index.html index.htm;
proxy_pass http://acs;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
#...
}
⑥没问题的话通过命令检查Nginx配置是否正确,正确则启动Nginx
> cd /usr/local/nginx-1.23.4/sbin
> ./nginx -v #查看nginx 版本
> ./nginx #启动命令
> ./nginx -s stop #关闭命令
> ./nginx -s reload #重新加载命令
⑦通过Nginx请求服务器
4.Keepalived集群
以192.168.110.60作为主节点为例
①在/usr/local下创建keeplived目录,将准备好的keepalived-2.1.5.tar.gz包上传到该目录下并解压
②编译和安装
预编译keepalived:
> cd /usr/src/keepalived-1.3.5/
> ./configure --prefix=/usr/local/keepalived/
.....
> make && make install
③安装完成后,keepalived的默认配置文件地址和我们安装的地址不一样,所以复制过去就可以了
> cp /usr/src/keepalived-1.3.5/keepalived/etc/init.d/keepalived /etc/init.d/
> mkdir -p /etc/keepalived
> cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
> cp /usr/src/keepalived-1.3.5/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
④配置环境变量
> vim /etc/profile
后面添加如下内容:
export PATH=$PATH:/usr/local/keepalived/sbin
⑤然后将准备好的配置文件和脚本根据实际情况修改好后上传到服务器
! Configuration File for keepalived
global_defs {
router_id LVS_DEVEL #主备名称不要一样
vrrp_skip_check_adv_addr
vrrp_garp_interval 0.001 #默认值是0,一般来说不报错就不用改,有些限制必须是大于0的数
vrrp_gna_interval 0.001 #默认值是0,一般来说不报错就不用改,有些限制必须是大于0的数
script_user root
enable_script_security
}
vrrp_script chk_http_nginx {
script "/etc/keepalived/nginx_check.sh" #这是一个用来监测nginx的脚本,
interval 2
}
vrrp_instance nginx {
state MASTER #备用节点改成BACKUP
interface ens33
virtual_router_id 51 #这个主备值要一致
priority 120 #默认值是100,主节点要>100,备用节点要<100
advert_int 1
nopreempt #根据需要添加这个参数,我是希望主机宕机后虚拟ip被抢占后,主机恢复后还可以抢占回来
authentication {
auth_type PASS
auth_pass 123456 #这个密码限制8位数还是6位数
}
track_script {
chk_http_nginx
}
virtual_ipaddress {
192.168.110.10 dev ens33 scope global #根据你的网卡信息不同改
}
}
nginx_check.sh脚本内容
#!/bin/bash
##使用ps -C nginx --no-header 来检测nginx 是否启动
date=`date`
nginx_status=`ps -C nginx --no-header|wc -l`
if [ $nginx_status -eq 0 ];then
echo "nginx没有启动"
/usr/local/nginx-1.23.4/sbin/nginx
if [ $? -eq 0 ];then
echo "$date nginx已经成功启动"
fi
else
echo "$date nginx 已经启动 "
fi
查看网卡信息
⑥systemctl start keepalived启动keepalived后再使用systemctl status keepalived查看服务器状态,可以看到nginx的虚拟ip为192.168.110.180,并且这个ip也是可以访问的
查看本机网卡的时候也可以看到虚拟ip加入成功(用户需要能添加网卡ip的权限,否则keepalived会因为配置了虚拟ip而报错),如果脚本报错的话这行是看不到的
⑦如果systemctl *** keepalived命令无法执行大概是因为安装的时候没有安装好系统配置文件keepalived.service,可以自行添加
keepalived.service
[Unit]
Description=LVS and VRRP High Availability Monitor
After=network-online.target syslog.target
Wants=network-online.target
[Service]
Type=forking
PIDFile=/run/keepalived.pid
KillMode=process
EnvironmentFile=-/usr/local/keepalived/etc/sysconfig/keepalived
ExecStart=/usr/local/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
⑦将另一台服务器192.168.110.58的keepalived也部署并启动起来
⑧地址漂移说明:假设192.168.110.60宕机(keepalived不可用)了,这个对外的虚拟ip 192.168.110.180将会被其它可用的服务器192.168.110.58服抢占使用
⑨192.168.110.180这个地址不会因为某台机器宕机而不能使用
⑩设置keepalived开机自启,这样重新恢复192.168.110.60后keepalived自启可用(只是建议,自行考虑)
> systemctl enable keepalived
> systemctl daemon-reload #使配置生效
因为keepalived配置了nopreempt参数,恢复宕机的服务器后虚拟ip就会将被它抢占回来
5.EMQX集群部署
①创建一个emqx文件夹,上传准备好的emqx-5.0.26-el7-amd64.tar.gz包,解压
②根据准备好的步骤脚本配置好emqx.conf文件,添加集群配置
## NOTE:
## This config file overrides data/configs/cluster.hocon,
## and is merged with environment variables which start with 'EMQX_' prefix.
##
## Config changes made from EMQX dashboard UI, management HTTP API, or CLI
## are stored in data/configs/cluster.hocon.
## To avoid confusion, please do not store the same configs in both files.
##
## See https://docs.emqx.com/en/enterprise/v5.0/configuration/configuration.html
## Configuration full example can be found in emqx.conf.example
node {
##@前面的前缀自定义,不同节点之前也不需要保证唯一,@后面ip根据当前服务器ip配置即可,其他保持一致
name = "[email protected]"
cookie = "emqxsecretcookie"
data_dir = "data"
}
cluster {
##因为我是通过配置还有nginx完成的集群,所以选择静态集群方式,它会根据下面seeds集群列表自动创建集群,这样就不需要每次服务器启动之后又手动将每台emqx join集群了
discovery_strategy = static
static {
seeds = ["[email protected]", "[email protected]"]
}
}
dashboard {
listeners.http {
bind = 18083
}
}
authorization {
deny_action = ignore
no_match = allow
cache = { enable = true }
}
##这个可加可不加
mqtt {
max_packet_size = 10MB
}
③同样建议设置开机自启,编写脚本emqx.service到/usr/lib/systemd/system上
[Unit]
Description=emqx for auto start
Wants=network-online.target
[Service]
User=root
Type=forking
ExecStart=/data/Other/emqx/bin/emqx start
ExecStop=/data/Other/emqx/bin/emqx stop
[Install]
WantedBy=multi-user.target
添加开机自启,然后重新加载系统配置
> systemctl enable emqx
> systemctl daemon-reload
④启动Emqx并查看其服务状态
⑤单机Emqx客户端访问成功:http://192.168.110.60:18083/#/login?to=/dashboard/overview
⑥当192.168.110.58上面的Emqx同样配置好启动后,可以通过任一一个Emqx控制台看到集群内容
⑦由于我们集群需要对外提供一致ip,因此需要在Nginx配置好访问Emqx(需要使用其Websocket的话也要配置相应内容)
events {
#...
}
stream {
upstream mqtt_servers {
# down:表示当前的 server 暂时不参与负载
# max_fails:允许请求失败的次数;默认为 1
# fail_timeout:失败超时时间,默认 10s, max_fails 达到次数后暂停的请求时间
# backup:其它所有的非backup机器down或者忙的时候,请求backup机器
server 192.168.110.57:1883 max_fails=2 fail_timeout=10s;
server 192.168.110.60:1883 max_fails=2 fail_timeout=10s;
}
server {
# 这个是tcp连接时的端口
listen 6183;
proxy_pass mqtt_servers;
# 启用此项时,对应后端监听器也需要启用 proxy_protocol
proxy_protocol on;
proxy_connect_timeout 10s;
# 默认心跳时间为 10 分钟
proxy_timeout 1800s;
proxy_buffer_size 3M;
tcp_nodelay on;
}
}
http {
#...
#websocket配置
upstream mqtt_websocket_servers {
server 192.168.110.57:8083;
server 192.168.110.60:8083;
}
server {
# 这个是webscoket连接时的端口
listen 6683;
# 这个是webscoket连接时的地址
server_name localhost;
location /mqtt {
proxy_pass http://mqtt_websocket_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# 禁用缓存
proxy_buffering off;
proxy_connect_timeout 10s;
# WebSocket 连接有效时间
# 在该时间内没有数据交互的话 WebSocket 连接会自动断开,默认为 60s
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
# 反向代理真实 IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Nginx中stream模块一般用于tcp/UDP数据流的代理和负载均衡,可以通过stream模块代理转发TCP消息。从性能上来说,stream模块在应用层实现四层的转发,需要与两端建立起socket连接,然后两端的数据收发进行代理转发。Nginx默认不支持stream,所以需要在前面Nginx部署中第③步编译的时候加入–with-stream参数模块。否则nginx -t检查nginx配置是否正常的时候会提示错误并且nginx也起不起来
⑧现在Emqx可以通过Nginx虚拟ip连接MQTT或者WebSocket
可以看到Nginx代理的其中一台192.168.110.58也是可以收到MQTT消息的
⑨由于Tomcat集群部署,如果Emqx在生产消息的时候没有使用共享订阅模式,那么所有的Tomcat都将消费到同一个MQTT或者Websocket,这就导致数据紊乱。或者在系统中引入使用Redis、Zookeeper,或者在MySQL中加个消费幂等表,然后在代码中订阅消费消息前做唯一消费处理。