• redis入门(一)(转载)



    redis入门(一)

    前言

    1. Redis是什么? redis是一种基于键值对(key-value)的NoSQL数据库。Redis会将所有数据都存放在内存中,所以它的读写性能非常惊人。不仅如此,Redis还可以将内存的数据利用快照和日志的形式保存到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据不会“丢失”。Redis还提供了键过期、发布订阅、事务、流水线、Lua脚本等附加功能。
    2. Redis能做什么
      在谈为什么需要redis之前,先要清楚redis可以做什么。
    • 缓存。通过引入缓存加快数据的访问速度,降低后端数据源的压力。
    • 排行榜。redis提供给列表和有序几何数据结构可以很方便的构建各种排行榜系统。
    • 计数器。redis原生支持高性能的计数功能,可以为视频播放量、网页浏览数等提供支持。
    • 消息队列。redis提供发布订阅功能。

    特性

    我们为什么选择redis?

    速度快

    官方给出的读写速度可以达到10W/s,以下是我本机双核四线程低压i7上测试的对字符串的读写速度。

    C:UsersDm_ca> redis-benchmark -n 100000  -t set,get -q -a test1
    SET: 11993.28 requests per second
    GET: 57603.69 requests per second
    

    若使用redis管道技术可以得到更高的读写速度

    C:UsersDm_ca> redis-benchmark -n 100000 -t set,get -q -a test1 -P 2
    SET: 19466.62 requests per second
    GET: 133155.80 requests per second
    Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。

    下表是谷歌公司给出的各层级硬件执行速度,内存的响应速度是100ns,redis将数据全部从内存加载可以更快的读写数据。
    20191024172138.png_www.wityx.com

    简单稳定

    早期版本代码在2W行左右,3.0添加了集群等特性增值5W行。相比其他NoSQL 数据库来说代码量少得多。

    丰富的功能

    支持发布订阅、持久化、集群及管道等其他常用的功能。

    历史

    2008年,Redis的作者Salvatore Sanfilippo在开发一个叫LLOOGG的网站时,需要实现一个高性能的队列功能,最开始是使用MySQL来实现的,但后来发现无论怎么优化SQL语句都不能使网站的性能提高上去,于是他决定自己做一个专属于LLOOGG的数据库,这个就是Redis的前身。后来,Salvatore Sanfilippo将Redis1.0的源码开放到GitHub上,可能连他自己都没想到,Redis后来如此受欢迎。

    历史版本

    Redis借鉴了Linux操作系统对于版本号的命名规则:版本号第二位如果是奇数,则为非稳定版本(例如2.7、2.9、3.1),如果是偶数,则为稳定版本(例如2.6、2.8、3.0、3.2)。当前奇数版本就是下一个稳定版本的开发版本。

    1. Redis 2.6
      Redis 2.6在2012年正式发布,经历了17个版本,到 2.6.17版本,相比于Redis2.4,主要特性如下:
      • 服务端支持Lua脚本。
      • 从节点提供只读功能。
      • 重构了大量的核心代码,所有集群相关的代码都去掉了,cluster功能将会是3.0版本最大的亮点。
      • 其他若干修复与优化
    2. Redis 2.8
      Redis2.8在2013年11月22日正式发布,经历了24个版本,到2.8.24版本,相比于Redis2.6,主要特性如下:
      • 添加部分主从复制的功能,在一定程度上降低了由于网络问题,造成频繁全量复制生成RDB对系统造成的压力。
      • 可以通过config set命令设置maxclients。
      • 可以用bind命令绑定多个IP地址。
      • configre write命令可以将config set持久化到Redis配置文件中。
      • 其他若干修复与优化
    3. Redis 3.0
      • Redis Cluster:Redis的官方分布式实现。
      • 全新的embedded string对象编码结果,优化小对象内存访问,在特定的工作负载下速度大幅提升。
      • config set设置maxmemory时候可以设置不同的单位单位(之前只能是字节)
      • incr命令性能提升。
      • 其他若干修复与优化
    4. Redis 3.2
      • 新的List编码类型:quicklist。
      • 从节点读取过期数据保证一致性。
      • 新的RDB格式,但是仍然兼容旧的RDB。
      • 加速RDB的加载速度。
      • 其他若干修复与优化
    5. Redis 4.0
      • 提供了模块系统,方便第三方开发者拓展Redis的功能
      • PSYNC2.0:优化了之前版本中,主从节点切换必然引起全量复制的问题。
      • 提供了RDB-AOF混合持久化格式,充分利用了AOF和RDB各自优势。
      • Redis Cluster兼容NAT和Docker。
      • 其他若干修复与优化

    更多细节可以查看Redis版本历史介绍

    安装与启动

    redis编译后,有许多可执行文件,我们先了解一下各个文件的用途。在windows版本和官方redis版的文件都是差不多的。

    可执行文件 作用
    redis-server 启动redis服务
    redis-cli redis命令行客户端
    redis-benchmark redis基准测试工具
    redis-check-aof redis AOF持久化文件检测和修复工具
    redis-check-dump redis RDB持久化文件检测和修复工具
    redis-sentinel 启动redis哨兵服务

    windows版本是没有redis-sentinel,在启动的时候可以通过--sentinel参数以哨兵模式启动。

    安装

    windows版本

    1. 下载

      Redis官方并不支持Windows操作系统,但是Redis作为一款优秀的开源技术吸引到了微软的注意,微软的开源技术组在GitHub上维护一个Redis的分支
      目前在windows版本最新的redis是3.2.100,可以到这里下载

      下载的压缩文件内容如下图所示。
      1.png_www.wityx.com

      windows版本的redis可以以2种方式运行,一种是通过cmd命令框启动redis服务进程。另一种是将redis安装为windows服务,并以windows服务运行。

      .config是redis配置文件。在服务安装的时候我们可以指定配置文件,若没有指定,则使用redis默认配置。

    2. 直接启动

      通过redis-server 配置名可以直接启动redis服务。
      20191029092007.png_www.wityx.com

    3. 安装服务

      生产环境建议将redis安装为windows服务,避免cmd框不小心被关掉。
      打开cmd窗口,通过redis-server --service-install 配置文件路径 --service-name 服务名安装服务。如redis-server --service-install redis.windows-service.conf --service-name redis-test
      若配置格式没有问题,安装成功后再windows服务中则会有名为redis-test的服务。

      安装windows服务时必须指定配置文件
      若没有指定服务名,则使用Redis作为默认的服务名称。

    4. 启动服务

      通过redis-server --service-start --service-name 服务名启动指定的redis服务。

    5. 停止服务

      通过redis-server --service-stop --service-name 服务名停止指定的redis服务。

    6. 卸载

      通过redis-server --service-install --service-name 服务名卸载Redis服务。卸载服务前需要先停止服务。

    linux版本

    1. 下载

      在Linux安装软件通常有两种方法,第一种是通过各个操作系统的软件管理软件进行安装,例如CentOS有yum管理工具,Ubuntu有 apt。但是由于Redis的更新速度相对较快,而这些管理工具不一定能更新到最新的版本,同时Redis的安装本身不是很复杂,所以一推荐使用第二种方式:源码的方式进行安装,整个安装只需以下四步即可完成。
      1) 下载Redis指定版本的源码压缩包到当前目录。
      2) 解压缩Redis源码压缩包。
      3) 编译(编译之前确保操作系统已经安装gcc)。
      4) 安装。

      wget http://download.redis.io/releases/redis-5.0.5.tar.gz
      tar xzf redis-5.0.5.tar.gz
      cd redis-5.0.5
      make
    2. 编译

      我本机是在windows的Linux子系统上运行,安装的是Ubuntu,windows商店中的ubuntu是最小化安装,因此许多必要的开发包都是没有的,比如make,因此需要安装make,而编译redis源码还要依赖gcc,因此确保自己本地的linux已经安装了gccmake.

      在ubuntu下可以使用sudo apt-get install build-essential安装gcc相关的包,使用sudo apt-get install make安装make包。
      若都安装完成,则可以在redis目录下通过make命令进行源码编译

    3. 启动

      通过src/redis-server启动redis-server,通过src/redis-server 配置名以指定的配置文件启动。若直接启动默认以前台服务进程执行,将会阻塞命令行。修改配置文件daemonize no改为daemonize yes以守护进程的方式执行。

      守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
      可以通过修改配置文件中的port修改绑定指定端口

    4. 客户端连接

      通过src/redis-cli连接redis服务,通过src/redis-server -vsrc/redis-cli -v可以查看redis的版本号。

    数据类型与内部编码

    数据结构

    redis可以保存string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。在后面的版本逐步又添加了Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等数据结构。

    通过type命令可以查看当前键的数据类型。

    127.0.0.1:26379> type key1
    string

    20191029103621.png_www.wityx.com

    内部编码

    通过type返回的仅仅是对外的数据结构,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现。

    redis通过构建简单动态字符串、链表、压缩列表、整数集合、哈希表(字典)、跳跃表等内部编码构造出进行组合实现了各种数据结构,通过这种方式 ,Redis会在合适的场景选择合适的内部编码。从而优化不同场景下的使用效率。

    20191029103659.png_www.wityx.com

    • 如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,需要注意的是long或double类型表示的浮点数在Redis中也是作为字符串值来保存的。如果我们要保存一个浮点数到字符串对象里面,那么程序会先将这个浮点数转换成字符串值,然后再保存转换所得的字符串值。

    • embstr编码是专门用于保存短字符串的一种优化编码方式,embstr编码通过调用一次内存分配函数来分配一块连续的空间,而raw编码会调用两次内存分配函数。同理释放embstr编码的字符串只需要调用一次内存释放函数来分配一块连续的空间,而释放raw编码的字符串会调用两次内存释放函数。

      embstr编码的字符串对象实际上是只读的。当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。因为这个原因,embstr编码的字符串对象在执行修改命令之后,总会变成一个raw编码的字符串对象。

    • Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
    • 压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
    • 链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。
    • 整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。
    • 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

    常用API与使用场景

    常用命令

    1. 查看所有键:keys *

      127.0.0.1:26379> set key1 1
      OK
      127.0.0.1:26379> set key2 2
      OK
      127.0.0.1:26379> set key3 3
      OK
      127.0.0.1:26379> set key22 22
      OK
      127.0.0.1:26379> keys *
      1) "key22"
      2) "key3"
      2) "key2"
      3) "key1"

      该命令还支持模糊查询
      shell 127.0.0.1:26379> keys *2* 1) "key22" 2) "key2"

    2. 键总数:dbsize

      127.0.0.1:26379> dbsize
      (integer) 3
    3. 检查键是否存在:exists key,可以传入多个key,返回存在key的个数

      127.0.0.1:26379> exists key1
      (integer) 1
      127.0.0.1:26379> exists key4
      (integer) 0
      127.0.0.1:26379> exists key1 key2
      (integer) 2
    4. 删除键:del key,可以同时删除多个key,返回成功删除key的数量

      127.0.0.1:26379> del key1 key2
      (integer) 2
    5. 键过期:expire key seconds

      127.0.0.1:26379> expire key3 2
      (integer) 1
    6. 查询键剩余过期时间:ttl key。大于等于0则是键剩余的过期时间,-1表示未设置过期时间。

      127.0.0.1:26379> ttl key3
      (integer) 2
      127.0.0.1:26379> ttl key1
      (integer) -1
    7. 查询redis服务状态:info [section]。可以通过info查询redis所有信息,或者通过info section查询指定的部分信息。

      127.0.0.1:26379> info
      # Server
      redis_version:5.0.6
      redis_git_sha1:00000000
      redis_git_dirty:0
      redis_build_id:6f31570182dc95d9
      redis_mode:standalone
      ...
      # Keyspace
      db0:keys=3,expires=0,avg_ttl=0
      
      127.0.0.1:26379> info keyspace
      # Keyspace
      db0:keys=3,expires=0,avg_ttl=

    字符串

    字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。

    常用API

    1. 设置键set key value [ex seconds] [px milliseconds] [nx| xx]
      • ex seconds:为键设置秒级过期时间。
      • px milliseconds:为键设置毫秒级过期时间。
      • nx:键必须不存在,才可以设置成功,用于添加。
      • xx:与nx相反,键必须存在,才可以设置成功,用于更新。

      由于nx和xx的特性,只有一个客户端能设置成功,因此可以做为分布式锁的一种实现。

    2. 读取键:get key
    3. 批量设置:mset key value [key value ...]
    4. 批量读取:mget key [key ...]

    内部编码

    字符串类型的内部编码有3种:

    • int:8个字节的长整型。
    • embstr:小于等于44个字节的字符串。
    • raw:大于44个字节的字符串。

      redis3.0以前是以39个字符为边界,由于redis3.0对embstr性能做了优化导致长度边界发生变化。详情可以看Redis的embstr与raw编码方式不再以39字节为界了!

    使用场景

    1. 缓存

      20191031120713.png_www.wityx.com

      与关系型数据库不同的是,Redis没有命令空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符)。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用业务名:对象名:id:[属性]作为键名(也可以不是分号)。例如数据库名为vs,用户表名为user,那么对应的键可以用vs:user:1vs:user:1:name来表示,如果当前Redis只被一个业务使用,甚至可以去掉vs:。如果键名比较长,例如user:{uid}:friends:messages:{mid},可以在能描述键含义的前提下适当减少键的长度,例如变为u:{uid}:fr:m:{mid},从而减少由于键过长的内存浪费。

    2. 计数

      通过incr key对计数进行累加,可用于播放量,访问量等场景。

    3. 限速

      通过set 和 incr组合使用实现1分钟内最多发送5条短信。

      127.0.0.1:26379> set 182XXXXXXXX 1 ex 60 nx
      OK
      127.0.0.1:26379> set 182XXXXXXXX 1 ex 60 nx
      (nil)
      127.0.0.1:26379> incr 182XXXXXXXX
      (integer) 2

      可以看到通过nx参数只有在不存在的时候才会设置成功。

    4. 分布式锁

      可以通过set加nx或xx参数实现分布式锁。关于Redis实现分布式锁可以参考Distributed locks with Redis

    列表

    列表(list)类型是用来存储多个有序的字符串,一个列表最多可以存储2^32^-1个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是有序的,且可以插入重复数据。

    常用API

    1. 从右边插入数据:rpush key value [value ...]
    2. 从左边插入数据:lpush key value [value ...]
    3. 从右边弹出数据:rpop key
    4. 从左边弹入数据:lpop key
    5. 从左侧阻塞弹出:blpop key [key ...] timeout
    6. 从右侧阻塞弹出:brpop key [key ...] timeout

      当使用阻塞弹出时,若列表为空,则将会阻塞指定的时间,若传递的timeout为0,会一致阻塞;若个客户端同时阻塞时,有数据插入列表时,先阻塞的先可以获取到值。

    7. 获取指定范围内的元素列表:lrange key start end
    8. 获取列表指定索引下标的元素:lindex key index
    9. 获取列表长度:llen key
    10. 修改指定索引下标的元素:lset key index newValue

    内部编码

    列表类型的内部编码有两种:

    • ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
    • linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。

      Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现

    使用场景

    1. 消息队列
      Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
    • lpush+lpop=Stack(栈)
    • lpush+rpop=Queue(队列)
    • lpsh+ltrim=CappedCollection(有限集合)
    • lpush+brpop=MessageQueue(消息队列)

    哈希

    在Redis中,哈希类型是指键值本身又是一个键值对结构,形如value={{field1,value1},...{fieldN,valueN}}

    常用API

    1. 设置值:hset key field value
    2. 获取指定键的field值:hget key field
    3. 删除指定键的field:hdel key field [field ...]
    4. 计算指定键的field的个数:hlen key
    5. 批量获取:hmget key field [field ...]
    6. 批量设置:hmset key field value [field value ...]
    7. 判断field是否存在:hexists key field
    8. 获取指定键的所有内容:hexists key field

    内部编码

    哈希类型的内部编码有两种:

    • ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
    • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

    使用场景

    1. 缓存关系型数据库的用户信息

      • 关系型数据库保存方式
        20191029163219.png_www.wityx.com

      • hash类型保存方式
        20191029163253.png_www.wityx.com

      需要注意的是,关系型数据库是结构化的,每一列都要为其设置值(即使未空也可能包括NULL或特殊的标识表示NULL),而NOSQL则是稀疏的,有字段的才需要设置值。因此关系型数据库需要占用更大的内存空间。
      Redis不适合去模拟复杂的查询关系。

    2. 保存关系型数据的常用方式。
      • 每个记录每条属性一个键。
        • 优点:简单直观,每个属性都可以更新,互不影响。
        • 缺点:占用了太多的key。查找一个用户的信息比较麻烦,要获取n次key。
      • 将用户序列化后保存到一个键中。
        • 优点:合理的序列化提高redis内存使用效率。
        • 缺点:序列化和反序列化有一定的开销。同时每次更新一个属性都需要获取全部数据反序列化更新后再重新序列化保存到redis。
      • 每个用户字段用对应一个field-value。
        • 优点:简单直观,合理使用可以减少redis内存使用。每个属性互不影响。
        • 缺点:要控制hash的内部编码转换,若使用hashtable,会消耗更多内存。

    集合

    集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。

    常用API

    1. 添加元素:sadd key element [element ...]
    2. 删除元素:srem key element [element ...]

      添加和删除可以对多个元素进行操作,返回的是执行成功的数量。

    3. 计算元素个数:scard key
    4. 计算指定键的field的个数:hlen key
    5. 判断元素是否在集合中:sismember key element
    6. 还有一些集合的操作这里不做具体讲解

    内部编码

    集合类型的内部编码有两种:

    • intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
    • hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

    使用场景

    保存去重后的用户信息,比如IP白名单等

    有序集合

    有序集合它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助我们在实际开发中解决很多问题。

    常用API

    有序集合在集合基础上多了一个分值,并通过分支排序。

    1. 添加成员:zadd key score member [score member ...]。Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:
      • nx:member必须不存在,才可以设置成功,用于添加。
      • xx:member必须存在,才可以设置成功,用于更新。
      • ch:返回此次操作后,有序集合元素和分数发生变化的个数。
      • incr:对score做增加,相当于后面介绍的zincrby。
    2. 计算成员个数:zcard key
    3. 计算 某个 成员 的 分数 zscore key member
    4. 计算 成员 的 排名 zrank key member zrevrank key member
      • zrank是从分数从低到高返回排名。
      • zrevrank是从分数从高到低返回排名。
    5. 删除成员:zrem key member [member ...]
    6. 增加成员的分数:zincrby key increment member
    7. 返回指定排名范围的成员:zrange key start end [withscores]zrevrange key start end [withscores]
    8. 返回指定分数范围的成员:zrangebyscore key min max [withscores] [limit offset count]zrevrangebyscore key max min [withscores] [limit offset count]
    9. 返回指定分数范围成员个数:zcount key min max
    10. 删除指定排名内的升序元素:zremrangebyrank key start end
    11. 删除指定分数范围的成员:zremrangebyscore key min max

    内部编码

    有序集合类型的内部编码有两种:

    • ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
    • skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

    使用场景

    根据某个信息排序,比如一些排行榜功能,外卖的根据距离或综合评分排序等。

    总结

    本节简单介绍了redis历史。同时介绍了redis的安装部署的相关知识,最后介绍了开发常用的一些API和使用场景。


    参考文档

    1. redis
    2. redis开发与运维
    3. Redis版本历史介绍
    4. redis配置文件详解
    5. linux下/var/run目录下.pid文件的作用
    6. Redis的embstr与raw编码方式不再以39字节为界了
    7. Distributed locks with Redis

    本文地址:https://www.cnblogs.com/Jack-Blog/p/11776146.html
    作者博客:杰哥很忙
    欢迎转载,请在明显位置给出出处及链接

  • 相关阅读:
    LinkedList类源码浅析(一)
    ArrayList类源码浅析(三)
    我谁也没等,因为谁也不会来
    维持一段友谊
    最甜美的悲伤
    小美人访谈录笔记[1]
    1984我想对这个世界说些什么
    我喜欢我
    等不来的始终等不来,无须报以希望
    我抱有怀疑
  • 原文地址:https://www.cnblogs.com/qq575654643/p/11777999.html
Copyright © 2020-2023  润新知