微服务入门技术栈 知识总结笔记(SpringCloud + RabbitMQ + Docker + Redis + 搜索Elasticsearch + 分布式)
·
一、微服务与 SpringCloud
1. 架构演进
- 单体架构:所有功能打成一个包部署。优点:简单、部署成本低;缺点:耦合度高、维护/升级困难。适合小项目。
- 分布式架构:按业务功能拆成独立项目(服务)。优点:降低耦合、易扩展;缺点:调用关系复杂。
- 微服务:一种"良好设计的分布式架构"。四大特征:单一职责(粒度更小)、自治(团队/技术/数据/部署独立)、面向服务(标准接口、跨语言)、隔离性强(隔离+容错+降级)。做到高内聚、低耦合。
微服务不是具体技术,而是一种架构标准;SpringCloud 是它的一站式落地方案。
2. SpringCloud 是什么
- 国内最主流的微服务框架,官网 https://spring.io/projects/spring-cloud。
- 集成了各种微服务功能组件,并基于 SpringBoot 自动装配,开箱即用。
- 常见组件:注册中心、配置中心、远程调用、负载均衡、网关、熔断限流等。
- 版本关系:SpringCloud 依赖 SpringBoot。
二、服务拆分与远程调用
1. 拆分原则
- 不同微服务不要重复开发相同业务。
- 微服务数据独立,不要访问其它微服务的数据库。
- 微服务把自己的业务暴露为接口,供其它服务调用。
2. 远程调用(RestTemplate 原始方式)
// 1. 在启动类注册 RestTemplate
@Bean
public RestTemplate restTemplate(){ return new RestTemplate(); }
// 2. 注入并调用
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
- 服务提供者:被其它微服务调用的服务(提供接口)。
- 服务消费者:调用其它微服务的服务。
- 角色是相对的:A 调 B、B 调 C,则 B 既是消费者也是提供者。
3. 痛点
硬编码 IP+端口、无法负载均衡、无服务健康感知 → 引入注册中心。
三、注册中心:Eureka 与 Nacos
1. 为什么需要注册中心
- 服务有多个实例时,消费者如何知道实例地址?如何选一个?如何感知宕机?
- 注册中心解决:服务注册、服务发现(拉取)、心跳续约、剔除故障实例。
2. Eureka 结构
eureka-server:独立微服务,保存"服务名 → 实例地址列表"映射。eureka-client:每个微服务引入,既注册又发现。- 流程:实例启动注册 → 消费者按服务名拉列表 → Ribbon 负载均衡选一个 → 实例每 30s 发心跳,超时不跳则剔除。
关键配置:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
spring:
application:
name: userservice
3. Nacos(阿里,国内主流)
- 功能比 Eureka 丰富,既是注册中心也是配置中心。
- 引入依赖(注意注释掉 eureka 依赖):
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
spring:
cloud:
nacos:
server-addr: localhost:8848
4. Nacos 核心概念
- 分级存储模型:服务 → 集群(如 HZ 杭州、SH 上海)→ 实例。同集群优先访问,跨机房才访问其它集群。
- 权重配置:控制台改实例权重,权重越大访问越多;权重=0 永不被访问。用于让性能好机器多扛量。
- 环境隔离 namespace:不同 namespace 的服务互相不可见(默认 public)。可通过命名空间 ID 隔离 dev/test/prod。
5. Nacos vs Eureka
| 对比 | Nacos | Eureka |
|---|---|---|
| 服务注册/拉取 | 都支持 | 都支持 |
| 健康检查 | 临时实例心跳;非临时实例主动检测 | 仅心跳 |
| 实例剔除 | 临时实例不正常才剔除;非临时不剔除 | 不正常即剔除 |
| 列表更新 | 支持变更推送,更及时 | 定时拉取 |
| 一致性 | 默认 AP,含非临时实例时 CP | AP |
临时实例:
ephemeral: false设为非临时(永久)实例。
四、负载均衡 Ribbon
- SpringCloud 底层用 Ribbon 实现负载均衡。给 RestTemplate 加
@LoadBalanced即可用服务名代替 IP。 - 原理:
LoadBalancerInterceptor拦截请求 → 从 Eureka 按服务名取实例列表 →IRule选一个 → 替换 URL 发起真实请求。 - 内置策略:
RoundRobinRule(轮询,默认)、RandomRule、WeightedResponseTimeRule、RetryRule等。一般用默认即可。 - 饥饿加载(避免首次慢):
ribbon:
eager-load:
enabled: true
clients: userservice
五、配置管理:Nacos Config
1. 统一配置管理
- 实例多了逐个改配置易错 → Nacos 集中管理,且支持热更新。
- 核心配置、需要热更新的配置才放 Nacos;基本不变的留本地。
- 读取顺序:先读
bootstrap.yaml(在 application.yml 之前),得知 Nacos 地址,再拉取配置合并。
# bootstrap.yaml
spring:
application:
name: userservice
profiles:
active: dev
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml
文件 ID 规则:${spring.application.name}-${profiles.active}.${file-extension} → 例 userservice-dev.yaml。
2. 配置热更新(两种方式)
- 方式一:在
@Value所在类加@RefreshScope。 - 方式二(推荐):用
@ConfigurationProperties(prefix="pattern")代替@Value,自动刷新。
3. 配置共享
userservice.yaml(无环境)可被多环境共享;userservice-dev.yaml仅 dev。- 优先级(高→低):Nacos 当前环境配置 > Nacos 共享配置 > 本地 application.yml > bootstrap。
- 生产务必部署 Nacos 集群(非单机)。
六、声明式 HTTP 调用:Feign
RestTemplate 代码可读性差、URL 难维护 → 用 Feign(声明式 http 客户端)。
1. 基本使用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@EnableFeignClients // 启动类开启
@FeignClient("userservice") // 声明客户端
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
// 调用:userClient.findById(userId) 即可,无需手写 RestTemplate
2. 自定义配置
| 类型 | 作用 |
|---|---|
Logger.Level | 日志级别 NONE/BASIC/HEADERS/FULL |
Decoder/Encoder | 响应解析/请求编码 |
Retryer | 失败重试 |
配置(针对某服务或全局 default):
feign:
client:
config:
userservice:
loggerLevel: FULL
3. 性能优化(★重点)
- Feign 底层默认用
URLConnection(无连接池)。改用 HttpClient/OKHttp 连接池:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
feign:
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
client:
config:
default:
loggerLevel: BASIC
4. 最佳实践(抽取)
- 把 FeignClient、相关 POJO、默认配置抽到独立模块
feign-api,各消费者引入依赖。 - 解决扫描包问题:
@EnableFeignClients(basePackages="cn.itcast.feign.clients")或clients={UserClient.class}。
七、服务网关 Gateway
1. 为什么需要网关
- 所有微服务的统一入口。三大功能:请求路由、权限控制、限流。
- 实现:
Gateway(响应式 WebFlux,性能好)与Zuul(Servlet 阻塞式)。选 Gateway。 - 架构:请求 → 网关 → 按规则路由到某微服务(带负载均衡)→ 返回。
2. 快速入门
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service
uri: lb://userservice # lb=负载均衡,后跟服务名
predicates:
- Path=/user/** # 路径匹配
lb://userservice 表示按服务名从注册中心拉列表并负载均衡。
3. 断言工厂(Predicate)
决定请求是否符合路由条件。常用 Path=/user/**;还有 After/Before/Between、Header、Method、Query、RemoteAddr 等十几种。入门掌握 Path 即可。
4. 过滤器
- 路由过滤器 GatewayFilter(31 种):对请求/响应加工,如
AddRequestHeader、RequestRateLimiter(限流)。 - 配置在路由下只对该路由生效;写在
default-filters下对所有路由生效。
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=Truth, itcast-awesome
5. 全局过滤器 GlobalFilter
- 自己写代码实现拦截逻辑(登录校验、权限、限流)。实现
GlobalFilter接口,加@Order指定顺序(值越小越先执行)。
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
String auth = exchange.getRequest().getQueryParams().getFirst("authorization");
if("admin".equals(auth)) return chain.filter(exchange);
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
}
- 执行顺序:order 值相同则
defaultFilter > 路由过滤器 > GlobalFilter。
6. 跨域(CORS)
浏览器禁止跨域 ajax,网关统一解决:
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
corsConfigurations:
'[/**]':
allowedOrigins: ["http://localhost:8090"]
allowedMethods: ["GET","POST","DELETE","PUT","OPTIONS"]
allowedHeaders: "*"
allowCredentials: true
maxAge: 360000
八、消息队列 RabbitMQ
1. 同步 vs 异步
- 同步(Feign):实时得结果;缺点:耦合高、性能/吞吐下降、级联失败。
- 异步(事件模式):发布者发事件到 Broker(中间人),订阅者各自处理。优点:吞吐提升、故障隔离、解耦、流量削峰;缺点:架构复杂、依赖 Broker 可靠。
2. MQ 选型对比
| MQ | 公司 | 语言 | 可用性 | 单机吞吐 | 延迟 | 可靠性 |
|---|---|---|---|---|---|---|
| RabbitMQ | Rabbit | Erlang | 高 | 一般 | 微秒级 | 高 |
| ActiveMQ | Apache | Java | 一般 | 差 | 毫秒级 | 一般 |
| RocketMQ | 阿里 | Java | 高 | 高 | 毫秒级 | 高 |
| Kafka | Apache | Scala&Java | 高 | 非常高 | 毫秒内 | 一般 |
追求可靠/低延迟选 RabbitMQ;追求吞吐选 RocketMQ/Kafka。
3. RabbitMQ 角色
- publisher(生产者)、consumer(消费者)、exchange(交换机,路由)、queue(队列,存消息)、virtualHost(虚拟主机,租户隔离)。
4. 五种消息模型
- 基本队列(HelloWorld):一生产者一队列一消费者。
- WorkQueue:多个消费者绑同一队列,共同消费("能者多劳"靠
prefetch:1)。 - 发布/订阅(Pub/Sub):引入 exchange,三类交换机:
- Fanout:广播,发给所有绑定队列。
- Direct:按 RoutingKey 精确匹配。
- Topic:RoutingKey 支持通配符(
#多词,*一词)。
- exchange 只转发不存储,无队列绑定则消息丢失。
5. SpringAMQP(企业常用)
- 封装了 RabbitTemplate,自动声明队列/交换机/绑定,注解式监听。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
rabbitmq:
host: 192.168.150.101
port: 5672
virtual-host: /
username: itcast
password: 123321
- 发送:
rabbitTemplate.convertAndSend(queueName, message); - 接收:
@RabbitListener(queues = "simple.queue")
public void listen(String msg){ System.out.println("收到:" + msg); }
- 基于注解声明交换机+队列+绑定(Direct 示例):
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red","blue"}
))
public void listen(String msg){ ... }
6. 消息转换器(★重要)
- 默认 JDK 序列化:体积大、有安全漏洞、可读性差。
- 改用 JSON:引入
jackson依赖,启动类加 Bean:
@Bean
public MessageConverter jsonMessageConverter(){ return new Jackson2JsonMessageConverter(); }
- 生产者和消费者都要配,否则反序列化失败。
九、Docker 容器化部署
1. Docker 解决什么问题
- 依赖冲突:把应用 + Libs + Deps + 配置一起打包,放隔离容器运行。
- 环境差异:镜像含完整运行环境(含系统函数库),仅依赖 Linux 内核,可跨 Linux 系统运行。
- 优势:启动快、体积小、隔离好、一行命令启停。
2. 与虚拟机区别
- 虚拟机:模拟硬件+完整 OS,重、慢。
- Docker:只封装函数库,是系统进程,轻、快。
3. 核心概念
- 镜像(Image):应用及其依赖、环境、配置打包成的只读文件。
- 容器(Container):镜像运行起来形成的进程(隔离)。一个镜像可起多个容器。
- DockerHub / 镜像仓库:托管镜像(如 MySQL、Redis 镜像)。国内有阿里云镜像库。
- 架构:CS 模式,客户端发指令,服务端(守护进程)管镜像/容器。
4. 常用命令
- 镜像:
docker pull nginx、docker images、docker save -o nginx.tar nginx:latest、docker load -i nginx.tar、docker rmi nginx。 - 容器:
docker run --name mn -p 80:80 -d nginx、docker ps、docker ps -a、docker logs -f mn、docker stop/start/rm、docker exec -it mn bash。 - 镜像名格式:
[repository]:[tag],不写 tag 默认latest。
5. 数据卷(容器数据管理)
- 容器与数据耦合难维护 → 用数据卷解耦。
- 数据卷是宿主机目录的虚拟映射,容器内写操作落到宿主机。
docker volume create html
docker run --name mn -v html:/usr/share/nginx/html -p 80:80 -d nginx
- 也可直接挂宿主机目录:
-v /tmp/mysql/data:/var/lib/mysql。 - 数据卷模式:耦合低、目录深;目录直挂:耦合高、目录好找。
6. Dockerfile 自定义镜像
- 文本文件,用指令描述构建过程,每行一层。
- 第一行必须是
FROM(基础镜像,如java:8-alpine)。
FROM java:8-alpine
COPY ./app.jar /tmp/app.jar
EXPOSE 8090
ENTRYPOINT java -jar /tmp/app.jar
- 构建:
docker build -t javaweb:1.0 .
7. Docker-Compose 部署微服务集群
- 用一个 yaml 描述多个容器如何运行,免去手动逐个
docker run。
version: "3.2"
services:
nacos:
image: nacos/nacos-server
environment:
MODE: standalone
ports:
- "8848:8848"
mysql:
image: mysql:5.7.25
environment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- "$PWD/mysql/data:/var/lib/mysql"
userservice:
build: ./user-service
orderservice:
build: ./order-service
gateway:
build: ./gateway
ports:
- "10010:10010"
- 容器间用服务名互联(非 IP)。把微服务配置里的 mysql/nacos 地址改为容器名。
- 打包:
finalName设为app,每个微服务 Dockerfile 用app.jar;部署:docker-compose up -d。
8. 私有镜像仓库
- 推送前先 tag(加仓库地址前缀):
docker tag nginx:latest 192.168.150.101:8080/nginx:1.0
docker push 192.168.150.101:8080/nginx:1.0
docker pull 192.168.150.101:8080/nginx:1.0
十、分布式搜索引擎 Elasticsearch
1. ES 是什么
- 开源分布式搜索引擎,负责存储、搜索、分析。底层基于 Lucene(Java 搜索引擎类库)。
- ELK/Elastic Stack:elasticsearch + kibana(可视化)+ Logstash/Beats(采集)。
- 与 MySQL 分工:MySQL 管事务型写、保一致性;ES 管海量数据搜索/分析。两者结合 + 数据同步。
2. 倒排索引(核心原理)
- 正向索引:按 id 找文档,但按字段模糊查只能全表扫描。
- 倒排索引:文档分词得到词条 → 建"词条→文档id"索引。搜索时先分词,拿词条查倒排索引得文档 id,再取文档。速度极快,无需全表扫。
- 优点:模糊/分词搜索快;缺点:只能按词条建索引,不能按字段排序。
3. ES 概念 vs MySQL
| MySQL | ES | 说明 |
|---|---|---|
| Table | Index | 索引=文档集合(类似表) |
| Row | Document | 文档=一条 JSON 数据 |
| Column | Field | 字段 |
| Schema | Mapping | 映射=字段约束 |
| SQL | DSL | JSON 风格查询语句 |
4. 索引库与文档操作(DSL)
- 创建索引库(含 mapping):
PUT /hotel
{
"mappings": {
"properties": {
"name": { "type": "text", "analyzer": "ik_max_word", "copy_to": "all" },
"brand": { "type": "keyword", "copy_to": "all" },
"price": { "type": "integer" },
"location": { "type": "geo_point" },
"all": { "type": "text", "analyzer": "ik_max_word" }
}
}
}
- mapping 关键属性:
type(text 分词 / keyword 精确 / 数值 / boolean / date / object / geo_point)、index(是否索引)、analyzer、copy_to(合并到 all 字段方便单字段搜)。 - 索引库一旦创建无法改 mapping,只能加新字段:
PUT /索引库/_mapping。 - 文档 CRUD:
- 新增:
POST /索引库/_doc/文档id {json} - 查询:
GET /索引库/_doc/id - 删除:
DELETE /索引库/_doc/id - 全量改:
PUT /索引库/_doc/id {json}(先删后增) - 增量改:
POST /索引库/_update/id {"doc":{字段}}
- 新增:
5. 分词器
- IK 分词器:
ik_smart(粗粒度)、ik_max_word(细粒度)。创建倒排索引和搜索时都会分词。 - 可扩展/停用词条:改
IKAnalyzer.cfg.xml。
6. DSL 查询分类
- 查询所有:
match_all。 - 全文检索:
match(单字段)、multi_match(多字段),走分词+倒排匹配。建议用copy_to+单字段all查询,性能更好。 - 精确查询:
term(词条精确,不分词)、range(范围,如价格)。用于 keyword/数值/日期/布尔。 - 地理查询:
geo_distance(附近 N 公里)、geo_bounding_box(矩形)。 - 复合查询:
bool(must 与/参与算分、should 或、must_not 非/不算分、filter 必须/不算分)、function_score(控制算分排名)。 - 查询 JSON 结构:
query+from/size(分页)+sort(排序)+highlight(高亮)。 - 排序:keyword/数值/日期可排;地理用
_geo_distance。 - 分页:
from+size支持随机翻页但有 10000 上限(深度分页问题);search_after适合向下滚动翻页。 - 高亮:必须配合全文检索查询,给命中词加
<em>标签。
7. RestHighLevelClient(Java 操作 ES)
- 步骤:初始化
RestHighLevelClient→ 建XxxRequest(Index/Get/Update/Delete/Bulk/Search)→ 准备参数(DSL)→ 发请求 → 解析结果。 - 查询解析:从外到内
SearchHits → getTotalHits().value(总数)→ getHits()(文档数组)→ getSourceAsString()反序列化为对象。 - 高亮结果在
hit.getHighlightFields(),需替换原字段。
8. 数据聚合(aggregations)
- 桶(Bucket)聚合:分组,如
terms按品牌分组、date_histogram按日期。 - 度量(Metric)聚合:计算值,如
avg/max/min/stats。可嵌套在桶内对每个组算。 aggs与query同级;query限定聚合范围;size:0只返回聚合结果不含文档。- 参加聚合的字段必须是 keyword/日期/数值/布尔。
9. 自动补全(supplement/suggest)
- 字段类型必须是
completion;用 Completion Suggester 匹配以输入开头的词条。 - 拼音补全:装 pinyin 分词器 + 自定义 analyzer(ik + pinyin filter),搜索时用
ik_smart避免同音字干扰。
10. 数据同步(ES 与 MySQL)
三种方案:
- 同步调用:业务改库后直接调 ES 接口。简单但耦合高。
- 异步通知(推荐入门):改库后发 MQ 消息,ES 端监听并更新。低耦合,依赖 MQ 可靠。
- 监听 binlog(canal):完全解耦,但增加数据库负担、实现复杂。
11. ES 集群(分布式核心)
- 解决:海量存储(分片 shard 拆分到多节点)、单点故障(副本 replica 备份)。
- 概念:cluster(同名节点组)、node、shard(主分片)、replica(副本分片)。
- 节点职责分离:master(选主/管理)、data(数据 CRUD)、coordinator(路由/汇总)。生产建议分离。
- 脑裂:节点失联导致出现多个 master。解决:选票需 > (eligible节点+1)/2,eligible 节点数取奇数(如 3)。ES7 后默认配置,一般不发生。
- 分布式存储:对文档 id 做 hash 决定落哪个分片(故分片数创建后不可改)。
- 分布式查询:scatter phase(分发到各分片)+ gather phase(汇总结果)。
- 故障转移:master 监控节点,宕机则把分片迁移到其它节点保数据安全。
十一、Redis 缓存 (★重要 之前学过)
1. Redis 是什么
- 基于内存的键值(key-value)数据库,单线程模型但极快(十万级 QPS),常用于缓存、分布式锁、会话存储、计数器、消息队列(轻量)。
- 数据持久化:RDB(快照)、AOF(日志);生产常主从+哨兵或 Cluster 集群。
2. 常用数据类型
String:字符串/计数(incr),最常用。Hash:对象属性(用户信息)。List:队列、最新列表。Set/ZSet:去重集合 / 有序集合(排行榜、延迟队列)。
3. Spring Boot 整合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Autowired
private StringRedisTemplate redisTemplate; // 默认已配 JDK 序列化,建议改 JSON
// 常用
redisTemplate.opsForValue().set("key", "val", 30, TimeUnit.MINUTES);
String v = redisTemplate.opsForValue().get("key");
- 缓存注解(Spring Cache):
@EnableCaching+@Cacheable/@CachePut/@CacheEvict,简化缓存读写。
4. 典型缓存问题与应对(重要)
- 缓存穿透:查不存在的数据,绕过缓存打库。→ 缓存空值、布隆过滤器。
- 缓存击穿:热点 key 过期瞬间大量请求打库。→ 互斥锁、逻辑过期。
- 缓存雪崩:大量 key 同时过期/Redis 宕机。→ 过期时间加随机值、Redis 高可用集群。
5. 与微服务关系
- 网关/服务间共享会话、限流计数、热点数据缓存都依赖 Redis。
- 与 ES 定位不同:Redis 是内存 KV(快、容量小),ES 是搜索分析引擎(磁盘、海量)。
十二、分布式入门
- 服务治理:注册中心(Nacos)解决"服务在哪、谁健康";配置中心统一配置+热更新。
- 远程调用:Feign 声明式调用 + 连接池优化;Ribbon 做客户端负载均衡。
- 流量入口:Gateway 统一路由、鉴权、限流、跨域。
- 异步解耦:RabbitMQ 用 Broker 解耦服务,交换机路由消息,JSON 序列化。
- 部署运维:Docker 镜像+容器+数据卷,Compose 一键编排微服务集群。
- 搜索分析:Elasticsearch 倒排索引+DSL+聚合,MySQL 与 ES 通过 MQ/binlog 同步。
- 缓存加速:Redis 扛热点读、共享状态,注意穿透/击穿/雪崩。
- 高可用:Nacos 集群、ES 分片+副本+故障转移、Redis 主从/Cluster,都是"分布式"的基础保障。
微服务入门知识总结
- 懂单体→分布式→微服务概念,跑通 SpringCloud 父子工程。
- 注册中心 Nacos → 配置中心 → Feign → Gateway,搭一个多服务可调用、可路由的小系统。
- 引入 RabbitMQ 做异步解耦(如下单后发消息)。
- 用 Docker + Compose 把整套服务容器化部署。
- 接入 Elasticsearch 做商品/酒店搜索,理解倒排索引与 DSL。
- 用 Redis 做缓存,补上分布式缓存与高可用认知。
重点记住:微服务核心是"拆得开、找得到、调得动、挂不了"。
Nacos 管注册配置,Feign/Gateway 管调用入口,MQ 管异步,Docker 管部署,ES 管搜索,Redis 管缓存。
更多推荐

所有评论(0)