大家好, 年底了, 年味儿越来越浓了.2019 年的寒冬被定义为未来 10 年中最好的一年, 对于这一说法悲观的人和乐观的人的理解是不一样的.但是不管是寒冬还是盛夏, 我们都应该坚持不断的积累和主动的思考.拥抱变化, 坚定信心.
简单描述一下我们的需求, 就是通过 docker 来搭建一套 rabbitmq 的集群, 用于接受业务传来的数据, 然后把数据写到消息队列中, 然后消费者消费消息, 生成日志文件, 接着大数据采集系统定时来采集数据.这样做的一个好处就是, 我们的服务可以直接部署到业务系统所在的服务器集群中.
年末的时候, 在忙完了各种活动项目之后, 接到了一个新的项目, 数据打点项目.需要在各指定机房搭建数据采集服务, 然后有数据中心定时去拉取数据.为此我们采用的解决方案是 基于 docker 搭建 rabbitmq, 利用 Openresty 来进行数据的生产和 nginx 代理.来, 让我们一起了解一些概念.
以下内容是从官网搬来的:
OpenResty® 是一个基于 Nginx https://openresty.org/cn/nginx.html 与 Lua 的高性能 web 平台, 其内部集成了大量精良的 Lua 库, 第三方模块以及大多数的依赖项. 用于方便地搭建能够处理超高并发, 扩展性极高的动态 Web 应用, Web 服务和动态网关.
OpenResty® 通过汇聚各种设计精良的 Nginx https://openresty.org/cn/nginx.html 模块(主要由 OpenResty 团队自主开发), 从而将 Nginx https://openresty.org/cn/nginx.html 有效地变成一个强大的通用 Web 应用平台. 这样, Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx https://openresty.org/cn/nginx.html 支持的各种 C 以及 Lua 模块, 快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统.
OpenResty® 的目标是让你的 Web 服务直接跑在 Nginx https://openresty.org/cn/nginx.html 服务内部, 充分利用 Nginx https://openresty.org/cn/nginx.html 的非阻塞 I/O 模型, 不仅仅对 HTTP 客户端请求, 甚至于对远程后端诸如 MySQL,PostgreSQL,Memcached 以及 Redis 等都进行一致的高性能响应.
之所以选择 OpenResty, 是看中了其中 nginx 服务和对 lua 脚本的支持, nginx 可以做服务代理, lua 脚本我们可以用来编写消息生产者的脚本.好了, 开始我们的部署之旅吧(再罗嗦一句, 你需要提前了解 docker, 同时保证部署环境已经安装了 docker, 为了部署成功, 请保证有俩台服务器, 或者虚拟机也行, 同时保持俩台服务器或者虚拟机的 ip 在统一网段)!
一.搭建 swarm 集群
swarm 集群的角色包含有 leader,node 在 swarm 集群搭建中, 重要的是保证 token 的一致
1.swarm manager(初始化 swarm leader): docker swarm init --advertise-addr 192.168.7.201
提示: 这一步会产生 token,token 为 swarm 集群的唯一标识
- 2.swarm group(成员加入集群)
- docker swarm join --token SWMTKN-1-35ucts3e9bg5onl1pyqwh03j1z1micdb88ziq78m4pfr1zulhf-70w2bdzpjyr8xqc1p77mue04r 192.168.7.201:2377
token 是在初始化 swarm manager 时返回的.
注意: 记得为集群成员追加 label, 便于 rabbitmq 绑定相应的节点, bi-tool-02 是节点名称
- docker node update --label-add rabbitmq=master bi-tool-02
- docker node update --label-add rabbitmq=slave1 bi-tool-01
这里的 label 是我们给 swarm 集群中各节点起的一个别名, 以 docker node update --label-add rabbitmq=master bi-tool-02 为例, 是给节点 bi-tool-02 增加标签, 标签为: rabbitmq=master , 我们可以通过 docker node inspect bi-tool-02 来查看节点配置内容.可以看到的内容如下:
- "Labels": {
- "rabbitmq": "master"
- },
追加标签的目的是为了我们在搭建 rabbitmq 集群的时候, 方便 rabbitmq 节点的绑定.
二.启动 network 服务
network 用于提供容器间通许
命令: docker network create --driver overlay rabbitmq-network
三.搭建 rabbitmq 集群
1. 创建镜像
docker build -t stomp-rabbitmq:latest .
创建镜像所需要的 dockerfile 文件见附件 (博客园不能上传附件吗???? 等我咨询完了, 我再补充这个附件)
放在了百度网盘上:
https://pan.baidu.com/s/1PI1nL6TL9pJxsWUaTBleig
提取码: sg9p
2. 创建 rabbitmq master 队列
命令(master):sudo docker service create --name stomp-rabbitmq-master --hostname="stomp-rabbitmq-master" --network rabbitmq-network -p 5772:5672 -p 15772:15672 -p 12345:12345 --mount type=bind,target=/var/lib/rabbitmq/,source=/home/agent/rabmq/ --constraint node.labels.rabbitmq==master -e RABBITMQ_CLUSTER_NODES='rabbit@stomp-rabbitmq-slave1' -e "AUTOCLUSTER_CLEANUP=true" -e "CLEANUP_WARN_ONLY=false" -e "RABBITMQ_ERLANG_COOKIE=thisissecretkey" stomp-rabbitmq:latest
注意: 要确保 / home/agent/rabmq/ 路径的存在
备注:
--network 为设置网络环境, rabbitmq-network 是创建好的 docker network, 类型为 overlay.
-p 为将容器的端口暴露到宿主机的端口上, 这样可以通过宿主机也就是服务器的端口访问到容器对应的端口, 前方前为宿主机端口, 后方为容器端口. 其中 5672 为 amqp 通信端口, 15672 为管理界面端口, 12345 为 stomp 通信端口.
--mount 为将容器内的目录映射到宿主机上(/var/lib/rabbitmq/ 保存了队列与交换机等的信息, 而且保存了持久化的队列里的消息), 这样当容器出现问题时, 启动新容器时由于已经挂载到了宿主机上持久化, 关键信息可以不丢失, 新容器相当于和旧容器一样. 前方为容器, 后方为宿主机目录.
--constraint 为创建 service 时将 service 指定在某台机器上创建, 本次使用的是通过 lable 指定, 在之前我已经对这三台服务器进行了 label 指定.
-e 为指定容器内的环境变量, 比如其中的 "RABBITMQ_ERLANG_COOKIE=thisissecretkey"
3. 创建 rabbitmq slave 队列
命令(slave):sudo docker service create --name stomp-rabbitmq-slave1 --hostname="stomp-rabbitmq-slave1" --network rabbitmq-network -p 5773:5672 -p 15773:15672 -p 23456:12345 --mount type=bind,target=/var/lib/rabbitmq/,source=/home/agent/rabmq/ --constraint node.labels.rabbitmq==slave1 -e RABBITMQ_CLUSTER_NODES='rabbit@rabbitmq-master' -e "AUTOCLUSTER_CLEANUP=true" -e "CLEANUP_WARN_ONLY=false" -e "RABBITMQ_ERLANG_COOKIE=thisissecretkey" stomp-rabbitmq:latest
在执行完 sudo docker service create --name stomp-rabbitmq-slave1 后, 其实就相当于启动了一个容器, 可以通过 docker ps 看到, 如果看不到, 那说明服务启动失败, 通过进程守护进入容器用到的 id 就是 此处看到的 id
(将分支1加入到集群), 通过守护进程进入到容器内部执行
(进入容器)命令: docker exec -it 36383eefcf87 /bin/sh
- docker exec -it /bin/sh
- rabbitmqctl stop_app
- rabbitmqctl join_cluster rabbit@stomp-rabbitmq-master
- rabbitmqctl start_app
4. 创建 Openresty(nginx+lua)
docker build -t openresty-product:0.0.1 .
命令: docker service create --name openresty-product-service --mode global -p 8080:80 --mount type=bind,target=/var/log/nginx/,source=/home/agent/openresty-file/var/log/nginx --mount type=bind,target=/opt/,source=/home/agent/openresty-file/openrestyFile --mount type=bind,target=/etc/nginx/conf.d,source=/home/agent/openresty-file/etc/nginx/conf.d --mount type=bind,target=/usr/local/openresty/nginx/conf,source=/home/agent/openresty-file/usr/local/openresty/nginx/conf openresty-product:0.0.1
用于外网访问下载数据文件
命令: dockerservice create --name openresty-file-server --constraint node.labels.rabbitmq==master -p 80:80 --mount type=bind,target=/var/log/nginx/,source=/home/agent/finalDataFile/var/log/nginx --mount type=bind,target=/opt/,source=/home/agent/finalDataFile --mount type=bind,target=/etc/nginx/conf.d,source=/home/agent/finalDataFile/etc/nginx/conf.d openresty/openresty:xenial
nginx 的相关配置, 我们就不在此细说了, 我可以给大家看个例子
- server {
- listen 80;
- server_name localhost;
- #charset koi8-r;
- access_log /var/log/nginx/host.access.log main;
- location / {
- root /usr/local/openresty/nginx/HTML;
- index index.HTML index.htm;
- }
- location /hello {
- # root /usr/local/openresty/nginx/HTML;
- # index index.HTML index.htm;
- default_type 'text/html';
- content_by_lua 'ngx.say("hello world")';
- }
- location /file {
- proxy_pass http://192.168.7.201:8090/;
- access_log /var/log/nginx/file.access.log main;
- error_log /var/log/nginx/file.error.log;
- }
- location /dataagent/v1/test {
- # deny all;
- default_type 'text/html';
- # lua_code_cache off;
- # content_by_lua 'ngx.say("test.test.com access")';
- access_log /var/log/nginx/chaoshen.access.log main;
- error_log /var/log/nginx/chaoshen.error.log;
- content_by_lua_file /opt/openresty-docker/openresty/lua-dataagent/chaoshen_yuenan.lua;
- }
- error_page 500 502 503 504 /50x.HTML;
- location = /50x.HTML {
- root /usr/local/openresty/nginx/HTML;
- }
- }
配置中 /dataagent/v1/test content_by_lua_file 对应的脚本文件就是用来生产消息的, 我们也直接来看代码
- package.path = '/opt/openresty-docker/openresty/lua-dataagent/lib/resty/?.lua;'
- local rabbitmq = require "rabbitmqstomp"
- local opts = {
- username = 'guest',
- password = 'guest',
- vhost = '/'
- }
- local mq, err = rabbitmq:new(opts)
- if not mq then
- ngx.log(4,'cannot new mq')
- ngx.log(4,err)
- return
- end
- mq:set_timeout(60000)
- -- connect to the rabbitmq on local machine
- local ok, err = mq:connect('192.168.7.201', 12345)
- if not ok then
- -- connect to other rabbitmq in cluster
- ok, err = mq:connect('192.168.7.200', 23456)
- if not ok then
- -- connect to last rabbitmq in cluster
- -- put messages into errorLog
- ngx.log(4,'cannot connect mq')
- ngx.log(4,err)
- return
- end
- end
- -- local msg = "d.g.test.com/gp.do?ac=s_publishgame&action=login&appId=133214321432&serverId=bj_server1&channel=test_gamecenter&accountId=q1132143214&playerId=q1132143214&tm=1458874365&first=1"
- local msg = string.sub(ngx.var.request_uri,22,-1)
- local result = string.gsub(msg,".*/","d.g.test.com/",1)
- local send_receipt_id = ngx.now()*1000
- local headers = {}
- headers["destination"] = "/exchange/statistical/statistical.test"
- headers["receipt"] = send_receipt_id
- headers["app-id"] = "luaresty"
- headers["persistent"] = "true"
- headers["content-type"] = "application/plian"
- local ok, err = mq:send(result, headers)
- if not ok then
- ngx.log(4,'cannot send mq')
- ngx.log(4,err)
- return
- end
- local ok, err = mq:set_keepalive(10000, 100)
- if not ok then
- ngx.log(4,'cannot set_keepalive mq')
- ngx.log(4,err)
- return
- end
- -- ngx.say('success:',msg)
5. 配置 RabbitMq
RabbitMq 比较好的一个资料站点: https://www.jianshu.com/p/124cda48e4ea
https://www.jianshu.com/p/61a90fba1d2a
交换机, statistical
路由规则, statistical.test
队列, test
在 rabbitmq 中建好, 就能往里发消息了
添加 "交换机","路由规则","消息队列"
- // 声明交换机
- rabbitmqctl eval 'rabbit_exchange:declare({resource, <<"/">>, exchange, <<"statistical">>}, topic, true, false, false, []).'
- // 声明消息队列
- rabbitmqctl eval 'rabbit_amqqueue:declare({resource, <<"/">>, queue, <<"test">>}, true, false, [], none).'
- // 绑定交换机, 路由规则, 消息队列
- rabbitmqctl eval 'rabbit_binding:add({binding, {resource, <<"/">>, exchange, <<"statistical">>}, <<"statistical.test">>, {resource, <<"/">>, queue, <<"test">>}, []}).'
6. 测试消息生产
curl -i "http://192.168.7.200:8080/dataagent/v1/test/topic-sendtime-id/d.g.test.com/gp.do"
重点提示:
1. 首先要理解集群的架构
2. 保证打点日志文件的存储空间足够大
3. 了解 openresty
openresty: 是一个基于 Nginx http://openresty.org/cn/nginx.html 与 Lua 的高性能 Web 平台, 其内部集成了大量精良的 Lua 库, 第三方模块以及大多数的依赖项. 用于方便地搭建能够处理超高并发, 扩展性极高的动态 Web 应用, Web 服务和动态网关.
4. 要了解 rabbitmq 的相关知识, 保证交换机, 路由, 消息队列的正常创建
5. 注意容器中配置文件和物理路径的映射关系
6.lua 脚本中, rabbitMq 的地址一版是内网 (局域网) 地址
7.swarm 集群中, 注意个集群节点 label 的自定义
上面都有关于此问题的描述
问题排查思路:
1. 查看 nginx 日志来排查 nginx 异常日志
2. 理解架构途中消息的传递路径, 顺着路近一步步追查
好了, 就写道这里, 我是 jerry 百
来源: https://www.cnblogs.com/mbailing/p/docker_rabbitmq.html