一 链路追踪介绍

在这里插入图片描述

1.1 skywalking是什么

对于一个大型的几十个、几百个微服务构成的微服务架构系统,通常会遇到下面一些问题,比如:

  1. 如何串联整个调用链路,快速定位问题?
  2. 如何缕清各个微服务之间的依赖关系?
  3. 如何进行各个微服务接口的性能分折?
  4. 如何跟踪整个业务流程的调用处理顺序?

skywalking是一个国产开源框架,2015年由吴晟开源 , 2017年加入Apache孵化器。skywalking是分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。它是一款优秀的APM(Application Performance Management)工具,包括了分布式追踪、性能指标分析、应用和服务依赖分析等。

官网:http://skywalking.apache.org/
下载:http://skywalking.apache.org/downloads/
Github:https://github.com/apache/skywalking
文档:https://skywalking.apache.org/docs/main/v9.0.0/readme/
中文文档: https://skyapm.github.io/document-cn-translation-of-skywalking/

1.2 链路追踪框架对比

  1. Zipkin是Twitter开源的调用链分析工具,目前基于springcloud sleuth得到了广泛的使用,特点是轻量,使用部署简单。
  2. Pinpoint是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能强大,接入端无代码侵入。
  3. SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能较强,接入端
    无代码侵入。目前已加入Apache孵化器。
  4. CAT是大众点评开源的基于编码和配置的调用链分析,应用监控分析,日志采集,监控报警等一系列的监控平台工具。

在这里插入图片描述

1.3 性能对比

模拟了三种并发用户:500,750,1000。使用jmeter测试,每个线程发送30个请求,设置思考时间为10ms。使用的采样率为1,即100%,这边与生产可能有差别。pinpoint默认的采样率为20,即50%,通过设置agent的配置文件改为100%。zipkin默认也是1。组合起来,一共有12种。下面看下汇总表:
在这里插入图片描述
从上表可以看出,在三种链路监控组件中,skywalking的探针对吞吐量的影响最小,zipkin的吞吐量居中。pinpoint的探针对吞吐量的影响较为明显,在500并发用户时,测试服务的吞吐量从1385降低到774,影响很大。然后再看下CPU和memory的影响,在内部服务器进行的压测,对CPU和memory的影响都差不多在10%之内。

1.4 Skywalking主要功能特性

1、多种监控手段,可以通过语言探针和service mesh获得监控的数据;
2、支持多种语言自动探针,包括 Java,.NET Core 和 Node.JS;
3、轻量高效,无需大数据平台和大量的服务器资源;
4、模块化,UI、存储、集群管理都有多种机制可选;
5、支持告警;
6、优秀的可视化解决方案;

二 SkyWalking 环境搭建部署

在这里插入图片描述

  • skywalking agent和业务系统绑定在一起,负责收集各种监控数据
  • Skywalking oapservice是负责处理监控数据的,比如接受skywalking agent的监控数据,并存储在数据库中;接受skywalking webapp的前端请求,从数据库查询数据,并返回数据给前端。Skywalking oapservice通常以集群的形式存在。
  • skywalking webapp,前端界面,用于展示数据。
  • 用于存储监控数据的数据库,比如mysql、elasticsearch等

2.1 下载 SkyWalking

下载:http://skywalking.apache.org/downloads/
在这里插入图片描述

目录结构
在这里插入图片描述
在这里插入图片描述
想改端口的话(默认8080),修改webapp目录下的webapp.yml里的端口即可(这里改为8868)

只要双击启动startUp.bat,则自动就会把oapService.bat与webappService.bat也启动
在这里插入图片描述

2.2 搭建SkyWalking OAP 服务

启动脚本bin/startup.sh
在这里插入图片描述
日志信息存储在logs目录
在这里插入图片描述
启动成功后会启动两个服务,一个是skywalking-oap-server,一个是skywalking-web-ui : 8868

浏览器访问8868端口卡伊查看界面

