您的位置:首页 > 其它

【博客大赛】由浅入深,一篇文章带你读懂Dubbo

2021-04-16 00:22 106 查看

一、基础知识

1、分布式基础理论

1.1)什么是分布式系统

《分布式系统原理与泛型》定义:

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统(比如京东商城等)

分布式系统(distributed system)是建立在网络之上的软件系统

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不乱的演进。

1.2)、发展演变

  • 单一应用架构

当网站流量很小是,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本,此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

单一应用架构有易于开发、易于测试、易于部署的优点,但是也有很明显的缺陷,那就是不易扩展、不易于协同开发,后期维护等缺陷

  • 垂直应用架构

当随着应用规模不断扩大,单体应用放在一个服务器上,那么单台服务器的压力就扛不住了,这个时候就需要垂直应用架构。就是将单体应用架构拆分成一个个小应用,而且每一个应用都是完整的,当某一块应用访问量比较大时,就可以多放几台服务器上。

优点是分工合作容易,每个人只负责自己的模块,互不干扰,性能扩展容易等。缺点是界面和业务逻辑不能分离,不能完全理想的独立等缺点。

  • 分布式服务架构

  • 流动计算架构

1.3)ROC

  • 什么叫RPC

RPC[Remote Procedure Call]是指远程过程调用,是一种进程间通信方式。他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

RPC的核心就是起到一个网络连接的作用,将俩个服务器进行通信

RPC框架有很多,如:dubbo、gRPC、Thrift、HSF等

2、dubbo核心概念及准备

  • 2.1)简介

    Apache Dubbo(incubating)是一款高性能、轻量级的开源javaRPC框架、它提供了三大核心能力:面向接口的远程调用,智能容错和负载均衡,以及服务自动注册和发展

  • 2.2)dubbo的前世今生

    Dubbo为阿里巴巴公司开发一款高性能Java RPC框架

    其于2011年将其开源到GitHub上,但是在2014年10年月份,发布其Dubbo2.4.11版本后,就停止更新了

    有很多公司在使用Dubbo,比较出名的有当当网在基于Dubbo的基础上开发了自身的RPC框架DubboX;网易考拉也基于Dubbo开发了其自身的RPC框架DubboK

    2017年SpringCloud开始发力抢占分布式市场,阿里巴巴见此状,又开始更新Dubbo

    2018年1月阿里巴巴将DubboX与Dubbo合并,发布Dubbo2.6

    2018年2月15号,阿里巴巴将Dubbo贡献给Apache

  • 2.3)dubbo的设计结构

  • 2.4)Zookeeper注册中心

    在windows上安Zookeeper

      下载Zookeeper.使用的版本为zookeeper-3.4.11.tar.gz

    1. 解压缩zookeeper-3.4.11.tar.gz

    2. 进入到zookeeper-3.4.11\conf目录, 复制zoo_sample.cfg, 并重新命名我zoo.cfg

    3. 进入到zookeeper-3.4.11新建目录data

    4. 修改zoo.cfg配置文件中dataDir配置项

    dataDir=../data

    1. 在cmd窗口中执行, 启动zk

​ E:\zookeeper-3.4.11\bin>zkServer.cmd

​ 7.新开cmd窗口, 启动zk客户端验证是否正常

​ E:\zookeeper-3.4.11\bin>zkCli.cmd

​ 8.列出节点, 创建新节点, 查询特定节点的值

[zk: localhost:2181(CONNECTED) 2] ls /   列出节点
[zookeeper]
[zk: localhost:2181(CONNECTED) 3] create -e /atguigu 123456   创建节点
Created /atguigu
[zk: localhost:2181(CONNECTED) 4] ls /
[zookeeper, atguigu]
[zk: localhost:2181(CONNECTED) 5] get /atguigu  查询特定节点的值
123456
cZxid = 0x2
ctime = Sun Aug 11 15:24:52 CST 2019
mZxid = 0x2
mtime = Sun Aug 11 15:24:52 CST 2019
pZxid = 0x2
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x100c7723f2b0000
dataLength = 6
numChildren = 0
[zk: localhost:2181(CONNECTED) 6]
  • 2.5)环境搭建

