Redis基础
Memcache和Redis的区别?
- Memcahce
- 支持简单数据类型
- 不支持数据持久化
- 不支持主从
- 不支持分片
- Redis
- 数据类型丰富
- 支持数据磁盘持久化存储
- 支持主从
- 支持分片
为什么Redis这么快?
- 10W+QPS(每秒查询次数10W+)
- 完全基于内存,绝大多数请求时纯粹的内存操作,执行效率高
- 数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
- 数据结构简单,对数据操作也简单
- 与关系型数据库相比,Redis不需要去预定义表结构以及关联其他表查询,性能高于关系型数据库
- 采用单线程,单线程也能处理高并发请求,向多核也可以启动多实例
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU
- 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路I/O复用模型,非阻塞IO
- 利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,等待IO流进入,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒 ,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作
- 这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
Redis的数据类型
- String
- Hash
- List
- Set
- Sorted Set
- HyperLogLog
- 用于计数
- Geo
- 用于支持存储地理位置信息
- 指定位置添加经纬度(多个)
geoadd key longitude latitude member [key longitude latitude member ...]
- 查询指定Key的经纬度信息(多个)
geopos key member [member ...]
- 计算两个位置间的距离,可指定单位m米、km千米、mi英里、ft英寸
geodist key member1 member2 [unit]
- 计算指定经纬度位置指定半径范围内的其他位置信息,可指定结果返回个数,距离排序,位置的经纬度,距离,Hash值,半径的单位
- 可以通过STORE将查询的结果集存储到其他key中,注意命令行不能填写WITH****这些选项
georadius key longitude latitude radius m|km|mi|ft [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
- 计算指定member位置指定半径范围内的其他位置信息,可指定结果返回个数,距离排序,位置的经纬度,距离,Hash值,半径的单位
georadiusbymember key longitude latitude radius m|km|mi|ft [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
- 查询指定位置的geoHash值
geohash key member
Redis求交集、并集、差集
- Sorted Set (Zset)
- zadd [集合A] [...value]
- zadd [集合B] [...value]
- sinter A B - 交集
- sunion A B - 并集
- sdiff A B C - BC与A的差集
Redis底层数据类型基础
- 简单动态字符串
- 链表
- 字典
- 跳跃表
- 整数集合
- 压缩列表
- 对象
从海量数据中查询某一固定前缀Key?
keys Patterns
- 会一次性返回所有匹配的key列表,匹配的数量过大会导致服务卡顿,可能带来内存的消耗和阻塞Redis服务器
scan cursor [MATCH pattern] [COUNT count]
- 游标匹配获取key集合,程序中遍历分批次获取。
如何实现Redis分布式锁?
setnx
+expire
实现的分布式锁
class RedisLock{
private Redis redis;
// 获取分布式锁
public boolean lock(String key,String requestId){
// 设置key,成功返回1,失败返回0
Integer flag = redis.setnx(key,requestId);
// 获取到锁,避免死锁设置过期时间
if(flag == 1){
redis.expire(key,10);
return true;
}
return false;
}
// 释放分布式锁
public boolean unLock(String key,String requestId){
// 先判断是否是自己设置的锁,再进行删除
Long flag = redis.exec("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end",key,requestId);
return flag == 1;
}
public static void main(String[] args){
// 加锁
if (redisLock.lock("lock","requestId")){
// 独占资源的业务逻辑
doMyWork();
// 解锁
redisLock.unLock("lock","requestId");
}
}
}
以上使用
setnx
和expire
实现的分布式锁有以下的缺陷:
因redis
的每一条命令都是原子性操作,但是上述setnx
和expire
分开执行,原子性已不复存在,此时setnx
执行完毕后程序发生异常,key
未设置过期时间,则将产生key
的死锁,使得其他抢占的线程无法获取锁执行下面的业务逻辑;
- 使用
set key value [expiration EX seconds|PX milliseconds] [NX|XX]
进行加锁的原子性操作
set lockKey requestId EX 10 NX
# EX 10 - 设置key的过期时间10秒
# NX - 表示key已存在时,返回nil,否则设置lockKey对应的requestId后返回OK
大量的Key同时过期的注意事项
- 集中过期,由于清除大量的key很耗时,会出现短暂的卡顿现象
- 解决方案:在设置key的过期时间时,给每个Key加上随机值
如何实现异步队列
- 使用List数据类型,
rpush
生产消息,lpop
消费消息- 缺点:
-
- 没有等待队列有值就直接消费,并非事件驱动模式
-
- 只能提供给一个消费者消费
-
- 弥补 1:
- 可以通过在应用层引入Sleep机制去调用lpop重试
- 使用
blpop
阻塞队列直到有消息返回或者超时
- 缺点:
- pub/sub主题订阅模式
- 发送者(publish)发送消息,订阅者(subscribe)接收消息
- 订阅者可以订阅任意数量的频道
- 事件驱动模式,不需要主动去请求队列询问是否有值
- 订阅同一个
topic
的多个消费者可以实时接收到消息 - 缺点:
- 消息的发布是无状态的,无法保证可达
- 某个消费者在发布消息时下线后重新上线时获取不到消息的
- 弥补:使用RabbitMQ等保证消息可靠性的消息中间件
Redis如何做持久化
- RDB(快照)持久化:保存某个时间点的全量数据快照
-
save:阻塞Redis的服务器进程,直到RDB文件被创建完毕
-
bgsave:Fork出一个子进程来创建RDB文件,不阻塞服务器进程
- 检查是否已开启AOF持久化,没开启才可进行bgsave
- 系统调用
fork()
:创建进程,实现Copy-On-Write
-
lastsave: 查看最近一次保存操作的时间戳
-
自动化触发RDB持久化的方式:
- 根据
redis.conf
配置里的save m n
定时触发(用的是bgsave) - 主从复制时,主节点自动触发
- 执行Debug Reload
- 执行shutdown且没有开启AOF持久化
- 根据
-
缺点:
- 内存数据的全量同步,数据量大会由于IO而严重影响性能
- 可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据
-
- AOF持久化:保存写状态
- 记录下除了查询以外的所有变更数据库状态的指令
- 以append的形式追加保存到AOF文件中(增量)
- 实战操作:
- 1.修改
redis.conf
配置appendonly yes
开启AOF增量备份模式appendfilename "appendonly.aof"
指定AOF文件名appendfsync eveysec
每秒进行一次备份
- 1.修改
- RDB和AOF的优缺点
- RDB
- 优点:全量数据快照,文件小,恢复快
- 缺点:无法保存最近一次快照之后的数据
- AOF
- 优点:可读性高,适合保存增量数据,数据不易丢失
- 缺点:文件体积大,恢复时间长
- RDB
- RDB-AOF混合持久化方式
- bgsave做镜像全量持久化,AOF做增量持久化
使用Pipeline的好处
- Pipeline和Linux的管道类似
- Redis基于请求/响应模型,单个请求处理需要一一应答
- Pipeline批量执行指令,节省多次IO往返的时间
- 有顺序依赖的指令建议分批执行
Redis的同步机制
- 主从同步原理
- 全同步过程
- salve发送sync命令到master
- master启动一个后台进程,将Redis中的数据快照保存到文件中
- master将保存的数据快照期间接收到的写命令缓存起来
- master完成写文件操作后,将该文件发送给salve
- 使用新的AOF文件替换掉旧的AOF文件
- master将这期间收集的增量写命令发送给salve
- 增量同步机制
- master接收到用户的操作指令,判断是否需要同步到salve(查询操作除外的其他操作)
- 将操作记录追加到AOF文件
- 将操作同步到salve:1. 对齐主从库;2. 往响应缓存写入指令
- 将缓存中的数据发送给salve
- 缺点
- 主从模式下,只有一个master节点和多个salve节点,master节点负责写操作,salve负责读操作
- 当master节点宕机后,redis无法对外提供服务
- 没有达到高可用
- 全同步过程
- Redis Sentinel 哨兵
- 解决主从同步master宕机后的主从切换问题
- 监控:检查主从服务器是否运行正常
- 提醒:通过API向管理员或者其他应用程序发送故障通知
- 自动故障迁移:主从切换
- 留言协议Gossip
- 解决主从同步master宕机后的主从切换问题
Redis的集群原理
- 分片:按照某种规则区划分数据,分散存储在多个节点上