1、C++内存分区
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局静态存储区和常量存储区。
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆:就是那些由 new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中。在以前的C语言中,全局变量又分为初始化的和未初始化的。在C++里面没有这个区分了,他们共同占用同一块内存区。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
代码段:代码段(code segment / text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

根据c/c++对象生命周期不同,c/c++的内存模型有三种不同的内存区域,即自由存储区,动态区、静态区。

  1. 自由存储区:局部非静态变量的存储区域,即平常所说的栈。
  2. 动态区:用operator new ,malloc分配的内存,即平常所说的堆。
  3. 静态区:全局变量 静态变量 字符串常量存在位置。

.text 部分是编译后程序的主体,也就是程序的机器指令。
.data 和 .bss 保存了程序的全局变量,.data保存有初始化的全局变量,.bss保存只有声明没有初始化的全局变量。
heap(堆)中保存程序中动态分配的内存,比如 C 的malloc申请的内存,或者C++中new申请的内存。堆向高地址方向增长。
stack(栈)用来进行函数调用,保存函数参数,临时变量,返回地址等。共享内存的位置[2]在堆和栈之间。

C++对象的成员函数存放在内存哪里?
类成员函数和非成员函数代码存放在代码段。如果类有虚函数,则该类就会存在虚函数表。虚函数表在Linux/Unix 中存放在可执行文件的只读数据段中(rodata),即前面提到的代码段,而微软的编译器将虚函数表存放在常量段。

2、C++内存泄漏的几种情况

  1. 在类的构造函数和析构函数中没有匹配的调用new和delete函数
    两种情况下会出现这种内存泄露:一是在堆里创建了对象占用了内存,但是没有显示地释放对象占用的内存;二是在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存
  2. 没有正确地清除嵌套的对象指针
  3. 在释放对象数组时在delete中没有使用方括号
    方括号是告诉编译器这个指针指向的是一个对象数组,同时也告诉编译器正确的对象地址值病调用对象的析构函数,如果没有方括号,那么这个指针就被默认为只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果造成了内存泄露。如果在方括号中间放了一个比对象数组大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的奔溃。如果方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个析构函数,结果会造成内存泄露。释放单个对象、单个基本数据类型的变量或者是基本数据类型的数组不需要大小参数,释放定义了析构函数的对象数组才需要大小参数。
  4. 指向对象的指针数组不等同于对象数组对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了
  5. 缺少拷贝构造函数两次释放相同的内存是一种错误的做法,同时可能会造成堆的崩溃。按值传递会调用(拷贝)构造函数,引用传递不会调用。在C++中,如果没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数,会逐个成员拷贝的方式来复制数据成员,如果是以逐个成员拷贝的方式来复制指针被定义为将一个变量的地址赋给另一个变量。这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的。所以,如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符。
  6. 缺少重载赋值运算符
    这种问题跟上述问题类似,也是逐个成员拷贝的方式复制对象,如果这个类的大小是可变的,那么结果就是造成内存泄露,如下图:
  7. 关于nonmodifying运算符重载的常见迷思
    a. 返回栈上对象的引用或者指针(也即返回局部对象的引用或者指针)。导致最后返回的是一个空引用或者空指针,因此变成野指针。
    b. 返回内部静态对象的引用。
    c. 返回一个泄露内存的动态分配的对象。导致内存泄露,并且无法回收。
    解决这一类问题的办法是重载运算符函数的返回值不是类型的引用,二应该是类型的返回值,即不是 int&而是int。
  8. 没有将基类的析构函数定义为虚函数
    当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄露。
  9. 野指针:指向被释放的或者访问受限内存的指针
    造成野指针的原因:
  10. 指针变量没有被初始化(如果值不定,可以初始化为NULL)。
  11. 指针被free或者delete后,没有置为NULL,free 和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL。
  12. 指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。

1.定义

云原生架构是基于云原生技术的一组架构原则和设计模式的集合,旨在将云应用中的非业务代码部分进行最大化地剥离,从而让云设施接管应用中原有的大量非功能特性(如弹性、韧性、安全、可观测性、灰度等),使业务不再有非功能性业务中断困扰的同时,具备轻量、敏捷、高度自动化的特点。

云原生架构强调应用与云环境的紧密结合,通过自动化部署、弹性伸缩、分布式处理和持续交付等方式,使应用能够快速响应变化,实现更高的性能和可靠性。其核心理念包括微服务、容器化、DevOps、CI/CD和可观测性,使得应用更易于部署、管理和扩展。

2.特点

基于云原生架构的应用特点包括:

  1. 代码结构发生巨大变化:不再需要掌握文件及其分布式处理技术,不再需要掌握各种复杂的网络技术,简化让业务开发变得更敏捷、更快速。
  2. 非功能性特性大量委托给云原生架构来解决:比如高可用能力、容灾能力、安全特性、可运维性、易用性、可测试性、灰度发布能力等。
  3. 高度自动化的软件交付:基于云原生的自动化软件交付可以把应用自动部署到成千上万的节点上。

云原生架构和微服务架构的区别是什么?
微服务架构和云原生架构是现代软件开发中两个紧密相关且经常一起使用的概念,但它们关注的侧重点和应用的范围等有所不同。
云原生架构是一种基于云环境设计和构建应用程序的方法,它天然利用了云计算的优势,如弹性、可扩展性、自动化和敏捷性。Matt Stine 于2013年首次提出云原生(CloudNative)的概念,随着技术的不断演进,其定义也在不断地迭代和更新,云原生可以概括为四个要素:微服务、容器、DevOps 和持续交付。
云原生架构的主要目的是如何最大化地利用云平台的特性来实现高效的资源利用、快速迭代和自动化运维。微服务架构的目的则是为了提高应用程序的模块化,使得开发、部署和扩展可以更加独立和灵活。

3.云原生的原则

云原生具有以下原则:( 服务弹性可见,韧性自动零变)

  1. 服务化原则:通过服务化架构把不同生命周期的模块分离出来,分别进行业务迭代。
  2. 弹性原则:弹性是指系统的部署规模可以随着业务量的变化而自动伸缩。
  3. 可观测原则:通过日志、链路跟踪和度量等手段,使得多次服务调用的耗时、返回值和参数都清晰可见。
  4. 韧性原则:软件所依赖的软硬件组件出现各种异常时,软件表现出来的抵御能力。
  5. 所有过程自动化原则:让自动化工具理解交付目标和环境差异,实现整个软件交付和运维的自动化。
  6. 零信任原则:不应该信任网络内部和外部的任何人/设备/系统,需要基于认证和授权重构访问控制的信任基础。
  7. 架构持续演进原则:架构具备持续演进能力。

4.主要架构模式

云原生涉及的主要架构模式有:
(1)服务化架构模式:要求以应用模块为颗粒度划分一个应用软件,以接口契约(例如IDL)定义性此业务关系,以标准协议(HTTP、gRPC等)确保彼此的互联互通,结合领域模型驱动(DomainDriven Design,DDD)、测试动开发(Test Driven Design; TDD)、容器化部署提升每个接口的代码质量和迭代速度。

(2)Mesh化架构模式:Mesh化架构是把中间件框架(如RPC、缓存、异步消息等)从业务进程中分离, 在业务进程中只保留“薄”的客户端部分。让中间件SDK与业务代码进一步解耦,从而使得中间件升级对业务进程没有影响,甚至迁移到另外一个平台的中间件也对业务透明。

(3)Serverless 模式:业务流量到来/业务事件发生时,云会启动或调度一个已启动的业务进程进行处理,处理完成后云自动会关闭/调度业务进程,等待下一次触发。开发者不用关心应用运行地点、操作系统、网络配置、CPU性能等,将应用的整个运行都委托给云。Serverless模式适合事件驱动的数据计算任务、计算时间短的请求/响应应用、没有复杂相互调用的长周期任务。

(4)存储计算分离模式:分布式环境中的CAP困难主要是针对有状态应用,由于一致性(Consistency,C),可用性(Availability,A),分区容错性(Partition Tolerance,P)三者无法同时满足,最多满足其中两个。所以无状态应用不存在一致性这个维度,可以获得很好的可用性和分区容错性,因而获得更好的弹性。

(5)分布式事务模式。由于业务需要访问多个微服务,所以会带来分布式事务问题,否则数据就会出现不一致。因此架构师需要根据不同的场景选择合适的分布式事务模式,常用的有:

    ·XA模式(传统采用XA模式);由于XA规范是实现分布式事务处理的标准,通常采用两阶段提交(2 Prepare Commit,2PC)的方法,具有很强的一致性,但是由于需要两次网络交互,所以性能差。
    ·基于消息的最终一致性(BASE):在可用性和一致性相冲突的情况下,为了权衡二者,BASE 提出只要满足基本可用(BA)和最终一致性(E),接受数据的软状态或未确定状

态(S),来优先实现性能,所以这类系统通常具备很高的性能。但正是由于应用的特点,选择可用性和一致性的妥协方案,导致通用性有限。

    ·TCC模式:采用Try-Confirm-Cancel二阶段模式,事务隔离性可控,高效,但需要应用代码将业务模型拆成二阶段,所以对业务侵入性强,设计开发维护等成本很高。
    ·SAGA模式:每个正向事务都对应一个补偿事务,若正向事务执行失败,则会执行补偿事务进行回滚。所以开发维护成本高。
    ·开源项目 SEATA的AT模式:它将TCC模式中的二阶段委托给底层代码框架,并且取消了行锁,所以非常高性能且无代码开发工作量,且可以自动执行回滚操作,但存在一些使用场景限制。

(6)可观测架构:可观测架构包括Logging、Tracing、Metrics,其中 Loggìng 提供多个级别跟踪,例如INFO/DEBUG/WARNING/ERROR;Tracing 收集一个请求从前端到后端的访问日志聚合,形成完整调用链路跟踪;Metrics 则提供对系统量化的多维度度量,包括并发度、耗时、可用时长、容量等。

(7)事件驱动架构:事件驱动架构(Event Driven Architecture,EDA)是一种应用/组件间的集成架构模式。适用于增强服务韧性、数据变化通知、构建开放式接口、事件流处理、命令查询的责任分离(Command Query Responsibility Segregation,CQRS)把对服务状态有影响的命令用事件来发起,而对服务状态没有影响的查询才使用同步调用的API接口等。

5.典型的云原生架构反模式
架构设计有时候需要根据不同的业务场景选择不同的方式,常见的云原生反模式有:
(1)庞大的单体应用:缺乏依赖隔离,代码耦合,责任和模块边界不清晰,模块间接口缺乏治理,变更影响扩散,不同模块间的开发进度和发布时间要求难以协调,一个子模块不稳定导致整个应用都变慢,扩容时只能整体扩容而不能对达到瓶颈的模块单独扩容等。

(2)单体应用“硬拆”为微服务:强行把耦合度高、代码量少的模块进行服务化拆分;拆分后服务的数据是紧密耦合的;拆分后成为分布式调用,严重影响性能。

(3)缺乏自动化能力的微服务:人均负责模块数上升,人均工作量增大,也增加了软件开发成本

6、云原生架构相关技术
1.容器技术
容器作为标准化软件基础单元,它将应用及其所有依赖项打包发布,由于依赖项齐备,应用不再受环境限制,在不同计算环境间快速、可靠地运行。容器部署模式与其他模式的比较如图17.2。

2、容器编排技术 K8S

容器编排技术包括资源调度、应用部署与管理、自动修复、服务发现与负载均衡、弹性伸缩、声明式API、可扩展性架构、可移植性。
Kubernetes的控制平面包含四个主要的组件:APlServer、Controller、Scheduler以及etcd。

3.微服务

    微服务模式将后端单体应用拆分为松耦合的多个子应用,每个子应用负责一组子功能。这些子应用称为“微服务”,多个“微服务”共同形成了一个物理独立但逻辑完整的分布式微服务体系。这些微服务相对独立,通过解耦研发、测试与部署流程,提高整体迭代效率。微服务设计约束如下:

(1)微服务个体约束:微服务应用的功能在业务领域划分上应是相互独立的。
(2)微服务与微服务之间的横向关系:在合理划分好微服务间的边界后,从可发现性和可交互性处理微服务间的横向关系。可发现性是指当服务A-发布和扩/缩容的时候,依赖服务A的服务B在不重新发布的前提下,能够自动感知到服务A的变化。可交互性是指服务A采用什么样的方
式可以调用服务B。
(3)微服务与数据层之间的纵向约束:提倡数据存储隔离(Data Storage Segregation, DSS)原则,对于数据的访问都必须通过相对应的微服务提供的API来访问。
(4)全局视角下的微服务分布式约束:高效运维整个系统,从技术上实现全自动化的 CI/CD流水线满足对开发效率的诉求,并在这个基础上支持蓝绿、金丝雀等不同发布策略,以满足对业务发布稳定性的诉求。

4.无服务器技术

无服务器技术的特点:
(1)全托管的计算服务-客户只需要编写代码构建应用,无须关注同质化的、负担繁重的基于服务器等基础设施的开发、运维、安全、高可用等工作。
(2)通用性-结合云 BaaS(后端云服务)API的能力,能够支撑云上所有重要类型的应用。
(3)自动弹性伸缩-让用户无须为资源使用提前进行容量规划。
(4)按量计费-让企业的使用成本有效降低,无须为闲置资源付费。
无服务器技术的关注点是:计算资源弹性调度(容错、资源利用率、性能、数据驱动)、负载均衡和流控、安全性。

5.服务网格(Service,Mesh)
服务网格旨在将那些微服务间的连接、安全、流量控制和可观测等通用功能下沉为平台基础设施,实现应用与平台基础设施的解耦。图17.3展示了服务网格的典型架构。服务A调用服务B的所有请求,都被其下的服务代理截获,代理服务A完成到服务B的服务发现、熔断、限流等策略,
而这些策略的总控是在控制平面(Control Plane)上配置。

    服务网格的主要技术:Istio、Linkerd、Consul。