从https://github.com/locationbai/incubator-dubbo-ops-master上clone下源码,然后进入到incubator-dubbo-ops-master-master\dubbo-admin目录中,修改

incubator-dubbo-ops-master-master\dubbo-admin\src\main\resources目录下的application.properties文件内容(主要是Zookeeper的地址要正确)

server.port=7001
spring.velocity.cache=false
spring.velocity.charset=UTF-8
spring.velocity.layout-url=/templates/default.vm
spring.messages.fallback-to-system-locale=false
spring.messages.basename=i18n/message
spring.root.password=root
spring.guest.password=guest

dubbo.registry.address=zookeeper://127.0.0.1:2181

从源码构建出admin的jar包

mvn clean package

然后通过java命令运行build出的jar包

java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

然后通过浏览器访问

http://localhost:7001

用账户名称root,密码为root进行登录

3、dubbo实现

  • 3.1)创建提供消费者工程

搭建gmall-interface项目用于存放公共的bean和接口

搭建order-service-consumer作为服务消费者

搭建user-service-provider作为服务提供者

  • 3.2)服务提供者配置

provider.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schem
8000
a/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<!--当前项目在整个分布式架构里面的唯一名称,计算依赖关系的标签-->
<dubbo:application name="provider"></dubbo:application>

<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!--当前服务发布所依赖的协议;webserovice、Thrift、Hessain、http-->
<dubbo:protocol name="dubbo" port="20880"/>

<!--服务发布的配置,需要暴露的服务接口-->
<dubbo:service interface="com.yueyue.service.UserService" ref="providerService"/>

<!--Bean bean定义-->
<bean id="providerService" class="com.yueyue.service.impl.UserServiceImpl"/>

</beans>

启动类MainApplication:

public static void main(String[] args) throws IOException {
//加载xml配置文件启动
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
System.in.read(); // 按任意键退出
}

pom.xml进行架包的依赖及管理

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>user-service-provider</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.5</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.32.Final</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>

这里注意看下结构,不要放乱了。

  • 3.3)服务消费者配置

consimer.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd ">

<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="order-service-consumer"  />

<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="UserService" interface="com.yueyue.service.UserService" />
</beans>

主启动方法:

public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = context.getBean(OrderService.class);
orderService.initOrder("1");
System.in.read();
}

pom.xml架包依赖及管理

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>order-service-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.5</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.32.Final</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>

  • 3.4)抽取公共bean和接口

    OrderService接口:

public interface OrderService {
void initOrder(String userId);
}

UserService接口:

public interface UserService {
List<UserAddress> getUserAddressList(String userId);
}

4、dubbo与springboot整合

当服务提供者和服务消费者均基于SpringBoot来做开发的话,其配置形式与上面普通的基于Spring的项目来说不同之处在于:

  1. 导入Dubbo基于SpringBoot的starter
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
  1. 在SpringBoot中通过application.properties配置文件代替Spring项目中的xml配置文件
服务提供者配置示例如下
dubbo.application.name=user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

dubbo.protocol.name=dubbo
dubbo.protocol.port=20881

dubbo.monitor.protocol=registry
服务消费者配置示例如下
server.port=8081

dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.monitor.protocol=registry
  1. 服务提供者通过Dubbo中的@Service注解进行体现
@Service//暴露服务
@Component
public class UserServiceImpl implements UserService
  1. 服务消费者引用服务提供者的需要用@Reference体现
@Service
public class OrderServiceImpl implements OrderService {

//@Autowired
@Reference
UserService userService;

5、dubbo配置

  • 5.1)配置dubbo.properties&属性加载顺序

