ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

架构权衡评估方法(ATAM):如何评估一个系统的质量

2021-11-07 13:58:33  阅读:217  来源: 互联网

标签:场景 ATAM OrderBO 权衡 系统 void orderBO public 评估


欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习


1 质量属性

在系统设计和开发过程中,我们比较容易关注系统的功能维度,例如有没有实现预期功能,输入参数和输出参数是否匹配等等,这是比较容易衡量的。

但是我们不能就此止步,因为满足功能是系统的基本要求,还需要关注系统的非功能维度,例如系统性能是否优秀,代码可扩展性如何,出现异常是否可以自动降级等等,这些指标决定了系统能否提供高质量的服务。

我之前在文章《结构化思维如何指导技术系统优化》提到了三个质量属性:高性能、高可用、高扩展。本文我们可以充实为六个质量属性:性能、可用性、可修改性、可靠性、安全性、易用性。


01 质量属性.jpg


1.1 性能

1.1.1 如何定义

性能有两个定义维度:第一个维度是单位时间内可以做多少事情,第二个维度是做完单位数量的事情需要多少时间,常用以下参数进行量化:

QPS:每秒处理请求数

TPS:每秒处理事务数

并发数:同一时刻处理请求数/事务数

响应时间:系统对请求做出响应的时间

并发数 = QPS x RT


1.1.2 如何提升

提升性能可以从两个维度思考,第一个维度是时间维度,从事前、事中、事后三个时间节点进行优化。事前是指在访问最开始就拒绝无效流量。事中可以使用提高并发度(并发编程),增加资源(服务器、分布式缓存、读写分离,分库分表),减少交互(批量请求)等方案。事后是指数据分析需求可以放到离线数据中心进行,不要放在主应用和主数据库进行。

第二个维度是层次维度,每一层都可以进行优化。系统架构一般分为数据层、缓存层、服务层、网关层、客户端、代理层,每一层都可以进行优化,每一层都可以按照事前、事中、事后进行优化,而不是一提到优化就是加缓存,应该更加全面地思考。


10 总体分层.jpg


1.2 可用性

1.2.1 如何定义

可用性是指系统正常运行时间占总运行时间比例,业界常用X个9指标进行量化,例如可用性达到5个9,那么全年系统不可用时间只有5分钟:


02 可用性.jpg


1.2.2 如何提升

(1) 非线性

我们从另一个概念理解可用性:非线性,这个概念在生活中无处不在。

假设要赶早上8点钟的火车,如果6:30出发可以在7:00到达车站,所以得到一个结论:只要30分钟就可以到达车站。

如果早上睡晚一点7:15出发,那么按照预期7:45可以到达车站。但是最可能的结果是错过这趟火车。因为正好遇上早高峰导致至少需要1个小时才能到达车站。

我们再分析一个互联网秒杀场景。假设秒杀系统当每秒30个请求时,响应时间是10毫秒。如果按照线性思维可以做出如下设计:

每秒30个访问量响应时间10毫秒

每秒300个访问量响应时间100毫秒

每秒3000个访问量响应时间1000毫秒

如果按照这个思路做系统设计将会发生重大错误。因为当每秒3000个访问量发生时,响应时间可能不是1000毫秒,而是可能直接导致系统崩溃。

这就是非线性,事物不是简单线性叠加关系,当达到某个临界值时会造成一种截然不同的结果。


(2) 提升策略

冗余 + 自动故障转移

最基本的冗余策略就是主从模式。原理是准备两台机器,部署了同一份代码,在功能层面是相同的,都可以对外提供相同的服务。

一台机器启动提供服务,这就是主服务器。另一台机器启动在一旁待命,不提供服务,随时监听主服务器的状态,这就是从服务器。当发现主服务器出现故障时,从服务器立刻替换主服务器,继续为用户提供服务。

自动故障转移策略是指当主系统发生异常时,应该可以自动探测到异常,并自动切换为备用系统。不应该只依靠人工去切换成,否则故障处理时间会显著增加。

降级策略

当系统遇到无法承受的压力时,选择暂时关闭一些非关键的功能,或者延时提供一些功能,把此刻所有的资源都提供给现在最关键的服务。

在秒杀场景中下订单就是最核心最关键的功能。当系统压力将要到达临界值时,可以暂时先关闭一些非核心功能如查询功能。

还有一种降级策略,当系统依赖的下游服务出现错误,甚至已经完全不可用了,那么此时就不能再调用这个下游服务了,否则可能导致雪崩。所以直接返回兜底方案,把下游服务直接降级。

延时策略

用户下订单成功后就需要进行支付。假设秒杀系统下订单每秒访问量是3000,有没有必要将每秒3000次访问量压力传递给支付服务器?

答案是没有必要。因为用户秒杀成功后可以稍晚付款,例如可以跳转到一个支付页面,提示用户只要在10分钟内支付完成即可。

这样流量就被分摊至几分钟,有效保护了系统。技术架构还可以使用消息队列做缓冲,让下游系统根据处理能力拉取消息。

隔离策略

物理隔离:应用分别部署在不同物理机、不同机房,资源之间不会互相影响。

线程隔离:不同类型的请求进行分类,交给不同的线程池处理,当一类请求出现高耗时和异常,不影响另一类请求访问。


1.3 可修改性

1.3.1 如何定义

可修改性是指是否能够以较高的性价比对系统进行变更的能力,可以分为以下四种类型:

可扩展性:系统扩展新构件时对其它构件的影响程度

可维护性:系统修改旧构件时对其它构件的影响程度

结构重组:重新组织构件关系的难易程度

可移植性:在不同硬件平台、编程语言、操作系统间移植的难易程度


1.3.2 如何提升

可修改性最终还是在解决牵一发而动全身的复杂性问题,复杂业务之所以复杂,一个重要原因是涉及角色或者类型较多,如果平铺直叙地进行设计会出现if-else代码块,可读性和可修改性都很低。

我们分析一个下单场景。当前有ABC三种订单类型:A订单价格9折,物流最大重量不能超过9公斤,不支持退款。B订单价格8折,物流最大重量不能超过8公斤,支持退款。C订单价格7折,物流最大重量不能超过7公斤,支持退款。按照需求字面含义平铺直叙地写代码也并不难:

public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;

    @Override
    public void createOrder(OrderBO orderBO) {
        if (null == orderBO) {
            throw new RuntimeException("参数异常");
        }
        if (OrderTypeEnum.isNotValid(orderBO.getType())) {
            throw new RuntimeException("参数异常");
        }
        // A类型订单
        if (OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.9);
            if (orderBO.getWeight() > 9) {
                throw new RuntimeException("超过物流最大重量");
            }
            orderBO.setRefundSupport(Boolean.FALSE);
        }
        // B类型订单
        else if (OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.8);
            if (orderBO.getWeight() > 8) {
                throw new RuntimeException("超过物流最大重量");
            }
            orderBO.setRefundSupport(Boolean.TRUE);
        }
        // C类型订单
        else if (OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.7);
            if (orderBO.getWeight() > 7) {
                throw new RuntimeException("超过物流最大重量");
            }
            orderBO.setRefundSupport(Boolean.TRUE);
        }
        // 保存数据
        OrderDO orderDO = new OrderDO();
        BeanUtils.copyProperties(orderBO, orderDO);
        orderMapper.insert(orderDO);
    }
}

上述代码从功能上完全可以实现业务需求,但是程序员不仅要满足功能,还需要思考代码的可维护性。如果新增一种订单类型,或者新增一个订单属性处理逻辑,那么我们就要在上述逻辑中新增代码,如果处理不慎就会影响原有逻辑。

为了避免牵一发而动全身这种情况,设计模式中的开闭原则要求我们面向新增开放,面向修改关闭,我认为这是设计模式中最重要的一条原则。

需求变化通过扩展,而不是通过修改已有代码实现,这样就保证代码稳定性。扩展也不是随意扩展,因为事先定义了算法,扩展也是根据算法扩展,用抽象构建框架,用实现扩展细节。标准意义的二十三种设计模式说到底最终都是在遵循开闭原则。

如何改变平铺直叙的思考方式?这就要为问题分析加上纵向和横向两个维度,我选择使用分析矩阵方法,其中纵向表示策略,横向表示场景:


05 订单_分析矩阵.jpg


(1) 纵向做隔离

纵向维度表示策略,不同策略在逻辑上和业务上应该是隔离的,本实例包括优惠策略、物流策略和退款策略,策略作为抽象,不同订单类型去扩展这个抽象,策略模式非常适合这种场景。本文详细分析优惠策略,物流策略和退款策略同理。

// 优惠策略
public interface DiscountStrategy {
    public void discount(OrderBO orderBO);
}

// A类型优惠策略
@Component
public class TypeADiscountStrategy implements DiscountStrategy {

    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.9);
    }
}

// B类型优惠策略
@Component
public class TypeBDiscountStrategy implements DiscountStrategy {

    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.8);
    }
}

// C类型优惠策略
@Component
public class TypeCDiscountStrategy implements DiscountStrategy {

    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.7);
    }
}

// 优惠策略工厂
@Component
public class DiscountStrategyFactory implements InitializingBean {
    private Map<String, DiscountStrategy> strategyMap = new HashMap<>();

    @Resource
    private TypeADiscountStrategy typeADiscountStrategy;
    @Resource
    private TypeBDiscountStrategy typeBDiscountStrategy;
    @Resource
    private TypeCDiscountStrategy typeCDiscountStrategy;

