架构 | 青训营笔记

123 阅读13分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记

架构在日常的校园学习中很难会接触的到,而架构在真正的项目实战中是一个不可或缺的层次,这次在青训营中加深了我对于架构的理解,在这篇文章中,将总结我对于架构的理解。

印象最深刻的一句话是,没有最好的架构,只有最合适的架构,每一种架构都有适合它的应用场景,所以,架构师的职业不可或缺。

#  如何做架构设计

 需求先行 弄清楚解决什么问题 、

业界调研 业界都有哪些解决方案可供参考 

 技术选型 内部/社区都有哪些基础组件 

 异常情况 考虑清楚xxx不行了怎么办

# 什么是架构

简单的一句话定义,软件整体结构与组件的抽象描述,它决定了实现一个软件的结构和组件。

# 架构的多样性

## 1.1 单体架构

单体架构师软件创造的初级阶段,就是把所有的功能实现到一个进程里,并只部署到一台机器上,这样的架构优点是简单,而优点也仅仅就是简单了,它有很大的问题,就是运维需要部署,没有容灾,容易造成服务失败等等问题。最大的优点简单,这也保证了它所特定的适用场景

## 1.2分布式部署

分布式部署的基层就是实现功能的水平扩容,分布式,蛋糕店的师傅会做面包,分布式就是多个师傅来做面包,分布式实现多个单体,优点:运维时不需要停服 缺点:一个师傅的职责比较多,开发效率不高,爆炸半径比较大

## 1.3 SOA

垂直应用架构,简单的来理解,就是将面包店的师傅的职责分成一个部分,该和面的和面,该蒸糕的蒸糕,实现职责的分明。就是将不同的功能单元抽象为服务,集合了优点,运维时不需要停服,职责分明,爆炸半径小等优点。各种服务之间,通过通信实现的合作尤为重要,SOA定义了服务服务之间的通信标准。微服务架构就是SOA的去中心化演进方向,是水平与竖直的切分,微服务架构也是SOA的一部分

# 什么是微服务架构

微服务架构就是实现一个有一个服务,其中在服务的同时要保证,数据一致性,高可用性,服务之间的治理,有统一的通信标准。在实现各个服务之间的解耦,互相联系不紧密的同时也需要防止每个服务切的过微,服务太小不容易治理。水平切分,多个师傅,垂直切分,做蛋糕的步骤。

   在企业级的后端架构中,要实现多个店面,就是多个服务方向,多个师傅,就是分布式,多个服务切分,就是微服务。要实现在后端架构做大之后要有方向的梳理,要有对未来的规划,可拓展,高可用。

 2 各种服务

2.1 云计算

定义:通过软件自动化管理,提供计算资源的服务网络,是现代互联网大厂规模熟悉分析和存储的即使,是一种虚拟化的方案,也是一种编排方案。

lassS 基础设施及服务 Infrastructure 是一个平台 相当于开面包店中的买房子 租房子

PssS Platform 平台及服务  提供一种清包 全包 

SaaS 软件及服务 选择从零开始还是培训过的,之间可以用的软件。可以提供软件服务

FaaS 功能及服务 手工 机器制作 

联名IP 新品 业务层 减少维护

2.2 云原生

云原生组织,在公有云,自由云,混合云等新型的动态网络中,构建一个可运行弹性拓展的应用。

  是一种弹性资源,通过虚拟化的容器实现快速的扩缩容,实现计算资源的精确使用。计算资源的调度,如淘宝的热销榜单,微博的今日热点更新,都是通过消息队列中的在线更新,解耦,削峰,也尝尝用作大数据分析。

弹性存储资源类型

是一种经典的存储对象,如一个企业的宣传视频,大数据,用户的消费记录,关系性数据库:强关系数据,时间 地点 消费金额

## DevOps

是云原生时代信息交付的利器,以一种自动化流程,提高软件开发的效率和交付的效率。

# 在选取架构的时候要考虑到以下指标,及根据自己的实际情况选取架构方式

容量 技术选型 

服务化 同步 异步 单侧 构建 性能瓶颈 

运维黑洞 设计 开发 测试 上线 调优 重构 赋能 开源 