skywalking-oap-server服务启动后会暴露11800 和 12800 两个端口,分别为收集监控数据的端口11800和接受前端请求的端口12800,修改端口可以修改config/applicaiton.yml
在这里插入图片描述
skywalking-web-ui服务会占用 8080 端口, 修改端口可以修改webapp/webapp.yml
在这里插入图片描述
server.port:SkyWalking UI服务端口,默认是8080;
collector.ribbon.listOfServers:SkyWalking OAP服务地址数组,SkyWalking UI界面的数据是通过请求SkyWalking OAP服务来获得;

访问:http://192.168.3.100:8080/
在这里插入图片描述
页面的右下角可以中英文切换,可以切换选择要展示的时间区间的跟踪数据

2.3 SkyWalking中三个概念

服务(Service) :表示对请求提供相同行为的一系列或一组工作负载,在使用Agent时,可以定义服务的名字;
服务实例(Service Instance) :上述的一组工作负载中的每一个工作负载称为一个实例, 一个服务实例实际就是操作系统上的一个真实进程;
端点(Endpoint) :对于特定服务所接收的请求路径, 如HTTP的URI路径和gRPC服务的类名 + 方法签名;
在这里插入图片描述

三 SkyWalking 接入微服务

3.1 linux环境—通过jar包方式接入

准备一个springboot程序,打成可执行jar包,写一个shell脚本,在启动项目的Shell脚本上,通过 -javaagent 参数进行配置SkyWalking Agent来跟踪微服务;
startup.sh脚本:

#!/bin/sh
# SkyWalking Agent配置
export SW_AGENT_NAME=springboot‐skywalking‐demo #Agent名字,一般使用`spring.application.name`
export SW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800 #配置 Collector 地址。
export SW_AGENT_SPAN_LIMIT=2000 #配置链路的最大Span数量,默认为 300。
export JAVA_AGENT=‐javaagent:/usr/local/soft/apache‐skywalking‐apm‐bin‐es7/agent/skywalking‐agent.jar
java $JAVA_AGENT ‐jar springboot‐skywalking‐demo‐0.0.1‐SNAPSHOT.jar #jar启动

启动日志
在这里插入图片描述
等同于

java ‐javaagent:/usr/local/soft/apache‐skywalking‐apm‐bin‐es7/agent/skywalking‐agent.jar
‐DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
‐DSW_AGENT_NAME=springboot‐skywalking‐demo ‐jar springboot‐skywalking‐demo‐0.0.1‐SNAPSHOT.jar

参数名对应agent/config/agent.config配置文件中的属性。
属性对应的源码:org.apache.skywalking.apm.agent.core.conf.Config.java

# The service name in UI
agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
# Backend service addresses.
collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:127.0.0.1:11800}

我们也可以使用skywalking.+配置文件中的配置名作为系统配置项来进行覆盖。 javaagent参数配置方式优先级更高

测试: 访问你的微服务
在这里插入图片描述

3.2 windos环境—在IDEA中使用Skywalking

在运行的程序配置jvm参数,如下图所示:
在这里插入图片描述

# skywalking‐agent.jar的本地磁盘的路径
‐javaagent:D:\apache\apache‐skywalking‐apm‐es7‐8.4.0\apache‐skywalking‐apm‐bin‐es7\agent\skywalking‐agent.jar
# 在skywalking上显示的服务名
‐DSW_AGENT_NAME=springboot‐skywalking‐demo
# skywalking的collector服务的IP及端口
‐DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.3.100:11800

-DSW_AGENT_COLLECTOR_BACKEND_SERVICES 可以指定远程地址, 但是-javaagent必须绑定你本机物理路径的skywalkingagent.jar

3.3 Skywalking跨多个微服务跟踪

