Redis入门
NoSQL
概述
NoSQL(Not Only SQL)
,不仅仅是SQL
,是一项全新的数据库理念,泛指非关系型的数据库。随着互联网web2.0
网站的兴起,非关系型的数据库现在成了一个极其热门的新领域,非关系数据产品的发展非常迅速。而传统的关系数据库在应付web2.0
网站,特别是超大规模和高并发的SNS
类型的web2.0
纯动态网站已经显得力不从心,暴露了很多难以克服的问题。
1. High performance 对数据库高并发读写的需求
web2.0网站要根据用户个性化信息来实现生成动态页面和提供动态信息,所以基本无法使用动态页面静态化技术,因此数据库并发负载非常高,往往要达到每秒上万次读写请求。关系数据库应付上万次SQL查询勉强还顶得住,但是应付上万次SQL写数据请求,硬盘IO就已经无法承受了。例如,网站的实时统计在线用户状态,记录热门帖子的点击次数,投票技术等都属于对高并发写请求的需求。
2. Huge Storage 对海量数据的高效流存储和访问的需求
类似Facebook,twitter,Friendfeed这样的SNS网站,每天用户产生海量的用户动态,以Friendfeed为例,一个月就达到2.5亿条用户动态。对于关系数据库来说,在一张2.5亿条记录的表里面进行SQL查询,效率是极其低下乃至不可忍受的。
3. High Scalability && High Availability 对数据的高可扩展性和高可用性的需求
在基于web的架构中,数据库是最难以进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,你的数据库却没有办法像web server和app server那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展是非常痛苦的事情。往往需要停机维护和数据迁移。而NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
分类
键值(key-value)存储数据库
典型应用:内容缓存,主要用于处理大量数据的高访问负载
数据模型:一系列键值对
优势:快速查询; 劣势:存储的数据缺少结构化
列存储数据库
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展;
劣势:功能相对局限
文档型数据库
典型应用:web应用(与key-value类似,value是结构化的)
数据模型:一系列键值对
优势:数据结构要求不严格;
劣势:查询性能不高,而且缺乏统一的查询语法
图形(Graph)数据库
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法;
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
特点
在大数据存储上具备关系型数据库无法比拟的性能优势:
-
易扩展:
NoSQL
数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。 -
大数据量,高性能:
NoSQL
数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。 -
灵活的数据模型:
NoSQL
无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这个点在大数据量的web2.0
时代尤其明显。 -
高可用:
NoSQL
在不太影响性能的情况下,就可以方便的实现高可用的架构。比如Cassandra,HBase
模型,通过复制模型也能实现高可用。
Redis
概述
Redis
是用C
语言开发的一个开源的高性能键值对(key-value)
数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis
支持的键值数据类型如下:
1)字符串类型; 2)散列类型;
3)列表类型; 4)集合类型; 5)有序集合类型
Redis
的应用场景:缓存(数据查询、短连接、新闻内容、商品内容等)(最多使用);聊天室的在线好友列表。任务队列。(秒杀、抢购、12306
等);应用排行榜;网站访问统计;数据过期处理(精确到毫秒);分布式集群架构中的session
分离。
Jedis
Redis
不仅是使用命令来操作,基本上主流的语言都是有客户端支持,比如java、C、C#、C++、php、Node.js、Go
等。- 在官方网站里列一些Java的客户端,有
Jedis、Redisson、Jredis、JDBC-Redis
等,其中官方推荐使用Jedis
和Redisson
。在企业中用的最多的就是Jedis
。
连接池连接
@Test
public void testJedisPool() {
// 1.获取连接池配置对象,设置配置项
JedisPoolConfig config = new JedisPoolConfig();
// 最大连接数
config.setMaxTotal(30);
// 最大空闲数
config.setMaxIdle(10);
// 2.获取连接池
JedisPool jedisPool = new JedisPool(config, "192.168.137.128", 6379);
// 3.获取核心对象
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
// 4.设置数据
jedis.set("name", "kuraki");
// 5.获取数据
String name = jedis.get("name");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
// 虚拟机关闭时,释放pool资源
if (jedisPool != null) {
jedisPool.close();
}
}
}
单实例连接
Test
public void testJedisSingle() {
// 设置ip地址和端口号
Jedis jedis = new Jedis("192.168.137.128", 6379);
// 设置数据
jedis.set("name", "kuraki");
// 获取数据
String name = jedis.get("name");
// 释放资源
jedis.close();
}
Redis的数据结构
存储String
字符串类型是Redis
中最为基础的数据存储类型,它在Redis
中是二进制安全的,这边意味着该类型存入和获取的数据相同。在Redis
中字符串类型的Value最多可以容纳的数据长度是512M
。
常用命令
set key value
:设定key
持有指定的字符串value
,如果该key
存在则进行覆盖操作。总是返回OK。get key
:获取key
的value
。如果与该key
关联的value
不是String
类型,redis
将返回错误信息,因为get
命令只能用于获取String value
;如果该**key
不存在,返回nil
(类似null
)。getset key value
:先获取该key
的值,然后再设置该key
的值。del key
:删除指定key
,返回值是一个integer
值。incr key
:将指定的key
的value
原子性的递增。- 如果该
key
不存在,其初始值为0,在incr
之后其值为1。 - 如果
value
的值不能成功转成整型,如hello
,则该操作将执行失败并返回相应的错误信息。
- 如果该
decr key
:将指定的key的value原子性的递减。- 如果该
key
不存在,其初始值为0,在incr
之后其值为-1; - 如果
value
的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息。
- 如果该
扩展命令
incrby key increment
:将指定的key
的value
原子性增加increment
。- 如果该
key
不存在,其初始值为0;在incrby
之后,该值为increment
。 - 如果该值不能转成整型,执行失败并返回错误信息。
- 如果该
decrby key decrement
:将指定的key
的value
原子性减少decrement
。- 如果
key
不存在,其初始值为0;在decrby
之后,该值为-decrement
。 - 如果该值不能转成整型,执行失败并返回错误信息。
- 如果
append key value
:拼凑字符串。- 如果该
key
存在,则在原有的value
后追加该值; - 如果该
key
不存在,则重新创建一个key/value
。
- 如果该
存储Hash
Redis
中的Hash
类型可以看成具有String key
和String Value
的map
容器。所以该类型非常适合于存储对象的信息,如Username、Password
和Age
等。如果Hash
中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash
可以存储几十亿个键值对。
常用命令
-
赋值
hset key field value
:为指定的key
设定field/value
对(键值对)hmset key field value[field2 value2…]
:设置key
中的多个field/value
。
-
取值
hget key field
:返回指定的key
中的field
的值;hmget key fileds
:获取key
中的多个field
的值;hgetall key
:获取key中的所有field-value
。
-
删除
hdel key field[field… ]
:可以删除一个或多个字段,返回值是被删除的字段个数;del key
:删除整个list
。
-
增加数字
hincrby key field increment
:设置key
中filed
的值增加increment
。
扩展命令
hexists key field
:判断指定的key
中的filed
是否存在hlen key
:获取key
所包含的field
的数量hkeys key
:获取所有的key
hvals key
:获取所有的value
存储list
-
在
Redis
中,List
类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,该键并不存在,Redis
将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会从数据库中删除。List
中可以包含的最大元素量是4294967295。 -
从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效。
ArrayList
使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计位移操作,所以比较慢。LinkedList
使用双向链表方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针即可,速度非常快。然而通过下标查询元素时需要从头开始索引,所以比较慢。
-
双向链表中添加数据
-
双向链表中删除数据
常用命令
- 两端添加
lpush key values[value1 value2…]
:在指定的key
所关联的list
的头部插入所有的values
,如果该key
不存在,该命令在插入前创建一个与该key
关联的空链表,之后再向该链表的头部插入数据。插入成功后,返回元素的个数。rpush key values[value1 value2…]
:在该list
的尾部添加元素。
- 查看链表
lrange key start end
:获取链表中从start到end的元素值,start、end
从0开始计数,也可为负数,若为-1则表示链表尾部的元素,-2则表示倒数第二个,依次类推…
- 两端弹出
lpop key
:返回并弹出指定的key
关联的链表中的第一个元素,即头部元素。如果该key
不存在,返回nil
;若key
存在,则返回链表的头部元素。rpop key
:从尾部弹出元素。
- 获取列表中元素的个数
llen key
:返回指定的key
关联的链表中的元素的数量。
扩展命令
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/after pivot value
:在pivot
元素前或者后插入value
这个元素。rpoplpush resource destination
:将链表中的尾部元素弹出并添加到头部。
【补充】resource和destination代表的都是key。
使用场景
Redis
链表经常会被用于消息队列的服务,以完成多程序之间的消息交换。假设一个应用程序正在执行LPUSH
操作向链表中添加新的元素,我们通常将这样的程序称之为生产者(Producer)
,而另外一个应用程序正在执行RPOP
操作从链表中取出元素,我们称之为消费者(Consumer)
。- 如果此时,消费者程序在取出消息元素后立刻崩溃,由于该消息已经被取出且没有被正常处理,那我们就可以认为该消息已经丢失,由此可能导致业务数据丢失,或业务状态的不一致等现象的发生。然而通过使用
RPOPLPUSH
命令,消费者程序从主消息队列中取出消息之后再将其插入到备份队列中,直到消费者程序完成正常的处理逻辑后再将该消息从备份队列中删除。 - 同时我们还可以提供一个守护线程,当发现备份队列中的消息过期时,可以重新将其放回到主消息队列中,以便其它的消费者程序继续处理。
存储set
- 在
Redis
中,可以将Se
t类型看作没有排序的字符集合,和List
类型一样,也可以在该类型的数据值上执行添加、删除或判断某一个元素是否存在等操作。这些操作的时间复杂度为O(1)
,即常量时间内完成此操作。Set
可包含的最大元素数量是4294967295。 - 和
List
类型不同的是,Set
集合中不允许出现重复的元素。和List
类型相比,Set
类型在功能上还存在着一个非常重要的特性,即在服务器端完成多个Sets
之间的聚合计算操作,如unions、intersections
和differences
。由于这些操作均在服务器端完成,因此效率极高,而且节省了大量的网络IO
开销。
常用命令
- 添加/删除命令
sadd key values[value1、value2…]
:向set
中添加数据,如果该key
的值已有则不会重复添加。srem key members[member1、member2…]
:删除set
中指定的成员。
- 获取集合中的元素
smembers key
:获取set中所有的成员;sismember key member
:判断参数中指定成员是否在该set
中,1表示存在,0表示不存在或者该key本身就不存在。(无论集合中有多少元素都可以急速的返回结果)。
- 差集运算
A-B
sdiff key1 key2...
:返回key1
与key2
中相差的成员,与key
的顺序有关。返回差集。
- 交集运算
A∩B
sinter key1 key2 key3…
:返回交集。
- 并集运算
A∪B
sunion key1 key2 key3 …
:返回并集。
扩展命令
scard key
:获取set中成员的数量;srandmember key
:随机返回set
中的一个成员;sdiffstore destination key1 key2
:将key1、key2
相差的成员存储在destination
上;sinterstore destination key[key…]
:将返回的交集存储在destination
上;sunionstore destination key[key…]
:将返回的并集存储在destination
上。
使用场景
- 可以使用
Redis
的Set
数据类型跟踪一些唯一性数据,比如访问某一博客的唯一IP
地址信息。对于此,我们仅需要在每次访问该博客时将访问者的IP
存入Redis
中,Set
数据类型会自动保证IP
地址的唯一性。 - 充分利用
Set
类型的服务器聚合操作方便、高效的特性,可以用于维护数据对象之间的关联关系。比如如何获得哪些同时购买了多个商品的客户,只需要购买了这些商品的客户Set
,使用intersections
命令就可以发挥它的方便和效率的优势了。
存储sortedset
Sorted-Set
和Set
类型极其类似,它们都是字符串的集合,都不允许重复的成员出现在一个Set
中。它们之间的主要差别是Sorted-Set
中的每一个成员都会有一个分数(score)与之关联,Redis
正是通过分数来为集合中的成员进行从大到小的排序。然而需要指出的是,尽管Sorted-Set
中的成员必须是唯一的,但是分数(score)却是可以重复的。- 在
Sorted-Set
中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合中成员数量的对数。由于Sorted-Set
中的成员在集合中的位置时有序的,因此,即便是访问位于集合中部的成员也是相当高效的。Redis
所具有的这一特征在很多其它类型的数据库中很难实现,换句话说,在该点上要想达到和Redis
同样的高效,在其它数据库中进行建模是非常困难的。例如:游戏排名、微博热搜等。
常用命令
- 添加元素
zadd key score1 member score2 member
:将所有成员以及该成员的分数放到sorted-set
中。如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数,不包含之前已经存在的元素。
- 获得元素
zscore key member
:返回指定成员的分数zcard key
:获取集合中的成员数量
- 删除元素
zrem key member[member…]
:移除集合中指定的成员,可以指定多个成员。
- 范围查询
zrange key start end[withscores]
:获取集合中脚标为start ~ end
的成员,[withscores]
参数表名返回的成员包含其分数。zrevrange key start stop[withscores]
:按照元素分数从大到小的顺序返回索引,从start
到stop
之间的所有元素(包含两端的元素)zremrangebyrank key start stop
:按照排名范围删除元素。zremrangebyscore key min max
:按照分数范围删除元素。
扩展命令
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
:返回成员在集合中的排名(从大到小)。
使用场景
- 可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行
ZADD
命令更新玩家的分数,此后再通过ZRANGE
命令获取积分TOPTEN
的用户信息。当然我们也可以利用ZRANK
命令通过username
来获取玩家的排名信息。最后我们将组合使用ZRANGE
和ZRANK
命令快速的获取和某个玩家积分相近的其他用户的信息。 Sorted-Set
类型还可以用于构建索引数据。
keys的通用操作
keys pattern
:获取所有与pattern
匹配的key
,返回所有与该key
匹配的keys
。*
表示任意一个或多个字符,?
表示任意一个字符。del key1 key2 …
:删除指定的key
。exists key
:判断该key是否存在,1代表存在,0表示不存在rname key newkey
:为当前的key重命名expire key
:设置过期时间,单位:秒。ttl key
:获取该key
所剩的超时时间,如果没有设置超时,返回-1。如果返回-2,表示超时不存在。type key
:获取指定key
的类型。该命令将以字符串的格式返回。返回的字符串为string、list、set、hash
和zset
,如果key
不存在则返回none
。
Redis特性
多数据库
- 一个
Redis
实例可以包括多个数据库,客户端可以指定连接某个redis
实例的哪个数据库,就好比一个mysql
中创建多个数据库,客户端连接时指定连接哪个数据库。 - 一个
redis
实例最多可提供16个数据库,下标从0到15,客户端默认连接第0号数据库,也可以通过select选择连接哪个数据库,如下连接1号库:move newkey 1
:将当前库的key移植到1号库中。
- 服务器命令
ping
,测试连接是否存活echo
,在命令行打印一些内容select
,选择数据库。Redis
数据库编号从0~15
,可以选择任意一个数据数据库来进行数据的存取。quit
,退出连接dbsize
,返回当前数据库中key
的数目。info
,获取服务器的信息和统计。flushdb
,删除当前选择数据库中所有的key
。flushall
,删除所有数据库中的所有key
。
Redis事务
- 和众多数据库一样,
Redis
作为NoSQL
数据库也同样提供了事务机制。 - 在
Redis
中,MULTI / EXEC / DISCARD
这三个命令是我们实现事务的基石。
事务特征
- 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,
Redis
不会再为其它客户端的请求提供任何服务,从而保证了事务中的所有命令被原子的执行。 - 和关系性数据库中的事务相比,在
Redis
事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。 - 可以通过
MULTI
命令开启一个事务,相当于BEGINTRANSACTION
语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC / DISCARD
命令来提交/回滚该事务内的所有操作。这两个Redis
命令可被视为等同于关系型数据库中的COMMIT / ROLLBACL
语句。 - 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行
EXEC
命令之后,那么该事务中的所有命令都会被服务器执行。 - 当使用
Append-Only
模式时,Redis
会通过调用系统函数write
将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,那么此时也许只有部分数据会被写入到磁盘,而另外一部分数据却已经丢失。Redis
服务器会在重新启动时执行一系列必要的一致性检测,一旦发生类似问题,就会立即退出并给出相应的错误提示。此时,充分利用Redis
工具包中的redis-check-aof
工具,该工具可以帮助我们定位到数据不一致的错误,并将已写入的部分数据进行回滚。
总结
- 叙述了
NoSQL
数据库的概念、分类以及特点; - 详细介绍了
Redis
数据库的数据结构及其命令; - 简述一些通用的操作
- 简单介绍了
redis
数据库的一些特性。