EXCHANGE 2013 队列

 

每当咱在Exchange里查看队列的时候,我们会看到队列分成好几个组,每个邮箱数据库都有自己的目标队列,DAG、AD站点也是,AD林也是一个队列,最后最多的就是外部SMTP域队列。

当传输服务处理队列里的邮件时,它首先会尝试投递队列里的第一封邮件,如果邮件投递成功,传输服务会去告诉传输高可用系统(后边的章节里会介绍到)这封邮件投递成功。如果不成功,那么邮件会留在队列里,然后过段时间进行重试。当重试次数打到某个预设值时,或者是重试的间隔超时,这封邮件就被视为未送达,然后返回发送者一个NDR报告。

Exchange不同版本之间的队列行为各有千秋,但是基本目的都差不多,邮件进入队列直到它们被其他组件检索和处理投递完成。

队列类型:

不同的队列类型对应不同的作用,所以Exchange有多种队列类型也并不奇怪。首先是三种永久性队列

提交队列(Submission Queue):提交队列里保存并组织邮件等待分拣器处理,任何刚刚抵达服务器的邮件都会被放到这个队列里,然后分拣器对此队列中检索邮件,确定收件人的位置和路由,再重定向给相应的合适的其他队列。

无法送达队列(Unreachable Queue):包含着无法路由给目的地的邮件,比如发一封邮件给一个没有MX记录的SMTP域,那么这封邮件就会出现在无法到达队列里,发送给无效收件人的邮件也会出现在这里。Exchange会周期性的重试这个队列里的邮件,当检测到路由更改的时候,也会进行重试。所以这里面的邮件要么就是退回一封NDR,要么就会被成功发送。每台服务器上只有一个无法送达队列,且该队列在平常没有内容的时候是不可见的(EMS和EAC都看不到,除非里头有东西)。

病毒邮件队列(Poison Message Queue):病毒邮件队列也通常为空且不可见,它里边儿保存(隔离)了一些被视为是病毒的邮件(废话),这些邮件通常会导致传输服务崩溃。病毒邮件队列里的邮件不会自动尝试重新提交,管理员需要手动去删除或者恢复它们。

然后是另外三种队列:

传递队列(Delivery Queue):最常用的队列,保存着通过使用SMTP传递到本地或者远程目标的邮件。每个目的地都会有一个传递队列,如果目标服务是传输代理类型的连接器,那么非SMTP目标也会使用传输队列。每个队列中的邮件传递完毕之后,该队列会空置一段时间,然后被自动删除,这个时间可以由Set-TransportService命令里的QueueMaxIdleTime参数来控制,默认为3分钟。

卷影冗余队列(Shadow Redundancy Queues):卷影队列在邮件传输时会保留邮件的冗余副本。

Safety Net:保留传输服务器成功传递的邮件备份。管理工具无法访问Safety Net队列。

关于最后两种负责传输系统冗余的队列,我们在后面传输的高可用里会详细讲到,这里就暂且不提。

队列数据库

在Exchange 2010当中,所有队列信息被存储在一个ESE(Extensible Storage Engine)数据库中,默认位于%ExchangeInstallPath%\TransportRoles\Data\Queue中。使用ESE格式作为队列数据库,而不是面对一堆文件,所以队列数据库(Mail.que)也和邮箱数据库一样,有ESE事务日志文件(Trn*.log),检查点文件(Trn*.chk),以及保留日志文件(Tnres0001.jrs Tnres0002.jrs)。

Exchange 2013的队列数据库与Ex2010和Ex2007的不同之处在于邮件消息是如何存放到队列数据库中的,Ex2007和Ex2010针对每封邮件单独进行操作,邮件量变大意味着大量对数据库里的表的增删操作,而Exchange 2013则将每个小时的邮件聚集到一张表中,比如说在上午9点到10点钟到达的邮件归类到一张表中,10点准的时候,服务器会新建一张表,然后将后续邮件再放进去;如果前面的邮件都发送成功了,就把9点的那张表给删除掉。

EdgeTransport.exe.config文件中可以配置队列数据库的存放位置,你可以通过修改这个来将队列数据库移动到更合适的地方(重启Microsoft Exchange传输服务生效)。如图:

image

