Linux安装RabbitMq和Erlang-搭建集群

Linux安装RabbitMq和Erlang-搭建集群

Rabbitmq rpm包 下载地址:https://github.com/rabbitmq/rabbitmq-server/releases
Erlang rpm包 下载地址:https://github.com/rabbitmq/erlang-rpm/releases
Rabbitmq Erlang版本要求:https://www.rabbitmq.com/which-erlang.html

Linux中安装RabbitMq

下载好的 rpm包,放入 /usr/local/software/下:

rpm常用命令

  • -i 安装软件包(–install)

  • –nodeps 不验证软件包的依赖

  • –force 强制安装,即使覆盖其他包的文件也要安装

  • -v 可视化,提供更多的详细信息的输出

  • -h 显示安装进度

  • -a 查询所有已经安装的软件包

  • -f 查询文件所属于的软件包

  • -q 查询软件包(通常用来看下还未安装的软件包,注意,查询时不需要带包名后缀)

  • -l 显示软件包的文件列表

  • -e 卸载指定软件包(注意,卸载时需要指定具体包名后缀)

  • -U 升级软件包,很少用

常用组合:

  • -ivh 安装指定rmp包并显示安装进度

  • -qa 查询所有已经安装的软件包

  • -qi 查询软件包安装信息

  • -ql 显示软件包的文件列表

  • -qf 查询文件所属的文件包

安装文件

  1. 安装erlang:rpm -ivh erlang-23.0-1.el7.x86_64.rpm

  2. 安装rabbitmq所依赖的插件:yum install socat -y

  3. 安装rabbitmq:rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm

el*:表示发行商的版本,el6表示这个软件包是在rhel6.x/centos6.x下使用;rpm包有个特点,centOS7的rpm包一般只能装到CentOS7的系统里,CentOS6的软件包只能装到CentOS6的系统里
noarch:说明这样的软件包可以在任何平台安装和运行,不需要特定的硬件平台

常用命令

  • 添加开机启动服务:chkconfig rabbitmq-server on

  • 启动服务:/sbin/service rabbitmq-server start

  • 查看服务状态:/sbin/service rabbitmq-server status

  • 停止服务:/sbin/service rabbitmq-server stop

  • 开启 web 管理插件:rabbitmq-plugins enable rabbitmq_management , 然后再浏览器中输入当前IP + 15672端口号即可访问,默认账号:guest,密码:guest。登录会发现出现以下问题:出现下面问题按照添加用户命令执行即可,然后使用添加的用户登录。

  • 开启应用:rabbitmqctl stop_start

  • 关闭应用:rabbitmqctl stop_app

添加用户命令

  • 创建账号:rabbitmqctl add_user 账号 密码

  • 设置用户角色:rabbitmqctl set_user_tags 账号 administrator

  • 设置用户权限:rabbitmqctl set_permissions -p "/" 账号 ".*" ".*" ".*"

  • 获取用户和角色:rabbitmqctl list_users

 

RabbitMq搭建集群

克隆出来两台一样的虚拟机,如下图所示

修改三台服务器的主机名称

  1. vi /etc/hostname

  2. 分别修改为:node1、node2、node3

配置各个节点的 hosts 文件,让各个节点都能互相识别对方

  1. vi /etc/hosts

  2. 分别在三个服务器的host文件中添加以下配置

192.168.85.129 node1
192.168.85.130 node2
192.168.85.131 node3

node1 节点上执行远程操作命令

scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/.erlang.cookie
scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/.erlang.cookie

启动 RabbitMQ 服务,顺带启动 Erlang 虚拟机和 RbbitMQ 应用服务 在三台节点上分别执行以下命令:

rabbitmq-server -detached

node2node3 节点上执行以下命令:

注意:(rabbitmqctl stop 会将Erlang 虚拟机关闭,rabbitmqctl stop_app 只关闭 RabbitMQ 服务) 记得关闭防火墙

1. rabbitmqctl stop_app 
2. rabbitmqctl reset
3. rabbitmqctl join_cluster rabbit@node1
4. rabbitmqctl start_app (只启动应用服务)

查看集群状态

rabbitmqctl cluster_status

