背景介绍

需求:定时消息. 支持分布式,宕机后消息可恢复,消息的最终一致性.
目标:
1. 支持业务方定时消息的场景,例如”定一个明天早上8点钟的闹钟”
2. 消除重复的 ScheduledExecutorService 代码
3. 高可用.

调研:开源的kafuka和rabbitmq无延迟队列,但可以通过自身特性实现.
阿里的mq 免费版支持秒级别,收费版支持毫秒级.
方案一:定时扫mysql,取出到期的任务,通知业务方.(http相对tcp延迟高)
方案二:通过Rabbitmq的 DeadQueue 及消息的TTL特性实现,消息会经历两次publish, 第一次投放到delay-queue中,第二次是消息过期时通过routingkey 投放到实际期望投放的queue中.(kafuka的持久化需要手工处理,且数据最终一致性不满足业务场景)

环境部署文档

1.打包好定制的Docker镜像.

2.分别运行Rabbitmq节点.

2.1 第一版方案节点间通过docker的network相互发现, 但部署到三台不同的机器上还需要服务注册发现.
2.2 节点间相互发现直接使用FQDN(Fully Qualified Domain Name完全合格域名/全程域名缩写,访问时将由DNS进行解析得到IP)

docker run -d -h {机器hostname} --name rabbit  -p "4369:4369" -p "5672:5672"    -p "15672:15672"  -p "25672:25672"   -p "35197:35197" -e "RABBITMQ_USE_LONGNAME=true"  -e "RABBITMQ_LOGS=/var/log/rabbitmq/rabbit.log"   -v /data/users/ltfu/rabbitmq/data:/var/lib/rabbitmq  -v /data/users/ltfu/rabbitmq/logs:/var/log/rabbitmq {镜像id}

2.3 这里有三点很重要
2.3.1 指定的-h 参数必须同机器 hostname .
2.3.2 RABBITMQ_USE_LONGNAME = true 代表通过FQDN发现集群节点.
2.3.3 指定挂载的目录.

3.节点加入集群

3.1 三台机器都启动后,将三个节点 /data 目录下的 .erlang.cookie 改成相同的值.
3.2 三台机器分别 在hosts文件中加入节点的 ip 及 host .
加入后分别重启正在运行rabbitmq的docker容器。
3.3 在两个Rabbitmq Slave节点上执行(不退出docker容器):

docker exec rabbit rabbitmqctl stop_app
docker exec rabbit rabbitmqctl join_cluster rabbit@{Master Hostname}
docker exec rabbit rabbitmqctl start_app

3.4退出集群

docker exec rabbit bash -c "rabbitmqctl stop_app && rabbitmqctl reset && rabbitmqctl start_app"

4.选择队列需要的镜像模式

若队列名称以ha开头, 则集群中的所有节点为该队列配置镜像

rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'

除了amq开头的交换器绑定队列,设置所有队列为镜像队列.

docker exec rabbitmq1 rabbitmqctl set_policy HA '^(?!amq\.).*' '{"ha-mode": "all"}'

以 two 开头的队列,镜像到集群中的任意两个节点,自动同步:

rabbitmqctl set_policy ha-two "^two\."'{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

5.为集群提供一个入口,通过Haproxy做负载均衡

5.1 安装Haproxy

sudo add-apt-repository ppa:vbernat/haproxy-1.6
sudo apt-get update
sudo apt-get install haproxy
haproxy -v #验证安装成功

5.2 修改配置文件

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy # 改变当前工作目录
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy  # 默认用户
        group haproxy # 默认用户组
        daemon        # 创建1个进程进入deamon模式运行

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # Default ciphers to use on SSL-enabled listening sockets.
        # For more information, see ciphers(1SSL). This list is from:
        #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3
###########默认配置#########
defaults
        log     global
        mode    http # 默认的模式mode { tcp|http|health },tcp是4层,http是7层,health只会返回OK
        option  httplog # 采用http日志格式
        option  dontlognull # 启用该项,日志中将不会记录空连接。所谓空连接就是在上游的负载均衡器或者监控系统为了探测该 服务是否存活可用时,需要定期的连接或者获取某一固定的组件或页面,或者探测扫描端口是否在监听或开放等动作被称为空连接;官方文档中标注,如果该服务上游没有其他的负载均衡器的话,建议不要使用该参数,因为互联网上的恶意扫描或其他动作就不会被记录下来
        timeout connect 5000   # 连接超时时间
        timeout client  50000  # 客户端连接超时时间
        timeout server  50000  # 服务器端连接超时时间
        option  httpclose      # 每次请求完毕后主动关闭http通道 
        option  httplog
        option  redispatch     # serverId对应的服务器挂掉后,强制定向到其他健康的服务器
        maxconn     60000      # 最大连接数
        retries     3          # 默认3次连接失败认为服务不可用
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http

listen http_front
        bind 0.0.0.0:1080 #监听端口
        stats refresh 30s #统计页面自动刷新时间  
        stats uri /haproxy?stats #统计页面url
        stats realm MobvoiHaproxy Manager #统计页面密码框上提示文本
        stats auth user:password #统计页面用户名和密码设置
        #stats hide-version #隐藏统计页面上HAProxy的版本信息

listen rabbitmq_admin
    bind 0.0.0.0:8004 
    server 机器0host  0ip:15672
    server 机器1host  1ip:15672
    server 机器2host  2ip:15672

listen rabbitmq_cluster
    bind  0.0.0.0:5673
    option tcplog
    mode tcp
    timeout client  3h
    timeout server  3h
    option          clitcpka
    balance roundrobin
    server   机器0host  0ip:5672 check inter 5s rise 2 fall 3
    server   机器1host  1ip:5672 check inter 5s rise 2 fall 3
    server   机器2host  2ip:5672 check inter 5s rise 2 fall 3

5.3 重启Haproxy

sudo service haproxy restart

5.4 通过
{Haproxy机器域名}:1080/haproxy?stats 查看Haproxy状态
{Haproxy机器域名}:8004 查看Rabbitmq集群状态
{Haproxy机器域名}:5673 TCP连接Rabbitmq集群