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
Redis支持的客户端操做语言很是丰富,达到40多种。就Java来讲,也有不少访问驱动实现,咱们最经常使用的仍是Jedis。 Jedis活跃度很高,可以跟上Redis服务端发布的最新功能,并且使用简单,基本和Redis命令行语法类似。数据库
名称数组 |
类型缓存 |
数据存储选项安全 |
查询类型服务器 |
附加功能数据结构 |
---|---|---|---|---|
Redis多线程 |
使用内存存储(in-memory)的非关系数据库并发 |
字符串、列表、集合、散列表、有序集合app |
每种数据类型都有本身的专属命令,另外还有批量操做(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拥有两种不一样形式的持久化方法,它们均可以用小而紧凑的格式将存储在内存中的数据写入硬盘:
第一种持久化方法为RDB,即时间点转储(point-in-time dump)。有一份数据,就把这一份数据总体保存一份,每隔必定的时间就保存一下数据,保存的是最终的结果。转储操做既能够在“指定时间段内有指定数量的写操做执行”这一条件被知足时执行,又能够经过调用两条转储到硬盘(dump-to-disk)命令中的任何一条来执行;
第二种持久化方法是AOF,将全部修改了数据库的命令都写入一个只追加(append-only)文件里面,保存的是命令操做。用户能够根据数据的重要程度,将只追加写入设置为从不一样步(sync)、每秒同步一次或者每写入一个命令就同步一次。
Redis实现了主从复制特性:执行复制的从服务器会链接上主服务器,接收主服务器发送的整个数据库的初始副本(copy);以后主服务器执行的写命令,都会被发送给全部链接着的从服务器去执行,从而实时地更新从服务器的数据集。由于从服务器包含的数据会不断地进行更新,因此客户端能够向任意一个从服务器发送读请求,以此来避免对主服务器进行集中式的访问。
string是redis最基本的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string能够包含任何数据。好比jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
除了get、set、incr、decr mget等操做外,Redis还提供了下面一些操做:
String是最经常使用的一种数据类型,普通的key/value存储均可以归为此类,value其实不只是String, 也能够是数字:好比想知道何时封锁一个IP地址(访问超过几回)。INCRBY命令让这些变得很容易,经过原子递增保持计数。
m,decr等操做时会转成数值型进行计算,此时redisObject的encoding字段为int。
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("*"));
Redis 列表是简单的字符串列表,按照插入顺序排序。能够添加一个元素到列表的头部(左边)或者尾部(右边)。列表最多可存储 2^32 - 1 元素 (4294967295, 每一个列表可存储40多亿)。
lpush,rpush,lpop,rpop,lrange,BLPOP(阻塞版)等。
Redis list的应用场景很是多,也是Redis最重要的数据结构之一。
咱们能够轻松地实现最新消息、排行榜等功能(好比新浪微博的 TimeLine )。
Lists的另外一个应用就是消息队列,能够利用List的PUSH操做,将任务存在Lists中,而后工做线程再用POP操做将任务取出进行执行。
Redis list的实现为一个双向链表,便可以支持反向查找和遍历,更方便操做,不过带来了部分额外的内存开销,Redis内部的不少实现,包括发送缓冲队列等也都是用的这个数据结构。
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));
Redis的Set是string类型的无序集合。集合是经过哈希表实现的,因此添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 2^32 - 1 (4294967295, 每一个集合可存储40多亿个成员)。
sadd,srem,spop,sdiff ,smembers,sunion 等。
Redis set对外提供的功能与list相似是一个列表的功能,特殊之处在于set是能够自动排重的,当你须要存储一个列表数据,又不但愿出现重复数据时,set是一个很好的选择,而且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
在微博应用中,能够将一个用户全部的关注人存在一个集合中,将其全部粉丝存在一个集合。由于 Redis 很是人性化的为集合提供了求交集、并集、差集等操做,那么就能够很是方便的实现如共同关注、共同喜爱、二度好友等功能,对上面的全部集合操做,你还可使用不一样的命令选择将结果返回给客户端仍是存集到一个新的集合中。
set 的内部实现是一个 value永远为null的HashMap,实际就是经过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的缘由。
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));
Redis hash 是一个键值对集合。它是一个string类型的field和value的映射表,hash特别适合用于存储对象。集合中最大的成员数为 2^32 - 1 (4294967295, 每一个集合可存储40多亿个成员)。
hget,hset,hgetall 等。
存储、读取、修改用户属性。
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 一个属性同样只修改某一项属性值。
Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不一样实现,这个Hash的成员比较少时Redis为了节省内存会采用相似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
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));
Redis zset 和 set 一tring类型元素的集合,且不容许重复的成员。不一样的是每一个元素都会关联一个double类型的分数。redis正是经过分数来为集合中的成员进行从小到大的排序。zset的成员是惟一的,但分数(score)却能够重复。
zadd,zrange,zrem,zcard等
以某个条件为权重,好比按顶的次数排序。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的倒序来获取工做任务。让重要的任务优先执行。
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是全部的成员,排序依据是HashMap里存的score,使用跳跃表的结构能够得到比较高的查找效率,而且在实现上比较简单。
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