机构设计之高流量QPS微服务分布式设计

发布时间 2023-06-11 15:33:35作者: 孙龙-程序员

1,系统设计原则及技术指标

系统-技术设计原则

好系统是迭代出来的。

先解决核心的问题,预测未来可能出现的问题。第一版 1000人,所以单机。

不要过度复杂化系统。

先行的规划和设计。

对现有的问题有方案,对未来系统有预案。

无状态原则:

无状态:对单词请求的处理,不依赖于其他的请求。

处理一次请求所需的全部信息,都在这个请求中,要么可以从外部获取(配置文件,外部存储(redis)),

服务器本身不存储任何信息。

总结:服务器是不保存请求状态的服务,就是无状态。

所有需要鉴权的都是有状态?token。

服务器是不是可以 无限扩展,水平扩展。

token:

1。 不存token。坏处:不好控制。token可以自过期(jwt)。

2。存token。坏处:存储负担。

拆分原则:

巨石系统。

用户量大,业务复杂了,公司可以投入的资源多了。

拆:高内聚,低耦合。

系统维度:商品系统,购物车系统,支付系统,优惠券系统等等。

需要和产品经理配合。

电商系统:用户系统,订单系统。

将这个系统尽量做成黑盒。

功能维度:优惠券系统:后台创建券的系统,用户领券系统,用券系统。

**读写维度:**读写比例特征进行拆分。读服务(多级缓存)和写服务(分区、分库分表)。

切面:cdn。对系统功能进行详细的梳理。aop。

模块:基础架构组,二方库。数据库连接模块,分库分表模块,综合消息队列(统一配置,主流消息队列 ),。

服务化原则:单节点?节点集群?

服务化原则:1万个服务,url。服务治理:自动注册与发现。流量不可控,限流、熔断、降级、隔离、恢复。

项目介绍。带上自己的思考。

代码:埋点。

系统-业务设计原则

防重原则,幂等原则

识别什么是重复?读?和写(有些不用,逻辑删除update 0->0,update set = 0 where = 1),每次都+1。?。

针对重复做拒绝。

模块服用原则:当你有拷贝欲望的时候,就要考虑代码的重构了。沉淀通用功能。

可追溯原则:任何问题,要有据可查,要好定位。

反馈原则:给调用方一个明确的反馈。

A:用户名不存在,账号密码错误,用户无权限。

B:登录错误,请重试。

备份原则:

代码备份:git,分支

数据备份:运维备份。操作记录备份。

人员备份:也就是 不因某个人离职,而导致项目的停滞。

规范。定期review,提交之前有责任人。罚款。gerrit。

分享:挑刺。

软件质量衡量标准

从你不同的维度对我负责的项目进行评判。 面试、晋升,工作中。

晋升:总结过去(衡量的标准),展望未来

ios/iec 250xxxxx。

功能性:满足功能要求。

效能:投入多少,产出多少。

兼容性:

易用性:

可靠性:容错,可恢复。

安全:

可维护性:

可移植性:


我自己舒服,第二别人觉得我厉害。


答疑:

二方库:自己公司内部的公共模块。

在别人的烂代码上做功能扩展怎么破?感觉烂又不敢动,太难了。

解决方案:保证黑盒。由小到大(一行代码,一个方法,一个类,一个模块)。

互联网项目:oa(卖出去的)。bat。

表现自己的思考。

系统衡量指标

吞吐量

单位时间内,能接受和发出的数据量。业务、配置。

TPS: Transaction Per Second,事务:请求,处理,响应。

QPS:Queries Per Second,

tps和qps:之间的换算关系,必须由指定的业务场景来定。

并发数

300 并发用户数?并发连接数?并发请求数?并发线程数?

响应时间&平均响应时间

RT:

阿姆达尔定理:

加速比:Rm=T老/T新。

增强比例:p=m/总

T新=T老 * [(1-p)+p/Rm]

P大的。Rm

io。Rm


可靠性指标

串联:99% 99% 99% 99% 99% 越乘以 越小= 95%

冗余:

并联:1-(1-r1)*(1-r2)(1-r3)** 越乘以 越大?

消除单点。

化串联为并联:

你出去买东西,一手交钱一手交货。你从网上买东西时,你先付钱,一段时间后才会给你发货,这就爸交钱和交货分开了,它能不能发货不影响你付账

项目面试要点

需求。必须熟悉。

解决方案。校验,token,黑名单,权限。

难点。重点:过程。找原因(原理,源码),并解决。

1。突出解决问题的能力。2。技术深度。

不好编,作为一个大牛,怎么可以有难点

黄晟

可以说项目中遇到的问题、瓶颈,怎么解决的

 

2,客户端优化

客户端优化

资源的获取,资源的处理,资源的展示。

资源:样式文件,脚本文件,图片,视频,文本等等。

浏览器:DOM树。

过程:

1。资源下载。

快:压缩。

不必要的cookie。

1k 1一亿个呢? 8亿。

JavaScript:删除无效字符,注释:1。减少体积。2。代码安全。语义合并。

CSS:类似。语义合并。9行。原来10个按钮 10个样式,class 1个样式。

