Redis

Redis是什么

  1. Redis是1个内存数据库,非关系型,基于Key-Value存储,速度快
  2. 支持多种数据类型,String、Hash、List、Set和ZSet
  3. Redis支持主从复制,集群;支持分布式系统下的高可用
  4. Redis提供了多种附加功能,包括事务, 发布-订阅,消息队列和分布式锁

Redis基于事件驱动

Redis是1个事件驱动程序,主要处理2种事件,文件事件和时间事件

  1. 文件事件,Client通过socket连接到Server,对Server进行读写操作,由文件事件负责执行Client的请求并响应
  2. 时间事件,Redis中的一些操作需要定时执行,例如负责管理Redis资源的serverCron()默认100ms执行1次
    (1)更新Redis内存使用情况
    (2)检查Client是否连接超时,将超时的连接释放掉
    (3)删除过期key
    (4)将aof_buf缓冲区中的数据写入到AOF文件

数据结构

String,最常规的get/set
Hash,
List,可以从左或从右插入,底层是1个双向链表
Set,可用于分布式系统中的全局去重,JVM只能做单个应用的去重
ZSet,排序Set,有1个权重参数score,Set中元素以score排序。可用于排行榜,取Top N应用

过期策略和内存淘汰进制(结合做的分布式session说)

持久化策略

Redis是1个内存数据库,如果Redis挂掉或停机,Redis中的数据会丢失,因此Redis提供了两种持久化策略,RDB和AOF,各有利弊。Redis官网推荐同时使用RDB和AOF

  1. RDB,将某1时间点的数据库快照保存到1个RDB文件中
    RDB会将数据写入到1个临时文件中,写入操作完成后,用临时文件替换上次持久化的文件

Redis使用SAVE/BGSAVE命令,创建RDB文件

SAVE会阻塞主线程,SAVE期间,Client的一切读写命令都会被阻塞

BGSAVE会创建1个子线程执行RDB文件的创建和写入工作,不会阻塞主线程,BGSAVE期间,Redis能够正常的响应Client的读写请求

Redis配置文件中,默认3种情况下会执行RDB:
(1)900s内,key发生1次变化 save 900 1
(2)300s内,key发生10次变化 save 300 10
(3)1s内,key发生10000次变化 save 1 10000

  1. AOF,保存Redis所有执行过的写命令
    当Redis故障重启时,会将所有的写命令重新执行,来恢复数据库状态。 AOF默认是关闭的
    (1)配置文件redis.conf中, appendonly设置为yes,则打开AOF
    (2)写命令会先被放入aof_buf缓冲区,默认1s进行1次fsync,将aof_buf中存放的写命令写入AOF文件
    (3)因为采取追加方式,所以AOF文件会越来越多,当超过设定的阈值时,会采取AOF重写,将多条写命令合并为1条,来减小AOF文件大小

  2. 优缺点
    (1)RDB可能导致数据不完整,2次RDB之间,如果Redis故障,上次RDB之后,Redis接收的所有写命令会丢失
    (2)相同的数据规模,AOF文件比RDB文件大,且AOF恢复时间慢
    (3)Redis规定,如果开启AOF,会优先使用AOF恢复数据库状态;未开启AOF时,才使用RDB

主从模式

  1. 目的
    提高性能,主服务器用来执行写命令,从服务器设置为只读;主从同步,异步进行,同步不会影响主的处理性能

  2. 做法
    1主2从3Sentinel
    (1)配置文件redis.conf中,使用slaveof <master_ip> <master_port>的方式让从服务器来复制主服务器
    (2)配置文件默认,从服务器是只读的,slave-only-read值为yes
    (3)配置文件默认,从服务器每隔10s向主发送心跳
    (4)Sentinel是Redis哨兵,用来监视Redis主服务器和从服务器,当被监视的主服务器处于下线状态时,哨兵会从主服务器下的从服务器中挑选1个,让其成为主服务器

  3. 旧版复制SYNC
    分为同步和命令传播

    同步,发生在初次复制(从向主发送slaveof命令),将从的数据库状态,更新到主所处的数据库状态
    (1)从向主发送SYNC
    (2)主执行BGSAVE命令,使用子线程创建RDB文件,并使用缓冲区记录执行BGSAVE期间,主所执行的所有写命令
    (3)创建完RDB文件,主会发生给从,从接收并载入,从将自己的数据库状态更新到主执行BGSAVE之前的状态
    (4)主将缓冲区中存放的写命令发送给从,从执行,将自己的状态更新到与主一致

    命令传播,发生在同步后,当Client向主写入数据,主从状态不一致,为了再次让主从状态一致,主会将造成不一致的写命令发送给从,让从执行,使主从状态再次一致

    旧版复制的缺陷
    对于已经完成同步,处于命令传播阶段的主从服务器。当主从连接断开后,重新建立连接时,会像初次复制一样,使用BGSAVE生成RDB文件,生成的是全量的数据
    但是断线重连后,造成主从不一致的数据,是断线期间Client写入主的数据,再次执行初次同步,非常浪费主从服务器的资源。主执行BGSAVE,浪费主的CPU、内存和磁盘I/O;从载入RDB文件,会阻塞Client请求;传输RDB文件,还会浪费网络资源

  4. 新版复制PSYNC
    分为完全重同步和部分重同步,完全重同步就是旧版复制SYNC的初次复制
    部分重同步,用于断线后重连,如果条件允许,主会将断线期间发生的写命令发给从,让从执行,使主从状态达到一致。从而避免了旧版复制SYNC断线重连,重新生成RDB,浪费主从服务器资源和网络资源的情况
    (1)复制偏移量offset
    主从都维护1个offset,主发送n个字节,offset+n,从接收n个字节,offset+n。通过比较主从offset,可知主从是否一致
    (2)复制积压缓冲区
    主维护1个固定长度为1MB的先进先出队列(FIFO)
    命令传播时,主不但向从发送写命令,还将写命令放入复制积压缓冲区,复制积压缓冲区的这些写命令的每个字节都有复制偏移量offset标记。但是复制积压缓冲区这个FIFO是有大小限制的,默认1MB,断线重连后,从通过PSYNC命令向主发送自己的offset,主会根据这个offset来决定执行完全重同步还是部分重同步
    (1)若offset之后的数据在复制积压缓冲区,执行部分重同步
    (2)否则,执行完全重同步

    (3)Server ID
    不论主从,Redis服务器都有Server ID,在Redis启动时自动生成,是1个40位的随机16进制String
    初次复制时,主将ID发送给从,从保存;断线重连时,从发送保存的主ID,如果ID相同,尝试执行部分重同步;否则在断线重连期间,Redis主可能发生了变化,执行完全重同步

