2024年8月

引言

在当今这个数字化时代,互联网的普及和技术的飞速发展使得应用程序面临着前所未有的挑战,其中最为显著的就是高并发访问的需求。随着用户数量的激增和业务规模的扩大,如何确保应用在高并发场景下依然能够稳定运行、快速响应,成为了所有开发者和技术团队必须面对的重要课题。

Kubernetes(K8s),作为云原生时代的标志性技术之一,凭借其强大的容器编排能力、自动化部署和扩展功能,在解决高并发问题方面展现出了巨大的潜力。K8s通过抽象底层资源,提供了一套高效、灵活、可扩展的容器管理方案,使得开发者能够更加专注于业务逻辑的实现,而无需过多关注底层基础设施的复杂性和运维难题。

然而,尽管K8s为应用的高并发处理提供了强有力的支持,但其复杂的配置和管理流程仍然对开发者的技术能力提出了较高的要求。为了降低这一门槛,让更多的开发者能够充分利用K8s的优势,低代码平台应运而生。低代码平台通过提供可视化的开发环境、拖拽式的组件库以及自动化的部署和管理工具,极大地简化了应用的开发、测试和部署流程,使得开发者能够用更少的代码和更短的时间完成高质量的软件开发。

因此,本文将深入探讨低代码平台如何与K8s结合,以支持并实现高并发的应用。

K8s 简介

早期,各个组织是在物理服务器上运行应用程序。 由于无法限制在物理服务器中运行的应用程序资源使用,因此会导致资源分配问题。 例如,如果在同一台物理服务器上运行多个应用程序, 则可能会出现一个应用程序占用大部分资源的情况,而导致其他应用程序的性能下降。

因此,虚拟化技术被引入了。虚拟化技术允许你在单个物理服务器的 CPU 上运行多台虚拟机(VM)。 虚拟化能使应用程序在不同 VM 之间被彼此隔离,且能提供一定程度的安全性, 因为一个应用程序的信息不能被另一应用程序随意访问。

虽然虚拟化技术也提高了资源利用率,但每个虚拟机都运行自己的操作系统内核,这导致了较高的资源消耗,包括内存和存储空间,随而来之的便是较高的成本和不足的灵活性。于是乎,容器部署变得流行起来。容器类似于 VM,但是更宽松的隔离特性,使容器之间可以共享操作系统(OS)。 容器比起 VM 被认为是更轻量级的。且与 VM 类似,每个容器都具有自己的文件系统、CPU、内存、进程空间等。 由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。

容器是打包和运行应用程序的好方式。在生产环境中, 你需要管理运行着应用程序的容器,并确保服务不会下线。 例如,如果一个容器发生故障,则你需要启动另一个容器。 如果此行为交由给系统处理,是不是会更容易一些?于是就轮到Kubernetes登场了,Kubernetes 提供了一个可弹性运行分布式系统的框架。 满足系统的扩展要求,可以实现负载均衡,存储编排,自我修复等多个功能

Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具的使用范围相当广泛。 Kubernetes这个名字源于希腊语,意为“舵手”或“飞行员”。用来"驾驶"容器,实现各种各样的功能。K8s 这个缩写是因为 K 和 s 之间有 8 个字符的关系。

低代码平台简介

低代码开发是一种软件开发方法论,旨在通过减少手动编写代码的工作量,加快应用程序的开发速度和交付时间。它基于图形化的界面和可视化工具,使开发者能够使用拖放和配置等简单操作来创建应用程序。低代码开发具有以下的一些特点:

  • 技术门槛低:低代码平台通过图形化界面和预定义组件,降低了技术门槛,使得非专业程序员也能参与应用开发。
  • 开发效率高:由于减少了编写代码的工作量,低代码平台能够显著提高开发效率,缩短开发周期。
  • 灵活性与可扩展性:低代码平台支持自定义组件和业务逻辑,能够满足不同应用场景和需求,同时支持后续的功能扩展和升级。
  • 易于维护与升级:低代码平台提供统一的开发环境和管理工具,使得应用程序的维护和升级更加简单高效。

低代码开发具有以下的优势:

  • 快速开发:无需编写大量代码,大大缩短了开发周期。
  • 降低门槛:非技术人员也能轻松上手,降低了应用开发的门槛。
  • 易于维护:由于采用模块化设计,应用易于维护和扩展。
  • 节约成本:降低了人工成本和时间成本,为企业节省了开发费用。

未来,低代码平台将继续向更加智能化、集成化、云原生化方向发展,为企业提供更高效、更灵活的应用开发解决方案。同时,随着技术的不断成熟和市场的不断扩展,低代码平台将在更多领域和场景中发挥重要作用。接下来将进入到本文主要的部分,介绍如何使用低代码集成K8s实现负载均衡,应对高并发的应用场景。

低代码支持 K8s 实现高并发的具体步骤

介绍完基础概念之后,我们就可以介绍如何使用低代码平台去集成k8s实现负载均衡了,市面上有许多的代码开发平台,那此次就以企业级低代码开发平台——
活字格
为例,介绍活字格k8s的负载均衡方案。

环境准备

安装K8s