需要重新设置用户

  1. 创建账号:rabbitmqctl add_user 账号 密码

  2. 设置用户角色:rabbitmqctl set_user_tags 账号 administrator

  3. 设置用户权限:rabbitmqctl set_permissions -p "/" 账号 ".*" ".*" ".*"

使用上面创建的账号随便访问三台地址都可访问了,如图所示:有三个节点

解除集群节点(node2 和 node3 机器分别执行 - 选择执行)

rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
rabbitmqctl cluster_status
rabbitmqctl forget_cluster_node rabbit@node2 (node1 机器上执行)

上面这些,已经搭建好了集群但是不可复用,意思就是我在 node1节点上生成的消息 node2和node3没有,假如node1宕机了那消息就会丢失,所以就需要用到 镜像队列。

 

镜像队列

引入镜像队列(Mirror Queue)的机制,可以将队列镜像到集群中的其他 Broker 节点之上,如果集群中的一个节点失效了,队列能自动地切换到镜像中的另一个节点上以保证服务的可用性。

搭建步骤

  1. 在 node1、node2、node3 任意节点上添加以下策略:

  2. 在给 node1 上发送一条消息 注意名称要符合上图的规则,上图队列名称是以 hello开头的。

  3. 停掉 node1 之后发现 node2 成为镜像队列

就算整个集群只剩下一台机器了 依然能消费队列里面的消息说明队列里面的消息被镜像队列传递到相应机器里面了

生产者代码

/**
 * 队列名称
 */
public static final String QUEUE_NAME = "hello";

/**
 * 普通生产者
 */
public static void normalProducer() throws Exception {
    // 创建一个连接工厂
    ConnectionFactory factory = new ConnectionFactory();
    // 工厂IP 连接RabbitMq的队列
    factory.setHost("192.168.85.129");
    // 用户名
    factory.setUsername("zjh");
    // 密码
    factory.setPassword("zjh");

    // 创建连接
    Connection connection = factory.newConnection();
    // 获取信道
    Channel channel = connection.createChannel();
    /*
        生成一个队列
        1.队列名称
        2.队列里面的消息是否持久化,默认情况消息存储在内存中
        3.该队列是否只提供一个消费者进行消费 是否进行消息共享,true可以多个消费者消费
        4.是否自动删除 最后一个消费者断开连接以后 该队列是否自动删除 true自动删除 false不自动删除
        5.其他参数
     */
    channel.queueDeclare(QUEUE_NAME, true, false, false, null);

    // 发消息 初次使用
    String message = "hello world";

    /*
        发送一个消费
        1.发送到哪个交换机
        2.路由的Key是哪个,本次队列的名称
        3.其他的参数信息
        4.发送消息的消息体
     */
    channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
    System.out.println("消息发送完毕");
}

消费者代码

/**
 * 队列名称
 */
public static final String QUEUE_NAME = "hello";

/**
 * 接收消息
 */
public static void main(String[] args) throws Exception {
    // 创建一个连接工厂
    ConnectionFactory factory = new ConnectionFactory();
    // 工厂IP 连接RabbitMq的队列
    factory.setHost("192.168.85.130");
    // 用户名
    factory.setUsername("zjh");
    // 密码
    factory.setPassword("zjh");

    // 创建连接
    Connection connection = factory.newConnection();
    // 获取信道
    Channel channel = connection.createChannel();

    // 声明接收消息回调
    DeliverCallback deliverCallback = (consumerTag, message) -> System.out.println(new String(message.getBody()));

    // 声明取消接收消息回调
    CancelCallback cancelCallback = consumerTag -> System.out.println("消息消费被中断");

    /*
        消费者消费信息
        1.消费哪个队列
        2.消费成功之后是否要自动应答 true代表自动应答 false代表手动应答
        3.消费者未成功消费的回调
        4.消费者取消消费的回调
     */
    channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
}

测试结果

可以看到代码中,消费者和生产者的连接ip是不同的,因为发送的消息到 node1 节点上之后需要将 node1 节点停掉,然后消费 node2 或者 node3 节点其目的就是为了测试镜像队列,查看是否备份到了其他机器消息是否丢失,如果成功消费说明备份成功消息未丢失。这样做的话坏处也很明显,就是如果宕机了不能及时更改,就需要借助下方负载均衡实现。