过期key淘汰策略和内存淘汰策略

Redis可以为key设置生存时间,当key超时,会自动删除key。Redis有3种key淘汰策略

  1. 定时删除
    主动删除,设置key生存时间的同时,设置1个定时器Timer,定时器会在key过期时,自动删除key
    优点:对内存友好,保证key在过期时立刻被删除,节省内存空间
    缺点:对CPU不友好,创建定时器Timer浪费CPU资源
  2. 惰性删除
    被动删除,放任key不管,在每次从Redis中取值时,才会检查key是否过期,如果过期会删除key
    优点:对CPU友好,在获取key时才检查key是否过期
    缺点:对内存不友好,只有不去获取过期key,过期key会一直存放在内存中,造成内存泄漏
  3. 定期删除
    主动删除,每隔一段时间,扫描1次,检查key是否过期,对过期key进行删除
    定时删除会浪费CPU资源,惰性删除会浪费内存资源,定期删除是定时删除和惰性删除的折中

Redis采用惰性删除和定期删除结合的方式,来淘汰过期key。Redis的serverCron()每隔100ms,随机抽查部分key,检查是否存在过期key,有则删除;另外,当获取key的时候,会检查key是否过期,如果过期则删除

惰性删除结合定期删除,无法完全的解决问题,如果定期删除没有删除过期key,并且客户端也没有获取过该key,那么Redis的内存会越来越高,此时会执行Redis的内存淘汰策略,Redis一共有6种内存淘汰策略:

  1. NoEviction, 内存不足时,拒绝新的写入数据
  2. AllKeys-lru, 移除最近最少使用的key
  3. AllKeys-random,随机移除key
  4. Volatile-lru,在设置过期时间的key中,移除最近最少使用的key
  5. Volatile-random,在设置过期时间的key中,随机移除key
  6. Volatile-ttl,在设置过期时间的key中,优先移除离过期时间最近的key

如果没有设置key的过期时间, 不满足先决条件, volatile-*和noeviction(不删除)一致

热点数据缓存

热点数据是经常被查询,很少被修改的数据;查询时,因为Redis基于内存,速度非常快,我们会先查询缓存Redis,如果缓存中有,返回。缓存中没有,需要查询MySQL,并将结果放入到缓存,供下次查询使用

[缓存了哪些热点数据]

MySQL和Redis双写一致

对于数据的操作,就是对数据的CRUD,其中查属于读,增删改属于写

  1. 读操作,就是查询时先查询Redis,如果Redis中存在数据,直接返回,不存在则查询MySQL并将结果存放到Redis。所以读操作不涉及双写不一致的情况

  2. 新增时,只需要将数据插入到MySQL,不需要管Redis,因为如果有线程在Redis中查询不到该数据,会查询MySQL,并将结果放入Redis供下次查询使用
    当对数据进行更新和删除时,会出现MySQL和Redis双写不一致的情况。不管是先删缓存,再写MySQL;还是先写MySQL,再删缓存,都会出现不一致。Redis只能保证最终一致性,而不能保证强一致,所以要求强一致的数据不能放入缓存

    (1)先写MySQL,再删缓存
    如果写完MySQL,线程崩溃,那么只更新了MySQL的值,未更新缓存的值。所有查询操作使用的都是原来的脏数据,MySQL和Redis数据不一致
    (2)先删缓存,再写MySQL
    线程A删除缓存之后,写MySQL之前,线程B查询数据,此时Redis中的数据被删除,重新从MySQL中读取,因为线程A还未写MySQL,所以查询的还是原数据,线程B将原数据再次放入缓存。导致MySQL和Redis数据不一致

