Redis要点汇总
单机Redis
为什么会有Redis的出现
- 传统关系型数据库磁盘存储,必须给出schema类型(字节宽度),所以数据库是行级存储。数据表很大性能会下降。如果表有索引,增删改表慢,查询速度并发过多也会慢
- SAP的HANA是内存级别的关系型数据裤,虽然快但是非常贵,而且数据在磁盘和内存中体积是不一样的(因为磁盘上没有指针,所以同样的数据会多地复制存储,这样就会产生数据涨出,而内存是有指针的,所以同样的数据只存一份,然后用不同指针指向该片数据,所以内存存放数据空间是小于磁存放的)
- 磁盘->寻址:ms,带宽:G/M
内存->寻址:ns,带宽:很大
磁盘比内存在寻址上慢了10W倍,I/O buffer:成本问题,磁盘与磁道,一个扇区512Byte
索引4K,操作系统,无论你读取多少都是最少从磁盘拿4K
- Redis和memcache是一种折中的选择,速度快,而且价格便宜
架构师数据存储选型对比网站
技术选型和技术对比: https://db-engines.com/en
https://redis.io
http://www.redis.cn/ ---中文官网
Redis安装
https://redis.io
http://www.redis.cn/ ---中文官网
- BIO阻塞I/O:应用程序进程如果发起1K个请求,则开启1K个socket文件描述符,socket在等待内核返回数据时是阻塞式的,数据未准备好就一直阻塞等待,直到返回数据后才等待下一个socket的返回
- NIO轮询非阻塞I/O:应用进程如果发起1K个请求,则在用户空间不停轮询这1K个socket文件描述符,查看是否有结果返回。这种方法虽然不阻塞,但是效率太低,有大量无效的循环
-
I/O多路复用(select,poll,epoll)
select:能打开的文件描述符个数有限(最多1024个),如果有1K个请求,用户进程每次都要把1K个文件描述符发送给内核,内核在内部轮询后将可读描述符返回,用户进程再依次读取。因为文件描述符(fd)相关数据需要在用户态和内核态之间拷来拷去,所以性能还是比较低
poll:可打开的文件描述符数量提高,但性能仍然不够
epoll(Linux下多为该技术):用户态和内核态之间不用文件描述符(fd)的拷贝,而是通过mmap技术开辟共享空间,所有fd用红黑树存储,有返回结果的fd放在链表中,用户进程通过链表读取返回结果,伪异步I/O,性能较高
- AIO:异步I/O,性能最高,但是使用非常复杂,不是很常用(windows系统中多见)
RPC I/O模型,BIO,NIO,多路复用I/O,AIO
- BIO阻塞I/O:应用程序进程如果发起1K个请求,则开启1K个socket文件描述符,socket在等待内核返回数据时是阻塞式的,数据未准备好就一直阻塞等待,直到返回数据后才等待下一个socket的返回
- NIO轮询非阻塞I/O:应用进程如果发起1K个请求,则在用户空间不停轮询这1K个socket文件描述符,查看是否有结果返回。这种方法虽然不阻塞,但是效率太低,有大量无效的循环
-
I/O多路复用(select,poll,epoll)
select:能打开的文件描述符个数有限(最多1024个),如果有1K个请求,用户进程每次都要把1K个文件描述符发送给内核,内核在内部轮询后将可读描述符返回,用户进程再依次读取。因为文件描述符(fd)相关数据需要在用户态和内核态之间拷来拷去,所以性能还是比较低
poll:可打开的文件描述符数量提高,但性能仍然不够
epoll(Linux下多为该技术):用户态和内核态之间不用文件描述符(fd)的拷贝,而是通过mmap技术开辟共享空间,所有fd用红黑树存储,有返回结果的fd放在链表中,用户进程通过链表读取返回结果,伪异步I/O,性能较高
- AIO:异步I/O,性能最高,但是使用非常复杂,不是很常用(windows系统中多见)
缓存数据类型
memcache
key,value型存储:value是没有类型的概念,不同于Redis。对于复杂的结构可以存json,在应用程序端自己计算
该方法
这不符合计算向数据移动的思想
Redis
1. Redis是单进程、单线程、单实例、采用epoll的多路复用I/O模式,因此可以按顺序执行一个client端发起的所有命令
2. Redis存储的是二进制安全的字节流,而不是字符流
3. Redis的value类型String、List、Hash、Set、Sorted\_set
1) String:可以存储字符串、数值、bitmap(1.可用于用户登录天数统计,时间窗口随机 2.活跃用户统计,时间窗口
随机)
2) List:同向命令使用时栈,反向命令使用是队列,List正向索引(0,1,2...),反向索引(-1,-2,-3...)
3) Hash: key field value
4) Set:无序性和随机性,不去重,可以做随机抽取的动作
5) Sorted\_set:通过权重数值进行排序,默认相同权重,按字典序排序,排序采用算法跳跃表(skip list)
Redis进阶使用
- 订阅和发布
- Redis的事务:谁先exec,先执行哪个事物,Redis不支持事物的回滚
- 布隆过滤器(升级版:Counting 布隆过滤器、布谷鸟过滤器):通过多重Hash算法大概率过滤不存在的Key,解决缓存穿透问题,但是仍然有一些不存在Key的查询会到达数据库,可以在Redis中设置相关Key,value设置为null,但是这样的设置需要设定一个过期时间(https://github.com/RedisBloom/RedisBloom)
缓存中的常见问题
- 击穿
- 雪崩
- 穿透
- 一致性(双写)
Redis单机持久化
- RDB(存储和恢复较快,但是不实时,会丢数据):快照/副本
- AOF(数据全,但是文件较大,恢复较慢):日志,4.0以后的Redis支持AOF重写,将老的数据RDB到AOF中,增量部分为AOF
Redis集群
为什么要有Redis集群
Redis单机单进程,可以做缓存也可以用作数据库,具有两种持久化方式RDB和AOF
单机、单节点、单实例问题:
1. 单点故障
2. 容量有限
3. 访问压力过大
利用AKF微服务拆分原则,进行一变多的拆分
AKF----X,Y,Z三个方向进行拆分
X轴:Redis的全量镜像备份,解决单点故障和访问压力问题
Y轴:根据业务、功能进行Redis库的拆分,比如商品一个库,价格一个库,解决容量有限和访问压力问题
Z轴:根据优先级、逻辑再进行拆分,比如商品1~10000一个库,20001~30000一个库
X轴数据镜像备份数据一致性问题:
数据同步的三种架构:
- 更新主库,并同步阻塞更新备库,强一致性会破坏可用性
- 更新主库,并异步更新备库,弱一致性,可能带来数据的丢失(Redis选取方式)
- 更新主库,并同步阻塞更新kafka(可靠的消息队列集群,响应速度较快),最终数据一致性,可能短时间存在数据不一致情况
Redis系统最强调的是性能,它就是为快而生,所以没有选取最后kafka的方案,而是选取的第二种方案
Redis主从复制模式
Redis的主备方式,主读写,自己又是一个单点,仍然存在单点故障问题,如果主挂了,就不能再进行写数据操作,可能读数据操作还可以进行(从备库读取)
因此需要对主做HA高可用,自动故障转移,选择“代替人”,将从升级为主
需要监控程序监控主的健康情况,为了保证不误杀,需要多个监控程序同时监控,并对主的健康情况进行投票表决,因此监控程序一般为奇数个,超过半数监控认为主挂了,才会选出新的主
主从复制配置项:
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no ----主从复制是否经过磁盘
repl-backlog-size 1mb ----增量复制配置
min-replicas-to-write 3
min-replicas-max-lag 10
Redis高可用Sentinel哨兵模式
哨兵启动方法
redis-sentinel
redis-server --sentinel
#配置文件,Redis源码目录
port 26379
sentinel monitor mymaster 127.0.0.1 6381 2
|
需要至少几票表决才能决定换主
Redis容量问题解决
AKF拆分原则:X、Y、Z
主从复制可以解决主的HA,X问题,但是单节点容量问题还没有解决,因此需要对数据进行拆分,数据的拆封有四种方案:
- 数据可以分类,可以根据业务进行拆分
- 数据不可以分类,通过modula算法(hash+取模)进行sharding,弊端:取模的数必须固定、影响分布式下的扩展性
- 数据不可以分类,通过randam算法(随机lpush到Redis一个节点上,再另一头rpop取)进行sharding,类似消息队列场景
- 数据不可以分类,通过kemata算法(一致性哈希算法)进行sharding,hash算法包括(CRC16、CRC32、FNV、MD5映射算法)
一致性Hash算法:规划一个虚拟的环,上面有0~2^32个虚拟的点,通过计算(IP的HASH值)让每个物理节点位于环上面的某一个点,数据来了计算它位于环上的某一个点,这个点一般没有物理节点,就顺时针找寻离他最近的物理节点,这样如果新加一个节点,那么不需要数据迁移,只需要等该节点自己慢慢预热,他后面的节点设置LRU、LFU淘汰算法,就可以实现数据转移
优点:添加节点、的确可以分担其他节点的压力,不会造成全局洗牌
缺点:新增节点造成一小部分数据不能命中,带来击穿问题,压到mysql,可以通过设法取离数据最近的两个Redis方法解决
以上方案都是将Redis当缓存来使用,而不是当数据库使用
Redis集群代理方案
如果没有Redis集群代理,则连接成本过高,每个客户端都要连接每个Server
在代理层实现sharding算法,modula、random、kemata,代理proxy前面还可以使用LVS+VIP,让客户端的访问是透明的,keepalived保证LVS的高可用
数据分片的预分配方案:先将数据预分片为较多快,每个实际节点存几块(Redis cluster方案)
proxy代理方案
1. twitter的Redis集群方案,在Github上面搜索twemproxy ---- 不支持事务(https://github.com/twitter/twemproxy http://www.redis.cn/topics/partitioning.html)
2. predixy Redis集群代理方案,支持sentinel和cluster ---- 不支持事务(https://github.com/joyieldInc/predixy/blob/master/README_CN.md)
3. redis cluster ---- 共16384个槽位,每个节点分一些槽位,可以很方便的进行槽位迁移,均衡,客户端请求任意sharding,如果数据不在该sharding,则自动跳转sharding。该方案支持事物,但需要业务自己设计Key(基因法)保证数据在一个sharding上(http://www.redis.cn/topics/cluster-tutorial.html)
Redis 面试常见问题和API
Redis常见问题
击穿
前置条件:单个KEY在缓存中过期,但是忽然有大量并发查询,缓存还没有预热
后果:KEY的过期造成大量并发压到数据库
解决方案:1.get key 2.setnx(分布式锁) 3-1.OK,去DB取数据 3-2.false sleep ->1
该方案的问题:1.如果第一个人挂了,不释放锁怎么办?->可以设置锁的过期时间
2.如果第一个人没挂,但是锁过期怎么办?->多线程,一个线程取DB,另外一个线程监控是否DB返回,没有
返回延长锁时间
#以上解决方案非常繁琐,通过redis实现分布式锁做协调很麻烦,原因Redis是AP模型,分布式锁是CP模型,用AP实现CP,不合理
穿透
前置条件:从业务接收的查询是系统中根本不存在的数据
后果:查询总是透过缓存,抵达数据库
解决方案:使用布隆过滤器(布隆过滤器有缺陷,只能增加不能删除),可以添加key对应空值解决或者用布谷鸟过滤器(可
以删除)
雪崩->击穿
前置条件:大量KEY同时失效
后果:造成大量访问达到DB
解决方案:如果KEY之间是时点性无关的,那么设置随机过期时间,如果是有关的,那么和击穿方案一样,如果能预知缓存结
果可以提前预热缓存
RedisAPI
SpringBoot
https://docs.spring.io/spring-data/redis/docs/2.2.4.RELEASE/reference/html/#
Jedis
https://github.com/xetorthio/jedis
Lettuce
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。