可靠性、可扩展性、可维护性

发布时间 2023-03-27 16:36:49作者: RainbowMagic

大多应用程序的开发通常有以下几个模块构成:
数据库:持久化数据,便于下次访问,关系型数据库MySQL。
缓存:缓存复杂热点数据,便于下次快速访问,内存非关系型数据库Redis。
索引:根据关键字检索结果,全文检索引擎Elastic Search
流式处理:持续不断的从数据中读取数据,通过处理生成结果,Flink。
批处理:批量处理大量的数据,定时任务 xxl-job。

通常我们可以直接拿取以上框架直接进行开发,但是由于各框架的设计理念以及应用场景的不同,必须都搞清楚框架之间的差异才能更好的构建系统。

认识数据系统

随着互联网的快速发展,诞生了许多优秀的开源框架,但是各开源框架应用场景不单单只是一种,各框架之间的使用界限开始变得模糊,例如Redis既可以做缓存也可以做消息队列,而Rabbit MQ可以存储一些数据,虽然在大多数商家下不这么使用Rabbit MQ。
其次,由于系统的复杂性,单个框架可能无法满足常的业务需求,需要将业务功能拆分,根据各个组件的特性,各个组件处理单独的任务,多个组件相互配合,从而完成相应业务需求。
如下图所示,API通过组件相关的组件完成具体业务下去,用户通过API访问系统,系统如果是查询操作则会先取缓存中查询,如果缓存命中则直接返回,如果缓存未命中则取数据库查询,查询完毕后保存到缓存中,如果缓存中的数据发生改变则需要将缓存作废或更新。
还有一些应用功能是类似Google、baidu、京东商品的搜索的方式,利用全文检索引擎,通过关键字将结果快速的检索出来。
还有一些功能是一部任务,需要再后台执行,例如发送邮件的功能。
img
在应用的运行的过程中,会出现一些问题,根据,例如当某一服务down掉是优先保证数据一致性还是服务的可用性?当系统发生降级时该如何给用户提供良好的信息?前后端API交互是如何将API设置合理一些?

影响软件系统设计的因素

可靠性

保证可靠性一般需要满足以下几点:

  • 首先得满足业务功能需求,而业务功能是通过系统调研来完成的,调研所知用户需要什么样的功能,我们才开发什么样的功能,如果开发出的业务功能不满足用户需求的话,那么开发出的软件毫无意义,根本不会有人去使用。
  • 其次不能只考虑用户完全正确的操作软件的情况,操作软件错误则崩溃,应道给予一个容错机制,例如用户输入错误则给予提示。
  • 在性能方面满足日常的负载和数据量,如果不满足那么程序允许缓慢,异常的概率会增大,也可能占用服务器大量资源,最终导致服务器宕机。
  • 在安全方面,应对满足恶意请求未经授权访问软件以及恶意攻击等,例如爬虫,跨域攻击,如果不满足这条的话软件则没有安全性,导致泄露用户信息以及服务器提权的方式也时有发生。

错误:可能出现的出错的事情。
容错/弹性:容忍故障的能力。即便是系统故障,仍能保证系统功能不被影响。容错是在特定场景下指定的故障,不然不太显示,例如如果整个地球的服务器全部宕机,那么如果想要保证系统还可以正常运行的话,得需要在宇宙范围内进行系统冗余,花费非常高且不现实。
失效:系统故障,无法为用户提供相应的服务。
故障总会在意想不到的情况下发生,我们没办法将所有的故障情况都枚举出来,因此需要利用软件使得系统故意出现故障来测验系统是否具备健壮性,chaos Monkey就是这种测试的典型例子。

硬件故障

当硬件变多时,随着运行时间变长,硬件肯定会发生故障,研究表明一个硬盘运行时间大概是10 - 50年,只有硬盘变多,那么每天可能有一块硬盘发生故障。可以添加冗余硬件设备来保证硬件的高可用,当硬件发生故障时,快速的将服务切换到冗余设备中,使得服务不受影响,常见的冗余操作有添加备用电源、备用CPU、磁盘RAID等。
硬件与硬件之间通常是相互独立的,一个硬件挂了不代表另一个硬件也挂了,除非它们直接有某种相关性,例如在同一机房的温度变高,致使机房内的所有的机器都发生故障。

软件错误

通常软件与软件之间会存在一种关联关系,比如一些应用程序依赖于gcc,一些应用程序依赖于Nacos,当gcc和Nacos出现错误时,不出意外的,肯定会影响当前我们正在运行的程序,由于各个软件相互依赖,所以当出现软件错误时,修复起来会比较困难。
软件错误在程序运行的时候一般引而不发的状态,当运行一段时间后,触发了某种条件,软件错误才会出现,比如,由于Linux内核的bug,程序会在某一时间点出现错误或是由于开发环境的不同,程序在本地测试运行是正常的,打包上线则会出异常。
软件错误没有什么太好的解决方案,只能依赖于在开发过程中,对软件各依赖进行详细的检查,避免出现依赖冲突的问题亦或是在开发过程中,在可能出现软件错误的地方,打出详细的日志以便于在发生故障时,可以知道软件在执行过程中出现了什么问题,便于找出错误并进行修复,亦或是在软件发布上线时,尽可能的对软件进行详细测试,测试通过之后再部署上线。

人为错误

软件无论是运行还是开发,都是依赖于人的,如果假定人是不可靠的,我们应当以以下方式对系统进行构建:

  • 在编码过程中,应当对以最小出错的方式来构建系统,例如精心拆分微服务,构建API以及设置管理界面,使用户在使用软件的过程中,可以更好的做它希望的事情,而不是搞坏软件。但是如果对用户限制过多,人们会想绕过它,所以我们需要将限制与不限制之间做出很好的平衡。
  • 将容易出错的接口抽出来,部署到与线上环境相同的服务器上进行测试,在测试的过程中可以导入线上服务器的用户数据来保证真实性。这样做的好处是即便系统出现错误也没办法对用户进行影响。
  • 对系统进行完全的测试,从单元测试到全系统模块测试。
  • 当出现人为失误时,应当保证可以快速的恢复。例如系统的回滚机制。
  • 对系统进行监控,当出现错误时,可以更快的发现并找到错误。
  • 对软件的使用进行培训,使得用户可以正确的使用软件。

可扩展性

软件即便是当前运行正常也不代表以后运行正常,当使用软件用户变多时,软件是否可以满足正常运转。可扩展性是用于描述系统应对出现负载增加时的属于。

描述负载

首先需要选择负载参数,跟负载参数才能更好的描述负载信息,常见的负载参数有缓存命中率、请求发送次数、同一时间用户在线数等等。
已推特为例:

  • 发送推特消息时,用户将消息推送到所有的关注者,平均每秒大概需要4.6k个请求。
  • 主页时间线浏览时,平均300k个请求查看关注者的信息。
    每个用户都有许多关注者和被关注者,当用户发送消息时,需要将消息推送到所有被关注者中取,而用户平时浏览信息时需要拉取所有的关注者的信息。、
    推特大概有以下两种解决方案:
  1. 分为三张表,用户关注信息、消息、用户表。用户发送的信息统一存储到消息表中,当前登录用户可以根据关注信息表中得到的关注用户id,根据id取消息表拉取阈值相关的消息,最后根据时间线排序即可。
    img

可维护性