Cache

Redis

Redis的定义

Redis是Remote Dictionary Server(远程字典服务器)的缩写,它以字典结构存储数据,并允许其他应用通过TCP协议读写字典中的内容,用C语言编写的。
Redis是开源免费的,支持多语言的API(C/C++/C#/Java/Javascript/Lua/PHP/Python/Ruby/Go),具有原子性,可持久化的KV数据库
Redis遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。
与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
Redis的特点 1.丰富的数据结构 字符串(String) 散列(hash) 列表(list) 集合(set) 有序集合(zset) 2. 单进程模型
Redis使用单进程模型来处理客户端的请求。对读写等事件的响应是通过对epoll函数的包装来做到的。
Redis的实际处理速度完全依靠主进程的执行效率
注:epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
3.数据库个数
Redis默认16个数据库,类似数组下表从零开始,初始默认使用0号库(集群中只能使用0号库)
4.持久化与数据备份
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
持久化方式
RDB AOF
数据备份的方式
主从复制
原子 – Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC指令包起来。

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
Redis的优点
对数据高并发读写
对海量数据的高效率存储和访问
对数据的可扩展性(水平、加一台服务器和垂直、升级内存)和高可用性
注:可扩展性分为水平扩展与垂直扩展,例如:加一台服务器就叫水平扩展;升级服务器的内存称为垂直扩展。

Redis与Memorycache的区别

  1. Redis是单线程的
    Redis使用单线程,而Memcached是多线程,MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀。
    Redis保证了对于非复合的操作,没有线程安全问题。虽然Redis使用的是单线程,但是Redis 足够快。
  2. Redis申请内存
    Redis使用现场申请内存的方式来存储数据,并且可以配置虚拟内存;
    Memcached使用预分配的内存池的方式。
  3. Redis可以持久化
    Redis 与Memcached一样,为了保证效率,数据都是缓存在内存中。
    Redis会周期性的把更新的数据写入磁盘或者把修改 操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步,而Memcached只是存放在内存中,服务器故障关机后数据就会消失,所以Redis的容灾性会更强。
  4. Redis支持5种数据类型
    Redis支持五种数据类型:string(字符串),list(链表), Hash(哈希),set(集合)及zset(有序集合)。
    Memcached只是简单的key与value

Redis应用场景

  1. 取最新N个数据的操作
    比如典型的博客网站的最新文章,通过下面方式,我们可以将最新的5条评论的ID放在Redis的List集合中,并将超出集合部分从数据库获取。
  2. 排行榜应用,取TOP N操作
    取最新N个数据的操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。
  3. 需要精准设定过期时间的应用
    比如你可以把上面说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。
  4. 计数器应用
    Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。
  5. Uniq操作,获取某段时间所有数据排重值
    这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。
  6. 实时系统,反垃圾系统
    通过上面说到的set功能,你可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。
  7. Pub/Sub构建实时消息系统
    Redis的Pub/Sub系统可以构建实时的消息系统,比如很多用Pub/Sub构建的实时聊天系统的例子。
  8. 构建队列系统
    使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。
  9. 缓存
    性能优于Memcached,数据结构更多样化。

分布式锁

  • redis分布式锁
    先拿setnx来争抢锁,抢到之后再用expire来给锁加一个过期时间,防止锁忘了释放。
    如果再setnx之后,expire之前线程意外crash或者要重启维护,把setnx和expire合成一条命令来用。
    1、SETNX
    SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
    2、expire
    expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
    3、delete
    delete key:删除key
    在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
    例子:
    比如:客户端A拿到锁并设置了锁的过期时间为10S,但是由于某种原因客户端A执行时间超过了10S,此时锁自动过期,那么客户端B拿到了锁,然后客户端A此时正好执行完毕删除锁,但是此时删除的是客户端B加的锁,如何防止这种不安全的情况发生呢?
    方案一:
    我们可以让获得锁的线程开启一个守护线程,用来给自己的锁“续期”。
    当过去了9S,客户端A还没执行完,这时候守护线程会执行expire指令,把锁再“续期”10S,守护线程从第9S开始执行,每9秒执行一次。
    当客户端A执行完任务,会显式关掉守护线程。
    如果客户端A忽然宕机,由于A线程和守护线程在同一个进程,守护线程也会停下。这把锁到了超时的时候,没人给它续期,也就自动释放了。
    方案二:
    我们也可以在加锁的时候把set的value值设置成一个唯一标识,标识这个锁是谁加的锁,在删除锁的时候判断是不是自己加的那把锁,如果不是则不删除。
  • zk分布式锁
    在获取锁的时候在固定节点下创建一个自增的临时节点,然后获取节点列表,按照增量排序,假如当前创建的节点是排在第一个的,那就表明这个节点是得到了执行的权限,假如在它前面还有其它节点,那么就对它的上一个节点进行监听,等到上一个节点被删除了,那么该节点就得到了执行的权限了。
    创建一个锁目录lock
    希望获得锁的线程A就在lock目录下,创建临时顺序节点
    获取锁目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
    线程B获取所有节点,判断自己不是最小节点,设置监听(watcher)比自己次小的节点(只关注比自己次小的节点是为了防止发生“羊群效应”)

线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是最小的节点,获得锁。

  1. 在locks节点下创建临时顺序节点node_n
  2. 判断当前创建的节点是否为locks节点下所有子节点中最小的子节点
  3. 是则获取锁,进行业务处理,否则将节点从小到大排序,监听当前节点上一个节点的删除事件
  4. 事件触发后回到步骤2进行判断,直至拿到锁

问题

什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1: 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

Redis 的回收策略(淘汰策略)

一般的剔除策略有 FIFO 淘汰最早数据、LRU 剔除最近最少使用、和 LFU 剔除最近使用频率最低的数据几种策略。
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的 lru、ttl 以及 random 是三种不同的淘汰策略,再加上一种 no-enviction 永不回收的策略。
使用策略规则:
(1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
(2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

缓存算法

a.LFU(Least Frequently Used)
根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
b.LRU(LeastRecently User)根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
c.FIFO(First inFirst out)
最先进入的数据,最先被淘汰。一个很简单的算法。只要使用队列数据结构即可实现。那么FIFO淘汰算法基于的思想是”最近刚访问的,将来访问的可能性比较大”

redis集群

  • 主从复制
    有且仅有一个为主节点Master。从节点Slave可以有多个
    主节点Master可读、可写.
    从节点Slave只读。(read-only)
    因此,主从模型可以提高读的能力,在一定程度上缓解了写的能力。因为能写仍然只有Master节点一个,可以将读的操作全部移交到从节点上,变相提高了写能力。
  • 哨兵模式
    Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
    监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
    提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
    自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会进行选举,将其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
    在哨兵模式中,仍然只有一个Master节点。当并发写请求较大时,哨兵模式并不能缓解写压力
  • Redis-cluster集群
    (1)由多个Redis服务器组成的分布式网络服务集群;
    (2)集群之中有多个Master主节点,每一个主节点都可读可写;
    (3)节点之间会互相通信,两两相连;
    (4)Redis集群无中心节点。
    集群分片策略
    Redis-cluster分片策略,是用来解决key存储位置的。
    集群将整个数据库分为16384个槽位slot,所有key-value数据都存储在这些slot中的某一个上。一个slot槽位可以存放多个数据,key的槽位计算公式为:slot_number=crc16(key)%16384,其中crc16为16位的循环冗余校验和函数。
    集群中的每个主节点都可以处理0个至16383个槽,当16384个槽都有某个节点在负责处理时,集群进入上线状态,并开始处理客户端发送的数据命令请求。
    Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
    有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。
    集群redirect转向
    由于Redis集群无中心节点,请求会随机发给任意主节点;
    主节点只会处理自己负责槽位的命令请求,其它槽位的命令请求,该主节点会返回客户端一个转向错误;
    客户端根据错误中包含的地址和端口重新向正确的负责的主节点发起命令请求。
    是否使用过 Redis 集群,集群的原理是什么?
    (1)Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为master,继续提供服务。
    (2)Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。
    Redis 的同步机制了解么?
    Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将 rdb 文件全量同步到复制节点,复制节点接受完成后将 rdb 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值