为什么分布式一定要有Redis?
这篇文档是读了一个公众号的文章后修改后复制,以前的学习深度不够,现在还是理解不够,以后再写文档,继续加深。
一:说明
1.说明
考虑到绝大部分写业务的程序员,在实际开发中使用 Redis 的时候,只会 Set Value 和 Get Value 两个操作,对 Redis 整体缺乏一个认知。
这也是我的状况。
2.下面是要说明的问题
为什么使用 Redis
使用 Redis 有什么缺点
单线程的 Redis 为什么这么快
Redis 的数据类型,以及每种数据类型的使用场景
Redis 的过期策略以及内存淘汰机制
Redis 和数据库双写一致性问题
如何应对缓存穿透和缓存雪崩问题
如何解决 Redis 的并发竞争 Key 问题
二:为什么使用Redis
1.原因
主要从两个方面进行考虑:性能与并发
当然,Redis 还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件,如 ZooKpeer 等代替,并不是非要使用 Redis。因此,这个问题主要从性能和并发两个角度去答。
2.性能:
如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。
、
题外话:
"在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。
另外,超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验。"
那么瞬间、刹那、一弹指具体是多少时间呢?
那么,经过周密的计算,一瞬间为 0.36 秒、一刹那有 0.018 秒、一弹指长达 7.2 秒。
3.并发
如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。
这个时候,就需要使用 Redis 做一个缓冲操作,让请求先访问到 Redis,而不是直接访问数据库。
三:Redis的缺点
1.主要有的四个问题
缓存和数据库双写一致性问题
缓存雪崩问题
缓存击穿问题
缓存的并发竞争问题
四:为什么这么快
1.单线程工作模式
这个问题是对 Redis 内部机制的一个考察。根据我的面试经验,很多人都不知道Redis 是单线程工作模型。所以,这个问题还是应该要复习一下的。
2.主要原因
纯内存操作
单线程操作,避免了频繁的上下文切换
采用了非阻塞 I/O 多路复用机制
3.多路复用机制
我参考了部分的比喻。
小曲只雇佣一个快递员。然后呢,客户送来的快递,小曲按送达地点标注好,然后依次放在一个地方。
最后,那个快递员依次的去取快递,一次拿一个,然后开着车去送快递,送好了就回来拿下一个快递。
因此:
只有单个线程(一个快递员),通过跟踪每个 I/O 流的状态(每个快递的送达地点),来管理多个 I/O 流。
4.线程模型
简单来说,就是我们的 redis-client 在操作的时候,会产生具有不同事件类型的 Socket。
在服务端,有一段 I/O 多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
需要说明的是,这个 I/O 多路复用机制,Redis 还提供了 select、epoll、evport、kqueue 等多路复用函数库
五:数据类型,以及使用场景
1.String
这个没啥好说的,最常规的 set/get 操作,Value 可以是 String 也可以是数字。一般做一些复杂的计数功能的缓存。
2.List
使用 List 的数据结构,可以做简单的消息队列的功能。
另外还有一个就是,可以利用 lrange 命令,做基于 Redis 的分页功能,性能极佳,用户体验好。
3.Set
因为 Set 堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用 JVM 自带的 Set 进行去重?
因为我们的系统一般都是集群部署,使用 JVM 自带的 Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
4.Sorted Set
Sorted Set多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。
可以做排行榜应用,取 TOP N 操作。Sorted Set 可以用来做延时任务。最后一个应用就是可以做范围查找。
5.Hash
这里 Value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。
我在做单点登录的时候,就是用这种数据结构存储用户信息,以 CookieId 作为 Key,设置 30 分钟为缓存过期时间,能很好的模拟出类似 Session 的效果。
六:过期策略与内存淘汰机制
1.基本的问题
这个问题相当重要,到底 Redis 有没用到家,这个问题就可以看出来。
比如你 Redis 只能存 5G 数据,可是你写了 10G,那会删 5G 的数据。怎么删的,这个问题思考过么?
还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有思考过原因么?
回答:Redis 采用的是定期删除+惰性删除策略。
2.为什么不使用定时删除
定时删除,用一个定时器来负责监视 Key,过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。
在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 Key,因此没有采用这一策略。
3.定时删除与惰性删除的机制
定期删除,Redis 默认每个 100ms 检查,是否有过期的 Key,有过期 Key 则删除。
需要说明的是,Redis 不是每个 100ms 将所有的 Key 检查一次,而是随机抽取进行检查(如果每隔 100ms,全部 Key 进行检查,Redis 岂不是卡死)。
因此,如果只采用定期删除策略,会导致很多 Key 到时间没有删除。于是,惰性删除派上用场。
也就是说在你获取某个 Key 的时候,Redis 会检查一下,这个 Key 如果设置了过期时间,那么是否过期了?如果过期了此时就会删除。
4.上面机制出现的问题
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除 Key。然后你也没即时去请求 Key,也就是说惰性删除也没生效。这样,Redis的内存会越来越高。那么就应该采用内存淘汰机制。
5.内存淘汰机制
在 redis.conf 中有一行配置:
该配置就是配内存淘汰策略的(什么,你没配过?好好反省一下自己):
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。推荐使用,目前项目在用这种。
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。应该也没人用吧,你不删最少使用 Key,去随机删。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。不推荐。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。依然不推荐。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。不推荐。
PS:如果没有设置 expire 的 Key,不满足先决条件(prerequisites);那么 volatile-lru,volatile-random 和 volatile-ttl 策略的行为,和 noeviction(不删除) 基本上一致。
七:Redis与数据库双写一致性问题
1.做法
一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。
答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。
另外,我们所做的方案从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
回答:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
八:如何应对缓存穿透和缓存雪崩问题
1.缓存穿透
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
缓存穿透解决方案:
利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。
采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。
2.缓存雪崩
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
缓存雪崩解决方案:
给缓存的失效时间,加上一个随机值,避免集体失效。
使用互斥锁,但是该方案吞吐量明显下降了。
双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。
然后细分以下几个小点:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。
九:如何解决 Redis 的并发竞争 Key 问题
1.问题
这个问题大致就是,同时有多个子系统去 Set 一个 Key。这个时候大家思考过要注意什么呢?
2.解决方式
我并不推荐使用 Redis 的事务机制。因为我们的生产环境,基本都是 Redis 集群环境,做了数据分片操作。
你一个事务中有涉及到多个 Key 操作的时候,这多个 Key 不一定都存储在同一个 redis-server 上。因此,Redis 的事务机制,十分鸡肋。
如果对这个 Key 操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可,比较简单。
如果对这个 Key 操作,要求顺序
假设有一个 key1,系统 A 需要将 key1 设置为 valueA,系统 B 需要将 key1 设置为 valueB,系统 C 需要将 key1 设置为 valueC。
期望按照 key1 的 value 值按照 valueA > valueB > valueC 的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。
假设时间戳如下:
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统 B 先抢到锁,将 key1 设置为{valueB 3:05}。接下来系统 A 抢到锁,发现自己的 valueA 的时间戳早于缓存中的时间戳,那就不做 set 操作了,以此类推。
其他方法,比如利用队列,将 set 方法变成串行访问也可以。总之,灵活变通。
为什么分布式一定要有Redis?的更多相关文章
- 为什么分布式一定要有redis?
为什么分布式一定要有redis? 孤独烟 架构师小秘圈 昨天 作者:孤独烟 来自:http://rjzheng.cnblogs.com/ 1.为什么使用redis 分析:博主觉得在项目中使用red ...
- 为什么分布式一定要有redis?(转)
为什么分布式一定要有redis? 程序员小灰 6天前 点击上方“程序员小灰”,选择“置顶公众号” 有趣有内涵的文章第一时间送达! 作者:孤独烟 来自:http://rjzheng.cnblogs.co ...
- 如何用分布式缓存服务实现Redis内存优化
Redis是一种支持Key-Value等多种数据结构的存储系统,其数据特性是“ALL IN MEMORY”,因此优化内存十分重要.在对Redis进行内存优化时,先要掌握Redis内存存储的特性比如字符 ...
- Redis 入门到分布式 (一)Redis初识
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.Redis特性目录 Redis的特性: 速度快 持久化 多种数据结构 支持多种编辑语言 功能丰富 简 ...
- 分布式日志2 用redis的队列写日志
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...
- fourinone分布式缓存研究和Redis分布式缓存研究
最近在写一个天气数据推送的项目,准备用缓存来存储数据.下面分别介绍一下fourinone分布式缓存和Redis分布式缓存,然后对二者进行对比,以供大家参考. 1 fourinone分布式缓存特性 1 ...
- 组件-------(一)redis系列--安装部署redis+实现redis分布式缓存 java+Spring+redis
目的:解决单机session不能共享问题,插入查询数据库时间效率问题,实现分布式缓存. 准备材料:Redis 下载链接 http://pan.baidu.com/s/1dEGTxvV 相关jar包如果 ...
- 分布式锁的实现(redis)
1.单机锁 考虑在并发场景并且存在竞态的状况下,我们就要实现同步机制了,最简单的同步机制就是加锁. 加锁可以帮我们锁住资源,如内存中的变量,或者锁住临界区(线程中的一段代码),使得同一个时刻只有一个线 ...
- 分布式系列十二: Redis高级主题
持久化 Redis 支持持久化, 其持久化数据有两种方式. 两种可以同时使用. 如果同时使用, Reids 在重启时将使用 AOF 方式来还原数据. RDB 按照一定策略定时同步内存的数据到磁盘.文件 ...
- 【分布式缓存系列】Redis实现分布式锁的正确姿势
一.前言 在我们日常工作中,除了Spring和Mybatis外,用到最多无外乎分布式缓存框架——Redis.但是很多工作很多年的朋友对Redis还处于一个最基础的使用和认识.所以我就像把自己对分布式缓 ...
随机推荐
- Day8--------------yum软件包管理
1.url三段式:协议.域名.路径 例如:http://wan.360.cn/game 2.本地yum配置: vim /etc/yum.repos.d/local.repo [local] #固定格式 ...
- layui 子页面向父页面传值
实现功能:单击确定按钮将选中的id传到父页面并关闭当前子页面. 首先在父页面定义一个函数: //分配产品 function ChooseAdidValues(v) { if (v != "& ...
- Java多线程之控制执行顺序
概念: 多线程在并发环境中的正常执行顺序是随机无序的,并不能按照期盼的结果输出. 因为启动一个线程时,线程并不会立即执行,而是等待CPU的资源调度,CPU能调度哪个线程,是通过多种复杂的算法计算而来. ...
- Confluence 6 白名单允许进入和禁用
允许进入 启用 CORS 请求,将会从特定的 URL 来源中允许进入(Allow Incoming).URL 格式必须匹配格式 scheme://host[:port],结尾是没有反斜杠的(:port ...
- Java的课后作业——18.10.18
日期:2018.10.18 星期四 博客期:020 小试验任务: 我就发一下代码好了!!! package test1; import java.util.Scanner; public class ...
- Android源码分析二 硬件抽象层(HAL)
一 什么是HAL HAL 可定义一个标准接口以供硬件供应商实现,这可让 Android 忽略较低级别的驱动程序实现.借助 HAL,您可以顺利实现相关功能,而不会影响或更改更高级别的系统.HAL 实现会 ...
- Python1 简介及安装、基础
Python介绍 Python是面向对象,高级语言,解释,动态和多用途编程语言.Python易于学习,而且功能强大,功能多样的脚本语言使其对应用程序开发具有吸引力. Python的语法和动态类型具有其 ...
- anaconda利用pip安装module
开始_程序 中搜索:anaconda prompt (控制台) 输入pip 出现pip的一些信息,可以忽略 接着输入 pip install 模块名称 例如:pip install alphalens ...
- shiro 单点登录原理 实例
原创 2017年02月08日 17:39:55 4006 Shiro 1.2开始提供了Jasig CAS单点登录的支持,单点登录主要用于多系统集成,即在多个系统中,用户只需要到一个中央服务器登录一次即 ...
- 在WPF中获取DATAGRIDTEMPLATECOLUMN模板定义的内容控件(转载)
原文:http://www.cnblogs.com/eric_ibm/p/3772516.html xaml格式描述: <DataGrid Name="dataGrid" G ...