redis 常见问题一

什么是Redis?

  • Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构。
  • 性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
  • 它支持多种类型的数据结构。 Redis 内置了 复制,LUA脚本, LRU驱动事件,事务 和磁盘持久化, 并提供高可用性能力。
  • 单个value的最大限制是1GB。
  • 对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的Memcached来用。
  • Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

Redis相比Memcached有哪些优势?

1 网络IO模型

Memcached是多线程非阻塞IO复用的网络模型,多线程模型可以发挥多核作用,但是引入了cache coherency和锁的问题。

redis使用单线程的IO复用模型,,但一些简单的计算功能,比如排序、聚合等,单线程模型施加会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞的。 redis 利用队列技术将并发访问变为串行访问, 消除了传统数据库串行控制的开销。

2 数据类型支持不同

Memcached使用key-value形式存储和访问数据,在内存中维护一张巨大的HashTable,使得对数据查询的时间复杂度降低到O(1),保证了对数据的高性能访问。

redis支持的数据类型要丰富得多。最为常用的数据类型主要由五种:String、Hash、List、Set和Sorted Set。

3 内存管理机制不同

Memcached使用预分配的内存池的方式,使用slab和大小不同的chunk来管理内存,Item根据大小选择合适的chunk存储,内存池的方式可以省去申请/释放内存的开销,并且能减小内存碎片产生,但这种方式也会带来一定程度上的空间浪费。

redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片,Redis跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的。

4 数据持久化支持

Redis是基于内存的存储系统,但是它支持内存数据的持久化的,而且提供两种主要的持久化策略(RDB快照和AOF日志)。

Memcached是不支持数据持久化操作的。

数据一致性问题

Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题。

redis没有提供cas 命令,不过Redis提供了事务的功能。

集群管理方式不同

Memcached本身并不支持分布式,只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。当客户端向Memcached集群发送和获取数据,都要自己计算出查询数据所在的节点。

Redis已经支持了分布式存储功能。Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,具有线性可伸缩的功能。节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。当前Redis Cluster支持的最大节点数就是4096。Redis Cluster使用的分布式算法也很简单:crc16( key ) % HASH_SLOTS_NUMBER。Redis Cluster引入了Master节点和Slave节点。


Redis支持哪几种数据类型?

String、List、Set、Sorted Set、hashes

HyperLogLog、Geo、Pub/Sub

Redis Module,像 BloomFilter,RedisSearch,Redis-ML


Redis主要消耗什么物理资源?

redis是内存中的数据结构存储系统,主要依赖于内存。


Redis的全称是什么?

Remote Dictionary Server 远程字典服务


Redis有哪几种数据淘汰策略?

  • noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  • allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random: 回收随机的键使得新添加的数据有空间存放。
  • volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

一个字符串类型的值能存储最大容量是多少?

512M


为什么Redis需要把所有数据放到内存中?

为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。

Redis集群方案应该怎么做?都有哪些方案?