Skywalking跨多个微服务跟踪,只需要每个微服务启动时添加javaagent参数即可。
测试:
启动微服务mall-gateway,mall-order,mall-user ,配置skywalking的jvm参数
http://localhost:8888/user/findOrderByUserId/1
在这里插入图片描述
注意:此处存在bug,跟踪链路不显示gateway

拷贝agent/optional-plugins目录下的gateway插件到agent/plugins目录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

拓补图

四 Skywalking持久化跟踪数据

默认使用的H2数据库存储(H2是内存数据库)
config/application.yml
在这里插入图片描述

4.1 基于mysql持久化

  1. 修改config目录下的application.yml,使用mysql作为持久化存储的仓库(默认h2是内存数据库)
    在这里插入图片描述
  2. 修改mysql连接配置
    在这里插入图片描述
storage:
 #选择使用mysql 默认使用h2,不会持久化,重启skyWalking之前的数据会丢失
 selector: ${SW_STORAGE:mysql}
 #使用mysql作为持久化存储的仓库
mysql:
 properties:
	 #数据库连接地址
	 jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://1ocalhost:3306/swtest"}
	 #用户名
	 dataSource.user: ${SW_DATA_SOURCE_USER:root}
	 #密码
	 dataSource.password: ${SW_DATA_SOURCE_PASSWORD:root}

注意:需要添加mysql数据驱动包,因为在lib目录下是没有mysql数据驱动包的,所以修改完配置启动是会报错,启动失败的。
在这里插入图片描述
3. 添加mysql数据驱动包到oap-libs目录下
在这里插入图片描述
在项目里找一个mysql驱动复制到oap-libs目录
在这里插入图片描述
4. 启动Skywalking
在这里插入图片描述
查看swtest数据库,可以看到生成了很多表
在这里插入图片描述

说明启动成功了,打开配置对应的地址http://192.168.3.100:8080/,可以看到skywalking的web界面。
在这里插入图片描述
测试:重启skywalking,验证跟踪数据会不会丢失

五 自定义SkyWalking链路追踪

如果我们希望对项目中的业务方法,实现链路追踪,方便我们排查问题,可以使用如下的代码
引入依赖

<!‐‐ SkyWalking 工具类,版本与SkyWalking版本一致即可 ‐‐>
<dependency>
	<groupId>org.apache.skywalking</groupId>
	<artifactId>apm‐toolkit‐trace</artifactId>
	<version>8.5.0</version>
</dependency>

5.1 @Trace将方法加入追踪链路

如果一个业务方法想在ui界面的跟踪链路上显示出来,只需要在业务方法上加上@Trace注解即可
在这里插入图片描述
测试:
在这里插入图片描述

5.2 加入@Tags或@Tag

我们还可以为追踪链路增加其他额外的信息,比如记录参数和返回信息。实现方式:在方法上增加@Tag或者@Tags。
@Tag 注解中 key = 方法名 ;

value = returnedObj :记录返回值信息
value = "arg[0]":记录参数信息,0是参数下标位置

@Trace
//returnedObj:记录返回值
@Tag(key = "list", value = "returnedObj")
public List<User> list(){
  return userMapper.list();
}

@Trace
@Tags({@Tag(key = "param", value = "arg[0]"),@Tag(key = "user", value = "returnedObj")})
public User getById(Integer id){
  return userMapper.getById(id);
}

在这里插入图片描述
在这里插入图片描述
性能分析
skywalking的性能分析,在根据服务名称、端点名称,以及相应的规则建立了任务列表后,在调用了此任务列表的端点后,skywalking会自动记录,剖析当前端口,生成剖析结果,具体流程如图:
在这里插入图片描述
在这里插入图片描述
然后访问几次接口,刷新下边的页面
在这里插入图片描述
在这里插入图片描述
可以查看哪行代码比较慢
在这里插入图片描述
在这里插入图片描述

六 Skywalking集成日志框架

logback官方配置

其他日志框架配置

引入依赖

