共计 3533 个字符,预计需要花费 9 分钟才能阅读完成。
前言
对于 Web 来说,用户量和访问量增肯定水平上推动我的项目技术和架构的更迭和提高。可能会有以下的一些情况:
- 页面并发量和访问量并不多,MySQL
足以撑持
本人逻辑业务的倒退。那么其实能够不加缓存。最多对动态页面进行缓存即可。 - 页面的并发量显著增多,数据库有些压力,并且有些数据更新频率较低
重复被查问
或者查问速度较慢
。那么就能够思考应用缓存技术优化。对高命中的对象存到 key-value 模式的 Redis 中,那么,如果数据被命中,那么能够不通过效率很低的 db。从高效的 redis 中查找到数据。 - 当然,可能还会遇到其余问题,你还通过动态页面缓存页面、cdn 减速、甚至负载平衡这些办法进步零碎并发量。这里就不做介绍。
缓存思维无处不在
咱们从一个算法问题开始理解缓存的意义。
问题 1:
- 输出一个数 n(n<20), 求
n!
;
剖析 1 :
- 单单思考算法,不思考数值越界问题。当然咱们晓得
n!=n * (n-1) * (n-2) * ... * 1= n * (n-1)!
; 那么咱们能够用一个递归函数解决问题。
static long jiecheng(int n) {if(n==1||n==0)return 1;
else {return n*jiecheng(n-1);
}
}
复制代码
这样每输出求一次须要执行 n
次。问题 2:
- 输出 t 组数据(可能成千盈百),每组一个 xi(xi<20), 求
xi!
;
剖析 2 :
- 如果应用
递归
, 输出 t 组数据,每次输出为 xi,那么每次都要执行次数为: 当每次输出的 Xi 过大或者 t 过大都会造成不小的累赘!工夫复杂度为O(n2) - 那么是否换个思维的。没错、是
打表
。打表罕用于 ACM 算法中,罕用于解决多组输入输出、图论搜寻后果、门路贮存问题。那么,对于这个求阶乘。咱们只须要申请一个数组,依照编号从前往后将在需要的数存到数组中,前面再获得时候间接输入数组值就能够,思维很明确吧:
import java.util.Scanner;
public class test {public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
int t=sc.nextInt();
long jiecheng[]=new long[21];
jiecheng[0]=1;
for(int i=1;i<21;i++)
{jiecheng[i]=jiecheng[i-1]*i;
}
for(int i=0;i<t;i++) {int x=sc.nextInt();
System.out.println(jiecheng[x]);
}
}
}
复制代码
- 工夫复杂度才 O(n)。这里的思维就和
缓存
思维差不多。先将数据在 jiecheng[21]数组中贮存。执行一次计算。当前面持续拜访的时候就相当于当问动态数组值。每次都为 O(1 的操作)。
缓存的利用场景
缓存实用于高并发的场景,晋升服务容量。次要是将从 常常被拜访的数据
或者查问 老本较高
从慢的介质中存到比拟快的介质中,比方从 硬盘
—> 内存
。咱们晓得大多数关系数据库是 基于硬盘读写
的,其效率和资源无限,而 redis 是基于内存的,其读写速度差异差异很大。当并发过高关系数据库性能达到瓶颈时候,就能够策略性将常拜访数据放到 Redis 进步零碎吞吐和并发量。
对于罕用网站和场景,关系数据库次要可能慢在两个中央:
- 读写 IO 性能较差
- 一个数据可能通过较大量计算失去
所以应用缓存可能缩小磁盘 IO 次数和关系数据库的计算次数。读取上速度快也从两个方面体现:
- 基于内存,读写较快
- 应用哈希算法间接定位后果不须要计算
所以对于像样的,有点规模的网站,缓存是很 necessary
的,而 Redis 无疑是最好的抉择之一。
须要留神的问题
缓存使用不当会带来很多问题。所以须要对一些细节进行认真考量和设计。当然最难得数据一致性在上面独自剖析。
是否用缓存
我的项目不能为了用缓存而用缓存,缓存并肯定适宜所有场景,如果对 数据一致性要求极高 ,又或者 数据频繁更改而查问并不多,又或者基本没并发量的、查问简略的不肯定须要缓存,还可能浪费资源使得我的项目变得臃肿难保护,并且应用 redis 缓存多多少少可能会遇到数据一致性问题须要思考。
缓存正当设计
在设计缓存的时候,很可能会遇到多表查问,如果遇到多表查问缓存的键值对就须要正当思考,是拆分还是合在一起?当然如果组合品种多但常呈现的不多也能够间接缓存,具体的设计要依据我的项目业务需要来看,并没有一个十分相对的规范。
过期策略抉择
- 缓存装的是绝对热点和罕用的数据,Redis 资源也是无限,须要抉择一个正当的策略让缓存过期删除。咱们学过
操作系统
也晓得在计算机的缓存实现中有先进先出的算法(FIFO);最近起码应用算法(LRU);最佳淘汰算法(OPT);起码拜访页面算法(LFR)等磁盘调度算法。设计 Redis 缓存时候也能够借鉴。依据工夫来的 FIFO 是最好实现的。且 Redis 在全局 key
反对过期策略。 - 并且过期工夫也要依据零碎状况正当设置,如果硬件好点以后能够略微久一点,然而过期工夫过久或者过短可能都不太好,过短可能缓存命中率不高,而过久很可能造成很多冷门数据存储在 Redis 中不开释。
数据一致性问题★
下面其实提到数据一致性问题。如果对一致性要求极高那么不倡议应用缓存。上面略微梳理一下缓存的数据。在 Redis 缓存中常常会遇到数据一致性问题。对于一个缓存,上面列举几种状况:
读
read
:从 Redis 中读取,如果 Redis 中没有,那么就从 MySQL 中获取更新 Redis 缓存。上面流程图形容惯例场景,没啥争议:
写 1:先更新数据库,再更新缓存(一般低并发)
更新数据库信息,再更新 Redis 缓存。这是惯例做法,缓存基于数据库,取自数据库。
然而其中可能遇到一些问题,例如上述如果更新缓存失败 (宕机等其余情况),将会使得数据库和 Redis 数据不统一。 造成 DB 新数据,缓存旧数据。
写 2:先删除缓存,再写入数据库(低并发优化)
解决的问题
这种状况可能无效防止 写 1 中避免写入 Redis 失败的问题。将缓存删除进行更新。现实是让下次访问 Redis 为空去 MySQL 获得最新值到缓存中。然而这种状况仅限于低并发的场景中而不实用高并发场景。
存在的问题
写 2 尽管可能 看似写入 Redis 异样的问题
。看似较为好的解决方案然而在高并发的计划中其实还是有问题的。咱们在 写 1 探讨过如果更新库胜利,缓存更新失败会导致脏数据。咱们现实是删除缓存让 下一个线程
拜访适宜更新缓存。问题是:如果这 下一个线程来的太早、太巧 了呢?
因为多线程你也不晓得谁先谁后,谁快谁慢。如上图所示状况,将会呈现 Redis 缓存数据和 MySQL 不统一。当然你能够对 key 进行 上锁
。然而锁这种重量级的货色对并发性能影响太大,能不必锁就别用!上述情况就高并发下仍然会造成 缓存是旧数据,DB 是新数据。并且如果缓存没有过期这个问题会始终存在。
写 3:延时双删策略
这个就是延时双删策略,能过缓解在 写 2 中在更新 MySQL 过程中有读的线程进入造成 Redis 缓存与 MySQL 数据不统一。办法就是 删除缓存 -> 更新缓存 -> 延时 (几百 ms)(可异步) 再次删除缓存 。即便在更新缓存途中产生 写 2 的问题。造成数据不统一,然而延时 (具体实间依据业务来,个别几百 ms) 再次删除也能很快的解决不统一。
然而就写的计划其实还是有破绽的,比方第二次删除谬误、多写多读高并发状况下对 MySQL 拜访的压力等等。当然你能够抉择用 MQ 等音讯队列异步解决。其实理论的解决很难顾及到十拿九稳,所以不少大佬在设计这一环节可能会因为一些纰漏会被喷。作为菜菜的笔者在这里就更不献丑了,各位大佬欢送奉献你们的计划。
写 4:间接操作缓存,定期写入 sql(适宜高并发)
当有 一堆并发 (写)
扔过去的后,后面几个计划即便应用音讯队列异步通信但也很难给用户一个舒服的体验。并且对大规模操作 sql 对系统也会造成不小的压力。所以还有一种计划就是间接操作缓存,将缓存定期写入 sql。因为 Redis 这种非关系数据库又基于内存操作 KV 相比传统关系型要快很多。
下面实用于高并发状况下业务设计,这个时候以 Redis 数据为主,MySQL 数据为辅助。定期插入 (如同数据备份库一样)。当然,这种高并发往往会因为业务对 读
、写
的程序等等可能有不同要求,可能还要借助 音讯队列
以及 锁
实现针对业务上对数据和程序可能会因为高并发、多线程带来的不确定性和不稳定性,进步业务可靠性。
总之,越是 高并发
、越是对 数据一致性要求高
的计划在数据一致性的设计方案须要 思考和顾及
的越简单、越多
。上述也是笔者针对 Redis 数据一致性问题的学习和自我发散(胡扯) 学习,欢送进群 973961276 一起来吹水聊技术,如果有解释了解不合理或者还请各位大佬斧正!