环境是软件运行的基础,所以我们需要准备一个至少拥有两个节点的K8s环境,一台文件服务器,一个镜像仓库。k8s作为集群部署方案 ,硬件设备至少需要两个节点以上。在本示例中,准备了3个节点,节点情况如下:

  • 登录到k8s-server,并进行 k8s 的安装。本文不介绍k8s的安装,k8s的安装可以参考下方文档,或者其他相关的技术文档。k8s 的安装教程可以参考:
    https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-linux/
  • 如果您只是用于学习,可以选择使用 k3s 模拟。k3s 是一个完全兼容的 Kubernetes 发行版,策略与 k8s 几乎完全一致,但是所需要的资源仅仅是 k8s 的一半,很适合前期的学习与验证(本教程使用 k3s 进行环境的构建)。也可以使用 minikube 在个人计算机上来模拟k8s 集群。
  • 当您的环境安装完成时,在 server 节点上应当可以正常使用 kubectl 命令对 k8s 进行管理。使用 kubectl get node 查看集群节点状态

文件服务器

为了确保节点中的公共资源可以共享,我们需要准备一台文件服务器,并创建共享文件目录,并确保该共享路径可以挂载到每个节点的指定路径上。文件服务器的协议可以遵照您现有的标准进行制定,如NAS、NFS等。本文中,选择在 k8s-master 通过 NFS 构建共享文件目录。

  1. 请为所有的节点安装 nfs 相关的依赖。
  2. 登录到k8s-master,创建文件根目录fgc-k8s-lbroot。下面的5个目录也可以分别创建在不同的文件服务器上,或者不同的目录下,根据实际情况创建即可(可自定义)。并在该目录中创建 5 个子文件夹(名称不可更改)。
# 用于共享的根目录
sudo mkdir /fgc-k8s-lbroot
# 附件存储目录
sudo mkdir /fgc-k8s-lbroot/ForguncyAttach
# 日志存储路径
sudo mkdir /fgc-k8s-lbroot/ForguncyLogs
# 备份存储路径
sudo mkdir /fgc-k8s-lbroot/ForguncyRestore
# 网站存储路径
sudo mkdir /fgc-k8s-lbroot/ForguncySites
# 网站可执行文件存储路径
sudo mkdir /fgc-k8s-lbroot/ForguncySitesBin
  1. 为当前目录修改用户组并赋予读写权限
