MYSQL教程实战篇:使用haproxy搭建负载均衡集群

作者 : IT 大叔 本文共13618个字,预计阅读时间需要35分钟 发布时间: 2020-11-14

负载均衡集群

架构是这样子的:一台主服务器,只负责写;多台从服务器(起码两台),只负责读;一台负载均衡节点,负责均匀的分配请求到多台从服务器。

所以负载均衡集群至少需要4台服务器

使用负载均衡的好处:
1.如果有一台从服务器挂掉了,负责负载均衡的服务器会将本来分配到该从服务器的请求分配给其他从服务器,不至于导致挂了一台从服务器,那么该台服务器的请求就得不到相应。

2.如果没有负载均衡节点的调度,那么可能请求从节点A的人很多,造成A节点压力很大,请求节点C的人很少,造成资源浪费,但是有了负载均衡节点就不会有这种情况。

负载均衡工具:
Lvs,HaProxy,Nginx等

Nginx是基于http协议的,也就是说,只能用于web应用(网站,Api之类的),也就是说不能单独对MySQL进行负载均衡,只能对HTTP请求进行负载均衡
LVS Linux的虚拟服务,但是需要的机器很多,适合大型服务
HaProxy 基于TCP协议,可以代理MySQL

性能 LVS > HaProxy > Nginx

这里主要演示搭建HaProxy实现mysql从节点的负载均衡。

1个主节点A,2个从节点B,C,1个负载均衡节点D

架构如下:
A 和 B,C已建立主从复制关系

业务逻辑层(如PHP),已经配置好读写分离,当执行增删改时会去请求A,执行select时会随机请求B,C中的随便一个节点。

搭建haproxy负载均衡,将B,C绑定到节点D上面(注意D节点上不需要安装有数据库,只需要安装有haproxy即可),业务逻辑层的配置也进行修改,改为:执行增删改时会去请求A,执行select时会请求D节点。D节点就会将请求平均的分配到B,C节点,然后将从节点返回的数据返回给业务层。
当然,由于像TP5这样的框架,请求数据库节点默认时3306端口,所以D节点的haproxy最好绑定3306端口,当业务层请求“D的ip:3306”时,业务层认为他请求的时一个数据库,其实D节点不是数据库,只是个中间代理而已,当然返回的是数据库的数据。

下面是具体操作:
A 首先,对主节点和2个从节点进行主从复制

B 搭建haproxy:
先对2个从节点创建2个用户名密码相同的用户(在主节点创建即可,因为另外两个从节点会复制主节点的数据,在mysql库的user表自动增加用户记录)。
关闭四台机器的防火墙。将下载好的haproxy放到负载均衡节点,解压,编译安装,具体安装过程可以上网查,这里我将安装过程贴出来:
将下载的tar.gz文件放到/root下,然后:

tar -xzf haproxy-2.1.2.tar.gz

cd haproxy-2.1.2

make TARGET=Linux31     # 这里需要使用uname -r查看系统版本centos6.X需要使用TARGET=linux26  centos7.x使用linux31 

make install PREFIX=/usr/local/haproxy

mkdir /usr/local/haproxy/conf   # 创建配置文件目录

cp examples/option-http_proxy.cfg /usr/local/haproxy/conf/haproxy.cfg   # 复制配置文件

配置文件内容如下:

global
        maxconn         20000
        ulimit-n        16384
        log             127.0.0.1 local0
        uid             200
        gid             200
        chroot          /var/empty
        nbproc          1
        daemon

frontend test-proxy
        bind            192.168.200.10:8080
        mode            http
        log             global
        option          httplog
        option          dontlognull
        option          nolinger
        option          http_proxy
        maxconn         8000

 

global
用来设定全局配置参数,属于进程级的配置,通常和操作系统配置有关。

global下
log:全局的日志配置,local0 是日志设备,info表示日志级别。其中日志级别有err、warning、info、debug四种可选。这个配置表示使用 127.0.0.1 上的 rsyslog 服务中的local0日志设备,记录日志等级为info。

