2024年7月

在学术界和科技行业,LaTeX 已成为撰写高质量文档的标准工具。然而,传统的 LaTeX 使用体验常常伴随着以下挑战:

  • 学习曲线陡峭
  • 环境配置复杂
  • 多人协作困难
  • 实时预览不便

当然,市面上不乏很多在线 LaTeX 编辑平台,但它们大多是封闭的商业服务,无法完全满足用户对数据隐私和自主可控的需求。那么,有没有一种既能享受在线协作的便利,又能确保数据安全的解决方案呢?

今天给大家推荐一款革命性的在线 LaTeX 编辑器 - Overleaf,以及它的本地部署工具包 Overleaf Toolkit。

Overleaf 简介

Overleaf
是一个强大的在线 LaTeX 编辑和协作平台,旨在简化科技写作流程。自2012年推出以来,Overleaf 凭借其直观的界面和强大的功能,迅速成为学术界和科技行业的首选工具。

Overleaf 的核心优势

  1. 用户友好的界面
    :降低 LaTeX 入门门槛,新手也能快速上手。
  2. 实时协作
    :多人可同时编辑,告别版本混乱。
  3. 实时预览
    :边写边看,像 Typora 一样所见即所得。
  4. 丰富的模板库
    :涵盖各种文档类型,快速启动项目。
  5. 在线访问
    :随时随地,一个浏览器搞定所有。
  6. 版本控制
    :内置 Git 功能,轻松管理文档历史。

谁在使用 Overleaf?

据官方数据,目前已有超过 1000 万用户正在使用 Overleaf,包括:

  • 世界顶尖大学 (如哈佛、MIT、斯坦福等)
  • 领先研究机构 (如 CERN、NASA 等)
  • 知名出版社 (如 Springer、Elsevier 等)

Overleaf Toolkit:开源版 Overleaf

尽管 Overleaf 的在线服务非常便捷,但出于数据安全、网络限制或定制需求等原因,很多机构和个人可能更倾向于在本地部署 Overleaf。这就是 Overleaf Toolkit 发挥作用的地方。

Overleaf Toolkit
是一套用于快速搭建本地 Overleaf 实例的工具集。对,你没听错,它能让你在自己的服务器上运行一个功能完备的 Overleaf 平台!

这个开源项目的核心特性是:

  1. 简化部署
    :利用 Docker 容器技术,将 Overleaf 的安装部署过程简化到几个命令。
  2. 灵活可控
    :支持切换社区版 (免费) 和企业版,数据完全存储在本地。
  3. 持续更新
    :与 Overleaf 官方保持同步,不断引入新的特性和改进。

目前 Overleaf 在 GitHub 上已经收获了 1.3 万 star,Overleaf Toolkit 已成为机构私有化部署 Overleaf 的首选方案。

Overleaf Toolkit vs 其他方案

为了更直观地展示 Overleaf 的优势,我们可以将其与其他主流的 LaTeX 写作方案进行对比:

特性 Overleaf 传统 LaTeX 编辑器 其他在线 LaTeX 平台
易用性 ★★★★★ ★★ ★★★★
协作功能 ★★★★★ ★★★
实时预览 ★★★★★ ★★ ★★★★
模板库 ★★★★★ ★★ ★★★
版本控制 ★★★★ ★★★ ★★★
自定义能力 ★★★★ ★★★★★ ★★★
离线使用 ★★(通过Toolkit) ★★★★★

可以看到,与传统的本地 LaTeX 编辑器 (如 TeXstudio) 相比,Overleaf Toolkit 最大的优势在于
支持多人实时协作和版本控制
。而与同为在线协作平台的 ShareLaTeX 相比,Overleaf Toolkit
完全开源,可私有化部署
,更能满足对数据安全和自主可控有要求的用户。

Overleaf 的安装使用

听到这里,你是不是已经跃跃欲试了?接下来,我们就一起来看看如何安装和使用 Overleaf。

Overleaf 的安装和使用相对简单,提供了 Docker 镜像,可以直接通过容器部署。

对于没有技术背景的同学而言,你也不用担心安装问题,
Sealos 应用商店
提供了一键部署的应用模板,点一下鼠标即可完成部署,非常丝滑。

如果你想快速部署一个 Overleaf,又不想陷入繁琐的安装和配置过程
,可以试试 Sealos。

直接打开
Overleaf 应用模板
,然后点击右上角的 “去 Sealos 部署”。

如果您是第一次使用
Sealos
,则需要注册登录 Sealos 公有云账号,登录之后会立即跳转到模板的部署页面。

这里面有几个变量,如果你不知道它们各自是什么意思,最好不要修改,使用默认值即可。

点击右上角的 “部署应用” 开始部署。部署完成后,直接点击应用的 “详情” 进入该应用的详情页面。

等待应用状态变成 running 之后,直接点击外网地址便可打开 Overleaf 的可视化界面。

首先需要注册一个用户:

注册完了之后会跳转到登录页面,登录完成后就可以开始正式使用了。

左边是编辑栏,右边是预览界面。

除了源代码编辑器之外,Overleaf 还支持可视化编辑器,也就是像 Typora 一样所见即所得。

其他功能大家可以自己探索,参考
官方 Wiki

写在最后

Overleaf 为 LaTeX 用户提供了一个强大而灵活的解决方案,无论是在线协作还是本地部署,都能满足你的需求。

如果你正在寻找一种方式来改善团队的科技写作体验,或者只是想要一个更加用户友好的 LaTeX 环境,那么可以试试 Overleaf。

软件包管理

【1】、Linux软件类型

  • 开源软件

    软件源代码开放,供用户免费学习,允许用户二次开发,用户使用放心,后期如果开发者不再进行维护,会有其他人进行维护

  • 闭源软件

    软件代码不公开发布,无法二次开发,后期开发者如果不进行维护损失很大

【2】、开源软件包类型

  • 源码包


    • 优点:

      可以看到软件源代码,安装灵活,可以自定义安装路径和按照功能,卸载方便

    • 缺点:

      安装过程麻烦,需要用户手动编译,需要手动解决软件包的依赖关系

  • 二进制包(RPM包)


    • 优点

      已经被编译好了,安装简单、安装速度快

    • 缺点

      所有功能、路径无法自定义

封包类型 说明
rpm软件包 扩展名为.rpm,适用于RedHat系列
deb软件包 扩展名为.deb,适用于Debian系列
源代码软件包 通常以.tar.gz、.tar.bz2格式的压缩包提供包含程序的原始代码文件
其他 提供install.sh、setup、*bin、*.pl
绿色免安装、提供已经编译好的程序及相关软件

【3】、RPM软件包

Redhat Package Manager

  • 由红帽公司提出,Redhat、SUSE等系列采用
  • 建立集中数据库,记录软件包安装/卸载等变化信息,分析软件包依赖关系

RPM包文件名特征

  • 软件名-版本信息.操作系统.硬件架构.rpm

    firefox-91.9.0-1.el8_5.x86_64.rpm

rpm软件包下载网站

1、RPM包的安装位置

文件类别 默认安装位置
普通执行程序(命令) /usr/bin、/bin/
服务器程序、管理工具 /usr/sbin/、/sbin/
配置文件 /etc/、/etc/软件名/
日志文件 /var/log/、/var/log/软件名
程序文档、man手册页 /usr/share/doc/、/usr/share/man/

2、RPM查询软件

-q:查询某一软件是否安装

-qa:查询系统中安装的所有软件包

-ql:显示软件在系统中安装产生了哪些数据

-qi:显示软件的详细信息

-qf:查看某一个命令是由哪一个软件包产生的

# 查看软件在系统中安装产生了哪些数据
rpm -ql tree-1.7.0-15.el8.x86_64
/usr/bin/tree
/usr/lib/.build-id
/usr/lib/.build-id/47
/usr/lib/.build-id/47/6e749c56abc75d25e3bc5c29e83507a605fd53
/usr/share/doc/tree
/usr/share/doc/tree/LICENSE
/usr/share/doc/tree/README
/usr/share/man/man1/tree.1.gz
# 查询软件的详细信息
{24-05-29 16:28}locahost:/ root# rpm -qi tree-1.7.0-15.el8.x86_64 
Name        : tree
Version     : 1.7.0
Release     : 15.el8
Architecture: x86_64
Install Date: Tue 28 May 2024 07:45:59 PM CST
Group       : Unspecified
Size        : 108563
License     : GPLv2+
Signature   : RSA/SHA256, Mon 12 Apr 2021 05:23:54 PM CST, Key ID 15af5dac6d745a60
Source RPM  : tree-1.7.0-15.el8.src.rpm
Build Date  : Mon 12 Apr 2021 04:38:17 PM CST
Build Host  : ord1-prod-x86build003.svc.aws.rockylinux.org
Relocations : (not relocatable)
Packager    : infrastructure@rockylinux.org
Vendor      : Rocky
URL         : http://mama.indstate.edu/users/ice/tree/
Summary     : File system tree viewer
Description :
The tree utility recursively displays the contents of directories in a
tree-like format.  Tree is basically a UNIX port of the DOS tree
utility.
# 查看系统中安装了多少个软件包
{24-05-29 16:43}locahost:/ root# rpm -qa | wc -l
663
# 查看某一个命令是由哪一个软件包产生的
{24-05-29 16:51}locahost:/ root# rpm -qf /usr/bin/chsh 
util-linux-user-2.32.1-43.el8.x86_64
{24-05-29 16:51}locahost:/ root# rpm -qf /usr/bin/ls  
coreutils-8.30-15.el8.x86_64