<!‐‐ apm‐toolkit‐logback‐1.x ‐‐>
<dependency>
	<groupId>org.apache.skywalking</groupId>
	<artifactId>apm‐toolkit‐logback‐1.x</artifactId>
	<version>8.5.0</version>
</dependency>

添加logback-spring.xml文件,并配置 %tid 占位符(tid就是追踪链路id,到时候拿着这个id在skywalking浏览器界面直接搜就行)

<?xml version="1.0" encoding="UTF‐8"?>
<configuration>
	<!‐‐ 引入 Spring Boot 默认的 logback XML 配置文件 ‐‐>
	<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
	
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<!‐‐ 日志的格式化 ‐‐>
		<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
			<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
			 	<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
			 </layout>
	 	</encoder>
	
	 </appender>
	
	<!‐‐ 设置 Appender ‐‐>
	<root level="INFO">
	  <appender‐ref ref="console"/>
	</root>

</configuration>

测试
在这里插入图片描述
注意:不支持模糊查询,需要把tid全部复制到浏览器里执行
在这里插入图片描述
Skywalking通过grpc上报日志 (需要v8.4.0+)

即把项目日志放到skywalking的“日志”里
在这里插入图片描述

gRPC报告程序可以将收集到的日志转发到SkyWalking OAP服务器上

logback-spring.xml中添加

<!‐‐ v8.4.0提供 ‐‐>
<appender name="grpc‐log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
	<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
		<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
		 	<Pattern>%d{yyyy‐MM‐dd HH:mm:ss.SSS} [%X{tid}] [%thread] %‐5level %logger{36} ‐%msg%n</Pattern>
		</layout>
	</encoder>
</appender>

<root level="info">
  <appender‐ref ref="grpc‐log" />
</root>

在这里插入图片描述
以后直接在页面查看日志就行,不需要打开服务器看日志了
在这里插入图片描述
在这里插入图片描述
如果skywalking不是部署在本地的话,需要做如下配置,不然上边的日志不生效

打开agent/config/agent.config配置文件,添加如下配置信息:

# skywalking远程服务地址
plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:127.0.0.1}
# skywalking端口
plugin.toolkit.log.grpc.reporter.server_port=${SW_GRPC_LOG_SERVER_PORT:11800}
# 日志最大数量
plugin.toolkit.log.grpc.reporter.max_message_size=${SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760}
# 超时时间
plugin.toolkit.log.grpc.reporter.upstream_timeout=${SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT:30}

以上配置是默认配置信息,agent与oap在本地的可以不配
在这里插入图片描述

Skywalking UI效果
在这里插入图片描述
https://github.com/apache/skywalking-rocketbot-ui/pull/428

七 SkyWalking 告警功能

SkyWalking 告警功能是在6.x版本新增的,其核心由一组规则驱动,这些规则定义在config/alarm-settings.yml文件中。 告警规则的定义分为两部分:

  1. 告警规则:它们定义了应该如何触发度量警报,应该考虑什么条件。
  2. Webhook(网络钩子):定义当警告触发时,哪些服务终端需要被告知

告警规则
SkyWalking 的发行版都会默认提供config/alarm-settings.yml文件,里面预先定义了一些常用的告警规则。如下:

  1. 过去 3 分钟内服务平均响应时间超过 1 秒。
  2. 过去 2 分钟服务成功率低于80%。
  3. 过去 3 分钟内服务响应时间超过 1s 的百分比
  4. 服务实例在过去 2 分钟内平均响应时间超过 1s,并且实例名称与正则表达式匹配。
  5. 过去 2 分钟内端点平均响应时间超过 1 秒。
  6. 过去 2 分钟内数据库访问平均响应时间超过 1 秒。
  7. 过去 2 分钟内端点关系平均响应时间超过 1 秒。

这些预定义的告警规则,打开config/alarm-settings.yml文件即可看到