http请求压缩:

head中加参数:Accept-Encoding:gzip,deflate

表示 客户端 可以接受的压缩内容的格式。

服务端响应:head: Content-Encoding:gzip

在服务端:手动做一些压缩的操作。

减少请求:

资源数目多、体积小,频繁创建http链接?

雪碧图:样式文件 background-postition:

js合并。

矢量图:<svg >

base64图片。

40% 以下。gzip 对文本文件的压缩 能压缩到原来的 40%以下。

转移给第三方。


连接:

http1.0 http 1.1()

http没有长短连接一说,长短连接 是 针对tcp的。

http是针对 请求和响应模式的,只要服务端给了响应,本次http连接 结束。

http1.1 发请求的时候,在请求头中:connection:keep-alive。

好处:减少了 创建和销毁连接的 消耗。

例子:网页,(css,js,html),baidu.com。

header里设置超时。

长轮询 和 短轮询

长轮询:发现服务端没有变化的话,那么将当前请求挂起一段时间,如果没变化,一直等到超时,如果有变化,则返回。

假如是查询数据库的,那不是一直要不停的查询吗?

1。 用redis。

2。发布 订阅。

形成自己的解决问题的 思路的框架。

双工通信

netty,websocket(ws)。

http在tcp的基础上 有 长 短 轮询。

tcp链接 有 长短。

现在 基本上都是http1。1 默认支持长连接。

长轮询 :是服务端控制的。

双工通信:前后端 可以彼此 交互。

connection: keep-alive

keep-alive: timeout=60s

资源的缓存

页面缓存,客户端本地缓存()

页面的缓存:可以控制 客户端、各级代理(中间的各个节点)、 对页面资源的缓存。

如何控制:headers : Cache-Control:public

可缓存性:

pubic(服务器 响应中): 各级都能缓存。

private(服务器 响应中): 只能 客户端 缓存,中间各级不缓存。

no-cache: (请求,响应):可以缓存,但是不能直接使用缓存,要去服务端验证一下。

no-store: (请求,响应):哪都不要存。

缓存有效期:

max-age=秒,缓存可以存活的时间。

s-maxage=秒,在各级节点存活的时间,如果是 客户端存储忽略。

max-stale=秒,可以忍受资源过期的时间。

min-fresh=秒,

重新验证和加载的设置

must-revalidate: 服务器重新验证之前,不可以使用该资源。

proxy-revalidate: 各级节点有效。

no-transform: 不能压缩图像。

only-if-cached: 只要缓存的,不要服务器的资源。

header:

Cache-Control: pulic, max-age=10,no-transform

缓存更新不及时:

1。更新文件名:版本号,url时间戳的变化。my-js.js。my-js-1.js。客户端和服务器要达成一致。

发布上线一个文件,这个文件名是变的,那么 缓存的文件 就失效了。

2。验证 缓存的有效性。

基于 文件的最后修改时间。

服务端:last-modified : 最后修改时间。

客户端:if-modified-since :自己需要资源的时间。

你要我1个手机,我给你手机,手机贴标:时间戳。。304,不返回具体资源。200:返回具体资源。

image.png

基于:版本号的。

etag: 版本号。

客户端在请求中:if-none-match:

2。 解析。元素,样式,脚本。

目的? 优化解析。

1。优化正常解析流程。

元素

css样式文件 render tree 布局好, 绘制。

js脚本

回流:

重绘:

目的:缩小 回流和重绘的 范围。

方法:分几个块。

2。创新解析流程。:

虚拟dom(Virtual DOM)。算法,比较前后两个dom的区别。

如果改变不了 它 本来的面目,那么就给 它 化化妆,改变展现的面目。

oracle 物化视图?

缓存 redis?


懒加载

h5 app 界面。

懒加载:仅仅加载 最基础的元素,以后,再根据用户的操作,进行局部加载。将原来一次性要加载的内容拆分成了多次加载。分流。

回流、重绘。

app上的,

树形组件,折叠面板,标签页。

尽量灵活一些,支持多种情况。

到了不得不看具体数据的时候,才调用后端。

但是也不一定,一些 经常不变的数据,就没必要了。

预加载

先后顺序。

1 2 3 4 5 ---10

1。同一个域名下。

拉去资源

<link rel="preload" href="xxxx.js" />

<link rel="prefetch" href="xxxx.js" />

2。不同域名下。

A,B,C

减少域名解析此时:dns缓存。

DNS预获取。

dns解析速度,被很多人忽视。蚊子腿也是肉。

预解析:

dns解析,多级递归查询。对时间是消耗,我们想办法节省时间。

我们在当前页面,完成对下一个页面域名的解析,而在下一个页面直接使用预解析之后的结果。

dns prefetching。

告知浏览器 打开域名的预解析

<meta http-equiv="x-dns-prefetch-control" content="on">

解析谁

<link rel="dns-prefetch" href="//www.baidu.com">

<meta http-equiv="x-dns-prefetch-control" content="off">

拉去资源

<link rel="preload" href="xxxx.png" />

<link rel="prefetch" href="xxxx.js" />

