Sentinel搭建与使用

 目录

 

一、Sentinel 介绍

二、Sentinel的使用

2.1     Sentinel的组成

2.2     Sentinel控制台

2.2.1   获取控制台

2.2.2   启动控制台

2.2.3   访问控制台

2.3  微服务集成

2.3.1   引入Sentinel依赖

2.3.2   修改应用配置

2.4     限流

2.4.1   基于接口资源限流

2.4.2   基于FeignClient限流

2.5     熔断策略

2.5.1   慢调用比例

2.5.2   异常比例

2.5.3   异常数

2.6     规则持久化

2.6.1   整合nacos做配置持久化存储

2.6.2   Sentinel控制台改造


一、Sentinel 介绍

        Sentinel,中文翻译为哨兵,是为微服务提供流量控制、熔断降级的功能,它和Hystrix提供的功能一样,可以有效的解决微服务调用产生的“雪崩”效应,为微服务系统提供了稳定性的解决方案。随着Hytrxi进入了维护期,不再提供新功能,Sentinel是一个不错的替代方案。通常情况,Hystrix采用线程池对服务的调用进行隔离,Sentinel采用了用户线程对接口进行隔离,二者相比,Hystrxi是服务级别的隔离,Sentinel提供了接口级别的隔离,Sentinel隔离级别更加精细,另外Sentinel直接使用用户线程进行限制,相比Hystrix的线程池隔离,减少了线程切换的开销。另外Sentinel的DashBoard提供了在线更改限流规则的配置,也更加的优化。

 

从官方文档的介绍,Sentinel 具有以下特征:

  • 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
  • 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。

 

官方文档:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub

 

二、Sentinel的使用

2.1     Sentinel的组成

  1. 控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等,控制台依赖于JDK8环境。
  2. 核心库(Java客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对Dubbo/Spring Cloud等框架也有较好的支持。

 

2.2     Sentinel控制台

2.2.1   获取控制台

Sentinel是开源的,我们可以下载官方编译好的jar包直接运行,也可以拉去Sentinel的源码自己编译运行,这里直接下载官方编译好的最新版本jar包sentinel-dashboard-1.8.2.jar。

Sentinel控制台jar包:Releases · alibaba/Sentinel · GitHub

Sentinel控制台源码:GitHub - alibaba/Sentinel: A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)

 

2.2.2   启动控制台

通过如下命令启动控制台,其中通过-Dserver.port=8849指定控制台的端口为8849,如果不指定端口,默认端口为8080

nohup java -server -Xms64m -Xmx256m -Dserver.port=8849 -Dcsp.sentinel.dashboard.server=192.168.138.70:8849 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar &

具体的启动参数介绍:

  • -Dserver.port=8849控制台端口,sentinel控制台是一个spring boot程序。客户端配置文件需要填对应的配置,如:spring.cloud.sentinel.transport.dashboard=127.0.0.1:8849
  • -Dcsp.sentinel.dashboard.server=localhost:8849控制台的地址,指定控制台后客户端会自动向该地址发送心跳包。
  • -Dproject.name=sentinel-dashboard  指定Sentinel控制台程序的名称
  • -Dcsp.sentinel.api.port=8719 可选项,客户端提供给Dashboard访问或者查看Sentinel的运行访问的参数,默认8719

2.2.3   访问控制台

http://127.0.0.1:8849/#/login

默认登陆账户:sentinel / sentinel

 

 

2.3  微服务集成

2.3.1   引入Sentinel依赖

在子工程pom文件引入如下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2.3.2   修改应用配置

在工程的配置文件application.yml文件中,新增2个配置:

  • spring.cloud.sentinel.transport.port: 8719 ,这个端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了1个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。
  • spring.cloud.sentinel.transport.dashboard: 8080,这个是Sentinel DashBoard控制台的地址。
server:
  port: 8085

spring:
  application:
    name: sentinel-demo

  cloud:
    nacos:
      discovery:
        #server-addr: 192.168.230.1:80
        server-addr: 192.168.230.129:8848
        username: nacos
        password: nacos
    sentinel:
      transport:
        #Sentinel与控制台交互的端口
        port: 8719
        #Sentinel DashBoard的地址
        dashboard: 127.0.0.1:8849

