文档章节

搭建高可用MongoDB集群(分片)

l
 linjin200
发布于 09/17 09:19
字数 3059
阅读 10
收藏 0

搭建高可用MongoDB集群(分片)

MongoDB基础请参考:https://blog.51cto.com/kaliarch/2044423

MongoDB(replica set)请参考:https://blog.51cto.com/kaliarch/2044618

一、概述

1.1 背景

为解决mongodb在replica set每个从节点上面的数据库均是对数据库的全量拷贝,从节点压力在高并发大数据量的场景下存在很大挑战,同时考虑到后期mongodb集群的在数据压力巨大时的扩展性,应对海量数据引出了分片机制。

1.2 分片概念

分片是将数据库进行拆分,将其分散在不同的机器上的过程,无需功能强大的服务器就可以存储更多的数据,处理更大的负载,在总数据中,将集合切成小块,将这些块分散到若干片中,每个片只负载总数据的一部分,通过一个知道数据与片对应关系的组件mongos的路由进程进行操作。

1.3 基础组件

其利用到了四个组件:mongos,config server,shard,replica set

mongos:数据库集群请求的入口,所有请求需要经过mongos进行协调,无需在应用层面利用程序来进行路由选择,mongos其自身是一个请求分发中心,负责将外部的请求分发到对应的shard服务器上,mongos作为统一的请求入口,为防止mongos单节点故障,一般需要对其做HA。

config server:配置服务器,存储所有数据库元数据(分片,路由)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是存缓存在内存中来读取数据,mongos在第一次启动或后期重启时候,就会从config server中加载配置信息,如果配置服务器信息发生更新会通知所有的mongos来更新自己的状态,从而保证准确的请求路由,生产环境中通常也需要多个config server,防止配置文件存在单节点丢失问题。

shard:在传统意义上来讲,如果存在海量数据,单台服务器存储1T压力非常大,无论考虑数据库的硬盘,网络IO,又有CPU,内存的瓶颈,如果多台进行分摊1T的数据,到每台上就是可估量的较小数据,在mongodb集群只要设置好分片规则,通过mongos操作数据库,就可以自动把对应的操作请求转发到对应的后端分片服务器上。

replica set:在总体mongodb集群架构中,对应的分片节点,如果单台机器下线,对应整个集群的数据就会出现部分缺失,这是不能发生的,因此对于shard节点需要replica set来保证数据的可靠性,生产环境通常为2个副本+1个仲裁。

1.4 架构图

图片.png

二、安装部署

2.1 基础环境

 

为了节省服务器,采用多实例配置,三个mongos,三个config server,单个服务器上面运行不通角色的shard(为了后期数据分片均匀,将三台shard在各个服务器上充当不同的角色。),在一个节点内采用replica set保证高可用,对应主机与端口信息如下:

主机名 IP地址 组件mongos 组件config server shard

 

 

mongodb-1

 

 

172.20.6.10

 

 

  端口:20000

 

 

端口:21000

主节点:   22001
副本节点:22002
仲裁节点:22003

 

mongodb-2

 

172.20.6.11

 

  端口:20000

 

端口:21000

仲裁节点:22001
主节点:   22002
副本节点:22003

 

mongodb-3

 

172.20.6.12

 

  端口:20000

 

端口:21000

副本节点:22001
仲裁节点:22002
主节点:   22003

图片.png

2.2、安装部署

2.2.1 软件下载目录创建

wget -c https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.4.10.tgz
tar -zxvf mongodb-linux-x86_64-rhel62-3.4.10.tgz
ln -sv mongodb-linux-x86_64-rhel62-3.4.10 mongodb
echo "PATH=$PAHT:/usr/local/mongodb/bin">/etc/profile.d/mongodb.sh
source /etc/profile.d/mongodb.sh
 

2.2.2 创建目录

分别在mongodb-1/mongodb-2/mongodb-3创建目录及日志文件

mkdir -p /data/mongodb/mongos/{log,conf}
mkdir -p /data/mongodb/mongoconf/{data,log,conf}
mkdir -p /data/mongodb/shard1/{data,log,conf}
mkdir -p /data/mongodb/shard2/{data,log,conf}
mkdir -p /data/mongodb/shard3/{data,log,conf}
touch /data/mongodb/mongos/log/mongos.log
touch /data/mongodb/mongoconf/log/mongoconf.log
touch /data/mongodb/shard1/log/shard1.log
touch /data/mongodb/shard2/log/shard2.log
touch /data/mongodb/shard3/log/shard3.log
 