相同的配置如果出现在三个位置,优先级从上往下逐渐降低(最上面的覆盖最下边的)

  1. 如果项目是基于Spring来做的开发,那么优先级顺序为:

    -D > dubbo.xml > dubbo.properties

  2. 如果项目是基于SpringBoot来做的开发,那么优先级为:

    -D > application.properties > dubbo.properties

  • 5.2)配置启动检查

在默认的情况下,当服务调用者项目启动的时候会自动检查要调用的服务是否正常,如果被调用的服务不正常的话,其自己就会启动失败。为了让项目能正常启动,而不去检查所依赖的服务是否正常,可以关闭服务检查。下面的配置片段就是在服务消费者consumer.xml中关闭了服务检查

<context:component-scan base-package="com.atguigu.gmall.service.impl"></context:component-scan>

<dubbo:application name="order-service-consumer"></dubbo:application>

<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>

<!--  配置本地存根-->

<!--声明需要调用的远程服务的接口;生成远程服务代理  -->
<!--
1)、精确优先 (方法级优先,接口级次之,全局配置再次之)
2)、消费者设置优先(如果级别一样,则消费方优先,提供方次之)
-->
<!-- timeout="0" 默认是1000ms-->
<!-- retries="":重试次数,不包含第一次调用,0代表不重试-->
<!-- 幂等(设置重试次数)【查询、删除、修改】、非幂等(不能设置重试次数)【新增】 -->
<dubbo:reference interface="com.atguigu.gmall.service.UserService"
id="userService" timeout="5000" retries="3" version="*">
<!-- <dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method> -->
</dubbo:reference>

<!-- 配置当前消费者的统一规则:所有的服务都不检查 -->
<dubbo:consumer check="false" timeout="5000"></dubbo:consumer>

<!--<dubbo:monitor protocol="registry"></dubbo:monitor>-->
<dubbo:monitor address="127.0.0.1:7070"></dubbo:monitor>
  • 5.3)配置超时&配置覆盖关系

当服务消费者调用服务提供者服务的时候,会存在超时的情况;而Dubbo提供为服务消费者设置调用服务提供者服务的超时时间,也提供了服务提供者为其自身提供的服务设置超时时间;

对于服务提供者和服务消费者Dubbo均提供了以下四种粒度的设置

​ 1 .方法级别

  1. 接口级别

  2. 服务消费者级别

  3. 服务提供者级别

  • 5.4)配置_重试次数
<!-- timeout="0" 默认是1000ms-->
<!-- retries="":重试次数,不包含第一次调用,0代表不重试-->
<!-- 幂等(设置重试次数)【查询、删除、修改】、非幂等(不能设置重试次数)【新增】 -->
<dubbo:reference interface="com.atguigu.gmall.service.UserService"
<!-- 调用userService失败后会重试3次,加上第一次的调用,若调用一直失败的话,总共会调用4次-->
id="userService" timeout="5000" retries="3" version="*">
<!-- <dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method> -->
</dubbo:reference>
  • 5.5)配置_多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移(通过视频的演示发现,Dubbo对于多版本的支持的易用性上做得非常好)

  1. 在低压力时间段,先升级一半提供者为新版本

  2. 再将所有消费者升级为新版本

  3. 然后将剩下的一半提供者升级为新版本
老版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="1.0.0" />

新版本服务提供者配置:
<dubbo:service interface="com.foo.BarService" version="2.0.0" />

老版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />

新版本服务消费者配置:
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />

如果不需要区分版本,可以按照以下的方式配置 [1]:
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
  • 5.6)配置_本地存根

通常情况才采用远程服务调用后,对于服务消费者的请求,均是在服务提供者的远程机器上执行的!通过采用本地存根的形式就可以将服务消费者请求的一些验证操作在其本地进行执行,这样就减少了网络的传输,提高了执行效率

  • 5.7)与springboot的三种整合方式

第一种方式:

注解的方式:在java类上使用Dubbo提供的注解