2.4     限流

2.4.1   基于接口资源限流

写一个RestController,在接口上加上@SentinelResource注解就可以了。

import com.alibaba.csp.sentinel.annotation.SentinelResource;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

@RestController

public class SentinelController {

    @GetMapping("/hi")

    @SentinelResource(value="hi")

    public String hi(@RequestParam(value = "name",defaultValue = "zmx",required = false)String name){
        return "hi "+name;
    }

}

关于@SentinelResource 注解,有以下的属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry类型,可选项(默认为 EntryType.OUT)
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项
  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。

关于@SentinelResource 注解更多使用可参考官方文档:注解支持 · alibaba/Sentinel Wiki · GitHub

启动Nacos,并启动nacos-provider项目,在浏览器请求几次接口http://127.0.0.1:8085/hi,返回sentinel控制台,可以看到sentinel-demo服务的接口实时监控信息:

 

 

点击簇点链路,可以看到sentinel-demo服务的接口资源信息:

 

 

点击/hi接口对应的流控按钮,在弹出的设置界面选择阈值类型为QPS,单机阈值设置为2,点击新增:

 

 

点击新增按钮后,会自动跳转到流控规则页面,这里展示了我们配置的流量控制规则:

 

 

由于刚刚配置的限流规则QPS为2,也就是限制/hi接口每秒最多能处理2个请求,这时再在浏览器快速按F5刷新请求接口http://127.0.0.1:8085/hi,会发现有时候能正常返回,有时候返回的是Blocked by Sentinel (flow limiting),说明限流起作用了:

正常返回:

 

 

被限流时返回:

 

 

2.4.2   基于FeignClient限流

首先需要在pom文件引入openfeign的依赖:

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

在工程的配置文件application.yml文件中配置,新增配置feign.sentinel.enabled=true,用以开启Feign和sentinel的自动适配:

server:
  port: 8085

spring:
  application:
    name: sentinel-demo
  cloud:
    nacos:
      discovery:
#        server-addr: 192.168.230.1:80
        server-addr: 192.168.230.129:8848
        username: nacos
        password: nacos
    sentinel:
      transport:
        #Sentinel与控制台交互的端口
        port: 8719
        #Sentinel DashBoard的地址
        dashboard: 127.0.0.1:8849

#开启Feign和sentinel的自动适配
feign:
  sentinel:
    enabled: true

写一个FeignClient,调用service-provider服务的/hi接口:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient("service-provider")
public interface ProviderClient {

    @GetMapping("/hi")
    String hi(@RequestParam(value = "name", defaultValue = "zmx", required = false) String name);

}

写一个RestController调用ProviderClient,代码如下:

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.myibc.client.ProviderClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SentinelController {

    @Autowired
    private ProviderClient providerClient;

    /**
     * feign调用限流测试
     *
     * @return
     */
    @GetMapping("/hi-feign")
    public String hiFeign(){

        return providerClient.hi("feign");

    }

}

启动服务可能会报如下错误,这是由于依赖的sentinel的版本与openfeign版本不一致导致的,sentinel默认依赖的版本是2.2.7.RELEASE,而openfeign如果不指定版本,默认依赖的版本是2.2.9.RELEASE

将openfeign和loadbalancer依赖的版本好都改为2.2.7.RELEASE:

 

 

还需要在启动类上加上@EnableFeignClients注解,并指定FeignClient的包扫描路径:

 

 

进行以上修改后,再一次启动服务就可以启动成功了。

 

将service-provider服务也启动起来,通过浏览器调用一次上一步在sentinel-demo服务中写的测试接口http://127.0.0.1:8085/hi-feign,以触发sentinel-demo服务通过feign调用service-provider服务的/hi接口,然后返回sentinel控制台簇点链路页面,如下图,可以看到sentinel为feign调用生成的资源信息GET:http://service-provider/hi,资源名定义规则为httpmethod:protocol://requesturl

 点击feign资源后面的流控按钮,添加流控,QPS设置为2,在浏览器上按F5快速刷新访问http://127.0.0.1:8085/hi-feign,浏览器在正常情况下是能够正常返回如下的信息,但是被限流时会返回错误页面:

 

 

