1、基础知识java
1.全局命令python
bigjun@myubuntu:/$ redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> set java jedis OK 127.0.0.1:6379> set python redis-py OK 127.0.0.1:6379> keys * 1) "hello" 2) "python" 3) "java" 4) "myname" 127.0.0.1:6379> dbsize (integer) 4 127.0.0.1:6379> exists java (integer) 1 127.0.0.1:6379> exists not_exist_key (integer) 0 127.0.0.1:6379> del java (integer) 1 127.0.0.1:6379> exists java (integer) 0 127.0.0.1:6379> keys * 1) "hello" 2) "python" 3) "myname" 127.0.0.1:6379> del python myname (integer) 2 127.0.0.1:6379> keys * 1) "hello" 127.0.0.1:6379> get hello "world" 127.0.0.1:6379> expire hello 10 (integer) 1 127.0.0.1:6379> ttl hello (integer) 5 127.0.0.1:6379> ttl hello (integer) 3 127.0.0.1:6379> ttl hello (integer) 2 127.0.0.1:6379> ttl hello (integer) 1 127.0.0.1:6379> ttl hello (integer) 0 127.0.0.1:6379> ttl hello (integer) -2 127.0.0.1:6379> ttl hello (integer) -2 127.0.0.1:6379> get hello (nil) 127.0.0.1:6379> del hello (integer) 0 127.0.0.1:6379> keys * (empty list or set) 127.0.0.1:6379> set a b OK 127.0.0.1:6379> type a string 127.0.0.1:6379> rpush mylist a b c d e f g (integer) 7 127.0.0.1:6379> type mylist list 127.0.0.1:6379> type not_exsit_key none 127.0.0.1:6379> shutdown nosave not connected>
2.数据结构和内部编码mysql
使用type key命令能够返回当前键的数据结构类型,分别包括:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。redis
实际上Redis每种数据结构都有本身底层的内部编码实现,并且是多种实现,这样Redis会在合适的场景选择合适的内部编码。算法
能够经过object encoding命令查询内部编码:sql
127.0.0.1:6379> keys * 1) "a" 127.0.0.1:6379> rpush mylist a b c (integer) 3 127.0.0.1:6379> object encoding a "embstr" 127.0.0.1:6379> object encoding mylist "quicklist"
Redis这样设计有两个好处:数据库
3.单线程架构编程
Redis使用单线程架构和I/O多路复用模型来实现高性能的内存数据库服务。ubuntu
Redis客户端调用都经历了发送命令、执行命令、返回结果三个过程。后端
由于Redis是单线程来处理命令的,因此一条命令从客户端到服务器端不会马上被执行,全部命令都会进入一个队列中,而后逐个被执行,能够肯定不会有两条命令被同时执行,不会产生并发问题,这就是Redis单线程的基本模型。
为何Redis使用单线程模型会达到每秒万级别的处理能力呢:
单线程能带来几个好处:
单线程会有一个问题:
对于每一个命令的执行时间是有要求的,若是某个命令执行过长,会形成其余命令的阻塞,对于Redis这种高性能的服务来讲是致命的,因此Redis是面向快速执行场景的数据库。
2、字符串
字符串类型是Redis最基础的数据结构。首先键都是字符串类型,值能够是字符串(简单的字符串、复杂的字符串(JSON、XML))、数字(整数、浮点数)、甚至是二进制(图片、音频、视频),可是值最大不能超多512MB。
1.命令
(1)经常使用命令
set key [ex seconds] [px milliseconds] [nx|xx]
ex seconds:为键设置秒级过时时间
px milliseconds:为键设置毫秒级过时时间
nx:键必须不存在,才能够设置成功,用于添加
xx:与nx相反,键必须存在,才能够设置成功,用于更新
除了set选项,Redis还提供setex和setnx两个命令:
setex key seconds value:做用和ex选项同样
setnx key value:做用和nx选项同样
先来验证nx和xx两个选项:
127.0.0.1:6379> exists hello (integer) 0 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> setnx hello redis (integer) 0 127.0.0.1:6379> get hello "world" 127.0.0.1:6379> set hello jedis xx OK 127.0.0.1:6379> get hello "jedis"
因为键hello已存在,因此setnx失败,返回结果为0,而set xx成功,返回结果为OK。
因为Redis的单线程命令处理机制,若是有多个客户端同时执行setnx key value,根据setnx特性只有一个客户端能设置成功,setnx能够做为分布式锁的一种实现方案。
127.0.0.1:6379> get hello "jedis" 127.0.0.1:6379> get not_exist_key (nil)
mset key value [key value...]
经过mset命令一次性设置4个键值对:
127.0.0.1:6379> mset a 1 b 2 c 3 d 4 OK
127.0.0.1:6379> mget a b c d 1) "1" 2) "2" 3) "3" 4) "4"
若是某些键不存在,则它的值为空:
127.0.0.1:6379> mget a b f d 1) "1" 2) "2" 3) (nil) 4) "4"
执行n次get命令的时间:n次get时间=n次网络时间+n次命令时间
执行1次mget命令的时间:1次mget时间=1次网络时间+n次命令时间
批量操做有助于提升业务处理效率,可是每次批量操做所发送的命令数不是无节制的,若是数量过多可能形成Redis阻塞或者网络阻塞。
incr key 用于对值作自增操做,返回结果分为三种状况: 1.值不是整数,返回错误。 2.值是整数,返回自增后的结果。 3.键不存在,按照值为0自增,返回结果为1。
除了incr命令,Redis还提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数)
(integer) 0 127.0.0.1:6379> incr key (integer) 1 127.0.0.1:6379> incr key (integer) 2 127.0.0.1:6379> incr hello (error) ERR value is not an integer or out of range
(2)不经常使用命令
127.0.0.1:6379> get hello "jedis" 127.0.0.1:6379> append hello Java (integer) 9 127.0.0.1:6379> get hello "jedisJava"
127.0.0.1:6379> get hello "jedisJava" 127.0.0.1:6379> strlen hello (integer) 9
127.0.0.1:6379> exists hello (integer) 0 127.0.0.1:6379> getset hello world (nil) 127.0.0.1:6379> getset hello redis "world"
127.0.0.1:6379> set redis pest OK 127.0.0.1:6379> setrange redis 0 b (integer) 4 127.0.0.1:6379> get redis "best"
127.0.0.1:6379> getrange redis 0 1 "be"
2.内部编码
字符串类型的内部编码有3种:
Redis会根据当前值的类型和长度决定使用哪一种内部编码实现。
(1)整数类型:
127.0.0.1:6379> set int 8023 OK 127.0.0.1:6379> object encoding int "int"
(2)短字符串类型:
127.0.0.1:6379> set shortstring "hello world" OK 127.0.0.1:6379> object encoding shortstring "embstr"
(3)场字符串类型:
127.0.0.1:6379> set longstring "I am a string which has greater than 39 byte, you know?" OK 127.0.0.1:6379> object encoding longstring "raw" 127.0.0.1:6379> strlen longstring (integer) 55
3.使用场景
(1)缓存功能
比较典型的缓存使用场景是,Redis做为缓存层,MySQL做为存储层,绝大部分请求的数据都是从Redis中获取。
因为Redis具备支撑高并发的特性,因此缓存一般能起到加速读写和下降后端压力的做用。
例如这么一个场景:用户想要获取用户信息的话,首先须要根据用户提供的id,先去Redis中寻找用户信息,若是没有从Redis中获取到用户信息,那就须要从MySQL中进行获取,并将结果回写到Redis,添加1小时过时时间,若是这一个小时之内用户再次想获取信息的话,就直接从Redis中去获取到信息而不用再返回到MySQL中了。
UserInfo getUserInfo(long id){
// 根据用户提供的id,定义Redis中键key的值 userRedisKey = "user:info:" + id
// 根据键key的值,从Redis中获取到对应的value值 value = redis.get(userRedisKey);
// 声明UserInfo类的对象变量 UserInfo userInfo;
// 若是从Redis中获取到了value值 if (value != null) {
// 将Redis中存的value值反序列化为Java对象userInfo userInfo = deserialize(value); } else {
// 若是没有从Redis中获取到值,那么就从MySQL中去寻找 userInfo = mysql.get(id);
// 若是从MySQL中获取到了值 if (userInfo != null)
// 将从MySQL中获取到的值序列化并回写到Redis中并设置过时时间1小时 redis.setex(userRedisKey, 3600, serialize(userInfo)); } return userInfo; }
(2)计数
许多应用都会使用Redis做为计数的基础工具,它能够实现快速计数、查询缓存的功能,同时数据能够异步落地到其余数据源。视频播放数系统就是使用Redis做为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1:
long incrVideoCounter(long id) { key = "video:playCount:" + id; return redis.incr(key); }
(3)共享Session
一个分布式Web服务将用户的Session信息(例如用户登陆信息)保存在各自服务器中,这样会形成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不一样服务器上,用户刷新一次访问可能会发现须要从新登陆,这个问题是用户没法容忍的。
为了解决这个问题,可使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登陆信息都直接从Redis中集中获取。
(4)限速
不少应用出于安全的考虑,会在每次进行登陆时,让用户输入手机验证码,从而肯定是不是用户本人。可是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次:
phoneNum = "138xxxxxxxx"; key = "shortMsg:limit:" + phoneNum; // SET key value EX 60 NX isExists = redis.set(key,1,"EX 60","NX"); if(isExists != null || redis.incr(key) <=5){ // 经过 }else{ // 限速 }
上述就是利用Redis实现了限速功能,例如一些网站限制一个IP地址不能在一秒钟以内访问超过n次也能够采用相似的思路。
3、哈希
Redis中,哈希类型是指键值自己又是一个键值对结构,形如value={{field1,value1},...{fieldN,valueN}}。
1.命令
(1)设置值:hset key field value (设置成功返回1,反之返回0。和setnx命令同样有hsetnx命令)
127.0.0.1:6379> hset user:1 name tom (integer) 1
(2)获取值:hget key field
127.0.0.1:6379> hget user:1 name "tom" 127.0.0.1:6379> hget user:2 name (nil) 127.0.0.1:6379> hget user:1 age (nil)
(3)删除field:hdel key field [field...](返回成功删除field的个数)
127.0.0.1:6379> hdel user:1 name (integer) 1 127.0.0.1:6379> hget user:1 name (nil) 127.0.0.1:6379> hdel user:1 age (integer) 0
(4)计算field个数:hlen key
127.0.0.1:6379> hset user:1 name LianJiang (integer) 1 127.0.0.1:6379> hset user:1 age 23 (integer) 1 127.0.0.1:6379> hset user:1 sex boy (integer) 1 127.0.0.1:6379> hlen user:1 (integer) 3
(5)批量设置或获取field-value:hmget key field [field...] hmset key field value [field value ...]
127.0.0.1:6379> hmset user:1 name QiaoJiang age 22 sex boy OK 127.0.0.1:6379> hmget user:1 name age 1) "QiaoJiang" 2) "22"
(6)判断field是否存在:hexists key field
127.0.0.1:6379> hexists user:1 name (integer) 1 127.0.0.1:6379> hexists user:1 city (integer) 0
(7)获取全部field: hkey key
127.0.0.1:6379> hkeys user:1 1) "name" 2) "age" 3) "sex"
(8)获取全部value:hvals key
127.0.0.1:6379> hvals user:1 1) "QiaoJiang" 2) "22" 3) "boy"
(9)获取全部的field-value:hgetall key
127.0.0.1:6379> hgetall user:1 1) "name" 2) "QiaoJiang" 3) "age" 4) "22" 5) "sex" 6) "boy"
(10)自增指定数字,自增指定浮点数:hincrby key field hincrbyfloat key field
(11)计算value的字符串长度:hstrlen key field
127.0.0.1:6379> hstrlen user:1 name (integer) 9
2.内部编码
(1)当field个数比较少且没有大的value时,内部编码为ziplist
(2)当有value大于64字节,内部编码会由ziplist变为hashtable
(3)当field个数超过512,内部编码也会有ziplist变为hashtable
3.使用场景
将关系型数据库和Redis哈希类型数据库作对比:
相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,而且在更新操做上会更加便捷。能够将每一个用户的id定义为键后缀,多对field-value对应每一个用户的属性:
UserInfo getUserInfo(long id){ // 用户 id 做为 key 后缀 userRedisKey = "user:info:" + id; // 使用 hgetall 获取全部用户信息映射关系 userInfoMap = redis.hgetAll(userRedisKey); UserInfo userInfo; if (userInfoMap != null) { // 将映射关系转换为 UserInfo userInfo = transferMapToUserInfo(userInfoMap); } else { // 从 MySQL 中获取用户信息 userInfo = mysql.get(id); // 将 userInfo 变为映射关系使用 hmset 保存到 Redis 中 redis.hmset(userRedisKey, transferUserInfoToMap(userInfo)); // 添加过时时间 redis.expire(userRedisKey, 3600); } return userInfo; }
可是须要注意的是哈希类型和关系型数据库有两点不一样之处:
总结一下三种缓存用户信息的方法并给出三种方案的实现方法和优缺点分析。
set user:1:name tom set user:1:age 23 set user:1:city beijing
优势:简单直观,每一个属性都支持更新操做。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,因此此种方案通常不会在生产环境使用。
set user:1 serialize(userInfo)
优势:简化编程,若是合理的使用序列化能够提升内存的使用效率。
缺点:序列化和反序列化有必定的开销,同时每次更新属性都须要把所有数据取出进行反序列化,更新后再序列化到Redis中。
hmset user:1 name tomage 23 city beijing
优势:简单直观,若是使用合理能够减小内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
4、列表
列表(list)类型是用来存储多个有序的字符串,如图2-18所示,a、b、c、d、e五个元素从左到右组成了一个有序的列表,列表中的每一个字符串称为元素(element),一个列表最多能够存储2 32 -1个元素。在Redis中,能够对列表两端插入(push)和弹出(pop),还能够获取指定范围的元素列表、获取指定索引下标的元素等(如图2-18和图2-19所示)。列表是一种比较灵活的数据结构,它能够充当栈和队列的角色,在实际开发上有不少应用场景。
列表类型有两个特色:
1.命令
(1)添加 rpush lpush linsert
127.0.0.1:6379> rpush listkey c b a (integer) 3 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a"
127.0.0.1:6379> lpush listkey d e f (integer) 6 127.0.0.1:6379> lrange listkey 0 -1 1) "f" 2) "e" 3) "d" 4) "c" 5) "b" 6) "a"
127.0.0.1:6379> linsert listkey before b java (integer) 7 127.0.0.1:6379> lrange listkey 0 -1 1) "f" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 7) "a"
(2)查找 lrange lindex lien
索引下标有两个特色:
①索引下标从左到右分别是0到N-1,可是从右到左分别是-1和-N。
②lrange中的end选项包含了自身,若是想落得第2到第4个元素,end的值就要为3
127.0.0.1:6379> lrange listkey 1 3 1) "e" 2) "d" 3) "c" 127.0.0.1:6379> lrange listkey 0 6 1) "f" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 7) "a"
127.0.0.1:6379> lindex listkey -1 "a"
127.0.0.1:6379> llen listkey (integer) 7
(3)删除 lpop rpop lrem ltrim
127.0.0.1:6379> lrange listkey 0 -1 1) "f" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 7) "a" 127.0.0.1:6379> lpop listkey "f" 127.0.0.1:6379> lrange listkey 0 -1 1) "e" 2) "d" 3) "c" 4) "java" 5) "b" 6) "a"
127.0.0.1:6379> rpop listkey "a" 127.0.0.1:6379> lrange listkey 0 -1 1) "e" 2) "d" 3) "c" 4) "java" 5) "b"
lrem key count value 从列表中找到等于value的元素进行删除,根据count的不一样分为三种状况: count>0,从左到右,删除最多count个元素 count<0, 从右到左,删除最多count绝对值个元素 count=0,删除全部 127.0.0.1:6379> lpush listkey a a a a a (integer) 10 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "a" 3) "a" 4) "a" 5) "a" 6) "e" 7) "d" 8) "c" 9) "java" 10) "b" 127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b"
127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "e" 3) "d" 4) "c" 5) "java" 6) "b" 127.0.0.1:6379> ltrim listkey 3 -1 OK 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "java" 3) "b"
(4)修改 lset key index newValue
127.0.0.1:6379> lset listkey 2 python OK 127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "java" 3) "python"
(5)阻塞操做(经过timeout定义阻塞时间) blpop key [key ...] timeout brpop key [key ...] timeout
在客户端1执行timeout=0时,会一直阻塞下去
127.0.0.1:6379> brpop list:test 3 (nil) (3.06s)
127.0.0.1:6379> brpop list:test 0
...
这个时候在客户端2添加了数据element1
127.0.0.1:6379> rpush list:test element1 (integer) 1
客户端2添加完以后客户端1当即有返回:
127.0.0.1:6379> brpop list:test 3 (nil) (3.06s) 127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1" (71.97s)
127.0.0.1:6379> rpush list:test element1 (integer) 1 127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1"
2.内部编码
(1)当元素个数较少且没有大元素时,内部编码为ziplist
(2)当元素个数超过512个,内部编码变为linkedlist
(3)当某个元素超过64字节,内部编码也会变为linkedlist
3.使用场景
列表的使用场景不少,能够记住这些公式:
(1)消息队列
Redis的lpush+brpop命令组合便可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个消费者客户端保证了消费的负载均衡和高可用性。
(2)文章列表
hmset acticle:1 title xx timestamp 1476536196 content xxxx ... hmset acticle:k title yy timestamp 1476512536 content yyyy
lpush user:1:acticles article:1 article3 ... lpush user:k:acticles article:5 ...
articles = lrange user:1:articles 0 9 for article in {articles} hgetall {article}
使用列表类型保存和获取文章列表会存在两个问题。
5、集合
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不同的是,集合中不容许有重复元素,而且集合中的元素是无序的,不能经过索引下标获取元素。如图2-22所示,集合user:1:follow包含
着"it"、"music"、"his"、"sports"四个元素,一个集合最多能够存储2 ^32 -1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决不少实际问题。
1.命令
(1)集合内操做
127.0.0.1:6379> exists myset (integer) 0 127.0.0.1:6379> sadd myset a b c (integer) 3 127.0.0.1:6379> smembers myset 1) "a" 2) "b" 3) "c" 127.0.0.1:6379> sadd myset a b (integer) 0
127.0.0.1:6379> sadd myset a b (integer) 0 127.0.0.1:6379> srem myset a b (integer) 2 127.0.0.1:6379> srem myset a (integer) 0 127.0.0.1:6379> smembers myset 1) "c"
127.0.0.1:6379> smembers myset 1) "c" 127.0.0.1:6379> scard myset (integer) 1
127.0.0.1:6379> sismember myset c (integer) 1 127.0.0.1:6379> sismember myset a (integer) 0
127.0.0.1:6379> sadd myset a b d e f g (integer) 6 127.0.0.1:6379> smembers myset 1) "c" 2) "d" 3) "b" 4) "e" 5) "a" 6) "g" 7) "f" 127.0.0.1:6379> srandmember myset 3 1) "c" 2) "f" 3) "g" 127.0.0.1:6379> srandmember myset 3 1) "a" 2) "c" 3) "g"
127.0.0.1:6379> smembers myset 1) "c" 2) "d" 3) "b" 4) "e" 5) "a" 6) "g" 7) "f" 127.0.0.1:6379> spop myset 1 1) "d" 127.0.0.1:6379> spop myset 2 1) "f" 2) "e" 127.0.0.1:6379> smembers myset 1) "b" 2) "a" 3) "c" 4) "g"
127.0.0.1:6379> smembers myset 1) "b" 2) "a" 3) "c" 4) "g"
(2)集合间操做
首先定义两个集合,user:1:follow & user:2:follow
127.0.0.1:6379> sadd user:1:follow it music his sports (integer) 4 127.0.0.1:6379> sadd user:2:follow it news ent sports (integer) 4
127.0.0.1:6379> sinter user:1:follow user:2:follow 1) "it" 2) "sports"
127.0.0.1:6379> sunion user:1:follow user:2:follow 1) "ent" 2) "his" 3) "it" 4) "news" 5) "music" 6) "sports"
127.0.0.1:6379> sdiff user:1:follow user:2:follow 1) "his" 2) "music" 127.0.0.1:6379> sdiff user:2:follow user:1:follow 1) "ent" 2) "news"
127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow (integer) 2 127.0.0.1:6379> type user:1_2:inter set 127.0.0.1:6379> smembers user:1_2:inter 1) "it" 2) "sports"
2.内部编码
(1)当元素个数较少且都为整数时,内部编码为intset
(2)当元素个数超过512个,内部编码变为hashtable
(3)当某个元素不为整数时,内部编码也会变为hashtable
3.使用场景
集合类型的应用场景一般分为几下几种:
其中使用最多的场景是标签(tag),主要分为如下功能:
(1)给用户添加标签
sadd user:1:tags tag1 tag2 tag5 sadd user:2:tags tag2 tag3 tag5 ... sadd user:k:tags tag1 tag2 tag4 ...
(2)给标签添加用户
sadd tag1:users user:1 user:3 sadd tag2:users user:1 user:2 user:3 ... sadd tagk:users user:1 user:2 ...
(3)删除用户下的标签
srem user:1:tags tag1 tag5 ...
(4)删除标签下的用户
srem tag1:users user:1 srem tag5:users user:1 ...
(5)计算用户共同感兴趣的标签
sinter user:1:tags user:2:tags
6、有序集合
有序集合保留了集合不能有重复成员的特性,但不一样的是,有序集合中的元素能够排序。可是它和列表使用索引下标做为排序依据不一样的是,它给每一个元素设置一个分数(score)做为排序的依据。如图2-24所示,该有序集合包含kris、mike、frank、tim、martin、tom,它们的分数分别是一、9一、200、220、250、251,有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助咱们在实际开发中解决不少问题。(有序集合中的元素不能重复,可是score能够重复,就和一个班里的同窗学号不能重复,可是考试成绩能够相同)
列表、集合和有序集合三者的异同点:
1.命令
(1)集合内操做
zadd key [NX|XX] [CH] [INCR] score member [score member ...] nx:member必须不存在,才能够设置成功,用于添加 xx:member必须存在,才能够设置成功,用于更新 ch:返回这次操做后,有序集合元素和分数发生变化的个数 incr:对score作增长,至关于zincrby 127.0.0.1:6379> zadd user:ranking 251 tom (integer) 1 127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin (integer) 5
127.0.0.1:6379> zcard user:ranking (integer) 6
127.0.0.1:6379> zscore user:ranking tom "251" 127.0.0.1:6379> zscore user:ranking not_exist_member (nil)
127.0.0.1:6379> zrank user:ranking tom (integer) 5 127.0.0.1:6379> zrevrank user:ranking tom (integer) 0
127.0.0.1:6379> zrem user:ranking mike (integer) 1
127.0.0.1:6379> zscore user:ranking tom "251" 127.0.0.1:6379> zincrby user:ranking 9 tom "260"
127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "kris" 2) "1" 3) "frank" 4) "200" 5) "tim" 6) "220" 7) "martin" 8) "250" 9) "tom" 10) "260" 127.0.0.1:6379> zrevrange user:ranking 0 3 withscores 1) "tom" 2) "260" 3) "martin" 4) "250" 5) "tim" 6) "220" 7) "frank" 8) "200" 127.0.0.1:6379> zrevrange user:ranking 0 3 1) "tom" 2) "martin" 3) "tim" 4) "frank"
127.0.0.1:6379> zrangebyscore user:ranking 200 221 withscores 1) "frank" 2) "200" 3) "tim" 4) "220" 127.0.0.1:6379> zrangebyscore user:ranking 200 220 withscores 1) "frank" 2) "200" 3) "tim" 4) "220" 127.0.0.1:6379> zrangebyscore user:ranking 200 219 withscores 1) "frank" 2) "200" 127.0.0.1:6379> zrevrangebyscore user:ranking 260 200 withscores 1) "tom" 2) "260" 3) "martin" 4) "250" 5) "tim" 6) "220" 7) "frank" 8) "200" 127.0.0.1:6379> zrevrangebyscore user:ranking 260 201 withscores 1) "tom" 2) "260" 3) "martin" 4) "250" 5) "tim" 6) "220"
127.0.0.1:6379> zrevrangebyscore user:ranking 260 -inf withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"
7) "frank"
8) "200"
9) "kris"
10) "1"
127.0.0.1:6379> zcount user:ranking 200 220 (integer) 2
127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "kris" 2) "1" 3) "frank" 4) "200" 5) "tim" 6) "220" 7) "martin" 8) "250" 9) "tom" 10) "260" 127.0.0.1:6379> zremrangebyrank user:ranking 0 2 (integer) 3 127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "martin" 2) "250" 3) "tom" 4) "260"
127.0.0.1:6379> zrange user:ranking 0 -1 withscores 1) "martin" 2) "250" 3) "tom" 4) "260" 127.0.0.1:6379> zremrangebyscore user:ranking 250 +inf (integer) 2 127.0.0.1:6379> zrange user:ranking 0 -1 withscores (empty list or set)
(2)集合间操做
首先定义两个有序集合:
127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin 251 tom (integer) 6 127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom (integer) 4 127.0.0.1:6379> zrange user:ranking:1 0 -1 withscores 1) "kris" 2) "1" 3) "mike" 4) "91" 5) "frank" 6) "200" 7) "tim" 8) "220" 9) "martin" 10) "250" 11) "tom" 12) "251" 127.0.0.1:6379> zrange user:ranking:2 0 -1 withscores 1) "james" 2) "8" 3) "mike" 4) "77" 5) "martin" 6) "625" 7) "tom" 8) "888"
zinterstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max]
destination :交集计算结果保存到这个键
numkeys:须要作交集计算键的个数
key [key ...]:须要作交集计算的键
weights weight [weight ...]:每一个键的权重,在作交集计算时,每一个键中的每一个member会将本身分数乘以这个权重,每一个键的权重默认是1
aggregate sum|min|max:计算成员交集后,分值能够按照sum(和)、min(最小值)、max(最大值)作汇总,默认值是sum
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"
127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2 (integer) 7 127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores 1) "kris" 2) "1" 3) "james" 4) "8" 5) "mike" 6) "168" 7) "frank" 8) "200" 9) "tim" 10) "220" 11) "martin" 12) "875" 13) "tom" 14) "1139"
2.内部编码
(1)当元素较少且每一个元素较小时,内部编码为ziplist
(2)当元素个数超过128个,内部编码变为skiplist
(3)当某个元素大于64字节时,内部编码变为hashtable
3.使用场景
有序集合比较典型的使用场景就是排行榜系统。例如视频网站须要对用户上传的视频作排行榜,榜单的维度多是多个方面的:按照上传时间、按照播放数量、按照点赞次数、按照收藏次数等。
以按照点赞次数记录天天上传视频的排行榜,主要须要实现如下4个功能:
(1)添加用户赞数
例如用户mike上传了一个视频,并得到了3个赞,可使用有序集合的zadd和zincrby功能:
zadd user:ranking:2016_03_15 mike 3
若是以后再得到一个赞,可使用zincrby:
zincrby user:ranking:2016_03_15 mike 1
(2)取消用户赞数
因为各类缘由(例如用户注销、用户做弊)须要将用户删除,此时须要将用户从榜单中删除掉,可使用zrem。例如删除成员tom:
zrem user:ranking:2016_03_15 mike
(3)展现获赞数最多的十个用户
zrevrangebyrank user:ranking:2016_03_15 0 9
(4)展现用户信息及用户分数
将用户名做为键后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可使用zscore和zrank两个功能:
hgetall user:info:tom zscore user:ranking:2016_03_15 mike zrank user:ranking:2016_03_15 mike
7、键管理
1.单个键管理
127.0.0.1:6379> get hello "world" 127.0.0.1:6379> rename hello redis OK 127.0.0.1:6379> get redis "world" 127.0.0.1:6379> get hello (nil) 若是在rename以前,键已经存在,那么它的值将被覆盖: 127.0.0.1:6379> get redis "world" 127.0.0.1:6379> get java "jedis" 127.0.0.1:6379> rename redis java OK 127.0.0.1:6379> get java "world" 127.0.0.1:6379> gert redis (error) ERR unknown command 'gert' 127.0.0.1:6379> get redis (nil) 为了防止被强行rename,可使用renamenx来确保只有newKey不存在时才被覆盖: 127.0.0.1:6379> get java "world" 127.0.0.1:6379> get python "redis-py" 127.0.0.1:6379> renamenx java python (integer) 0 127.0.0.1:6379> get java "world"
127.0.0.1:6379> keys * 1) "python" 2) "java" 127.0.0.1:6379> randomkey "python" 127.0.0.1:6379> randomkey "java" 127.0.0.1:6379> randomkey "python" 127.0.0.1:6379> randomkey "python"
①expire key seconds:键在seconds秒后过时
②expireat key timastamp:键在秒级时间戳timestamp后过时
③pexpire key milliseconds:键在milliseconds毫秒后过时
④pexpireat key milliseconds-timestamp:键在毫秒时间戳timestamp后过时
①move key db
②dump key + restore key ttl value
③migrate host port key| destination-db timeout [COPY] [REPLACE] [KEYS key]
2.遍历键
127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> mset hello world redis test jedis best hill high OK 127.0.0.1:6379> keys * 1) "redis" 2) "hill" 3) "jedis" 4) "hello" 127.0.0.1:6379> keys [j,r]edis 1) "redis" 2) "jedis" 127.0.0.1:6379> keys h?ll* 1) "hill" 2) "hello"
考虑到Redis的单线程架构,若是Redis包含了大量的键,执行keys命令极可能会形成Redis阻塞,因此通常建议不要在生产环境中使用keys命令。在一下三种状况下,可使用:
①在一个不对外提供服务的Redis从节点上执行,这样不会阻塞到客户端的请求,可是会影响到主从复制。
②若是确认键值总数确实比较少,能够执行该命令。
③可使用scan命令渐进式地遍历全部键,能够有效防止阻塞。
Redis存储键值对实际使用的是hashtable的数据结构:
每次执行scan,只扫描一个字典中的一部分键,直到将字典中的全部键遍历完毕:
scan cursor [match pattern] [count number] cursor:必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。 match pattern:可选参数,它的做用的是作模式的匹配,这点和keys的模式匹配很像。 count number:可选参数,它的做用是代表每次要遍历的键个数,默认值是10,此参数能够适当增大。 127.0.0.1:6379> keys * 1) "s" 2) "u" 3) "d" 4) "h" 5) "y" 6) "b" 7) "q" 8) "p" 9) "r" 10) "i" 11) "m" 12) "z" 13) "f" 14) "w" 15) "c" 16) "n" 17) "e" 18) "a" 19) "k" 20) "v" 21) "j" 22) "x" 23) "l" 24) "o" 25) "t" 26) "g" 127.0.0.1:6379> scan 0 1) "17" 2) 1) "s" 2) "e" 3) "b" 4) "q" 5) "f" 6) "h" 7) "m" 8) "t" 9) "u" 10) "d" 127.0.0.1:6379> scan 17 1) "7" 2) 1) "a" 2) "v" 3) "j" 4) "p" 5) "r" 6) "l" 7) "o" 8) "y" 9) "z" 10) "x" 127.0.0.1:6379> scan 7 1) "0" 2) 1) "i" 2) "k" 3) "w" 4) "c" 5) "n" 6) "g"
3.数据库管理
MySql这种关系型数据库支持在一个实例下有多个数据库存在的,可是与关系型数据库用字符来区分不一样数据库名不一样,Redis只是用数字做为多个数据库的实现。
Redis默认配置中有16个数据库,在redis.conf中有databases 16定义了默认有16个数据库。
当使用redis-cli -h {ip} -p {port}时默认使用的就是0号数据库,最后一个数据库是15号数据库,不一样数据库之间的数据没有任何关联,甚至能够存在相同的键。
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> get hello "world" 127.0.0.1:6379> select 15 OK 127.0.0.1:6379[15]> get hello (nil)
为何Redis最好是只使用0号数据库,不提倡使用多个数据库:
①Redis是单线程的,。若是使用多个数据库,那么这些数据库仍热是使用一个CPU,彼此之间仍是会受到影响的。
②多数据库的使用方式,会让调试和运维不一样业务的数据库变得困难,假若有一个慢查询存在,仍然会影响其余数据库,这样会使得别的业务方定位问题很是的困难。
③部分Redis的客户端根本就不支持这种方式。即便支持,在开发的时候来回切换数字形式的数据库,很容易弄乱。
若是要使用多个数据库功能,彻底能够在一台机器上部署多个Redis实例,彼此用端口来作区分,由于现代计算机或者服务器一般是有多个CPU的,这样既保证了业务之间不会受到影响,又合理地使用了CPU资源。
127.0.0.1:6379> flushdb OK 127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> set hello world OK 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> set redis jedis OK 127.0.0.1:6379[1]> flushall OK 127.0.0.1:6379[1]> get redis (nil) 127.0.0.1:6379[1]> select 0 OK 127.0.0.1:6379> get hello (nil)