架构设计小心得

项目开发流程:

image-20220303112748431image-20220303112748431
image-20220303113042065image-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个逻辑发号器,分别发尾号为0999的号。
        每发一个号,每个发号器加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,即keepalivedVRRP竞选协议,心跳检测
3.会存在session问题

web集群-session解决:

1.Session解决三大方案:保持ip_hash、复制、共享。
2.使用redissession共享

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 磁盘缓存,一般关闭
硬件
    磁盘阵列缓存,带电源,突然断电也没事,一般开启
posted @ 2022-03-04 17:51  心平万物顺  阅读(71)  评论(0编辑  收藏  举报