zoukankan      html  css  js  c++  java
  • Redis小结

    一、简单的介绍
    Redis是一个开源的使用C语言编写、开源、支持网络、可基于内存亦可持久化的日志型、高性能的Key-Value数据库,并提供多种语言的API。其支持的数据结构类型有字符串(string)、哈希(hash)、列表(list)、集合(set)和有序集合(zset)。
     
    Redis 与其他 key - value 缓存产品有以下三个特点:
    Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
    Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
    Redis支持数据的备份,即master-slave模式的数据备份。
     
    Redis优势:
    性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
    丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
    原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
    丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
     
    二、部署安装
    下载
    $ wget http://download.redis.io/releases/redis-2.8.19.tar.gz
    解压
    $ tar xzf redis-2.8.19.tar.gz
    $ cd redis-2.8.19
    编译
    $ make
    安装
    $makefile
    Redis 由四个可执行文件:redis-benchmark、redis-cli、redis-server、redis-stat 这四个文件,加上一个redis.conf就构成了整个redis的最终可用包。它们的作用如下:
    
        redis-server:Redis服务器的daemon启动程序
    
        redis-cli:Redis命令行操作工具。当然,你也可以用telnet根据其纯文本协议来操作
    
        redis-benchmark:Redis性能测试工具,测试Redis在你的系统及你的配置下的读写性能
    
        redis-stat:Redis状态检测工具,可以检测Redis当前状态参数及延迟状况
    
    现在就可以启动redis了,redis只有一个启动参数,就是他的配置文件路径。
    
    redis-server /etc/redis.conf
    
    注意,默认复制过去的redis.conf文件的daemonize参数为no,所以redis不会在后台运行,这时要测试,我们需要重新开一个终端。修改为yes则为后台运行redis。另外配置文件中规定了pid文件,log文件和数据文件的地址,如果有需要先修改,默认log信息定向到stdout.
    
    下面是redis.conf的主要配置参数的意义:
    
        daemonize:是否以后台daemon方式运行
    
        pidfile:pid文件位置
    
        port:监听的端口号
    
        timeout:请求超时时间
    
        loglevel:log信息级别
    
        logfile:log文件位置
    
        databases:开启数据库的数量
    
        save * *:保存快照的频率,第一个*表示多长时间,第三个*表示执行多少次写操作。在一定时间内执行一定数量的写操作时,自动保存快照。可设置多个条件。
    
        rdbcompression:是否使用压缩
    
        dbfilename:数据快照文件名(只是文件名,不包括目录)
    
        dir:数据快照的保存目录(这个是目录)
    
        appendonly:是否开启appendonlylog,开启的话每次写操作会记一条log,这会提高数据抗风险能力,但影响效率。
    
        appendfsync:appendonlylog如何同步到磁盘(三个选项,分别是每次写都强制调用fsync、每秒启用一次fsync、不调用fsync等待系统自己同步)
    
    这时你可以打开一个终端进行测试了,配置文件中默认的监听端口是6379。
     
    备注:
    Redis支持两种数据持久化方式:RDB方式和AOF方式。前者会根据配置的规则定时将内存中的数据持久化到硬盘上,后者则是在每次执行写命令之后将命令记录下来。两种持久化方式可以单独使用,但是通常会将两者结合使用。
    save seconds updates,save配置,指出在多长时间内,有多少次更新操作,就将数据同步到数据文件。这个可以多个条件配合,比如默认配置文件中的设置,就设置了三个条件。
     
    appendonly yes/no ,appendonly配置,指出是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面的save条件来同步的,所以有的数据会在一段时间内只存在于内存中。
     
    appendfsync no/always/everysec ,appendfsync配置,no表示等操作系统进行数据缓存同步到磁盘,always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次。
    三、五种数据类型
    1、字符串类型
    字符串是Redis中最基本的数据类型,它能够存储任何类型的字符串,包含二进制数据。可以用于存储邮箱,JSON化的对象,甚至是一张图片,一个字符串允许存储的最大容量为512MB(未来可能没有该限制)。字符串是其他四种类型的基础,与其他几种类型的区别从本质上来说只是组织字符串的方式不同而已。
     
    SET 赋值,用法: SET key value
    GET 取值,用法: GET key
    INCR 递增数字,仅仅对数字类型的键有用,相当于Java的i++运算,用法: INCR key
    INCRBY 增加指定的数字,仅仅对数字类型的键有用,相当于Java的i+=3,用法:INCRBY key increment,意思是key自增increment,increment可以为负数,表示减少。
    DECR 递减数字,仅仅对数字类型的键有用,相当于Java的i–-,用法:DECR key
    DECRBY 减少指定的数字,仅仅对数字类型的键有用,相当于Java的i-=3,用法:DECRBY key decrement,意思是key自减decrement,decrement可以为正数,表示增加。
    INCRBYFLOAT 增加指定浮点数,仅仅对数字类型的键有用,用法:INCRBYFLOAT key increment
    APPEND 向尾部追加值,相当于Java中的”hello”.append(“ world”),用法:APPEND key value
    STRLEN 获取字符串长度,用法:STRLEN key
    MSET 同时设置多个key的值,用法:MSET key1 value1 [key2 value2 ...]
    MGET 同时获取多个key的值,用法:MGET key1 [key2 ...]
     
    2、哈希类型
    哈希类型相当于Java中的HashMap,他的值是一个字典,保存很多key,value对,每对key,value的值个键都是字符串类型,换句话说,哈希类型不能嵌套其他数据类型。一个哈希类型键最多可以包含2的32次方-1个字段。
     
    HSET 赋值,用法:HSET key field value
    HMSET 一次赋值多个字段,用法:HMSET key field1 value1 [field2 values]
    HGET 取值,用法:HSET key field
    HMGET 一次取多个字段的值,用法:HMSET key field1 [field2]
    HGETALL 一次取所有字段的值,用法:HGETALL key
    HEXISTS 判断字段是否存在,用法:HEXISTS key field
    HSETNX 当字段不存在时赋值,用法:HSETNX key field value
    HINCRBY 增加数字,仅对数字类型的值有用,用法:HINCRBY key field increment
    HDEL 删除字段,用法:HDEL key field
    HKEYS 获取所有字段名,用法:HKEYS key
    HVALS 获取所有字段值,用法:HVALS key
    HLEN 获取字段数量,用法:HLEN key
     
    3、列表类型
     
    列表类型(list)用于存储一个有序的字符串列表,常用的操作是向队列两端添加元素或者获得列表的某一片段。列表内部使用的是双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度是O(1),获取越接近列表两端的元素的速度越快。但是缺点是使用列表通过索引访问元素的效率太低(需要从端点开始遍历元素)。所以列表的使用场景一般如:朋友圈新鲜事,只关心最新的一些内容。借助列表类型,Redis还可以作为消息队列使用。
     
    LPUSH 向列表左端添加元素,用法:LPUSH key value
    RPUSH 向列表右端添加元素,用法:RPUSH key value
    LPOP 从列表左端弹出元素,用法:LPOP key
    RPOP 从列表右端弹出元素,用法:RPOP key
    LLEN 获取列表中元素个数,用法:LLEN key
    LRANGE 获取列表中某一片段的元素,用法:LRANGE key start stop,index从0开始,-1表示最后一个元素
    LREM 删除列表中指定的值,用法:LREM key count value,删除列表中前count个值为value的元素,当count>0时从左边开始数,count<0时从右边开始数,count=0时会删除所有值为value的元素
    LINDEX 获取指定索引的元素值,用法:LINDEX key index
    LSET 设置指定索引的元素值,用法:LSET key index value
    LTRIM 只保留列表指定片段,用法:LTRIM key start stop,包含start和stop
    LINSERT 像列表中插入元素,用法:LINSERT key BEFORE|AFTER privot value,从左边开始寻找值为privot的第一个元素,然后根据第二个参数是BEFORE还是AFTER决定在该元素的前面还是后面插入value
    RPOPLPUSH 将元素从一个列表转义到另一个列表,用法:RPOPLPUSH source destination
     
    4、集合类型
    集合在概念在高中课本就学过,集合中每个元素都是不同的,集合中的元素个数最多为2的32次方-1个,集合中的元素师没有顺序的。
     
    SADD 添加元素,用法:SADD key value1 [value2 value3 ...]
    SREM 删除元素,用法:SREM key value2 [value2 value3 ...]
    SMEMBERS 获得集合中所有元素,用法:SMEMBERS key
    SISMEMBER 判断元素是否在集合中,用法:SISMEMBER key value
    SDIFF 对集合做差集运算,用法:SDIFF key1 key2 [key3 ...],先计算key1和key2的差集,然后再用结果与key3做差集
    SINTER 对集合做交集运算,用法:SINTER key1 key2 [key3 ...]
    SUNION 对集合做并集运算,用法:SUNION key1 key2 [key3 ...]
    SCARD 获得集合中元素的个数,用法:SCARD key
    SDIFFSTORE 对集合做差集并将结果存储,用法:SDIFFSTORE destination key1 key2 [key3 ...]
    SINTERSTORE 对集合做交集运算并将结果存储,用法:SINTERSTORE destination key1 key2 [key3 ...]
    SUNIONSTORE 对集合做并集运算并将结果存储,用法:SUNIONSTORE destination key1 key2 [key3 ...]
    SRANDMEMBER 随机获取集合中的元素,用法:SRANDMEMBER key [count],当count>0时,会随机中集合中获取count个不重复的元素,当count<0时,随机中集合中获取|count|和可能重复的元素。
    SPOP 从集合中随机弹出一个元素,用法:SPOP key
     
    5、有序集合类型
    有序集合类型与集合类型的区别就是他是有序的。有序集合是在集合的基础上为每一个元素关联一个分数,这就让有序集合不仅支持插入,删除,判断元素是否存在等操作外,还支持获取分数最高/最低的前N个元素。有序集合中的每个元素是不同的,但是分数却可以相同。有序集合使用散列表和跳跃表实现,即使读取位于中间部分的数据也很快,时间复杂度为O(log(N)),有序集合比列表更费内存。
     
    ZADD 添加元素,用法:ZADD key score1 value1 [score2 value2 score3 value3 ...]
    ZSCORE 获取元素的分数,用法:ZSCORE key value
    ZRANGE 获取排名在某个范围的元素,用法:ZRANGE key start stop [WITHSCORES],按照元素从小到大的顺序排序,从0开始编号,包含start和stop对应的元素,WITHSCORE选项表示是否返回元素分数
    ZREVRANGE 获取排名在某个范围的元素,用法:ZREVRANGE key start stop [WITHSCORES],和上一个命令用法一样,只是这个倒序排序的。
    ZRANGEBYSCORE 获取指定分数范围内的元素,用法:ZRANGEBYSCORE key min max,包含min和max,(min表示不包含min,(max表示不包含max,+inf表示无穷大
    ZINCRBY 增加某个元素的分数,用法:ZINCRBY key increment value
    ZCARD 获取集合中元素的个数,用法:ZCARD key
    ZCOUNT 获取指定分数范围内的元素个数,用法:ZCOUNT key min max,min和max的用法和5中的一样
    ZREM 删除一个或多个元素,用法:ZREM key value1 [value2 ...]
    ZREMRANGEBYRANK 按照排名范围删除元素,用法:ZREMRANGEBYRANK key start stop
    ZREMRANGEBYSCORE 按照分数范围删除元素,用法:ZREMRANGEBYSCORE key min max,min和max的用法和4中的一样
    ZRANK 获取正序排序的元素的排名,用法:ZRANK key value
    ZREVRANK 获取逆序排序的元素的排名,用法:ZREVRANK key value
    ZINTERSTORE 计算有序集合的交集并存储结果,用法:ZINTERSTORE destination numbers key1 key2 [key3 key4 ...] WEIGHTS weight1 weight2 [weight3 weight4 ...] AGGREGATE SUM | MIN | MAX,numbers表示参加运算的集合个数,weight表示权重,aggregate表示结果取值
    ZUNIONSTORE 计算有序几个的并集并存储结果,用法和14一样,不再赘述。
     
     
    备注:
      Redis为不同数据类型分别提供了一组参数来控制内存使用,Redis中哈希是value内部为一个HashMap,如果该Map的成员数比较少,则会采用类似一维线性的紧凑格式来存储该Map, 即省去了大量指针的内存开销,这个参数控制对应在redis.conf配置文件中下面2项:
    hash-max-zipmap-entries 64 
    hash-max-zipmap-value 512 
    hash-max-zipmap-entries含义是当value这个Map内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即    value内部有64个以下的成员就是使用线性紧凑存储,超过该值自动转成真正的HashMap。
    hash-max-zipmap-value 含义是当 value这个Map内部的每个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。
    以上2个条件任意一个条件超过设置值都会转换成真正的HashMap,也就不会再节省内存了,那么这个值是不是设置的越大越好呢,答案当然是否定的,HashMap的优势就是查找和操作的时间复杂度都是O(1)的,而放弃Hash采用一维存储则是O(n)的时间复杂度,如果成员数量很少,则影响不大,否则会严重影响性能,所以要权衡好这个值的设置,总体上还是最根本的时间成本和空间成本上的权衡。
    同样类似的参数还有:
    list-max-ziplist-entries 512
    说明:list数据类型多少节点以下会采用去指针的紧凑存储格式。
    list-max-ziplist-value 64 
    说明:list数据类型节点值大小小于多少字节会采用紧凑存储格式。
    set-max-intset-entries 512 
    说明:set数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。
     
    四、持久化
    Redis的强大性能很大程度上都是因为所有数据都是存储在内存中的,然而当Redis重启后,所有存储在内存中的数据将会丢失,在很多情况下是无法容忍这样的事情的。所以,我们需要将内存中的数据持久化!典型的需要持久化数据的场景如下:
        将Redis作为数据库使用;
        将Redis作为缓存服务器使用,但是缓存丢失后会对性能造成很大影响,所有缓存同时失效时会造成服务雪崩,无法响应。
     
    Redis支持两种数据持久化方式:RDB方式和AOF方式。前者会根据配置的规则定时将内存中的数据持久化到硬盘上,后者则是在每次执行写命令之后将命令记录下来。两种持久化方式可以单独使用,但是通常会将两者结合使用。当同时使用RDB和AOF时,这种情况下Redis重启时,它会优先使用AOF文件来还原数据集,因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。RDB在恢复大数据集时的速度比AOF的恢复速度快。AOF文件有序地保存了对Redis执行的所有写入操作。
     
    1、RDB方式
    RDB方式的持久化是通过快照的方式完成的。当符合某种规则时,会将内存中的数据全量生成一份副本存储到硬盘上,这个过程称作”快照”,Redis会在以下几种情况下对数据进行快照:
        根据配置规则进行自动快照;
        用户执行SAVE, BGSAVE命令;
        执行FLUSHALL命令;
        执行复制(replication)时。
     
    1)、根据配置自动快照
    Redis允许用户自定义快照条件,当满足条件时自动执行快照,快照规则的配置方式如下:
        save 900 1
        save 300 10
        save 60 10000
    每个快照条件独占一行,他们之间是或(||)关系,只要满足任何一个就进行快照。上面配置save后的第一个参数T是时间,单位是秒,第二个参数M是更改的键的个数,含义是:当时间T内被更改的键的个数大于M时,自动进行快照。比如save 900 1的含义是15分钟内(900s)被更改的键的个数大于1时,自动进行快照操作。
     
    2)、行SAVE或BGSAVE命令
    除了让Redis自动进行快照外,当我们需要重启,迁移,备份Redis时,我们也可以手动执行SAVE或BGSAVE命令主动进行快照操作。
    SAVE命令:当执行SAVE命令时,Redis同步进行快照操作,期间会阻塞所有来自客户端的请求,所以放数据库数据较多时,应该避免使用该命令;
    BGSAVE命令: 从命令名字就能看出来,这个命令与SAVE命令的区别就在于该命令的快照操作是在后台异步进行的,进行快照操作的同时还能处理来自客户端的请求。执行BGSAVE命令后Redis会马上返回OK表示开始进行快照操作,如果想知道快照操作是否已经完成,可以使用LASTSAVE命令返回最近一次成功执行快照的时间,返回结果是一个Unix时间戳。
     
    3)、执行FLUSHALL命令
    当执行FLUSHALL命令时,Redis会清除数据库中的所有数据。需要注意的是:不论清空数据库的过程是否触发 了自动快照的条件,只要自动快照条件不为空,Redis就会执行一次快照操作,当没有定义自动快照条件时,执行FLUSHALL命令不会进行快照操作。
     
    4)、执行复制
    当设置了主从模式时,Redis会在复制初始化是进行自动快照。
     
    快照执行的过程如下:
        Redis使用fork函数复制一份当前进程(父进程)的副本(子进程);
        父进程继续处理来自客户端的请求,子进程开始将内存中的数据写入硬盘中的临时文件;
        当子进程写完所有的数据后,用该临时文件替换旧的RDB文件,至此,一次快照操作完成。
    需要注意的是:
        在执行fork是时候操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略,即fork函数发生的一刻,父进程和子进程共享同一块内存数据,当父进程需要修改其中的某片数据(如执行写命令)时,操作系统会将该片数据复制一份以保证子进程不受影响,所以RDB文件存储的是执行fork操作那一刻的内存数据。所以RDB方式理论上是会存在丢数据的情况的(fork之后修改的的那些没有写进RDB文件)。
        通过上述的介绍可以知道,快照进行时时不会修改RDB文件的,只有完成的时候才会用临时文件替换老的RDB文件,所以就保证任何时候RDB文件的都是完整的。这使得我们可以通过定时备份RDB文件来实现Redis数据的备份。RDB文件是经过压缩处理的二进制文件,所以占用的空间会小于内存中数据的大小,更有利于传输。
        Redis启动时会自动读取RDB快照文件,将数据从硬盘载入到内存,根据数量的不同,这个过程持续的时间也不尽相同,通常来讲,一个记录1000万个字符串类型键,大小为1GB的快照文件载入到内存需要20-30秒的时间。
     
    备注:
    1、动态开启与关闭RDB
    config set save "1 2"
    config set save ""
    2、复制与bgsave都是fork一个子进程,且是将当前进程所占的内存复制一份,故这两个操作要求当前系统的内存必须大于Redis所占用内存的2倍,否则这两个操作执行不了;save是使用当前进程进行文件保存,故Redis会阻塞
     
     
     
    上图中红色部分是执行bgsave之后的日志;而粉色部分是执行save之后的日志;而黄色的是当其新增一个从时的日志;最后选中的部分是当前更改命令符合RDB配置规则后的日志。
     
     
    2、AOF方式
    在使用Redis存储非临时数据时,一般都需要打开AOF持久化来降低进程终止导致的数据丢失,AOF可以将Redis执行的每一条写命令追加到硬盘文件中,这已过程显然会降低Redis的性能,但是大部分情况下这个影响是可以接受的,另外,使用较快的硬盘能提高AOF的性能。
    动态开启与关闭AOF
    config set appendonly yes
    config set appendonly no
     
    如果将所有的命令都写到AOF文件的话,将是一个比较蠢行为,因为前面两个命令会被第三个命令覆盖,所以AOF文件完全不需要保存前面两个文件,事实上Redis确实就是这么做的。删除AOF文件中无用的命令的过程成为”AOF重写”,AOF重写可以在配置文件中做相应的配置,当满足配置的条件时,自动进行AOF重写操作。配置如下:
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb

    修改如:config set auto-aof-rewrite-min-size 1048576
    第一行的意思是,目前的AOF文件的大小超过上一次重写时的AOF文件的百分之多少时再次进行重写,如果之前没有重写过,则以启动时AOF文件大小为依据。
    第二行的意思是,当AOF文件的大小大于64MB时才进行重写,因为如果AOF文件本来就很小时,有几个无效的命令也是无伤大雅的事情。
    这两个配置项通常一起使用。
     
    虽然每次执行更改数据库的内容时,AOF都会记录执行的命令,但是由于操作系统本身的硬盘缓存的缘故,AOF文件的内容并没有真正地写入硬盘,在默认情况下,操作系统会每隔30s将硬盘缓存中的数据同步到硬盘,但是为了防止系统异常退出而导致丢数据的情况发生,我们还可以在Redis的配置文件中配置这个同步的频率:
    # appendfsync always
    appendfsync everysec
    # appendfsync no
    第一行表示每次AOF写入一个命令都会执行同步操作,这是最安全也是最慢的方式;
    第二行表示每秒钟进行一次同步操作,一般来说使用这种方式已经足够;
    第三行表示不主动进行同步操作,这是最不安全的方式。
     
     
    第一个红色标记部分,是将appendonly由no改为yes时,执行的日志
     
    第二个黄色标记部分,是在Redis客户端中执行bgrewriteaof命令时,产生的日志
    大体过程如下:
        1)、redis调用fork ,现在有父子两个进程
        2)、子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
        3)、父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
        4)、当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
        5)、现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
     
    需要注意到是重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
     
    红色标记部分是RDB方式进行持久化,黄色部分是AOF方式进行的持久化。
     
     
    五、主从
     
    我们将一台Redis服务器作主库(Matser),其他三台作为从库(Slave),主库只负责写数据,每次有数据更新都将更新的数据同步到它所有的从库,而从库只负责读数据。这样一来,就有了两个好处:
        读写分离,不仅可以提高服务器的负载能力,并且可以根据读请求的规模自由增加或者减少从库的数量,棒极了;
        数据被复制成了了好几份,就算有一台机器出现故障,也可以使用其他机器的数据快速恢复。
        需要注意的是:在Redis主从模式中,一台主库可以拥有多个从库,但是一个从库只能隶属于一个主库。
     
    动态配置主从
    新增从:slaveof ip port
    将从提升为主:slaveof no one
     
      配置好slave服务器连接的master后,slave会建立和master的连接,然后发送sync命令。无论是第一次同步建立的连接还是连接断开后的重新连接,master都会启动一个后台进程,将数据库快照保存到文件中.同时master主进程会开始收集新的写命令并缓存起来。当后台进程完成写文件后,master就将快照文件发送给slave,slave将文件保存到磁盘上,然后加载到内存将数据库快照恢复到slave上。slave完成快照文件的恢复后,master就会把缓存的命令都转发给slave,slave更新内存数据库。后续master收到的写命令都会通过开始建立的连接发送给slave。从master到slave的同步数据的命令和从 client到master发送的命令使用相同的协议格式。当master和slave的连接断开时,slave可以自动重新建立连接。如果master同时收到多个slave发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给所有slave。
    具体的主从,可以看Redis 主从配置
     
    参考:
     
     
  • 相关阅读:
    leetcode 70 Climbing Stairs
    leetcode 203 Remove Linked List Elements
    【HMM】
    【设计总结】
    【docker】导出导入容器
    【设计工具】样机图
    【设计细节】apple sound
    【产品分析】盒马生鲜 套路
    【喂到嘴经济】这个词有点意思
    【需求分类】KANO模型
  • 原文地址:https://www.cnblogs.com/xiaoxian1369/p/5248109.html
Copyright © 2011-2022 走看看