参考《Java EE 互联网轻量级框架整合开发》相关章节
一.Redis概述
1.适用场景
- 传统关系型数据库无法满足高并发场景(如商品抢购、主页瞬间大量访问)的需要
- Redis适用于缓存场景,现实场景中读操作远多于写操作,80%的读取集中在20%的数据
- Redis适用于高速读/写场景,将高速读/写的数据缓存到Redis,满足一定条件后再写入数据库
2.Redis性能优势
- 基于ANSI C语言编写,接近于汇编语言的机器语言,运行速度快
- 基于内存的读/写,比磁盘IO快
- 数据库结构比较简单(只有6种数据类型),规则较少
3.NoSQL与传统数据库
- NoSQL使用内存存储数据,传统数据库使用磁盘
- NoSQL数据结构比较简单,功能有限,不如传统数据库的SQL语句强大
- NoSQL基于内存,持久化能力受外部条件影响,不如基于磁盘的传统数据库
- NoSQL数据完整性、事务能力、安全性、可靠性及可扩展性都远不及传统数据库
- NoSQL短期内难以取代传统数据库,但能够作为提高互联网应用性能的辅助工具
二.Redis数据结构和常用命令
1.字符串
- Redis最基本的数据结构,以键值对的形式存储于Redis内部,Redis通过键查找值
- Redis字符串基本命令
| 命令 | 说明 | 备注 |
|---|---|---|
| set key value | 设置键值对 | |
| get key | 通过键获取值 | |
| del key | 通过键删除键值对 | 返回删除的键值对个数 |
| strlen key | 求key指向字符串的长度 | 返回长度 |
| getset key value | 修改原key对应的值,并返回原值 | 若原值为空则返回空 |
| getrange key start end | 获取子串 | 返回(start,end)截取的字符串 |
| append key value | 将新value加入到key指向的value末尾 | 返回拼接后value的长度 |
- 如果字符串是数字(整数或浮点数),Redis还能支持简单的加减运算
| 命令 | 说明 | 备注 |
|---|---|---|
| incr key | 原字段加1 | 限整数 |
| incrby key increment | 原字段加整数(increment) | 限整数 |
| decr key | 原字段减1 | 限整数 |
| decrby key decrement | 原字段减整数(decrement) | 限整数 |
| incrbyfloat key increment | 原字段加浮点数(increment) | 整数或浮点数 |
2.哈希
- Redis中hash是一个String类型的field和value的映射表,一个哈希里有许多键值对,适合存储对象
- Redis哈希常用命令
| 命令 | 说明 | 备注 |
|---|---|---|
| hdel key field1 [field2...] | 删除hash结构中的某字段 | 可同时删除多个字段 |
| hexists key field | 判断hash结构中是否存在field字段 | 存在返回1,否则返回0 |
| hgetall key | 获取hash结构中所有键值 | |
| hincrby key field increment | hash结构的指定字段加上一个整数 | 要求该字段本身为整数字符串 |
| hincrby float key field increment | hash结构的指定字段加上一个浮点数 | 要求该字段本身为数字型字符串 |
| hkeys key | 返回hash中所有键 | |
| hvals key | 返回hash中所有值 | |
| hlen key | 返回hash中键值对的数量 | |
| hmget key field1 [field2 ...] | 返回hash中指定键的值,可以是多个键 | 依次返回值 |
| hmset key field1 value1 [field2 value2 ...] | hash设置多个键值对 | |
| hset key field value | hash设置单个键值对 | |
| hsetnx key field vaule | 当hash结构中不存在对应的键,才设置值 |
3.链表(linked-list)
- Redis链表底层是双向链表
- 便于增删但不便于查询
- 因为是双向链表结构,所以Redis链表命令分为左操作和右操作两种,常见链表命令如下
| 命令 | 说明 | 备注 |
|---|---|---|
| lpush key node1 [node2...] | 把node1加到链表最左边 | |
| rpush key node1 [node2...] | 把node1加到链表最右边 | |
| lindex key index | 读取下标为index的节点 | 返回节点字符串 |
| llen key | 求链表的长度 | 返回链表节点数 |
| lpop key | 删除左边第一个节点,并将其返回 | |
| rpop key | 删除右边第一个节点,并将其返回 | |
| insert key before/after pivot node | 在节点pivot的前/后插入node节点 | 如果list不存在,则报错;如果没有值为pivot的节点,将插入失败返回-1 |
| lpushx list node | 如果存在key为list的链表,则从左边插入节点node | 如果list不存在,则失败 |
| rpushx list node | 如果存在key为list的链表,则从右边插入节点node | 如果list不存在,则失败 |
| Irange list start end | 获取链表list从start下标到end下标的节点值 | 包含start和end下标 |
| lrem list count value | 如果count为0,则删除所有值等于value的节点;若count不为0,则从左到右删除不大于count绝对值数目值为value的节点 | |
| lset key index node | 设置列表下标为index的节点的值为node | |
| ltrim key start stop | 修剪链表,只保留从start到stop区间的节点 | 包含start和end下标 |
- 上述命令未对Redis的链表加锁,在多个Redis客户端同时操作同一链表时将造成并发数据安全和一致性问题,Redis提供链表的阻塞命令以保障链表操作的安全性
| 命令 | 说明 | 备注 |
|---|---|---|
| blpop key timeout | 移出并获取链表的第一个元素,如果链表没有元素会阻塞链表直到等待超时或发现可弹出元素为止 | 相对于lpop命令线程安全 |
| brpop key timeout | 移出并获取链表的最后一个元素,如果链表没有元素会阻塞链表直到等待超时或发现可弹出元素为止 | 相对于rpop命令线程安全 |
| rpoplpush key src dest | 将原链表最右边一个元素移除,并插入目标链表最左边 | 不能设置超时时间 |
| brpoplpush key src dest timeout | 将原链表最右边一个元素移除,并插入目标链表最左边,可以设置超时时间 |
4.集合
- Redis集合是一个哈希表结构,插入、删除和查找的复杂度都是O(1)
- 集合是无序、不可重复的,集合的每一个元素都是String数据结构类型
- Redis集合可对两个或多个集合求交集、差集和并集,常用命令如下
| 命令 | 说明 | 备注 |
|---|---|---|
| sadd key member1 [member2 member3 ...] | 给集合key增加成员 | 可以同时增加多个 |
| srem key member1 [member2 ...] | 移除集合key的元素 | |
| scard key | 统计集合key的成员数 | |
| sdiff key1 [key2] | 找出两个集合的差集 | 参数如果只有一个集合,则返回这个集合的所有元素 |
| sdiffstore des key1 [key2] | 找出两个集合的差集,并保存在集合des中 | |
| sinter key1 [key2] | 求集合key1和集合key2的交集 | 参数如果只有一个集合,则返回这个集合的所有元素 |
| sinterstore des key1 key2 | 求集合key1和集合key2的交集并保存到des | |
| sismember key member | 判断member是否为集合key的成员 | 是返回1,否返回0 |
| smembers key | 返回集合key的所有成员 | 数据量大时需考虑迭代遍历 |
| smove src des member | 将成员member从集合src迁移到集合des | |
| spop key | 随机弹出集合key的一个元素 | |
| srandmember key[count] | 随机返回集合key的一个或多个元素 | count为整数,不填默认为1,负数取绝对值,count大于集合元素总数时返回整个集合 |
| sunion key1 [key2] | 求集合key1和集合key2的并集 | |
| sunionstore des key1 key2 | 求并集并将结果保存到键为des的集合 |
5.有序集合
- Redis有序集合和集合类似,通过哈希表实现,添加、删除、查找的时间复杂度均为O(1)
- 区别在于每个元素除了值之外,还多一个分数,Redis根据分数进行排序
| 命令 | 说明 | 备注 |
|---|---|---|
| zadd key score1 value1 [score2 value2 ...] | 给有序集合key增加成员 | 如果不存在有序集合key,则自动创建 |
| zcard key | 统计有序集合key的成员数 | |
| zcount key min max | 根据分数返回对应的成员列表 | 分数在最小值min与最大值max间 |
| zincrby key increment member | 给有序集合成员值为member的分数增加increment | |
| zinterstore desKey numkeys key1 [key2 key3 ...] | 求多个有序集合的交集并存入desKey | |
| zlexcount key min max | 求有序集合key成员值在min和max的范围集合 | |
| zrange key start stop [withscores] | 按照分值的大小从小到大返回成员,start和stop参数可截取某一段返回,若输入可选项withscores则连同分数一起返回 | |
| zrank key member | 按从小到大求有序集合的排行 | |
| zrangebylex key min max [limit offset count] | 根据值的大小,从小到大排序,min和max为最小/最大值,Redis求出范围集合后根据偏移量offset和限定返回数count返回对应的成员 | |
| zrangebyscore key min max [withscore] [limit offset count] | 根据分数的大小,从小到大排序,min和max为最小/最大值,Redis求出范围集合后根据偏移量offset和限定返回数count返回对应的成员 | |
| zremrangebyscore key start stop | 根据分数区间进行删除 | |
| zremrangebyrank key start stop | 按照分数排行从小到大的顺序删除 | |
| zremrangebylex key min max | 按照值得分布进行删除 | |
| zrevrange key start stop [withscores] | 从大到小按分数排序,参数参照zrange | |
| zrevrangebyscore key max min [withscores] | 从大到小按分数排序,参数参见zrangebyscore | |
| zrevrank key member | 按从大到小顺序求元素的排行 | |
| zscore key member | 返回成员的分数值 | |
| zunionstore desKey numKeys key1 [key2 key3 key4...] | 求多个有序集合的并集 | numKeys是有序集合的个数 |
6.基数(HyperLogLog)
- 基数并不存储元素,存储元素需要较大内存空间
- 基数用于给某个有重复元素的数据集合评估需要的空间单元数(即数据集合中不重复的元素数量)
三. Redis事务
1.概述
- 在多个客户端同时向Redis系统发送命令的时候,同一个数据同时可能被不同线程操纵,产生并发下的数据一致性问题
- Redis通过事务解决并发场景的数据安全问题,事务使用Multi-Exec命令组合
1)事务是一个被隔离的操作,事务中的方法都会被Redis进行序列化并按顺序执行,事务在执行过程中不会被其他客户端发出的命令所打断
2)事务是一个原子性操作,要么全部执行,要么全部不执行
2.事务过程
Redis事务将经历3个过程
1)开启事务
2)命令进入队列
3)执行事务事务命令
| 命令 | 说明 | 备注 |
|---|---|---|
| multi | 开启事务,之后的命令进入队列,但不会立刻执行 | |
| watch key1 [key2...] | 监听某些键,当被监听的键在事务执行前被修改,则事务将被回滚 | 乐观锁机制 |
| unwatch key1 [key2...] | 取消监听的键 | |
| exec | 执行事务,但如果被监听的键发生改变,则执行回滚 | 执行事务队列存储的命令前,Redis会检测被监听的键值对有没有发生变化 |
| discard | 回滚事务 | 回滚后的事务不能再提交 |
- 事务执行流程示例
| 时刻 | 客户端1 | 客户端2 | 说明 |
|---|---|---|---|
| T1 | set key1 value1 | 客户端1:返回OK | |
| T2 | watch key1 | 客户端1:监控key1 | |
| T3 | multi | 客户端1:开启事务 | |
| T4 | set key2 value2 | 客户端1:事务命令入列 | |
| T5 | - | set key1 value1 | 客户端2:修改key1的值 |
| T6 | exec | - | 客户端1:执行事务,执行前检测到key1的值被其他命令修改过,所以将进行回滚 |
3.流水线(pipelined)
- Redis通过事务提供队列,作为一个可以批量执行任务的队列,但使用事务会检测对应的锁和序列化命令,存在系统开销
- Redis提供流水线技术,用来在没有任何附加条件的场景下使用队列批量执行一系列的命令,从而提高系统性能
- 应用场景
1)Redis执行读/写速度非常快,系统的瓶颈往往是网络通信的延时
2)使用Redis的流水线(本质是一种通信协议),可以有效提高性能
3)使用流水线产生的返回对象,可能占用服务器上较多的内存空间,导致OOM异常
4.发布订阅
- 场景
使用银行卡消费时,银行通过微信、短信或邮件通知用户该笔交易信息 - 观察者模式
1)记账系统是消息源,收到交易指令,成功记账后,就会发布消息
2)要有消息渠道,记账系统通过消息渠道向订阅者发布消息
3)要有订阅者(微信、短信、邮件等系统)订阅消息渠道的消息
四. 超时命令——Redis内存回收
1. Redis键值对的超时
- 超时命令
| 命令 | 说明 | 备注 |
|---|---|---|
| persist key | 持久化key,取消超时时间 | |
| ttl key | 查看key的超时时间 | 以秒计算,-1代表没有超时时间,-2代表不存在key或key已超时 |
| expire key seconds | 设置超时时间戳 | 以秒为单位 |
| expireat key timestamp | 设置超时时间点 | 用unix时间戳确定 |
| pptl key milliseconds | 查看key的超时时间戳 | 以毫秒为单位 |
| pexpire key | 设置键值超时的时间 | 以毫秒为单位 |
| pexpireat key stamptimes | 设置超时时间点 | 以毫秒为单位的unix时间戳 |
- Redis的key超时不会被自动回收,而是被标识为已经超时
1)好处:避免因很大的键值对超时的回收造成卡顿
2)坏处:被标记为超时而未回收的键值对会占用空间
2. Redis回收机制
- 定时回收:在某个确定的时间统一回收超时的键值对
1)可以完全回收超时的键值对
2)一次性回收的键值对较多时,Redis会停顿影响业务,故定时回收一般在没有业务发生的时刻触发 - 惰性回收:再次执行访问(get命令)超时的键时回收该键
1)优点是可以指定回收超时的键值对
2)缺点是通过get命令指定回收,需要额外执行get操作