preload: 针对当前页面,更早的去下载资源,当前页面的资源。

音频,视频,embed,图片,js,css,

prefetch: 针对下一页,如果下个页面 存在比较大的资源,当前页面处理完,浏览器闲置的时候,

会去加载 该资源。

扩展:

图片->base64->放到 css文件里。

客户端数据库

一个用户1k,1000w个用户 10G。

cookie算一个:过期时间,坏处:

sqllite。

动静分离

媒体类网站:一篇新闻,

数据不仅包括 传统意义上的页面,也包括 没有和访问者相关的个性化数据。

缓存:

把静态数据 放到 离用户最近的地方。:浏览器,cdn,服务端的cache。

链接和数据做映射。url1 获得一个数据。key value。如果缓存到了 客户端,都不用发http请求了。

nginx,apache。

怎么做?

url唯一,做映射。

特殊展示的元素做分离:尽可能的多做静态化。

将页面中的cookie,与用户个性化相关的 东西 去掉。

架构方案:

静态服务器:nginx,apache

统一缓存管理服务:缓存做分发。

上CDN得了。

数据库查的 数据 + 样式文件 = html文件。

客户端优化 到此为止

用户在客户端中的优化。

 

网络分发

请求 从 客户端出来之后的优化。

DNS

目的:将域名解析成IP地址。

理解成:web服务,挂着一个 分布式数据库,并且提供了 crud功能。

记录类型:

A(Address):域名和IP的对应关系。

CName(Canonical Name):域名和域名的对应关系。

NS(name server):域名和 能解析此域名的域名服务器 的对应关系。

服务器类型

根域名服务器:13个。

A:主根,其他:辅根。2020年8月统计:1097个根域名服务器。

根服务器的镜像服务器:

域名解析流程

教科书:

LDNS。www.baidu.com

缓存

xxx.a.baidu.com hosts

浏览器

操作系统:hosts

LDNS:

a.baidu.com ns服务器

回答问题:

域名和域名的对应关系怎么理解?

xiaodoufu.com zhangdou.com

优化:

1。提前做好dns缓存。

2。A 记录,域名 对应 多个 Ip?结论:通过dns做负载均衡。

缺点:时效性问题。负载均衡算法 无法灵活使用。


CDN

结论:大型系统,部署多个节点。

请求多个节点如何做优化:

1。将请求分配到多个节点上,减少每个节点的并发数。

2。将请求落到离用户最近(地理位置最近,网络拓扑最近)的节点上。

cdn:放静态资源。

cdn两个关键节点:

源站:核心业务系统,所有信息的源头。

缓存(边缘)节点:静态资源。

image.png

流程:画图。

节点数目:

设计CDN

用户来源(如何识别,根据ip地址查询用户 地理信息)

就近分发()

1。www.mashibing.com: cname www.mashibing.com---xiaodoufu.com

if(地理信息 是否 是 北京的){

指向 北京

}else{

指向 杭州。

}

内容的缓存:

/xxx.html /xxx.png 静态 缓存到 cdn中。

做好映射:/xxx.html value:响应的值。

扩展:

数据来源:

1。启动时,初始化。内置好。

2。增量更新。有效期。

CDN总结

流程图OK

扩展一下(类似于:注册中心):

地址的获取:

内容的请求:

多地址直连

功能:将用户动态的请求 做分发。

用户、调用方。

cdn服务商,管理服务的通讯录。维护用户的cdn节点(源站 :提供动态内容)的列表。

cdn节点(真正提供服务的节点)。

方案:

注册中心:优化:客户端缓存。

规则中心

更简化的方法:将选择权 交个用户。

代理

请求到了确定的IP。IP不一定是真正提供服务的机器,只是他们对外提供的统一的IP。

正向代理

反向代理

识别用户的请求:

应用层 http ftp

表示层

会话层

传输层 tcp

网络层

数据链路层

物理层

4层反向代理:根据用户的ip和端口。

7层反向代理:根据用户协议,方法,头,正文参数,cookie。掌握更多的内容,更智能,效率低。

upstream:

access_by_lua_file.

负载均衡算法

轮询:RR。Round Robin。挨个发。适用于所有服务器硬件都相同的场景。一个一个来,

代码实现:

加权:WRR。Weight round robin。

代码实现:比如两个服务,一个权重是6 ,另一个权重是4。

1-10之间去 随机数。如果取到 1-6,那么 找6权重的服务。

如果取到 7-10,找 4权重的服务。

随机

random。

hash(原地址散列) Source Hashing

请求来的Ip 是 a, 我将a进行hash ,做成 对应到 1好 服务节点。

好处:便于session维护。

hash

最少链接 Least connecitons

将请求路由到 最少链接的服务器上。

代码实现:

redis存储结构:hash。 hset key field value

服务集群方案

并发和并行

并行:在某个时间点,多个任务进行。

并发:在某个时间点,只有一个任务进行。但是 在一个时间段,有多个任务同时进行。

集群。

集群缺点:

注意幂等。(幂等:每次操作都是一样的结果)

注意数据存储的共享。

杨通

