前言
本文旨在利用两台云服务器实践各种技术栈的分布式部署,仍以酒店展示与酒店管理项目为主体,但进行了分布式拓展,因此架构如下图所示:
Let’s do it!
非局域网间使用swarm配置overlay进行容器通信的坑实在太多了,排到最后也没有排干净,放弃了,如果对排坑过程感兴趣,或者你也想实践一下在非局域网间自己搭建一个“局域网”(bushi),请移步我的排坑博客
下面,让我们开始更为简单方便的非局域网集群工作吧!核心思想就是IP直连来搭建集群以及限制IP访问来保护资源
仍然采用docker stack deploy的方式来部署,因为可以直接跨主机部署容器,简单高效,有关于这方面的内容依然可以参考上面列举的排坑博客,下面让我们开始吧!
第一部分:集群的搭建
搭建Nacos集群,挂载nginx负载均衡
流程总结为:
- 搭建mysql数据库,并在对应库中执行nacos集群必备的sql语句建立table
- 执行yml文件,在Server1和Server2上分别建立两个nacos集群服务,在Server1上建立nginx代理服务
- 测试连接
启动nacos集群和nginx代理服务的yml文件如下:
# Use root/example as user/password credentials
version: '3.1'
services:
nacos1:
image: nacos/nacos-server:latest
volumes:
- /usr/local/nacos2/logs:/home/nacos/logs
ports:
- "8848:8848"
- "9848:9848"
- "9555:9555"
env_file:
- ../env/nacos-ip.env
environment:
- NACOS_SERVER_IP=[Server1_IP]
restart: on-failure
deploy:
replicas: 1
placement:
constraints:
- node.hostname == [Server1_Hostname]
nacos2:
image: nacos/nacos-server:latest
volumes:
- /usr/local/nacos2/logs:/home/nacos/logs
ports:
- "8849:8848"
- "9849:9848"
env_file:
- ../env/nacos-ip.env
environment:
- NACOS_SERVER_IP=[Server2_IP]
restart: always
deploy:
replicas: 1
placement:
constraints:
- node.hostname == [Server2_Hostname]
nginx:
image: nginx:latest
volumes:
- /usr/local/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
- /usr/local/nginx/logs:/var/log/nginx
- /usr/local/nginx/conf.d:/etc/nginx/conf.d
- /usr/local/nginx/conf/log_format.conf:/etc/nginx/log_format.conf
ports:
- "8080:8080"
restart: always
deploy:
replicas: 1
placement:
constraints:
- node.hostname == [Server1_Hostname]
备注:
- 由
docker stack deploy
启动的服务的日志不好查找,但正如我们所说,可以通过docker logs
去查看服务启动的容器的日志,该方式适用于那些错误启动的容器,用来排查启动中的参数错误;也可以通过挂载卷的形式将启动日志输出到宿主机文件中,该方式适用于那些因未知原因无法启动的容器,用来排查启动错误
有关于nginx挂载卷的配置,可以参考文章:docker启动nginx的标准挂载配置
- 由于外网和内网的问题,nacos在没有配置环境值
NACOS_SERVER_IP
时,优先以网卡IP作为Leader节点(否则集群无法生效),这就会导致多出一个节点的同时,该节点还不可用(因为是内网)。要解决该问题就需要在yml文件中添加环境值NACOS_SERVER_IP=[Server_IP]
搭建RabbitMQ集群
在搭建RabbitMQ集群时
而与nacos所不同的是,RabbitMQ构建集群时并未给我们提供修正IP和端口的接口,我们无法对其发送和接收的端口进行修正。这意味着如果是两台外网主机,使用Overlay进行搭建时由于端口限制,必须将两个RabbitMQ的暴露端口映射到与默认配置不同的位置,这就导致了数据共享的不可行
因此,在两台外网主机上搭建RabbitMQ集群时,我们不能通过docker stack deploy
一键部署,而应在两台主机上进行分别部署,以暴露与默认端口匹配的相应外网端口
分别在Server1和Server2执行docker run
命令,命令中需要注意的有两点:
- 将
/var/lib/rabbitmq
文件夹和/etc/hosts
都映射了出来,目的是方便查看/var/lib/rabbitmq
文件夹中的.erlang.cookie
文件是否匹配,以及配置/etc/hosts
为外网IP与主机号。(.erlang.cookie
文件不匹配时,容易报错TCP connection succeeded but Erlang distribution failed
) - 额外开放4369和25672端口作为集群访问的端口(外网原因,必须将端口映射出来,如果是局域网,这些端口在开放防火墙的前提是可以直接访问的)
docker run -d --hostname rabbitmq01 --name rabbitmqCluster01 -v /usr/local/rabbitmq01/rabbitmq:/var/lib/rabbitmq -v /usr/local/rabbitmq01/etc/hosts:/etc/hosts -p 15672:15672 -p 5672:5672 -p 4369:4369 -p 25672:25672 -e RABBITMQ_ERLANG_COOKIE='rabbitmqCookie' rabbitmq:3.7-management
docker run -d --hostname rabbitmq02 --name rabbitmqCluster02 -v /usr/local/rabbitmq02/rabbitmq:/var/lib/rabbitmq -v /usr/local/rabbitmq02/etc/hosts:/etc/hosts -p 15672:15672 -p 5672:5672 -p 4369:4369 -p 25672:25672 -e RABBITMQ_ERLANG_COOKIE='rabbitmqCookie' rabbitmq:3.7-management
/etc/hosts
的配置如下所示:
[server1外网IP] rabbitmq01
[server2外网IP] rabbitmq02
容器启动后,在Server1中进入容器rabbitmqCluster01,执行重载命令:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit
在Server2中进入容器rabbitmqCluster02,执行重载和加入集群命令:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbitmq01
rabbitmqctl start_app
rabbitmqctl cluster_status
exit
此时可以看到集群中两个节点加入完成
此时两个MQ节点是普通集群模式,虽然互相之间可以看到队列和交换机的配置信息,但无法备份消息,节点间还没有实现数据的同步设置。
因此需要配置以下规则,保证所有数据都需要在两个节点间同步=,实现镜像集群:
rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'
在镜像集群模式下,创建队列的节点作为主节点,任何对该队列信息的访问都必须路由到该节点处理,镜像节点仅是起到数据安全性的保障。(当然,通过代码控制,在各个节点上负载均衡的创建队列,镜像模式自然可以起到应对高并发场景的功能)
搭建Elasticsearch集群
Elasticsearch集群的搭建相比于RabbitMQ会更简单一点,因为它开放了多种地址信息的配置:
- 本地监听地址:用于确定本机elasticsearch
- 发布地址:用于向外发布信息
- 集群节点地址:用于确定集群信息
虽然相比于Nacos进行跨域集群的配置要更为复杂一些,但原理基本一致,而且只要配置好发布地址和集群节点地址,我们的Elasticsearch集群就可以通过外网开始集群工作了
在两个服务器上通过下面的批处理程序start.sh来启动Elasticsearch,启动后将自动建立集群关系:
sudo mkdir -p /usr/local/elasticsearch/config
sudo mkdir -p /usr/local/elasticsearch/data
sudo mkdir -p /usr/local/elasticsearch/logs
sudo mkdir -p /usr/local/elasticsearch/plugins
sudo chmod -R 775 /usr/local/elasticsearch
sudo cat <<EOF > /usr/local/elasticsearch/config/stack.yml
version: '3.7'
services:
es01:
image: elasticsearch:7.12.1
environment:
- network.bind_host=0.0.0.0
- network.publish_host=Server1_IP
# 以下两行命令是对跨域访问的支持,*代表支持所有跨域IP访问,而http.cors.enabled默认是false,因此需自行配置
- http.cors.enabled=true
- http.cors.allow-origin=*
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.seed_hosts=Server2_IP:9300
- cluster.initial_master_nodes=Server1_IP:9300,Server2_IP:9300
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- /usr/local/elasticsearch/data:/usr/share/elasticsearch/data
- /usr/local/elasticsearch/logs:/usr/share/elasticsearch/logs
- /usr/local/elasticsearch/plugins:/usr/share/elasticsearch/plugins
ports:
- 9200:9200
- 9300:9300
EOF
docker-compose -f /usr/local/elasticsearch/config/stack.yml up
Error 1. "failed to resolve host [\"Server_IP:Port\"]"
报错信息不是无法连接,而是无法解析,这是yml文件的读取特点,它不会将"“处理成字符串的标识(yml默认所有的类型都是string),因此其会通过转义字符将”“解析,服务器读取到的IP地址就成了"Server_IP:Port”。去掉yml文档中添加的无效""即可
Error 2. "connnect timeout 30s"这个就是无法连接的问题了,依次查看以下三个信息:
- 是否将9300端口映射出来
- 防火墙是否放开9300端口
- 是否配置外网发布地址,并允许跨域访问
三个集群建立中的知识总结
总的来说,集群的建立就分为两步:
- 在各个服务器声明单个节点
- 节点间进行信息交互,建立集群
我们根据节点间进行信息交互的设置方式,将集群建立分为两类:
- 第一类(简单类):在启动阶段可由用户自主配置集群节点的IP与端口,自动建立集群关系,如:nacos, elasticsearch
- 第二类(复杂类):在启动阶段没有提供配置集群节点的接口,集群关系建立需进入容器使用对应的命令,如:rabbitmq
对于第一类,我们需要注意以下信息:
- 用于内部访问的所有端口是否都暴露,如果没有请暴露至外网,否则将无法建立跨域集群
- 各个必须的端口间是否有默认规则,如果有请遵守。如果因为特殊原因不能遵守,需要注意是否有开放对应的配置接口,否则集群对接将失效
- 比如nacos,9848端口就是根据8848+100得到,如果不遵守,而nacos又没有开放对应接口,此时就会报错
- 当需要跨域建立集群时,是否默认开放了跨域请求,如果没有,是否有相关的配置接口
对于第二类,除上述注意点外,我们还需关注:
- 主机地址的配置,必须进入容器配置hosts,将其他节点的外网IP添加进来
总的来说,在以后我们进行集群设计时,也应尽量学习nacos的配置简单以及elasticsearch的接口丰富,尽量避免rabbitmq这类繁复的集群建立方法
第二部分:微服务的部署
部署的过程按照:
- 打包各模块
- 形成模块+Dockfile+yml的文件夹,上传该部署资料
- 通过docker-compose命令启动容器,记得设置Gateway的默认端口映射,以便于外网访问
部署前一定要确保本地验证没有问题
总结
到这里,跨主机的微服务部署算是完成了,说说使用体验吧:
- 第一次访问时,hotel_demo进行酒店信息展示的速度很慢,但第一次访问后,不管是翻页速度,查询速度都还比较满意。究其原因还是elasticsearch的跨域分片导致的内部查询延时过高,而第一次访问除了需要加载酒店数据外,还需要加载过滤信息,因而导致载入缓慢问题
- hotel_admin同样有相同的问题,但延时较hotel_demo低一些
要解决这些问题,主要是从两方面考虑:
- 集群部署不应该跨域,而是在同一局域网内,保证集群内部信息沟通的畅通与快速
- 应增添分布缓存功能,提高用户的频繁访问时良好体验
附录
es索引
首先将es索引纪录在此:
PUT /hotel
{
"settings": {
"analysis": {
"analyzer": {
"text_analyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
},
"completion_analyzer": {
"tokenizer": "keyword",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
},
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"id": {
"type": "keyword",
"index": false
},
"name" :{
"type": "text",
"analyzer": "text_analyzer",
"search_analyzer": "ik_smart",
"copy_to": "select"
},
"address": {
"type": "keyword",
"index": false
},
"price": {
"type": "integer"
},
"score": {
"type": "integer"
},
"brand": {
"type": "keyword",
"copy_to": "select"
},
"city": {
"type": "keyword",
"copy_to": "select"
},
"starName": {
"type": "keyword",
"copy_to": "select"
},
"business": {
"type": "keyword",
"copy_to": "select"
},
"location": {
"type": "geo_point"
},
"pic": {
"type": "keyword",
"index": false
},
"select": {
"type": "text",
"analyzer": "text_analyzer",
"search_analyzer": "ik_smart"
},
"isAD": {
"type": "boolean"
},
"suggestion": {
"type": "completion",
"analyzer": "completion_analyzer",
"search_analyzer": "keyword"
}
}
}
}
}
连接数据库Tips
连接数据库时注意使用utf后缀,否则可能会出现数据库回写时中文变?号的错误:
?useUnicode=true&characterEncoding=utf8&useSSL=false
maven打包时跳过测试
修改pom.xml文件,增加跳过测试字段
<properties>
<skipTests>True</skipTests>
</properties>