3、rpm软件包管理

  • 软件包依赖问题


    • 树形依赖:a--->b--->c--->d
    • 环形依赖:a--->b--->c--->d--->a
    • 模块依赖:需要模块文件支持
  • 如何使用光驱所提供的软件包


    • 首先我们需要将光驱挂载到系统中

      [root@bogon ~]# ll /dev/cdrom
      lrwxrwxrwx. 1 root root 3 Apr  9 08:35 /dev/cdrom -> sr0
      #这是光驱所在的位置
      
    • 进行挂载


      • 创建磁盘分区

        mkdir /mnt/cdrom
        
      • 把光驱挂载到创建的磁盘分区下,这是临时挂载,重启或者关机后失效

        mount /dev/sr0 /mnt/cdrom
        
      • 永久挂载,修改配置文件,在path:/erc/fstab写入如下文件,保存并退出

        /dev/sr0		/mnt/cdrom		iso9660 defaults	0 0
        

对于RPM软件包的管理,我们使用rpm命令去进行管理

-ivh:i安装,v显示详细信息,h显示软件安装进度

{24-05-29 17:15}locahost:/opt root# rpm -i /mnt/sr0/AppStream/Packages/v/vsftpd-3.0.3-36.el8.x86_64.rpm
{24-05-29 17:20}locahost:/opt root# rpm -q /mnt/sr0/AppStream/Packages/v/vsftpd-3.0.3-36.el8.x86_64.rpm
vsftpd-3.0.3-36.el8.x86_64

-Uvh:升级安装包(升级前先做好备份)

-e:卸载软件包

-qpi:查询软件包的信息

--nodesps:卸载软件包时忽略依赖关系
(一般不要忽略)

--import:导入红帽签名文件,一般来源于光盘的包都存在签名

【4】、yum机制

软件仓库

  • 本地仓库(也称本地yum源):自己搭建的,创建文件,在文件中配置仓库参数
    • 特点:不需要连接外网,软件包存放本机
  • 网络仓库:阿里、中科大、清华大学、163仓库等
    • 特点:需要连接外网、无需任何配置,直接下载

yum可以帮助我们自动解决依赖

  • yum repolist:列出可用的软件仓库

    [root@xu ~]# yum repolist 
    Updating Subscription Management repositories.
    Unable to read consumer identity
    
    This system is not registered with an entitlement server. You can use subscription-manager to register.
    
    repo id                                                                                 repo name
    appstream                                                                               local_redhat_appstream
    base                                                                                    local_redhat_base
    
  • yum list:列出软件包(包含为安装的),也可以查找包

    [root@xu ~]# yum list nginx
    Updating Subscription Management repositories.
    Unable to read consumer identity
    
    This system is not registered with an entitlement server. You can use subscription-manager to register.
    
    Last metadata expiration check: 0:09:44 ago on Tue 09 Apr 2024 10:14:00 PM CST.
    Available Packages
    nginx.x86_64                                                                1:1.14.1-9.module+el8.0.0+4108+af250afe                                                                appstream
    
  • yum provides 命令:搜索该命令是由哪个软件包提供的

  • yum update:升级软件包

  • yum list 软件包 --showduplicate:列出软件包所有的版本

    [root@xu yum.repos.d]# yum list nginx --showduplicates 
    Updating Subscription Management repositories.
    Unable to read consumer identity
    
    This system is not registered with an entitlement server. You can use subscription-manager to register.
    
    nginx stable repo                                                                                                                                            51 kB/s |  55 kB     00:01    
    Available Packages
    nginx.x86_64                                                              1.16.0-1.el8.ngx                                                                                      nginx-stable
    nginx.x86_64                                                              1:1.14.1-9.module+el8.0.0+4108+af250afe                                                               appstream   
    nginx.x86_64                                                              1:1.16.1-1.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.18.0-1.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.18.0-2.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.20.0-1.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.20.1-1.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.20.2-1.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.22.0-1.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.22.1-1.el8.ngx                                                                                    nginx-stable
    nginx.x86_64                                                              1:1.24.0-1.el8.ngx                                                                                    nginx-stabl
    
  • yum install --setopt=obsoletes=0 软件包:指定安装版本(使用yum安装软件,如果不指定软件版本,则默认安装软件仓库中的最新版)

  • [root@xu yum.repos.d]# yum install --setopt=obsoletes=0 nginx*1.20.0 -y
    
  • yum clean all :清楚缓存

  • yum makecache :生成缓存

    在使用网络仓库时一般会生成缓存,提高下载速度,记录软件在网络仓库所在的位置

【5】、yum软件仓库配置

1、永久挂载光驱

  • 创建磁盘分区

    mkdir /mnt/cdrom
    
  • 把光驱挂载到创建的磁盘分区下,这是临时挂载,重启或者关机后失效

    mount /dev/sr0 /mnt/cdrom
    
  • 永久挂载,修改配置文件,在path:/erc/fstab写入如下文件,保存并退出

    /dev/sr0		/mnt/cdrom		iso9660 defaults	0 0
    

2、yum的本地配置

  • 修改yum的repo文件

  • 在path:/etc/yum.repo.d/rhel8.repo中填入以下内容,保存退出

    [basos] # 具有唯一性,不可重复
    name=cdrom baseos packages #仓库描述,描述信息,自定义,不具备唯一性
    baseurl=file:///mnt/cdrom/BaseOS #指定软件仓库地址,file://用于指定本地软件包存放位置
    enabled=1 #软件仓库是否启动,1启动,0不启动
    gpgcheck=0 #是否检测软件包签名,0不检测,1检测,一般自己创建的仓库都是0
    [appstream]
    name=cdrom baseos packages
    baseurl=file:///mnt/cdrom/AppStream
    enabled=1
    gpgcheck=0
    
  • 清理yum的缓存文件

    [root@localhost ~]# yum clean all
    Updating Subscription Management repositories.
    Unable to read consumer identity
    
    This system is not registered with an entitlement server. You can use subscription-manager to register.
    
    20 files removed
    
    
  • 重新加载cache

    [root@localhost ~]# yum makecache 
    Updating Subscription Management repositories.
    Unable to read consumer identity
    
    This system is not registered with an entitlement server. You can use subscription-manager to register.
    
    mplayer                                                                                                              5.6 MB/s |  23 kB     00:00    
    cdrom baseos packages                                                                                                 33 MB/s | 2.4 MB     00:00    
    cdrom appstream package                                                                                               65 MB/s | 8.1 MB     00:00    
    Metadata cache created.
    

3、禁用yum源

  • 修改yum源的名字

    能够正常使用yum源的必要条件:

    1、yum源的文件名必须以:.repo结尾

    2、yum源的文件必须在path:/etc/yum.repo.d/目录下

    因此我们可以采用移动文件夹或者修改yum源的文件路径

  • 修改yum源的配置文件,将enabled=1,改为enabled=0

    yum源的配置文件中enabled=1表示允许调用这个源

    我们把想要禁用的源中的enabled改为0,则表示这会源被禁用。

4、系统升级 / kernel升级

  • 把虚拟机的光驱更换为更新的版本(8.8--->8.9)

  • 将新的光驱挂载到/mnt/cdrom下

  • yum list kernel 列出当前使用的kernel,和可用的kernel

  • yum update 更新yum源(更新所有的源-->系统升级)

    yum install kernel -y,下载新的kernel(只更新kernel-->内核升级)

  • systemctl reboot,重启系统,升级完成

  • 如果是系统升级,重启后会有两个选项(8.8 / 8.9)我们选择8.9,进入系统

  • 查找系统中所有的kernel version

    rpm -qa | grep kernel
    

    447是旧的版本

    513是新的版本

  • 将旧的版本删除

    rpm -qa | grep kernel | grep 477 | rpm -e
    

5、下载阿里云的yum的repo文件

  • 使用wget下载

    wget https://mirrors.aliyun.com/repo/Centos-8.repo
    
  • 我们可以禁用其他的repo源,也可以保留,但是要保证每一个repo源的唯一性,即repo源文件中【name】的唯一性

【6】、源码包管理

现有源码包(tar包)-----> 编译安装

  • 自定义性强,灵活度高

再有rpm软件包----->yum rpm

