[ Mongodb ] 全量备份和增量备份

1. 前言

由于线上的mongodb 数据体量越来越大,如果没有完善的备份方案,发生故障势必造成业务很长时间的暂停。参考了网上方案,写出以下总结和备份方案:

备份方案分为两种:全备和增量备份,二者结合起来使用。

 参考链接:https://www.cnblogs.com/xuliuzai/p/9917137.html  感谢作者的分享。

2. 构建mongodb 副本集测试环境

首先在测试环境进行测试,过程如下:

  测试机:192.168.118.16  系统:CentOS 7

首先搭建mongodb 副本集(为了和线上环境保持一致)这里使用 mongodb 3.6 的版本,建议和生产环境相同的版本。

Mongdb 没啥安装的, 开箱即用。副本集参考链接:https://www.cnblogs.com/hukey/p/5769548.html

1
2
3
4
5
6
7
8
9
rs0:PRIMARY> rs.isMaster();
{
    "hosts" : [
        "192.168.118.16:27017",
        "192.168.118.16:27018",
        "192.168.118.16:27019"
    ],

副本集创建成功。

接下来,向集群里写入数据:

1
2
3
4
5
6
7
8
9
10
11
rs0:PRIMARY> for(var i=1;i<=10000;i++) db.users.insert({id:i, name:"hukey",city:"xi'an"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> show dbs;
admin   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB
rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> db.users.count();
10000

 写入了 1W 条数据。

准备工作完成。

 

3. mongodb 全量备份及恢复

全备脚本 [ mongodb_back_all.sh ] 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
# Author:hukey
 
host='192.168.118.16'
port='27017'
sourcepath='/mongodb/bin'
targetpath='/mongodb/backup'
nowtime=$(date "+%Y%m%d")
 
 
 
start(){
    $sourcepath/mongodump --host $host --port $port --oplog --gzip --out ${targetpath}/${nowtime}
}
 
execute(){
echo "=========================$(date) backup all mongodb back start  ${nowtime}========="
start
if [ $? -eq 0 ];then
    echo "The MongoDB BackUp Successfully!"
else
    echo "The MongoDB BackUp Failure"
fi
}
 
if [ ! -d "${targetpath}/${nowtime}" ];then
    mkdir -p "${targetpath}/${nowtime}"
fi
 
execute
 
backtime=$(date -d '-7 days' "+%Y%m%d")
if [ -d "${targetpath}/${backtime}/" ];then
    rm -rf "${targetpath}/${backtime}/"
    echo "=======${targetpath}/${backtime}/===删除完毕=="
fi
 
echo "========================= $(date) backup all mongodb back end ${nowtime}========="

 

全库还原脚本 [ mongodb_restore_all.sh ] 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/bash
# Author:hukey
 
echo -e "\033[31;1m*****[ Mongodb ] 全库恢复脚本*****\033[0m"
host=192.168.118.16
mongo_bin=/mongodb/bin/
backpath='/mongodb/backup'
 
 
echo -e "\033[32;1m[ 选择要恢复全库的日期 ] \033[0m"
for backfile in `ls $backpath`; do
    echo $backfile
done
 
read -p ">>>" date_bak
 
if [[ $date_bak == "" ]] || [[ $date_bak == '.' ]] || [[ $date_bak == '..' ]]; then
    echo -e "\033[31;1m输入不能为特殊字符.\033[0m"
    exit 1
fi
 
 
if [ -d $backpath/$date_bak ];then
    read -p "请确认是否恢复全库备份[y/n]:" choice
 
    if [ "$choice" == "y" ];then
        echo -e "\033[32;1m正在恢复全库备份,请稍后...\033[0m"
        $mongo_bin/mongorestore --host $host --port 27017 --oplogReplay --gzip $backpath/$date_bak/
        if [ $? -eq 0 ];then
            echo -e "\033[32;1m--------全库恢复成功.--------\033[0m"
        else
            echo -e "\033[31;1m恢复失败,请手动检查!\033[0m"
            exit 3
        fi
    else
        exit 2
    fi
else
    echo "\033[31;1m输入信息错误.\033[0m"
    exit 1
fi

 

执行全量备份脚本:

1
2
3
4
5
6
7
8
9
10
[root@192.168.118.16 /mongodb/script]#sh mongodb_back_all.sh
=========================Thu Sep 12 11:57:32 CST 2019 backup all mongodb back start  20190912=========
2019-09-12T11:57:32.863+0800    writing admin.system.version to
2019-09-12T11:57:32.866+0800    done dumping admin.system.version (1 document)
2019-09-12T11:57:32.867+0800    writing test.users to
2019-09-12T11:57:32.955+0800    done dumping test.users (10000 documents)
2019-09-12T11:57:32.956+0800    writing captured oplog to
2019-09-12T11:57:32.975+0800        dumped 1 oplog entry
The MongoDB BackUp Successfully!
========================= Thu Sep 12 11:57:32 CST 2019 backup all mongodb back end 20190912=========

 

查看备份数据:

1
2
[root@192.168.118.16 /mongodb/script]#ls /mongodb/backup/20190912/
admin  oplog.bson  test

 

在测试全库还原之前,首先需要清库数据(注意:本次操作是在测试环境)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
10000
rs0:PRIMARY> db.dropDatabase();
{
    "dropped" : "test",
    "ok" : 1,
    "operationTime" : Timestamp(1568264437, 2),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1568264437, 2),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}

 

执行全库恢复脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@192.168.118.16 /mongodb/script]#sh mongodb_restore_all.sh
*****[ Mongodb ] 全库恢复脚本*****
[ 选择要恢复全库的日期 ]
20190912
>>>20190912
请确认是否恢复全库备份[y/n]:y
正在恢复全库备份,请稍后...
2019-09-12T13:43:58.956+0800    preparing collections to restore from
2019-09-12T13:43:58.959+0800    reading metadata for test.users from /mongodb/backup/20190912/test/users.metadata.json.gz
2019-09-12T13:43:59.019+0800    restoring test.users from /mongodb/backup/20190912/test/users.bson.gz
2019-09-12T13:44:01.950+0800    [#################.......]  test.users  28.0KB/37.9KB  (73.9%)
2019-09-12T13:44:04.083+0800    [########################]  test.users  37.9KB/37.9KB  (100.0%)
2019-09-12T13:44:04.083+0800    no indexes to restore
2019-09-12T13:44:04.084+0800    finished restoring test.users (10000 documents)
2019-09-12T13:44:04.084+0800    replaying oplog
2019-09-12T13:44:04.084+0800    done
--------全库恢复成功.--------

 

全库的备份和还原已经实现,可以通过 crontab 来制定计划任务触发。

 

4. mongodb 增量备份及恢复

增量备份的思路是通过 oplog 来实现的,大家可以通过文档搜索了解。
直接上脚本:

增量备份 [ mongodb_backup_incremental.sh ] 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/bin/bash
# Author:hukey
command_linebin='/mongodb/bin/mongo'
port=27017
 
if [ ! -d "/mongodb/backup/mongodbOplog_bak/mongo-$port" ];then
    mkdir -p /mongodb/backup/mongodbOplog_bak/mongo-$port
fi
 
if [ ! -d "/mongodb/backup/mongodbOplog_bak/log-$port" ];then
    mkdir -p /mongodb/backup/mongodbOplog_bak/log-$port
fi
 
bkdatapath=/mongodb/backup/mongodbOplog_bak/mongo-$port
bklogpath=/mongodb/backup/mongodbOplog_bak/log-$port
 
logfilename=$(date +"%Y%m%d")
 
echo "===MongoDB 端口为" $port "的差异备份开始,开始时间为" $(date -d today +"%Y%m%d%H%M%S")
 
paramBakEndDate=$(date +%s)
echo "===本次备份时间参数中的结束时间为:" $paramBakEndDate
 
diffTime=$(expr 60 \* 60)
echo "===备份设置的间隔时间为:" $diffTime
 
paramBakStartDate=$(expr $paramBakEndDate - $diffTime)
echo "===本次备份时间参数中的开始时间为:" $paramBakStartDate
 
diffTime=$(expr 61 \* 60)
paramAfterBakRequestStartDate=$(expr $paramBakEndDate - $diffTime)
echo "===为保证备份的连续性,本次备份后,oplog中的开始时间需小于:" $paramAfterBakRequestStartDate
 
bkfilename=$(date -d today +"%Y%m%d%H%M%S")
 
command_line="${command_linebin} 192.168.118.16:27017"
 
opmes=$(/bin/echo "db.printReplicationInfo()" | $command_line --quiet)
 
echo $opmes > /tmp/opdoctime$port.tmplog
opbktmplogfile=/tmp/opdoctime$port.tmplog
opstartmes=$(grep "oplog first event time" $opbktmplogfile | awk -F 'CST' '{print $1}' | awk -F 'oplog first event time: '  '{print $2}' | awk -F ' GMT' '{print $1}'  )
oplogRecordFirst=$(date -d "$opstartmes"  +%s)
echo "===oplog集合记录的开始时间为[格式化]:" $oplogRecordFirst
if [ $oplogRecordFirst -le $paramBakStartDate ]; then
    echo "Message --检查设置备份时间合理。备份参数的开始时间在oplog记录的时间范围内。"
else
    echo "Fatal Error --检查设置的备份时间不合理合理。备份参数的开始时间不在oplog记录的时间范围内。请调整oplog size或调整备份频率。本次备份可以持续进行,但还原时数据完整性丢失。"
fi
 
/mongodb/bin/mongodump -h 192.168.118.16 --port $port  -d local -c oplog.rs  --query '{ts:{$gte:Timestamp('$paramBakStartDate',1),$lte:Timestamp('$paramBakEndDate',9999)}}' -o $bkdatapath/mongodboplog$bkfilename
 
 
opmes=$(/bin/echo "db.printReplicationInfo()" | $command_line --quiet)
echo $opmes > /tmp/opdoctime$port.tmplog
opbktmplogfile=/tmp/opdoctime$port.tmplog
opstartmes=$(grep "oplog first event time" $opbktmplogfile | awk -F 'CST' '{print $1}' | awk -F 'oplog first event time: '  '{print $2}' | awk -F ' GMT' '{print $1}'  )
oplogRecordFirst=$(date -d "$opstartmes"  +%s)
echo "===执行备份后,oplog集合记录的开始时间为[时间格式化]:" $oplogRecordFirst
 
if [ $oplogRecordFirst -le $paramAfterBakRequestStartDate ]; then
    echo "Message --备份后,检查oplog集合中数据的开始时间,即集合中最早的一笔数据,时间不小于61分钟的时间(即参数 paramAfterBakRequestStartDate)。这样可以保证每个增量备份含有最近一个小时的全部op操作,满足文件的持续完整性,逐个还原无丢失数据风险。"
else
    echo "Fatal Error --备份后,检查oplog集合的涵盖的时间范围过小(小于61min)。设置的备份时间不合理合理,备份后的文件不能完全涵盖最近60分钟的数据。请调整oplog size或调整备份频率。本次备份可以持续进行,但还原时数据完整性丢失。"
fi
 
if [ -d "$bkdatapath/mongodboplog$bkfilename" ]
then
    echo "Message --检查此次备份文件已经产生.文件信息为:" $bkdatapath/mongodboplog$bkfilename >> $bklogpath/$logfilename.log
else
    echo "Fatal Error --备份过程已执行,但是未检测到备份产生的文件,请检查!" >> $bklogpath/$logfilename.log
fi
 
keepbaktime=$(date -d '-3 days' "+%Y%m%d%H")*
if [ -d $bkdatapath/mongodboplog$keepbaktime ]; then
    rm -rf $bkdatapath/mongodboplog$keepbaktime
    echo "Message -- $bkdatapath/mongodboplog$keepbaktime 删除完毕" >> $bklogpath/$logfilename.log
fi
 
echo "===MongoDB 端口为" $port "的差异备份结束,结束时间为:" $(date -d today +"%Y%m%d%H%M%S")

 

这个脚本比较长,需要注意一个变量: diffTime

第一次定义这个变量的时候,是为了定义备份的时长,从此刻到之前 65 * 60ms 之前的时间,也就是备份从现在到之前 1小时5分这段时间的增量;

第二次定义这个变量的时候,是为了避免数据增长过快,覆盖了还未备份的数据的,比较的依据是 mongodb db.printReplicationInfo(); 的 oplog first event time 时间。

这两个定义的时间可灵活调整。

 

增量备份还原 [ mongodb_backup_incremental.sh ] 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/bash
# Author:hukey
 
host=192.168.118.16
port=27017
mongo_bin=/mongodb/bin/
backpath='/mongodb/backup/mongodbOplog_bak/mongo-27017'
 
 
echo -e "\033[31;1m*****[ Mongodb ] 增量恢复脚本*****\033[0m"
 
echo -e "\033[32;1m[ 选择要恢复增量的日期(格式:年月日时分秒) ] \033[0m"
for time_file in `ls $backpath`; do
    echo $time_file
done
 
read -p ">>>" date_bak
if [[ $date_bak == "" ]] || [[ $date_bak == '.' ]] || [[ $date_bak == '..' ]]; then
    echo -e "\033[31;1m输入不能为特殊字符.\033[0m"
    exit 1
fi
if [ -d $backpath/$date_bak ]; then
    read -p "请确认是否恢复[$date_bak]增量备份[y/n]:" choice
    if [ "$choice" == "y" ];then
        mkdir -p /tmp/mongodb/ && cp -a $backpath/$date_bak/local/oplog.rs.bson /tmp/mongodb/oplog.bson
        $mongo_bin/mongorestore --host $host --port $port --oplogReplay /tmp/mongodb/ && rm -rf /tmp/mongodb/
        if [ $? -eq 0 ];then
            echo -e "\033[32;1m--------[$date_bak]增量恢复成功.--------\033[0m"
        else
            echo -e "\033[31;1m恢复失败,请手动检查!\033[0m"
            exit 3
        fi
    else
        exit 2
    fi
else
    echo -e "\033[31;1m输入信息错误.\033[0m"
    exit 1
fi

 

测试下增量备份脚本和还原增量脚本

 

(1)首先写入一批数据到 mongodb

1
2
3
4
5
6
rs0:PRIMARY> for(var i=1;i<=100;i++) db.users.insert({id:i,name:"hukey"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
100

写入了 100 条数据成功。

 

(2)执行增量备份脚本

1
2
3
4
5
6
7
8
9
10
11
12
[root@192.168.118.16 /mongodb/script]#sh mongodb_backup_incremental.sh
===MongoDB 端口为 27017 的差异备份开始,开始时间为 20190912141545
===本次备份时间参数中的结束时间为: 1568268945
===备份设置的间隔时间为: 3900
===本次备份时间参数中的开始时间为: 1568265045
===为保证备份的连续性,本次备份后,oplog中的开始时间需小于: 1568265285
===oplog集合记录的开始时间为[格式化]: 1568260252
Message --检查设置备份时间合理。备份参数的开始时间在oplog记录的时间范围内。
2019-09-12T14:15:48.648+0800    Failed: error connecting to db server: no reachable servers
===执行备份后,oplog集合记录的开始时间为[时间格式化]: 1568260252
Message --备份后,检查oplog集合中数据的开始时间,即集合中最早的一笔数据,时间不小于61分钟的时间(即参数 paramAfterBakRequestStartDate)。这样可以保证每个增量备份含有最近一个小时的全部op操作,满足文件的持续完整性,逐个还原无丢失数据风险。
===MongoDB 端口为 27017 的差异备份结束,结束时间为: 20190912141548

 

(3)再次新增 100 条数据到 mongodb

1
2
3
4
5
6
rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> for(var i=1;i<=100;i++) db.users.insert({id:i,name:"xiaofei"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.users.count();
200

 

(4)再次执行增量备份

1
2
3
4
5
6
7
8
9
10
11
12
[root@192.168.118.16 /mongodb/script]#sh mongodb_backup_incremental.sh
===MongoDB 端口为 27017 的差异备份开始,开始时间为 20190912141728
===本次备份时间参数中的结束时间为: 1568269048
===备份设置的间隔时间为: 3900
===本次备份时间参数中的开始时间为: 1568265148
===为保证备份的连续性,本次备份后,oplog中的开始时间需小于: 1568265388
===oplog集合记录的开始时间为[格式化]: 1568260252
Message --检查设置备份时间合理。备份参数的开始时间在oplog记录的时间范围内。
2019-09-12T14:17:31.696+0800    Failed: error connecting to db server: no reachable servers
===执行备份后,oplog集合记录的开始时间为[时间格式化]: 1568260252
Message --备份后,检查oplog集合中数据的开始时间,即集合中最早的一笔数据,时间不小于61分钟的时间(即参数 paramAfterBakRequestStartDate)。这样可以保证每个增量备份含有最近一个小时的全部op操作,满足文件的持续完整性,逐个还原无丢失数据风险。
===MongoDB 端口为 27017 的差异备份结束,结束时间为: 20190912141731

 

到此,已知:

第一次增量备份前,数据是 100 条

第二次增量备份前,数据是 200 条

 

(5)删除 mongodb 数据,恢复第一次增量备份数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rs0:PRIMARY> db.users.count();
200
rs0:PRIMARY> db.dropDatabase();
{
    "dropped" : "test",
    "ok" : 1,
    "operationTime" : Timestamp(1568270110, 2),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1568270110, 2),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}
rs0:PRIMARY>
rs0:PRIMARY> show dbs;
admin   0.000GB
config  0.000GB
local   0.001GB

 

执行恢复增量备份的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@192.168.118.16 /mongodb/script]#sh mongodb_restore_incremental.sh
*****[ Mongodb ] 增量恢复脚本*****
[ 选择要恢复增量的日期(格式:年月日时分秒) ]
mongodboplog20190912144121
mongodboplog20190912144403
>>>mongodboplog20190912144121
请确认是否恢复[mongodboplog20190912144121]增量备份[y/n]:y
2019-09-12T14:44:34.534+0800    preparing collections to restore from
2019-09-12T14:44:34.534+0800    replaying oplog
2019-09-12T14:44:37.531+0800    oplog  1.12MB
2019-09-12T14:44:40.021+0800    oplog  1.79MB
2019-09-12T14:44:40.021+0800    done
--------[mongodboplog20190912144121]增量恢复成功.--------

 

此时,mongodb 应该是 100 条数据才对,验证下:

1
2
3
4
rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
100

 

数据正确,执行第二次增量备份的还原:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@192.168.118.16 /mongodb/script]#sh mongodb_restore_incremental.sh
*****[ Mongodb ] 增量恢复脚本*****
[ 选择要恢复增量的日期(格式:年月日时分秒) ]
mongodboplog20190912144121
mongodboplog20190912144403
>>>mongodboplog20190912144403
请确认是否恢复[mongodboplog20190912144403]增量备份[y/n]:y
2019-09-12T14:45:41.886+0800    preparing collections to restore from
2019-09-12T14:45:41.887+0800    replaying oplog
2019-09-12T14:45:44.882+0800    oplog  1.04MB
2019-09-12T14:45:47.882+0800    oplog  1.75MB
2019-09-12T14:45:50.341+0800    oplog  1.81MB
2019-09-12T14:45:50.341+0800    done
--------[mongodboplog20190912144403]增量恢复成功.--------

 

此时,mongodb 应该是 200 条数据才对,验证下:

1
2
3
4
rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
200

 

正确,数据恢复没有问题。增量备份完成。

 

5. 后记

对于线上生产环境,目前的备份解决方案是:

  全量备份 1 周执行一次,增量备份每天执行一次。

  后期准备在建立一个备用的副本集,每天都会将备份数据还原到 新建的副本集中,进行数据备份的校验,准备还是通过脚本来实现。后期在编写。

本文作者:hukey

本文链接:https://www.cnblogs.com/hukey/p/11512062.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hukey  阅读(11289)  评论(2编辑  收藏  举报
编辑推荐:
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
阅读排行:
· 用 DeepSeek 给对象做个网站,她一定感动坏了
· DeepSeek+PageAssist实现本地大模型联网
· 手把手教你更优雅的享受 DeepSeek
· Java轻量级代码工程
· 从 14 秒到 1 秒:MySQL DDL 性能优化实战
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 彩虹 Jay
彩虹 - Jay
00:00 / 00:00
An audio error has occurred.

彩虹 + 轨迹 (Live) - 周杰伦 (Jay Chou)

彩虹

词:周杰伦

曲:周杰伦

哪里有彩虹告诉我

哪里有彩虹告诉我

能不能把我的愿望还给我

能不能把我的愿望还给我

为什么天这么安静

为什么天这么安静

所有的云都跑到我这里

有没有口罩一个给我

有没有口罩一个给我

释怀说了太多就成真不了

释怀说了太多就成真不了

也许时间是一种解药

也许时间是一种解药

也是我现在正服下的毒药

也是我现在正服下的毒药

看不见你的笑 我怎么睡得着

看不见你的笑 我怎么睡得着

你的声音这么近我却抱不到

你的声音这么近我却抱不到

没有地球太阳还是会绕

没有地球太阳还是会绕

没有理由我也能自己走

没有理由我也能自己走

你要离开 我知道很简单

你要离开 我知道很简单

你说依赖 是我们的阻碍

你说依赖 是我们的阻碍

就算放开 但能不能别没收我的爱

就算放开 但能不能别没收我的爱

当作我最后才明白

当作我最后才明白

看不见你的笑 要我怎么睡得着

看不见你的笑 要我怎么睡得着

你的声音这么近我却抱不到

没有地球太阳还是会绕 会绕

没有理由我也能自己走掉

释怀说了太多就成真不了

也许时间是一种解药 解药

也是我现在正服下的毒药

轨迹

词:黄俊郎

曲:周杰伦

我会发着呆然后忘记你

接着紧紧闭上眼

想着哪一天 会有人代替

想着哪一天 会有人代替

让我不再想念你

我会发着呆 然后微微笑

我会发着呆 然后微微笑

接着紧紧闭上眼

又想了一遍 你温柔的脸

又想了一遍 你温柔的脸

在我忘记之前