Haproxy 实现负载均衡

Haproxy 提供高可用性、负载均衡及基于TCPHTTP 应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案,包括 Twitter,Reddit,StackOverflow,GitHub 在内的多家知名互联网公司在使用。Haproxy 实现了一种事件驱动、单一进程模型,此模型支持非常大的井发连接数。
扩展 nginx,lvs,haproxy 之间的区别: http://www.ha97.com/5646.html

详细介绍

注意事项

最好是在新节点上搭建,不要图方便再原有的 rabbitmq 节点上搭建,不然可能会出现以下错误

linux 服务器需要关闭:selinuxfirewalld

selinux是什么?有啥用?如何开启和关闭?

  1. 它叫做 "安全增强型Linux(Security-Enhanced Linux)",简称 SELinux,它是 Linux 的一个安全子系统。
  2. 其主要作用就是最大限度地减小系统中服务进程可访问的资源(根据的是最小权限原则)。避免权限过大的角色给系统带来灾难性的结果。
# 查看selinux状态
getenforce
# 临时关闭 selinux
setenforce 0
# 永久关闭selinux
vi /etc/selinux/config
将 SELINUX=enforcing 改为 SELINUX=disabled

# 查看firewalld状态
systemctl status firewalld
# 关闭
systemctl stop firewalld
# 关闭开机自启
systemctl disable firewalld

安装

yum install -y haproxy

直接编辑(使用yum安装后的haproxy的配置文件在 /etc/haproxy/haproxy.cfg)

编辑文件

vi /etc/haproxy/haproxy.cfg

编辑内容

直接将以下内容复制进去就行,然后把 ip地址 改下,就ok

#---------------------------------------------------------------------
# Example configuration for a possible web application.  See the
# full configuration options online.
#
#   http://haproxy.1wt.eu/download/1.4/doc/configuration.txt
#
#---------------------------------------------------------------------

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    # to have these messages end up in /var/log/haproxy.log you will
    # need to:
    #
    # 1) configure syslog to accept network log events.  This is done
    #    by adding the '-r' option to the SYSLOGD_OPTIONS in
    #    /etc/sysconfig/syslog
    #
    # 2) configure local2 events to go to the /var/log/haproxy.log
    #   file. A line like the following can be added to
    #   /etc/sysconfig/syslog
    #
    #    local2.*                       /var/log/haproxy.log
    #
    log         127.0.0.1 local2      # 定义全局的syslog服务器。日志服务器需要开启UDP协议,最多可以定义两个。基于syslog记录日志到指定设备,级别有(err、warning、info、debug)

    chroot      /var/lib/haproxy      # 锁定haproxy的运行目录,把haproxy的进程禁锢在一个文件夹内
    pidfile     /var/run/haproxy.pid  # 指定haproxy的pid文件存放路径,启动进程的用户必须有权限访问此文件。要和service指定的pid路径一样
    maxconn     4000                  # 每个haproxy进程的最大并发连接数,默认4000,可以是100000,还可以更大:一百万
    user        haproxy               # 默认用户
    group       haproxy               # 默认组
    daemon                            # 以后台守护进程的方式运行

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats  # 创建监控所用的套接字目录

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http                  # 默认的模式mode { tcp|http|health },tcp是4层,http是7层,health只会返回OK。后面listen的优先级比默认高,可以单独设置。
    log                     global
    option                  httplog               
    option                  dontlognull           # 启用该项,日志中将不会记录空连接。所谓空连接就是在上游的负载均衡器
    option http-server-close                      # 每次请求完毕后主动关闭http通道
    option forwardfor       except 127.0.0.0/8
    option                  redispatch            # serverId对应的服务器挂掉后,强制定向到其他健康的服务器,重新派发
    retries                 3                     # 3次连接失败就认为服务不可用
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s    # 客户端请求从haproxy到后端server最长连接等待时间(TCP连接之前),默认单位ms
    timeout client          1m     # 设置haproxy与客户端的最长非活动时间,默认单位ms,建议和timeout server相同
    timeout server          1m     # 客户端请求从haproxy到后端服务端的请求处理超时时长(TCP连接之后),默认单位ms,如果超时,会出现502错误,此值建议设置较大些,访止502错误。
    timeout http-keep-alive 10s    # session会话保持超时时间,此时间段内会转发到相同的后端服务器
    timeout check           10s    # 对后端服务器的默认检测超时时间
    maxconn                 3000   # 最大连接数