第二种方式:

配置文件方式:在xml配置文件中对Dubbo进行配置,包括服务应用的名称配置,注册中心配置,通信规则,服务提供者信息,服务消费者信息等

第三种方式:

Java Config方法:通过java配置类(在一个java类上使用@Configuration注解)的方式配置注册中心,通信规则,服务提供者信息,服务消费者信息等

6、高可用

6.1)zookeeper宕机与dubbo直连

采用Dubbo进行微服务的架构,若使用Zookeeper作为注册中心的话,当注册中心宕机时,是不影响服务调用的,因为关于服务调用的信息,在服务消费者和服务提供者本地有缓存

Dubbo也提供了直连的方式,使服务调用的双方直接进行通信,从而可以绕过注册中心

6.2)负载均衡机制

负载均衡策略

Random LoadBalance

  • 随机,按权重设置随机概率
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

  • 轮询,按公约后的权重设置权重轮询比率。
  • 存在慢的提供者积累请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃的随机,活跃数指调用后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

  • 一致性Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其他提供者,不会引起剧烈变动。

6.3)服务降级

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方法处理,从而释放服务器资源以保证核心交易正常运转或高效运转就是服务降级

  • mock=force:return+null 表示消费方对该服务的方法调用直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响
  • 还可以改为mock=fall:return+null 表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

6.4)服务容错

Dubbo有自己的容错机制,如下:

Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的操作,比如新增记录。

Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

Faiback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks=2来设置最大并行数

Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

如果是采用Dubbo微服务框架的话,可以直接使用Dubbo提供的上面的容错功能

如果项目采用了Dubbo作为微服务框架时,在项目中也可以使用Hystrix

7、Dubbo原理

7.1)RPC原理

一次完整的RPC调用流程(同步调用,异步另说)如下:
1)服务消费方(client)调用以本地调用方法调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3)client stub找到服务地址,并将消息发送到服务端;
4)server stub收到消息后解码;
5)server stub根据解码结果调用本地的服务;
6)本地服务执行并将结果返回给server stub
7)server stub将返回结果打包成消息并发送至消费方
8)client stub接收到消息,并进行解码
9)服务消费方得到最终结果
RPC框架的目标就是要2-8这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。

7.2)nrtty通信原理

7.3)Dubbo框架整体设计

7.4)标签解析

7.5)服务暴露流程

简单来说Dubbo进行服务暴露的流程如下:

  1. 当项目启动的时候Dubbo首先通过DubboBeanDefinitionParser来解析xml配置文件(在本例中为provider.xml)。解析信息就是xml配置的xml元素,例如服务提供者实现的接口信息,注册中心信息等

  2. 当解析完xml配置文件后,有两种信息尤为关键,一个是ServiceBean,一个是ServiceConfig

  3. 通过ServiceBean提供的信息(服务提供者相关的一些信息,服务的访问地址等)Dubbo在底层会启动Netty服务器从而监听在此ServiceBean中的服务配置的地址和端口上

  4. 在启动Netty服务器的同时,Dubbo会根据ServiceConfig信息将服务提供者的信息注册到配置中心(Zookeeper中)

  5. 最终根据服务提供者的信息Dubbo不仅启动了Netty服务器,也将该服务提供者的信息注册进了配置中心(Zookeeper),也在本地的providerInvokers中保存了服务提供者的信息

7.6)服务引用流程

下图为Dubbo服务消费者调用远程服务的流程。大体上与Dubbo进行服务暴露的流程是相似的

7.7)服务调用流程

服务消费者调用服务提供者的流程简单来说就是,服务消费者通过配置中心获取到服务提供者相关的信息后,首先进行一些Filter(下图中的local,mock,cache判断。例如cache服务消费者是否启用了缓存功能)。

然后进行服务调用(通常底层是通过客户端的方式与服务器端进行通信。常用的客户端通过下图可以看出有Netty,mina等)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: