亿级流量多级缓存

数据库设计

数据库设计

数据库范式。等级越高、越严格。

第一范式:不可拆分

反例:尽量不要用json,一般是可以单独做成一张表的。先看设计是否合理,再看合适的存储组件

第二范式:必须有主键,非主属性,必须依赖于主属性

反例:好友关系列表。主键:关注人id,被关注人Id,关注人头像

第三范式:传递依赖

反例:员工表:员工id。部门id,部门名称。部门简介

反范式:提供效率,做一些不符合范式的表,来减少查询关联表的次数,提高效率

如果你的系统是重业务的系统,对性能和并发的要求不高,最好用第三方范式。

如果系统对某些查询较频繁,可以考虑冗余,反范式。

巨量数据的优化

数据量增加—》响应时间的增加。

常用方法:建立索引

表分区、分库分表、读写分离

表分区

表。每个表对应磁盘上的一个文件。ibd。data目录下

分流:对一个ibd文件的请求,分发到对多个ibd文件的请求。

将数据多个物理表进行存储逻辑上,还是一张表

好处:

  1. 当查询条件 可 以判定 某个数据位于哪个分区,那么直接在分区中查询,不用扫描整个表。
  2. 业务代码不用改。
  3. 分区进行单独管。备份,恢复。

分区是可以通过navicat实现的

range。0-10,11-20。
partition by range
list: partition by list xxxxxxxxxxx values in (1,2)
hash:partion by hash

分区注意事项

尽量保证常用查询落到一个分区中

分区条件放到where上

坏处:如果出现跨区查询,适得其反

分库分表

目的:

1.业务拆分

2.适应不同的场景,读多写少,读少写多

3.数据隔离。核心业务和非核心业务拆分

怎么拆

水平分的话一般就是多个字段一般不能拆开,需要频繁一起查询

婚恋网站查询最多的就是年龄和性别,就可以垂直分

以系统能不能扛得住为主,先用简单方法,解决不了再复杂

分库分表的方法

范围分区:选取有序的列,按照一定的范围去划分

选取有序的列,按照一定的范围去划分。

就是分段大小的选取

分段小:导致子表数量多,增加维护难度

分段大:有可能单表依然存在性能问题

依据就是:分表后,表的各方面性能,能否满足系统要求

优点:可以平滑的扩充新表,只需增加子表的数据量,原有的数据不用动。

缺点:数据分布不均匀,指的是数据量的不均匀,因为它选取的有序的列,按照固定的范围划分。

hash分区:选取列,进行hash运算。%10=

优点:相反

缺点:相反

分库分表的问题

分布式ID问题

拆分维度问题

用户id查的快,订单id查的快,所以分的时候不要把用户id拿出去,具体看哪个字段用的多

创建一个索引表、映射表。订单id查得慢,就可以用订单id和用户id做一张关联表,这样就可以快了(过渡方案

join问题

商品、订单、用户原来在一张表里

分库后通过sql的join就能查询,购买了化妆品的用户中的女用户。如果这几张表分布在不同的库里就尴尬了

单库join没问题,多库join失败

解决方式

  1. 在代码层面做join。
  2. es。canal->es。

目前:数据库越简单越好。 禁止3张表 做关联,禁用存储过程,禁用触发器。

事务

分库了,分布式事务的解决方案,XA的jar包可以查两个库做2pc的提交,但是不好用。用shardingsphere更好

成本问题:

非必要不分库,不要过度设计

XA,jar

哪怕1个亿数据,只要查询速度ok,也不用分库分表

读写分离

分流:将请求分流。防止阻塞。

数据库锁(分)对数据库并发有影响

X锁,写锁,只能我一人,读、写干不了

S锁:读锁

场景:读多写少,适合读写分离。读少写多反而用途不大

一主(写)多从(读)

只要有select分配到读库里读

路由问题

select S 锁 从库 ; create update delete add. X 锁 写库。 mybatis插件。

主从复制问题

  1. 写主库,如何高效同步到从库

从库用sql命令写,会失去读写分离的意义,因为回到了从前

主库:binlog,传给从库,写入从库的relayLog,解析relayLog。重现数据

  1. 会有时间差

(一)

编造:注册(主库),登录(查从库),此时提示用户不存在

注册后第一次查询指到主库去

如何看出是第一次注册:注册完,写个redis,key用户id1.删掉key

(二)

或者先去从库查,没有再去主库查

主业务用主库,非主业务用从库

读写分离、分库分表实现

读写分离

将不同的sql 分发到不同的机器上。

select-----某台机器

分库分表

where id = 1某台机器上

拦截、判断、分发

代码封装:dao抽象一层。夹层。tddl(Taobao Distributed Data Layer) 头都大了。

Shardingjdbc。需要改代码。

中间件封装:mycat。

缓存设计

缓存设计

导流:将原本复杂的操作请求(sql 大堆),引导到简单的请求上。前人栽树后人乘凉。

空间换时间的做法

什么数据用于做缓存?

读多写少

redis, memcached,localcache guava,客户端缓存,

user_info_xxxx : 姓名,年龄,xxx。getKey 内存操作

select * from user where id = xxx。 硬盘IO

缓存收益

要看成本收益,公司的人学的也需要成本

浏览器的缓存也是kv值。用kv值是最简单的

缓存命中的总时间:计算key的时间、查询key的时间、转换值的时间

一般不会拿Redis去做持久化存储的

耗时特别长的查询(复杂sql),读多写少才会划算,不然不值得做缓存

缓存键的设计

单向函数得当就会将碰撞的几率降低(哈希)

给一个输入很容易能算出结果,但是给结果很难算出输入(单向函数)

输入敏感(输入改一点,值就变了)

如何减少查询key的时间

主要取决于物理位置(放在内存还是硬盘)

转换值的时间

先序列化处理,再反序列化,就会比较耗时

或者是纯对象,比较高效,但是会有数据污染,读取缓存中的数据,都是拿的同一个引用,A对这个对象操作,B读这个时候数据就被污染

实际中:前缀_业务关键信息_后缀

缓存的更新机制

涉及到删除的操作,用查和更新两个操作去想异常情况

只有更新缓存的操作,就只用去想服务器或者网络延迟这方面的原因

被动更新

调用方:

暂存方(缓存):

数据提供方:根据数据提供方的变化,缓存要进行更新。也就是双写一致

更新:要么我去取,要么你给我

被动:有效期到后,再次写入。

数据提供方可能已经被修改了

  1. 客户端数据,缓存中没有,从提供方获取,写入缓存(有一个过期时间)
  2. 在t内,所有的查询,都由缓存提供,所有的写直接写数据库
  3. 当缓存时间到点了,缓存时间才变没有,回到第一步,继续更新缓存

适合对实时性要求不高的场景,比如:商品关注的人数

主动更新

主动:被其他操作直接填充。

数据库:更新数据库

缓存:更新缓存,删除缓存

主动更新的四种方式

更新数据库,更新缓存

从线程安全的角度来考虑

数据不一致的风险比较高,所以一般不采用

数据库先改成1,再改成2

缓存这边,2先到了,先更新缓存为2,后面更新缓存为1

更新数据库,删除缓存

经常采用的方式

删除缓存是为了节省计算时间

业务要求:修改数据库,然后经过大量的计算,才能得出缓存的值。浪费了性能。如果缓存还没用,更浪费。

cache-aside模式

前提:缓存无数据,数据库无数据

A:查询,B:更新

异常流程(读的速度慢了,存在的情况很低)

就是说这个方案也有问题?这次是读的速度慢了?

读比写慢 概率很低,极低。(指的是A去查的速度,因为A写缓存的操作也算是读里面的,比B写的速度还慢)

A查缓存无数据,去读数据库是旧值

B更新数据库,新值

B删除缓存

A将旧值写入缓存(缓存没有,A要往里面写)

如果非要解决,可以再采用延时双删

删除缓存,更新数据库

第一次删除缓存,并没有来得及更新数据库,就进行了查缓存操作,此时没有缓存,就去查数据库,缓存更新为旧值

一般不采用,因为一般读比写快

通过延时双删来解决,过一段时间再把缓存删一遍,程序停止一会再把缓存删掉

会造成吞吐量下降,改成异步的,可以不用等前一个线程睡眠结束

延时双删其中有操作失败了怎么办

删除缓存。失败了不继续了就行

更新数据库。事务回滚

第二次删除缓存。重试删除。当你前面的操作无法回滚时,为了保证后续数据的一致性,(最便宜的做法)只能硬着头发往前走。回去代价太大了(此时事务已经结束,几乎已经无法利用事务回滚)。所以就是重试,用for循环去重试,如果用到重试的地方要借用中间件(消息队列重发消息)

或者用canal解决。binlog开启,会把操作的东西写在binlog日志里,用canal去订阅binlog日志,如果删除成功,没事,如果删除失败就放到消息队列里面,将二次删除key和业务代码解耦

**延时多久?**评估业务时间,读的基础上再过个200ms左右,也不能完全解决

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

更新缓存,更新数据库

更新了缓存,更新了数据库,但是数据库回滚了

或者是程序挂了,数据库没更新上去

数据不一致的风险比较高,所以一般不采用

先操作完缓存,再操作数据库。当用户来查的时候,缓存里总有数据

一般中小方案也不采用以下两种

一般都是必须要用缓存来作为主存储了

Read/Write Through

缓存改成功了,数据库写失败了。那么读就是读到库里没有的数据了

Write Behind

先往缓存写,缓存写成功直接返回

缓存和数据库同步的操作通过异步的方式进行。不用担心用户读到的数据是错的

数据库的操作非常耗时,当成异步也可以降低写操作的时间,提高系统吞吐量

缓存清理

如果所有的数据都放缓存,命中率100%

缓存比较贵,需要一套机制,来让有限的缓存空间发挥最大的作用

如何判断一个数据在未来被访问的次数

当清理一个数据的时候,它一直被访问,就认为它马上的未来也会被访问

最近刚被写入,也很可能被访问

清理机制

时效性清理机制

有一个生存ttl,当到了过期时间的时候就会被清理掉,为其他的数据腾空空间

set k v ex 10 s

set cookie 过期时间

设置过期时间10s怎么让他到了10s之后就过期呢,利用定时任务轮询,每一段时间查看一下

读一次,记录一次,时间。阈值。

自动清理机制:cookie和redis都有一个过期时间(本质就是轮询)

数目阈值式清理机制

判断缓存中的数量达到一定值,对缓存进行清理

阈值:根据自己的业务来定

采取什么策略去清理

Iru:规律:最近最少使用算法。LinkedHashMap 套。fifo,lru

经常访问的放前面,不常访问的放后面

map:存键值对

一个LinkedListHashMap既可以实现LRU也可以实现FIFO顺序:插入顺序(fifo),访问顺序(lru)

random:随机

fifo:先进先出

利用一个定长的队列来存储就可以了

实际中,在服务器上,缓存可以放到Map中,只有一台服务器上可以用。多个服务器共用需要通过消息中间件。Redis是通过一些配置不是这样实现的。

软引用清理

什么情况下会适时的释放空间,gc。

识别出要清理的缓存,然后清除。

gc root引用判断。

强:哪怕自己oom,不清理(不用)

软:当空间不足的时候,会被回收

弱:

虚:更容易被回收

用软引用,空间不足时进行缓存清理

把值放到SoftReference包装中

缓存清理总结

时效性清理+数目阈值

一个个拆开来回答,时效性清理能保证什么,数目阈值能保证什么

防止短时间内密集的查询导致缓存空间的急剧增大

lru+软引用:保证热数据,最大限度地提高缓存命中率

不建议仅仅使用软引用,缓存的存活与否就失去了控制,全交给了GC

目的:提高缓存命中率,节省空间,提示性能

缓存清理机制总结

缓存风险

不是用的组件越多越好

每增加一个环节就多一分风险,增加缓存也不例外

做架构能不用就不用

缓存穿透

缓存中没有,数据库也没有。

方案:在第一次调用的时候,数据提供方返回一个空值,将空值放到缓存中。

缓存雪崩

大量缓存突然失效,导致大量的请求,倾泻到数据库上,引起数据库压力骤增。

时效式清理:批量缓存,统一时间到期。缓存ttl=(固定时间,结合业务)+随机时间。

软引用清理:某个时间点,空间突然紧张,常用的缓存用强引用,不常用的用软引用。

缓存击穿

高频率的缓存,突然失效,大量请求倾泻到数据库上。

lru:

read write through or write behind.更新机制:无所谓。数据永远留在缓存当中。

缓存预热

read write through or write behind

预热:高频访问的数据,提前准备

打车的时候用的最高频率的功能就是预估价格

计价规则,提前加载到缓存中

系统启动前,加载缓存,不让缓存统一时间过期

电商系统:热门商品,提前加入缓存。网约车中,计价规则提前加入缓存

热门数据,加到缓存

缓存风险总结

遇到风险,分析原因,解决之。

原因:更新机制,清理机制。

缓存的位置

CPU一般是三级缓存L1,L2,L3

级别越小越靠近CPU,容量最小,速度最快

如何避免cpu浪费时间

多线程,排队,异步

cpu和其他协同工作慢了,那就只能从以下角度出发

  1. 减少我的等待时间---------缓存
  2. 我尽量多做事情--------多线程

目的:降本增效。

级联系统缓存位置

级联:一级一级的

要想系统性能好,缓存一定要趁早

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

缓存一般是系统内部做,不会专门做一个缓存系统中间做,要么在调用方,要么在被调用方

客户端缓存位置

目的:降低用户响应时间

打车系统:订单不是访问量最大的,有些人预估价格可能并不下单

秒杀系统:并不是下单请求量最高,而是商品详情页

降级:有些时候就不存储缓存了,没必要,过年的时候打车去北京西站滴滴崩了。

有些时候就不做个人详情的了,就给静态的大家都用的

保证优先级高的正常运行

百度也把凤巢让出来了,将自己的一部分服务器给抢红包功能

代码:storage

浏览器:cookie。如果非必要,不要用。会增大服务端的压力

静态缓存

用的html,没有走的服务器就生成的。apache

如果每个用户查出来的都一样。电商网站的物流信息,数据库存一份,缓存也存一份,因为所有人都要用

凡是与用户个人无关,具有较强通用性的数据都可以作为静态数据缓存

不适合缓存通用性很差的数据。

服务缓存

个性化的动态的不值得缓存,但是这些数据的生成都有一个过程

尽量找那些服务器里输出固定响应值的数据做缓存

一定固定的视频、图片、欢迎页一般缓存到客户端里,不放在服务端。服务端里的是工作人员可以操作到的。Apache是服务端,我们手机是装不了的

能减少I/O就减少I/O

数据库本身的缓存

数据库耗时比较久

冗余字段

订单表里有用户姓名,照理来说应该是存ID再去用户表找的,这样会浪费时间,干脆直接存姓名

查询缓存

建议不用,因为Mysql8以上抛弃他了

如果有相同的sql语句,会直接采用缓存结果,不会再去执行

缓存的sql语句和sql结果

更改表中的数据,那个缓存就会失效。对于频繁改变的表,不合适。只适合不频繁改变,且sql语句基本相同,版本也比较低就可以用。

清理碎片,flush query cache., reset query cache.

历史表

将数据放到历史表中,以后的操作比如说统计,可以延迟操作,而中间的数据存储,相当于一次缓存

十天前的一些东西可能不用了,把它挪走,相当于一个消息队列,先把这些仍在里面,以后要用就用。

新老数据一起查,就不用这么做了,只适合一些不怎么查的,只做统计用的数据

写缓存

读缓存是先读缓存,没有再去数据库拿

目的:削峰

防止请求洪峰,压垮系统

调用方发一堆处理不了了,减少数据处理方的压力

写缓存收益

原:数据处理方时间

后:写缓存时间+读缓存时间+传递时间+数据处理方时间

收益在于用户:减少了用户的响应时间

写缓存实践

利用redis,发布订阅

MQ也是

数据库(先写数据,剩下的和主业务无关的操作,后置)

目的:只要能减少用户的响应时间:就OK

适合场景:请求峰谷值变化明显、对实时性要求不高的场景。

请求不是异步处理,时效性比较差,要关注业务本身的要求

如果有相同的sql语句,会直接采用缓存结果,不会再去执行

缓存的sql语句和sql结果

更改表中的数据,那个缓存就会失效。对于频繁改变的表,不合适。只适合不频繁改变,且sql语句基本相同,版本也比较低就可以用。

清理碎片,flush query cache., reset query cache.

历史表

将数据放到历史表中,以后的操作比如说统计,可以延迟操作,而中间的数据存储,相当于一次缓存

十天前的一些东西可能不用了,把它挪走,相当于一个消息队列,先把这些仍在里面,以后要用就用。

新老数据一起查,就不用这么做了,只适合一些不怎么查的,只做统计用的数据

写缓存

读缓存是先读缓存,没有再去数据库拿

目的:削峰

防止请求洪峰,压垮系统

调用方发一堆处理不了了,减少数据处理方的压力

写缓存收益

原:数据处理方时间

后:写缓存时间+读缓存时间+传递时间+数据处理方时间

收益在于用户:减少了用户的响应时间

写缓存实践

利用redis,发布订阅

MQ也是

数据库(先写数据,剩下的和主业务无关的操作,后置)

目的:只要能减少用户的响应时间:就OK

适合场景:请求峰谷值变化明显、对实时性要求不高的场景。

请求不是异步处理,时效性比较差,要关注业务本身的要求

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值