    public DiscountStrategy getStrategy(String type) {
        return strategyMap.get(type);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeADiscountStrategy);
        strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBDiscountStrategy);
        strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCDiscountStrategy);
    }
}

// 优惠策略执行
@Component
public class DiscountStrategyExecutor {
    private DiscountStrategyFactory discountStrategyFactory;

    public void discount(OrderBO orderBO) {
        DiscountStrategy discountStrategy = discountStrategyFactory.getStrategy(orderBO.getType());
        if (null == discountStrategy) {
            throw new RuntimeException("无优惠策略");
        }
        discountStrategy.discount(orderBO);
    }
}

(2) 横向做编排

横向维度表示场景,一种订单类型在广义上可以认为是一种业务场景,在场景中将独立的策略进行串联,模板方法设计模式适用于这种场景。

模板方法模式一般使用抽象类定义算法骨架,同时定义一些抽象方法,这些抽象方法延迟到子类实现,这样子类不仅遵守了算法骨架约定,也实现了自己的算法。既保证了规约也兼顾灵活性,这就是用抽象构建框架,用实现扩展细节。

// 创建订单服务
public interface CreateOrderService {
    public void createOrder(OrderBO orderBO);
}

// 抽象创建订单流程
public abstract class AbstractCreateOrderFlow {

    @Resource
    private OrderMapper orderMapper;

    public void createOrder(OrderBO orderBO) {
        // 参数校验
        if (null == orderBO) {
            throw new RuntimeException("参数异常");
        }
        if (OrderTypeEnum.isNotValid(orderBO.getType())) {
            throw new RuntimeException("参数异常");
        }
        // 计算优惠
        discount(orderBO);
        // 计算重量
        weighing(orderBO);
        // 退款支持
        supportRefund(orderBO);
        // 保存数据
        OrderDO orderDO = new OrderDO();
        BeanUtils.copyProperties(orderBO, orderDO);
        orderMapper.insert(orderDO);
    }

    public abstract void discount(OrderBO orderBO);

    public abstract void weighing(OrderBO orderBO);

    public abstract void supportRefund(OrderBO orderBO);
}

// 实现创建订单流程
@Service
public class CreateOrderFlow extends AbstractCreateOrderFlow {

    @Resource
    private DiscountStrategyExecutor discountStrategyExecutor;
    @Resource
    private ExpressStrategyExecutor expressStrategyExecutor;
    @Resource
    private RefundStrategyExecutor refundStrategyExecutor;

    @Override
    public void discount(OrderBO orderBO) {
        discountStrategyExecutor.discount(orderBO);
    }

    @Override
    public void weighing(OrderBO orderBO) {
        expressStrategyExecutor.weighing(orderBO);
    }

    @Override
    public void supportRefund(OrderBO orderBO) {
        refundStrategyExecutor.supportRefund(orderBO);
    }
}

1.4 可靠性

1.4.1 如何定义

可靠性包括容错性和健壮性,系统面对错误输入仍能保证正确输出的能力,可以分为两种类型:系统可靠性和业务可靠性。

系统可靠性是指面对出现基本错误的输入,系统能够识别和拦截,而不是任由其在构件中传递,造成错误数据或者引发系统异常。例如空值引发的空指针异常,不应该出现在系统中。

业务可靠性是指输入参数在基本校验通过的情况下,系统能够进行业务校验,不会引发超出业务预期的输出结果。例如电商系统中的超卖现象,重复创建订单现象都是业务可靠性较低的表现。


1.4.2 如何提升

(1) 拦截

提升可靠性的关键是应该尽早在上层识别并拦截异常数据,阻止其在构件中流动,避免产生系统异常和错误数据,尤其当产生错误数据后,数据修复难度大。

提升系统可靠性可以在服务入口增加判空校验、参数类型校验、范围校验、合法枚举值校验等基本校验,一旦发现异常直接拒绝。

提升业务可靠性可以增强业务校验,例如库存预扣减,活动有效期校验,参与活动次数校验,扣减库存校验,分布式锁控制并发等方案,如果校验规则复杂可以引入规则引擎进行条件组合,不满足业务条件直接拒绝请求。


(2) 告警

如果第一阶段没有将异常输入拦截成功,那么就要在发生异常时及时感知,异常分为系统异常和业务异常。

系统异常是不允许出现的异常,例如空指针,操作数据库失败等异常,一旦出现就要立即告警。

业务异常可以分为以下类型:

业务异常:单位时间出现X次需要告警

延时监控:某指标单位时间内是否变化

数据监控:单位时间数据指标是否正常


1.5 安全性与易用性