#
# haproxy 管理页面
#
listen admin_stats *:8090              #为 haproxy 访问状态监控页面配置,取名为 admin_stats,端口号自定义
    stats enable                       #启用监听端口
    mode http                          #http的7层模式
    log global                         #继承global中log的定义
    stats uri /stats                   #监控页面的url访问路径,即 http://ip/stats 访问监控页面
    stats realm Haproxy\ Statistics    #监控页面的密码框提示信息
    stats auth admin:admin             #监控页面的用户和密码,可以设置多个用户名
    stats admin if TRUE                #当通过认证才可管理
    stats refresh 30s                  #页面自动刷新时间30s

#
# rabbitmq web页面访问,当前ip + 15672就可以访问
# check inter 2000 每隔2s检测一次,fall 5是5次失败认为服务器不可用
# 
listen rabbitmq_admin
   bind 0.0.0.0:15672
   mode http               #配置HTTP模式
   option httplog
   balance roundrobin      #简单的轮询
   server  node1 192.168.85.129:15672 check check inter 2000 fall 5
   server  node2 192.168.85.130:15672 check check inter 2000 fall 5
   server  node3 192.168.85.131:15672 check check inter 2000 fall 5

#
# rabbitmq 集群
#
listen rabbitmq_cluster
    bind 0.0.0.0:5672
    mode tcp            #配置TCP模式
    option tcplog
    balance roundrobin  #简单的轮询
    server  rabbitmq-node1 192.168.85.129:5672 check check inter 2000 fall 5
    server  rabbitmq-node2 192.168.85.130:5672 check check inter 2000 fall 5
    server  rabbitmq-node3 192.168.85.131:5672 check check inter 2000 fall 5 

haproxy rsyslog日志配置

# 编辑此文件
vi /etc/rsyslog.conf
# 在文件的最后一行,加上:
local2.* /var/log/haproxy/rabbitmq.log

重启日志服务 + 启动haproxy代理服务

# 重启日志服务
systemctl restart rsyslog.service

# 启动haproxy。其中 -f 代表指定配置文件的路径。
haproxy -f /etc/haproxy/haproxy.cfg

# 重新启动haproxy
haproxy -f /etc/haproxy/haproxy.cfg -st `cat /var/run/haproxy.pid`

# 停止haproxy
kill -9 `cat /var/run/haproxy.pid`

# 查看haproxy启动的端口
ss -nplt | grep haproxy

可能会出现以下警告,可以忽略。

访问 haproxy 管理页面

http://ip:8090/stats

访问 rabbitmq web界面

http://ip:15672/

测试步骤

  1. 还是上方的生产者和消费者代码,生产者代码不变启动发送消息,如下图所示:

  2. 然后我们将生产者连接的服务器给关闭了,将消费者的地址改为安装了 haproxy 的服务器ip 进行消费,如下图所示消费成功。

如果我们负载均衡服务器挂掉了,那就会导致所有的rabbitmq服务器都不可用,就需要借助 Keepalived

Keepalived 引用于 https://blog.csdn.net/want_you_gogo/article/details/127285107

 

Keepalived 主备

用keepalived做主备,避免单点问题、实现高可用。在 192.168.85.132(主)、192.168.85.133(备) 节点上分别安装Keepalived。

安装相关命令

# 安装
yum install -y keepalived
# 备份原有配置
cp /etc/keepalived/keepalived.conf{,.bak}
# 配置Keepalived
vi /etc/keepalived/keepalived.conf

编辑 192.168.85.132 机器

! Configuration File for keepalived

# keepalived全局配置
global_defs {
    #每个keepalived节点的唯一标识,不能与备机相同。
    router_id keepalived_master
}

