Redis入门教程(二)

推荐阅读:

Redis入门教程(一)https://www.cnblogs.com/jichi/p/10285346.html

5. Redis 的数据结构

5.1 Redis 数据结构介绍

redis是一种高级的key-value的存储系统, 其中value支持五种数据类型。

1、字符串(String)

2、哈希(hash)

3、字符串列表(list)

4、字符串集合(set)

5、有序字符串集合(sorted set)

而关于key 的定义呢,需要大家注意的几点:

1key 不要太长, 最好不要操作1024 个字节,这不仅会消耗内存还会降低查找效率

2key 不要太短,如果太短会降低key 的可读性

3在项目中, key 最好有一个统一的命名规范

5.2 存储string

5.2.1 概述

字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型在存入和获取的数据相同。在Redis中字符串类型的Value最多可以容纳的数据长度为512M。

5.2.2 常用命令

5.2.2.1 赋值

• set key value:设定key 持有指定的字符串value,如果该key 存在则进行覆盖操作。总是返回ok。

5.2.2. 2 取值

• get key :获取key 的value 。如果与该key 关联的value 不是String 类型,redis将返回错误信息,因为get 命令只能用于获取String value;如果该key 不存在,返回(nil)。

• getset key value:先获取该key 的值,然后在设置该key 的值。

5.2.2.3 删除

• del key:删除指定key

5.2.2.4 数值增减

• incr key :将指定的key 的value 原子性的递增1.如果该key 不存在,其初始值为0 ,在incr之后其值为1 。如果value的值不能转成整型,如hello,该操作将执行失败井返回相应的错误信息。

• decr key : 将指定的key 的value 原子性的递减1.如果该key 不存在,其初始值为0 , 在decr 之后其值为-1 。如果value的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息。

5.2.2.5其他命令

• incrby key increment :将指定的key 的value 原子性增加increment,如果该key 不存在,器初始值为0 , 在incrby 之后,该值为increment 。如果该值不能转成整型,如hello 则失败井返回错误信息。

• append key value:拼凑字符串。如果该key存在,则在原有的value 后追加该值;如果该key 不存在,则重新创建一个key/value

5.3 存储hash

5.3.1 概述

Redis中的Hash 类型可以看成具有String Key 和String Value 的map 容器。所以该类型非常适合于存储值对象的信息。如UsernamePassword 和Age 等。如果Hash 中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash 可以存储4294967295 个键值对。

5.3.2 常用命令

5.3.2.1 赋值

• hset key field value:为指定的key设定field/value对(键值对)。

• hmset key field value [field2 value2 ... ]:设置key中的多个filed/value。

5.3.2.2 取值

• hget key field:返回指定的key中的field的值

• hmget key fileds:获取key 中的多个filed 的值

• hgetall key:获取key 中的所有filed-value

5.3.2.3 删除

• hdel key field [field …]:可以删除一个或多个字段,返回值是被删除的字段个数

• del key:删除整个 list

5.3.2.4 增加数字

• hincrby key field increment:设置key 中filed 的值增加increment,如:age增加20

5.3.3 自学命令

• hexists key field:判断指定的key 中的filed 是否存在

• hlen key:获取key 所包含的field 的数量

• hkeys key :获得所有的key

• hvals key:获得所有的value

5.4 存储list

5.4.1 概述

在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在, Red is将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

List 中可以包含的最大元素数量是4294967295 。

从元素插入和删除的效率视角来看, 如果我们是在链表的两头插入或删除元素, 这将会是非常高效的操作,即使链表中己经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是, 如果元素插入或删除操作是作用于链表中间, 那将会是非常低效的。相信对于有良好数据结构基础的开发者而言,这一点并不难理解。

1ArrayList使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要涉及到位移操作,所以比较慢。

2LinkedList 使用双向链接方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快,然后通过下标查询元素时需要从头开始索引,所以比较慢。

5.4.2 常用命令

5.4.2.1 两端添加

• lpush key values[valuel value2 ... ]:在指定的key 所关联的list 的头部插入所有的values,如果该key 不存在,该命令在插入之前创建一个与该key 关联的空链表,之后再向该链表的头部插入数据。插入成功,返回元素的个数。