源码包安装步骤,以
nginx
为例

  • 官网下载源码包

    [root@xu ~]# wget https://nginx.org/download/nginx-1.24.0.tar.gz
    #我们通过官网下载的源码包一般都是tar包
    
  • 系统安装源码包依赖

    #我们可以去nginx官网查询下载nginx时所需要的依赖,通常我们对于依赖的下载是通过yum去下载,每一个软件的依赖都是不同的,需要去对应的官网进行查询
    
  • 解压源码包&进入源码包安装路径

    [root@xu ~]# tar -xf nginx-1.24.0.tar.gz 
    [root@xu ~]# ls
    anaconda-ks.cfg  nginx-1.24.0  nginx-1.24.0.tar.gz
    [root@xu ~]# cd nginx-1.24.0/
    [root@xu nginx-1.24.0]# ls
    auto  CHANGES  CHANGES.ru  conf  configure  contrib  html  LICENSE  man  README  src
    
  • configure检测系统环境&指定安装路径与功能

    #通过config我们可以指定安装路径,指定安装的功能模块,这就是通过源码包安装的优势,方便我们的管理和自定义
    #在我们完成指定之后,他就会帮我们去检测系统安装环境
    [root@xu nginx-1.24.0]# ./configure --prefix=/usr/local/ngix
    checking for OS
     + Linux 4.18.0-513.5.1.el8_9.x86_64 x86_64
    checking for C compiler ... found
     + using GNU C compiler
     + gcc version: 8.5.0 20210514 (Red Hat 8.5.0-20) (GCC) 
    checking for gcc -pipe switch ... found
    checking for -Wl,-E switch ... found
    checking for gcc builtin atomic operations ... found
    ...
    
  • make编译,将原代码转换为二进制

    #检测完成后,将源码,编译为二进制文件,所谓的源码都是一个一个的用编程语言写的源文件,因此需要通过编译成为计算机可以识别的二进制文件
    [root@xu nginx-1.24.0]# make
    make -f objs/Makefile
    make[1]: Entering directory '/root/nginx-1.24.0'
    cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs \
    	-o objs/src/core/nginx.o \
    	src/core/nginx.c
    ...
    
  • make install 安装软件包

    #编译为二进制文件后,我们就可以直接去安装了
    [root@xu nginx-1.24.0]# make install
    
  • 补充


    • 在我们通过源码包安装后,我想增加新的功能,我们还需要重新编译,但不安装

      #由于我们是通过源码包安装的软件,因此对其功能的选择是很方便的,我们首先看一下nginx当前的信息
      [root@xu sbin]# ./nginx -V
      nginx version: nginx/1.24.0
      built by gcc 8.5.0 20210514 (Red Hat 8.5.0-20) (GCC) 
      configure arguments: --prefix=/usr/local/nginx
      #我们想增加新的功能时,必须要带上之前使用config指定的内容,再重新通过config去检查安装环境,进行编译
      [root@xu nginx-1.24.0]# ./configure --prefix=/usr/local/nginx/ --with-http_ssl_module
      checking for OS
       + Linux 4.18.0-513.5.1.el8_9.x86_64 x86_64
      checking for C compiler ... found
       + using GNU C compiler
       + gcc version: 8.5.0 20210514 (Red Hat 8.5.0-20) (GCC) 
      checking for gcc -pipe switch ... found
      checking for -Wl,-E switc[root@xu nginx-1.24.0]# make
      make -f objs/Makefile
      make[1]: Entering directory '/root/nginx-1.24.0'
      cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs \
      	-o objs/src/core/nginx.o \
      h ... found
      checking for gcc builtin atomic operations ... found
      checking for C99 variadic macros ... found
      #然后再重新编译
      [root@xu nginx-1.24.0]# make
      make -f objs/Makefile
      make[1]: Entering directory '/root/nginx-1.24.0'
      cc -c -pipe  -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g  -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs \
      	-o objs/src/core/nginx.o \
      ## !!!编译完成后不要安装!!!,如果安装会覆盖之前的内容
      
      
      #在编译完成后,nginx源码包目录下会有一个objs目录,里面由nginx可执行文件,我们需要将nginx复制到安装路径下的/sbin/下,替换掉原来的nginx可执行文件
      [root@xu nginx-1.24.0]# ls
      auto  CHANGES  CHANGES.ru  conf  configure  contrib  html  LICENSE  Makefile  man  objs  README  src
      [root@xu objs]# cp ./nginx /usr/local/nginx/sbin/
      cp: overwrite '/usr/local/nginx/sbin/nginx'? y
      [root@xu objs]# cd /usr/local/nginx/sbin/
      [root@xu sbin]# ls
      nginx
      
      #最后我们检查nginx的版本信息,至此增加完成
      [root@xu sbin]# ./nginx -V
      nginx version: nginx/1.24.0
      built by gcc 8.5.0 20210514 (Red Hat 8.5.0-20) (GCC) 
      built with OpenSSL 1.1.1k  FIPS 25 Mar 2021
      TLS SNI support enabled
      configure arguments: --prefix=/usr/local/nginx/ --with-http_ssl_module
      
      

【7】、自定义软件仓库

yum运行思路:

运行yum命令程序---->仓库配置文件---->软件包仓库

软件包仓库:

1、众多的软件包

2、仓库数据文件(仓库清单)

  • 模拟在Linux不能上网的情况下去按照软件

    1、我们首先需要通过宿主机把软件下载好,在使用传输工具或者命令传到虚拟机中

    2、然后就可以在虚拟机中进行安装了

  • 解压传入的压缩包,里面是一个个的rpm软件包

  • 在下载之前需要让yum去识别这个软件的安装方式

1、首先看下光盘中的安装方式是什么样的

BaseOS和AppStream目录下的repodata里面的repomd存储的是下载方式的索引,在使用yum去下载时都需要在repomd里找到对应的软件下载方式后再去packages目录下去下载

repodata也就仓库清单

[root@localhost ~]# ls /mnt/cdrom/BaseOS/repodata/
005f3daf06049c081565a4066404ce4ca5bb0275b8a9e5a04c725b09ac0bbb07-comps-BaseOS.x86_64.xml
6dcf6de7dd1089a452d5caa14fa1db8ce7cb79aad28a47a95f5aed14bd22f767-productid.gz
9b3a380a984ef4d552ce527b40f3415775e18955b6ca9a9c305f60cf6529f602-filelists.xml.gz
a28f983c96f2d691c966620431244d8c4a8415ec349e31b9cce280783ec323d3-primary.xml.gz
ab4ec8c050898edbe33646155dff8b15d16e3d29f3a13b029b2e2e6ca5c24a57-other.xml.gz
dd3fd62a2077ecf119b6f7971aa8882a546356fef66a874bf4860f8a6d45f416-comps-BaseOS.x86_64.xml.gz
productid
repomd.xml
TRANS.TBL
[root@localhost ~]# ls /mnt/cdrom/AppStream/repodata/
2adc257c8b938376e8d66f70b6dd601f46111b6f46e06d610d67f4e9daab434c-primary.xml.gz
2c384cc97bc979264430666010a3f52aeca381fd2137abba70ab6672429ac194-filelists.xml.gz
45295e7b00a783d7ef66a3d6f2f5890477a61c5c465f7190a910b50d7cf222e4-modules.yaml.gz
468fea75becd836ab1fc45fe77d820e37ab22b18cc2a4a992f65b81833f96854-comps-AppStream.x86_64.xml
6dcf6de7dd1089a452d5caa14fa1db8ce7cb79aad28a47a95f5aed14bd22f767-productid.gz
ad09469a33bac2649097cdf63bdc43d4415c6bfaffabdf6ce3da47b6fcb03596-comps-AppStream.x86_64.xml.gz
e0bb60aac5d524ef1cf945f2a934e27a63d77238bc2b25f13c7335286bf1869c-other.xml.gz
productid
repomd.xml
TRANS.TBL

2、生成repodata文件

因此我们也需要按照BaseOS和AppStream目录下的repodata生成新的repodata

使用createrepo命令去生成mplayer的repodata文件

createrepo默认是没有下载的

yum list createrepo # 查看下载createrepo所需要的包
createrepo_c # 需要createrepo_c
yum install createrepo_c -y
createrepo /opt/tools #生成了repodata文件

3、编写yum的repo源

[mytools]
name=mytools
baseurl=file:///opt/tools
enabled=1
gpgcheck=0

清理yum下载的包,重进加载缓存,下载mplayer

yum clean
yum makecache
yum install mplayer

4、repomd.xml、repodata、yum源的repo文件的作用、联系

  • repomd.xml文件的作用是存放软件的索引信息,通过yum去下载时,会根据repo文件中的【name】去找对应目录下的repomd.xml

  • repodata目录的作用是收集当前软件的各种信息(版本信息等),并且写入repomd.xml中

  • yum源的repo文件,在使用yum去下载时会去所有的repo文件中去找到对应路径,去看哪个路径下有有对应软件的repodata目录(如果找遍了所有的源都没有,则会报错)。因此在写repo源文件时,一定要保证路径的正确性

  • 联系


    • 首先使用createrepo命令

      会发生两个步骤


      • 在软件包所在的目录生成repodata目录
      • repodata目录收集对应软件的各种信息,写入repomd.xml中
    • 通过yum去下载软件


      • 首先去/etc/yum.repos.d/目录下查找可用的repo源文件
        • 可用的repo文件也就是在baseurl写的路径中可以找到repodata目录
      • 进而可以找到repom.xml文件,索引到对应的软件,然后在packages中下载
  • 在一个系统中可以存在多个yum源,但是一般来说通常只保留一个,可用的repo源越多,当一个源出错时,可能会造成连带影响

