1、什么是NoSQL
NoSQL,泛指非关系型的数据库,它可以作为关系型数据库的良好补充。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
NoSQL数据库的四大分类如下:
1). 键值(Key-Value)存储数据库
- 相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
- 典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
- 数据模型: 一系列键值对
- 优势: 快速查询
- 劣势: 存储的数据缺少结构化
2). 列存储数据库
- 相关产品:Cassandra, HBase, Riak
- 典型应用:分布式的文件系统
- 数据模型:以列簇式存储,将同一列数据存在一起
- 优势:查找速度快,可扩展性强,更容易进行分布式扩展
- 劣势:功能相对局限
3). 文档型数据库
- 相关产品:CouchDB、MongoDB
- 典型应用:Web应用(与Key-Value类似,Value是结构化的)
- 数据模型: 一系列键值对
- 优势:数据结构要求不严格
- 劣势: 查询性能不高,而且缺乏统一的查询语法
4). 图形(Graph)数据库
- 相关数据库:Neo4J、InfoGrid、Infinite Graph
- 典型应用:社交网络
- 数据模型:图结构
- 优势:利用图结构相关算法。
- 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
2、什么是redis
redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
- 字符串类型(string)
- 散列类型(hash)
- 列表类型(list)
- 集合类型(set)
- 有序集合类型(sortset)
3、redis的应用场景
- 缓存(数据查询、短连接、新闻内容、商品内容等等)
- 分布式集群架构中的session分离
- 任务队列(秒杀、抢购、12306等等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒)
4、redis安装
4.1 环境安装
redis是C语言开发,建议在linux上运行,在这使用Centos7作为安装环境。
安装redis需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gcc环境,需要安装gcc:yum install gcc-c++(root用户)
4.2 redis安装
下载redis,在这使用redis-4.0.14版本
wget http://download.redis.io/releases/redis-4.0.14.tar.gz
#将redis-4.0.14.tar.gz拷贝到/usr/local下
解压源码
tar -zxvf redis-4.0.14.tar.gz
进入解压后的目录进行编译
cd /usr/local/redis-4.0.14
make
安装到指定目录,如 /usr/local/redis
cd /usr/local/redis-4.0.14
make PREFIX=/usr/local/redis install
redis.conf是redis的配置文件,redis.conf在redis源码目录。
注意修改port作为redis进程的端口,port默认6379。
拷贝配置文件到安装目录下。将源码目录中的配置文件 redis.conf 其拷贝到安装路径下。并设置redis可远程访问,修改绑定地址为 0.0.0.0
cp /usr/local/redis-4.0.14/redis.conf /usr/local/redis/bin
# 编辑redis.conf文件,并修改绑定地址为 0.0.0.0
vi redis.conf
bind 0.0.0.0
到这单机的redis就搭建好了,当然在微服务架构中要搭建集群来保证高可用。
5、启动redis
5.1 redis服务端启动
在安装目录下bin/redis-server用来启动redis服务端,输入 /usr/local/redis/bin/redis-server 即可启动。
5.2 redis停止
强行终止Redis进程可能会导致redis持久化数据丢失。正确停止Redis的方式应该是向Redis服务发送SHUTDOWN命令,方法为:
cd /usr/local/redis
./bin/redis-cli shutdown save
连接本机6379端口的redis服务执行shutdown停止
save:在停止redis服务之前将所有的数据持久化保存。
停止其他端口的redis
cd /usr/local/redis
./bin/redis-cli -p 6380 shutdown save
5.3 redis客户端启动
执行bin/redis-cli连接本地redis服务端: ./redis-cli
指定连接redis服务的ip和端口: ./redis-cli -h 192.168.10.131 -p 6380
redis提供了ping命令来测试客户端与redis的连接是否正常,如果连接正常会收到回复PONG
使用set和get可以向redis设置数据、获取数据。
6、redis多数据库
6.1 redis实例
一个redis进程就是一个redis实例,一台服务器可以同时有多个redis实例,不同的redis实例提供不同的服务端口对外提供服务,每个redis实例之间互相影响。每个redis实例都包括自己的数据库,数据库中可以存储自己的数据。
6.2 多数据库测试
一个Redis实例可以包括多个数据库,客户端可以指定连接某个redis实例的哪个数据库,就好比一个mysql中创建多个数据库,客户端连接时指定连接哪个数据库。
一个redis实例最多可提供16个数据库,下标从0到15,客户端默认连接第0号数据库,也可以通过select选择连接哪个数据库,如下连接1号库,在1号库中查询上节设置的数据,结果查询不到:
重新选择第0号数据库,查询数据:
如果选择一个不存在数据库则会报错:
注意:redis不支持修改数据库的名称,只能通过select 0、select 1…选择数据库。
7、redis数据类型
redis数据就是以key-value形式来存储的,key只能是字符串类型,value可以是以下五种类型:String、List、Set、SortedSets、Hash。
命令不区分大小写,但key和value区分大小写。命令不需要记住,要用的时候再来查找就可以了
7.1 String类型
- string 是 redis 最基本的类型,一个 key 对应一个 value。
- string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
- string 类型是 redis 最基本的数据类型,string 类型的值最大能存储 512MB。
7.1.1 set/get/append/strlen
127.0.0.1:6379> select 0 #切换到第1个数据库,默认共有16个数据库,索引从0开始
OK
127.0.0.1:6379> keys * #显示所有的键key
(empty list or set)
127.0.0.1:6379> set name tom #设置键
OK
127.0.0.1:6379> get name #获取键对应的值
"tom"
127.0.0.1:6379> exists mykey #判断该键是否存在,存在返回1,不存在返回
(integer) 0
127.0.0.1:6379> append mykey "hello" #如果该键不存在,则创建,返回当前value的长度
(integer) 5
127.0.0.1:6379> append mykey " world" #如果该键已经存在,则追加,返回追加后value的长度
(integer) 11
127.0.0.1:6379> get mykey #获取mykey的值
"hello world"
127.0.0.1:6379> strlen mykey #获取mykey的长度
(integer) 11
#EX和PX表示失效时间,单位为秒和毫秒,两者不能同时使用;NX表示数据库中不存在时才能设置,XX表示存在时才能设置
127.0.0.1:6379> set mykey "this is test" EX 5 NX
OK
127.0.0.1:6379> get mykey
"this is test"
7.1.2 incr/decr/incrby/decrby
127.0.0.1:6379> flushdb #清空数据库
OK
127.0.0.1:6379> set mykey 20
OK
127.0.0.1:6379> incr mykey #递增1
(integer) 21
127.0.0.1:6379> decr mykey #递减1
(integer) 20
127.0.0.1:6379> del mykey #删除该键
(integer) 1
127.0.0.1:6379> decr mykey
(integer) 1
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> INCR mykey
(integer) 1
127.0.0.1:6379> set mykey 'hello' #将该键的Value设置为不能转换为整型的普通字符串
OK
127.0.0.1:6379> incr mykey #在该键上再次执行递增操作时,Redis将报告错误信息
(error) ERR value is not an integer or out of range
127.0.0.1:6379> set mykey 10
OK
127.0.0.1:6379> incrby mykey 5 #递增5,即步长
(integer) 15
127.0.0.1:6379> decrby mykey 10 #递减10
(integer) 5
7.1.3 getset/setex/setnx
# getset 获取的同时并设置新的值
127.0.0.1:6379> incr mycount #将计数器的值原子性的递增1
(integer) 1
127.0.0.1:6379> getset mycount 666 #在获取计数器原有值的同时,并将其设置为新值
"1"
127.0.0.1:6379> get mycount
"666"
# setex 设置过期时间
127.0.0.1:6379> setex mykey 10 "hello" #设置指定Key的过期时间为10秒,等同于set mykey hello ex 10
OK
127.0.0.1:6379> ttl mykey #查看指定Key的过期时间(秒数)
(integer) 8
# setnx 当key不存在时才能设置
127.0.0.1:6379> del mykey
(integer) 0
127.0.0.1:6379> setnx mykey "aaa" #key不存在,可以设置,等同于set mykey aaa nx
(integer) 1
127.0.0.1:6379> setnx mykey "bbb" #key存在,不能设置
(integer) 0
127.0.0.1:6379> get mykey
"aaa"
7.1.4 setrange/getrange 设置/获取指定索引位置的字符
127.0.0.1:6379> set mykey "hello world"
OK
127.0.0.1:6379> get mykey
"hello world"
127.0.0.1:6379> setrange mykey 6 dd #从索引为6的位置开始替换(索引从0开始)
(integer) 11
127.0.0.1:6379> get mykey
"hello ddrld"
127.0.0.1:6379> setrange mykey 20 dd #超过的长度使用0代替
(integer) 22
127.0.0.1:6379> get mykey
"hello ddrld\x00\x00\x00\x00\x00\x00\x00\x00\x00dd"
127.0.0.1:6379> getrange mykey 3 12 #获取索引为[3,12]之间的内容
"lo ddrld\x00\x00"
7.1.5 setbit/getbit 设置/获取指定位的BIT值,应用场景:考勤打卡
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> setbit mykey 7 1 #设置从0开始计算的第七位BIT值为1,返回原有BIT值0
(integer) 0
127.0.0.1:6379> get mykey #获取设置的结果,二进制的0000 0001的十六进制值为0x01
"\x01"
127.0.0.1:6379> setbit mykey 6 1 #设置从0开始计算的第六位BIT值为1,返回原有BIT值0
(integer) 0
127.0.0.1:6379> get mykey #获取设置的结果,二进制的0000 0011的十六进制值为0x03
"\x03"
127.0.0.1:6379> getbit mykey 6 #返回了指定Offset的BIT值
(integer) 1
127.0.0.1:6379> getbit mykey 10 #如果offset已经超出了value的长度,则返回0
(integer) 0
7.1.6 mset/mget/msetnx
127.0.0.1:6379> mset key1 "hello" key2 "world" #批量设置了key1和key2两个键
OK
127.0.0.1:6379> mget key1 key2 #批量获取了key1和key2两个键的值。
1) "hello"
2) "world"
#批量设置了key3和key4两个键,因为之前他们并不存在,所以该命令执行成功并返回1
127.0.0.1:6379> msetnx key3 "itany" key4 "liu"
(integer) 1
127.0.0.1:6379> mget key3 key4
1) "itany"
2) "liu"
#批量设置了key3和key5两个键,但是key3已经存在,所以该命令执行失败并返回0
127.0.0.1:6379> msetnx key3 "hello" key5 "world"
(integer) 0
#批量获取key3和key5,由于key5没有设置成功,所以返回nil
127.0.0.1:6379> mget key3 key5
1) "itany"
2) (nil)
7.2 List类型
- 在redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。
- List中可以包含的最大元素数量是2^32 - 1(4294967295, 每个列表可存储40多亿) 。
- 从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。
7.2.1 lpush/lpushx/lrange
127.0.0.1:6379> flushdb
OK
#创建键mykey及与其关联的List,然后将参数中的values从左到右依次插入
127.0.0.1:6379> lpush mykey a b c d
(integer) 4
127.0.0.1:6379> lrange mykey 0 2 #获取从位置0开始到位置2结束的3个元素
1) "d"
2) "c"
3) "b"
#获取链表中的全部元素,其中0表示第一个元素,-1表示最后一个元素
127.0.0.1:6379> lrange mykey 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> lrange mykey -3 -2 #获取从倒数第3个到倒数第2个的元素
1) "c"
2) "b"
#lpushx表示键存在时才能插入,mykey2键此时并不存在,因此该命令将不会进行任何操作,其返回值为0
127.0.0.1:6379> lpushx mykey2 e
(integer) 0
#可以看到mykey2没有关联任何List Value
127.0.0.1:6379> lrange mykey2 0 -1
(empty list or set)
#mykey键此时已经存在,所以该命令插入成功,并返回链表中当前元素的数量
127.0.0.1:6379> lpushx mykey e
(integer) 5
#获取该键的List中的第一个元素
127.0.0.1:6379> lrange mykey 0 0
1) "e"
7.2.2 lpop/llen
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> lpush mykey a b c d
(integer) 4
#取出链表头部的元素,该元素在链表中就已经不存在了
127.0.0.1:6379> lpop mykey
"d"
127.0.0.1:6379> lpop mykey
"c"
#在执行lpop命令两次后,链表头部的两个元素已经被弹出,此时链表中元素的数量是2
127.0.0.1:6379> llen mykey
(integer) 2
7.2.3 lrem/lindex/lset/ltrim
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> lpush mykey a b c d a c
(integer) 6
#从头部(left)向尾部(right)操作链表,删除2个值等于a的元素,返回值为实际删除的数量
127.0.0.1:6379> lrem mykey 2 a
(integer) 2
127.0.0.1:6379> lrange mykey 0 -1 #查看删除后链表中的全部元素
1) "c"
2) "d"
3) "c"
4) "b"
127.0.0.1:6379> lindex mykey 1 #获取索引值为1(头部的第二个元素)的元素值
"d"
127.0.0.1:6379> lset mykey 1 e #将索引值为1(头部的第二个元素)的元素值设置为新值e
OK
127.0.0.1:6379> lindex mykey 1 #查看是否设置成功
"e"
127.0.0.1:6379> lindex mykey 6 #索引值6超过了链表中元素的数量,该命令返回nil
(nil)
#设置的索引值6超过了链表中元素的数量,设置失败,该命令返回错误信息。
127.0.0.1:6379> lset mykey 6 h
(error) ERR index out of range
#仅保留索引值0到2之间的3个元素,注意第0个和第2个元素均被保留。
127.0.0.1:6379> ltrim mykey 0 2
OK
127.0.0.1:6379> lrange mykey 0 -1 #查看trim后的结果
1) "c"
2) "e"
3) "c"
7.2.4 linsert
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> lpush mykey a b c d e
(integer) 5
127.0.0.1:6379> linsert mykey before a a1 #在a的前面插入新元素a1
(integer) 6
127.0.0.1:6379> lrange mykey 0 -1 #查看是否插入成功,从结果看已经插入
1) "e"
2) "d"
3) "c"
4) "b"
5) "a1"
6) "a"
127.0.0.1:6379> linsert mykey after e e2 #在e的后面插入新元素e2,从返回结果看已经插入成功
(integer) 7
127.0.0.1:6379> lrange mykey 0 -1 #再次查看是否插入成功
1) "e"
2) "e2"
3) "d"
4) "c"
5) "b"
6) "a1"
7) "a"
127.0.0.1:6379> linsert mykey after k a #在不存在的元素之前或之后插入新元素,该命令操作失败,并返回1
(integer) 1
127.0.0.1:6379> linsert mykey1 after a a2 #在不存在的Key插入新元素,该命令操作失败,返回0
(integer) 0
7.2.4 rpush/rpushx/rpop/rpoplpush
127.0.0.1:6379> del mykey
(integer) 1
#从链表的尾部插入参数中给出的values,插入顺序是从左到右依次插入
127.0.0.1:6379> rpush mykey a b c d
(integer) 4
127.0.0.1:6379> lrange mykey 0 -1 #查看链表中的元素,注意元素的顺序
1) "a"
2) "b"
3) "c"
4) "d"
#该键已经存在并且包含4个元素,rpushx命令将执行成功,并将元素e插入到链表的尾部。
127.0.0.1:6379> rpushx mykey e
(integer) 5
#由于mykey2键并不存在,因此该命令不会插入数据,其返回值为0。
127.0.0.1:6379> rpushx mykey2 e
(integer) 0
127.0.0.1:6379> lrange mykey 0 -1 #查看链表中所有的元素
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
127.0.0.1:6379> rpop mykey #从尾部(right)弹出元素,即取出元素
"e"
127.0.0.1:6379> lrange mykey 0 -1 #查看链表中所有的元素
1) "a"
2) "b"
3) "c"
4) "d"
127.0.0.1:6379> lpush mykey2 m n #创建mykey2
(integer) 2
#将mykey的尾部元素弹出,然后插入到mykey2的头部(原子性的完成这两步操作)
127.0.0.1:6379> rpoplpush mykey mykey2
"d"
#通过lrange命令查看mykey在弹出尾部元素后的结果
127.0.0.1:6379> lrange mykey 0 -1
1) "a"
2) "b"
3) "c"
#通过lrange命令查看mykey2在插入元素后的结果
127.0.0.1:6379> lrange mykey2 0 -1
1) "d"
2) "n"
3) "m"
#将source和destination设为同一键,将mykey中的尾部元素移到其头部
127.0.0.1:6379> rpoplpush mykey mykey
"c"
127.0.0.1:6379> lrange mykey 0 -1 #查看结果
1) "c"
2) "a"
3) "b"
7.3 Set类型
- redis 的 Set 是 string 类型的无序集合。
- 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
- 与List类型不同的是,Set集合中不允许出现重复的元素,这一点和Java中的set容器是完全相同的。
- 集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
7.3.1 sadd/smembers/sismember/scard
#由于该键myset之前并不存在,因此参数中的三个成员都被正常插入
127.0.0.1:6379> sadd myset a b c
(integer) 3
#查看集合中的元素,从结果可以,输出的顺序和插入顺序无关(无序的)
127.0.0.1:6379> smembers myset
1) "a"
2) "c"
3) "b"
#由于参数中的a在myset中已经存在,因此本次操作仅仅插入了d和e两个新成员(不允许重复)
127.0.0.1:6379> sadd myset a d e
(integer) 2
127.0.0.1:6379> smembers myset #查看插入的结果
1) "a"
2) "c"
3) "d"
4) "b"
5) "e"
127.0.0.1:6379> sismember myset a #判断a是否已经存在,返回值为1表示存在
(integer) 1
127.0.0.1:6379> sismember myset f #判断f是否已经存在,返回值为0表示不存在
(integer) 0
127.0.0.1:6379> scard myset #获取集合中元素的数量
(integer) 5
7.3.2 srandmember/spop/srem/smove
127.0.0.1:6379> del myset
(integer) 1
127.0.0.1:6379> sadd myset a b c d
(integer) 4
127.0.0.1:6379> smembers myset #查看集合中的元素
1) "c"
2) "d"
3) "a"
4) "b"
127.0.0.1:6379> srandmember myset #随机返回一个成员,成员还在集合中
"c"
127.0.0.1:6379> spop myset #取出一个成员,成员会从集合中删除
"b"
127.0.0.1:6379> smembers myset #查看移出后Set的成员信息
1) "c"
2) "d"
3) "a"
#从Set中移出a、d和f三个成员,其中f并不存在,因此只有a和d两个成员被移出,返回为2
127.0.0.1:6379> srem myset a d f
(integer) 2
127.0.0.1:6379> smembers myset #查看移出后的输出结果
1) "c"
127.0.0.1:6379> del myset
(integer) 1
#为后面的smove命令准备数据
127.0.0.1:6379> sadd myset a b
(integer) 2
127.0.0.1:6379> sadd myset2 c d
(integer) 2
#将a从myset移到myset2,从结果可以看出移动成功
127.0.0.1:6379> smove myset myset2 a
(integer) 1
#再次将a从myset移到myset2,由于此时a已经不是myset的成员了,因此移动失败并返回0。
127.0.0.1:6379> smove myset myset2 a
(integer) 0
#分别查看myset和myset2的成员,确认移动是否真的成功。
127.0.0.1:6379> smembers myset
1) "b"
127.0.0.1:6379> smembers myset2
1) "c"
2) "d"
3) "a"
7.3.3 sdiff/sdiffstore/sinter/sinterstore/sunion/sunionstore
127.0.0.1:6379> flushdb
OK
#准备测试数据
127.0.0.1:6379> sadd myset a b c d
(integer) 4
127.0.0.1:6379> sadd myset2 c
(integer) 1
127.0.0.1:6379> sadd myset3 a c e
(integer) 3
#获取多个集合之间的不同成员,要注意匹配的规则
#先将myset和myset2进行比较,a、b和d三个成员是两者之间的差异成员,然后再用这个结果继续和myset3进行差异比较,b和d是myset3不存在的成员(差集)
127.0.0.1:6379> sdiff myset myset2 myset3
1) "d"
2) "b"
127.0.0.1:6379> sdiff myset3 myset2 myset
1) "e"
#将3个集合的差异成员存储到与diffkey关联的Set中,并返回插入的成员数量
127.0.0.1:6379> sdiffstore diffkey myset myset2 myset3
(integer) 2
#查看一下sdiffstore的操作结果
127.0.0.1:6379> smembers diffkey
1) "d"
2) "b"
#获取多个集合之间的交集,这三个Set的成员交集只有c
127.0.0.1:6379> sinter myset myset2 myset3
1) "c"
#将3个集合中的交集成员存储到与interkey关联的Set中,并返回交集成员的数量
127.0.0.1:6379> sinterstore interkey myset myset2 myset3
(integer) 1
#查看一下sinterstore的操作结果
127.0.0.1:6379> smembers interkey
1) "c"
#获取多个集合之间的并集
127.0.0.1:6379> sunion myset myset2 myset3
1) "b"
2) "c"
3) "d"
4) "e"
5) "a"
#将3个集合中成员的并集存储到unionkey关联的set中,并返回并集成员的数量
127.0.0.1:6379> sunionstore unionkey myset myset2 myset3
(integer) 5
#查看一下sunionstore的操作结果
127.0.0.1:6379> smembers unionkey
1) "b"
2) "c"
3) "d"
4) "e"
5) "a"
7.3.4 应用范围
- 可以使用Redis的Set数据类型跟踪一些唯一性数据,比如访问某一博客的唯一IP地址信息。对于此场景,我们仅需在每次访问该博客时将访问者的IP存入Redis中,Set数据类型会自动保证IP地址的唯一性。
- 充分利用Set类型的服务端聚合操作方便、高效的特性,可以用于维护数据对象之间的关联关系。比如所有购买某一电子设备的客户ID被存储在一个指定的Set中,而购买另外一种电子产品的客户ID被存储在另外一个Set中,如果此时我们想获取有哪些客户同时购买了这两种商品时,Set的交集命令就可以充分发挥它的方便和效率的优势了。
7.4 SortedSets(zset)类型
- redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
- 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
- zset的成员是唯一的,但分数(score)却可以重复。
7.4.1 zadd/zrange/zcard/zrank/zcount/zrem/zscore/zincrby
# 添加一个分数为1的成员
127.0.0.1:6379> zadd myzset 1 "one"
(integer) 1
# 添加两个分数分别是2和3的两个成员
127.0.0.1:6379> zadd myzset 2 "two" 3 "three"
(integer) 2
# 通过索引获取元素,0表示第一个成员,-1表示最后一个成员。WITHSCORES选项表示返回的结果中包含每个成员及其分数,否则只返回成员
127.0.0.1:6379> zrange myzset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
127.0.0.1:6379> zcard myzset # 获取myzset键中成员的数量
(integer) 3
127.0.0.1:6379> zrank myzset one # 获取成员one在集合中的索引,0表示第一个位置
(integer) 0
127.0.0.1:6379> zrank myzset four # 成员four并不存在,因此返回nil
(nil)
# 获取符合指定条件的成员数量,分数满足表达式 1 <= score <= 2的成员的数量
127.0.0.1:6379> zcount myzset 1 2
(integer) 2
127.0.0.1:6379> zrem myzset one two # 删除成员one和two,返回实际删除成员的数量
(integer) 2
127.0.0.1:6379> zcard myzset # 查看是否删除成功
(integer) 1
127.0.0.1:6379> zscore myzset three # 获取成员three的分数。返回值是字符串形式
"3"
127.0.0.1:6379> zscore myzset two # 由于成员two已经被删除,所以该命令返回nil
(nil)
127.0.0.1:6379> zincrby myzset 2 three # 将成员three的分数增加2,并返回该成员更新后的分数
"5"
127.0.0.1:6379> zincrby myzset 1 three # 将成员three的分数增加1,并返回该成员更新后的分数
"4"
127.0.0.1:6379> zrange myzset 0 1 withscores # 查看在更新了成员的分数后是否正确
1) "three"
2) "4"
7.4.2 zrangebyscore/zremrangebyscore/zremrangebyrank
127.0.0.1:6379> del myzset
(integer) 1
127.0.0.1:6379> zadd myzset 1 one 2 two 3 three 4 four
(integer) 4
#通过分数获取元素,获取分数满足表达式1 <= score <= 2的成员
127.0.0.1:6379> zrangebyscore myzset 1 2
1) "one"
2) "two"
#-inf表示第一个成员,+inf表示最后一个成员,limit后面的参数用于限制返回成员的数量,
#2表示从位置索引(0based)等于2的成员开始,取后面3个成员,类似于MySQL中的limit
127.0.0.1:6379> zrangebyscore myzset -inf +inf withscores limit 2 3
1) "three"
2) "3"
3) "four"
4) "4"
#根据分数删除成员,删除分数满足表达式1 <= score <= 2的成员,并返回实际删除的数量
127.0.0.1:6379> zremrangebyscore myzset 1 2
(integer) 2
#看出一下上面的删除是否成功
127.0.0.1:6379> zrange myzset 0 -1
1) "three"
2) "four"
#根据索引删除成员,删除索引满足表达式0 <= rank <= 1的成员
127.0.0.1:6379> zremrangebyrank myzset 0 1
(integer) 2
#查看上一条命令是否删除成功
127.0.0.1:6379> zcard myzset
(integer) 0
7.4.3 zrevrange/zrevrangebyscore/zrevrank
127.0.0.1:6379> del myzset
(integer) 0
127.0.0.1:6379> zadd myzset 1 one 2 two 3 three 4 four
(integer) 4
#按索引从高到低的方式获取成员
127.0.0.1:6379> zrevrange myzset 0 -1 WITHSCORES
1) "four"
2) "4"
3) "three"
4) "3"
5) "two"
6) "2"
7) "one"
8) "1"
#由于是从高到低的排序,所以位置等于0的是four,1是three,并以此类推
127.0.0.1:6379> zrevrange myzset 1 3
1) "three"
2) "two"
3) "one"
#按索引从高到低的方式根据分数获取成员,分数满足表达式3 >= score >= 0的成员
127.0.0.1:6379> zrevrangebyscore myzset 3 0
1) "three"
2) "two"
3) "one"
#limit选项的含义等同于zrangebyscore中的该选项,只是在计算位置时按照相反的顺序计算和获取
127.0.0.1:6379> zrevrangebyscore myzset 4 0 limit 1 2
1) "three"
2) "two"
#获取成员one在集合中的索引,由于是从高到低的排序,所以one的位置是3
127.0.0.1:6379> zrevrank myzset one
(integer) 3
#由于是从高到低的排序,所以four的位置是0
127.0.0.1:6379> zrevrank myzset four
(integer) 0
7.4.4 应用范围
- 可以用于大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP 10的用户信息。当然我们也可以利用ZRANK命令通过username来获取玩家的排行信息。最后我们将组合使用ZRANGE和ZRANK命令快速的获取和某个玩家积分相近的其他用户的信息。
- SortedSets类型还可用于构建索引数据。
7.5 Hash(哈希)类型
- redis hash 是一个键值(key=>value)对集合。
- redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
7.5.1 hset/hget/hlen/hexists/hdel/hsetnx
#给键值为myhash的键设置字段为field1,值为itany
127.0.0.1:6379> hset myhash field1 "itany"
(integer) 1
#获取键值为myhash,字段为field1的值
127.0.0.1:6379> hget myhash field1
"itany"
#myhash键中不存在field2字段,因此返回nil
127.0.0.1:6379> hget myhash field2
(nil)
#给myhash关联的Hashes值添加一个新的字段field2,其值为liu
127.0.0.1:6379> hset myhash field2 "liu"
(integer) 1
#获取myhash键的字段数量
127.0.0.1:6379> hlen myhash
(integer) 2
#判断myhash键中是否存在字段名为field1的字段,由于存在,返回值为1
127.0.0.1:6379> hexists myhash field1
(integer) 1
#删除myhash键中字段名为field1的字段,删除成功返回1
127.0.0.1:6379> hdel myhash field1
(integer) 1
#再次删除myhash键中字段名为field1的字段,由于上一条命令已经将其删除,因为没有删除,返回0
127.0.0.1:6379> hdel myhash field1
(integer) 0
#通过hsetnx命令给myhash添加新字段field1,其值为itany,因为该字段已经被删除,所以该命令添加成功并返回1
127.0.0.1:6379> hsetnx myhash field1 "itany"
(integer) 1
#由于myhash的field1字段已经通过上一条命令添加成功,因为本条命令不做任何操作后返回0
127.0.0.1:6379> hsetnx myhash field1 "itany"
(integer) 0
7.5.2 hincrby
127.0.0.1:6379> del myhash
(integer) 1
#准备测试数据
127.0.0.1:6379> hset myhash field 5
(integer) 1
#给myhash的field字段的值加1,返回加后的结果
127.0.0.1:6379> hincrby myhash field 1
(integer) 6
#给myhash的field字段的值加1,返回加后的结果
127.0.0.1:6379> hincrby myhash field 1
(integer) 5
#给myhash的field字段的值加10,返回加后的结果
127.0.0.1:6379> hincrby myhash field 10
(integer) 5
7.5.2 hmset/hmget/hgetall/hkeys/hvals
127.0.0.1:6379> del myhash
(integer) 1
#为该键 myhash,一次性设置多个字段,分别是 field1 = "hello", field2 = "world"
127.0.0.1:6379> hmset myhash field1 "hello" field2 "world"
OK
#获取 myhash 键的多个字段,其中 field3 并不存在,因为在返回结果中与该字段对应的值为 nil
127.0.0.1:6379> hmget myhash field1 field2 field3
1) "hello"
2) "world"
3) (nil)
#返回myhash键的所有字段及其值,从结果中可以看出,他们是逐对列出的
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
#仅获取myhash键中所有字段的名字
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field2"
#仅获取myhash键中所有字段的值
127.0.0.1:6379> hvals myhash
1) "hello"
2) "world"
7.6 Key操作命令
redis 键命令用于管理 redis 的键。
redis 键命令的基本语法如下:
redis 127.0.0.1:6379> COMMAND KEY_NAME
7.6.1 keys/del/exists/move/rename/renamenx
127.0.0.1:6379> flushdb
OK
#添加String类型的数据
127.0.0.1:6379> set mykey 2
OK
#添加List类型的数据
127.0.0.1:6379> lpush mylist a b c
(integer) 3
#添加Set类型的数据
127.0.0.1:6379> sadd myset 1 2 3
(integer) 3
#添加SortedSet类型的数据
127.0.0.1:6379> zadd myzset 1 "one" 2 "two"
(integer) 2
#添加Hash类型的数据
127.0.0.1:6379> hset myhash username "tom"
(integer) 1
#根据参数中的模式,获取当前数据库中符合该模式的所有key,从输出可以看出,该命令在执行时并不区分与Key关联的Value类型
127.0.0.1:6379> keys my*
1) "myset"
2) "mykey"
3) "myzset"
4) "myhash"
5) "mylist"
#删除了两个Keys
127.0.0.1:6379> del mykey mylist
(integer) 2
#查看刚刚删除的Key是否还存在,从返回结果看,mykey确实已经删除了
127.0.0.1:6379> exists mykey
(integer) 0
#查看一下没有删除的Key,以和上面的命令结果进行比较
127.0.0.1:6379> exists myset
(integer) 1
#将当前数据库中的myset键移入到ID为1的数据库中
127.0.0.1:6379> move myset 1
(integer) 1
#切换到ID为1的数据库
127.0.0.1:6379> select 1
OK
#查看当前数据库中的所有key
127.0.0.1:6379[1]> keys *
1) "myset"
#在重新打开ID为0的缺省数据库
127.0.0.1:6379[1]> select 0
OK
#清空数据库
127.0.0.1:6379> flushdb
OK
#准备新的测试数据
127.0.0.1:6379> set mykey "hello"
OK
#将mykey改名为mykey1
127.0.0.1:6379> rename mykey mykey1
OK
#由于mykey已经被重新命名,再次获取将返回nil
127.0.0.1:6379> get mykey
(nil)
#通过新的键名获取
127.0.0.1:6379> get mykey1
"hello"
#为renamenx准备测试key
127.0.0.1:6379> set oldkey "hello"
OK
127.0.0.1:6379> set newkey "world"
OK
#当新名称不存在时才会执行。由于newkey已经存在,因此该命令未能成功执行
127.0.0.1:6379> renamenx oldkey newkey
(integer) 0
#查看newkey的值,发现它并没有被renamenx覆盖
127.0.0.1:6379> get newkey
"world"
7.6.2 ttl/persist/expire/expireat
127.0.0.1:6379> flushdb
OK
#准备测试数据,将该键的超时设置为100秒
127.0.0.1:6379> set mykey "hello" ex 100
OK
#通过ttl命令查看还剩多少秒
127.0.0.1:6379> ttl mykey
(integer) 97
#立刻执行persist命令,该存在超时的键变成持久化的键,即将该Key的超时去掉
127.0.0.1:6379> persist mykey
(integer) 1
#ttl的返回值告诉我们,该键已经没有超时了
127.0.0.1:6379> ttl mykey
(integer) -1
#为后面的expire命令准备数据
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> set mykey "hello"
OK
#设置该键的超时被100秒
127.0.0.1:6379> expire mykey 100
(integer) 1
#用ttl命令看当前还剩下多少秒,从结果中可以看出还剩下96秒
127.0.0.1:6379> ttl mykey
(integer) 96
#重新更新该键的超时时间为20秒,从返回值可以看出该命令执行成功
127.0.0.1:6379> expire mykey 20
(integer) 1
#再用ttl确认一下,从结果中可以看出被更新了
127.0.0.1:6379> ttl mykey
(integer) 17
#立刻更新该键的值,以使其超时无效。
127.0.0.1:6379> set mykey "world"
OK
#从ttl的结果可以看出,在上一条修改该键的命令执行后,该键的超时也无效了
127.0.0.1:6379> ttl mykey
(integer) 1
7.6.3 type/randomkey
127.0.0.1:6379> del mykey
(integer) 1
#添加不同类型的测试数据
127.0.0.1:6379> set mykey 2
OK
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> sadd myset 1 2 3
(integer) 3
127.0.0.1:6379> zadd myzset 1 "one" 2 "two"
(integer) 2
127.0.0.1:6379> hset myhash username "tom"
(integer) 1
#分别查看数据的类型
127.0.0.1:6379> type mykey
string
127.0.0.1:6379> type mylist
list
127.0.0.1:6379> type myset
set
127.0.0.1:6379> type myzset
zset
127.0.0.1:6379> type myhash
hash
#返回数据库中的任意键
127.0.0.1:6379> randomkey
"oldkey"
#清空当前打开的数据库
127.0.0.1:6379> flushdb
OK
#由于没有数据了,因此返回nil
127.0.0.1:6379> randomkey
(nil)
8、redis事务
和其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制。在redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事务的基石。redis中事务的特征:
- 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。
- 和关系型数据库中的事务相比,在redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。
- 我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为"BEGIN TRANSACTION"语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。
- 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。
- 当使用AppendOnly模式时,redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用redis工具包中提供的redischeckaof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。
8.1 命令列表
- multi:用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子的执行。
- exec:执行在一个事务内命令队列中的所有命令,同时将当前连接的状态恢复为正常状态,即非事务状态。如果在事务中执行了WATCH命令,那么只有当WATCH所监控的Keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,否则EXEC将放弃当前事务中的所有命令。
- discard:回滚事务队列中的所有命令,同时再将当前连接的状态恢复为正常状态,即非事务状态。如果WATCH命令被使用,该命令将UNWATCH所有的Keys。
- watch :用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
# 1. 事务被正常执行
#在当前连接上启动一个新的事务
127.0.0.1:6379> multi
OK
#执行事务中的第一条命令,从该命令的返回结果可以看出,该命令并没有立即执行,而是存于事务的命令队列
127.0.0.1:6379> incr t1
QUEUED
#又执行一个新的命令,从结果可以看出,该命令也被存于事务的命令队列
127.0.0.1:6379> incr t2
QUEUED
#执行事务命令队列中的所有命令,从结果可以看出,队列中命令的结果得到返回
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
#只有当提交事务后,在其他连接中才能看到变化
# 2. 事务中存在失败的命令
#开启一个新的事务
127.0.0.1:6379> multi
OK
#设置键a的值为string类型的3
127.0.0.1:6379> set a 3
QUEUED
#从键a所关联的值的头部弹出元素,由于该值是字符串类型,而lpop命令仅能用于List类型,因此在执行exec命令时,该命令将会失败
127.0.0.1:6379> lpop a
QUEUED
#再次设置键a的值为字符串4
127.0.0.1:6379> set a 4
QUEUED
#获取键a的值,以便确认该值是否被事务中的第二个set命令设置成功
127.0.0.1:6379> get a
QUEUED
#从结果中可以看出,事务中的第二条命令lpop执行失败,而其后的set和get命令均执行成功,这一点是Redis的事务与关系型数据库中的事务之间最为重要的差别
127.0.0.1:6379> exec
1) OK
2) (error) ERR Operation against a key holding the wrong kind of value
3) OK
4) "4"
# 3. 若在事务队列中存在命令性错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会执行
#开启一个新的事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
# 错误命令
127.0.0.1:6379> getset k1
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors
# 4. 回滚事务
#为键t2设置一个事务执行前的值
127.0.0.1:6379> set t2 tt
OK
#开启一个事务
127.0.0.1:6379> multi
OK
#在事务内为该键设置一个新值
127.0.0.1:6379> set t2 ttnew
QUEUED
#放弃事务
127.0.0.1:6379> discard
OK
#查看键t2的值,从结果中可以看出该键的值仍为事务开始之前的值
127.0.0.1:6379> get t2
"tt"
9、Java访问Redis
Jedis是一个封装了redis的java客户端,集成了redis的一些命令操作,并提供了连接池管理功能
- 添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
- 基本用法
public void test01(){
//获取jedis的连接
Jedis jedis=new Jedis("192.168.10.131",6379);
//验证
jedis.auth("itany");
//操作redis
jedis.set("name","tom");
jedis.set("password","123","nx");
System.out.println(jedis.get("name"));
jedis.lpush("mylist","jack","alice","mike");
System.out.println(jedis.lrange("mylist",0,1));
jedis.sadd("myset","aaa","bbb","ccc");
System.out.println(jedis.smembers("myset"));
System.out.println();
jedis.zadd("myzset",10,"a");
jedis.zadd("myzset",20,"b");
jedis.zadd("myzset",15,"c");
System.out.println(jedis.zrange("myzset",0,1));
System.out.println();
jedis.hset("user","name","alice");
jedis.hset("user","age","21");
Set<String> keys = jedis.hkeys("user");
for (String key:keys){
System.out.println(key+"="+jedis.hget("user",key));
}
Set<String> keys = jedis.keys("*");
System.out.println(keys);
//关闭连接
jedis.close();
}
有哪写的不够好,欢迎大佬们可以提出来,小弟会进行修改,谢谢观看。