架构设计小心得
项目开发流程:
image-20220303112748431image-20220303113042065
短链接设计:
方案一:
设计:
id BIGINT,自增主键
url 长地址,也就是需要跳转的原地址
缺点:
(1)如果数据比较大,比如几百亿,你的url地址依然过长
(2)你的数据具有规律性,别人用一个简单的脚本就可以遍历出你的跳转地址!
方案二:
设计:
id BIGINT,自增主键
key 短串,需要加唯一索引
url 长地址,也就是需要跳转的原地址
有两种方法生成key,一个是id转62进制(每一个新url需要查询是否已发id,lru缓存),另一个是url进行hash(可能出现冲突,比如md5碰撞几率极小)
推荐:
通过发号策略,给每一个过来的长地址,发一个号即可,小型系统直接用mysql的自增索引就搞定了。
如果是大型应用,可以考虑各种分布式key-value系统做发号器。不停的自增就行了。 key为长url
数据过长问题:
由于我们的短链接是由 a-z、A-Z 和 0-9 共 62 个字符可以选择。因此,我们可以讲十进制的数字id,转换为一个62进制的数,例如201314就可以转换为Qn0。
数据规律性:
(1)不希望反推出全局ID
使用洗牌算法
(2)希望反推出全局ID
固定位加减
细节优化:
(1)分库分表(增加并发)
问题1:分片key?
如果这个系统是放在公网,给大家使用的。建议上来就分库分表,数据量过1000万是很容易的。这里涉及到一个问题,拿全局发号器给的自增id做分片健,还是拿转换后的key做分片键。
数据库存放id->长url的记录,所以用id。
key通过进制转换,拿到id后查询数据库位置,直接根据主键查询。
问题2:如何保证发号器的大并发高可用?
我们是否可以实现两个发号器,一个发单号,一个发双号,这样就变单点为多点了?依次类推,我们可以实现1000个逻辑发号器,分别发尾号为0到999的号。
每发一个号,每个发号器加1000,而不是加1。这些发号器独立工作,互不干扰即可。
而且在实现上,也可以先是逻辑的,真的压力变大了,再拆分成独立的物理机器单元。
(2)读写分离
这种系统显然,读远大于写。建议可以考虑做读写分离。
(3)引入缓存
假设,我们在一个时间。给手机推送短信链接的短信后。显然,后面的一段时间内,对该短链接的请求量会大大提升。没有必要每次都去数据库查询,因此可以引入redis缓存。
(4)全局发号器用其他算法行不行
可以。这里只是要一个全局唯一ID而已。自己要估算好,使用其他算法所带来的性能影响。以及采用其他算法,会不会造成生成的生成的ID过于规律。
(5)防攻击
做好被恶意攻击的准备,防止自增ID的值,被全部耗光。
(6)跳转用301还是302
301是永久重定向,302是临时重定向。短地址一经生成就不会变化,所以用301是符合http语义的。同时对服务器压力也会有一定减少。
但是如果使用了301,我们就无法统计到短地址被点击的次数了。而这个点击次数是一个非常有意思的大数据分析数据源。
能够分析出的东西非常非常多。所以选择302虽然会增加服务器压力,但是我想是一个更好的选择。
分布式一致性:
CAP原理:
一个分布式系统最多只能同时满足 一致性(Consistency),可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
场景:
领导者选举(leader election):进程对leader达成一致;
互斥(mutual exclusion):进程对进入临界区的进程达成一致;
原子广播(atomic broadcast):进程对消息传递(delivery)顺序达成一致。
强一致性算法:
raft共识算法(consensus algorithm)
zab是对Paxos底层算法的工业实现
raft:
raft会先选举出leader,leader完全负责replicated log的管理。leader负责接受所有客户端更新请求,然后复制到follower节点,并在“安全”的时候执行这些请求。如果leader故障,followes会重新选举出新的leader。
状态:
leader
follower
candidate
过程:flower->candidata->leader,leader会有任期term
日志提交:
首先由leader即节点a接收到,并且节点a写入一条日志。由于这条日志还没被其他任何节点接收,所以它的状态是uncommitted。
为了提交这条日志,Leader会将这条日志通过心跳消息复制给其他的Follower节点:
一旦有大多数节点成功写入这条日志,那么Leader节点的这条日志状态就会更新为committed状态,并且值更新为5:
选举超时:
为了防止3个节点(假设集群由3个节点组成)同时发起投票,会给每个节点分配一个随机的选举超时时间(Election Timeout),即从Follower状态成为Candidate状态需要等待的时间。
在这个时间内,节点必须等待,不能成为Candidate状态。如下图所示,节点C优先成为Candidate,而节点A和B还在等待中:
心跳超时
leader节点会固定间隔时间向两个Follower节点A和C发送心跳消息,这个固定间隔时间被称为heartbeat timeout。
重新选举:
选举过程中,如果Leader节点出现故障,就会触发重新选举。如下图所示,Leader节点B故障(灰色),这时候节点A和C就会等待一个随机时间(选举超时),谁等待的时候更短,谁就先成为Candidate,然后向其他节点发送投票请求:
网络分区:
节点A和B在一个网络分区,节点C、D和E在另一个网络分区,如下图所示,且节点B和节点C分别是两个网络分区中的Leader节点
一个客户端,并且往节点B上发送了一个SET 3,由于网络分区的原因,这个值不能被另一个网络分区中的Leader即节点C拿到,它最多只能被两个节点(节点B和C)感知到,所以它的状态是uncomitted(红色)
另一个客户端准备执行SET 8的操作,由于可以被同一个分区下总计三个节点(节点C、D和E)感知到,3个节点已经符合大多数节点的条件。所以,这个值的状态就是committed
假设网络恢复正常,如下图所示。节点B能感知到C节点这个Leader的存在,它就会从Leader状态退回到Follower状态,并且节点A和B会回滚之前没有提交的日志(SET 3产生的uncommitted日志)。
同时,节点A和B会从新的Leader节点即C节点获取最新的日志(SET 8产生的日志),从而将它们的值更新为8。如此以来,整个集群的5个节点数据完全一致了
一致性Hash算法:
介绍:
对于大量随机的请求或调用,通过一定形式的Hash将他们均匀的散列,从而实现压力的平均化。
问题:
如果将Key作为缓存的Key,对应的Group储存该Key的Value,就可以实现一个分布式的缓存系统
但只要集群的数量N发生变化,之前的所有Hash映射就会全部失效。
解决:
一致性Hash通过构建环状的Hash空间代替线性Hash空间的方法解决了这个问题。
整个Hash空间被构建成一个首尾相接的环,使用一致性Hash时需要进行两次映射。
第一次,给每个节点(集群)计算Hash,然后记录它们的Hash值,这就是它们在环上的位置。
第二次,给每个Key计算Hash,然后沿着顺时针的方向找到环上的第一个节点,就是该Key储存对应的集群。
当节点被删除时,其余节点在环上的映射不会发生改变,只是原来打在对应节点上的Key现在会转移到顺时针方向的下一个节点上去。增加一个节点也是同样的,最终都只有少部分的Key发生了失效。
不过发生节点变动后,整体系统的压力已经不是均衡的了,下文中提到的方法将会解决这个问题。
数据倾斜:
如果节点的数量很少,而hash环空间很大(一般是 0 ~ 2^32),直接进行一致性hash上去,大部分情况下节点在环上的位置会很不均匀,挤在某个很小的区域。
最终对分布式缓存造成的影响就是,集群的每个实例上储存的缓存数据量不一致,会发生严重的数据倾斜。
缓存雪崩
如果每个节点在环上只有一个节点,那么可以想象,当某一集群从环中消失时,它原本所负责的任务将全部交由顺时针方向的下一个集群处理。例如,当group0退出时,它原本所负责的缓存将全部交给group1处理。
这就意味着group1的访问压力会瞬间增大。设想一下,如果group1因为压力过大而崩溃,那么更大的压力又会向group2压过去,最终服务压力就像滚雪球一样越滚越大,最终导致雪崩。
引入虚拟节点
解决上述两个问题最好的办法就是扩展整个环上的节点数量,因此我们引入了虚拟节点的概念。一个实际节点将会映射多个虚拟节点,这样Hash环上的空间分割就会变得均匀。
同时,引入虚拟节点还会使得节点在Hash环上的顺序随机化,这意味着当一个真实节点失效退出后,它原来所承载的压力将会均匀地分散到其他节点上去。
另一种解法:
将环上的空间均匀的映射到一个线性空间,每个节点占用一定的区间,那么数据的映射是均匀的。
问题是原来分配到一个节点的,现在由于区间变小,可能分配到另一个节点上。解决:线性空间失效的那部分平分到其他区间。
服务架构设计:
单机时代-单机模式:
单台服务器Web+DB
1.用户浏览器输入url。
2.DNS服务器域名解析
3.web服务器架构:apache,数据读写用mysql。
数据库每日凌晨备份,保存到本地:
主从复制
定时:写一个linux定时脚本
单机时代-动静分离:
单台服务器Web+DB
Nginx+uwsgi实现动静分离
nginx的location 写root
静态资源开启压缩
方案:
方案一:
最前端nginx做负载均衡,不动静分离,不用项目代码
后面的多台nginx+uwsgi实现动静分离。
优点:适用性强,访问日志可定制
方案二:
最前端nginx做负载均衡和动静分离,需项目代码
后面的多台uwsgi。
优点:架构简单
数据定时备份
单机时代-数据库分离:
web服务器和数据库服务器单独部署
或者数据库服务器用来做从,备份,只读
一定要set global readonly,以防用错库
mysql主从备份不能限速
日志log是否备份、保留多久:每天整库的备份
web服务器资源备份到数据库服务器
数据库每日凌晨备份,保存到web服务器
单机时代-组件分离:
静态资源专门搞一个服务器
方法一:NFS文件挂载到这个服务器
方法二:这个服务器部署nginx,只能访问静态资源
静态服务器使用一级独立域名www.shop-pic.com防止cookie提交
静态服务器可以使用多个二级域名w提高加载速度
静态资源作为NFS,存放在NFS上
数据库每日备份到NFS
优点:
1.多个二级域名,增加浏览器下载速度,浏览器对每个域名请求有并发限制。
2.拆分域名防止cookie提交。默认浏览器保留的cookie访问相同域名时会自动发送。占用带宽
3. html的css文件js文件位置,提高浏览器加载速度
4. 静态资源压缩
5.多个域名需预解析,html5预获取dns,<link rel="dns-prefetch">
web集群-HTTP重定向:
1.使用HTTP重定向302和请求头Location(代码实现),把一部分数据转移到其他节点。
多个下载节点选择
2.数据库使用复制技术做主从
3.NFS使用rsync进行定时备份
4.该架构常见于下载站点,web站点不常见
web集群-DNS轮询:
1.使用DNS轮询实现负载均衡集群,将域名解析为不同ip。
2.无法动态感知web服务器故障,无法快速故障转移
3.常用于和其他集群模式配合,以及智能DNS与其他集群模式配合
web集群-反向代理负载均衡:
1.使用Nginx作为反向代理,依靠proxy模块,实现负载均衡。
2.Nginx支持多种负载均衡算法和健康检测(web服务器是否返回,没返回剔除)
web集群-负载均衡+高可用:
1.引入Keepalived,解决反向代理单点故障,多个Nginx配置一样。
2.DNS解析的公网IP使用VIP,即keepalived的VRRP竞选协议,心跳检测
3.会存在session问题
web集群-session解决:
1.Session解决三大方案:保持ip_hash、复制、共享。
2.使用redis做session共享
web集群-haproxy+keepalived:
1.使用haproxy四层和七层负载均衡替代nginx。
web集群-LVS+keepalived:
1.使用LVS四层和二层负载均衡替代haproxy。
2.二层修改mac地址,四层修改ip和port
web集群-CDN:
1.引入CDN解决静态资源全球访问。
2.降低机房带宽、提高静态资源访问速度
3.一般购买cdn使用
4.调度、内容分发、内容管理、运营管理
web集群-分布式缓存:
1.引入分布式缓存,做数据库读缓存、写缓存(异步写,有高可用问题,用消息队列延迟写到数据库)。
2.使用持久化的Nosql解决大量K/V类型数据的读写问题
web集群-LVS+Haproxy:
1.LVS做四层负载均衡(七层负载性能扛不住)。
2.Haproxy/Nginx做统一七层调度(四层分发给七层、七层分发到集群),四层专注调度,七层专注URL处理、https证书等
3.如果业务扩展,可以使用多组负载均衡
web集群-SOA(量大到集群扛不住):
1.通过SOA进行业务解耦(服务注册中心、商品服务、购物车、结算中心。。。)。
2.使用消息队列作为SOA服务间的消息传递
3.使用注册中心用于服务注册,自动切换集群容量,进行扩容和缩容
4.SOA集群的负载均衡有内部负载均衡提供
5.分服务后分库比较简单
负载均衡:
keepalived是什么及作用?
基于vrrp协议的一款高可用软件
Keepalived的作用是检测服务器的状态,如果有一台web服务器宕机,或工作出现故障,Keepalived将检测到,
并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工作正常后Keepalived
自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务
器。
原理:
master向backend定时(3秒)发IP多播包,告知自己在线
竞选协议来竞选master
VRRP模块做HA健康检测,配置master或backend等信息,多台服务器的keepalived的一台成为master路由器后,将请求转发给内部LVS
LVS模块做NAT转发,配置virtual_server或组,里面包含多个real_server,real_server里设置健康检测方式和权重等
haproxy是什么以及作用?
一个使用C语言编写的自由及开放源代码软件
HAProxy是一个使用C语言编写的自由及开放源代码软件[1],其提供高可用性、负载均衡,以及基于TCP和HTTP
的应用程序代理。
HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。HAProxy运行在当前
的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构
中, 同时可以保护你的web服务器不被暴露到网络上。
HAProxy实现了一种事件驱动, 单一进程模型,此模型支持非常大的并发连接数。多进程或多线程模型受内存限制
、系统调度器限制以及无处不在的锁限制,很少能处理数千并发连接。事件驱动模型因为在有更好的资源和时间管
理的用户空间(User-Space) 实现所有这些任务,所以没有这些问题。此模型的弊端是,在多核系统上,这些程
序通常扩展性较差。这就是为什么他们必须进行优化以 使每个CPU时间片(Cycle)做更多的工作。
包括 GitHub、Bitbucket[3]、Stack Overflow[4]、Reddit、Tumblr、Twitter[5][6]和 Tuenti[7]在内的知
名网站,及亚马逊网络服务系统都使用了HAProxy。
作用:
负载均衡
对比nginx:
七层代理,L4-L7
对session保持支持较多,如cookie
负载均衡算法多,htp请求头、ip、最小连接数、cookie、url、轮询、权重轮询
四层负载均衡和七层的区别:
四层:转发,功能少,性能高
修改报文源Ip、目标IP等,
七层:代理,功能多,能根据url,ip等http协议做定制操作,灵活。
独立的TCP连接到服务器,服务器然后TCP握手到Server端
方案:
四层在前面,七层在后面
LVS:
四层 转发 内核级别 内存和CPU消耗不大,配置简单,能做数据库、web服务器
性能高
调度算法多
无健康检测
Nginx:
七层 代理 应用级别 正则表达式做路由分发
可以负载均衡、web服务器
性能好
缺点:不支持自定义URL检测,对某个url做健康检查
不支持session保持,通过ip_hash解决
缓存:
用户层缓存:
DNS缓存:
本地DNS缓存
local缓存
服务器缓存
浏览器缓存 -- 预获取dns prefetching
# 问题:如果域名对应的ip要更改,要考虑运营商的缓存,做个反向代理
浏览器:
浏览器缓存页面内容
基于最后修改时间的http缓存协商,比如静态页面,有最后一次修改时间,没有修改使用缓存,返回http304
基于标签Etag,适合修改时间经常变动,内容却无变化的情况。浏览器将etag发给服务器,服务器判断etag是否改变
如果无改变,服务器返回304
基于过期时间,Expires、cache-control,为静态资源设置超时时间。
Expires看客户端时间,时间不对会出现马上超时的情况
cache-control给时间周期,根据浏览器本地时间来判断
# 刷新:
回车 所有没有过期的缓存直接使用
F5 刷新按钮 发起请求,没有过期的缓存返回304
ctrl+F5\点击刷新 强制刷新 重新请求缓存
# 问题:
静态资源缓存了30天要刷新怎么办?
改静态资源名字
给js增加时间戳 code.js?2013355 对于不同的时间戳,浏览器会重新请求,类似cdn
代理层:
CDN反向代理缓存
内容分发网络,解决带宽以及用户就近访问的问题,安全,用户请求会被cdn的dns解析器分发给就近、负载轻的节点
调度
缓存
性能优化
安全
web层:
解释器
opcache 操作码缓存 如高级语言的中间码缓存,下次直接执行,python的pyc
web服务器
web服务器缓存,如Nginx的FastCGI缓存,Proxy cache
应用层:
应用服务
local cache 应用本地缓存
动态内容缓存 缓存动态内容输出,开发框架自带
页面静态化 将不常变动的内容缓存,变化内容如价格动态定期获取更新,
数据层:
分布式缓存
redis等,主要读缓存、写缓存也可以,持久化当数据库
数据库
mysql的innodb缓存、myisam缓存
系统层:
操作系统
CPU L1的数据缓存、指令缓存 L2 L3
内存缓存
内存高速缓存、page cache
物理层:
硬盘
disk cache 磁盘缓存,一般关闭
硬件
磁盘阵列缓存,带电源,突然断电也没事,一般开启