【8】、网络yum仓库

构建网络服务,通过网络服务提供仓库内容

  • 构建FTP服务
  • 利用FTP服务共享目录,我们将自定义软件仓库,放到共享目录下即可
  • 在客户端通过curl去访问ftp共享服务目录
# 服务端进行配置
cp  -r  /etc/yum.repos.d/mytools  /var/ftp/rpms
# 客户端进行测试
curl ftp://192.168.121.180/rpms

在客户端需要写yum的repo文件,告诉yum你可以去ftp共享目录下获取软件

[ftp]
name=my_ftp
baseurl=ftp://192.168.121.180/rpms
gpgcheck=0
enable=1

利用这种架构,

在ftp服务器上可以为很多主机提供一个自定义的网路yum仓库

利用ftp服务提供光盘yum仓库的提供

我们只需将光驱设备挂载到/var/ftp目录下即可

mount /dev/cdrom  /var/ftp/dvd/
# 修改/etc/fstab文件

# 在客户端进行测试
curl ftp://192.168.121.180/dvd/
dr-xr-xr-x    4 0        0            2048 Nov 21  2023 AppStream
dr-xr-xr-x    4 0        0            2048 Nov 21  2023 BaseOS
dr-xr-xr-x    3 0        0            2048 Nov 21  2023 EFI
-r--r--r--    1 0        0            2204 Nov 03  2023 LICENSE
-r--r--r--    1 0        0             883 Nov 21  2023 TRANS.TBL
dr-xr-xr-x    3 0        0            2048 Nov 21  2023 images
dr-xr-xr-x    2 0        0            2048 Nov 21  2023 isolinux
-r--r--r--    1 0        0              88 Nov 21  2023 media.repo

# 修改客户端的配置文件
[basos]
name=local_basos
baseurl=ftp://192.168.121.180/dvd/BaseOS
gpgcheck=0
enable=1

[appstream]
name=local_appstream
baseurl=ftp://192.168.121.180/dvd/AppStream
gpgcheck=0
enable=1

【9】、yum仓库更新

yum仓库的更新分为两个方面

  • 仓库数据文件的更新
  • yum缓存的更新
# 服务端仓库数据文件的更新
mv /opt/tools/xxx.rpm /root
createrepo --update /var/ftp/rpms

# 客户端yum缓存的更新
yum makecache
yum repoinfo

本文分享自华为云社区
《GaussDB(DWS)性能调优:DM区优化案例——维度表关联条件存在会计期》
,作者: O泡果奶~。

当前DM(P1、P3、CBGDM)存在维度表与主表关联时使用会计期作为关联条件,会导致出现大内存占用或未识别数据倾斜的问题

【场景一】f.period_id = 维度表.period_id

1.1、【问题描述】

主表和维度表关联过程中将会计期作为关联条件,导致维度表未进行分区剪枝,可能会产生大内存占用的情况

1.2、【原始SQL】

仅呈现SQL中的问题,详细SQL见附件

FROM
DMACC.dm_adp_ar_trx_dtl_tmp F
INNER JOIN DMDIM.DM_DIM_REGION_RC_D REG ON F.COA_GEO_PC_KEY
=REG.GEO_PC_KEY
INNER JOIN DMDIM.DM_DIM_PRODUCT_T_D T9 ON F.PROD_KEY
=T9.PROD_KEY
AND T9.PROD_POV_ID
= 1INNER JOIN DMDIM.DM_DIM_PROJECT_D J ON F.PROJ_KEY=J.PROJ_KEY
INNER JOIN DMDIM.DM_DIM_CONTRACT_D HT ON HT.CONTRACT_KEY
=F.CONTRACT_KEY
LEFT JOIN DMCOMMON.DWR_CONFIG_DOMESTIC_FINANCE_V FIN ON F.COA_COMPANY_KEY
=FIN.COMPANY_KEY
AND F.COA_GEO_PC_KEY
=FIN.GEO_PC_KEY
LEFT JOIN DMAR.DWB_FMD_DIM_INVOICE_PAY_PLAN_D PP ON F.AR_INVOICE_PAY_PLAN_ID
=PP.AR_INVOICE_PAY_PLAN_ID
AND F.PERIOD_ID
=PP.PERIOD_ID
LEFT JOIN DMARDI.DWR_DIM_AR_INVOICE_V INV ON F.AR_INVOICE_ID
=INV.AR_INVOICE_ID
INNER JOIN DMARDI.DWR_DIM_AR_APPLICATION_V APP ON F.AR_APPLICATION_RECORD_ID
=APP.AR_APPLICATION_RECORD_ID
INNER JOIN DMARDI.DWR_DIM_AR_RECEIPT_V RCP ON F.AR_RECEIPT_RECORD_ID
=RCP.AR_RECEIPT_RECORD_ID
INNER JOIN DMARDI.DWR_DIM_AR_RECEIPT_TYPE_V RT ON RCP.RECEIPT_RECORD_TYPE_ID
=RT.AR_RECEIPT_TYPE_ID
LEFT JOIN (
SELECT C
.CONTRACT_KEY,
D.COMPANY_KEY,
R.FIRST_SHIP_DATE
FROM
DMDIM.dm_dim_contract_d C,
DMDIM.DM_DIM_COMPANY_D D,
DMARDI.DWR_CTRCT_FIRST_SHIP_DATE_R R
WHERE
C.CONTRACT_ID
=R.CONTRACT_ID
AND D.COMPANY_ID
=R.COMPANY_ID
) FR ON F.CONTRACT_KEY
=FR.CONTRACT_KEY
AND F.COA_COMPANY_KEY
=FR.COMPANY_KEY
INNER JOIN DMDIM.DM_DIM_SALES_MODE_D MO ON F.SALES_MODE_KEY
=MO.SALES_MODE_KEY
JOIN DMDIM.DM_DIM_JOURNAL_SOURCE_D T29 ON F.JE_SOURCE_ID
=T29.JE_SOURCE_ID
JOIN DMDIM.DM_DIM_JOURNAL_CATEGORY_D T30 ON F.JE_CATEGORY_ID
= T30.JE_CATEGORY_ID

1.3、【性能分析】

image.png
image.png
image.png
从上图的执行计划可以看出,由于用会计期作为关联条件,导致维度表未进行分区剪枝,数据量大,不但产生了数据倾斜,同时还由于数据量大出现了关联下盘,大大降低了sql执行性能。
主表只有一个会计期,可以识别出对应的会计期,然后对SQL进行如下改写:

FROM
DMACC.dm_adp_ar_trx_dtl_tmp F
INNER JOIN DMDIM.DM_DIM_REGION_RC_D REG ON F.COA_GEO_PC_KEY
=REG.GEO_PC_KEY
INNER JOIN DMDIM.DM_DIM_PRODUCT_T_D T9 ON F.PROD_KEY
=T9.PROD_KEY
AND T9.PROD_POV_ID
= 1INNER JOIN DMDIM.DM_DIM_PROJECT_D J ON F.PROJ_KEY=J.PROJ_KEY
INNER JOIN DMDIM.DM_DIM_CONTRACT_D HT ON HT.CONTRACT_KEY
=F.CONTRACT_KEY
LEFT JOIN DMCOMMON.DWR_CONFIG_DOMESTIC_FINANCE_V FIN ON F.COA_COMPANY_KEY
=FIN.COMPANY_KEY
AND F.COA_GEO_PC_KEY
=FIN.GEO_PC_KEY
LEFT JOIN DMAR.DWB_FMD_DIM_INVOICE_PAY_PLAN_D PP ON F.AR_INVOICE_PAY_PLAN_ID
=PP.AR_INVOICE_PAY_PLAN_ID
AND PP.PERIOD_ID
= '202406'LEFT JOIN DMARDI.DWR_DIM_AR_INVOICE_V INV ON F.AR_INVOICE_ID=INV.AR_INVOICE_ID
INNER JOIN DMARDI.DWR_DIM_AR_APPLICATION_V APP ON F.AR_APPLICATION_RECORD_ID
=APP.AR_APPLICATION_RECORD_ID
INNER JOIN DMARDI.DWR_DIM_AR_RECEIPT_V RCP ON F.AR_RECEIPT_RECORD_ID
=RCP.AR_RECEIPT_RECORD_ID
INNER JOIN DMARDI.DWR_DIM_AR_RECEIPT_TYPE_V RT ON RCP.RECEIPT_RECORD_TYPE_ID
=RT.AR_RECEIPT_TYPE_ID
LEFT JOIN (
SELECT C
.CONTRACT_KEY,
D.COMPANY_KEY,
R.FIRST_SHIP_DATE
FROM
DMDIM.dm_dim_contract_d C,
DMDIM.DM_DIM_COMPANY_D D,
DMARDI.DWR_CTRCT_FIRST_SHIP_DATE_R R
WHERE
C.CONTRACT_ID
=R.CONTRACT_ID
AND D.COMPANY_ID
=R.COMPANY_ID
) FR ON F.CONTRACT_KEY
=FR.CONTRACT_KEY
AND F.COA_COMPANY_KEY
=FR.COMPANY_KEY
INNER JOIN DMDIM.DM_DIM_SALES_MODE_D MO ON F.SALES_MODE_KEY
=MO.SALES_MODE_KEY
JOIN DMDIM.DM_DIM_JOURNAL_SOURCE_D T29 ON F.JE_SOURCE_ID
=T29.JE_SOURCE_ID
JOIN DMDIM.DM_DIM_JOURNAL_CATEGORY_D T30 ON F.JE_CATEGORY_ID
= T30.JE_CATEGORY_ID

经优化后,执行计划如下图所示,维度表进行了分区剪枝,数据量减少,缓解了数据倾斜,也避免了关联下盘的问题。
image.png
image.png

【场景二】f left join 维度表 on f.period_id = 维度表.period_id and 维度表.period_id = ‘会计期’

2.1、【问题描述】

主表和维度表关联过程中将会计期作为关联条件,同时还为维度表会计期进行赋值,可能会产生数据倾斜未识别的情况

2.2、【原始SQL】

FROM
dmdp.dm_dpc_inv_m_dtl_f_TEM_A LT1
LEFT JOIN dmcommon.dm_dim_prod_key_r LT2 ON LT1.prod_key
=LT2.old_key
AND LT1.period_id
=LT2.period_id
AND LT2.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_reg_key_r LT3 ON LT1.period_id=LT3.period_id
AND LT1.geo_pc_key
=LT3.old_key
AND LT3.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT4 ON LT1.period_id=LT4.period_id
AND LT1.account_dept_cust_key
=LT4.old_key
AND LT4.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_proj_key_r LT5 ON LT1.period_id=LT5.period_id
AND LT1.proj_key
=LT5.old_key
AND LT5.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT6 ON LT1.period_id=LT6.period_id
AND LT1.enterprise_cust_key
=LT6.old_key
AND LT6.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_rep_key_r LT7 ON LT1.period_id=LT7.period_id
AND LT1.report_item_id
=LT7.old_key
AND LT7.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_supply_center_key_r LT8 ON LT1.period_id=LT8.period_id
AND LT1.supply_center_key
=LT8.old_key
AND LT8.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_inv_key_r LT9 ON LT1.period_id=LT9.period_id
AND LT1.inventory_class_key
=LT9.old_key
AND LT9.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_bus_key_r LT10 ON LT1.period_id=LT10.period_id
AND LT1.business_status_key
=LT10.old_key
AND LT10.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_hisi_key_r LT11 ON LT1.period_id=LT11.period_id
AND LT1.hisi_prod_key
=LT11.old_key
AND LT11.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_inv_org_key_r LT12 ON LT1.period_id=LT12.period_id
AND LT1.inventory_org_key
=LT12.old_key
AND LT12.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT13 ON LT1.period_id=LT13.period_id
AND LT1.end_cust_key
=LT13.old_key
AND LT13.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT14 ON LT1.period_id=LT14.period_id
AND LT1.sign_cust_key
=LT14.old_key
AND LT14.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT15 ON LT1.period_id=LT15.period_id
AND LT1.agent_distribution_cust_key
=LT15.old_key
AND LT15.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_com_key_r LT16 ON LT1.period_id=LT16.period_id
AND LT1.company_key
=LT16.old_key
AND LT16.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_con_key_r LT17 ON LT1.period_id=LT17.period_id
AND LT1.contract_key
=LT17.old_key
AND LT17.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_con_key_r LT18 ON LT1.period_id=LT18.period_id
AND LT1.loan_contract_key
=LT18.old_key
AND LT18.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_supply_center_key_r LT19 ON LT1.period_id=LT19.period_id
AND LT1.target_supply_center_key
=LT19.old_key
AND LT19.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_subinventory_key_r LT20 ON LT1.period_id=LT20.period_id
AND LT1.subinventory_key
=LT20.old_key
AND LT20.PERIOD_ID
= 202406WHERE1 = 1AND partition_value IN (0, 1 )

2.3、【性能分析】

image.png
image.png
上图的执行计划可以看出,在主表一开始关联过程中就存在数据倾斜,导致SQL执行性能差。
image.png
image.png
详细执行计划中,虽然维度表进行了分区剪枝,但由于使用了 left join,导致关联条件中维度表的常量period_id不能直接赋值给主表period_id,主表关联后的结果重分布时将period_id作为了分布键之一,这会影响优化器的倾斜优化。
可以将f.period_id = 维度表.period_id这一关联条件删掉,对sql进行如下改写

FROM
dmdp.dm_dpc_inv_m_dtl_f_TEM_A LT1
LEFT JOIN dmcommon.dm_dim_prod_key_r LT2 ON LT1.prod_key
=LT2.old_key
AND LT2.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_reg_key_r LT3 ON LT1.geo_pc_key=LT3.old_key
AND LT3.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT4 ON LT1.account_dept_cust_key=LT4.old_key
AND LT4.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_proj_key_r LT5 ON LT1.proj_key=LT5.old_key
AND LT5.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT6 ON LT1.enterprise_cust_key=LT6.old_key
AND LT6.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_rep_key_r LT7 ON LT1.report_item_id=LT7.old_key
AND LT7.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_supply_center_key_r LT8 ON LT1.supply_center_key=LT8.old_key
AND LT8.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_inv_key_r LT9 ON LT1.inventory_class_key=LT9.old_key
AND LT9.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_bus_key_r LT10 ON LT1.business_status_key=LT10.old_key
AND LT10.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_hisi_key_r LT11 ON LT1.hisi_prod_key=LT11.old_key
AND LT11.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_inv_org_key_r LT12 ON LT1.inventory_org_key=LT12.old_key
AND LT12.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT13 ON LT1.end_cust_key=LT13.old_key
AND LT13.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT14 ON LT1.sign_cust_key=LT14.old_key
AND LT14.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_cus_key_r LT15 ON LT1.agent_distribution_cust_key=LT15.old_key
AND LT15.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_com_key_r LT16 ON LT1.company_key=LT16.old_key
AND LT16.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_con_key_r LT17 ON LT1.contract_key=LT17.old_key
AND LT17.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_con_key_r LT18 ON LT1.loan_contract_key=LT18.old_key
AND LT18.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_supply_center_key_r LT19 ON LT1.target_supply_center_key=LT19.old_key
AND LT19.PERIOD_ID
= 202406LEFT JOIN dmcommon.dm_dim_subinventory_key_r LT20 ON LT1.subinventory_key=LT20.old_key
AND LT20.PERIOD_ID
= 202406WHERE1 = 1AND partition_value IN (0, 1 )

改写后,执行计划如下所示

image.png

可以看出,执行计划不但进行了分区剪枝,同时优化器还进行了倾斜优化,提高了SQL执行性能

点击关注,第一时间了解华为云新鲜技术~

大家好,我是码农先森。

1994年我出生在湖南的农村,就在同年加拿大的拉斯姆斯·勒多夫创造了 PHP,这时的 PHP 还只是用 Perl 编写的 CGI 脚本。或许是时间的巧合 PHP 变成了我后半生谋生的手段,当时拉斯姆斯·勒多夫写这些脚本的目的,只是为了统计自己网站的访问者。就是这样一个简单的目的就诞生了「世界上最好的语言 PHP」,伟大的事业往往是最简单的开始。这些用 Perl 编写的 CGI 脚本,可以认为是 PHP 的雏形,亦或是称为 PHP 的第一个版本「PHP1.0」也不为过。

据说下面这段代码是 PHP 第一个版本,不知大家有没有似曾相似的感觉。

<!--include /text/header.html-->

<!--getenv HTTP_USER_AGENT-->
<!--ifsubstr $exec_result Mozilla-->
  Hey, you are using Netscape!<p>
<!--endif-->

<!--sql database select * from table where user='$username'-->
<!--ifless $numentries 1-->
  Sorry, that record does not exist<p>
<!--endif exit-->
  Welcome <!--$user-->!<p>
  You have <!--$index:0--> credits left in your account.<p>

<!--include /text/footer.html-->

也就在这一年,网景公司(Netscape)发布了 Navigator 浏览器0.9版,这是历史上第一个比较成熟的网络浏览器,一经发布则轰动业界,成熟的网络浏览器为 PHP 的快速发展提供了天然的优质土壤。

次年,拉斯姆斯·勒多夫发布了 PHP2.0 这个版本它不仅可以访问数据库,还能嵌入 HTML 中动态处理数据。新的特性吸引了一批 PHP 的爱好者,其中也包括了后来加入到了 PHP 核心开发组的 Zeev Suraski(泽埃夫·苏拉斯基)和 Andi Gutmans(安迪·古曼兹),著名的「Zend VM 引擎」便是由这两名开发者所主导开发的。

时间来到了1998年底,中国的即时通讯也在这时开始崛起,最典型的就是 OICQ 也就是 QQ 的前身。这时的 PHP3.0 官方版本也正式发布,这个版本最强的特性是它的可扩展模块,这点吸引了大量的业余开发者提交了新的扩展,这同时为 PHP 生态的发展埋下了深深的根基,以至于后续出现的 Swoole 异步通信扩展也是得福于此。

两年之后,在千禧之年「2000年」这也是互联网泡沫刚刚破灭的第一年。PHP4.0 发布了,这个版本首次推出了由 C 语言编写的 Zend VM 虚拟机引擎,新程序的运行性能相较于之前提升了近十倍。此外,语法上还引入了类和对象的支持,这便是面向对象特性的最初模样。

在国内非典还盛行的时期「2003年」,一个基于 PHP 开发的开源内容管理系统 WordPress v1.0 正式发布,一经发布就得到广大开发者的喜爱,让搭建网站博客的门槛极大的减低。根据目前最新统计,WordPress 的市场份额持续增长,已达到惊人的 46%,成为全球最流行的 CMS。这意味着在互联网上,大约有一半的网站都是使用 WordPress 创建的。可想而知看现有的数据就可以推测出当时的 WordPress 热度高涨,这也同时推动了 PHP 的广泛传播。还记得有次改写 WordPress 的源代码,习惯了面向对象编程的我,在面向过程的源代码面前仿佛被乌云笼罩,久久无法下手,不知 PHPer 有没有同感?

非典刚刚结束的次年「2004年」,新时代的 PHP5.0 正式发布,这是一个 PHP 跨时代的到来。并且之后多年里 PHP 一直在 v5 的版本上迭代,是 PHP 发展史上历时最长的一个大版本,这个版本里全面引入了面向对象特性,并且将 Zend VM 虚拟引擎升级到了第二代,使其性能得到显著的提升。

在 PHP5 发布的第三年「2006年」,国产 PHP 开发框架 ThinkPHP 诞生「当时的框架原名还是 FCS」,ThinkPHP 的发布惠及了国内大量的 PHP 开发者。其中我的处女项目就是用的 ThinkPHP3.2 开发的,相信有很多的 PHPer 也和我一样,吃到了这个国产框架的红利。但也是由于国产框架的推出,让 PHP 开发项目的门槛进一步的减低,吸引了大量非科班专业的人转行从事 PHP 开发,从而也造成了如今 PHP 开发者的技术水平层次不齐的局面。

PHP 长期以来的应用场景都是聚焦于 CMS 内容网站的建设,以至于在异步通信领域无所建树。直到 2012 年 Swoole 开始发布第一个版本,虽说刚开始的 Swoole 并不完美,但也填补了 PHP 在异步网络通信领域的空白。同时让 PHP 也能在实时通信、物联网通信、游戏开发等领域大展拳脚,在 PHP 的发展上做到了锦上添花。就在同年 Go 语言正式的第一个版本 v1.0 横空出世,标志着 Go 语言正式走向成熟,在往后的多年里一直备受编程界的强烈关注。Go 语言以高性能而闻名于世,而 PHP 在高性能方面存在着明显的短板,这也为多年以后 PHPer 转型 Go 语言而埋下了伏笔。

PHP 经过在 v5 版本上长达十多年的迭代,终于在2015年底正式发布了 PHP7,这是 PHP 一次质的飞跃。大家都知道改革开放对中国的经济产生了重大的影响,同样 PHP7 在 PHP 的发展史上也是产生了同等级别影响。同时让我们值得骄傲的是中国新浪微博工程师惠新辰「俗称鸟哥」,也是唯一的一名中国人成为了 PHP7 的核心发起人之一。PHP7 的问世让新浪微博系统的性能也得到了大幅度的提升,其实新浪微博平台第一代架构采用的是 LAMP 架构,后台系统的开发语言使用的是 PHP,此外还有很多平台最开始使用的也是 PHP 进行开发的,比如脸书FaceBook、阿里巴巴的淘宝、百度、腾讯等,这一点也足以证明 PHP 当时的火爆程度。

当我们还在受新冠疫情的困扰,每天核酸不断。而 PHP8 已在2020年底悄然发布,作为一个大版本的更新。其最大的亮点是引入了 JIT 编译技术,该技术能够在 PHP 代码执行时编译成机器码,而不是每次运行时都需要解释执行。JIT 编译技术提高了代码的执行效率,使得 PHP 运行更快,性能提升尤其显著,大幅度改善了 PHP 的执行性能。简而言之,PHP8 为 PHP 生态系统注入了强劲的动力。

现如今虽说疫情已经离我们远去,但疫情对全球经济影响仍在持续,加上某些国家的局部动荡,导致了全球经济的连续下滑。在这样的大背景下,我们所处的互联网行业也不容乐观,国内互联网公司裁员风波肆起,这也间接导致了程序员之间的内卷加剧。经济好比是一块蛋糕,经济萎靡不振蛋糕则会越来越小。程序员无法外展,那么只能内卷,更何况 PHP 一直处于被鄙视链的边缘,很多做 Java、C/C++、Go 等语言的就自认为更高级技术更牛,随着被其他语言的挤兑歧视,这也让长期只从事 PHP 开发的程序员被迫淘汰出局。我想这便是,如今被人认为 PHP 没落的真实原因吧?

幻想一下,如果回到疫情之前,世界经济持续高涨。国内的互联网行业也还是如火如荼,中小企业也蓬勃发展、生机盎然,呈现着一片繁荣的景象。那我想必 PHP 还是会在编程语言排行榜上持续霸榜,不至于跌落到前十开外吧。从所周知很多国内外的知名大公司也是缘于 PHP 起步的,同时也让 PHP 相关的从业者受益匪浅。从过往来看 PHP 对互联网行业所带来贡献,也足以对得起网络上一直流行的称号「PHP是世界上最好的语言」。

有次在 v2ex 社区上看到有位网友发布了一个帖子,其主要内容是感谢 PHP 给他带来的收益,让他一个前端的切图仔,也能上手使用 PHP 做后端开发,促使他接了很多的外包项目获得了可观的回报。但有人反驳说了,一个拧螺丝的打工仔,还感谢起扳手来了?但我想说的是如果连扳手都没有,那你可能连拧螺丝的机会都没有。纵观 PHP 这些年的发展也是在逐渐的迎合开发者,有些语言出身就在罗马「Go 语言出自谷歌的 C 语言之父」,而有些语言则在出身普通,势必会带来不一样的成长路径和结果。

时代造就了马云,同样也成就了 PHP。PHP9 还在紧锣密鼓的开发中,据说为了更好的性能,会引入类似 async/await 原生异步编程的方式,这对于 PHP 来说将是一种前沿且高效的编程模式。最后,随着世界经济的逐步恢复,互联网行业的不断迭代创新。PHP 也会焕发新生若在其坚守易用性、灵活性之外,再加强在高性能方面的建树,势必会稳稳地屹立在 Web 开发之巅不可动摇。

众人拾柴火焰高,PHP 程序员应该要抱团取暖勇度寒冬,努力推动 PHP 生态的建设。早日让众多唱衰「PHP不行了」的声音消失殆尽,并重回巅峰时刻。

感谢大家阅读,与君共勉。


欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

作者:
小牛呼噜噜

大家好,我是呼噜噜,由于x86保护模式是比较复杂晦涩的,所以特地单拉出来,实模式和保护模式一个重要的更新就是对内存的管理与保护,并且随着软件的发展,为了极致地压榨CPU的性能,硬件和软件都做出了许多努力,为了更好的管理内存,引入分段,分页,段页等等。本文会沿着内存的主线,穿插于实模式和保护模式之间,并结合历史渊源,更好地讲解这里面的发展与变化。

实模式

代号8086

当计算机启动时,实模式运行的时间对我们人来说是无感的,但是并不是其不重要,本文笔者想讲的故事,它的起点来源一个产品,一个划时代芯片,
8086
,其是Intel公司推出的最早,也是最流行的面向个人电脑的CPU型号

我们可以看到上图有10个
引脚
,由于芯片是对称的,所以8086芯片一共(只)有20个引脚。不像现在的CPU那样成百上千的都有,脚这么多可不仅仅是为了爬得快

我们一起来看下8086的引脚图:

这些引脚有哪些作用?主要有下面这几种:

  1. 电源线Vcc(40),地线GND(1和20)
  2. 地址/数据引脚
  • 地址/数据
    分时复用
    引脚AD15-AD0(39,2-16):传送地址时单向输出,传送数据时双向输入或输出
  • 地址状态
    分时复用
    引脚A19/S6~ A16/S3(35-38):输出、三态引脚。T1状态做地址线,T2-T4状态用于输出状态信息
  • 所谓
    分时复用
    就是
    在同一根传输线上,在不同时间传送不同的信息
    ,所以8086对应的地址线
    16+4=20根
  1. 控制引脚
NMI(17):非屏蔽中断请求信号,不受IF影响,此信号一出现,当前指令,执行结束后立即进行中断处理。
INTR(18):可屏蔽中断请求信号,输入高电平有效。
CLK(19):系统时钟,输入
RESET(21):复位信号,输入,高电平有效。复位信号使处理器马上结束现行操作,对处理器的内部寄存器进行初始化
READY(22):数据准备好信号线,输入,高电平有效,由存储器或I/O端口发来。CPU在每个总线周期的T3状态对READY采样,若为低电平,则自动插入一个或几个等待状态Tw,直到变为高电平才能进入T4状态
TEST(23):等待测试信号,输入,CPU执行 WAIT指令时,每隔5个时钟周期对引脚进行一次测试,若为高电平,CPU处于等待状态;低电平时执行下一条指令。
RD(32):读控制信号,输出。RD=0,表示执行一个对存储器或I/O端口的读操作。
BHE/S7(34):高八位数据总线允许/状态复用引脚输出。
MN/MX(33):最小/最大工作方式控制信号,输入。接高电平时为最小工作方式。
...大家了解一下即可

这里需要特别注意地址总线,我们知道CPU除了还能访问内存,还能访问硬件,这些都是通过
总线
来实现的。

总线
是贯穿整个系统的是一组电子管道,是连接各个部件的信息传输线,是各个部件共享的传输介质,称作总线,它
携带信息字节并负责在各个计算机部件间传递
。总线按系统总线传输信息内容的不同,可以分为3 种:
数据总线、地址总线和控制总线


我们可以发现8086的寻址空间是1M,这个是怎么得来的呢?寻址空间主要受地址总线宽度影响,地址总线宽度20,也就表示有20根地址线,又因为内存的单位是字节Byte,所以
2^20B=1024KB=1MB

对总线感兴趣地,拓展可见:
什么是计算机中的高速公路-总线?

分段机制

由于8086那个时代CPU、内存都很昂贵, CPU 和寄存器等宽度都是 16 位的,在段不重叠的情况下,能表示的最大地址
0xFFFF
,最大可寻址2^16=64KB,然而8086有20根地址线,可寻址的最大内存空间是1MB。CPU和寄存器的寻址能力远远不能满足使用

所以Inte工程师们耗尽头发,发明了
分段技术
,将内存分为一个个"段",段最大可为64KB,段由三部分组成:

  • 段基址(Base address):段的初始地址
  • 段界限(limit):表示段的长度,段界限决定了偏移量的最大值,也就是段内偏移最大能够寻址到的位置,
  • 段属性(Attributes):表示段的属性,比如是否可读,可写,权限等

那么16 的位的寄存器究竟该如何能访问20位的地址空间呢?

计算方式是:
实际物理地址 = segment段基址 <<4 + offset段内偏移地址
,左移4位就是乘以16。这样就实现用16位的寄存器,生成20位的地址。从而扩大CPU寻址能力,实现对1MB内存空间的寻址

为了实现分段,同时8086引入
专门为分段而生

段寄存器
,如
CS、DS、ES、SS

  1. CS:代码段寄存器,存放代码段的段基址
  2. DS是数据段寄存器,存放数据段的段基址
  3. ES是扩展段寄存器,存放当前程序使用附加数据段的段基址,该段是串操作指令中目的串所在的段
  4. SS是堆栈段寄存器,存放堆栈段的段基址
  5. 后面80836还新增2个寄存器:FS标志段寄存器、GS全局段寄存器。

在采用分段机制之前,工程师要在程序中要访问内存,需要把物理地址写死在程序中,简单而粗暴,但是如果其他程序也同时需要同一块内存地址,只能排队等待,这太让人着急了,所以采用分段机制的另一个重要的好处是:程序可以
重定位

重定向
就是将程序中指令的地址改成另一个地址,但该地址处的内容还是原内存地址处的内容。即使分段后,
程序还是直接操作同一块实际物理内存
,但在程序中的逻辑地址是不一样的,这样计算机多道程序得以勉强的"并发"运行。笔者认为分段的初衷更多是程序重定向问题的解决

由于这样程序中指令了只用到16位地址,缩短了指令长度,也变相地提高了程序执行速度。

保护模式

但随着8086的普及,人们渐渐发现"实模式"(那个时候还没有实模式、保护模式的概念,只有一个工作模式)有个最大问题,就是安全问题,实模式哪怕引入段后,还是直接操作系统的实际内存,程序之间的地址没有隔离,自己写个程序可以访问别人的程序地址,甚至是操作系统的程序地址,所以一不小心就直接把操作系统给干挂了,所以那个时候的程序员编写程序都得小心翼翼的

保护模式
概念首次出现于80286,并将以前"老办法"称为
实模式
,80286 虽然有了保护模式,地址总线是 24根,寻址空间变成了
2^24 =16MB
, 但其CPU、通用寄存器还是16位, 即单独的一个寄存器还是只能访问64KB的空间,要想访问完整的 16MB 内存,只能频繁地变换段基址,非常影响计算机的性能

因此80286太鸡肋了,很快Intel推出了
80386DX
,CPU、寄存器、地址总线都是32位的,寻址空间直接达4GB,在当时CPU非常昂贵的时代背景下,可以说"硬件直接拉满",从这个时候开始,保护模式才大放异彩!

需要注意的是80386并不是立即升到32位的,先出的
80386SX
的CPU、通用寄存器还是16位,地址总线是 24根

此时CPU、寄存器、地址总线都支持寻址4GB,更换偏移地址,就能够访问内存的每一个字节,那么
其实已经不需要分段机制
了。但是为了向前兼容,兼容性是CPU能否长久保持生命力的一个重要保证,还是保留了分段机制,但保护模式下的段基地址都设为了
0
,意味着每个段的起始地址都是一样的,其实
在操作系统层不再分段

那时的程序员访问内存时
被迫用多个小段再加上不断换段基址的方式访问
,非常容易写着写着就忘了前面的内存地址,对程序员的心智产生极大的负担,
不再分段
也叫做
平坦模式
,嗯,对程序员来说以后访问内存操作一路平坦

80386和8086常用寄存器

保护模式与实模式相比有了许多变化,我们先来看下80386和8086寄存器的前后对比,由于80386的寄存器大部分变成32位,同时还必须兼容实模式,所以实模式只用寄存器的前16位

80386寄存器主要为3类:

  1. 通用寄存器。这八个 32 位通用寄存器主要用于包含算术和逻辑运算的操作数。这8个通用寄存器都是由8086的相应16位通用寄存器扩展成32位而得。名字分别是:EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP
  2. 段寄存器。段寄存器CS、DS、SS、ES、FS、GS就是用来标识这6个当前可寻址的内存段。80386新增FS标志段寄存器、GS全局段寄存器,段寄存器因为16位够用了,所以并没有扩展到32位。这些专用寄存器允许系统软件设计者选择平面或分段的内存组织模型
  3. 状态和指令指针寄存器。这些专用寄存器用于记录和改变 80386 处理器状态的某些方面,指令指针寄存器EIP是一个32位寄存器,是从8086的IP扩充而来。标志寄存器EFLAGS也是一个32位寄存器,其中只使用了15位,从8086的FLAGS寄存器扩展而来。

为了帮助大家理解,笔者特点画了张图,其中粉红色代表80386的扩展部分:

当然80386还有其他一些特殊的寄存器,比如IDTR、GDTR、CR0、CR1、CR2和CR3等,这个我们留待下文再讲

GDT、GDTR

我们需要思考一个问题,保护模式是如何保护 程序访问内存时安全的?

保护程序访问内存时安全的,其实换个角度就是,让程序只能访问安全的内存,更进一步地说,我们可以对内存进行权限控制,规定哪些内存可以被哪一类地程序访问。

所以保护模式下会在访问内存时增加了许多"描述信息",比如段自身的访问权限,段的最大长度限制(16位)、段的线性基址(32位)、段的特权级、段是否在内存、读写许可等等相关信息

那么这些信息,首先需要一个
数据结构
来保存所有的相关描述信息,这就是
段描述符,
段描述符8个字节长,也就是64bit。需要注意
每个段都需要一个段描述符

下面我们就是
80386段描述符
的结构图:

段描述符核心就是:段基地址,段界限,访问权限DPL。
段描述符的具体参数,笔者这里就不详细贴出来了,太多太杂,感兴趣地可以自行去看
Global Descriptor Table - OSDev Wiki

如果我们直接通过一个64bit段描述符来引用一个段的时候,就必须使用一个64bit长的段寄存器装入这个段描述符,但是我们刚刚看到段寄存器仍然是16bit,这是Intel为了兼容实模式。所以我们就无法直接通过段寄存器来直接引用64bit的段描述符。