弄一个数据共享的模块?

瞿菲

分布式事务吧

无状态节点集群

做集群的时候,不要修改服务内存的数据。

无状态:请求到达服务器,携带了服务端所需要的所有参数,服务端(服务的内存)不存储

所有跟请求相关的任何数据。map

有状态:在服务端存储之前的请求信息,用于后面请求的处理。

集群一般用无状态。确保无状态,必须保证所有接口都是恒等类,系统内存中存储的数据不能发生变化。可以

考虑通过 公共存储,实现无状态。

协作的问题:

定时任务,发短信。

锁:在程序执行的时候判断是否可以发?

外部唤醒:

内部和外部。

单一服务节点集群

选服?实时对战。长连接。

关键:实现用户和服务器对应关系的映射。

手动选择服务器

用户id分配服务器

解决了有状态的问题。

容错性差。

信息共享节点集群

多个服务共同连接:共享的存储。

协作:

缺点:受到共享存储的限制。存储容量,读写性能。故障的单点,瓶颈所在。

信息一致节点集群

读写分离。

分流。

存储之间:数据一致性问题。

强一致:

弱一致:最终一致

分布式系统

统一接口的定义。彼此当成黑盒。

微服务

总结:

一个服务---服务的分身(复制多份)----垂直切分。


如何实现的高并发:

高并发系统中的代码,就是我们普通的代码。

if else for

大量的机器,架构设计。

项目上线----加机器------优化架构------沉淀技术(中间件)----

课程后面加代码实践。

CAP

一致性。

越往上发展,设计和理论越显得重要。

是理解分布式系统的起点。

C 一致性,A 可用性,P 分区容错性,不可能同时满足,三者中选二挑一。

数据的一致性,从什么角度去看?

读、写。

P:分区容错性。

分区:

容错:

形成分区的原因只有网络故障这一种吗

网络的8大谬误:

1 网络总是可靠的:

2 没有延迟。

3 带宽无限

4 网络总是安全的。

5 网络拓扑不会改变。

6 只有一个管理员

7 传输代价为0:

8 网络是同构的。

P 必须保证。即使网络出现问题,我们的系统也要能正常使用。

三选二挑一。

P CA。

那P可以通过哪些方式保证呢?

数据被复制到其他节点上。提前把数据给你。mysql,redis:slaveOf ,zk。

为什么只能选 AP,CP?

一致性:写什么,就能读出什么。写:原子操作。

强一致性:写操作完成,后续的所有的读都能看到新数据。

弱一致性:写操作后,对该数据的读,可能是新值,也可能是旧值。

最终一致性:写后,读在一段时间内,可能读的是旧值,但是 最终,能读到新值。

可用性:向未崩溃的节点发请求,总能收到响应。有数据就行,管它对不对。

如何取舍?

看业务要求,或者说容忍度。

(中间件去聊)

AP:web缓存,dns,cdn。(大部分情况下)

不错的策略:

保证可用性和分区容错性,保证AP,兼顾C一致性(舍弃强一致性,保证最终一致性)。

电商:买东西 送积分。先买东西,积分次日到账。

红包,

如果保证强一致性,会对吞吐量造成负面影响。

A、B两系统,合起来完成一个业务。

A用10s,B用10s。一共 20s。10s。解耦。

信息一致性方案:

前面所有内容:服务和服务之间的并发。

服务内的并发

多进程

每个进程之间,资源独立,具有很强的隔离性。

java -jar xxxx.jar: 启动一个java进程。

两台物理机:a、b、c三个服务。

多线程

一个方法:先计算,然后 等待io,

出租车 计费:

时长计费(1min 多少钱),里程计费(1公里 多少钱)。

future。

目的:

1 提高效率。

2 实现异步。提前释放主线程。(降低了响应时间,节省了保持客户端和服务连接的资源)

线程数的计算

公式一:线程数=cpu核数 * cpu利用率 * (1+w/c)。

cpu利用率:0-1之间。

双核:0.4s, 0.6, = 50%。

算的时候,用100%。

线程数=cpu核数 * (1+w/c)。

w:等待时间,c:计算时间。wait/computer

2c cpu 2ms, 1ms == 2*(1+2)=6 个线程。

直观的结论:等待越久,线程数越高。

wc:

公式二:

线程数=cpu核数/(1-阻塞系数)。

阻塞系数:计算密集型:0,IO密集型:1。

统一公式:

cpu核数 * (1+w/c)=cpu核数/(1-阻塞系数)

阻塞系数= w/w+c。

实际以压测为准。

线程数,qps,机器配置。

cpu核数 * (1+w/c)

IO密集型和计算密集型的计算方式不一样吧老师

2 cpu核数。

计算密集型: cpu核数

压测为准。

 

缓存设计

分流:到达服务之前,减少服务处理的请求数。

并发:到达服务之后,提升服务处理的请求数。

缓存设计

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

缓存:空间换时间的一个做法。

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

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

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

缓存的收益

成本,收益。

读、写。

位置:介于 请求方和提供方之间。

收益:节省了响应时间。

成本:

kv

计算key的时间,查询key的时间,转换值的时间。命中率P。

所有数据的查询时间=计算key的时间+查询key的时间+转换值的时间+(1-p) * 原始查询时间

计算key的时间+查询key的时间+转换值的时间+(1-p) * 原始查询时间 <<<< 所有数据 的原始查询时间

适合:耗时特别长的查询(复杂sql),读多写少。

缓存键的设计

kv

单向函数:给定输入,很容易,很快能计算出结果,但是给你结果,很难计算出输入。

正向快速,逆向困难,输入敏感,冲突避免。

sha-256

冲突的概率 极低。

查询key的速度 取决于:物理位置 (内存,硬盘)。

值:

序列化

对象

总结:

无碰撞。高效生成。高效查询,高效转换。

上面所有:都被中间件提供的api 封装了。

实际中:前缀_业务关键信息_后缀。 公司统一制定规范。

user_order_xxxx:

user+order+xxxx:

user-order-xxxx:

$

缓存的更新机制

被动更新

调用方 暂存方(缓存) 数据提供方

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

1。客户端 查数据,缓存中没有,从提供方获取,写入缓存(有一个过期时间t)。

2。在t内,所有的查询,都由缓存提供。所有的写,直接写数据库。

3。当 缓存数据 t 到点了,缓存 数据 变没有。后面的查询,回到了第1步。

适合:对数据准确性和实时性要求不高的场景。比如:商品 关注的人数。

主动更新

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

数据库:更新数据库

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

保持一个定量,考虑围绕它的变量,这样才不会有异常的遗漏。

更新缓存,更新数据库

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

更新数据库,更新缓存

一般也不采用。

请求被阻塞,

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

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

删除缓存,更新数据库

一般不采用,因为大概率 读比写快。

延时双删,休眠多久,系统吞吐量下降。

昨天被高德一个面试问:说,你这个延时双删有这么几步操作。如果其中某一步失败了这么办?

删除缓存

更新数据库:事务,回滚就OK。

第二次删除缓存

重试删除:当你前面的操作,无法回滚时,为了保证后续数据的一致性,

(最便宜的做法)硬着头皮往前走,重试。

借用中间件:消息队列,重发消息。

系统外订阅:canal。binlog。

二次删除key,和我们的业务代码解耦。

更新数据库,删除缓存

经常采用的方式。

cache-aside模式。

异常流程:

前提:缓存无数据。数据库有数据。

A:查询,B:更新

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

B更新数据库 新值

B删除缓存

A 将旧值写入缓存。

脏数据。

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

读比写慢 概率很低,极低。

缓存无数据。

如果非要解决,延时双删。再删除一次。

Read/Write Through

程序启动时,将数据库 的数据, 放到缓存中,不能等启动完成,再放缓存中。

Write Behind

降低了写操作的时间,提高了系统吞吐量。

双写一致性。

缓存清理机制

如何提升缓存命中率:尽可能多的缓存。所有数据都放缓存,命中率 100%。

我们需要用有限的缓存空间,发挥最大的作用。

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

读的时间频繁:当清理一个数据的时候,发现,它一直被访问,那我就认为他 马上的未来,也会被访问。

写入时间的时间节点。

我是问代码怎么实现:当清理数据时,发现他一直被访问。

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

读:

getKey, k =0 ttl 1min , incr

if(!getK > 1){

delete k

}

时效性清理

给缓存设置一个过期时间,到期 缓存 自动 清理。

缓存中的数据 有 一个 生存时间:ttl。过期时间。set k v ex 10 s

set cookie   过期时间。

k v 10s

定时任务轮询。delete

自动清理机制: cookie redis expire .。(本质:轮询)

数目阈值式清理机制

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

阈值:根据自己的业务来定。1g,1m,1024个, 800 80%。

采取什么策略去清理:

fifo: 先进先出

package com.example.cachetest;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 数据阈值式清理
 */
public class CacheThresholdTest {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        for (int i = 0; i < 4; i++) {
            setCache(queue,""+i);
        }

    }

    public static void setCache(Queue<String> queue,String cache){
        int size = queue.size();
        if (size >= 3){
            queue.poll();
        }
        queue.add(cache);
        System.out.println("缓存中的值如下:");
        for (String q: queue) {
            System.out.println(q);
        }
    }
}

random:随机

lru:规律:

LinkedHashMap 套。fifo,lru。

map:存 键值对。

顺序:插入顺序 fifo,访问顺序 lru。

removeEldestEntry。

package com.example.cachetest;

import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.Map;
public class TestCache {
    @Test
    public void testLinkedHashMap() {
        // 在介绍accessOrder属性的时候说accessOrder设置为false时,按照插入顺序,设置为true时,按照访问顺序
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(5, 0.75F, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                //当LinkHashMap的容量大于等于5的时候,再插入就移除旧的元素
                return this.size() >= 5;
            }
        };
        map.put("1", "bb");
        map.put("2", "dd");
        map.put("3", "ff");
        map.put("4", "hh");

        System.out.println("原始顺序:");
        print(map);
        map.get("2");
        System.out.println("2 最近访问:");
        print(map);