正常返回:

 

被限流时返回:

 

 

如果被限流时不想返回此错误页面,可以在@FeignClient上添加fallback降级回调处理类,代码如下:

@FeignClient(name = "service-provider", fallback = ProviderFallback.class, configuration = FeignConfiguration.class)
public interface ProviderClient {

    @GetMapping("/hi")

    String hi(@RequestParam(value = "name", defaultValue = "zmx", required = false) String name);

}

 

降级回调处理类实现ProviderClient接口,用于处理调用失败时的逻辑:

public class ProviderFallback implements ProviderClient {

    @Override
    public String hi(String name) {
        //返回调用失败的提示
        return "service-provider/hi被限流或降级了";
    }
}

 

重启sentinel-demo服务,返回sentinel控制台发现之前配置的限流规则都不见了,这是因为sentinel默认将限流规则保存到了服务的内存中,因此当服务重启后,限流规则配置也就丢失了,后面会讲到如何将限流规则配置到nacos中,这样服务重启后规则配置就不会丢失了。重新配置限流规则阈值类型为QPS,单机阈值为2,在浏览器快速按F5刷新多次请求http://127.0.0.1:8085/hi-feign接口,会发现当触发限流时,返回的不是错误页面了,而是刚刚在降级回调处理类中返回的错误提示了:

 

 

需要注意的是,被限流的时候FeignClient并不会调用service-provider的接口,而是在sentinel-demo工程里直接报错:

 

 

2.5     熔断策略

写一个controller测试接口,使用Thread.sleep(500)模拟处理业务耗时,这里模拟耗时500毫秒,在@SentinelResource注解上加上BlockException异常处理的方法名,用于当发生熔断时,处理BlockException异常:

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SentinelController {

    /**
     * 熔断测试
     *
     * @return
     */
    @GetMapping("/fusing")
    @SentinelResource(value="fusing", blockHandler = "fusingTestBlockHandle")
    public String fusingTest(@RequestParam(value = "name",defaultValue = "zmx",required = false) String name) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "success" + name;
    }

    /**
     * BlockException异常处理
     * @param name
     * @param e
     * @return
     */
    public String fusingTestBlockHandle(String name, BlockException e) {
        e.printStackTrace();
        String msg = "fusingTest接口被熔断了,请稍后再试,name=" + name;
        System.out.println(msg);
        return msg;
    }

}

 

重新启动服务,在sentinel控制台可以看到此接口资源:

 

 

2.5.1   慢调用比例

点击资源后面对应的熔断按钮,选择熔断策略为慢调用比例,这个熔断策略的逻辑是按接口的平均响应耗时统计来决定是否触发熔断。按下图所示配置好各个参数,点击新增。

 

 

参数说明:

  • 最大RT:即接口的最大响应时间,单位:毫秒,当接口实际响应时间超过该设定的值时,sentinel会认为此次请求是一个慢调用请求
  • 比例阈值:取值范围0.1~1.0,在统计时长区间内,当sentinel统计到的慢调用次数占总的调用次数的比例,达到此阈值时,触发熔断。
  • 熔断时长:单位:秒,首次触发熔断,多长时间后再次尝试请求服务。如果首次熔断后,没有达到设定的熔断时长,再次请求时,不会直接调用服务,而是直接抛出异常,或者走blockHandler指定的异常处理逻辑。如果达到设定的熔断时长之后,再次请求服务时,如果sentinel发现请求耗时还是无法满足最大RT设定值时,会继续熔断,直到请求耗时满足最大RT设定值时恢复正常。
  • 最小请求数:统计时长区间内,需要采集的最小请求样本数,如果统计时长区间内,所有请求都达到了最大RT,但是总的请求数没有达到设定的最小请求数,那么也不会触发熔断
  • 统计时长:单位:毫秒

 

按以上参数配置好熔断规则之后,在浏览器里不停的快速请求http://127.0.0.1:8085/fusing接口,前面的请求虽然比较耗时,响应时间都超过了设定的200毫秒,但是可以正常返回,但是再多请求几次之后,就会返回被熔断的提示,说明熔断规则已经生效。

正常返回:

 

 

被熔断时返回:

 

 