maxconn:设定每个haproxy进程可接受的最大并发连接数,此选项等同于Linux命令行选项“ulimit -n”。

user/group:设置运行 haproxy进程的用户和组,也可使用用户和组的 uid和gid 值来替代。

daemon:设置 HAProxy进程进入后台运行。这是推荐的运行模式。

nbproc:设置 HAProxy 启动时可创建的进程数,此参数要求将HAProxy 运行模式设置为“daemon”,默认只启动一个进程。根据使用经验,该值的设置应该小于服务器的 CPU核数。创建多个进程,能够减少每个进程的任务队列,但是过多的进程可能会导致进程的崩溃。

pidfile:指定 HAProxy进程的 pid文件。启动进程的用户必须有访问此文件的权限。

defaults 部分
默认参数的配置部分。在此部分设置的参数值,默认会自动被引用到下面的 frontend、backend和 listen部分中,因此,如果某些参数属于公用的配置,只需在 defaults 部分添加一次即可。而如果在 frontend、backend和 listen部分中也配置了与 defaults 部分一样的参数,那么defaults部分参数对应的值自动被覆盖。

frontend部分

此部分用于设置接收用户请求的前端虚拟节点。frontend是在 HAProxy1.3版本之后才引入的一个组件,同时引入的还有 backend组件。通过引入这些组件,在很大程度上简化了 HAProxy配置文件的复杂性。frontend可以根据 ACL规则直接指定要使用的后端

backend部分
此部分用于设置集群后端服务集群的配置,也就是用来添加一组真实服务器,以处理前端用户的请求。添加的真实服务器类似于 LVS中的real server节点。

5、listen部分

此部分是 frontend 部分和 backend 部分的结合体。在 HAProxy1.3 版本之前,HAProxy 的所有配置选项都在这个部分中设置。为了保持兼容性,HAProxy 新的版本仍然保留了 listen 组件的配置方式。目前在 HAProxy 中,两种配置方式任选其一即可。

这里我不过多的讲解选项,只讲解比较重要的选项:
上面的是官方给出的配置文件,下面是我自己设置的配置:

global
    daemon
    nbproc  1
    pidfile /usr/local/haproxy/conf/haproxy.pid  # 这个待会要自己建
        
defaults
    mode tcp        # 默认的模式(tcp|http|health),tcp是4曾,http是7层,health只返回OK
    retries 2       # 两次连接失败就认为服务器不可用,也可以通过后面设置
    option redispatch   # 当server选项指定的服务器挂掉后,强制定向到其他健康的服务器
    option abortonclose     #当服务器负载很高,自动结束当前队列处理的比较久的连接
    maxconn 4096        # 默认最大连接数
    timeout connect 5000ms  #连接超时时间
    timeout client 30000ms  #客户端超时
    timeout server 30000ms  #服务器超时
    log 127.0.0.1 local0 err

# 上面这些配置直接拿来用即可,不用死机,有印象知道其含义即可
# 下面的配置就是我们要自己设置的

listen test1        # 配置负载均衡的节点,test1是名字,可以随意
    bind 0.0.0.0:3300   # haproxy服务监听的IP和端口,这里的0.0.0.0就是本机
    mode tcp    # 数据库连接,肯定是tcp协议,这里其实可以不指定,因为前面已经指定过了
    
    server s1 节点1的IP:3306    # 指定从节点1的IP和端口
    server s2 节点2的IP:3306    # 指定从节点2的IP和端口

# haproxy只需要绑定从节点的IP,因为读写时分离的,读都会去请求haproxy服务,然后haproxy服务才会去请求绑定的从节点,写会单独去请求主节点,不会经过haproxy的。
接下来,创建pid文件,并启动haproxy

vi /usr/local/haproxy/conf/haproxy.pid
echo 1 > haproxy.pid 