下表列出了图中常用几个参数的配置意思,这些参数不是最完全的,可以参照Technet给出的全表(https://technet.microsoft.com/zh-cn/library/bb125022%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396#QueueDBFiles),对config文件里添加需要的项目并赋值以达到配置目的:

参数

含义

QueueDatabasePath

指定队列数据库的默认位置,如果你要修改该位置,那么到目标位置创建好文件夹,然后停止传输服务,将队列数据库拷贝过去,然后重启传输服务即可

QueueDatabaseLoggingPath

指定队列数据库事务日志文件的存放位置,不一定要和队列数据库放在一起

QueueDatabaseLoggingBufferSize

指定在将事务记录写入到事务日志文件之前用于缓存它们的内存。默认为5MB。

QueueDatabaseLoggingFileSize

指定事务日志文件大小。默认为5M

QueueDatabaseOnlineDefragEnabled

是否为队列数据库打开在线碎片整理(Exchange里翻译为联机……)默认情况下这个选项不存在config文件里,如果要关闭该功能则需要手动添加该项。

QueueDatabaseOnlineDefragSchedule

队列数据库在线碎片整理计划任务开始时间,默认为凌晨1点

QueueDatabaseOnlineDefragTimeToRun

指定在线碎片整理任务的最长时间,默认为3个小时。

 

如果手动更改了EdgeTransport.exe.config文件之后,每次安装CU之前都记得备份一下自定义的项目,因为CU和SP的更新里都会覆盖这些配置。其他的config文件也是这个道理。

另外,针对队列数据库的移动,Exchange在Scripts文件夹里提供了一个脚本“Move-TransportDatabae.ps1”,可以直接使用这个脚本来进行移动,需要目标文件夹所在磁盘有2GB的空间。打比方我现在要将队列数据库移动到D:\Exchange\Queues文件夹里,那么我就可以这么输入:

.\Move-TransportDatabae –QueueDatabasePath D:\Exchange\Queues\ –QueueDatabaseLoggingPath D:\Exchange\Queues\
 

 

队列速率

大多数新手管理员都能理解“队列”是某种系统或者组件的性能指标。比如磁盘I/O队列,CPU队列等等。Exchange 2013中邮件队列的运行效率是通过队列的三个属性来计算的。传入率(IncomingRate)和传出率(OutgoingRate)属性代表邮件进入和离开邮件的速率。速率(Velocity)为传出率减去传入率得到的队列消耗速率值。除了以上三个值,还应当考虑邮件数量(MessageCount)属性,该属性显示一个队列中的邮件数量。管理员应该将这些属性综合起来判断一个队列是否正常。

传入率和传出率通过计算最后60秒内每5秒传入/传出邮件的数量的平均值来得出。咱们可以举个例子来帮助理解,下表为过去60秒中,每5秒进入队列和离开队列的邮件数量。

时间

5s

10s

15s

20s

25s

30s

35s

40s

45s

50s

55s

60s

总共

进入队列

12

3

5

7

14

4

1

3

2

7

9

5

72

离开队列

4

8

1

2

6

10

5

7

8

3

4

1

59

 

进入队列的数值取平均值,即72/12为6,离开队列的平均值为59/12为4.9,那么这时的队列速率为4.9-6= -1.1,也就是说队列速率为负值,进入队列的量大于离开队列的值,这就会导致该队列永远不会被清空,就像是一个水池里,放水的速度小于灌水的速度,那么这个水池永远不会空。这个时候再加上队列里的邮件数量属性加以考虑,如果队列里已经堆积了一定数量邮件,但是队列速率趋近于0,那么说明该队列虽繁忙,但是一直是有效的在处理邮件。而如果说一个队列的Velocity为很大的负值,而且MessageCount值又特别大的话,那就说明这个队列的邮件流是有问题的。

查看队列

一般来说,邮件在Exchange中的处理是非常迅速的,如果队列里出现了十几封甚至几十封邮件排队的情况的时候,就说明该当前服务器或者网络处于一个比较高的负载。这时候你可以使用队列查看器或者是Get-Queue命令来查看单独服务器上的队列,也可以使用Get-QueueDigest汇总查看多台服务器上的队列情况。

Get-Queue

下面是一个Get-Queue命令在一台全角色的Exchange服务器上的输出,按MessageCount按升序排列。如果不在命令后面用-Server指定服务器的话,该命令就只返回当前Powershell连接的服务器的队列信息。

image

命令当中-SortOrder后边接的是按那种属性排序,属性前面的“+”号代表以升序排列,如果是“-”号则是以降序排列。还可以使用-Filiter参数来匹配一些过滤条件,比如:

Get-Queues -Server EX01 -Filter {MessageCount -gt 20} -SortOrder: -MessageCount
 

可以注意到,Ex2013的Get-Queue命令会详细告诉你队列速率(上面讲过的Velocity)。这与Ex2010不同,还有其他地方的不同则是等到后面讲邮件路由和传递组的时候会提到。

Exchange中的队列会在没有邮件进出的情况下被关闭,所以你可能在某工作日的高峰期看到队列里出现数据库队列和一堆SMTP目标域队列,也可能在深夜的时候发现其他的队列全消失,只剩下一个提交队列在那。另一种队列清除的情况就是邮件超时,即默认一封邮件如果超过2天没有传递成功,那么就会发送NDR给发件人,至于为啥默认是两天,微软认为超过两天之久的邮件再进行重试发送就没啥意义了,还不如返回给发件人让发件人以另外的方式联系收件人。

为了保证队列的唯一性,传输系统在建立队列的时候会用服务器名加上一个每次增加的数字以区分每个队列,这个数字会在传输服务重启的时候重置掉。

Get-QueueDigest

这条命令前边儿已经提过了,可以将多台服务器上的队列一起汇总显示,算是一个不错的改进。这里多台服务器可以是同一个林中的服务器,或者是同一DAG、同一AD站点的成员,也可以是指定列表中的多台服务器。用法和简单,在后面带上一些参数开关就可以了(-Forest,-DAG后面可以跟多个DAG名字用逗号隔开,-Server逗号隔开多台服务器,-Site),全都是字面意思,而且还有-Filter开关,可以进行过滤筛选。

使用Exchange队列查看器

尽管Exchange 2013的控制中心不再是以前那种基于MMC管理控制台的风格,而变成了网页的EAC,但是也有EAC做不到的事情,比如查看队列内容,Ex2013保留了以前的队列查看器,主要还是从易用性上面考虑,虽然使用起来性能不怎么样,队列查看器的内容每5秒刷新一次。但是时常会看到下边儿写着“X个任务进行中……”

image

队列状态:

每一个队列都会呈现下面几种状态:

活动:正在传输邮件

连接:正在连接下一跃点

就绪:代表该队列的所有邮件都传输成功,目前队列是空的。

重试:代表该队列正在等待重试连接

挂起:管理员手动挂起该队列,但是新邮件仍然会进入队列,但是邮件不会离开队列。

需要注意一下的是,如果是复杂的DAG环境里,那么邮件会被提交到其他Mailbox服务器进行队列而非是在本地服务器进行队列,这是由于Exchange可以将邮件传递给主要传递组里的任意服务器(当然是基于最小cost)。所以有可能你在当前这台服务器上查不到关于该邮件的队列信息,最好还是使用Get-QueueDigest来查看整个DAG里的队列。

 

 

队列优先级

Exchange 2013当中,微软增加了一个“优先队列”的功能,打开它时,被标记为高优先级的邮件会比一般邮件先传送,而被标记为低优先级的邮件则会比一般邮件后传送;开启该功能之后呢,分拣器和路由算法不会受到任何影响,仅仅只是传输服务会在每个投递队列里按照优先级排列邮件。

队列优先级的设置主要通过修改EdgeTransport.exe.config配置文件的键值的方式来进行,首先通过PriorityQueuingEnabled键值设置为True开启该功能,接着按照需要调整下表里的细节选项,调整完成之后需要重启传输服务生效(Restart-Service MSExchangeTransport)

参数

含义

PriorityQueuingEnabled

设置为True开启此MBX上的队列优先级功能,默认为False,则下面所有的选项均不生效。

MaxHighPriorityMessageSize

指定高优先级的邮件大小,超过该邮件大小的邮件将被视为是普通邮件。默认为250KB。输入值时,需要带上KB或者MB作为单位。该值应该小于Set-TransportConfig里的MaxSendMessageSize值。

LowPriorityDelayNotificationTimeout

NormalPriorityDelayNotificationTimeout

HighPriorityDelayNotificationTimeout

根据邮件优先级来指定延迟发送状态通知(DSN)邮件的超时间隔,默认为低:8小时,普通:4小时,高优先级:30分钟。意思就是高优先级的邮件如果因为某些原因被延迟了也应该早点获得延迟状态的通知。值的格式为dd.hh:mm:ss

LowPriorityMessageExpirationTimeout

NormalPriorityMessageExpirationTimeout

HighPriorityMessageExpirationTimeout

指定传输服务尝试发送失败邮件的最长时间,如果超过这个时间,则会给发件人返回NDR。与上面一条类似,高优先级的邮件会获得一个更早的邮件无法送达(NDR)的通知。默认为低:2天,中:2天,高:8小时,值的格式与上一条相同

MaxPerDomainLowPriorityConnections

MaxPerDomainNormalPriorityConnections

MaxPerDomainHighPriorityConnections

这些键可以指定传输服务与任一单个远程域可以建立的最大连接数。使用邮箱服务器上的传递队列和发送连接器建立传出到远程域的连接。默认为低:2,中:15,高:3。这三个键加起来的总值不能超过Set-TransportService上的MaxPerDomainOutboundConnections属性

从上面的描述里可以看到,EdgeTransport.exe.config与Set-TransportService里的参数是有一个相互影响关系的。Set-TrasportService命令里的关于邮件Delay和Expiration的设置,则直接就对应EdgeTransport.exe.config里关于Normal邮件优先级的设置。

禁用排队优先级时,将忽略 EdgeTransport.exe.config 配置文件中的所有排队优先级邮件限制。Set-TransportService cmdlet 中的所有邮件限制适用于经过邮箱服务器上传输服务的所有邮件。

启用排队优先级时,EdgeTransport.exe.config 配置文件中的排队优先级邮件限制将覆盖 Set-TransportService cmdlet 中对应的邮件限制。Set-TransportService cmdlet 中的其他所有邮件限制仍适用于经过邮箱服务器上传输服务的低优先级、普通优先级和高优先级邮件。

另外还有一个就是Set-Mailbox命令里的DowngradeHighPriorityMessagesEnabled参数,默认为False,设置为True的话,从该MBX服务器发送的任何高优先级的邮件将自动降级为普通优先级。

管理队列

在日常操作过程当中,有可能会碰到需要手动干预调整队列的情况。一部分操作可以通过Exchange队列管理器的UI界面来完成,EMS也提供了一些命令来让管理员可以完成更详细的针对队列的操作,接下来咱们一块看看。

查看并管理队列中的邮件

考虑这样一个场景,你突然发现队列中出现邮件堆积,而且堆积的数量并不减少,且当前服务器的负载并不高,你想搞清楚为何这些邮件会堆积在队里里面。于是你使用Get-Queue命令获取堆积的那个队列的信息,如下:

wKioL1XAQwrjl4-CAALIQtNn7ag031.jpg

从这些信息里,可以看到是因为DNS查询失败引起的邮件堆积,这时候你就得查查服务器配置里的DNS解析、域控的DNS公网转发、公网上究竟有没有存在这个SMTP域名等等。

也有的时候会碰到以下这种报错:

"421 4.2.1 Unable to connect. Attempted failover to alternate host, but that did not succeed. Either there are no alternate hosts, or delivery failed to all alternate hosts."

这种报错就代表当前的发送连接器里的源服务器无法连接到NextHopDomain里的下一跳目标,这种情况得先从自身查起,检查自己这边儿的CAS(代理发送连接器的话)或者是MBX能否访问外网,或是能到达目标SMTP域邮件服务器的25端口等。查明自身这边儿没问题,才能有足够底气跟对方联系:“你们邮件服务器收不到咱们的邮件!赶紧看看是不是反压了/数据库挂了/断网了!”

接着可以使用Get-Message命令可以获取这些被卡住的队列中的每一封邮件的信息。

wKioL1XAQxnySyjCAAEzhXLs-qs455.jpg

注意返回的邮件列表中,前面有一个Identity,通过这个Identity还可以继续用get-message命令获取更详细的信息。

wKiom1XAQTChCkkqAAL9Sf7p2Xw061.jpg

与Exchange 2010不同的是,Exchange 2013使用更长的数字来标识队列内的邮件,队列中邮件的Identity里的ID号与该邮件本身的Message-ID属性没关系,MessageID属性将会伴随该邮件一起在公网上移动,哪怕到了目标服务器也不会被更改。

如果你想暂停这封邮件的传递,或者是一组邮件的传递,那么可以使用Suspend-Message命令,带上-identity参数指名要操作的队列中的邮件。

Suspend-Message -Identity ‘EX01\5272\19460496818233’
 

然后可以使用Resume-Message命令来恢复该邮件的传递:

Resume-Message -Identity ‘EX01\5272\19460496818233’
 

Remove-Message命令可以从队列里删除掉该邮件,但是注意有一个参数-WithNDR,即要不要返回发件人一条信息通知一下他,如果咱们这里不要返回NDR,那么该参数就置为$False:

Remove-Message -Identity ‘EX01\5272\19460496818233’-WithNDR $False
 

注意,在使用Remove-Message之前,不需要先暂停掉整个队列。

Exchange 2013还提供了另外的命令以供管理员操作邮件,即Export-Message与AssembleMessage。可以非常简单的选择邮件并且Export该邮件,包括该邮件的各个组成部分,导出为一个文本文档,Export邮件动作并不会将其从队列中移除。需要注意的是,要Export邮件,就必须得先暂停队列,或者是Suspend-Message。导出的邮件可以被检查或者编辑,然后重新放到分拣或者是重播目录里再次发送。这样就有一个后门可以……不,有一个便捷的方式可以先将某台队列卡住的服务器上队列里的邮件弄出来放到另一台可以到达目的地的服务器上进行发送。

Export-Message的用法还是指定邮件的-Identity参数,而且该命令导出的是一个二进制对象,还得配合上一个脚本AssembleMessage.ps1来编译为可以阅读的.eml格式文件,咱们可以用一系列简单的管道调用来完成这一系列操作:

Get-Message -Identity ‘EX01\5272\19460496818233’ | Suspend-Message | Export-Message | AssembleMessage –Path c:\exporttemp\
 

如果导出一个队列里的所有邮件就比较麻烦,因为AssembleMessage脚本一次只能调用一封邮件对象,所以咱们这里得用上一点Code的技巧:

$theMessages = @(Get-Message -Queue ‘EX01\5272’)
$theMessages | foreach {$i++; Export-Message $_.Identity | AssembleMessage -Path ("c:\temp"+$i+ ".eml")}
 

第一行的意思是在指定队列里取出所有的邮件,并且存储为一个数组,第二行语句则是遍历该数组,将数组中的每一个元素(每一封邮件)传递给Export-Message,接着由AssembleMessage处理并导出为文件。

暂停和恢复队列

前面已经提到过,一个队列可以有多种状态:暂停,活动,重试,就绪等等…在有些场景下,管理员当然也会希望针对队列按照自己的要求进行操作,比方临时性的暂停往某个特定的远程域投递邮件,或者是让Exchange立即开始重试投递上一次投递失败的邮件。

这时候就涉及到Resume-Queue、Suspend-Queue、Retry-Queue两条命令了。使用也非常简单,后面带上队列名就可以了,远程SMTP域名也可以,因为队列数据库可以通过这两者来识别你具体要进行操作的队列。

Suspend-Queue EX01\5272
 
Suspend-Queue Contoso.com
 

暂停队列的操作需要Confirm,而恢复或者是重试则不需要确认。而且这三条命令也支持使用-Filter参数来进行筛选批量操作,这样你就可以让服务器上所有处于“重试”状态的队列马上进行重试投递了:

Retry-Queue -Filter {Status -eq “retry”}
 

通过分拣目录提交邮件

分拣目录一般作用是提供给某些应用生成邮件存放在这,然后传输服务从这个目录读取邮件并进行投递。比如某些监控应用的组件探测到某台服务器的内存占用超标,于是它就会生成一封邮件类似下面的格式:

To: xuqi@contoso.com
From: servermonitor@contoso.com
Date: 14 July 2015 04:52AM
Subject: 【紧急】邮件前端Edge Transport内存使用率超过9%
MIME-Version: 1.0
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>
<body><pre>设备名称:邮件前端
设备地址:10.0.0.133
设备类型:windows
告警内容:内存超过9%
</pre></body></html>
 

默认情况下,分拣目录位于%ExchangeInstallPath% \TransportRoles\Pickup\,也可以使用Set-TransportServer命令来修改该路径:

Set-TransportServer –Identity MBX02 –PickupDirectoryPath ‘D:\Exchange\PickupDirectory’
 

基本思路就是,应用程序按照基本SMTP邮件格式创建一封.eml扩展名的文本邮件,然后放到Pickup目录中。每5秒间隔Exchange会检查一次这里(不能修改),然后传输在目录中找到的任何.eml文件。

其他不是以.eml结尾的文件会被忽略掉,Exchange每5秒钟会从分拣目录取走12封邮件进行处理,如果超过12封则剩下的会在下一次5秒进行处理。可以通过Set-TransportServer里的参数进行调整这个邮件量,比方我这里调整成每分钟处理250封,那么每5秒处理250/12=20.8333封邮件。:

Set-TransportServer –PickupDirectoryMaxMessagesPerMinute 250
 

处于分拣目录的.eml文件首先被重命名为.tmp扩展名的文件,然后Exchange会检测该文件是否存在必要的结构以转换成正常的邮件。如果存在同名的.tmp文件,则会重命名为<原始文件名><日期>.tmp的格式,如果再重命名失败,则就生成日志报错,然后处理下一个文件。如果没问题的话,系统将向.tmp文件发出一个delete on close指令,文件虽然显示仍然位于pickup目录,但是无法打开。转换好的邮件成功进入提交队列之后,系统发出close指令,.tmp命令就被移除。如果删除失败,将生成事件日志错误。如果分拣目录中存在 .tmp 文件时重新启动 Microsoft Exchange 传输服务,则所有的 .tmp 文件将重命名为 .eml 文件,并将被重新处理。这可能会导致邮件重复传输。

邮件文件的基本要求如下:

1、 邮件文件必须是符合基本 SMTP 邮件格式的文本文件。支持 MIME 邮件头字段和内容。

2、 邮件文件的文件扩展名必须是 .eml。

3、 在邮件头的 Sender 或 From 邮件头字段中必须至少存在一个电子邮件地址。如果在 Sender 和 From 字段中各存在一个电子邮件地址,则 From 字段中的电子邮件地址将在邮件信封中用作邮件的原始发件人。

4、 Sender 字段中只能存在一个电子邮件地址。不允许存在多个电子邮件地址。如果 From 字段中只存在一个电子邮件地址,则 Sender 字段为可选项。

5、 From 字段中允许存在多个电子邮件地址,但是 Sender 字段中还必须只存在一个电子邮件地址。Sender 字段中的地址随后将在邮件信封中用作邮件的原始发件人。

6、 To、Cc 或 Bcc 字段中必须至少存在一个电子邮件地址。

7、 邮件头和邮件正文之间必须存在一个空行。

8、 邮件头的最大大小为 64 KB,最大收件人数为 100。可以使用 Set-TransportService cmdlet 更改这些限制。这些设置只影响分拣目录。

Exchange将从邮件头中移除Received、Resent-*、Bcc栏的内容,如果只有Bcc栏里有收件人地址,Exchange会使用Undisclosed Recipients来代替To一栏的内容,即收件人不详。如果日期和Message-ID两个字段丢失,Exchange会对其进行修改,使用格式 <GUID>@<defaultdomain> 添加一个 Message-Id 字段与添加分拣目录进行邮件处理的日期和时间。

重播目录

重播目录用于重新提交导出的 Exchange 邮件以及从外部网关服务器接收邮件。类似分拣目录一样,Exchange每5秒钟检查一次重播目录,并且处理在里面发现的邮件信息。这些邮件已针对重播目录进行格式化。管理员不应该手动去增加重播目录中的内容,因为放在这里的邮件内容大量使用X-HEADER字段,非常容易弄错。所以如果要手动提交新邮件,还是使用分拣目录吧。

关于重播目录的更多,请参考:https://technet.microsoft.com/zh-CN/library/bb124230(v=exchg.150).aspx#ReplayReqs

posted on 2018-09-19 13:56  tengq  阅读(1031)  评论(0编辑  收藏  举报

导航