# 检测 Haproxy 脚本
vrrp_script check_haproxy {
   script "/etc/keepalived/haproxy_check.sh"  #脚本所在的目录
   interval 10                                #检测 haproxy 心跳频率:每隔10秒检测一次
   weight 11                                  #权重
}

# 虚拟路由器配置
vrrp_instance haproxy {
    state MASTER         # 设置虚拟路由器状态为MASTER,表示为主。
    interface ens33      # 绑定当前虚拟路由器所使用的物理网卡,如eth0、bond0等,可通过 ifconfig 获得。
    virtual_router_id 51 # 每个虚拟路由器的唯一标识。同属一个虚拟路由器的多个keepalived节点必须相同,务必要确保在同一网络中此值唯一。
    priority 90          # 当前物理节点在此虚拟路由器中的优先级,值越大优先级越高。注意:主机的优先权要比备机高。
    advert_int 1         # 心跳检查频率,单位:秒。
    
    # 认证机制
    authentication {
        auth_type PASS # 认证类型
        auth_pass 1111 # 秘钥,同一虚拟路由器的多个 keepalived 节点 auth_pass 值必须保持一致
    }

    # 虚拟路由器的VIP,不指定网卡时默认添加在eth0上。在添加VIP地址时,需确保将要使用的VIP不存在,避免冲突。
    virtual_ipaddress {
        192.168.85.140  # 对外开放的虚拟ip
    }

    # 调用检测 haproxy 的脚本
    track_script {
        check_haproxy
    }
}

编辑 192.168.85.133 机器

! Configuration File for keepalived

# keepalived全局配置
global_defs {
    # 每个keepalived节点的唯一标识,不能与备机相同。
    router_id keepalived_backup
}

#检测 Haproxy 脚本
vrrp_script check_haproxy {
   script "/etc/keepalived/haproxy_check.sh"  # 脚本所在的目录
   interval 10                                # 检测 haproxy 心跳频率:每隔10秒检测一次
   weight 11                                  # 权重
}

#虚拟路由器配置
vrrp_instance haproxy {
    state BACKUP         # 设置虚拟路由器状态为BACKUP,表示为备。
    interface ens33      # 绑定当前虚拟路由器所使用的物理网卡,如eth0、bond0等,可通过 ifconfig 获得。
    virtual_router_id 51 # 每个虚拟路由器的唯一标识。同属一个虚拟路由器的多个keepalived节点必须相同,务必要确保在同一网络中此值唯一。
    priority 80          # 当前物理节点在此虚拟路由器中的优先级,值越大优先级越高。注意:主机的优先权要比备机高。
    advert_int 1         # 心跳检查频率,单位:秒。
    
    # 认证机制
    authentication {
        auth_type PASS # 认证类型
        auth_pass 1111 # 秘钥,同一虚拟路由器的多个 keepalived 节点 auth_pass 值必须保持一致
    }

    # 虚拟路由器的VIP,不指定网卡时默认添加在eth0上。在添加VIP地址时,需确保将要使用的VIP不存在,避免冲突。
    virtual_ipaddress {
        192.168.85.140  # 对外开放的虚拟ip
    }

    # 调用检测 haproxy 的脚本
    track_script {
        check_haproxy
    }
}

详细的keepalived配置可参考:https://blog.51cto.com/johnnyfang/5419017

编写 haproxy 检测脚本

脚本作用:检测 haproxy 服务,如果haproxy服务挂了则先尝试自动重启 haproxy 服务;如果重启不成功,则关闭 Keepalived 服务并切换到backup。
注意:两台 haproxy 服务器都要编写以下内容

# 编辑文件
vi /etc/keepalived/haproxy_check.sh
#!/bin/bash
#haproxy存活检测脚本
#当发现haproxy服务挂掉时,先自动重启haproxy的服务,如果启动不成功则关闭当前节点的Keepalived服务,并将虚拟vip飘到备用节点上。

#-----------全局变量------------
# 本机ip
localIP=`ip a|grep inet|grep global|grep brd|head -n 1|awk '{printf $2}'|cut -d "/" -f1`
# backup节点ip
backupIP="192.168.85.133"

