锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Redis学习(含 Springboot 整合 Redis)

时间:2022-09-07 12:00:00 k5连接器1x8p连接器ba附带连接器0123连接器

Redis

NoSQL (not only sql)

在现代计算系统中,网络每天都会产生大量的数据。

这些数据的很大一部分是关系数据库管理系统(RDBMS)来处理。 1970年 E.F.Codd’s关系模型论文 “A relational model of data for large shared data banks这使得数据建模和应用程序编程更加简单。

应用实践证明,关系模型非常适合客户服务器编程,远远超出预期利益。今天,它是网络和商业应用中结构化数据存储的主导技术。

NoSQL 这是一项全新的数据库革命性运动,早期有人提出,到2009年趋势越来越高。NoSQL支持者提倡使用非关系数据存储。与铺天盖地的关系数据库相比,这一概念无疑是一种新思维的注入。

什么是NoSQL?

NoSQL,指非关系数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。

NoSQL存储超大规模数据(如谷歌或Facebook每天为用户收集万亿比特的数据)。这些类型的数据存储可以横向扩展,无需固定模式和多余操作。

对于NoSQL没有明确的范围和定义,但它们都有以下共同特征:

易扩展

NoSQL数据库有很多种,但一个共同的特点是去除关系数据库的关系特征。数据之间没有关系,所以很容易扩展。实际上,它带来了可扩展的能力。

大数据量,高性能

NoSQL数据库具有非常高的读写性能,尤其是在大数据量下。由于其无关性,数据库结构简单。MySQL使用Query Cache。NoSQL的Cache是记录级,是细粒度Cache,所以NoSQL在这个层面上,性能要高得多。

数据模型灵活

NoSQL无需事先为要存储的数据建立字段,您可以随时存储自定义的数据格式。在关系数据库中,添加和删除字段是一件非常麻烦的事情。如果它是一个非常大的数据量表,那么添加字段就是一场噩梦。这是大数据量的Web 2.特别是0时代。

高可用

NoSQL在不影响性能的情况下,可以轻松实现高可用架构。Cassandra、HBase模型也可以通过复制模型实现高可用性。

NoSQL优缺点

优点:

  • - 高可扩展性
  • - 分布式计算
  • - 低成本
  • - 架构的灵活性、半结构化数据
  • - 没有复杂的关系

缺点:

  • - 没有标准化
  • - 查询功能有限(到目前为止)
  • - 最终一致性是不直观的程序

NoSQL 数据库分类

键值(Key-Value)存储数据库

这类数据库主要使用哈希表,其中有特定的键和指针指向特定的数据。Key/value模型对于IT系统的优点是简单易部署。但如果数据库管理员是如果(DBA)只查询或更新部分值,Key/value效率低下。举例如:Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB。

存储数据库

这部分数据库通常用于处理分布式存储的大量数据。键仍然存在,但它们的特点是指向多列。这些列由列家族安排。Cassandra, HBase, Riak.

文档数据库

文档数据库的灵感来自Lotus Notes办公软件,类似于第一个键存储。这类数据模型是版本化文档,半结构化文档以特定格式存储,如JSON。文档数据库可以看作是键值数据库的升级版本,允许嵌套键值。文档数据库在处理网页等复杂数据时,查询效率高于传统键值数据库。如:CouchDB, MongoDb. 国内也有文档数据库SequoiaDB,已经开源。

图形(Graph)数据库

图形结构数据库与其他行列和刚性结构相同SQL不同的数据库使用灵活的图形模型,可以扩展到多个服务器。NoSQL数据库中没有标准的查询语言(SQL),因此,需要制定数据模型定数据模型。许多NoSQL数据库都有REST类型的数据接口或查询API。如:Neo4J, InfoGrid, Infinite Graph。

类型 部分代表 特点
列存储 HbaseCassandraHypertable 顾名思义,数据是按列存储的。最大的特点是方便存储结构化和半结构化数据,方便数据压缩,查询一列或几列非常大IO优势。
文档存储 MongoDBCouchDB 类似的文档存储通常用于文档存储json格式存储,存储内容为文档类型。这样,就有机会对某些字段建立索引,实现与数据库相关的某些功能。
key-value存储 Tokyo Cabinet / TyrantBerkeley DBMemcacheDBRedis 可以通过key快速查询value。一般来说,无论存储如何value按单全收格式。(Redis包含其他功能)
图存储 Neo4JFlockDB 图形关系的最佳存储。采用传统关系数据库解决,性能低,设计使用不方便。
对象存储 db4oVersant 通过类似于对象语言的语法操作数据库,通过对象访问数据。
xml数据库 Berkeley DB XMLBaseX 高效的存储XML并支持数据XML内部查询语法,如XQuery,Xpath。