sudo chown nobody:nogroup /fgc-k8s-lbroot/*
sudo chmod -R 777 /fgc-k8s-lbroot/*
  1. 将当前目录导出,使其可以被外部共享。
# 该文件是 NFS 服务器的配置文件之一,过编辑此文件,系统管理员可以控制哪些文件系统可以通过NFS协议被远程主机挂载和访问
sudo vim /etc/exports
# 在文件内加入如下配置,使其变为共享,编写完成后保存退出
/fgc-k8s-lbroot *(rw,sync,no_subtree_check)
#退出后刷新配置,确保生效
sudo exportfs -arv
  1. 目录 /fgc-k8s-lbroot 便可以被其他服务器进行挂载并读写。您需要进入到所有的 worker 服务器上将该目录进行挂载。
# 登录 worker 服务器
ssh  k8s-worker1
# 创建挂载路径
sudo mkdir -p /mnt/fgc_k8s_lb
# 打开系统挂载的配置文件
sudo vim /etc/fstab
# 在配置文件中配置将文件管理共享出的目录地址
198.19.249.12:/fgc-k8s-lbroot /mnt/fgc_k8s_lb nfs hard,intr,rw 0 0
  1. 需要留意,上一步挂载配置会在节点重启后进行自动挂载,如果没有重启的话,您需要通过 mount 命令手动挂载。
  2. 文件共享挂载的工作完成,您可以在任何一个节点的挂载路径下读写共享文件目录。

OK,现在活字格的 k8s 环境已经搭建完成,让我们在下个环节正式开启安装!

安装活字格服务器

安装Helm

为了方便k8s 的包管理,我们需要引入 Helm 来管理活字格服务的配置模板。

#下载安装包并安装
wget https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gz
sudo tar -zxvf helm-v3.14.0-linux-amd64.tar.gz
sudo mv linux-amd64/helm /usr/local/bin/helm
#查看版本
helm version

安装完成后,执行查看版本命令得到如图结果:

下载活字格负载均衡服务器安装文件

点击Hzg-K8S-HelmChart-10.0.3.0.zip,下载文件,并解压到本地目录,会看到如下目录文件:

详细设置如下:

  • values.yaml :为主要配置文件,如果使用离线仓库,请修改仓库地址和Tag,否则默认使用官网镜像,请将文件服务器地址改为实际使用的地址。
  • Chart.yaml:可以修改Chart的名称,一般不需要修改,保持默认。
  • service-redis.yml:默认启动一个redis服务,不需要的可以删除本文件,删除时将文件deployment-redis.yaml一并删除。
  • service-influx.yml:这是日志服务的service文件,负载均衡情况下,日志运行在一个独立的POD下,并且不支持负载均衡,只能有一个pod,一般不需要修改。
  • service-fgc.yml:活字格服务的service文件,一般不需要修改。
  • pvs.yaml:K8S的PV文件,请根据自身的存储情况进行修改,如果是NFS协议的文件服务,一般不需要修改。
  • pvcs.yaml:K8S的PVC文件,一般不需要修改。
  • deployment-redis.yaml:默认启动的redis的POD文件,不需要的话可以删除本文件,删除时将文件service-redis.yaml一并删除。
  • deployment-influx.yaml:活字格日志的POD文件,一般不需要修改。
  • deployment-fgc.yaml:活字格服务的POD文件,一般不需要修改。

修改chart文件的内容

从 上方详细设置内容可知,template 中的配置与 value.yaml是我们关注的重点。一般情况下,template 的配置项无需修改,只修改 value.yaml 中的变量值即可。对于本文中的环境,修改value.yaml文件如下:

打包并安装

配置完成后,在 chart 所在的根目录上,直接运行 helm package your-chart-name 即可将咱们的 k8s 配置打包成一个后缀为 tgz 的标准的 chart 文件。之后运行 helm install 命令,将 活字格服务对应的 chart 进行 k8s 安装。

#打包,执行下面打包命令后,会生成一个forguncy-k8s-server-10.0.0.0.tgz文件,即为helm的安装文件
helm package mychart-forguncy
#使用上面输出的forguncy-k8s-server-10.0.0.0.tgz文件,安装活字格服务(下面命令中的myfgcchart-test3为自定义的安装名称,建议不要太长)
helm install myfgcchart-test3 forguncy-k8s-server-10.0.0.0.tgz

执行上述命令后,活字格负载均衡服务已经安装完成,进入配置阶段。如果需要更新或者卸载,请执行对应的helm命令,如:

#使用新的values.yaml文件更新
helm upgrade myfgcchart-test3 /root/forguncy-k8s-server-10.0.0.0.tgz --values /root/mychart-forguncy/values.yaml
#卸载安装的活字格服务
helm uninstall myfgcchart-test3

至此,活字格服务已经成功安装在 k8s 集群上,我们可以通过 kubectl 命令来查看活字格服务的状态:

服务说明

我们会创建 3 个 deployment,分别是forguncy-pod,redis-pod,influx-pod:

  • forguncy-pod:活字格服务的 pod 集声明,运行活字格的所有服务,包括控制台以及咱们发布的业务应用。是活字格服务的主程序POD,可以有多个副本。
  • redis-pod:redis 服务的 pod 集声明,运行 redis 服务,用于活字格应用负载均衡时的状态共享。功能性上和原有的负载均衡策略的 redis 类似。是redis服务,可选安装
  • influx-pod:influxDB 服务的 pod 集声明,运行 influxDB 服务,服务于活字格的日志模块。是活字格服务的日志系统POD,只能有一个副本。

同时,针对于对应的 deployment,会创建相应的 service。服务会通过 service 暴露的端口进行提供。外部可以通过任何一个 k8s 节点 IP 加端口号的方式访问到活字格的服务,例如访问活字格管理控制台的访问地址为:
http://198.19.249.12:32666/UserService/AdminPortal/。其中,198.19.249.12
是 k8s-server 的 IP,32666 是 forguncy-service 对外暴露的端口。直接在浏览器中访问结果如图。

配置负载均衡

开启负载均衡

活字格服务安装好以后,默认不是负载均衡状态,需要手动开启相关设置。在单 pod 的前提下,我们登录到活字格服务器管理控制台中,开启负载均衡。

对应的负载均衡入口没有发生变化,仍处于设置列表中。当开启负载均衡后,需要将用户信息数据库配置到外联数据库中。此外,redis 的服务也同样需要配置。需要注意,考虑到 k8s 的特性,每次 pod 的创建与销毁,其 IP 都会发生变化,所以这里 redis 服务应当配置为 service 的名称,而不是特定的 IP。

ps:默认安装的 redis 服务是没有密码的。

测试连接成功后,点击保存,会重启活字格的服务,会重新生成新的 forguncy-pod。

此时,为了确保后续应用与节点的正常访问,请确保您的活字格集群已经激活 k8s 对应的授权。

动态扩容

当我们开启负载均衡并重启成功后,活字格侧的操作就完成了。后续所有的扩容、自愈行为都依赖于 k8s 本身的行为策略进行执行。

目前活字格的服务只有一个 pod ,现在可以根据咱们实际需要去动态的扩展节点了。

# 将 pod 的节点升级到 6 个
kubectl scale deployment fgc-chart-test-forguncy-pod --replicas=6

该命令为 kubectl 自带的动态扩容命令,k8s 会基于您设定的 replicas 值去动态的创建包含服务的 pod 并自动负载。可以通过 kubectl get pod 查看当前的 pod 状态。也可以在扩容的时候通过参数 —watch 查看 pod 的变化过程。

如果您希望查看请求被指向了哪个节点,可以通过修改对应的配置项开关来实现。对应的配置路径位于:

/fgc-k8s-lbroot/ForguncySites/SettingsFolder/ForguncyRouterConfig.json

将其中的 ShowXUpstream 的值设置为 true 即可。重置 pod 数量后,可以通过 cookie 查看被指向的 pod 节点名称。

应用发布与路由

现在可以向集群发布应用了。当应用发布成功后,应用的设置域名会自动配置为 http://{publishserver}/{appname},例如:
http://198.19.249.12:32666/stock-management,其中:
{Publishserver} 198.19.249.12:32666是您在设计器中配置发布的服务器。

{Appname} stock-management是您在设计器中发布的应用程序名称。

需要注意,由于应用服务是基于集群的,所以必须使用外联库,使用内建库的应用会访问异常。

当然,在实际的运维场景中,我们可以需要对应用服务进行路由。对于活字格的一些服务来说也需要会话保持功能,所以我们需要开启 k8s 中 Ingress 和 Service 的会话保持配置(Service已在安装脚本中默认配置,Ingress需要手动添加)。

如 Ingress 增加基于 Cokkie 的注解:

nginx.ingress.kubernetes.io/affinity: "cookie" 

总结

至此低代码如何集成K8s实现负载均衡应对高并发场景到这里就分享完了,感谢大家的阅读

扩展链接:

从表单驱动到模型驱动,解读低代码开发平台的发展趋势

低代码开发平台是什么?

基于分支的版本管理,帮助低代码从项目交付走向定制化产品开发

02.计算器存储器的原理

目录介绍

  • 01.什么是存储器
    • 1.1 了解存储器是什么
    • 1.2 存储器类型
  • 02.存储器系统设计
    • 2.1 存储器分层设计
    • 2.2 存储器层次结构
    • 2.3 高速缓存设计思想
    • 2.4 虚拟内存访问内存
  • 03.存储器类型
    • 3.1 按照材质划分
    • 3.2 按芯片类型划分
    • 3.3 内存 vs CPU
    • 3.4 存储器访问权限
    • 3.5 用户态 vs 内核态
    • 3.6 案例mmap内存映射
  • 04.理解局部性原理
    • 4.1 时间局部性
    • 4.2 空间局部性
  • 05.存储器详细说明
    • 5.1 CPU 寄存器
    • 5.2 CPU 高速缓存
    • 5.3 内存
    • 5.4 硬盘
  • 06.计算机地址映射
    • 6.1 地址映射有哪些
    • 6.2 直接映射
    • 6.3 块映射
    • 6.4 多级映射
    • 6.5 哈希映射
    • 6.6 思考题分析

01.什么是存储器

1.1 了解存储器是什么

  • 计算机存储器是计算机系统中用于存储数据和指令的硬件设备。
    • 它是计算机的核心组成部分之一,用于存储和检索数据以供处理和操作。

1.2 存储器类型

  • 它可以分为主存储器(主内存)和辅助存储器(辅助内存)两种类型。
    • 主存储器(主内存):主存储器是计算机中用于存储当前正在执行的程序和数据的地方。它是计算机系统中最快的存储器,也是CPU直接访问的存储器。
    • 辅助存储器(辅助内存):辅助存储器用于长期存储数据和程序,以及在主存储器不足时作为扩展存储器。辅助存储器的访问速度较慢,但容量通常比主存储器大得多。

02.存储器系统设计

2.1 存储器分层设计

  • 为什么存储器系统要分层?
    • 内存和硬盘都是存储器设备。其实,在 CPU 内部中的寄存器和 CPU L1/L2/L3 缓存也同样是存储设备,而且它们的访问速度比内存和硬盘快几个数量级。
    • 那么,为什么要使用内存和硬盘,直接扩大 CPU 的存储能力不行吗?这就要提到存储器的 3 个主要的性能指标: 速度 + 容量 + 每位价格。
  • 一般来说,存储器的容量越大则速度越低,速度越高则价格越高。
    • 想要获得一个同时满足容量大、速度高且价格低的 “神奇存储器” 是很难实现的。
    • 因此,现代计算机系统会采用分层架构,以满足整个系统在容量、速度和价格上最大的性价比。

2.2 存储器层次结构

  • 在现代计算机系统中,一般采用 “CPU 寄存器 - CPU 高速缓存 - 内存 - 硬盘” 四级存储器结构。自上而下容量逐渐增大,速度逐渐减慢,单位价格也逐渐降低。
    • 1、CPU 寄存器: 存储 CPU 正在使用的数据或指令。寄存器是最靠近 CPU 控制器和运算器的存储器,它的速度最快;
    • 2、CPU 高速缓存: 存储 CPU 近期要用到的数据和指令。CPU 高速缓存是位于 CPU 和内存中间的一层缓存。缓存和内存之间的数据调动是由硬件自动完成的,对上层是完全透明的。
    • 3、内存: 存储正在运行或者将要运行的程序和数据;
    • 4、硬盘: 存储暂时不使用或者不能直接使用的程序和数据。硬盘主要解决存储器系统容量不足的问题,硬盘的速度虽然比内存慢,但硬盘的容量可以比内存大很多,而且断电不丢失数据。
  • 这些存储器设备在计算机系统中协同工作,提供了不同层次和速度的存储能力,以满足计算机的数据存储和访问需求。
  • 在此基础上,对各个层级上进行局部优化,就形成了完整的存储器系统:
    • 优化 1 - CPU 三级缓存: 在 CPU Cache 的概念刚出现时,CPU 和内存之间只有一个缓存,随着芯片集成密度的提高,现代的 CPU Cache 已经普遍采用 L1/L2/L3 多级缓存的结构来改善性能;
    • 优化 2 - 虚拟内存: 程序不能直接访问物理地址,而是访问虚拟地址,虚拟地址需要经过地址变换(Address Translation)才能映射到存放数据的物理地址;
    • 优化 3 - 页缓存: 为了提高读写效率和保护磁盘,操作系统在文件系统中使用了页缓存机制。

2.3 高速缓存设计思想

  • 为什么在 CPU 和内存之间增加高速缓存?
    • 原因 1 - 弥补 CPU 和内存的速度差(主要): 由于 CPU 和内存的速度差距太大,为了拉平两者的速度差,现代计算机会在两者之间插入一块速度比内存更快的高速缓存。只要将近期 CPU 要用的信息调入缓存,CPU 便可以直接从缓存中获取信息,从而提高访问速度;
    • 原因 2 - 减少 CPU 与 I/O 设备争抢访存: 由于 CPU 和 I/O 设备会竞争同一条内存总线,有可能出现 CPU 等待 I/O 设备访存的情况。而如果 CPU 能直接从缓存中获取数据,就可以减少竞争,提高 CPU 的效率。
  • CPU 三级缓存
    • image

2.4 虚拟内存访问内存

  • 为了满足系统的多进程需求和大内存需求,操作系统在内存这一层级使用了虚拟内存管理。
    • 当物理内存资源不足时,操作系统会按照一定的算法将最近不常用的内存换出(Swap Out)到硬盘上,再把要访问数据从硬盘换入(Swap In)到物理内存上。
    • 至于操作系统如何管理虚拟地址和内存地址之间的关系(段式、页式、段页式),对上层应用完全透明。

03.存储器类型

3.1 按照材质划分

  • 1、磁表面存储器: 在金属或塑料表面涂抹一层磁性材料作为记录介质,用磁头在磁层上进行读写操作。例如磁盘、磁带、软盘等,已经逐渐淘汰。
  • 2、光盘存储器: 在金属或塑料表面涂抹一层磁光材料作为记录介质,用激光在磁层上进行读写操作。例如 VCD、DVD 等,已经逐渐淘汰。
  • 3、半导体存储器: 由半导体器件组成的存储器,现代的半导体存储器都会用超大规模集成电路技术将存储器制成芯片,具有体积小、功耗低、存取速度快的优点,是目前主流的存储器技术。

3.2 按芯片类型划分

  • 半导体存储器按照存取方式划分可以分为 2 种:
  • 1、RAM(Random-Access Memory 随机存取存储器):
    • 指可以通过指令对任意存储单元进行读写访问的存储器,在断电后会丢失全部信息。RAM 的容量没有 ROM 大,但速度比 ROM 快很多,通常用作计算机主存。
  • 2、ROM(Read-Only Memory 只读存储器):
    • 指只能进行读取操作的存储器,断电后信息不丢失。随着半导体技术的发展,在 ROM 的基础上又发展出 EEPROM(电可擦除只读存储器)等技术,它们并不符合 ROM 只读的命名,但由于是在 ROM 上衍生的技术,才沿用了原来的叫法。
    • 现在我们更熟悉的 HDD(机械硬盘)和 SSD(固态硬盘) 都是 ROM 的衍生技术。

3.3 内存 vs CPU

3.3.1 访问速度对比

  • 为什么内存的访问速度比 CPU 差这么多?内存的访问速度比CPU慢的主要原因有以下几个方面:
  • 技术差异:内存和CPU使用不同的技术实现。
    • 内存通常使用DRAM(动态随机存取存储器)技术,而CPU使用的是更快速的SRAM(静态随机存取存储器)技术。
    • DRAM的访问速度相对较慢,因为它需要经过一系列复杂的电路和访问步骤来读取和写入数据。
  • 物理距离:内存通常位于计算机主板上,而CPU位于主板上的芯片组或处理器插槽中。
    • 这意味着内存与CPU之间存在物理距离,数据需要通过总线传输到CPU。这种物理距离导致了延迟和传输时间,从而降低了内存访问速度。
  • 容量差异:内存通常具有较大的容量,可以存储大量的数据。
    • 相比之下,CPU的寄存器和缓存存储器容量较小,但速度更快。这是因为较大容量的内存需要更多的电路和组件来实现,这些组件之间的电信号传输需要更多的时间。
  • 层次结构:计算机系统中的存储器通常按照层次结构进行组织,包括寄存器、缓存、主存储器和辅助存储器。
    • 每个层次的存储器速度和容量都不同,较高层次的存储器速度更快但容量较小,而较低层次的存储器速度较慢但容量更大。这种层次结构的设计是为了平衡存储器的速度和容量需求。
3.3.2 关联性说明
  • 内存和CPU之间的关系是紧密相连的。
    • CPU从内存中读取指令和数据,并对其进行处理。内存的访问速度对CPU的性能至关重要,较快的内存可以提供更快的数据传输,从而加快计算机的运行速度。
    • 同时,较大的内存容量可以容纳更多的程序和数据,使得CPU可以同时处理更多的任务。
    • 内存用于存储程序和数据,提供临时存储空间;而CPU执行指令和处理数据,是计算机的核心计算单元。它们之间的协同工作决定了计算机的性能和效率。

3.4 存储器访问权限

  • 在计算机系统中,存储器(Memory)的访问权限可以根据操作系统的设计和保护机制分为:
    • 用户态(User Mode)和内核态(Kernel Mode)。
  • 操作系统通过将处理器从用户态切换到内核态来实现对系统资源的保护和管理。
    • 当应用程序需要执行特权操作或访问受限资源时,它必须通过系统调用(System Call)将控制权转移到内核态。系统调用是一种特殊的指令,它允许应用程序请求操作系统提供特定的服务和功能。
    • 通过将用户态和内核态分离,操作系统可以确保应用程序无法直接访问和破坏关键的系统资源。这种分离提供了安全性和稳定性,同时允许操作系统对资源进行有效的管理和保护。

3.5 用户态 vs 内核态

  • 用户态(User Mode):
    • 在用户态下,应用程序和用户进程运行。在用户态下,程序只能访问受限的内存区域,通常是分配给应用程序的用户空间。
    • 用户态的程序不能直接访问操作系统的核心功能和敏感资源,如硬件设备和操作系统的内核数据结构。
  • 内核态(Kernel Mode):
    • 在内核态下,操作系统内核运行。内核态具有更高的权限和更广泛的访问权限。
    • 在内核态下,操作系统可以访问和控制系统的所有硬件资源、内存和其他核心功能。内核态的程序可以执行特权指令和访问受保护的内核数据结构。
  • MMKV诞生的背景
    • 针对微信聊天该业务,高频率,同步,大量数据写入磁盘的需求。不管用sp,还是store,还是数据库,只要在主线程同步写入磁盘,会很卡。
    • 解决方案就是:使用内存映射mmap的底层方法,相当于系统为指定文件开辟专用内存空间,内存数据的改动会自动同步到文件里。
    • 用浅显的话说:MMKV就是实现用「写入内存」的方式来实现「写入磁盘」的目标。内存的速度多快呀,耗时几乎可以忽略,这样就把写磁盘造成卡顿的问题解决了。

3.6 案例mmap内存映射

  • 用这个案例来了解MMKV在用户态和内核态的速度,然后理解内存映射
    • 用户态:读取硬盘上的某一段数据通常都需要经过 硬盘->内核->用户,即数据需要经历两次拷贝,效率十分低下。
    • 内核态:mmap将设备的物理地址映射到进程的虚拟地址,则用户操作虚拟内存时就相当于对物理设备进行操作了,减少了内核到用户的一次数据拷贝,从而提高数据的吞吐率。
  • 内存映射(Memory Mapping)是一种将文件或设备的内容映射到进程的虚拟内存空间的技术。
    • 在许多操作系统中,包括Unix、Linux和Windows,都提供了内存映射的机制。其中,mmap(Memory Map)是Unix和Linux系统中用于实现内存映射的系统调用。
    • 通过使用mmap,可以将文件或设备的内容直接映射到进程的虚拟内存空间,使得进程可以像访问内存一样访问文件或设备的内容,而无需进行显式的读写操作。这种映射可以是读写的,也可以是只读的。
  • 内存映射的优点包括:
    • 简化IO操作:通过内存映射,可以将文件或设备的内容直接映射到内存,从而简化了对文件或设备的读写操作。
    • 提高性能:由于内存映射允许直接访问文件或设备的内容,避免了数据的复制和缓冲区的使用,因此可以提高IO操作的性能。
    • 共享内存:多个进程可以将同一个文件或设备映射到各自的虚拟内存空间,从而实现共享内存的通信方式。

04.理解局部性原理

  • 局部性原理(Locality Principle)是计算机科学中的一个重要概念
    • 指的是在程序执行期间,访问内存或存储的数据和指令往往具有一定的局部性倾向。
  • 在计算机组成原理中,很多策略中都会体现到局部性原理,在学习中可以有意识地关联起来。
    • 例如在管理 CPU 高速缓存时,除了可以将当前正在访问的内存数据加到到缓存中,还可以把相邻内存的数据一并缓存起来(即 CPU 缓存行),也能够提高缓存命中率。
  • 哪些地方用到了局部性原理
    • 高速缓存、预取技术和指令重排等优化技术都是基于局部性原理的。

4.1 时间局部性

  • 时间局部性(Temporal Locality): 时间局部性表示一个指令或数据被访问过后,在短时间内有很大概率会再次访问。
    • 例如,在程序中的一些函数、循环语句或者变量往往会在短时间内被多次调用;
    • 程序中的循环、迭代和重复操作等结构导致了数据和指令的重复访问。通过利用时间局部性,计算机系统可以将频繁访问的数据和指令缓存在高速缓存(Cache)中,以提高访问速度。

4.2 空间局部性

  • 空间局部性(Spatial Locality): 空间局部性表示一个指令或数据被访问过之后,与它相邻地址的数据有很大概率也会被访问。
    • 例如,在程序中访问了数据的首项元素之后,往往也会访问继续后续的元素。
    • 程序中的数据结构和数组等通常在内存中是连续存储的。通过利用空间局部性,计算机系统可以预取相邻的数据或指令到高速缓存中,以提高访问效率。

05.存储器详细说明

5.1 CPU 寄存器

  • CPU寄存器(Central Processing Unit Registers)
    • 是位于CPU内部的一组高速存储器单元,用于存储和处理指令、数据和中间结果。寄存器是CPU中最快速的存储器,其访问速度比主存储器(内存)更快。
  • 案例说明
    • 一个常见的例子是计算器程序。计算器程序需要执行各种算术和逻辑操作,这些操作通常涉及到CPU寄存器的使用。
    • CPU寄存器用于存储操作数和计算结果。通过将数据加载到寄存器中,CPU可以更快地访问和处理这些数据,从而提高计算器程序的性能。
  • 假设我们有一个简单的计算器程序,可以执行加法和减法操作。以下是一个使用CPU寄存器的计算器程序的示例:
    • 初始化寄存器:程序开始时,将需要使用的寄存器初始化为适当的值。例如,我们可以使用两个通用寄存器(如R1和R2)来存储操作数和结果。
    • 输入操作数:用户输入要进行计算的两个操作数。这些操作数可以存储在内存中的特定位置,然后通过将其加载到寄存器中进行访问。
    • 加法操作:将第一个操作数加载到一个寄存器(如R1),将第二个操作数加载到另一个寄存器(如R2)。然后,使用CPU的加法指令将这两个寄存器中的值相加,并将结果存储在另一个寄存器(如R3)中。
    • 减法操作:类似地,将第一个操作数加载到一个寄存器(如R1),将第二个操作数加载到另一个寄存器(如R2)。然后,使用CPU的减法指令将这两个寄存器中的值相减,并将结果存储在另一个寄存器(如R3)中。
    • 输出结果:将计算得到的结果从寄存器(如R3)中取出,并将其显示给用户。

5.2 CPU 高速缓存

  • CPU高速缓存(CPU Cache)是计算机系统中的一种高速存储器,位于CPU内部或与CPU紧密集成。
    • 它用于提高CPU对内存数据的访问速度,减少对主存储器的访问次数。
  • 高速缓存的设计基于局部性原理,即程序倾向于访问附近的内存位置。
    • 高速缓存通过在CPU和主存储器之间建立一个更小但更快的存储层次结构,存储最近或频繁访问的数据和指令。
    • 当CPU需要访问数据时,它首先检查高速缓存,如果数据在缓存中,则可以快速获取。如果数据不在缓存中,则需要从主存储器中获取,并将其加载到缓存中以供后续访问。
  • 高速缓存通常分为多个级别,例如L1、L2和L3缓存。
    • L1缓存是最接近CPU核心的缓存,速度最快但容量最小。L2缓存位于L1缓存之后,容量较大但速度稍慢。L3缓存是一种更大容量但速度更慢的缓存,通常位于CPU核心之外,多个核心共享。
  • 案例说明
    • 一个常见的案例来说明CPU高速缓存的作用是矩阵乘法运算。假设我们有两个矩阵A和B,需要计算它们的乘积C。矩阵乘法涉及大量的数据读取和计算操作,因此可以用来说明高速缓存的重要性。
  • 以下是一个简化的示例(帮助理解CPU 高速缓存):
    • 初始化矩阵:将矩阵A和B的元素加载到主存储器中。
    • 计算乘积:在计算乘积C的过程中,CPU从主存储器中读取矩阵A和B的元素,并将它们存储在高速缓存中。由于高速缓存的容量有限,只能存储部分数据。
    • 数据访问:在计算乘积C时,CPU需要反复访问矩阵A和B的元素。如果所需的元素已经存在于高速缓存中,CPU可以快速访问它们,从而提高计算速度。这是因为高速缓存的访问速度比主存储器快得多。
    • 缓存命中和缓存未命中:当CPU需要访问一个元素时,它首先检查高速缓存中是否存在该元素。如果存在,称为缓存命中(cache hit),CPU可以直接从高速缓存中读取该元素。如果不存在,称为缓存未命中(cache miss),CPU需要从主存储器中读取该元素,并将其存储在高速缓存中以供后续访问。

5.3 内存

  • 内存(Memory)是计算机系统中用于存储数据和指令的硬件设备。它是计算机系统中的主要存储器,用于临时存储正在执行的程序和数据。
    • 内存通常是基于半导体技术的,例如动态随机存储器(DRAM)或静态随机存储器(SRAM)。它提供了快速的读写访问速度,使得CPU能够快速地读取和写入数据。
    • 需要注意的是,内存是一种易失性存储器,即当计算机系统断电时,内存中的数据将会丢失。因此,为了长期存储数据,通常需要使用辅助存储器(如硬盘驱动器或固态硬盘)。
    • 内存管理是操作系统的重要功能之一,它负责分配和管理内存资源,确保不同程序和进程之间的内存隔离和保护。
  • 计算机内存的使用案例有很多
    • 程序执行:当计算机上的程序被执行时,它们的指令和数据需要加载到内存中。内存提供了一个临时的存储空间,使得CPU能够快速访问和处理这些指令和数据。程序的执行过程中,内存会被不断读取和写入。
    • 数据存储:计算机内存也用于存储数据。例如,当您在文档编辑器中编写文章时,您的输入和编辑内容会被暂时存储在内存中。内存的快速读写速度使得您可以实时地编辑和查看文档。
    • 多任务处理:当计算机同时运行多个程序时,内存被用于存储这些程序的指令和数据。操作系统会将不同的程序加载到内存的不同区域,并为每个程序分配一定的内存空间。这样,每个程序都可以独立地运行,而不会相互干扰。
    • 图形和多媒体处理:计算机内存在图形和多媒体处理中起着重要作用。例如,当您播放视频或运行图形密集型游戏时,视频和图像数据需要加载到内存中进行处理和显示。

5.4 硬盘

  • 硬盘(Hard Disk Drive,HDD)是计算机系统中常见的存储设备之一,用于长期存储和检索数据。
    • 持久性存储:硬盘上的数据可以长期存储,即使计算机关闭或断电,数据也不会丢失。
  • 计算机硬盘使用的常见案例
    • 文件存储和管理:硬盘用于存储用户创建的各种文件,如文档、图片、音频、视频等。用户可以将文件组织在硬盘的不同目录和文件夹中,以便于管理和检索。
    • 多媒体存储和播放:硬盘提供了存储和播放各种多媒体内容的能力。用户可以将音乐、电影、电视节目等多媒体文件存储在硬盘上,并使用相应的应用程序进行播放和观看。

06.计算机地址映射

6.1 地址映射有哪些

  • 在计算机系统中,地址映射是将逻辑地址(或虚拟地址)映射到物理地址的过程。以下是几种常见的地址映射方式:
    • 1.直接映射;
    • 2.块映射
    • 3.多级映射
    • 4.哈希映射

6.2 直接映射

  • 直接映射(Direct Mapping):直接映射是最简单的地址映射方式。
    • 它将逻辑地址的某个范围直接映射到物理地址的相应位置。
    • 例如,逻辑地址0映射到物理地址0,逻辑地址1映射到物理地址1,以此类推。这种映射方式适用于具有固定大小的地址空间。
    • 在直接映射中,每个主存块只能映射到缓存中的一个特定位置,这个位置是通过主存块的地址与缓存大小进行计算得出的。
  • 计算机直接映射例子有哪些
    • CPU 缓存:计算机的 CPU 缓存通常使用直接映射来加速数据访问。主存中的数据块被映射到缓存中的特定位置,以便快速访问。每个主存块只能映射到缓存中的一个位置。
    • 数据库缓存:数据库系统中的缓存可以使用直接映射来提高查询性能。数据库中的数据块被映射到缓存中的特定位置,以便快速访问常用的数据。每个数据块只能映射到缓存中的一个位置。
  • 直接映射优缺点分析
    • 直接映射的优点是简单且易于实现,但缺点是可能会出现映射冲突,导致缓存命中率下降。

6.3 块映射

  • 块映射(Block Mapping):块映射将逻辑地址划分为固定大小的块,并将每个块映射到物理地址的相应块。
    • 这种映射方式可以提高地址空间的利用率,并减少映射表的大小。
    • 常见的块映射方式包括页表映射(Page Table Mapping)和段表映射(Segment Table Mapping)。
    • 用于将逻辑地址空间中的块映射到物理地址空间中的块。在块映射中,逻辑块被映射到物理块,以实现数据的存储和访问。
  • 计算机块映射例子有哪些
    • 文件系统:在文件系统中,文件被分成多个逻辑块,每个逻辑块被映射到存储设备(如硬盘)上的物理块。文件系统使用块映射来管理文件的存储和访问,以便快速定位和读取文件数据。
    • 磁盘映像:在虚拟化环境中,磁盘映像文件通常被分成多个逻辑块,每个逻辑块被映射到宿主机上的物理块。这种块映射方式使得虚拟机可以访问和管理磁盘映像文件中的数据。
  • 块映射的优缺点分析
    • 块映射的优点是可以实现高效的数据存储和访问,但缺点是可能会出现映射冲突和碎片化的问题。

6.4 多级映射

  • 多级映射(Multilevel Mapping):多级映射是一种将逻辑地址映射到物理地址的层次结构映射方式。
    • 它将逻辑地址划分为多个级别,并使用多个映射表来实现地址映射。这种映射方式可以减少映射表的大小,并提高地址转换的效率。
  • 计算机多级映射例子有哪些
    • 虚拟内存管理:在操作系统中,虚拟内存管理使用多级页表来实现逻辑地址到物理地址的映射。逻辑地址空间被分成多个级别的页表,每个级别的页表负责映射一部分地址空间。这种多级映射方式使得操作系统可以管理大范围的虚拟内存,并且可以根据需要进行动态分配和回收。

6.5 哈希映射

  • 哈希映射(Hash Mapping):哈希映射使用哈希函数将逻辑地址映射到物理地址。
    • 它通过计算逻辑地址的哈希值,并将其映射到物理地址的相应位置。这种映射方式可以提供较好的地址分布和查找效率。
  • 计算机哈希映射例子有哪些
    • 计算机中的哈希映射常用于实现哈希表(Hash Table)数据结构,用于高效地存储和检索数据。
    • 假设我们有一个存储学生信息的哈希映射,其中学生的姓名作为键(Key),学生的分数作为值(Value)。我们可以使用学生的姓名来计算哈希值,并将其映射到相应的物理地址。

    import java.util.HashMap;
    
    public class StudentHashMapExample {
        public static void main(String[] args) {
            // 创建一个哈希映射
            HashMap<String, Integer> studentMap = new HashMap<>();
    
            // 添加学生信息到哈希映射
            studentMap.put("Alice", 85);
            studentMap.put("Bob", 92);
            studentMap.put("Charlie", 78);
            studentMap.put("David", 90);
    
            // 获取学生信息
            int aliceScore = studentMap.get("Alice");
            System.out.println("Alice's score: " + aliceScore);
    
            // 更新学生信息
            studentMap.put("Alice", 88);
            aliceScore = studentMap.get("Alice");
            System.out.println("Updated Alice's score: " + aliceScore);
    
            // 删除学生信息
            studentMap.remove("Bob");
            System.out.println("Bob's information removed.");
    
            // 遍历学生信息
            for (String name : studentMap.keySet()) {
                int score = studentMap.get(name);
                System.out.println(name + "'s score: " + score);
            }
        }
    }
    

6.6 思考题分析

  • Java 创建对象的引用是通过直接映射找到对象吗?
    • 在 Java 中,创建对象的引用并不是通过直接映射找到对象的。Java 中的对象引用实际上是指向对象在堆内存中的地址的值。
    • 当我们使用 new 关键字创建一个对象时,Java 会在堆内存中为该对象分配内存空间,并返回该对象在堆内存中的地址。我们可以将这个地址赋值给一个对象引用变量,从而创建一个对象的引用。
    • 对象引用变量存储的是对象在堆内存中的地址,而不是直接映射到对象。当我们通过对象引用变量访问对象的成员变量或方法时,Java 会根据对象引用变量中存储的地址找到对象在堆内存中的位置,并进行相应的操作。
    • 简而言之,Java 中的对象引用是通过存储对象在堆内存中的地址来实现的,而不是通过直接映射找到对象。

更多内容推荐