本系列内容根据自己的学习和理解的基础上,将介绍Redis相关的知识和一些基础操作。如果有写的不对的地方,请各位多多提点。
一篇学会Redis(上)
NoSQL
NoSQL最常见的解释是“non-relational”, “Not Only SQL”也被很多人接受。NoSQL仅仅是一个概念,泛指非关系型的数据库,区别于关系数据库,它们不保证关系数据的ACID特性。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
- NoSQL的特点
- 方便扩展。数据间没有关联,是非关系型的数据库。
- 大数据量,高性能。NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说性能就要高很多。
- 灵活的数据类型多样性。NoSQL无须事先为要存储的数据建立字段,随时可以存储自定义的数据格式。
- 高可用。NoSQL在不太影响性能的情况,就可以方便地实现高可用的架构。
- 和关系型数据库一样,也需要安装。
Redis
Redis(Remote Dictionary Server),远程字典服务,是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
- Redis默认存在16个数据库,默认使用第0个数据库(序号从0开始),可用select操作切换。
- 默认端口号是6379。
- Redis是单线程的。
Redis是将数据存储在内存中的,使用单线程去操作是效率最高的。因为在多线程中线程的调度需要需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换,CPU上下文的切换的代价比较高,对于内存上的操作,没有CPU上下文的切换的操作的效率是最高的,多次读写都是在一个CPU上的话,单线程就是最佳的方案。
线程具体实现原理可查看之前写过的内容中对线程部分内容:从头开始学习JVM(七)—— Java内存模型与线程.
Redis的优点
- 速度快,因为数据存在内存中,时间复杂度都较低。
- 支持丰富数据类型,支持string,list,set,sorted set,hash。
- 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行。
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除。
什么情况下选择Redis
项目中使用Redis的目的,主要是从两个角度去考虑:性能、并发。由于内存的读写速度远快于硬盘,因此Redis的的的在性能上对比其他基于硬盘存储的数据库有非常明显的优势。
- 性能:
在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存,这样,后面的请求就去缓存中读取,请求使得能够迅速响应。
- 并发:
在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用的的Redis的做一个缓冲操作,让请求先访问到的Redis的的,而不是直接访问数据库。
Memcache 与 Redis 的区别
(1)存储方式
- Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。支持数据的备份,即master-slave模式的数据备份。
(2)数据支持类型
- Memcache对数据类型支持相对简单。 Redis有较复杂的数据类型。
(3)使用底层模型不同
- 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
- Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
(4)value大小
- redis最大可以达到1GB,而memcache只有1MB。
- Redis 比 Memcached 的优势
- memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型。
- redis的速度比memcached快很多。
- redis可以持久化其数据。
Redis的数据类型与常用操作
Redis是Key-Value数据库,其中value可存储的数据类型有字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
接下来对这些数据类型的操作进行说明。
Redis key类型
类型 | 常用操作 | 解释 |
---|---|---|
key | SET key value | 给key设置value值 |
GET key | 获取key 所储存的value | |
MOVE key db | 将key及其所存的value移到指定数据库 | |
DEL key | 删除存活的key及其value | |
KEY * | 查看所有的key | |
TYPE key | 返回 key 所储存的值的类型 | |
EXSITE key | 检查给定 key 是否存在 | |
EXPIRE key seconds | 为给定 key 设置过期时间,单位为秒 | |
TTL key | 返回给定 key 的剩余生存时间,单位为秒 |
Redis String类型
String类型在Redis中可以灵活运用,它的使用场景有:
- 计数器(value里存放数字则可以运算)。
- 统计多单位的数量。
- 对象缓存储存(可以活用String类型存储对象类型)。
类型 | 常用操作 | 解释 |
---|---|---|
string | APPEND key value | 给key设置value字符串,若key不存在等同与set操作 |
STRLEN key | 返回 key 所储存的字符串值的长度 | |
INCR / DECR key | 将 key 中储存的数字值增一 / 减一 | |
INCR / DECR key value | 将 key 对应的value中储存的数字值增一 / 减一 | |
GETRANGE key start end | 返回 key 上的value 从start到end长度的子字符串值 | |
SETRANGE key start end | 替换key 上的value 从start到end长度的子字符串值 | |
SETEX key seconds value | set with expire,给key设置带有过期时间的value值,单位为秒 | |
SETNX key value | set if not exist,若不存在key则set,设置失败返回0 | |
MSET [key value …] | 同时设置多个值 | |
MGET [key value …] | 同时获取多个值 | |
MSETNX [key value …] | 同时设置多个不存在key的值,该操作具有原子性,要么都成功,要么都失败 | |
GETSET key value | 若存在值,返回原值并设置新值,若不存在返回null | |
对象 | ||
SET user:1:name zhangsan | key上存储对象名.id.字段名,value上存实际值 | |
SET user:1 {name:zhangsan,age:22} | key上存储对象名.id,value上以json格式存储对象 |
Redis Hash类型
Redis中的Hash(哈希)类型是key-map的存储结构,即value中存储的是map(filed-value)类型,其中key可以重复。本质和String没有太大的区别。Hash 特别适合用于存储对象。
类型 | 常用操作 | 解释 |
---|---|---|
Hash | HSET key field value | 将哈希表 key 中的字段 field 的值设为 value,Hash中key是可以重复的,field重复则覆盖 |
HMSET key [field value…] | 批量设置值 | |
HGETALL key | 获取在哈希表中指定 key 的所有字段和值,Hash中key是可以重复的 | |
HDEL key [field…] | 删除一个或多个哈希表字段 | |
HLEN key | 获取哈希表key中字段的数量 | |
HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在 | |
HKEYS key / HVALS key | 返回key中的所有 field / value 值 | |
HINCRBY / HDECRBY key field increment | 为哈希表 key 中的指定字段的整数值加上 / 减少增加 increment 整数,即整数value +/- increment | |
HINCRBYFLOAT / HDECRBYFLOAT key field increment | 浮点数的增加/减少 | |
HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值 |
Redis List类型
List实际上是一个链表,在节点前后都可以插入数据。
- 如果key不存在则创建新的链表,如果key存在则更新内容,如果移除了所有值,链表即不存在了(空链表)。
- 在两头插入值效率较高,如果在中间插入值需要遍历,则效率相对低一些。
- Redis中,可以把List灵活运用成堆、栈、队列、阻塞队列等结构。
类型 | 常用操作 | 解释 |
---|---|---|
List | LPUSH key [value …] | 将一个或多个值插入名为key的列表头部 |
RPUSH key [value …] | 将一个或多个值插入名为key的列表尾部 | |
LPOP / RPOP key | 从头部 / 尾部移除一个元素,并将值返回 | |
LSET key index value | 按索引设置值,从头开始从0数起 | |
LINSERT key BEFORE / AFTER pivot value | 在列表的元素前 / 后插入元素 | |
LRANGE key start stop | 从左到右的顺序查看指定范围内的数据,[0,-1]则是查看所有数据 | |
LINDEX key index | 从头部数起以下标获取元素,从0开始数起 | |
LLEN key | 获取列表长度 | |
LREM key count value | remove from left,从头部开始移除指定个数的元素 | |
LTRIM key start stop | 截取指定范围内的值,从头开始从0数起 | |
RPOPLPUSH source destination | 从 source 列表取出最后一个元素,放到 destination 列表顶部 |
Redis Set类型
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据(无需不重复)。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
类型 | 常用操作 | 解释 |
---|---|---|
Set | SADD key [member …] | 向集合添加一个或多个成员,在Redis2.4版本以前, SADD 只接受单个 member 值。 |
SCARD key | 获取集合的成员数 | |
SMEMBERS key | 返回集合中的所有成员 | |
SISMEMBER key member | 查看key中是否存在指定的member,存在返回个数,不存在返回0 | |
SREM key [member …] | 移除一个或多个置顶值 | |
SRANDMEMBER key [count] | 随机抽选key中指定个数的元素 | |
SPOP key | 随机移除key中的一个元素,并将其返回 | |
SMOVE source destination member | 将指定的member从source集合取出放入destination集合 | |
SDIFF key1 [key2] | 用key1去与key2比较取差集 | |
SINTER key1 [key2] | 取交集 | |
SUNION key1 [key2] | 去并集 |
差集、交集、并集:
SADD key1 "a" "b" "c" "d"
SADD key2 "a" "c" "e"
SDIFF key1 key2 = {b,d} //用key1去与key2比较取差集(不一样的部)
SINTER key1 key2 = {a,c} //用key1去与key2比较取交集 (相交的相同部分)
SUNION key1 key2 = {a,b,c,d,e} //用key1去与key2比较取并集 (合并在一起去重)
Redis Sorted Set类型
在Set的基础上增加了一个值,作为排序标识。
类型 | 常用操作 | 解释 |
---|---|---|
sorted set | ZADD key [score member …] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序集合的成员个数 | |
ZCOUNT key min max | 计算在有序集合中指定区间分数的成员个数 | |
ZRANGE key start stop [WITHSCORES] | [按分数正序]显示指定下标区间内的值 ,[0,-1]显示所有值 | |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | [按分数正序]显示有序集合指定分数区间内的成员及他们的分数,[-inf +inf]负到正无穷显示所有值 | |
ZREVRANGE key start stop [WITHSCORES] | [按分数倒序]显示指定下标区间内的值 ,[0,-1]显示所有值 |
Redis各个数据类型的容量
Redis数据类型 | 容量 / 个数 |
---|---|
String | 一个String类型的value最大可以存储512M |
List / Set /Hash /Sorted Set | 元素个数最多为2^32-1个,也就是4294967295个 |
Redis三种特殊数据类型与常用操作
Redis geospatial类型
在Redis3.2版本之后推出了地理空间(geospatial,GEO)类型,这个功能可以推算两地之间的距离。GEO的底层实现是Sorted Set,可以用Sorted Set的命令操作。推算距离之前需初始化地理坐标数据。
longitude 经度,范围在-180到180之间,latitude 纬度,范围在-85.051128278到85.051128278之间。
类型 | 常用操作 | 解释 |
---|---|---|
GEO | GEOADD key lo la name | 增加对应name的经度、纬度 |
GEOPOS key [name…] | 获取一个或多个指定名称地名的经纬度 | |
GEODIST key name1 name2 [unit] | 获取两地之间的直线距离,[unit]为单位(m:米;km:千米;ml:英里;ft:英尺) | |
GEORADIUS key lo la number [unit] | 以指定经纬度地点为中心,number数值为半径的地点名称 | |
GEORADIUSBYMEMBER key name number [unit] | 根据已经录入的name为中心,number数值为半径的地点名称 |
Redis Hyperlog类型
基数(Hyperlog)类型是Redis2.8.9版本更新后加入的数据结构,即不重复的元素,用于基数统计算法,HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,存在误差。若不容错则不能使用该类型。
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(其中不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
举例:网站的UV(一个人访问一个网站多次,还是统计为一人次),传统方式是将用户存入set集合,因不可重复性,统计id个数得出结果。这种方式保存了大量的id,浪费了空间。基数可以用于这种情况。
类型 | 常用操作 | 解释 |
---|---|---|
Hyperlog | PFADD key [element …] | 创建一组元素到基数中 |
PFCOUNT [key …] | 返回基数估算值(不重复个数) | |
PFMERGE destkey [sourcekey …] | 将一个sourcekey基数或多个sourcekey基数 合并到 destkey中 |
Redis Bitmaps类型
位图(Bitmaps)也是一种Redis数据结构,通过操作二进制位来进行记录,只有0和1两个状态位。由于字符串是二进制安全blobs(二进制大对象),它们的最大长度为512Mb,因此可以设置多达232个不同的比特位。
类型 | 常用操作 | 解释 |
---|---|---|
Bitmaps | SETBIT key offset value | 设置或者清空key的value(字符串)在offset处的bit值(只能只0或者1) |
GETBIT key offset | 查看key中offset处的位状态 | |
BITCOUNT key start end | 统计指定范围内状态位为1的个数,没有范围则统计全部 | |
BITOP operation destkey [key …] | 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。(operation) BITOP 命令支持AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数 |
- 运用场景:
- 用户签到打卡。
SETBIT sign [0-7] [0/1] //使用offset存储本周几,value存储是否打卡标识。
//可以扩展成本月签到等类似场景,offset取当天运算后的时间戳
- 用户在线状态。
//按日期、用户id存入在线标识
SETBIT 2020-06-12-online uid [0/1]
//统计当天在线人数
BITCOUNT 2020-06-12-online uid
- 统计活跃用户。
//使用时间作为cacheKey,然后用户ID为offset,如果当日活跃过就设置为1
SETBIT stay_2020-06-11 uid [0/1]
SETBIT stay_2020-06-12 uid [0/1]
//按位与得,然后统计出当天/月/年的活跃用户
//假设当前站点有5000W用户,那么一天的数据大约为50000000/8/1024/1024=6MB
BITOP AND newstay stay_2020-06-11 stay_2020-06-12
BITCOUNT newstay uid
事务
Redis单条命令是原子性的(要么都成功,要么都失败),但是Redis的事务是不保证原子性的。
Redis事务的本质:一组命令的组合。一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行。一次性、顺序性、排他性地执行这些命令。
Redis的事务没有隔离级别的概念。在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
事务的相关操作:
操作命令 | 说明 |
---|---|
multi | 标记一个事务块的开始 |
exec | 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) |
discard | 取消事务,放弃事务块中的所有命令 |
watch [key …] | 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 ) |
unwatch | 取消watch对所有key的监控 |
一次事务的完整操作:
MULTI //开启事务
//命令操作入队
set k1 v1
set k2 v2
get k2
//DISCARD //提交事务之前可以取消事务
//提交事务,开始执行命令,未提交之前命令仅在队列中并未真正执行
EXEC
错误
- 命令性错误(编译时错误)
若某一条命令有错出现编译时错误,则事务中的命令都不会执行。
getget key //命令输入错误
>(error)EXECABORT Transcation discarded....
- 语法性错误(运行时时错误)
若某一条命令存在语法性错误,则事务中的其他命令仍然可以执行。
set key1 v1 //给key1设置值为v1字符串
incr key1 //给字符串增加1
>(error)ERR value is not integer or out of range
监控
介绍监控之前先复习一下乐观锁与悲观锁:
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接运行,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
乐观锁机制采取了更加宽松的加锁机制。它并不是真正的锁,而是一种概念。
悲观锁,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。
一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。悲观锁主要是共享锁或排他锁
Redis中的监控采用了乐观锁的方式,保持了它的高性能。
set k1 10 //设置k1 默认值为10
---窗口1 | ---窗口2
WATCH k1 | WATCH k1
MULTI | MULTI
decrby k1 5 | incrby k1 10
EXEC | EXEC
get k1 | >(nil) 失败
> 15
watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。