2.5.2   异常比例

 

参数说明:

  • 比例阈值:取值区间0.1~1.0,统计时长区间内,如果发生异常的请求数占总的请求数的比例达到该阈值后,sentinel会触发熔断机制

2.5.3   异常数

 

参数说明:

  • 异常数:统计时长区间内,如果发生异常的请求数达到该阈值时,sentinel触发熔断机制

2.6     规则持久化

2.6.1   整合nacos做配置持久化存储

        通过2.4.2 基于FeignClient限流我们可以知道,当重启服务后,sentinel配置的限流规则会丢失,这是因为sentinel默认将限流规则存储在了服务的内存当中,所以重启服务后,规则配置就丢失了。本地环境测试可以这么使用,但是在生产环境通常需要将规则配置持久化到数据库中,这样服务重启后就不会丢失配置。Setinel支持使用文件、zk、apollo、redis、consul和nacos做为持久化存储,在这里采用nacos来持久化存储规则配置。

首先在sentinel-demo服务的pom文件引入所需依赖:

<!--nacos-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- sentinel整合nacos数据源-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

在application配置文件新增sentinel整合nacos相关的配置:

server:
  port: 8085

spring:
  application:
    name: sentinel-demo

  cloud:
    nacos:
      discovery:
        #server-addr: 192.168.230.1:80
        server-addr: 192.168.230.129:8848
        username: nacos
        password: nacos
      config:
        #server-addr: 192.168.230.1:80
        server-addr: 192.168.230.129:8848
        file-extension: yaml
        prefix: sentinel-demo
        username: nacos
        password: nacos
    sentinel:
      transport:
        #Sentinel与控制台交互的端口
        port: 8719
        #Sentinel DashBoard的地址
        dashboard: 127.0.0.1:8849
      #服务启动后直接与sentinel建立心跳连接
      eager: true
      #Sentinel使用Nacos存储规则
      datasource:
        #限流配置
        flow:
          nacos:
            #nacos的访问地址,这里也可以是nigix代理nacos的集群地址
            server-addr: 192.168.230.129:8848
            #nacos中存储规则的dataId,对于dataId使用了${spring.application.name}变量,这样可以根据应用名来区分不同的规则配置
            data-id: ${spring.application.name}-flow-rules
            #nacos中存储规则的groupId
            group-id: SENTINEL_GROUP
            #对应不同环境的名称空间,如果配置,可以不填
            #namespace:
            #规则存储格式:json
            data-type: json
            #定义存储的规则类型,取值见:org.springframework.cloud.alibaba.sentinel.datasource.RuleType
            rule-type: flow
	#如果有开启了nacos认证,必须要配置用户名密码,否则服务无法从nacos拉取规则
            username: nacos
            password: nacos

#开启Feign和sentinel的自动适配
feign:
  sentinel:
    enabled: true

 

在Nacos控制台,对应的namespace,新建一个/hi接口的限流规则json配置文件,dataId为sentinel-demo-flow-rules,group为SENTINEL_GROUP,如下:

 

可以看到上面配置规则是一个json数组类型,数组中的每个对象是针对每一个保护资源的配置对象,每个对象中的属性解释如下:

  • resource:资源名,即限流规则的作用对象
  • limitApp:流控针对的调用来源,若为default 则不区分调用来源
  • grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
  • count:限流阈值
  • strategy:调用关系限流策略
  • controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
  • clusterMode:是否为集群模式 true-是 false-否

 

启动sentinel-demo 服务,注册到nacos到,打开Sentinel控制台,可以看到在nacos上新建的限流规则,再次重启sentinel-demo服务后,刷新Sentinel控制台,可以看到规则依然存在,说明服务是从nacos中获取的规则配置,如下图:

 

在浏览器快速刷新请求http://127.0.0.1:8085/hi接口,可以发现有时候会返回被限流的提示,说明配置生效了:

 