• rpush key values[valuelvalue2... ]:在该list 的尾部添加元素。

5.4.2.2 查看列表

• lrange key start end : 获取链表中从start 到end 的元素的值,start、end 从0 开始计数;也可为负数,若为-1 则表示链表尾部的元素,- 2则表示倒数第二个,依次类推..

5.4.2.3 两端弹出

• lpop key:返回并弹出指定的key 关联的链表中的第一个元素, 即头部元素。如果该key 不存在,返回nil;若key 存在,则返回链表的头部元素。

• rpop key:从尾部弹出元素。

5.4.2.4 获取列表中元素的个数

• llen key:返回指定的key 关联的链表中的元素的数量。

5.4.3 扩展命令(了解)

• lpushx key value:仅当参数中指定的key 存在时,向关联的list的头部插入value。如果不存在,将不进行插入。

• rpushx key value:在该list的尾部添加元素

• lrem key count value:删除count个值为value的元素,如果count 大于0,从头向尾遍历并删除count 个值为value 的元素,如果count 小于0,则从尾向头遍历并删除。如果count 等于0,则删除链表中所有等于value 的元素。

• lset key index value:设置链表中的index的脚标的元素值,0代表链表的头元素,-1代表链表的尾元素。操作链表的脚标不存在则抛异常。

• linsert key before I after pivot value : 在pivot 元素前或者后插入value 这个元素。

• rpoplpush resource destination:将链表中的尾部元素弹出井添加到头部。[循环操作]

5.4.4 使用场景

rpoplpush的使用场景:

Redis链表经常会被用于消息队列的服务,以完成多程序之间的消息交换。假设一个应用程序正在执行LPUSH操作向链表中添加新的元素,我们通常将这样的程序称之为"生产者(Producer)",而另外一个应用程序正在执行RPOP 操作从链表中取出元素,我们称这样的程序为"消费者(Consumer)"。如果此时,消费者程序在取出消息元素后立刻崩溃,由于该消息已经被取出且没有被正常处理,那么我们就可以认为该消息己经丢失,由此可能会导致业务数据丢失,或业务状态的不一致等现象的发生。然而通过使用RPOPLPUSH 命令,消费者程序在从主消息队列中取出消息之后再将其插入到备份队列中,直到消费者程序完成正常的处理逻辑后再将该消息从备份队列中删除。同时我们还可以提供一个守护进程,当发现备份队列中的消息过期时,可以重新将其再放回到主消息队列中,以使其它的消费者程序继续处理。

5.5存储set

5.5.1 概述

在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。需要说明的是,这些操作的时间复杂度为O(1),即常量时间内完成操作。Set可包含的最大元素数量是4294967295。

和List 类型不同的是, Set集合中不允许出现重复的元素,这一点和C++标准库中的set容器是完全相同的。换句话说,如果多次添加相同元素, Set中将仅保留该元素的一份拷贝。和List 类型相比, Set 类型在功能上还存在着一个非常重要的特性,即在服务器端完成多个Sets之间的聚合计算操作,如unions、intersections 和differences。由于这些操作均在服务端完成,因此效率极高,而且也节省了大量的网络IO开销。

5.5.2 常用命令

5.5.2.1 添加/删除元素

• sadd key values[value1、value2 …]:向set中添加数据,如果该key 的值己有则不会重复添加

• srem key members [member1、member2...]:删除set 中指定的成员

5.5.2.2 获得集合中的元素

• smembers key:获取set 中所有的成员

• sismember key member:判断参数中指定的成员是否在该set中,1表示存在,0示不存在或者

该key 本身就不存在。(无集合中有多少元素都可以极速的返回结果)

5.5.2.3 集合的差集运算A-B

• sdiff key1 key2:返回key1与key2中相差的成员,而且与key的顺序有关。即返问空值。

5.5.2.4 集合的交集运算A n B

• sinter key1 key2 key3 . . .:返回交集。

5.5.2.5 集合的并集运算A U B

• sunion key1 key2 key3 . . . :返回并集。

5.5.3 扩展命令( 了解)

• scard key:获取set 中成员的数量

• srandmember key:随机返回set 中的一个成员

• sdiffstore destination key1 key2 ...:将key1、key2相差的成员存储在destination 上