告警规则配置项的说明:

  • Rule name:规则名称,也是在告警信息中显示的唯一名称。必须以_rule结尾,前缀可自定义
  • Metrics name:度量名称,取值为oal脚本中的度量名,目前只支持long、double和int类型。见Official OAL script
  • Include names:该规则作用于哪些实体名称,比如服务名,终端名(可选,默认为全部)
  • Exclude names:该规则作不用于哪些实体名称,比如服务名,终端名(可选,默认为空)
  • Threshold:阈值
  • OP: 操作符,目前支持 >、<、=
  • Period:多久告警规则需要被核实一下。这是一个时间窗口,与后端部署环境时间相匹配
  • Count:在一个Period窗口中,如果values超过Threshold值(按op),达到Count值,需要发送警报
  • Silence period:在时间N中触发报警后,在TN -> TN + period这个阶段不告警。 默认情况下,它和Period一样,这意味着相同的告警(在同一个Metrics name拥有相同的Id)在同一个Period内只会触发一次
  • message:告警消息

Webhook(网络钩子)
Webhook可以简单理解为是一种Web层面的回调机制,通常由一些事件触发,与代码中的事件回调类似,只不过是Web层面的。由于是Web层面的,所以当事件发生时,回调的不再是代码中的方法或函数,而是服务接口。例如,在告警这个场景,告警就是一个事件。当该事件发生时,SkyWalking就会主动去调用一个配置好的接口,该接口就是所谓的Webhook。
SkyWalking的告警消息会通过 HTTP 请求进行发送,请求方法为 POST,Content-Type 为 application/json,其JSON 数据实基于List<org.apache.skywalking.oap.server.core.alarm.AlarmMessage进行序列化的。JSON数据示例:

[
  {
    "scopeId": 1,
    "scope": "SERVICE",
    "name": "serviceA",
    "id0": "12",
    "id1": "",
    "ruleName": "service_resp_time_rule",
    "alarmMessage": "alarmMessage xxxx",
    "startTime": 1560524171000
  },
  {
    "scopeId": 1,
    "scope": "SERVICE",
    "name": "serviceB",
    "id0": "23",
    "id1": "",
    "ruleName": "service_resp_time_rule",
    "alarmMessage": "alarmMessage yyy",
    "startTime": 1560524171000
  }
]

字段说明:

  • scopeId、scope:所有可用的 Scope 详见 org.apache.skywalking.oap.server.core.source.DefaultScopeDefine
  • name:目标 Scope 的实体名称
  • id0:Scope 实体的 ID
  • id1:保留字段,目前暂未使用
  • ruleName:告警规则名称
  • alarmMessage:告警消息内容
  • startTime:告警时间,格式为时间戳

邮件告警功能实践
根据以上两个小节的介绍,可以得知:SkyWalking是不支持直接向邮箱、短信等服务发送告警信息的,SkyWalking只会在发生告警时将告警信息发送至配置好的Webhook接口。
但我们总不能人工盯着该接口的日志信息来得知服务是否发生了告警,因此我们需要在该接口里实现发送邮件或短信等功能,从而达到个性化的告警通知。
接下来开始动手实践,这里基于Spring Boot进行实现。首先是添加依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring‐boot‐starter‐mail</artifactId>
</dependency>

配置邮箱服务:

server:
 port: 9134

#邮箱配置
 spring:
	 mail:
	 host: smtp.163.com
	 #发送者邮箱账号
	 username: 你的邮箱@163.com
	 #发送者密钥
	 password: 你的邮箱服务密钥
	 default‐encoding: utf‐8
	 port: 465 #端口号465或587
	 protocol: smtp
	 properties:
	 mail:
	 debug:
	 false
	 smtp:
	 socketFactory:
	 class: javax.net.ssl.SSLSocketFactory

根据SkyWalking发送的JSON数据定义一个DTO,用于接口接收数据:

@Data
public class SwAlarmDTO {
	private Integer scopeId;
	private String scope;
	private String name;
	private Integer id0;
	private Integer id1;
	private String ruleName;
	private String alarmMessage;
	private Long startTime;
}