2.2.3 配置config server副本集

在mongodb3.4版本后要求配置服务器也创建为副本集,在此副本集名称:replconf

在三台服务器上配置config server副本集配置文件,并启动服务

cat>/data/mongodb/mongoconf/conf/mongoconf.conf<<EOFdbpath=/data/mongodb/mongoconf/data
logpath=/data/mongodb/mongoconf/log/mongoconf.log        #定义config server日志文件
logappend=true
port = 21000
maxConns = 1000            #链接数
journal = true            #日志开启
journalCommitInterval = 200
fork = true            #后台执行
syncdelay = 60
oplogSize = 1000
configsvr = true        #配置服务器
replSet=replconf        #config server配置集replconf
EOF

mongod -f /data/mongodb/mongoconf/conf/mongoconf.conf        #三台服务器均启动config server
 

图片.png

任意登录一台服务器进行配置服务器副本集初始化

use admin                
config = {_id:"replconf",members:[             
{_id:0,host:"172.20.6.10:21000"},
{_id:1,host:"172.20.6.11:21000"},
{_id:2,host:"172.20.6.12:21000"},]
}
rs.initiate(config);
 

图片.png

查看集群状态:

replconf:OTHER> rs.status()
{
    "set" : "replconf",
    "date" : ISODate("2017-12-04T07:42:09.054Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "configsvr" : true,
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1512373328, 1),
            "t" : NumberLong(1)
        },
        "readConcernMajorityOpTime" : {
            "ts" : Timestamp(1512373328, 1),
            "t" : NumberLong(1)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1512373328, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1512373328, 1),
            "t" : NumberLong(1)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "172.20.6.10:21000",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 221,
            "optime" : {
                "ts" : Timestamp(1512373328, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T07:42:08Z"),
            "infoMessage" : "could not find member to sync from",
            "electionTime" : Timestamp(1512373296, 1),
            "electionDate" : ISODate("2017-12-04T07:41:36Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "172.20.6.11:21000",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 42,
            "optime" : {
                "ts" : Timestamp(1512373318, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1512373318, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T07:41:58Z"),
            "optimeDurableDate" : ISODate("2017-12-04T07:41:58Z"),
            "lastHeartbeat" : ISODate("2017-12-04T07:42:08.637Z"),
            "lastHeartbeatRecv" : ISODate("2017-12-04T07:42:07.648Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.10:21000",
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "172.20.6.12:21000",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 42,
            "optime" : {
                "ts" : Timestamp(1512373318, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1512373318, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T07:41:58Z"),
            "optimeDurableDate" : ISODate("2017-12-04T07:41:58Z"),
            "lastHeartbeat" : ISODate("2017-12-04T07:42:08.637Z"),
            "lastHeartbeatRecv" : ISODate("2017-12-04T07:42:07.642Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.10:21000",
            "configVersion" : 1
        }
    ],
    "ok" : 1
}
 

此时config server集群已经配置完成,mongodb-1为primary,mongdb-2/mongodb-3为secondary

2.2.4 配置shard集群

三台服务器均进行shard集群配置

shard1配置

cat >/data/mongodb/shard1/conf/shard.conf <<EOF
dbpath=/data/mongodb/shard1/data
logpath = /data/mongodb/shard1/log/shard1.log
port = 22001
logappend = true
nohttpinterface = true
fork = true
oplogSize = 4096
journal = true
#engine = wiredTiger
#cacheSizeGB = 38G
smallfiles=true
shardsvr=true        #shard服务器
replSet=shard1        #副本集名称shard1
EOF

mongod -f /data/mongodb/shard1/conf/shard.conf        #启动shard服务
 

图片.png

查看此时服务已经正常启动,shard1的22001端口已经正常监听,接下来登录mongodb-1服务器进行shard1副本集初始化

mongo 172.20.6.10:22001
use admin                
config = {_id:"shard1",members:[             
{_id:0,host:"172.20.6.10:22001"},
{_id:1,host:"172.20.6.11:22001",arbiterOnly:true},
{_id:2,host:"172.20.6.12:22001"},]
}
rs.initiate(config);
 

图片.png

查看集群状态,只列出了部分信息:

{
            "_id" : 0,
            "name" : "172.20.6.10:22001",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",            #mongodb-1为primary
            "uptime" : 276,
            "optime" : {
                "ts" : Timestamp(1512373911, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T07:51:51Z"),
            "infoMessage" : "could not find member to sync from",
            "electionTime" : Timestamp(1512373879, 1),
            "electionDate" : ISODate("2017-12-04T07:51:19Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "172.20.6.11:22001",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",            #mongodb-2为arbiter
            "uptime" : 45,
            "lastHeartbeat" : ISODate("2017-12-04T07:51:53.597Z"),
            "lastHeartbeatRecv" : ISODate("2017-12-04T07:51:51.243Z"),
            "pingMs" : NumberLong(0),
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "172.20.6.12:22001",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",        #mongodb-3为secondary
            "uptime" : 45,
            "optime" : {
                "ts" : Timestamp(1512373911, 1),
                "t" : NumberLong(1)
            },
 

此时shard1 副本集已经配置完成,mongodb-1为primary,mongodb-2为arbiter,mongodb-3为secondary。

同样的操作进行shard2配置和shard3配置

注意:进行shard2的副本集初始化,在mongodb-2, 初始化shard3副本集在mongodb-3上进行操作。

shard2配置文件

cat >/data/mongodb/shard2/conf/shard.conf <<EOF
dbpath=/data/mongodb/shard2/data
logpath = /data/mongodb/shard2/log/shard2.log
port = 22002
logappend = true
nohttpinterface = true
fork = true
oplogSize = 4096
journal = true
#engine = wiredTiger
#cacheSizeGB = 38G
smallfiles=true
shardsvr=true
replSet=shard2
EOF

mongod -f /data/mongodb/shard2/conf/shard.conf
 

shard3配置文件

cat >/data/mongodb/shard3/conf/shard.conf <<EOF
dbpath=/data/mongodb/shard3/data
logpath = /data/mongodb/shard3/log/shard3.log
port = 22003
logappend = true
nohttpinterface = true
fork = true
oplogSize = 4096
journal = true
#engine = wiredTiger
#cacheSizeGB = 38G
smallfiles=true
shardsvr=true
replSet=shard3
EOF

mongod -f /data/mongodb/shard2/conf/shard.conf
 

图片.png

在mongodb-2上进行shard2副本集初始化

mongo 172.20.6.11:22002    #登录mongodb-2

use admin                
config = {_id:"shard2",members:[             
{_id:0,host:"172.20.6.10:22002"},
{_id:1,host:"172.20.6.11:22002"},
{_id:2,host:"172.20.6.12:22002",arbiterOnly:true},]
}
rs.initiate(config);
 

查看shard2副本集状态

{
            "_id" : 0,
            "name" : "172.20.6.10:22002",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",        #mongodb-2为secondary
            "uptime" : 15,
            "optime" : {
                "ts" : Timestamp(1512374668, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1512374668, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T08:04:28Z"),
            "optimeDurableDate" : ISODate("2017-12-04T08:04:28Z"),
            "lastHeartbeat" : ISODate("2017-12-04T08:04:30.527Z"),
            "lastHeartbeatRecv" : ISODate("2017-12-04T08:04:28.492Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.11:22002",
            "configVersion" : 1
        },
        {
            "_id" : 1,
            "name" : "172.20.6.11:22002",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",        #mongodb-2为primary
            "uptime" : 211,
            "optime" : {
                "ts" : Timestamp(1512374668, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T08:04:28Z"),
            "infoMessage" : "could not find member to sync from",
            "electionTime" : Timestamp(1512374666, 1),
            "electionDate" : ISODate("2017-12-04T08:04:26Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "172.20.6.12:22002",        #mongodb-3为arbiter
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 15,
            "lastHeartbeat" : ISODate("2017-12-04T08:04:30.527Z"),
            "lastHeartbeatRecv" : ISODate("2017-12-04T08:04:28.384Z"),
            "pingMs" : NumberLong(0),
            "configVersion" : 1
        }
 

登录mongodb-3进行shard3副本集初始化

mongo 172.20.6.12:22003    #登录mongodb-3

use admin                
config = {_id:"shard3",members:[             
{_id:0,host:"172.20.6.10:22003",arbiterOnly:true},
{_id:1,host:"172.20.6.11:22003"},
{_id:2,host:"172.20.6.12:22003"},]
}
rs.initiate(config);
 

查看shard3副本集状态

{
            "_id" : 0,
            "name" : "172.20.6.10:22003",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",        #mongodb-1为arbiter
            "uptime" : 18,
            "lastHeartbeat" : ISODate("2017-12-04T08:07:37.488Z"),
            "lastHeartbeatRecv" : ISODate("2017-12-04T08:07:36.224Z"),
            "pingMs" : NumberLong(0),
            "configVersion" : 1
        },
        {
            "_id" : 1,
            "name" : "172.20.6.11:22003",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",        #mongodb-2为secondary
            "uptime" : 18,
            "optime" : {
                "ts" : Timestamp(1512374851, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1512374851, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T08:07:31Z"),
            "optimeDurableDate" : ISODate("2017-12-04T08:07:31Z"),
            "lastHeartbeat" : ISODate("2017-12-04T08:07:37.488Z"),
            "lastHeartbeatRecv" : ISODate("2017-12-04T08:07:36.297Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.12:22003",
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "172.20.6.12:22003",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",        #mongodb-3为primary
            "uptime" : 380,
            "optime" : {
                "ts" : Timestamp(1512374851, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-12-04T08:07:31Z"),
            "infoMessage" : "could not find member to sync from",
            "electionTime" : Timestamp(1512374849, 1),
            "electionDate" : ISODate("2017-12-04T08:07:29Z"),
            "configVersion" : 1,
            "self" : true
        }
 

此时shard集群全部已经配置完毕。

2.2.5 配置路由服务器mongos

目前三台服务器的配置服务器和分片服务器均已启动,配置三台mongos服务器

由于mongos服务器的配置是从内存中加载,所以自己没有存在数据目录configdb连接为配置服务器集群

cat >/data/mongodb/mongos/conf/mongos.conf<<EOF
--logpath=/data/mongodb/mongos/log/mongos.log
logappend=true
port = 20000
maxConns = 1000
configdb=replconf/172.20.6.10:21000,172.20.6.11:21000,172.20.6.12:21000	   #制定config server集群
fork = true
EOF

mongos -f /data/mongodb/mongos/conf/mongos.conf        #启动mongos服务
 

图片.png

目前config server集群/shard集群/mongos服务均已启动,但此时为设置分片,还不能使用分片功能。需要登录mongos启用分片

登录任意一台mongos

mongo 172.20.6.10:20000
use admin
db.runCommand({addshard:"shard1/172.20.6.10:22001,172.20.6.11:22001,172.20.6.12:22001"})
db.runCommand({addshard:"shard2/172.20.6.10:22002,172.20.6.11:22002,172.20.6.12:22002"})
db.runCommand({addshard:"shard3/172.20.6.10:22003,172.20.6.11:22003,172.20.6.12:22003"})
 

图片.png

查看集群

图片.png

三、 测试

目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,此时进行数据插入,数据能够自动分片。连接在mongos上让指定的数据库、指定的集合分片生效。

注意:设置分片需要在admin数据库进行

use admin
db.runCommand( { enablesharding :"kaliarchdb"});    #开启kaliarch库分片功能
db.runCommand( { shardcollection : "kaliarchdb.table1",key : {_id:"hashed"} } )    #指定数据库里需要分片的集合tables和片键_id
 

设置kaliarchdb的 table1 表需要分片,根据 _id 自动分片到 shard1 ,shard2,shard3 上面去。

图片.png

查看分片信息

图片.png

测试插入数据

use kaliarchdb;
for (var i = 1; i <= 100000; i++) db.table1.save({_id:i,"test1":"testval1"});
 

图片.png

查看分片情况:(省去部分信息)

db.table1.stats()
{
    "sharded" : true,
    "capped" : false,
    "ns" : "kaliarchdb.table1",
    "count" : 100000,        #总count
    "size" : 3800000,
    "storageSize" : 1335296,
    "totalIndexSize" : 4329472,
    "indexSizes" : {
        "_id_" : 1327104,
        "_id_hashed" : 3002368
    },
    "avgObjSize" : 38,
    "nindexes" : 2,
    "nchunks" : 6,
    "shards" : {
        "shard1" : {
            "ns" : "kaliarchdb.table1",
            "size" : 1282690,
            "count" : 33755,        #shard1的count数
            "avgObjSize" : 38,
            "storageSize" : 450560,
            "capped" : false,
            ......

    "shard2" : {
                "ns" : "kaliarchdb.table1",
                "size" : 1259434,
                "count" : 33143,        #shard2的count数
                "avgObjSize" : 38,
                "storageSize" : 442368,
                "capped" : false,
            .......
    "shard3" : {
            "ns" : "kaliarchdb.table1",
            "size" : 1257876,    
            "count" : 33102,            #shard3的count数
            "avgObjSize" : 38,
            "storageSize" : 442368,
            "capped" : false,
 

此时架构中的mongos,config server,shard集群均已经搭建部署完毕,在实际生成环境话需要对前端的mongos做高可用来提示整体高可用。

©著作权归作者所有:来自51CTO博客作者KaliArch的原创作品,如需转载,请注明出处,否则将追究法律责任

你的鼓励让我更有动力

© 著作权归作者所有

l

linjin200

粉丝 26
博文 1005
码字总数 1170031
作品 0
福州
程序员
私信 提问
MongoDB集群部署 - 带访问控制的分片副本集

1. 前言   Ceilometer将meter、event等数据保存在MongoDB中,之前将MongoDB部署在控制节点上,使用三副本模式,时间长了发现meter数据爆炸式增长,区区2T的磁盘捉襟见肘,而想删除旧数据,...

Sai18
2018/08/16
0
0
zanePerfor前端性能监控系统高可用之Mongodb副本集读写分离架构

HI!,你好,我是zane,zanePerfor是一款最近我开发的一个前端性能监控平台,现在支持web浏览器端和微信小程序段。 我定义为一款完整,高性能,高可用的前端性能监控系统,这是未来会达到的目...

2018/11/14
0
0
搭建高可用mongodb集群(四)—— 分片

按照上一节中《搭建高可用mongodb集群(三)—— 深入副本集》搭建后还有两个问题没有解决: 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?数据压力大到机器支撑不了的...

观澜而索源
2014/03/29
349
0
21.36 mongodb分片介绍

21.36 mongodb分片介绍 分片就是将数据库进行拆分,将大型集合分隔到不同服务器上。比如,本来100G的数据,可以分割成10份存储到10台服务器上,这样每台机器只有10G的数据。 通过一个mongos的...

脑洞老湿_
2017/10/20
24
0
mongodb副本集加分片集群安全认证使用账号密码登录

mongodb副本集加分片集群安全认证使用账号密码登录 2017年12月05日 20:48:59 大伟爱自由 阅读数 8298更多 分类专栏: mongoDB 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转...

linjin200
09/12
14
0

没有更多内容

加载失败,请刷新页面

加载更多

c语言实现Sqlite3的创建db和增删改查db操作

SQLite,是一款轻型的数据库,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中使用广泛,现在准备学习一下sqlite3的使用方法并写一个测试demo,后面在项目智能...

jorin_zou
28分钟前
4
0
【2019年8月版本】OCP 071认证考试最新版本的考试原题-第2题

choose three Which three are true about the CREATE TABLE command? A) It can include the CREATE...INDEX statement for creating an index to enforce the primary key constraint. B) ......

oschina_5359
31分钟前
5
0
如何在二维码中循环批量插入图片

现在二维码种类比较多,为了突出二维码的个性及吸引客户,很多朋友都喜欢在二维码上插入图片。想要每个二维码都与众不同,但是有的时候需要批量插入图片数量有限,如果制作的二维码比较多的话...

中琅软件
32分钟前
6
0
LTR那点事—AUC及其与线上点击率的关联详解

LTR(Learning To Rank)学习排序是一种监督学习(SupervisedLearning)的排序方法,现已经广泛应用于信息索引,内容推荐,自然语言处理等多个领域。以推荐系统为例,推荐一般使用多个子策略...

达观数据
32分钟前
5
0
IntelliJ 如何显示代码的代码 docs

希望能够在 IntelliJ 代码上面显示方法的 docs。 如何进行显示? 你可以使用 Ctrl + Q 这个快捷键来查看方法的 Docs。 https://blog.ossez.com/archives/3061...

honeymoose
36分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部