Redis概述

REmote DIctionary Server(Redis) 也就是说,远程字典服务是由 Salvatore Sanfilippo 写的 key-value 存储系统是一个跨平台的非关系数据库,也被称为结构化数据库。

Redis 使用开源 ANSI C 语言编写,遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言 API。

Redis 由于值,通常称为数据结构服务器(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。

Redis 是一个开源(BSD内存中的数据结构存储系统可用作数据库、缓存和信息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 不同层次的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

特点

  • 性能极高 – Redis能读1.1万次/s,写作速度为81000次/s 。
  • 数据类型丰富 – Redis支持二进制案例 Strings, Lists, Hashes, Sets 及 Ordered Sets 操作数据类型。
  • 原子 – Redis所有的操作都是原子的,这意味着要么成功执行,要么完全失败。单个操作是原子的。多个操作还支持事务,即原子,通过MULTI和EXEC包起指令。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等特点。

Redis能干嘛

1、内存存储、持久化,内存中时断电即失,所以说持久化很重要(rdb、aof)

2.高效率可用于高速缓存

3.发布订阅系统

4.地图信息分析

5.计时器、计数器(浏览量)

6、。。。

Windows安装redis

1、github下载压缩包https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100

2、直接解压

3、打开服务端

4、打开客户端测试

在这里插入图片描述

命令 redis-server.exe redis.windows.conf 解决双击闪退问题

Linux安装redis:

下载redis压缩包:

wget https://download.redis.io/releases/redis-6.2.6.tar.gz

解压到对应安装目录:

mkdir /usr/local/redis6

sudo tar -zxvf ~/redis-6.2.6.tar.gz -C /usr/local/redis6

编译

cd到/usr/local/redis6目录,输入命令make执行编译命令,接下来控制台会输出各种编译过程中输出的内容。

make -version 查看make是否安装

如果没有安装make可以使用

sudo apt install -y make

进入安装目录:安装必要的依赖:

sudo apt install -y gcc

sudo apt install -y tcl

**注意:**如果其中下载失败并提示更新,使用:

sudo apt-get update

执行命令:

youxin@youxin-virtual-machine:/usr/local/redis6/redis-6.2.6$ sudo make MALLOC=libc

执行命令:

make test

如果测试全部通过,则证明上一步的make操作准确无误

执行安装命令:

sudo make PREFIX=/usr/local/redis6/redis6/ install

在redis的根目录下有一个配置文件redis.conf,把它考本到安装的redis目录,也就是上面指定的PREFIX文件夹:

sudo cp redis.conf …/redis6/

配置环境变量:

sudo vim /etc/profile

在文件最后加入:

export REDIS_HOME=/usr/local/redis6/redis6/

export PATH= P A T H : PATH: PATH:REDIS_HOME/bin

然后执行:source /etc/profile使之生效

启动redis服务:

redis-server /usr/local/redis6/redis6/redis.conf #如果没有指定配置文件,则会走默认的配置文件

启动redis客户端

redis-cli -p 6379 --raw

关闭redis服务:

shutdown

查看redis端口:

ps -ef |grep redis

强行关闭端口:

kill -9 PID

性能测试

redis-benchmark是一个官方自带的压力测试工具

redis-benchmark测试命令参数:

redis 性能测试工具可选参数如下所示:

序号 选项 描述 默认值
1 -h 指定服务器主机名 127.0.0.1
2 -p 指定服务器端口 6379
3 -s 指定服务器 socket
4 -c 指定并发连接数 50
5 -n 指定请求数 10000
6 -d 以字节的形式指定 SET/GET 值的数据大小 2
7 -k 1=keep alive 0=reconnect 1
8 -r SET/GET/INCR 使用随机 key, SADD 使用随机值
9 -P 通过管道传输 请求 1
10 -q 强制退出 redis。仅显示 query/sec 值
11 –csv 以 CSV 格式输出
12 *-l*(L 的小写字母) 生成循环,永久执行测试
13 -t 仅运行以逗号分隔的测试命令列表。
14 *-I*(i 的大写字母) Idle 模式。仅打开 N 个 idle 连接并等待。

简单测试:

redis-benchmark -h localhost -p 6379 -c 100 -n 100000 本地测试,端口6379,100个并发连接,100000个请求总共

基础知识

redis默认有16个数据库

默认使用的是第0个

可以使用select进行切换数据库

dbsize 查看数据库大小

keys * 查看数据库中所有的key

flushall 清除全部数据库

flushdb 清除当前数据库

注意:redis是单线程的:redis是基于内存操作,CPU并不是Redis的性能瓶颈,redis的瓶颈是根据内存和网络带宽,既然可以使用单线程来实现,就使用单线程了

redis是c语言写的,官方的数据为10W+的QPS,这个不比Memcache差!

为什么redis单线程还这么快:高性能服务器不一定是多线程的,多线程的效率不一定不单线程效率高,redis是将所有数据全部放在内存中的,所以说使用单线程操作效率就是最高的,多线程(CPU会上下文切换:非常耗时),对于内存系统来说,如果没有上下文切换就是效率最高的~多次读写都是在一个CPU上的,在内存情况下,这个就是最佳方案。

五大数据类型

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial)索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key

exists key 判断是否存在该key

move key 1移除key,1代表移除的数据库

EXPIRE name 10 设置过期时间为10秒

ttl name 查看剩余时间

type key 查看key的类型

String(字符串类型)

#############################################################
127.0.0.1:6379> set key1 "hello"  # 设置值
OK
127.0.0.1:6379> get key1  #获取值
"hello"
127.0.0.1:6379> append key1 " world" # 追加值,如果该key不存在则新建一个字符串相当于set key
(integer) 11
127.0.0.1:6379> get key1
"hello world"
127.0.0.1:6379> strlen key1 # 获取值的长度
(integer) 11
##############################################################
127.0.0.1:6379> set views 0 #设置views初始值为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCR views # 设置自增1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 自减1
(integer) 1
127.0.0.1:6379> DECR views
(integer) 0
127.0.0.1:6379> get views
"0"
# 步长 i+=
127.0.0.1:6379> incrby views 10 #设置步长为10并增加
(integer) 10
127.0.0.1:6379> get views
"10"
##############################################################
# 字符串范围range
127.0.0.1:6379> getrange key1 1 4 #截取字符串1-4
"ello"
127.0.0.1:6379> getrange key1 0 -1 # 截取全部的字符串
"hello world"
# 替换setrange
127.0.0.1:6379> setrange key1 0 nihao #从0位置开始替换
(integer) 11
127.0.0.1:6379> get key1
"nihao world“ ############################################################## #setex(set with expire) # 设置过期时间 #setnx(set if not exist) # 不存在再设置(在分布式中会常常使用) 127.0.0.1:6379> setex key2 30 youxin #设置key2并在30秒后过期 OK 127.0.0.1:6379> ttl key2 (integer) 26 127.0.0.1:6379> ttl key2 #查看剩余时间 (integer) 21 127.0.0.1:6379> setnx key3 cloud # 如果key3不存在就创建,如果存在就创建失败 (integer) 1 127.0.0.1:6379> get key3 "cloud" 127.0.0.1:6379> setnx key3 rain (integer) 0 # 再次创建失败 127.0.0.1:6379> keys * 1) "key3" 2) "views" 3) "key1" 127.0.0.1:6379> get key3 # 值没有改变 "cloud" ############################################################## # mset 同时设置多个值 # mget 同时获取多个值 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值 OK 127.0.0.1:6379> keys * 1) "k2" 2) "k3" 3) "k1" 127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> msetnx k3 v3 k5 v5 #如果都不存在就设置多个值, msetnx是一个原子性的操作,要么一起成功,要么一起失败 (integer) 0 127.0.0.1:6379> keys * 1) "k2" 2) "k3" 3) "k1" ############################################################## # 对象 set user:1 {name:zhangsan,age=20} # 设置一个user:1对象,值为json字符来保存一个对象 # 这里的key是一个巧妙的设计:user:{id}:{filed} 127.0.0.1:6379> mset user:1:name zhangsan user:1:age 22 OK 127.0.0.1:6379> mget user:1:name user:1:age 1) "zhangsan" 2) "22" ############################################################## getset #先get再set 127.0.0.1:6379> getset db mysql #如果不存在,则返回nil (nil) 127.0.0.1:6379> get db "mysql" 127.0.0.1:6379> getset db oracle #如果已经存在,返回原来的值并更新新的值 "mysql" 127.0.0.1:6379> get db "oracle"
##############################################################

String类似的使用场景:value除了是字符串还可以是数字

  • 计数器
  • 统计多单位数量
  • 粉丝数
  • 对象缓存存储!

List

在redis可以把list变成栈,队列,阻塞队列

所有的List命令都是以l开头的

##############################################################
127.0.0.1:6379> lpush list1 one # 放置值,并从头部插入
(integer) 1
127.0.0.1:6379> lpush list1 two
(integer) 2
127.0.0.1:6379> lpush list1 three
(integer) 3 
127.0.0.1:6379> lrange list1 0 -1 # 遍历所有
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list1 0 1 # 通过具体的区间得到值
1) "three"
2) "two"
127.0.0.1:6379> rpush list1 four #从队列的尾部插入
(integer) 4
127.0.0.1:6379> lrange list1 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
##############################################################
#LPOP
#RPOP
127.0.0.1:6379> lpop list1 1 #弹出List1的第一个元素
1) "three"
127.0.0.1:6379> rpop list1 #弹出list1的最后一个元素
"four"
127.0.0.1:6379> lrange list1 0 -1
1) "two"
2) "one"
##############################################################
# Lindex
127.0.0.1:6379> lindex list1 0 #获取下标为0的值
"two"
127.0.0.1:6379> lindex list1 1
"one"
127.0.0.1:6379> lpop list1
"two"
127.0.0.1:6379> lpop list1
"one"
127.0.0.1:6379> lindex list1 0 #当队列为空时返回nil
(nil)
##############################################################
#Llen获取队列长度
127.0.0.1:6379> lpush list2 one
(integer) 1
127.0.0.1:6379> lpush list2 two
(integer) 2
127.0.0.1:6379> lpush list2 three
(integer) 3
127.0.0.1:6379> llen list2 #返回列表长度
(integer) 3
##############################################################
#Lrem 移除指定的值,精确匹配
127.0.0.1:6379> lpush list2 three
(integer) 4
127.0.0.1:6379> lrem list2 1 one #移除1个one
(integer) 1
127.0.0.1:6379> lrem list2 2 three #移除两个three
(integer) 2
127.0.0.1:6379> lrange list2 0 -1
1) "two"
##############################################################
# trim 修剪:list 截断
127.0.0.1:6379> lpush list3 value1
(integer) 1
127.0.0.1:6379> lpush list3 value2
(integer) 2
127.0.0.1:6379> lpush list3 value3
(integer) 3
127.0.0.1:6379> lpush list3 value4
(integer) 4
127.0.0.1:6379> ltrim list3 1 2 # 截取下标为1到2的值
OK
127.0.0.1:6379> LRANGE list3 0 -1 #输出list3
1) "value3"
2) "value2"
##############################################################
#rpoplpush 移除列表最后一个元素并添加到另一个元素
127.0.0.1:6379> RPOPLPUSH list3 list4
"value2"
127.0.0.1:6379> lrange list3 0 -1 #查看原来的列表
1) "value3"
127.0.0.1:6379> lrange list4 0 -1 #查看新的列表确实存在弹出来的值
1) "value2"
##############################################################
#lset 指定下标替换值,如果下标不存在则会报错
127.0.0.1:6379> exists list3 #判断是否存在list3
(integer) 1
127.0.0.1:6379> lset list3 0 value4 #替换list3中下标为0的值为value4
OK
127.0.0.1:6379> lrange list3 0 -1 
1) "value4"
##############################################################
# Linsert key after|before pivot value在指定位置之前或之后插入
127.0.0.1:6379> lrange list3 0 -1
1) "value4"
127.0.0.1:6379> LINSERT list3 before value4 value5 #在value4前面插入
(integer) 2
127.0.0.1:6379> lrange list3 0 -1
1) "value5"
2) "value4"
127.0.0.1:6379> LINSERT list3 after value4 value6 #在value4后面插入
(integer) 3
127.0.0.1:6379> lrange list3 0 -1
1) "value5"
2) "value4"
3) "value6"
##############################################################