haproxy -f /usr/local/haproxy/conf/haproxy.cfg   # -f是指定配置文件,haproxy命令要添加到环境变量中,这个命令在/usr/local/haproxy/sbin下。

启动成功后,就已经搭建负载均衡完毕。

下面为实战教程:

任务:主从复制 + 读写分离 + 负载均衡

主:54.22.37.21
从1:54.22.37.20
从2:54.22.37.19
均衡:204.175.124.51

操作对象:test数据库

 

架构图如下:

MYSQL教程实战篇:使用haproxy搭建负载均衡集群插图

简易Mysql负载均衡配置架构图

 

========================= 主节点配置 =========================

# 授权一个允许从节点主从复制的用户repl

grant replication slave on *.* to 'repl'@'54.22.37.%' identified by "xxxxx";

# 开启二进制日志

server-id =21
log-bin = /var/lib/mysql/mysql-bin
log-bin-index = /var/lib/mysql/mysql-bin.index
binlog_format = mixed
expire_logs_days    = 10
max_binlog_size   = 100M

# 重启服务

# 备份test数据库

# 获取master状态

show master status
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000007 |   921390 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+

 

================== 从节点1(54.22.37.20)配置 =====================

# 尝试远程连接一下

# 创建test库并导入test

# 配置同步日志

server-id = 20
relay-log = /var/lib/mysql/relay-bin
relay-log-index = /var/lib/mysql/relay-bin.index
replicate-do-db = test
slave-skip-errors = all

PS:这里我没有开启二进制日志,只开启了同步日志

# 指向主节点

stop slave;     # 先停止之前的同步
reset slave;    # 清空之前的同步
change master to master_host="54.22.37.21",master_user="repl",master_password="xxxxx",master_log_file="mysql-bin.000007",master_log_pos=921390;     #指向主节点
start slave;    # 开启同步

 

# 查看同步的状态

show slave status;
*************************** 1. row ***************************
               Slave_IO_State:
                  Master_Host: 54.22.37.21
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000007
          Read_Master_Log_Pos: 921390
               Relay_Log_File: relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: mysql-bin.000007
             Slave_IO_Running: No
            Slave_SQL_Running: Yes
              Replicate_Do_DB: test
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 921390
              Relay_Log_Space: 154
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File:
           Master_SSL_CA_Path:
              Master_SSL_Cert:
            Master_SSL_Cipher:
               Master_SSL_Key:
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 1593
                Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 86
                  Master_UUID:
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind:
      Last_IO_Error_Timestamp: 200115 20:13:05
     Last_SQL_Error_Timestamp:
               Master_SSL_Crl:
           Master_SSL_Crlpath:
           Retrieved_Gtid_Set:
            Executed_Gtid_Set:
                Auto_Position: 0
         Replicate_Rewrite_DB:
                 Channel_Name:
           Master_TLS_Version:

显示同步失败
重点看这几个地方
Slave_IO_Running: No
Slave_SQL_Running: Yes
Replicate_Do_DB: test
Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.

同步失败的原因在Last_IO_Error说的很明白,具有相同的UUID,这是因为
主:54.22.37.21
从1:54.22.37.20
从2:54.22.37.19
这三台服务器是从一个镜像拷出来的,所以,他们mysql的UUID是相同的。

 

我们只要将UUID该成不同的就行:

show variables like "%uuid%";   # 查看当前uuid
select uuid();                  # 生成一个新的uuid,复制该uuid
show varialbes like "%datadir"  # 查看数据目录位置,uuid文件的存放位置也在这里,yum安装的mysql一般在/var/lib/mysql

 

# 修改/var/lib/mysql/auto.cnf uuid就写在这个文件里

[auto]
server-uuid=dd8d34de-be32-11e9-931e-04155d871a13

将其修改为刚刚复制的uuid

重启服务

# 重新检查同步状态