# haproxy 服务存活检测
if [ $(ps -C haproxy --no-header | wc -l) -eq 0 ]; then
    # 记录日志
    echo "检测到节点${localIP}的HAProxy服务挂掉,正在尝试重启中..." >> /etc/keepalived/keepalived.log
    # 重启haproxy服务
    haproxy -f /etc/haproxy/haproxy.cfg

    sleep 2s

    # haproxy 自动重启成功
    if [ $(ps -C haproxy --no-header | wc -l) -eq 1 ]; then
        echo "检测到节点${localIP}的HAProxy服务挂掉,正在尝试重启中...重启成功!" >> /etc/keepalived/keepalived.log
    fi
fi

# 这里最好休眠2s等待haproxy启动成功,不然下面的判断有可能还会出现找不到haproxy服务进程的情况
# 注意:这个sleep时间一定要比keepalived.conf配置里"检测 haproxy 心跳频率:interval  10"设置的时间要短,否则将卡在sleep这!
sleep 2s

# 自动重启不成功,进行vip切换操作,邮件+短信通知
if [ $(ps -C haproxy --no-header | wc -l) -eq 0 ];then
    echo "检测到节点${localIP}的haproxy服务挂掉,尝试重启失败,停掉此节点上的keepalived服务,虚拟vip将飘到backup节点:${backupIP}" >> /etc/keepalived/keepalived.log
    
    # 停止 keealived 服务
    service keepalived stop
fi

给脚本添加权限

chmod +x /etc/keepalived/haproxy_check.sh

创建日志文件名称

# 编辑文件名
vi /etc/keepalived/keepalived.log
# 保存
:wq

启动keepalived的服务

启动顺序:先启动master节点服务,再启动backup节点的服务。

# 启动keepalived服务
service keepalived start
# 查看keepalived状态
service keepalived status
#查看keepalived启动日志:
journalctl -xe
#查看keepalived日志
tail -f /var/log/messages
#停掉keepalived服务
service keepalived stop
  1. 在master上查看主机ip信息

可知绑定在 ens33 上的vip正常出现在master节点上,且主机 ip 多出了一个 192.168.85.140(配置文件中默认添加的虚拟VIP地址),而且也能ping通。

  1. 在backup上查看主机ip信息则没有此虚拟ip

测试vip(虚拟ip:192.168.85.140)是否可用

使用 vip(虚拟ip:192.168.85.140) 访问 Rabbitmq 管理平台和 haproxy 监控平台


测试vip飘移(主备切换)

# 启动 haproxy 服务
haproxy -f /etc/haproxy/haproxy.cfg
# 停止 kaproxy 服务
kill -9 `cat /var/run/haproxy.pid`
# 重新启动 haproxy 服务
haproxy -f /etc/haproxy/haproxy.cfg -st `cat /var/run/haproxy.pid`
# 查看 haproxy 服务状态
ss -nplt | grep haproxy
# 启动 keepalived 服务
service keepalived start
# 查看 keepalived 服务状态
service keepalived status
# 停止 keepalived 服务
service keepalived stop
  1. 关闭 master 节点(192.168.85.132) 的 haproxy 服务:

此时, keepalived 每隔 10s 触发一次的haproxy心跳检测脚本haproxy_check.sh里自动恢复了 master 节点的 haproxy 服务。虚拟 ip 没有飘到 backup 节点:

  1. 模拟 master 节点的 haproxy 服务无法自动恢复时

    再次 kill 掉 master 节点的 haproxy 服务:


结论:虚拟 ip 能在主备上自动切换,切换后 vip 自动从 master 上飘到 backup 上

  1. 飘移后继续测试vip是否可用:
    http://192.168.85.140:15672
    http://192.168.85.140:8090/stats

结论:用虚拟ip(192.168.85.140)访问 haproxy 监控统计平台和 RabbitMQ 管理平台,依然能访问成功!

  1. 恢复 master(启动haproxy、keepalived),看看 vip 能否飘回来

结论:当 master 节点恢复后,vip 自动飘回 master 节点!

posted @ 2022-11-18 17:54  橙香五花肉  阅读(53)  评论(0编辑  收藏  举报