小结:

  • 它实际上是一个链表,before node after,left ,right都可以插入值
  • 如果key不存在,则创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入或者改动值,效率最高!中间元素改动,相对来说效率会低一点

消息队列:(LPUSH RPOP)栈:(LPUSH LPOP)

Set(集合)

set中的值是不能重复的,且是无序的

##############################################################
# sadd添加元素
# smember获取所有元素
# sismember判断是否存在元素
127.0.0.1:6379> sadd set1 hello #set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd set1 " world"
(integer) 1
127.0.0.1:6379> SMEMBERS set1
1) "hello"
2) " world"
127.0.0.1:6379> SISMEMBER set1 "hello" #判断元素是否在set中,存在返回1,不存在返回0
(integer) 1
127.0.0.1:6379> sismember set1 "he"
(integer) 0
##############################################################
#scard获取set集合中元素个数
127.0.0.1:6379> scard set1
(integer) 2
127.0.0.1:6379> sadd set1 nihao
(integer) 1
127.0.0.1:6379> scard set1
(integer) 3
##############################################################
#srem移除指定元素
127.0.0.1:6379> srem set1 nihao
(integer) 1
127.0.0.1:6379> scard set1
(integer) 2
127.0.0.1:6379> SMEMBERS set1
1) "hello"
2) " world"
127.0.0.1:6379> sadd set1 nihao 
(integer) 1
127.0.0.1:6379> sadd set1 shijie 
(integer) 1
127.0.0.1:6379> srem set1 nihao shijie #可以一次移除多个元素
(integer) 2
127.0.0.1:6379> SMEMBERS set1
1) "hello"
2) " world"
##############################################################
#srandmember 随机获取指定个元素
127.0.0.1:6379> SRANDMEMBER set1
"hello"
127.0.0.1:6379> SRANDMEMBER set1
" world"
127.0.0.1:6379> SRANDMEMBER set1
" world"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "hello"
2) " world"
##############################################################
#spop随机删除Key
127.0.0.1:6379> sadd set1 youxin
(integer) 1
127.0.0.1:6379> spop set1
"youxin"
127.0.0.1:6379> SMEMBERS set1
1) "hello"
2) " world"
##############################################################
#smove将一个指定的值移动到另外一个集合中
127.0.0.1:6379> smove set1 set2 hello
(integer) 1
127.0.0.1:6379> SMEMBERS set1
1) " world"
127.0.0.1:6379> SMEMBERS set2
1) "youxin"
2) "hello"
##############################################################
#sdiff 差集
#sinter 交集
#sunion 并集
127.0.0.1:6379> sadd set3 a
(integer) 1
127.0.0.1:6379> sadd set3 b
(integer) 1
127.0.0.1:6379> sadd set3 c
(integer) 1
127.0.0.1:6379> sadd set4 c
(integer) 1
127.0.0.1:6379> sadd set4 d
(integer) 1
127.0.0.1:6379> sadd set4 f\
(integer) 1
127.0.0.1:6379> sdiff set3 set4
1) "b"
2) "a"
127.0.0.1:6379> SINTER set3 set4
1) "c"
127.0.0.1:6379> sunion set3 set4
1) "b"
2) "a"
3) "c"
4) "d"
5) "f\\" 
应用场景:共同关注等
##############################################################

