一、Redis简介
Redis是一个开源的高性能键值对NoSQL数据库(C语言开发,开源)。
提供多种键值数据类型来适应不同场景下的数据存储需求,并借助许多高层级的接口使其可以胜任诸如缓存、队列系统等不同角色。
NoSQL:一类新出现的数据库(not only sql)
- 泛指非关系型的数据库
- 不支持SQL语法
- 存储结构跟传统关系型数据库中的那种关系表完全不同,nosql中存储的数据都是KV形式
- NoSQL的世界中没有一种通用的语言,每种nosql数据库都有自己的api和语法,以及擅长的业务场景
- NoSQL中的产品种类相当多:
- Mongodb
- Redis
- Hbase hadoop
- Cassandra hadoop
NoSQL和SQL数据库的比较:
- 适用场景不同:sql数据库适合用于关系特别复杂的数据查询场景,nosql反之
- “事务”特性的支持:sql对事务的支持非常完善,而nosql基本不支持事务
- 两者在不断地取长补短,呈现融合趋势
Redis特性
1、Redis是单线程模型。
2、内存存储与持久化
- 优点:性能高,Redis数据库所有数据存储在内存中,由于内存读写速度远远高于硬盘,所以性能比那些存储在硬盘上的数据库高。
- 缺点:程序退出,内存中的数据会消失。因此Redis提出了内存持久化的支持,将内存中的数据异步写入硬盘,不影响继续提供服务。
3、Redis支持数据的备份,即master-slave模式的数据备份。
Redis优势
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Redis数据类型
Redis 以字典结构存储数据,并允许其他应用通过TCP协议读写字典内容,同大多数字典一样,字典中键值除了字符串还可以是其他数据类型,目前Redis支持的五大类型:
- 字符串类型
- 哈希(散列)类型
- 列表类型
- 集合类型
- 有序集合类型
这种字典形式的存储结构与MySQL等关系型数据库的二维表形式有很大差异。
Redis应用场景
Redis虽然是作为数据库开发的,但鉴于其出色性能,Redis可以作为缓存、队列系统来用。
- 缓存:Redis可以为每个键设置生存时间(Time To Live)TTL , 到期后会自动删除。该功能可以使Redis当缓存来用。Redis支持持久化和丰富的数据类型,使其成为另一个非常流行缓存系统Memcached(支持多线程,多核服务器上其性能更高)的有力竞争者。虽然Redis是单线程,但是其性能足够优异,绝大部分场合下,其性能不会成为瓶颈。
- 队列:Redis列表类型键可以用来实现队列。并支持阻塞式读取,很容易实现高性能队列。更高层面上,Redis支持发布、订阅消息模式,可以基于此构建聊天室等系统。
- GitHub,StackOverflow, Filckr,Instagram都是Redis的用户。
二、基础准备
首先,需要安装Redis,具体步骤可以根据自己的环境来百度。
启动与停止:
- reids-server :启动Redis服务器 Redis默认端口:6379 (redis-server --port 6380 // 自定义端口号)
- redis-cli: Redis自带命令客户端
终止:
- redis-cli SHUTDOWN
- or kill Redis 进程pid 也能正常结束Redis
除了设置port外,Redis还支持设置是否开启持久化,日志级别等。Redis支持通过使用配置文件的方法, 在启动Redis时将配置文件路径作为参数传递给redis-server:
eg: redis-server /path/to/redis.conf
多数据库:
Redis实例提供多个字典来存储数据,可以将每个字典理解成一个独立的数据库。从0号递增,默认支持16个数据库。
- SELECT 1 # 选择1号数据库。
注意:
Redis不支持自定义数据库名字。每个数据库以编号命名。开发者必须记得每个数据库存的数据是那些。另外,不支持为每个数据库设置密码。要么可以全部访问,要么全部不能访问。一个空的Redis实例占用内存只有1MB左右,所以不用担心内存负担。
三、入门
详解五大类型
3.1 字符串类型(string)
Redis的最基本数据类型,能存储任何形式的字符串,包括二进制数据。可以存储邮箱、json化对象、甚至图片,
字符串类型是其他四种类型的基础。其他类型数据与字符串类型的差别从某种角度来说只是阻止字符串的形式不同。
Redis 字符串是动态字符串,是可以修改的字符串,采用预分配冗余空间的方式来减少内存的频繁分配,当字符串长度小于1M时,扩容都是加倍现有空间,超过1M,扩容时一次只会多扩1M,一个字符串类型允许存储的最大容量是512MB。
eg:列表型是列表形式组织的字符串。集合型是集合形式组织的字符串。
常用命令:
1. 读写
- 写:set key value
- 读:get key
Redis中最简单的两个命令
2. incr num # 递增数字,让当前的键值递增,并返回递增后的值。
redis 中所有的操作都是原子操作(原子不可拆分的意思,最小的执行单位,不会在执行的过程中被其他命令插入打断)
incrby key increment # same with incr but this words can define the value of increment
such as
redis > incrby bar 2
(integer) 2
redis > incrby bar 3
(integer) 5
decr key # 键值递减
decrby key decrement # 减少指定的证书, 让键值递减
3. incrbyfloat key increment # 增加指定浮点数
increbyfloat bar 2.7
"6.7"
4. append key value # 向尾部追加
5. strlen key # 获取字符串长度,如果键不存在则返回0。
6. 批量操作
- mget key [key ....] # 同时获取多个值
- mset key value [ key value .....] # 同时设置多个值
7. 位操作
字符串有多个字节组成,每个字节又有8个bit组成, 可以将一个字符串看成多个bit的组合,便是bitmap(位图)数据结构。位图具体使用会放到后面说,这里介绍下位操作:
- getbit key offset
- setbit key offset value
- bitcount key [start] [end]
- bitop operation destkey key [key ...]
3.2 散列类型(hash)
应用场景:
比如博客系统,希望列表页中的每个文章只显示标题部分,用字符串只能把文章数据字符串取出进行反序列化。其中大部分内容是不必要的。
用户信息的存储,部分获取,字符串的话就要一次性全部读取,浪费网络流量。
- 散列型的键值也是一种字典结构,其存储了字段和字段值的映射。
- 字段值只能是字符串,不支持其他数据类型。
- 散列类型适合存储对象,适用对象类别与ID组成键名,使用字段表示对象的属性值。
- hash缺点:hash结构的存储消耗要高于单个字符串。
命令:
1.赋值取值
- hset key field value
- hget key field
- hmset key field value [field value ...]
- hmget key field [field ...]
- hgetall key
2.判断字段是否存在
- hexists key field
3.当字段不存在时赋值
- hsetnx key field value
4.增加数字
- hincrby key field increment
5.删除字段
- hdel key field [field ...] # 删除一个或多个字段,返回值是被删除的字段的个数。
6.只获取字段名或字段值
- hkeys key
- hvals key
7.获取字段数量
- hlen key
3.3 列表类型(list)
应用场景:新鲜事,队列。
列表类型可以存储一个有序的字符串列表,常用的操作是想列表的两端添加元素,或者获得列表的一个片段。
列表类型内部是采用双向链表实现的。所以向列表两端添加元素的时间复杂度为O(1)。
获取越接近两端的元素就越快。使用链表的代价是通过索引访问元素比较慢。这种特性使我们能非常快速的完成关系型数据库难以应付的场景,如社交网站新鲜事,人们只关心最新的内容。
![](https://i-blog.csdnimg.cn/blog_migrate/a83141b2d43a5fe1c3e8d567071ed7ac.png)
更深入一些,Redis底层存储并不仅仅是简单的linkedlist, 而是称为快速链表quicklist的结构。
所谓quick list,再列表元素比较少的时候会使用一块连续的内存存储,该结构时ziplist, 也即压缩列表,它将所有元素挨着一起存储,分配的时一块连续的内存。当数据量多的时候才改为quicklist。因为普通链表需要附加值真空间太大,会浪费空间,且会加重内存的碎片化,如只是存个int数据,结构上还需要加上额外的指针prev 和next 。链表和ziplist组合起来称为quicklist。多个ziplist使用双向指针穿起来使用。既满足快速插入删除,同时不会出现太大的空间冗余。
Redis列表常用作异步队列,将需要延后处理的任务结构体序列化成字符串塞进Redis列表,另一个线程从这个列表中轮询数据进行处理。
命令:
1.向列表两端添加元素
- lpush key value [value ...]
- rpush key value [value ...]
2.从列表两端弹出元素
- lpop key
- rpop key
3.获取列表中元素的个数
- llen key
4.获取列表片段
- lrange key start stop
- lrange numbers -2 -1
5.删除列表中指定的值
- lrem key count value
- count >0 从列表左边开始删除前count个值为value的元素
- count <0 从列表右边开始删除前count个值为value的元素
- count = 0 删除所有值为value的元素
6.获得、设置指定索引的元素值
- lindex key index
- lset key index value
7.只保留列表指定片段
- ltrim key start end # 可以删除指定索引之外的所有元素,其指定列表范围的方法与lrange命令相同。
- lindex name 1 # O(n) 慎用
- lrange name 0 -1 # O(n) 慎用
- ltrim name 1 -1 # O(n) 慎用
8.向列表中插入元素
- linsert key before|after privot value
- linsert numbers after 7 3
9.将元素从一个列表转到另一个列表
- rpoplpush source destination # source右出后左进destination
3.4 集合类型(set)
应用场景:博客中的文章标签。存储活动的中奖用户ID,因为有去重功能,保证同一用户不会中奖2次。
集合中每个元素都是不同且无序。列表是有序的。集合具有唯一性,列表不具备。
集合类型在Redis中使用值为空的散列表实现。所以这些操作的时间复杂度都是O(1)多个集合类型键之间还可以进行并集交集,差集运算。集合中最后一个元素移除后,数据结构自动删除,内存被回收。
命令:
1.增加、删除
- sadd key member [member ...]
- srem key member [ member ...]
2.获取集合中所有元素
- smembers key # 返回集合中所有的元素
3.判断元素是否在集合中
- sismember key member
4.集合间运算
- sdiff key [key...] #对多个集合执行差集运算
- sinter key [key...] # 对多个集合进行交集运算
- sunion key [key...] # 对多个集合执行并集运算
5.获取集合元素个数
- scard key
6.进行集合运算并将结果存储
- sdiffstore destination key [key ...]
- sinterstore destination key[key...]
- sunionstore destination key[key...]
7.随机获取集合中的元素
- srandmember key [count]
3.5 有序集合类型(zset)
zset 应该是Redis提供的最为有特色的数据结构,它也是在面试中面试官最爱问的数据结构,一方面它是set,保证了内部value的唯一性,另一方面它给每个value赋予一个score,代表这个value的排序权重,它的内部实现用的跳跃表。
同样的,zset中的最后一个value被移除后,数据结构自动删除,内存被回收。zset可以用来保存粉丝列表,value是粉丝的用户ID, score是关注事件,可以对粉丝列表按照关注时间进行排序。
应用场景:实现按点击量排序 改进按时间排序 学生成绩(可以按照学生成绩进行排序)
有序集合一些方面与列表相似。
- 二者都是有序的
- 二者都可以获得某一范围的元素
二者区别:
- 列表类型是通过链表实现的,获取靠近两端的数据速度极快,当元素增多后,访问中间数据的速度会比较慢,更加适合如新鲜事,日志这样很少访问的中间元素的应用。
- 有序集合类型是使用散列表与跳跃表实现的。即使读取位于中间部分的数据速度也很快
- 列表中不能简单的调整某个元素的位置,但是有序集合可以。
- 有序集合要比列表更加耗费内存。
命令:
1.增加
- zadd key score member [score member ...]
2.获得元素分数
- zscore key member
3.获得排名在某个范围内的元素列表
- zrange key start stop [withsocres]
- zrange key start stop [withscores]
4.获得分数范围的元素
- zrangebyscore key min max [withscores] [limit offset count]
5.增加某个元素的分数
- zincrby key increment member
6.获得集合中元素的数量
- zcard key
7.获得指定分数范围内元素的个数
- zcount key min max
8.删除一个或多个元素
- zrem key member [member ...]
9.按照排名范围删除元素
- zremrangebyrank key start stop
10.按照分数范围删除元素
- zremrangebyscore key min max
11.获得元素的排名
- zrank key member
- zrevrank key member
12.计算有序集合交集
- zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
容器型数据结构的通用规则 (list/set/hash/zset)
1.create if not exists
- 如果容器不存在,就创建一个,再进行操作。比如rpush操作开始没有列表,redis会自动创建一个,然后rpush。
2.drop if no elements
- 如果容器里元素没有了,那么立即删除元素,释放内存。意味着lpop操作到最后一个元素,列表就消失了。
四、进阶
4.1事务
所谓事务就是一组命令的集合,事务同命令一样都是Redis最小执行单位,一个事务中的命令要么执行,要么都不执行。
- 原理:将属于同一个事务的命令发给Redis,然后让Redis依次执行这些命令。
127.0.0.1:6379> multi # 告诉Redis 接下来要发送的命令同属于一个事务,先不执行
OK
127.0.0.1:6379> sadd "user:1:following" 2
QUEUED
127.0.0.1:6379> sadd "user:2:followers" 1
QUEUED
127.0.0.1:6379> exec # 告诉Redis将事务队列中的所有命令按照发送顺序执行
1) (integer) 1
2) (integer) 1
另外,Redis事务还能保证一个事务内的命令依次执行而不被其他命令插入。
4.1.2错误处理
- 语法错误:直接报错。
- 运行错误:命令执行时出现的错误,散列类型命令操作集合类型的键。
Redis没有回滚,开发者必须在事务执行出错后自己处理。有利有弊,正因为不支持回滚,使Redis事务可以保持简洁和快速。
4.1.3 watch命令
事务可以解决一些问题(保证事务内的命令不被其他命令插入、影响),但有时,事物内部的一个命令会需要另一个命令的的返回值,例如GET, SET ,因为事务中的命令执行结果都是最后一起返回的,所以无法将上一条命令结果作为下一条命令的参数。
watch:监控一个或多个键,一旦其中有一个键被修改或删除,之后的事务就不会执行。监控一致持续到EXEC命令。执行EXEC取消对所有键的监控。
4.2 生存时间
Redis中使用expire命令设置一个键的生存时间,到时间后Redis会自动删除它。
- expire key seconds seconds 表示键生存时间。
eg:
set name scrat
expire name 5 # 5s后过期
setex name 5 scrat # 等价于 set + expire
查看一个键还有多久被删除使用TTL
- eg: ttl foo
PERSIST 取消键生存时间,设置成永久。
更多:Python 目录