show slave status;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 54.22.37.21
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000007
Read_Master_Log_Pos: 921390
Relay_Log_File: relay-bin.000003
Relay_Log_Pos: 320
Relay_Master_Log_File: mysql-bin.000007
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB: test
...

同步成功;

================== 从节点2(54.22.37.19)配置 =====================

做和从节点1相同的操作

在主节点测试插入几条数据试试,看看是否同步。

================== 负载均衡节点配置(204.175.124.51) ===============

# 安装和配置好haproxy(之前章节有安装和配置的步骤操作)
配置好的haproxy的配置文件内容如下:

global
    daemon
    nbproc  1
    pidfile /usr/local/haproxy/conf/haproxy.pid  # 这个待会要自己建
        
defaults
    mode tcp        # 默认的模式(tcp|http|health),tcp是4曾,http是7层,health只返回OK
    retries 2       # 两次连接失败就认为服务器不可用,也可以通过后面设置
    option redispatch   # 当server选项指定的服务器挂掉后,强制定向到其他健康的服务器
    option abortonclose     #当服务器负载很高,自动结束当前队列处理的比较久的连接
    maxconn 4096        # 默认最大连接数
    timeout connect 5000ms  #连接超时时间
    timeout client 30000ms  #客户端超时
    timeout server 30000ms  #服务器超时
    log 127.0.0.1 local0 err

listen test1        # 配置负载均衡的节点,test1是名字,可以随意
    bind 0.0.0.0:3300   # haproxy服务监听的IP和端口,这里的0.0.0.0就是本机
    mode tcp    # 数据库连接,肯定是tcp协议,这里其实可以不指定,因为前面已经指定过了
    
    server s1 54.22.37.19:3306    # 指定从节点1的IP和端口
    server s2 54.22.37.20:3306    # 指定从节点2的IP和端口

PS:由于负载均衡本身就有mysql,mysql占用了3306端口。所以,haproxy绑定3300端口以防止冲突。
haproxy只需指定从节点,只和从节点连接。主节点只有一个只负责写,由业务层直接连接,无需由haproxy连接。haproxy会对查询请求平均分配到两个从节点。

# 创建haproxy.pid文件

cd /usr/local/haproxy/conf && touch haproxy.cfg && echo 1>haproxy.cfg 
chmod 755 haproxy.cfg

 

================== 从节点授权用户给负载均衡节点 ===============

# 在正式启动haproxy服务之前,先在所有从节点创建一个供负载均衡节点连接到从节点的用户
我们可以这样,修改从节点1和从节点2的配置文件,加多一条 replicate-do-db=mysql,然后重启mysql
表示从节点同步mysql库
然后在主节点创建和授权这个供负载均衡节点使用的用户,这样所有从节点就也都有这个用户了

grant all on *.* to "haproxy"@"204.175.124.51" identified by "xxxxx"

创建了haproxy用户给ip 204.175.124.51(负载均衡节点),拥有所有权限。

最后回到负载均衡节点,我们启动haproxy服务

haproxy -f /usr/local/haproxy/conf/haproxy.cfg          # 指定配置文件启动haproxy,haproxy命令已添加到环境变量

 

========================= 测试 ========================

最后我们进行测试,如果你的负载均衡节点没有设置防火墙,那么可以直接在本地进行测试:

mysql -h204.175.124.51 -uhaproxy -P3300 -pxxxxx        # 这里就像连mysql一样去连负载均衡节点的的haproxy服务即可,这里表面上像是连204.175.124.51mysql,其实连的是该节点的haproxy服务。
show variables like "%server_id%";      # 查看所连的节点的server-id

过多一段时间再执行一次上面的查询,发现显示的server-id不同,就说明haproxy将请求分配到了两个从节点,通过连接haproxy可以访问到两个从节点。

这里说一下整个连接的过程:
应用层------->请求负载均衡节点的haproxy服务
负载均衡节点haproxy-------->通过一定算法,请求两台从节点中的一台

