Redis 是 Remote Dictionary Service 的简称;也是远程字典服务;是一个开源的、基于内存的高性能键值存储系统,支持多种数据结构(如字符串、哈希、列表、集合、有序集合等)。常用于缓存、消息队列、实时分析等场景。Redis 是内存数据库,KV 数据库,数据结构数据库;
内存数据库指的是redis存储在内存中不像MySQL一样存在磁盘中。
KV数据库指的是整体结构是以key-value的结构存储
数据结构数据库指的是value可以是5种数据结构:string,list,hash,set,zset
具体应用
记录朋友圈点赞数、评论数和点击数(hash)
记录朋友圈说说列表(排序),便于快速显示朋友圈(list)
记录文章的标题、摘要、作者和封面,用于列表页展示(hash)
记录朋友圈的点赞用户ID列表(list),评论ID列表(list),用于显示和去重计数(zset)
缓存热点数据,减少数据库压力(hash)
如果朋友圈说说 ID 是整数 id,可使用 redis 来分配朋友圈说说 id(计数器)(string)
通过集合(set)的交并差集运算来实现记录好友关系(set) 游戏业务中,每局战绩存储(list)
具体结构图
value类型
string
字符数组,该字符串是动态字符串 raw,字符串长度小于1M 时,加倍扩容;超过 1M 每次只多扩 1M;字符串最大长度为 512M;
注意:redis 字符串是二进制安全字符串;可以存储图片,二进制协议等二进制数据;但一半不存储,因为redis是内存数据库,存储这些很不合适
基础命令
# 设置 key 的 value 值
SET key val
# 获取 key 的 value
GET key
# 执行原子加一的操作
INCR key
# 执行原子加一个整数的操作
INCRBY key increment
# 执行原子减一的操作
DECR key
# 执行原子减一个整数的操作
DECRBY key decrement
# 如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做
# set Not eXist ok 这个命令是否执行了 0,1 是不是操作结果是不是成功
SETNX key value
# 删除 key val 键值对
DEL key
# 设置或者清空key的value(字符串)在offset处的bit值。 setbit embstr raw int
# 动态字符串 能够节约内存
SETBIT key offset value
# 返回key对应的string在offset处的bit值
GETBIT key offset
# 统计字符串被设置为1的bit数.
BITCOUNT key
累加器
# 统计阅读数 累计加1
incr reads
# 累计加100
incrby reads 100
分布式锁
# 加锁 加锁 和 解析 redis 实现是 非公平锁 ectd zk 用来实现公平锁
# 阻塞等待 阻塞连接的方式
# 介绍简单的原理: 事务
setnx lock 1 # 不存在才能设置 定义加锁行为 占用锁
setnx lock uuid # expire 30 过期
set lock uuid nx ex 30
# 释放锁
del lock
if (get(lock) == uuid)
del(lock);
list
双向链表实现,列表首尾操作(删除和增加)时间复杂度O(1);查找中间元素时间复杂度为 O(n)
基础命令
# 从队列的左侧入队一个或多个元素
LPUSH key value [value ...]
# 从队列的左侧弹出一个元素
LPOP key
# 从队列的右侧入队一个或多个元素
RPUSH key value [value ...]
# 从队列的右侧弹出一个元素
RPOP key
# 返回从队列的 start 和 end 之间的元素 0, 1 2 负索引
LRANGE key start end
# 从存于 key 的列表里移除前 count 次出现的值为 value 的元素
# list 没有去重功能 hash set zset
LREM key count value
# 它是 RPOP 的阻塞版本,因为这个命令会在给定list无法弹出任何元素的时候阻塞连接
BRPOP key timeout # 超时时间 + 延时队列
应用
我们可以利用链表同时只进行一边的出和进来实现不同的结构
栈(先进后出)
LPUSH + LPOP
# 或者
RPUSH + RPOP
队列(先进先出)
LPUSH + RPOP
# 或者
RPUSH + LPOP
阻塞队列
LPUSH + BRPOP
# 或者
RPUSH + BLPOP
hash
散列表,在很多高级语言当中包含这种数据结构;c++ unordered_map 通过 key 快速索引 value;
基础命令
# 获取 key 对应 hash 中的 field 对应的值
HGET key field
# 设置 key 对应 hash 中的 field 对应的值
HSET key field value
# 设置多个hash键值对
HMSET key field1 value1 field2 value2 ... fieldn valuen
# 获取多个field的值
HMGET key field1 field2 ... fieldn
# 给 key 对应 hash 中的 field 对应的值加一个整数值
HINCRBY key field increment
# 获取 key 对应的 hash 有多少个键值对
HLEN key
# 删除 key 对应的 hash 的键值对,该键为field
HDEL key field
set
集合;用来存储唯一性字段,不要求有序;
基础命令
# 添加一个或多个指定的member元素到集合的 key中
SADD key member [member ...]
# 计算集合元素个数
SCARD key
# SMEMBERS key
SMEMBERS key
# 返回成员 member 是否是存储的集合 key的成员
SISMEMBER key member
# 随机返回key集合中的一个或者多个元素,不删除这些元素
SRANDMEMBER key [count]
# 从存储在key的集合中移除并返回一个或多个随机元素
SPOP key [count]
# 返回一个集合与给定集合的差集的元素
SDIFF key [key ...]
# 返回指定所有的集合的成员的交集
SINTER key [key ...]
# 返回给定的多个集合的并集中的所有成员
SUNION key [key ...]
zset
有序集合;用来实现排行榜;它是一个有序唯一;
基础命令
# 添加到键为key有序集合(sorted set)里面
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
# 从键为key有序集合中删除 member 的键值对
ZREM key member [member ...]
# 返回有序集key中,成员member的score值
ZSCORE key member
# 为有序集key的成员member的score值加上增量increment
ZINCRBY key increment member
# 返回key的有序集元素个数
ZCARD key
# 返回有序集key中成员member的排名
ZRANK key member
# 返回存储在有序集合key中的指定范围的元素 order by id limit 1,100
ZRANGE key start stop [WITHSCORES]
# 返回有序集key中,指定区间内的成员(逆序)
ZREVRANGE key start stop [WITHSCORES]
redis pipeline
客户端一般的机制是客户端发送请求给redis,等待响应回来后再发下一个命令。
pipeline是客户端提供的机制,通过一次发送多次请求命令,减少网络传输事件。如下图
redis事务
redis事务其实可以类比原子操作来理解
在多核的时候我们探讨原子操作,在多条并发连接的时候探讨事务。
事务是用户定义的一系列数据库操作,这些操作视为一个完整的逻辑处理工作单元,要么全都执行,要么全部不执行,是不可分割的工作单元。
指令
MULTI
开启事务
EXEC
提交事务
DISCARD
取消事务
WATCH
检测 key 的变动,若在事务执行中,key 变动则取消事务;在事务开启前调用,乐观锁实现。
具体应用
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
注意这里使用的是乐观锁,即默认不会有别的连接来变动key,如果有key的变动,那么执行失败
lua脚本
lua 脚本实现原子性,redis 中加载了一个 lua 虚拟机,用来执行 redis lua 脚本,redis lua 脚本的执行是原子性的,当某 个脚本正在执行的时候,不会有其他命令或者脚本被执行,lua 脚本当中的命令会直接修改数据状态,lua 脚本 mysql 存储区别:MySQL存储过程不具备事务性,所以也不具备原子性。
# 从文件中读取 lua脚本内容
cat test1.lua | redis-cli script load --pipe
# 加载 lua脚本字符串 生成 sha1
> script load 'local val = KEYS[1]; return val'
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"
# 检查脚本缓存中,是否有该 sha1 散列值的lua脚本
> script exists "b8059ba43af6ffe8bed3db65bac35d452f8115d8"
1) (integer) 1
# 清除所有脚本缓存
> script flush
OK
# 如果当前脚本运行时间过长(死循环),可以通过 script kill 杀死当前运行的脚本
> script kill
(error) NOTBUSY No scripts in execution right now.
有了sha1散列值就可以通过值来运行已经编译的脚本。
EVAL
# 测试使用
EVAL script numkeys key [key ...] arg [arg ...]
EVALSHA
# 线上使用
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
redis的ACID特性分析
A 原子性;事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败;redis 不支持回滚;即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直 到将事务队列中的所有命令都执行完毕为止。
C 一致性;事务的前后,所有的数据都保持一个一致的状态,不能违反数据的一致性检测;这里 的一致性是指预期的一致性而不是异常后的一致性;所以 redis 也不满足;这个争议很大:redis 能 确保事务执行前后的数据的完整约束;但是并不满足业务功能上的一致性;比如转账功能,一个扣 钱一个加钱;可能出现扣钱执行错误,加钱执行正确,那么最终还是会加钱成功;系统凭空多了 钱;
I 隔离性;各个事务之间互相影响的程度;redis 是单线程执行,天然具备隔离性;
D 持久性;redis 只有在 aof 持久化策略的时候,并且需要在 appendfsync=always 才具备持久性;实际项目中几乎不会使用 redis.conf 中 aof 持久化策略;
面试时候回答:lua 脚本满足原子性和隔离性;一致性和持久性不满足。
redis异步连接
首先我们要明确实现异步连接要用什么库,我们为了异步连接需要做什么。
hiredis库里已经完成了协议解析,读写事件,缓冲区操作,协议加密
我们需要适配事件对象 适配事件操作函数即可。
更多资料在:https://github.com/0voice查询