1.twemproxy,大概概念是,它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个redis实例后,使用时在本需要连接redis的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性hash算法,将请求转接到具体redis,将结果再返回twemproxy。使用方式简便(相对redis只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

2.codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。

3.redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。

4.在业务代码层实现。


Redis集群方案什么情况下会导致整个集群不可用?

有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。


MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。


Redis有哪些适合的场景?

缓存

  • 原生字符串:简单直观。但占用过多的键,内存占用较大,信息内聚性比较差,数据块大不推荐。
  • 序列化字符串:简化编程,合理使用可以提高内存的使用效率。但序列化和反序列化有一定的开销。
  • 哈希:简单直观,如果使用合理可以减少内存空间的使用。但要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

自增ID

用来处理相关的发号业务,例如分布式业务场景中的的自增ID,短链系统的id发号器等。

计数器

  • 动态计数:例如 点赞数,评论数,收藏数,浏览数 等。
  • 静态计数: 多维度的数据统计,例如每月,每周,每时的一些动态数据。也可跨语言取数,例如c++语言通过自定义规则统计定点数据将结果存入redis,然后通过php去取数。如果你有elk等一些成熟且部门推荐的技术栈,那也是极好的的。

session共享

分布式结构下,将用户的Session进行集中管理,每次用户更新或查询登陆信息都直接从Redis中获取。

session集中式管理,不受应用数量影响。很容易实现单点登入登出。

限速或者频率访问限制

例如: set u100:code 123456 ex 600

验证码,邮件等接口访问频率限制指定描述,防止接口被频繁访问。

也可以用来做简单的api限速(INCR + EXPIRE

分布式锁

分布式环境,不同的系统或同一个系统的不同主机之间共享了资源时,就需要互斥来防止彼此干扰来保证一致性。

例如单机多进程消费task,布式多任务处理等。

消息队列

redis实现消息队列与专有的MQ消息中间件相比,没有高级特性,ACK保证等。

但在日常的简单业务中,异步处理,应用层解耦,消息通讯,redis都是可以满足。

  • pub/sub,订阅/发布模式
  • 基于stream类型的实现(redis 5.0+)
  • lpush+rpop / rpush+lpop / lpush + brpop = Message Queue(消息队列)

当然list还提供其他的场景,自行选择

  • lpush + lpop = Stack(栈)
  • lpush + rpop = Queue(队列)
  • lpush + ltrim = Capped Collection(有限集合)

独立数据统计

利用集合当中元素不唯一性,可以快速实时统计唯一性的数据。例如:统计网站的独立IP。

SADD ip 192.168.1.100 
SADD ip 192.168.1.101
SADD ip 192.168.1.102
SADD ip 192.168.1.100
SADD ip 192.168.1.101


128.127.0.0.1:6379> SMEMBERS ip
1) "192.168.1.100"
2) "192.168.1.102"
3) "192.168.1.101"

交集数据统计

共同好友列表,共同爱好等。例如统计喜欢的编程语言。

sadd u:1 php java golang 
sadd u:2 java php golang
sadd u:3 java php node

127.0.0.1:6379> sinter u:1 u:3
1) "java"
2) "php"

权重排行

有序集合的元素都会关联一个score,所以我们可以将其使用在一些权重场景。

例如排行榜,积分榜。甚至瀑布流,产品推荐,广告排行等。

基数统计

这里的技术统计和上面的唯一性统计是有不同的。
这里的基数统计,是利用Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

  • 消耗空间极小,支持海量的数据统计。
  • 统计的结果是一个带有 0.81% 标准错误(standard error)的近似值。
  • 每个hyperloglog key占用了12K的内存用于标记基数
  • redis 对 HyperLogLog 的存储进行了优化,在计数比较小时,它的存储空间采用稀疏矩阵存储,空间占用很小,仅仅在计数慢慢变大,稀疏矩阵占用空间渐渐超过了阈值时才会一次性转变成稠密矩阵,才会占用 12k 的空间
pfadd access_ip '192.168.1.200' '192.168.1.201' '192.168.1.220' '192.168.1.203'

pfcount access_ip
(integer) 4

地理位置应用

Redis 在 3.2 版本实现了一个地理位置计算的特性。例如测量两个城市的距离。

geoadd cn_city 116.405285 39.904989 beijing
geoadd cn_city 121.472644 31.231706 shanghai

geodist cn_city beijing shanghai km
"1067.5980"

发布/订阅

pub/sub 是 Redis 内置的一个非常强大的特性。

发布订阅模式。改变了以往的pull模型,改为服务端主动push,客户端进行订阅消费。

我们可根据这个特性,创建实时的聊天系统,系统通知。当然也可以做通讯模型,来规划系统架构。


Redis如何设置密码及验证密码?

设置密码:config set requirepass 123456

授权密码:auth 123456


Redis集群的主从复制模型是怎样的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.


Redis集群会有写操作丢失吗?为什么?

Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。


Redis集群之间是如何复制的?

异步复制


Redis集群最大节点个数是多少?

16384个


Redis集群如何选择数据库?

Redis集群目前无法做数据库选择,默认在0数据库。


怎么测试Redis的连通性?

ping


Redis中的管道有什么用?

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。


怎么理解Redis事务?

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。


Redis事务相关的命令有哪几个?

MULTI、EXEC、DISCARD、WATCH


Redis key的过期时间和永久有效分别怎么设置?

EXPIRE和PERSIST命令。


Redis回收进程如何工作的?

客户端运行了新的命令,添加了新的数据。Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。


Redis回收使用的是什么算法?

LRU算法


Redis如何做大量数据插入?

Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。


