Redis小结
一. Redis简介
Redis是一个速度非常快的高性能的key-value存储系统。redis的出现,很大程度补偿了memcached这类key/value存储的不足。Redis支持存储五种value数据类型,包括string(字符串)、list(链表)、set(集合)、hash(哈希类型)和zset(sorted set --有序集合)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中。redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文 件,并且在此基础上实现了master-slave(主从)同步。Redis是基于内存的数据结构存储开源系统,采用C语言编写,数据存在内存里面,运行效率极高。可作为内存数据库、缓存或消息代理中间件,前两种情况实际当中使用更多些。
Redis支持的客户端操作语言非常丰富,达到40多种。就Java来说,也有很多访问驱动实现,我们最常用的还是Jedis。 Jedis活跃度很高,能够跟上Redis服务端发布的最新功能,而且使用简单,基本和Redis命令行语法相似。
二. Redis的优点
- 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
- 支持丰富数据类型,支持string,list,set,sorted set,hash
- 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
三. Redis与其他一些数据库和缓存服务器的特性与功能的对比如下:
名称 |
类型 |
数据存储选项 |
查询类型 |
附加功能 |
---|---|---|---|---|
Redis |
使用内存存储(in-memory)的非关系数据库 |
字符串、列表、集合、散列表、有序集合 |
每种数据类型都有自己的专属命令,另外还有批量操作(bulk operation)和不完全(partial)的事务支持 |
发布与订阅,主从复制(master/slave replication),持久化,脚本(存储过程,stored procedure) |
memcached |
使用内存存储的键值缓存 |
键值之间的映射 |
创建命令、读取命令、更新命令、删除命令以及其他几个命令 |
为提升性能而设的多线程服务器 |
MySQL |
关系数据库 |
每个数据库可以包含多个表,每个表可以包含多个行;可以处理多个表的视图(view);支持空间(spatial)和第三方扩展 |
|
支持ACID性质(需要使用InnoDB),主从复制和主主复制 (master/master replication) |
PostgreSQL |
关系数据库 |
每个数据库可以包含多个表,每个表可以包含多个行;可以处理多个表的视图;支持空间和第三方扩展;支持可定制类型 |
|
支持ACID性质,主从复制,由第三方支持的多主复制(multi-master replication) |
MongoDB |
使用硬盘存储(on-disk)的非关系文档存储 |
每个数据库可以包含多个表,每个表可以包含多个无schema(schema-less)的BSON文档 |
创建命令、读取命令、更新命令、删除命令、条件查询命令等 |
支持map-reduce操作,主从复制,分片,空间索引(spatial index) |
四. Redis的持久化方式
在使用类似Redis这样的内存数据库时,一个首先要考虑的问题就是“当服务器被关闭时,服务器存储的数据将何去何从呢?”
Redis拥有两种不同形式的持久化方法,它们都可以用小而紧凑的格式将存储在内存中的数据写入硬盘:
第一种持久化方法为RDB,即时间点转储(point-in-time dump)。有一份数据,就把这一份数据整体保存一份,每隔一定的时间就保存一下数据,保存的是最终的结果。转储操作既可以在“指定时间段内有指定数量的写操作执行”这一条件被满足时执行,又可以通过调用两条转储到硬盘(dump-to-disk)命令中的任何一条来执行;
第二种持久化方法是AOF,将所有修改了数据库的命令都写入一个只追加(append-only)文件里面,保存的是命令操作。用户可以根据数据的重要程度,将只追加写入设置为从不同步(sync)、每秒同步一次或者每写入一个命令就同步一次。
五. Redis的主从同步
Redis实现了主从复制特性:执行复制的从服务器会连接上主服务器,接收主服务器发送的整个数据库的初始副本(copy);之后主服务器执行的写命令,都会被发送给所有连接着的从服务器去执行,从而实时地更新从服务器的数据集。因为从服务器包含的数据会不断地进行更新,所以客户端可以向任意一个从服务器发送读请求,以此来避免对主服务器进行集中式的访问。
六. Redis的value的数据类型
1. String(字符串)
string是redis最基本的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
1.1 常用命令
除了get、set、incr、decr mget等操作外,Redis还提供了下面一些操作:
- 获取字符串长度
- 往字符串append内容
- 设置和获取字符串的某一段内容
- 设置及获取字符串的某一位(bit)
- 批量设置一系列字符串的内容
1.2 应用场景
String是最常用的一种数据类型,普通的key/value存储都可以归为此类,value其实不仅是String, 也可以是数字:比如想知道什么时候封锁一个IP地址(访问超过几次)。INCRBY命令让这些变得很容易,通过原子递增保持计数。
1.3 实现方式
m,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。
1.4 实例
1 // get,set 2 jedis.set("hello", "world"); 3 print(1, jedis.get("hello")); 4 jedis.rename("hello", "newhello"); 5 print(1, jedis.get("newhello")); 6 jedis.setex("hello2", 15, "world"); 7 // 数值操作 8 jedis.set("pv", "100"); 9 jedis.incr("pv"); 10 jedis.decrBy("pv", 5); 11 print(2, jedis.get("pv")); 12 print(3, jedis.keys("*"));
2. List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。列表最多可存储 2^32 - 1 元素 (4294967295, 每个列表可存储40多亿)。
2.1 常用命令
lpush,rpush,lpop,rpop,lrange,BLPOP(阻塞版)等。
2.2 应用场景
Redis list的应用场景非常多,也是Redis最重要的数据结构之一。
我们可以轻松地实现最新消息、排行榜等功能(比如新浪微博的 TimeLine )。
Lists的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出进行执行。
2.3 实现方式
Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
2.4 实例
1 // 列表操作, 最近来访, 粉丝列表,消息队列 2 String listName = "list"; 3 jedis.del(listName); 4 for (int i = 0; i < 10; ++i) { 5 jedis.lpush(listName, "a" + String.valueOf(i)); 6 } 7 print(4, jedis.lrange(listName, 0, 12)); // 最近来访10个id 8 print(5, jedis.llen(listName)); 9 print(6, jedis.lpop(listName)); 10 print(7, jedis.llen(listName)); 11 print(8, jedis.lrange(listName, 2, 6)); // 最近来访10个id 12 print(9, jedis.lindex(listName, 3)); 13 print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.AFTER, "a4", "xx")); 14 print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.BEFORE, "a4", "bb")); 15 print(11, jedis.lrange(listName, 0, 12));
3.Set(集合)
Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
3.1 常用命令
sadd,srem,spop,sdiff ,smembers,sunion 等。
3.2 应用场景
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
- 共同好友、二度好友
- 利用唯一性,可以统计访问网站的所有独立 IP
- 好友推荐的时候,根据 tag 求交集,大于某个 threshold 就可以推荐
在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。因为 Redis 非常人性化的为集合提供了求交集、并集、差集等操作,那么就可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
3.3 实现方式
set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
3.4 实例
1 String likeKey1 = "newsLike1"; 2 String likeKey2 = "newsLike2"; 3 for (int i = 0; i < 10; ++i) { 4 jedis.sadd(likeKey1, String.valueOf(i)); 5 jedis.sadd(likeKey2, String.valueOf(i * 2)); 6 } 7 print(20, jedis.smembers(likeKey1)); 8 print(21, jedis.smembers(likeKey2)); 9 print(22, jedis.sunion(likeKey1, likeKey2)); 10 print(23, jedis.sdiff(likeKey1, likeKey2)); 11 print(24, jedis.sinter(likeKey1, likeKey2)); 12 print(25, jedis.sismember(likeKey1, "12")); 13 print(26, jedis.sismember(likeKey2, "12")); 14 jedis.srem(likeKey1, "5"); 15 print(27, jedis.smembers(likeKey1)); 16 // 从1移动到2 17 jedis.smove(likeKey2, likeKey1, "14"); 18 print(28, jedis.smembers(likeKey1)); 19 print(29, jedis.scard(likeKey1));
4.Hash(哈希)
Redis hash 是一个键值对集合。它是一个string类型的field和value的映射表,hash特别适合用于存储对象。集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
4.1 常用命令
hget,hset,hgetall 等。
4.2 应用场景
存储、读取、修改用户属性。
Redis的Hash实际是内部存储的Value为一个HashMap, 并提供了直接存取这个Map成员的接口, 如:hmset user:001 name "李三" age 18 birthday "20010101" ,也就是说,Key仍然是用户ID,value是一个Map,这个Map的key是成员的属性名,value是属性值, 这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。Redis 的 Hash 结构可以像在数据库中 Update 一个属性一样只修改某一项属性值。
4.3 实现方式
Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
4.4 实例
1 // hash, 可变字段 2 String userKey = "userxx"; 3 jedis.hset(userKey, "name", "jim"); 4 jedis.hset(userKey, "age", "12"); 5 jedis.hset(userKey, "phone", "18666666666"); 6 print(12, jedis.hget(userKey, "name")); 7 print(13, jedis.hgetAll(userKey)); 8 jedis.hdel(userKey, "phone"); 9 print(14, jedis.hgetAll(userKey)); 10 print(15, jedis.hexists(userKey, "email")); 11 print(16, jedis.hexists(userKey, "age")); 12 print(17, jedis.hkeys(userKey)); 13 print(18, jedis.hvals(userKey)); 14 jedis.hsetnx(userKey, "school", "zju");//这个方法是先判断有没有这个字段,没有的话才进行设置 15 jedis.hsetnx(userKey, "name", "yxy"); 16 print(19, jedis.hgetAll(userKey));
5.zset(sorted set:有序集合)
Redis zset 和 set 一tring类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
5.1 常用命令
zadd,zrange,zrem,zcard等
5.2 使用场景
- 带有权重的元素,比如一个游戏的用户得分排行榜
- 比较复杂的数据结构,一般用到的场景不算太多
以某个条件为权重,比如按顶的次数排序。ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名,非常直接而且操作容易。
Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。比如:twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。比如:全班同学成绩的SortedSets,value可以是同学的学号,而score就可以是其考试得分,这样数据插入集合的,就已经进行了天然的排序。
另外还可以用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
5.3 实现方式
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
5.4 实例
1 // 排序集合,有限队列,排行榜 2 String rankKey = "rankKey"; 3 jedis.zadd(rankKey, 15, "Jim"); 4 jedis.zadd(rankKey, 60, "Ben"); 5 jedis.zadd(rankKey, 90, "Lee"); 6 jedis.zadd(rankKey, 75, "Lucy"); 7 jedis.zadd(rankKey, 80, "Mei"); 8 print(30, jedis.zcard(rankKey)); 9 print(31, jedis.zcount(rankKey, 61, 100)); 10 // 改错卷了 11 print(32, jedis.zscore(rankKey, "Lucy")); 12 jedis.zincrby(rankKey, 2, "Lucy"); 13 print(33, jedis.zscore(rankKey, "Lucy")); 14 jedis.zincrby(rankKey, 2, "Luc"); 15 print(34, jedis.zscore(rankKey, "Luc")); 16 print(35, jedis.zcount(rankKey, 0, 100)); 17 // 1-4 名 Luc 18 print(36, jedis.zrange(rankKey, 0, 10)); 19 print(36, jedis.zrange(rankKey, 1, 3)); 20 print(36, jedis.zrevrange(rankKey, 1, 3)); 21 for (Tuple tuple : jedis.zrangeByScoreWithScores(rankKey, "60", "100")) { 22 print(37, tuple.getElement() + ":" + String.valueOf(tuple.getScore())); 23 } 24 print(38, jedis.zrank(rankKey, "Ben")); 25 print(39, jedis.zrevrank(rankKey, "Ben")); 26 String setKey = "zset"; 27 jedis.zadd(setKey, 1, "a"); 28 jedis.zadd(setKey, 1, "b"); 29 jedis.zadd(setKey, 1, "c"); 30 jedis.zadd(setKey, 1, "d"); 31 jedis.zadd(setKey, 1, "e"); 32 print(40, jedis.zlexcount(setKey, "-", "+")); 33 print(41, jedis.zlexcount(setKey, "(b", "[d")); 34 print(42, jedis.zlexcount(setKey, "[b", "[d")); 35 jedis.zrem(setKey, "b"); 36 print(43, jedis.zrange(setKey, 0, 10)); 37 jedis.zremrangeByLex(setKey, "(c", "+"); 38 print(44, jedis.zrange(setKey, 0, 2));
参考:http://www.epubit.com.cn/article/200
http://www.jb51.net/article/54774.htm
http://blog.csdn.net/gaogaoshan/article/details/41039581/