而且每个段都有自己的段描述符,这些信息非常庞大,不是一个或者几个寄存器就能够保存的下去的,需要在内存中开辟出一段空间,当操作系统启动时,加载到
内存
中。在这个专门的内存空间中,所有的段描述符都依次排放在一起,这就构成一个
全局描述符表GDT
(Global Descriptor Table ),GDT是全局的,所以对一个系统来说是唯一的

又因为
全局描述符表GDT
是在内存中的,CPU是无法直接找到的,需要告诉它,这就是需要一个全新的寄存器
GDTR
,来专门告诉CPU,GDT在内存的位置

问题又来了,现在
全局描述符表GDT
有了,有了它我们就能去找内存所有的段,但是我们如何去查这张表呢?我们这里借鉴一下实模式(同时也是为了兼容实模式),在保护模式下,段寄存器(比如 ds、ss、cs)中存放的不再是寻址段的基地址,而是一个一个"GDT表索引",称为
段选择符
(或称
段选择子

在保护模式下,通过段寄存器存放的
段选择符
(或称段选择子),由段选择符从
全局描述符表GDT
中找到8个字节长的
段描述符
,段描述符里存储着
段基址,
再加上
偏移地址
就可以得到
实际内存物理地址
。这里我们只考虑了段模式,页模式暂不展开,其实页模式也是基于段模式的

我们段寄存器还是16位,那么段选择符也是16位的,其中的13bit用来作"索引index",下面我们看下
80386段选择符
的结构图:

当地址访问时,如果
段选择符

请求特权级别RPL
的权限低于
段描述符

特权级DPL
时(一共分为四层:0、1、2、3,其中0为最高特权级,3 为最低特权级),就会拒绝访问,于是就达到了"保护"的作用!

LDT、LDTR

LDT局部描述符表,LDT结构和GDT是差不多的,主要区别在于GDT是全局的,而LDT是局部的(local),GDT在整个操作系统中是唯一的,而LDT在系统中可以存在多个

每一个LDT自身作为一个段存在,存放在LDT类型的段里,这个LDT既然也是段,那么它也会有一个描述符,就放在GDT里面。
寄存器LDTR
内容是一个段选择符,它是用来到GDT里面寻找LDT的

LDT只是一个可选的数据结构,我们可以完全不使用它,使用它或许可以带来一些方便性,但同时也带来复杂性,如果我们想让自己的操作系统内核保持简洁,以及可移植性,则最好不要使用它。这里只做简单地科普介绍

IDT、IDTR

IDT
,Interrupt Descriptor Table,即
中断描述符表
,和GDT类似,记录着0~255的中断号和调用函数之间的关系,与中段向量表有些相似,但要包含更多的信息。

中断机制是操作系统中极为重要的一个部分。操作系统在管理输人输出设备时,在处理外部的各种事件时,都需要通过中断机制进行处理,操作系统在管理输人输出设备时,在处理外部的各种事件时,都需要通过中断机制进行处理

实模式下,16位的中断机制依赖的是
中断向量表
,中断向量表初始化在
0x0000
处,位置是固定的。为了让操作系统的代码中的逻辑地址和实际物理地址一致,操作系统启动时会把system模块搬到零地址处,这样中断向量表就会被覆盖

而在保护模式下,中断机制用的是中断描述符表(IDT),位置是不固定的,设计操作系统时可以灵活设置,只需最后把其地址赋值给
IDTR寄存器
。中断描述符表寄存器
IDTR
是一个48位的寄存器,其低16位保存中断描述符表的大小,高32位保存IDT的基址。

当中断发生时,CPU获取到中断向量后,通过IDTR的值,去查找IDT中断描述符表,得到相应的中断描述符,再根据中断描述符记录的信息来作权限判断,运行级别转换,最终调用相应的中断处理程序

段页机制


在分段机制下的保护模式一切都岁月静好,直到有一天,我们系统有大量程序在运行,比如微信,钉钉等,把内存都占了,只剩下2个空闲内存段1和空闲内存段2。现在我们想在我们系统中运行百度网盘(假设运行需占用2个内存段),明明我们内存中有足够的内存段,但就因为不是连续的,会导致百度网盘运行失败。

我们只能把钉钉先关了,然后百度网盘才能正常打开 或者把钉钉先移到磁盘中,然后就可以运行百度网盘了,这个叫内存交换,但是段的大小比较大,而且磁盘和内存相比要慢很多,所以这种方式效率不高。

通过上面的小例子,相信大家理解了分段机制一些不足的地方:段的大小比较大,而且
由于段的大小是不固定的
,导致内存碎片化(内存有断断续续的间隙,且每个间隙都不一样大!);程序无法动态使用内存;程序只能存放在连续的内存中......

所以Intel引入了
分页机制
,分页的初衷是为了解决内存不足,但由于80286的
段交换
时性能堪忧,决定引入分页,同时为了兼容x86的分段机制,就形成独特的
段页机制

将内存划分为一个个比段更精细的"页",
页的大小固定为4K
,方便更精细化管理。由于分段机制下,程序都是需要提前指定基地址,加载到指定内存中,现在为了实现程序运行时,内存地址自动分配,并按需加载。那必须得先解除线性地址与物理地址对应的关系,这一切需要增加一个"中间层"来实现。

这个中间层主要是3个部分:
CR3 控制寄存器,页目录表page directory,页表page Table
。当页功能开启时,段部件产生的地址就不再是物理地址了,而是线性地址,线性地址还要经页部件转换后,才是物理地址。我们来看下段页机制的工作流程:

CPU内部有一个控制寄存器
CR3
,存放着当前进程的
页目录表
的物理内存基地址,页目录表存放的是
页表
的物理内存基地址,页表存放的是

的物理内存基地址

其中当操作系统开启分页后,分页机制接收的线性地址其实是
虚拟地址

在操作系统看来它是连续的
,但它实际上通过页表映射到
多个不连续的物理内存页
,这样就极大的利用了物理内存,不会出现使用分段机制后产生的大量内存碎片那种情况。

因为页表需要映射整个内存地址,如果是单一的,那么线性地址前20位都查一张表的话,
2^20=1M
, 每个页表项是4字节,如果页表项全满的话, 便是4M大小,换句话说就是页表本身也占用了4MB的物理内存空间。如果我们结合系统资源分配和调度运行的基本单位-进程来说,为了保证进程的正常执行,
每个进程都得有自己的页表
,那么如果进程一多,页表会占有很大的内存空间。

所以现代操作系统都是采取
二级页表
的方式:
页目录表和页表
,这也是我们上图画的结构。其实本质就是
拆分,把一个大表(页表)拆成多个小表,而且不一次性地将全部页表项建好,可以在需要时能够动态创建页表
。 然后统一由一个页目录表来存储这些页表 ,其中页目录项和页表项一样,大小都是4KB

我们将二级页表内存转换流程联系在一起就是:将线性地址,分为
高10位、中间10位、低12位
三个部分,其中高10位作为页目录表的索引(页目录表中有
2^10=1024
个项,PDE),中间10位作为页表的索引(每个页表也有1024个项,PTE),
低12位就是偏移地址,大小
2^12=4KB
,和页的固定大小正好相等。

所以二级页表能够寻址
4KB*1024*1024=4G
,这也是32根地址线能够寻址的最大地址了。

分页其实并不是由操作系统决定的,而是由CPU决定的
。因为线性地址到物理地址的转换算法如上图,已经固定流程套路,而且是比较复杂的(从页目录表到页表再到物理页),为了加快转换的效率,我们直接在硬件上让它自动执行转化。所以CPU中集成了专门用来干这项工作的硬件模块,这个模块被称为
页部件

当程序中给出一个线性地址时,
页部件
分析线性地址, 按照以上算法,自动在页表中检索到物理地址。我们需要注意的是
CR3寄存器
存放的是实际物理地址,这个是给
CPU
看的,不是给操作系统看的。操作系统要访问内存就必须知道它的线性地址才行,线性地址必须连续,至于线性地址的对应实际物理地址可以不连续!

页目录表和页表的参数如下,和之前的gdt是类似的,大家感兴趣地可以自行查阅intel开发手册,我们这就不展开了


段机制实现虚拟地址到线性地址的转换,分页机制实现线性地址到物理地址的转换
,一切的改变都是为了更好地管理与保护内存!

尾语

通过本文的阅读与理解,带着大家穿插了解那个年代x86的历史渊源,大家会更容易明白实模式和保护模式的区别以及分段,段页的所遇到的局限和改进,许多奇奇怪怪地设定都是为了向前兼容,难免负重而行,但一个成熟的产品,良好的兼容性就是它生命力重要的体现。

实模式和保护模式是现代操作系统的前置知识,即使现代操作系统已经天翻地覆的改变,但依旧有他们的影子,理解它们,会让大家对底层知识有更深刻地理解。笔者能力有限,本文还是有许多细节没有讲到,欢迎大家讨论

参考资料:

英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A-英特尔®

https://pdos.csail.mit.edu/6.828/2008/readings/i386/s02_03.htm


作者:小牛呼噜噜 ,首发于公众号「
小牛呼噜噜
」,高质量好文等你关注!