Redis
启动Redis:
进入Redis目录:
cd C:\Program Files\Redis
redis-cli
Redis相关指令:
字符串(String):
keys *:查询所有key
dbsize:查询所有key返回所有key的总数量
exists key:查询某一个key是否存在(返回值为1存在,返回0不存在)
del key:删除key(返回值为1为删除成功,返回0为删除失败)
expire key seconds:设置key的过期时间(默认单位为秒)
ttl key:查询key的过期状态(返回值分为3种。当返回值为大于0的数字即距离过期剩余时间,默认单位秒;返回值为-2代表当前key已经过期不存在;返回值为-1为未设置过期时间)
persist:清除掉已经设置的过期时间
type key:查询当前key的数据类型
randomkey:随机从已有key中返回一个key value
rename key:重命名key命名(默认会覆盖原有的key名字;即:如果当前Redis里面已存在一个key为zh的数据,将另外一个key进行重命名为zh的时候会将原有的zh的key的数据进行覆盖,解决方法:使用renamenx key指令重命名)
mset:批量设置key value;格式为key value key value...
mget:批量查询key(使用批量查询或者批量设置的指令可提高整体运行性能,节省单次设置或者查询的网络性能消耗)
Redis相关指令:
Hash:
存储相对复杂的数据结构
查找原理:
Java中hash的存储查询原理:hashmap-- key-->value(通过key获取value值)
Redis中hash的存储查询原理:redis key-->hash(field(域))-->value(通过key获取到域,并通过域得到对应的value值)
hset
hget
hgetall:获取当前key下的所有field以及所有的value(数据过多可能产生线程阻塞,防止线程阻塞可使用hscan扫描)
列表(集合(有序)):
用于存储多个有序的字符串,一个列表最多可存储2的32次方减1的元素
rpush:右边插入
lpush:左边插入
lrange:获取指定范围内的元素列表(如:lrange zh 0 -1为获取所有元素,0 1则为获取0和1范围之间的元素)
linsert:在指定位置插入元素(如:linsert zh after bin zhb会在key为zh的value为bin后面插入zhb的value值;在bin之前插入值则将after改为before)
lpop:从链表的最左端弹出(获取)元素
rpop:从链表的最右端弹出(获取)元素(弹出后的元素会从当前key移除)
lrem:从左往右的删除链表中对应元素的数量(如:lrem zh 5 b。即:从zh链表中由左往右删除5个b元素)
llen:获取当前链表长度
lindex:获取指定下标的元素
lset:修改指定下标
集合(无序,不允许存在重复成员):
sadd:以set集合的形式新增元素(如:sadd set: a b c;即:往set:1集合里面插入a b c三个值)
scard:查询当前set集合里面的元素的数量(如:scard set:1;即:查询set:1这个集合里面所有元素的数量)
sismember:判断当前元素是否在集合内(如:sismember set:1 a;即:判断a这个元素是否存在于set:1这个集合当中,若存在返回值为1,否则返回值为0)
srandmember:从指定集合当中随机返回一个元素(如:srandmember set:1 1;即:从set:1集合随机返回一个元素)
spop:随机从指定集合删除一个元素并返回;可用于抽奖系统(如:spop set:1 1;即:随机从set:1集合中删除某一元素并返回被删除的元素)
smembers:返回集合内所有元素(如:smember set:1;即:返回set:1集合内所有元素);注意,返回结果是无序
集合运算:
sinter:返回2个集合的交集(如:sinter set:1 set:2;即:获取set:1和set:2集合的交集元素)
sunion:返回2个集合的并集(如:sunion set:1 set:2;即:返回 set:1和set:2的并集元素,重复元素合并为一个元素)
sdiff:返回2个集合的差集(如:sdiff set:1 set:2;即:返回set:1集合和set:2集合的差集,区分前后顺序,返回set:1集合元素移出set:2元素共存的元素后的set:1剩余元素)
需要注意:集合运算的时候是比较耗费性能的操作,需要将集合内的元素进行遍历后运算;为了解决此遍历过程的性能浪费,Redis提供将这些运算后的数据存储到新的集合的操作
sinterstore:将指定2个集合的交集放到新的集合(如:sinterstore set:inter set:1 set:2;即:将set:1和set:2的交集放到set:inter集合)
sinsunionstore:将指定2个集合的并集放到新的集合
sdiffstore:将指定2个集合的差集放到新的集合
集合(有序,不允许存在重复成员):
zadd:新增一个有序集合,根据分数排序(如:zadd test 99 xiaoming 87 lihua 59 xiaohong;即:在test集合新增xiaoming,lihua,xiaohong几个元素,Redis会根据前面数字从小到大进行有序排列)
包含[NX]:指被插入元素必须为集合不存在的元素,常用于添加
包含[XX]:指元素必须存在才能设置成功,常用于数据更新或修改
包含[CH]:指当前操作后元素和分数发生变化的总数
包含[INCR]:对当前元素的分数进行累加
zcard:返回当前集合的成员个数
zscore:查询某一元素的分数(用法:zscore key 成员名)
zrank:查询某一元素排名(由低到高排序)
zrerank:查询某一元素排名(由高到低排序)
zrem:删除某一元素(可删除多个)
zincrby:增加元素的分数(如:zincrby test 10 xiaoming;即:给test集合里的xiaoming元素增加10分)
zrange:返回由低到高的指定位置的元素和分数;倒叙排列为zrevrange
zrangebyscore:按照分数范围进行元素查询(默认排序为由小到大,反之则为zrevrangebyscore)
zcount:返回分数范围内的成员个数(如:zcount test 70 90;即:返回分数在70-90分之间的元素个数)
zremrangebyscore:删除指定范围内的元素
zinterstore:交集运算后的结果放到指定集合里面
对u:gaokao和u2:gaokao的交集元素进行加权平均计算
Redis实现消息中间件:
list实现:
提供从头到尾的放置和拿去元素的功能
lpush-->rpop,消息从左边生产,从右边消费
由于rpop作为消费者去进行使用,无法确定消费需求,会造成服务器性能大量浪费;因此实际使用的时候需要使用阻塞式的命令进行消息获取,brpop b(blocking)lpop,即:没有消息线程会进入阻塞休眠状态,消息传递获取数据
pub/sub
stream
zset:可实现延时队列
zset里的分数机制:将到期时间作为zset的分数(score)
zrangebyscore查询元素,如果存在满足条件调用zrem进行业务相关处理
取值时间和分数相关联
Redis高级数据结构:BitMaps
使用位图法的好处:节省内存资源,相较于常规数据处理操作,使用位图法能节省32倍的内存开销,适用于并发较大,数据结构相对
底层实现:字符串形式实现
操作指令:
setbit:设置bit元素
getbit:获取bit元素
bitcount:获取当前key对应bit的所有元素数量
strat,end用于指定查询范围
bitop:bit运算
问题:当前有10亿数量自然数,乱序排列,需要进行排序,且在32位机器完成,仅2G内存,如何完成?
答案:设计10亿大小的位数组,用自然数作为下标
排序后结果要求去重
去重且排序
布隆过滤器:
计算公式:
可用于解决缓存击穿的问题
ln:以e为底的对数
m:位数组的大小
n:放入布隆过滤器的元素的个数
k:多少个函数(函数个数)比较合适
p:失误率(hash冲突的概率)
布隆过滤器判断存在的不一定存在,不存在的一定不存在
布隆过滤器是基于bitmap实现
HyperLogLog:提供不精确的去重复计数方案(误差率:0.81%)
pfadd:每一个pfadd新增的key会自带HyperLogLog
pfcount:统计UV访问量
pfmerge:把多个HyperLogLog的值进行汇总后传入到destkey
分桶概念再平均
调和平均数
对于每一个使用pfadd增加的字符串,对应的key会被Redis转化为64位bit串,其中前14bit用于定位桶(可以理解为桶ID),那么存在的桶的数量为2^14个,即16384个;剩下的50位bit可
在做UV统计的时候,相当于对2^14个桶的数据进行汇总做调和平均数的计算,计算最后得到的一个相对精确的值
Redis的GEO:
Redis3.2以后提供了GEO(地理定位)功能,支持存储地理位置信息用来实现诸如附近位置,摇一摇等依赖于地理位置信息的功能
原理:通过使用GeoHash的算法,将2维的经纬度映射到对应的一维空间;GeoHash算法以不同的位作为不同的计算精度进行存储;Redis中以一个52位的整数来存放地理位置的编码
geoadd:longitude代表地理位置的经度,latitude代表了地理位置的维度,member代表成员名字;geoadd支持一次性增加多个位置信息
geopos:获取某一个地方的经纬度,以member作为查询条件
geodist:计算两个成员(member)之间的距离;m代表米,km代表公里,ft代表英尺,mi代表英里
georadius:计算某一个目标范围之内附近的成员;radius代表设定半径
pipeline运行原理:
RTT:数据在网络通讯上往返的时间
由于服务端和客户端之间最快的网络传输介质是光纤,而光信号在光纤当中会出现约33%的性能损耗,假设服务端和客户端的距离为800公里,那么根据光信号的传输速度理论上得出公式为800*2/(300000*(2/3))=8ms,换言之,1秒钟传输125次,那么为了满足服务的高吞吐量和高并发性能,pipeline也就应运而生
pipeline的RTT:pipeline下的一个RTT对应着多个指令,在没有pipeline的情况下,一条指令对应一个RTT
将一批命令组装成pipeline发送给Redis
pipeline的命令包并非是越大越好,命令包过大会导致网络被过度占用;建议控制在内核的输入输出缓冲区大小之内,4kb-8kb之间
Redis事务:
Redis还提供简单的事务,将一组需要一起执行的命令放到multi和exec两个命令之间;multi为事物开始,exec为事务结束
在执行过multi指令后,服务是无法查到当前,需要在执行exec后查询才能返回两条结果(事务A和事务B同时存在的2个结果)
需要注意:Redis的事务不提供回滚功能
watch指令:使用watch后,multi失效,即整个事务失效
语法错误会让事务不执行
Lua
lua脚本的优点:
减少网络开销,lua中多个命令可以放在一个脚本运行
原子操作,Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入
复用性,客户端发送脚本存储在Redis中,意味其他客户端可复用这一脚本执行相同逻辑
Redis的生产和消费:
PUB/SUB:
消费者和发布者是不进行直接通信,发布者通过频道进行消息发布,订阅通过订阅频道进行消息消费
PUB/SUB遵从发布即忘的原则;即发布后不会确认消息发布状态
发布消息:
publish channel
他的返回值代表当前频道有几个订阅者订阅此频道
订阅消息:
subscrible channel
查看订阅数:
pubsub numsub channel
取消订阅:
unsubscribe channel
按模式订阅和取消订阅:
psubscribe ch*
punsubscribe ch*
为解决Redis作为消息中间件不可靠的问题,Redis从5.x后推出Redis Stream
Redis Stream借鉴了kafuka的设计模式,一个消息队列里面允许存在多个消费者群组(Group),每个消费者群组存在一个last_delivered_id用来记录被消费到的位置(可视同为offset,即偏移量),每一个群组之间相互独立不受影响;不同的群组之间的消息最终是由消费者所消费,一个群组的内部的消费者之间属于竞争关系,因为消息只被消费一次(可理解为集群消费模式);
pending_ids[ ]:消息确认,确保消息是否消费成功(Pending Entries List(PEL))
xadd:往stream中添加消息
其中:*代表当前消息的序号由Redis自动生成
Stream当中为了避免因为时间回调导致数据重复的问题,由此提出Last_generated_id用于记录最后一个消息的id,若发现当前时间早于或等于当前时间,那么自动根据最大id进行当前时间戳的id自增功能进而保证消息是递增关系
xlen:查询stream当中存在多少条消息数量
xrange:xrange key - +查询对应key的stream下的所有消息,-为最小(id)值,+为最大(id)值;也可以指定id进行查看
xdel:xdel key id删除指定stream中的某一个id值
xread:xread count num(指定数字) streams key id从指定的id处读取指定条数的消息(需要注意:指定的id不被包含到返回的结果里面)
xread block 0 count num(指定数字) streams $读取消费生产者发送的最后一条消息
单消费者:
用在消息需要顺序消费的时候使用
xgroup create:创建消费者群组
xgroup create key zhb1 0-0:zhb1为消费者群组名
xinfo stream key:查询当前key的整体情况
last_delivered_id:用于记录每个消费群组消费的偏移量
last_generated_id:用来记录最后一个消息的id
pending:待确认消息的条数
xack:消息的确认,消息确认后从pending列表中自动移除
xpending:
xclaim:消息在消费者之间进行转移
Redis做消息中间件总结:
List形式:发送消息使用lpush;消费消息使用brpop
优点:实现简单,消息的延迟几乎为0
缺点:无法做消息确认,无法执行广播模式且不能重复消费
zset形式:
优点:可以实现延迟队列
缺点:获取消息时需要进行轮询获取,且不允许写重复消息
pub/sub形式:
优点:足够简单,允许一个消息发送给多个消费者,即时发送,速度快
缺点:消息不可靠
stream形式:
优点:具备一个消息中间件的大部分功能
缺点:比较复杂
Redis的持久化:因为Redis的数据是暂存在内存当中,当服务器宕机重启后数据会丢失,因此需要对内存的数据进行持久化操作
1、RDB持久化过程
优点:持久化速度相较于AOF持久化过程和恢复过程更快
缺点:无法做到实时持久化和秒级持久化,在fork子进程进行持久化过程中,对资源开销相对较高;且由于存储文件(*.rdb)为二进制的文件存储,因此Redis的持久化文件会随Redis版本的升级导致新版本Redis生成的文件无法被老版本的Redis识别
原理:将Redis内存当中的数据生成一个快照存入磁盘当中,存储格式为*.rdb;RDB持久化分手动和自动触发模式
手动触发命令:save命令:使用时会阻塞当前Redis(调用主线程进行持久化操作,由于Redis的单线程的设计模式,因此执行此命令会阻塞Redis),直到RDB持久化过程完成为止;在需要持久化文件比较大的时候会造成长时间的阻塞,线上环境不推荐使用此命令
bgsave命令:Redis进程执行fork操作,创建子进程,由子线程完成持久化;阻塞时间可忽略,是从save命令优化而来
bgsave命令使用后会使用写时复制技术(CopyOnWrite)将主进程的消息进行copy到子进程,子进程再将copy出来数据进行RDB持久化操作
自动触发命令:关闭Redis的时候自动执行
配置自动触发:save m n:在m秒,n次修改的时候自动触发
从节点:从节点刚上线的时候会触发全量复制的功能;主节点调用bigsave生成*.rdb文件,并发送给从节点,从节点根据当前rdb文件执行全量复制的操作
RDB的关闭:save命令后面参数放入空值,如:save " "
2、AOF(Append Only File)持久化过程
优点:数据持久化可实时进行写入到磁盘中,可理解为rdb持久化方式的一个补充
缺点:
原理:记录写命令,恢复的过程中将写命令进行重演
开启命令:Redis.conf设置:appenonly yes(默认不开启,为no)
持久化过程:
1、所有写入命令(set/hset)会append追加到aof_buf缓冲区
2、AOF缓冲区向硬盘做sync同步
3、随着AOF文件越来越大,需要定期对AOF文件进行rewrite重写,达到压缩空间的目的
4、当Redis服务重启,可以load加载AOF文件进行恢复
流程图:
AOF文件resp协议(文本文件)存储,其优点是兼容性好,需要进行修改的时候直接手动修改即可
使用AOF缓冲区的优势:由于Redis的设计模式是单线程的,如果不加缓冲区容易造成Redis的性能下降
因为使用AOF缓冲区,因此在使用AOF持久化的时候需要考虑落盘时间的问题
落盘策略:
appendfsync always:每次执行写入AOF缓冲命令的时候,立刻调用系统的fsync命令,马上同步到AOF文件中;每次会强制执行同步落盘操作(优点:即时落盘,断电数据不丢失;缺点:性能差)
appendfsync everysec(Redis的默认持久化同步策略):每秒钟调用系统的write操作,写入到操作系统缓冲区,再每秒钟调用一次fsync,写完返回;换言之,每秒钟调用一次write指令和fsync指令(兼顾其他2种策略的优缺点)一般服务器宕机最多丢失1秒钟的数据
appendfsync no:系统调用完write的操作后不对AOF进行写入(只调用系统的write,优点:性能好;缺点:容易丢失数据)
write:延迟写机制,发送对应write命令后,系统会将对应指令存入到对应IO缓冲区即返回写入成功;何时进行落盘由系统进行调度
fsync:强制同步机制,文件确实落盘后才返回成功
AOF重写机制:
压缩每一个AOF文件的大小
AOF重写的触发机制:
手动触发:
使用bgrewriteaof命令
自动触发:
自动触发取决于2个参数:
auto-aof-rewrite-percentage100:aof文件大小比起上次重写时的大小,增长率为100%的时候重写(比如:上次写入aof文件大小为24M的大小,当文件增长到48M的时候会触发重写一次)
auto-aof-rewrite-min-size64mb:aof文件大小,超过64M的时候重写
在进行自动重写操作时,Redis也会判断是否满足进行重写条件,当没有重写任务时,且满足了重写条件,系统会自动fork一条子进程进行该操作,fork出的子线程会将aof文件重写进缓冲区,保存主线程fork出的子进程以后所产生的新数据(新产生的数据遵从合并原则机制进行数据的合并压缩),从而产生新的压缩过的aof文件,新的aof文件替代掉旧的aof文件,整个aof的重写完成
Redis的持久化优先级:
1、AOF和RDB同时开启情况下,优先加载AOF
2、若关闭AOF,Redis会加载RDB文件
3、加载AOF/RDB成功,则Redis重启成功
4、AOF/RDB加载过程中存在错误时,启动失败打印错误信息
Redis的key的遍历:
keys *:将所有的key遍历一遍
性能较差,由于Redis是单线程设计,在读取大量数据的时候容易引起阻塞
scan:扫描Redis里面存放了的key,每次遍历一小部分的key,遍历的时候需要手动设置一个游标(比如使用scan 0这个指令,表示从头开始进行遍历,返回的第一个值就是游标;如果是scan 20,则是从第19位开始遍历)
遍历一小部分的数据,默认情况下每次使用scan命令会从当前游标开始向后遍历后10条数据,手动修改是从count命令进行修改扫描数据条数
缺点:不能保证能遍历所有的键,在遍历过程中可能会出现新的消息写入
sscan:用于迭代集合键中的元素
hscan:用于迭代hash键中的键值对
zscan:用于迭代有序集合中的元素(包含元素成员和元素分值)
分布式锁:
分布式锁机制必须满足一个互斥条件,即:一个进程拿到锁,另外一个进程需要等待或者无法拿到锁
setnx lock:setnx lock value给某个value进行申请加锁
del lock:释放锁
该操作会出现一种情况,在A服务拿到这个锁资源,A服务器宕机,则A服务不会自动释放锁,会造成永久性占用锁资源问题。解决这一问题,给锁一个超时即可处理:使用expire lock命令。
如:expire lock 10代表当前锁过期时间为10秒;
该操作也会存在问题,由于该操作需要执行两条命令,所以可能存在只执行一条命令,另外一条命令来不及执行的情况,即执行setnx lock 1后执行expire lock 10失败(比如服务异常等情况)
处理这些可能存在的问题可使用Lua脚本或者pipeline来进行封装处理
为解决上述问题,在Redis2.6.12以后,使用set指令的时候可以通过设置set lock value ex time nx来确保消息的原子性,ex为超时时间,nx保证排他性(如set lock 1 ex 10 nx);这种方式依旧存在一个锁过期的和释放别的服务拥有的锁的问题(业务处理时间大于锁过期时间,锁被需要加锁的服务释放掉并释放别人拥有的锁的问题)
问题:
1、锁过期问题
2、释放别的服务拥有的锁的问题
分布式锁在Java客户端的实现:
实现lock接口(jedis提供)
使用分布式锁,性能调优建议现在本地让各线程抢锁,能大幅缩减网络访问造成的性能丢失
键的迁移
把部分数据迁移到另一台Redis服务器
Redis内部库默认划分16个库,通常开发中使用的是第0号库(第一个,可理解为16个库对应16个下标为0-15号),16个库中的数据是相互隔离的
指令:
move:Redis数据库的内部库进行迁移(如:从第0号库把数据迁移到第2号库使用该指令)
dump
store:在Redis实例之间进行数据迁移
dump+store:dump name,restore name
原理:在A服务上将A服务的数据dump出来进行持久化(格式为rdb格式的),将持久化文件拷贝到B服务,通过restore指令进行反序列化把数据在B服务器进行复原
migrate:在Redis实例间进行数据迁移migrate是将dump、restore、del三个指令进行组合简化操作,migrate命令具备原子性,Redis3.0.6以后支持多键迁移功能
Redis高可用高并发机制:
Redis复制:将当前redis服务器设置为对应redis服务器的从节点,设置后的从节点复制主节点所有数据
1.配置文件中加入slaveof{masterHost}{masterPort}随Redis启动生效
2.redis-sever启动命令后加--slaveof{masterHost}{masterPort}生效
3.使用命令slaveof{masterHost}{masterPort}生效
slaveof本身只是异步指令,不会复制数据
info replication:查看当前节点状态和信息
slaveof no one:取消从节点状态,从之前主节点获取到的数据进行保留
当一个从节点重新更换主节点后,从节点会将之前旧的主节点获取到的数据进行清除,将新的主节点数据复制到当前从节点当中
主从复制过程:
配置完slaverof,slave启动
1.保存主节点信息(记录主节点的位置信息)
2.主从建立socket连接(往主节点发送ping命令)
3.发送ping命令(确保主节点的存活状态,主节点会给从节点一个应答)
4.权限验证(验证密码是否正确,验证通过会进行数据同步)
5.同步数据集
6.命令持续复制
Redis2.8版本以后使用psync命令同步,过程分为全量复制和部分复制
psync命令运行需要以下支持:
主从节点各自复制偏移量,主节点复制积压缓冲区,主节点运行id
全量复制:
第一次建立主从后使用,如果重复执行则会清空从节点Redis原有数据重新写入
psync runid offest:主节点将数据通过RDB的形式执行bgsave产生持久化文件,然后发送给从节点,当发送数据量特别大的情况下(传输时间超过60秒),在发送RDB文件给从节点的期间内,主节点如果还在接收新的数据,那么主节点会将新的数据复制到客户端缓冲区内进行记录
部分(增量)复制:
网络出现问题,从节点再次连接主节点,主节点补发缺失数据;或者每次主节点获取新的数据时使用
心跳:
主从有长连接心跳,主节点默认每个10秒向从节点发送ping命令,repl-ping-slave-period控制发送频率,即间隔时间
主从相互发送心跳,主节点每隔10秒发送一个命令给从节点判断是否存活,从节点每隔1秒向主节点发送一个命令,除了判断主节点的状态之外,还要发送从节点的状态信息(replconf ack{offset}命令),用于上报偏移量,检查主从节点的同步状态;如果判断数据同步存在偏差,主节点会从客户端数据缓冲区里面拉去存在偏差或者丢失的数据重新发送给从节点
哨兵模式:
建立在复制基础之上
晋升机制:
检测到主节点宕机后,将从节点晋升为主节点
哨兵机制原理:
主节点出现宕机,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用
哨兵节点会同时监控主节点和从节点,通常哨兵节点部署位集群模式
当哨兵节点监控到master节点为下线状态(需多个哨兵节点进行确认为master不可用的情况下),哨兵会将哨兵节点的其中一台主机作为一台master主机来替换原有主节点,并通知客户端主节点的变更信息来完成故障转移的功能
哨兵服务器的启动方式:
方法1:./redis-sever ../配置文件 --sentinel
方法2:redis-sentinel 配置文件
quorum:
1、判断主节点是否可用
2、sentinel领导者选举有关
选举模式:哨兵节点的一半数量+1
sentinel个数相关,max(quorum)
failover-timeout:
1、哨兵选出从节点
2、晋升从节点(slave no one,会一直尝试将从节点晋升为主节点,如果一直未成功则会尝试到超时为止,当超时失败则意味着故障转移的失败,需要人工介入处理)
3、命令其他从节点复制新的主节点
注意:已经被判断为被宕机的主节点被替换后恢复,会被降级为从节点
哨兵集群的节点数建议为奇数个
1、节点数为奇数个的时候,集群会对外提供服务,如果是偶数个,会发生脑裂
2、集群节点为偶数和集群节点为奇数个的时候,容错率一样,造成资源浪费
哨兵实现原理:
sentinel集群每隔10秒向主节点和从节点发送一次info
集群的配置:
cluster-enabled yes:设置Redis节点为集群模式
cluster-config-file nodes-{port}.conf:设置Redis配置文件