接着定义一个接口,实现接收SkyWalking的告警通知,并将数据发送至邮箱:

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/alarm")
public class SwAlarmController {

   private final JavaMailSender sender;

   @Value("${spring.mail.username}")
   private String from;

	 /**
	 * 接收skywalking服务的告警通知并发送至邮箱
	 */
	 @PostMapping("/receive")
	 public void receive(@RequestBody List<SwAlarmDTO> alarmList) {
		 SimpleMailMessage message = new SimpleMailMessage();
		 // 发送者邮箱
		 message.setFrom(from);
		 // 接收者邮箱
		 message.setTo(from);
		 // 主题
		 message.setSubject("告警邮件");
		 String content = getContent(alarmList);
		 // 邮件内容
		 message.setText(content);
		 sender.send(message);
		 log.info("告警邮件已发送...");
	 }

	 private String getContent(List<SwAlarmDTO> alarmList) {
		 StringBuilder sb = new StringBuilder();
		 for (SwAlarmDTO dto : alarmList) {
			 sb.append("scopeId: ").append(dto.getScopeId())
			 .append("\nscope: ").append(dto.getScope())
			 .append("\n目标 Scope 的实体名称: ").append(dto.getName())
			 .append("\nScope 实体的 ID: ").append(dto.getId0())
			 .append("\nid1: ").append(dto.getId1())
			 .append("\n告警规则名称: ").append(dto.getRuleName())
			 .append("\n告警消息内容: ").append(dto.getAlarmMessage())
			 .append("\n告警时间: ").append(dto.getStartTime())
			 .append("\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐\n\n");
		 }
		
		 return sb.toString();
	 }
 }

最后将该接口配置到SkyWalking中,Webhook(网络钩子)的配置位于config/alarm-settings.yml文件的末尾,格式为http://{ip}:{port}/{uri}。如下示例:

[root@ip‐236‐048 skywalking]# vim config/alarm‐settings.yml
webhooks:
  ‐ http://127.0.0.1:9134/alarm/receive #一旦发生告警就会请求这个接口

在这里插入图片描述

测试告警功能
完成告警接口的开发及配置后,我们来进行一个简单的测试。这里有一条调用链路如下:
在这里插入图片描述
我在/producer接口中增加了一行睡2秒的代码:

@Override
@Trace
@Tag(key="getAll",value="returnedObj")
public List<Order> all() throws InterruptedException {
	TimeUnit.SECONDS.sleep(2);
	return orderMapper.selectAll();
}

http://localhost:8072/order/all
执行完测试代码,等待约两分钟后,告警接口的控制台输出了一段日志信息
在这里插入图片描述

八 Skywalking高可用

在大多数生产环境中,后端应用需要支持高吞吐量并且支持高可用来保证服务的稳定,所以你始终需要在生产环境进行集群管理。

Skywalking集群是将skywalking oap作为一个服务注册到nacos上,只要skywalking oap服务没有全部宕机,保证有一个skywalking oap在运行,就能进行跟踪。

搭建一个skywalking oap集群需要:
(1)至少一个Nacos(也可以是nacos集群)
(2)至少一个ElasticSearch/mysql(也可以是es/msql集群)
(3)至少2个skywalking oap服务;
(4)至少1个UI(UI也可以集群多个,用Nginx代理统一入口)

  1. 修改config/application.yml文件
    使用nacos作为注册中心
    在这里插入图片描述
    修改nacos配置
    在这里插入图片描述
    可以选择性修改监听端口
    在这里插入图片描述
    修改存储策略,使用elasticsearch7作为storage
    在这里插入图片描述
    在这里插入图片描述
  2. 配置ui服务webapp.yml文件的listOfServers,写两个地址
    在这里插入图片描述
    3.启动服务测试
    启动Skywalking服务,指定springboot应用的jvm参数
    ‐DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.3.10:11800,192.168.3.12:11800
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