• sinterstore destination key[key ...]:将返回的交集存储在destination 上

• sunionstore destination key[key ...]:将返回的并集存储在destination 上

5.5.4 使用场景

1、可以使用Redis的Set数据类型跟踪一些唯一性数据,比如访问某一博客的唯一IP 地址信息。对于此场景,我们仅需在每次访问该博客时将访问者的IP 存入Red is 中, Set 数据类型会自动保证IP

地址的唯一性。

2、充分利用Set 类型的服务端聚合操作方便、高效的特性,可以用于维护数据对象之间的关联关系。

比如所有购买某一电子设备的客户ID被存储在一个指定的Set 中,而购买另外一种电子产品的客户ID被存储在另外一个Set 中,如果此时我们想获取有哪些客户同时购买了这两种商品时,Set 的intersections命令就可以充分发挥它的方便和效率的优势了。

5.6 存储sortedset

5.6.1 概述

Sorted-Set和Set 类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Set 中的每一个成员都会有一个分数(score)与之关联, Redis正是通过分数来为集合中的成员进行从小到大的排序。然而需要额外指出的是, 尽管Sorted-Set 中的成员必须是唯一的,但是分数(score)却是可以重复的。

在Sorted-Set 中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合中成员数量的对数。由于Sorted-Set 中的成员在集合中的位置是有序的,因此,即便是访问位于集合中部的成员也仍然是非常高效的。事实上,Redis所具有的这一特征在很多其它类型的数据库中是很难实现的,换句话说,在该点上要想达到和Red is 同样的高效,在其它数据库中进行建模是非常困难的。

例如: 游戏排名、微博热点话题等使用场景。

5.6.2 常用命令

5.6.2.1 添加元素

• zadd key score member score2 member2 ...:将所有成员以及该成员的分数存放到sorted-set 中。

如果该元素己经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数,

不包含之前已经存在的元素。

5.6.2.2 获得元素

• zscore key member:返回指定成员的分数

• zcard key:获取集合中的成员数量

5.6.2.3 删除元素

• zrem key member[member…]:移除集合中指定的成员,可以指定多个成员。

5.6.2.4 范围查询

• zrange key start end [withscores]:获取集合中脚标为start-end 的成员,[with scores]参数表明返回的成员包含其分数。

• zremrangebyrank key start stop : 按照排名范围删除元素

• zremrangebyscore key min max : 按照分数范围删除元素

5.6.3 扩展命令(了解)

• zrangebyscore key min max [withscores] [limit offset count]: 返回分数在[min, max]的成员并按照分数从低到高排序。

[withscores]:显示分数

[limit offset count]: offset表明从脚标为offset的元素开始并返回count 个成员。

• zincrby key increment member:设置指定成员的增加的分数。返回值是更改后的分数。

• zcount key min max : 获取分数在[min,max]之间的成员

• zrank key member:返回成员在集合中的排名。(从小到大)

