[{"content":"dotfiles是什么？ 在 Linux 系统下，软件的相关配置通常保存在用户的主目录（$HOME）下。例如，bash Shell 的配置文件就位于 $HOME/.bashrc。这些配置文件通常以点（.）开头，因此它们可以统称为 dotfiles。由于 dotfiles 默认是隐藏文件，且在 $HOME 目录下往往分散于多个不同的文件或目录中，直接管理它们会显得比较麻烦。常见的解决方法是通过将所有的配置文件放在一个名叫dotfiles的目录类，然后通过一些工具来高效管理dotfiles。 常见的dotfiles工具：\nGUN Stow：通过软链接的方式将点文件组织到一个目录，并将目录的中文件链接到对应的位置。（我使用的工具） Chezmoi：一个专门用于管理 dotfiles 的工具，支持跨平台和多机器同步。 Ansible：通过自动化工具管理和部署 dotfiles。 Stow用法 Stow基本用法 安装stow\n1 sudo pacman -S stow stow的核心命令格式：\n1 stow [OPTIONS] PACKAGE PACKAGE：软件包名（通常是目录名称）。 OPTIONS：配置选项。 创建软链接 1 stow bash 删除软链接\n1 stow -D bash 更新软链接\n1 stow --restow bash 模拟操作 在实际进行软链接之前，可以使用-n选项来模拟操作并查看对应的结果：\n1 stow -n stow 这里将会显示要创建软链接，但是不会实际执行。\n指定目录 stow会默认将软链接添加到当前目录的父目录，如$HOME/dotfiles该目录的父目录则是$HOME。如果要将要更改软链接的模板目录可以使用-t选项。\n1 stow -t /path/to/target bash 忽略文件或目录 如果想忽略某些文件或目录，可以使用 --ignore=REGEX 选项。例如，忽略所有 .bak 文件：\n1 stow --ignore=\u0026#39;\\.bak$\u0026#39; bash bash代表软件包名，也就是对应的目录名称，-或--则是选项。\nStow高级用法 配置.stow-local-ignore .stow-local-ignore 文件用于指定在当前 stow 进行软链接时，需要忽略的文件或目录。它的文件内容是一个正则表达式列表，匹配的文件或目录将不会被 stow 处理。该文件需要放置在 dotfiles 仓库的根目录中（例如 ~/dotfiles/.stow-local-ignore）。 使用示例 如果想要忽略所有以 .bak 结尾的文件，可以在 .stow-local-ignore 中添加以下内容：\n1 .*\\.bak$ 配置.stowrc .stowrc 文件用于设置stow 的全局配置选项。可以在此文件中指定 stow 的默认行为，例如目标目录、忽略模式等。该文件可以放置在 用户的主目录（~/.stowrc） 或 Stow 仓库的根目录（例如 ~/dotfiles/.stowrc）。如果在两个位置都存在，Stow 会优先使用主目录中的配置。 使用示例 如果想要stow每次运行时都自动忽略.git目录，则可在stowrc中添加以下内容：\n1 --ignore=\u0026#39;^\\.git$\u0026#39; 处理冲突 在进行软链接的时候，如果目标目录存在同名文件，则stow会报错，此时可以使用以下选项解决问题：\n--adopt： --override： 管理dotfiles 假设你的dotfiles目录结构如下：\n1 2 3 4 5 6 7 8 9 10 ~/dotfiles/ ├── bash/ │ ├── .bashrc │ └── .bash_profile ├── git/ │ ├── .gitconfig │ └── .gitignore_global └── vim/ ├── .vimrc └── .vim/ 可以使用一下命令建立软链接：\n1 2 3 cd dotfiles # 对bash git vim都建立软链接 stow bash git vim 如果dotfiles目录结构如下：\n1 2 3 4 ~/dotfiles/ ├── .bashrc ├── .gitconfig └── .vimrc 则可在dotfiles目录下使用stow .命令，该命令将会把.bashrc、.gitconfig、vimrc都链接到$HOME。 直接使用stow .命令可能会将.git目录链接到$HOME目录，建议 创建一个 .stow-local-ignore 文件 并显式忽略 .git 目录。具体步骤如下： 创建.stow-local-ignore 文件：\n1 touch ~/dotfiles/.stow-local-ignore 添加忽略规则： 在 .stow-local-ignore 文件中添加以下内容：\n1 ^\\.git$ ","permalink":"https://lyj404.github.io/blog/post/manage-dotfiles-with-stow/","summary":"\u003ch1 id=\"dotfiles是什么\"\u003edotfiles是什么？\u003c/h1\u003e\n\u003cp\u003e在 Linux 系统下，软件的相关配置通常保存在用户的主目录（\u003ccode\u003e$HOME\u003c/code\u003e）下。例如，\u003ccode\u003ebash\u003c/code\u003e Shell 的配置文件就位于 \u003ccode\u003e$HOME/.bashrc\u003c/code\u003e。这些配置文件通常以点（\u003ccode\u003e.\u003c/code\u003e）开头，因此它们可以统称为 \u003ccode\u003edotfiles\u003c/code\u003e。由于 \u003ccode\u003edotfiles\u003c/code\u003e 默认是隐藏文件，且在 \u003ccode\u003e$HOME\u003c/code\u003e 目录下往往分散于多个不同的文件或目录中，直接管理它们会显得比较麻烦。常见的解决方法是通过将所有的配置文件放在一个名叫\u003ccode\u003edotfiles\u003c/code\u003e的目录类，然后通过一些工具来高效管理\u003ccode\u003edotfiles\u003c/code\u003e。\n\u003cstrong\u003e常见的\u003ccode\u003edotfiles\u003c/code\u003e工具：\u003c/strong\u003e\u003c/p\u003e","title":"使用Stow管理dotfiles"},{"content":"由于 GFW 的存在，在使用或下载国外相关软件时，通常需要使用代理。代理软件中常见的代理模式是系统代理，即在系统层面设置代理，将系统中所有的网络流量通过指定的代理服务器进行转发。在设置系统代理之后，通常所有软件都会通过代理访问网络。然而，如果使用 Linux 系统并在终端进行操作，终端通常不会通过系统代理访问网络。这种情况下，通常需要使用 tun 模式。除了 tun 模式，还可以通过环境变量设置终端代理，或者使用 proxychains-ng。\ntun模式会在系统中创建一个虚拟的网络接口，将系统中所有的网络请求都走该虚拟网络接口\n设置终端环境代理 设置终端环境代理，通常可以分为两种情况。一是直接在 shell 中执行对应的设置命令，在当前终端不关闭的情况下，环境代理会一直生效；二是将对应的环境变量写入到对应的 shell 文件中，这样即使终端关闭后重新打开终端，仍然可以使用代理。我推荐的方式是第二种。 由于我使用的shell是fish，因此需要在~/.config/fish/config.fish中写入一下内容：\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 设置代理 function proxy set -gx http_proxy \u0026#34;http://127.0.0.1:port\u0026#34; set -gx https_proxy \u0026#34;http://127.0.0.1:port\u0026#34; echo \u0026#34;✅ Proxy started\u0026#34; end # 取消代理 function unproxy set -e http_proxy set -e https_proxy echo \u0026#34;❌ Proxy stopped\u0026#34; end bash/zsh的写法如下：\n1 2 3 4 5 6 7 8 9 10 11 function proxy { export http_proxy=\u0026#34;http://127.0.0.1:port\u0026#34; export https_proxy=\u0026#34;http://127.0.0.1:port\u0026#34; echo \u0026#34;✅ Proxy started\u0026#34; } function unproxy { unset http_proxy unset https_proxy echo \u0026#34;❌ Proxy stopped\u0026#34; } bash的配置文件通常在~/.bashrc，zsh的配置文件通常在~/zshrc\n安装proxychains-ng 安装命令如下：\n1 sudo pacman -S proxychains-ng 在安装完proxychains-ng之后，通常需要在/etc/proxychains.conf中设置代理对应的路径和端口，设置内容如下：\n1 2 socks5 127.0.0.1 port http 127.0.0.1 port yay使用proxychains-ng出现的问题 proxychains-ng的原理是通过一个动态链接库（LD_PRELOAD）劫持网络调用工具，强制应用程序通过指定的代理进行网络访问。yay是Go语言编写的一个程序，由于go语言的特性，导致Go语言在编译时会静态链接其标准库（包括net库），这就导致在网络调用的时候不会通过动态链接库的方式进行，从而在yay使用proxychains-ng会出现网络问题。 解决方案：\n1 2 # 下载gcc-go，使用gcc-go替换go来重新编译yay yay -S gcc-go 编译yay有两种方式，方式一：\n1 2 # 在下载gcc-go之后，重新下载yay，并通过添加参数的方式重新编译yay yay -S yay --rebuild 方式二：\n1 2 3 4 # 克隆AUR中yay仓库，之后使用 makepkg 重新编译 yay: git clone https://aur.archlinux.org/yay.git cd yay makepkg -si 测试终端代理是否生效 设置终端环境代理可以通过以下命令查询环境代理是否设置正确。\n1 2 3 4 5 # 查询环境代理的命令 env | grep -i proxy # 正常结果，如出现错误则为空 http_proxy=http://127.0.0.1:port https_proxy=http://127.0.0.1:port 终端环境代理测试代理是否生效：\n1 2 # 在执行proxy方法之后，可使用该命令测试 curl -I https://www.google.com proxychains-ng测试代理是否生效：\n1 proxychains curl -I https://www.google.com 正确测试解决如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 curl -I https://www.google.com HTTP/1.1 200 Connection established HTTP/2 200 content-type: text/html; charset=ISO-8859-1 content-security-policy-report-only: object-src \u0026#39;none\u0026#39;;base-uri \u0026#39;self\u0026#39;;script-src \u0026#39;nonce-CEM Jfgk-QlEcSWxpqHWFBg\u0026#39; \u0026#39;strict-dynamic\u0026#39; \u0026#39;report-sample\u0026#39; \u0026#39;unsafe-eval\u0026#39; \u0026#39;unsafe-inline\u0026#39; https: h ttp:;report-uri https://csp.withgoogle.com/csp/gws/other-hp accept-ch: Sec-CH-Prefers-Color-Scheme p3p: CP=\u0026#34;This is not a P3P policy! See g.co/p3phelp for more info.\u0026#34; date: Thu, 27 Feb 2025 12:34:14 GMT server: gws x-xss-protection: 0 x-frame-options: SAMEORIGIN expires: Thu, 27 Feb 2025 12:34:14 GMT cache-control: private set-cookie: AEC=AVcja2cm7HsWHO8EalaFr76QfZHlawHX8Q5jqvkHmCBtJ4ZYpYsAFuiEBA; expires=Tue, 26- Aug-2025 12:34:14 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax set-cookie: NID=522=ESYsfQ_Q-ezQQSlz4TBjAW1ni7HtuuMEUphgmtw4ViwFobUuv7sOIuUgEWBWCbIX6eLcOuUc cIzh3mKsEInI0go5rL-UJcdy2V_I4mrN6zcK7stCwMml2xAOBMjtF0Dc-CjP1eQW1Ft3mcGBy5lwsspMZTaoJbasewZC U4IA6Ix4j8Lz9IdyxXfF4dzRR3ZOUgpMmaUrXw; expires=Fri, 29-Aug-2025 12:34:14 GMT; path=/; domai n=.google.com; HttpOnly alt-svc: h3=\u0026#34;:443\u0026#34;; ma=2592000,h3-29=\u0026#34;:443\u0026#34;; ma=2592000 ","permalink":"https://lyj404.github.io/blog/post/using-proxy-in-terminal/","summary":"\u003cp\u003e由于 GFW 的存在，在使用或下载国外相关软件时，通常需要使用代理。代理软件中常见的代理模式是系统代理，即在系统层面设置代理，将系统中所有的网络流量通过指定的代理服务器进行转发。在设置系统代理之后，通常所有软件都会通过代理访问网络。然而，如果使用 Linux 系统并在终端进行操作，终端通常不会通过系统代理访问网络。这种情况下，通常需要使用 \u003ccode\u003etun 模式\u003c/code\u003e。除了 \u003ccode\u003etun 模式\u003c/code\u003e，还可以通过环境变量设置终端代理，或者使用 \u003ccode\u003eproxychains-ng\u003c/code\u003e。\u003c/p\u003e","title":"终端使用代理"},{"content":"Git提交规范 在使用git 提交代码的时候，由于每个人都有自己的书写风格，导致git log很混乱，不利于阅读和维护，因此形成了一套通用git提交规范。 格式：\n1 type(scope) : subject type是必须项，主要用来描述commit的类别，且只允许使用以下的标示：\nfeat：新功能（feature） 用于提交新功能 例如：feat: 添加用户注册功能 fix：修复bug 用于提交bug修复 例如：fix：修复登录不成功的问题 docs： 用于提交文档相关的修改 例如：docs：更新README style：代码格式改变 用于提交仅格式化、标点符号、空白等不影响代码运行和逻辑的变更 例如：style：删除多余空格 refactor：代码重构 用于提交某个已有功能重构 例如：refactor：重构用户登录功能 perf：性能优化 用于提交提升程序性能的代码修改 例如：perf： 优化首页加载速度 test：测试相关的操作 用于提交测试相关的内容 例如：test：增加用户登录的单元测试 build：用于描述与项目构建系统相关的更改 用于提交与项目构建系统相关的更改 例如：build：jdk8升级JDK17 revert：撤销上次的commit 用于提交撤销的相关操作 例如：revert：撤销登录逻辑的更改 ci：持续集成相关的配置的更改 用于提交CI相关的修改 例如：ci：修改GitHub Action配置 chore：构建过程或辅助工具的更改 用于提交构建过程、辅助工具等相关内容的修改 例如：chore：fastjson升级fastjson2 scoop是可选项，用于说明commit影响的范围，如：controller层、service层、dao层等等。 subject是必须项，主要是对commit进行简短描述，且不能超过50个字符。\n通过工具规范提交 模板文件 创建一个.gitmessage.txt文件，则可自定义git提交模板 例如：\n1 2 reason: resolve: 在模板文件定义之后，还需执行以下命令：\n1 git config --global commit.template /xx/xx/.gitmessage.txt 使用自定义模板提交方式，不使用git commit -m \u0026quot;xxx\u0026quot;，而是使用git commit，就会自动调用模板\ncommitizen工具 Commitizen是一个用于帮助我们规范化Git提交信息的工具，它提供了一种交互式的方式来生成符合提交规范的提交信息 安装：\n1 npm install -g commitizen 初始化Commitizen配置：\n1 2 3 4 5 6 7 8 # npm commitizen init cz-conventional-changelog --save-dev --save-exact # yarn commitizen init cz-conventional-changelog --yarn --dev --exact # pnpm commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact 使用方式：git cz\n不是前端项目，则需要在项目中创建package.json文件\nGitFlow GitFlow是一种功能强大的Git分支模型，目的在于帮助团队管理软件开发过程中的复杂性。由于每个组织或个人都有自己的git管理流程，随着时间的发展，在不停的实践中，逐渐形成了一些最佳实践的git管理流程，也就是我们所说的Git分支模型。 常见的分支模型有：\nGit Flow GitHub Flow GitLab Flow TrunkBased One Flow Aone Flow 其中Git Flow是最早诞生、并且得到广泛采用的\nGit Flow是什么？ Git Flow 是一种Git分支管理模型，它定义了一个围绕项目发布的严格分支建立模型。这个分支模型是团队成员需要遵守的代码管理方案，目的是通过规范化的流程，使得产品、开发与测试等各个部门更高效地协同工作。\nGit Flow核心概念 Git Flow的核心是分支管理。它将分支分成两类：主分支和辅助分支。主分支包括master和develop，而辅助分支包含feature、release、和hotfix分支。每种分支都有特定的作用和规则。\nGit Flow常用分支及说明 分支名称 说明 Production 生产分支，即Master分支。只能从其他分支合并，不能直接修改 Release 发布分支，基于Develop分支创建，等发布完成合并到Develop和Production分支去 Develop 主开发分支，包含了所有要发布到下一个Release的代码，该分支主要用于合并其他分支的代码 Feature 新功能分支，基于Develop分支创建，用于开发新功能，等开发完毕将合并到Develop分支 Hotfix 修复分支，基于Production分支创建，在bug修复完毕之后，将合并到Production和Develop分支，同时会在Production分支上打一个tag Git Flow工作流程 Git Flow分支模型的工作流程如下图所示： feature分支属于本地分支，其余的分支均是云端分支，且hotfix和release在将代码合并到master和develop分支之后，将会删除自身\n","permalink":"https://lyj404.github.io/blog/post/git-specifications-gitflow/","summary":"\u003ch1 id=\"git提交规范\"\u003eGit提交规范\u003c/h1\u003e\n\u003cp\u003e在使用\u003ccode\u003egit\u003c/code\u003e 提交代码的时候，由于每个人都有自己的书写风格，导致\u003ccode\u003egit log\u003c/code\u003e很混乱，不利于阅读和维护，因此形成了一套通用\u003ccode\u003egit\u003c/code\u003e提交规范。\n\u003cstrong\u003e格式：\u003c/strong\u003e\u003c/p\u003e","title":"Git提交规范和GitFlow"},{"content":"将SpringBoot项目的HTTP转化成HTTPS只需要在application.yml进行相关的配置，或者是通过实现 WebServerFactoryCustomizer\u0026lt;ConfigurableWebServerFactory\u0026gt; 创建一个配置类，以自动将 HTTP 流量重定向到 HTTPS。这两个方法均可实现将HTTP转为HTTPS。 具体步骤如下：\n获取SSL证书 可在阿里云申请免费的SSL证书，也可以使用Java 的 keytool 工具生成自签名证书，由于嫌麻烦所以我使用的是Java 的 keytool 工具生成自签名证书。\n1 keytool -genkey -alias myalias -keyalg RSA -keystore keystore.p12 -storetype PKCS12 该命令执行之后，会在命令行提示你输入一些密码以及一些其他信息，如姓名和组织。\n方法一 通过配置文件配置HTTPS 在 application.yml或application.properties 文件中，指定密钥库路径、类型、密码和别名： yaml文件写法：\n1 2 3 4 5 6 7 server: ssl: key-store: classpath:keystore.p12 key-store-password: your_password key-store-type: PKCS12 key-alias: myalias port: 8443 properties文件写法：\n1 2 3 4 5 server.port=8443 server.ssl.key-store=classpath:keystore.p12 server.ssl.key-store-password=your_password server.ssl.key-store-type=PKCS12 server.ssl.key-alias=myalias 方法二 使用WebServerFactoryCustomizer类配置HTTPS 创建一个配置类实现WebServerFactoryCustomizer并重写customize()方法，具体代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component public class ServerConfig implements WebServerFactoryCustomizer\u0026lt;ConfigurableWebServerFactory\u0026gt; { @Override public void customize(ConfigurableWebServerFactory factory) { Ssl ssl = new Ssl(); // 启动ssl ssl.setEnabled(true); ssl.setKeyStore(\u0026#34;classpath:keystore.p12\u0026#34;); ssl.setKeyStorePassword(\u0026#34;your_password\u0026#34;); ssl.setKeyStoreType(\u0026#34;PKCS12\u0026#34;); ssl.setKeyAlias(\u0026#34;myalias\u0026#34;); factory.setSsl(ssl); factory.setPort(8443); } } SSL证书需放在项目的src\\main\\resources下，且方法一和方法二只需实现一种即可。\n如果将key-store-password错误的写成key-password，则会报错如下：\n1 2 3 4 5 6 Caused by: org.apache.catalina.LifecycleException: Protocol handler start failed at org.apache.catalina.connector.Connector.startInternal(Connector.java:1061) ~[tomcat-embed-core-10.1.30.jar:10.1.30] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.30.jar:10.1.30] at org.apache.catalina.core.StandardService.addConnector(StandardService.java:219) ~[tomcat-embed-core-10.1.30.jar:10.1.30] ... 17 common frames omitted Caused by: java.lang.IllegalArgumentException: Private key must be accompanied by certificate chain ","permalink":"https://lyj404.github.io/blog/post/springboot-use-https/","summary":"\u003cp\u003e将SpringBoot项目的HTTP转化成HTTPS只需要在\u003ccode\u003eapplication.yml\u003c/code\u003e进行相关的配置，或者是通过实现 \u003ccode\u003eWebServerFactoryCustomizer\u0026lt;ConfigurableWebServerFactory\u0026gt;\u003c/code\u003e 创建一个配置类，以自动将 HTTP 流量重定向到 HTTPS。这两个方法均可实现将HTTP转为HTTPS。\n\u003cstrong\u003e具体步骤如下：\u003c/strong\u003e\u003c/p\u003e","title":"SpringBoot使用HTTPS"},{"content":"优化系统开机时间 关闭不必要的开机自启服务 通过systemctl命令查看当前启动的服务：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 systemctl list-unit-files --state=enabled # 我电脑上启动的服务 UNIT FILE STATE PRESET avahi-daemon.service enabled disabled bluetooth.service enabled disabled firewalld.service enabled disabled getty@.service enabled enabled NetworkManager-dispatcher.service enabled disabled NetworkManager-wait-online.service enabled disabled NetworkManager.service enabled disabled nvidia-hibernate.service enabled disabled nvidia-persistenced.service enabled disabled nvidia-resume.service enabled disabled nvidia-suspend.service enabled disabled optimus-manager.service enabled disabled power-profiles-daemon.service enabled disabled sddm.service enabled disabled sshd.service enabled disabled systemd-timesyncd.service enabled enabled avahi-daemon.socket enabled disabled systemd-userdbd.socket enabled enabled fstrim.timer enabled disabled 19 unit files listed. 可以禁用的服务：\n1 2 3 4 5 6 7 8 9 10 # 用于局域网内的设备发现（如Bonjour协议），通常是用于网络发现功能 sudo systemctl disable avahi-daemon.service # 用户等待网络完全启动后再继续其他服务，对于大多数桌面用户而言可以禁用，因为会导致延长启动时间 sudo systemctl disable NetworkManager-wait-online.service # 用户NVIDIA驱动管理系统休眠和恢复的，如不需要系统休眠或挂起的功能可以禁用 sudo systemctl disable nvidia-hibernate.service nvidia-suspend.service nvidia-resume.service # 如不需要集显和独显切换功能，或只有一个显卡的情况可以禁用 sudo systemctl disable optimus-manager.service # 用于管理电源性能配置，对于电源性能模式管理没啥要求的可以禁用 sudo systemctl disable power-profiles-daemon.service 禁用的服务avahi-daemon.service NetworkManager-wait-online.service optimus-manager.service\n优化GRUB设置 编辑/etc/default/grub文件，将GRUB_TIMEOUT值减少，例如：\n1 2 # 我的默认值为5，减少这个值 GRUB_TIMEOUT=\u0026#39;3\u0026#39; 保存grub配置后，还需运行以下命令更新GRUB：\n1 sudo grub-mkconfig -o /boot/grub/grub.cfg GRUB_TIMEOUT=1 是在 GRUB 配置文件（通常是 /etc/default/grub）中用于设置 GRUB 引导菜单显示时间的参数。具体作用如下：\n显示时间：设置 GRUB 启动菜单在屏幕上显示的秒数。在这个例子中，GRUB_TIMEOUT=1 表示 GRUB 菜单只会显示 1 秒钟，然后自动选择默认的启动项进入系统。 自动启动：如果在 1 秒内没有用户手动选择菜单项，GRUB 会自动启动预设的默认操作系统。 分析启动过程 通过systemd-analyze blame 和 systemd-analyze critical-chain 查看启动过程中哪些服务花费时间最长。\n1 2 systemd-analyze blame systemd-analyze critical-chain blame 命令会列出启动时间最长的服务，可以帮助你找到启动的瓶颈。 critical-chain 会显示启动关键路径上哪些服务影响了整个启动时间。 这是我启动时间最长的几个服务，大部分以关闭开机自启动\n1 2 3 4 3.436s plocate-updatedb.service 1.699s optimus-manager.service 1.672s nvidia-persistenced.service 1.139s NetworkManager.service 我使用的操作系统是Endeavour OS，但对于其他的Linux操作系统也可按照该步骤操作\n","permalink":"https://lyj404.github.io/blog/post/linux-starup-time-optimization/","summary":"\u003ch1 id=\"优化系统开机时间\"\u003e优化系统开机时间\u003c/h1\u003e\n\u003ch2 id=\"关闭不必要的开机自启服务\"\u003e关闭不必要的开机自启服务\u003c/h2\u003e\n\u003cp\u003e通过\u003ccode\u003esystemctl\u003c/code\u003e命令查看当前启动的服务：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e12\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e13\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e14\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e15\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e16\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e17\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e18\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e19\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e20\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e21\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e22\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e23\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e24\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl list-unit-files --state=enabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e# 我电脑上启动的服务\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eUNIT FILE                          STATE   PRESET  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eavahi-daemon.service               enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebluetooth.service                  enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efirewalld.service                  enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egetty@.service                     enabled enabled \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eNetworkManager-dispatcher.service  enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eNetworkManager-wait-online.service enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eNetworkManager.service             enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003envidia-hibernate.service           enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003envidia-persistenced.service        enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003envidia-resume.service              enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003envidia-suspend.service             enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eoptimus-manager.service            enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epower-profiles-daemon.service      enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esddm.service                       enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esshd.service                       enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemd-timesyncd.service          enabled enabled \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eavahi-daemon.socket                enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemd-userdbd.socket             enabled enabled \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efstrim.timer                       enabled disabled\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e19 unit files listed.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e可以禁用的服务：\u003c/strong\u003e\u003c/p\u003e","title":"Linux系统开机时间优化"},{"content":"WPS 安装WPS之后，需要将windows中的字体拷贝到Linux系统下，否则将会导致WPS出现字体缺失问题\n1 2 3 4 5 6 7 8 9 10 11 # 创建字体目录 sudo mkdir /usr/share/fonts/WindowsFonts # 复制字体 sudo cp Fonts/* /usr/share/fonts/WindowsFonts # 赋权 sudo chmod 644 /usr/share/fonts/WindowsFonts/* # 刷新 fc-cache -f 修复Linux系统下WPS打不开PDF Linux系统下打不开PDF是缺少了一个依赖文件libtiff.so.5 解决方法如下：\n1 sudo ln -s /usr/lib/x86_64-linux-gnu/libtiff.so.6.0.0 /usr/lib/x86_64-linux-gnu/libtiff.so.5 查看是否链接成功\n1 2 3 4 5 6 7 8 9 10 11 ls /usr/lib/x86_64-linux-gnu | grep libtiff # 输出结果如下 libtiff.a libtiff.so libtiff.so.5 libtiff.so.6 libtiff.so.6.0.0 libtiffxx.a libtiffxx.so libtiffxx.so.6 libtiffxx.so.6.0.0 在Linux操作系统下WPS不能将word转换成PDF也是因为缺少libtiff.so.5\nwps将word转换成PDF，PDF出现文字缺失问题 在Linux使用WPS将word文档转换成PDF，使用Okular打开PDF出现文字确实问题，且发送到手机微信，在手机微信上打开同样出现文字确实问题 解决方法： 安装poppler-data，poppler-data 是一个数据包，它提供了字体和其他信息，供 Poppler 使用。Poppler 是一个开源 PDF 渲染库，用于解析和渲染 PDF 文档。 arch系列软件安装poppler-data的方法：\n1 sudo pacman -S poppler-data 如还出现问题可能是Linux系统中缺少一些中文特定的字体，从而导致文档显示不正常。\n1 2 3 # ttf-wps-fonts 是 WPS 专用字体包 sudo pacman -S ttf-wps-fonts sudo pacman -S noto-fonts-cjk 最后则需要在WPS中选择已安装的字体（如：Noto Sans CJK SC）\n","permalink":"https://lyj404.github.io/blog/post/linux-wps-problem/","summary":"\u003ch2 id=\"wps\"\u003eWPS\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e安装WPS之后，需要将windows中的字体拷贝到Linux系统下，否则将会导致WPS出现字体缺失问题\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 创建字体目录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo mkdir /usr/share/fonts/WindowsFonts\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 复制字体\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo cp Fonts/* /usr/share/fonts/WindowsFonts\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 赋权\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo chmod \u003cspan class=\"m\"\u003e644\u003c/span\u003e /usr/share/fonts/WindowsFonts/*\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 刷新\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efc-cache -f\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch1 id=\"修复linux系统下wps打不开pdf\"\u003e修复Linux系统下WPS打不开PDF\u003c/h1\u003e\n\u003cp\u003eLinux系统下打不开PDF是缺少了一个依赖文件\u003ccode\u003elibtiff.so.5\u003c/code\u003e\n\u003cstrong\u003e解决方法如下：\u003c/strong\u003e\u003c/p\u003e","title":"解决Linux下WPS存在的问题"},{"content":"核显驱动安装 Intel核显驱动 通过以下命令安装Intel的核显驱动包：\n1 sudo pacman -S mesa lib32-mesa vulkan-intel lib32-vulkan-intel AMD核显驱动 对于AMD的核显首先需要确定对应的核显架构 架构对照如下：\nGPU 架构 Radeon 显卡 开源驱动 非开源驱动 GCN 4 及之后 多种* AMDGPU* AMDGPU PRO* GCN 3 多种 AMDGPU AMDGPU PRO GCN 2 多种 AMDGPU/ ATI* 不支持 GCN 1 多种 AMDGPU / ATI 不支持 TeraScale 2\u0026amp;3 HD 5000 - HD 6000 ATI 不支持 TeraScale 1 HD 2000 - HD 4000 ATI 不支持 旧型号 X1000 及之前 ATI 不支持 AMDGPU驱动安装命令： 1 sudo pacman -S mesa lib32-mesa xf86-video-amdgpu vulkan-radeon lib32-vulkan-radeon ATI驱动安装命令：\n1 sudo pacman -S mesa lib32-mesa xf86-video-ati NVIDIA独显 独显安装命令：\n1 sudo pacman -S nvidia-dkms nvidia-settings lib32-nvidia-utils 双显卡 安装GPU切换程序 对于核显和独显并存笔记本电脑，如果想要实现快捷的切换核显和独显，则需安装一个用来管理GPU切换程序optimus-manager\n1 yay -S optimus-manager optimus-manager-qt 将软件设置为开机自启动\n1 sudo systemctl enable optimus-manager.service 安装电源管理 电源管理主要用来确保在使用核显的时候，正确的关闭独显。\n1 sudo pacman -S bbswitch 开启独显模式可能会不生效，需要将NVIDIA 持久化守护进程设置为开机自启动，并重启设备，如果还是不生效，则需查看是否是X11协议\n1 sudo systemctl enable nvidia-persistenced WayLand下使用NVIDIA显卡驱动 配置环境变量 需要在/etc/environment下配置相关的环境变量\n1 2 3 4 GBM_BACKEND=nvidia-drm __GLX_VENDOR_LIBRARY_NAME=nvidia ENABLE_VKBASALT=1 LIBVA_DRIVER_NAME=nvidia kernel modules 配置 需要在/etc/mkinitcpio.conf添加相关的配置，如找不到对应的文件，则是系统没有安装mkinitcpio，需要先安装mkinitcpio，安装之后会自动生成/etc/mkinitcpio.conf\n1 sudo pacman -S mkinitcpio 在/etc/mkinitcpio.conf中找到MODULES=()那一行，并在小括号中添加nvidia nvidia_modeset nvidia_uvm nvidia_drm，除此之外还需查看hooks=()中是否有kms这个值，如有则需删除该值\n创建配置文件 新建一个文件/etc/modprobe.d/nvidia.conf，并向该文件添加一下内容：\n1 2 blacklist nouveau options nvidia_drm modeset=1 fbdev=1 最后执行sudo mkinitcpio -P，来重新生成initramfs\n以上操作需重启系统，才会生效\n","permalink":"https://lyj404.github.io/blog/post/endeavoursos-install-graphics-card-driver/","summary":"\u003ch1 id=\"核显驱动安装\"\u003e核显驱动安装\u003c/h1\u003e\n\u003ch2 id=\"intel核显驱动\"\u003eIntel核显驱动\u003c/h2\u003e\n\u003cp\u003e通过以下命令安装Intel的核显驱动包：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo pacman -S mesa lib32-mesa vulkan-intel lib32-vulkan-intel\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"amd核显驱动\"\u003eAMD核显驱动\u003c/h2\u003e\n\u003cp\u003e对于AMD的核显首先需要确定对应的核显架构\n\u003cstrong\u003e架构对照如下：\u003c/strong\u003e\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003eGPU 架构\u003c/th\u003e\n          \u003cth\u003eRadeon 显卡\u003c/th\u003e\n          \u003cth\u003e开源驱动\u003c/th\u003e\n          \u003cth\u003e非开源驱动\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eGCN 4 及之后\u003c/td\u003e\n          \u003ctd\u003e多种*\u003c/td\u003e\n          \u003ctd\u003eAMDGPU*\u003c/td\u003e\n          \u003ctd\u003eAMDGPU PRO*\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eGCN 3\u003c/td\u003e\n          \u003ctd\u003e多种\u003c/td\u003e\n          \u003ctd\u003eAMDGPU\u003c/td\u003e\n          \u003ctd\u003eAMDGPU PRO\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eGCN 2\u003c/td\u003e\n          \u003ctd\u003e多种\u003c/td\u003e\n          \u003ctd\u003eAMDGPU/ ATI*\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eGCN 1\u003c/td\u003e\n          \u003ctd\u003e多种\u003c/td\u003e\n          \u003ctd\u003eAMDGPU / ATI\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eTeraScale 2\u0026amp;3\u003c/td\u003e\n          \u003ctd\u003eHD 5000 - HD 6000\u003c/td\u003e\n          \u003ctd\u003eATI\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eTeraScale 1\u003c/td\u003e\n          \u003ctd\u003eHD 2000 - HD 4000\u003c/td\u003e\n          \u003ctd\u003eATI\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e旧型号\u003c/td\u003e\n          \u003ctd\u003eX1000 及之前\u003c/td\u003e\n          \u003ctd\u003eATI\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eAMDGPU驱动安装命令：\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo pacman -S mesa lib32-mesa xf86-video-amdgpu vulkan-radeon lib32-vulkan-radeon\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eATI驱动安装命令：\u003c/p\u003e","title":"EndeavourOS安装显卡驱动"},{"content":"desktop文件 在日常使用Linux系统的时候，经常会遇到一些软件只提供了可执行文件，没有提供可以直接通过包管理工具安装的途径，比如说常见的AppImage 文件或者是一些没有对特定发行版本进行安装包编译的开源项目，这些软件就只能通过命令行或者是在文件管理器下找到对应的启动文件在启动比较繁琐，不像那些通过包管理工具安装的软件，可以有桌面图标或者直接应用菜单中显示；而想要这些软件更包管理工具一样拥有桌面图标/应用菜单中显示，可以编写对应的desktop文件。\ndesktop文件是一个包含应用程序信息的存文本文件，通常根据所有用户的可见性的不同可以放在~/.local/share/applications/（针对当前用户）或者是/usr/share/applications/（针对所有用户）下。\n.desktop文件常用字段 Type：通常为Application（应用程序）或者是Link（URL链接） Name：应用程序显示的名称 Comment：简单的描述，通常在鼠标悬停时显示 GenericName：为应用程序提供一个通用的名称，用助于用户理解应用程序的功能 Exec：可执行文件的路径 Icon：应用的图标 StartupNotify：启动应用程序是否发送通知，值为true或false StartupWMClass：置顶应用程序窗口管理器的类名，有助于窗口管理器识别和分组应用程序的窗口 MimeType：指定了应用程序支持的 MIME 类型 [Desktop Action new-empty-window]：表示开始定义一个桌面动作 Keywords：为应用程序提供关键词，有助于用户在搜索应用程序时找到它 Terminal：是否在终端中运行，值为true或false Categories：应用程序的分类，用逗号分隔 在 .desktop 文件中，MimeType 字段用于指定应用程序可以处理的文件类型。\n具体案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [Desktop Entry] Name=Visual Studio Code Comment=Code Editing. Refined. GenericName=Text Editor Exec=/usr/bin/code %F Icon=visual-studio-code Type=Application StartupNotify=false StartupWMClass=Code Categories=TextEditor;Development;IDE; MimeType=text/plain;inode/directory;application/x-code-workspace; Actions=new-empty-window; Keywords=vscode; [Desktop Action new-empty-window] Name=New Empty Window Exec=/usr/bin/code --new-window %F Icon=visual-studio-code Categories的具体内容可以访问：\nhttps://specifications.freedesktop.org/menu-spec/latest/apa.html\nservice文件 .service 文件是systemd系统中的一个单元（Unit）配置文件，用于定义和配置系统服务，使得系统管理员可以更容易地管理这些服务的启动、停止和其他行为。而Systemd是一系列工具的集合，其作用不仅可以启动操作系统，还可以管理后台服务、日志归档、设备管理、电源管理、定时任务等等。 service文件存放路径：\n/etc/systemd/system/这是默认的系统服务文件路径，通常存放系统级服务 /lib/systemd/system/通常是用于存放预安装的系统服务文件 /etc/systemd/user/存放用户级服务 ~/.config/systemd/user/存放单个用户的服务 /run/systemd/system/：用于存放运行时生成的系统服务文件 /run/systemd/user/：用于存放运行时生成的用户级服务文件 文件基本结构 [Unit]: 包含描述信息、依赖关系 Description：简短描述 Documentation：文档地址 Requires：当前 Unit 依赖的其他 Unit，如果它们没有运行，当前 Unit 会启动失败 Wants：与当前 Unit 配合的其他 Unit，如果它们没有运行，当前 Unit 不会启动失败 BindsTo：与Requires类似，它指定的 Unit 如果退出，会导致当前 Unit 停止运行 Before：如果该字段指定的 Unit 也要启动，那么必须在当前 Unit 之后启动 After：如果该字段指定的 Unit 也要启动，那么必须在当前 Unit 之前启动 Conflicts：这里指定的 Unit 不能与当前 Unit 同时运行 Condition...：当前 Unit 运行必须满足的条件，否则不会运行 Assert...：当前 Unit 运行必须满足的条件，否则会报启动失败 [Service]: 定义服务的核心配置，如如何启动和停止服务 WantedBy：它的值是一个或多个 Target，当前 Unit 激活时（enable）符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中 RequiredBy：它的值是一个或多个 Target，当前 Unit 激活时，符号链接会放入/etc/systemd/system目录下面以 Target 名 + .required后缀构成的子目录中 Alias：当前 Unit 可用于启动的别名 Also：当前 Unit 激活（enable）时，会被同时激活的其他 Unit [Install]: 定义服务的安装信息，如何启用或禁用 Type：定义启动时的进程行为。它有以下几种值。 Type=simple：默认值，执行ExecStart指定的命令，启动主进程 Type=forking：以 fork 方式从父进程创建子进程，创建后父进程会立即退出 Type=oneshot：一次性进程，Systemd 会等当前服务退出，再继续往下执行 Type=dbus：当前服务通过D-Bus启动 Type=notify：当前服务启动完毕，会通知Systemd，再继续往下执行 Type=idle：若有其他任务执行完毕，当前服务才会运行 ExecStart：启动当前服务的命令 ExecStartPre：启动当前服务之前执行的命令 ExecStartPost：启动当前服务之后执行的命令 ExecReload：重启当前服务时执行的命令 ExecStop：停止当前服务时执行的命令 ExecStopPost：停止当其服务之后执行的命令 RestartSec：自动重启当前服务间隔的秒数 Restart：定义何种情况 Systemd 会自动重启当前服务，可能的值包括always（总是重启）、on-success、on-failure、on-abnormal、on-abort、on-watchdog TimeoutSec：定义 Systemd 停止当前服务之前等待的秒数 Environment：指定环境变量 具体案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 [Unit] # 服务描述 Description=Bluetooth service # 文档链接，指向 bluetoothd 的 man 页面 Documentation=man:bluetoothd(8) # 条件：只有当 /sys/class/bluetooth 目录存在时才启动此服务 ConditionPathIsDirectory=/sys/class/bluetooth [Service] # 服务类型：dbus，表示这是一个 D-Bus 服务 Type=dbus # D-Bus 服务的名称 BusName=org.bluez # 启动命令：执行 bluetoothd 守护进程 ExecStart=/usr/lib/bluetooth/bluetoothd # 允许服务通知系统其主进程的就绪状态 NotifyAccess=main # 看门狗超时设置（当前被注释） #WatchdogSec=10 # 服务崩溃后的重启策略（当前被注释） #Restart=on-failure # 设置进程的能力边界，只允许网络管理和绑定服务端口的能力 CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE # 限制进程可以创建的最大线程数为 1 LimitNPROC=1 # 文件系统锁定 # 保护 home 目录，禁止服务访问 ProtectHome=true # 严格保护系统目录，使其只读 ProtectSystem=strict # 为服务提供私有的 /tmp 目录 PrivateTmp=true # 保护内核可调参数，禁止修改 ProtectKernelTunables=true # 保护控制组，禁止修改 ProtectControlGroups=true # 指定服务的状态目录 StateDirectory=bluetooth # 设置状态目录的权限模式 StateDirectoryMode=0700 # 指定服务的配置目录 ConfigurationDirectory=bluetooth # 设置配置目录的权限模式 ConfigurationDirectoryMode=0555 # 执行映射 # 禁止内存中的写入和执行操作，提高安全性 MemoryDenyWriteExecute=true # 权限提升 # 禁止获取新的权限，提高安全性 NoNewPrivileges=true # 实时性 # 限制实时调度，防止服务影响系统实时性能 RestrictRealtime=true [Install] # 指定此服务应该在哪个目标（target）下启用 WantedBy=bluetooth.target # 为此服务创建一个别名，用于 D-Bus 激活 Alias=dbus-org.bluez.service 管理命令 1 2 3 4 5 6 7 8 9 10 11 12 # 启动服务 systemctl start app.service # 停止服务 systemctl stop app.service # 重启服务 systemctl restart app.service # 使服务开机自启动 systemctl enable app.service # 关闭开机自启动 systemctl disable app.service # 查看服务状态 systemctl status app.service fonts.conf文件 fonts.conf是字体配置文件，用于定制和调整字体的渲染及优先级设置。以下是一些常见用途：\n字体优先级：指定在显示特定语言或样式是优先使用哪种字体。 字体替换：字体不可用时，定义替换字体。 字体特性： 设置字体的渲染特性，如抗锯齿、亚像素渲染等。 语言支持： 定义特定语言或字符集的字体。 fonts.conf文件的路径分为两种，一是系统级别的配置，文件位置是：/etc/fonts/fonts.conf，二是用户级别的配置，位置是：~/.config/fontconfig/fonts.conf 安装对应字体：\n1 sudo pacman -S ttf-roboto noto-fonts ttf-dejavu adobe-source-han-sans-cn-fonts adobe-source-han-serif-cn-fonts 由于我使用的Endeavour OS系统是基于Arch Linux的一个发行版，因此可以使用pacman安装字体。\n用户字体配置文件内容如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 \u0026lt;?xml version=\u0026#34;1.0\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE fontconfig SYSTEM \u0026#34;fonts.dtd\u0026#34;\u0026gt; \u0026lt;fontconfig\u0026gt; \u0026lt;!-- 指定字体目录 --\u0026gt; \u0026lt;!-- 系统级字体目录，用于存放系统范围内的字体，所有用户都可以访问这些字体 --\u0026gt; \u0026lt;dir\u0026gt;/usr/share/fonts\u0026lt;/dir\u0026gt; \u0026lt;!-- 本地共享字体目录，类似于系统级目录，但管理员可以在这里安装特定于本地使用的字体 --\u0026gt; \u0026lt;dir\u0026gt;/usr/local/share/fonts\u0026lt;/dir\u0026gt; \u0026lt;!-- 用户 XDG 目录中的字体，遵循 XDG 目录结构，通常在 ~/.local/share/fonts --\u0026gt; \u0026lt;dir prefix=\u0026#34;xdg\u0026#34;\u0026gt;fonts\u0026lt;/dir\u0026gt; \u0026lt;!-- 用户主目录下的字体，用户可以在其主目录下安装私有字体 --\u0026gt; \u0026lt;dir\u0026gt;~/.fonts\u0026lt;/dir\u0026gt; \u0026lt;!-- 全局字体设置，用于影响全系统范围内的字体渲染特性 --\u0026gt; \u0026lt;!-- 启用自动微调，使字体在不同分辨率下更为清晰 --\u0026gt; \u0026lt;match target=\u0026#34;font\u0026#34;\u0026gt; \u0026lt;edit name=\u0026#34;autohint\u0026#34; mode=\u0026#34;assign\u0026#34;\u0026gt; \u0026lt;bool\u0026gt;true\u0026lt;/bool\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;!-- 启用抗锯齿，使字体边缘更平滑，减少锯齿现象 --\u0026gt; \u0026lt;edit name=\u0026#34;antialias\u0026#34; mode=\u0026#34;assign\u0026#34;\u0026gt; \u0026lt;bool\u0026gt;true\u0026lt;/bool\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;!-- 启用内嵌点阵字体的使用，特别是针对小尺寸和特定字体样式时，提供更为清晰的显示 --\u0026gt; \u0026lt;edit name=\u0026#34;embeddedbitmap\u0026#34; mode=\u0026#34;assign\u0026#34;\u0026gt; \u0026lt;bool\u0026gt;true\u0026lt;/bool\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;!-- 英文默认字体使用 Roboto 和 Noto Serif ，终端使用 DejaVu Sans Mono 。 --\u0026gt; \u0026lt;match\u0026gt; \u0026lt;!-- 为所有 serif 字体族的请求添加 Noto Serif 作为默认字体 --\u0026gt; \u0026lt;test qual=\u0026#34;any\u0026#34; name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;serif\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;prepend\u0026#34; binding=\u0026#34;strong\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Noto Serif\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;match target=\u0026#34;pattern\u0026#34;\u0026gt; \u0026lt;!-- 为所有 sans-serif 字体族的请求添加 Roboto 作为默认字体 --\u0026gt; \u0026lt;test qual=\u0026#34;any\u0026#34; name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;sans-serif\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;prepend\u0026#34; binding=\u0026#34;strong\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Roboto\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;match target=\u0026#34;pattern\u0026#34;\u0026gt; \u0026lt;!-- 为所有等宽（monospace）字体族的请求添加 DejaVu Sans Mono 作为默认字体 --\u0026gt; \u0026lt;test qual=\u0026#34;any\u0026#34; name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;monospace\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;prepend\u0026#34; binding=\u0026#34;strong\u0026#34;\u0026gt; \u0026lt;string\u0026gt;DejaVu Sans Mono\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;!-- 中文默认字体使用思源黑体和思源宋体，不使用 Noto Sans CJK SC 是因为该字体会在特定情况下显示片假字。 --\u0026gt; \u0026lt;match\u0026gt; \u0026lt;!-- 为中文语言的 serif 字体族请求添加思源宋体 CN --\u0026gt; \u0026lt;test name=\u0026#34;lang\u0026#34; compare=\u0026#34;contains\u0026#34;\u0026gt; \u0026lt;string\u0026gt;zh\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;test name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;serif\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;prepend\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Source Han Serif CN\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;match\u0026gt; \u0026lt;!-- 为中文语言的 sans-serif 字体族请求添加思源黑体 CN --\u0026gt; \u0026lt;test name=\u0026#34;lang\u0026#34; compare=\u0026#34;contains\u0026#34;\u0026gt; \u0026lt;string\u0026gt;zh\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;test name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;sans-serif\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;prepend\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Source Han Sans CN\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;match\u0026gt; \u0026lt;!-- 为中文语言的等宽（monospace）字体族请求添加 Noto Sans Mono CJK SC --\u0026gt; \u0026lt;test name=\u0026#34;lang\u0026#34; compare=\u0026#34;contains\u0026#34;\u0026gt; \u0026lt;string\u0026gt;zh\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;test name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;monospace\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;prepend\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Noto Sans Mono CJK SC\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;!-- 处理Windows 和 Linux下的常用中文字体 --\u0026gt; \u0026lt;!-- 将所有常见的中文字体映射到思源黑体和思源宋体，这样当这些字体未安装时会使用思源黑体和思源宋体。 解决特定程序指定使用某特定字体，并且在字体不存在情况下不会使用回退字体导致中文显示不正常的情况。 --\u0026gt; \u0026lt;match target=\u0026#34;pattern\u0026#34;\u0026gt; \u0026lt;test qual=\u0026#34;any\u0026#34; name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;SimHei\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;assign\u0026#34; binding=\u0026#34;same\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Source Han Sans CN\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;match target=\u0026#34;pattern\u0026#34;\u0026gt; \u0026lt;test qual=\u0026#34;any\u0026#34; name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;SimSun\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;assign\u0026#34; binding=\u0026#34;same\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Source Han Serif CN\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;match target=\u0026#34;pattern\u0026#34;\u0026gt; \u0026lt;test qual=\u0026#34;any\u0026#34; name=\u0026#34;family\u0026#34;\u0026gt; \u0026lt;string\u0026gt;SimSun-18030\u0026lt;/string\u0026gt; \u0026lt;/test\u0026gt; \u0026lt;edit name=\u0026#34;family\u0026#34; mode=\u0026#34;assign\u0026#34; binding=\u0026#34;same\u0026#34;\u0026gt; \u0026lt;string\u0026gt;Source Han Serif CN\u0026lt;/string\u0026gt; \u0026lt;/edit\u0026gt; \u0026lt;/match\u0026gt; \u0026lt;/fontconfig\u0026gt; ","permalink":"https://lyj404.github.io/blog/post/linux-general-configuration-file/","summary":"\u003ch1 id=\"desktop文件\"\u003edesktop文件\u003c/h1\u003e\n\u003cp\u003e在日常使用Linux系统的时候，经常会遇到一些软件只提供了可执行文件，没有提供可以直接通过包管理工具安装的途径，比如说常见的\u003ccode\u003eAppImage\u003c/code\u003e 文件或者是一些没有对特定发行版本进行安装包编译的开源项目，这些软件就只能通过命令行或者是在文件管理器下找到对应的启动文件在启动比较繁琐，不像那些通过包管理工具安装的软件，可以有桌面图标或者直接应用菜单中显示；而想要这些软件更包管理工具一样拥有桌面图标/应用菜单中显示，可以编写对应的\u003ccode\u003edesktop\u003c/code\u003e文件。\u003c/p\u003e","title":"Linux通用配置文件"},{"content":"更换当前语言 列出当前安装的语言：\n1 locale -a 生成语言支持\n1 sudo locale-gen en_US.UTF-8 更改语言环境变脸 临时更改：\n1 export LANG=en_US.UTF-8 永久更改（需要/etc/default/locale 文件）：\n1 2 3 sudo nano /etc/default/locale # 在文件中添加一下内容，注释其他的语言 LANG=en_US.UTF-8 环境变量 1 export PATH=$HOME/.local/bin:$PATH :$PATH：作用是在保留原有的PATH内容，并在PATH内容的签名添加相关的依赖\n硬链接和软链接 硬链接的特点 共享相同的inode： 硬链接指向同一个inode，即同一个文件数据块。删除其中一个硬链接不会影响文件数据，只有当所有硬链接都被删除时，文件数据才会被删除 无法夸文件系统： 硬链接只能在同一个文件系统内创建，不能跨文件系统 不能链接目录： 普通用户不能创建指向目录的硬链接（防止文件系统的混乱） 硬链接的相关命令 1 2 3 4 5 6 # 创建硬链接 ln file1 hardlink_to_file1 # 取消硬链接 rm hardlink_to_file1 # 验证硬链接（能看到两个文件有相同的inode） ls -li file1 hardlink_to_file1 删除这个硬链接不会影响 hardlink_to_file1 的数据，除非 hardlink_to_file1 没有其他硬链接。\n软链接的特点 指向路径： 软链接是一个独立的文件，包含另外一个文件或目录的路径。它们类似于Windows的快捷方式 可以跨文件系统： 软链接可以指向不同的文件系统中的文件或目录 可以链接目录： 可以创建指向目录的软链接 相对和绝对路径： 软链接可以使用相对或绝读路径 软链接相关的命令 1 2 3 4 5 6 # 创建软链接 ln -s /path/to/file1 symlink_to_file1 # 取消软链接 rm symlink_name # 验证软链接 ls -l file1 symlink_to_file1 inode 在Linux和其他的Unix类操作系统中，inode（索引节点）是文件系统的一个重要概念，用于描述文件的元数据和存储位置。每个文件和目录都有一个对应的inode，存储该文件或目录的相关信息\ninode包含的信息：\n文件类型： 如普通文件、目录、符号链接等 权限和模式： 文件的读、写、执行权限，以及文件类型信息 链接计数： 指向该inode的硬链接数量 所有者和组： 文件所有者的用户ID和组ID 文件大小： 以字节为单位的文件大小 时间戳： 包含访问时间、修改时间、改变时间 数据块指针： 指向实际存储文件数据的数据块指针列表 inode不包含文件名，文件名和inode是通过目录向联系在一起的。目录将文件名映射到相应的inode\ninode的相关命令操作 查看文件的inode编号：\n1 ls -i Java面试宝典\\(牛客网\\).pdf 输出结果如下：\n1 5509495 \u0026#39;Java面试宝典(牛客网).pdf\u0026#39; 查看inode信息：\n1 stat Java面试宝典\\(牛客网\\).pdf 输出结果如下：\n1 2 3 4 5 6 7 8 File: Java面试宝典(牛客网).pdf Size: 14643112 Blocks: 28600 IO Block: 4096 regular file Device: 8,2\tInode: 5509495 Links: 1 Access: (0600/-rw-------) Uid: ( 1000/ lyj) Gid: ( 1000/ lyj) Access: 2024-05-19 15:38:36.904412714 +0800 Modify: 2024-04-03 13:22:56.879405504 +0800 Change: 2024-04-03 13:22:56.895405189 +0800 Birth: 2024-04-03 13:22:56.115420560 +0800 文件系统的inode数量是固定的。如果inode数量用尽了，即使磁盘空间还有剩余，也无法新建文件，且不同的文件系统有不同的inode结构和管理方式，如 ext4、XFS、Btrfs 等\n重定向操作符\u0026gt; 作用： \u0026gt; 将命令的标准输出（stdout）重定向到一个文件。如果该文件已经存在，他会被覆盖。如果文件不存在则会创建一个新文件。\n1 echo \u0026#34;Hello World\u0026#34; \u0026gt; file2 该命令的作用：\necho \u0026quot;Hello World\u0026quot;：echo命令用于在终端打印字符串 \u0026gt;：重定向操作符，将echo命令的输出重定向到文件 file2：目标文件名。如果文件存在，内容则被覆盖，不存在会创建新的文件 其他重定向操作符 追加操作符\u0026gt;\u0026gt;： 将输出追加到文件末尾，而不是覆盖文件内容\n1 echo \u0026#34;Hello Again\u0026#34; \u0026gt;\u0026gt; file2 该命令将会在file2文件的末尾追加\u0026quot;Hello Again\u0026quot;\n输入重定向 \u0026lt;： 将文件内容作为命令的输入\n1 sort \u0026lt; file2 该命令会将 file2 的内容作为 sort 命令的输入并进行排序\n错误重定向 2\u0026gt;： 将标准错误（stderr）重定向到文件\n1 ls non_existent_file 2\u0026gt; error.log 该命令会将 ls 命令尝试列出一个不存在文件的错误信息重定向到 error.log 文件中\n合并输出和错误\u0026amp;\u0026gt;： 将标准输出和标准错误重定向到一个文件\n1 command \u0026amp;\u0026gt; output.log 该命令会将 command 的所有输出（包括错误信息）重定向到 output.log 文件\nLinux系统常用命令 文件和目录管理 ls：列出目录内容\n1 2 ls -l # 以详细列表的形式列出文件 ls -a # 显示所有文件，包括隐藏文件 cd：更改目录\n1 2 cd /path/to/directory # 切换到指定目录 cd .. # 返回上一级目录 pwd：显示当前工作目录\n1 pwd # 显示当前所在的目录路径 cp：复制文件或目录\n1 2 cp source.txt destination.txt # 复制文件 cp -r source_dir/ destination/ # 复制目录 mv：移动或重命名文件或目录\n1 2 mv oldname.txt newname.txt # 重命名文件 mv file.txt /path/to/newdir/ # 移动文件到新目录 rm：删除文件或目录\n1 2 rm file.txt # 删除文件 rm -r directory/ # 递归删除目录及其内容 mkdir：创建目录\n1 2 mkdir new_directory # 创建新目录 mkdir -p path/to/new_directory # 递归创建目录 touch：创建空文件或更新文件的时间戳\n1 touch newfile.txt find：用于在目录树中查找文件和目录\n*-name**：按文件名查找 1 find /path/to/search -name \u0026#34;*.txt\u0026#34; # 查找所有以 .txt 结尾的文件 -type：按文件类型查找 1 2 find /path/to/search -type d # 查找所有目录 find /path/to/search -type f # 查找所有普通文件 -size：按文件大小查找 1 find /path/to/search -size +1M # 查找大于 1MB 的文件 -mtime：按修改时间查找 1 find /path/to/search -mtime -7 # 查找 7 天内修改过的文件 -exec：对找到的文件执行命令 1 find /path/to/search -name \u0026#34;*.log\u0026#34; -exec rm {} \\; # 查找并删除所有 .log 文件 grep 命令：用于搜索文件中的文本模式\n-i：忽略大小写 1 grep -i \u0026#34;pattern\u0026#34; file.txt # 搜索时忽略大小写 -r：递归搜索目录 1 grep -r \u0026#34;pattern\u0026#34; /path/to/dir # 递归搜索目录中的文件 -v：排除匹配项 1 grep -v \u0026#34;pattern\u0026#34; file.txt # 显示不包含 \u0026#34;pattern\u0026#34; 的行 -n：显示匹配行的行号 1 grep -n \u0026#34;pattern\u0026#34; file.txt # 显示包含 \u0026#34;pattern\u0026#34; 的行及其行号 -l：显示包含匹配项的文件名 1 grep -l \u0026#34;pattern\u0026#34; /path/to/dir/* # 仅显示包含匹配项的文件名 文件查看和编辑 cat：显示文件内容\n1 cat file.txt more 和 less：分页查看文件内容\n1 2 more file.txt # 从头开始显示文件内容 less file.txt # 支持上下滚动查看文件内容 head 和 tail：查看文件的头部或尾部内容\n1 2 head -n 10 file.txt # 查看文件的前 10 行 tail -n 10 file.txt # 查看文件的后 10 行 nano 或 vi / vim：编辑文件\n1 2 nano file.txt # 使用 nano 编辑器打开文件 vim file.txt # 使用 vim 编辑器打开文件 系统信息和管理 df：显示磁盘空间使用情况\n1 df -h # 以人类可读的格式显示磁盘使用情况 du：显示文件或目录的大小\n1 du -sh /path/to/directory # 递归显示目录大小 free：显示内存使用情况\n1 free -h # 以人类可读的格式显示内存使用情况 top 和 htop：实时显示系统资源和进程信息\n1 top # 显示实时的系统资源使用信息 ps：显示当前进程信息\n1 ps -aux # 显示所有用户的所有进程 -e或-A：显示所有进程 -f：显示完整格式 -u：按用户过滤进程 -aux：显示所有进程，包括不属于终端的进程，并显示详细信息 kill 和 killall：终止进程 1 2 kill PID # 终止指定 PID 的进程 killall process # 终止指定名称的进程 kill -9：强制终止进程 kill -6：让进程异常终止 kill -l：列出所有信号名称 kill -15：发送默认终止信号（较为优雅地终止） shutdown 和 reboot：关机和重启系统 1 2 sudo shutdown -h now # 立即关机 sudo reboot # 重启系统 网络管理 ifconfig 或 ip：查看和配置网络接口\n1 2 ifconfig ip addr show ping：测试网络连通性\n1 ping google.com curl 和 wget：下载文件或测试 HTTP 请求\n1 2 curl http://example.com wget http://example.com/file.zip netstat 或 ss：查看网络连接和端口使用情况\n1 2 netstat -tuln # 显示所有监听的端口 ss -tuln # 类似于 netstat，但输出更快 权限和用户管理 chmod：更改文件权限\n1 2 chmod 755 file.txt # 设置文件权限为 755 chmod +x script.sh # 给脚本添加可执行权限 文件所有者（7）：rwx（读、写、执行） 组用户（5）：r-x（读和执行，没有写权限） 其他用户（5）：r-x（读和执行，没有写权限） chown：更改文件或目录的所有者 1 chown user:group file.txt # 修改文件的所有者 sudo：以超级用户权限执行命令\n1 sudo command # 使用 sudo 以 root 权限运行命令 useradd 和 usermod：管理用户\n1 2 sudo useradd newuser # 添加新用户 sudo usermod -aG sudo user # 将用户添加到 sudo 组 压缩和解压缩 tar：打包和解压 tar 文件\n1 2 tar -czvf archive.tar.gz /path/to/directory # 打包并压缩 tar -xzvf archive.tar.gz # 解压 -c：创建新的tar文件 -x：解压tar文件 -z：使用gzip压缩或解压 -j：使用bzip2压缩或解压 -f：指定文件名 -v：显示详细信息 zip 和 unzip：压缩和解压 zip 文件 1 2 zip -r archive.zip /path/to/directory # 压缩文件 unzip archive.zip # 解压 zip 文件 zip -r：递归压缩目录 zip -e：为压缩文件设置密码 zip -x：排除指定文件 gzip 和 gunzip：压缩和解压 gzip 文件 1 2 gzip file.txt # 压缩文件 gunzip file.gz # 解压文件 nano nano基本操作快捷键 保存文件： Ctrl + o 退出nano： Ctrl + x 剪贴文本： Ctrl + k 粘贴文本： Ctrl + u 选择文本： Ctrl + Shift + 6 撤销： Alt + u 重做： Alt + e 移动到行首： Ctrl + a 移动到行尾： Ctrl + e 向下翻页： Ctrl + y 向下翻页： Ctrl + v 移动到文件开头： Ctrl + home 移动到文件结尾： Ctrl + end 搜索文本： Ctrl + w 搜索并替换文本： Ctrl + \\ 切换用户 su命令用来在Linux系统下切换用户身份。\n1 2 # 切换到指定到指定用户。若不指定用户名，默认切换到root用户 su [username] 常用参数：\n-或-l或--login：启动一个登录shell，会加载目标用户的环境变量，类似于重新登录 -c [command]：运行指定命令，然后退出。例如：su -c \u0026quot;ls -a\u0026quot; -s [shell]：指定要使用的shell。默认使用目标用户的登录shell -m或--preserve-environment：保留当前环境变量。而不是切换到目标用户的环境。 -p：与-m相同，保留环境 切换shell chsh用于更改用户登录shell。 基本用户：\n1 chsh -s /path/to/shell 常用参数：\n-s，--shell SHELL：指定新的登录shell。例如：chsh -s /bin/zsh -l --list-shells：列出系统商可用的shell -h，--help：显示帮助信息 -u，--username USER：指定要更改shell的用户（需要root权限）。例如：sudo chsh -s /bin/bash username 系统手册 man 是用于查看系统手册页的，可以帮助了解命令的用法和参数。 基本用法：\n1 man [section] name 常用参数：\n-k, --apropos keyword：搜索手册页中包含指定关键字的条目。例如：man -k ssh -f, --whatis name：显示指定命令或文件的简要描述。例如：man -f ls -a：显示所有匹配的手册页，而不仅仅是第一个。例如：man -a printf -l, --local-file file：查看本地文件中的手册页。例如：man -l ./myfile.1 -C config_file：使用指定的配置文件。例如：man -C /path/to/config -P pager：指定用于显示手册页的分页程序。例如：man -P less ls -M path：指定手册页的搜索路径。例如：man -M /usr/share/man ls nano常用配置 nano全局配置文件路径/etc/nanorc ，用户配置路径~/.nanorc\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 显示行号 set linenumbers # 设置 Tab 大小 set tabsize 4 # 将tab转为空格 set tabstospaces # 允许自动缩进 set autoindent # 禁用自动换行 set nowrap # 保存时修剪行尾空白 set trimblanks # 禁用备份文件 set backup # 不在文件末尾添加新行 set nonewlines # 启用语法高亮 include \u0026#34;/usr/share/nano/*.nanorc\u0026#34; KDE6.4切换X11 从KDE6.4开始默认使用WayLand了，导致在系统设置或SDDM登录界面没有切换X11的选项，但是由于现阶段有些软件对于WayLand的兼容性还是没有X11好，因此我还是打算切换到X11，现如今想要切换X11需要安装一个额外的软件包\n1 sudo pacman -S plasma-x11-session 安装之后需要重启或注销才能生效\n","permalink":"https://lyj404.github.io/blog/post/linux-daily-use-record/","summary":"\u003ch1 id=\"更换当前语言\"\u003e更换当前语言\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003e列出当前安装的语言：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003elocale -a\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e生成语言支持\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo locale-gen en_US.UTF-8\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"更改语言环境变脸\"\u003e更改语言环境变脸\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e临时更改：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eexport\u003c/span\u003e \u003cspan class=\"nv\"\u003eLANG\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003een_US.UTF-8\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e永久更改（需要\u003ccode\u003e/etc/default/locale\u003c/code\u003e 文件）：\u003c/strong\u003e\u003c/p\u003e","title":"Linux日常使用记录"},{"content":"安装依赖 Debian编译Emacs需要一些依赖，下载方法如下：\n1 2 sudo apt build-dep emacs sudo apt install libtree-sitter-dev 下载和解压Emacs Emacs29.3下载地址\n1 2 3 4 # 下载 wget https://mirror.its.dal.ca/gnu/emacs/emacs-29.3.tar.xz # 解压 tar xvf emacs-29.3.tar.xz 编译Emacs 1 2 3 4 5 6 7 8 9 10 11 12 # 生成配置脚本和Makefile ./autogen.sh # 配置编译选项 ./configure --with-native-compilation=aot --with-native-compilation --with-json --with-tree-sitter CC=gcc-12 # 设置8个核心并行编译 make -j 8 # 查看Emacs版本 src/emacs --version # 快速启动测试一下Emacs src/emacs -Q # 将编译后的Emacs安装到指定目录 sudo make install 如果使用的是Wayland，可以需要添加--with-pgtk选项以获得更好的兼容性\n--without-compress-install是在安装时不要压缩 Emacs 的文档和信息文件。这可以使安装过程更快，但会增加磁盘空间的使用 --with-native-compilation 启用 Emacs 的原生编译支持，aot 指定了原生编译的具体类型 --with-json 允许 Emacs 支持 JSON 文件的读写操作 --with-tree-sitter 启用对 Tree-sitter 语法高亮库的支持 CC=gcc-12 指定使用 gcc-12 作为编译器来编译 Emacs 卸载Emacs 1 2 3 4 5 # 卸载Emacs sudo make uninstall # 清理构建文件 make clean make distclean make clean 清除编译过程中生成的文件，但不删除配置文件和日志 make distclean 更彻底地清理，包括删除配置文件和日志，回到初始状态 ","permalink":"https://lyj404.github.io/blog/post/debian-build-emacs/","summary":"\u003ch1 id=\"安装依赖\"\u003e安装依赖\u003c/h1\u003e\n\u003cp\u003eDebian编译Emacs需要一些依赖，下载方法如下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt build-dep emacs\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt install libtree-sitter-dev\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"下载和解压emacs\"\u003e下载和解压Emacs\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://mirror.its.dal.ca/gnu/emacs/emacs-29.3.tar.xz\"\u003eEmacs29.3\u003c/a\u003e下载地址\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 下载\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewget https://mirror.its.dal.ca/gnu/emacs/emacs-29.3.tar.xz\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 解压\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etar xvf emacs-29.3.tar.xz\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch1 id=\"编译emacs\"\u003e编译Emacs\u003c/h1\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e12\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 生成配置脚本和Makefile\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e./autogen.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 配置编译选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e./configure --with-native-compilation\u003cspan class=\"o\"\u003e=\u003c/span\u003eaot --with-native-compilation --with-json --with-tree-sitter \u003cspan class=\"nv\"\u003eCC\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003egcc-12\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 设置8个核心并行编译\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emake -j \u003cspan class=\"m\"\u003e8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 查看Emacs版本\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esrc/emacs --version\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 快速启动测试一下Emacs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esrc/emacs -Q\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 将编译后的Emacs安装到指定目录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo make install\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e如果使用的是Wayland，可以需要添加\u003ccode\u003e--with-pgtk\u003c/code\u003e选项以获得更好的兼容性\u003c/p\u003e","title":"Debian编译Emacs"},{"content":"OpenSSH是SSH（Secure Shell）协议的免费且开源的实现，主要用于安全地远程控制计算机或在计算机之间传输文件。SSH协议通过加密通道确保了数据传输的安全性，而OpenSSH则提供了这一协议的具体应用。 为了提升用户体验和操作便捷性，OpenSSH客户端允许用户通过一个名为config的配置文件自定义SSH连接的参数。这个配置文件位于用户的~/.ssh目录中，该目录通常会在用户首次运行SSH命令时自动创建。通过编辑config文件，用户可以为不同的主机设置特定的连接选项，如自定义的用户名、端口号、使用的私钥文件等，从而简化日常的SSH连接操作。\n~/.ssh/config该位置的配置通常只影响特定用户，/etc/ssh/ssh_config位置的配置是全局配置文件，影响系统上的所有用户\n基本语法 config配置文件由一系列的配置指令实现，每一个指令占一行。注释以#开头\n常用指令 Host： 用于指定后继指令适用的主机。可以是单个主机名、IP地址，或者使用通配符*来匹配所有主机。 HostName： 指定要连接的远程主机的名称或IP地址。 Port： 指定远程主机上的端口号，默认为22 User： 指定要使用的远程用户名。 IdentityFile： 指定包含用户的私钥的文件路径。 IdentitiesOnly： 设置为yes时，仅使用IdentityFile中指定的私钥进行认证。 AddKeysToAgent： 设置为yes时，将私钥添加到ssh-agent。 UseKeychain： 在macOS上，指定是否使用macOS的keychain存储私钥。 ForwardAgent： 指定一个或多个跳板机（jump host），格式为user@host，用于连接到目标主机。 LocalForward： 设置为yes时，允许转发本地的认证代理（如ssh-agent）到远程主机。 RemoteForward： 设置远程端口转发。 ssh-agent用于在用户使用 SSH 进行认证时，自动提供用户的私钥。它是 Openssh 的一部分，\n示例配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 为所有主机设置默认的用户名 Host * User lyj # 连接到example.com的特定配置 Host example.com HostName real.example.com User specialuser Port 20 IdentityFile ~/.ssh/my_special_key # 使用跳板机连接到目标主机 Host targethost ProxyJump user@jumphost # 本地端口转发 Host gateway LocalForward 8080 localhost:80 在使用ssh命令的时候，默认先查找全局配置文件，然后在查找用户的配置文件。如果找到匹配的Host条目，它会应用相应的配置指令。\n","permalink":"https://lyj404.github.io/blog/post/ssh-configuration-file-description/","summary":"\u003cp\u003e\u003ccode\u003eOpenSSH\u003c/code\u003e是SSH（Secure Shell）协议的免费且开源的实现，主要用于安全地远程控制计算机或在计算机之间传输文件。SSH协议通过加密通道确保了数据传输的安全性，而OpenSSH则提供了这一协议的具体应用。 为了提升用户体验和操作便捷性，OpenSSH客户端允许用户通过一个名为\u003ccode\u003econfig\u003c/code\u003e的配置文件自定义SSH连接的参数。这个配置文件位于用户的\u003ccode\u003e~/.ssh\u003c/code\u003e目录中，该目录通常会在用户首次运行SSH命令时自动创建。通过编辑\u003ccode\u003econfig\u003c/code\u003e文件，用户可以为不同的主机设置特定的连接选项，如自定义的用户名、端口号、使用的私钥文件等，从而简化日常的SSH连接操作。\u003c/p\u003e","title":"ssh配置文件说明"},{"content":"安装Nvidia 驱动 更新软件源\n1 sudo apt update 安装显卡驱动\n通常情况下使用apt安装nvidia-driver即可成功安装驱动，如果不确定可安装nvidia-detect命令识别GPU 来确认推荐的驱动程序包\n1 2 3 sudo apt install nvidia-driver # 安装 nvidia-detect sudo apt install nvidia-detect 识别GPU推荐的驱动\n1 2 3 4 5 6 7 8 9 10 11 nvidia-detect # 输入信息如下： Detected NVIDIA GPUs: 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP107M [GeForce GTX 1050 3 GB Max-Q] [10de:1c91] (rev a1) Checking card: NVIDIA Corporation GP107M [GeForce GTX 1050 3 GB Max-Q] (rev a1) Your card is supported by all driver versions. Your card is also supported by the Tesla 470 drivers series. It is recommended to install the nvidia-driver package. 检查驱动是否成功安装\n如果提示错误 NVIDIA-SMI has failed because it could not communicate with the NVIDIA driver. 那么是因为电脑或者笔记本启用了安全启动，解决方法为进 BIOS 关闭安全启动，或者使用 mokutil 更新 Nvidia UEFI 安全启动签名\n1 2 3 nvidia-smi # 如果报命令找不到，请先安装命令 sudo apt install nvidia-smi 禁用开源驱动 1 2 sudo echo blacklist nouveau \u0026gt; /etc/modprobe.d/blacklist-nvidia-nouveau.conf sudo update-initramfs -u 如果报权限不足，请使用root用户\n1 2 3 4 5 6 7 8 9 su echo blacklist nouveau \u0026gt; /etc/modprobe.d/blacklist-nvidia-nouveau.conf update-initramfs -u #输出信息如下： update-initramfs: Generating /boot/initrd.img-6.1.0-18-amd64 #退出root用户 exit #重启电脑 reboot 在核显和独显之间切换 Debian并没有像Ubuntu一样有nvidia-prime的软件包，因此不能使用sudo prime-select 核显或独显名字来切换显卡，但是有一个名叫envycontrol的开源项目可是实现通过和nvidia-prime一样使用命令行切换GPU。 安装envycontrol\n1 2 # 进入安装包所在的目录使用一下命令安装 sudo apt install ./python3-envycontrol_3.4.0-1_all.deb 安装之后即可使用envycontrol来切换GPU模式了 切换到核显的命令\n1 sudo envycontrol -s integrated 切换到混合模式的命令\n1 sudo envycontrol -s hybrid --rtd3 切换到 NVIDIA 独立显卡的命令\n1 sudo envycontrol -s nvidia --force-comp 切换完成之后需要重启电脑才能生效\n1 reboot ","permalink":"https://lyj404.github.io/blog/post/debian-graphics-card-driver-switch/","summary":"\u003ch1 id=\"安装nvidia-驱动\"\u003e安装Nvidia 驱动\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003e更新软件源\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e安装显卡驱动\u003c/strong\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e通常情况下使用apt安装\u003ccode\u003envidia-driver\u003c/code\u003e即可成功安装驱动，如果不确定可安装\u003ccode\u003envidia-detect\u003c/code\u003e命令识别GPU 来确认推荐的驱动程序包\u003c/p\u003e","title":"Debian实现核显和Nvidia独显切换"},{"content":"问题描述 由于我将Debian系统安装在移动硬盘上，在系统刚装完重启的时候没有出现问题，但在我关机之后，隔离一段时间再次启动移动硬盘中的Debian系统的时候，在BIOS的启动项中就只有电脑本身的EFI启动项了，移动硬盘中的Debian对应的EFI启动项消失了\n修复准备 需要准备一个Live CD启动盘，可使用Ventoy或者是balenaEtcher，制作启动盘，并将对应的Debian Live 12.5 cinnamon系统的Live CD镜像刻录到启动盘。我使用的cinnamon桌面环境，可选择不同桌面的Live CD。\nLive CD 指的是一种可以在计算机启动时直接从光盘或USB设备中运行的操作系统。用户无需安装操作系统，只需将 Live CD 插入计算机并重启，即可在不影响硬盘上现有操作系统的情况下运行一个完整的操作系统。这种方式通常用于系统故障恢复、操作系统安全性检测、系统测试和演示等目的。\n开始chroot修复 在电脑开始的时候通过按快捷键的方式进入BIOS启动页面，选择Live CD对应的启动项，进入对应的Debian Live CD中。\n查看挂载情况 可查用不同的方式查看挂载情况，使用命令行的方式可以采用lsblk命令，也可通过直接查看需要修复EFI分区的系统对应的fstab文件\n1.通过lsblk查看挂载情况 1 2 3 4 5 6 $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 232.9G 0 disk ├─sda1 8:6 0 646M 0 part /boot/efi ├─sda2 8:7 0 78.1 0 part / └─sda3 8:8 0 948M 0 part /swap 2.通过fstab文件查看挂载情况 fstab 文件是 Linux 和类 Unix 系统中的一个重要配置文件，用于指定在系统引导时要挂载的文件系统及其相关参数。 在 fstab 文件中，每一行描述了一个文件系统的挂载信息，通常包括以下字段：\n设备文件（Device）：指定要挂载的设备的路径或 UUID（唯一标识符）。 挂载点（Mount Point）：指定设备挂载到文件系统的哪个目录下。 文件系统类型（Filesystem Type）：指定设备上的文件系统类型，如 ext4、NTFS、FAT32 等。 挂载选项（Mount Options）：指定挂载设备时使用的选项，如读写权限、自动挂载等。 备份级别（Dump）：指定是否要备份该文件系统，一般设置为 0。 检查顺序（fsck order）：指定系统引导时检查文件系统的顺序，一般设置为 0 表示不检查。 具体操作流程：直接通过桌面的文件管理器进入需要修复的EFI的系统的目录下面，进入/etc目录，找到fatab文件并打开，该文件内描述了对应引导时要挂载的文件系统。 对根分区进行挂载 1 2 sudo mkdir /mnt/debian sudo mount /dev/sda2 /mnt/debian # sda1为 / 分区所在设备 对EFI分区进行挂载 在 UEFI 模式下安装 Linux 发行版时，通常会单独为 /boot/efi 目录创建一个 EFI 分区。这个 EFI 分区通常是一个 FAT32 格式的分区，用于存储引导加载程序和其他引导相关文件。默认挂载根目录时并不会挂载这个目录，因为它们不在同一个分区，所以需要efi所在的设备进行相应的挂载，否则内核无法重新安装：\n1 sudo monut /dev/sda1 /mnt/debian/boor/efi 除此之外为了确保在chroot环境中能够访问到必要的设备节点和系统信息，需要对一些虚拟目录进行手动绑定，这是因为在chroot环境中，根目录已经被改变，原本的/dev、/sys、/proc等目录已经不再对应实际的设备节点和系统信息。\n1 2 3 sudo mount --bind /dev /mnt/debian/dev sudo mount --bind /proc /mnt/debian/proc sudo mount --bind /sys /mnt/debian/sys 挂载完成之后可以使用lsblk命令查看对应的挂载情况，如果对应的分区都对应到/mnt/debian这相关的路径则挂载成功\n使用chroot对efi进行修复 chroot是一个Unix-like系统中的命令，用于改变进程的根目录。具体来说，chroot命令会将指定目录作为根目录，以至于进程在这个环境下运行时，该目录将被视为根目录，而进程无法访问其他目录。 chroot的主要作用包括：\n安全隔离：通过将进程的根目录更改为指定目录，可以实现进程的安全隔离，使其无法访问系统中的其他文件和目录，从而增加系统的安全性。 测试和开发环境：在软件开发过程中，可以使用chroot来创建一个独立的环境，用于测试和调试软件，而不影响到系统中其他部分的稳定性。 系统恢复和修复：在系统遇到问题时，可以使用chroot来进入一个临时的环境，在其中修复系统文件或执行其他操作，以便恢复系统功能。 虚拟化：在虚拟化环境中，chroot也有一定的作用，可以帮助实现简单的容器化，尽管相比较现代的容器技术，chroot的隔离性和功能更为有限。 1 sudo chroot /mnt/debian /bin/bash 执行该命令如果报错 bash: chroot: command not found，可能是用户没有对应的环境变量\n1 2 3 4 # 打印用户对应的环境变量 echo $PATH # 我的输出结果如下 # /usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games 添加对应的环境变量\n1 2 3 4 5 sudo nano /etc/profile # 在/etc/profile中添加下列环境变量 export PATH=$PATH:/usr/sbin:/sbin # 使配置生效 source /etc/profile 执行chroot命令之后将会切换到root用户，这说明执行成功\n安装GRUB GRUB（GNU Grand Unified Bootloader）是一个开源的引导加载程序，用于在计算机启动时加载操作系统。它允许用户在多个安装的操作系统之间进行选择，并提供了引导配置和管理的功能。GRUB支持多种操作系统，包括各种Linux发行版、Windows、macOS等。\nGRUB的主要作用是在计算机启动时负责引导加载操作系统内核，并提供一个菜单界面供用户选择不同的操作系统或启动选项。通过配置GRUB，用户可以自定义引导菹程，添加新的操作系统选项，修改引导参数等。因此，GRUB在启动管理方面扮演着重要的角色，使得用户可以轻松地管理多个操作系统或引导选项。 执行该命令安装GRUB：\n1 2 grub-install --target=x86_64-efi --efi-directory=/boot/efi/ --removable # 命令执行之后输入installed finished说明执行成功 卸载挂载的虚拟目录 1 2 3 4 # 从root用户切换成普通用户 exit # 卸载虚拟目录 sudo umount -Rlv /mnt/debian/{dev,proc,sys} -R: 递归地卸载指定目录及其子目录中挂载的文件系统 -l: 立即卸载，即使文件系统正被使用也会立刻卸载 -v: 显示详细信息，包括每个步骤的操作信息 执行上诉步骤之后，EFI分区就已经修复好了，在重启电脑之后即可在BIOS中看到对应EFI启动项\ncinnamon桌面无法启动问题描述 由于最近删除了一些软件，因此使用了apt的autoremove命令来清理一些系统中不必要包和依赖，由于autoremove命令统计出来可以清理的包名太多，一时没细看，因此导致我的cinnamon桌面挂了，报错信息如下：Filaed to load session \u0026lsquo;cinnamon\u0026rsquo;\n修复过程 由于我的network-manager也被autoremove清理了，因此只好通过chroot去修复，先按照上诉方法挂在自己的系统 复制livecd的文件到自己的系统\n1 2 # 目录根据自己的挂在目录来 sudo cp -v /etc/resolv.conf /mnt/debian/etc 因为我使用的DHCP来自动获取网络配置，而我的网络相关的软件由于已经被卸载，所以需要livecd的网络配置，而DHCP 客户端通常会自动生成或修改 /etc/resolv.conf 文件\n复制完成之后可以执行上面的chroot命令，执行之后，由于我的network-manager已经别卸载所以需要先安装\n1 sudo apt install network-manager 修复cinnamon桌面环境 勤快的可以查看apt的日志，从而获取到autoremove移除了那些依赖，apt日志目录如下/var/log/apt/`，我由于比较懒惰，因此直接安装cinnamon桌面了\n1 sudo apt install task-cinnamon-desktop 安装成功之后退出root用户，并按照上诉的教程卸载挂在的虚拟目录。\n","permalink":"https://lyj404.github.io/blog/post/chroot-fix-debian-problem/","summary":"\u003ch1 id=\"问题描述\"\u003e问题描述\u003c/h1\u003e\n\u003cp\u003e由于我将Debian系统安装在移动硬盘上，在系统刚装完重启的时候没有出现问题，但在我关机之后，隔离一段时间再次启动移动硬盘中的Debian系统的时候，在BIOS的启动项中就只有电脑本身的EFI启动项了，移动硬盘中的Debian对应的EFI启动项消失了\u003c/p\u003e","title":"chroot修复Debian"},{"content":"在Linux系统中，不同的目录具有不同的作用和功能，每个目录都扮演着特定的角色，有助于组织和管理文件系统。 Linux下各目录的作用：\n目录名 说明 具体示例 /bin 存放系统可执行的二进制文件(命令) 如：ls、cp、mv等基本命令 /boot 存放启动Linux系统所需的文件，包括内核文件和引导加载程序(bootloader) 如：vmliuz（内核文件）、grub（GRUB引导程序） /dev 存放设备文件，用于与硬件设备进行交互 如：sda（硬盘）、tty（终端设备） /etc 存放系统的配置文件 如：passwd（用户密码文件）、hosts（主机名与IP地址映射） /home 存放普通用户的主目录 如：/home/username /lib 存放共享库文件，为运行时链接额的程序提供支持 如：libc.so（c语言库）、libm.so（数字库） /media 自动挂载可移动介质(如CD、DVD、USB驱动器)的目录 如：/media/cdrom、/media/usb /mnt 手动挂载临时文件系统的目录 如：/mnt/cdrom、/mnt/usb /opt 存放可选的第三方软件和程序包 如：/opt/program-name /proc 虚拟文件系统，提供有关内核和进程的信息 如：/proc/cpuinfo（CPU信息）、/proc/meninfo（内存信息） /root 超级用户(root)的主目录 /root /sbin 存放系统管理员使用的系统管理命令 如：ifconfig（网络配置）、fdisk（磁盘分区） /tmp 存放临时文件，系统重启后会清空 如：临时下载文件、临时缓存文件 /usr 存放用户和系统共享的只读数据，类似于Program Files目录 如：/usr/bin（用户可执行的二进制文件）/usr/lib（共享库文件） /var 存放经常变化的文件，比如日志、缓存和临时文件 如：/var/log（日志文件）、/var/cache（缓存文件） /srv 存放服务（services）相关的数据文件，比如说HTTP服务器、FTP服务器 如：/srv/www（web服务器文件）、/srv/ftp（FTP服务器文件） /usr/local 存放用户自行安装的软件和程序 如：/usr/local/bin、/usr/local/lib GRUB（GNU GRand Unified Bootloader）是一个广泛使用的开源引导加载程序，用于在计算机启动时加载操作系统。它具有以下主功能和特点：\n多操作系统支持：GRUB支持多种操作系统和内核的启动，包括Linux、Windows、Mac OS等，使用户能够在同一计算机上选择不同的操作系统进行启动。 灵活性：GRUB提供了丰富的配置选项和命令行界面，可以根据需要进行灵活的配置和管理，如添加、删除或修改启动菜单项等。 模块化设计：GRUB采用模块化的设计，允许将引导加载程序和文件系统驱动程序作为独立的模块加载，从而提高了系统的灵活性和可维护性。 图形界面支持：GRUB2版本引入了对图形界面的支持，使用户可以通过图形菜单界面轻松选择启动项，提升了用户体验。 错误恢复：GRUB具有强大的错误恢复功能，可以在系统引导出现问题时提供修复选项，帮助用户解决引导问题。 配置文件：GRUB使用grub.cfg文件（或者在旧版本中使用menu.lst文件）来存储启动菜单的配置信息，用户可以编辑该文件来自定义引导菜单。 ","permalink":"https://lyj404.github.io/blog/post/linux-dirctory-description/","summary":"\u003cp\u003e在Linux系统中，不同的目录具有不同的作用和功能，每个目录都扮演着特定的角色，有助于组织和管理文件系统。\n\u003cstrong\u003eLinux下各目录的作用：\u003c/strong\u003e\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e目录名\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n          \u003cth\u003e具体示例\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/bin\u003c/td\u003e\n          \u003ctd\u003e存放系统可执行的二进制文件(命令)\u003c/td\u003e\n          \u003ctd\u003e如：ls、cp、mv等基本命令\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/boot\u003c/td\u003e\n          \u003ctd\u003e存放启动Linux系统所需的文件，包括内核文件和引导加载程序(bootloader)\u003c/td\u003e\n          \u003ctd\u003e如：vmliuz（内核文件）、grub（GRUB引导程序）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/dev\u003c/td\u003e\n          \u003ctd\u003e存放设备文件，用于与硬件设备进行交互\u003c/td\u003e\n          \u003ctd\u003e如：sda（硬盘）、tty（终端设备）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/etc\u003c/td\u003e\n          \u003ctd\u003e存放系统的配置文件\u003c/td\u003e\n          \u003ctd\u003e如：passwd（用户密码文件）、hosts（主机名与IP地址映射）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/home\u003c/td\u003e\n          \u003ctd\u003e存放普通用户的主目录\u003c/td\u003e\n          \u003ctd\u003e如：/home/username\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/lib\u003c/td\u003e\n          \u003ctd\u003e存放共享库文件，为运行时链接额的程序提供支持\u003c/td\u003e\n          \u003ctd\u003e如：libc.so（c语言库）、libm.so（数字库）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/media\u003c/td\u003e\n          \u003ctd\u003e自动挂载可移动介质(如CD、DVD、USB驱动器)的目录\u003c/td\u003e\n          \u003ctd\u003e如：/media/cdrom、/media/usb\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/mnt\u003c/td\u003e\n          \u003ctd\u003e手动挂载临时文件系统的目录\u003c/td\u003e\n          \u003ctd\u003e如：/mnt/cdrom、/mnt/usb\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/opt\u003c/td\u003e\n          \u003ctd\u003e存放可选的第三方软件和程序包\u003c/td\u003e\n          \u003ctd\u003e如：/opt/program-name\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/proc\u003c/td\u003e\n          \u003ctd\u003e虚拟文件系统，提供有关内核和进程的信息\u003c/td\u003e\n          \u003ctd\u003e如：/proc/cpuinfo（CPU信息）、/proc/meninfo（内存信息）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/root\u003c/td\u003e\n          \u003ctd\u003e超级用户(root)的主目录\u003c/td\u003e\n          \u003ctd\u003e/root\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/sbin\u003c/td\u003e\n          \u003ctd\u003e存放系统管理员使用的系统管理命令\u003c/td\u003e\n          \u003ctd\u003e如：ifconfig（网络配置）、fdisk（磁盘分区）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/tmp\u003c/td\u003e\n          \u003ctd\u003e存放临时文件，系统重启后会清空\u003c/td\u003e\n          \u003ctd\u003e如：临时下载文件、临时缓存文件\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/usr\u003c/td\u003e\n          \u003ctd\u003e存放用户和系统共享的只读数据，类似于Program Files目录\u003c/td\u003e\n          \u003ctd\u003e如：/usr/bin（用户可执行的二进制文件）/usr/lib（共享库文件）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/var\u003c/td\u003e\n          \u003ctd\u003e存放经常变化的文件，比如日志、缓存和临时文件\u003c/td\u003e\n          \u003ctd\u003e如：/var/log（日志文件）、/var/cache（缓存文件）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/srv\u003c/td\u003e\n          \u003ctd\u003e存放服务（services）相关的数据文件，比如说HTTP服务器、FTP服务器\u003c/td\u003e\n          \u003ctd\u003e如：/srv/www（web服务器文件）、/srv/ftp（FTP服务器文件）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e/usr/local\u003c/td\u003e\n          \u003ctd\u003e存放用户自行安装的软件和程序\u003c/td\u003e\n          \u003ctd\u003e如：/usr/local/bin、/usr/local/lib\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eGRUB（GNU GRand Unified Bootloader）是一个广泛使用的开源引导加载程序，用于在计算机启动时加载操作系统。它具有以下主功能和特点：\u003c/p\u003e","title":"Linux目录作用详解"},{"content":"在使用Git的过程中，有些文件比如说日志、临时文件、编译的中间文件文件等不需要提交到Git仓库，这个时候需要设置对应的忽略规则，来忽略这些文件的提交。使用场景：比如说在使用git add .命令的时候不小心把不想提交的文件添加到缓存中去了，虽然可以使用git reset HEAD example.txt ，又或者是使用git add 具体文件的方式来避免将不想要提交的文件添加到暂存区，但是这样终归没有直接使用git add .方便，这个时候就可以使用Git为我们提供.gitignore文件，只要在文件中申明哪些文件不需要添加到暂存区，然后在使用git add .命令的时候就不会被添加到暂存区了。\n实现在Git中忽略不想提交的文件 直接在项目中定义.gitignore文件，然后在.gitignore文件中定义要忽略的文件 在Git项目的设置中指定忽略文件，在.git/info/exclude文件中写入要忽略的文件 定义Git全局的.gitignore文件，使用命令如下 git config --global core.excludesfile ~/.gitignore .gitignore忽略规则的优先级 由高到低的优先级如下：\n从命令行中读取可用的忽略规则 当前目录定义的规则 父级目录定义的规则，依次递推 $GIT_DIR/info/exclude 文件中定义的规则 core.excludesfile中定义的全局规则 .gitignore的匹配语法 空行：空行将被忽略。 以 # 开头的表示为注释。 斜杠 /：如果斜杠出现在行首，表示只匹配项目根目录；如果斜杠出现在其他位置，表示匹配任意位置。 !：感叹号用于取反，即排除某些匹配的文件或目录。 *：星号匹配零个或多个字符。 ?：问号匹配一个字符。 **：双星号匹配任意子目录。 /dir/：表示忽略根目录下的dir目录。 dir/：表示忽略任意位置的dir目录。 /*.txt：表示只匹配根目录下的.txt文件。 debug.log：表示忽略任意位置的debug.log文件。 **/logs/：表示匹配所有子目录中的logs目录。 *.log：表示匹配所有以.log结尾的文件。 assets/：表示匹配名为assets的目录。 build：表示匹配名为build的文件或目录。 build/：表示只匹配名为build的目录。 gitignore该开源项目收集了所有可用的.gitignore模板\nJava .gitignore template\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* .gitignore配置不生效原因和解决 在Git中，即使在.gitignore中标明了要忽略的文件或目录，但如果这些文件已经被纳入版本管理，依然会出现在git push的目录中，并且在使用git status查看状态时仍然显示为被追踪的文件。这是因为Git会缓存已经纳入版本管理的文件，即使在.gitignore中声明了忽略路径也不会生效。\n要解决这个问题，需要先将本地缓存删除，然后再进行提交操作。这样就可以确保被声明为忽略的文件不会被提交到版本库中，也不会出现在推送的目录中。 解决方法: git清除本地缓存（改变成未track状态），然后再提交:\n1 2 3 4 git rm -r --cached . git add . git commit -m \u0026#39;update .gitignore\u0026#39; git push -u origin master 或者在clone的时候手动设置不要检查特定文件的更改情况：git update-index --assume-unchanged PATH\n需要注意的点：.gitignore只能忽略原来没有被track的文件，如果文件已经被纳入了版本库中，则修改.gitignore是无效的。想要.gitignore起作用，必须要在这些文件不在暂存区中才可以，.gitignore文件只是忽略没有被staged(cached)文件，对于已经被staged文件，加入ignore文件时一定要先从staged移除，才可以忽略。\n","permalink":"https://lyj404.github.io/blog/post/gitignore-file-use/","summary":"\u003cp\u003e在使用Git的过程中，有些文件比如说日志、临时文件、编译的中间文件文件等不需要提交到Git仓库，这个时候需要设置对应的忽略规则，来忽略这些文件的提交。使用场景：比如说在使用\u003ccode\u003egit add .\u003c/code\u003e命令的时候不小心把不想提交的文件添加到缓存中去了，虽然可以使用\u003ccode\u003egit reset HEAD example.txt \u003c/code\u003e ，又或者是使用\u003ccode\u003egit add 具体文件\u003c/code\u003e的方式来避免将不想要提交的文件添加到暂存区，但是这样终归没有直接使用\u003ccode\u003egit add .\u003c/code\u003e方便，这个时候就可以使用Git为我们提供\u003ccode\u003e.gitignore\u003c/code\u003e文件，只要在文件中申明哪些文件不需要添加到暂存区，然后在使用\u003ccode\u003egit add .\u003c/code\u003e命令的时候就不会被添加到暂存区了。\u003c/p\u003e","title":".gitignore文件的配置使用"},{"content":"MongoDB简介 MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供一个可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。 MongoDB官网地址： MongoDB官网\nMongoDB概念 SQL术语 MongoDB术语 说明 database database 数据库 table collection 数据库表/集合 row document 数据记录行/文档 column field 数据字段/域 index index 索引 table joins 表连接，MongoDB不支持表连接 primary key primary key 主键/MongoDB自动将_id字段设置为主键 数据库 MongoDB中可以建立多个数据库，默认的数据库为db ，该数据库存储在data目录中，每一个数据库都有自己集合和权限，不同的数据库放置在不同的文件中。\n1 2 3 4 5 6 7 8 9 10 # 显示MongoDB服务器上的所有数据库 show dbs # 示例输出 # admin 0.000GB # local 0.000GB # test 0.001GB # 显示当前所使用的数据库 db # 指定使用某个数据库 use local MongoDB默认自带三个数据库：admin、local、config\nadmin 数据库：MongoDB中主要的管理数据库，包含了MongoDB实例的所有用户和角色信息。通过该数据库可以进行用户管理、权限分配以及控制MongoDB实例的运行状态等操作。 local 数据库：这是一个存储临时数据的地方，例如复制同步过程中的操作日志。在这个数据库中，可以找到一些关键的集合(collection)，例如startup_log和oplog.rws ，它们记录了MongoDB实例的启动日志和操作日志。 config 数据库：包含了MongoDB的分片信息，用于支持分片集群。在一个分片集群中，每个节点都有一个config 数据库，用于存储集群的各种配置和元数据。 数据库的名字应是满足以下条件的任意UTF-8字符串： 不能是空字符串 不得含有\u0026rsquo; \u0026lsquo;（空格)、.、$、/、\\和\\0 (空字符) 应全部小写 最多64字节 文档(Document) 文档是一组键值(key-value)对(即BSON)。MongoDB的文档不需要设置相同的字段，并且相同的字段不需要相同的数据类型，这与关系型数据库有很大的区别，也是MongoDB的特点。\n1 {\u0026#34;site\u0026#34;:\u0026#34;www.runoob.com\u0026#34;, \u0026#34;name\u0026#34;:\u0026#34;菜鸟教程\u0026#34;} RDBMS 与 MongoDB 对应的术语：\nRDBMS MongoDB 数据库 数据库 表格 集合 行 文档 列 字段 表联合 嵌入文档 主键 主键（MongoDB提供了key为_id） 注意：\n文档中的键/值对是有序的。 文档中的值不仅可以是在双引号里面的字符串，还可以是其他几种数据类型（甚至可以是整个嵌入的文档)。 MongoDB区分类型和大小写。 MongoDB的文档不能有重复的键。 文档的键是字符串。除了少数例外情况，键可以使用任意UTF-8字符。 文档键的命名规范：\n键不能含有\\0 (空字符)。 .和$有特别的意义，只有在特定环境下才能使用。 以下划线\u0026quot;_\u0026ldquo;开头的键是保留的(不是严格要求的)。 集合 集合就是MongoDB文档组，类似于RDBMS（关系型数据库管理系统：Retational Database Management System）中的表格。集合存在于数据库中，没有固定的结构；集合可以插入不同格式和类型的数据，但通常情况下插入集合的数据都会有一定的关联性。\n1 2 3 {\u0026#34;site\u0026#34;:\u0026#34;www.baidu.com\u0026#34;} {\u0026#34;site\u0026#34;:\u0026#34;www.google.com\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Google\u0026#34;} {\u0026#34;site\u0026#34;:\u0026#34;www.runoob.com\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;菜鸟教程\u0026#34;,\u0026#34;num\u0026#34;:5} 当第一个文档插入时，集合就会被创建。\n合法的集合名 集合名不能是空字符串\u0026rdquo;\u0026quot;。 集合名不能含有\\0字符（空字符)。 集合名不能以\u0026quot;system.\u0026ldquo;开头，这是为系统集合保留的前缀。 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含，这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合，否则千万不要在名字里出现$。 capped collections capped collections是固定大小的collecting有很高的性能以及队列过期的特性（过期按照插入的顺序），有点类似于“RRD”。capped collections 是高性能自动的维护对象的插入顺序。非常适合类似于记录日志的功能，和标准的collection不同，必须要显示的创建一个capped collections，指定一个 collection 的大小，单位是字节。collection 的数据存储空间值提前分配的。\n1 db.createCollection(\u0026#34;mycoll\u0026#34;, {capped:true, size:100000}) 在 capped collection 中，你能添加新的对象。 能进行更新，然而，对象不会增加存储空间。如果增加，更新就会失败 。 使用 Capped Collection 不能删除一个文档，可以使用 drop() 方法删除 collection 所有的行。 删除之后，你必须显式的重新创建这个 collection。 在32bit机器中，capped collection 最大存储为 1e9( 1X109)个字节。 元数据 数据库的信息是存储在集合中。它们使用系统的命名空间：\n1 dbname.system.* 在MongoDB数据库中名字空间 \u0026lt;dbname\u0026gt;.system.*是包含多种系统信息的特殊集合(Collection):\n集合命名空间 描述 dbname.system.namespaces 列出所有名字空间。 dbname.system.indexes 列出所有索引。 dbname.system.profile 包含数据库概要(profile)信息。 dbname.system.users 列出所有可访问数据库的用户。 dbname.local.sources 包含复制对端（slave）的服务器信息和状态。 对于修改系统集合中的对象有如下限制：\n在{{system.indexes}}插入数据，可以创建索引。但除此之外该表信息是不可变的(特殊的drop index命令将自动更新相关信息)。 {{system.users}}是可修改的。 {{system.profile}}是可删除的。 MongoDB 数据类型 MongoDB中常用的几种数据类型。\n数据类型 描述 String 字符串。存储数据常用的数据类型。在 MongoDB 中，UTF-8 编码的字符串才是合法的。 Integer 整型数值。用于存储数值。根据你所采用的服务器，可分为 32 位或 64 位。 Boolean 布尔值。用于存储布尔值（真/假）。 Double 双精度浮点值。用于存储浮点值。 Min/Max keys 将一个值与 BSON（二进制的 JSON）元素的最低值和最高值相对比。 Array 用于将数组或列表或多个值存储为一个键。 Timestamp 时间戳。记录文档修改或添加的具体时间。 Object 用于内嵌文档。 Null 用于创建空值。 Symbol 符号。该数据类型基本上等同于字符串类型，但不同的是，它一般用于采用特殊符号类型的语言。 Date 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间：创建 Date 对象，传入年月日信息。 Object ID 对象 ID。用于创建文档的 ID。 Binary Data 二进制数据。用于存储二进制数据。 Code 代码类型。用于在文档中存储 JavaScript 代码。 Regular expression 正则表达式类型。用于存储正则表达式。 重要的数据类型 ObjectId ObjectId 类似唯一主键，可以很快的去生成和排序，包含 12 bytes，含义是：\n前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间，比北京时间晚了 8 个小时 接下来的 3 个字节是机器标识码 紧接的两个字节由进程 id 组成 PID 最后三个字节是随机数 MongoDB 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的，默认是个 ObjectId 对象 由于 ObjectId 中保存了创建的时间戳，所以你不需要为你的文档保存时间戳字段，你可以通过 getTimestamp 函数来获取文档的创建时间:\n1 2 3 \u0026gt; var newObject = ObjectId() \u0026gt; newObject.getTimestamp() ISODate(\u0026#34;2017-11-25T07:21:10Z\u0026#34;) ObjectId 转为字符串\n1 2 \u0026gt; newObject.str 5a1919e63df83ce79df8b38f 字符串 BSON 字符串都是 UTF-8 编码。\n时间戳 BSON 有一个特殊的时间戳类型用于 MongoDB 内部使用，与普通的 日期 类型不相关。 时间戳值是一个 64 位的值。其中：\n前32位是一个 time_t 值（与Unix新纪元相差的秒数） 后32位是在某秒中操作的一个递增的序数 在单个 mongod 实例中，时间戳值通常是唯一的。在复制集中， oplog 有一个 ts 字段。这个字段中的值使用BSON时间戳表示了操作时间。 BSON 时间戳类型主要用于 MongoDB 内部使用。在大多数情况下的应用开发中，你可以使用 BSON 日期类型。\n日期 表示当前距离 Unix新纪元（1970年1月1日）的毫秒数。日期类型是有符号的, 负数表示 1970 年之前的日期。\n1 2 3 4 5 6 7 8 9 10 \u0026gt; var mydate1 = new Date() //格林尼治时间 \u0026gt; mydate1 ISODate(\u0026#34;2018-03-04T14:58:51.233Z\u0026#34;) \u0026gt; typeof mydate1 object \u0026gt; var mydate2 = ISODate() //格林尼治时间 \u0026gt; mydate2 ISODate(\u0026#34;2018-03-04T15:00:45.479Z\u0026#34;) \u0026gt; typeof mydate2 object 这样创建的时间是日期类型，可以使用 JS 中的 Date 类型的方法。返回一个时间类型的字符串：\n1 2 3 4 5 6 7 \u0026gt; var mydate1str = mydate1.toString() \u0026gt; mydate1str Sun Mar 04 2018 14:58:51 GMT+0000 (UTC) \u0026gt; typeof mydate1str string \u0026gt; Date() Sun Mar 04 2018 15:02:59 GMT+0000 (UTC) MongoDB连接 连接MongoDB的URL语法 mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]\nmongodb:// 这是固定的格式，必须要指定。 username:password@ 可选项，如果设置，在连接数据库服务器之后，驱动都会尝试登录这个数据库 host1 必须的指定至少一个host, host1 是这个URI唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集，请指定多个主机地址。 portX 可选的指定端口，如果不填，默认为27017 /database 如果指定username:password@，连接并验证登录指定数据库。若不指定，默认打开 test 数据库。 ?options 是连接选项。如果不使用/database，则前面需要加上/。所有连接选项都是键值对name=value，键值对之间通过\u0026amp;或;（分号）隔开 连接URL案例： mongodb://admin:123456@localhost/test options选项： 选项 描述 replicaSet=name 验证replica set的名称。 Impliesconnect=replicaSet. slaveOk=true|false - true:在connect=direct模式下，驱动会连接第一台机器，即使这台服务器不是主。在connect=replicaSet模式下，驱动会发送所有的写请求到主并且把读取操作分布在其他从服务器。- false: 在 connect=direct模式下，驱动会自动找寻主服务器. 在connect=replicaSet 模式下，驱动仅仅连接主服务器，并且所有的读写命令都连接到主服务器。 safe=true|false - true: 在执行更新操作之后，驱动都会发送getLastError命令来确保更新成功。(还要参考 wtimeoutMS).false: 在每次更新之后，驱动不会发送getLastError来确保更新成功。 w=n 驱动添加 { w : n } 到getLastError命令. 应用于safe=true。 wtimeoutMS=ms 驱动添加 { wtimeout : ms } 到 getlasterror 命令. 应用于 safe=true. fsync=true|false - true: 驱动添加 { fsync : true } 到 getlasterror 命令.应用于 safe=true.- false: 驱动不会添加到getLastError命令中。 journal=true|false 如果设置为 true, 同步到 journal (在提交到数据库前写入到实体中). 应用于 safe=true connectTimeoutMS=ms 可以打开连接的时间。 socketTimeoutMS=ms 发送和接受sockets的时间。 MongoDB创建数据库 MongoDB创建数据库的语法格式：\n1 use DATABASE_NAME 创建一个数据库\n1 2 3 4 5 6 7 8 9 10 11 12 use runoob # 查看数据库 db runoob # 插入一条数据 db.runoob.insertOne({\u0026#34;name\u0026#34;:\u0026#34;lyj\u0026#34;}) # 查看所有数据库，发现新建的数据库已存在 show dbs admin 0.000GB config 0.000GB local 0.000GB lyj 0.000GB 如果现在查看所有数据库，会发现当前新建的数据库并不存在，只有往数据库中插入数据之后，才能查看到\nMongoDB删除数据库 MongoDB删除数据库的语法格式：\n1 db.dropDatabase() 删除数据库具体操作\n1 2 3 4 5 6 7 8 9 # 选中需要删除的数据库 use lyj # 执行删除数据库的命令 db.dropDatabase() # 查看所有数据库，发现新建的数据库已删除 show dbs admin 0.000GB config 0.000GB local 0.000GB MongoDB创建集合 MongoDB创建集合的语法格式：\n1 db.createCollection(name, options) options具体参数\n字段 类型 描述 capped 布尔 （可选）如果为 true，则创建固定集合。固定集合是指有着固定大小的集合，当达到最大值时，它会自动覆盖最早的文档。 当该值为 true 时，必须指定 size 参数。 autoIndexId 布尔 3.2 之后不再支持该参数。（可选）如为 true，自动在 _id 字段创建索引。默认为 false。 size 数值 （可选）为固定集合指定一个最大值，即字节数。 如果 capped 为 true，也需要指定该字段。 max 数值 （可选）指定固定集合中包 创建集合\n1 2 3 4 5 6 7 # 创建数据库 use test # 创建集合 db.createCollection(\u0026#34;lyj\u0026#34;) # 查看已有集合show collections/show tables两个命令都是查询已有集合 show collections lyj MongoDB删除集合 MongoDB删除集合的语法格式：\n1 db.collection.drop() 删除集合\n1 2 3 4 # 删除名叫lyj的集合 db.lyj.drop() # 查看是否删除成功 show tables MongoDB插入文档 MongoDB插入文档的语法格式：\n1 2 3 db.COLLECTION_NAME.insert(document) 或 db.COLLECTION_NAME.save(document) save()：如果 _id 主键存在则更新数据，如果不存在就插入数据。该方法新版本中已废弃，可以使用 db.collection.insertOne() 或 db.collection.replaceOne() 来代替。 insert(): 若插入的数据主键已经存在，则会抛 org.springframework.dao.DuplicateKeyException 异常，提示主键重复，不保存当前数据。 3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。 db.collection.insertOne() 语法格式： 1 2 3 4 5 6 db.collection.insertOne( \u0026lt;document\u0026gt;, { writeConcern: \u0026lt;document\u0026gt; } ) db.collection.insertMany() 语法格式：\n1 2 3 4 5 6 7 db.collection.insertMany( [ \u0026lt;document 1\u0026gt; , \u0026lt;document 2\u0026gt;, ... ], { writeConcern: \u0026lt;document\u0026gt;, ordered: \u0026lt;boolean\u0026gt; } ) 参数说明：\ndocument：要写入的文档。 writeConcern：写入策略，默认为 1，即要求确认写操作，0 是不要求。 ordered：指定是否按顺序写入，默认 true，按顺序写入 insertOne() 表示插入一条数据，insertMany() 表示插入多条数据\n插入文档\n1 2 3 4 5 6 7 8 9 10 # 该命令会往test数据的lyj集合中插入文档 db.lyj.insert({title: \u0026#39;MongoDB 教程\u0026#39;, description: \u0026#39;MongoDB 是一个 Nosql 数据库\u0026#39;, by: \u0026#39;菜鸟教程\u0026#39;, url: \u0026#39;http://www.runoob.com\u0026#39;, tags: [\u0026#39;mongodb\u0026#39;, \u0026#39;database\u0026#39;, \u0026#39;NoSQL\u0026#39;], likes: 100 }) # 查看已插入的文档 db.lyj.find() MongoDB更新文档 MongoDB更新文档的语法格式：\n1 2 3 4 5 6 7 8 9 db.collection.update( \u0026lt;query\u0026gt;, \u0026lt;update\u0026gt;, { upsert: \u0026lt;boolean\u0026gt;, multi: \u0026lt;boolean\u0026gt;, writeConcern: \u0026lt;document\u0026gt; } ) 参数说明：\nquery : update的查询条件，类似sql update查询内where后面的。 update : update的对象和一些更新的操作符（如$,$inc\u0026hellip;）等，也可以理解为sql update查询内set后面的 upsert : 可选，这个参数的意思是，如果不存在update的记录，是否插入objNew,true为插入，默认是false，不插入。 multi : 可选，mongodb 默认是false,只更新找到的第一条记录，如果这个参数为true,就把按条件查出来多条记录全部更新。 writeConcern :可选，抛出异常的级别。 更新文档\n1 db.lyj.update({\u0026#39;title\u0026#39;:\u0026#39;MongoDB 教程\u0026#39;},{$set:{\u0026#39;title\u0026#39;:\u0026#39;MongoDB\u0026#39;}}) MongoDB删除文档 MongoDB删除数据库的语法格式：\n1 2 3 4 5 6 7 db.collection.remove( \u0026lt;query\u0026gt;, { justOne: \u0026lt;boolean\u0026gt;, writeConcern: \u0026lt;document\u0026gt; } ) 参数说明：\nquery :（可选）删除的文档的条件。 justOne : （可选）如果设为 true 或 1，则只删除一个文档，如果不设置该参数，或使用默认值 false，则删除所有匹配条件的文档。 writeConcern :（可选）抛出异常的级别。 删除文档\n1 db.lyj.remove({\u0026#39;title\u0026#39;:\u0026#39;MongoDB 教程\u0026#39;}) MongoDB查询文档 MongoDB查询文档的语法格式：\n1 db.collection.find(query, projection) query ：可选，使用查询操作符指定查询条件 projection ：可选，使用投影操作符指定返回的键。查询时返回文档中所有键值， 只需省略该参数即可（默认省略）。 pretty() 方法以格式化的方式来显示所有文档db.lyj.find().pretty()\nMongoDB 与 RDBMS Where 语句比较 操作 格式 范例 RDBMS中的类似语句 等于 {\u0026lt;key\u0026gt;:\u0026lt;value\u0026gt;} db.col.find({\u0026quot;by\u0026quot;:\u0026quot;菜鸟教程\u0026quot;}).pretty() where by = '菜鸟教程' 小于 {\u0026lt;key\u0026gt;:{$lt:\u0026lt;value\u0026gt;}} db.col.find({\u0026quot;likes\u0026quot;:{$lt:50}}).pretty() where likes \u0026lt; 50 小于或等于 {\u0026lt;key\u0026gt;:{$lte:\u0026lt;value\u0026gt;}} db.col.find({\u0026quot;likes\u0026quot;:{$lte:50}}).pretty() where likes \u0026lt;= 50 大于 {\u0026lt;key\u0026gt;:{$gt:\u0026lt;value\u0026gt;}} db.col.find({\u0026quot;likes\u0026quot;:{$gt:50}}).pretty() where likes \u0026gt; 50 大于或等于 {\u0026lt;key\u0026gt;:{$gte:\u0026lt;value\u0026gt;}} db.col.find({\u0026quot;likes\u0026quot;:{$gte:50}}).pretty() where likes \u0026gt;= 50 不等于 {\u0026lt;key\u0026gt;:{$ne:\u0026lt;value\u0026gt;}} db.col.find({\u0026quot;likes\u0026quot;:{$ne:50}}).pretty() where likes != 50 MongoDB的逻辑运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 # and运算符 db.lyj.find({key1:value1, key2:value2}).pretty() # 案例 db.lyj.find({\u0026#34;by\u0026#34;:\u0026#34;菜鸟教程\u0026#34;, \u0026#34;title\u0026#34;:\u0026#34;MongoDB 教程\u0026#34;}).pretty() { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;56063f17ade2f21f36b03133\u0026#34;), \u0026#34;title\u0026#34; : \u0026#34;MongoDB 教程\u0026#34;, \u0026#34;description\u0026#34; : \u0026#34;MongoDB 是一个 Nosql 数据库\u0026#34;, \u0026#34;by\u0026#34; : \u0026#34;菜鸟教程\u0026#34;, \u0026#34;url\u0026#34; : \u0026#34;http://www.runoob.com\u0026#34;, \u0026#34;tags\u0026#34; : [ \u0026#34;mongodb\u0026#34;, \u0026#34;database\u0026#34;, \u0026#34;NoSQL\u0026#34; ], \u0026#34;likes\u0026#34; : 100 } # or运算符 \u0026gt;db.col.find( { $or: [ {key1: value1}, {key2:value2} ] } ).pretty() # 案例 db.lyj.find({$or:[{\u0026#34;by\u0026#34;:\u0026#34;菜鸟教程\u0026#34;},{\u0026#34;title\u0026#34;: \u0026#34;MongoDB 教程\u0026#34;}]}).pretty() { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;56063f17ade2f21f36b03133\u0026#34;), \u0026#34;title\u0026#34; : \u0026#34;MongoDB 教程\u0026#34;, \u0026#34;description\u0026#34; : \u0026#34;MongoDB 是一个 Nosql 数据库\u0026#34;, \u0026#34;by\u0026#34; : \u0026#34;菜鸟教程\u0026#34;, \u0026#34;url\u0026#34; : \u0026#34;http://www.runoob.com\u0026#34;, \u0026#34;tags\u0026#34; : [ \u0026#34;mongodb\u0026#34;, \u0026#34;database\u0026#34;, \u0026#34;NoSQL\u0026#34; ], \u0026#34;likes\u0026#34; : 100 } # and和or一起使用 db.lyj.find({\u0026#34;likes\u0026#34;: {$gt:50}, $or: [{\u0026#34;by\u0026#34;: \u0026#34;菜鸟教程\u0026#34;},{\u0026#34;title\u0026#34;: \u0026#34;MongoDB 教程\u0026#34;}]}).pretty() { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;56063f17ade2f21f36b03133\u0026#34;), \u0026#34;title\u0026#34; : \u0026#34;MongoDB 教程\u0026#34;, \u0026#34;description\u0026#34; : \u0026#34;MongoDB 是一个 Nosql 数据库\u0026#34;, \u0026#34;by\u0026#34; : \u0026#34;菜鸟教程\u0026#34;, \u0026#34;url\u0026#34; : \u0026#34;http://www.runoob.com\u0026#34;, \u0026#34;tags\u0026#34; : [ \u0026#34;mongodb\u0026#34;, \u0026#34;database\u0026#34;, \u0026#34;NoSQL\u0026#34; ], \u0026#34;likes\u0026#34; : 100 } MongoDB $type操作符 $type操作符是基于BSON类型来检索集合中匹配的数据类型，并返回结果。 MongoDB 中可以使用的类型如下表所示：\n类型 数字 备注 Double 1 String 2 Object 3 Array 4 Binary data 5 Undefined 6 已废弃。 Object id 7 Boolean 8 Date 9 Null 10 Regular Expression 11 JavaScript 13 Symbol 14 JavaScript (with scope) 15 32-bit integer 16 Timestamp 17 64-bit integer 18 Min key 255 Query with -1. Max key 127 案列 1 2 3 # 想获取 \u0026#34;col\u0026#34; 集合中 title 为 String 的数据，这两个的含义都是一样的 db.col.find({\u0026#34;title\u0026#34; : {$type : 2}}) db.col.find({\u0026#34;title\u0026#34; : {$type : \u0026#39;string\u0026#39;}}) MongoDB Limit和Skip方法 MongoDB的Limit()方法的语法格式：\n1 db.COLLECTION_NAME.find().limit(NUMBER) Limit()方法\n1 db.lyj.find({},{\u0026#34;title\u0026#34;:lyj,_id:0}).limit(2) MongoDB的Skip()方法的语法格式：\n1 db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER) Skip()方法\n1 db.lyj.find({},{\u0026#34;title\u0026#34;:1,_id:0}).limit(1).skip(1) MongoDB sort方法 在 MongoDB 中使用 sort() 方法对数据进行排序，sort() 方法可以通过参数指定排序的字段，并使用 1 和 -1 来指定排序的方式，其中 1 为升序排列，而 -1 是用于降序排列。 sort()方法的语法格式：\n1 db.COLLECTION_NAME.find().sort({KEY:1}) sort()方法\n1 db.lyj.find({},{\u0026#34;title\u0026#34;:1,_id:0}).sort({\u0026#34;likes\u0026#34;:-1}) MongoDB 索引 索引通常能够极大的提高查询的效率，如果没有索引，MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。 这种扫描全集合的查询效率是非常低的，特别在处理大量的数据时，查询可能要花费几十秒甚至几分钟，这对网站的性能是非常致命的。 索引是特殊的数据结构，索引存储在一个易于遍历读取的数据集合中，索引是对数据库表中一列或多列的值进行排序的一种结构\nMongoDB使用 createIndex() 方法来创建索引。\ncreateIndex()方法基本语法格式：\n1 2 3 db.collection.createIndex(keys, options) # 实例 db.lyj.createIndex({\u0026#34;title\u0026#34;:1}) createIndex() 接收可选参数，可选参数列表：\nParameter Type Description background Boolean 建索引过程会阻塞其它数据库操作，background可指定以后台方式创建索引，即增加 \u0026ldquo;background\u0026rdquo; 可选参数。 \u0026ldquo;background\u0026rdquo; 默认值为false。 unique Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. name string 索引的名称。如果未指定，MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 dropDups Boolean 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. sparse Boolean 对文档中不存在的字段数据不启用索引；这个参数需要特别注意，如果设置为true的话，在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. expireAfterSeconds integer 指定一个以秒为单位的数值，完成 TTL设定，设定集合的生存时间。 v index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 weights document 索引权重值，数值在 1 到 99,999 之间，表示该索引相对于其他索引字段的得分权重。 default_language string 对于文本索引，该参数决定了停用词及词干和词器的规则的列表。 默认为英语 language_override string 对于文本索引，该参数指定了包含在文档中的字段名，语言覆盖默认的language，默认值为 language. MongoDB aggregate() 方法 aggregate() 方法在MongoDB中的作用是聚合 aggregate() 方法的基本语法格式：\n1 db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION) 聚合的表达式:\n表达式 描述 实例 $sum 计算总和。 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, num_tutorial : {$sum : \u0026ldquo;$likes\u0026rdquo;}}}]) $avg 计算平均值 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, num_tutorial : {$avg : \u0026ldquo;$likes\u0026rdquo;}}}]) $min 获取集合中所有文档对应值得最小值。 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, num_tutorial : {$min : \u0026ldquo;$likes\u0026rdquo;}}}]) $max 获取集合中所有文档对应值得最大值。 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, num_tutorial : {$max : \u0026ldquo;$likes\u0026rdquo;}}}]) $push 将值加入一个数组中，不会判断是否有重复的值。 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, url : {$push: \u0026ldquo;$url\u0026rdquo;}}}]) $addToSet 将值加入一个数组中，会判断是否有重复的值，若相同的值在数组中已经存在了，则不加入。 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, url : {$addToSet : \u0026ldquo;$url\u0026rdquo;}}}]) $first 根据资源文档的排序获取第一个文档数据。 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, first_url : {$first : \u0026ldquo;$url\u0026rdquo;}}}]) $last 根据资源文档的排序获取最后一个文档数据 db.mycol.aggregate([{$group : {_id : \u0026ldquo;$by_user\u0026rdquo;, last_url : {$last : \u0026ldquo;$url\u0026rdquo;}}}]) 管道 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。 MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。 表达式：处理输入文档并输出。表达式是无状态的，只能用于计算当前聚合管道的文档，不能处理其它的文档。 聚合框架中常用的操作：\n$project：修改输入文档的结构。可以用来重命名、增加或删除域，也可以用于创建计算结果以及嵌套文档。 $match：用于过滤数据，只输出符合条件的文档。$match使用MongoDB的标准查询操作。 $limit：用来限制MongoDB聚合管道返回的文档数。 $skip：在聚合管道中跳过指定数量的文档，并返回余下的文档。 $unwind：将文档中的某一个数组类型字段拆分成多条，每条包含数组中的一个值。 $group：将集合中的文档分组，可用于统计结果。 $sort：将输入文档排序后输出。 $geoNear：输出接近某一地理位置的有序文档。 管道操作符实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # $project实例 db.article.aggregate( { $project : { title : 1 , author : 1 , }} ); # $match实例 db.articles.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $group: { _id: null, count: { $sum: 1 } } } ] ); # $skip实例 db.article.aggregate( { $skip : 5 }); ","permalink":"https://lyj404.github.io/blog/post/mongodb-description/","summary":"\u003ch1 id=\"mongodb简介\"\u003eMongoDB简介\u003c/h1\u003e\n\u003cp\u003eMongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供一个可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。\n\u003cstrong\u003eMongoDB官网地址：\u003c/strong\u003e \u003ca href=\"https://www.mongodb.com\"\u003eMongoDB官网\u003c/a\u003e\u003c/p\u003e","title":"MongoDB的基本操作"},{"content":"Domain Driver Design MVC架构 在mvc架构中，主要划分为三个组件：Model（模型）、View（视图）、Controller（控制器） 传统三层架构模型(MVC) DDD架构 DDD架构主要将应用分成四个层次：\n表现层（Presentation Layer）：负责与用户进行交互，展示数据和接收用户输入。表现层通常包括用户界面、控制器、视图和用户输入验证等组件。 应用层（Application Layer）：负责协调处理用户请求，并将其转化为对领域层的操作。应用层中的服务和命令处理程序执行应用程序逻辑，调用领域层中的领域对象来完成具体的业务处理。应用层还负责事务管理和协调不同领域对象之间的交互。 领域层（Domain Layer）：是整个架构的核心，包含了业务领域的核心概念、业务规则和业务逻辑。领域层由聚合、实体、值对象等领域对象组成，它们封装了业务行为和状态，并通过领域服务之间的交互来完成复杂的业务操作。 基础设施层（Infrastructure Layer）：提供支持上述三个层次的基础设施和技术实现，如数据库访问、消息队列、外部服务和框架等。基础设施层隐藏了底层技术的细节，提供统一的接口供上层调用。 DDD架构模型 COLA COLA架构 Adapter层：负责对前端展示的路由和适配，对应传统的B/S架构来说，adapter相当于之前的controller App层：主要负责获取输入，组装上下文，参数校验，调用领域层做业务处理，如果需要的话，发送消息通知等。层次是开放的，应用层也可以绕过领域层，直接访问基础实施层 Domain层：主要是封装了核心业务逻辑，并通过领域服务（Domain Service）和领域对象（Domain Entity）的方法对App层提供业务实体和业务逻辑计算。领域是应用的核心，不依赖任何其他层次； Infrastructure层：主要负责技术细节问题的处理，比如数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外，领域防腐的重任也落在这里，外部依赖需要通过gateway的转义处理，才能被上面的App层和Domain层使用。 Client层：对外提供的功能API，如果外部模块需要使用本项目功能只需引用 client 中的 API 接口即可实现功能、\nadapter web 处理页面请求的Controller app 业务功能包（user、order）放xxxServiceImpl 业务名 app executor 修改相关的执行逻辑 app query(executor包下) 查询相关的执行逻辑 client api(放xxxService) 存放对外功能的api client dto（data、query） 对外返回的对象及调用方传入的参数对象 domain gateway 防腐层，让 Infrastructure 层实现逻辑 domain 业务功能包（entity、值对象） 根据业务功能分包，包中存放业务实体及值对象 infrastructure convertor 存放将 DO 转化为 entity的类 infrastructure config 存放配置相关 infrastructure gateway.impl 实现 domain 层的 gateway 接口的实现类 infrastructure dataobject 存放数据库对象的DO infrastructure mapper mapper文件 ","permalink":"https://lyj404.github.io/blog/post/domain-driver-design-desciption/","summary":"\u003ch1 id=\"domain-driver-design\"\u003eDomain Driver Design\u003c/h1\u003e\n\u003ch2 id=\"mvc架构\"\u003eMVC架构\u003c/h2\u003e\n\u003cp\u003e在mvc架构中，主要划分为三个组件：Model（模型）、View（视图）、Controller（控制器）\n\u003cstrong\u003e传统三层架构模型(MVC)\u003c/strong\u003e \u003ca href=\"https://imgse.com/i/pPvQ5vD\"\u003e\u003cimg alt=\"pPvQ5vD.png\" loading=\"lazy\" src=\"https://z1.ax1x.com/2023/10/08/pPvQ5vD.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"ddd架构\"\u003eDDD架构\u003c/h2\u003e\n\u003cp\u003eDDD架构主要将应用分成四个层次：\u003c/p\u003e","title":"DDD领域驱动设计"},{"content":"SPI SpringBoot的自动装配是通过SPI的方式实现的，并进行了进一步的优化，从而实现了自动装配。SPI全称为Serveice Provider Interface，是Java提供的一种服务发现机制。它允许不同的组件在运行时动态的扩展、替换和加载实现。SPI是一种基于接口和实现分离的设计模式。 在SPI机制中，定义一个接口作为服务的标准化接口，然后通过类路径下提供特定配置文件来指定具体的实现。这个配置文件通常位于META-INF/services目录下，以接口的全限定名命名。配置文件中列出了实现该接口的具体类的全限定名。 当需要使用某项服务时，应用程序可以通过SPI机制查找平加载对应的实现类。Java运行时会通过读取配置文件获取到实现类的信息，并实例化对应的类。这样能够做到在不修改代码的情况下，通过添加/替换配置文件中的实现类，来改变程序的行为或者增加新功能。\nSPI的应用 JDBC数据库驱动 日志框架（SLF4） SpringBoot自动装配原理 SpringBoot自动配置是默认开启的，spring.boot.enableautoconfiguration=true，可以通过application.properties或application.yml来关闭自动配置。 SpringBoot应用都会创建一个启动类，启动类上包含了@SpringBootApplication注解，这个注解是一个复合的注解，分别对这三个注解进行了封装@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。\n1 2 3 4 5 6 7 8 @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } @SpringBootApplication注解的实现\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication @SpringBootConfiguration：用于标识一个类是SpringBoot的配置类。 @EnableAutoConfiguration：启动SpringBoot的自动配置机制。 @ComponentScan：扫描被@Component注解的Bean，注解默认会扫描启动类所在的包下的所有类。\n@EnableAutoConfiguration是实现SpringBoot自动配置的核心，该注解上通过@Import注解导入了一个AutoConfigurationImportSelector类，AutoConfigurationImportSelector 类实现了 ImportSelector接口，也就实现了这个接口中的 selectImports方法，这个方法主要是用来获取所有符合条件的类的全限定类名。 @EnableAutoConfiguration源码\n1 2 3 4 5 6 7 8 9 10 11 12 13 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = \u0026#34;spring.boot.enableautoconfiguration\u0026#34;; Class\u0026lt;?\u0026gt;[] exclude() default {}; String[] excludeName() default {}; } AutoConfigurationImportSelector源码\n1 2 3 4 5 6 7 8 9 10 11 public class AutoConfigurationImportSelector implements DeferredImportSelector{ public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } } } 通过selectImports()方法调用getAutoConfigurationEntry(annotationMetadata)，getAutoConfigurationEntry(annotationMetadata)方法通过SpringFactoriesLoader.loadFactoryNames()，扫描所有含有META-INF/spring.factories的jar包，然后读取META-INF/spring.factories文件中所配置的类的全限定类名。在这些配置类中所定义的Bean会根据条件注解来决定是否需要将其导入到Spring的容器中。 一般条件判断都会有@ConditionalOnClass这样的注解，判断是否有对应的class文件，如果有则加载该类，并将这个配置类所有的Bean放入Spring容器中。\n","permalink":"https://lyj404.github.io/blog/post/springboot-autoconfiguration-principle/","summary":"\u003ch1 id=\"spi\"\u003eSPI\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003eSpringBoot\u003c/code\u003e的自动装配是通过SPI的方式实现的，并进行了进一步的优化，从而实现了自动装配。\u003ccode\u003eSPI\u003c/code\u003e全称为Serveice Provider Interface，是Java提供的一种服务发现机制。它\u003cstrong\u003e允许不同的组件在运行时动态的扩展、替换和加载实现\u003c/strong\u003e。\u003ccode\u003eSPI\u003c/code\u003e是一种基于接口和实现分离的设计模式。\n在\u003ccode\u003eSPI\u003c/code\u003e机制中，定义一个接口作为服务的标准化接口，然后通过类路径下提供特定配置文件来指定具体的实现。这个配置文件通常位于\u003ccode\u003eMETA-INF/services\u003c/code\u003e目录下，以接口的全限定名命名。配置文件中列出了实现该接口的具体类的全限定名。\n当需要使用某项服务时，应用程序可以通过\u003ccode\u003eSPI\u003c/code\u003e机制查找平加载对应的实现类。Java运行时会通过读取配置文件获取到实现类的信息，并实例化对应的类。这样能够做到在不修改代码的情况下，通过添加/替换配置文件中的实现类，来改变程序的行为或者增加新功能。\u003c/p\u003e","title":"SpringBoot自动装配原理"},{"content":"sychronized sychronized是Java的关键字，用于实现线程之间的同步，保证多个线程对共享资源的安全访问，也被成为同步锁。 sychronized的作用是保证在同一时刻，被修饰的代码块或者方法只会有一个线程执行，以达到保证并发安全的效果。\nsychronized的使用方式 sychronized主要有三种使用方式：\n修饰实例方法：对于当前实例加锁 1 2 public sychronized void methodName(){ } 修饰静态方法：对于当前类对象加锁 1 public static sychronized void methodName(){} 修饰代码块：对给定的对象加锁 1 sychronized(this){} sychronized的底层实现 sychronized的底层实现依赖于JVM，因此sychronized的与JVM内存的存储：Java对象头、以及monitor对象监视器有关。\nJava对象头 在JVM中，对象在内存中的存储布局，分为三个部分：\n对象头 实例数据 对齐填充 在Java中，每个对象都有一个对象头（Object Header），它包含了一些用于管理对象的元数据信息。 对象头的具体结构和内容取决于JVM的实现和配置，并且可能会因为不同版本的JVM和不同的操作系统而有所区别。一些常见的对象头结构和字段如下：\nMark Word（标记字段）：占用对象头的一部分，用于存储对象的标记状态和锁信息。sychronized使用的锁对象是存储在Java的对象头的标记字段里。标记字段通常包含了以下信息 对象的哈希码（用于支持hashCode()方法） 锁状态（是否被锁定、偏向锁或轻量级锁的标识等） 并发标记（用于支持垃圾回收、对象分代等） 偏向锁的线程ID 偏向时间戳 类型指针：指向对象的类元数据的指针，用于确定对象属于哪个类。 数组长度：如果对象是数组类型，则会包含数字的长度信息。 除常见字段外，对象头可能还包含其他与垃圾回收、锁机制和JIT编译等相关的信息。并且对象头的大小是固定的，在不同JVM上可能有所不同。例如：在32位JVM上对象头通常占8个字节，而在64位JVM上通常占12或16个字节。\nMonitor 通过javap -c -s -v -l SynchronizedDemo.class命令反编译代码，可以看到相对应的字节码指令。 sychronized在修饰代码块的时候，JVM采用monitorenter和monitorexit两个指令来实现同步，monitorenter指令指向同步代码块开始的位置，monitorexit指令指向同步代码块结束的位置。 sychronized在修饰实例方法的时候，JVM采用ACC_SYNCHRONIZED标识符来实现同步，通过这个标识来指明这是一个同步方法 上述的三个命令都是基于Monitor实现的。\n实例对象结构中有对象头，对象头中有一个结构Mark Word，Mark Word的指针指向了Monitor。 Monitor是一种同步机制，在JVM中，Monitor的实现是由ObjectMonitor实现的，称之为Monitor锁。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ObjectMonitor() { _header = NULL; _count = 0; // 由于synchronized是可重入锁，count用于记录当前对象锁拥有者线程获取锁的次数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; // 调用了wait方法，处于WAIT/TIME_WAIT的线程，会被加入到WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // 处于等待锁block状态的线程，会被加入到EntryList _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; } 对象监视器主要基于ObjectMonitor结构体中的EntryList、WaitSet两个队列以及计数器count实现的。\n当有多个线程同时想要获取某个对象锁时，首先会进入EntryList队列。 当某个线程获取到对象锁时，线程成为对象锁的拥有者，准备开始运行加锁代码时，执行字节码指令monitorenter，此时count++。 当对象拥有者再次获取锁时，由于sychronized锁是可重入的，此时count++，而不是在EntryList队列中阻塞等待锁。 每个加锁代码块运行完成或因异常退出时，都会执行monitorexit指令，此时count--，当count等于0时，拥有对象锁的线程释放锁。 拥有锁的线程在运行的过程中调用了wait()方法，线程将会进入到WaitSet队列中，等待被notify()唤醒或等待的时间到了，才可能再次成为锁的拥有者。 锁升级 锁解决了数据的安全性问题，但是同时带来了性能的下降，因此在JDK1.6之后对于sychronized锁做了一些优化，为了减少获得锁和释放锁带来ed开销，引入了偏向锁、轻量级锁、重量级锁。 ] 无锁 没有对资源进行锁定，所有的线程都能访问并修改同一个资源，但是同时只有一个线程能够成功。 偏向锁 大多数情况下，锁不仅不存在多线程竞争，而且总是同一线多次获得，为了让线程获取锁的代价更低引入了偏向锁。偏向锁会偏向第一个获得它的线程，如果在接下来的执行过程中，该锁没有被其他线程获取，则持有偏向锁的线程永远不需要同步。 轻量级锁 当偏向锁被另外的线程获取时，偏向锁会升级为轻量锁，其他线程会通过自旋的方式尝试获取锁，不会阻塞，从而提高性能。 重量级锁 其他线程试图获取锁时，都会被阻塞，只有持有锁的线程释放锁之后才会唤醒这些线程。\n当无锁的时候，Mark Word记录对象的hashCode，锁标志位是01，是否偏向锁是0 当对象被当做同步锁并有一个线程抢到锁时，锁标志位还是01，但是是否偏向锁变成了01，并且记录了当前线程的ID 当有其他线程来获取锁时，发现处于偏向锁状态，会使用CAS操作来尝试获取锁，并且这个操作时有可能成功的，获取获取成功，Mark Word中保存的线程ID将会替换成当前线程的；如果抢锁失败，偏向锁会升级成为轻量级锁，JVM会在当前线程的线程栈开辟一块单独的空间，保存指向对象锁Mark Word的指针，同时在对象锁Mark Word中保存指向这片空间的指针，如果保存成功，代表线程抢到了锁，就把Mark Word的锁标志位改为00 轻量级锁抢锁失败，JVM会使用自旋锁，自旋锁不是一个锁，只是代表不断地重试，尝试抢锁。自旋锁在JDK1.7是默认开始的，且自旋次数由JVM决定，自旋锁重试之后任然失败，将会升级成为重量级锁，锁标志位改为10。 ","permalink":"https://lyj404.github.io/blog/post/sychronized-underlying-implementation/","summary":"\u003ch1 id=\"sychronized\"\u003esychronized\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003esychronized\u003c/code\u003e是Java的关键字，用于实现线程之间的同步，保证多个线程对共享资源的安全访问，也被成为同步锁。\n\u003ccode\u003esychronized\u003c/code\u003e的作用是保证在同一时刻，被修饰的代码块或者方法只会有一个线程执行，以达到保证并发安全的效果。\u003c/p\u003e","title":"sychronized底层实现"},{"content":"哈希表 HashMap是数据结构中的哈希表在Java中的具体实现。 哈希表(Hash Table)是一种常见的数据结构，也被称为散列表。它通过将键映射到存储桶(Buckets)中的位置来高效的存储和检索数据。哈希表使用一个哈希函数来计算键的散列值，然后将散列值映射到存储桶的索引上。 哈希表由存储桶数组和哈希函数组成。\n存储桶数组 存储桶数组(Bucket Array)是哈希表中用于存储数据的主要结构，它是由一个固定数量的存储桶(Bucket)组成的数组。每个存储桶可以存储一个或多个元素，每个元素都由一个键值对(key-value pair)组成，其中键用于计算哈希值，而值代表实际存储的数据。\n哈希函数 哈希函数(Hash Function)是一种算法，它可以将任意长度的输入数据映射成固定长度输出，这个输出通常称为哈希值。\n哈希函数构造 哈希函数的构造是哈希表实现的关键之一。常见的哈希函数构造方法：\n直接定值法：直接通过键值映射到对应的数组位置。 数字分析法：取键的某些数字作为映射的位置。 平方取中法：取键平放的中间几位作为映射的位置。 折叠法：将键分割成数位相同的几段，然后把它们的叠加和作为映射的位置。 随机数法：使用一个随机数发生器生成一个随机数作为哈希值。 除留余数法：键除以一个足够大的质数，所得的余数作为哈希地址。 哈希冲突 哈希冲突(Hash Collision)是指两个或多个不同的元素被哈希函数映射到相同的哈希值。哈希表设计的目标是将元素均匀的分布在不同的槽位上，以避免冲突和提高效率。然而，由于哈希函数的限制和输入数据的多样性，冲突是难以避免的。\n解决方法 哈希冲突(Hash Collision)解决方法：\n开放地址法：当发生冲突的时候，通过线性探测、二次探测、双重哈希等方法，在哈希表中寻找到下一个可用的位置。 链地址法：当发生冲突的时候，在哈希表的每一个槽位上维护一个链表，将冲突的元素存储在同一个槽位上。 再哈希法：构造多个哈希函数，在发生冲突时，更换哈希函数，直到找到空闲位置。 实现HashMap 整体设计方案：\n哈希函数：采用hashCode()加上除留余数法 哈希冲突解决方法：链地址法 定义成员变量 定义MyHashMap的成员变量：\n定义默认容量 定义默认负载因子 定义MyHashMap的大小 定义桶数组 自定义内部节点类来实现桶数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 //自定义内部节点类 class Node\u0026lt;K, V\u0026gt;{ /** * 键 */ private K key; /** * 值 */ private V value; /** * 后继 */ private Node\u0026lt;K, V\u0026gt; next; public Node(K key, V value) { this.key = key; this.value = value; } public Node(K key, V value, Node\u0026lt;K, V\u0026gt; next) { this.key = key; this.value = value; this.next = next; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** * 默认容量 */ private static final int DEFAULT_CAPACITY = 16; /** * 默认负载因子 */ private static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * MyHashMap的大小 */ private int size; /** * 桶数组 */ private Node\u0026lt;K, V\u0026gt;[] buckets; 初始化 定义两个构造函数，一个为无参构造函数，并为桶设置默认容量；另一个为有参构造函数，通过参数指定桶的容量。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** * 无参构造方法，设置桶数组大小为默认容量 */ public MyHashMap(){ this.buckets = new Node[DEFAULT_CAPACITY]; this.size = 0; } /** * 有参构造器，设置桶的容量 * @param capacity int */ public MyHashMap(int capacity){ buckets = new Node[capacity]; this.size = 0; } 哈希函数 通过hashCode()和除留余数法实现哈希函数，来获取哈希地址。\n1 2 3 4 5 6 7 8 /** * 哈希函数，获取哈希地址 * @param key K * @return int */ private int hash(K key, int length){ return Math.abs(key.hashCode() \u0026amp; (length - 1)); } 添加元素 添加元素逻辑：\n判断是否需要扩容 获取元素插入位置 如果要插入的位置为空，则插入元素 如果不为空，则遍历链表 如果元素的key和节点的相同，则覆盖，否则新建节点插入链表头部(JDK7的HashMap使用头插法) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /** * 添加方法 * @param key K * @param value V */public void put(K key, V value){ if (size \u0026gt;= buckets.length * DEFAULT_LOAD_FACTOR) { resize(); } putVal(key, value, buckets); } /** * 将元素存入Node数组 * @param key K * @param value V * @param table Node */private void putVal(K key, V value, Node\u0026lt;K,V\u0026gt;[] table) { //获取插入位置 int index = hash(key); Node\u0026lt;K, V\u0026gt; node = table[index]; //如果要插入的位置为空，则插入元素 if (node == null) { table[index] = new Node\u0026lt;\u0026gt;(key, value); size++; return; } //如果不为空，说明发生了哈希冲突，遍历链表使用链地址法 while (node != null){ //如果元素的key和节点的相同，则覆盖 if ((node.key.hashCode() == key.hashCode()) \u0026amp;\u0026amp; (node.key == key || node.key.equals(key))){ node.value = value; return; } node = node.next; } //新建节点插入链表头部 Node\u0026lt;K, V\u0026gt; kvNode = new Node\u0026lt;\u0026gt;(key, value, table[index]); table[index] = kvNode; size++; } 扩容 扩容逻辑：\n创建两倍容量的新数组 将当前桶中的元素重新哈希到新的数组 重新MyHashMap设置大小 将旧的桶数组的元素移到新的桶数组中 将扩容后的数组赋值给原来的桶数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * 重新哈希元素 * @param newNode Node\u0026lt;K,V\u0026gt;[] */private void rehash(Node\u0026lt;K,V\u0026gt;[] newNode) { //重新MyHashMap设置大小 this.size = 0; //将旧的桶数组的元素移到新的桶数组中 for (int i = 0; i \u0026lt; buckets.length; i++){ if (buckets[i] == null){ continue; } Node\u0026lt;K, V\u0026gt; node = buckets[i]; while (node != null){ //将元素放入新的桶数组 putVal(node.key, node.value, newNode); node = node.next; } } } 获取元素 获取元素逻辑：\n通过哈希获取地址 如果链表存在遍历链表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /** * 获取元素 * @param key K * @return V */public V get(K key){ //获取地址 int index = hash(key, buckets.length); if (buckets[index] == null) { return null; } Node\u0026lt;K, V\u0026gt; node = buckets[index]; while (node != null){ if ((node.key.hashCode() == key.hashCode()) \u0026amp;\u0026amp; (node.key == key || node.key.equals(key))){ return node.value; } //如果链表存在遍历链表 if (node.next != null){ node = node.next; } } return null; } 删除元素 删除元素逻辑：\n如果key存在则删除，不存在直接返回 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /** * 移除元素 * @param key K */public void remove(K key){ //获取地址 int index = hash(key, buckets.length); if (buckets[index] == null) { return; } if (buckets[index].key.equals(key)) { buckets[index] = buckets[index].next; return; // 移除成功，结束操作 } Node\u0026lt;K, V\u0026gt; node = buckets[index]; while (node.next != null) { if (node.next.key.equals(key)) { node.next = node.next.next; return; // 移除成功，结束操作 } node = node.next; // 继续遍历链表下一个节点 } } 完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 public class MyHashMap\u0026lt;K, V\u0026gt; { class Node\u0026lt;K, V\u0026gt;{ /** * 键 */ private K key; /** * 值 */ private V value; /** * 后继 */ private Node\u0026lt;K, V\u0026gt; next; public Node(K key, V value) { this.key = key; this.value = value; } public Node(K key, V value, Node\u0026lt;K, V\u0026gt; next) { this.key = key; this.value = value; this.next = next; } } /** * 默认容量 */ private static final int DEFAULT_CAPACITY = 16; /** * 默认负载因子 */ private static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * MyHashMap的大小 */ private int size; /** * 桶数组 */ private Node\u0026lt;K, V\u0026gt;[] buckets; /** * 无参构造方法，设置桶数组大小为默认容量 */ public MyHashMap(){ this.buckets = new Node[DEFAULT_CAPACITY]; this.size = 0; } /** * 有参构造器，设置桶的容量 * @param capacity int */ public MyHashMap(int capacity){ buckets = new Node[capacity]; this.size = 0; } /** * 哈希函数，获取哈希地址 * @param key K * @return int */ private int hash(K key, int length){ return Math.abs(key.hashCode() \u0026amp; (length - 1)); } /** * 添加方法 * @param key K * @param value V */ public void put(K key, V value){ if (size \u0026gt;= buckets.length * DEFAULT_LOAD_FACTOR) { resize(); } putVal(key, value, buckets); } /** * 将元素存入Node数组 * @param key K * @param value V * @param table Node */ private void putVal(K key, V value, Node\u0026lt;K,V\u0026gt;[] table) { //获取插入位置 int index = hash(key, table.length); Node\u0026lt;K, V\u0026gt; node = table[index]; //如果要插入的位置为空，则插入元素 if (node == null) { table[index] = new Node\u0026lt;\u0026gt;(key, value); size++; return; } //如果不为空，说明发生了哈希冲突，遍历链表使用链地址法 while (node != null){ //如果元素的key和节点的相同，则覆盖 if ((node.key.hashCode() == key.hashCode()) \u0026amp;\u0026amp; (node.key == key || node.key.equals(key))){ node.value = value; return; } node = node.next; } //新建节点插入链表头部 Node\u0026lt;K, V\u0026gt; kvNode = new Node\u0026lt;\u0026gt;(key, value, table[index]); table[index] = kvNode; size++; } /** * 扩容 */ private void resize() { //创建两倍容量的新数组 Node\u0026lt;K, V\u0026gt;[] newNode = new Node[buckets.length * 2]; rehash(newNode); buckets = newNode; } /** * 重新哈希元素 * @param newNode Node\u0026lt;K,V\u0026gt;[] */ private void rehash(Node\u0026lt;K,V\u0026gt;[] newNode) { //重新MyHashMap设置大小 this.size = 0; //将旧的桶数组的元素移到新的桶数组中 for (int i = 0; i \u0026lt; buckets.length; i++){ if (buckets[i] == null){ continue; } Node\u0026lt;K, V\u0026gt; node = buckets[i]; while (node != null){ //将元素放入新的桶数组 putVal(node.key, node.value, newNode); node = node.next; } } } /** * 获取元素 * @param key K * @return V */ public V get(K key){ //获取地址 int index = hash(key, buckets.length); if (buckets[index] == null) { return null; } Node\u0026lt;K, V\u0026gt; node = buckets[index]; while (node != null){ if ((node.key.hashCode() == key.hashCode()) \u0026amp;\u0026amp; (node.key == key || node.key.equals(key))){ return node.value; } //如果链表存在遍历链表 if (node.next != null){ node = node.next; } } return null; } /** * 移除元素 * @param key K */ public void remove(K key){ //获取地址 int index = hash(key, buckets.length); if (buckets[index] == null) { return; } if (buckets[index].key.equals(key)) { buckets[index] = buckets[index].next; return; // 移除成功，结束操作 } Node\u0026lt;K, V\u0026gt; node = buckets[index]; while (node.next != null) { if (node.next.key.equals(key)) { node.next = node.next.next; return; // 移除成功，结束操作 } node = node.next; // 继续遍历链表下一个节点 } } /** * 返回MyHashMap大小 * @return int */ public int size(){ return this.size; } } 测试 1 2 3 4 5 6 7 8 public class Test { public static void main(String[] args) { MyHashMap\u0026lt;String, String\u0026gt; map = new MyHashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;test\u0026#34;, \u0026#34;lyj is test\u0026#34;); map.remove(\u0026#34;test\u0026#34;); System.out.println(map.get(\u0026#34;test\u0026#34;)); } } ","permalink":"https://lyj404.github.io/blog/post/java1.7-hashmap-implement/","summary":"\u003ch1 id=\"哈希表\"\u003e哈希表\u003c/h1\u003e\n\u003cp\u003eHashMap是数据结构中的哈希表在Java中的具体实现。\n哈希表(Hash Table)是一种常见的数据结构，也被称为散列表。它通过将键映射到存储桶(Buckets)中的位置来高效的存储和检索数据。哈希表使用一个哈希函数来计算键的散列值，然后将散列值映射到存储桶的索引上。\n哈希表由\u003ccode\u003e存储桶数组\u003c/code\u003e和\u003ccode\u003e哈希函数\u003c/code\u003e组成。\u003c/p\u003e","title":"实现HashMap(JDK1.7)"},{"content":"循环依赖 循环依赖是指两个或多个模块或组件之间相互依赖，形成一个闭环情况。\n1 2 3 4 5 6 7 8 9 10 @Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; } Spring解决循环依赖的限制条件 出现循环依赖的Bean必须是单例Bean，原型Bean不可以 原型Bean每次获取的时候都会创建一个新的Bean，假设A、B循环依赖了，A里面要注入B，B里面要注入A，注入一次就会产生一个新的Bean，最终会无穷无尽。 不全是使用构造器注入的 均采用setter方法注入的，可以被Spring解决 均采用构造器方式注入的，不可以被Spring解决 setter和构造器两种方式都使用，需要具体分析 使用构造器注入时，当A依赖B，而B又依赖A，Spring会尝试先创建A。但是，在创建A时，由于它依赖的B还没有创建完成，无法传递给构造器进行注入，从而导致循环依赖无法被解决。\n三级缓存 Spring通过三级缓存来解决循环依赖问题。\n单例对象缓存(singletonObjects)：存放完整的Bean对象，包括所有属性都已经注入完成的Bean。 早期对象缓存(earlySingletonObjects)：存放半成品对象，即Bean的实例化和依赖注入已经完成，但是初始化还未执行完成的Bean。 单例工厂缓存(singletonFactories)：存放用于创建完整Bean对象的Factory对象。 Spring 解决循环依赖的过程 Spring容器启动之后，开始创建Bean。 容器根据配置信息实例化完成A，但是还没有初始化，紧接着A与一个ObjectFactory对象放入三级缓存。如果A被AOP代理，通过这个工厂获取到的就是A代理后的对象，如果没有代理，工厂最后获取到的就是A的实例化对象 初始化A，为A属性赋值，发现要依赖注入B，于是将B实例化，在B还未初始化的时候将B和一个ObjectFactory对象放入三级缓存 初始化B，发现需要依赖注入A，此时在三级缓存中找到了A与ObjectFactory\u0026lt;?\u0026gt; singletonFactory，通过singletonFactory.getObject()，得到A的引用，并将其存入二级缓存，且从三级缓存移除。 B从二级缓存中获取到不完整的A，并注入到B中，B初始化完成，并将B在二级缓存、三级缓存中的引用清除，同时将实例化完成的B存入一级缓存 B实例化完成后，继续A的初始化，A从一级缓存中获取到B，同样，并将A二级缓存、三级缓存中的引用清除，同时将实例化完成的A存入一级缓存 A和B实例完成，并且二级缓存和三级缓存中都没有A和B了 为什么要三级缓存？二级为什么不可以？ 三级缓存中存放的是生成具体对象的匿名内部类，在获取对象的时候，既可以生成代理对象，又可以生成普通的对象，使用三级缓存可以保证不管什么时候使用的都是一个对象。\n如果只有二级缓存的情况，往二级缓存中放一个普通Bean对象，在Bean初始化的时候，通过BeanPostProcessor生成代理对象之后，会覆盖掉二级缓存中普通Bean对象，从而导致获取的Bean对象不一致。\n","permalink":"https://lyj404.github.io/blog/post/spring-circular-dependency/","summary":"\u003ch1 id=\"循环依赖\"\u003e循环依赖\u003c/h1\u003e\n\u003cp\u003e循环依赖是指两个或多个模块或组件之间相互依赖，形成一个闭环情况。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Component\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eA\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Autowired\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Component\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eB\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Autowired\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eA\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://imgse.com/i/pPfYDFs\"\u003e\u003cimg alt=\"循环依赖\" loading=\"lazy\" src=\"https://z1.ax1x.com/2023/09/16/pPfYDFs.png\"\u003e\u003c/a\u003e\u003c/p\u003e","title":"Spring循环依赖"},{"content":"Bean的生命周期 在Java应用中，普通的Java Bean的生命周期很简单。使用关键字new对Bean进行实例化，之后Bean就可使用了，当Bean不在被使用就会被Java的垃圾回收机制回收。概括的说就是两个阶段：\n实例化 不使用之后垃圾回收 Spring的Bean的生命周期则比较复杂，Spring的Bean由IOC容器进行实例化、组装和管理。IOC(Inversion of Control，控制反转)是一种软件设计原则，用于实现松耦合和可维护的代码结构。\nSpring Bean的生命周期主要分为四个部分\n实例化(Instantiation) 属性赋值(Populate) 初始化(Initialization) 销毁(Destruction) Spring Bean详细生命周期： 在Spring框架中，Bean的生命周期经历了以下几个阶段：\n实例化(Instantiation)：Spring容器根据配置信息或注解创建Bean的实例。 属性赋值(Populate)：Spring容器将属性值和依赖注入到Bean中。 设置Bean名称：如果Bean实现了BeanNameAware接口，将会调用setBeanName()方法，把Bean的名称传递给它。 设置BeanFactory的引用：如果Bean实现了BeanFactoryAware接口，将会调用setBeanFactory()方法，把Bean所在的BeanFactory传递给它。 设置其他自定义上下文：如果Bean实现了ApplicationContextAware接口，将会调用setApplicationContext()方法，把ApplicationContext传递给它。 Bean后处理(BeanPostProcessor)：Spring容器会对所有的BeanPostProcessor进行初始化，这些后处理可以对Bean实例化前后进行一些自定义操作。 初始化(Initialization)：如果Bean实现了InitializingBean接口，将会调用afterPropertiesSet()方法来执行自定义初始化逻辑。另外，还可以通过配置文件使用init-method属性制定Bean的初始化方法。 自定义初始化(Custom Initialization)：在Bean初始化之后，可以执行一些自定义的初始化逻辑。 销毁(Destruction)：如果Bean实现了DisposableBean接口，将会调用destroy()方法来执行自定义销毁逻辑。同样，还可以通过配置文件使用destory-method属性制定Bean的初始化方法。 自定义销毁(Custom Destruction)：在 Bean 销毁之前，可以执行一些自定义的销毁逻辑。 Bean的生命周期可以通过配置方式进行管理，例如使用XML配置或者是注解。同时，Spring还提供了各种扩展点和回调接口，使开发者能够在Bean的生命周期的不同阶段进行定制化操作。\n扩展点 Spring如果监测到Bean实现了Aware接口，则会为其注入相应的依赖。因此通过让Bean实现Aware接口，能够让Bean获得相应的Spring容器资源 Aware接口：\nBeanNameAware：注入当前Bean对应的名称 BeanClassLoaderAware：注入加载当前Bean的ClassLoader BeanFactoryAware：注入当前BeanFactory的引用 BeanPostProcessor BeanPostProcessor是Spring的一个重要的接口，用于在Bean初始化前后进行扩展和自定义处理。它是Bean生命周期的一部分，主要用于在容器实例化Bean和将其添加到应用程序上下文之后，以及Bean初始化前后对Bean的定制化操作。\n1 2 3 4 5 6 7 8 9 public interface BeanPostProcessor { default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } } postProcessBeforeInitialization(Object bean, String beanName)：Bean初始化之前调用该方法。接收两个参数：正在初始化的Bean的对象和Bean的名称。可以对此方法中的Bean进行修改和增强。如果返回的对象不为null，则该对象作为最终的Bean实例。 postProcessAfterInitialization(Object bean, String beanName)：在Bean初始化之后调用该方法。接收两个参数：已经完成初始化的Bean对象和Bean的名称。可以对此方法中的Bean进行修改和增强。如果返回的对象不为null，则该对象作为最终的Bean实例。 通过实现BeanPostProcessor接口，在Spring容器实例化Bean和初始化Bean的过程中介入，可以实现一些功能：\n属性值的校验、修正和增强。 动态代理，用于实现AOP的切面 注入依赖关系的解析和处理 条件化的Bean的创建和初始化 对Bean进行包装或装饰 ","permalink":"https://lyj404.github.io/blog/post/spring-bean-lifecycle/","summary":"\u003ch1 id=\"bean的生命周期\"\u003eBean的生命周期\u003c/h1\u003e\n\u003cp\u003e在Java应用中，普通的Java Bean的生命周期很简单。使用关键字\u003ccode\u003enew\u003c/code\u003e对Bean进行实例化，之后Bean就可使用了，当Bean不在被使用就会被Java的垃圾回收机制回收。概括的说就是两个阶段：\u003c/p\u003e","title":"Spring Bean的生命周期"},{"content":"运行时数据区域 JVM在运行Java程序的过程中会把管理的内存分成不同的数据区域 线程私有：\n虚拟机栈 本地方法栈 程序计数器 线程共享：\n堆 方法区（元空间） 直接内存 虚拟机栈 Java虚拟机栈是一个线程私有的内存区域，用于存储每个方法执行时的局部变量表、操作数栈、动态链接、方法返回地址。每个方法在执行时都会创建一个栈帧，当方法执行完毕后，对应的栈帧将会出栈销毁。 局部变量表主要存放编译器可知的各种数据类型(基本数据类型)和对象引用 操作数栈用于在方法执行过程中进行数据操作和计算。操作数栈采用栈的数据结构，用于存储方法执行过程中的局部变量、中间结果和操作数。 动态链接是指在方法调用过程中进行方法的查找和链接，实现方法的动态绑定。主要用于支持Java程序中多态性和动态方法调用 方法返回地址是指在方法调用过程中，用于记录方法返回的地址信息。当一个方法被调用时，JVM会为该方法创建一个新的栈帧，并将其推入虚拟机栈中。\n本地方法栈 本地方法栈与虚拟机栈类似，不同的是它主要是为执行本地方法服务(Native Method 使用C/C++编写的方法)。\n程序计数器 程序计数器是一块比较小的内存空间，用于记录当前线程执行的字节码指令地址。当线程执行Java方法时，程序计数器用于指向下一条要执行的指令。\n堆 堆是JVM中最大的一块内存，用于存储对象实例和数组。堆被所有线程共享，因此在创建Java对象时，对象实例存储在堆中。 在JDK7及之前的版本中，堆主要分为三部分：\n新生代 老年代 永久代 在JDK8之后，永久代已经被元空间取代，元空间使用的是本地内存。 对象首先都会在Eden区进行分配，在经过一次Young GC之后，如果对象还存活，则会进入from或者to，并且对象的年龄也会随之增加，在对象的年龄达到一定的程度之后，对象将会进入老年代\n字符串常量池 字符串常量池是Java中一个特殊的内存区域，用于存储字符串常量。位于堆内存中，并在在JVM启动时就被创建。字符串常量池是JVM为了提升字符串的性能和减少内存消耗而开辟的一块区域，主要避免字符串重复创建 字符串常量池的主要特点：\n字符串常量共享：字符串常量池中的字符串对象是唯一的，即相同的字符串字面量只会在常量池中存储一份。这是通过使用字符串的intern()方法将字符串对象添加到常量池中实现的。 提高性能和节省内存：由于字符串常量池中的字符串对象共享，因此当多个字符串字面量具有相同的内容时，它们可以引用一个对象，避免重复创建字符串对象，从而提高性能并节省内存。 存储在堆中：尽管字符串常量池位于堆中，但与普通的Java对象不同，字符串常量池中的字符串对象是在编译期间确定并加载到常量池中的。它们在程序运行期间一直存在，不会被来及回收 方法区(元空间) 方法区是线程共享的内存区域，用于存储类的类信息、字段信息、方法信息、常量、静态变量、编译器编译后的代码等数据。JDK 8及之前版本中方法区又被称为“永久代”（Permanent Generation），但在JDK 8之后已经被改为“元空间”（MetaSpace）。 永久代被移除的原因：\n永久代内存不足导致内存溢出：永久代中存储的内容会随着应用的启动和停止不断增加，直到达到 PermGen 区的上限。在某些应用中，如果没有进行充分的调优，就很容易导致 PermGen 内存溢出。 调优复杂：由于 PermGen 区本质上是 JVM 堆区的一部分，因此在进行内存调优时非常麻烦。而且需要根据具体应用情况计算出合理的内存大小，比较繁琐。 元空间无需考虑内存大小：元空间是 Java 虚拟机内部的一块本地内存，不再使用 JVM 堆区的永久代，可以自动调整大小。这样就避免了内存溢出和调优的烦恼。 元空间允许热部署：元空间还可以支持热部署，也就是在运行期间动态更新类的定义，而不需要重新启动应用。 运行时常量池 运行时常量池是方法区的一部分，用于存储编译器生成的各种字面量和符号引用。每个类都有自己的运行时常量池，其中包含了该类所使用的常量、变量和方法的符号引用等信息。 运行时常量池的特点：\n动态性：运行时常量池是在类加载过程中动态创建的。在加载过程中，字节码文件中的常量会被解析和转换成具体的内存结构，并存放在运行时常量池中。 独立性：每个类都有自己的运行时常量池，它与其他类的运行时常量池相互独立，存储着该类的常量信息。 存放常量：运行时常量池主要用于存放类文件中的常量。这些常量包括字符串常量、字面量和符号引用。通过运行时常量池，可以在运行时通过索引或其他方式访问和使用这些常量。 加速性能：运行时常量池可以提高程序的执行效率。在类加载的过程中，一些常量会被提前解析和计算，并存放在运行时常量池中。这样，在实际运行过程中可以直接使用运行时常量池中的常量，避免了重复计算和解析的开销。 直接内存 JVM中的直接内存是一种使用堆外内存（Off-heap）来实现的内存分配方式，也称为堆外内存（Off-heap Memory Allocation）。 直接内存的优势：\n无需垃圾回收：Java堆内存需要进行垃圾回收，会对程序的性能产生一定影响。而直接内存不受JVM堆空间大小限制，无需进行垃圾回收，避免了大量的垃圾回收操作，从而提高了程序的性能和稳定性。 更高的访问速度：由于直接内存在堆外分配，因此不会占用JVM堆空间，也不会受到JVM堆空间大小的限制。同时，由于直接内存采用了DMA（直接内存访问）技术，可以实现更高效的读取和写入操作。 更大的空间：由于直接内存位于堆外，因此不受JVM堆大小的限制，可以使用更多的内存空间。并且，直接内存的分配和释放速度快，不会受到堆内存碎片等问题的影响，从而可以更好地支持大规模的、高性能的应用。 ","permalink":"https://lyj404.github.io/blog/post/java-jvm-memory-structure/","summary":"\u003ch1 id=\"运行时数据区域\"\u003e运行时数据区域\u003c/h1\u003e\n\u003cp\u003eJVM在运行Java程序的过程中会把管理的内存分成不同的数据区域\n\u003ca href=\"https://imgse.com/i/pPfYqOO\"\u003e\u003cimg alt=\"JVM\" loading=\"lazy\" src=\"https://z1.ax1x.com/2023/09/16/pPfYqOO.png\"\u003e\u003c/a\u003e\n\u003cstrong\u003e线程私有：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e虚拟机栈\u003c/li\u003e\n\u003cli\u003e本地方法栈\u003c/li\u003e\n\u003cli\u003e程序计数器\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e线程共享：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e堆\u003c/li\u003e\n\u003cli\u003e方法区（元空间）\u003c/li\u003e\n\u003cli\u003e直接内存\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"虚拟机栈\"\u003e虚拟机栈\u003c/h2\u003e\n\u003cp\u003eJava虚拟机栈是一个线程私有的内存区域，用于存储每个方法执行时的\u003cstrong\u003e局部变量表、操作数栈、动态链接、方法返回地址\u003c/strong\u003e。每个方法在执行时都会创建一个栈帧，当方法执行完毕后，对应的栈帧将会出栈销毁。\n\u003cstrong\u003e局部变量表\u003c/strong\u003e主要存放编译器可知的各种数据类型(基本数据类型)和对象引用\n\u003cstrong\u003e操作数栈\u003c/strong\u003e用于在方法执行过程中进行数据操作和计算。操作数栈采用栈的数据结构，用于存储方法执行过程中的局部变量、中间结果和操作数。\n\u003cstrong\u003e动态链接\u003c/strong\u003e是指在方法调用过程中进行方法的查找和链接，实现方法的动态绑定。主要用于支持Java程序中多态性和动态方法调用\n\u003cstrong\u003e方法返回地址\u003c/strong\u003e是指在方法调用过程中，用于记录方法返回的地址信息。当一个方法被调用时，JVM会为该方法创建一个新的栈帧，并将其推入虚拟机栈中。\u003c/p\u003e","title":"JVM内存结构"},{"content":"Shell 什么是Shell？ shell编程就是对一堆Linux命令的逻辑化处理。\nShell编程Hello World 新建文件touch helloworld.sh 添加执行权限chmod +x helloworld.sh 编辑sh脚本 1 2 #!/bin/bash echo \u0026#34;helloworld\u0026#34; 在shell种#表示注释。shell的第一行比较特殊，一般都会以#!开头来指定使用的shell类型\nshell的类型：\nbash zsh fish 运行脚本./helloworld.sh。运行shell脚本需要添加./的路径，直接使用helloworld.sh，Linux系统回去PATH里寻找，一般只有/bin, /sbin，/usr/bin，/usr/sbin在PATR里，因此直接使用helloworld.sh是找不到命令的 Shell的变量 Shell的变量一般分为三种：\n自定义变量：就在当前shell种有效，其他shell中无效。 Linux定义的环境变量： 例如PATH，HOME，使用env命令可以查看所有的环境变量，而set命令既可以查看环境变量也可以查看自定义变量。 Shell变量： Shell变量是有Shell程序设置的特殊变量。Shell变量中有一部分是环境变量，一部分是局部变量 常用的环境变量： PATH 决定了 shell 将到哪些目录中寻找命令或程序\nHOME 当前用户主目录\nHISTSIZE 历史记录数\nLOGNAME 当前用户的登录名\nHOSTNAME 指主机的名称\nSHELL 当前用户 Shell 类型\nLANGUAGE 语言相关的环境变量，多语言可以修改此环境变量\nMAIL 当前用户的邮件存放目录\nPS1 基本提示符，对于 root 用户是#，对于普通用户是$\n使用Linux定义的环境变量 echo $HOME：查看当前用户目录 echo $SHELL 查看当前用户的Shell类型 使用自定义变量 1 2 3 #!/bin/bash hello=\u0026#34;helloworld\u0026#34; echo $hello Shell变量名的命名规范：\n命名只能使用英文字母，数字和下划线，首字母不能以数字开头，但可以使用下划线开头 中间不能有空格，但可以使用下划线 不能使用标点符号 不能使用bash的关键字 Shell的字符串 Shell的字符串可以是单引号，也可以是双引号。在单引号中所有的特殊字符($、\\等)都没有特殊含义。在双引号中，除了\u0026quot;$\u0026quot;、\u0026quot;\u0026quot;、反引号和感叹号（需开启 history expansion），其他的字符没有特殊含义。 单引号：\n1 2 3 4 #!/bin/bash name=\u0026#39;lyj\u0026#39; hello=\u0026#39;Hello, My name is $name!\u0026#39; echo $hello 输出结果\n1 Hello, My name is $name! 双引号：\n1 2 3 4 #!/bin/bash name=\u0026#39;lyj\u0026#39; hello=\u0026#34;Hello, My name is $name!\u0026#34; echo $hello 输出结果\n1 Hello, My name is lyj! 字符串常见操作 拼接字符串 1 2 3 4 5 6 7 8 9 10 #!/bin/bash name=\u0026#34;lyj\u0026#34; # 使用双引号拼接 greeting=\u0026#34;hello, \u0026#34;$name\u0026#34; !\u0026#34; greeting_1=\u0026#34;hello, ${name} !\u0026#34; echo $greeting $greeting_1 # 使用单引号拼接 greeting_2=\u0026#39;hello, \u0026#39;$name\u0026#39; !\u0026#39; greeting_3=\u0026#39;hello, ${name} !\u0026#39; echo $greeting_2 $greeting_3 获取字符串的长度 1 2 3 4 5 6 7 #!/bin/bash #获取字符串长度 name=\u0026#34;lyj\u0026#34; # 方式一 echo ${#name} #输出 10 # 方式二 expr length \u0026#34;$name\u0026#34;; 使用 expr命令时，表达式中的运算符左右必须包含空格，如果不包含空格，将会输出表达式本身。\n1 2 expr 5+1 // 直接输出 5+1 expr 5 + 1 // 输出 6 对于某些运算符，还需要我们使用符号\\进行转义，否则就会提示语法错误。\n1 2 expr 5 * 1 // 输出错误 expr 5 \\* 1 // 输出5 截取字符串 1 2 3 #从字符串第 1 个字符开始往后截取 10 个字符 str=\u0026#34;abcdefghij is a great man\u0026#34; echo ${str:0:10} #输出:abcdefghij 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!bin/bash #author:amau var=\u0026#34;https://www.runoob.com/linux/linux-shell-variable.html\u0026#34; # %表示删除从后匹配, 最短结果 # %%表示删除从后匹配, 最长匹配结果 # #表示删除从头匹配, 最短结果 # ##表示删除从头匹配, 最长匹配结果 # 注: *为通配符, 意为匹配任意数量的任意字符 s1=${var%%t*} #h s2=${var%t*} #https://www.runoob.com/linux/linux-shell-variable.h s3=${var%%.*} #http://www s4=${var#*/} #/www.runoob.com/linux/linux-shell-variable.html s5=${var##*/} #linux-shell-variable.html Shell的数组 bash只支持一维数组不支持多维数据。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash array=(1 2 3 4 5); # 获取数组长度 length=${#array[@]} # 或者 length2=${#array[*]} #输出数组长度 echo $length #输出：5 echo $length2 #输出：5 # 输出数组第三个元素 echo ${array[2]} #输出：3 unset array[1]# 删除下标为1的元素也就是删除第二个元素 for i in ${array[@]};do echo $i ;done # 遍历数组，输出：1 3 4 5 unset array; # 删除数组中的所有元素 for i in ${array[@]};do echo $i ;done # 遍历数组，数组元素为空，没有任何输出内容 Shell的基本运算符 算数运算 1 2 3 4 5 #!/bin/bash a=3;b=3; val=`expr $a + $b` #输出：Total value : 6 echo \u0026#34;Total value : $val\u0026#34; 关系运算 1 2 3 4 5 6 7 8 9 #!/bin/bash score=90; maxscore=100; if [ $score -eq $maxscore ] then echo \u0026#34;A\u0026#34; else echo \u0026#34;B\u0026#34; fi 逻辑运算 1 2 3 4 #!/bin/bash a=$(( 1 \u0026amp;\u0026amp; 0)) # 输出：0；逻辑与运算只有相与的两边都是1，与的结果才是1；否则与的结果是0 echo $a; 布尔运算 字符串运算 1 2 3 4 5 6 7 8 9 #!/bin/bash a=\u0026#34;abc\u0026#34;; b=\u0026#34;efg\u0026#34;; if [ $a = $b ] then echo \u0026#34;a 等于 b\u0026#34; else echo \u0026#34;a 不等于 b\u0026#34; fi 文件测试运算 1 2 3 4 5 6 #!/bin/bash file=\u0026#34;/usr/learnshell/test.sh\u0026#34; # 判断文件是否可读 if [ -r $file ] # 判断文件是否可写 if [ -w $file ] Shell的流程控制 if条件语句 shell的if条件语句不能包含空语句\n1 2 3 4 5 6 7 8 9 10 11 12 #!/bin/bash a=3; b=9; if [ $a -eq $b ] then echo \u0026#34;a 等于 b\u0026#34; elif [ $a -gt $b ] then echo \u0026#34;a 大于 b\u0026#34; else echo \u0026#34;a 小于 b\u0026#34; fi for循环语句 输出列表的数据\n1 2 3 4 for loop in 1 2 3 4 5 do echo \u0026#34;The value is: $loop\u0026#34; done 产生十个随机数\n1 2 3 4 5 #!/bin/bash for i in {0..9}; do echo $RANDOM; done 输出一到五\n1 2 3 4 5 #!/bin/bash length=5 for((i=1;i\u0026lt;=length;i++));do echo $i; done; while语句 1 2 3 4 5 6 7 #!/bin/bash int=1 while(( $int\u0026lt;=5 )) do echo $int let \u0026#34;int++\u0026#34; done Shell的函数 没有参数和返回值的函数 1 2 3 4 5 6 7 #!/bin/bash hello(){ echo \u0026#34;这是我的第一个 shell 函数!\u0026#34; } echo \u0026#34;-----函数开始执行-----\u0026#34; hello echo \u0026#34;-----函数执行完毕-----\u0026#34; 有返回值的函数 输入两个数字相加并返回结果:\n1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash funWithReturn(){ echo \u0026#34;输入第一个数字: \u0026#34; read aNum echo \u0026#34;输入第二个数字: \u0026#34; read anotherNum echo \u0026#34;两个数字分别为 $aNum 和 $anotherNum !\u0026#34; return $(($aNum+$anotherNum)) } funWithReturn echo \u0026#34;输入的两个数字之和为 $?\u0026#34; 有参数的函数 1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash funWithParam(){ echo \u0026#34;第一个参数为 $1 !\u0026#34; echo \u0026#34;第二个参数为 $2 !\u0026#34; echo \u0026#34;第十个参数为 $10 !\u0026#34; echo \u0026#34;第十个参数为 ${10} !\u0026#34; echo \u0026#34;第十一个参数为 ${11} !\u0026#34; echo \u0026#34;参数总数有 $# 个!\u0026#34; echo \u0026#34;作为一个字符串输出所有参数 $* !\u0026#34; } funWithParam 1 2 3 4 5 6 7 8 9 34 73 ","permalink":"https://lyj404.github.io/blog/post/shell-program/","summary":"\u003ch1 id=\"shell\"\u003eShell\u003c/h1\u003e\n\u003ch2 id=\"什么是shell\"\u003e什么是Shell？\u003c/h2\u003e\n\u003cp\u003eshell编程就是对一堆Linux命令的逻辑化处理。\u003c/p\u003e\n\u003ch2 id=\"shell编程hello-world\"\u003eShell编程Hello World\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e新建文件\u003ccode\u003etouch helloworld.sh\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e添加执行权限\u003ccode\u003echmod +x helloworld.sh\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e编辑sh脚本\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#!/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;helloworld\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e在shell种\u003ccode\u003e#\u003c/code\u003e表示注释。shell的第一行比较特殊，一般都会以\u003ccode\u003e#!\u003c/code\u003e开头来指定使用的shell类型\u003c/p\u003e","title":"Shell编程"},{"content":"JUC 1、什么是JUC 1 2 3 java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks java.util 工具包\n业务：普通的线程代码Thread\nRunnable 没有返回值，效率相比Callable较低\n2、线程和进程 线程、进程\n进程：一个程序，QQ.ext Music.exe 程序的集合\n一个进程往往可以包含多个线程，至少包含一个\nJava默认有几个线程？ 2个 main GC\n线程：开了一个进程Typora，写字，自动保存(线程负责)\n对于Java而言：Thread Runnable Callable\nJava真的可以开启线程吗？ 开不了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public synchronized void start() { /** * This method is not invoked for the main method thread or \u0026#34;system\u0026#34; * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state \u0026#34;NEW\u0026#34;. */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group\u0026#39;s list of threads * and the group\u0026#39;s unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } //本地方法，底层的c++, Java无法操作硬件 private native void start0(); 并发、并行\n并发编程：并发、并行\n并发（多线程操作一个资源）\nCPU一核，模拟出来多条线程，快速交替 并行（多个人一起行走）\nCPU多核，多个线程可以同时执行: 线程池 1 2 3 4 5 6 7 public class Test1 { public static void main(String[] args) { //获取cpu的核数 //cpu密集型，IO密集型 System.out.println(Runtime.getRuntime().availableProcessors()); } } 并发编程的本质：充分利用cup的资源\n线程有几个状态\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public enum State { //新生 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待 WAITING, //超时等待 TIMED_WAITING, //终止 TERMINATED; } wait/sleep区别\n来自不同的类\nwait =\u0026gt; Object\nsleep =\u0026gt; Thread\n关于锁的释放\nwait会释放锁,sleep睡觉了,抱着锁睡觉,不会释放\n使用的范围是不同的\nwait\nwait必须在同步代码块中\nsleep可以在任何地方睡\n是否需要捕获异常\nwait不需要捕获异常\nsleep必须捕获异常\n3、Lock锁(重点) 传统Synchronize\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class SaleTicketDemo01 { public static void main(String[] args) { //并发：多个线程操作一个资源类 Ticket ticket = new Ticket(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 40; i++) { ticket.sale(); } },\u0026#34;A\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 40; i++) { ticket.sale(); } },\u0026#34;B\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 40; i++) { ticket.sale(); } },\u0026#34;C\u0026#34;).start(); } } /** * 资源类 oop */ class Ticket{ /** * 属性、方法 */ private int number = 50; /** * 卖票的方式 * synchronized 本质：队列，锁 */ public synchronized void sale(){ if(number \u0026gt; 0){ System.out.println(Thread.currentThread().getName() + \u0026#34;卖出了\u0026#34; + (number--) + \u0026#34;票，剩余：\u0026#34; + number); } } } Lock\n公平锁: 十分公平\n非公平锁: 十分不公平,可以插队(默认)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class SaleTicketDemo02 { public static void main(String[] args) { //并发：多个线程操作一个资源类 Ticket2 ticket = new Ticket2(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 40; i++) { ticket.sale(); } },\u0026#34;A\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 40; i++) { ticket.sale(); } },\u0026#34;B\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 40; i++) { ticket.sale(); } },\u0026#34;C\u0026#34;).start(); } } /** * 资源类 oop */ class Ticket2{ /** * 属性、方法 */ private int number = 50; Lock lock = new ReentrantLock(); /** * 卖票的方式 * synchronized 本质：队列，锁 */ public void sale(){ //加锁 lock.lock(); try { //业务代码 if(number \u0026gt; 0){ System.out.println(Thread.currentThread().getName() + \u0026#34;卖出了\u0026#34; + (number--) + \u0026#34;票，剩余：\u0026#34; + number); } }catch (Exception e){ e.printStackTrace(); }finally { //解锁 lock.unlock(); } } } Synchronized 和Lock区别\nSynchronized 内置的Java关键字,Lock是一个Java类\nSynchronized 无法判断锁的状态,Lock可以判断是否获取到锁\nSynchronized 会自动释放锁,Lock必须要手动释放锁! 如果不释放会死锁\nSynchronized线程1(获得锁、阻塞)、线程2（等待，傻傻的等待）；Lock锁就不一定会等待下去\nSynchronized 可重入锁，不可以中断，非公平的；lock，可重入锁，可以判断锁，非公平（可以自己设置）\nSynchronized 适合锁少量的代码同步问题，Lock适合锁大量的同步方法\n4、生产者和消费者问题 生产者和消费者问题 Synchronized版\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class A { public static void main(String[] args) { Data data = new Data(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { try { data.increment(); }catch (InterruptedException e){ e.printStackTrace(); } } },\u0026#34;A\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },\u0026#34;B\u0026#34;).start(); } } /** * 数字，资源类 * 等待、业务、通知 */ class Data{ private int number = 0; /** * +1 */ public synchronized void increment() throws InterruptedException{ if(number != 0){ //等待 this.wait(); } number ++; //通知其他线程，我+1完成了 System.out.println(Thread.currentThread().getName() + \u0026#34;=\u0026gt;\u0026#34; + number); this.notifyAll(); } /** * -1 */ public synchronized void decrement() throws InterruptedException { if(number == 0){ //等待 this.wait(); } number --; //通知其他线程，我-1完成了 System.out.println(Thread.currentThread().getName() + \u0026#34;=\u0026gt;\u0026#34; + number); this.notifyAll(); } } 存在a,b,c,d四个线程！虚假唤醒问题\nif 改为 while\nJUC版的生产者和消费者\n通过lock可以找到condition\n代码实现\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public class B { public static void main(String[] args) { Data2 data2 = new Data2(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { try { data2.increment(); }catch (InterruptedException e){ e.printStackTrace(); } } },\u0026#34;A\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { try { data2.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },\u0026#34;B\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { try { data2.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },\u0026#34;C\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { try { data2.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },\u0026#34;D\u0026#34;).start(); } } class Data2{ private int number = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); /** * +1 */ public void increment() throws InterruptedException{ lock.lock(); try { while (number != 0){ //等待 condition.await(); } number ++; //通知其他线程，我+1完成了 System.out.println(Thread.currentThread().getName() + \u0026#34;=\u0026gt;\u0026#34; + number); condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } /** * -1 */ public void decrement() throws InterruptedException { lock.lock(); try { while (number == 0){ //等待 condition.await(); } number --; //通知其他线程，我-1完成了 System.out.println(Thread.currentThread().getName() + \u0026#34;=\u0026gt;\u0026#34; + number); condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } } 任何一个新的技术，绝对不是仅仅只是覆盖了原来的技术，优势和补充\nCondition精准的通知\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public class C { public static void main(String[] args) { Data3 data = new Data3(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { data.printA(); } },\u0026#34;A\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { data.printB(); } },\u0026#34;B\u0026#34;).start(); new Thread(() -\u0026gt; { for (int i = 0; i \u0026lt; 10; i++) { data.printC(); } },\u0026#34;C\u0026#34;).start(); } } class Data3{ private final Lock lock = new ReentrantLock(); private final Condition condition1 = lock.newCondition(); private final Condition condition2 = lock.newCondition(); private final Condition condition3 = lock.newCondition(); private int number = 1; public void printA(){ lock.lock(); try { while (number != 1){ //等待 condition1.await(); } System.out.println(Thread.currentThread().getName() + \u0026#34;=\u0026gt;AAA\u0026#34;); number = 2; condition2.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void printB(){ lock.lock(); try { while (number != 2){ condition2.await(); } System.out.println(Thread.currentThread().getName() + \u0026#34;=\u0026gt;BBB\u0026#34;); number = 3; condition3.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void printC(){ lock.lock(); try { while (number != 3){ condition3.await(); } System.out.println(Thread.currentThread().getName() + \u0026#34;=\u0026gt;CCC\u0026#34;); number = 1; condition1.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } //生产线： 下单-》支付-》交易-》物流 } 5、8锁现象 如何判断锁是谁！永远的知道，什么是锁，锁到底锁的是谁？\n深刻理解锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 /** * @author: lyj * @date: 2022/7/30 14:21 * 8锁，就是关于锁的8个问题 * 1、标准情况下：两个线程先打印 发短信还是 打电话？ * 2.发短信延迟4秒，两个线程是先打印发短信还是打电话 发短信 */ public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -\u0026gt;{ phone.sendSms(); },\u0026#34;B\u0026#34;).start(); try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } new Thread(() -\u0026gt;{ phone.call(); },\u0026#34;B\u0026#34;).start(); } } class Phone{ /** * synchronize 锁的对象是方法的调用者 * 两个方法，谁先拿到谁先执行 */ public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(\u0026#34;sendSms\u0026#34;); } public synchronized void call(){ System.out.println(\u0026#34;call\u0026#34;); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Test2 { public static void main(String[] args) { Phone1 phone = new Phone1(); Phone1 phone1 = new Phone1(); new Thread(() -\u0026gt;{ phone.sendSms(); },\u0026#34;B\u0026#34;).start(); try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } new Thread(() -\u0026gt;{ phone1.call(); },\u0026#34;B\u0026#34;).start(); } } class Phone1{ /** * synchronize 锁的对象是方法的调用者 */ public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(\u0026#34;sendSms\u0026#34;); } public synchronized void call(){ System.out.println(\u0026#34;call\u0026#34;); } //没有锁 ，不受锁的影响 public void hello(){ System.out.println(\u0026#34;hello\u0026#34;); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Test3 { public static void main(String[] args) { Phone2 phone = new Phone2(); Phone2 phone1 = new Phone2(); new Thread(() -\u0026gt;{ phone.sendSms(); },\u0026#34;A\u0026#34;).start(); try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } new Thread(() -\u0026gt;{ phone1.call(); },\u0026#34;B\u0026#34;).start(); } } class Phone2 { //static静态方法 //类一加载就有了，锁的是class public static synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\u0026#34;sendSms\u0026#34;); } public static synchronized void call() { System.out.println(\u0026#34;call\u0026#34;); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 /** * @author: lyj * @date: 2022/7/30 14:46 * 7.1个静态同步方法，1个普通同步方法，一个对象，先打印发短信还是打电话 * 8.两个对象是先打印打电话还是发短信 */ public class Test4 { public static void main(String[] args) { Phone3 phone = new Phone3(); Phone3 phone1 = new Phone3(); new Thread(() -\u0026gt;{ phone.sendSms(); },\u0026#34;A\u0026#34;).start(); try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } new Thread(() -\u0026gt;{ phone1.call(); },\u0026#34;B\u0026#34;).start(); } } class Phone3 { //static静态方法 //类一加载就有了，锁的是class public static synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\u0026#34;sendSms\u0026#34;); } public synchronized void call() { System.out.println(\u0026#34;call\u0026#34;); } } 总结\nnew this 具体的一个手机\nstatic class为唯一的一个模板\n6、集合类不安全 List不安全\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ListTest { public static void main(String[] args) { /** * 并发下ArrayList不安全 * 解决方法： * 1.List\u0026lt;String\u0026gt; list = new Vector\u0026lt;\u0026gt;(); * 2.List\u0026lt;String\u0026gt; list = Collections.synchronizedList(new ArrayList\u0026lt;\u0026gt;()); * 3.List\u0026lt;String\u0026gt; list = new CopyOnWriteArrayList\u0026lt;\u0026gt;(); */ //CopyOnWrite 写入是复制， cow 计算机程序设计领域的一种优化策略 //多个线程调用的时候，list，读取的时候，固定的，写入（覆盖） //在写入的时候避免覆盖，造成数据问题 List\u0026lt;String\u0026gt; list = new CopyOnWriteArrayList\u0026lt;\u0026gt;(); for (int i = 0; i \u0026lt;= 100; i++) { new Thread(() -\u0026gt;{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } } Set不安全\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * @author: lyj * @date: 2022/7/30 15:11 * Exception in thread \u0026#34;1\u0026#34; java.util.ConcurrentModificationException */ public class SetTest { public static void main(String[] args) { // Set\u0026lt;String\u0026gt; set = new HashSet\u0026lt;\u0026gt;(); // Set\u0026lt;String\u0026gt; set = Collections.synchronizedSet(new HashSet\u0026lt;\u0026gt;()); Set\u0026lt;String\u0026gt; set = new CopyOnWriteArraySet\u0026lt;\u0026gt;(); for (int i = 0; i \u0026lt; 30; i++) { new Thread(() -\u0026gt;{ set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } } } hashset底层\n1 2 3 4 5 6 7 8 9 public HashSet() { map = new HashMap\u0026lt;\u0026gt;(); } // add set本质就是map key是无法重复的 public boolean add(E e) { return map.put(e, PRESENT)==null; } private static final Object PRESENT = new Object(); //不变的常量 HashMap不安全\n7、Callable（简单） 1、可以有返回值\n2、可以抛出异常\n3、方法不同,run()/call();\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { new Thread().start(); MyThread myThread = new MyThread(); //适配器 FutureTask futureTask = new FutureTask(myThread); new Thread(futureTask,\u0026#34;A\u0026#34;).start(); //获取Callable的返回值 Integer o = (Integer) futureTask.get(); System.out.println(o); } } class MyThread implements Callable\u0026lt;Integer\u0026gt; { @Override public Integer call() throws Exception { System.out.println(\u0026#34;call()\u0026#34;); return 1024; } } 8、常用的辅助类 8.1、CountDownLatch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class CountDownLatcheDemo { public static void main(String[] args) throws InterruptedException { //总数6 CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i \u0026lt;= 6; i++) { new Thread(() -\u0026gt;{ System.out.println(Thread.currentThread().getName() + \u0026#34;go out\u0026#34;); countDownLatch.countDown(); },String.valueOf(i)).start(); } //等待计数器归零 countDownLatch.await(); System.out.println(\u0026#34;close Door\u0026#34;); } } countDownLatch.countDown()//数量-1\ncountDownLatch.await()//等待计数器归零，然后在向下执行\n每次有线程调用countDownLatch.countDown()减一，等归零之后会唤醒await()\n8.2、CycliBarrier 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class CyclicBarrierDemo { public static void main(String[] args) { /** * 集齐7个龙珠召唤神龙 */ //召唤龙珠的线程 CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -\u0026gt;{ System.out.println(\u0026#34;召唤神龙成功\u0026#34;); }); for (int i = 0; i \u0026lt; 7; i++) { final int temp = i; new Thread(() -\u0026gt;{ System.out.println(Thread.currentThread().getName()+ \u0026#34;收集\u0026#34; + temp + \u0026#34;龙珠\u0026#34;); try { //等待 cyclicBarrier.await(); }catch (InterruptedException e){ e.printStackTrace(); }catch (BrokenBarrierException e){ e.printStackTrace(); } },String.valueOf(i)).start(); } } } 8.3、Semaphore Semaphore: 信号量\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class SemaphoreTest { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3); for (int i = 0; i \u0026lt; 6; i++) { new Thread(() -\u0026gt;{ //acquire 得到 try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + \u0026#34;抢到车位\u0026#34;); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName() + \u0026#34;离开车位\u0026#34;); }catch (InterruptedException e){ e.printStackTrace(); }finally { semaphore.release(); } //release 释放 },String.valueOf(i)).start(); } } } semaphore.acquire()获得，假设如果已经满了，等待，等待被释放为止\nsemaphore.release()释放，会将当前的信号量释放+1，然后唤醒等待的线程\n9、读写锁 ReadWriteLock\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 /** * @author: lyj * @date: 2022/7/30 16:01 * ReadWriteLock * 读-读，可以共存 * 读-写，不能共存 * 写-写，不能共存 */ public class ReadWriteLockTest { public static void main(String[] args) { MyCacheLock myCache = new MyCacheLock(); for (int i = 1; i \u0026lt;= 5; i++) { final int temp = i; new Thread(() -\u0026gt;{ myCache.put(temp+\u0026#34;\u0026#34;,temp+\u0026#34;\u0026#34;); },String.valueOf(i)).start(); } for (int i = 1; i \u0026lt;= 5; i++) { final int temp = i; new Thread(() -\u0026gt;{ myCache.get(temp+\u0026#34;\u0026#34;); },String.valueOf(i)).start(); } } } /** * 自定义缓存 */ class MyCache{ private volatile Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(16); //存，写 public void put(String key,Object value){ System.out.println(Thread.currentThread().getName() + \u0026#34;写入\u0026#34; + key); map.put(key,value); System.out.println(Thread.currentThread().getName() + \u0026#34;写入ok\u0026#34;); } //取，读 public void get(String key){ System.out.println(Thread.currentThread().getName() + \u0026#34;读取\u0026#34; + key); Object obj = map.get(key); System.out.println(Thread.currentThread().getName() + \u0026#34;读取ok\u0026#34;); } } class MyCacheLock{ private volatile Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(16); //读写锁，更加细粒度的操作 private ReadWriteLock lock = new ReentrantReadWriteLock(); //存，写入的时候，只希望同时只有一个线程写 public void put(String key,Object value){ lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + \u0026#34;写入\u0026#34; + key); map.put(key,value); System.out.println(Thread.currentThread().getName() + \u0026#34;写入ok\u0026#34;); }catch (Exception e){ e.printStackTrace(); }finally { lock.writeLock().unlock(); } } //取，读，所有人都可以读 public void get(String key){ lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + \u0026#34;读取\u0026#34; + key); Object obj = map.get(key); System.out.println(Thread.currentThread().getName() + \u0026#34;读取ok\u0026#34;); } catch (Exception e) { e.printStackTrace(); }finally { lock.readLock().unlock(); } } } 10、阻塞队列 **什么情况下会使用阻塞队列：**多线程并发处理，线程池\n使用队列\n添加、移除\n四组API 方式 抛出异常 有返回值，不抛异常 阻塞等待 超时等待 添加 add offer put offer 移除 remove poll take poll 判断队列首 element peek 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * 抛出异常 */ public static void test1(){ //队列的大小 ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3); System.out.println(arrayBlockingQueue.add(\u0026#34;a\u0026#34;)); System.out.println(arrayBlockingQueue.add(\u0026#34;b\u0026#34;)); System.out.println(arrayBlockingQueue.add(\u0026#34;c\u0026#34;)); //Exception in thread \u0026#34;main\u0026#34; java.lang.IllegalStateException: Queue full //System.out.println(arrayBlockingQueue.add(\u0026#34;d\u0026#34;)); System.out.println(arrayBlockingQueue.remove()); System.out.println(arrayBlockingQueue.remove()); System.out.println(arrayBlockingQueue.remove()); //Exception in thread \u0026#34;main\u0026#34; java.util.NoSuchElementException //System.out.println(arrayBlockingQueue.remove()); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * 不抛异常 */ public static void test2(){ ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3); System.out.println(blockingQueue.offer(\u0026#34;a\u0026#34;)); System.out.println(blockingQueue.offer(\u0026#34;b\u0026#34;)); System.out.println(blockingQueue.offer(\u0026#34;c\u0026#34;)); //false //System.out.println(blockingQueue.offer(\u0026#34;c\u0026#34;)); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); //null //System.out.println(blockingQueue.poll()); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * 等待，阻塞（一直阻塞） */ public static void test3() throws InterruptedException { ArrayBlockingQueue\u0026lt;String\u0026gt; blockingQueue = new ArrayBlockingQueue\u0026lt;\u0026gt;(3); blockingQueue.put(\u0026#34;a\u0026#34;); blockingQueue.put(\u0026#34;b\u0026#34;); blockingQueue.put(\u0026#34;c\u0026#34;); //队列没有位置了，一直阻塞 //blockingQueue.put(\u0026#34;d\u0026#34;); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * 等待，阻塞（等待超时） */ public static void test4() throws InterruptedException{ ArrayBlockingQueue\u0026lt;String\u0026gt; blockingQueue = new ArrayBlockingQueue\u0026lt;\u0026gt;(3); blockingQueue.offer(\u0026#34;a\u0026#34;); blockingQueue.offer(\u0026#34;b\u0026#34;); blockingQueue.offer(\u0026#34;c\u0026#34;); blockingQueue.offer(\u0026#34;d\u0026#34;,2, TimeUnit.SECONDS); System.out.println(\u0026#34;========\u0026#34;); blockingQueue.poll(); blockingQueue.poll(); blockingQueue.poll(); blockingQueue.poll(2,TimeUnit.SECONDS); } SynchronousQueue同步队列\n没有容量，进去一个元素，必须等待取出来之后，才能再里面放一个元素\nput 、take\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 /** * @author: lyj * @date: 2022/7/30 19:38 * 同步队列和其他的BlockingQueue不一样 */ public class SynchronousQueueTest { public static void main(String[] args) { //同步队列 BlockingQueue\u0026lt;String\u0026gt; queue = new SynchronousQueue\u0026lt;\u0026gt;(); new Thread(() -\u0026gt;{ try { System.out.println(Thread.currentThread().getName() + \u0026#34;put 1\u0026#34;); queue.put(\u0026#34;1\u0026#34;); System.out.println(Thread.currentThread().getName() + \u0026#34;put 2\u0026#34;); queue.put(\u0026#34;2\u0026#34;); System.out.println(Thread.currentThread().getName() + \u0026#34;put 3\u0026#34;); queue.put(\u0026#34;3\u0026#34;); } catch (InterruptedException e) { e.printStackTrace(); } },\u0026#34;T1\u0026#34;).start(); new Thread(() -\u0026gt;{ try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + queue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + queue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + queue.take()); } catch (InterruptedException e) { e.printStackTrace(); } },\u0026#34;T2\u0026#34;).start(); } } 11、线程池（重点） 线程池：三大方法、7大参数、4钟拒绝策略\n池化技术\n程序的运行，本质：占用系统的资源！优化资源的使用！ =》池化技术\n线程池、连接池、内存池、对象池……\n池化技术：事先准备好一些资源，有人要用，就来我这里拿，用完之后返还\n线程池的好处：\n1、降低资源的消耗\n2、提高相应的速度\n3、方便管理\n线程复用，可以控制最大并发数，管理线程\n三大方法\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /** * @author: lyj * @date: 2022/7/30 19:54 * Executors工具，3大方法 * 使用了线程池，使用线程池来创建线程 */ public class Demo01 { public static void main(String[] args) { //单个线程 //ExecutorService pool = Executors.newSingleThreadExecutor(); //创建一个固定线程池的大小 //ExecutorService pool = Executors.newFixedThreadPool(5); //可伸缩的，遇强则强，遇弱则弱 ExecutorService pool = Executors.newCachedThreadPool(); try { for (int i = 0; i \u0026lt; 10; i++) { pool.execute(() -\u0026gt;{ System.out.println(Thread.currentThread().getName() + \u0026#34; ok\u0026#34;); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池用完，程序结束，关闭线程池 pool.shutdown(); } } } 7大参数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 //源码分析 public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue\u0026lt;Runnable\u0026gt;())); } public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue\u0026lt;Runnable\u0026gt;()); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue\u0026lt;Runnable\u0026gt;()); } //本质：ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize,//核心线程池大小 int maximumPoolSize,//最大核心线程大小 long keepAliveTime,//超时了没有人调用就会释放 TimeUnit unit,//超时单位 BlockingQueue\u0026lt;Runnable\u0026gt; workQueue,//阻塞队列 ThreadFactory threadFactory,//线程工厂，创建线程的，一般不用动 RejectedExecutionHandler handler) {//拒绝策略 if (corePoolSize \u0026lt; 0 || maximumPoolSize \u0026lt;= 0 || maximumPoolSize \u0026lt; corePoolSize || keepAliveTime \u0026lt; 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; } 手动创建线程池\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Demo02 { public static void main(String[] args) { //银行满了，还有人进来，不处理这个人，抛出异常 ExecutorService pool = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque\u0026lt;\u0026gt;(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); try { //最大承载数，Dequeue + max for (int i = 0; i \u0026lt; 9; i++) { pool.execute(() -\u0026gt;{ System.out.println(Thread.currentThread().getName() + \u0026#34; ok\u0026#34;); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池用完，程序结束，关闭线程池 pool.shutdown(); } } } 四种拒绝策略\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /** * @author: lyj * @date: 2022/7/31 10:26 * 七大参数 * 四大拒绝策略 * new ThreadPoolExecutor.AbortPolicy() 银行满了，还有人进来，不处理这个人，抛出异常 * new ThreadPoolExecutor.CallerRunsPolicy() 哪来的去哪里 * new ThreadPoolExecutor.DiscardPolicy() 队列满了，丢掉任务，不会抛出异常， * new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了，尝试去和最早的竞争，也不会抛出异常 * */ public class Demo02 { public static void main(String[] args) { //银行满了，还有人进来，不处理这个人，抛出异常 ExecutorService pool = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque\u0026lt;\u0026gt;(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); try { //最大承载数，Dequeue + max for (int i = 0; i \u0026lt; 9; i++) { pool.execute(() -\u0026gt;{ System.out.println(Thread.currentThread().getName() + \u0026#34; ok\u0026#34;); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池用完，程序结束，关闭线程池 pool.shutdown(); } } } 了解：\n1 2 3 4 //最大线程池到底如何定义 //1.cup密集型，几核就是几，可以保持cup的效率最高 //2.IO密集型，判断你程序中十分耗IO的线程 //程序 15个大型任务，io十分占用资源 12、ForkJoin 什么是ForkJoin\nForkJoin在jdk1.7，并行执行任务！提高效率，大数据量！\n大数据：Map Reduce（把大任务拆分成小人物）\nForkJoin特点：工作窃取（一个线程执行完了，就会帮助未完成的线程去完成任务）\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 //测试 public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { //6065 //test1(); //4223 //test2(); //229 test3(); } public static void test1(){ Long sum = 0L; long start = System.currentTimeMillis(); for (Long i = 1L; i \u0026lt;= 10_0000_0000L ; i++) { sum += i; } long end = System.currentTimeMillis(); System.out.println(\u0026#34;sum=\u0026#34; + sum + \u0026#34; 时间：\u0026#34; + (end-start)); } public static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask\u0026lt;Long\u0026gt; task = new ForkJoinDemo(0L, 10_0000_0000L); //提交任务 ForkJoinTask\u0026lt;Long\u0026gt; submit = forkJoinPool.submit(task); long sum = submit.get(); long end = System.currentTimeMillis(); System.out.println(\u0026#34;sum=\u0026#34; + sum + \u0026#34; 时间：\u0026#34; + (end-start)); } public static void test3(){ long start = System.currentTimeMillis(); //stream并行流 long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0,Long::sum); long end = System.currentTimeMillis(); System.out.println(\u0026#34;sum=\u0026#34; + sum + \u0026#34; 时间：\u0026#34; + (end-start)); } } //forkjoin public class ForkJoinDemo extends RecursiveTask\u0026lt;Long\u0026gt; { private Long start; private Long end; /** * 临界值 */ private Long temp = 10000L; public ForkJoinDemo(Long start, Long end) { this.start = start; this.end = end; } /** * 计算方法 * @return */ @Override protected Long compute() { if((end-start) \u0026lt; temp){ Long sum = 0L; for (Long i = start; i \u0026lt;= end; i++) { sum += i; } return sum; }else{ //中间值 long middle = (start + end) /2; ForkJoinDemo task1 = new ForkJoinDemo(start,middle); //拆分任务，把任务压入线程队列 task1.fork(); ForkJoinDemo task2 = new ForkJoinDemo(middle+1,end); task2.fork(); return task1.join() + task2.join(); } } } 13、异步回调 Future设计的初衷：对将来的某个事件的结果进行建模\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Demo01 { public static void main(String[] args) throws ExecutionException, InterruptedException { //发起一个请求 //没有返回值的runAsync异步回调 /* CompletableFuture\u0026lt;Void\u0026gt; completableFuture = CompletableFuture.runAsync(() -\u0026gt; { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + \u0026#34;runAsync\u0026#34;); }); System.out.println(\u0026#34;==============\u0026#34;); //获取阻塞执行结果 completableFuture.get(); */ /** * 有返回值的supplyAsync 异步回调 */ CompletableFuture\u0026lt;Integer\u0026gt; completableFuture = CompletableFuture.supplyAsync(() -\u0026gt;{ System.out.println(Thread.currentThread().getName() + \u0026#34;supplyAsync\u0026#34;); int i = 1/0; return 1024; }); completableFuture.whenComplete((t,u) -\u0026gt; { System.out.println(\u0026#34;t = \u0026#34; + t); System.out.println(\u0026#34;u = \u0026#34; + u); }).exceptionally((e) -\u0026gt;{ System.out.println(e.getMessage()); return 2333; }).get(); } } 14、JMM 什么是JMM\nJMM：Java内存模型，不存在的东西，概念！约定！\n关于JMM的一些同步的约定\n1、线程解锁前，必须把共享变量立刻刷回主存\n2、线程加锁前，必须读取主存中的最新值到工作内存中\n3、加锁和解锁是同一把锁\n问题：程序不知道线程中的值已经被修改过来\n15、Volatile Volatile的理解\nVolatile是Java虚拟机提供的轻量的同步机制\n1、保证可见性\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class JMMDemo { /** * 不加volatile程序就会死循环， * 加了volatile可以保证可见性 */ private volatile static int num = 0; public static void main(String[] args) { new Thread(() -\u0026gt;{ while (num == 0){ } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } num = 1; System.out.println(num); } } 2、不保证原子性\n原子性：不可分割\n线程A在执行任务的时候，不能被打扰，也不能被分割。要么同时成功，要么同时失败\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /** * @author: lyj * @date: 2022/7/31 15:37 * 不保证原子性 */ public class VDemo01 { private volatile static int num = 0; public static void add(){ num++; } //理论上是两万 public static void main(String[] args) { for (int i = 0; i \u0026lt; 20; i++) { new Thread(() -\u0026gt; { for (int j = 0; j \u0026lt; 1000; j++) { add(); } }).start(); } while (Thread.activeCount() \u0026gt; 2){ Thread.yield(); } System.out.println(Thread.currentThread().getName() + num); } } 如果不加lock和synchronize，怎么保证原子性\n使用源自类，解决原子性问题\n原子类为什么这么高级\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /** * @author: lyj * @date: 2022/7/31 15:37 * 不保证原子性 */ public class VDemo01 { private volatile static AtomicInteger num = new AtomicInteger(); public static void add(){ //num++; num.getAndIncrement(); } //理论上是两万 public static void main(String[] args) { for (int i = 0; i \u0026lt; 20; i++) { new Thread(() -\u0026gt; { for (int j = 0; j \u0026lt; 1000; j++) { add(); } }).start(); } while (Thread.activeCount() \u0026gt; 2){ Thread.yield(); } System.out.println(Thread.currentThread().getName() + num); } } 3、禁止指令重排\n什么是指令重排：你写的程序，计算机并不是按照你写的那样去执行的\n源代码 \u0026mdash;\u0026gt; 编译器优化的重排 \u0026mdash;\u0026gt; 指令并行也能重排 \u0026mdash;\u0026gt; 内存系统也可能重排 \u0026mdash;\u0026gt; 执行\n1 2 3 4 5 int x = 1;//1 int y = 2;//2 x = x +5;//3 y = x * x;//4 我们所期望的: 1234 但是执行的时候可能是： 2134 1324 volatile可以避免指令重排\n内存屏障。CPU指令。作用：\n1、保证特定的操作的执行顺序\n2、可以保证某些变量的内存可见性（利用这些特性volatile实现了可见性）\n16、彻底玩转单例模式 饿汉式 DCL懒汉式\n饿汉式\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /** * @author: lyj * @date: 2022/7/31 15:59 * 饿汉式单例 */ public class Hungry { /** * 可能会浪费空间 */ private byte[] data1 = new byte[1024 * 1024]; private byte[] data2 = new byte[1024 * 1024]; private byte[] data3 = new byte[1024 * 1024]; private byte[] data4 = new byte[1024 * 1024]; private Hungry(){} public static final Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } } DCL懒汉式\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class LazyMan { private static boolean flag = false; private LazyMan(){ synchronized (LazyMan.class){ if(!flag){ flag = true; }else{ throw new RuntimeException(\u0026#34;不要去使用反射破坏\u0026#34;); } } System.out.println(\u0026#34;Thread.currentThread().getName() = \u0026#34; + Thread.currentThread().getName()); } private volatile static LazyMan lazyMan; public static LazyMan getInstance(){ if (lazyMan == null) { synchronized (LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); /** * 1。分配内存空间 * 2.执行构造方法，初始化对象 * 3.把这个对象指向这个空间 */ } } } return lazyMan; } /** * 多线程并发 */ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { // for (int i = 0; i \u0026lt; 10; i++) { // new Thread(() -\u0026gt;{ // LazyMan.getInstance(); // }).start(); // } //反射 // LazyMan instance = LazyMan.getInstance(); Field flag = LazyMan.class.getDeclaredField(\u0026#34;flag\u0026#34;); flag.setAccessible(true); Constructor\u0026lt;LazyMan\u0026gt; declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan instance2 = declaredConstructor.newInstance(); flag.set(instance2,false); LazyMan instance3 = declaredConstructor.newInstance(); System.out.println(instance3); System.out.println(instance2); } } 静态内部类\n1 2 3 4 5 6 7 8 9 10 11 12 public class Holder { private Holder(){} public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER = new Holder(); } } enum类\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /** * @author: lyj * @date: 2022/7/31 16:22 * enum是什么？ 本身也是一个class * */ public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ //NoSuchMethodException: com.lyj.demo01.singer.EnumSingle.\u0026lt;init\u0026gt;() public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle enumSingle = EnumSingle.INSTANCE; Constructor\u0026lt;EnumSingle\u0026gt; declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance = declaredConstructor.newInstance(); System.out.println(enumSingle); System.out.println(instance); } } 17、深入理解CAS 什么是CAS\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); /** * 期望、更新 * 如果我期望的值达到了，那么就更新，否则，就不更新 CAS是CPU的并发器 */ System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } } CAS：比较当前工作内存中的值和主内存的值，如果这个值是期望的，那么则执行操作！如果不是一直循环\n缺点：\n1、循环会耗时\n2、一次性只能保证一个共享变量的原子性\n3、ABA问题\nCAS: ABA问题（狸猫换太子）\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); /** * 期望、更新 * 如果我期望的值达到了，那么就更新，否则，就不更新 CAS是CPU的并发器 */ //=======捣乱的线程========= System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); //=======捣乱的线程========= System.out.println(atomicInteger.compareAndSet(2020, 6666)); System.out.println(atomicInteger.get()); } } 18、原子引用 带版本号的原子引用\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 //解决ABA问题 public class CASDemo { public static void main(String[] args) { //AtomicInteger atomicInteger = new AtomicInteger(2020); //如果泛型是包装类，注意引用问题 AtomicStampedReference\u0026lt;Integer\u0026gt; atomicInteger = new AtomicStampedReference\u0026lt;\u0026gt;(1,1); new Thread(() -\u0026gt;{ //获得版本号 int stamp = atomicInteger.getStamp(); System.out.println(\u0026#34;a1=\u0026#34; + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp() + 1)); System.out.println(\u0026#34;a2=\u0026#34; + atomicInteger.getStamp()); System.out.println(atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp() + 1)); System.out.println(\u0026#34;a3=\u0026#34; + atomicInteger.getStamp()); },\u0026#34;a\u0026#34;).start(); new Thread(() -\u0026gt;{ //获得版本号 int stamp = atomicInteger.getStamp(); System.out.println(\u0026#34;b1=\u0026#34; + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicInteger.compareAndSet(1, 6, atomicInteger.getStamp(), atomicInteger.getStamp() + 1)); System.out.println(\u0026#34;b2=\u0026#34; + atomicInteger.getStamp()); },\u0026#34;b\u0026#34;).start(); /** * 期望、更新 * 如果我期望的值达到了，那么就更新，否则，就不更新 CAS是CPU的并发器 */ //=======捣乱的线程========= /* System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); //=======捣乱的线程========= System.out.println(atomicInteger.compareAndSet(2020, 6666)); System.out.println(atomicInteger.get());*/ } } 19、各种锁的理解 19.1 公平锁、非公平锁 公平锁：非常公平，不能插队\n非公平锁：非常不公平，可以插队(默认都是非公平锁)\n1 2 3 4 5 6 7 8 public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } 19.2 可重入锁 可重入锁(递归锁)\nSynchronize\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Demo01 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -\u0026gt;{ phone.sms(); },\u0026#34;A\u0026#34;).start(); new Thread(() -\u0026gt;{ phone.sms(); },\u0026#34;B\u0026#34;).start(); } } class Phone{ public synchronized void sms(){ System.out.println(Thread.currentThread().getName() + \u0026#34;sms\u0026#34;); call(); } public synchronized void call(){ System.out.println(Thread.currentThread().getName() + \u0026#34;call\u0026#34;); } } lock\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Dmeo02 { public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(() -\u0026gt;{ phone.sms(); },\u0026#34;A\u0026#34;).start(); new Thread(() -\u0026gt;{ phone.sms(); },\u0026#34;B\u0026#34;).start(); } } class Phone2{ Lock lock = new ReentrantLock(); public void sms(){ //锁必须配对不然会死锁 lock.lock(); lock.lock(); try { System.out.println(Thread.currentThread().getName() + \u0026#34;sms\u0026#34;); call(); } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } public void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + \u0026#34;call\u0026#34;); } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } } 19.3 自旋锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 //自定义自旋锁 public class SpinLockDemo { AtomicReference\u0026lt;Thread\u0026gt; atomicReference = new AtomicReference\u0026lt;\u0026gt;(); /** * 加锁 */ public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + \u0026#34;===\u0026gt; myLock\u0026#34;); while (!atomicReference.compareAndSet(null,thread)){} } /** * 加锁 */ public void myUnLock(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + \u0026#34;===\u0026gt; myUnLock\u0026#34;); atomicReference.compareAndSet(thread,null); } } //测试 public class TestSpinLock { public static void main(String[] args) throws InterruptedException { // ReentrantLock reentrantLock = new ReentrantLock(); // reentrantLock.lock(); // reentrantLock.unlock(); /** * 底层使用的自旋锁CAS */ SpinLockDemo lock = new SpinLockDemo(); new Thread(() -\u0026gt;{ lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.myUnLock(); } },\u0026#34;t1\u0026#34;).start(); TimeUnit.SECONDS.sleep(1); new Thread(() -\u0026gt;{ lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.myUnLock(); } },\u0026#34;t2\u0026#34;).start(); lock.myUnLock(); } } 19.4 死锁 死锁测试\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MyThread implements Runnable{ private final String lockA; private final String lockB; public MyThread(String lockA,String lockB){ this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName() + \u0026#34;lock: \u0026#34; + lockA + \u0026#34;get =\u0026gt;\u0026#34; + lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName() + \u0026#34;lock: \u0026#34; + lockB + \u0026#34;get =\u0026gt;\u0026#34; + lockA); } } } } 1、使用jps -l定位进程号\n2、使用jstack 进程号找到死锁\n","permalink":"https://lyj404.github.io/blog/post/java-juc-description/","summary":"\u003ch1 id=\"juc\"\u003eJUC\u003c/h1\u003e\n\u003ch2 id=\"1什么是juc\"\u003e1、什么是JUC\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ejava.util.concurrent\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ejava.util.concurrent.atomic\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ejava.util.concurrent.locks\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003ejava.util 工具包\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e业务：普通的线程代码Thread\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eRunnable\u003c/strong\u003e  没有返回值，效率相比Callable较低\u003c/p\u003e\n\u003ch2 id=\"2线程和进程\"\u003e2、线程和进程\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e线程、进程\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e进程：一个程序，QQ.ext Music.exe 程序的集合\u003c/p\u003e","title":"JUC"},{"content":"Docker概述 Docker为什么出现？ 一款产品：开发-上线 两套环境！应用环境！应用配置！\n开发 ··· 运维。问题：我的电脑用运行！版本更新，导致服务不可用！对于运维来说，考研太大了\n环境配置是十分麻烦，每一个机器都要部署环境(集群redis,es、Hadoop)！费时费力\n发布一个项目(jar + Redis Mysql + jdk ES),项目能不能都带上环境安装打包\nDocker给以上的问题，提出了解决方案\nDocker能做什么： 虚拟机技术缺点：\n资源占用十分多 冗余步骤多 启动很慢 容器化技术\n容器化技术不是模拟一个完整的系统\n比较docker和虚拟机技术的不同：\n传统虚拟机，虚拟处一条硬件，运行一个完整的操作系统，然后在这个系统上安装软件 容器内的应用直接运行在宿主机的内，容器没有自己的内核，也没有虚拟我们的硬件，所以轻便了 每一个容器是相互隔离的，每个容器内都有一个数据自己的文件系统，互不影响 DevOps（开发、运维）\n应用更快速的交付和部署:\n传统：一堆帮助文档，安装程序\nDocker:打包镜像发布测试，一键运行\n更便捷的升级和扩缩容:\n使用了Docker之后，我们部署应用就像搭积木一样！\n项目打包为一个镜像，扩展服务器A!服务器B！\n更简单的系统运维:\n在容器化之后，我的开发，测试环境都是高度一致的\n更高效的计算机资源利用:\nDocker是内核级别的虚拟化，可以再一个物理机上运行很多的容器实例！服务器性能可以压榨到极致\nDocker安装 Docker基本组成 镜像(image)：\ndocker镜像就好比一个模板，可以通过这个模板来创建容器服务，tomcat镜像==\u0026gt;run==\u0026gt;tomcat容器(提供服务器)\n容器(container)：\ndocker利用容器技术，独立运行一个或者一组应用，通过镜像来创建。启动，停止，删除，基本命令！\n目前可以把这个容器理解为就是一个简易的linux系统\n仓库(repository)：\n仓库就是存放镜像的地方\n仓库分为公有和私有\nDocker Hub（默认是国外的）\n阿里云……都有容器服务器(配置镜像加速)\n安装Docker 环境准备\n1.需要会一点点linux的基础\n2.CentOs7\n3.连接服务器\n环境查看\n查看系统内核版本\n1 2 3 # 系统内核是3.10以上 [root@localhost /]# uname -r 3.10.0-1160.el7.x86_64 系统版本\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 NAME=\u0026#34;CentOS Linux\u0026#34; VERSION=\u0026#34;7 (Core)\u0026#34; ID=\u0026#34;centos\u0026#34; ID_LIKE=\u0026#34;rhel fedora\u0026#34; VERSION_ID=\u0026#34;7\u0026#34; PRETTY_NAME=\u0026#34;CentOS Linux 7 (Core)\u0026#34; ANSI_COLOR=\u0026#34;0;31\u0026#34; CPE_NAME=\u0026#34;cpe:/o:centos:centos:7\u0026#34; HOME_URL=\u0026#34;https://www.centos.org/\u0026#34; BUG_REPORT_URL=\u0026#34;https://bugs.centos.org/\u0026#34; CENTOS_MANTISBT_PROJECT=\u0026#34;CentOS-7\u0026#34; CENTOS_MANTISBT_PROJECT_VERSION=\u0026#34;7\u0026#34; REDHAT_SUPPORT_PRODUCT=\u0026#34;centos\u0026#34; REDHAT_SUPPORT_PRODUCT_VERSION=\u0026#34;7\u0026#34; 帮助文档\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #卸载旧的docker yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine #需要的安装包 yum install -y yum-utils #设置镜像仓库 yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo #阿里云的镜像 yum-config-manager \\ --add-repo \\ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #更新yum软件包索引 yum makecache fast #安装docker相关的 docker-ce社区 ee企业版 yum install docker-ce docker-ce-cli containerd.io #启动Dockers systemctl start docker # 查看docker版本 docker version #hello-world docker run hello-world 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 latest: Pulling from library/hello-world 2db29710123e: Pull complete Digest: sha256:80f31da1ac7b312ba29d65080fddf797dd76acfb870e677f390d5acba9741b17 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the \u0026#34;hello-world\u0026#34; image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ 1 2 3 4 #查看下载的hello-world镜像 [root@localhost /]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest feb5d9fea6a5 7 months ago 13.3kB 删除docker\n1 2 3 4 # 1.卸载依赖 yum remove docker-ce docker-ce-cli containerd.io # 2.删除资源 rm -rf /var/lib/docker Docker为什么比VM快 1、Docker有着比虚拟机更少的抽象层\n2、Docker利用的是宿主机的内核，vm需要是Guest OS\n所以说，新建一个容器时，docker不需要虚拟机一样需要重新加载一个操作系统内核，避免引导。虚拟机是加载Guest OS，分钟级别的，而docker是利用宿主机的操作系统内核，省略了这个复杂的过程，秒级\nDokcer的常用命令 帮助文档 1 2 3 docker version #显示dokcer的版本系统 docker info #显示dokcer的系统信息，包括镜像和容器的数量 docker 命令 --help #万能命令 镜像命令 1 2 3 4 5 6 7 8 9 10 11 docker images #查看所有的镜像 #解释 REPOSITORY 镜像的仓库源 TAG\t镜像的标签 IMAGE ID 镜像的id CREATED 镜像的创建时间 SIZE 镜像的大小 #可选项 -a, --all # 列出所有镜像 -q, --quiet # 只显示镜像id docker search镜像搜索 1 2 3 4 5 6 7 8 9 10 11 12 13 [root@localhost lyj]# docker search mysql NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql MySQL is a widely used, open-source relation… 12562 [OK] mariadb MariaDB Server is a high performing open sou… 4830 [OK] percona Percona Server is a fork of the MySQL relati… 576 [OK] phpmyadmin phpMyAdmin - A web interface for MySQL and M… 536 [OK] bitnami/mysql Bitnami MySQL Docker Image 71 [OK] # 可选项，通过搜索来过滤 --filter=STARS=3000 [root@localhost lyj]# docker search mysql --filter=STARS=3000 NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql MySQL is a widely used, open-source relation… 12562 [OK] mariadb MariaDB Server is a high performing open sou… 4830 [OK] docker pull下载镜像 1 2 3 #下载镜像docker pull 镜像名[:tag] [root@localhost lyj]# docker pull mysql Using default tag: latest dokcer rmi 删除镜像 1 2 3 4 #全部删除 [root@localhost lyj]# docker rmi -f $(docker images -aq) #单个删除 docker rmi -f 镜像id 容器命令 说明：有了镜像才可以创建容器，Linux，下载一个centos镜像来测试学习\n1 docker pull centos 新建容器并启动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 docker run [可选参数] image # 参数说明 --name=“Name”容器名字， tomcat01,tomcat02,用来区分容器 --d 后台方式运行 --it 使用交互方式运行，进入容器查看内容 --p 指定容器的端口 -p 8080:8080 -p ip:主机端口：容器端口 -p 主机端口：容器端口(常用) -p 容器端口 容器端口 -p 随机指定端口 # 测试，启动并进入容器 [root@localhost lyj]# docker run -it centos /bin/bash [root@a7546e01fc42 /]# ls #查看容器内的centos，基础版本，很多命令都是不完善的 bin etc lib lost+found mnt proc run srv tmp var dev home lib64 media opt root sbin sys usr #从容器中退回主机 [root@a7546e01fc42 /]# exit exit 列出所有的运行中的容器 1 2 3 4 5 6 7 8 9 10 # docker ps 命令 -a #列出当前正在运行的容器+带出历史运行过的容器 -n=? #显示最近创建的容器 -q #只显示容器的编号 [root@localhost lyj]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES [root@localhost lyj]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a7546e01fc42 centos \u0026#34;/bin/bash\u0026#34; 3 minutes ago Exited (0) About a minute ago condescending_williams 9913150f68cb feb5d9fea6a5 \u0026#34;/hello\u0026#34; About an hour ago Exited (0) About an hour ago ecstatic_hodgkin 退出容器 1 2 exit #直接停止并退出容器 Ctrl + P + Q #容器不停止但退出 删除容器 1 2 3 docker rm 容器id #删除指定内容，不能删除正在运行的容器，如果要强制删除 rm -f docker rm -f $(docker ps -aq) #删除所有内容 docker ps -a -q|xargs docker rm #删除所有容器 启动和停止容器的操作 1 2 3 4 docker start 容器id #启动容器 docker restart 容器id #重启容器 docker stop 容器id #停止当前正在运行的容器 docker kill 容器id #强制停止当前容器 常用的其他命令 后台启动容器 1 2 3 4 5 6 # 命令 docker run -d 镜像名 [root@localhost lyj]# docker run -d centos 0f13a674e8d67ff74b2498339d3c21d3ff2f99b3993a4121af9fa86246be916d [root@localhost lyj]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 常见的坑：docker容器使用后台运行，就必须要有一个前台进程，docker发现没有应用，就会自动停止，容器容器后发现自己没有提供服务，就会立刻停止，就是没有程序了 查看日志 1 2 3 4 5 6 7 docker logs -f -t --tail 容器，没有日志 #自己编写一段shell脚本 \u0026#34;while true;do echo lyj;slepp 1;done\u0026#34; #显示日志 -tf #显示日志 --tail number #显示日志条数 docker logs -ft --tail 10 24ac83cd18b4 查看容器中的进程信息 1 2 3 4 5 #命令 docker top 容器id [root@localhost lyj]# docker top 24ac83cd18b4 UID PID PPID C STIME TTY TIME CMD root 67867 67847 0 17:00 ? 00:00:00 /bin/sh -c while true;do echo lyj;sleep 1;done root 68185 67867 0 17:03 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1 查看镜像的元数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 # 命令 [root@localhost lyj]# docker inspect 24ac83cd18b4 [ { \u0026#34;Id\u0026#34;: \u0026#34;24ac83cd18b425e9f503e5899842dd1603559c0443926a393e814b7259aeb427\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2022-05-13T09:00:28.314887816Z\u0026#34;, \u0026#34;Path\u0026#34;: \u0026#34;/bin/sh\u0026#34;, \u0026#34;Args\u0026#34;: [ \u0026#34;-c\u0026#34;, \u0026#34;while true;do echo lyj;sleep 1;done\u0026#34; ], \u0026#34;State\u0026#34;: { \u0026#34;Status\u0026#34;: \u0026#34;running\u0026#34;, \u0026#34;Running\u0026#34;: true, \u0026#34;Paused\u0026#34;: false, \u0026#34;Restarting\u0026#34;: false, \u0026#34;OOMKilled\u0026#34;: false, \u0026#34;Dead\u0026#34;: false, \u0026#34;Pid\u0026#34;: 67867, \u0026#34;ExitCode\u0026#34;: 0, \u0026#34;Error\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;StartedAt\u0026#34;: \u0026#34;2022-05-13T09:00:28.676332024Z\u0026#34;, \u0026#34;FinishedAt\u0026#34;: \u0026#34;0001-01-01T00:00:00Z\u0026#34; }, \u0026#34;Image\u0026#34;: \u0026#34;sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6\u0026#34;, \u0026#34;ResolvConfPath\u0026#34;: \u0026#34;/var/lib/docker/containers/24ac83cd18b425e9f503e5899842dd1603559c0443926a393e814b7259aeb427/resolv.conf\u0026#34;, \u0026#34;HostnamePath\u0026#34;: \u0026#34;/var/lib/docker/containers/24ac83cd18b425e9f503e5899842dd1603559c0443926a393e814b7259aeb427/hostname\u0026#34;, \u0026#34;HostsPath\u0026#34;: \u0026#34;/var/lib/docker/containers/24ac83cd18b425e9f503e5899842dd1603559c0443926a393e814b7259aeb427/hosts\u0026#34;, \u0026#34;LogPath\u0026#34;: \u0026#34;/var/lib/docker/containers/24ac83cd18b425e9f503e5899842dd1603559c0443926a393e814b7259aeb427/24ac83cd18b425e9f503e5899842dd1603559c0443926a393e814b7259aeb427-json.log\u0026#34;, \u0026#34;Name\u0026#34;: \u0026#34;/elastic_gagarin\u0026#34;, \u0026#34;RestartCount\u0026#34;: 0, \u0026#34;Driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;Platform\u0026#34;: \u0026#34;linux\u0026#34;, \u0026#34;MountLabel\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;ProcessLabel\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;AppArmorProfile\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;ExecIDs\u0026#34;: null, \u0026#34;HostConfig\u0026#34;: { \u0026#34;Binds\u0026#34;: null, \u0026#34;ContainerIDFile\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;LogConfig\u0026#34;: { \u0026#34;Type\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;Config\u0026#34;: {} }, \u0026#34;NetworkMode\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;PortBindings\u0026#34;: {}, \u0026#34;RestartPolicy\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;no\u0026#34;, \u0026#34;MaximumRetryCount\u0026#34;: 0 }, \u0026#34;AutoRemove\u0026#34;: false, \u0026#34;VolumeDriver\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;VolumesFrom\u0026#34;: null, \u0026#34;CapAdd\u0026#34;: null, \u0026#34;CapDrop\u0026#34;: null, \u0026#34;CgroupnsMode\u0026#34;: \u0026#34;host\u0026#34;, \u0026#34;Dns\u0026#34;: [], \u0026#34;DnsOptions\u0026#34;: [], \u0026#34;DnsSearch\u0026#34;: [], \u0026#34;ExtraHosts\u0026#34;: null, \u0026#34;GroupAdd\u0026#34;: null, \u0026#34;IpcMode\u0026#34;: \u0026#34;private\u0026#34;, \u0026#34;Cgroup\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Links\u0026#34;: null, \u0026#34;OomScoreAdj\u0026#34;: 0, \u0026#34;PidMode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Privileged\u0026#34;: false, \u0026#34;PublishAllPorts\u0026#34;: false, \u0026#34;ReadonlyRootfs\u0026#34;: false, \u0026#34;SecurityOpt\u0026#34;: null, \u0026#34;UTSMode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;UsernsMode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;ShmSize\u0026#34;: 67108864, \u0026#34;Runtime\u0026#34;: \u0026#34;runc\u0026#34;, \u0026#34;ConsoleSize\u0026#34;: [ 0, 0 ], \u0026#34;Isolation\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;CpuShares\u0026#34;: 0, \u0026#34;Memory\u0026#34;: 0, \u0026#34;NanoCpus\u0026#34;: 0, \u0026#34;CgroupParent\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;BlkioWeight\u0026#34;: 0, \u0026#34;BlkioWeightDevice\u0026#34;: [], \u0026#34;BlkioDeviceReadBps\u0026#34;: null, \u0026#34;BlkioDeviceWriteBps\u0026#34;: null, \u0026#34;BlkioDeviceReadIOps\u0026#34;: null, \u0026#34;BlkioDeviceWriteIOps\u0026#34;: null, \u0026#34;CpuPeriod\u0026#34;: 0, \u0026#34;CpuQuota\u0026#34;: 0, \u0026#34;CpuRealtimePeriod\u0026#34;: 0, \u0026#34;CpuRealtimeRuntime\u0026#34;: 0, \u0026#34;CpusetCpus\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;CpusetMems\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Devices\u0026#34;: [], \u0026#34;DeviceCgroupRules\u0026#34;: null, \u0026#34;DeviceRequests\u0026#34;: null, \u0026#34;KernelMemory\u0026#34;: 0, \u0026#34;KernelMemoryTCP\u0026#34;: 0, \u0026#34;MemoryReservation\u0026#34;: 0, \u0026#34;MemorySwap\u0026#34;: 0, \u0026#34;MemorySwappiness\u0026#34;: null, \u0026#34;OomKillDisable\u0026#34;: false, \u0026#34;PidsLimit\u0026#34;: null, \u0026#34;Ulimits\u0026#34;: null, \u0026#34;CpuCount\u0026#34;: 0, \u0026#34;CpuPercent\u0026#34;: 0, \u0026#34;IOMaximumIOps\u0026#34;: 0, \u0026#34;IOMaximumBandwidth\u0026#34;: 0, \u0026#34;MaskedPaths\u0026#34;: [ \u0026#34;/proc/asound\u0026#34;, \u0026#34;/proc/acpi\u0026#34;, \u0026#34;/proc/kcore\u0026#34;, \u0026#34;/proc/keys\u0026#34;, \u0026#34;/proc/latency_stats\u0026#34;, \u0026#34;/proc/timer_list\u0026#34;, \u0026#34;/proc/timer_stats\u0026#34;, \u0026#34;/proc/sched_debug\u0026#34;, \u0026#34;/proc/scsi\u0026#34;, \u0026#34;/sys/firmware\u0026#34; ], \u0026#34;ReadonlyPaths\u0026#34;: [ \u0026#34;/proc/bus\u0026#34;, \u0026#34;/proc/fs\u0026#34;, \u0026#34;/proc/irq\u0026#34;, \u0026#34;/proc/sys\u0026#34;, \u0026#34;/proc/sysrq-trigger\u0026#34; ] }, \u0026#34;GraphDriver\u0026#34;: { \u0026#34;Data\u0026#34;: { \u0026#34;LowerDir\u0026#34;: \u0026#34;/var/lib/docker/overlay2/5c73a261c173cec8db9c2dd48143a5b963b2cce23dda4eb01262802efc898c54-init/diff:/var/lib/docker/overlay2/993f875b14fb02906ce0a4cc96d2fd0bd51ac4c3137d23be448fb77fefd26c14/diff\u0026#34;, \u0026#34;MergedDir\u0026#34;: \u0026#34;/var/lib/docker/overlay2/5c73a261c173cec8db9c2dd48143a5b963b2cce23dda4eb01262802efc898c54/merged\u0026#34;, \u0026#34;UpperDir\u0026#34;: \u0026#34;/var/lib/docker/overlay2/5c73a261c173cec8db9c2dd48143a5b963b2cce23dda4eb01262802efc898c54/diff\u0026#34;, \u0026#34;WorkDir\u0026#34;: \u0026#34;/var/lib/docker/overlay2/5c73a261c173cec8db9c2dd48143a5b963b2cce23dda4eb01262802efc898c54/work\u0026#34; }, \u0026#34;Name\u0026#34;: \u0026#34;overlay2\u0026#34; }, \u0026#34;Mounts\u0026#34;: [], \u0026#34;Config\u0026#34;: { \u0026#34;Hostname\u0026#34;: \u0026#34;24ac83cd18b4\u0026#34;, \u0026#34;Domainname\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;User\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;AttachStdin\u0026#34;: false, \u0026#34;AttachStdout\u0026#34;: false, \u0026#34;AttachStderr\u0026#34;: false, \u0026#34;Tty\u0026#34;: false, \u0026#34;OpenStdin\u0026#34;: false, \u0026#34;StdinOnce\u0026#34;: false, \u0026#34;Env\u0026#34;: [ \u0026#34;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0026#34; ], \u0026#34;Cmd\u0026#34;: [ \u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;while true;do echo lyj;sleep 1;done\u0026#34; ], \u0026#34;Image\u0026#34;: \u0026#34;centos\u0026#34;, \u0026#34;Volumes\u0026#34;: null, \u0026#34;WorkingDir\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Entrypoint\u0026#34;: null, \u0026#34;OnBuild\u0026#34;: null, \u0026#34;Labels\u0026#34;: { \u0026#34;org.label-schema.build-date\u0026#34;: \u0026#34;20210915\u0026#34;, \u0026#34;org.label-schema.license\u0026#34;: \u0026#34;GPLv2\u0026#34;, \u0026#34;org.label-schema.name\u0026#34;: \u0026#34;CentOS Base Image\u0026#34;, \u0026#34;org.label-schema.schema-version\u0026#34;: \u0026#34;1.0\u0026#34;, \u0026#34;org.label-schema.vendor\u0026#34;: \u0026#34;CentOS\u0026#34; } }, \u0026#34;NetworkSettings\u0026#34;: { \u0026#34;Bridge\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;SandboxID\u0026#34;: \u0026#34;c3c2cb8267717831d8702df2cc45333b92f175b66461de902323aa9d61e94252\u0026#34;, \u0026#34;HairpinMode\u0026#34;: false, \u0026#34;LinkLocalIPv6Address\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;LinkLocalIPv6PrefixLen\u0026#34;: 0, \u0026#34;Ports\u0026#34;: {}, \u0026#34;SandboxKey\u0026#34;: \u0026#34;/var/run/docker/netns/c3c2cb826771\u0026#34;, \u0026#34;SecondaryIPAddresses\u0026#34;: null, \u0026#34;SecondaryIPv6Addresses\u0026#34;: null, \u0026#34;EndpointID\u0026#34;: \u0026#34;f73444ad58e812ca8039b7ccd3c683dbaa63873183a14c1a2d395136f6414d5b\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;172.17.0.1\u0026#34;, \u0026#34;GlobalIPv6Address\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;GlobalIPv6PrefixLen\u0026#34;: 0, \u0026#34;IPAddress\u0026#34;: \u0026#34;172.17.0.2\u0026#34;, \u0026#34;IPPrefixLen\u0026#34;: 16, \u0026#34;IPv6Gateway\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:ac:11:00:02\u0026#34;, \u0026#34;Networks\u0026#34;: { \u0026#34;bridge\u0026#34;: { \u0026#34;IPAMConfig\u0026#34;: null, \u0026#34;Links\u0026#34;: null, \u0026#34;Aliases\u0026#34;: null, \u0026#34;NetworkID\u0026#34;: \u0026#34;b3b838684f4e337f0a1e6fb386dd7b7a6860ba5e5b34bf1b2d32fd1833a058b5\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;f73444ad58e812ca8039b7ccd3c683dbaa63873183a14c1a2d395136f6414d5b\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;172.17.0.1\u0026#34;, \u0026#34;IPAddress\u0026#34;: \u0026#34;172.17.0.2\u0026#34;, \u0026#34;IPPrefixLen\u0026#34;: 16, \u0026#34;IPv6Gateway\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;GlobalIPv6Address\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;GlobalIPv6PrefixLen\u0026#34;: 0, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:ac:11:00:02\u0026#34;, \u0026#34;DriverOpts\u0026#34;: null } } } } ] 进入当前正在运行的容器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #我们通常容器都是使用后台方式运行的，需要进入容器，修改一些配置 #命令 docker exec -it 容器id bashShell #测试 [root@localhost lyj]# docker exec -it 24ac83cd18b4 /bin/bash [root@24ac83cd18b4 /]# ls bin etc lib lost+found mnt proc run srv tmp var dev home lib64 media opt root sbin sys usr [root@24ac83cd18b4 /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 09:00 ? 00:00:06 /bin/sh -c while true;do echo lyj;sleep 1;done root 9470 0 0 11:39 pts/0 00:00:00 /bin/bash root 9501 1 0 11:39 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/binroot 9502 9470 0 11:39 pts/0 00:00:00 ps -ef #方式二 docker attach 容器id #测试 [root@localhost lyj]# docker attach 24ac83cd18b4 #正在执行当前的代码 #docker exec 进入容器后开启一个新的终端，可以在里面操作（常用） #docker attach 进入容器正在执行的终端，不会启动新的进程 从容器内拷贝文件到主机上 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 docker cp 容器id:容器内路径 目的的主机路径 #测试 [root@localhost home]# docker attach e1c59c6358fd [root@e1c59c6358fd /]# cd /home [root@e1c59c6358fd home]# ls [root@e1c59c6358fd home]# touch test.java [root@e1c59c6358fd home]# ls test.java [root@e1c59c6358fd home]# exit exit [root@localhost home]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES [root@localhost home]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e1c59c6358fd centos \u0026#34;/bin/bash\u0026#34; 2 minutes ago Exited (0) 8 seconds ago romantic_aryabhata [root@localhost home]# docker cp e1c59c6358fd:/home/test.java /home [root@localhost home]# ls lyj lyj.java test.java docker练习 Docker 安装nginx\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 1.搜索镜像 search # 在docker hub上搜索nginx # 2.下载镜像 pull [root@localhost lyj]# docker pull nginx Using default tag: latest latest: Pulling from library/nginx a2abf6c4d29d: Pull complete a9edb18cadd1: Pull complete 589b7251471a: Pull complete 186b1aaa4aa6: Pull complete b4df32aa5a72: Pull complete a0bcbecc962e: Pull complete Digest: sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31 Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latest [root@localhost lyj]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 605c77e624dd 4 months ago 141MB centos latest 5d0da3dc9764 8 months ago 231MB # -d 后台运行 # --name 给容器命令 # -p 宿主机端口，容器内部端口 [root@localhost lyj]# docker run -d --name nginx01 -p 3344:80 nginx 65c821bada5be37eddada13eb537c095857535a5393d9d45dc8505798c80f259 # 进入容器 [root@localhost lyj]# docker exec -it nginx01 /bin/bash root@65c821bada5b:/# whereis nginx nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx root@65c821bada5b:/# ls bin dev docker-entrypoint.sh home lib64 mnt proc run srv tmp var boot docker-entrypoint.d etc lib media opt root sbin sys usr root@65c821bada5b:/# cd /ect/nginx bash: cd: /ect/nginx: No such file or directory root@65c821bada5b:/# cd /etc/nginx root@65c821bada5b:/etc/nginx# ls conf.d fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params Docker布局tomcat\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 官方的使用 docker run -it --rm tomcat:9.0 #用来测试，用完即删 # 我们之前的启动都是后台，停止容器之后，容器还是可以查到 # 下载在启动 docker pull tomcat:9.0 # 启动运行 docker run -d -p 3355:8080 --name tomcat01 tomcat # 测试访问没有问题 # 进入容器 [root@localhost lyj]# docker exec -it tomcat01 /bin/bash # 发现问题：1.Linux命令太少了。 2.没有webapps. 阿里云镜像的原因，默认是最小的镜像，所以不必要的都剔除了 # 保证最小可运行镜像 Docker 部署es+kibana\n1 2 3 4 5 6 7 8 9 10 # es 暴露的端口很多！ # es 十分耗内存 # es 的数据一般需要放置到安全目录 # --net somenetwork ？ 网络设置 docker run -d --name elasticsearch --net somenetwork -p 9200:9200 -p 9300:9300 -e \u0026#34;discovery.type=single-node\u0026#34; elasticsearch:tag # 启动了 linux就卡住了 docker stats 查看cpu的状态 # es 是十分耗内存的 # 查看 docker stats 1 # 关闭es,修改配置文件 可视化 portainer(先用这个) 1 2 docker run -d -p 8088:9000 \\ --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer Rancher(CI/CD再用) 什么portainer? Docker图形化界面管理工具！提供一个后台面板供我们操作！\n测试：访问外网\nDocker镜像原理 镜像是什么 镜像是一种轻量级、可执行的独立软件包，用来打包软件运行环境和基于运行环境开发的软件，它包含了运行某个软件所需的所有的内容，包括代码、运行时、库、环境变量和配置文件。\n如何得到镜像 从远程仓库下载 朋友拷贝给你 自己制作一个镜像DockerFile Docker 镜像加载原理 UnionFS（联合文件系统）\n**UnionFS（联合文件系统）：**UnionFS文件系统（UnionFS）是一种分层、轻量级并且高性能的文件系统，它支持对文件系统的修改，作为一次提交一层层的叠加，同时可以将不同的目录挂载到同一个虚拟文件系统下（unite several directories into a single virtual file system）。Union文件系统所是Docker镜像的基础。镜像可以通过分层来进行继承，基于基础镜像（没有父镜像），可以制作各种具体的应用镜像。\n**特性：**一次同时加载多个文件系统，但才外面看起来，只能看到一个文件系统，联合加载会把各层文件系统叠加起来，这样最终的文件系统会包含所有底层的文件和目录\nDocker镜像加载原理\ndocker的镜像实际上由一层一层的文件系统组成，这个层级的文件系统UnionFS。\nbootfs(boot file system)主要包含bootloader和kernel，bootloader主要是引导加载kernel，linux刚启动后会加载bootfs文件系统，在docker镜像的最底层是bootfs。这一层与我们典型的linux/unix系统是一样的，包括boot加载器和内核。当boot加载完成之后整个内核就都在内存中了，此时内存的使用权已由bootfs转交给内核，此时系统也会卸载bootfs。\nrootfs（root file system）,在bootfs智商，包含的就是典型的linux系统中的/dev，/proc，/bin，/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版本，比如ubuntu，centos等等\n平时我们安装虚拟机的CentOs都是好几个g，为什么docker这里才几百兆\n对于一个精简的os，rootfs可以很小，只需要包含最基本的命令，工具和程序库就可以了，因为底层直接用Host的kernel，自己只需要提供rootfs就可以了。由此可见对于不同的linux发行版，bootfs基本是一致的，rootfs会有差别，因此不同的发行版本可用公用bootfs。\n分层理解 分层的镜像\n为什么Docker镜像要采用这种分层的结构？\n1 2 3 4 5 6 7 8 9 10 11 12 [root@localhost lyj]# docker pull redis Using default tag: latest latest: Pulling from library/redis a2abf6c4d29d: Already exists c7a4e4382001: Pull complete 4044b9ba67c9: Pull complete c8388a79482f: Pull complete 413c8bb60be2: Pull complete 1abfd3011519: Pull complete Digest: sha256:db485f2e245b5b3329fdc7eff4eb00f913e09d8feb9ca720788059fdc2ed8339 Status: Downloaded newer image for redis:latest docker.io/library/redis:latest 最大的好处，莫过于资源共享了！比如有多个镜像都从相同的Base镜像构建而来，那么宿主机只需要在磁盘上保留一份base镜像，同时内存中也只需要加载一份base镜像，这样就可以为所有的容器服务了，而且镜像的每一层都可以被共享。\n查看镜像分层可以通过docker image inspect命令！\n1 [root@localhost lyj]# docker image inspect redis:latest commit镜像 1 2 3 docker commit 提交内容成为一个新的副本 # 命令和git类似 docker commit -m=\u0026#34;提交的描述信息\u0026#34; -a=\u0026#34;作者\u0026#34; 容器id 目标镜像名：[TAG] 实践\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 启动一个默认的tomcat # 发现默认的tomcat是没有webapps应用的，原因在于官方的镜像webapps下的文件 # 将文件复制到webapps下 p -r webapps.dist/* webapps # 将修改过的容器通过commit操作提交为一个镜像，以后就可以使用修改过的镜像 [root@localhost lyj]# docker commit -a=\u0026#34;lyj\u0026#34; -m=\u0026#34;add webapps app content\u0026#34; 79602de12c77 tomcat02:1.0 sha256:60a918d863158b5ceb76c17bac25c9e974b2e0f6e107255c6538b100e80549c7 [root@localhost lyj]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE tomcat02 1.0 60a918d86315 3 seconds ago 684MB nginx latest 605c77e624dd 4 months ago 141MB tomcat 9.0 b8e65a4d736d 4 months ago 680MB tomcat latest fb5657adc892 4 months ago 680MB redis latest 7614ae9453d1 4 months ago 113MB centos latest 5d0da3dc9764 8 months ago 231MB portainer/portainer latest 580c0e4e98b0 14 months ago 79.1MB elasticsearch 7.6.2 f29a1ee41030 2 years ago 791MB 容器数据卷 什么是容器数据卷 **docker的理念：**将应用和环境打包成一个镜像\n容器之间由一个数据共享的技术，Docker容器中产生的数据，可以同步到本地\n卷技术：将容器目录挂载到linux上面\n使用数据卷 直接使用命令挂载\n1 2 3 4 docker run -it -v 主机目录：容器目录 [root@localhost home]# docker run -it -v /home/test:/home centos /bin/bash #查看是否挂载成功 docker inspect 5ccb1ac870bf 实践：安装Mysql\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 下载镜像 [root@localhost /]# docker pull mysql:5.7 5.7: Pulling from library/mysql # 运行容器，挂载数据 # 安装启动mysql需要设置密码 # 官方： docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag # 启动成功 # -v 数据卷挂载 # -d 后台启动 # -p 端口映射 # -e 环境配置 # --name 容器名字 docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7 # 启动成功之后，连接docker的mysql # 通过navicat连接数据库 # 创建一个数据库，测试文件是否挂载成功 [root@localhost data]# ls auto.cnf client-cert.pem ibdata1 ibtmp1 private_key.pem server-key.pem ca-key.pem client-key.pem ib_logfile0 mysql public_key.pem sys ca.pem ib_buffer_pool ib_logfile1 performance_schema server-cert.pem test # 容器删除之后，挂载的数据卷依旧没有丢失文件 具名和匿名挂载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 匿名挂载 -v 容器内路径 -P 随机端口 docker run -d -P --name nginx01 -v /etc/nginx nginx [root@localhost /]# docker volume ls DRIVER VOLUME NAME local d13150 # 剧名挂载 [root@localhost /]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx e35b3cf78a4964b5bcc6836c43c5e60665316c310e04580ee2ab42dde5473eb7 [root@localhost /]# docker volume ls DRIVER VOLUME NAME local d131501ea9c086991b05e9e59fa9902d265b46a339c15202c59bc9c023f4ab2f local juming-nginx 1 2 3 4 5 6 7 8 9 10 11 12 13 # 查看具名挂载的位置 [root@localhost /]# docker volume inspect juming-nginx [ { \u0026#34;CreatedAt\u0026#34;: \u0026#34;2022-05-14T15:49:21+08:00\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Labels\u0026#34;: null, \u0026#34;Mountpoint\u0026#34;: \u0026#34;/var/lib/docker/volumes/juming-nginx/_data\u0026#34;, \u0026#34;Name\u0026#34;: \u0026#34;juming-nginx\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34; } ] 所有docker容器内的卷，没有指定目录都是在/var/lib/docker/volumes/xxx/_data\n通过具名挂载可以方便的找到卷，大多数使用的都是具名挂载\n1 2 3 4 # 如何确定是具名挂载合适匿名挂载，还是指定路径挂载 -v 容器内路径 #匿名挂载 -v 卷名：容器内路径 #具名挂载 -v /宿主机路径：容器内路径 #指定路径挂载 拓展：\n1 2 3 4 5 6 7 # 通过 -v 容器内路径 ro rw 改变读写权限 # ro readonly 只读 # rw readwrite 可读可写 docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx #ro 说明这个路径只能通过宿主机来操作，容器内部无法操作 初识DockerFile DockerFile就是用来构建docker镜像的构建文件！\n通过脚本生成镜像，镜像是一层一层的，脚本是一个个命令，每一个命令就是一层\n1 2 3 4 5 6 7 8 # 创建一个dockerfile文件，名字可以随便 # 文件中的内容 指令(大写) 参数 FORM centos VOLUME [\u0026#34;volume01\u0026#34;,\u0026#34;volume02\u0026#34;] CMD echo \u0026#34;----end----\u0026#34; CMD /bin/bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [root@localhost docker-test-volume]# docker build -f /home/docker-test-volume/dockerfile1 -t lyj/centos:1.0 . Sending build context to Docker daemon 2.048kB Step 1/4 : FROM centos ---\u0026gt; 5d0da3dc9764 Step 2/4 : VOLUME [\u0026#34;volume01\u0026#34;,\u0026#34;volume02\u0026#34;] ---\u0026gt; Running in adf475a0af98 Removing intermediate container adf475a0af98 ---\u0026gt; 4dd2b9a5d3cc Step 3/4 : CMD echo \u0026#34;----end----\u0026#34; ---\u0026gt; Running in 3f617792852a Removing intermediate container 3f617792852a ---\u0026gt; 6b2f0b022f0d Step 4/4 : CMD /bin/bash ---\u0026gt; Running in 216f3f309b22 Removing intermediate container 216f3f309b22 ---\u0026gt; ac1b086b26d7 Successfully built ac1b086b26d7 Successfully tagged lyj/centos:1.0 启动自己的容器\n1 docker run -it ac1b086b26d7 /bin/bash 自己容器挂载的路径\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 \u0026#34;Mounts\u0026#34;: [ { \u0026#34;Type\u0026#34;: \u0026#34;volume\u0026#34;, \u0026#34;Name\u0026#34;: \u0026#34;c9e87917b6ac7b8b8e659dc3aa18454c67480f8b18328873c8eb4005126cc260\u0026#34;, \u0026#34;Source\u0026#34;: \u0026#34;/var/lib/docker/volumes/c9e87917b6ac7b8b8e659dc3aa18454c67480f8b18328873c8eb4005126cc260/_data\u0026#34;, \u0026#34;Destination\u0026#34;: \u0026#34;volume01\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Mode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;RW\u0026#34;: true, \u0026#34;Propagation\u0026#34;: \u0026#34;\u0026#34; }, { \u0026#34;Type\u0026#34;: \u0026#34;volume\u0026#34;, \u0026#34;Name\u0026#34;: \u0026#34;6716d8d8d2108ef678bd3d68632ec45a1f8b1086a3e9cab08965c0b73edde3f4\u0026#34;, \u0026#34;Source\u0026#34;: \u0026#34;/var/lib/docker/volumes/6716d8d8d2108ef678bd3d68632ec45a1f8b1086a3e9cab08965c0b73edde3f4/_data\u0026#34;, \u0026#34;Destination\u0026#34;: \u0026#34;volume02\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Mode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;RW\u0026#34;: true, \u0026#34;Propagation\u0026#34;: \u0026#34;\u0026#34; } ], 数据卷容器 多个容器同步数据\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 启动三个镜像，通过我们自己写的镜像去启动 [root@localhost lyj]# docker run -it --name docker01 lyj/centos:1.0 [root@af22ef053837 /]# ls bin etc lib lost+found mnt proc run srv tmp var volume02 dev home lib64 media opt root sbin sys usr volume01 [root@af22ef053837 /]# ls -l total 0 lrwxrwxrwx. 1 root root 7 Nov 3 2020 bin -\u0026gt; usr/bin drwxr-xr-x. 5 root root 360 May 17 02:18 dev drwxr-xr-x. 1 root root 66 May 17 02:18 etc drwxr-xr-x. 2 root root 6 Nov 3 2020 home lrwxrwxrwx. 1 root root 7 Nov 3 2020 lib -\u0026gt; usr/lib lrwxrwxrwx. 1 root root 9 Nov 3 2020 lib64 -\u0026gt; usr/lib64 drwx------. 2 root root 6 Sep 15 2021 lost+found drwxr-xr-x. 2 root root 6 Nov 3 2020 media drwxr-xr-x. 2 root root 6 Nov 3 2020 mnt drwxr-xr-x. 2 root root 6 Nov 3 2020 opt dr-xr-xr-x. 186 root root 0 May 17 02:18 proc dr-xr-x---. 2 root root 162 Sep 15 2021 root drwxr-xr-x. 11 root root 163 Sep 15 2021 run lrwxrwxrwx. 1 root root 8 Nov 3 2020 sbin -\u0026gt; usr/sbin drwxr-xr-x. 2 root root 6 Nov 3 2020 srv dr-xr-xr-x. 13 root root 0 May 17 02:13 sys drwxrwxrwt. 7 root root 171 Sep 15 2021 tmp drwxr-xr-x. 12 root root 144 Sep 15 2021 usr drwxr-xr-x. 20 root root 262 Sep 15 2021 var drwxr-xr-x. 2 root root 6 May 17 02:18 volume01 drwxr-xr-x. 2 root root 6 May 17 02:18 volume02 删除docker01的容器之后数据依然存在\n容器之间买配置信息的传递，数据卷容器的生命周期一致持续到没有容器使用为止\nDockerFile DokerFile介绍 dockerFile是用来构建docker镜像的文件！命令参数脚本！\n构建步骤：\n编写一个dockerfile文件 docker build 构建成为一个镜像 docker run 运行镜像 docker push 发布镜像(Docker Hub、阿里云镜像仓库) 很多官方镜像都是基础包，很多功能没有，我们通常会自己搭建镜像\nDockerFile构建过程 基础知识：\n1、每个保留关键字(指令)都是必须大写字母\n2、执行顺序是从上到下的\n3、# 表示注释\n4、每一个指令都会创建提交一个新的镜像层，并提交！\ndockerFile是面向开发的，以后发布项目，做镜像，就需要编写dockerfile文件，这个文件很简单，dockerfil逐渐成为企业交付的标准，必须要掌握！\n步骤：开发、部署、运维缺一不可\nDockerFile：构建文件，定义了一切的步骤，源代码\nDockerImages：通过dockerfile构建生成的镜像，最终发布和运行的产品\nDocker容器：容器就是镜像运行起来提供服务器\n指令： 1 2 3 4 5 6 7 8 9 10 11 12 FROM\t# 基础镜像，一切从这里开始构建 MAINTAINER\t# 镜像是谁写的，姓名+邮箱 RUN\t# 镜像构建的时候需要运行的命令 ADD # 步骤，tomcat镜像，这个tomcat压缩包！添加内容 WORKDIR # 镜像的工作目录 VOLUEM # 挂载的目录 EXPOSE # 保留端口配置 CMD\t# 指定这个容器启动的时候要运行的命令，只有最后一个会生效，可被替代 ENTRYPOINT # 指定这个容器启动的时候要运行的命令，可以追加命令 ONBUILD # 当构建一个被继承DockerFile这个时候就会运行ONBUILD的指令 COPY # 类似ADD,将我们的文件拷贝到镜像中 ENV # 构建的时候设置环境变量！ 创建一个属于自己的centos\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 编写dockerfile的文件 [root@localhost dockerfile]# vim mydockerfile-centos [root@localhost dockerfile]# cat mydockerfile-centos FROM centos MAINTANER lyj-2063074967@qq.com ENV MYPATH /usr/local WORKDIR $MYPATH RUN yum -y install vim RUN yum -y install net-tools EXPOSE 80 CMD echo $MYPATH CMD echo \u0026#34;---end---\u0026#34; CMD /bin/bash # 构建镜像 [root@localhost dockerfile]# docker build -f mydockerfile-centos -t mycentos:0.1 . # 测试运行 docker run -it mycentos:0.1 # 查看历史变更 ot@localhost dockerfile]# docker history 5d0da3dc9764 IMAGE CREATED CREATED BY SIZE COMMENT 5d0da3dc9764 8 months ago /bin/sh -c #(nop) CMD [\u0026#34;/bin/bash\u0026#34;] 0B \u0026lt;missing\u0026gt; 8 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B \u0026lt;missing\u0026gt; 8 months ago /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0… 231MB CMD 和 ENTRYPOINT 区别\n测试cmd\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 [root@localhost dockerfile]# vim dockerfile-cmd-test # 脚本 FROM centos:7 CMD [\u0026#34;ls\u0026#34;,\u0026#34;-a\u0026#34;] # 构建 [root@localhost dockerfile]# docker build -f dockerfile-cmd-test -t cmdtest . # 命令生效 [root@localhost dockerfile]# docker run 744c3d93e4a3 . .. .dockerenv anaconda-post.log bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var # 想追加命令 il, ls -al [root@localhost dockerfile]# docker run 744c3d93e4a3 -l docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: \u0026#34;-l\u0026#34;: executable file not found in $PATH: unknown. # cmd情况下-l 替代了ls -a 测试ENTRYPOINT\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 [root@localhost dockerfile]# vim dockerfile-cmd-entryponit # 脚本 FROM centos:7 ENTRYPOINT [\u0026#34;ls\u0026#34;,\u0026#34;-a\u0026#34;] [root@localhost dockerfile]# docker build -f dockerfile-cmd-entryponit -t entrypoint-test . Sending build context to Docker daemon 4.096kB Step 1/2 : FROM centos:7 ---\u0026gt; eeb6ee3f44bd Step 2/2 : ENTRYPOINT [\u0026#34;ls\u0026#34;,\u0026#34;-a\u0026#34;] ---\u0026gt; Running in 84619c168165 Removing intermediate container 84619c168165 ---\u0026gt; a0a8da7d7890 Successfully built a0a8da7d7890 Successfully tagged entrypoint-test:latest [root@localhost dockerfile]# docker run a0a8da7d7890 . .. .dockerenv anaconda-post.log bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var [root@localhost dockerfile]# docker run a0a8da7d7890 -l total 12 drwxr-xr-x. 1 root root 6 May 17 03:33 . drwxr-xr-x. 1 root root 6 May 17 03:33 .. -rwxr-xr-x. 1 root root 0 May 17 03:33 .dockerenv -rw-r--r--. 1 root root 12114 Nov 13 2020 anaconda-post.log lrwxrwxrwx. 1 root root 7 Nov 13 2020 bin -\u0026gt; usr/bin drwxr-xr-x. 5 root root 340 May 17 03:33 dev drwxr-xr-x. 1 root root 66 May 17 03:33 etc drwxr-xr-x. 2 root root 6 Apr 11 2018 home lrwxrwxrwx. 1 root root 7 Nov 13 2020 lib -\u0026gt; usr/lib lrwxrwxrwx. 1 root root 9 Nov 13 2020 lib64 -\u0026gt; usr/lib64 drwxr-xr-x. 2 root root 6 Apr 11 2018 media drwxr-xr-x. 2 root root 6 Apr 11 2018 mnt drwxr-xr-x. 2 root root 6 Apr 11 2018 opt dr-xr-xr-x. 187 root root 0 May 17 03:33 proc dr-xr-x---. 2 root root 114 Nov 13 2020 root drwxr-xr-x. 11 root root 148 Nov 13 2020 run lrwxrwxrwx. 1 root root 8 Nov 13 2020 sbin -\u0026gt; usr/sbin drwxr-xr-x. 2 root root 6 Apr 11 2018 srv dr-xr-xr-x. 13 root root 0 May 17 02:13 sys drwxrwxrwt. 7 root root 132 Nov 13 2020 tmp drwxr-xr-x. 13 root root 155 Nov 13 2020 usr drwxr-xr-x. 18 root root 238 Nov 13 2020 var Dockerfile中很多命令都十分相似，我们需要了解它们的区别，我的最好的学习就是对比测试\n实践：Tomcat镜像 1、准备镜像文件tomcat压缩包，jdk的压缩包\n2、准备dockerfile文件，官方命令Dockerfile,build时会自动寻找这个文件，就不需要-f指定了\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 FROM centos:7 MAINTAINET lyj-2063074967@qq.com COPY readme.txt /usr/local/readme.txt ADD jdk-8u181-linux-x64.tar.gz /usr/local/ ADD apache-tomcat-9.0.63.tar.gz /usr/lcoal/ RUN yum -y install vim ENV MYPATH /usr/local WORKDIR $MYPATH ENV JAVA_HOME /usr/local/jdk1.8.0_181 ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.63 ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.63 ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin EXPOSE 8080 CMD /usr/local/apache-tomcat-9.0.63/bin/startup.sh \u0026amp;\u0026amp; tail -F /usr/local/apache-tomcat-9.0.63/bin/logs/catalina.out 3、构建镜像\n1 # docker build -t diytomcat . Docker网络 理解Docker0 清空所有环境（镜像和容器）\n1 2 3 4 5 6 # docker 如何处理容器网络访问 [root@localhost /]# docker run -d -P --name tomcat01 tomcat # 查看容器的内部网络地址 ip addr # linux可以Ping 容器内部 原理\n1、每启动一个docker容器，docker就会给docker容器分配一个ip,只要安装了docker，就会有一个网卡docker0桥接模式，使用的技术是evth-pair技术\n2、在启动一个容器又多了一个网卡\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 [root@localhost /]# ip addr 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens33: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:32:a0:bc brd ff:ff:ff:ff:ff:ff inet 192.168.108.110/24 brd 192.168.108.255 scope global noprefixroute ens33 valid_lft forever preferred_lft forever inet6 fe80::32c9:7680:6e1:ea04/64 scope link noprefixroute valid_lft forever preferred_lft forever 3: virbr0: \u0026lt;NO-CARRIER,BROADCAST,MULTICAST,UP\u0026gt; mtu 1500 qdisc noqueue state DOWN group default qlen 1000 link/ether 52:54:00:c7:4f:55 brd ff:ff:ff:ff:ff:ff inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0 valid_lft forever preferred_lft forever 4: virbr0-nic: \u0026lt;BROADCAST,MULTICAST\u0026gt; mtu 1500 qdisc pfifo_fast master virbr0 state DOWN group default qlen 1000 link/ether 52:54:00:c7:4f:55 brd ff:ff:ff:ff:ff:ff 5: br-11e4c4d39e6c: \u0026lt;NO-CARRIER,BROADCAST,MULTICAST,UP\u0026gt; mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:20:e8:3d:98 brd ff:ff:ff:ff:ff:ff inet 172.18.0.1/16 brd 172.18.255.255 scope global br-11e4c4d39e6c valid_lft forever preferred_lft forever 6: docker0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue state UP group default link/ether 02:42:cc:cd:5d:3d brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:ccff:fecd:5d3d/64 scope link valid_lft forever preferred_lft forever 8: vethbd6aa9d@if7: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue master docker0 state UP group default link/ether ba:cc:e4:b3:69:1e brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::b8cc:e4ff:feb3:691e/64 scope link valid_lft forever preferred_lft forever 10: veth9702194@if9: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 96:99:e6:6c:aa:b6 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::9499:e6ff:fe6c:aab6/64 scope link valid_lft forever preferred_lft forever 容器带来的网卡，都是一对的，evth-pair就是一对虚拟设备接口，他们都是成对出现的，一段连着协议，一段彼此相连，正因为这个特性，evth-pari充当一个桥接，连接各种虚拟网络设备的，Docker容器之间的连接，都是使用evth-paur技术\nlink 1 docker run -P --name tomcat03 --link tomcat02 tomcat 1 2 3 4 5 6 7 8 [root@localhost /]# docker exec -it tomcat02 cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.17.0.3 555cc43e71d6 link的本质：就是在hosts配置增加了171.17.0.3 容器id\n自定义网络 查看所有docker 网络\n1 2 3 4 5 6 [root@localhost /]# docker network ls NETWORK ID NAME DRIVER SCOPE 835678176bfb bridge bridge local 33bd4092d423 host host local c67b96605b65 none null local 11e4c4d39e6c somenetwork bridge local 网络模式 bridge：桥接docker（默认）自己创建网络也是用bridge模式\nnone：不配置网络\nhost：和宿主机共享网络\ncontainer：容器网络联通（用的少，局限很大）\n测试\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 直接启动的命令， --net bridge,而这个就docker0 docker run -d -P --name tomcat01 docker run -d -P --name tomcat01 --net bridge tomcat # docker0特点，默认，域名不用访问，--link可以打通连接 # 自定义一个网络 # --diver 桥接模式 # --subnet 子网 # --gateway 网关 [root@localhost /]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet dc5bf1caafaa16013e9f9f0654982ed97cd589713f5925ddbd2908c0ccb1351b [root@localhost /]# docker network ls NETWORK ID NAME DRIVER SCOPE 835678176bfb bridge bridge local 33bd4092d423 host host local dc5bf1caafaa mynet bridge local c67b96605b65 none null local # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 [root@localhost /]# docker run -d -P --name tomcat-net-01 --net mynet \u0026#34;docker run\u0026#34; requires at least 1 argument. See \u0026#39;docker run --help\u0026#39;. Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container [root@localhost /]# docker run -d -P --name tomcat-net-01 --net mynet tomcat a07c51438812bcfc529fd503c929866a6f77d30a7e8716c72f9548ffc5025877 [root@localhost /]# docker run -d -P --name tomcat-net-02 --net mynet tomcat ecdda4c64e007c8c7ef24de2dd581cbe967593c2fc730805f769c68930b82f6a [root@localhost /]# docker network inspect mynet [ { \u0026#34;Name\u0026#34;: \u0026#34;mynet\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;dc5bf1caafaa16013e9f9f0654982ed97cd589713f5925ddbd2908c0ccb1351b\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2022-05-18T11:06:01.965660686+08:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: {}, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;192.168.0.0/16\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;192.168.0.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: false, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: { \u0026#34;a07c51438812bcfc529fd503c929866a6f77d30a7e8716c72f9548ffc5025877\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;tomcat-net-01\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;de83ba706e481876e2973336408e5c071da47133ce309c7801bf8935bb391618\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:00:02\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.0.2/16\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ecdda4c64e007c8c7ef24de2dd581cbe967593c2fc730805f769c68930b82f6a\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;tomcat-net-02\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;066c8632e98ebfb0d8cd3ebc3da9bc28df18749532ebd71fda9561e810e0a49b\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:00:03\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.0.3/16\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; } }, \u0026#34;Options\u0026#34;: {}, \u0026#34;Labels\u0026#34;: {} } ] 自定义的网络docker已经帮我们维护好了对应的关系\n好处：\nredis-不同的集群使用不同的网络，保证了集群是安全和健康的\nmysql-不同的集群使用不同的网络，保证了集群是安全和健康的\n网络连通 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@localhost /]# docker network --help Usage: docker network COMMAND Manage networks Commands: connect Connect a container to a network create Create a network disconnect Disconnect a container from a network inspect Display detailed information on one or more networks ls List networks prune Remove all unused networks rm Remove one or more networks Run \u0026#39;docker network COMMAND --help\u0026#39; for more information on a command. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 # 测试打通tomcat01到mynet [root@localhost /]# docker network connect mynet tomcat01 # 连通之后就是tomcat01放到了mynet网络下 [root@localhost /]# docker network inspect mynet [ { \u0026#34;Name\u0026#34;: \u0026#34;mynet\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;dc5bf1caafaa16013e9f9f0654982ed97cd589713f5925ddbd2908c0ccb1351b\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2022-05-18T11:06:01.965660686+08:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: {}, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;192.168.0.0/16\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;192.168.0.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: false, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: { \u0026#34;3e25bc8a0e7586f3105c12ca550d5a28b0407ba5aa9bd362c4993422eb1729dd\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;tomcat01\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;5e6c258c064273d14450c2acfb98a34455524b11af4980378b62de1ae2249271\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:00:04\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.0.4/16\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;a07c51438812bcfc529fd503c929866a6f77d30a7e8716c72f9548ffc5025877\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;tomcat-net-01\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;de83ba706e481876e2973336408e5c071da47133ce309c7801bf8935bb391618\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:00:02\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.0.2/16\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ecdda4c64e007c8c7ef24de2dd581cbe967593c2fc730805f769c68930b82f6a\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;tomcat-net-02\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;066c8632e98ebfb0d8cd3ebc3da9bc28df18749532ebd71fda9561e810e0a49b\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:c0:a8:00:03\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;192.168.0.3/16\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; } }, \u0026#34;Options\u0026#34;: {}, \u0026#34;Labels\u0026#34;: {} } ] 1 2 # 移除网络 [root@localhost /]# docker network rm somenetwork 部署redis集群 创建redis网卡\n1 [root@localhost /]# docker network create redis --subnet 172.38.0.0/16 通过脚本创建六个reids配置\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for port in $(seq 1 6); \\ do \\ mkdir -p /mydata/redis/node-${port}/conf touch /mydata/redis/node-${port}/conf/redis.conf cat \u0026lt;\u0026lt; EOF \u0026gt;/mydata/redis/node-${port}/conf/redis.conf port 6379 bind 0.0.0.0 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-announce-ip 172.38.0.1${port} cluster-announce-port 6379 cluster-announce-bus-port 16379 appendonly yes EOF done 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 # 启动容器 for port in $(seq 1 6); \\ do \\ docker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \\ -v /mydata/redis/node-${port}/data:/data \\ -v /mydata/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \\ -d --net redis --ip 172.38.0.1${port} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf; \\ done #进入一个容器 docker exec -it redis-1 /bin/sh # 创建集群 redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1 \u0026gt;\u0026gt;\u0026gt; Performing hash slots allocation on 6 nodes... Master[0] -\u0026gt; Slots 0 - 5460 Master[1] -\u0026gt; Slots 5461 - 10922 Master[2] -\u0026gt; Slots 10923 - 16383 Adding replica 172.38.0.15:6379 to 172.38.0.11:6379 Adding replica 172.38.0.16:6379 to 172.38.0.12:6379 Adding replica 172.38.0.14:6379 to 172.38.0.13:6379 M: 9d90cb6cfe72a617664d5cc5ed83f654c2e9865b 172.38.0.11:6379 slots:[0-5460] (5461 slots) master M: ff448dc8dea2517b2c77b2558e04cbeda7fab184 172.38.0.12:6379 slots:[5461-10922] (5462 slots) master M: fc808d7350a36679cdc645dbaa8ec18702ca9e99 172.38.0.13:6379 slots:[10923-16383] (5461 slots) master S: a595255f05ffe1317a217143bc87ec97cc287432 172.38.0.14:6379 replicates fc808d7350a36679cdc645dbaa8ec18702ca9e99 S: 88f5fca469eab471e64edebb7a75f4422c9ada08 172.38.0.15:6379 replicates 9d90cb6cfe72a617664d5cc5ed83f654c2e9865b S: 73f4b703c96d5c36e5480574bee473aef9914da8 172.38.0.16:6379 replicates ff448dc8dea2517b2c77b2558e04cbeda7fab184 Can I set the above configuration? (type \u0026#39;yes\u0026#39; to accept): yes \u0026gt;\u0026gt;\u0026gt; Nodes configuration updated \u0026gt;\u0026gt;\u0026gt; Assign a different config epoch to each node \u0026gt;\u0026gt;\u0026gt; Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join ... \u0026gt;\u0026gt;\u0026gt; Performing Cluster Check (using node 172.38.0.11:6379) M: 9d90cb6cfe72a617664d5cc5ed83f654c2e9865b 172.38.0.11:6379 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: ff448dc8dea2517b2c77b2558e04cbeda7fab184 172.38.0.12:6379 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: a595255f05ffe1317a217143bc87ec97cc287432 172.38.0.14:6379 slots: (0 slots) slave replicates fc808d7350a36679cdc645dbaa8ec18702ca9e99 S: 73f4b703c96d5c36e5480574bee473aef9914da8 172.38.0.16:6379 slots: (0 slots) slave replicates ff448dc8dea2517b2c77b2558e04cbeda7fab184 S: 88f5fca469eab471e64edebb7a75f4422c9ada08 172.38.0.15:6379 slots: (0 slots) slave replicates 9d90cb6cfe72a617664d5cc5ed83f654c2e9865b M: fc808d7350a36679cdc645dbaa8ec18702ca9e99 172.38.0.13:6379 slots:[10923-16383] (5461 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. \u0026gt;\u0026gt;\u0026gt; Check for open slots... \u0026gt;\u0026gt;\u0026gt; Check slots coverage... SpringBoot项目打包Docker镜像 1、构建springboot项目\n2、打包应用\n3、编写Dockerfile\n4、构建镜像\n5、发布运行\nDocker Compose 简介： Docker\nDockerFile build run 手动操作，单个容器！\n微服务，100个微服务！依赖关系\nDocker Compose 来轻松高效的管理容器，定义运行多个容器。\nCompose是docker官方的开源项目，需要安装，\nDockerfile让程序可以在任何地方运行，web服务、redis、MySQL、nginx\u0026hellip;多个容器\nCompose\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 version: \u0026#34;3.9\u0026#34; # optional since v1.27.0 services: web: build: . ports: - \u0026#34;8000:5000\u0026#34; volumes: - .:/code - logvolume01:/var/log links: - redis redis: image: redis volumes: logvolume01: {} docker-compose -up 100个服务\nCompose：重要的概念\n服务services，容器，应用。（web、redis、mysql） 项目project，一组关联的容器。博客，web,mysql Compose 安装 1 curl -L https://get.daocloud.io/docker/compose/releases/download/v2.5.0/docker-compose-`uname -s`-`uname -m` \u0026gt; /usr/local/bin/docker-compose 授权\n1 2 3 4 chmod +x docker-compose [root@localhost bin]# docker-compose version Docker Compose version v2.5.0 体验 python应用，计数器，redis。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 mkdir composetest cd composetest vim app.py # 文件内容 import time import redis from flask import Flask app = Flask(__name__) cache = redis.Redis(host=\u0026#39;redis\u0026#39;, port=6379) def get_hit_count(): retries = 5 while True: try: return cache.incr(\u0026#39;hits\u0026#39;) except redis.exceptions.ConnectionError as exc: if retries == 0: raise exc retries -= 1 time.sleep(0.5) @app.route(\u0026#39;/\u0026#39;) def hello(): count = get_hit_count() return \u0026#39;Hello World! I have been seen {} times.\\n\u0026#39;.format(count) vim requirements.txt # 文件内容 flask redis vim Dockerfile # 文件内容 # syntax=docker/dockerfile:1 FROM python:3.7-alpine RUN echo -e http://mirrors.ustc.edu.cn/alpine/v3.12/main/ \u0026gt; /etc/apk/repositories WORKDIR /code ENV FLASK_APP=app.py ENV FLASK_RUN_HOST=0.0.0.0 RUN apk add --no-cache gcc musl-dev linux-headers COPY requirements.txt requirements.txt RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --no-cache-dir -r requirements.txt EXPOSE 5000 COPY . . CMD [\u0026#34;flask\u0026#34;, \u0026#34;run\u0026#34;] vim docker-compose.yml # 文件内容 version: \u0026#34;3.9\u0026#34; services: web: build: . ports: - \u0026#34;8000:5000\u0026#34; redis: image: \u0026#34;redis:alpine\u0026#34; # 运行 docker-compose up yaml 规则 docker-compose.yaml核心\n1 2 3 4 5 6 7 8 9 10 11 12 # 3层 version: \u0026#39;\u0026#39; #版本 services: #服务 服务1： web # 服务配置 images build network 服务2： redis 服务3： redis # 其他配置 网络/卷、 全局规则 开源项目 下载程序，安装数据库、配置……\ncompose应用。=》一键启动\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 mkdir my_wordpress cd my_wordpress vim docker-compose.yml # 文件内容 version: \u0026#34;3.9\u0026#34; services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest volumes: - wordpress_data:/var/www/html ports: - \u0026#34;8000:80\u0026#34; restart: always environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpress volumes: db_data: {} wordpress_data: {} 前台启动\n1 2 3 4 5 docker -d docker-compose -d [+] Running 2/2 ⠿ Container my_wordpress-db-1 Started 0.5s ⠿ Container my_wordpress-wordpress-1 Started 1.2s 测试： 1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class HelloController { @Autowired StringRedisTemplate redisTemplate; @GetMapping(\u0026#34;/hello\u0026#34;) public String hello(){ Long views = redisTemplate.opsForValue().increment(\u0026#34;views\u0026#34;); return \u0026#34;hello lyj views:\u0026#34; + views; } } 1 2 3 # application.propertiees server.port=8080 spring.redis.host=redis 1 2 3 4 5 6 7 8 9 10 # Dockerfile FROM java:8 COPY *.jar /app.jar CMD [\u0026#34;--server.port=8080\u0026#34;] EXPOSE 8080 ENTRYPOINT [\u0026#34;java\u0026#34;,\u0026#34;-jar\u0026#34;,\u0026#34;/app.jar\u0026#34;] 1 2 3 4 5 6 7 8 9 10 11 12 # docker-compose.yml version: \u0026#34;3.9\u0026#34; services: lyjapp: build: . image: lyj depends_on: - redis ports: - \u0026#34;8080:8080\u0026#34; redis: image: \u0026#34;library/redis:alpine\u0026#34; 项目要重新打包\n1 docker-compose up --build ","permalink":"https://lyj404.github.io/blog/post/docker-description/","summary":"\u003ch2 id=\"docker概述\"\u003eDocker概述\u003c/h2\u003e\n\u003ch3 id=\"docker为什么出现\"\u003eDocker为什么出现？\u003c/h3\u003e\n\u003cp\u003e一款产品：开发-上线 两套环境！应用环境！应用配置！\u003c/p\u003e\n\u003cp\u003e开发 ··· 运维。问题：我的电脑用运行！版本更新，导致服务不可用！对于运维来说，考研太大了\u003c/p\u003e\n\u003cp\u003e环境配置是十分麻烦，每一个机器都要部署环境(集群redis,es、Hadoop)！费时费力\u003c/p\u003e","title":"Docker"},{"content":"数据库设计规范 数据库名称规范 所有的数据库对象名称必须使用小写字母并用下划线分割（mysql数据库大小写敏感） 例如: 不同的数据库名 DbName dbname 不同的表名 Table table taBLe\n所有数据库对象名称禁止使用mysql保留关键字 例如 ：select id , username , from ,age from table_user 会造成mysql 歧义\n数据库对象的命名要做到见名识义，并且最好不要超过32个字符 例如：秒杀用户表 miaosha_userdb good!! 用户账号表 user_account good!!\n所有的临时表必须义tmp为前缀并且已日期为后缀，备份库备份表必须义bak为前缀并且已日期为后缀，大的可以到处sql\n所有的数据存储的列名和列类型必须一致 custom_inf order_master customer_id int unsigned customer_id int unsigned\n必须一致否则会影响性能，一般这种列都是作为表的关联列，如果两个字段的数据类型不同，数据库则会进行隐式的数据类型 转换降低性能，造成列上的索引失效！！\n存储规范 所有的表必须使用Innodb存储引擎 支持事物行级锁，更好的恢复性，并发性能更好 大部分数据库表字符集要统一 （大部分使用UTF-8） 编码乱码 所有的表和字段都要添加注释 数据字典的维护 尽量控制单表的数据量大小，建议控制在500万以内 当然500万并不是mysql的限制 过大可以通过历史数据归档，分库分表的手段来控制数据量的大小（订单表一类的比较重要） 尽量做到冷热数据分离，减少表的宽度 列最大4096列 减少磁盘io,保证热数据的内存缓存命中率，控制列数量也可以更加有效的利用缓存，避免读入无用的冷数据 经常使用的列放在一个表中 禁止在表中建立预留字段 预留字段命名很难做到见识义，无法选择合适的类型，对预留字段修改会对表进行锁定 行为规范 禁止存储图片，文件等二进制文件，造成大量的io操作 （存在相应的文件服务器上） 禁止再线上进行数据库的压力测试 禁止从开发环境测试环境直连生产环境 索引设计规范 建议单表索引数量不超过5个 索引并不是越多越好过多索引降低效率，优化器过多索引选出最优解 innodb 是按照那个索引的顺序来组织表的呢 主键 不使用更新频繁的列作为主键，不使用多列主键 不使用uuid,md5,hash 字符串列作为主键 不能保证顺序增长\n那些哪些列上建立索引？？？\n我们经常在select update delete语句的where从句的列建立索引 包含在order by , group by , distinct 中的字段 多表join的关联列 如何选择索引的顺序？ 联合索引中 索引从左到右的顺序 建立索引的目的 查询数据的时候可以根据索引来进行数据查找\u0026ndash; 从而减少磁盘的随机io , 增加查询的性能，所以 如果我们的索引能够过滤出更少的数据那么我们从磁盘读入的数据则越少\n1.区分度最高的列放在联合索引的最左侧 区分度\u0026ndash;数据唯一值的数量/总行数 区分度最大的就应该是主键了 2.尽量字段长度小的列放在联合索引的最左侧 3.使用最频繁的列放在最左侧\n尽量避免冗余和重复的索引 重复索引了： primary key(id) , index(id) , unique index(id) 冗余： index(a,b,c) , index(a.b)\n对于频繁使用的查询先优先考虑覆盖索引（包含了所有的查询字段的索引）\n尽量使用外键 不建议使用外检约束，但是一定要在表与表之间的关联键上建立索引\n数据库字段设计规范 优先选择符合存储需要的最小的数据类型 1.将字符串转换成数字类型存储 INET_ATON(\u0026lsquo;255.255.255.255\u0026rsquo;) = 4294967295 字符串转ip 将字符串转换成数字类型存储 INET_NTOA(\u0026lsquo;4294967295\u0026rsquo;) = 255.255.255.255 ip 转字符串 2.对于非负数据采用无符号整形进行存储 signed int -2147483648 \u0026ndash; 2147483647 unsigned int -0 \u0026ndash; 4294967295 3.VARCHAR(N)中的N代表的是字符数而不是字节数 使用UTF-8存储汉字varchar(255) = 765个字节 存储255个汉字 4.过大的长度会消耗更多的内存 varchar是一个可变的长度 避免使用text,blob数据类型 建议blob或者时text分离到单独的表中 避免使用enum数据类型 尽可能的所有列都定义为NOT NULL 索引null列需要额外的空间来保存，所以需要占用更多的空间 进行比较和计算的时候要对null值进行特别的处理 字符串存储日期型的数据不是正确的 无法用日期函数来进行计算和比较 用字符串存储日期要占用更多的空间 timestamp 和datatime 类型存储时间 timestamp 存储范围有限制 1970-01-01 00:00:01 ～2038-01-19 03:14:07 timestamp占用4字节和INT相同，但是INT可读性能高 财务相关的金额类数据，必须由decimal类型存储 decimal类型为精准的浮点数，在计算时不会丢失精度 占用空间由定义的宽度来决定 可用于存储比bigint更大的整数类型 数据库sql开发范式 建议使用预编译语句对数据库进行操作\n避免数据类型的隐式转换 不同表的相同列的数据类型不一致 会导致索引失效\n重复利用已经存在的索引 1.避免使用双%%的查询条件 如 a like \u0026lsquo;%1323%\u0026rsquo; 2.一个sql只能利用到复合索引中的一列进行范围查询 有 a,b,c列的联合索引，在查询条件中有a列的范围查询，则在b,c列上的索引将不会被用到， 在定义联合索引时，如果a列要用到范围查找的话，就要把a列放到联合索引的右侧 3.使用left join 或者not exists 来优化not in（偶尔也会导致索引失效） 操作\n程序链接不同数据库要使用不同的账号，禁止跨库连接为迁移和分库分表做预备\n禁止select * 必须使用select \u0026lt;字段列表\u0026gt; （* 无法覆盖索引 减少表结构变更 对程序带来的影响）\ninsert 明确字段列表\n禁止使用子查询，可以把子查询优化为join操作 子查询结果集无法使用索引 子查询会产生临时表操作，如果子查询数据量大则会更严重\n避免使用过多的join 关联表 于Mysql来说，是存在关联缓存的，缓存的大小可以由join_buffer_size参数进行设置 在Mysql中，对于同一个SQL多关联（join）一个表，就会多分配一个关联缓存，如果在一个SQL中关联的表越多， 所占用的内存也就越大 如果程序中大量的使用了多表关联的操作，同时join_buffer_size设置的也不合理的情况下，就容易造成服务器内存溢出的情况， 就会影响到服务器数据库性能的稳定性 同时对于关联操作来说，会产生临时表操作，影响查询效率 Mysql最多允许关联61个表，建议不超过5个\n减少同数据库的交互次数 多个相同的操作合并在一起\n对应同一列进行or判断时，使用in代替or\nin 的值不要超过500个 in 操作可以更有效的利用索引，or大多数情况下很少能利用到索引 禁止order by rand() 进行随机排序 会把表中所有符合条件的数据装载到内存中，然后在内存中对所有数据根据随机生成的值进行排序 并且可能会对每一行都生成一个随机值，如果满足条件的数据集非常大，就会消耗大量的CPU和IO及内存资源 推荐在程序中获取一个随机值，然后从数据库中获取数据的方式\nwhere 从句禁止对列进行函数转换和计算(导致无法使用相关列的索引) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 SELECT（正确写法） * FROM miaosha_message WHERE create_time \u0026gt;= \u0026#39;20190101\u0026#39; AND create_time \u0026lt; \u0026#39;20190102\u0026#39; SELECT （错误写法） * FROM miaosha_message WHERE date (create_time) = \u0026#39;20190101\u0026#39; 不会有重复值时使用UNION ALL 而不是UNION UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作 UNION ALL 不会再对结果集进行去重操作\n拆分大sql变为小sql 大SQL:逻辑上比较复杂，需要占用大量CPU进行计算的SQL MySQL 一个SQL只能使用一个CPU进行计算 SQL拆分后可以通过并行执行来提高处理效率\n数据库操作行为规范 过大数据的（100万）批量写操作要分批多次操作 1.大批量操作可能会导致严重的主从延迟 2. binlog日志为row格式时会产生大量的日志 大批量写操作会产生大量日志，特别是对于row格式二进制数据而言，由于在row格式中会记录每一行数据的修改，我们一次修改的数据越多， 产生的日志量也就会越多，日志的传输和恢复所需要的时间也就越长，这也是造成主从延迟的一个原因 3. 避免产生大事务操作 大批量修改数据，一定是在一个事务中进行的，这就会造成表中大批量数据进行锁定，从而导致大量的阻塞，阻塞会对MySQL的性能产生非常大的影响 特别是长时间的阻塞会占满所有数据库的可用连接，这会使生产环境中的其他应用无法连接到数据库，因此一定要注意大批量写操作要进行分批 4.对于大表的修改使用pt-online-schema-change 1.原理： 会在原表的结构上建造一个新表 复制数据 2.避免延迟，修改时锁表 5.禁止super权限滥用 6.数据账号连接最小\n","permalink":"https://lyj404.github.io/blog/post/database-design-specifications/","summary":"\u003ch1 id=\"数据库设计规范\"\u003e数据库设计规范\u003c/h1\u003e\n\u003ch2 id=\"数据库名称规范\"\u003e数据库名称规范\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e所有的数据库对象名称必须使用小写字母并用下划线分割（mysql数据库大小写敏感）\n例如: 不同的数据库名  DbName dbname 不同的表名 Table table taBLe\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e所有数据库对象名称禁止使用mysql保留关键字\n例如 ：select id , username , from ,age from table_user 会造成mysql 歧义\u003c/p\u003e","title":"数据库设计规范"},{"content":"JWT 是 JSON Web Token 的缩写，是一种用于在网络应用环境间传递安全信息的简洁的、URL安全的表述性声明规范。JWT 可以被用来在不同的应用程序之间传递声明，例如用户身份信息、授权信息等。\nJWT的工作原理 服务端生成一个JWT令牌，并在其中包含需要传递的声明。 服务端将JWT令牌发送给客户端。 客户端在向服务端发送请求时，将JWT令牌包含在请求头中。 服务端验证JWT令牌的有效性和完整性，并根据声明做出相应的授权策略。 JWT的优势 简洁明了：JWT使用JSON格式，易于阅读和理解 URL安全：JWT可以安全的嵌入在URL中，而不会泄露敏感信息 可扩展性：JWT可以扩展以支持新的声明和功能。 JWT的应用场景 用户身份验证：JWT可以用来在不同的应用程序之间传递用户身份信息，列入在单点登录(SSO)场景中。 授权：JWT可以用来在不同的应用程序之间传递授权信息，例如允许用户访问特定的资源。 信息交换：JWT可以用来在不同的应用程序之间安全的交换信息。 JWT组成 JWT有三部分组成：\n头部（header）：头部通常包含两个字段： 类型（typ）： 表示令牌的类型，通常为“JWT” 签名算法： 表示签名算法，例如\u0026quot;HS256\u0026quot;、\u0026ldquo;RS256\u0026rdquo; 等 载荷（Payload）: 载荷包含需要传递的声明，例如用户身份信息、授权信息等。声明可以是任何 JSON 对象，例如： 1 2 3 4 5 6 7 { \u0026#34;iss\u0026#34;: \u0026#34;issuer\u0026#34;, \u0026#34;sub\u0026#34;: \u0026#34;subject\u0026#34;, \u0026#34;aud\u0026#34;: \u0026#34;audience\u0026#34;, \u0026#34;exp\u0026#34;: 1577836800, \u0026#34;iat\u0026#34;: 1577836000 } 签名（Signature）: 签名是用于验证令牌真实性和完整性的哈希值。它是使用头部和载荷以及一个密钥生成的。 JWT的作用主要是防止信息被篡改，而不是为了加密信息被人无法破解。 防止篡改： 由于签名是包含在令牌中的，并且是使用秘钥生成的，因此任何对令牌内容的修改都会导致签名验证失败。 防止伪造： 由于签名是使用服务器端的秘钥生成的，因此只有服务器端才能生成有效的令牌。 JWT 的头部和载荷是未加密的，因此任何人都可以查看其中的内容。即使签名是加密的，但签名算法的密钥也是可以被破解的。\nJWT常用于在网络应用环境间传递安全信息。JWT 可以防止信息被篡改，但并不是为了加密信息让人无法破解。\n","permalink":"https://lyj404.github.io/blog/post/jwt-description/","summary":"\u003cp\u003eJWT 是 JSON Web Token 的缩写，是一种用于在网络应用环境间传递安全信息的简洁的、URL安全的表述性声明规范。JWT 可以被用来在不同的应用程序之间传递声明，例如用户身份信息、授权信息等。\u003c/p\u003e","title":"JWT(JSON WEB TOKEN)"},{"content":"基本配置 命令行配置软件源 禁用原有的软件源 1 sudo zypper mr -da openSuSe Tumbleweed更换清华软件源 1 2 sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/tumbleweed/repo/oss/\u0026#39; mirror-oss sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/tumbleweed/repo/non-oss/\u0026#39; mirror-non-oss openSUSE Leap15.2或更高版本更换清华软件源\n1 2 3 4 sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/distribution/leap/$releasever/repo/oss/\u0026#39; mirror-oss sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/distribution/leap/$releasever/repo/non-oss/\u0026#39; mirror-non-oss sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/update/leap/$releasever/oss/\u0026#39; mirror-update sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/update/leap/$releasever/non-oss/\u0026#39; mirror-update-non-oss Leap 15.3 用户还需添加 sle 和 backports 源\n1 2 sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/update/leap/$releasever/sle/\u0026#39; mirror-sle-update sudo zypper ar -cfg \u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/update/leap/$releasever/backports/\u0026#39; mirror-backports-update Leap 15.3 注：若在安装时没有启用在线软件源，sle 源和 backports 源将在系统首次更新后引入，请确保系统在更新后仅启用了六个所需软件源。可使用 zypper lr 检查软件源状态，并使用 zypper mr -d 禁用多余的软件源\n添加中国软件源 1 sudo zypper addrepo \u0026#39;https://download.opensuse.org/repositories/home:/opensuse_zh/openSUSE_Tumbleweed\u0026#39; openSUSE_zh 中国软件源包含WPS、网易云音乐等\n刷新软件源 1 sudo zypper ref 常用zypper命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # 查看帮助 zypper --help #更新本地软件包列表，请将 \u0026lt;package\u0026gt; 替换为相应的软件包包名 zypper refresh zypper ref #简短版本 #安装新软件包 zypper install \u0026lt;package\u0026gt; zypper in \u0026lt;package\u0026gt; #删除一个软件包 zypper remove \u0026lt;package\u0026gt; zypper rm \u0026lt;package\u0026gt; # 卸载程序，注意添加 --clean-deps 或者 -u，否则不会卸载依赖项！ zypper remove --clean-deps \u0026lt;package-name\u0026gt; #升级一个软件包 zypper update \u0026lt;package\u0026gt; zypper up \u0026lt;package\u0026gt; #升级全部软件包（Tumbleweed） zypper dist-upgrade zypper dup #搜索软件包 zypper search \u0026lt;keyword\u0026gt; zypper se \u0026lt;keyword\u0026gt; # 查找本地安装的程序 zypper search --installed-only \u0026lt;package-name\u0026gt; #列出全部的软件源 zypper lr -P #删除软件的同时清楚软件依赖 sudo zypper rm \u0026lt;package\u0026gt; --clean-deps # 将 \u0026lt;package\u0026gt; 替换为你要删除软件包的包名 sudo zypper rm -u \u0026lt;package\u0026gt; #自动删除要卸载的软件包后不再需要的依赖项。 #添加rpm源 rpm -ivh \u0026lt;packname\u0026gt;.rpm #删除rpm源 rpm -e \u0026lt;packname\u0026gt;.rpm 更新系统 openSUSE Tumbleweed更新系统的命令： 1 sudo zypper dup 1 2 # opensuse leap更新系统的命令 sudo zypper update 查看哪些服务需要重启 1 sudo zypper ps -s 重启电脑 1 reboot Flatpak Flatpak 是一款用于 Linux 软件部署和软件包管理的工具。它为软件提供了一个沙箱环境，在这个环境中，用户可以在与系统其他部分隔离的情况下运行应用软件 安装Flatpak\n1 sudo zypper in flatpak 添加 Flatpak 仓库\n1 flatpak remote-add --if-not-exists flathub [https://flathub.org/repo/flathub.flatpakrepo](https://flathub.org/repo/flathub.flatpakrepo) Flatpak基本命令 搜索软件\n1 flatpak search [软件名称] #如： flatpak search atom 安装软件\n1 flatpak install [软件名称] #如： flatpak install Atom 运行软件\n1 flatpak run [应用 ID] #如： flatpak run io.atom.Atom 查看已安装软件\n1 flatpak list 卸载软件\n1 flatpak uninstall [软件名称] 将Flatpak软件安装到用户目录\n1 flatpak --user install flathub io.github.xiaoyifang.goldendict_ng 国产软件\n1 2 3 4 5 6 WPS： com.wps.Office QQ： com.qq.QQ 网易云音乐： com.netease.CloudMusic QQ Music： com.qq.QQmusic Icalingua++： io.github.Icalingua.Icalingua Wemeet： com.tencent.wemeet AppImage AppImage是一种在 Linux 系统中用于分发便携式软件而不需要超级用户权限来安装它们的格式。 AppImageLauncher可以AppImage集成到应用程序启动器 安装\u0026amp;\u0026amp;使用\n从 GitHub 下载 在终端，移动到 AppImageLauncher 的下载目录，运行 sudo zypper in appimagelauncher-2.2.0-gha111.d9d4c73.x86_64.rpm 即可安装 AppImageLauncher。 打开应用程序启动器，打开 AppImageLauncher Settings，点击 appImageLauncherd，添加你常用于存放 AppImage文件的目录。默认的目录是 ~/Applications。 AppImageLauncher 会自动识别存放在指定目录的AppImage文件，然后将它们添加到应用程序启动器中。 使终端走代理 安装 proxychains-ng\n1 sudo zypper in proxychains-ng 编辑配置文件\n1 sudo nano /etc/proxychains.conf proxychains.conf文件默认有一个socks4的配置，需要注释，不然会冲突\n1 2 3 # 将port更换成代理软件的端口 http 127.0.0.1 port socks5 127.0.0.1 port 使用代理\n1 proxychains4 \u0026lt;你的命令行\u0026gt; 安装字体 1 2 # 使用命令安装 sudo zypper search font 手动安装，直接下载Nerd Font字体 FiraCode Nerd Font 对于下载之后的字体解压，并复制到/usr/share/fonts/目录下\n/usr/share/fonts/是系统字体目录，~/.local/share/fonts/是用户字体目录，/usr/share/X11/fonts/是x11字体目录 刷新字体\n1 fc-cache -fv Zsh 安装zsh 1 sudo zypper in zsh 下载oh-my-zsh 1 git clone https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh 使用oh-my-zsh配置 1 cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc 将zsh设为默认shell 1 chsh -s /bin/zsh 配置~/.zshrc文件，使得全局命令生效 1 export PATH=$HOME/bin:/usr/local/bin:/sbin:/usr/sbin:$PATH 安装主题美化 1 git clone https://hub.fastgit.org/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k 设置主题是提示readonly，使用以下命令sudo chown lyj:lyj ~/.zshrc\n命令提示插件 1 git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 语法高亮插件 1 2 #安装插件 git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting 修改配置文件~/.zshrc 1 2 3 4 5 6 7 plugins=( git # other plugins... zsh-autosuggestions zsh-syntax-highlighting extract ) zsh报not found theme powerlevel10k之类的信息，打开~/.zshrc文件，设置ZSH_THEME=\u0026quot;powerlevel10k/powerlevel10k\u0026quot;\nNvidia显卡驱动 添加Nvidia软件源\n1 2 3 4 # Leap的显卡软件源 sudo zypper addrepo --refresh \u0026#39;https://download.nvidia.com/opensuse/leap/$releasever\u0026#39; NVIDIA # Tumbleweed的显卡软件源 sudo zypper addrepo --refresh https://download.nvidia.com/opensuse/tumbleweed NVIDIA 获取架构信息\n1 lspci | grep VGA 驱动版本:\nGeForce 700 系列（Kepler）及更高版本，即 NVIDIA 于 2013 年 4 月后发售的显卡，安装nvidia-video-G06 GeForce 600 系列，安装x11-video-nvidiaG05 GTX4xx/5xx Fermi 系列，安装x11-video-nvidiaG04 安装显卡驱动 1 zypper install nvidia-video-G06 重启电脑reboot 使用nvidia-smi命令\n1 zypper install nvidia-compute-utils-G06 对于同时用于核显和独享的电脑，显卡驱动安装之后重启电脑可能会黑屏，使用Ctrl + Alt +F3进入命令行界面\n切换显卡\n1 2 3 4 # 切换独享 sudo prime-select nvidia # 切换核显 sudo prime-select intel 电脑黑屏能看见鼠标使用prime-select boot intel\n注销电脑\n1 reboot 开发配置 Git 1 2 # 安装GIT sudo zypper install git Git配置\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 设置git代理,只对github.com使用代理，其他仓库不走代理： git config --global http.https://github.com.proxy http://127.0.0.1:2080 git config --global https.https://github.com.proxy http://127.0.0.1:2080 取消github代理： git config --global --unset http.https://github.com.proxy git config --global --unset https.https://github.com.proxy # 添加个人信息： git config --global user.email \u0026#34;2063074967@qq.com\u0026#34; # 改成你的邮箱 git config --global user.name \u0026#34;lyj\u0026#34; # 改成你的名字 # 记住密码 git config --global credential.helper store # GitHub生成秘钥 ssh-keygen -t rsa -b 4096 -C \u0026#34;lyj404@qq.com\u0026#34; # 创建或编辑 ~/.ssh/config 文件，添加以下内容，来简化ssh认证流程 IdentityFile ~/.ssh/id_rsa # 指定了默认的GitHub私钥文件位置 # 或者通过将密钥添加到 SSH 代理来简化认证流程 eval \u0026#34;$(ssh-agent -s)\u0026#34; ssh-add ~/.ssh/id_rsa VSCode 1 2 3 4 5 6 7 8 #导入密钥 sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc #添加软件源 sudo zypper addrepo https://packages.microsoft.com/yumrepos/vscode vscode #刷新软件源 sudo zypper refresh #安装vscode sudo zypper install code Nodejs 在/etc/profile.d目录下创建一个配置文件\n1 sudo touch configuration.sh 配置node环境变量,在configuration.sh下添加以下配置\n1 2 3 4 #set nodejs enviroment NODEJS_HOME=/home/lyj/environment/node-v18.18.2-linux-x64 PATH=$PATH:$NODEJS_HOME/bin export NODEJS_HOME PATH 更新配置\n1 source configuration.sh 切换淘宝镜像源\n1 npm config set registry http://registry.npmmirror.com Java 下载java，配置环境变量/etc/profile.d/configuration.sh\n1 2 3 4 5 #set java environment JAVA_HOME=/home/lyj/environment/Java/jdk-17.0.9 CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar PATH=$PATH:$JAVA_HOME/bin export JAVA_HOME CLASSPATH PATH Maven 下载Maven，配置环境变量/etc/profile.d/configuration.sh\n1 2 3 4 #set maven enviroment MAVEN_HOME=/home/lyj/environment/apache-maven-3.9.5 PATH=$PATH:$MAVEN_HOME/bin export MAVEN_HOME PATH 设置阿里云镜像/home/lyj/environment/apache-maven-3.9.5/conf/settings.xml\n1 2 3 4 5 6 \u0026lt;mirror\u0026gt; \u0026lt;id\u0026gt;aliyunmaven\u0026lt;/id\u0026gt; \u0026lt;mirrorOf\u0026gt;*\u0026lt;/mirrorOf\u0026gt; \u0026lt;name\u0026gt;阿里云公共仓库\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;https://maven.aliyun.com/repository/public\u0026lt;/url\u0026gt; \u0026lt;/mirror\u0026gt; 设置本地仓库位置\n1 \u0026lt;localRepository\u0026gt;/home/lyj/environment/repo\u0026lt;/localRepository\u0026gt; MySQL 下载MySQL软件源 mysql80-community-release-sl15-8.noarch.rpm 添加MySQL软件源\n1 sudo zypper in ./mysql80-community-release-sl15-8.noarch.rpm 开始安装MySQL\n1 sudo zypper in mysql-community-server 启动MySQL\n1 2 3 4 # 开启MySQL服务 systemctl start mysql # 查看MySQL服务状态 systemctl status mysql 查看MySQL默认密码\n1 sudo grep \u0026#39;temporary password\u0026#39; /var/log/mysql/mysqld.log 登录MySQL并修改密码\n1 2 3 4 5 6 7 8 9 10 # 登录 mysql -u root -p # 设置一个符合安全策略的密码 ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;Root_12root\u0026#39;; # 调整密码等级 set global validate_password.policy=0; # 调整密码长度 set global validate_password.length=1; # 修改密码 ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;123456\u0026#39;; MongoDB 导入MongoDB公钥\n1 sudo rpm --import https://www.mongodb.org/static/pgp/server-7.0.asc 添加 MongoDB 的软件源\n1 2 sudo zypper install zypper-migration-plugin sudo zypper ar -f https://repo.mongodb.org/zypper/suse/15/mongodb-org/4.4/x86_64/ mongodb 刷新软件源并安装软件\n1 2 sudo zypper ref sudo zypper in mongodb-org 启动 MongoDB 服务\n1 systemctl start mongod 设置开机自启\n1 systemctl enable mongod 修改MongoDB设置\n编辑 MongoDB 配置文件 1 sudo nano /etc/mongod.conf 在配置文件中找到 bindIp 项，并将其修改为 0.0.0.0，这将使 MongoDB 监听所有可用 IP 地址 重启MongoDB服务sudo systemctl restart mongod 修改防火墙设置 1 2 3 4 5 6 # 查看防火墙对应的端口是否开启 sudo firewall-cmd --query-port=27017/tcp # 打开防火墙对应的端口 sudo firewall-cmd --add-port=27017/tcp --permanent # 重新加载防火墙规则 sudo firewall-cmd --reload 使用Navicat连接MongoDB报错如下\n1 2 Cannot connect to MongoDB. No suitable servers found: serverSelectionTimeoutMS\u0026#39; expired: (TLS handshake failed: error00000000:lb(0):func(0):reason(0) 解决方法在Navicat中取消使用SSL选项\nPostgreSQL 添加软件源并刷新\n1 2 sudo zypper addrepo https://download.postgresql.org/pub/repos/zypp/repo/pgdg-sles-15-pg17-devel.repo sudo zypper ref 安装软件\n1 sudo zypper in postgresql15-server 初始化数据库并设置密码\n1 2 3 4 5 6 7 8 # 使用root用户身份切换到postgres用户 sudo su - postgres # 初始化PostgreSQL, -D 参数用于指定新的数据库目录 initdb -D /var/lib/pgsql/data # 设置密码 psql -c \u0026#34;ALTER USER postgres WITH PASSWORD \u0026#39;123456\u0026#39;;\u0026#34; # 退出postgres用户 exit 启动PostgreSQL或设置开机自启\n1 2 3 4 # 启动PostgreSQL sudo systemctl start postgresql # 设置开机自启 sudo systemctl enable postgresql 设置远程连接\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 修改postgresql.conf sudo nano /var/lib/pgsql/data/postgresql.conf # 新增listen_addresses参数，默认只允许本地连接，设置为允许来自任何 IP 地址的连接 listen_addresses = \u0026#39;*\u0026#39; # 修改pg_hba.conf sudo nano /var/lib/pgsql/data/pg_hba.conf # 在文件末尾添加配置，运行所有IP连接 host all all 0.0.0.0/0 md5 # 重新启动PostgreSQL服务以使更改生效 sudo systemctl restart postgresql # 查找防火墙是否开放PostgreSQL对应的端口，yes是开放，no是未开放 sudo firewall-cmd --query-port=5432/tcp # 让防火墙开放PostgreSQL对应的端口 sudo firewall-cmd --add-port=5432/tcp --permanent # 重新加载防火墙规则 sudo firewall-cmd --reload Docker 安装docker和docker-compose\n1 zypper install docker python3-docker-compose 设置开机自启动\n1 sudo systemctl enable docker Lazygit openSUSE Tumbleweed openSUSE Leap\n1 2 3 source /etc/os-release sudo zypper ar https://download.opensuse.org/repositories/devel:/languages:/go/$VERSION_ID/devel:languages:go.repo sudo zypper ref \u0026amp;\u0026amp; sudo zypper in lazygit Jetbrains产品光标不跟随的问题 JetBrainsRuntime-for-Linux-x64\n工具安装 Flameshot(截图工具) 安装\n1 2 3 sudo zypper in flameshot # 使用flatpak安装 flatpak install flathub org.flameshot.Flameshot 配置快捷键（KDE Plasma）\n获取配置文件 1 wget https://raw.githubusercontent.com/flameshot-org/flameshot/master/docs/shortcuts-config/flameshot-shortcuts-kde.khotkeys 禁用默认的截图快捷键 打开 KDE 设置，找到 快捷键 ，点击 快捷键 ，然后在右侧菜单栏中删除 Spectale\n导入设置 在原页面中，点击左侧的 自定义快捷键 ，再点击 编辑 ，然后点击 导入，保存，并注销重新登陆。\n系统急救 按Ctrl + Alt + F1进入内核终端页面 按Ctrl + Alt + F3进入命令行界面 按Ctrl + Alt + F7切换会桌面环境\nkonsole设置方案出错之后可以使用以下命令恢复默认配置:\n1 mv ~/.config/konsolerc ~/.config/konsolerc_backup OpenSUSE常用网址 查找软件 OpenSUSE software OpenSUSE wiki OpenSUSE wiki\n","permalink":"https://lyj404.github.io/blog/post/opensuse-configuration/","summary":"\u003ch1 id=\"基本配置\"\u003e基本配置\u003c/h1\u003e\n\u003ch2 id=\"命令行配置软件源\"\u003e命令行配置软件源\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e禁用原有的软件源\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper mr -da\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003col start=\"2\"\u003e\n\u003cli\u003eopenSuSe Tumbleweed更换清华软件源\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper ar -cfg \u003cspan class=\"s1\"\u003e\u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/tumbleweed/repo/oss/\u0026#39;\u003c/span\u003e mirror-oss\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper ar -cfg \u003cspan class=\"s1\"\u003e\u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/tumbleweed/repo/non-oss/\u0026#39;\u003c/span\u003e mirror-non-oss\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eopenSUSE Leap15.2或更高版本更换清华软件源\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper ar -cfg \u003cspan class=\"s1\"\u003e\u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/distribution/leap/$releasever/repo/oss/\u0026#39;\u003c/span\u003e mirror-oss\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper ar -cfg \u003cspan class=\"s1\"\u003e\u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/distribution/leap/$releasever/repo/non-oss/\u0026#39;\u003c/span\u003e mirror-non-oss\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper ar -cfg \u003cspan class=\"s1\"\u003e\u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/update/leap/$releasever/oss/\u0026#39;\u003c/span\u003e mirror-update\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zypper ar -cfg \u003cspan class=\"s1\"\u003e\u0026#39;https://mirrors.tuna.tsinghua.edu.cn/opensuse/update/leap/$releasever/non-oss/\u0026#39;\u003c/span\u003e mirror-update-non-oss\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eLeap 15.3 用户还需添加 sle 和 backports 源\u003c/p\u003e","title":"OpenSUSE配置"},{"content":"This is lyj\u0026rsquo;s blog\n","permalink":"https://lyj404.github.io/blog/about/","summary":"about","title":"关于"}]