安全性是指系统防止非法用户访问的能力,易用性是指系统使用的难易程度,本文不展开论述,下一个章节会再提到。


2 架构评估方法

2.1 三种评估方法

因为涉及到众多变量和场景,所以评估一个复杂技术系统的质量并不是一件容易的事情。业界有以下三种评估方法:

第一是基于问卷的方式,通过问卷调查对系统比较熟悉的相关人员得到结论,这种方式主观性很强。

第二是基于度量的方式,对系统指标完全量化,基于量化指标评价系统,这种方式需要评估者对系统非常熟悉。

第三种是基于场景的方式,筛选出系统的关键场景,根据系统在不同场景中的表现进行评估,这种方式具有一定的主观性,需要评估者对系统较为熟悉,这也是目前较为流行的架构评估方法。

架构权衡评估方法(ATAM)全称是 Architecture Tradeoff Analysis Method,由卡梅隆大学软件工程协会提出,是一种基于场景的架构评估方法,核心是结合质量属性效用树对系统进行评价,确定风险点、敏感点、权衡点,并对系统架构做出决策和折中。

ATAM分为以下步骤,其中123为描述和介绍阶段,456为调查和分析阶段,78为测试阶段,9为报告阶段。


03 ATAM.jpg


2.2 ATAM实例

本文以《结合DDD讲清楚编写技术方案的七大维度》足球运动员信息管理系统为例看看ATAM如何实施。

第一阶段是描述和介绍阶段,首先由架构师向大家介绍什么是ATAM方法,其次由产品经理介绍开发足球运动员信息管理系统商业动机,最后由架构师介绍系统整体架构,例如怎样划分领域,系统分为持久层、缓存层、中间件、业务中台、服务层、网关层、客户端和代理层等等。

第二阶段是调查和分析阶段,不同需求方均提出了相关需求,所涉及质量场景如下:

(1) 在100毫秒内响应用户请求

(2) 主数据库发生故障后,10秒内自动切换至从库

(3) 主机房发生故障后,5分钟内请求重定向至灾备机房

(4) 新增球员比赛和训练指标,开发工作在5人日内完成

(5) 使用包含SSL数字证书的HTTPS访问协议

(6) 球员信息管理界面要求简单易用

(7) 出现异常引导用户至错误页面,不能展示异常栈信息

(8) 对于球员信息配置功能的灵活度尚未达成共识,影响了系统可修改性

(9) 对于球员比赛实时收集响应时间的要求,影响了系统数据存储设计

(10) 主教练提出了训练指标新模式,影响了系统性能和可修改性


根据上述场景生成质量属性效用树,(1)属于性能,(2)(3)属性可用性,(4)属于可修改性,(5)属于安全性,(6)属于易用性,(7)属于可靠性:


04 质量属性效用树.jpg


我们再根据这些场景分析系统的风险点、敏感点、权衡点。风险点是指某些操作会给系统带来隐患和风险,(8)属于风险点。敏感点是指为了实现某个特定质量属性,一个或多个系统组件所具有的特性,(9)属于敏感点。权衡点是指某些操作会影响系统的多个质量属性,(10)属于权衡点。

第三个阶段是测试阶段,根据足球运动员信息管理系统特性,我们首先确定场景优先级,由高到低分别是:性能、可靠性、可修改性、安全性、可用性、易用性。

架构权衡分析方法所谓权衡在此得到了体现,质量属性每个都很重要,但是根据系统特点需要对质量属性有优先级排序,架构设计时需要所有权衡和折中。

确定了优先级之后,我们需要具体阐述针对每个质量属性系统采取了哪些方案,例如提升性能使用了缓存,提升可修改性使用了策略模式,提升可靠性使用了统一异常处理框架等等,具体方案可以参考本文第一章节。

第四个阶段是报告阶段,我们将评估过程和结果都汇总整理成文档,其中包括质量属性效用树、风险点、敏感点、权衡点和每次评估会议纪要,以及最终的架构决策。


3 文章总结

第一系统满足功能性需求是最基本的要求,作为架构师不能就此止步,不仅应该关注功能性需求,还应该关注非功能性需求,质量属性就是衡量系统质量的重要指标。

第二架构评估方法分为基于问卷、基于度量、基于场景的方式,目前业内较为流行的是基于场景的评估方法,ATAM是一种优秀的基于场景评估方法。

第三ATAM以质量属性效用树为核心,帮助架构师识别项目风险点、敏感点、权衡点,指导架构师做出合理架构决策。


4 延伸阅读

《结构化思维如何指导技术系统优化》

《结合DDD讲清楚编写技术方案的七大维度》

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习

标签:场景,ATAM,OrderBO,权衡,系统,void,orderBO,public,评估
来源: https://blog.csdn.net/woshixuye/article/details/121190730

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有