为什么要做Redis分区?

分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。


你知道有哪些Redis分区实现方案?

  • 客户端分区:客户端就决定数据会被存储到哪个redis节点或者从哪个redis节点读取。
  • 代理分区 :客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
  • 查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由。

Redis分区有什么缺点?

  • 涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
  • 同时操作多个key,则不能使用Redis事务.
  • 分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集
  • 当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。
  • 分区时动态扩容或缩容可能非常复杂。

Redis持久化数据和缓存怎么做扩容?

如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。

如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。


分布式Redis是前期做还是后期规模上来了再做好?为什么?

既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。

一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。

这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。

主要还是要根据数量增长速度进行一个前期评估。


Twemproxy是什么?

  • Twemproxy是Twitter维护的(缓存)代理系统,代理Memcached的ASCII协议和Redis协议。
  • 它是单线程程序,使用c语言编写,运行起来非常快。
  • 它是采用Apache 2.0 license的开源软件。
  • Twemproxy支持自动分区,如果其代理的其中一个Redis节点不可用时,会自动将该节点排除
  • Twemproxy本身不存在单点问题,因为你可以启动多个Twemproxy实例,然后让你的客户端去连接任意一个Twemproxy实例。
  • Twemproxy是Redis客户端和服务器端的一个中间层。

支持一致性哈希的客户端有哪些?

Redis-rb、Predis等。


Redis与其他key-value存储有什么不同?

  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作。
  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。
  • Redis在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

都有哪些办法可以降低Redis的内存使用情况呢?

  • 采用ziplist压缩列表(列表、散列、有续集和)
  • 进行分片,提高系统负载能力
  • 将信息打包转换成字节存储
  • 进行配置优化

查看Redis使用情况及状态信息用什么命令?

info


Redis的内存用完了会发生什么?

如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将Redis当缓存来使用配置淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。


Redis是单线程的,如何提高多核CPU的利用率?

可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。


一个Redis实例最多能存放多少的keys?List、Set、Sorted Set他们最多能存放多少元素?

理论上Redis可以处理多达232的keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。

任何list、set、和sorted set都可以放232个元素。

换句话说,Redis的存储极限是系统中的可用内存值。


Redis常见性能问题和解决方案?

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  • 尽量避免在压力很大的主库上增加从库
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。


Redis提供了哪几种持久化方式?

Redis 提供两种持久化机制 RDB 和 AOF 机制:

持久化之 RDB (Redis Database)

在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)

优点

  • 体积小:RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。适合备份。
  • 灾难恢复(disaster recovery)快:它只有一个文件,内容都非常紧凑,可以(在加密后)将它传送到别的数据中心。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
  • 性能高:父进程在保存rdb时候只需要fork一个子进程,无需父进程的进行其他io操作,也保证了服务器的性能。

缺点

  • 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障, 会发生数据丢失。
  • 每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端。

AOF (Append-only file)

所有的命令行记录以 redis 命令请求协议的格式完全持久化存储)保存为 aof 文件。 fsync 策略,AOF 的速度可能会慢于 RDB

优点:

1、数据安全, aof 持久化可以配置 appendfsync 属性, 有 always, 每进行一次命令操作就记录到 aof 文件中一次。

2、通过 append 模式写文件, 即使中途服务器宕机, 可以通过 redis-check-aof 工具解决数据一致性问题。

3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前( 文件过大时会对命令进行合并重写), 可以删除其中的某些命令( 比如误操作的 flushall))

缺点:

1、AOF 文件比 RDB 文件大, 且恢复速度慢。

2、数据集大的时候, 比 rdb 启动效率低。


如何选择合适的持久化方式?

一般来说, 如果想达到足以媲美PostgreSQL的数据安全性, 你应该同时使用两种持久化功能。如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。

有很多用户都只使用AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外, 使用RDB还可以避免之前提到的AOF程序的bug。


修改配置不重启Redis会实时生效吗?

针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 CONFIG GET *命令获取更多信息。

但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。

参考

降低Redis内存占用
https://www.cnblogs.com/Infernal/p/11231966.html
REDIS持久化之RDB和AOF的区别
https://www.jianshu.com/p/1d9ab6bc0835
50道Redis面试题
https://juejin.im/post/5c9b344c51882530c6308b32

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容