应用层请求的是负载均衡节点的haproxy服务,而不是它的mysql服务,所以负载均衡节点的mysql就算关闭了或者根本没安装mysql都没事。
只要负载均衡节点没有设置防火墙,应用层在执行mysql -h204.175.124.51 -uhaproxy -P3300 -pxxxxx的时候都可以连接到haproxy服务

在负载均衡节点上,haproxy服务使用在应用层输入的mysql的用户名和密码去请求从服务器,所以在从节点授权用户的时候,授权的ip不是应用层的IP,也不是从节点的ip,而是负载均衡节点的ip,因为这个用户是给负载均衡节点用的。应用层无法直接连到从节点,因为从节点没有授权用户给应用层。

 

================== 限制客户端连接负载均衡节点 ===============

最后为了安全起见,要给均衡节点设置防火墙,限定特定的ip或者ip段才能连接负载均衡节点

我的Linux是Centos 7.2版本,防火墙是firewalld命令
系统是默认不开启防火墙的。接下来介绍一些简单的防火墙知识和命令

systemctl status firewalld  # 查看防火墙状态
systemctl stop firewalld    # 关闭防火墙
systemctl start firewalld   # 开启防火墙
systemctl is-enabled firewalld  #查看防火墙是否开机自启
systemctl enable firewalld.service  # 开机自启防火墙
systemctl disable firewalld.service # 开机禁用防火墙

firewalld-cmd 基本使用
firewall 可以看成整个防火墙服务,而firewall-cmd可以看成是其中的一个功能,可用来管理端口和ip

firewall-cmd --zone=public --list-ports  # 查看开放的所有端口

firewall-cmd --zone=public --list-ports  # 查看所有开放的情况

firewall-cmd --zone=public --add-port=80/tcp --permanent    # 开放80端口,永久有效。必须重新加载才能生效:firewall-cmd --reload  

firewall-cmd --zone=public --remove-port=9898/tcp --permanent  #关闭指定端口

firewall-cmd对端口的操作,如开放端口等信息,都放在在"/etc/firewall/zones/public.xml"中记录

上面是对端口的操作。

如果想针对IP和端口设置如下:

firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address='192.168.10.0/24' service name='ssh' drop"
# 表示192.168.10这个IP段的用户无法通过ssh远程连接这个机器 service name='ssh' 表示对哪个服务进行操作。


firewall-cmd --permanent --zone=public --add-rich-rule="rule family='ipv4' source address='192.168.10.50' port port=22 protocol=tcp reject"
# 表示对192.168.10.50这个用户禁掉22端口

firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address='192.168.10.50' port port=22 protocol='tcp' accept"
# 允许该用户连接22端口

drop和reject都是禁止连接,只不过方式不同。推荐使用reject。

如果要取消上一条的设置,比如取消掉accept这个操作,使用--remove-rich-rule即可
firewall-cmd --permanent --remove-rich-rule="rule family=ipv4 source address='192.168.10.50' port port=22 procotol='tcp' accept"

记得最后要 firwall-cmd --reload 才能生效

那么接下来我们设置只允许某ip段的用户连接负载均衡节点。

# 我的服务器默认没有开启防火墙,所以现在任何服务器都能连接我服务器的任何端口,所以haproxy自然也可以连接

# 现在开启防火墙

service firewalld start 

# 这个时候,任何IP都连不了我的任何端口;所以我自己的博客网打不开了(80端口),但是可以通过ssh远程连接服务器,因为firewall默认允许ssh服务连接。

# 现在开启80端口,让我的博客网能够访问:

firewall-cmd --zone=public --add-port=80/tcp  --permanent   # 这一步你们可以不用设置

# 对173.12.234这个IP段(这是家里的WiFi的IP段)开启3300这个端口,让它可以连接haproxy服务

firewall-cmd --permanent --zone=public --add-rich-rule="rule family=ipv4 source address='173.12.234.0/24' port port=3300 protocol='tcp' accept"

