可靠性、可扩展性、可维护性
每个应用程序的开发通常有以下几个模块构成:
数据库:持久化数据,便于下次访问,关系型数据库MySQL。
缓存:缓存复杂热点数据,便于下次快速访问,内存非关系型数据库Redis。
索引:根据关键字检索结果,全文检索引擎Elastic Search
流式处理:持续不断的从数据中读取数据,通过处理生成结果,Flink。
批处理:批量处理大量的数据,定时任务 xxl-job。
通常我们可以直接拿取以上框架直接进行开发,但是由于各框架的设计理念以及应用场景的不同,必须都搞清楚框架直接的差异才能更好的构建系统。
认识数据系统
随着互联网的快速发展,诞生了许多优秀的开源框架,但是各开源框架应用场景不单单只是一种,各系统之间的界限开始变得模糊,例如Redis既可以做缓存也可以做消息队列,而Rabbit MQ。
其次,由于系统的复杂性,单个系统可能无法满足常的业务需求,需要将业务功能拆分,根据各个组件的特性,每个组件 处理单独的任务,多个组件一块应用代码拼接起来,从而完成业务需求。
如下图所示,API通过组件相关的组件完成具体业务下去,用户通过API访问系统,系统如果是查询操作则会先取缓存中查询,如果缓存命中则直接返回,如果缓存未命中则取数据库查询,查询完毕后保存到缓存中,如果缓存中的数据发生改变则需要将缓存作废或更新。
还有一些应用功能是类似Google、baidu、京东商品的搜索引擎的方式,通过全文检索将索引查出并返回。
还有一些功能是一部任务,需要再后台执行,例如发送邮件的功能。
在应用的运行的过程中,会出现一些问题,根据,例如当某一服务down掉是优先保证数据一致性还是服务的可用性?当系统发生降级时该如何给用户提供良好的信息?前后端API交互是如何将API设置合理一些?
影响软件系统设计的因素
可靠性
保证可靠性一般需要满足以下几点:
- 首先得满足业务功能需求,而业务功能是通过系统调研来完成的,调研所知用户需���什么样的功能,我们才开发什么样的功能,如果开发出的业务功能不满足用户需求的话,那么开发出的软件毫无意义,根本不会有人去使用。
- 其次不能只考虑用户完全正确的操作软件的情况,操作软件错误则崩溃,应道给予一个容错机制,例如用户输入错误则给予提示。
- 在性能方面满足日常的负载和数据量,如果不满足那么程序允许缓慢,异常的概率会增大,也可能占用服务器大量资源,最终导致服务器宕机。
- 在安全方面,应对满足恶意请求未经授权访问软件以及恶意攻击等,例如爬虫,跨域攻击,如果不满足这条的话软件则没有安全性,导致泄露用户信息以及服务器提权的方式也时有发生。
错误:可能出现的出错的事情。
容错/弹性:容忍故障的能力。即便是系统故障,仍能保证系统功能不被影响。容错是在特定场景下指定的故障,不然不太显示,例如如果整个地球的服务器全部宕机,那么如果想要保证系统还可以正常运行的话,得需要在宇宙范围内进行系统冗余,花费非常高且不现实。
失效:系统故障,无法为用户提供相应的服务。
故障总会在意想不到的情况下发生,我们没办法将所有的故障情况都枚举出来,因此需要利用软件使得系统故意出现故障来测验系统是否具备健壮性,chaos Monkey就是这种测试的典型例子。
硬件故障
当硬件变多时,随着运行时间变长,硬件肯定会发生故障,研究表明一个硬盘运行时间大概是10 - 50年,只有硬盘变多,那么每天可能有一块硬盘发生故障。可以添加冗余硬件设备来保证硬件的高可用,当硬件发生故障时,快速的将服务切换到冗余设备中,使得服务不受影响,常见的冗余操作有添加备用电源、备用CPU、磁盘RAID等。
硬件与硬件之间通常是相互独立的,一个硬件挂了不代表另一个硬件也挂了,除非它们直接有某种相关性,例如在同一机房的温度变高,致使机房内的所有的机器都发生故障。
软件错误
通常软件与软件之间会存在一种关联关系,比如一些应用程序依赖于gcc,一些应用程序依赖于Nacos,当gcc和Nacos出现错误时,不出意外的,肯定会影响当前我们正在运行的程序,由于各个软件相互依赖,所以当出现软件错误时,修复起来会比较困难。
软件错误在程序运行的时候一般引而不发的状态,当运行一段时间后,触发了某种条件,软件错误才会出现,比如,由于Linux内核的bug,程序会在某一时间点出现错误或是由于开发环境的不同,程序在本地测试运行是正常的,打包上线则会出异常。
软件错误没有什么太好的解决方案,只能依赖于在开发过程中,对软件各依赖进行详细的检查,避免出现依赖冲突的问题亦或是在开发过程中,在可能出现软件错误的地方,打出详细的日志以便于在发生故障时,可以知道软件在执行过程中出现了什么问题,便于找出错误并进行修复,亦或是在软件发布上线时,尽可能的对软件进行详细测试,测试通过之后再部署上线。
人为错误
软件无论是运行还是开发,都是依赖于人的,如果假定人是不可靠的,我们应当以以下方式对系统进行构建:
- 在编码过程中,应当对以最小出错的方式来构建系统,例如精心拆分微服务,构建API以及设置管理界面,使用户在使用软件的过程中,可以更好的做它希望的事情,而不是搞坏软件。但是如果对用户限制过多,人们会想绕过它,所以我们需要将限制与不限制之间做出很好的平衡。
- 将容易出错的接口抽出来,部署到与线上环境相同的服务器上进行测试,在测试的过程中可以导入线上服务器的用户数据来保证真实性。这样做的好处是即便系统出现错误也没办法对用户进行影响。
- 对系统进行完全的测试,从单元测试到全系统模块测试。
- 当出现人为失误时,应当保证可以快速的恢复。例如系统的回滚机制。
- 对系统进行监控,当出现错误时,可以更快的发现并找到错误。
- 对软件的使用进行培训,使得用户可以正确的使用软件。
可扩展性
软件即便是当前运行正常也不代表以后运行正常,当使用软件用户变多时,软件是否可以满足正常运转。可扩展性是用于描述系统应对出现负载增加时的属于。
描述负载
首先需要选择负载参数,跟负载参数才能更好的描述负载信息,常见的负载参数有缓存命中率、请求发送次数、同一时间用户在线数等等。
已推特为例:
- 发送推特消息时,用户将消息推送到所有的关注者,平均每秒大概需要4.6k个请求。
- 主页时间线浏览时,平均300k个请求查看关注者的信息。
每个用户都有许多关注者和被关注者,当用户发送消息时,需要将消息推送到所有被关注者中取,而用户平时浏览信息时需要拉取所有的关注者的信息。、
推特大概有以下两种解决方案:
-
分为三张表,用户关注信息、消息、用户表。用户发送的信息统一存储到消息表中,当前登录用户可以根据关注信息表中得到的关注用户id,根据id取消息表拉取阈值相关的消息,最后根据时间线排序即可。
-
在用户时就根据发消息的用户的id查询,谁关注了该用户,之后逐个向关注用户的缓存信息中推送消息即可。查询时直接根据当前登录的用户id去缓存获取消息数据即可,因为之前已经计算并缓存了消息,所有取的时候非常快。
但是方法2有种弊端,如果关注者是个大网红,关注者有几十万粉丝,在发消息时,想几十万粉丝的缓存中添加数据,这样一来计算量毫无疑问会变的非常大,推特的设计是五秒内完成所有响应操作,这将会是一种非常大的挑战。
推特最终的解决方案是将两种方法进行结合,分情况使用两种方法,如果关注数少的人用方法1,关注多的大网红用方法2,这样一来就方法2计算量大的问题且系统压力也下降下来了。
描述性能
了解了负载之后,需要设想一下,当负载变高时,系统是否会变慢甚至宕机,怎样优化这些使得系统可以解决这些问题。
吞吐量:系统在一段时间内每秒可处理数据的条数。
响应时间:一对请求响应花费的时间数。
延迟时间与响应时间并不相同,延迟时间是处理请求所花费的时间,从后端的方面考虑就是,调用一个接口花费所需时间而响应时间则是,前端发送请求到后端,后端计算完返回给前端,后端处理完毕后经过网络传输后展示在页面上的时间。
因为网络原因、系统软件运行原因、机房温度等等影响软件执行的因素,每次发送请求的响应时间都不相同的,有时响应时间变大,有时有很小,所以一般在描述性能时,一般不使用单独的某一次的响应时间来衡量性能是否优秀,一般是一段时间内的响应时间或是多少请求下的响应时间,将这些时间列出来,统计其分布信息,如下图所示
根据图表计算出响应时间的中位数才能更好的衡量出系统请求具体所需的时间。想要更好的确定系统的性能则需要将百分位扩大,常用的阈值有95 99等,也就是95%或99%的请求的响应时间是多少,如果95%的请求时间为1.5秒,那么表示100个请求中有95个请求是小于或等于1.5秒,而100个请求中有5个请求是大于或等于1.5秒的。
长尾效应很重要,长尾效应会直接影响用户的体验,所以要选择一个合适的百分比来对系统的性能进行衡量。在电商购物平台中,用户购买的东西越多则发送的请求会越慢,对于电商系统来说,这部分用户才是对应用最有价值的客户,所以我们必须优化系统使用户体验更加良好。
排队延迟往往会影响请求响应时间,由于服务器并发数量有限,先进入的请求会在计算完毕后才能执行下一个请求,即便是后面的请求是简单的请求,立即可以返回。所以要在进行系统测试应当持续的向客户端发送请求,而不是等响应完毕后才继续发送下一个请求,这样才能很好的测试出系统在真实运行的时候的响应时间。
应对负载的方法
实践中的百分数
在调用一个请求时,如果这个请求调用了其他多个请求,即便是这些请求都是并发执行的,很显然,这个请求得等待最慢的那个请求发送完毕才算执行完毕,即长尾效应。
为了方便后期的优化,可以根据百分比持续的对服务的调用情况进行监控,最后根据响应时间生成一张性能图表。
一个简单的实现就是记录时间窗口内的所有响应时间,然后每隔一分钟对窗口内的请求进行排序,如果请求过多的话,这种排序方式可能效率不是很好,可以使用近似算法来对排序进行优化,一般是利用直方图来聚合响应时间的,如下图就是一个直方图。
为了解决系统访问高负载的问题,一般有两种解决方法,分别是水平扩展和垂直扩展,所谓垂直扩展就是购买性能更好的机器来应对高负载的使用情况,需要注意的是,性能越好的服务器越昂贵且扩展水平有限,到最后有可能还得需要水平扩展,而优点则是不需要考虑各个机器上的通讯问题。而水平扩展则是通过将服务分割并添加多台服务器,将服务部署在不同的服务器上,访问时以负载均衡的方式来访问,以此来降低单台服务器负载高的问题。
有些系统具有容错的特性,系统可以对负载进行监控,当系统负载变高时,则会自动添加计算机资源,其他系统则是人工手动判断是否达到高负载,如果到达的话则手动扩展计算机资源。
可维护性
即便是软件开发完毕之后上线的服务器中,也不能保证系统在运行过程中没出现bug,所以我们在开发系统的过程中需要考虑以下几个方面来使得后期项目上线完毕后尽可能的少出现bug或可以更快的找到问题的所在。
可运维性:编写详细的系统文档、合理代码、构建简单易懂的系统功能,在将系统交付给运维人员时,可以更好的使运维人员上手而不用学太多东西。
简单性:在实现系统的过程中尽量配备完备的代码以及简单可读性高的代码,使新员工加入项目团队使可以更好的上手。
可演化性:后续PM在为系统加需求或者该需求时可以更好的在原有的基础上直接进行修改,尽量代码各模块直接调用的耦合度降低,不然会出现改其中一个功能影响其他业务功能的情况。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2022-03-27 商品检索传入参数分析