Redis持久化详解
作者:互联网
一:Redis持久化概述
持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置
1:RDB持久化方式
RDB(Redis DataBase)持久化是在设置的某个时间点内,将当前进程中的数据生成快照(Snapshot)保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,会自动读取快照文件恢复数据;也可以通过 save 和 bgsave 命令实现手动持久化
注:RDB方式是将我们存储的 数据 保存到硬盘上
2:AOF持久化方式
AOF(append only file)持久化:以独立日志的方式记录每次增删改命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。当我们下次重启Redis服务时会先执行AOF文件来恢复数据
注:RDB方式是将我们存储的 增删改命令 保存到硬盘上
二:RDB持久化
1:RDB持久化触发方式
手动触发: save 命令: 执行此命令会阻塞当前Redis服务器,直到持久化完成,生产环境一般禁止使用(因为会阻塞其它客户端的访问操作)。 bgsave 命令: 该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。 自动触发: ①:根据我们在配置文件配置的 save m n 的规则来自动触发(后面介绍) ②:执行退出shutdown命令时会自动触发(前提是我们配置的持久化机制为RDB) 执行退出shutdown nosave命令时不会自动触发 ③:执行flushAll会自动触发持久化(但是没有数据,只有一个空的.rdb文件) 执行flushdb不会触发持久化 ④:执行debug reload会自动触发持久化
①:执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。 ②:父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通过info stats命令查看latest_fork_usec选项, 可以获取最近一个fork操作的耗时,单位为微秒。 ③:父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令。 ④:子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后 一次生成RDB的时间,对应info统计的rdb_last_save_time选项。 ⑤:进程发送信号给父进程表示完成,父进程更新统计信息。
2:RDB配置文件
Redis常用配置: # 默认情况下Redis将自动保存数据: # 3600 秒(1 小时)后,如果至少更改了 1 个键 # 300 秒(5 分钟)后,如果至少更改了 100 个键 # 60 秒(1 分钟)后,如果至少更改了10000个键 # 您可以通过取消注释以下三行来显式设置这些(可自定义save 秒 键) save 3600 1 save 300 100 save 60 10000 # 当bgsave出现错误时,Redis是否停止执行写命令; # yes:则当硬盘出现问题时,可以及时发现,避免数据的大量丢失; # no:则Redis无视bgsave的错误继续执行写命令,当对Redis服务器的系统(尤其是硬盘)使用了监控时,该选项考虑设置为no stop-writes-on-bgsave-error yes # 是否开启RDB文件压缩 rdbcompression yes # 是否开启RDB文件的校验,在写入文件和读取文件时都起作用;关闭checksum在写入文件和启动文件时大约能带来10%的性能提升, # 但是数据损坏时无法发现 rdbchecksum yes # 存储后的RDB文件名 默认dump.rdb dbfilename dump.rdb # 是否删除主从复制时创建的RDB文件(在没有开启持久化的实例中,最好将其删除) rdb-del-sync-files no # 存储持久化文件的目录 以上使用“dbfilename”配置指令,附加文件也将在此目录中创建 dir ./
3:基本操作演示
# 执行save 客户端: 127.0.0.1:6379> save OK 服务端: 910:M 12 Dec 2021 14:59:31.373 * DB saved on disk # 执行bgsave 客户端: 127.0.0.1:6379> bgsave Background saving started 服务端: 910:M 12 Dec 2021 15:00:44.755 * Background saving started by pid 913 913:C 12 Dec 2021 15:00:44.777 * DB saved on disk 910:M 12 Dec 2021 15:00:44.825 * Background saving terminated with success # 执行flushall 客户端: 127.0.0.1:6379> FLUSHALL OK 服务端: 910:M 12 Dec 2021 15:01:55.217 * DB saved on disk # 自动存储(save 30 2)当30秒内执行2次命令则会在30秒后执行自动持久化RDB 客户端: 127.0.0.1:6379> set name zhangsan OK 127.0.0.1:6379> set age 22 OK 服务端: 245:M 12 Dec 2021 15:06:09.045 * 2 changes in 30 seconds. Saving... 245:M 12 Dec 2021 15:06:09.092 * Background saving started by pid 247 247:C 12 Dec 2021 15:06:09.118 * DB saved on disk 245:M 12 Dec 2021 15:06:09.200 * Background saving terminated with success # 在有RDB文件的情况下打开Redis服务 服务端: 1240:M 12 Dec 2021 15:06:35.975 * Loading RDB produced by version 6.2.5 1240:M 12 Dec 2021 15:06:35.975 * RDB age 26 seconds 1240:M 12 Dec 2021 15:06:35.975 * RDB memory usage when created 0.48 Mb 1240:M 12 Dec 2021 15:06:35.976 * DB loaded from disk: 0.001 seconds 1240:M 12 Dec 2021 15:06:35.976 * Ready to accept connections
4:save m n自动持久化原理
Redis的save m n,是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。 serverCron: 是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是 检查 save m n 配置的条件是否满足,如果满足就执行bgsave。 dirty: 计数器是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改); 而当save/bgsave执行完成后,会将dirty重新置为0。 例如:如果Redis执行了set name jack,则dirty值会+1;如果执行了sadd myset v1 v2 v3,则dirty值会+3; 注意dirty记录的是服务器进行了多少次修改,而不是客户端执行了多少修改数据的命令。 lastsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。 save m n的原理如下: 每隔100ms,执行serverCron函数;在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足, 就进行bgsave。对于每一个save m n条件,只有下面两条同时满足时才算满足: lastsave > m dirty >= n
5:RDB文件处理及修复
保存:RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定。 config set dir {newDir} 临时设置 rdb文件储存目录(注:日志和AOF文件存储目录也是引用此配置) config set dbfilename {newFileName} 临时设置rdb文件存储的文件名称(设置后下次自动持久化刷新启用) 压缩:Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小, 默认开启,可以通过参数 config set rdbcompression {yes|no} 动态修改。 注:上面修改的配置若想永久修改则去配置文件更改!! 校验:如果Redis加载损坏的RDB文件时拒绝启动,并打印如下日志: # Short read or OOM loading DB. Unrecoverable error, aborting now. 这时可以使用Redis提供的redis-check-dump工具检测RDB文件并获取对应的错误报告
三:AOF持久化
1:AOF持久化执行方式
默认都是自动触发RDB持久化方式的,若想AOF持久化方式我们首先得去配置文件开启此功能;appendonly yes 默认是未开启;而AOF持久化的文件名则需要通过 appendfilename 来配置,默认AOF文件名称为appendonly.aof;保存路径同RDB持久化方式一致,通过dir配置指定。
AOF的执行方式是:只要执行增删改命令就会执行一次追加操作,往 appendonly.aof 文件内追加增删改命令
流程:
①:所有的增删改命令会追加到aof_buf(AOF缓冲区)中。 ②:根据不同的同步策略将aof_buf中的内容同步到硬盘。 ③:随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。 ④:当Redis服务器重启时,可以加载AOF文件进行数据恢复。
AOF命令追加方式(append): Redis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈 命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点 具体格式略。在AOF文件中,除了用于指定数据库的select命令(如select 0 为选中0号数据库)是由Redis添加的, 其他都是客户端发送来的写命令。
2:AOF配置文件
# 是否开启AOF日志记录,默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了,但是redis如果中途宕机, # 会导致可能有几分钟的数据丢失(取决于dump数据的间隔时间),根据save来策略进行持久化, # Append Only File是另一种持久化方式, 可以提供更好的持久化特性,Redis会把每次写入的数据在接收后都写入 # appendonly.aof文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。默认不启用此功能 appendonly yes # 文本文件AOF的文件名,存放目录在dir配置中配置 appendfilename "appendonly.aof" # aof持久化策略的配置(appendfsync属性说明) # always: # 命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次增删改命令都要同步到AOF文件, # 这硬盘IO会成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能 # 处理几万个命令,而且会大大降低SSD的寿命。 # 总结:每次增删改都执行fsync,以保证数据同步到磁盘,安全性高,但性能较差 # no: # 命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下, # 文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。 # 总结:由操作系统保证数据同步到磁盘,Linux的默认fsync策略是30秒,最多会丢失30s的数据 # everysec: # 命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。 # everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。 # appendfsync always appendfsync everysec # appendfsync no # 在AOF rewrite期间,是否对aof新记录的append暂缓使用文件同步策略,主要考虑磁盘IO开支和请求阻塞时间 # no: # 表示"不暂缓",新的aof记录仍然会被立即同步到磁盘,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题 # yes: # 相当于将appendfsync设置为no,这说明并没有执行磁盘操作,只是写入了缓冲区,因此这样并不 # 会造成阻塞(因为没有竞争磁盘),但是如果这个时候redis挂掉,就会丢失数据。丢失多少数据呢?Linux # 的默认fsync策略是30秒,最多会丢失30s的数据,但由于yes性能较好而且会避免出现阻塞因此比较推荐rewrite # 即对aof文件进行整理,将空闲空间回收,从而可以减少恢复数据时间 no-appendfsync-on-rewrite no
# aof_current_size 和 aof_base_size 可以执行 info persistence 或直接 info # 执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值;文件重写触发条件之一 auto-aof-rewrite-percentage 100 # 执行AOF重写时,文件的最小体积,默认值为64MB。文件重写触发条件之一 auto-aof-rewrite-min-size 64mb # 是否加载由于某些原因导致的末尾异常的AOF文件(主进程被kill/断电等),建议yes aof-load-truncated yes # redis4.0新增RDB-AOF混合持久化格式,在开启了这个功能之后,AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容, # 其中RDB格式的内容用于记录已有的数据,而AOF格式的内容则用于记录最近发生了变化的数据,这样Redis就可以同时兼有RDB持 # 久化和AOF持久化的优点(既能够快速地生成重写文件,也能够在出现问题时,快速地载入数据),默认为no,即不启用此功能 aof-use-rdb-preamble yes
3:AOF文件重写(rewrite)
随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行, 也会导致数据恢复需要的时间过长。 文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令, 同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作! 关于文件重写需要注意的另一点是:对于AOF持久化来说,文件重写虽然是强烈推荐的,但并不是必须的;即使没有文件重写, 数据也可以被持久化并在Redis启动的时候导入;因此在一些实现中,会关闭自动的文件重写,然后通过定时任务在每天的某一时刻定时执行。 文件重写之所以能够压缩AOF文件,原因在于: ①:过期的数据不再写入文件 ②:无效的命令不再写入文件:如有些数据被重复设值(set name jack, set name tom)、 有些数据被删除了(sadd myset v1, del myset)等等 ③:多条命令可以合并为一个:如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd myset v1 v2 v3 不过为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令; 而是以某个常量为界将命令拆分为多条。 这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义,不可更改,3.0版本中值是64。 通过上述内容可以看出,由于重写后AOF执行的命令减少了,文件重写既可以减少文件占用的空间,也可以加快恢复速度
4:文件重写的触发(手动触发)
直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似,都是fork子进程进行具体工作,且都只有在fork时阻塞
执行手动触发AOF文件重写 客户端: 127.0.0.1:6379> bgrewriteaof Background append only file rewriting started 服务端: 1751:M 12 Dec 2021 20:33:45.401 * Background append only file rewriting started by pid 1753 1751:M 12 Dec 2021 20:33:45.736 * AOF rewrite child asks to stop sending diffs. 1753:C 12 Dec 2021 20:33:45.752 * Parent agreed to stop sending diffs. Finalizing AOF... 1753:C 12 Dec 2021 20:33:45.754 * Concatenating 0.00 MB of AOF diff received from parent. 1753:C 12 Dec 2021 20:33:45.769 * SYNC append only file rewrite performed 1751:M 12 Dec 2021 20:33:45.845 * Background AOF rewrite terminated with success 1751:M 12 Dec 2021 20:33:45.848 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB) 1751:M 12 Dec 2021 20:33:45.863 * Background AOF rewrite finished successfully
5:文件重写的触发(自动触发)
自动触发是根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage配置参数,以及aof_current_size和aof_base_size状态确定触发时机
auto-aof-rewrite-min-size: 执行AOF重写时,文件的最小体积,默认值为64MB。 auto-aof-rewrite-percentage: 执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值 可以通过config get命令查看: 127.0.0.1:6379[12]> config get auto-aof-rewrite-percentage 1) "auto-aof-rewrite-percentage" 2) "100" -- 百分比 100% 127.0.0.1:6379[12]> config get auto-aof-rewrite-min-size 1) "auto-aof-rewrite-min-size" 2) "67108864" -- 大小 64M
状态可以通过info persistence查看:(其它状态这里不介绍了) aof_base_size:上一次重写后AOF文件空间 aof_current_size:当前AOF文件空间 只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个参数同时满足时, 才会自动触发AOF重写,即bgrewriteaof操作 自动触发条件: aof_current_size > auto-aof-rewrite-min-size && (aof_current_size - aof_base_size) / aof_base_size >= auto-aof-rewrite-percentage
为了看执行效果我修改参数: auto-aof-rewrite-percentage 1 auto-aof-rewrite-min-size 1mb 当AOF达到大小峰值则会自动执行重写;然会重写后的aof文件会压缩的特别小 Redis服务器触发 自动重写 日志: 1611:M 13 Dec 2021 13:51:45.941 * Starting automatic rewriting of AOF on 105343400% growth 1611:M 13 Dec 2021 13:51:45.986 * Background append only file rewriting started by pid 1621 1611:M 13 Dec 2021 13:51:46.594 * AOF rewrite child asks to stop sending diffs. 1621:C 13 Dec 2021 13:51:46.610 * Parent agreed to stop sending diffs. Finalizing AOF... 1621:C 13 Dec 2021 13:51:46.610 * Concatenating 0.02 MB of AOF diff received from parent. 1621:C 13 Dec 2021 13:51:46.616 * SYNC append only file rewrite performed 1611:M 13 Dec 2021 13:51:46.671 * Background AOF rewrite terminated with success 1611:M 13 Dec 2021 13:51:46.674 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB) 1611:M 13 Dec 2021 13:51:46.681 * Background AOF rewrite finished successfully
6:文件重写流程
文件重写的流程,注意点: ①:重写由父进程fork出一个子进程进行持久化 ②:重写期间Redis执行的写命令,需要追加到新的AOF文件中,为此Redis引入了aof_rewrite_buf缓存 对照上图,文件自动重写的流程如下: 1:Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回, 如果存在bgsave命令则等bgsave执行完成后再执行。前面曾介绍过,这个主要是基于性能方面的考虑。 如果当前进程正在执行AOF(bgrewriteaof)重写,请求不执行并返回如下响应: ERR Background append only file rewriting already in progress 如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行,返回如下响应: Background append only file rewriting scheduled 2:父进程执行fork操作创建子进程,这个过程中父进程是阻塞的(开销等同于bgsave过程) 3.1:父进程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息; 同时不再阻塞父进程,并可以继续响应其它命令;所有增删改命令依然写入AOF缓冲区并根据appendfsync策略同步到硬盘, 保证原有AOF机制正确性。 3.2:由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写 缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间 Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。 4:子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制 默认为32MB,防止单次刷盘数据过多造成硬盘阻塞。 5.1:子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。 5.2:父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。 5.3:使用新的AOF文件替换老文件,完成AOF重写
8:文件校验与AOF文件修复
文件校验启动: 127.0.0.1:6379> config get aof-load-truncated -- 跳过AOF文件末尾不完整 1) "aof-load-truncated" 2) "yes" AOF和RDB文件载入类似,Redis载入AOF文件时会对AOF文件进行校验,若发现文件损坏时(就是被修改了),则日志会打印 错误,Redis启动也就失败了; 若AOF文件是因为文件结尾不完整(Redis服务器突然宕机断电),且AOF配置的 aof-load-truncated 为yes(默认)时, 则会忽略文件结尾的不完整,并在日志中输出警告;(注意:只能是结尾+行被损坏,不能修改或者跳行删除) 450:M 14 Dec 2021 09:40:41.141 # Server initialized 450:M 14 Dec 2021 09:40:41.142 # !!! Warning: short read while loading the AOF file !!! 450:M 14 Dec 2021 09:40:41.142 # !!! Truncating the AOF at offset 90 !!! 450:M 14 Dec 2021 09:40:41.144 # AOF loaded anyway because aof-load-truncated is enabled 450:M 14 Dec 2021 09:40:41.145 * DB loaded from append only file: 0.003 seconds 450:M 14 Dec 2021 09:40:41.146 * Ready to accept connections 若aof文件被损坏到无法启动则需要AOF文件修复: 注:AOF文件修复会定位到出错的那一行知道最后一行全部剔除删掉(备份后妥善操作) 使用Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复,进而再启动redis Linux下操作:redis-check-aof --fix 待修复的AOF文件
Windows下操作: PS C:\Users\redis-x64-6.2.5> .\redis-check-aof.exe --fix .\appendonly.aof 0x 56: Expected \r\n, got: 3233 AOF analyzed: size=113, ok_up_to=60, ok_up_to_line=19, diff=53 This will shrink the AOF from 113 bytes, with 53 bytes, to 60 bytes Continue? [y/N]: y Successfully truncated AOF
四:重启加载
AOF和RDB文件都可以用于Redis服务器重启时的数据恢复;但是Redis启动时会优先载入AOF文件来恢复数据,只有当AOF关闭时,才会载入RDB文件恢复数据
当AOF持久化关闭时,且无 .RDB持久化文件 1775:M 13 Dec 2021 18:07:47.923 # Server initialized 1775:M 13 Dec 2021 18:07:47.924 * Ready to accept connections 当AOF持久化关闭时,且有 .RDB持久化文件 -- 没有文件则不读取 80:M 13 Dec 2021 18:08:38.896 # Server initialized 80:M 13 Dec 2021 18:08:38.897 * Loading RDB produced by version 6.2.5 80:M 13 Dec 2021 18:08:38.897 * RDB age 5 seconds 80:M 13 Dec 2021 18:08:38.897 * RDB memory usage when created 0.51 Mb 80:M 13 Dec 2021 18:08:38.897 * DB loaded from disk: 0.001 seconds 80:M 13 Dec 2021 18:08:38.898 * Ready to accept connections 当AOF持久化开启时,且无 .AOF持久化文件 -- 没有文件则不读取 515:M 13 Dec 2021 18:09:36.702 # Server initialized 1515:M 13 Dec 2021 18:09:36.704 * Ready to accept connections 当AOF持久化开启时,且有 .AOF持久化文件(注:若.AOF文件为空则和无.AOF文件一样不执行) 178:M 13 Dec 2021 18:10:08.803 # Server initialized 178:M 13 Dec 2021 18:10:08.804 * DB loaded from append only file: 0.001 seconds 178:M 13 Dec 2021 18:10:08.804 * Ready to accept connections
五:AOF和RDB优缺点
RDB持久化:
优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。
缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。
AOF持久化:
与RDB持久化相对应,AOF的优点在于支持秒级持久化、兼容性好,缺点是文件大、恢复速度慢、对性能影响大。
标签:aof,持久,文件,Redis,详解,AOF,Dec,2021 来源: https://www.cnblogs.com/antLaddie/p/15413001.html