# 让其生效

firewall-cmd --reload

这样只有这个ip段能够连接haproxy,安全性大大增加。

在实际开发中,应该将负载均衡节点分配给应用层所在的服务器的ip或者IP段,因为只有这些服务器才会去连负载均衡节点去请求数据。

还有,由于从节点只负责读数据,所以从节点授权用户给负载均衡节点时,只用授权select权限即可,不用授权所有权限。这样即使有其他人连上了haproxy,也无法对数据进行删除或修改。

 

==================================================================

最后,配置一下Web应用连接mysql集群,以TP5为例,配置其根目录下/config/database.php。

该Web应用直接部署在负载均衡服务器204.175.124.51上。并在主节点授权一个增删改权限的用户zbpblog给Web应用(即负载均衡节点)


<?php
return [
    // 数据库类型
    'type'            => 'mysql',
    // 服务器地址
    'hostname'        => ["54.22.37.21",'127.0.0.1'],       # TP5默认该参数中第一个节点是主节点,之后的节点是从节点;这里虽然只指定了一个从节点的ip,但其实这个ip是haproxy代理,其实有两个从节点。假如没有使用haproxy进行负载均衡,这里应该写成 ["54.22.37.21",'54.22.37.20','54.22.37.19']
    // 数据库名
    'database'        => 'test',                            # 主节点和从节点的库名都是test
    // 用户名
    'username'        => ['zbpblog',"haproxy"],             # 主节点授权zbpblog给负载均衡节点,从节点授权haproxy用户给负载均衡节点
    // 密码
    'password'        => ['xxxxx',"xxxxx"],
    // 端口
    'hostport'        => ['3306','3300'],                   # 负载均衡节点的haproxy监听的是3300
    // 连接dsn
    'dsn'             => '',
    // 数据库连接参数
    'params'          => [],
    // 数据库编码默认采用utf8
    'charset'         => 'utf8',
    // 数据库表前缀
    'prefix'          => '',
    // 数据库调试模式
    'debug'           => true,
    // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
    'deploy'          => 1,                                 # 表示使用mysql分布式集群
    // 数据库读写是否分离 主从式有效
    'rw_separate'     => true,                              # 表示要读写分离,主库写,从库读
    // 读写分离后 主服务器数量
    'master_num'      => 1,                                 # 表示只有一个主节点,如果master_num>1,则默认前n个节点是主节点,其他为从节点
    // 指定从服务器序号
    'slave_no'        => [1],
    // 自动读取主库数据
    'read_master'     => false,
    // 是否严格检查字段是否存在
    'fields_strict'   => true,
    // 数据集返回类型
    'resultset_type'  => 'array',
    // 自动写入时间戳字段
    'auto_timestamp'  => false,
    // 时间字段取出后的默认时间格式
    'datetime_format' => 'Y-m-d H:i:s',
    // 是否需要进行SQL性能分析
    'sql_explain'     => false,
    // Builder类
    'builder'         => '',
    // Query类
    'query'           => '\\think\\db\\Query',
    // 是否需要断线重连
    'break_reconnect' => false,
    // 断线标识字符串
    'break_match_str' => [],
];

 

免责声明:
1. 本站资源转自互联网,源码资源分享仅供交流学习,下载后切勿用于商业用途,否则开发者追究责任与本站无关!
2. 本站使用「署名 4.0 国际」创作协议,可自由转载、引用,但需署名原版权作者且注明文章出处
3. 未登录无法下载,登录使用金币下载所有资源。
IT小站 » MYSQL教程实战篇:使用haproxy搭建负载均衡集群

常见问题FAQ

没有金币/金币不足 怎么办?
本站已开通每日签到送金币,每日签到赠送五枚金币,金币可累积。
所有资源普通会员都能下载吗?
本站所有资源普通会员都可以下载,需要消耗金币下载的白金会员资源,通过每日签到,即可获取免费金币,金币可累积使用。

发表评论