对于MySQL和Redis双写不一致这种情况,解决方案有很多,我选择了最简单的"双删+超时"的方式

在写MySQL操作之前和之后,都对Redis进行删除。先删除Redis,再写MySQL,为了避免了在写MySQL期间,有线程读取到MySQL中的旧值,并将其放入缓存,在写入MySQL成功后,再删除缓存。在JAVA代码中,调用写MySQL的函数,到真正的在数据库中将数据修改,需要一段时间,因此调用写MySQL函数之后,让线程等待一段时间,待写入MySQL成功后,再2nd删除缓存

双删+超时可以满足不部分需求,如果追求完美,它还有2个小的缺点
(1)在调用完写MySQL方法之后,但是操作数据库未生效之前,可能出现脏数据,但是这是非常小概率出现的
(2)在删除Redis缓存中数据时,如果删除失败,如何解决
需要提供1个重试机制,保证Redis中数据肯定被删除。可以将需要删除的key放入到消息队列中,自己生产数据,然后自己消费,保证一定删除key

 另一种比较实用的做法是,使用消息队列,订阅MySQL的binlog。这方面没有进行深入研究

缓存穿透和缓存雪崩

穿透: 当查询1个肯定不存在的数据时,因为Redis中没有,所以会去查询MySQL,又因为查询MySQL无结果,所以不会放入缓存。导致对不存在数据的每次请求,都会查询MySQL。这种情况称为缓存穿透

雪崩:多线程条件下,Redis在某个时间点,大量数据集体失效。产生大量的缓存穿透,所有的查询都落在MySQL上,造成MySQL负载过重而崩溃

  1. 缓存穿透
    对于缓存穿透,有2种解决方式
    (1)暴力方法,查询的数据肯定不存在,查询Redis和MySQL都不会得到结果。即使查询MySQL得不到结果,也会将空结果放入到Redis,只不过要设置1个生存时间ttl,让它在ttl到期时被删除。这种做法治标不治本,不过在一定程度上,降低了查询MySQL的次数

(2)使用布隆过滤器

  1. 需要k个哈希函数,k个哈希函数可以将key值散列成k个整数
  2. 还需要1个位数组,初始化时,每位都是0
  3. 当加入key时,使用k个哈希函数得到k个整数,并将数组中对应位置改为1
  4. 判断1个key是否在集合中时,使用k个哈希函数对key进行散列,得到k个整数,如果所有的位置都为1,认为该key在集合中

使用Google Guava类库下的BloomFilter实现,我们只需要指定向BloomFilter放入的值的个数,它会为我们自动生成哈希函数,和位数组。做了1个测试,向BloomFilter存入100万个int型数字,底层会创建1个700多万位的位数组,1个int占4字节,32位,正常存储100万个int需要3200万位,而BloomFilter只使用了1/5

  1. 初始化时,将所有数据库中的值都放入BloomFilter
  2. 查询时,会先使用BloomFilter检查查询的key是否存在,如果存在才会查询DB,否则不允许查询
  3. 新增数据时,也会将key加入到BloomFilter
  1. 缓存雪崩
    对于雪崩,采用Zookeeper实现了1个分布式锁,控制同时访问线程的个数,避免过多的线程对MySQL进行操作,导致MySQL崩溃

在JVM中,可以使用Semaphore来控制同时访问共享资源的线程个数。线程需要获取permit,才能操作共享资源,获取不到的线程阻塞,直到获取到permit才能执行

在分布式系统中,无法使用Semaphore来控制同时访问的线程个数,因为应用程序单独部署,运行在不同的进程上,无法使用JVM做线程同步。Apache Curator提供了分布式系统下的线程辅助类,使用的是InterProcessSemaphoreV2,它是1个分布式信号量,类似于JVM的Semaphore的permit,分布式系统下的进程需要获取到lease租约,才能运行,否则一直等待

InterProcessSemaphoreV2使用acquire()和release()来获取和释放租约lease
获取时,在zk上创建1个临时有序节点,如果子节点个数<lease个数,则获取成功;否则,阻塞等待
释放时,进程会将自己创建的临时节点删除,导致子节点个数发生变化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • 从这篇文章开始,将依次介绍Redis高可用相关的知识——持久化、复制(及读写分离)、哨兵、以及集群。 本文将先说明...
    不变甄心阅读 693评论 0 4
  • 前言 在上一篇文章中,介绍了Redis内存模型,从这篇文章开始,将依次介绍Redis高可用相关的知识——持久化、复...
    Java架构阅读 2,314评论 3 21
  • 企业级redis集群架构的特点 海量数据 高并发 高可用 要达到高可用,持久化是不可减少的,持久化主要是做灾难恢复...
    lucode阅读 2,206评论 0 7
  • 超强、超详细Redis入门教程 转载2017年03月04日 16:20:02 16916 转载自: http://...
    邵云涛阅读 17,443评论 3 313
  • “脸,我们无法自主(当然现在也可适度的自主,毕竟现代医学如此高明),但人到了四十岁以后,一个美人的脸和一个丑人的脸...
    春天的野林子阅读 235评论 0 2