在完成了上面的整合之后,对于接口流控规则的修改就存在两个地方了:Sentinel控制台、Nacos控制台。这个时候,通过Nacos修改该条规则是可以同步到Sentinel的,但是通过Sentinel控制台修改或新增规则却不可以同步到Nacos。因为当前版本的Sentinel控制台不具备同步修改Nacos配置的能力,而Nacos由于可以通过在客户端中使用Listener来实现自动更新。所以,在整合了Nacos做规则存储之后,需要知道在下面两个地方修改存在不同的效果:

  • Sentinel控制台中新增/修改规则:仅存在于服务的内存中,不会修改Nacos中的配置值,重启后恢复原来的值。
  • Nacos控制台中新增/修改规则:服务的内存中规则会更新,Nacos中持久化规则也会更新,重启后依然保持。

2.6.2   Sentinel控制台改造

通过上一步整合nacos做规则持久化存储后,会发现,虽然服务重启后规则不会丢失了,但是如果在Sentinel控制台新增或修改规则配置,并不会同步到持久化到nacos中,因此需要对Sentinel控制台做进一步的改造,以支持配置的双向同步和持久化。

2.6.2.1      获取源码

首先从官方github仓库拉取Sentinel控制台的源码:

GitHub - alibaba/Sentinel: A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)

 

将源码导入IDEA,目录结构如下,sentinel-dashboard模块就是我们要改造的控制台源码:

 

2.6.2.2      前端页面改造

修改sidebar.html页面路由(sentinel控制台左边菜单栏):

页面路径:sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar.html

找到如下代码:

 

 

修改flowV1为flow,也就是去掉V1,这样的话会前端调用的就是FlowControllerV2中的接口,如下图:

 

修改identity.js:

路径:sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js

找到如下代码,将FlowServiceV1改为FlowServiceV2

 

修改flow_service_v1.js:

路径:sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v1.js

找到如下代码,将以下两个地方的v1改为v2:

 

2.6.2.3      后端代码改造

修改sentinel-dashboard控制台模块的pom.xml,将test注释掉:

在test目录下找到如下目录:

sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos

将整个目录拷贝到源码目录:

sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos

 

 

在控制台的application.properties配置文件中新增以下配置:

# nacos config server
sentinel.nacos.server-addr=192.168.230.129:8848
#sentinel.nacos.data-id=
#sentinel.nacos.namespace=
sentinel.nacos.group-id=SENTINEL-GROUP
sentinel.nacos.username=nacos
sentinel.nacos.password=nacos

在sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos目录下新增一个nacos的配置类:

package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "sentinel.nacos")
public class NacosPropertiesConfiguration {

    private String serverAddr;
    private String dataId;
    private String groupId = "SENTINEL_GROUP"; // 默认分组
    private String namespace;
    private String username;
    private String password;

    //TODO 省略setter/getter方法
}

 

现在可以看到该目录下存在以下几个类:

 

 

修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2.java,注释掉之前默认注入的ruleProvider和rulePublisher两个bean,将从test目录拷贝过来的FlowRuleNacosProvider和FlowRuleNacosPublisher两个类注入进来,如下图,这两个类的作用分别为:

  • FlowRuleNacosProvider:实现Nacos的限流规则配置拉取。
  • FlowRuleNacosPublisher:实现Nacos的配置推送。

 

 

打开 NacosConfig.java,修改如下,主要是nacos配置中心的地址与namespace隔离环境的配置修改,如果没有设置namespace,就可以不设置 PropertyKeyConst.NAMESPACE:

  

 

2.3.2.4      编译打包

经过上面几个步骤的改造,基本已经修改好了,可以打包了:

先执行mvn install sentinel-parent保证依赖包已经install到本地仓库

再执行以下命令,将sentinel-dashboard模块打包成jar包:

mvn clean package -DskipTests -pl sentinel-dashboard

在target目录下找到编译好的jar包:

 

 

通过2.2.2 启动控制台命令运行jar包

2.3.2.5      测试验证

启动成功后,再一次访问sentinel,我们可以看到上一次测试时配置的资源名为/hi的流控规则:

 

查看nacos中也存在这条规则,并且配置参数一致,阈值都为2:

 

 

在sentinel控制台将阈值修改为3后保存:

 

 

返回nacos,刷新页面,可以看到count值也更新为了3,说明同步nacos成功:

 

至此,sentinel的数据持久化改造已经完成。

posted @ 2022-02-16 18:28  AlwaysZmx  阅读(762)  评论(0编辑  收藏  举报