redis基础和数据类型
# Reids基础
# 1. 认识redis
# 1.1 基本概念
- redis也有数据库的概念,一个数据库中可以保存一组数据;
- 各个数据库之间是相互隔离的,当然也可以在不同数据库之间复制数据;
- 每一个数据库都有一个id号,默认的数据库id为0;
- 可以使用select命令选择当前使用的数据库:select 1
- redis初始化的时候会默认创建16个数据库(这个配置可以在redis配置文件中databases 16);
- 特别注意,类似redis的key-value数据库系统,是绝对没有表的概念,可以简单理解为,所有的数据都是乱七八糟的堆在一起的;
# 1.2 redis的命令组
Redis命令十分丰富,包括的命令组有Cluster、Connection、Geo、Hashes、HyperLogLog、Keys、Lists、Pub/Sub、Scripting、Server、Sets、Sorted Sets、Strings、Transactions一共14个redis命令组两百多个redis命令。每个命令都代表着一种操作,有点类似mysql的命令。
这些命令组可以按功能分为以下几种类型:
- 对数据的操作: Strings、Lists、Sets、SortedSets、Hashes、Geo; 其中Geo可以做附近的人的功能
- 发布/订阅相关操作: Pub/Sub
- 事务控制: Transactions
- 脚本命令; Scripting
- 网络连接命令; Connection
- 数据库服务相关命令; Keys Server Cluster HyperLogLog
# 2. 数据类型
核心数据类型是 key & value: key用来标记一个数据;redis最突出的特点是提供了5种常用的数据存储类型(value的类型): String, List, Set, SortedSet, Hash
value表示一个key对应的值;在redis中,value可以是任何内容,redis把所有的value都作为byte处理;所以可以用来保存任何内容;
一般在key中需要体现出数据模型的结构.最简单数据模型demo:
key:1 value:{ "id":1, "name":"swk" } //但是上面的key容易产生key覆盖的问题,因为假如多个对象都是用ID做key, 所以一般都是这样做 key: user:1 employee:1 value: employee:{ "id":1, "deptId":1, "name":"mgr" } user:{ "id":1, "name":"swk" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 2.1 String 字符串
底层实现: 动态字符串
redis中最常见的数据类型,内容可以是任何值;
key和value 最大空间为512M
# a. 基础命令
set key value:设置一个值;
get key:返回key对应的value;
strlen key:返回key对应的value字符串长度;
append key value:给key对应的value追加值,如果key不存在,相当于set一个新的值;
getrange key start stop:返回key对应value的一个子字符串,位置从start到stop;
如果字符串的内容是数值(integer,在redis中,数值也是string),那么有如下常用的操作:
incr key:在给定key的value上增加1,并返回增加后的值,redis中的incr是一个原子操作,支持并发;
incrby key value:给定key的value上增加value值,并返回增加后的值,相当于key=key.value+value;这也是一个原子操作;
decr:在给定key的value上减少1;
decrby key value:给定key的value上减少value值;
# b. 应用场景
缓存功能:字符串最经典的使用场景,redis最为缓存层,Mysql作为储存层,绝大部分请求数据都是在redis中操作,由于redis具有支撑高并发特性,所以缓存通常能起到加速读写和降低 后端压力的作用。
计数器:许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询缓存的功能。
视频播放数系统就是使用redis作为视频播放数计数的基础组件。 比如:优酷视频的播放:incr video:videoId:playTimes 或者:文章浏览量:incr article:aricleId:clickTimes 或者粉丝数量:取关 decr author:authorId:fansNumber 这样的话就能极快的提高视频或者是文章的访问速度 简单总结就是:能够利用redis在缓存中做统计的工作,省去了在sql联表查询的功夫
1
2
3
4
5
6id生成器:我们在使用mysql时,把数据存在mysql后,该数据就会自动生成一个自增长的id,这个id肯定是不重复的,那类似这种生成一个不重复的id也可以用redis的string数据结构来做。
分布式锁
SET 命令有个 NX 参数可以实现「key不存在才插入」,可以用它来实现分布式锁:
- 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
- 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:
SET lock_key unique_value NX PX 10000
1- lock_key 就是 key 键;
- unique_value 是客户端生成的唯一的标识;
- NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
- PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。
而解锁的过程就是将 lock_key 键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。
可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。
// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
1
2
3
4
5
6这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。
# c. 注意事项
- 如果我们想要输入空格字符,那么只是敲一个空格是无法生效的,必须用双引号包一个空格字符的方式来引入空格
- Redis中的区间通常是:前闭后闭
- 在redis中,数值也是string
# 2.2 List 列表
底层实现: quickList
List类型是一个链表结构的集合,其主要功能有lpush,lpop,获取元素等.更详细的说,List类型是一个双端链表的结构,我们可以通过相关操作进行集合的头部或者尾部添加删除元素,list的设计非常简单精巧,即可以作为栈,又可以作为队列.满足绝大多数需求.
# a. 基础命令
lpush key value …:在一个list最前面添加一个或多个元素。
rpush key value …:在一个list最后面添加一个或多个元素。
llen key:返回指定list的元素个数。
lrange key start stop:获取一个list中的指定元素(如果是0,-1则表示从头取到末尾所有元素).
ltrim key start stop:裁剪指定list,剩下的内容从start到stop; 这里相当于对list进行剪裁,剪裁范围以外的元素丢弃
lpop key :从list最前面弹出一个元素。
rpop key:从list最后面弹出一个元素。
lindex key index:从list中获取索引为index的元素
# b. 应用场景
社区应用: 社区应用是一个很广泛的概念,我们平时用的微博,朋友圈,博客,论坛都算是社区应用,社区应用无非就是一些帖子或状态,然后可以给这些帖子或状态回贴或评论,还有就是可以点赞。
示例:1,点赞; 1,创建一条微博内容:set user:1:post:91 “hello world”; 2,点赞: lpush post:91:good “kobe.png” lpush post:91:good “jordan.png” lpush post:91:good “James.png” 3,查看有多少人点赞: llen post:91:good 4,查看哪些人点赞:lrange post:91:good 0 -1 示例2:回复 1,创建一条微博内容:set user:1:post:92 “oh my lady gaga” 2,回复:lpush post:92:reply “heihei” lpush post:92:reply “enen” 5,查询微博的回复:lrange post:92:reply 0 -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# c. 注意实现
- redis中的range是前后闭合的;
# 2.3 Set 集合
底层实现: hash表
set数据没有顺序,并且每一个值不能重复;
# a. 基础命令
- sadd key member…:给set添加一个或多个元素
- scard key:返回set的元素个数
- smembers key:返回指定set内所有的元素,以一个list形式返回
- srem key member:从set中移除一个给定元素
- sismember key member:判断给定的一个元素是否在set中,如果存在,返回1,如果不存在,返回0
- srandmember key count: 返回指定set中随机的count个元素
- sunion key1 key2…:用key和key…做并集,结果返回一个list;
- sdiff key1 key2…:用key1和key2…做差集,结果返回一个list;
- sinter key key … :用key和key…做交集,结果返回一list;
- sunionstore destination key key … :获取多个key对应的set之间的并集,并保存为新的key值;目标值也是一个set;
# b. 应用场景
指纹去重;
用户抽奖
sadd luckdraws ‘user1’ ‘user2’ ‘user3’ ‘user4’ ‘user5’ ‘user6’ ‘user7’ ‘user8’ ‘user9’ ‘user11’ ‘user12’ ‘user13’ 2)抽3个三等奖: srandmember luckdraws 3 srem luckdraws user…
1
2
3
4做set运算(二度好友推荐)
1)初始化好友圈: sadd user:1:friends ‘user:2’ ‘user:3’ ‘user:5’ sadd user:2:friends ‘user:1’ ‘user:3’ ‘user:6’ sadd user:3:friends ‘user:1’ ‘user:7’ ‘user:8’ 2)给user1推荐你可能认识的好友: a、用user1的好友的好友做并集 sunionstore user:1:groups user:2:friends user:3:friends user:5:friends b、用user:1:group和user:1的好友做差集 sdiffstore user:1:groups user:1:groups user:1:friends c、去除自己 srem user:1:groups user:1 3)在user:1:groups中随机推荐指定的好友数 srandmember user:1:groups 2
1
2
3
4
5
6
7
8
9
10
11
12
13做共同关注,se共同爱好好友推荐等等…
# 2.4 SortedSet 有序集合
底层实现: 跳跃表+ListPack
sorted set,每一个添加的值都有一个对应的分数,放进去的值按照该分数升序存在一个集合中,可以通过这个分数进行相关排序的操作。可以避免在SQL中使用Order by语句(十分低效)
# a. 基础命令
zadd key score member:添加一个带分数的元素,也可以同时添加多个.
zcount key min max :给定范围分数的元素个数
zrank key member :查询指定元素的分数在整个列表中的排名(从0开始)
zrange key start stop:获取集合中指定范围的元素,按分数升序排序输出
zrevrange key start stop:获取集合中指定范围的元素,按分数降序排序输出
zrank key member 查询排名
zincrby key score member:给指定的元素增加指定的分数
zrem key member:移除指定的元素
# b. 应用场景
排行榜:有序集合经典使用场景。例如社交网站需要对用户的帖子或者微博做排行榜
按照点击量排行: 1、初始化数据: zadd post:good:sort 1000 post:91 1020 post:92 2、给id为91的帖子增加8000个赞 zincrby post:good:sort 30 post:91 3、首页推荐10个最热门的帖子 zrevrange post:good:sort 0 10 4、从帖子点击量排行榜中移除id为91的帖子 zrem post:good:sort post:91
1
2
3
4
5
6
7
8
9
10
11
12
# 2.5 Hash 哈希
底层实现: ListPack +hash表
为什么要用Hash?
假如要存储的对象有很多属性,那么为了表示该对象,必然有很多Key来描述该对象,但是接下来的问题就变成了,假如我修改某个值,我是不是还需要把这个对象取出来,再加,是不是很麻烦?所以就有了Hash;
user:1:name "swk" user:1:age 500 user:1:id 1 user:1 {id:1,name:"swk",age:500}
1
2
3
4
5hashes可以理解为一个map,这个map由一对一对的字段和值组成,所以,可以用hashes来保存一个对象:
# a. 基本语法
hset key field value:给一个hashes添加一个field和value;
hget key field:可以得到一个hashes中的某一个属性的值:
hgetall key:一次性取出一个hashes中所有的field和value,使用list输出,一个field,一个value有序输出;
hmset key field1 value1 field2 value2 field3 value3…:一次性的设置多个值(hashes multiple set)
hmget key field1 field2…:一次性的得到多个字段值(hashes multiple get),以列表形式返回;
hincrby key field number:给hashes的一个field的value增加一个值(integer),这个增加操作是原子操作:
hkeys key:得到一个key的所有fields字段,以list返回:
hdel key fieldh:删除hashes一个指定的filed;
# b. 应用场景
- hashes使用场景:替代string,以更合理的方式保存对象;
# 3. 常见系统命令
返回满足的所有键 keys * (可以模糊查询)
注:该命令在服务端一定要禁用,顺丰的那位删库跑路的兄弟就是在服务器上敲了这个命令导致服务器产生了雪崩效应
del 删除一个key 返回值 1成功,0 失败
exists 是否存在指定key 返回值 1成功,0 失败
expire 设置某个key的过期时间(秒为单位),使用ttl命令查看剩余时间
persist 取消过期时间
select 选择数据库 数据库为0到15(一共16个数据库) 默认进入的是0数据库
move [key] [数据库下标] 将当前数据中的key转移到其他数据库中
randomkey 随机返回数据库里的一个key
rename重命名key
echo 打印名
dbsize 查看数据库的key数量
info 获取数据库信息
config get * 返回所有配置信息
很危险的两个命令,这里就不演示了,下面两个命令应该在生产环境中禁止使用:
flushdb 清空当前数据库
flushall清空所有数据库
# 4. redis 与memcache的区别
- Redis拥有更多的数据结构
- Redis相比Memcache来说,拥有更多的数据结构和支持更丰富的数据操作,通常在Memcache里,你需要将数据拿到客户端来进行类似的修改,在set进去。这就大大增加了网络IO的次数和体积,在Redis中,这些复杂的操作通常和一般的set/get一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么Redis是不错的选择
- Redis内存利用率对比
- 使用简单的key-value存储的话,Memcache的内存利用率更高,而Redis采用Hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcache
- 性能对比
- 由于Redis只使用了单核,而Memcache可以使用多核,所以平均每核上Redis在存储小数据比Memcache性能更高,而在100K以上的数据中,Memcache性能更高,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcache还有略有逊色。
- 集群模式
- Memcache没有原生的集群模式,需要依赖客户端来实现往集群中分片写入数据,但是Redis目前是原生支持cluster模式的。