Hash(Map集合)

Map集合,key-map! 这时候的值是一个map集合!本质和string类型没有太大的区别,还是一个简单的key-value

set hash field value

##############################################################
127.0.0.1:6379> hset hash1 field1 youxin
(integer) 1
127.0.0.1:6379> hget hash1 field1
"youxin"
##############################################################
#hmset设置多个值
#hmget获取多个值
#hgetall获取所有的值
127.0.0.1:6379> hmset hash1 field1 hello field2 world #如果已经存在则覆盖原来的值
OK
127.0.0.1:6379> hmget hash1 field1 field2
1) "hello"
2) "world"
127.0.0.1:6379> HGETALL hash1
1) "field1"
2) "hello"
3) "field2"
4) "world"
##############################################################
#hdel删除某一个值,对应的value值也删除了
127.0.0.1:6379> hdel hash1 field1
(integer) 1
127.0.0.1:6379> HGETALL hash1
1) "field2"
2) "world"
##############################################################
# hlen 获取当前hash的长度
127.0.0.1:6379> hlen hash1
(integer) 1
127.0.0.1:6379> hset hash1 field3 value3
(integer) 1
127.0.0.1:6379> hlen hash1
(integer) 2
##############################################################
#hexists判断对应的field和value是否存在
127.0.0.1:6379> HEXISTS hash1 field1
(integer) 0 #0代表不存在
127.0.0.1:6379> HEXISTS hash1 field3
(integer) 1 #1代表存在
##############################################################
#hkeys 只获得所有的field
#hvals 只获得所有的value
127.0.0.1:6379> hkeys hash1
1) "field2"
2) "field3"
127.0.0.1:6379> hvals hash1
1) "world"
2) "value3"
##############################################################
# huncrby 增加相应的步长,步长可以为负数(即减)
127.0.0.1:6379> HINCRBY hash1 field4 10
(integer) 16
127.0.0.1:6379> hget hash1 field4
"16"
127.0.0.1:6379> HINCRBY hash1 field4 -5
(integer) 11
127.0.0.1:6379> hget hash1 field4
"11"
##############################################################
# hsetnx 如果不存在则新增,如果存在则新增失败
127.0.0.1:6379> hsetnx hash1 field5 nihao
(integer) 1
127.0.0.1:637元器件数据手册IC替代型号,打造电子元器件IC百科大全!
          

相关文章