        map.get("3");
        System.out.println("3 最近访问:");
        print(map);


        map.put("5","oo");
        System.out.println("加元素");
        print(map);
    }
 
    void print(LinkedHashMap<String, String> source) {
        source.keySet().iterator().forEachRemaining(System.out::println);
    }
}

实现:k v。 map 一台服务器上能用。redis。

软引用清理

用空间换时间的模块。尽量用空间,用以提高缓存 命中率p。

适时的释放空间,gc。

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

gc root引用。

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

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

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

把值 放到 SoftReference 包装中。

package com.example.cachetest;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.*;

/**
 * 软引用 缓存 实验
 */
public class CacheSoftReferenceTest {
    public static void main(String[] args) throws InterruptedException {

        soft();
    }
    static void soft(){
        // 缓存
        Map<Integer, SoftRefedStudent> map = new HashMap<Integer, SoftRefedStudent>();
        ReferenceQueue<Student> queue = new ReferenceQueue<Student>();
        int i = 0;
        while (i < 10000000) {
            Student p = new Student();
            map.put(i, new SoftRefedStudent(i, p, queue));
            //p = null;
            SoftRefedStudent pollref = (SoftRefedStudent) queue.poll();
            if (pollref != null) {//找出被软引用回收的对象
                //以key为标志,从map中移除
                System.out.println("回收"+pollref.key);
                map.remove(pollref.key);

                System.out.println(i+"新一轮================================================");
                Iterator<Map.Entry<Integer, SoftRefedStudent>> iterator = map.entrySet().iterator();
                while (iterator.hasNext()){
                    Map.Entry entry = iterator.next();
                    if ((int)entry.getKey() == pollref.key){
                        System.out.println("见鬼了");
                    }
                }
                System.out.println(i+"新一轮================================================");


            }
            i++;
        }
        System.out.println("done");
    }
}