容灾 兜底 隆级 流控 隔离 继承测试 部署 成本优化 体验提升 通信标准 HTTP PRC

微服务中间件 PRC vs HTTP 

微服务中间件  选取微服务的中间件需要要考虑中间件的性能问题以及在服务治理上的程度,中间件使用的协议有可解释性。

分流 API服务 小程序 APP 兼容性 HTTP PRC有服务治理的能力 

性能高 HTTP 可解释性 KV缓存 消息队列 关系数据库 专心自己的业务逻辑 使用缓存服务提供的能力

kv缓存 key value缓存的方式 在消息队列中常常使用,关系型数据库,优点是使开发人员专心自己的业务逻辑进行开发,要使用缓存服务提供的能力。

# 服务网络

服务网络使微服务之间通讯的中间层

要有一个高性能的网格代理,并且要做到业务代码与服务治理之间的解耦。

PRC与PRC之间沟通,就是要加上一层代理,治理配置,服务发现。通常在Service中实现,在蛋糕店中就相当于,开启会员激励购买制度。满意度调查,研发新品等方式增加营业额(服务能力)。

# 基础设施层面 

物理资源是有限的 机器 带宽 资源利用受制于部署服务 可用资源可以比作是一个水池,是资源水位,要将各个资源的水位持平,实现均衡。 比如说用户层面 ,如果网络通信开销较大发生网络抖动导致运维成本提高 异构环境下 不同实例资源水位就会产生不均。

要考虑资源利用率,就是要降低物理资源成本,可以通过提供更多的弹性资源来增加效益,解决的思路可以有,离在线资源井池。分析各种业务的特点。

  在线业务的特点,就是IO密集型为主,表现为潮汐性,实时性,

离线业务的特点,计算密集型占多数,非实时型

如此,我们就可以在繁忙的时候,将离线业务的资源交给实时性的业务来用。同时,让一个资源水池,在做好在线隔离的时候,将一部分空闲的资源交给离线进程(离线业务)。实现的前提就是对资源进行虚拟化,在核心上CPU资源不会产生交互。

# 对资源进行扩缩容

对资源进行扩缩容,对于降低业务成本,提高服务可用性具有重大作用。比如说,白天刷抖音的人多,占用的资源也多,而在夜晚往往用不到白天所需要的资源层次。夜晚对资源进行缩容,白天进行扩容,而扩缩容可以实现自动化,可以有内存,CP5等作为指标。

# 微服务发展的方向

微服务具有亲和部署的特性,满足亲和性条件的容器可以部署到一台宿主机上,微服务中间件与服务网格通过共享内存实现通信。微服务的容灾,服务网格的流量治理,出现问题时的熔断,服务未实现时的重试,复杂环境下的流量治理,对自动扩缩容提供正向输入。实现CPU的水位负载均衡,服务网格的动态负载均衡

  # 架构实战 

 能者多劳 CPU水位负载均衡 应该怎么设计 

 输入 考虑关键点 动态的变化 

 输入 服务网格层面 支持带权重的负载均衡策略 

 注册中心存储了所有容器的权重信息 容器的资源使用情况 

 物理资源信息 CPU型号 基础设置即服务 资源探针 

 关键点 紧急回滚能力 大规模 极端场景 

采集宿主物理资源信息 调整容器注册的权重 复杂度低 完全分布式 可用性高 

无服务中间件没有适配成本 无紧急回滚能力 缺乏运行时自适应能力 

 容器动态权重的自适应调整 服务网格的服务发现 流量调度能力 

 服务网格上报PRC指标 极端场景的处理成为可能

# 如何保证缓存和数据库的一致性 

在日常开发中为了提高数据的响应速度,可能会讲一些热点数据保存在缓存中,这样不用每次去数数据中查询了,可用有效提高服务端的响应速度 最常用的缓存就是redis 

数据对实时性和精确性要求很高 则直接操作数据库 

 选中合适的数据存入到redis之后,每当读取数据的时候,就先去redis看有没有,

如果有则直接返回 如果没有则取数据库中读取,并且将从数据库中读取到的数据缓存到redis中。 

 如何做到一致性 

Cache-Aside 旁路缓存模式 尽可能地解决缓存与数据库不一致堵塞问题 

读缓存 

流程是这样的 

1 读取数据

 2 检查缓存中是否有需要的数据,如果命中(Cache Hit),则直接返回数据 

3 如果没有命中缓存,即Cache Miss,那么就去访问数据库 

4 将从数据库中读到的数据设置到缓存中 

5 返回数据 

写缓存

 1 写入 

2 更新数据库 

3 删除旧缓存 

为什么是删除旧缓存而不是更新旧缓存 

 删除缓存消耗太大了

 1 更新缓存 缓存的内容(更新缓存不是简单的更新一个Bean,缓存的都是一些复杂匀速那和计算结果),加缓存的必然性(如果不加缓存,不但无法提高并发量,同时也会给MySQL数据库带来巨大的负担), 结论(对于这样的缓存,更新起来并不容易,删除缓存效果更好) 

 2 对于一些写频繁的应用,如果按照(更新缓存-更新数据库)的模式来,比较浪费性能,例如,如果每次都要写缓存,可能写了十次,只读了一次,那么读到的数据只是第十次的,前面的九次都是无效数据,没必要更新 

 3 对于更新缓存的方式,在并发环境下,可能会造成数据错误的情况,更新数据库的内容的先后顺序和更新缓存的先后顺序有关,顺序一致则数据一致,因为网络问题,顺序不一致,则数据不一致 

 为什么不先删除旧的缓存,然后再更新数据库? 可能出现的场景,

有两个线程 A 线程首先删除缓存。 B 线程读取缓存,发现缓存中没有数据。 B 线程读取数据库。 

B 线程将从数据库中读取到的数据写入缓存。 

A 线程更新数据库。

 一套操作下来,我们发现数据库和缓存中的数据不一致了!所以,在 Cache-Aside 中是先更新数据库,再删除缓存。

 延迟双删 

 延迟双删是这样:先执行缓存清除操作,再执行数据库更新操作,延迟 N 秒之后再执行一次缓存清除操作,这样就不用担心缓存中的数据和数据库中的数据不一致了。 

 如何确保原子性 保证数据库更新完毕之后,也成功删除了缓存 

 一种常见的解决方案就是使用消息中间件来实现删除的重试。大家知道,MQ 一般都自带消费失败重试的机制,当我们要删除缓存的时候,就往 MQ 中扔一条消息,缓存服务读取该消息并尝试删除缓存,删除失败了就会自动重试。 

 Read-Through/Write-Through 

 Read-Through 

 是一种类似于 Cache-Aside 的缓存方法,区别在于,在 Cache-Aside 中,由应用程序决定去读取缓存还是读取数据库,这样就会导致应用程序中出现了很多业务无关的代码;而在 Read-Through 中,相当于多出来了一个中间层 Cache Middleware,由它去读取缓存或者数据库,应用层的代码得到了简化, Write-Through 在 Write-Through 策略中,所有的写操作都经过 Cache Middleware,每次写入时,Cache Middleware 会将数据存储在 DB 和 Cache 中,这两个操作发生在一个事务中,因此,只有两个都写入成功,一切才会成功。 

这种写数据的优势在于,应用程序只与 Cache Middleware 对话,所以它的代码更加干净和简单。 

Write Behind

 Write-Behind 缓存策略类似于 Write-Through 缓存,应用程序仅与 Cache Middleware 通信,Cache Middleware 会预留一个与应用程序通信的接口。 

Write-Behind 与 Write-Through 最大的区别在于,前者是数据首先写入缓存,一段时间后(或通过其他触发器)再将数据写入 Database,并且这里涉及到的写入是一个异步操作。这种方式下,Cache 和 DB 数据的一致性不强,对一致性要求高的系统要谨慎使用,如果有人在数据尚未写入数据源的情况下直接从数据源获取数据,则可能导致获取过期数据,不过对于频繁写入的场景,这个其实非常适用。 

将数据写入 DB 可以通过多种方式完成:

 一种是收集所有写入操作,然后在某个时间点(例如,当 DB 负载较低时)对数据源进行批量写入。 

另一种方法是将写入合并成更小的批次,例如每次收集五个写入操作,然后对数据源进行批量写入。