• zrevrank key member:返回成员在集合中的排名。(从大到小>

5.6.4 使用场景

1、可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD 命令更新玩家的分数,此后再通过ZRANGE 命令获取积分TOPTEN 的用户信息。当然我们也可以利用ZRANK命令通过username 来获取玩家的排行信息。最后我们将组合使用ZRANGE 和ZRANK 命令快速的获取和某个玩家积分相近的其他用户的信息。

2Sorted-Set 类型还可用于构建索引数据。

6. keys 的通用操作

• keys pattern:获取所有与pattern匹配的key,返回所有与该key匹配的keys。*表示任意一个或多个字符,?表示任意一个字符。

• del key1 key2 ...:删除指定的key

• exists key:判断该key是否存在, 1代表存在,0代表不存在

• rename key newkey:为当前的key 重命名

• expire key:设置过期时间,单位:秒

• ttl key:获取该key 所剩的超时时间,如果没有设置超时,返回-1。如果返回表示超时不存在。

• type key:获取指定key 的类型。该命令将以字符串的格式返回。

listsethash 和zset ,如果key 不存在返回none 。

7.Redis 特性

7. 1 多数据库

7.1.1 概念

一个Redis实例可以包括多个数据库, 客户端可以指定连接某个redis实例的哪个数据库, 就好比一个mysql中创建多个数据库,客户端连接时指定连接哪个数据库。

一个redis实例最多可提供16个数据库,下标从0 到15,客户端默认连接第0 号数据库,也可以通过select选择连接哪个数据库,如下连接1 号库:

连接0 号数据库:

7.1.2 将newkey移植到1号库

• move newkey 1 : 将当前库的key 移植到1 号库中

7.2 服务器命令

• ping:测试连接是否存活

//执行下面命令之前,我们停止redis服务器

redis 127.0.0.1:6379> ping

Could not connect to Redis at 127.0.0.1:6379: Connection refused

• echo:在命令行打印一些内容

• select:选择数据库。Redis数据库编号从0~15,可以选择任意一个数据库来进行数据的存取。

当选择16 时,报错,说明没有编号为16的这个数据库

• quit:退出连接

• dbsize:返回当前数据库中key 的数目。

• info:获取服务器的信息和统计。

• flushdb:删除当前选择数据库中的所有key。

• flushall:删除所有数据库中的所有key 。

在本例中我们先查看了一个1号数据库中有一个key,然后我切换到0号库执行flushall命令,结果1 号库中的key 也被清除了,说明命令工作正常。

7.3 消息订阅与发布

• subscribe channel:订阅频道,例:subscribe mychat,订阅mychat 这个频道

• psubscribe channel*:批量订阅频道,例: psubscribe s*,订阅以s开头的频道

• publish channel content:在指定的频道中发布消息,如publish mychat 'today is a newday'

•步骤1:在第一个连接中,订阅mychat频道。此时如果没有人"发布"消息,当前窗口处于等待状态。

•步骤2:在另一个窗口中,在mychat频道中,发布消息。

•步骤3:再第三个窗口,批量订阅以my 开头所有频道。

•步骤4:在第二个窗口,分别在"mychat"和"mychat2"发布消息。

7.4redis 事务

7.4.1 概念

和众多其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制。在Redis中,MU LTI/EXEC/DISCARD/这三个命令是我们实现事务的基石。

7.4.2 redis 事务特征

1、在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务, 从而保证了事物中的所有命令被原子的执行

2、和关系型数据库中的事务相比, 在Redis 事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。

3、我们可以通过MULTI 命令开启一个事务,有关系型数据库开发经验的人可以将其理解为BEGIN TRANSACTION语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK 语句。

4、在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC 命令之后,那么该事务中的所有命令部会被服务器执行。

5当使用Append-Only模式时, Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测, 一旦发现类似问题, 就会立即退出并给出相应的错误提示。

此时,我们就要充分利用Redis工具包中提供的redis-check-aof 工具,该工具可以帮助我们定位到数据不一致的错误,并将己经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

7.4.3 命令解释

• multi:开启事务用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子的执行,类似与关系型数据库中的:begi n transaction

• exec:提交事务,类似与关系型数据库中的:commit

• discard:事务回滚,类似与关系型数据库中的:rollback

7.4.4 测试

7.4.4.1 正常执行事务

• 步骤1:在窗口1,设置num,并设置数据

• 步骤2:在窗口2, num累加1,并获得数据

• 步骤3:在窗口1,获得数据

• 步骤4:在窗口1,开启事务,多次累加数据。

• 步骤5:在窗口2,获得数据

• 步骤6:提交事务

7.4.4.2 回滚

7.4.4.3 失败命令

8.redis持久化

8.1 概述

Redis的高性能是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘中,这一过程就是持久化。

Redis支持两种方式的持久化,一种是RDB方式,一种是AOF方式。可以单独使用其中一种或将二者结合使用。

1、RDB 持久化(默认支持,无需配置)

该机制是指在指定的时间问隔内将内存中的数据集快照写入磁盘。

2、AOF 持久化

该机制将以日志的形式记录服务器所处理的每一个写操作,在Redis服务器启动之初会读取该文件来重新构建数据库,以保证启动后数据库中的数据是完整的。

3、无持久化

我们可以通过配置的方式禁用Redis服务器的持久化功能,这样我们就可以将Redis视为一个功能加强版的memcached了。

4、redis可以同时使用RDB 和AOF

8.2RDB

8.2.1 优势

1、一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24 小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障, 我们可以非常容易的进行恢复。

2、对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上

3、性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork

出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行操

作了。

4、相比于AOF 机制,如果数据集很大,RDB的启动效率会更高。

8.2.2 劣势

1、如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB 将不是一个很好的选择。

因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

2、由于RDB是通过fork 子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1 秒钟

8.2.3 配置说明Snapshotting

8.2.3.1 快照参数设置

• save 900 1 #每900秒(15分钟)至少有1 个key发生变化,则dump 内存快照。

• save 300 10 #每300秒(5分钟)至少有10 个key 发生变化,则dump 内存快照。

• save 60 10000 #每60 秒(1 分钟)至少有10000个key 发生变化,则dump 内存快照。

8.2.3.2 保存位置设置

8.3AOF

8.3.1 优势

1、该机制可以带来更高的数据安全性, 即数据持久性。Red is 中提供了3 中同步策略, 即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现右机现象, 那么这一秒钟之内修改的数据将会丢失。而每修改同步, 我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见, 这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。

2、由于该机制对日志文件的写入操作采用的是append 模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中己经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过red is-check-aof 工具来帮助我们解决数据一致性的问题。

3如果日志过大,Redis可以自动启用rewrite机制。即Redis 以append 模式不断的将修改数据写入到老的磁盘文件中, 同时Redis 还会创建一个新的文件用于记录此期间有哪些修改命令被执行。

因此在进行rewrite 切换时可以更好的保证数据安全性。

4、AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

8.3.2 劣势

1、对于相同数量的数据集而言,AOF文件通常要大于RDB 文件

2、根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB 一样高效。

8.3.3 配置AOF

8.3.3.1 配置信息

• always #每次有数据修改发生时都会写入AOF 文件

• everysec #每秒钟同步一次, 该策略为AOF 的缺省策略

• no #从不同步。高效但是数据不会被持久化

重写AOF :若不满足重写条件时,可以手动重写,命令: bgrewriteaof

策略的选择:

 

8.3.3.2 数据恢复演示

1flushall操作清空数据库

2、及时关闭redis 服务器( 防止dump . rdb )。shutdown nosave

3、编辑aof文件,将日志中的flushall命令删除并重启服务即可

• 步骤1 : 开启a op , 并设置成总是保存。然后重启red is 。

• 步骤2 : 在窗口1 进行若干操作

• 步骤3 : 在窗口1 , 清空数据库

• 步骤4 : 在窗口2 , 关闭redis

• 步骤5 : 修改"appendonly.aof"文件,将最后的命令"flushall"删除

• 步骤6 : 在窗口1 启动redis,然后查询数据库内容

9.redis 使用场景

1、取最新N 个数据的操作

比如典型的取你网站的最新文章,通过下面方式,我们可以将最新的5000 条评论的ID 放在Red is的Li st 集合中, 并将超出集合部分从数据库获取

2排行榜应用,取TOP N 操作

这个需求与上面需求的不同之处在于, 前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set 出马了,将你要排序的值设置成so rted set 的sco re ,将具体的数据设置成相应的va lu e , 每次只需要执行一条ZADD 命令即可。

3需要精准设定过期时间的应用

比如你可以把上面说到的sorted set 的score 值设直成过期时间的时间戳, 那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis 中的过期数据,你完全可以把Redis 里这个过期时间当成是对数据库中数据的索引, 用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。

4、计数器应用

Redis的命令都是原子性的,你可以轻松地利用INCR , DECR 命令来构建计数器系统。

5Uniq操作,获取某段时间所有数据排重值

这个使用Redis 的set数据结构最合适了,只需要不断地将数据往set中扔就行了, set 意为集合, 所以会自动排重。

6、实时系统,反垃圾系统

通过上面说到的set 功能,你可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。

7Pub/Sub 构建实时消息系统

Redis的Pub/Sub 系统可以构建实时的消息系统,比如很多用Pub/Sub构建的实时聊天系统的例子。

8、构建队列系统

使用list 可以构建队列系统,使用sorted set 甚至可以构建有优先级的队列系统。

此为本人学习笔记总结,整合网上资料。希望大家能顺利入门。

posted @ 2019-03-14 08:46  经典鸡翅  阅读(684)  评论(1编辑  收藏  举报