class Student{
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

class SoftRefedStudent extends SoftReference<Student> {
    public int key;
    // 第3个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的
    /**
     * 当Student对象被回收后,SoftRefedStudent对象会被加入到queue中。
     */
    public SoftRefedStudent(int key, Student referent, ReferenceQueue<Student> q) {
        super(referent, q);
        this.key = key;
    }
}

缓存清理机制总结

时效式清理+数目阈值:防止:短期内,密集查询,导致缓存空间的急剧增大。

--自己的完整思路。

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

不建议:仅仅使用 软引用。因为我们失去了对它的控制。

目的:提高缓存命中率,节省空间,=》提升性能。

缓存风险概述

在系统中,每增加一个环节,就多一份风险。用是不得已。

缓存穿透

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

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

缓存雪崩

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

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

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

缓存击穿

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

lru:

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

缓存预热

read write through or write behind

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

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

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

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

热门数据,加到缓存。

缓存风险的总结

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

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

缓存的位置

缓存来源:L1 L2 L3。

缓存的读取过程:

思考:如何避免cpu浪费时间。

减少我的等待时间----缓存。

我尽量多做事情-----多线程。

目的:降本增效。

级联系统缓存位置

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

客户端缓存位置

1

2 秒杀系统,商品详情页就是静态文件(扣一块动态的)。

降级。

代码:storage。

浏览器:cookie。失效时间。如果非必要,不要用cookie做缓存。

静态缓存

1。静态页面。apache。录个视频,apache静态页。

2。通过数据库查出来的。

如果每个用户查出来的都一样。 物流信息:省市区。

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

已经有缓存页面,后台更改数据之后,如何让数据快速生效。cache aside。

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

服务缓存

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

数据库本身的缓存

aside。

数据库耗时比较久。

怎么做?

冗余字段。订单表里 id,有用户姓名,商品名称。

中间表:学生表,课程表,排课表。

查询缓存:建议不用。mysql8以上,抛弃了。query cache.

select * from user_info

SELECT * from user_info.

指定规范,大小写该统一就统一。my.ini my.cnf。

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

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

coder学员:新老数据,还在一起,统计数据剥离。

写缓存

目的:削峰。

数据处理方的 处理速率是固定的 ,为了 防止请求 洪峰 压垮系统。采用写缓存。

写缓存收益

只要能给数据进行更改的操作,都叫写。

数据处理方时间。10s

引入缓存后:写缓存时间 2s,从缓存读取数据的时间,传递时间,数据处理方时间。

收益在于:用户。-8s。减少了用户响应时间,提升了系统吞吐量。

读缓存和写缓存:

读缓存:用缓存的命中率,替换数据提供方的操作。能减少用户的请求时间,能减少系统的总处理时间。

写缓存:花费额外的时间。来延迟数据处理方的操作,减少用户的等待。只能减少请求响应的时间,反而会增加系统的总处理时间。

image.png

是。

1s 0.1s(1s) 10倍。

image.png

i like u(you)

4(for) u

2(to) B

log4j log for java

C#。

day day up:

cache aside。更新完数据库,删除缓存。 迁移到: 修改完页面,删除静态页。io.write("d:/apache/www/x.html")

写缓存实践

利用redis 发布订阅。

MQ

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

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

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

缓存总结

 

数据库设计

数据库优化的理由

收益:阿姆达尔定理

数据库单点压力:

积累压力:

数据库设计

1。先设计数据表的关系,再推导使用对象。

2。反之。

设计别扭的时候。1-2,3-2。

数据冗余:

数据不一致:

插入异常:删除异常。

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

反范式,建议 在范式的基础上进行修正,减少范式带来的负面影响。

第一范式:每个字段都是原子的,不能再拆分。

反例:json。先看设计是否合理,再选合适的存储组件。

第二范式:必须有主键(可以是一个,或 多个字段),非主属性,必须依赖于主键。

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

第三范式:没有传递依赖,非主属性直接依赖于主键,而不能间接依赖于主键。

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

反范式:字段冗余。

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

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

巨量数据的优化

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

常用的方法:建索引。mysql性能调优。

表分区->分库分表->读写分离

表分区

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

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

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

好处:

1。 当查询条件 可以判定 某个数据位于哪个分区,那么直接在分区中查询,不用扫描整个表。

2。业务代码不用改。

3。分区进行单独管。备份,恢复。

range。0-10,11-20。

partition by range

list: partition by list xxxxxxxxxxx values in (1,2)

hash:partion by hash

key

做分区的注意事项:

1。 做分区时,要结合查询规则。尽量保证常用查询落到一个分区中。

举例:大数据量: 学生姓名,老师姓名。如果以学生姓名作为条件查询较多。以学生姓名分区。

2。分区条件放到where语句中。比如上面的学生姓名。

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

分库分表

目的:

1。业务拆分。

2。通过分库分表应对高并发。需要看场景:读多写少(从库,缓存)?读少写多?

3。数据隔离。将核心业务和非核心业务进行拆分。

分库分表的设计

一个库里有两张表a,b。

如何拆?

1。不破坏表。a放一个库,b放一个库。

2。破坏表。垂直分 和 水平分

垂直分:

字段1,字段2,字段3,字段4。

数据条数不变,字段数变少。

场景:

水平分:

字段1,字段2,字段3,字段4。用户表:性别,年龄,姓名,介绍。

1

2

3

4

婚恋。

额外的操作:

路由操作:

拼接操作:

开发中:遇到问题,先用简单办法解决,解决不了,再复杂。

分库分表的方法

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

分段小:导致子表数量多,增加维护难度,分段大:有可能,单表依然存在性能问题。

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

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

缺点:数据分布不均匀。

hash路由:选取 列 ,进行hash运算。 %10 = 。

优点:相反,

缺点:相反。

10, 11 --1 。

11,11--0

分库分表的问题

分布式id的问题:

需要 全局id生成服务。

注意点:全局唯一(时间戳,机器号,序列号);高性能;高可用;易使用,好接入;

uuid,数据库自增,号段,redis自增,雪花算法,TinyID,UIDGenerator。leaf。

有独立的课。

拆分维度的问题:电商系统-订单表:,用户id,订单id,商品id。

依据:自己业务的情况。看那个字段用的多?

索引表、映射表:(过渡方案)

dts,datax:增量同步。

join问题:

商品、订单、用户。单库join没问题,多库join失败。

1。在代码层面做join。

2。es。canal --》es。

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

事务事务:

XA,jar包。不好用。shardingsphere.

成本问题:

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

1亿 1张表,10ms。100w1张表 1min。

读写分离

1个库。

2年后:10个库。

4年后:又慢了。

10个库里的每一个 做 读写分离。(前提)

分流:

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

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

S锁-读锁,

场景:读多写少。不适用:读少写多(反而用途不大)。

防止阻塞。

一主(写)多从(读)

路由问题:select S 锁 从库 ; create update delete add. X 锁 写库。 mybatis插件。

主从复制的问题:

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

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

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

base:

2 会有时间差。

(一)

编造业务场景:注册(主库),登录(查 从库)。

将注册后的第一次 查询 ,指到 主库上。

比如说:注册完,写个redis,key 用户id 1。删掉key。

和业务耦合大。

lich:我是先从库查,返回空后,再查一次主库,如果没有的话,那就真的没有了。-- 个别业务用。

(二)

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

用户系统,注册登录,都走主库, 用户介绍、xxx 走从库。

实现

分库分表,读写分离。

读写分离:将读和写,分开。

分库分表:将 数据 分库。

分-

分配机制-

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

select ----某台机器上

update --某台机器上。

where id = 1 某台机器上

where id = 2 某台机器上。

拦截,判断,分发。

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

Shardingjdbc。需要改代码。

中间件封装:mycat。

 

应用保护

 

系统压力太大,负载过高。

数据库慢查询,

核心思想:优先保证核心业务,优先保证大部分用户。

降级

论坛:

思想:丢车保帅。保证核心业务可用。

1。系统提供后门接口。

2。独立降级系统。对1封装。redis。

if(ifxxList){

getXXList();

}else{

getXXlis备用();

}

前提:代码提前规划好。

自动开关降级

提起写好降级逻辑。

触发降级的条件:

1。超时。阈值。

2。异常。 阈值。 1001 运行时异常。

3。限流。排队页面,卖光了。

降级手段:

减少不必要的操作,保留核心业务功能。

停止读数据库,准确结果转为近似结果。静态结果(猜你喜欢),同步转异步(写多读少)。功能裁剪(推荐干掉)。禁止写(高峰期减少不必要的写)。分用户降级。工作量证明 POW(验证码,数学题,拼图题等,滑块)。

熔断

A服务:X功能(熔断 )--B T。

阈值怎么定?5次/1s--3次,先预估,上线观察,最后调优。

保险丝。

熔断策略:

根据请求的失败率:熔断开关 打开。过一个时间窗口(10s),熔断开关关闭。开关再打开。--半开。Fail Fast。

hystrix:yml。每20个请求,当失败率50%,打开开关,等过5s,开关关闭。

根据响应时间:sentinel。时间阈值,超过 熔断开关打开。

限流

外部(请求太多了)、内部(资源)。

基于请求的限流

请求的总量。

直播间超过100人,出去。

限制时间量。

一个时间窗口内:接100个请求。

上线观察、调优。

1s 1万人。结果 5k人就扛不住了。

提前压测。

排队:缓冲。MQ,长连接给用户响应。

基于资源的限流

连接数,线程数,请求队列。cpu参数。

难点:确定关键资源,阈值。

池化技术:连接数,线程池。

队列大小:请求队列。10个。

cpu参数:perl。

tomcat:

acceptCount: 如果tomca线程池满了,

maxConnections:最大链接数。

maxThread:

固定时间窗口

滑动时间窗口

突刺。

漏桶算法:

漏桶的队列长度。

漏桶里有请求等着处理,但是我的出口,确实处理不了。

一个是浪费。

O(1)。

令牌桶算法

漏桶和令牌桶

漏桶:限制平滑的流入:

令牌桶:可以容忍部分突刺。

节流

去抖。 减少网络的请求呢?

定义一个时间窗口。1s 开源。

前置:a记录的转发,cdn。缓存扛不住) 限流。



请求来-中间环节--处理完。

(应用保护)

序列化(字节流),对象。

隔离

数据隔离:数据重要性排序。分库。

机器隔离:给重要的用户单独配置服务器。用户的标识去路由。

线程池隔离:线程池分配。hystrix。

信号量隔离:计数器。hystrix。

集群隔离:服务分组(注册中心),秒杀。(单独分出一组服务给 核心业务)

机房隔离:3个服务。局域网ip、路由。

读写隔离:主从。create update delete \ select (数据延迟,前面的课里讲过)

动静隔离:识别动静态数据,进行隔离。nginx,apache。

爬虫隔离:openrestry user-agent。 对ip的访问频率。

冷热隔离:秒杀,抢购。读:缓存。写:缓存+队列。

恢复

撤出限流,消除降级,关闭熔断。

半开关。

让吞吐量爬升。缓存预热。

1。类实例创建,反射调用创建实例。

2。缓存数据预热。

灰度发布。限流逐步提升:阈值。用户打标签(18以下用户10个,18以上 1万人。)

异地多活

异地

多活(不是备份)

1。请求某一个节点,都能正常响应。

2。某些系统故障,用户访问其他系统也能正常。

分类:

同城异区,跨城异地,跨国异地(隔离)

跨城异地:质变:RTT。Round Trip Time 50ms。京广。 京沪:30ms。 数据不一致。

跨国异地:延迟,已经无法让系统提供正常服务。

负载均衡。---dns cdn。分流。

数据不一致问题:

1。保证核心业务多活。

用户系统:注册,登录,修改用户信息。 广州 北京。

注册:

登录:

修改用户信息:根据时间合并。分布式ID。

1000万用户。每天注册 几万个。修改用户信息的呢 几千个。 1000w登录。

基于已有的数据。上天总是公平的。

2。保证核心数据最终一致性。

自建网络。国字头。

session数据。token。(量大,同步不值得。)

同步手段:

中间件的主从复制。mysql ,redis。

消息队列:订阅发布。

二次读取:

回源读取:

重新生成:

保证大部分用户:小部分用户忍。

异地多活设计步骤:

1。业务分级。

微信聊天和朋友圈。

广告收入。新闻网站。

公司、用户影响。

2。数据分类。

唯一性:手机号。额外存储。crud。

实时性:

可丢失性:session

可恢复性:session可恢复的?

3。 数据同步。

4。异常处理。

多通道同步:mysql主从复制+消息队列。两种方式走不同的网络。

同步和访问结合:

广州,北京。

A用户登录广州,生成 session,sessionID(带有广州气息的sessionID xxxxx1)。

session不同步给北京。

A访问北京的服务器:参数 sessionID(带有广州气息的sessionID xxxxx1)。

if(endWith(1)){

String session = http.调用(拿对应用户的session);

sesson.set(K,V);

}

javaer。

j2cache,nginx+lua

OVER!!!

方案性东西。找我讨论。

从0到1 的代码课,商城。

重试,持久化,重发。

迭代。

代码量不够:

system.out.println("hello world")。