为什么要分层
每一层去解决每一层的问题,对应不同的职责。
基于领域模型的分层设计更难一些。
分层模型的演进
分层模型V1.0时代-Servlet JSP 时代
- Servlet+Tomcat容器完成Web接入
- 使用JavaBean+JDBC完成数据的接入
- 使用JSP完成页面展示
劣势
V1.0这种分层模型有弊端,那就是做不到前后端分离,在web处理上只要doget\dopost。
分层模型V1.0时代-MVC分层设计
基于mvc的分层设计,出现了很多分层框架,比如ssh、mvc......
SSH框架
SSH就是由三个组件
我们至始到终是针对一个模型的业务对象去做的。把它从业务模型对象转换成数据模型。而这个业务模型对象的逻辑处理,封装都在业务逻辑层做相应的处理。
- 表示层:web的接入
- 业务逻辑层:web中的请求流转到业务逻辑层通过Spring去做一些处理
- 数据持久层:做业务逻辑的时候一旦需要数据持久化的时候,就通过数据持久层的Hibernate数据源获取数据,把一个领域模型转换成数据模型。
劣势
- Hibernate 自动生成 SQL 语句,这简化了开发,但开发者失去了对 SQL 的直接控制权。当需要进行复杂查询优化或使用数据库特定功能(如存储过程、高级索引)时,Hibernate 可能生成效率不高的 SQL,难以进行深度调优。
- 对于大量的数据更新、插入或删除操作,Hibernate 的缓存机制和对象状态管理会消耗大量内存,并且缺乏高效的批处理支持,性能通常不如直接使用 JDBC 或 MyBatis
- 表示层还在使用jsp,仍然做不到前后端分离
这些劣势在一定程度上催生了以 Spring、Spring MVC 和 MyBatis 为核心的 SSM 框架组合的流行.
SSM框架
- SpringMvc取代Struts,更好的做前后端分离,前端的url请求可以更好的通过get\post协议解析到后端具体的Controller方法中去
- Mybatis通过xml配置文件实现了半自动化sql,取代了Hibernate。
- Spring强大到增加了配置注解、数据源、声明式事务处理
分层模型2.0时代-SpringBoot all in one
弊端
- 解决了单一应用内的软件分层,却没有解决整体应用的分层
- 单一应用性能瓶颈,无法支撑亿级流量
- 团队协作问题
针对2.0时代的弊端,我们引入了分布式分层V3.0
分层模型V3.0时代-分布式分层
- 水平扩展:一台服务器上跑了以一个应用,我现在扩展服务器节点,我整体系统性能会上去,应用不被影响。当某层成为瓶颈时,通过增加该层的机器数量(水平扩展)来提升其处理能力。
- 负载均衡:其实就是nginx处理请求,平均分发到每台服务器上去。
- 高可用:服务器挂几个应用仍然不受影响,仍然能运行。
- 数据一致性:你架构从单体变到分布式的时候,数据也要不被影响。
亿级流量平台分层架构
在分布式架构下,利用好分层模型演进过程中的一些思想去构造一个亿级流量平台。
单一应用的分层架构我们讲到了MVC分层设计,我们可以用这个思想去解决分布式应用中的架构设计问题。
分布式分层--WEB概念层
分布式分层--业务概念层
业务架构师通常专注在业务服务模块的划分工作。
分布式分层--数据访问及存储层
分布式分层的这种理念与思想,归根结底是为了让:我们的应用能够跑在廉价的硬件设备上。
访问层架构设计-lvs接入系统
LVS的三种不同模式
-
LVS/NAT
- 这种模式有LVS性能瓶颈!
-
LVS-DR(企业级用的最多的一种模式)
后端服务集群在响应的时候没有LVS的概念,直接发给了客户端app。这样做就可以免去LVS系统接入的性能损耗。
-
LVS/TUN
这种模式就是把客户端的源ip也打包起来,到了后端服务器集群进行二次解析。
》
移动端的请求会先去DNS寻址,找到公网ip。通过公网ip找到LVS接入系统,从而将不同请求分发到机房的不同服务器上的不同Tomcat服务集群。请求到了Tomcat做了后端的逻辑处理,响应又会返回到LVS,返回到移动端。
访问层架构设计Nginx
职责分类:
- 接入层Nginx
- 职责(做的工作和业务无关)
- 请求解析
- 请求业务路由
- 业务负载均衡
- 响应压缩
- 职责(做的工作和业务无关)
- 应用层Nginx
- 职责(做的工作和业务有关)
- 应用负载均衡
- 缓存调度
- 授权认证
- 业务逻辑
- 业务限流
- 业务降级
- 职责(做的工作和业务有关)
Nginx 功能
反向代理
反向代理后端服务器集群
当我访问localhost:7777/static/index.html的时候,它会自动轮询访问localhost:8088/static/index.html和localhost:8099/static/index.html两个页面。
动静分离
在现代化软件开发中,大部分分都采用动静分离。动态资源通过代理到后端服务访问,静态资源放在CDN上,请求访问resource路径的时候就去CDN指定url访问。
缓存节点
我们可以通过nginx把经常访问的页面缓存起来,下次访问的时候就不用到后端来请求资源了。我们可以设置一个缓存期效给用户使用。
API网关层设计
分布式会话管理(网关层最重要的一个东西)
虽然我们在用同一个浏览器上网,上同一个网站,但连接的是不同的http请求,每一次都是独立的新的Http请求,这是因为Http请求具有无状态性。因此引入了session会话管理机制,记住用户每一次的登录状态,下次保留登录信息,保存会话状态。
分布式会话管理
区别于传统的依赖web server session的会话管理状态,需要引入集中的会话存储器,用于鉴别分布式状态下的BS端之间的会话标识。
会话管理的几种方式
-
cookie并非安全,被劫持概率高
-
token在现在前后端分离架构中用的比较多,凭证也会被劫持,伪造请求
-
Https请求防泄漏(这个最安全 )
-
风控主动失效以及过期机制
-
Session会话管理一般都放在Redis里或者数据库里
接入层控制
QS:接入层为什么需要控制?控制的是什么呢?
通过API网关接入层控制程序入口处需要实现的逻辑,包括:
- 身份验证
- 通过会话管理获取登录用户凭证
- 通过用户凭证获取到用户身份信息
- 验证对应URL是否被对应身份的用户访问
- 流量控制
- 对应的URL的流量是否可以承载,若不能,限流
- 对应服务分级的流量是否可以承载,若不能,限流。
- 对应整个系统的总流量是否可以承载,若不能,限流。
- 路由服务、
- 根据对应的url的规则找到响应服务
- 判定服务状态,做服务路由调用
- 记录调试
- 切面打印日志调试信息
- 切面打印cat监控
- 统计信息
服务调用及聚合
API网关通过了接入层控制并路由后进入核心的服务调用环节,通过对后端服务的调用并聚合服务输出的数据返回访问层。
分类:
- 重接入:SpringMvc+dubbo
- 轻接入:SpringCloud Zuul
之所以说这是重接入,是因为service层的逻辑代码都会聚合到Controller层返回,。一次请求,所有结果都聚合在Controller层返回。
轻接入其实就是用网关把请求路由出去,不管业务处理、不管聚合。
核心服务层设计
核心服务层设计之服务通信
服务&微服务
什么是服务?
传统的服务其实就是基于一个进程、我将我所有的业务逻辑放在一个jar包、一个war包去做一个单进程的部署。我们对这个进程进行简单的垂直拆分会在这个服务里做服务分层:
传统服务的缺点:
- 所有服务部署耦合在一起
- 隔离性弱,互相影响
- 部署臃肿
- 开发维护困难
微服务形态
我们把原本的单进程应用拆分成了多个服务应用:
把一个大的jar包根据业务领域的不同拆分成多个jar包,部署到多个服务器。【其实就是以业务为边界进行拆分】
微服务优点
- 服务高内聚低耦合
- 隔离性强,不会互相影响
- 单独部署
- 独立开发
微服务要解决的问题
- 服务治理
- 服务调用通信
- 健康管理
- 限流熔断
- 数据一致性
- 调用性能
- 研发流程,调试,部署
核心服务层设计之Dubbo服务治理(一)
Dubbo是用来解决微服务内服务治理问题轻量级开源Java RPC框架。
服务治理的功能
- 服务提供者注册服务
- 服务消费者获取服务,并通过负载均衡策略选择服务提供者
- 动态增减服务提供者和服务消费者
- 服务监控
- 服务限流
- 服务降级
- 高容错
- 定制化开发
核心服务层设计之异步化消息服务
消息中间件异步化的好处:
不会阻塞原来的业务
服务调用之间解耦,无需互相关注感知
商品交易服务无需感知交易服务的逻辑,只需要监听异步消息中间件即可。 商品销量的变化不需要添加在商品交易服务的逻辑里。
消息服务分类
核心服务层设计之调度、池化
任务调度
应用场景:
- 业务跑批轮训等待处理
- 失败异常重试
- 定时处理任务
实现方式
单机调度方式及实现
- Timer定时器机制
- 缺点:不能用于多线程,只能用于单线程
- ScheduledExecutor
- 内置了多线程,可以并发执行。
- Quartz (常用)
- 任务+调度
分布式调度及实现方式
Quartz分布式版本
有很多个机器都分布了这个形态:
比如有两个Quartz:
我们现在要做到:
- 两台机器的时间是同步对称的
- 代码部署也要对称
- 需要中间层同步竞争锁(分布式环境下的竞争锁)
Elastic-Job分布式版本
池化技术(一)
池化技术是用来减少系统消耗,提升系统性能的。
池化技术分为:
- 对象池
- 连接池
简单的说,池化技术就是通过复用来提升性能。
常用的连接池
没有线程池的时候,你每次做一件事情的时候都要新创建一个线程,工作模式如下:
有了线程池的话,主程序就会去申请执行线程池里的线程。被执行的线程就会从idle状态转换为busy状态,被申请的线程就会去执行对应的任务。
池化技术(二)
Java线程池逻辑
概念
- 核心线程数大小=最小线程数大小
- 线程池里初始化状态为idle的线程数量
- 最大线程数大小
- 其实就是最多有多少个状态为idle的线程
- 等待队列长度
- 当主程序提交任务的时候发现线程池里已经没有状态为idle的线程可用的时候就会进入等待队列
- 拒绝策略
- 线程池里的线程数满了,这个时候主程序提交任务就会被拒绝,没有多余的idle线程执行提交过来的任务
- idle等待时间
- 如果线程的idle时间过长就会被释放。
总结
Java线程池的策略是:首先使用核心线程数,然后使用等待队列,当非busy的核心线程会去取等待队列里的任务,直到等待队列里的任务被塞满了,这个时候开线程最大线程数。最大线程数达到了就执行拒绝策略,当线程池里的idle线程超时超过了设置的时间的时候就会被释放。
核心服务层架构设计之缓存、隔离
缓存技术(一)
设计原则
- 将数据写入、读取速度更快的存储(设备)
- 将数据缓存到离应用最近的位置
- 将数据缓存到离用户最近的位置
缓存分类
CDN缓存
就是把一些静态资源、页面进行CDN缓存,缓存到CDN服务器上。
反向代理缓存
一般做文件级别的缓存
分布式cache(Redis)
如何缓存
- 实时写入
- 异步写入
- 读取时实时写入
- 读取时异步写入
其实上面这种方案容易导致读写不一致的情况,我们可以升级一下:
但上面这种方案也不是完全可以保证读写一致,在升级:
缓存必须设置失效时间!
缓存服务器里的一些冷数据不经常访问,需要设置失效时间进行清除!
多级缓存
隔离术
隔离是指将系统或资源分隔开,系统隔离是为了在系统发生内部故障的时候限定传播范围、影响范围,即发生故障不会出现滚雪球效应。从而保证只有出现问题的服务不可用,其他服务可用。
常见隔离维度
微服务拆分属于进程拆分。
队列术
队列在数据结构中是一种线性表,从一端插入数据,然后从另一端删除数据。
流量削峰
- 排队有时候比并发效率更高
- 排队可以控制并发流量涌入
数据存储及接入层
什么是数据存储
数据的分类
数据存储的分类
数据存储如何选择
常用的数据存储中间件
关系型与非关系型数据库区别
关系型数据库其实就是把数据模型投射到表上面去,一张张表的关系就是数据模型之间的关系。
代理访问
未来做数据库拆分,垂直拆分,水平拆分的时候要用到数据库代理访问。
常见的被代理对象
- 后端服务器,如Nginx反向代理
- 数据库服务器,如Mycat代理
- 缓存服务器,如Twemproxy代理
监控、限流、降级
监控
为什么要监控?
通常监控的硬件指标
- CPU空闲时间
- 内存占用
- 等待IO返回时间-越少越好
- 网络带宽-不要被打满最好
通常监控的软件指标
- CPU负载
- 新生代清理次数
- 新生代GC时间
- 年老代清理次数
- 年老代GC时间
限流
限流的维度
限流算法原理
-
限制并发数
-
令牌桶算法
-
漏桶算法
降级
怎么降级
- 关闭接口并设置默认返回
- 降级逻辑