1. Redis的简介
Redis是一个基于内存的高性能key-value数据库,与memcached类似,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
1.1. Redis特点
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供了list、set、zset、hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份
1.2. Redis优势
- 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1),Redis能读的速度是110000次/s,写的速度是81000次/s
- 支持丰富数据类型,支持string,list,set,sorted set,hash
- 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
2. Redis持久化方式
Redis拥有两种不同形式的持久化方法,它们都可以用小而紧凑的格式将存储在内存中的数据写入硬盘
- RDB持久化:即时间点转储(point-in-time dump)。有一份数据,就把这一份数据整体保存一份,每隔一定的时间就保存一下数据,保存的是最终的结果。转储操作既可以在“指定时间段内有指定数量的写操作执行”这一条件被满足时执行,又可以通过调用两条转储到硬盘(dump-to-disk)命令中的任何一条来执行
- AOF持久化:将所有修改了数据库的命令都写入一个只追加(append-only)文件里面,保存的是命令操作。用户可以根据数据的重要程度,将只追加写入设置为从不同步(sync)、每秒同步一次或者每写入一个命令就同步一次。
3. Redis的value数据类型
3.1. string字符串
- string是redis最基本的类型,一个key对应一个value。
- string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
- string类型是Redis最基本的数据类型,一个键最大能存储512MB
3.1.1 常用命令
除了get、set、incr、decr mget等操作外,Redis还提供了下面一些操作:
- 获取字符串长度
- 往字符串append内容
- 设置和获取字符串的某一段内容
- 设置及获取字符串的某一位(bit)
- 批量设置一系列字符串的内容
3.1.2 应用场景
String是最常用的一种数据类型,普通的key/value存储都可以归为此类,value其实不仅是String, 也可以是数字:比如想知道什么时候封锁一个IP地址(访问超过几次)。INCRBY命令让这些变得很容易,通过原子递增保持计数。
3.1.3 实现方式
incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。
3.1.4 实例
//连接redis Jedis jedis = new Jedis("localhost",6379); //验证密码 jedis.auth("123"); System.out.println("连接成功"); System.out.println("-------------get/set操作---------------"); //set get jedis.set("Hello", "world1"); System.out.println(jedis.get("Hello")); //重命名key jedis.rename("Hello", "newHello"); System.out.println(jedis.get("newHello")); jedis.setex("Hello2", 3, "world2"); System.out.println(jedis.get("Hello2")); //获取一个或多个key的value List<String> mget = jedis.mget("newHello","Hello2"); System.out.println(mget); System.out.println("-------------数值操作---------------"); //数值操作 jedis.set("pv", "100"); jedis.incr("pv"); System.out.println(jedis.get("pv")); jedis.decrBy("pv",5); System.out.println(jedis.get("pv")); System.out.println(jedis.keys("*")); //断开连接 jedis.close(); |
输出结果:
3.2 List列表
Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。列表最多可存储 2^32 - 1 元素 (4294967295, 每个列表可存储40多亿)。
3.2.1 常用命令
lpush,rpush,lpop,rpop,lrange,BLPOP(阻塞版)等。
3.2.2 应用场景
- Redis list的应用场景非常多,也是Redis最重要的数据结构之一。
- 我们可以轻松地实现最新消息、排行榜等功能(比如新浪微博的TimeLine )。
- Lists的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出进行执行。
3.2.3 实现方式
Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
3.2.4 实例
//连接redis Jedisjedis = new Jedis("localhost",6379); //验证密码 jedis.auth("123"); System.out.println("连接成功"); System.out.println("-------------列表操作---------------"); // 列表操作, 最近来访, 粉丝列表,消息队列 StringlistName = "list"; jedis.del(listName); for (inti = 0; i < 10; ++i) { jedis.lpush(listName, "a" + String.valueOf(i)); } System.out.println(jedis.lrange(listName, 0, 12)); System.out.println("listName的size为:"+jedis.llen(listName)); System.out.println(jedis.lpop(listName)); System.out.println("listName的size为:"+jedis.llen(listName)); System.out.println(jedis.lrange(listName, 2, 6)); System.out.println(jedis.lindex(listName, 3)); //在a4值之后插入 System.out.println(jedis.linsert(listName, BinaryClient.LIST_POSITION.AFTER, "a4", "xx")); //在a4值之前插入 System.out.println(jedis.linsert(listName, BinaryClient.LIST_POSITION.BEFORE, "a4", "bb")); System.out.println(jedis.lrange(listName, 0, 12)); //断开连接 jedis.close(); |
输出结果:
3.3 Set集合
Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
3.3.1 常用命令
sadd,srem,spop,sdiff,smembers,sunion 等。
3.3.2 应用场景
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
- 共同好友、二度好友
- 利用唯一性,可以统计访问网站的所有独立 IP
- 好友推荐的时候,根据 tag 求交集,大于某个 threshold 就可以推荐
在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。因为 Redis 非常人性化的为集合提供了求交集、并集、差集等操作,那么就可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
3.3.3 实现方式
set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
3.3.4 实例
//连接redis Jedis jedis = new Jedis("localhost",6379); //验证密码 jedis.auth("123"); System.out.println("连接成功"); System.out.println("-------------set集合---------------"); String likeKey1 = "newsLike1"; String likeKey2 = "newsLike2"; for (inti = 0; i < 10; ++i) { jedis.sadd(likeKey1, String.valueOf(i)); jedis.sadd(likeKey2, String.valueOf(i * 2)); } System.out.println("likekey1:"+jedis.smembers(likeKey1)); System.out.println("likekey2:"+jedis.smembers(likeKey2)); //并集 System.out.println("并集:"+jedis.sunion(likeKey1, likeKey2)); //差集 System.out.println("差集:"+jedis.sdiff(likeKey1, likeKey2)); //交集 System.out.println("交集:"+jedis.sinter(likeKey1, likeKey2)); //判断是否有该值 System.out.println(jedis.sismember(likeKey1, "12")); System.out.println(jedis.sismember(likeKey2, "12")); //删除某个值 jedis.srem(likeKey1, "5"); System.out.println("likekey1:"+jedis.smembers(likeKey1)); //将"14"从2移动到1 jedis.smove(likeKey2, likeKey1, "14"); System.out.println("likekey1:"+jedis.smembers(likeKey1)); System.out.println("likekey2:"+jedis.smembers(likeKey2)); System.out.println(jedis.scard(likeKey1)); //断开连接 jedis.close(); |
![](https://i-blog.csdnimg.cn/blog_migrate/69291c2aa8db00c6f1b7ba0b604d0eb4.png)
3.4 Hash哈希
Redis hash 是一个键值对集合。它是一个string类型的field和value的映射表,hash特别适合用于存储对象。集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
3.4.1 常用命令
hget,hset,hgetall 等。
3.4.2 应用场景
存储、读取、修改用户属性。
Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口,如:hmset user:001 name:"李三" age:18birthday:"20010101" ,也就是说,Key仍然是用户ID,value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。Redis 的 Hash 结构可以像在数据库中 Update 一个属性一样只修改某一项属性值。
3.4.3 实现方式
Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht
3.4.4 实例
//连接redis Jedis jedis = new Jedis("localhost",6379); //验证密码 jedis.auth("123"); System.out.println("连接成功"); System.out.println("-------------hash---------------"); // hash, 可变字段 String userKey = "userxx"; jedis.hset(userKey, "name", "jim"); jedis.hset(userKey, "age", "12"); jedis.hset(userKey, "phone", "18666666666"); System.out.println(jedis.hget(userKey, "name")); System.out.println(jedis.hgetAll(userKey)); jedis.hdel(userKey, "phone"); System.out.println(jedis.hgetAll(userKey)); System.out.println(jedis.hexists(userKey, "email")); System.out.println(jedis.hexists(userKey, "age")); System.out.println(jedis.hkeys(userKey)); System.out.println(jedis.hvals(userKey)); jedis.hsetnx(userKey, "school", "zju");//这个方法是先判断有没有这个字段,没有的话才进行设置 jedis.hsetnx(userKey, "name", "yxy"); System.out.println(jedis.hgetAll(userKey)); //断开连接 jedis.close(); |
输出结果:
3.5 Zset(sorted set:有序集合)
Redis zset 和 set 一tring类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
3.5.1 常用命令
zadd,zrange,zrem,zcard等。
3.5.2 应用场景
- 带有权重的元素,比如一个游戏的用户得分排行榜
- 比较复杂的数据结构,一般用到的场景不算太多
以某个条件为权重,比如按顶的次数排序。ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名,非常直接而且操作容易。
Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。比如:twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。比如:全班同学成绩的SortedSets,value可以是同学的学号,而score就可以是其考试得分,这样数据插入集合的,就已经进行了天然的排序。
另外还可以用SortedSets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
3.5.3 实现方式
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
3.5.4 实例
//连接redis Jedis jedis = new Jedis("localhost",6379); //验证密码 jedis.auth("123"); System.out.println("连接成功"); System.out.println("-------------zset---------------"); // 排序集合,有限队列,排行榜 String rankKey = "rankKey"; jedis.zadd(rankKey, 15, "Jim"); jedis.zadd(rankKey, 60, "Ben"); jedis.zadd(rankKey, 90, "Lee"); jedis.zadd(rankKey, 75, "Lucy"); jedis.zadd(rankKey, 80, "Mei"); System.out.println("key的数量:"+jedis.zcard(rankKey)); System.out.println(jedis.zcount(rankKey, 61, 100)); // 改错卷了 System.out.println(jedis.zscore(rankKey, "Lucy")); jedis.zincrby(rankKey, 2, "Lucy"); System.out.println(jedis.zscore(rankKey, "Lucy")); jedis.zincrby(rankKey, 2, "Luc"); System.out.println(jedis.zscore(rankKey, "Luc")); System.out.println(jedis.zcount(rankKey, 0, 100)); // 1-4 名 Luc System.out.println(jedis.zrange(rankKey, 0, 10)); System.out.println(jedis.zrange(rankKey, 1, 3)); System.out.println(jedis.zrevrange(rankKey, 1, 3)); for (Tuple tuple : jedis.zrangeByScoreWithScores(rankKey, "60", "100")) { System.out.println(tuple.getElement() + ":" + String.valueOf(tuple.getScore())); } System.out.println(jedis.zrank(rankKey, "Ben")); System.out.println(jedis.zrevrank(rankKey, "Ben")); String setKey = "zset"; jedis.zadd(setKey, 1, "a"); jedis.zadd(setKey, 1, "b"); jedis.zadd(setKey, 1, "c"); jedis.zadd(setKey, 1, "d"); jedis.zadd(setKey, 1, "e"); //"-" "+"分别表示的得分最小值成员和得分最大值成员 System.out.println(jedis.zlexcount(setKey, "-", "+")); //"(b" 表示不包括b System.out.println(jedis.zlexcount(setKey, "(b", "[d")); //成员名称必须以"["开头 System.out.println(jedis.zlexcount(setKey, "[b", "[d")); jedis.zrem(setKey, "b"); System.out.println(jedis.zrange(setKey, 0, 10)); jedis.zremrangeByLex(setKey, "(c", "+"); System.out.println(jedis.zrange(setKey, 0, 2)); //断开连接 jedis.close(); |
输出结果: