Web技术浅聊
技术演变
前后不分离
早些年,页面的代码由后端动态生成,前后端存放在一个服务器应用内。由于服务器需要去生成前端代码,而且是单机架构,所以系统的QPS很低,CPU,内存,带宽,磁盘等很容易成为性能瓶颈。
- 数据库和应用部署在一台服务器上:维护和部署成本很低,CPU,内存,带宽,磁盘容易性能成为瓶颈
- 数据库和应用分开部署:提升了性能和安全性,但是带宽和QPS依旧有优化的余地。
前后分离
Ajax和jQuery的诞生,使得前端代码的生成可以尽可能的交给浏览器。前后分离部署,后端不在负责生成前端代码,只返回必要数据,后端的带宽问题和QPS都有得到改善。
前后分离,后端接口命名不规范导致前后联调困难,于是RESTful-API规范出现,如:
- GET:获取
- POST:新增加
- PUT:修改
- DELETE:删除
集群部署
即使前后分离,但是随着流量增大,单机部署后台依旧难以应对。某些接口并发量较大时,单点部署的结果要么响应慢,要么宕机掉线,于是集群部署出现了。
- 请求分发器:分发请求到各个节点
- 负债均衡:通过算法,根据节点实际情况分发流量。
分布式
通过拆分模块,将系统拆分成多个模块部署。如对于某些业务的接口流量很大,只要需要将流量大的拆分出来单独开发集群部署。这样做提高了吞吐量,同时也提高了容错。
SOA
拆分模块,为了解决多个模块内重复开发相同功能的问题,于是系统间频繁相互调用,这导致了系统耦合度非常高。于是有了SOA(面向服务架构),SOA通过EJB(企业服务总线)完成了中心化的结构,从而服务间的降低耦合度,但是EJB可能会成为系统的瓶颈。
微服务
在SOA基础上剔除了EJB,去中心化,同时根据职责实现更加细粒度的拆分,如拆分成注册中心,配置中心,网关等。
云原生
云环境提供微服务等组件,开发人员只关注写代码就行。(看了一些资料还是没看明白)
过渡阶段技术演变
缓存
可以解决数据库的性能瓶颈问题,实现读缓存,写合并。主要分为本地缓存(Ehcache,Caffeine),分布式缓存(MemCache,Redis)
消息中间件
缓存可以分担读压力,对于某些场景可以实现写合并降低写压力,但是合并写请求设计相对复杂,而且并不是所有场景都适用。消息中间件可以实现通过削峰填谷将高峰期流量放到低谷处理,降低高峰期的流量保证系统的安全的同时充分利用机器资源。
中间件功能:
- 同步转异步:异步化提升性能
- 模块解耦合:降低耦合度
- 流量削峰
数据库集群
MQ能够削峰,但是还是没有从根本上解决问题,流量增大,DB读压力依旧不小,于是有了数据库集群。通过是写读写分离提高性能。
主从架构下,为了保证数据库一致性,可以适用Canal来降低同步延迟。
分库分表
集群解决了访问压力,随着数据量的增大,存储问题和检索速度问题逐渐浮现,于是有了分库分表的解决方案。对于不支持分库分表的数据库,通过引入Sharding-sphere,MyCat这些中间件解决问题。随着技术发展,也有一些天然就支持分库分表的数据库出现。
多级负债均衡
网关(如Nginx)接受所有流量,随着流量增大,网关成了瓶颈,一些流量层接入层的方案出现,如实现Nginx集群搭建等。
多样化存储
随着数据量以及流量的变化,只用关系型数据库存储数据就显得力不从心,于是有了文档数据库,时序数据库,对象/文件存储,大数据套件等。
容器化
微服务数量越来越多,硬件成本以及维护变高,于是虚拟化容器技术出现,如docker。虚拟化容器技术解决了资源问题,但是部署依旧麻烦,于是有了K8S,解决了容器自动部署,扩展等问题。部署解决了,但是代码管理和打包还是麻烦,于是有了Jenkins,其通过和git的触发回调配合,完成了自动拉取最新代码生成镜像。
整个流程称为CI/CD:Git+Jenkins+Docker+K8S配合实现。大型系统通过CI/CD技术能提高团队效率。
部署相关概念
- 灰度发布
- 蓝绿发布
- 金丝雀发布
- 分批发布
- 滚动升级
- 版本回退
集群
集群解决单点故障最有效手段,保证高可用不可或缺的手段。
优势
- 高可用:解决单点故障
- 吞吐量:比单点更强大吞吐能力
- 扩展性:系统更灵活
- 性价比:多台廉价机器组合得到的性能堪比单台高昂价格同性能机器
集群分类
- 逻辑处理集群:无状态,主要是处理运算 需要请求分发器(实现负载均衡)
- 数据存储集群:存储数据
逻辑处理集群
- 分发器:分发请求到集群节点
- 负载均衡:如何分发请求,保证不同性能节点均衡承载流量。
为什么分发器性能那么高?因为分发器本身不处理请求,而且nginx底层采用多路复用模型,负责分发线程极少,从而能够处理的请求大
双热机机制:对nginx这类组件实现热备技术,避免单点故障,可以借助keepalived等来实现。
数据存储集群
需要考虑各个节点间的数据一致性和完整性。
主从模式
数据同步方案:同步,半同步,异步
可以用于实现读写分离,提高数据库吞吐量。
故障转移:主节点出现故障时,切换主节点。
像MySQL这样的数据库没有故障转移能力,发生故障时需要手动进行故障转移,或者使用第三方工具完成转移。故障转移机制,有两种方案(通常组合使用)可选:
- 探测模式:负责转移故障节点,定时探测其他节点是否存活。
- 上报模式:其他节点主动向主持故障转移节点上报是否存活。
级联复制
当从节点较多的时候,同步数据会对主节点磁盘,CPU,网络带宽都带来不小压力,于是有了级联复制模式,用于一主多从。主节点只向一个从节点同步数据,由该节点向其他节点同步数据。这样的方式缺点也很明显:数据延迟,级联故障。一般很少用级联复制模式。
多主热备
各个节点间互为主从,请求可以落在任意主节点上,写性能翻倍。大部分技术栈都只允许配置一主一丛,实现n主可以通过环型多主集群的方式搭建,这种方式延迟也非常高,所以一般双主就够了。
分片式集群
分布式存储技术,也叫分片式存储。
写入时能根据规则确定写入的节点,读取时同样根据规则到指定节点读取。
数据路由的重点在分片路由算法 以及分片路由的键,通常来说有范围分发,Hash取模,Hash槽等待,如Redis-cluster集群就通过CRC16算法计算Hash槽。
分片式还需要根据数据库的特性去考虑问题,对如何聚合,如何解决事务,如何解决多表联查等待这样的问题。
分片集群的类型:
- 中心化分片集群 中心节点负责路由规则,数据节点管理工作,以及数据分发请求,通常不存储数据。 数据节点存储数据。 如:MySQL官方不支持分片存储,可以通过MyCat中间件搭建分片式存储。
- 去中心化分片集群 Redis-Cluster采用了哈希槽(16384个)均分给每个节点,然后通过crc将key计算和取模到哈希槽。集群内所有节点都具备这样的计算计算能力,请求落到某个节点时,发现数据不在该节点会返回一个重定向的错误,通常来说这个计算工作在客户端就完成了。 Sharding-JDBC是MySQL的去中心化分片集群的一种解决方案。
分布式理论
CAP理论
- C:Consistency 要求同一时刻内,集群中所有节点数据完全一致。保证数据完整性和准确性。也就是用户写入数据后,下一次读请求,无论落到那个节点返回数据必须是最新的数据。
- A:Availability 也就是所任何时刻,分布式系统都能响应请求。如A节点出现错误,但是不影响整个系统对请求的处理。
- P:Partition tolerance 保证即使分布式系统因为网络原因或其他原因出现分区,各个分区也要能够单独对外提供服务。新节点加入和老节点离开,都可以视为分区出现,这里要求分布式系统能够动态处理这个问题。
为什么不能同时拥有CA?
保证A:要求部分节点出现故障时,将请求转移到健康节点处理,要感知到故障节点,系统内需要一个健康检查机制。
保证C:保证一次写都要完全写入所有节点,也就是上锁写,但是由于网络问题,无论是写还是撤销写都可能存在问题,也就是说要重试,这个时候其他读操作到来也许会出现超时的情况,从而可用性无法保证。
可用看出在分布式集群情况下无法实现CA,但是单个节点是可用实现CA的。
不能同时拥有CA,那就是只能3选2了,要么CP,要么AP。事实上,网络是不可控因素,P也是设计分布式系统时必须要考虑的问题,至于A和C需要根据具体场景进行考虑。
- 对于速度更重要的场景,可用考虑AP,如主从的异步复制
- 对于一致性要求高的场景,可用考虑CP,如主从的同步复制。
大部分分布式系统不需要CAP
分布式系统要么存储性质(Redis,Mysql,注册中心,配置中心,MQ)的,要么是非存储性质的,前者需要考虑CA还是CP,后者不存储数据仅仅负责计算的工作所以不需要CAP理论。
BASE理论
全称Basically Available、Soft state、Eventually consistent
- BA : 基本可用 系统出现故障时,抛弃部分可用,保证其他功能可用==>基本可用 如:熔断/限流,降级这些。
- S:软状态 允许系统存在中间状态,中间状态不影响整体可用,如事务使得sql在执行时有一个中间状态:未提交,这个中间状态不会影响其他事务读取数据,不影响整体可用。比如在执行备份的时候,开启一个事务,这个时候不阻塞其他事务执行,保证可用。
- E:最终一致性 一直处于软状态,最终会影响可用性。还是事务举例,事务不提交一直存在,也会有影响。最终一致性保证,保证中间状态在一定时间后会变成最终状态。比如MySQL和Redis的一致性问题,我们可用抛弃强一致性,保证可用性,允许中间状态的存在,从而提高性能。
BASE理论只是ACID的替代品。在ACID中的C要求一个状态转换为另外一个合理状态,ACID不接受中间状态,而BASE理论提出接受中间状态。
BASE理论中的C和CAP理论的C不是一个概念。同时BASE中的A和CAP理论中的A概念也不一样,CAP中的A强调的是节点失效,不影响整体,更多是针对集群,BASE则更多强调分片存储。
为什么可以说BASE是对CAP的补充?CAP是无法同时实现的,但是可以退而求其次保证最终一致性。
一致性模型
- CAP的C:任何时间完全一致,数据强一致性
- BASE的C:最终一致性
一致性可以分为3种:强一致性,弱一致性,最终一致性。
强一致性
数据强一致性
针对的是单个数据分布在不同节点
- 写请求:要写完所有节点才算成功
- 读请求:保证读取任意节点数据都一样。类似于事务,保证在所有节点读取到数据都一致,可以通过MVCC来实现,当然可以通过读写锁方式完成。
状态强一致性
针对分片存储的情况,同时操作多个数据,数据落于不同节点上==>如分布式事务。某个计算操作需要针对两个数据库完成操作,保证同时成功和同时失败。具体实现可以参考分布式事务实现方式,如XA方式等。
弱一致性
对数据一致性要求很低,适用于可靠性较好的场景或者对一致性需求可有可无的场景。
同样也分为数据弱一致性 ,状态弱一致性。Redis提供了事务机制,但是属于弱一致性,不会主动回滚,这个时候事务无论成功或者失败都会写入到数据库中,操作失败,数据库状态将处于一个不合法状态。
cpu架构上的一致性
- 顺序一致性:指令执行要一致
- 因果一致性:指令执行可以不一致,但是结果一致
- 会话一致性:保证用户会话在任何机器上都一致
最终一致性
允许出现不一致的情况,经过有限时间后,能够保证一致。数据库主从架构的异步复制和半同步复制就是最终一致性。
客户端一致性
客户端在写入数据到某个分布式系统后,分布式系统要保证下一次读写无论落到那个节点都是写入的数据最新值(不能是旧值)。
从客户端角度提出的一致性策略:
- 写跟随读策略:写跟随读,写的版本必须基于当前读到版本之后
- 管道水机访问存储策略:客户端请求落在同一个节点,具体模型可以分为 单调读一致性:每次读数据版本不能低于以及读写的版本 单调写一致性:每次写操作版本号前都要能基于上一个版本号基础上,如获取到写版本号为3,写入数据检查数据版本号是否为2。 读己所写一致性:读操作版本号必须大于等于先前写的版本号。
- 读副本选择策略:选择那个副本(节点)数据读取 CDN技术:选择比较近的节点 负载均衡:选择压力小的节点 根据数据新鲜程度:尽量保证读取最新数据
- 写提交级别策略:写操作提交时机 同步提交,异步提交,多数提交(写入多数后就提交)