diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 1a8ce85..62cb5f1 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -4,19 +4,15 @@ "type": "split", "children": [ { - "id": "45758c4f6e50afca", + "id": "fba6ba71c6e0e7b2", "type": "tabs", "children": [ { - "id": "048751669f9281f8", + "id": "09dba63fa2c08f99", "type": "leaf", "state": { - "type": "markdown", - "state": { - "file": "HomeBrew/Mac OS 神器 HomeBrew.md", - "mode": "source", - "source": false - } + "type": "empty", + "state": {} } } ] @@ -49,7 +45,7 @@ "type": "search", "state": { "query": "", - "matchingCase": false, + "matchingCase": true, "explainSearch": false, "collapseAll": false, "extraContext": false, @@ -85,7 +81,7 @@ "state": { "type": "backlink", "state": { - "file": "HomeBrew/Mac OS 神器 HomeBrew.md", + "file": "面试题资料.pdf", "collapseAll": false, "extraContext": false, "sortOrder": "alphabetical", @@ -102,7 +98,7 @@ "state": { "type": "outgoing-link", "state": { - "file": "HomeBrew/Mac OS 神器 HomeBrew.md", + "file": "面试题资料.pdf", "linksCollapsed": false, "unlinkedCollapsed": true } @@ -125,7 +121,7 @@ "state": { "type": "outline", "state": { - "file": "HomeBrew/Mac OS 神器 HomeBrew.md" + "file": "面试题资料.pdf" } } } @@ -146,12 +142,46 @@ "command-palette:打开命令面板": false } }, - "active": "048751669f9281f8", + "active": "8afd0c6afa6fd8cc", "lastOpenFiles": [ - "Docker 2375端口开启外网访问.md", - "软件测试/软件测试-安全测试.md", - "软件测试/软件测试-黑白盒测试.md", - "软件测试/软件测试-软测基础.md", - "软件测试/软件测试-自动化测试.md" + "青空笔记/Java设计模式笔记/Java设计模式(四).md", + "面试题资料.pdf", + "青空笔记/Java设计模式笔记/Java设计模式(三).md", + "数据库系统原理/单元复习资料/数据库复习3.md", + "数据库系统原理/单元复习资料/数据库第二章.png", + "数据库系统原理/练习题/第二次上机.md", + "数据库导论/笔记/数据库笔记05——查询数据——2022-03-17.md", + "数据库导论/笔记/数据库笔记06——视图——2022-04-05.md", + "数据库系统原理/练习题/第四次上级.md", + "数据库系统原理/单元复习资料/数据库复习4.md", + "数据库导论/笔记/数据库笔记01——数据库使用笔记.md", + "教程/网站面板/aaPanel-宝塔面板国际版安装教程(宝塔海外版).md", + "Activti 流程管理.md", + "面试/面试/Redis面试题-参考回答.md", + "面试/面试/消息中间件面试题-参考回答.md", + "面试/面试/框架篇面试题-参考回答.md", + "面试/面试/多线程相关面试题.md", + "面试/面试/Java集合相关面试题.md", + "六大设计模式.md", + "面试/面试/微服务面试题-参考回答.md", + "面试/面试/常见技术场景.md", + "面试/HashMap/HashMap集合(高级).md", + "面试/面试/img/image-20230521233600556.png", + "面试/面试/img/线程池的执行原理.jpg", + "面试/面试/img/简单工厂.jpg", + "面试/面试/img/image-20230521233926897.png", + "面试/面试/img/image-20230521233715574.png", + "面试/面试/img/image-20230521233934644.png", + "面试/面试/img/image-20230521233554657.png", + "面试/面试/img/image-20230521233150276.png", + "面试/面试/img/image-20230521233220905.png", + "面试/面试/设计模式.md", + "面试/面试/img", + "面试/面试/MySQL面试题-参考回答.md", + "面试/面试/JVM相关面试题.md", + "面试/面试", + "开发工具/Docker基本命令.md", + "HomeBrew/Mac OS 神器 HomeBrew.md", + "Docker 2375端口开启外网访问.md" ] } \ No newline at end of file diff --git a/Activti 流程管理.md b/Activti 流程管理.md index 174e449..131661d 100644 --- a/Activti 流程管理.md +++ b/Activti 流程管理.md @@ -1,9 +1,5 @@ # Activti流程管理 - - - - ```java Activti提供的主要的接口服务,其中主要用到 diff --git a/docker.md b/docker.md deleted file mode 100644 index 7772378..0000000 --- a/docker.md +++ /dev/null @@ -1,25 +0,0 @@ -docker镜像配置 -``` - -{ - "builder": { - "gc": { - "defaultKeepStorage": "20GB", - "enabled": true - } - }, - "experimental": false, - "features": { - "buildkit": true - }, - "registry-mirrors": [ - "https://9kkc1zdn.mirror.aliyuncs.com" - ] -} -``` - -恢复docekr数据 -``` -docker load -i all_images.tar - -``` \ No newline at end of file diff --git a/安装环境/CentOS 国内镜像源 x86-64.md b/安装环境/CentOS 国内镜像源 x86-64.md new file mode 100644 index 0000000..363c604 --- /dev/null +++ b/安装环境/CentOS 国内镜像源 x86-64.md @@ -0,0 +1,71 @@ +CentOS 配置国内 yum 源 + +## CentOS 7 x86-64 + +```shell +CentOS 7 x86-64 + +//进入root,切换至yum.repos.d目录 +cd /etc/yum.repos.d/ + +//创建新文件夹并将源文件备份为repo.bak +mkdir backup && mv *repo backup/ + +//下载国内yum源文件 +wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo + +//163 +##wget http://mirrors.163.com/.help/CentOS7-Base-163.repo + +//生成缓存 +yum clean all && yum makecache +``` + +## CentOS Stream 8 x86-64 + +````shell +CentOS Stream 8 x86-64 + +//进入root,切换至yum.repos.d目录 +cd /etc/yum.repos.d/ + +//创建新文件夹并将源文件备份为repo.bak +mkdir backup && mv *repo backup/ + +//下载国内yum源文件 +curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo + +//更新下载yum源地址 +sed -i -e"s|mirrors.cloud.aliyuncs.com|mirrors.aliyun.com|g " /etc/yum.repos.d/CentOS-* +sed -i -e "s|releasever|releasever-stream|g" /etc/yum.repos.d/CentOS-* + +//生成缓存 +yum clean all && yum makecache +```` + +## CentOS Stream 9 x86-64 + +```shell +CentOS Stream 9 x86-64 + +//进入root,切换至yum.repos.d目录 +cd /etc/yum.repos.d/ + +//创建新文件夹并将源文件备份为repo.bak +mkdir backup && mv *repo backup/ + +//下载国内yum源文件 +sed -i 's|metalink|#metalink|g' /etc/yum.repos.d/*.repo + +sed -i '/name=CentOS Stream $releasever - BaseOS/a baseurl=https://mirrors.aliyun.com/centos-stream/$stream/BaseOS/$basearch/os/' /etc/yum.repos.d/*.repo + +sed -i '/name=CentOS Stream $releasever - AppStream/a baseurl=https://mirrors.aliyun.com/centos-stream/$stream/AppStream/$basearch/os/' /etc/yum.repos.d/*.repo + +sed -i '/name=CentOS Stream $releasever - Extras packages/a baseurl=https://mirrors.aliyun.com/centos-stream/SIGs/$stream/extras/$basearch/extras-common/' /etc/yum.repos.d/*.repo + +//生成缓存 +yum clean all && yum makecache +``` + + + diff --git a/安装环境/Docker-Compose/Docker compose 安装Gitea.md b/安装环境/Docker-Compose/Docker compose 安装Gitea.md new file mode 100644 index 0000000..0befd8c --- /dev/null +++ b/安装环境/Docker-Compose/Docker compose 安装Gitea.md @@ -0,0 +1,60 @@ +首先检查docker-compose的安装情况 + +```shell +docker-compose -v +``` + +创建Docker-Compose + +```shell +cd /var/lib +mkdir docker-compose +``` + +编辑docker-compose文件 + +```shell +vi docker-compose.yml +``` + +在文件夹下编写`docker-compose.yml`文件 + +```yml +version: "3" + +networks: + gitea: + external: false + +services: + server: + image: gitea/gitea:1.20.5 + container_name: gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + restart: always + networks: + - gitea + volumes: + - ./gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "10800:3000" + - "2222:22" +``` + +启动docker-compose + +```shell +docker-compose up -d +``` + +检查运行情况 + +```shell +docker ps +docker-compose ps +``` + diff --git a/安装环境/Docker-Compose/Docker-Compose 安装 Nginx Proxy Manager.md b/安装环境/Docker-Compose/Docker-Compose 安装 Nginx Proxy Manager.md new file mode 100644 index 0000000..a966a33 --- /dev/null +++ b/安装环境/Docker-Compose/Docker-Compose 安装 Nginx Proxy Manager.md @@ -0,0 +1,40 @@ +首先检查docker-compose的安装情况 + +```shell +docker-compose -v +``` + +在文件夹下编写`docker-compose.yml`文件 + +```yml +version: '3.8' +services: + app: + image: 'jc21/nginx-proxy-manager:latest' + restart: unless-stopped + ports: + - '80:80' + - '81:81' + - '443:443' + volumes: + - ./data:/data + - ./letsencrypt:/etc/letsencrypt +``` + +启动docker-compose + +```shell +docker-compose up -d +``` + +检查运行情况 + +```shell +docker ps +docker-compose ps +``` + +``` +admin@example.com +changeme +``` \ No newline at end of file diff --git a/HomeBrew/Git.md b/安装环境/HomeBrew/Git.md similarity index 100% rename from HomeBrew/Git.md rename to 安装环境/HomeBrew/Git.md diff --git a/HomeBrew/Mac OS 神器 HomeBrew.md b/安装环境/HomeBrew/Mac OS 神器 HomeBrew.md similarity index 100% rename from HomeBrew/Mac OS 神器 HomeBrew.md rename to 安装环境/HomeBrew/Mac OS 神器 HomeBrew.md diff --git a/HomeBrew/Node.md b/安装环境/HomeBrew/Node.md similarity index 100% rename from HomeBrew/Node.md rename to 安装环境/HomeBrew/Node.md diff --git a/HomeBrew/smartctl 硬盘读写查看.md b/安装环境/HomeBrew/smartctl 硬盘读写查看.md similarity index 100% rename from HomeBrew/smartctl 硬盘读写查看.md rename to 安装环境/HomeBrew/smartctl 硬盘读写查看.md diff --git a/安装环境/K8s/Centos 9单节点安装K8s、KubeSphere(arm64).md b/安装环境/K8s/Centos 9单节点安装K8s、KubeSphere(arm64).md new file mode 100644 index 0000000..78ca4bf --- /dev/null +++ b/安装环境/K8s/Centos 9单节点安装K8s、KubeSphere(arm64).md @@ -0,0 +1,312 @@ +# Centos 9单节点安装kubernetes、KubeSphere(arm64) + +[TOC] + +首先说一下什么使用centos 7 ,因为我的mac PD虚拟机安装不上包括centos Stream 8,为此只能换到centos Stream 9 了,我的云服务上跑的都是Centos 7,暂时用不惯 Ubuntu 。KubeSphere最新版本中部分组件不支持arm64,在后面的启动可查包组件中会提到如何解决。 + +下面是我搭建版本以及环境版本 + +| | | +| ---------- | ------------------------ | +| 操作系统 | Centos Stream 9 | +| 系统配置 | 4核16GB内存 64GB磁盘空间 | +| kubernetes | 1.22.12 | +| kubesphere | 3.4.0 | + +## 环境需求配置 + +### 官方支持的操作系统及最低配置 + +| 操作系统 | 最低配置 | +| :----------------------------------------------------- | :---------------------------------- | +| **Ubuntu** *16.04*, *18.04*, *20.04*, *22.04* | 2 核 CPU,4 GB 内存,40 GB 磁盘空间 | +| **Debian** *Buster*, *Stretch* | 2 核 CPU,4 GB 内存,40 GB 磁盘空间 | +| **CentOS** *7.x* | 2 核 CPU,4 GB 内存,40 GB 磁盘空间 | +| **Red Hat Enterprise Linux 7** | 2 核 CPU,4 GB 内存,40 GB 磁盘空间 | +| **SUSE Linux Enterprise Server 15/openSUSE Leap 15.2** | 2 核 CPU,4 GB 内存,40 GB 磁盘空间 | + +### 以下是支持的容器版本 + +| 支持的容器 | 版本 | +| :---------------------------- | :------- | +| Docker | 19.3.8 + | +| containerd | 最新版 | +| CRI-O(试验版,未经充分测试) | 最新版 | +| iSula(试验版,未经充分测试) | 最新版 | + +### Kubernetes依赖 + +| 依赖项 | Kubernetes 版本 ≥ 1.18 | Kubernetes 版本 < 1.18 | +| :-------- | :--------------------- | :--------------------- | +| socat | 必须 | 可选但建议 | +| conntrack | 必须 | 可选但建议 | +| ebtables | 可选但建议 | 可选但建议 | +| ipset | 可选但建议 | 可选但建议 | + +## 配置软件依赖 + +### 设置服务器hostname名称 + +```shell +#查看主机名称 +hostname +#更改名称 +hostnamectl --static set-hostname master +#查看主机名称核验 +hostnamectl status +#可以重启一下服务器 +#reboot +``` + +### 关闭防火墙 + +```shell +systemctl disable firewalld +systemctl stop firewalld +systemctl status firewalld +``` + +### 关闭swap分区 + +```shell +swapoff -a +echo "vm.swappiness=0" >> /etc/sysctl.conf +sysctl -p /etc/sysctl.conf +``` + +### 配置epel源(国内服务器Centos7) + +```shell +rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm +``` + +### Centos Stream 9更换了dnf包管理器 + +> 首先,需要安装extra包,并确保已安装了centos-release-stream包: +> +> ```shell +> sudo dnf install -y centos-release-stream +> ``` +> +> 然后,使用下面的命令从EPEL存储库安装软件包: +> +> ```shell +> sudo dnf install -y epel-release +> ``` +> +> 相较于以前的CentOS 7和8版本,CentOS Stream 9使用dnf作为默认的包管理器,而不是yum。dnf包管理器相较于yum,提供了更好地性能,依赖解决和自动同步功能。 +> +> 建议在安装任何新的软件包之前,确保系统已经更新至最新状态: +> +> ```shell +> sudo dnf update -y +> ``` +> +> 注意:EPEL存储库中存在由社区提供和维护的大量软件包。将这些包添加到服务器可能会给您带来未知的安全风险。在使用来自EPEL的软件包之前,请确保您都确定其安全性。 + +### 更新yum + +```shell +yum -y update +``` + +### 安装依赖组件 + +```shell +yum install -y ebtables socat ipset conntrack +``` + +### 下载 KubeKey + +```shell +curl -sfL https://get-kk.kubesphere.io | VERSION=v3.0.10 sh - + +#访问gtihub受限制 +#export KKZONE=cn +#curl -sfL https://get-kk.kubesphere.io | VERSION=v3.0.10 sh - +``` + +### 添加kk权限 + +```shell +chmod +x kk +``` + +### 开始安装(最小化安装) + +模版 + +```shell +./kk create cluster [--with-kubernetes version] [--with-kubesphere version] +``` + +示例:同时安装 Kubernetes 和 KubeSphere + +```shell +./kk create cluster --with-kubernetes v1.22.12 --with-kubesphere v3.4.0 +``` + +- 安装 KubeSphere 3.4 的建议 Kubernetes 版本:v1.20.x、v1.21.x、v1.22.x、v1.23.x、* v1.24.x、* v1.25.x 和 * v1.26.x。带星号的版本可能出现边缘节点部分功能不可用的情况。因此,如需使用边缘节点,推荐安装 v1.23.x。如果不指定 Kubernetes 版本,KubeKey 将默认安装 Kubernetes v1.23.10。有关受支持的 Kubernetes 版本的更多信息,请参见[支持矩阵](https://www.kubesphere.io/zh/docs/v3.4/installing-on-linux/introduction/kubekey/#支持矩阵)。 +- 一般来说,对于 All-in-One 安装,您无需更改任何配置。 +- 如果您在这一步的命令中不添加标志 `--with-kubesphere`,则不会部署 KubeSphere,KubeKey 将只安装 Kubernetes。如果您添加标志 `--with-kubesphere` 时不指定 KubeSphere 版本,则会安装最新版本的 KubeSphere。 +- KubeKey 会默认安装 [OpenEBS](https://openebs.io/) 为开发和测试环境提供 LocalPV 以方便新用户。对于其他存储类型,请参见[持久化存储配置](https://www.kubesphere.io/zh/docs/v3.4/installing-on-linux/persistent-storage-configurations/understand-persistent-storage/)。 + +输入 `yes` 继续安装流程。 + +### 验证安装结果 + +```shell +kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f +``` + +输出信息会显示 Web 控制台的 IP 地址和端口号,默认的 NodePort 是 `30880`。现在,您可以使用默认的帐户和密码 (`admin/P@88w0rd`) 通过 `:30880` 访问控制台。 + +输出结果 + +```shell +##################################################### +### Welcome to KubeSphere! ### +##################################################### + +Console: http://10.211.55.30:30880 +Account: admin +Password: P@88w0rd +NOTES: + 1. After you log into the console, please check the + monitoring status of service components in + "Cluster Management". If any service is not + ready, please wait patiently until all components + are up and running. + 2. Please change the default password after login. + +##################################################### +https://kubesphere.io 2023-10-26 16:18:18 +##################################################### +``` + +检查 KubeSphere 相关组件的运行状况 + +```shell +kubectl get pod --all-namespaces +``` + +## 启用可插拔组件(可选) + +用于默认的最小化安装。若要在 KubeSphere 中启用其他组件,请参见[启用可插拔组件](https://www.kubesphere.io/zh/docs/v3.4/pluggable-components/)。 + +### KubeSphere DevOps 系统 + +基于 [Jenkins](https://jenkins.io/) 的 KubeSphere DevOps 系统是专为 Kubernetes 中的 CI/CD 工作流设计的,它提供了一站式的解决方案,帮助开发和运维团队用非常简单的方式构建、测试和发布应用到 Kubernetes。它还具有插件管理、[Binary-to-Image (B2I)](https://www.kubesphere.io/zh/docs/v3.4/project-user-guide/image-builder/binary-to-image/)、[Source-to-Image (S2I)](https://www.kubesphere.io/zh/docs/v3.4/project-user-guide/image-builder/source-to-image/)、代码依赖缓存、代码质量分析、流水线日志等功能。 + +DevOps 系统为用户提供了一个自动化的环境,应用可以自动发布到同一个平台。它还兼容第三方私有镜像仓库(如 Harbor)和代码库(如 GitLab/GitHub/SVN/BitBucket)。它为用户提供了全面的、可视化的 CI/CD 流水线,打造了极佳的用户体验,而且这种兼容性强的流水线能力在离线环境中非常有用。 + +有关更多信息,请参见 [DevOps 用户指南](https://www.kubesphere.io/zh/docs/v3.4/devops-user-guide/)。 + +#### 在安装KubeSphere后启动 DevOps + +1. 以 `admin` 用户登录控制台,点击左上角的**平台管理**,选择**集群管理**。 + +2. 点击**定制资源定义**,在搜索栏中输入 `clusterconfiguration`,点击搜索结果查看其详细页面。 + + 信息 + + 定制资源定义(CRD)允许用户在不新增 API 服务器的情况下创建一种新的资源类型,用户可以像使用其他 Kubernetes 原生对象一样使用这些定制资源。 + +3. 在**自定义资源**中,点击 `ks-installer` 右侧的更多中,选择**编辑 YAML**。 + +4. 在该 YAML 文件中,搜索 `devops`,将 `enabled` 的 `false` 改为 `true`。完成后,点击右下角的**确定**,保存配置。 + +```yaml +devops: + enabled: true # 将“false”更改为“true”。 +``` + +5.在 kubectl 中执行以下命令检查安装过程: + +```shell +kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f +``` + +#### 不兼容的pod镜像包括以下 + +| 镜像名称 | 官方提供版本 | 自测暂时可用版本 | DockerHub地址 | +| --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| minio | docker.io/minio/minio:RELEASE.2019-08-07T01-59-21Z | docker.io/minio/minio:RELEASE.2022-07-29T19-40-48Z | https://hub.docker.com/layers/minio/minio/RELEASE.2022-07-29T19-40-48Z/images/sha256-cc1067cf3d480ffc8636784bde413e4c52575da6471833fb0d3b17d38cb3cefd?context=explore | +| mc | docker.io/minio/mc:RELEASE.2019-08-07T23-14-43Z | docker.io/minio/mc:RELEASE.2022-07-29T19-17-16Z | https://hub.docker.com/layers/minio/mc/RELEASE.2022-07-29T19-17-16Z/images/sha256-5849d1feb6d60e215175a12011d0be5580a79ed4251cd0f6c824a1a24332a28a?context=explore | +| argocd_applicationset | registry.cn-beijing.aliyuncs.com/kubesphereio/argocd-applicationset:v0.4.1 | docker.io/baloisemichaelmuehlebach/argocd-applicationset:v0.4.1 | https://hub.docker.com/layers/baloisemichaelmuehlebach/argocd-applicationset/v0.4.1/images/sha256-f6a0b73b48e92c9b9977bde40c360c9b813c06845a7526469186dc7c2bb3a75f?context=explore | +| jenkins | registry.cn-beijing.aliyuncs.com/kubesphereio/ks-jenkins:v3.4.0-2.319.3-1 | docker.io/kubesphere/ks-jenkins:v3.4.1-2.319.3 | https://hub.docker.com/layers/kubesphere/ks-jenkins/v3.4.1-2.319.3/images/sha256-15a40d2da981451e903e1a59f5e6963e4291f20153eddb3d922dd965ccc1f6d3?context=explore | +| default-http-backend | registry.cn-beijing.aliyuncs.com/kubesphereio/defaultbackend-arm64:1.4 | mirrorgooglecontainers/defaultbackend-arm64:1.4 | https://hub.docker.com/layers/mirrorgooglecontainers/defaultbackend-arm/1.4/images/sha256-f02490f65b15e347661216d55b8fc0b5d7e472aa0b1ef9241be4eea880a3c594?context=explore | + +![image-20231027094147632](https://lsky.hhdxw.top/imghub/2023/10/image-202310271698370908.png) + +#### 更换方式 + +##### 1.直接在开启devops配置中设置仓库以及标签 + +直接在上文中`ks-installer`**编辑 YAML**文件中添加Docker的仓库(repository)与标签(tag)信息 + +![image-20231027102441263](https://lsky.hhdxw.top/imghub/2023/10/image-202310271698373481.png) + +```yaml +spec: + mc_tag: RELEASE.2022-07-29T19-17-16Z + mc_repo: docker.io/minio/mc + minio_repo: docker.io/minio/minio + minio_tag: RELEASE.2022-07-29T19-40-48Z + argocd_applicationset_repo: docker.io/baloisemichaelmuehlebach/argocd-applicationset + argocd_applicationset_tag:v0.4.1 + jenkins_repo: docker.io/kubesphere/ks-jenkins + jenkins_tag: v3.4.1-2.319.3 +``` + +##### 2.创建项目后,在项目中**编辑 YAML**中重新创建 + +1. 以 `admin` 用户登录控制台,点击左上角的**平台管理**,选择**集群管理**。 + +2. 点击**应用负载**下级目录**工作负载**,查看项目状态异常的项目点击进入项目。 + +3. 在**更多操作**中,选择**编辑 YAML**,找到如下内容更换完成并保存。 + + ```yaml + image: >- + registry.cn-beijing.aliyuncs.com/kubesphereio/ks-jenkins:v3.4.0-2.319.3-1 + #上述内容更换为下面,此处只举例,具体参考上面的表格 + image: 'docker.io/kubesphere/ks-jenkins:v3.4.1-2.319.3' + ``` + +4. 系统会自动启动新的副本,稍作等待副本完成后自动会删除错误的副本,如果没有启动可以在**更多操作**中,选择**重新创建**。 + +5. 另外可以在修改记录中查看修改对比记录 + + ![image-20231027102325546](https://lsky.hhdxw.top/imghub/2023/10/image-202310271698373405.png) + +#### 注意: + +本人确认正在使用上述特定版本号的服务,并确认在目前的使用过程中暂未出现问题。然而,这在任何程度上都不能被视为是对您个人使用此版本的保证或建议。 + +请您知悉,上述版本号仅供参考,并不能保证与所有的硬件、软件环境或与所有的使用场景完全兼容。在您决定使用该版本时,您需要自行评估可能存在的风险,其中可能包括但不限于功能不全、系统不稳定、可能存在的安全问题等情况。 + +对于因对我个人版本号使用情况的借鉴引发的任何形式的损失或问题,我个人以及相关的产品或服务提供者无需负担任何责任。如果您在使用过程中遇到任何问题,建议直接联系相关的产品或服务的提供者以寻求技术支持。 + +#### 附录: + +新版本的`minio`会让部分功能无法使用,`RELEASE.2022-10-29T06-21-33Z`版本以后可能不兼容。 + +![image-202310271698374117](https://lsky.hhdxw.top/imghub/2023/10/image-202310271698374117.png) + +#### 验证组件的安装 + +执行以下命令来检查容器组的状态: + +```shell +kubectl get pod -n kubesphere-devops-system +``` + +如果组件运行成功,输出结果如下: + +```shell +NAME READY STATUS RESTARTS AGE +devops-jenkins-5cbbfbb975-hjnll 1/1 Running 0 40m +s2ioperator-0 1/1 Running 0 41m +``` + diff --git a/安装环境/K8s/多节点部署Kubernetes集群.md b/安装环境/K8s/多节点部署Kubernetes集群.md new file mode 100644 index 0000000..39968b3 --- /dev/null +++ b/安装环境/K8s/多节点部署Kubernetes集群.md @@ -0,0 +1,642 @@ +# 多节点部署Kubernetes集群 + +[TOC] + +## 编辑环境 + +将主机名指向本机IP,**主机名只能包含:字母、数字、-(横杠)、.(点)** + +### 获取主机名 + +```shell +hostname +``` + +#### 临时设置主机名 + +```shell +hostname 主机名 +``` + +#### 永久设置主机名 + +```shell +sudo echo '主机名' > /etc/hostname +``` + +#### 编辑 hosts + +```shell +sudo vim /etc/hosts +``` + +```shell +10.211.55.20 k8s-node1 #集群主节点 +10.211.55.21 k8s-node2 #集群node节点 +10.211.55.19 k8s-node3 #集群node节点 +``` + +### 同步时间 + +```shell +sudo yum -y install ntpdate +sudo ntpdate ntp1.aliyun.com +sudo systemctl status ntpdate +sudo systemctl start ntpdate +sudo systemctl status ntpdate +sudo systemctl enable ntpdate +``` + +Centos9中ntp 软件包已被替换为 chrony + +```shell +sudo yum -y install chrony +sudo systemctl start chronyd +sudo systemctl enable chronyd +sudo systemctl restart chronyd +sudo chronyc makestep +date +``` + +### 安装并配置 bash-completion,添加命令自动补充 + +```shell +sudo yum -y install bash-completion +source /etc/profile +``` + +### 关闭防火墙 + +```shell +sudo systemctl stop firewalld.service +sudo systemctl disable firewalld.service +``` + +### 开通全部端口 + +```shell +firewall-cmd --zone=public --add-port=1-65535/udp --permanent +firewall-cmd --zone=public --add-port=1-65535/tcp --permanent +firewall-cmd --reload +``` + +### 打开端口转发 + +```shell +firewall-cmd --add-masquerade --permanent # 端口转发 +firewall-cmd --reload +firewall-cmd --list-all +firewall-cmd --list-all --zone=trusted +``` + +### 关闭交换空间 + +```shell +free -h +sudo swapoff -a +sudo sed -i 's/.*swap.*/#&/' /etc/fstab +free -h +``` + +### 关闭 selinux + +```shell +getenforce +cat /etc/selinux/config +sudo setenforce 0 +sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config +cat /etc/selinux/config +``` + +## 安装Docker + +### 卸载旧Docekr + +```shell +sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine +``` + +### 更新一下环境: + +``` +yum -y update +``` + +### 安装一些必要的系统工具: + +``` +sudo yum install -y yum-utils device-mapper-persistent-data lvm2 +``` + +### 添加软件源信息: + +``` +sudo yum install -y yum-utils +``` + +``` +sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #国内阿里源 +``` + +``` +sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #docker官方镜像源 +``` + +### 安装 Docker + +``` +sudo yum install docker-ce docker-ce-cli containerd.io +``` + +### 启动 Docker 后台服务 + +``` +sudo systemctl start docker +sudo systemctl enable docker +``` + +### 测试运行 hello-world + +``` +docker run hello-world +``` + +## 编辑Kubernetes环境 + +### 修改containerd + +```shell + #停止containerd + sudo systemctl stop containerd.service + + sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.bak + sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.bak + sudo containerd config default > $HOME/config.toml + sudo cp $HOME/config.toml /etc/containerd/config.toml +``` + +```shell +vi /etc/containerd/config.toml +``` + + ```shell + # 修改 /etc/containerd/config.toml 文件后,要将 docker、containerd 停止后,再启动 + sudo sed -i "s#registry.k8s.io/pause#registry.aliyuncs.com/google_containers/pause#g" /etc/containerd/config.toml +# https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/#containerd-systemd +# 确保 /etc/containerd/config.toml 中的 disabled_plugins 内不存在 cri +sudo sed -i "s#SystemdCgroup = false#SystemdCgroup = true#g" /etc/containerd/config.toml + ``` + +```shell +sudo systemctl enable --now containerd.service +# sudo systemctl status containerd.service + +# sudo systemctl status docker.service +sudo systemctl start docker.service +# sudo systemctl status docker.service +sudo systemctl enable docker.service +sudo systemctl enable docker.socket +sudo systemctl list-unit-files | grep docker +``` + +```shell +sudo mkdir -p /etc/docker + +sudo tee /etc/docker/daemon.json <<-'EOF' +{ + "registry-mirrors":[ + "https://9kkc1zdn.mirror.aliyuncs.com", + "https://hub-mirror.c.163.com/", + "https://docker.mirrors.ustc.edu.cn/", + "https://registry.docker-cn.com"], + "exec-opts": ["native.cgroupdriver=systemd"] +} +EOF +``` + +### 重启 + +```shell +sudo systemctl daemon-reload +sudo systemctl restart docker +sudo docker info + +sudo systemctl status docker.service +sudo systemctl status containerd.service +``` + +### 添加阿里云镜像仓库(注意其中电脑版本例如:aarch64、x86_64、ppc64le、armhfp、390x) + +```shell +# 文档:https://developer.aliyun.com/mirror/kubernetes + +cat < /etc/yum.repos.d/kubernetes.repo +[kubernetes] +name=Kubernetes +baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-aarch64/ +# 是否开启本仓库 +enabled=1 +# 是否检查 gpg 签名文件 +gpgcheck=0 +# 是否检查 gpg 签名文件 +repo_gpgcheck=0 +gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg + +EOF +``` + +## 安装K8s依赖 + +```shell +cat < +kube-system coredns-7bdc4cb885-rrlbq 0/1 Pending 0 71s +kube-system etcd-k8s-node1 1/1 Running 0 76s 10.211.55.20 k8s-node1 +kube-system kube-apiserver-k8s-node1 1/1 Running 0 78s 10.211.55.20 k8s-node1 +kube-system kube-controller-manager-k8s-node1 1/1 Running 0 76s 10.211.55.20 k8s-node1 +kube-system kube-proxy-64rdg 1/1 Running 0 71s 10.211.55.20 k8s-node1 +kube-system kube-scheduler-k8s-node1 1/1 Running 0 78s 10.211.55.20 k8s-node1 +``` + +```shell +kubectl get nodes -o wide +``` + +```shell +[root@localhost ~]# kubectl get nodes -o wide +NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME +k8s-node1 NotReady control-plane 2m7s v1.27.3 10.211.55.20 CentOS Stream 9 5.14.0-373.el9.aarch64 containerd://1.6.24 +``` + +### 控制面板:配置网络,选择 Calico 配置 + +归档文档:https://docs.tigera.io/archive/ + +| **Kubernetes 版本** | **Calico 版本** | **Calico 文档** | **Calico 配置** | +| ---------------------- | --------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 1.18、1.19、1.20 | 3.18 | https://docs.tigera.io/archive/v3.18/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.18/manifests/calico.yaml | +| 1.19、1.20、1.21 | 3.19 | https://docs.tigera.io/archive/v3.19/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.19/manifests/calico.yaml | +| 1.19、1.20、1.21 | 3.20 | https://docs.tigera.io/archive/v3.20/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.20/manifests/calico.yaml | +| 1.20、1.21、1.22 | 3.21 | https://docs.tigera.io/archive/v3.21/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.21/manifests/calico.yaml | +| 1.21、1.22、1.23 | 3.22 | https://docs.tigera.io/archive/v3.22/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.22/manifests/calico.yaml | +| 1.21、1.22、1.23 | 3.23 | https://docs.tigera.io/archive/v3.23/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.23/manifests/calico.yaml | +| 1.22、1.23、1.24、1.25 | 3.24 | https://docs.tigera.io/archive/v3.24/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.24/manifests/calico.yaml | +| 1.22、1.23、1.24 | 3.25 | https://docs.tigera.io/archive/v3.25/getting-started/kubernetes/requirements | https://docs.tigera.io/archive/v3.25/manifests/calico.yaml | +| 1.24、1.25、1.26、1.27 | 3.26(最新版,从 2023 年 5 月开始正式更新,未测试) | https://docs.tigera.io/calico/latest/getting-started/kubernetes/requirements | https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml | + +```shell +# 下载 依赖 +wget --no-check-certificate https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml +``` + +```shell +# 修改 calico.yaml 文件 +vim calico.yaml +``` + +```shell +# 在 - name: CLUSTER_TYPE 下方添加如下内容 +- name: CLUSTER_TYPE + value: "k8s,bgp" + # 下方为新增内容 +- name: IP_AUTODETECTION_METHOD + value: "interface=网卡名称" + +# 网卡名称通过ip addr进行查询 +# INTERFACE_NAME=ens33 +# sed -i '/k8s,bgp/a \ - name: IP_AUTODETECTION_METHOD\n value: "interface=INTERFACE_NAME"' calico.yaml +# sed -i "s#INTERFACE_NAME#$INTERFACE_NAME#g" calico.yaml +``` + +```shell +# 配置网络 +kubectl apply -f calico.yaml +``` + +### 控制面板:查看 pods、nodes + +```shell +kubectl get nodes -o wide +kubectl get pods --all-namespaces -o wide +``` + +## k8s安装与配置已完成,下面内容是测试。 + +### 控制面板:创建 nginx 服务 + +```shell +# 带 命名空间、Service 的完整版参见:https://jihulab.com/xuxiaowei-cloud/xuxiaowei-cloud/-/blob/main/docs/deployment/nginx-deployment.yaml +cat > nginx.yaml << EOF +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 2 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.23.2 + ports: + - containerPort: 80 + +EOF + +cat nginx.yaml + +kubectl apply -f nginx.yaml + +# 编辑 +# kubectl edit deployment nginx-deployment +``` + +### 查看服务 + +```shell +kubectl get pods --all-namespaces -o wide +kubectl get pods -o wide + +# 控制面板:查看pod,svc +kubectl get pod,svc -o wide +``` + + +```shell +# 控制面板:设置服务(将多个 nginx-deployment 的 pod 绑定在一起,通过一个 Service 端口统一对外提供) +kubectl expose deployment nginx-deployment --type=NodePort --name=nginx-service + +# 带 命名空间、Service 的完整版参见:https://jihulab.com/xuxiaowei-cloud/xuxiaowei-cloud/-/blob/main/docs/deployment/nginx-deployment.yaml +``` + +### 再次查看 + +```shell +# 控制面板:查看pod,svc +kubectl get pod,svc -o wide +``` + +### 删除部署 + +```shell +kubectl delete -f nginx.yaml +``` + +```shell +kubectl delete deployment pod名称 +``` + +## 其他辅助 + +### CentOS 命令自动补充 + +1. 安装 bash-completion + + ```shell + yum install -y bash-completion + ``` + +2. 拷贝 kubernetes 的自动补全脚本到系统补全目录中 + + ```shell + source <(kubectl completion bash) + echo "source <(kubectl completion bash)" >> ~/.bashrc + ``` + +3. 重新加载环境变量,使设置生效 + + ```shell + source ~/.bashrc + ``` + +### Token 相关命令 + +1. 控制平面节点上运行以下命令来获取令牌 + + ```shell + kubeadm token list + ``` + +2. 默认情况下,令牌会在 24 小时后过期,可以通过在控制平面节点上运行以下命令来创建新令牌 + + ```shell + kubeadm token create + ``` + +### 相关命令 + +1. 查看更多信息 + + ```shell + -o wide + ``` + +2. 查看所有命名空间 + + ```shell + --all-namespaces + ``` + +3. 查看指定命名空间 + + ```shell + -n 命名空间 + ``` + +4. 查看所有 pod + + ```shell + kubectl get pods --all-namespaces -o wide + ``` + +5. 查看 pod 描述 + + ```shell + kubectl -n 命名空间 describe pod 名称 + ``` + +6. 删除 pod + + ```shell + kubectl -n 命名空间 delete pod 名称 + ``` + +7. 进入 pod + + ```shell + kubectl exec -it pod名称 bash + ``` + +8. 查看 Service Account + + ```shell + kubectl get sa --all-namespaces + ``` + + ```shell + kubectl -n 命名空间 get sa + ``` + +9. 查看 pv + + ```shell + kubectl get pv + ``` + +10. 查看 pvc + + ```shell + kubectl get pvc + ``` + +11. 查看角色绑定 + + ```shell + kubectl get rolebinding --all-namespaces -o wide + ``` + +## 常见报错 + +### 子节点环境变量没有配置 + +```shel +[root@k8s-node2 ~]# kubectl get nodes +E1020 09:02:20.228531 3007 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused +E1020 09:02:20.228894 3007 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused +E1020 09:02:20.230721 3007 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused +E1020 09:02:20.232525 3007 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused +E1020 09:02:20.234259 3007 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused +The connection to the server localhost:8080 was refused - did you specify the right host or port? +``` + +首先先进入`/etc/kubernetes`文件夹下面然后查看你的配置文件 + +1.27版本使用的名称为`kubelet.conf`以前版本应该是`admin.conf` + +配置环境变量 + +```shell +echo "export KUBECONFIG=/etc/kubernetes/kubelet.conf" >> ~/.bash_profile + +source ~/.bash_profile +``` + +现在就已经正常了 + diff --git a/安装环境/K8s/本地部署K8s集群(一键部署).md b/安装环境/K8s/本地部署K8s集群(一键部署).md new file mode 100644 index 0000000..97e7d50 --- /dev/null +++ b/安装环境/K8s/本地部署K8s集群(一键部署).md @@ -0,0 +1,553 @@ +# 本地部署K8s集群(一键部署) + +[TOC] + +## 准备 Linux 主机 + +### 系统要求 + +| **Ubuntu** *16.04,18.04,20.04* | CPU:2 核,内存:4 G,硬盘:40 G | +| ------------------------------------------------------------ | -------------------------------- | +| **Debian** *Buster,Stretch* | CPU:2 核,内存:4 G,硬盘:40 G | +| **CentOS** *7*.x | CPU:2 核,内存:4 G,硬盘:40 G | +| **Red Hat Enterprise Linux** *7* | CPU:2 核,内存:4 G,硬盘:40 G | +| **SUSE Linux Enterprise Server** *15* **/openSUSE Leap** *15.2* | CPU:2 核,内存:4 G,硬盘:40 G | + +- CPU 必须为 x86_64,暂时不支持 Arm 架构的 CPU。 +- 系统应该为纯净镜像不包含其他服务 + +### 节点要求 + +- 所有节点必须都能通过 `SSH` 访问。 +- 所有节点时间同步。 +- 所有节点都应使用 `sudo`/`curl`/`openssl`/`tar`。 + +### 容器要求(可以不安装容器) + +| 支持的容器运行时 | 版本 | +| :---------------------------- | :------ | +| Docker | 19.3.8+ | +| containerd | 最新版 | +| CRI-O(试验版,未经充分测试) | 最新版 | +| iSula(试验版,未经充分测试) | 最新版 | + +### 依赖项要求 + +| 依赖项 | Kubernetes 版本 ≥ 1.18 | Kubernetes 版本 < 1.18 | +| :---------- | :--------------------- | :--------------------- | +| `socat` | 必须 | 可选,但建议安装 | +| `conntrack` | 必须 | 可选,但建议安装 | +| `ebtables` | 可选,但建议安装 | 可选,但建议安装 | +| `ipset` | 可选,但建议安装 | 可选,但建议安装 | + +### 网络和 DNS 要求 + +- 请确保 `/etc/resolv.conf` 中的 DNS 地址可用,否则,可能会导致集群中的 DNS 出现问题。 +- 网络配置使用防火墙规则或安全组,请务必确保基础设施组件可以通过特定端口相互通信。建议您关闭防火墙。 + +### 演示机器准备 + +| 主机 IP | 主机名 | 角色 | 配置 | +| :-------------- | :----- | :------------------ | -------------------------------- | +| 192.168.188.138 | master | control plane, etcd | CPU:4 核,内存:8 G,硬盘:40 G | +| 192.168.188.143 | node1 | worker | CPU:4 核,内存:8 G,硬盘:40 G | +| 192.168.188.144 | node2 | worker | CPU:4 核,内存:8 G,硬盘:40 G | + +## 初始化系统环境 + +所有服务器均需执行 + +### 更新yum源 + +```shell +yum -y update +``` + +### 配置主机名 + +```shell +#查看主机名称 +hostname +#更改名称 hostnamectl --static set-hostname 服务器名称 +hostnamectl --static set-hostname master +#查看主机名称核验 +hostnamectl status +#可以重启一下服务器 +#reboot +``` + +### 关闭防火墙 + +```shell +systemctl disable firewalld +systemctl stop firewalld +``` + +### 打开端口转发 + +```shell +firewall-cmd --add-masquerade --permanent # 端口转发 +firewall-cmd --reload +firewall-cmd --list-all +firewall-cmd --list-all --zone=trusted +``` + +### 开放全部端口 + +```shell +firewall-cmd --zone=public --add-port=1-65535/udp --permanent +firewall-cmd --zone=public --add-port=1-65535/tcp --permanent +firewall-cmd --reload +``` + +### 禁用 SELinux + +```shell +# 使用 sed 修改配置文件,实现彻底的禁用 +sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config + +# 使用命令,实现临时禁用,这一步其实不做也行,KubeKey 会自动配置 +setenforce 0 +``` + +### 关闭swap分区 + +```shell +swapoff -a +echo "vm.swappiness=0" >> /etc/sysctl.conf +sysctl -p /etc/sysctl.conf +``` + +### 配置epel源(国内服务器) + +``` +rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm +``` + +### 配置服务器时区 + +```shell +#配置服务器时区为 Asia/Shanghai +timedatectl set-timezone Asia/Shanghai +#验证 +timedatectl +``` + +### 配置时间同步 + +```shell +#安装 chrony 作为时间同步软件 +yum install chrony -y +``` + +```shell +#修改配置文件 /etc/chrony.conf,修改 ntp 服务器配置。 +vi /etc/chrony.conf + +# 删除所有的 server 配置 +# server 0.centos.pool.ntp.org iburst +# server 1.centos.pool.ntp.org iburst +# server 2.centos.pool.ntp.org iburst +# server 3.centos.pool.ntp.org iburst + +# 增加国内的 ntp 服务器,或是指定其他常用的时间服务器 +pool cn.pool.ntp.org iburst +``` + +```shell +#重启并设置 chrony 服务开机自启动。 +systemctl enable chronyd --now +``` + +```shell +#验证 chrony 同步状态。 +chronyc sourcestats -v +``` + +```shell +# 正常的输出结果如下 +[root@master ~]# chronyc sourcestats -v +210 Number of sources = 4 + .- Number of sample points in measurement set. + / .- Number of residual runs with same sign. + | / .- Length of measurement set (time). + | | / .- Est. clock freq error (ppm). + | | | / .- Est. error in freq. + | | | | / .- Est. offset. + | | | | | | On the -. + | | | | | | samples. \ + | | | | | | | +Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev +============================================================================== +tock.ntp.infomaniak.ch 41 22 297m -0.227 0.504 -12ms 4011us +makaki.miuku.net 26 10 312m -0.014 0.497 +27ms 4083us +electrode.felixc.at 22 15 241m -0.399 1.350 -49ms 6966us +ntp5.flashdance.cx 38 14 315m +0.363 0.600 +27ms 4865us +关闭系统防火墙 +``` + +### 安装依赖组件 + +```shell +yum install -y ebtables socat ipset conntrack +``` + +### 配置基于 SSH 密钥的身份验证(可选) + +仅需在`master` 主节点运行 + +```shell +#使用 ssh-keygen 命令生成一个新的 SSH 密钥对,一直按回车直到生密钥对 +ssh-keygen -t ed25519 +``` + +```shell +#将 SSH 公钥从 master 节点发送到其他节点。命令执行时输入 yes,以接受服务器的 SSH 指纹,然后在出现提示时输入 root 用户的密码。 +ssh-copy-id root@192.168.188.143 +ssh-copy-id root@192.168.188.144 +``` + +```shell +#验证登录 +ssh root@192.168.188.143 +ssh root@192.168.188.144 +``` + +```shell +#输出结果如下 +[root@master ~]# ssh root@192.168.188.143 +Last login: Tue Oct 24 00:29:36 2023 from 192.168.188.138 +[root@node1 ~]# +``` + +### 升级 CentOS7 内核(可选) + +仅需主`master` 主节点运行 + +CentOS 7.9 的默认内核版本为 **3.10.0-1160.102.1**,与其他操作系统内核版本动辄 5.x、6.x 相比 + +### 查看当前系统内核版本 + +```shell +uname -r + +# 3.10.0-1160.102.1.el7.x86_64 +``` + +### 查询当前系统与 Kernel 相关的软件包 + +```shell +#查询当前系统安装了哪些跟 Kernel 有关的软件包,升级内核的时候,一定要把已安装的相关 kernel 包一起升级。  +rpm -qa | grep kernel + +#kernel-3.10.0-1160.102.1.el7.x86_64 +#kernel-tools-3.10.0-1160.102.1.el7.x86_64 +#kernel-tools-libs-3.10.0-1160.102.1.el7.x86_64 +``` + +### 增加 ELRepo 软件源 + +```shell +#导入 RPM GPG public key +rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org + +#安装 ELRepo +yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm +``` + +### 查询可用的内核软件包 + +```shell +#启用新增加的 ELRepo 软件仓库,查询可用的内核软件包。 +yum --disablerepo="*" --enablerepo="elrepo-kernel" list available +``` + +```shell +[root@master ~]# yum --disablerepo="*" --enablerepo="elrepo-kernel" list available +已加载插件:fastestmirror +Loading mirror speeds from cached hostfile + * elrepo-kernel: mirrors.tuna.tsinghua.edu.cn +可安装的软件包 +kernel-lt-devel.x86_64 5.4.258-1.el7.elrepo elrepo-kernelkernel-lt-doc.noarch 5.4.258-1.el7.elrepo elrepo-kernelkernel-lt-headers.x86_64 5.4.258-1.el7.elrepo elrepo-kernelkernel-lt-tools-libs-devel.x86_64 5.4.258-1.el7.elrepo elrepo-kernelkernel-ml.x86_64 6.5.8-1.el7.elrepo elrepo-kernelkernel-ml-devel.x86_64 6.5.8-1.el7.elrepo elrepo-kernelkernel-ml-doc.noarch 6.5.8-1.el7.elrepo elrepo-kernelkernel-ml-headers.x86_64 6.5.8-1.el7.elrepo elrepo-kernelkernel-ml-tools.x86_64 6.5.8-1.el7.elrepo elrepo-kernelkernel-ml-tools-libs.x86_64 6.5.8-1.el7.elrepo elrepo-kernelkernel-ml-tools-libs-devel.x86_64 6.5.8-1.el7.elrepo elrepo-kernelperf.x86_64 5.4.258-1.el7.elrepo elrepo-kernelpython-perf.x86_64 5.4.258-1.el7.elrepo elrepo-kernel[root@master ~]# +#当前最新 lt 版内核为 5.4.258-1 a信用最新版本内核 +``` + +### 安装新版本内核 + +```shell +#只安装内核,同时安装其他包会报错 +yum --enablerepo=elrepo-kernel install kernel-lt + +#安装成功会提示 +#Complete! +``` + + + +### 配置新内核引导系统 + +```shell +#查看已经安装的 kernel 信息 +grubby --info=ALL | grep ^kernel + +#kernel=/boot/vmlinuz-5.4.258-1.el7.elrepo.x86_64 +#kernel=/boot/vmlinuz-3.10.0-1160.102.1.el7.x86_64 +#kernel=/boot/vmlinuz-3.10.0-1062.el7.x86_64 +#kernel=/boot/vmlinuz-0-rescue-fc61eb831f5b4eda852bfd7c596c8c87 +``` + +```shell +#查看当前系统默认内核 +grubby --default-kernel + +#/boot/vmlinuz-3.10.0-1160.102.1.el7.x86_64 +``` + +```shell +#修改系统默认内核为新内核 +grubby --set-default "/boot/vmlinuz-5.4.258-1.el7.elrepo.x86_64" +``` + +```shell +#查看系统默认内核 +grubby --default-kernel +/boot/vmlinuz-5.4.258-1.el7.elrepo.x86_64 +``` + +```shell +#重启 +reboot +``` + +进入引导画面后选中最新的内核默认启动 + +![image-20231024093226585](https://lsky.hhdxw.top/imghub/2023/10/image-202310241698111147.png) + +### 重启系统并查看系统内核 + +```shell +#查看内核版本信息 +uname -rs +#Linux 5.4.258-1.el7.elrepo.x86_64 +``` + +```shell +#查看已经安装的内核相关软件 +rpm -qa | grep kernel +#kernel-lt-5.4.258-1.el7.elrepo.x86_64 +#kernel-lt-tools-3.10.0-1160.102.1.el7.elrepo.x86_64 +#kernel-3.10.0-1160.102.1.el7.x86_64 +#kernel-3.10.0-1062.el7.x86_64 +#kernel-lt-tools-libs-3.10.0-1160.102.1.el7.elrepo.x86_64 +``` + +```shell +# 卸载旧版本的 kernel-tools 相关软件包 +yum remove kernel-tools-3.10.0-1160.102.1.el7.x86_64 kernel-tools-libs-3.10.0-1160.102.1.el7.x86_64 + +# 安装新版本的 kernel-tools 相关软件包 +yum --enablerepo=elrepo-kernel install kernel-lt-tools kernel-lt-tools-libs +``` + +## 下载 KubeKey + +下面只需要 `master` 主节点运行即可 + +先执行以下命令以确保您从正确的区域下载 KubeKey。 + +```shell +export KKZONE=cn +``` + +执行以下命令下载 KubeKey: + +```shell +curl -sfL https://get-kk.kubesphere.io | VERSION=v3.0.7 sh - +``` + +为 `kk` 添加可执行权限: + +```shell +chmod +x kk +``` + +## 创建集群 + +### 查看 KubeKey 支持的 Kubernetes 版本列表 + +```shell +./kk version --show-supported-k8s +``` + +### 创建配置文件 + +命令如下: + +```shell +./kk create config [--with-kubernetes version] [--with-kubesphere version] [(-f | --file) path] +``` + +- 安装 KubeSphere 3.3 的建议 Kubernetes 版本:v1.20.x、v1.21.x、* v1.22.x、* v1.23.x 和 * v1.24.x。带星号的版本可能出现边缘节点部分功能不可用的情况。因此,如需使用边缘节点,推荐安装 v1.21.x。如果不指定 Kubernetes 版本,KubeKey 将默认安装 Kubernetes v1.23.10。 +- 如果您在此步骤的命令中不添加标志 `--with-kubesphere`,则不会部署 KubeSphere,只能使用配置文件中的 `addons` 字段安装,或者在您后续使用 `./kk create cluster` 命令时再次添加这个标志。 +- 如果您添加标志 `--with-kubesphere` 时不指定 KubeSphere 版本,则会安装最新版本的 KubeSphere。 + +示例:(默认K8s版本1.23.10,KubeSphere3.3.2) + +```shell +./kk create config --with-kubesphere 3.3.2 +``` + +### 编辑配置文件 + +如果不改名称,那么将创建默认文件 `config-sample.yaml`。 + +```shell +vi config-sample.yaml +``` + +### 主机 + +请参照上方示例在 `hosts` 下列出您的所有机器并添加详细信息。 + +`name`:实例的主机名。 + +`address`:任务机和其他实例通过 SSH 相互连接所使用的 IP 地址。根据您的环境,可以是公有 IP 地址或私有 IP 地址。例如,一些云平台为每个实例提供一个公有 IP 地址,用于通过 SSH 访问。在这种情况下,您可以在该字段填入这个公有 IP 地址。 + +`internalAddress`:实例的私有 IP 地址。 + +### roleGroups + +- `etcd`:etcd 节点名称 +- `control-plane`:主节点名称 +- `worker`:工作节点名称 + +### 提示 + +- 在安装 KubeSphere 之前,您可以使用 `hosts` 下提供的信息(例如 IP 地址和密码)通过 SSH 的方式测试任务机和其他实例之间的网络连接。 + +- 在安装前,请确保端口 `6443` 没有被其他服务占用,否则在安装时会产生冲突(`6443` 为 API 服务器的默认端口)。 + +### 示例: + +```yml + +apiVersion: kubekey.kubesphere.io/v1alpha2 +kind: Cluster +metadata: + name: sample +spec: + hosts: + - {name: master, address: 192.168.188.138, internalAddress: 192.168.188.138, user: root, password: root} + #上面配置ssh密钥对以后可以使用秘钥直接访问,如没有配置,请使用下面注释内容通过远程访问 + - {name: node1, address: 192.168.188.143, internalAddress: 192.168.188.143, privateKeyPath: "~/.ssh/id_ed25519"} + - {name: node2, address: 192.168.188.144, internalAddress: 192.168.188.144, privateKeyPath: "~/.ssh/id_ed25519"} + #- {name: node1, address: 192.168.188.143, internalAddress: 192.168.188.143, user: root, password: root} + #- {name: node2, address: 192.168.188.144, internalAddress: 192.168.188.144, user: root, password: root} + roleGroups: + etcd: + - master + control-plane: + - master + worker: + - node1 + - node2 +``` + +### 使用配置文件创建集群 + +```shell +./kk create cluster -f config-sample.yaml +``` + +如果使用其他名称,则需要将上面的 `config-sample.yaml` 更改为您自己的文件。 + +整个安装过程可能需要 10 到 20 分钟,具体取决于您的计算机和网络环境。 + +### 验证安装 + +安装完成后,您会看到如下内容: + +```shell +##################################################### +### Welcome to KubeSphere! ### +##################################################### + +Console: http://192.168.188.138:30880 +Account: admin +Password: P@88w0rd +NOTES: + 1. After you log into the console, please check the + monitoring status of service components in + "Cluster Management". If any service is not + ready, please wait patiently until all components + are up and running. + 2. Please change the default password after login. + +##################################################### +https://kubesphere.io 2023-10-24 00:37:49 +##################################################### +``` + +## 安装插件 + +### devops + +以 `admin` 用户登录控制台,点击左上角的**平台管理**,选择**集群管理**。 + +点击**定制资源定义**,在搜索栏中输入 `clusterconfiguration`,点击搜索结果查看其详细页面。 + +定制资源定义(CRD)允许用户在不新增 API 服务器的情况下创建一种新的资源类型,用户可以像使用其他 Kubernetes 原生对象一样使用这些定制资源。 + +在**自定义资源**中,点击 `ks-installer` 右侧的更多,选择**编辑 YAML**。 + +在该 YAML 文件中,搜索 `devops`,将 `enabled` 的 `false` 改为 `true`。完成后,点击右下角的**确定**,保存配置。 + +``` +devops: + enabled: true # 将“false”更改为“true”。 +``` + +添加`minio`版本,在spec下面添加下面四个地址 + +```shell +spec: + minio_repo: docker.io.minio/minio + minio_tag: RELEASE.2021-12-29T06-49-06Z + mc_repo: docker.io.minio/mc + mac_tag: RELEASE.2021-12-29T06-52-55Z +``` + +在 kubectl 中执行以下命令检查安装过程: + +``` +kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f +``` + +## 卸载插件 + +### devops + +卸载 DevOps: + +``` +helm uninstall -n kubesphere-devops-system devops +kubectl patch -n kubesphere-system cc ks-installer --type=json -p='[{"op": "remove", "path": "/status/devops"}]' +kubectl patch -n kubesphere-system cc ks-installer --type=json -p='[{"op": "replace", "path": "/spec/devops/enabled", "value": false}]' +``` + +删除 DevOps 资源: + +``` +# 删除所有 DevOps 相关资源 +for devops_crd in $(kubectl get crd -o=jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep "devops.kubesphere.io"); do + for ns in $(kubectl get ns -ojsonpath='{.items..metadata.name}'); do + for devops_res in $(kubectl get $devops_crd -n $ns -oname); do + kubectl patch $devops_res -n $ns -p '{"metadata":{"finalizers":[]}}' --type=merge + done + done +done +# 删除所有 DevOps CRD +kubectl get crd -o=jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep "devops.kubesphere.io" | xargs -I crd_name kubectl delete crd crd_name +# 删除 DevOps 命名空间 +kubectl delete namespace kubesphere-devops-system +``` diff --git a/开发工具/Docker基本命令.md b/安装环境/基本命令/Docker基本命令.md similarity index 91% rename from 开发工具/Docker基本命令.md rename to 安装环境/基本命令/Docker基本命令.md index 5caba42..ef51570 100644 --- a/开发工具/Docker基本命令.md +++ b/安装环境/基本命令/Docker基本命令.md @@ -611,7 +611,7 @@ FROM [base_image] #### (2)⽤于将⽂件复制到镜像中,源可以使URL或者本地⽂件,也可以⼀个压缩⽂件(⾃动解压); ```shell -ADD +ADD [--chown=:] [源路径] [⽬标路径] ``` #### (3)⽤于将⽂件拷⻉到镜像中,源只能是本地⽂件; @@ -656,3 +656,59 @@ VOLUME <路径> VOLUME ["路径1", "路径2"...] ``` +#### (8)声明镜像创建者 + +```shell +MAINTAINER user_name +``` + +## Docker 开放2375端口 + + +开启端口命令 (--permanent永久生效,没有此参数重启后失效) + +```shell +firewall-cmd --zone=public --add-port=2375/tcp --permanent +``` + +重新载入 + +```shell +firewall-cmd --reload +``` + +使用 vim(vi) 编辑docker服务配置文件 + +```shell +vim /lib/systemd/system/docker.service +``` + +找到如下配置行 + +```shell +ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock +``` + +将其注释掉或者直接删除,替换成下面的配置行.然后保存退出 + +```shell +ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock +``` + +重新加载docker配置并重启服务 + +```shell +systemctl daemon-reload && systemctl restart docker +``` + +然后直接在命令行客户端输入如下命令,IP地址改为自己的 + +```shell +curl http://IP地址:2375/version +``` + +或者在浏览器直接访问,IP地址改为自己的 + +```shell +http://IP地址:2375/version +``` \ No newline at end of file diff --git a/开发工具/Git基本命令.md b/安装环境/基本命令/Git基本命令.md similarity index 100% rename from 开发工具/Git基本命令.md rename to 安装环境/基本命令/Git基本命令.md diff --git a/开发工具/Node基本命令.md b/安装环境/基本命令/Node基本命令.md similarity index 100% rename from 开发工具/Node基本命令.md rename to 安装环境/基本命令/Node基本命令.md diff --git a/开发工具/Redis基本命令.md b/安装环境/基本命令/Redis基本命令.md similarity index 100% rename from 开发工具/Redis基本命令.md rename to 安装环境/基本命令/Redis基本命令.md diff --git a/安装环境/安装教程/Centos 安装docker.md b/安装环境/安装教程/Centos 安装docker.md new file mode 100644 index 0000000..b024c08 --- /dev/null +++ b/安装环境/安装教程/Centos 安装docker.md @@ -0,0 +1,99 @@ +​ + +更新一下环境: + +``` +yum -y update +``` + +安装一些必要的系统工具: + +``` +sudo yum install -y yum-utils device-mapper-persistent-data lvm2 +``` + +添加软件源信息: + +``` +sudo yum install -y yum-utils +``` + +国内阿里源 + +``` +sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo +``` + +docker官方镜像源 + +``` +sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo +``` + +安装 Docker: + +``` +sudo yum install docker-ce docker-ce-cli containerd.io +``` + +启动 Docker 后台服务 + +``` +sudo systemctl start docker +sudo systemctl enable docker +``` + +测试运行 hello-world + +``` +docker run hello-world +``` + +部分镜像源 + +```shell +sudo vim /etc/docker/daemon.json +``` + +镜像源 + +``` +{ + "registry-mirrors": [ + "https://9kkc1zdn.mirror.aliyuncs.com", + "https://hub-mirror.c.163.com/", + "https://docker.mirrors.ustc.edu.cn/", + "https://registry.docker-cn.com" + "https://hub.docker.com" + ] +} +``` + +上传地址设置 + +```json + +``` + +重启开启镜像源 + +```shell +systemctl daemon-reload + +systemctl restart docker +``` + +安装 Docker-compose + + +```shell +yum install docker-compose-plugin +``` + +开放所有端口 + +```shell +firewall-cmd --zone=public --add-port=1-65535/udp --permanent +firewall-cmd --zone=public --add-port=1-65535/tcp --permanent +firewall-cmd --reload +``` diff --git a/Docker 2375端口开启外网访问.md b/安装环境/安装教程/Docker 2375端口开启外网访问.md similarity index 100% rename from Docker 2375端口开启外网访问.md rename to 安装环境/安装教程/Docker 2375端口开启外网访问.md diff --git a/安装环境/安装教程/Docker开放2375端口.md b/安装环境/安装教程/Docker开放2375端口.md new file mode 100644 index 0000000..136e5d5 --- /dev/null +++ b/安装环境/安装教程/Docker开放2375端口.md @@ -0,0 +1,62 @@ + + +开启端口命令 (--permanent永久生效,没有此参数重启后失效) + +``` +firewall-cmd --zone=public --add-port=2375/tcp --permanent +``` + +重新载入 + +``` +firewall-cmd --reload +``` + +使用 vim 编辑docker服务配置文件 + +``` +vim /lib/systemd/system/docker.service +``` + +找到如下配置行 + +``` +ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock +``` + +将其注释掉或者直接删除,替换成下面的配置行.然后保存退出 + +``` +ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock +``` + +重新加载docker配置并重启服务 + +``` +systemctl daemon-reload && systemctl restart docker +``` + +然后直接在命令行客户端输入如下命令,IP地址改为自己的 + + +``` +curl http://IP地址:2375/version +``` + +或者在浏览器直接访问,IP地址改为自己的 + +``` +http://IP地址:2375/version +``` + + +```xml +{ + "insecure-registries": [ + "10.211.55.2:8082" + ], + "registry-mirrors": [ + "10.211.55.2:8082" + ] +} +``` \ No newline at end of file diff --git a/安装环境/安装教程/ELK Stack .md b/安装环境/安装教程/ELK Stack .md new file mode 100644 index 0000000..4030901 --- /dev/null +++ b/安装环境/安装教程/ELK Stack .md @@ -0,0 +1,62 @@ +ELK Stack 是一个用于日志管理和分析的开源工具组合,它是一套开源免费、功能强大的日志分析管理系统。ELK可以将我们的系统日志、网站日志、应用系统日志等各种日志进行收集、过滤、清洗、然后进行集中存放并可用于实时检索、分析。这三款软件都是开源软件,通常配合使用,而且又先后归于Elastic.co公司名下,故又称为ELK Stack。官网 https://www.elastic.co/cn/elastic-stack + +## 1.Elasticsearch(简称为ES) + +它是一个实时的分布式搜索和分析引擎,基于Lucene库。ES可以处理大规模数据集,并提供快速的搜索、聚合和分析能力。它具有分布式架构,可以通过水平扩展来处理海量数据,并提供高可用性和容错机制。 + + - 作用:用于存储、搜索和分析大规模数据集,提供快速的搜索、聚合和分析能力。 + + - 使用: + - 存储数据:ES使用分片和副本机制,在多个节点上分布和复制数据,以实现高可用性和容错性。 + + - 搜索和查询:ES支持全文搜索、近实时搜索和复杂的查询操作,可以通过API进行搜索和过滤。 + + - 聚合和分析:ES提供聚合功能,可以对数据进行汇总和统计,并支持数据可视化。 + + - 可扩展性和分布式部署:ES可以通过水平扩展来处理大规模数据,并在集群中自动管理数据分布和负载均衡。 + +## 2.Logstash + +它是一个灵活的数据收集、处理和传输工具。Logstash可以从多种来源(如日志文件、数据库、消息队列等)收集数据,并对数据进行预处理、转换和标准化。它还支持多种输出方式,可以将处理后的数据发送到Elasticsearch等目标存储或分析系统。 + +- 作用:用于收集、处理和传输各种类型的数据,以准备数据供后续分析和存储。 +- 使用: + - 数据收集:Logstash支持从多种来源(如日志文件、数据库、消息队列等)收集数据。 + - 数据处理:Logstash提供丰富的过滤器插件,可以对数据进行解析、转换和标准化,以便后续处理和分析。 + - 数据传输:Logstash支持将处理后的数据发送到各种目标存储或分析系统,如Elasticsearch、Kafka等。 + +## 3.Kibana + + 它是一个用于数据可视化和分析的开源工具。Kibana提供了一个直观易用的Web界面,可以通过图表、表格、地图等形式展示和分析Elasticsearch中的数据。用户可以使用Kibana创建仪表盘、查询数据、构建可视化报告等。 + +- 作用:用于通过图表、表格、地图等形式对数据进行可视化和分析。 +- 使用: + - 数据查询与展示:Kibana提供一个直观易用的Web界面,用户可以使用Kibana查询和过滤Elasticsearch中的数据,并以图表、表格等形式展示数据。 + - 仪表盘创建:Kibana支持创建自定义的仪表盘,将多个可视化组件整合到一个页面上,以实现更全面和直观的数据展示。 + - 报表和警报:Kibana还提供报表功能,可以将可视化结果导出为PDF或CSV格式,同时支持设置警报,以便实时监控数据指标并触发相应的操作。 + +## 4.ELK Stack的典型工作流程 + +1. 数据收集:Logstash作为数据收集器从各种数据源(如日志文件、数据库、消息队列等)中收集数据,并进行过滤、解析和标准化处理。 + +2. 数据存储:经过处理后的数据被发送到Elasticsearch进行索引和存储。Elasticsearch使用分片和副本机制,将数据分布和复制在多个节点上,以实现高可用性和容错性。 + +3. 数据可视化和分析:用户可以通过Kibana访问Elasticsearch中的数据,使用Kibana的Web界面进行数据查询、过滤、聚合和可视化操作。Kibana提供了丰富的图表、表格、地图等可视化组件,用户可以创建仪表盘、报表和警报,以便实时监控和分析数据。 + +## 5.ELK Stack的优点 + +1. 灵活和可扩展:ELK Stack的三个组件都是开源的,并且具有良好的可扩展性。可以根据需求扩展集群规模,处理大规模数据集,满足不断增长的数据量和用户需求。 + +2. 实时性:ELK Stack能够实时处理和分析数据,可以快速响应变化,并提供近实时的搜索和可视化结果。 + +3. 丰富的功能:ELK Stack提供了丰富的功能和插件支持,可以进行复杂的数据处理、搜索、聚合和可视化操作。用户可以根据需求自定义工作流程,并满足各种数据分析和业务监控的需求。 + +4. 强大的查询能力:Elasticsearch作为核心组件,提供了强大的全文搜索和复杂查询功能,可以高效地检索和过滤数据。 + +## 6.ELK Stack的缺点 + +1. 学习较难:对于初学者来说,ELK Stack的学习不易,特别是对于不熟悉分布式系统和复杂查询语法的人来说。 + +2. 需要一定的硬件资源:由于ELK Stack处理大规模数据集和实时分析的能力,需要一定的硬件资源支持,包括存储空间、计算能力和网络带宽等。 + +3. 数据安全性和权限管理:ELK Stack在数据安全性和权限管理方面需要额外的配置和注意,以确保只有授权的用户可以访问和操作数据。 diff --git a/安装环境/安装教程/ELK.md b/安装环境/安装教程/ELK.md new file mode 100644 index 0000000..f419600 --- /dev/null +++ b/安装环境/安装教程/ELK.md @@ -0,0 +1,417 @@ +ELK Stack + +https://www.51cto.com/article/707776.html + +这次先讲解 ELK Stack 的方式,这种方式对我们的代码无侵入,核心思想就是收集磁盘的日志文件,然后导入到 Elasticsearch。 + +比如我们的应用系统通过 logback 把日志写入到磁盘文件,然后通过这一套组合的中间件就能把日志采集起来供我们查询使用了。 + +流程如下: + +- 先使用 Filebeat 把日志收集起来,然后把数据再传给 Logstash。 +- 通过 Logstash 强大的数据清洗功能。 +- 最终把数据写入到 Elasticsearch 中。 +- 并由 Kibana 进行可视化。 + +温馨提示:以下案例都在一台 ubuntu 虚拟机上完成,内存分配了 6G。 + +### 一、部署 Elasticsearch + +数据库获取 elasticsearch 镜像: + +``` +docker pull elasticsearch:7.7.1 +``` + +创建挂载目录: + +``` +mkdir -p /data/elk/es/{config,data,logs} +``` + +赋予权限: + +``` +chown -R 1000:1000 /data/elk/es +``` + +创建配置文件: + +``` +cd /data/elk/es/config +touch elasticsearch.yml +-----------------------配置内容---------------------------------- +cluster.name: "my-es" +network.host: 0.0.0.0 +http.port: 9200 +``` + +启动 elasticsearch 容器: + +``` +docker run -it -d -p 9200:9200 -p 9300:9300 --name es -e ES_JAVA_OPTS="-Xms1g -Xmx1g" -e "discovery.type=single-node" --restart=always -v /data/elk/es/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /data/elk/es/data:/usr/share/elasticsearch/data -v /data/elk/es/logs:/usr/share/elasticsearch/logs elasticsearch:7.7.1 +``` + +验证 elasticsearch 是否启动成功: + +``` +curl http://localhost:9200 +``` + +### 二、部署 Kibana 可视化工具 + +#### 2.1 安装 Kibana + +获取 kibana 镜像: + +``` +docker pull kibana:7.7.1 +``` + +获取elasticsearch容器 ip: + +``` +docker inspect --format '{{ .NetworkSettings.IPAddress }}' es +``` + +结果:172.17.0.2; + +创建 kibana 配置文件: + +``` +mkdir -p /data/elk/kibana/ +vim /data/elk/kibana/kibana.yml +``` + +配置内容: + +``` +#Default Kibana configuration for docker target +server.name: kibana +server.host: "0" +elasticsearch.hosts: ["http://172.17.0.2:9200"] +xpack.monitoring.ui.container.elasticsearch.enabled: true +``` + +#### 2.2 运行 kibana + +``` +docker run -d --restart=always --log-driver json-file --log-opt max-size=100m --log-opt max-file=2 --name kibana -p 5601:5601 -v /data/elk/kibana/kibana.yml:/usr/share/kibana/config/kibana.yml kibana:7.7.11. +``` + +访问 http://192.168.56.10:5601。这个 IP 是服务器的 IP。Kibana 控制台的界面如下所示,打开 kibana 时,首页会提示让你选择加入一些测试数据,点击 try our sample data 按钮就可以了。 + +### 三、部署 logstash 日志过滤、转换工具 + +#### 3.1 安装 Java JDK + +``` +yum install java-1.8.0-openjdk.x86_64 +``` + +修改 /etc/profile 文件: + +``` +sudo vim /etc/profile +``` + +添加如下的内容到你的 .profile 文件中: + +``` +# JAVA +JAVA_HOME="/usr/lib/jdk/jdk-12" +PATH="$PATH:$JAVA_HOME/bin" +``` + +再在命令行中打入如下的命令: + +``` +source /etc/profile1. +``` + +查看 java 是否配置成功: + +``` +java -version +``` + +#### 3.2 安装 logstash + +下载 logstash 安装包: + +``` +curl -L -O https://artifacts.elastic.co/downloads/logstash/logstash-7.7.1.tar.gz +``` + +解压安装: + +``` +tar -xzvf logstash-7.7.1.tar.gz +``` + +要测试 Logstash 安装,请运行最基本的 Logstash 管道。例如: + +``` +cd logstash-7.7.1 +bin/logstash -e 'input { stdin { } } output { stdout {} }' +``` + +等 Logstash 完成启动后,我们在 stdin 里输入以下文字,我们可以看到如下的输出: + +当我们打入一行字符然后回车,那么我们马上可以在 stdout 上看到输出的信息。如果我们能看到这个输出,说明我们的 Logstash 的安装是成功的。 + +我们进入到 Logstash 安装目录,并修改 config/logstash.yml 文件。我们把 config.reload.automatic 设置为 true。 + +```yaml +config.reload.automatic: true +``` + +另外一种运行 Logstash 的方式,也是一种最为常见的运行方式,运行时指定 logstash 配置文件。 + + + +#### 3.3 配置 logstash + +Logstash 配置文件有两个必需元素,输入(inputs)和输出(ouputs),以及一个可选元素 filters。输入插件配置来源数据,过滤器插件在你指定时修改数据,输出插件将数据写入目标。 + +创建 kibana 配置文件 weblog.conf: + + + +``` +mkdir -p /logstash-7.7.1/streamconf +vim /logstash-7.7.1/streamconf/weblog.conf +``` + +配置内容如下: + + + +``` +input { + tcp { + port => 9900 + } +} + +filter { + grok { + match => { "message" => "%{COMBINEDAPACHELOG}" } + } + + mutate { + convert => { + "bytes" => "integer" + } + } + + geoip { + source => "clientip" + } + + useragent { + source => "agent" + target => "useragent" + } + + date { + match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"] + } +} + +output { + stdout { } + + elasticsearch { + hosts => ["localhost:9200"] + } +} +``` + +在上面,我们同时保留两个输出:stdout 及 elasticsearch。事实上,我们可以定义很多个的输出。stdout 输出对于我们初期的调试是非常有帮助的。等我们完善了所有的调试,我们可以把上面的 stdout 输出关掉。 + +等更新完这个配置文件后,我们在另外一个 console 中发送第一个 log: + + + +``` +head -n 1 weblog-sample.log | nc localhost 9900 +``` + +这个命令的意思:我们使用 nc 应用读取第一行数据,然后发送到 TCP 端口号 9900,并查看 console 的输出。 + +这里的 weblog-sample.log 为样例数据,内容如下,把它放到本地作为日志文件。 + + + +``` +14.49.42.25 - - [12/May/2019:01:24:44 +0000] "GET /articles/ppp-over-ssh/ +HTTP/1.1" 200 18586 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; +rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5" +``` + +logstash 控制台打印出了 weblog-samle.log 中的内容: + +![image-20240104174304131](https://lsky.hhdxw.top/imghub/2024/01/image-202401041704361384.png) + +这一次,我们打开 Kibana,执行命令,成功看到 es 中的这条记录。 + +``` +GET logstash/_search +``` + +![image-20240104174326473](https://lsky.hhdxw.top/imghub/2024/01/image-202401041704361406.png) + +### 四、部署 Filebeat 日志收集工具 + +#### 4.1 安装 Filebeat + +``` +curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.7.1-linux-x86_64.tar.gz +tar xzvf filebeat-7.7.1-linux-x86_64.tar.gz +``` + +请注意:由于 ELK 迭代比较快,我们可以把上面的版本 7.7.1 替换成我们需要的版本即可。我们先不要运行 Filebeat。 + +#### 4.2 配置 Filebeat + +我们在 Filebeat 的安装目录下,可以创建一个这样的 filebeat_apache.yml 文件,它的内容如下,首先先让 filebeat 直接将日志文件导入到 elasticsearch,来确认 filebeat 是否正常工作。 + +``` +filebeat.inputs: +- type: log + enabled: true + paths: + - /home/vagrant/logs/*.log + +output.elasticsearch: + hosts: ["192.168.56.10:9200"] +``` + +paths 对应你的日志文件夹路径,我配置的是这个:/home/vagrant/logs/*.log,之前配置成 /home/vagrant/logs 不能正常收集。另外这里可以放入多个日志路径。 + +#### 4.3 测试 Filebeat + +在使用时,你先要启动 Logstash,然后再启动 Filebeat。 + +``` +bin/logstash -f weblog.conf +``` + +然后,再运行 Filebeat, -c 表示运行指定的配置文件,这里是 filebeat_apache.yml。 + +``` +./filebeat -e -c filebeat_apache.yml +``` + +运行结果如下所示,一定要确认下控制台中是否打印了加载和监控了我们指定的日志。如下图所示,有三个日志文件被监控到了:error.log、info.log、debug.log + +我们可以通过这个命令查看 filebeat 的日志是否导入成功了: + +``` +curl http://localhost:9200/_cat/indices?v +``` + +这个命令会查询 Elasticsearch 中所有的索引,如下图所示,filebeat-7.7.1-* 索引创建成功了。因为我没有配置索引的名字,所以这个索引的名字是默认的。 + +在 kibana 中搜索日志,可以看到导入的 error 的日志了。不过我们先得在 kibana 中创建 filebeat 的索引(点击 create index pattern 按钮,然后输入 filebeat 关键字,添加这个索引),然后才能在 kibana 的 Discover 控制台查询日志。 + +#### 4.4 Filebeat + Logstash + +接下来我们配置 filebeat 收集日志后,输出到 logstash,然后由 logstash 转换数据后输出到 elasticsearch。 + +```yaml +filebeat.inputs: + +- type: log + enabled: true + paths: + - /home/vagrant/logs/*.log + +output.logstash: + hosts: ["localhost:9900"] +``` + +修改 logstash 配置文件: + +``` +vim /logstash-7.7.1/streamconf/weblog.conf +``` + +配置了 input 为 beats,修改了 useragent: + +``` +input { + beats { + port => "9900" + } +} + +filter { + grok { + match => { "message" => "%{COMBINEDAPACHELOG}" } + } + + mutate { + convert => { + "bytes" => "integer" + } + } + + geoip { + source => "clientip" + } + + useragent { + source => "user_agent" + target => "useragent" + } + + date { + match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"] + } +} + +output { + stdout { + codec => dots {} + } + + elasticsearch { + hosts=>["192.168.56.10:9200"] + index => "apache_elastic_example" + } +} +``` + +然后重新启动 logstash 和 filebeat。有个问题,这次启动 filebeat 的时候,只监测到了一个 info.log 文件,而 error.log 和 debug.log 没有监测到,导致只有 info.log 导入到了 Elasticsearch 中。filebeat 只监测到了 info.log 文件 + +logstash 输出结果如下,会有格式化后的日志: + +我们在 Kibana dev tools 中可以看到索引 apache_elastic_example,说明索引创建成功,日志也导入到了 elasticsearch 中。 + +另外注意下 logstash 中的 grok 过滤器,指定的 message 的格式需要和自己的日志的格式相匹配,这样才能将我们的日志内容正确映射到 message 字段上。 + +例如我的 logback 的配置信息如下: + + + +而我的 logstash 配置如下,和 logback 的 pettern 是一致的。 + +```yaml +grok { + match => { "message" => "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n" } + } +``` + +然后我们在 es 中就能看到日志文件中的信息了。如下图所示: + + + +至此,Elasticsearch + Logstash + Kibana + Filebeat 部署成功,可以愉快地查询日志了~ + +后续升级方案: + +- 加上 Kafka。 +- 加上 Grafana 监控。 +- 链路追踪。 \ No newline at end of file diff --git a/安装环境/安装教程/GrayLog 分布式日志.md b/安装环境/安装教程/GrayLog 分布式日志.md new file mode 100644 index 0000000..4e00b15 --- /dev/null +++ b/安装环境/安装教程/GrayLog 分布式日志.md @@ -0,0 +1,451 @@ +`GrayLog`是一个轻量型的分布式日志管理平台,一个开源的日志聚合、分析、审计、展示和预警工具。在功能上来说,和 ELK类似,但又比 ELK要简单轻量许多。依靠着更加简洁,高效,部署使用简单的优势很快受到许多公司的青睐。 + +![](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704805912.png) + +官网:https://www.graylog.org/ + +>- `GrayLog`方案的优势 +>- 一体化方案,安装方便,不像ELK有3个独立系统间的集成问题。 +>- 采集原始日志,并可以事后再添加字段,比如http_status_code,response_time等等。 +> +>- 自己开发采集日志的脚本,并用curl/nc发送到`Graylog Server`,发送格式是自定义的GELF,`Flunted`和`Logstash`都有相应的输出GELF消息的插件。自己开发带来很大的自由度。实际上只需要用`inotifywait`监控日志的modify事件,并把日志的新增行用curl/netcat发送到`Graylog Server`就可。 +> +>- 搜索结果高亮显示,就像`google`一样。 +> +>- 搜索语法简单,比如: `source:mongo AND reponse_time_ms:>5000`,避免直接输入`elasticsearch`搜索json`语法` +> +>- 搜索条件可以导出为`elasticsearch`的搜索json`文本`,方便直接开发调用`elasticsearch rest api`的搜索脚本。 +## Graylog工作流程 + +![img](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704762652.jpeg) + +## 最小化安装基本框架 + +![img](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704762728.png) + +流程如下: + +- 微服务中的`GrayLog`客户端发送日志到`GrayLog`服务端 +- `GrayLog`把日志信息格式化,存储到`Elasticsearch` +- 客户端通过浏览器访问`GrayLog`,`GrayLog`访问`Elasticsearch` + +这里`MongoDB`是用来存储`GrayLog`的配置信息的,这样搭建集群时,`GrayLog`的各节点可以共享配置。 + +- `MongoDB` 用于存储配置元信息和配置数据(无需太多硬件资源配置) +- `Elasticsearch` 用于存储日志数据(内存大以及更高的磁盘IO) +- `Graylog` 用于读取日志、展示日志。(CPU密集) + +缺点: + +- 系统无冗余、容易出现单点故障,适合测试阶段 + +## 大型生产环境架构 + +![img](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704762891.png) + +>- 客户访问和日志源面对的是前端的`LB`,`LB`通过`Graylog REST API `负载管理Graylog集群 +>- `Elasticsearch` 使用集群方案,多节点存储数据,达到备份冗余、负载的效应 +>- `MongoDB` 集群,具备自动的容错功能(auto-failover),自动恢复的(auto-recovery)的高可用 +>- 各组件方便伸缩扩展 +## 安装部署 + +使用 docker-compose 最小化安装 + + +``` +#部署Elasticsearch +docker run -d \ + --name elasticsearch \ + -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ + -e "discovery.type=single-node" \ + -v es-data:/usr/share/elasticsearch/data \ + -v es-plugins:/usr/share/elasticsearch/plugins \ + --privileged \ + -p 9200:9200 \ + -p 9300:9300 \ +elasticsearch:7.17.5 + +#部署MongoDB(使用之前部署的服务即可) +docker run -d \ +--name mongodb \ +-p 27017:27017 \ +--restart=always \ +-v mongodb:/data/db \ +-e MONGO_INITDB_ROOT_USERNAME=xlcs \ +-e MONGO_INITDB_ROOT_PASSWORD=123321 \ +mongo:4.4 + +#部署 +docker run \ +--name graylog \ +-p 9000:9000 \ +-p 12201:12201/udp \ +-e GRAYLOG_HTTP_EXTERNAL_URI=http://:9000/ \ +-e GRAYLOG_ELASTICSEARCH_HOSTS=http://:9200/ \ +-e GRAYLOG_ROOT_TIMEZONE="Asia/Shanghai" \ +-e GRAYLOG_WEB_ENDPOINT_URI="http://:9000/:9000/api" \ +-e GRAYLOG_PASSWORD_SECRET="somepasswordpepper" \ +-e GRAYLOG_ROOT_PASSWORD_SHA2=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 \ +-e GRAYLOG_MONGODB_URI=mongodb://xlcs:123321@:27017/admin \ +-d \ +graylog/graylog:4.3 +``` + +命令解读: + +- 端口信息: + +- - `-p 9000:9000`:`GrayLog`的http服务端口,9000 + - `-p 12201:12201/udp`:`GrayLog`的GELF UDP协议端口,用于接收从微服务发来的日志信息 + +- 环境变量 + +- - `-e GRAYLOG_HTTP_EXTERNAL_URI`:对外开放的ip和端口信息,这里用9000端口 + - `-e GRAYLOG_ELASTICSEARCH_HOSTS`:`GrayLog`依赖于`ES`,这里指定`ES`的地址 + - `-e GRAYLOG_WEB_ENDPOINT_URI`:对外开放的API地址 + - `-e GRAYLOG_PASSWORD_SECRET`:密码加密的秘钥 + - `-e GRAYLOG_ROOT_PASSWORD_SHA2`:密码加密后的密文。明文是`admin`,账户也是`admin` + - `-e GRAYLOG_ROOT_TIMEZONE="Asia/Shanghai"`:`GrayLog`容器内时区 + - `-e GRAYLOG_MONGODB_URI`:指定`MongoDB`的链接信息 + +- `graylog/graylog:4.3`:使用的镜像名称,版本为4.3 + +查看运行状态 +![image-20240109092047285](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704763247.png) + +访问地址 http://:9000/ , 如果可以看到如下界面说明启动成功。 + +![image-20240109092430238](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704763470.png) + +通过 `admin/admin`登录,即可看到欢迎页面,目前还没有数据: + +![image-20240109092519114](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704763519.png) + +## 简单使用以及介绍 + +>- `Search` 搜索:该选项允许您按特定条件搜索和筛选日志数据,例如时间范围、源、消息和其他字段。还可以将搜索保存为仪表板小部件或警报。 +>- `Streams` 流:流用于实时处理和过滤传入的日志数据。可以根据特定规则和条件创建流,Graylog 将自动对数据进行分类和索引以供进一步分析。 +>- `Alerts` 警报:警报允许您为特定事件或条件设置通知。例如,可以设置警报以在出现突然错误峰值或在日志中出现特定消息时通知。 +>- `Dashboards` 仪表板:仪表板允许您以有意义的方式可视化日志数据。可以创建自定义仪表板,其中包含图表、图形和表格,显示想要监控的数据。 +>- `Enterprise` 企业版:高级版功能,提供额外的功能,如基于角色的访问控制、LDAP/AD集成和集群,以实现高可用性和可扩展性。 +>- `Security` 安全性:该选项提供加密、身份验证和授权等安全功能,以确保日志数据受到保护。 +>- `System` 系统:该选项提供系统级别的设置和配置,例如管理用户、设置输入和输出插件以及监控系统性能。 +### 1.配置 `Inputs` + +部署完成`GrayLog`后,需要配置`Inputs`才能接收微服务发来的日志数据。 + +第一步,在`System`菜单中选择`Inputs`: + +![image-20240109094050433](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704764450.png) + +第二步,在页面的下拉选框中,选择`GELF UDP`: + +![5037550cc85d85f5fe12861a64843466](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704764674.png) + +然后点击`Launch new input`按钮: + +![21110bb0182c00ebea37f4e235375fd3](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704764708.png) + +点击`save`保存: + +![image-20240109094333858](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704764614.png) + +可以看到,`GELF UDP Inputs `保存成功。 + +### 2.整合微服务 + +现在,GrayLog的服务端日志收集器已经准备好,我们还需要在项目中添加GrayLog的客户端,将项目日志发送到GrayLog服务中,保存到ElasticSearch。 + +基本步骤如下: + +- 引入GrayLog客户端依赖 +- 配置Logback,集成GrayLog的Appender +- 启动并测试 + +导入依赖: + +```xml + + biz.paluch.logging + logstash-gelf + 1.15.0 + +``` + +配置`Logback`,在配置文件中增加 `GELF`的`appender`: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + ${ENCODING} + + + + + + + + ${LOG_HOME}/${APP_NAME}/debug/${POD_NAME}-%d{yyyy-MM-dd}-%i.log + + ${LOG_FILE_MAX_SIZE} + + ${LOG_FILE_MAX_HISTORY} + + ${LOG_TOTAL_SIZE_CAP} + + + DEBUG + ACCEPT + DENY + + + ${NORMAL_LOG_PATTERN} + ${ENCODING} + + + + + + + + ${LOG_HOME}/${APP_NAME}/info/${POD_NAME}-%d{yyyy-MM-dd}-%i.log + + ${LOG_FILE_MAX_SIZE} + + ${LOG_FILE_MAX_HISTORY} + + ${LOG_TOTAL_SIZE_CAP} + + + + INFO + ACCEPT + DENY + + + ${NORMAL_LOG_PATTERN} + ${ENCODING} + + + + + + + + ${LOG_HOME}/${APP_NAME}/error/${POD_NAME}-%d{yyyy-MM-dd}-%i.log + + ${LOG_FILE_MAX_SIZE} + + ${LOG_FILE_MAX_HISTORY} + + ${LOG_TOTAL_SIZE_CAP} + + + ERROR + ACCEPT + DENY + + + ${NORMAL_LOG_PATTERN} + ${ENCODING} + + + + + + + ${LOG_HOME}/${APP_NAME}/druid/SlowSql_${POD_NAME}-%d{yyyy-MM-dd}-%i.log + + ${LOG_FILE_MAX_SIZE} + + ${LOG_FILE_MAX_HISTORY} + + ${LOG_TOTAL_SIZE_CAP} + + + ERROR + ACCEPT + DENY + + + ${NORMAL_LOG_PATTERN} + ${ENCODING} + + + + + + + + + + + + + + + + + udp: + + 12201 + 1.1 + + ${appName} + true + true + true + yyyy-MM-dd HH:mm:ss,SSS + 8192 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +点击`search`按钮即可看到日志数据: + +![image-20240109095535652](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704765336.png) + +### 3.日志回收策略 + +到此graylog的基础配置就算完成了,已经可以收到日志数据。 + +但是在实际工作中,服务日志会非常多,这么多的日志,如果不进行存储限制,那么不久就会占满磁盘,查询变慢等等,而且过久的历史日志对于实际工作中的有效性也会很低。 + +Graylog则自身集成了日志数据限制的配置,可以通过如下进行设置: + +![image-20240109145002566](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704783002.png) + +选择`Default index set`的`Edit`按钮: + +![38fba6a5b77842928d69838c6e366c62](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704785689.png) + +GrayLog有3种日志回收限制,触发以后就会开始回收空间,删除索引: + +![2c6218061870d65547522aebf22335e3](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704785718.png) + +分别是: + +- `Index Message Count`:按照日志数量统计,默认超过`20000000`条日志开始清理 +- `Index Size`:按照日志大小统计,默认超过`1GB`开始清理 +- `Index Message Count`:按照日志日期清理,默认日志存储1天 + +### 4.搜索语法 + +在search页面,可以完成基本的日志搜索功能: + +![c645ec766d0f2eb6109fab1344d04cd1](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704785655.png) + +```shell +#不指定字段,默认从message字段查询 +输入:undo + +#输入两个关键字,关系为or +undo 统计 + +#加引号是需要完整匹配 +"undo 统计" + +#指定字段查询,level表示日志级别,ERROR(3)、WARNING(4)、NOTICE(5)、INFO(6)、DEBUG(7) +level: 6 + +#或条件 +level:(6 OR 7) +``` + +更多查询官网文档:https://docs.graylog.org/docs/query-language + +### 5.自定义展示字段 + +![bd999948509ec96203230c1a1139ce78](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704785625.png) + +效果如下: + +![image-20240109153728442](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704785848.png) + +### 6.日志统计仪表盘 + +GrayLog支持把日志按照自己需要的方式形成统计报表,并把许多报表组合一起,形成DashBoard(仪表盘),方便对日志统计分析。 + +创建仪表盘 + +![image-20240109153845240](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704785925.png) + +![9585c83b65fc665c3a9db3055c997c8f](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704788275.png) + +![image-20240109153955165](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704785995.png) + +在这里创建小组件 +![f29f62d315bee87d182a570f408da69c](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704788310.png) + + + + + +![image-20240109161518566](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704788119.png) + +![image-20240109161531180](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704788131.png) + +![image-20240109161550617](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704788151.png) + +![image-20240109161648891](https://lsky.hhdxw.top/imghub/2024/01/image-202401091704788209.png) diff --git a/安装环境/安装教程/Harbor.md b/安装环境/安装教程/Harbor.md new file mode 100644 index 0000000..a3f4066 --- /dev/null +++ b/安装环境/安装教程/Harbor.md @@ -0,0 +1,117 @@ +Harbor是一个开源的可信云原生注册表,用于存储、签名和扫描内容。它为开源Docker发行版添加了安全、身份和管理等功能。 + +## 自动安装 + +```console +curl -LO https://raw.githubusercontent.com/bitnami/containers/main/bitnami/harbor-portal/docker-compose.yml + +curl -L https://github.com/bitnami/containers/archive/main.tar.gz | tar xz --strip=2 containers-main/bitnami/harbor-portal && cp -RL harbor-portal/config . && rm -rf harbor-portal + +docker-compose up +``` + +## 手动安装 + +### 1、下载安装包 + +```shell +wget https://github.com/goharbor/harbor/releases/download/v2.7.4/harbor-offline-installer-v2.7.4.tgz +``` + +### 2、解压安装包,进入目录并展示文件 + +```shell +tar -xvf harbor-offline-installer-v2.7.4.tar +cd harbor +ll +``` + +![image-20231208150823727](https://lsky.hhdxw.top/imghub/2023/12/image-202312081702019310.png) + +### 3、复制harbor.yml配置文件并编辑 + +```shell +cp harbor.yml.tmpl harbor.yml +vim harbor.yml +``` + +### 4、编辑文件内容如图 + +![image-20231208151131781](https://lsky.hhdxw.top/imghub/2023/12/image-202312081702019492.png) + +### 5、启动项目 + +```shell +./install.sh +``` + +即可看到安装,等待安装完毕即可。 + +## 简单使用 + +首先进入页面,输入上面设置的密码登录(默认访问80端口,ip:80) + +![image-20231208151408705](https://lsky.hhdxw.top/imghub/2023/12/image-202312081702019649.png) + +进入页面后新建一个项目例如 `public` + +![image-20231208151853837](https://lsky.hhdxw.top/imghub/2023/12/image-202312081702019934.png) + +访问级别是公开,存储限制为无限(镜像代理可以去配置aliyun,此代理是用户在harbor仓库中找不到,对应镜像,然后去代理仓库中查找镜像) + +![image-20231208152259575](https://lsky.hhdxw.top/imghub/2023/12/image-202312081702020179.png) + +打开另外一台服务器,配置`docker`镜像设置 + + + +```shell +sudo vim /etc/docker/daemon.json +``` + +将下面的内容复制进去(``更换为上述`Harbor`服务器地址) + +```xml +{ + "registry-mirrors": [ + "https://hub.docker.com", + "http://:80" + ], + "insecure-registries" : [ + ":80" + ] +} +``` + +保存退出,然后重新加载配置启动`docker` + +```shell +sudo systemctl daemon-reload +sudo systemctl restart docker +``` + +在本地镜像(下载了一个`redis`作为演示)打一个标签 + +```shell +#下载镜像 +docker pull redis + +#给镜像打标签 +# redis:latest 被打标签的本地镜像 +# :80/public/redis:v1新的标签名称 +# 是指定的 IP 地址,80 是端口号,public/redis:v1 是新的标签。 +docker tag redis:latest :80/public/redis:v1 + + +#登录远程Harbor仓库 +docker login -u -p http://:80 + +#推送镜像 +docker push :80/public/redis:v1 +``` + +![image-20231208154658318](https://lsky.hhdxw.top/imghub/2023/12/image-202312081702021618.png) + +在`Harbor`中就可以看到镜像了 + +![image-20231208230150057](https://lsky.hhdxw.top/imghub/2023/12/image-202312081702047710.png) \ No newline at end of file diff --git a/安装环境/安装教程/Minio.md b/安装环境/安装教程/Minio.md new file mode 100644 index 0000000..855186b --- /dev/null +++ b/安装环境/安装教程/Minio.md @@ -0,0 +1,9 @@ +```shell +docker run -p 9000:9000 -p 9090:9090 --name minio \ + -d --restart=always \ + -e "MINIO_ACCESS_KEY=minio" \ + -e "MINIO_SECRET_KEY=password" \ + -v database:/data \ + minio/minio server \ + /data --console-address ":9090" -address ":9000" +``` \ No newline at end of file diff --git a/开发工具/安装教程/MongoDB.md b/安装环境/安装教程/MongoDB.md similarity index 100% rename from 开发工具/安装教程/MongoDB.md rename to 安装环境/安装教程/MongoDB.md diff --git a/开发工具/安装教程/Mysql.md b/安装环境/安装教程/Mysql.md similarity index 100% rename from 开发工具/安装教程/Mysql.md rename to 安装环境/安装教程/Mysql.md diff --git a/安装环境/安装教程/Nexus3.md b/安装环境/安装教程/Nexus3.md new file mode 100644 index 0000000..a22a699 --- /dev/null +++ b/安装环境/安装教程/Nexus3.md @@ -0,0 +1,429 @@ +![img](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704267879.png) + +Nexus 3 是一款由 Sonatype 公司开发的仓库管理工具。它是一个用于存储、发布和管理软件组件的集中式仓库,特别适用于 Java 开发人员。Nexus 3 支持 Maven、Gradle 和其他构建工具,并提供了强大的搜索、版本控制和权限管理功能。使用 Nexus 3 可以帮助团队更好地组织和共享代码库,并提高开发效率。 + +## 1.Nexus 3 的安装 + +### 1.docker启动命令 + +或者可以使用Docker-Compose进行编排 + +linux x86-64 + +```shell +docker run -d -p 8081:8081 --name nexus sonatype/nexus3 +``` + +aarch64 +```shell +docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 --name nexus klo2k/nexus3 +``` +### 2.访问 Nexus 3 + +通过访问 http://localhost:8081 进入Nexus 3 的网站 + +![image-20231227152301705](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703661782.png) + +### 3.获取密码并登录 + +![image-20231227154319306](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703662999.png) + +点击页面右上角的"Sign in",按弹窗提示找到默认密码 +- 默认账号:admin +- 默认密码:容器内的 "/nexus-data/admin.password" +- 会提示重置密码,改个记得住的密码(或者用默认密码),后面配置需要用到 + +在命令行中使用下面的命令进行获取密码 + +```shell +docker exec nexus cat /nexus-data/admin.password +``` + +### 4.修改密码并设置 + +![image-20231227152749263](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703662069.png) + +默认开启匿名访问 + +![image-20231227152900691](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703662141.png) + +这就基本安装完成 + +![image-20231227154643340](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703663203.png) + + + +## 2.内容介绍 + +![](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703663203.png) + +右侧分为四个一级菜单分别是 `Welcome` `Search` `Browse` `Upload` + +- Welcome + + - 欢迎界面,展示仓库的数据 + + - Total components : 总组件数量 + + - Unique logins : 登录数量(30天内) + + - Peak requests per minute : 每分钟峰值请求(24小时内) + + - Peak requests per day : 每天的峰值请求(30天内) + +![image-20231227172644727](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703669205.png) + +- Search + + - 用于在所有仓库中,搜索是否存在某个`jar`包(依赖) + - Group(组织):如果我们想搜索 Spring Framework 的构件,可以使用 group:org.springframework 进行查询。 + - Artifact(构件):如果我们想搜索 Spring Framework 的 web 模块,可以使用 artifact:spring-web 进行查询。 + - Version(版本):如果我们想搜索 Spring Framework 版本为 5.3.10.RELEASE 的构件,可以使用 version:5.3.10.RELEASE 进行查询。另外,如果想模糊匹配多个版本,可以使用通配符 ,例如 version:5.3. 可以匹配到所有 5.3.x 版本。 + - Base Version(基本版本):如果我们想搜索 Spring Framework 的 SNAPSHOT 版本,可以使用 baseVersion:5.3.11-SNAPSHOT 进行查询。这样就可以忽略掉 SNAPSHOT 部分,只匹配基本版本号为 5.3.11 的构件。 + - Classifier(分类器):如果我们想搜索 Spring Framework 的 Windows 版本构件,可以使用 classifier:windows 进行查询。另外,有些 Maven 项目会为不同的环境打包不同的构件,例如 dev、test、prod 等,可以使用 classifier 进行搜索。 + - Extension(扩展名):如果我们只想搜索 jar 文件,可以使用 extension:jar 进行查询。另外,如果我们想搜索所有类型的构件,可以省略该查询条件,默认会搜索所有扩展名的文件。 + +![](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703669229.png) + + +- Browse + - 检查该nexus私服拥有多少私有的仓库 + - maven-central 是一个代理仓库,它代表了 Maven 中央仓库,它是默认的 Maven 仓库,包含了大量的开源 Java 类库和框架,可以改成国内代理,如阿里云([https://maven.aliyun.com/repository/public](https://maven.aliyun.com/repository/public)) + - maven-public 是一个托管的仓库,它允许将自己的库发布到其中,并使其可供其他人使用。 + - maven-releases 是一个托管的仓库,用于发布稳定版本的库。 + - maven-snapshots 是一个托管的仓库,用于发布开发中或者测试版的库。 + +![image-20231227172852084](https://lsky.hhdxw.top/imghub/2023/12/image-202312271703669332.png) + + +- Upload + + - 上传 jar 包到私有仓库 + - 点击仓库后可以进行上传 jar 包 + +![image-20240102150816215](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704247153.png) + + + +## 3. Jar包推送仓库 + +### 1.修改项目pom.xml文件配置 + +- 直接改造Maven项目(或者新建一个Maven项目) + - 执行"mvn package"能打包出jar的项目就行 + - 如下在pom.xml添加nexus3地址信息 + - 与dependencies/build同级 + +```xml +...略 +...略 + + + + maven-releases + maven-releases + ${这里填写从nexus页面上复制的maven-releases的url} + + + maven-snapshots + maven-snapshots + ${这里填写从nexus页面上复制的maven-snapshots的url} + + +``` + +![image-20240103151816700](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266297.png) + +- 推送包 + - 执行 "mvn deoloy" + - 此时报错 "...status: 401 Unauthorized",说明项目配置正确 + +### 2.修改Maven软件配置 + +- 打开 _${maven根目录}/conf/settings.xml_ + - 增加nexus账号密码 + - id要和在项目pom.xml中配置的一致 + +```xml + + + maven-releases + admin + ${密码} + + + maven-snapshots + admin + ${密码} + + +``` + +![image-20240103151929256](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266369.png) + +- 再次执行"mvn deploy"就不会报错了 + - 刷新nexus页面上可以看到上传的包 + - 项目版本不带"-SNAPSHOT"在"maven-releases"目录 + + - 否则在"maven-releases"目录 + + +### 3.使用deploy推送 + +![image-20240103152221677](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266542.png) + +![image-20240103152112596](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266473.png) + +![image-20240103152000386](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266400.png) + +## 4. 引用jar包 + +### 1.修改Maven软件配置 + +- 打开 _${maven根目录}/conf/settings.xml_ + - 启用镜像,如果有其他镜像可以注释掉 +- 重启IDE,pom.xml中像线上的库一样添加dependency即可使用 + +```xml + + nexus-public + * + 私有仓库 + ${这里填写从nexus页面上复制的maven-public的url} + +``` + +![image-20240103151929256](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266369.png) + +### 2.项目引入jar包 + +```xml + + com.example + mini-demo + 0.0.1-20231206.141630-1 + +``` + +- 可以看到没有刷新之前是爆红显示在本地找不到该jar包 + +![image-20240103152406703](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266647.png) + +- 刷新Maven后就可以看到成功引入该jar包 + +![image-20240103152426226](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704266666.png) + +## 5.docker镜像拉取 + +### 1.创建一个存储库 + +![image-20240103160627759](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704269188.png) + +- 选择如下配置 + - Type 选择 File 或者 S3(自行配置) + - Name 可以自行设置(这里默认 docker ) + - 然后不选择容量提示,点击确认保存 + +![image-20240103161409621](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704269650.png) + +### 2.创建镜像仓库 + +![image-20240103162040214](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704270040.png) + +- 仓库分三种 + - Docker (group) 是指在一个集群环境中使用 Docker。在这种情况下,多个 Docker 容器被组织成一个群组,共享资源并通过集中的管理方式进行操作。这使得容器之间可以相互通信和协调,并且可以提供高可用性和负载均衡等功能。 + - Docker (hosted) 是指在托管环境中使用 Docker。这意味着 Docker 引擎和容器管理系统(如 Docker Swarm 或 Kubernetes)被部署在第三方云服务提供商或托管服务上。这样,用户可以将自己的应用程序打包为容器,并在托管环境中运行,而无需关心底层基础设施的维护和管理。 + - Docker (proxy) 是指在 Docker 网络中使用代理服务器。代理服务器充当位于容器和外部网络之间的中间人,转发请求和响应。使用代理服务器可以提供额外的安全性、负载均衡和缓存等功能,并允许容器与外部网络进行通信,同时隔离容器内部网络。 + +#### 1.创建代理仓库 Docker (proxy) + +![image-20240103162218913](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704270139.png) + +- Docker仓库配置设置 + + - Name 仓库名称,命名为 docker-registry-163 + + - Online 是否接受传入,选择勾选接受传入 + + - HTTP http连接器,配置为 http 使用,不配置 + + - HTTPS https连接器,配置为 https 使用,不配置 + - HTTP连接器和HTTPS连接器:用于配置Docker仓库使用的协议。HTTP连接器使用HTTP协议,而HTTPS连接器使用HTTPS协议。 + + - Allow anonymous docker pull 允许匿名 docker 拉取,默认开启 + + - Enable Docker V1 APl 启动 Docker V1 API ,默认开启 + + - Remote storage 远程存储,代理仓库设置为163镜像库(其他也可) + + - Docker Index Docker 索引,选择为Docker Hub + + - Use the Nexus Repository truststore 使用Nexus存储库信任存储,默认不开启 + - 默认情况下不开启,表示是否使用Nexus存储库的信任存储。开启后,Nexus会使用自己的存储库来管理信任证书。 + + - Foreign Layer Caching 外层缓存,默认不启用 + - 外层缓存可以提高Docker镜像的下载速度和性能。 + + - Maximum component age 最大构建年龄,默认1440 + + - 如果一些镜像或组件已经存在于仓库中超过了这个时间,它们将被自动清理掉以释放空间。 + + - Maximum metadata age 最大元数据年龄,默认1440 + + - 仓库中存储的元数据(metadata)的最长保留时间。这些元数据包括标签(tag)、索引(index)等信息。 + + - 限制存储空间:通过设置最大构建年龄和最大元数据年龄,可以控制仓库中存储的镜像和相关组件的数量和大小。当镜像或组件的存在时间超过设定的最大年龄时,它们将被自动清理,从而释放存储空间。 + + - 管理版本控制:Docker 镜像和相关组件可能会随着时间的推移多次更新和构建。通过设置最大构建年龄,您可以确保仓库中只保留最新的镜像和组件,这有助于避免过时的版本占用存储空间。 + + - 提高性能:仓库中存储的元数据(如标签、索引等)也会随着时间的推移不断增长。通过设置最大元数据年龄,可以定期清理过时的元数据,从而提高仓库的性能和响应速度。 + + - 维护仓库健康:定期清理过时的镜像、组件和元数据有助于保持仓库的健康状态。它可以防止仓库变得杂乱不堪,减少潜在的错误和故障。 + - Blob store 存储库,选择前面设置的 docker + - Not found cache enabled 未找到启用的缓存,默认勾选 + - 用于启用或禁用 "Not Found Cache" 功能。当启用此功能时,如果某个请求在仓库中未找到所需的内容(例如镜像或组件),仓库将会缓存这个"未找到"的结果。这样,当后续的请求再次发生时,仓库可以更快地响应,而不必重新执行相同的查询 + - Not found cache TTL 未找到缓存TTL,默认1440 + - 指 "Not Found Cache" 功能中缓存的超时时间。一旦某个未找到的结果被缓存起来,在经过一段时间后(即 TTL 时间)将会自动过期并从缓存中清除。 + - Cleanup Policies 清理政策,不做更改 + - 用于定义清理操作的策略。它允许您指定仓库中过期或不再需要的镜像、组件或其他资源的自动清理规则。可以根据各种条件(例如最后访问时间、构建日期等)来定义清理政策,以确保仓库保持干净和高效。 + +![image-20240103162453578](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704270293.png) + +![image-20240103162505966](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704270306.png) + +#### 2.创建主仓库 Docker (group) + +- 配置 + - Name 为 docker-registry + - HTTP 对外接口为 8082(前面创建时候启用8082端口) + - 勾选允许匿名拉取 + - 勾选 Docker V1 API + - 存储库设置为 docker + - Member repositories 存储库设置为前面创建的 docker-registry-163 + +![image-20240103165831888](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704272312.png) + +#### 3.启用 Docker + +![image-20240103170429111](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704272669.png) + +#### 4.对应服务器配置镜像仓库 + +- ip以及配置 + - Nexus 3 安装在了本地电脑的 docker 中,本地IP 10.211.55.2 + - 目标服务器 CentOS Stream 9 aarch64 ,IP 10.211.55.40 + +```sh +vim /etc/docker/daemon.json +``` + +在文件中写入下面的配置 + +```json +{ + "insecure-registries": [ + "10.211.55.2:8082" + ], + "registry-mirrors": [ + "http://10.211.55.2:8082" + ] +} +``` + +重启 Docker + +```sh +systemctl daemon-reload +systemctl restart docker +``` + +## 6.docker镜像推送 + +#### 1.nexus 3 配置 + +创建 docker-private 存储库 + +![image-20240103231907873](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704295148.png) + +- 创建镜像仓库 docker(hosted) + - HTTP 端口 8083 + - 开启匿名拉取 + - 开启 Docker V1 API 接口 + - 存储库 docker-private + +![image-20240103232044665](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704295245.png) + +docker-registry 仓库中增加存储库 docker-private + +![image-20240103232319285](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704295399.png) + +- 创建角色 + - Role ID 角色ID ,设置为 docker-publish + - Role Name 角色名称,设置为 docker-publish + - Role Description 角色描述,设置为 docker-publish + - Privilegs 权限,设置为 nx-repository-admin-docker-docker-private-* + - nx-repository-admin-docker-docker-private-* ,代表Docker 仓库中的私有仓库。这个特权表示被赋予的角色将对私有 Docker 仓库具有管理权限,包括创建、删除、推送和拉取镜像等操作。 + - Roles 角色,设置为 nx-admin + +![image-20240103232509055](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704295509.png) + +![image-20240103232522379](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704295522.png) + +- 创建用户 + - ID ,设置为docker + - Fisrst Name ,设置为docker + - Last Name ,设置为docker + - Email ,设置为docker@gmail + - Password ,设置为docker + - status 状态,设置为 Active + - Roles 角色,赋予 docker-publish + +![image-20240103233251898](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704295972.png) + +#### 2.配置服务器 + +- ip以及配置 + - Nexus 3 安装在了本地电脑的 docker 中,本地IP 10.211.55.2 + - 目标服务器 CentOS Stream 9 aarch64 ,IP 10.211.55.40 + +```sh +vim /etc/docker/daemon.json +``` + +在文件中写入下面的配置 + +```json +{ + "insecure-registries": [ + "10.211.55.2:8082", + "10.211.55.2:8083" + ], + "registry-mirrors": [ + "http://10.211.55.2:8082" + ] +} +``` + +```sh +//重启 Docker +systemctl daemon-reload +systemctl restart docker + +//查看本地镜像 +docker images + +//打标签 +docker tag redis:latest 10.211.55.2:8083/redis:v1.0 + +//登录仓库 +docker login -u docker -p docker 10.211.55.2:8083 + +//推送打标签的镜像 +docker push 10.211.55.2:8083/redis:v1.0 +``` + +![image-20240103234013588](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704296413.png) + +在本地仓库就能看到上传的镜像了 + +![image-20240103234244993](https://lsky.hhdxw.top/imghub/2024/01/image-202401031704296565.png) diff --git a/开发工具/安装教程/Nginx.md b/安装环境/安装教程/Nginx.md similarity index 100% rename from 开发工具/安装教程/Nginx.md rename to 安装环境/安装教程/Nginx.md diff --git a/开发工具/安装教程/Portainer.md b/安装环境/安装教程/Portainer.md similarity index 95% rename from 开发工具/安装教程/Portainer.md rename to 安装环境/安装教程/Portainer.md index fbdb85d..033c341 100644 --- a/开发工具/安装教程/Portainer.md +++ b/安装环境/安装教程/Portainer.md @@ -26,8 +26,8 @@ docker images ### 4、运行镜像 -```she l -docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer +```shell +docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce ``` 1. `-d` 表示在后台以守护进程的方式运行容器。 diff --git a/开发工具/安装教程/RabbitMQ.md b/安装环境/安装教程/RabbitMQ.md similarity index 89% rename from 开发工具/安装教程/RabbitMQ.md rename to 安装环境/安装教程/RabbitMQ.md index 24a5ee5..19e9e35 100644 --- a/开发工具/安装教程/RabbitMQ.md +++ b/安装环境/安装教程/RabbitMQ.md @@ -18,6 +18,9 @@ docker pull rabbitmq docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq ``` +```shell +docker run -d --restart=always -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:3.8-management +``` ### 4.安装插件 找到镜像ID @@ -29,7 +32,7 @@ docker ps 进入容器 ```shell -docker exec -it 镜像ID /bin/bash +docker exec -it my-rabbit /bin/bash ``` 安装插件 diff --git a/开发工具/安装教程/Redis.md b/安装环境/安装教程/Redis.md similarity index 100% rename from 开发工具/安装教程/Redis.md rename to 安装环境/安装教程/Redis.md diff --git a/安装环境/安装教程/Skywalking链路追踪.md b/安装环境/安装教程/Skywalking链路追踪.md new file mode 100644 index 0000000..3bbea80 --- /dev/null +++ b/安装环境/安装教程/Skywalking链路追踪.md @@ -0,0 +1,438 @@ +## 什么是APM + +随着微服务架构的流行,一次请求往往需要涉及到多个服务,因此服务性能监控和排查就变得更复杂 + +- 不同的服务可能由不同的团队开发、甚至可能使用不同的编程语言来实现 +- 服务有可能布在了几千台服务器,横跨多个不同的数据中心 + +因此,就需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题,这就是APM系统,全称是(**A**pplication **P**erformance **M**onitor,当然也有叫 **A**pplication **P**erformance **M**anagement tools) + +APM最早是谷歌公开的论文提到的 [Google Dapper](http://bigbully.github.io/Dapper-translation)。Dapper是Google生产环境下的分布式跟踪系统,自从Dapper发展成为一流的监控系统之后,给google的开发者和运维团队帮了大忙,所以谷歌公开论文分享了Dapper。 + +## 技术选型 + +市面上的全链路监控理论模型大多都是借鉴Google Dapper论文,重点关注以下三种APM组件: + +- [**Zipkin**](https://link.juejin.im/?target=http%3A%2F%2Fzipkin.io%2F):由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括:数据的收集、存储、查找和展现。 +- [**Pinpoint**](https://pinpoint.com/):一款对Java编写的大规模分布式系统的APM工具,由韩国人开源的分布式跟踪组件。 +- [**Skywalking**](https://skywalking.apache.org/zh/):国产的优秀APM组件,是一个对JAVA分布式应用程序集群的业务运行情况进行追踪、告警和分析的系统。现在是Apache的顶级项目之一。 + +选项就是对比各个系统的使用差异,主要对比项: + +1. **探针的性能** + 主要是agent对服务的吞吐量、CPU和内存的影响。微服务的规模和动态性使得数据收集的成本大幅度提高。 +2. **collector的可扩展性** + 能够水平扩展以便支持大规模服务器集群。 +3. **全面的调用链路数据分析** + 提供代码级别的可见性以便轻松定位失败点和瓶颈。 +4. **对于开发透明,容易开关** + 添加新功能而无需修改代码,容易启用或者禁用。 +5. **完整的调用链应用拓扑** + 自动检测应用拓扑,帮助你搞清楚应用的架构 + +三者对比如下: + +| 对比项 | zipkin | pinpoint | skywalking | +| ---------------- | ------ | -------- | ---------- | +| 探针性能 | 中 | 低 | **高** | +| collector扩展性 | **高** | 中 | **高** | +| 调用链路数据分析 | 低 | **高** | 中 | +| 对开发透明性 | 中 | **高** | **高** | +| 调用链应用拓扑 | 中 | **高** | 中 | +| 社区支持 | **高** | 中 | **高** | + +综上所述,使用skywalking是最佳的选择。 + +## SkyWalking 是什么 + +![](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704861050.png) + +SkyWalking是一款采用Java语言开发的分布式应用性能监控系统,支持跟踪、监控和诊断分布式系统,特别是以微服务架构和云原生应用为代表的新一代大规模分布式系统,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。它提供了实时应用拓扑分析、应用性能指标监控、调用链路追踪、慢服务检测、性能优化建议等功能。 + +## SkyWalking 功能 + + - 多种监控手段。可以通过语言探针和 service mesh 获得监控是数据。 + - 多个语言自动探针。包括 Java、.NET Core、Node.JS、Go。 + - 轻量高效。无需大数据平台,和大量的服务器资源。 + - 模块化。UI、存储、集群管理都有多种机制可选。 + - 支持告警。 + - 优秀的可视化解决方案。 + - 开源免费,社区活跃 + +## 用来解决什么问题 + + SkyWalking作为新一代APM系统,主要解决了以下几方面的问题: + + - 分布式系统监控难题:在复杂的分布式系统中,调用链路蔓延多个服务,难以跟踪。SkyWalking提供了全链路追踪能力,可以清晰还原整个调用流程。 + + - 应用性能监控不足:很多系统对性能指标的监控不够全面和深入。SkyWalking可以从多个维度监控应用性能,找到瓶颈。 + + - 故障定位时间长:系统发生问题后,通过日志难以定位根因。而SkyWalking的追踪信息可以快速定位故障服务。 + + - 依赖关系复杂度高:微服务系统中间依赖关系错综复杂。SkyWalking提供服务拓扑 map,一目了然。 + + - 监控成本高:许多APM工具需要PAYLOAD接入,增加额外开销。SkyWalking使用无侵入式探针,减少系统侵入。 + + - 技术栈限制严重:旧监控系统只支持少数语言。SkyWalking提供Java、.NET等主流语言的探针。 + + - 监控视图单一:较老的APM系统UI功能简陋。SkyWalking提供丰富的时序图、拓扑图等可视化监控视图。 + + - 云原生支持不足:早期APM系统不支持新型微服务技术栈。SkyWalking在云原生支持上做了大量优化。 + + 总之,SkyWalking作为新一代解决方案,很好地解决了传统APM系统在分布式链路跟踪和应用监控等方面的痛点,值得推荐使用。 + +## SkyWalking 整体架构 + +![架构图](https://skywalking.apache.org/zh/2020-04-19-skywalking-quick-start/0081Kckwly1gkl533fk5xj31pc0s8h04.jpg) + + - 上部分 Agent :负责从应用中,收集链路信息,发送给 SkyWalking OAP 服务器。目前支持 SkyWalking、Zikpin、Jaeger 等提供的 Tracing 数据信息。而我们目前采用的是,SkyWalking Agent 收集 SkyWalking Tracing 数据,传递给服务器。 + - 下部分 SkyWalking OAP :负责接收 Agent 发送的 Tracing 数据信息,然后进行分析(Analysis Core) ,存储到外部存储器( Storage ),最终提供查询( Query )功能。 + - 右部分 Storage :Tracing 数据存储。目前支持 ES、MySQL、Sharding Sphere、TiDB、H2 多种存储器。而我们目前采用的是 ES ,主要考虑是 SkyWalking 开发团队自己的生产环境采用 ES 为主。 + - 左部分 SkyWalking UI :负责提供控台,查看链路等等。 + +## 简单使用 + +### 1.安装单机环境 + +使用 Docker-compose 启动容器,我的服务器有点扛不住,所以使用本地虚拟机来运行,IP地址为10.211.55.50,系统使用的是CentOS Stream 9 + +```yaml +version: "3" +services: + elasticsearch: + image: elasticsearch:8.4.2 + container_name: elasticsearchsky + ports: + - "9200:9200" + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:9200/_cluster/health || exit 1"] #⼼跳检测,成功之后不再执⾏后⾯的退出 + interval: 60s #⼼跳检测间隔周期 + timeout: 10s + retries: 3 + start_period: 60s #⾸次检测延迟时间 + environment: + discovery.type: single-node #单节点模式 + ingest.geoip.downloader.enabled: "false" + bootstrap.memory_lock: "true" + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + TZ: "Asia/Shanghai" + xpack.security.enabled: "false" #单机模式 + ulimits: + memlock: + soft: -1 + hard: -1 + skywalking-oap: + image: apache/skywalking-oap-server:9.3.0 + container_name: skywalking-oap + depends_on: + elasticsearch: + condition: service_healthy + links: + - elasticsearch + environment: + SW_HEALTH_CHECKER: default + SW_STORAGE: elasticsearch + SW_STORAGE_ES_CLUSTER_NODES: 10.211.55.50:9200 + JAVA_OPTS: "-Xms2048m -Xmx2048m" + TZ: Asia/Shanghai + SW_TELEMETRY: prometheus + healthcheck: + test: ["CMD-SHELL", "/skywalking/bin/swctl ch"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + restart: on-failure + ports: + - "11800:11800" + - "12800:12800" + skywalking-ui: + image: apache/skywalking-ui:9.3.0 + container_name: skywalking-ui + depends_on: + skywalking-oap: + condition: service_healthy + links: + - skywalking-oap + ports: + - "8080:8080" + environment: + SW_OAP_ADDRESS: http://10.211.55.50:12800 + SW_HEALTH_CHECKER: default + TZ: Asia/Shanghai + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8080 || exit 1"] #⼼跳检测,成功之后不再执⾏后⾯的退出 + interval: 60s #⼼跳检测间隔周期 + timeout: 10s + retries: 3 + start_period: 60s #⾸次检测延迟时间 +``` + + + +```yaml +version: '3.8' +services: + elasticsearch: + # image: docker.elastic.co/elasticsearch/elasticsearch-oss:${ES_VERSION} + image: elasticsearch:7.9.0 + container_name: elasticsearch + # --restart=always : 开机启动,失败也会一直重启; + # --restart=on-failure:10 : 表示最多重启10次 + restart: always + ports: + - "9200:9200" + - "9300:9300" + healthcheck: + test: [ "CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1" ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + environment: + - discovery.type=single-node + # 锁定物理内存地址,防止elasticsearch内存被交换出去,也就是避免es使用swap交换分区,频繁的交换,会导致IOPS变高; + - bootstrap.memory_lock=true + # 设置时区 + - TZ=Asia/Shanghai + # - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + + oap: + image: apache/skywalking-oap-server:8.9.1 + container_name: oap + restart: always + # 设置依赖的容器 + depends_on: + elasticsearch: + condition: service_healthy + # 关联ES的容器,通过容器名字来找到相应容器,解决IP变动问题 + links: + - elasticsearch + # 端口映射 + ports: + - "11800:11800" + - "12800:12800" + # 监控检查 + healthcheck: + test: [ "CMD-SHELL", "/skywalking/bin/swctl ch" ] + # 每间隔30秒执行一次 + interval: 30s + # 健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败; + timeout: 10s + # 当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。 + retries: 3 + # 应用的启动的初始化时间,在启动过程中的健康检查失效不会计入,默认 0 秒。 + start_period: 10s + environment: + # 指定存储方式 + SW_STORAGE: elasticsearch + # 指定存储的地址 + SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200 + SW_HEALTH_CHECKER: default + SW_TELEMETRY: prometheus + TZ: Asia/Shanghai + # JAVA_OPTS: "-Xms2048m -Xmx2048m" + + ui: + image: apache/skywalking-ui:8.9.1 + container_name: skywalking-ui + restart: always + depends_on: + oap: + condition: service_healthy + links: + - oap + ports: + - "8088:8080" + environment: + SW_OAP_ADDRESS: http://oap:12800 + TZ: Asia/Shanghai + +``` + +![image-20240110101134534](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704852694.png) + +访问 http://10.211.55.50:8080就能看到SkyWalking UI的面板了 + +![image-20240110103640735](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704854201.png) + +### 2.下载 SkyWalking Agent + +下载 SkyWalking Agent 地址:https://skywalking.apache.org/downloads/ + +![image-20240110095842377](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704851923.png) + +```shell +# 下载完成后解压 +tar xvf apache-skywalking-java-agent-9.1.0.tgz +``` + +![image-20240110101843212](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704853123.png) + +### 3.项目引入SkyWalking Agent + +![image-20240110102208214](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704853328.png) + +在IDEA编辑运行配置增加虚拟机选项,如下所示 + +javaagent:上面下载的jar包的路径 + +Dskywalking.agent.service_name:服务组::服务名称 + +Dskywalking.collector.backend_service:SkyWalking地址 + +```shell +-javaagent:/Users/yovinchen/Desktop/project/skywalking-agent/skywalking-agent.jar +-Dskywalking.agent.service_name=ms::xlcs-parent-service-acl +-Dskywalking.collector.backend_service=10.211.55.50:11800 +``` + +然后运行项目就能够在SkyWalking UI中看到服务 + +![image-20240110103640735](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704854201.png) + +## 功能介绍 + +### 普通服务 + +#### 服务 + +##### Service 服务 + +![image-20240110103640735](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704854201.png) + +首页中展示的是现有的服务项 + +- **Service Groups(服务组):** 将服务按照某种逻辑分组的方式展示,有助于对大型系统中服务的管理和监控。 +- **Service Names(服务名称):** 表示不同的服务单元或模块的名称,每个服务都会有一个唯一的服务名称用于标识。 +- **Load (calls / min)(负载 - 调用次数 / 分钟):** 指每个服务单位时间内的调用次数,反映了服务的负载情况。 +- **Success Rate (%)(成功率):** 表示服务成功处理的请求在总请求数中的比率,以百分比形式表示。 +- **Latency (ms)(延迟):** 表示服务处理请求所需的时间,通常以毫秒为单位。 +- **Apdex(应用程序性能指数):** 是一种度量应用程序性能的指标,基于用户对延迟的感知。它将用户体验划分为满意、可容忍和不满意三个级别,通过比较满意请求的数量和所有请求的总数,计算出一个衡量应用性能的指数。 + +##### Topology 拓扑结构 + +![image-20240110104159073](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704854519.png) + +展示的是该项目调用的拓扑结构图,可以清楚的看到各服务之间的调用情况 + +##### Trace 链路追踪 + +![image-20240110104657552](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704854818.png) + +在这里可以通过查询查看详细程序的调用链路、详细的调用时间、调用的模块、我们可以通过这些信息判断出哪一个服务/SQL查询比较慢进而去优化。 + +下面是一些不同的展示方式便于分析问题 + +![image-20240110105041733](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704855042.png) + +![image-20240110105052331](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704855052.png) + +![image-20240110105102508](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704855063.png) + +##### Log 日志 + +![image-20240110105137090](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704855097.png) + +#### 虚拟数据库 + +SkyWalking 可以通过分析项目中的调用情况推断出相应的数据库 + +![image-20240110105548556](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704855349.png) + +- **Service Names(服务名称):** 在虚拟数据库中,可能指代不同数据库或数据库集群的特定名称,用于标识和区分各个数据库服务单元。 +- **Latency (ms)(延迟):** 表示在处理请求时所需的时间,以毫秒为单位。对于数据库而言,延迟可能表示执行查询、读取或写入数据所需的时间。 +- **Successful Rate (%)(成功率):** 表示成功执行的请求在总请求数中的比率,用百分比表示。对于数据库,可能表示成功完成查询或事务的比率。 +- **Traffic (calls / min)(流量 - 调用次数 / 分钟):** 指代在一分钟内数据库所收到的请求或调用次数。这可能包括查询、写入或其他与数据库相关的操作。 + +#### 虚拟缓存 + +SkyWalking 可以通过分析项目中的调用情况推断出相应的缓存 + +![image-20240110105603828](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704855364.png) + +- **Service Names(服务名称):** 在虚拟缓存中,这可能是不同缓存服务或缓存集群的特定名称,用于标识和区分各个缓存服务单元。 +- **Access Latency (ms)(访问延迟):** 表示从缓存中获取数据所需的时间,通常以毫秒为单位。这是指从发出请求到获得所需数据的时间。 +- **Successful Access Rate (%)(成功访问率):** 表示从缓存成功获取数据的请求在总请求数中的比率,以百分比表示。对于缓存而言,这代表着有效利用缓存的程度。 +- **Access Traffic (calls / min)(访问流量 - 调用次数 / 分钟):** 表示在一分钟内针对缓存的访问请求次数。这可能包括读取、写入或其他缓存操作的总次数。 + +#### 虚拟消息队列 + +SkyWalking 可以通过分析项目中的调用情况推断出相应的消息队列 + +![image-20240110105618984](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704855379.png) + +- **Service Names(服务名称):** 这指代不同的服务单元或者模块的名称,每个服务可能是消息队列的一个实例,消费者、生产者或者其他与消息传递相关的组件。 +- **Transmission Latency (ms)(传输延迟):** 表示消息从生产者发送到消费者接收所需的时间。这个指标衡量了消息在系统中传输的速度,通常以毫秒为单位。 +- **Consume Successful Rate (%)(消费成功率):** 表示消费者成功接收并处理消息的比率,即成功消费的消息数量占总消息量的百分比。 +- **Consume Traffic (calls / min)(消费流量 - 调用次数 / 分钟):** 表示消费者消费消息的频率,即消费者在一分钟内处理的消息数量。 +- **Produce Successful Rate (%)(生产成功率):** 表示生产者成功将消息发送到消息队列或者系统中的比率,即成功发送的消息数量占总发送消息量的百分比。 +- **Produce Traffic (calls / min)(生产流量 - 调用次数 / 分钟):** 表示生产者向消息队列或者系统发送消息的频率,即在一分钟内发送的消息数量。 + +### 基础设施 + +#### linux + +SkyWalking 可以实现监控 Linux 系统情况 + +![image-20240111111955577](https://lsky.hhdxw.top/imghub/2024/01/image-202401111704943196.png) + +Linux服务器接入SkyWalking需要两个工具: + +node_exporter pentelemetry-collector + +https://github.com/prometheus/node_exporter/releases + +```shell +wget -i https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-arm64.tar.gz +``` + +通过命令解压 + +```shell +tar -xvf node_exporter-1.7.0.linux-arm64.tar.gz +``` + +创建一个系统服务,让node_exporter能够开机自启并通过系统管控 + +```shell +vim /etc/systemd/system/node_exporter.service +``` + +```yaml +[Unit] +Description=node exporter service +Documentation=https://prometheus.io +After=network.target + +[Service] +Type=simple +User=root +Group=root +ExecStart=/root/node_exporter-1.7.0.linux-arm64/node_exporter #这里是node_exporter的解压地址 +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` + +执行命令刷新系统服务 + +``` +systemctl daemon-reload +``` + +开启node_exporter服务 + +``` +systemctl start node_exporter +``` + +这里服务默认监听9100端口,请勿占用,就完成了node_exporter的安装 + + + diff --git a/开发工具/安装教程/Tomcat.md b/安装环境/安装教程/Tomcat.md similarity index 100% rename from 开发工具/安装教程/Tomcat.md rename to 安装环境/安装教程/Tomcat.md diff --git a/安装环境/安装教程/Ubuntu安装Docker.md b/安装环境/安装教程/Ubuntu安装Docker.md new file mode 100644 index 0000000..c0b0fa3 --- /dev/null +++ b/安装环境/安装教程/Ubuntu安装Docker.md @@ -0,0 +1,36 @@ +先更新源,并且安装必要的依赖软件 + +```shell +sudo apt update +sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common +``` + +导入源仓库的 GPG key + +```shell +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - +``` + +添加 Docker APT 软件源 + +```shell +sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" +``` + +安装 Docker 最新版本 + +```shell +sudo apt install docker-ce docker-ce-cli containerd.io +``` + +至此安装完成。 +输入命令验证是否安装成功,查看是否显示Docker版本号。 + +```shell +docker version +``` + +```shell +sudo apt-get update +sudo apt install docker-compose-plugin +``` \ No newline at end of file diff --git a/安装环境/安装教程/YuFei.83721.@.md b/安装环境/安装教程/YuFei.83721.@.md new file mode 100644 index 0000000..dd79d09 --- /dev/null +++ b/安装环境/安装教程/YuFei.83721.@.md @@ -0,0 +1 @@ +yufei83721@gmail.com \ No newline at end of file diff --git a/安装环境/安装教程/elasticsearch + kibana.md b/安装环境/安装教程/elasticsearch + kibana.md new file mode 100644 index 0000000..066302a --- /dev/null +++ b/安装环境/安装教程/elasticsearch + kibana.md @@ -0,0 +1,22 @@ +``` +docker run -p 9200:9200 -p 9300:9300 --name elasticsearch \ +-e "discovery.type=single-node" \ +-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \ +-d elasticsearch:7.8.0 +``` + +``` +docker exec -it elasticsearch /bin/bash +``` + +``` +./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.5/elasticsearch-analysis-ik-7.17.5.zip +``` + +``` +docker restart elasticsearch +``` + +``` +docker run -d --name kibana --link elasticsearch:elasticsearch -p 5601:5601 kibana:7.8.0 +``` diff --git a/安装环境/安装教程/gitea+Drone.md b/安装环境/安装教程/gitea+Drone.md new file mode 100644 index 0000000..315a433 --- /dev/null +++ b/安装环境/安装教程/gitea+Drone.md @@ -0,0 +1,99 @@ +version: "3" +networks: + dronenet: + +services: + server: + image: gitea/gitea:1.20.5 + container_name: gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + restart: always + networks: + - dronenet + volumes: + - ./gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "2222:22" + - "10800:3000" + + drone-server: + image: drone/drone:latest + container_name: drone-server + ports: + - "1380:80" + - "8000:8000" + - "9000:9000" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /dnmp/drone/:/var/lib/drone/:rw + restart: always + environment: + - DRONE_GITEA_SERVER=http://10.211.55.12:10800 + - DRONE_DEBUG=true + - DRONE_GIT_ALWAYS_AUTH=false + - DRONE_GITEA_CLIENT_ID=30503c87-93d0-4678-81d0-d8ea2aef8f25 + - DRONE_GITEA_CLIENT_SECRET=gto_hdtxrgkyhpuzsggn5xeoujnub7q4hdsbafd56zl23rx2ppknxska + - DRONE_RUNNER_CAPACITY=2 + - DRONE_SERVER_HOST=10.211.55.12 + - DRONE_SERVER_PROTO=http + - DRONE_RPC_SECRET=9c3921e3e748aff725d2e16ef31fbc42 + - DRONE_TLS_AUTOCERT=false + - DRONE_USER_CREATE=username:yovinchen,admin:true + - TZ=Asia/Shanghai + networks: + - dronenet + + drone-agent: + image: drone/agent:latest + container_name: drone-agent + command: agent + restart: always + depends_on: + - drone-server + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - DRONE_RPC_SERVER=http://10.211.55.12:9000 + - DRONE_RPC_SECRET=9c3921e3e748aff725d2e16ef31fbc42 + - DRONE_DEBUG=true + - DRONE_LOGS_DEBUG=true + - DRONE_LOGS_PRETTY=true + - DRONE_LOGS_NOCOLOR=false + - TZ=Asia/Shanghai + networks: + - dronenet + + + +docker pull drone/drone:2 +docker stop drone +docker rm drone +docker run -d --name=drone \ + --env=DRONE_GITEA_SERVER=http://10.211.55.12:10800 \ + --env=DRONE_GITEA_CLIENT_ID=52c774fd-f670-400f-9e7b-8bcd68cfa75a \ + --env=DRONE_GITEA_CLIENT_SECRET=gto_ozlilshmhxn2atjz5po2gn6tickcwijt4hleobwnqhhbh5vxovva \ + --env=DRONE_RPC_SECRET=aaf5eab277a8d9236d822025aeaa4530 \ + --env=DRONE_SERVER_HOST=10.211.55.12:1080 \ + --env=DRONE_SERVER_PROTO=http \ + --publish=1080:80 \ + --publish=4443:443 \ + --restart=always \ + --detach=true \ + drone/drone:2 +docker pull drone/drone-runner-docker:1 +docker stop drone-runner +docker rm drone-runner +docker run -d --name=drone-runner \ + --link drone:drone \ + -p 9030:3000 \ + --env=DRONE_RPC_PROTO=http \ + --env=DRONE_RPC_HOST=10.211.55.12:1080 \ + --env=DRONE_RPC_SECRET=aaf5eab277a8d9236d822025aeaa4530 \ + --env=DRONE_RUNNER_CAPACITY=2 \ + --env=DRONE_RUNNER_NAME=my-first-runner \ + -v /var/run/docker.sock:/var/run/docker.sock \ + drone/drone-runner-docker:1 \ No newline at end of file diff --git a/安装环境/安装教程/jenkins.md b/安装环境/安装教程/jenkins.md new file mode 100644 index 0000000..51a3ad8 --- /dev/null +++ b/安装环境/安装教程/jenkins.md @@ -0,0 +1,15 @@ +``` +docker run -d -uroot -p 9095:8080 -p 50000:50000 --name jenkins -v /home/jenkins_home:/var/jenkins_home -v /etc/localtime:/etc/localtime jenkins/jenkins +``` + +``` +docker run \ + -u root \ + --rm \ + -d \ + -p 8080:8080 \ + -p 50000:50000 \ + -v jenkins-data:/var/jenkins_home \ + -v /var/run/docker.sock:/var/run/docker.sock \ + jenkinsci/blueocean +``` \ No newline at end of file diff --git a/安装环境/安装教程/nacos.md b/安装环境/安装教程/nacos.md new file mode 100644 index 0000000..cdd756f --- /dev/null +++ b/安装环境/安装教程/nacos.md @@ -0,0 +1,8 @@ +``` +docker run -d --name nacos-server -p 8848:8848 -p 9848:9848 \ +-e MODE=standalone \ +-e JVM_XMS=512m \ +-e JVM_XMX=512m \ +-v /logs/nacos:/home/nacos/logs \ +nacos/nacos-server:v2.2.3 +``` \ No newline at end of file diff --git a/安装环境/安装教程/安装Minio服务.md b/安装环境/安装教程/安装Minio服务.md new file mode 100644 index 0000000..6174216 --- /dev/null +++ b/安装环境/安装教程/安装Minio服务.md @@ -0,0 +1,209 @@ +## MinIO是什么? + +![](https://lsky.hhdxw.top/imghub/2024/01/image-202401101704861743.png) + +官方解释:MinIO 是一个基于Apache License v2.0开源协议的[对象存储](https://cloud.tencent.com/product/cos?from_column=20065&from=20065)服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。 + +MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。 + +## MinIO有哪些优势 + +### 1. 开发文档全面 + +MinIO作为一款基于Golang 编程语言开发的一款高性能的分布式式存储方案的开源项目,有十分完善的官方文档。。 + +官网文档地址:https://docs.min.io/cn/ + +### 2.高性能 + +MinIO号称是目前速度最快的对象存储服务器。在标准硬件上,对象存储的读/写速度最高可以高达183 GB/s和171 GB/s。对象存储可以作为主存储层,用来处理Spark、Presto、TensorFlow、H2O.ai等各种复杂工作负载以及成为Hadoop HDFS的替代品。 + +MinIO用作云原生应用程序的主要存储,和传统对象存储相比,云原生应用程序需要更高的吞吐量和更低的延迟。而这些都是MinIO能够达成的性能指标。 + +### 3.容器化集成方便 + +MinIO提供了与k8s、etcd、docker等主流容器化技术深度集成方案。 + +## 安装MinIo服务 + +想要安装Minio有两种方案,第一种是直接在服务器上安装,还有一种是使用Docker快速部署。 + +官方文档:https://www.minio.org.cn/docs/cn/minio + +### 直接安装 + +安装包下载页面:https://dl.min.io/server/minio/release/,我们需要选择好对应架构和系统的安装包,比如Ubuntu/Debian系统下,我们可以像这样安装: + +```sh +wget https://dl.min.io/server/minio/release/linux-amd64/minio_20230831153116.0.0_amd64.deb -O minio.deb +sudo dpkg -i minio.deb +``` + +在CentOS/Redhat(RHEL)系统下,可以像这样安装: + +```sh +wget https://dl.min.io/server/minio/release/linux-amd64/minio-20230831153116.0.0.x86_64.rpm -O minio.rpm +sudo dnf install minio.rpm +``` + +安装完成后,我们需要创建一个用于存放minio文件数据的目录: + +```sh +sudo mkdir /mnt/minio +``` + +然后创建一个minio使用的用户,并为此用户分配对应目录的访问权限: + +```sh +groupadd -r minio-user +useradd -M -r -g minio-user minio-user +chown minio-user:minio-user /mnt/minio +``` + +接着我们需要创建Minio的配置文件: + +```sh +sudo vim /etc/default/minio +``` + +编写以下内容到配置文件中: + +```properties +#系统管理员账号和密码 +MINIO_ROOT_USER=minio +MINIO_ROOT_PASSWORD=password +#设置用于存储数据的目录 +MINIO_VOLUMES="/mnt/minio" +#项目接口访问地址 +MINIO_SERVER_URL="http://0.0.0.0:9000" +#配置其他选项,比如后台管理系统的地址 +MINIO_OPTS="--console-address 0.0.0.0:9090" +``` + +配置完成之后就可以启动Minio服务了: + +```sh +sudo systemctl enable minio.service +sudo systemctl start minio.service +``` + +### 使用Docker安装 + +对于各位小伙伴本地开发测试,这里更推荐使用Docker镜像安装,非常简单快捷,本地安装好Docker客户端之后,只需要执行以下命令就能快速部署: + +```sh +docker run -p 9000:9000 -p 9090:9090 --name minio \ + -d --restart=always \ + -e "MINIO_ACCESS_KEY=minio" \ + -e "MINIO_SECRET_KEY=password" \ + -v database:/data \ + minio/minio server \ + /data --console-address ":9090" -address ":9000" +``` + +非常方便快捷。 + +------ + +## 使用Java操作对象存储 + +前面我们搭建好了对象存储服务,接着就可以开始愉快地使用了。 + +### 基本使用 + +Minio已经为我们提供了封装好的依赖,我们只需要直接使用即可: + +```xml + + io.minio + minio + 8.3.9 + +``` + +接着我们需要创建一个Minio客户端用于访问对象存储服务器: + +```java +MinioClient client = MinioClient.builder() + .endpoint("http://localhost:9000") //对象存储服务地址,注意是9000那个端口 + .credentials("minio", "password") //账户直接使用管理员 + .build(); +``` + +对于Java的相关操作,可以说非常全面,包含对桶的操作、对象操作等等,文档地址:https://www.minio.org.cn/docs/cn/minio/linux/developers/java/API.html,这里我们先演示一下文件上传: + +```java +//首先找到我们需要上传的文件 +File file = new File("/Users/nagocoler/Downloads/test.jpg"); +//构造一个输入流用于文件传输 +FileInputStream stream = new FileInputStream(file); +//填写上传的相关参数,使用PutObjectArgs的Builder来完成 +PutObjectArgs put = PutObjectArgs.builder() + .bucket("test") //设置需要上传的桶名称 + .object("666.jpg") //设置上传后保存的文件名称 + .stream(stream, file.length(), -1) //配置流以及大小还有分片大小 + .build(); +client.putObject(put); //使用putObject方法完成上传操作 +``` + +这样就可以将文件上传到对象存储服务器中了。 + +接着是下载文件: + +```java +//填写下载相关的参数,使用GetObjectArgs来完成 +GetObjectArgs get = GetObjectArgs.builder() + .bucket("test") //设置需要下载的桶名称 + .object("666.jpg") //设置下载后保存的文件名称 + .build(); +GetObjectResponse response = client.getObject(get); +//使用输出流保存下载的文件到本地 +FileOutputStream stream = new FileOutputStream("/Users/nagocoler/Downloads/download.png"); +stream.write(response.readAllBytes()); +stream.close(); +``` + +最后是删除桶里的文件: + +```java +RemoveObjectArgs remove = RemoveObjectArgs.builder() + .bucket("test") //设置需要删除文件的桶名称 + .object("666.jpg") //设置删除的文件名称 + .build(); +client.removeObject(remove); //删就完事 +``` + +### 服务器文件上传和下载 + +实例代码: + +```java +@RestController +@RequestMapping("/") +public class FileController { + + @Resource + MinioClient client; + + @PostMapping("upload") + public String upload(@RequestParam("file") MultipartFile file) throws Exception { + InputStream stream = file.getInputStream(); + String name = UUID.randomUUID().toString(); + PutObjectArgs args = PutObjectArgs.builder() + .bucket("test") + .object("upload/"+name) + .stream(stream, file.getSize(), -1) + .build(); + client.putObject(args); + return name; + } + + @GetMapping("file/{name}") + public void file(@PathVariable("name") String name, + HttpServletResponse response) throws Exception{ + GetObjectResponse object = client.getObject(GetObjectArgs.builder().bucket("test").object("upload/"+name).build()); + ServletOutputStream stream = response.getOutputStream(); + stream.write(object.readAllBytes()); + } +} +``` \ No newline at end of file diff --git a/未命名.canvas b/未命名.canvas deleted file mode 100644 index 9e26dfe..0000000 --- a/未命名.canvas +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/杂记/typora快捷键.md b/杂记/typora快捷键.md new file mode 100644 index 0000000..49e1f42 --- /dev/null +++ b/杂记/typora快捷键.md @@ -0,0 +1,92 @@ +[TOC] + +## 摘要 + +您可以使用快捷键快速插入或修改样式,或者执行Typora支持的其他操作。 您可以在菜单栏中的每个菜单项的右侧找到快捷键。 + +### 自动完成 + +在macOS上,您可以`Esc`按键打开内联数学预览功能,自动完成表情符号等操作。 + +| 功能 | 热键(Windows / Linux) | 热键(macOS) | +| ------------------ | ----------------------- | ------------------- | +| 新建 | Ctrl + N | Command + N | +| 打开在新窗户 | Ctrl + Shift + N | Command + Shift + N | +| 新建标签 | *(不支持)* | Command + T | +| 打开 | Ctrl + O | Command + O | +| 快速打开 | Ctrl + P | Command + Shift + O | +| 重新打开关闭的文件 | Ctrl + Shift + T | Command + Shift + T | +| 保存 | Ctrl + S | Command + S | +| 另存为/重复 | Ctrl + Shift + S | Command + Shift + S | + +### 编辑 + +| 功能 | 热键(Windows / Linux) | 热键(macOS) | +| --------------------------------- | -------------------------- | ----------------------------------- | +| 新段落 | Shift + Enter | Shift + Enter | +| 剪切 | Ctrl + X | Command + X | +| 复制 | Ctrl + C | Command + C | +| 粘贴 | Ctrl + V | Command + V | +| 复制 | Ctrl + Shift + C | Command + Shift + C | +| 粘贴为纯文本 | Ctrl + Shift + V | Command + Shift + V | +| 全选 | Ctrl + A | Command + A | +| 选择行/句子 选择行(在表中) | Ctrl + L | Command + L | +| 删除行(在表中) | Ctrl + Shift +delete | Command + Shift +delete | +| 选择样式范围 选择单元格(在表中) | Ctrl + E | Command + E | +| 选择单词 | Ctrl + D | Command + D | +| 删除单词 | Ctrl + Shift + D | Command + Shift + D | +| 跳到顶部 | Ctrl +主页 | Command +↑ | +| 跳转到选择 | Ctrl + J | Command + J | +| 跳到Buttom | Ctrl +结束 | Command+↓ | +| 查找 | Ctrl + F | Command + F | +| 查找下一个 | F3 / Enter | Command + G / Enter | +| 查找上一个 | Shift + F3 / Shift + Enter | Command + Shift + G / Shift + Enter | + +### 段 + +| 功能 | 热键(Windows / Linux) | 热键(macOS) | +| ------------ | ----------------------- | -------------------- | +| 标题1至6 | Ctrl + 1/2/3/4/5/6 | 指令+ 1/2/3/4/5/6 | +| 段 | Ctrl + 0 | Command + 0 | +| 增加标题级别 | Ctrl + = | Command+ = | +| 降低标题级别 | Ctrl + - | Command+ - | +| 表格 | Ctrl + T | Command + Option + T | +| 代码块 | Ctrl + Shift + K | Command + Option + C | +| 数学块 | Ctrl + Shift + M | Command + Option + B | +| 引用 | Ctrl + Shift + Q | Command + Option + Q | +| 有序列表 | Ctrl + Shift + [ | Command + Option + O | +| 无序列表 | Ctrl + Shift +] | Command + Option + U | +| 缩进 | Ctrl + [ / Tab | Command + [ / Tab | + +### 格式 + +| 功能 | 热键(Windows / Linux) | 热键(macOS) | +| -------- | ----------------------- | --------------------- | +| 加粗 | Ctrl + B | Command + B | +| 斜体 | Ctrl + I | Command + I | +| 下划线 | Ctrl + U | Command + U | +| 代码 | Ctrl + Shift +` |Command + Shift + `| +| Strike | Alt + Shift + 5 | Ctrl + Shift +` | +| 超链接 | Ctrl + K | Command + K | +| 图片 | Ctrl + Shift + I | Command + Control + I | +| 清除格式 | Ctrl + \ | Command+ \ | + + + +### 视图 + +| 功能 | 热键(Windows / Linux) | 热键(macOS) | +| -------------------- | ----------------------- | --------------------- | +| 切换边栏 | Ctrl + Shift + L | Command + Shift + L | +| 大纲 | Ctrl + Shift + 1 | Command + Control+ 1 | +| 文章 | Ctrl + Shift + 2 | Command + Control + 2 | +| 文件树 | Ctrl + Shift + 3 | Command + Control + 3 | +| 源代码模式 | Ctrl + / | Command + / | +| 对焦模式 | F8 | F8 | +| 打字机模式 | F9 | F9 | +| 切换全屏 | F11 | Command + Option + F | +| 真实大小 | Ctrl + Shift + 0 | *(不支持)* | +| 放大 | Ctrl + Shift + = | *(不支持)* | +| 缩小 | Ctrl + Shift +- | *(不支持)* | +| 在打开的文档之间切换 | Ctrl + Tab | Command +` | +| 切换DevTools | Ctrl + Shift + I | -- | \ No newline at end of file diff --git a/照片/Pasted image 20230803101741.png b/照片/Pasted image 20230803101741.png deleted file mode 100644 index 39830e7..0000000 Binary files a/照片/Pasted image 20230803101741.png and /dev/null differ diff --git a/面试/面试/JVM相关面试题.md b/面试/面试/JVM相关面试题.md new file mode 100755 index 0000000..b71c656 --- /dev/null +++ b/面试/面试/JVM相关面试题.md @@ -0,0 +1,1726 @@ +# img + +## 1 JVM组成 + +### 1.1 JVM由那些部分组成,运行流程是什么? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +**JVM是什么** + +Java Virtual Machine Java程序的运行环境(java二进制字节码的运行环境) + +好处: + +- 一次编写,到处运行 + +- 自动内存管理,垃圾回收机制 + +![image-20230506094254360](img\image-20230506094254360.png) + +**JVM由哪些部分组成,运行流程是什么?** + +![image-20230506094411247](img\image-20230506094411247.png) + +从图中可以看出 JVM 的主要组成部分 + +- ClassLoader(类加载器) +- Runtime Data Area(运行时数据区,内存分区) +- Execution Engine(执行引擎) +- Native Method Library(本地库接口) + +运行流程: + +(1)类加载器(ClassLoader)把Java代码转换为字节码 + +(2)运行时数据区(Runtime Data Area)把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层系统去执行,而是有执行引擎运行 + +(3)执行引擎(Execution Engine)将字节码翻译为底层系统指令,再交由CPU执行去执行,此时需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的功能。 + + + +### 1.2 什么是程序计数器? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +程序计数器:线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。 + +>javap -verbose xx.class 打印堆栈大小,局部变量的数量和方法的参数。 + +![image-20230506094602329](img\image-20230506094602329.png) + + + +​ java虚拟机对于多线程是通过线程轮流切换并且分配线程执行时间。在任何的一个时间点上,一个处理器只会处理执行一个线程,如果当前被执行的这个线程它所分配的执行时间用完了【挂起】。处理器会切换到另外的一个线程上来进行执行。并且这个线程的执行时间用完了,接着处理器就会又来执行被挂起的这个线程。 + +​ 那么现在有一个问题就是,当前处理器如何能够知道,对于这个被挂起的线程,它上一次执行到了哪里?那么这时就需要从程序计数器中来回去到当前的这个线程他上一次执行的行号,然后接着继续向下执行。 + +​ 程序计数器是JVM规范中唯一一个没有规定出现OOM的区域,所以这个空间也不会进行GC。 + +### 1.3 你能给我详细的介绍Java堆吗? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +线程共享的区域:主要用来保存对象实例,数组等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。 + +![image-20230506094803545](img\image-20230506094803545.png) + +- 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到老年代区间。 +- 老年代主要保存生命周期长的对象,一般是一些老的对象 +- 元空间保存的类信息、静态变量、常量、编译后的代码 + +​ + +为了避免方法区出现OOM,所以在java8中将堆上的方法区【永久代】给移动到了本地内存上,重新开辟了一块空间,叫做**元空间**。那么现在就可以避免掉OOM的出现了。 + +![image-20230506094938843](img\image-20230506094938843.png) + +##### 元空间(MetaSpace)介绍 + +​ 在 HotSpot JVM 中,永久代( ≈ 方法区)中用于存放类和方法的元数据以及常量池,比如Class 和 Method。每当一个类初次被加载的时候,它的元数据都会放到永久代中。 + +​ 永久代是有大小限制的,因此如果加载的类太多,很有可能导致永久代内存溢出,即OutOfMemoryError,为此不得不对虚拟机做调优。 + +​ 那么,Java 8 中 PermGen 为什么被移出 HotSpot JVM 了? + +官网给出了解释:http://openjdk.java.net/jeps/122 + +~~~ +This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation. + +移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。 +~~~ + +1)由于 PermGen 内存经常会溢出,引发OutOfMemoryError,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的 OOM。 + +2)移除 PermGen 可以促进 HotSpot JVM 与 JRockit VM 的融合,因为 JRockit 没有永久代。 + +​ 准确来说,Perm 区中的字符串常量池被移到了堆内存中是在 Java7 之后,Java 8 时,PermGen 被元空间代替,其他内容比如**类元信息、字段、静态属性、方法、常量**等都移动到元空间区。比如 java/lang/Object 类元信息、静态属性 System.out、整型常量等。 + +​ 元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。 + +### 1.4 什么是虚拟机栈 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +Java Virtual machine Stacks (java 虚拟机栈) + +- 每个线程运行时所需要的内存,称为虚拟机栈,先进后出 + +- 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存 + +- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法 + +![image-20230506095140595](img\image-20230506095140595.png) + +1. 垃圾回收是否涉及栈内存? + + 垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放 + +2. 栈内存分配越大越好吗? + + 未必,默认的栈内存通常为1024k + + 栈帧过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个,如果把栈内存改为2048k,那么能活动的栈帧就会减半 + +3. 方法内的局部变量是否线程安全? + + - 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的 + + - 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全 + + - 比如以下代码: + + ![image-20230506095306061](img\image-20230506095306061.png) + +**栈内存溢出情况** + +- 栈帧过多导致栈内存溢出,典型问题:递归调用 + + ![image-20230506095401637](img\image-20230506095401637.png) + +- 栈帧过大导致栈内存溢出 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +组成部分:堆、方法区、栈、本地方法栈、程序计数器 + +1、堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。 +2、方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。 +3、栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。 +4、本地方法栈与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。 +5、程序计数器(PC寄存器)程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。 + + + +### 1.5 能不能解释一下方法区? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +#### 1.5.1 概述 + +- 方法区(Method Area)是各个线程共享的内存区域 + +- 主要存储类的信息、运行时常量池 + +- 虚拟机启动的时候创建,关闭虚拟机时释放 + +- 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace + +![image-20230506095504213](img\image-20230506095504213.png) + +#### 1.5.2 常量池 + +可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息 + +查看字节码结构(类的基本信息、常量池、方法定义)`javap -v xx.class` + +比如下面是一个Application类的main方法执行,源码如下: + +```java +public class Application { + public static void main(String[] args) { + System.out.println("hello world"); + } +} +``` + +找到类对应的class文件存放目录,执行命令:`javap -v Application.class` 查看字节码结构 + +```java +D:\code\jvm-demo\target\classes\com\heima\jvm>javap -v Application.class +Classfile /D:/code/jvm-demo/target/classes/com/heima/jvm/Application.class + Last modified 2023-05-07; size 564 bytes //最后修改的时间 + MD5 checksum c1b64ed6491b9a16c2baab5061c64f88 //签名 + Compiled from "Application.java" //从哪个源码编译 +public class com.heima.jvm.Application //包名,类名 + minor version: 0 + major version: 52 //jdk版本 + flags: ACC_PUBLIC, ACC_SUPER //修饰符 +Constant pool: //常量池 + #1 = Methodref #6.#20 // java/lang/Object."":()V + #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; + #3 = String #23 // hello world + #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V + #5 = Class #26 // com/heima/jvm/Application + #6 = Class #27 // java/lang/Object + #7 = Utf8 + #8 = Utf8 ()V + #9 = Utf8 Code + #10 = Utf8 LineNumberTable + #11 = Utf8 LocalVariableTable + #12 = Utf8 this + #13 = Utf8 Lcom/heima/jvm/Application; + #14 = Utf8 main + #15 = Utf8 ([Ljava/lang/String;)V + #16 = Utf8 args + #17 = Utf8 [Ljava/lang/String; + #18 = Utf8 SourceFile + #19 = Utf8 Application.java + #20 = NameAndType #7:#8 // "":()V + #21 = Class #28 // java/lang/System + #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; + #23 = Utf8 hello world + #24 = Class #31 // java/io/PrintStream + #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V + #26 = Utf8 com/heima/jvm/Application + #27 = Utf8 java/lang/Object + #28 = Utf8 java/lang/System + #29 = Utf8 out + #30 = Utf8 Ljava/io/PrintStream; + #31 = Utf8 java/io/PrintStream + #32 = Utf8 println + #33 = Utf8 (Ljava/lang/String;)V +{ + public com.heima.jvm.Application(); //构造方法 + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 3: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/heima/jvm/Application; + + public static void main(java.lang.String[]); //main方法 + descriptor: ([Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=1, args_size=1 + 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; + 3: ldc #3 // String hello world + 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V + 8: return + LineNumberTable: + line 7: 0 + line 8: 8 + LocalVariableTable: + Start Length Slot Name Signature + 0 9 0 args [Ljava/lang/String; +} +SourceFile: "Application.java" +``` + +下图,左侧是main方法的指令信息,右侧constant pool 是常量池 + +main方法按照指令执行的时候,需要到常量池中查表翻译找到具体的类和方法地址去执行 + +![image-20230506095634842](img\image-20230506095634842.png) + +#### 1.5.3 运行时常量池 + +常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址 + +![image-20230506100142724](img\image-20230506100142724.png) + + + + + +### 1.6 你听过直接内存吗? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +不受 JVM 内存回收管理,是虚拟机的系统内存,常见于 NIO 操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受 JVM 内存回收管理 + + + +举例: + +需求,在本地电脑中的一个较大的文件(超过100m)从一个磁盘挪到另外一个磁盘 + +![image-20230506100501905](img\image-20230506100501905.png) + +代码如下: + +```java +/** + * 演示 ByteBuffer 作用 + */ +public class Demo1_9 { + static final String FROM = "E:\\编程资料\\第三方教学视频\\youtube\\Getting Started with Spring Boot-sbPSjI4tt10.mp4"; + static final String TO = "E:\\a.mp4"; + static final int _1Mb = 1024 * 1024; + + public static void main(String[] args) { + io(); // io 用时:1535.586957 1766.963399 1359.240226 + directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592 + } + + private static void directBuffer() { + long start = System.nanoTime(); + try (FileChannel from = new FileInputStream(FROM).getChannel(); + FileChannel to = new FileOutputStream(TO).getChannel(); + ) { + ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb); + while (true) { + int len = from.read(bb); + if (len == -1) { + break; + } + bb.flip(); + to.write(bb); + bb.clear(); + } + } catch (IOException e) { + e.printStackTrace(); + } + long end = System.nanoTime(); + System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0); + } + + private static void io() { + long start = System.nanoTime(); + try (FileInputStream from = new FileInputStream(FROM); + FileOutputStream to = new FileOutputStream(TO); + ) { + byte[] buf = new byte[_1Mb]; + while (true) { + int len = from.read(buf); + if (len == -1) { + break; + } + to.write(buf, 0, len); + } + } catch (IOException e) { + e.printStackTrace(); + } + long end = System.nanoTime(); + System.out.println("io 用时:" + (end - start) / 1000_000.0); + } +} +``` + +可以发现,使用传统的IO的时间要比NIO操作的时间长了很多了,也就说NIO的读性能更好。 + +这个是跟我们的JVM的直接内存是有一定关系,如下图,是传统阻塞IO的数据传输流程 + +![image-20230506100548455](img\image-20230506100548455.png) + +下图是NIO传输数据的流程,在这个里面主要使用到了一个直接内存,不需要在堆中开辟空间进行数据的拷贝,jvm可以直接操作直接内存,从而使数据读写传输更快。 + +![image-20230506100621146](img\image-20230506100621146.png) + + + +### 1.7 堆栈的区别是什么? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +1、栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。 + +2、栈内存是线程私有的,而堆内存是线程共有的。 + +3,、两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。 + +栈空间不足:java.lang.StackOverFlowError。 + +堆空间不足:java.lang.OutOfMemoryError。 + +## 2 类加载器 + +### 2.1 什么是类加载器,类加载器有哪些? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆ + +要想理解类加载器的话,务必要先清楚对于一个Java文件,它从编译到执行的整个过程。 + +![](img\image-20220903233627146.png) + +- 类加载器:用于装载字节码文件(.class文件) +- 运行时数据区:用于分配存储空间 +- 执行引擎:执行字节码文件或本地方法 +- 垃圾回收器:用于对JVM中的垃圾内容进行回收 + +**类加载器** + +JVM只会运行二进制文件,而类加载器(ClassLoader)的主要作用就是将**字节码文件加载到JVM中**,从而让Java程序能够启动起来。现有的类加载器基本上都是java.lang.ClassLoader的子类,该类的只要职责就是用于将指定的类找到或生成对应的字节码文件,同时类加载器还会负责加载程序所需要的资源 + +**类加载器种类** + +类加载器根据各自加载范围的不同,划分为四种类加载器: + +- **启动类加载器(BootStrap ClassLoader):** + + 该类并不继承ClassLoader类,其是由C++编写实现。用于加载**JAVA_HOME/jre/lib**目录下的类库。 + +- **扩展类加载器(ExtClassLoader):** + + 该类是ClassLoader的子类,主要加载**JAVA_HOME/jre/lib/ext**目录中的类库。 + +- **应用类加载器(AppClassLoader):** + + 该类是ClassLoader的子类,主要用于加载**classPath**下的类,也就是加载开发者自己编写的Java类。 + +- **自定义类加载器:** + + 开发者自定义类继承ClassLoader,实现自定义类加载规则。 + +上述三种类加载器的层次结构如下如下: + +![image-20230506100746624](img\image-20230506100746624.png) + +类加载器的体系并不是“继承”体系,而是**委派体系**,类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。 + +### 2.2 什么是双亲委派模型? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就返回成功;只有父类加载器无法完成此加载任务时,才由下一级去加载。 + +![image-20230506100920042](img\image-20230506100920042.png) + +### 2.3 JVM为什么采用双亲委派机制 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +(1)通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。 + +(2)为了安全,保证类库API不会被修改 + +在工程中新建java.lang包,接着在该包下新建String类,并定义main函数 + +```java +public class String { + + public static void main(String[] args) { + + System.out.println("demo info"); + } +} +``` + +​ 此时执行main函数,会出现异常,在类 java.lang.String 中找不到 main 方法 + +![image-20220903144547378](img\image-20220903144547378.png) + +​ 出现该信息是因为由双亲委派的机制,java.lang.String的在启动类加载器(Bootstrap classLoader)得到加载,因为在核心jre库中有其相同名字的类文件,但该类中并没有main方法。这样就能防止恶意篡改核心API库。 + +### 2.4 说一下类装载的执行过程? + +>难易程度:☆☆☆☆☆ +> +>出现频率:☆☆☆ + +类从加载到虚拟机中开始,直到卸载为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)。 + +![image-20230506101032605](img\image-20230506101032605.png) + +**类加载过程详解** + +1.加载 + +![image-20230506101115674](img\image-20230506101115674.png) + +- 通过类的全名,获取类的二进制数据流。 + +- 解析类的二进制数据流为方法区内的数据结构(Java类模型) + +- 创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口 + +![image-20230506101213373](img\image-20230506101213373.png) + +2.验证 + +![image-20230506101420202](img\image-20230506101420202.png) + +**验证类是否符合JVM规范,安全性检查** + +(1)文件格式验证:是否符合Class文件的规范 +(2)元数据验证 + 这个类是否有父类(除了Object这个类之外,其余的类都应该有父类) + 这个类是否继承(extends)了被final修饰过的类(被final修饰过的类表示类不能被继承) + 类中的字段、方法是否与父类产生矛盾。(被final修饰过的方法或字段是不能覆盖的) +(3)字节码验证 + 主要的目的是通过对数据流和控制流的分析,确定程序语义是合法的、符合逻辑的。 +(4)符号引用验证:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量 + +>比如:int i = 3; +>字面量:3 +>符号引用:i + +3.准备 + +![image-20230506101445898](img\image-20230506101445898.png) + +**为类变量分配内存并设置类变量初始值** + +- static变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成 + +- static变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成 + +- static变量是final的引用类型,那么赋值也会在初始化阶段完成 + +![image-20230506101824622](img\image-20230506101824622.png) + +4.解析 + +![image-20230506101504632](img\image-20230506101504632.png) + +**把类中的符号引用转换为直接引用** + +比如:方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法。 + +![image-20230506102311951](img\image-20230506102311951.png) + +5.初始化 + +![image-20230506101625087](img\image-20230506101625087.png) + +**对类的静态变量,静态代码块执行初始化操作** + +- 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。 + +- 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。 + +6.使用 + +![image-20230506101641837](img\image-20230506101641837.png) + +JVM 开始从入口方法开始执行用户的程序代码 + +- 调用静态类成员信息(比如:静态字段、静态方法) + +- 使用new关键字为其创建对象实例 + +7.卸载 + +当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存 + + + +## 3 垃圾收回 + +### 3.1 简述Java垃圾回收机制?(GC是什么?为什么要GC) + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +为了让程序员更专注于代码的实现,而不用过多的考虑内存释放的问题,所以,在Java语言中,有了自动的垃圾回收机制,也就是我们熟悉的GC(Garbage Collection)。 + +有了垃圾回收机制后,程序员只需要关心内存的申请即可,内存的释放由系统自动识别完成。 + +在进行垃圾回收时,不同的对象引用类型,GC会采用不同的回收时机 + + + +换句话说,自动的垃圾回收的算法就会变得非常重要了,如果因为算法的不合理,导致内存资源一直没有释放,同样也可能会导致内存溢出的。 + +当然,除了Java语言,C#、Python等语言也都有自动的垃圾回收机制。 + + + +### 3.2 对象什么时候可以被垃圾器回收 + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +![image-20230506104954777](img\image-20230506104954777.png) + +简单一句就是:如果一个或多个对象没有任何的引用指向它了,那么这个对象现在就是垃圾,如果定位了垃圾,则有可能会被垃圾回收器回收。 + +如果要定位什么是垃圾,有两种方式来确定,第一个是引用计数法,第二个是可达性分析算法 + +#### 3.2.1 引用计数法 + +一个对象被引用了一次,在当前的对象头上递增一次引用次数,如果这个对象的引用次数为0,代表这个对象可回收 + +```java +String demo = new String("123"); +``` + +![image-20230506111102825](img\image-20230506111102825.png) + +```java +String demo = null; +``` + +![image-20230506111136231](img\image-20230506111136231.png) + +当对象间出现了循环引用的话,则引用计数法就会失效 + +![image-20230506111255401](img\image-20230506111255401.png) + +先执行右侧代码的前4行代码 + +![image-20230506111327590](img\image-20230506111327590.png) + +目前上方的引用关系和计数都是没问题的,但是,如果代码继续往下执行,如下图 + +![image-20230506111512450](img\image-20230506111512450.png) + +虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收。 + +优点: + +- 实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。 +- 在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报OOM错误。 +- 区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。 + +缺点: + +- 每次对象被引用时,都需要去更新计数器,有一点时间开销。 +- **浪费CPU资源**,即使内存够用,仍然在运行时进行计数器的统计。 +- **无法解决循环引用问题,会引发内存泄露**。(最大的缺点) + +#### 3.2.2 可达性分析算法 + +​ 现在的虚拟机采用的都是通过可达性分析算法来确定哪些内容是垃圾。 + +​ 会存在一个根节点【GC Roots】,引出它下面指向的下一个节点,再以下一个节点节点开始找出它下面的节点,依次往下类推。直到所有的节点全部遍历完毕。 + +>根对象是那些肯定不能当做垃圾回收的对象,就可以当做根对象 +> +>局部变量,静态方法,静态变量,类信息 +> +>核心是:判断某对象是否与根对象有直接或间接的引用,如果没有被引用,则可以当做垃圾回收 + +![image-20220904010634153](img\image-20220904010634153.png) + +​ X,Y这两个节点是可回收的,但是**并不会马上的被回收!!** 对象中存在一个方法【finalize】。当对象被标记为可回收后,当发生GC时,首先**会判断这个对象是否执行了finalize方法**,如果这个方法还没有被执行的话,那么就会先来执行这个方法,接着在这个方法执行中,可以设置当前这个对象与GC ROOTS产生关联,那么这个方法执行完成之后,GC会再次判断对象是否可达,如果仍然不可达,则会进行回收,如果可达了,则不会进行回收。 + +​ finalize方法对于每一个对象来说,只会执行一次。如果第一次执行这个方法的时候,设置了当前对象与RC ROOTS关联,那么这一次不会进行回收。 那么等到这个对象第二次被标记为可回收时,那么该对象的finalize方法就不会再次执行了。 + +**GC ROOTS:** + +- 虚拟机栈(栈帧中的本地变量表)中引用的对象 + +```java +/** + * demo是栈帧中的本地变量,当 demo = null 时,由于此时 demo 充当了 GC Root 的作用,demo与原来指向的实例 new Demo() 断开了连接,对象被回收。 + */ +public class Demo { + public static void main(String[] args) { + Demo demo = new Demo(); + demo = null; + } +} +``` + +- 方法区中类静态属性引用的对象 + +```java +/** + * 当栈帧中的本地变量 b = null 时,由于 b 原来指向的对象与 GC Root (变量 b) 断开了连接,所以 b 原来指向的对象会被回收,而由于我们给 a 赋值了变量的引用,a在此时是类静态属性引用,充当了 GC Root 的作用,它指向的对象依然存活! + */ +public class Demo { + public static Demo a; + public static void main(String[] args) { + Demo b = new Demo(); + b.a = new Demo(); + b = null; + } +} +``` + +- 方法区中常量引用的对象 + +```java +/** + * 常量 a 指向的对象并不会因为 demo 指向的对象被回收而回收 + */ +public class Demo { + + public static final Demo a = new Demo(); + + public static void main(String[] args) { + Demo demo = new Demo(); + demo = null; + } +} +``` + +- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象 + +### 3.3 JVM 垃圾回收算法有哪些? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +#### 3.3.1 标记清除算法 + +标记清除算法,是将垃圾回收分为2个阶段,分别是**标记和清除**。 + +1.根据可达性分析算法得出的垃圾进行标记 + +2.对这些标记为可回收的内容进行垃圾回收 + +![image-20230506112047190](img\image-20230506112047190.png) + +可以看到,标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。 + +同样,标记清除算法也是有缺点的: + +- 效率较低,**标记和清除两个动作都需要遍历所有的对象**,并且在GC时,**需要停止应用程序**,对于交互性要求比较高的应用而言这个体验是非常差的。 +- (**重要**)通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。 + +#### 3.3.2 复制算法 + +​ 复制算法的核心就是,**将原有的内存空间一分为二,每次只用其中的一块**,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。 + +​ 如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。 + +![image-20230506111919008](img\image-20230506111919008.png) + + + +1)将内存区域分成两部分,每次操作其中一个。 + +2)当进行垃圾回收时,将正在使用的内存区域中的存活对象移动到未使用的内存区域。当移动完对这部分内存区域一次性清除。 + +3)周而复始。 + +优点: + +- 在垃圾对象多的情况下,效率较高 +- 清理后,内存无碎片 + +缺点: + +- 分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低 + +#### 3.3.3 标记整理算法 + +​ 标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的直接清理可回收对象,而是将存活对象都向内存另一端移动,然后清理边界以外的垃圾,从而解决了碎片化的问题。 + +![image-20230506111957793](img\image-20230506111957793.png) + +1)标记垃圾。 + +2)需要清除向右边走,不需要清除的向左边走。 + +3)清除边界以外的垃圾。 + +优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。 + +与复制算法对比:复制算法标记完就复制,但标记整理算法得等把所有存活对象都标记完毕,再进行整理 + +### 3.4 分代收集算法 + +#### 3.4.1 概述 + +在java8时,堆被分为了两份:**新生代和老年代【1:2】**,在java7时,还存在一个永久代。 + +![image-20230506131229649](img\image-20230506131229649.png) + +对于新生代,内部又被分为了三个区域。Eden区,S0区,S1区【8:1:1】 + +当对新生代产生GC:MinorGC【young GC】 + +当对老年代代产生GC:Major GC + +当对新生代和老年代产生FullGC: 新生代 + 老年代完整垃圾回收,暂停时间长,**应尽力避免** + +#### 3.4.2工作机制 + +![image-20230506131308654](img\image-20230506131308654.png) + +- 新创建的对象,都会先分配到eden区 + +![image-20230506131415418](img\image-20230506131415418.png) + +- 当伊甸园内存不足,标记伊甸园与 from(现阶段没有)的存活对象 + +- 将存活对象采用复制算法复制到 to 中,复制完毕后,伊甸园和 from 内存都得到释放 + +![image-20230506131442503](img\image-20230506131442503.png) + +- 经过一段时间后伊甸园的内存又出现不足,标记eden区域to区存活的对象,将存活的对象复制到from区 + +![image-20230506131544447](img\image-20230506131544447.png) + +![image-20230506131607645](img\image-20230506131607645.png) + +- 当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升) + +**MinorGC、 Mixed GC 、 FullGC的区别是什么** + +![image-20230506131640893](img\image-20230506131640893.png) + +- MinorGC【young GC】发生在新生代的垃圾回收,暂停时间短(STW) + +- Mixed GC 新生代 + 老年代部分区域的垃圾回收,G1 收集器特有 + +- FullGC: 新生代 + 老年代完整垃圾回收,暂停时间长(STW),应尽力避免? + +>名词解释: +> +>STW(Stop-The-World):暂停所有应用程序线程,等待垃圾回收的完成 + +### 3.5 说一下 JVM 有哪些垃圾回收器? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +在jvm中,实现了多种垃圾收集器,包括: + +- 串行垃圾收集器 + +- 并行垃圾收集器 + +- CMS(并发)垃圾收集器 + +- G1垃圾收集器 + +#### 3.5.1 串行垃圾收集器 + +Serial和Serial Old串行垃圾收集器,是指使用单线程进行垃圾回收,堆内存较小,适合个人电脑 + +- Serial 作用于新生代,采用复制算法 + +- Serial Old 作用于老年代,采用标记-整理算法 + +垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。 + +![image-20230506154006266](img\image-20230506154006266.png) + + + +#### 3.5.2 并行垃圾收集器 + +Parallel New和Parallel Old是一个并行垃圾回收器,**JDK8默认使用此垃圾回收器** + +- Parallel New作用于新生代,采用复制算法 + +- Parallel Old作用于老年代,采用标记-整理算法 + +垃圾回收时,多个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。 + +![image-20230506154042673](img\image-20230506154042673.png) + +#### 3.5.2 CMS(并发)垃圾收集器 + +CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,该回收器是针对老年代垃圾回收的,是一款以获取最短回收停顿时间为目标的收集器,停顿时间短,用户体验就好。其最大特点是在进行垃圾回收时,应用仍然能正常运行。 + +![image-20230506154117857](img\image-20230506154117857.png) + +![image-20230506154107944](img\image-20230506154107944.png) + +### 3.6 详细聊一下G1垃圾回收器 + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +#### 3.6.1 概述 + +- 应用于新生代和老年代,**在****JDK9****之后默认使用****G1** + +- 划分成多个区域,每个区域都可以充当 eden,survivor,old, humongous,其中 humongous 专为大对象准备 + +- 采用复制算法 + +- 响应时间与吞吐量兼顾 + +- 分成三个阶段:新生代回收、并发标记、混合收集 + +- 如果并发失败(即回收速度赶不上创建新对象速度),会触发 Full GC + +![image-20230506154323950](img\image-20230506154323950.png) + +#### 3.6.2 Young Collection(年轻代垃圾回收) + +- 初始时,所有区域都处于空闲状态 + + ![image-20230506154542687](img\image-20230506154542687.png) + +- 创建了一些对象,挑出一些空闲区域作为伊甸园区存储这些对象 + + ![image-20230506154607558](img\image-20230506154607558.png) + +- 当伊甸园需要垃圾回收时,挑出一个空闲区域作为幸存区,用复制算法复制存活对象,需要暂停用户线程 + + ![image-20230506154633118](img\image-20230506154633118.png) + + ![image-20230506154705088](img\image-20230506154705088.png) + +- 随着时间流逝,伊甸园的内存又有不足 + +- 将伊甸园以及之前幸存区中的存活对象,采用复制算法,复制到新的幸存区,其中较老对象晋升至老年代 + + ![image-20230506154759809](img\image-20230506154759809.png) + + ![image-20230506154826981](img\image-20230506154826981.png) + + ![image-20230506154859985](img\image-20230506154859985.png) + + + +#### 3.6.3 Young Collection + Concurrent Mark (年轻代垃圾回收+并发标记) + +当老年代占用内存超过阈值(默认是45%)后,触发并发标记,这时无需暂停用户线程 + +![image-20230506155000503](img\image-20230506155000503.png) + +- 并发标记之后,会有重新标记阶段解决漏标问题,此时需要暂停用户线程。 + +- 这些都完成后就知道了老年代有哪些存活对象,随后进入混合收集阶段。此时不会对所有老年代区域进行回收,而是根据暂停时间目标优先回收价值高(存活对象少)的区域(这也是 Gabage First 名称的由来)。 + + ![image-20230506155047765](img\image-20230506155047765.png) + + + +#### 3.6.4 Mixed Collection (混合垃圾回收) + +复制完成,内存得到释放。进入下一轮的新生代回收、并发标记、混合收集 + +![image-20230506155116267](img\image-20230506155116267.png) + +其中H叫做巨型对象,如果对象非常大,会开辟一块连续的空间存储巨型对象 + +![image-20230506155146370](img\image-20230506155146370.png) + +### 3.7 强引用、软引用、弱引用、虚引用的区别? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆ + +#### 3.7.1 强引用 + +强引用:只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收 + +```java +User user = new User(); +``` + +![image-20230506155341703](img\image-20230506155341703.png) + +#### 3.7.2 软引用 + +软引用:仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收 + +```java +User user = new User(); +SoftReference softReference = new SoftReference(user); +``` + +![image-20230506155416293](img\image-20230506155416293.png) + +#### 3.7.3 弱引用 + +弱引用:仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象 + +```java +User user = new User(); +WeakReference weakReference = new WeakReference(user); +``` + +![image-20230506155501557](img\image-20230506155501557.png) + +>延伸话题:ThreadLocal内存泄漏问题 + +ThreadLocal用的就是弱引用,看以下源码: + +```java +static class Entry extends WeakReference> { + Object value; + + Entry(ThreadLocal k, Object v) { + super(k); + value = v; //强引用,不会被回收 + } +} +``` + +`Entry`的key是当前ThreadLocal,value值是我们要设置的数据。 + +`WeakReference`表示的是弱引用,当JVM进行GC时,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。但是`value`是强引用,它不会被回收掉。 + +>ThreadLocal使用建议:使用完毕后注意调用清理方法。 + +#### 3.7.4 虚引用 + +虚引用:必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存 + +![image-20230506155518510](img\image-20230506155518510.png) + +![image-20230506155552693](img\image-20230506155552693.png) + +## 4 JVM实践(调优) + +### 4.1 JVM 调优的参数可以在哪里设置参数值? + +>难易程度:☆☆ +> +>出现频率:☆☆☆ + +#### 4.1.1 tomcat的设置vm参数 + +修改TOMCAT_HOME/bin/catalina.sh文件,如下图 + +`JAVA_OPTS="-Xms512m -Xmx1024m" ` + +![image-20220904151948778](img\image-20220904151948778.png) + +#### 4.1.2 springboot项目jar文件启动 + +通常在linux系统下直接加参数启动springboot项目 + +```sh +nohup java -Xms512m -Xmx1024m -jar xxxx.jar --spring.profiles.active=prod & +``` + +>nohup : 用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行 +> +>参数 **&** :让命令在后台执行,终端退出后命令仍旧执行。 + +### 4.2 用的 JVM 调优的参数都有哪些? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +​ 对于JVM调优,主要就是调整年轻代、年老大、元空间的内存空间大小及使用的垃圾回收器类型。 + +https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html + +1)设置堆的初始大小和最大大小,为了防止垃圾收集器在初始大小、最大大小之间收缩堆而产生额外的时间,通常把最大、初始大小设置为相同的值。 + +``` +-Xms:设置堆的初始化大小 + +-Xmx:设置堆的最大大小 +``` + +2) 设置年轻代中Eden区和两个Survivor区的大小比例。该值如果不设置,则默认比例为8:1:1。Java官方通过增大Eden区的大小,来减少YGC发生的次数,但有时我们发现,虽然次数减少了,但Eden区满 + +的时候,由于占用的空间较大,导致释放缓慢,此时STW的时间较长,因此需要按照程序情况去调优。 + +``` +-XXSurvivorRatio=3,表示年轻代中的分配比率:survivor:eden = 2:3 +``` + +3)年轻代和老年代默认比例为1:2。可以通过调整二者空间大小比率来设置两者的大小。 + +``` +-XX:newSize 设置年轻代的初始大小 +-XX:MaxNewSize 设置年轻代的最大大小, 初始大小和最大大小两个值通常相同 +``` + +4)线程堆栈的设置:**每个线程默认会开启1M的堆栈**,用于存放栈帧、调用参数、局部变量等,但一般256K就够用。通常减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。 + +``` +-Xss 对每个线程stack大小的调整,-Xss128k +``` + +5)一般来说,当survivor区不够大或者占用量达到50%,就会把一些对象放到老年区。通过设置合理的eden区,survivor区及使用率,可以将年轻对象保存在年轻代,从而避免full GC,使用-Xmn设置年轻代的大小 + +6)系统CPU持续飙高的话,首先先排查代码问题,如果代码没问题,则咨询运维或者云服务器供应商,通常服务器重启或者服务器迁移即可解决。 + +7)对于占用内存比较多的大对象,一般会选择在老年代分配内存。如果在年轻代给大对象分配内存,年轻代内存不够了,就要在eden区移动大量对象到老年代,然后这些移动的对象可能很快消亡,因此导致full GC。通过设置参数:-XX:PetenureSizeThreshold=1000000,单位为B,标明对象大小超过1M时,在老年代(tenured)分配内存空间。 + +8)一般情况下,年轻对象放在eden区,当第一次GC后,如果对象还存活,放到survivor区,此后,每GC一次,年龄增加1,当对象的年龄达到阈值,就被放到tenured老年区。这个阈值可以同构-XX:MaxTenuringThreshold设置。如果想让对象留在年轻代,可以设置比较大的阈值。 + +``` +(1)-XX:+UseParallelGC:年轻代使用并行垃圾回收收集器。这是一个关注吞吐量的收集器,可以尽可能的减少垃圾回收时间。 + +(2)-XX:+UseParallelOldGC:设置老年代使用并行垃圾回收收集器。 +``` + +9)尝试使用大的内存分页:使用大的内存分页增加CPU的内存寻址能力,从而系统的性能。 + +``` +-XX:+LargePageSizeInBytes 设置内存页的大小 +``` + +10)使用非占用的垃圾收集器。 + +``` +-XX:+UseConcMarkSweepGC老年代使用CMS收集器降低停顿。 +``` + +### 4.3 说一下 JVM 调优的工具? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +#### 4.3.1 命令工具 + +##### 4.3.1.1 jps(Java Process Status) + +输出JVM中运行的进程状态信息(现在一般使用jconsole) + +![image-20220904104739581](img\image-20220904104739581.png) + +##### 4.3.1.2 jstack + +查看java进程内**线程的堆栈**信息。 + +```sh +jstack [option] +``` + +java案例 + +```java +package com.heima.jvm; + +public class Application { + + public static void main(String[] args) throws InterruptedException { + while (true){ + Thread.sleep(1000); + System.out.println("哈哈哈"); + } + } +} + +``` + +使用jstack查看进行堆栈运行信息 + +![image-20220904111059602](img\image-20220904111059602.png) + +##### 4.3.1.3 jmap + +用于生成堆转存快照 + +> jmap [options] pid 内存映像信息 +> +> jmap -heap pid 显示Java堆的信息 +> +> jmap -dump:format=b,file=heap.hprof pid +> +> ​ format=b表示以hprof二进制格式转储Java堆的内存 +> ​ file=用于指定快照dump文件的文件名。 + +例:显示了某一个java运行的堆信息 + +```java +C:\Users\yuhon>jmap -heap 53280 +Attaching to process ID 53280, please wait... +Debugger attached successfully. +Server compiler detected. +JVM version is 25.321-b07 + +using thread-local object allocation. +Parallel GC with 8 thread(s) //并行的垃圾回收器 + +Heap Configuration: //堆配置 + MinHeapFreeRatio = 0 //空闲堆空间的最小百分比 + MaxHeapFreeRatio = 100 //空闲堆空间的最大百分比 + MaxHeapSize = 8524922880 (8130.0MB) //堆空间允许的最大值 + NewSize = 178257920 (170.0MB) //新生代堆空间的默认值 + MaxNewSize = 2841640960 (2710.0MB) //新生代堆空间允许的最大值 + OldSize = 356515840 (340.0MB) //老年代堆空间的默认值 + NewRatio = 2 //新生代与老年代的堆空间比值,表示新生代:老年代=1:2 + SurvivorRatio = 8 //两个Survivor区和Eden区的堆空间比值为8,表示S0:S1:Eden=1:1:8 + MetaspaceSize = 21807104 (20.796875MB) //元空间的默认值 + CompressedClassSpaceSize = 1073741824 (1024.0MB) //压缩类使用空间大小 + MaxMetaspaceSize = 17592186044415 MB //元空间允许的最大值 + G1HeapRegionSize = 0 (0.0MB)//在使用 G1 垃圾回收算法时,JVM 会将 Heap 空间分隔为若干个 Region,该参数用来指定每个 Region 空间的大小。 + +Heap Usage: +PS Young Generation +Eden Space: //Eden使用情况 + capacity = 134217728 (128.0MB) + used = 10737496 (10.240074157714844MB) + free = 123480232 (117.75992584228516MB) + 8.000057935714722% used +From Space: //Survivor-From 使用情况 + capacity = 22020096 (21.0MB) + used = 0 (0.0MB) + free = 22020096 (21.0MB) + 0.0% used +To Space: //Survivor-To 使用情况 + capacity = 22020096 (21.0MB) + used = 0 (0.0MB) + free = 22020096 (21.0MB) + 0.0% used +PS Old Generation //老年代 使用情况 + capacity = 356515840 (340.0MB) + used = 0 (0.0MB) + free = 356515840 (340.0MB) + 0.0% used + +3185 interned Strings occupying 261264 bytes. +``` + + + +##### 4.3.1.4 jhat + +用于分析jmap生成的堆转存快照(一般不推荐使用,而是使用Ecplise Memory Analyzer) + +##### 4.3.1.5 jstat + +是JVM统计监测工具。可以用来显示垃圾回收信息、类加载信息、新生代统计信息等。 + +**常见参数**: + +①总结垃圾回收统计 + +```sh +jstat -gcutil pid +``` + +![image-20220904114511854](img\image-20220904114511854.png) + +| 字段 | 含义 | +| ---- | ---------------------- | +| S0 | 幸存1区当前使用比例 | +| S1 | 幸存2区当前使用比例 | +| E | 伊甸园区使用比例 | +| O | 老年代使用比例 | +| M | 元数据区使用比例 | +| CCS | 压缩使用比例 | +| YGC | 年轻代垃圾回收次数 | +| YGCT | 年轻代垃圾回收消耗时间 | +| FGC | 老年代垃圾回收次数 | +| FGCT | 老年代垃圾回收消耗时间 | +| GCT | 垃圾回收消耗总时间 | + +②垃圾回收统计 + +```sh +jstat -gc pid +``` + +![image-20220904115157363](img\image-20220904115157363.png) + +#### 4.3.2 可视化工具 + +##### 4.3.2.1 jconsole + +用于对jvm的内存,线程,类 的监控,是一个基于 jmx 的 GUI 性能监控工具 + +打开方式:java 安装目录 bin目录下 直接启动 jconsole.exe 就行 + +![image-20220904115936095](img\image-20220904115936095.png) + +可以内存、线程、类等信息 + +![image-20220904120057211](img\image-20220904120057211.png) + + + +##### 4.3.2.2 VisualVM:故障处理工具 + +能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈 + +打开方式:java 安装目录 bin目录下 直接启动 jvisualvm.exe就行 + +![image-20220904120356174](img\image-20220904120356174.png) + +监控程序运行情况 + +![image-20220904132011289](img\image-20220904132011289.png) + +查看运行中的dump + +![image-20220904132134095](img\image-20220904132134095.png) + +查看堆中的信息 + +![image-20220904132346495](img\image-20220904132346495.png) + +### 4.4 java内存泄露的排查思路? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +原因: + +如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量的时候,java虚拟机将抛出一个StackOverFlowError异常 + +如果java虚拟机栈可以动态拓展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成拓展,或者在建立新线程的时候没有足够的内存去创建对应的虚拟机栈,那java虚拟机将会抛出一个OutOfMemoryError异常 + +如果一次加载的类太多,元空间内存不足,则会报OutOfMemoryError: Metaspace + +![image-20230506155704119](img\image-20230506155704119.png) + + + +1、通过jmap指定打印他的内存快照 dump + +>有的情况是内存溢出之后程序则会直接中断,而jmap只能打印在运行中的程序,所以建议通过参数的方式的生成dump文件,配置如下: +> +>-XX:+HeapDumpOnOutOfMemoryError +>-XX:HeapDumpPath=/home/app/dumps/ 指定生成后文件的保存目录 + +2、通过工具, VisualVM(Ecplise MAT)去分析 dump文件 + +VisualVM可以加载离线的dump文件,如下图 + +文件-->装入--->选择dump文件即可查看堆快照信息 + +>如果是linux系统中的程序,则需要把dump文件下载到本地(windows环境)下,打开VisualVM工具分析。VisualVM目前只支持在windows环境下运行可视化 + +![image-20220904132925812](img\image-20220904132925812.png) + +3、通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题 + +![image-20220904133722905](img\image-20220904133722905.png) + +4、找到对应的代码,通过阅读上下文的情况,进行修复即可 + +### 4.5 CPU飙高排查方案与思路? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +1.使用top命令查看占用cpu的情况 + +![image-20220904161818255](img\image-20220904161818255.png) + +2.通过top命令查看后,可以查看是哪一个进程占用cpu较高,上图所示的进程为:30978 + +3.查看当前线程中的进程信息 + +```sh +ps H -eo pid,tid,%cpu | grep 40940 +``` + +>pid 进行id +> +>tid 进程中的线程id +> +>% cpu使用率 + +![image-20220904162117022](img\image-20220904162117022.png) + +4.通过上图分析,在进程30978中的线程30979占用cpu较高 + +>注意:上述的线程id是一个十进制,我们需要把这个线程id转换为16进制才行,因为通常在日志中展示的都是16进制的线程id名称 +> +>转换方式: +> +>在linux中执行命令 +> +>`printf "%x\n" 30979` +> +>![image-20220904162654928](img\image-20220904162654928.png) + +5.可以根据线程 id 找到有问题的线程,进一步定位到问题代码的源码行号 + +执行命令 + +```sh +jstack 30978 此处是进程id +``` + +![image-20220904162941977](img\image-20220904162941977.png) + + + +## 5.面试现场 + +### 5.1 JVM组成 + +>**面试官**:JVM由那些部分组成,运行流程是什么? +> +>**候选人:** +> +>嗯,好的~~ +> +>在JVM中共有四大部分,分别是ClassLoader(类加载器)、Runtime Data Area(运行时数据区,内存分区)、Execution Engine(执行引擎)、Native Method Library(本地库接口) +> +>它们的运行流程是: +> +>第一,类加载器(ClassLoader)把Java代码转换为字节码 +> +>第二,运行时数据区(Runtime Data Area)把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层系统去执行,而是有执行引擎运行 +> +>第三,执行引擎(Execution Engine)将字节码翻译为底层系统指令,再交由CPU执行去执行,此时需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的功能。 +> +>**面试官**:好的,你能详细说一下 JVM 运行时数据区吗? +> +>**候选人:** +> +>嗯,好~ +> +>运行时数据区包含了堆、方法区、栈、本地方法栈、程序计数器这几部分,每个功能作用不一样。 +> +>- 堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。 +>- 方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。 +>- 栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。 +>- 本地方法栈与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。 +>- 程序计数器(PC寄存器)程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。 +> +>**面试官**:好的,你再详细介绍一下程序计数器的作用? +> +>**候选人:** +> +>嗯,是这样~~ +> +>java虚拟机对于多线程是通过线程轮流切换并且分配线程执行时间。在任何的一个时间点上,一个处理器只会处理执行一个线程,如果当前被执行的这个线程它所分配的执行时间用完了【挂起】。处理器会切换到另外的一个线程上来进行执行。并且这个线程的执行时间用完了,接着处理器就会又来执行被挂起的这个线程。这时候程序计数器就起到了关键作用,程序计数器在来回切换的线程中记录他上一次执行的行号,然后接着继续向下执行。 +> +>**面试官**:你能给我详细的介绍Java堆吗? +> +>**候选人:** +> +>好的~ +> +>Java中的堆术语线程共享的区域。主要用来保存**对象实例,数组**等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。 +> +>​ 在JAVA8中堆内会存在年轻代、老年代 +> +>​ 1)Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用。在Eden区变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。 +> +>​ 2)Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区。 +> +>**面试官**:能不能解释一下方法区? +> +>**候选人:** +> +>好的~ +> +>与虚拟机栈类似。本地方法栈是为虚拟机**执行本地方法时提供服务的**。不需要进行GC。本地方法一般是由其他语言编写。 +> +>**面试官**:你听过直接内存吗? +> +>**候选人:** +> +>嗯~~ +> +>它又叫做**堆外内存**,**线程共享的区域**,在 Java 8 之前有个**永久代**的概念,实际上指的是 HotSpot 虚拟机上的永久代,它用永久代实现了 JVM 规范定义的方法区功能,**主要存储类的信息,常量,静态变量**,即时编译器编译后代码等,这部分由于是在堆中实现的,受 GC 的管理,不过由于永久代有 -XX:MaxPermSize 的上限,所以如果大量动态生成类(将类信息放入永久代),很容易造成 OOM,有人说可以把永久代设置得足够大,但很难确定一个合适的大小,受类数量,常量数量的多少影响很大。 +> +>​ 所以在 Java 8 中就把方法区的实现移到了本地内存中的元空间中,这样方法区就不受 JVM 的控制了,也就不会进行 GC,也因此提升了性能。 +> +>**面试官**:什么是虚拟机栈 +> +>**候选人:** +> +>虚拟机栈是描述的是方法执行时的内存模型,是线程私有的,生命周期与线程相同,每个方法被执行的同时会创建**栈桢**。保存执行方法时的**局部变量、动态连接信息、方法返回地址信息**等等。方法开始执行的时候会进栈,方法执行完会出栈【相当于清空了数据】,所以这块区域**不需要进行 GC**。 +> +>**面试官**:能说一下堆栈的区别是什么吗? +> +>**候选人:** +> +>嗯,好的,有这几个区别 +> +>第一,栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。 +> +>第二、栈内存是线程私有的,而堆内存是线程共有的。 +> +>第三、两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。 +> +>栈空间不足:java.lang.StackOverFlowError。 +> +>堆空间不足:java.lang.OutOfMemoryError。 + +### 5.2 类加载器 + +>**面试官**:什么是类加载器,类加载器有哪些? +> +>**候选人:** +> +>嗯,是这样的 +> +>JVM只会运行二进制文件,而类加载器(ClassLoader)的主要作用就是将**字节码文件加载到JVM中**,从而让Java程序能够启动起来。 +> +>常见的类加载器有4个 +> +>第一个是启动类加载器(BootStrap ClassLoader):其是由C++编写实现。用于加载JAVA_HOME/jre/lib目录下的类库。 +> +>第二个是扩展类加载器(ExtClassLoader):该类是ClassLoader的子类,主要加载JAVA_HOME/jre/lib/ext目录中的类库。 +> +>第三个是应用类加载器(AppClassLoader):该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。 +> +>第四个是自定义类加载器:开发者自定义类继承ClassLoader,实现自定义类加载规则。 +> +>**面试官**:说一下类装载的执行过程? +> +>**候选人:** +> +>嗯,这个过程还是挺多的。 +> +>类从加载到虚拟机中开始,直到卸载为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking) +> +>1.加载:查找和导入class文件 +> +>2.验证:保证加载类的准确性 +> +>3.准备:为类变量分配内存并设置类变量初始值 +> +>4.解析:把类中的符号引用转换为直接引用 +> +>5.初始化:对类的静态变量,静态代码块执行初始化操作 +> +>6.使用:JVM 开始从入口方法开始执行用户的程序代码 +> +>7.卸载:当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存 +> +>**面试官**:什么是双亲委派模型? +> +>**候选人:** +> +>嗯,它是是这样的。 +> +>如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传说到顶层的启动类加载器中,只有当父类加载器返回自己无法完成这个加载请求(它的搜索返回中没有找到所需的类)时,子类加载器才会尝试自己去加载 +> +>**面试官**:JVM为什么采用双亲委派机制 +> +>**候选人:** +> +>主要有两个原因。 +> +>第一、通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。 +> +>第二、为了安全,保证类库API不会被修改 + +### 5.3 垃圾回收 + +>**面试官**:简述Java垃圾回收机制?(GC是什么?为什么要GC) +> +>**候选人:** +> +>嗯,是这样~~ +> +>为了让程序员更专注于代码的实现,而不用过多的考虑内存释放的问题,所以,在Java语言中,有了自动的垃圾回收机制,也就是我们熟悉的GC(Garbage Collection)。 +> +>有了垃圾回收机制后,程序员只需要关心内存的申请即可,内存的释放由系统自动识别完成。 +> +>在进行垃圾回收时,不同的对象引用类型,GC会采用不同的回收时机 +> +>**面试官**:强引用、软引用、弱引用、虚引用的区别? +> +>**候选人:** +> +>嗯嗯~ +> +>强引用最为普通的引用方式,表示一个对象处于**有用且必须**的状态,如果一个对象具有强引用,则GC并不会回收它。即便堆中内存不足了,宁可出现OOM,也不会对其进行回收 +> +>软引用表示一个对象处于**有用且非必须**状态,如果一个对象处于软引用,在内存空间足够的情况下,GC机制并不会回收它,而在内存空间不足时,则会在OOM异常出现之间对其进行回收。但值得注意的是,因为GC线程优先级较低,软引用并不会立即被回收。 +> +>弱引用表示一个对象处于**可能有用且非必须**的状态。在GC线程扫描内存区域时,一旦发现弱引用,就会回收到弱引用相关联的对象。对于弱引用的回收,无关内存区域是否足够,一旦发现则会被回收。同样的,因为GC线程优先级较低,所以弱引用也并不是会被立刻回收。 +> +>虚引用表示一个对象处于**无用**的状态。在任何时候都有可能被垃圾回收。虚引用的使用必须和引用队列Reference Queue联合使用 +> +>**面试官**:对象什么时候可以被垃圾器回收 +> +>**候选人:** +> +>思考一会~~ +> +>如果一个或多个对象没有任何的引用指向它了,那么这个对象现在就是垃圾,如果定位了垃圾,则有可能会被垃圾回收器回收。 +> +>如果要定位什么是垃圾,有两种方式来确定,第一个是引用计数法,第二个是可达性分析算法 +> +>通常都使用可达性分析算法来确定是不是垃圾 +> +>**面试官**: JVM 垃圾回收算法有哪些? +> +>**候选人:** +> +>我记得一共有四种,分别是标记清除算法、复制算法、标记整理算法、分代回收 +> +>**面试官**: 你能详细聊一下分代回收吗? +> +>**候选人:** +> +>关于分代回收是这样的 +> +>在java8时,堆被分为了两份:新生代和老年代,它们默认空间占用比例是1:2 +> +>对于新生代,内部又被分为了三个区域。Eden区,S0区,S1区默认空间占用比例是8:1:1 +> +>具体的工作机制是有些情况: +> +>1)当创建一个对象的时候,那么这个对象会被分配在新生代的Eden区。当Eden区要满了时候,触发YoungGC。 +> +>2)当进行YoungGC后,此时在Eden区存活的对象被移动到S0区,并且**当前对象的年龄会加1**,清空Eden区。 +> +>3)当再一次触发YoungGC的时候,会把Eden区中存活下来的对象和S0中的对象,移动到S1区中,这些对象的年龄会加1,清空Eden区和S0区。 +> +>4)当再一次触发YoungGC的时候,会把Eden区中存活下来的对象和S1中的对象,移动到S0区中,这些对象的年龄会加1,清空Eden区和S1区。 +> +>5)对象的年龄达到了某一个限定的值(**默认15岁** ),那么这个对象就会进入到老年代中。 +> +>当然也有特殊情况,如果进入Eden区的是一个大对象,在触发YoungGC的时候,会直接存放到老年代 +> +>当老年代满了之后,**触发FullGC**。**FullGC同时回收新生代和老年代**,当前只会存在一个FullGC的线程进行执行,其他的线程全部会被挂起。 我们在程序中要尽量避免FullGC的出现。 +> +>**面试官**:讲一下新生代、老年代、永久代的区别? +> +>**候选人:** +> +>嗯!是这样的,简单说就是 +> +>**新生代**主要用来存放新生的对象。 +> +>**老年代**主要存放应用中生命周期长的内存对象。 +> +>**永久代**指的是永久保存区域。主要存放Class和Meta(元数据)的信息。在Java8中,永久代已经被移除,取而代之的是一个称之为“元数据区”(**元空间**)的区域。元空间和永久代类似,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。 +> +>**面试官**:说一下 JVM 有哪些垃圾回收器? +> +>**候选人:** +> +>在jvm中,实现了多种垃圾收集器,包括:串行垃圾收集器、并行垃圾收集器(JDK8默认)、CMS(并发)垃圾收集器、G1垃圾收集器(JDK9默认) +> +>**面试官**:Minor GC、Major GC、Full GC是什么 +> +>**候选人:** +> +>嗯,其实它们指的是不同代之间的垃圾回收 +> +>Minor GC 发生在新生代的垃圾回收,暂停时间短 +> +>Major GC 老年代区域的垃圾回收,老年代空间不足时,会先尝试触发Minor GC。Minor GC之后空间还不足,则会触发Major GC,Major GC速度比较慢,暂停时间长 +> +>Full GC 新生代 + 老年代完整垃圾回收,暂停时间长,**应尽力避免** + +### 5.4 JVM实践(调优) + +>**面试官**:JVM 调优的参数可以在哪里设置参数值? +> +>**候选人:** +> +>我们当时的项目是springboot项目,可以在项目启动的时候,java -jar中加入参数就行了 +> +> +> +>**面试官**:用的 JVM 调优的参数都有哪些? +> +>**候选人:** +> +>嗯,这些参数是比较多的 +> +>我记得当时我们设置过堆的大小,像-Xms和-Xmx +> +>还有就是可以设置年轻代中Eden区和两个Survivor区的大小比例 +> +>还有就是可以设置使用哪种垃圾回收器等等。具体的指令还真记不太清楚。 +> +> +> +>**面试官**:嗯,好的,你们平时调试 JVM都用了哪些工具呢? +> +>**候选人:** +> +>嗯,我们一般都是使用jdk自带的一些工具,比如 +> +>jps 输出JVM中运行的进程状态信息 +> +>jstack查看java进程内**线程的堆栈**信息。 +> +>jmap 用于生成堆转存快照 +> +>jstat用于JVM统计监测工具 +> +>还有一些可视化工具,像jconsole和VisualVM等 +> +>**面试官**:假如项目中产生了java内存泄露,你说一下你的排查思路? +> +>**候选人:** +> +>嗯,这个我在之前项目排查过 +> +>第一呢可以通过jmap指定打印他的内存快照 dump文件,不过有的情况打印不了,我们会设置vm参数让程序自动生成dump文件 +> +>第二,可以通过工具去分析 dump文件,jdk自带的VisualVM就可以分析 +> +>第三,通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题 +> +>第四,找到对应的代码,通过阅读上下文的情况,进行修复即可 +> +>**面试官**:好的,那现在再来说一种情况,就是说服务器CPU持续飙高,你的排查方案与思路? +> +>**候选人:** +> +>嗯,我思考一下~~ +> +>可以这么做~~ +> +>第一可以使用使用top命令查看占用cpu的情况 +> +>第二通过top命令查看后,可以查看是哪一个进程占用cpu较高,记录这个进程id +> +>第三可以通过ps 查看当前进程中的线程信息,看看哪个线程的cpu占用较高 +> +>第四可以jstack命令打印进行的id,找到这个线程,就可以进一步定位问题代码的行号 \ No newline at end of file diff --git a/面试/面试/Java集合相关面试题.md b/面试/面试/Java集合相关面试题.md new file mode 100755 index 0000000..94848cb --- /dev/null +++ b/面试/面试/Java集合相关面试题.md @@ -0,0 +1,1433 @@ +# img + +## 导学 + +这次课程主要涉及到的是List和Map相关的面试题,比较高频就是 + +- ArrayList + +- LinkedList + +- HashMap + +- ConcurrentHashMap + +![image-20230427162524322](img\image-20230427162524322.png) + +- ArrayList底层实现是数组 +- LinkedList底层实现是双向链表 +- HashMap的底层实现使用了众多数据结构,包含了数组、链表、散列表、红黑树等 + +在讲解这些集合之后,我们会讲解数据结构,知道了数据结构的特点之后,熟悉集合就更加简单了。在讲解数据结构之前,我们也会简单普及一下算法复杂度分析,让大家能够评判代码的好坏,也能更加深入去理解数据结构和集合。 + +## 1 算法复杂度分析 + +### 1.1 为什么要进行复杂度分析? + +我们先来看下面这个代码,你能评判这个代码的好坏吗? + +```java +/** + ** *求**1~n**的累加和 + ** @param* *n + ** @return +*/ +public int sum(int n) { + int sum = 0; + for ( int i = 1; i <= n; i++) { + sum = sum + i; + } + return sum; +} +``` + +其实学习算法复杂度的好处就是: + +- 指导你编写出性能更优的代码 + +- 评判别人写的代码的好坏 + +>相信你学完了算法复杂度分析,就有能力评判上面代码的好坏了 + +关于算法复杂度分析,包含了两个内容,一个是时间复杂度,一个是空间复杂度,通常情况下说复杂度,都是指时间复杂度,我们也会重点讲解时间复杂度 + +### 1.2 时间复杂度 + +#### 1.2.1 案例 + +时间复杂度分析:简单来说就是评估代码的执行耗时的,大家还是看刚才的代码: + +```java +/** + ** *求**1~n**的累加和 + ** @param* *n + ** @return +*/ +public int sum(int n) { + int sum = 0; + for ( int i = 1; i <= n; i++) { + sum = sum + i; + } + return sum; +} +``` + +分析这个代码的时间复杂度,分析过程如下: + +1.假如每行代码的执行耗时一样:1ms + +2.分析这段代码总执行多少行?3n+3 + +3.代码耗时总时间: T(n) = (3n + 3) * 1ms + +>T(n):就是代码总耗时 + +我们现在有了总耗时,需要借助大O表示法来计算这个代码的时间复杂度 + +#### 1.2.2 大O表示法 + +**大O表示法**:不具体表示代码真正的执行时间,而是表示**代码执行时间随数据规模增长的变化趋势**。 + +刚才的代码示例总耗时公式为:T(n) = (3n + 3) * 1ms + +>其中 (3n + 3) 是代码的总行数,每行执行的时间都一样,所以得出结论: +> +>**T(n)与代码的执行次数成正比(代码行数越多,执行时间越长)** + +不过,大O表示法只需要代码执行时间与数据规模的增长趋势,公式可以简化如下: + +T(n) =O(3n + 3)------------> T(n) = O(n) + +>当n很大时,公式中的低阶,常量,系数三部分并不左右其增长趋势,因此可以忽略,我们只需要记录一个最大的量级就可以了 + +下图也能表明数据的趋势 + +![image-20230427173120668](img\image-20230427173120668.png) + +#### 1.2.3 常见复杂度表示形式 + +![image-20230427173742389](img\image-20230427173742389.png) + +速记口诀:**常对幂指阶** + +越在上面的性能就越高,越往下性能就越低 + +下图是一些比较常见时间复杂度的时间与数据规模的趋势: + +![image-20230427173937663](img\image-20230427173937663.png) + +#### 1.2.4 时间复杂度O(1) + +实例代码: + +```java +public int test01(int n){ + int i=0; + int j = 1; + return i+j; +} +``` + +代码只有三行,它的复杂度也是O(1),而不是O(3) + +再看如下代码: + +```java +public void test02(int n){ + int i=0; + int sum=0; + for(;i<100;i++){ + sum = sum+i; + } + System.out.println(sum); +} +``` + +整个代码中因为循环次数是固定的就是100次,这样的代码复杂度我们认为也是O(1) + +一句话总结:**只要代码的执行时间不随着n的增大而增大,这样的代码复杂度都是O(1)** + +#### 1.2.5 时间复杂度O(n) + +实例代码1: + +```java +/** + * 求1~n的累加和 + * @param n + * @return + */ +public int sum(int n) { + int sum = 0; + for ( int i = 1; i <= n; i++) { + sum = sum + i; + } + return sum; +} +``` + +一层for循序时间复杂度就是O(n) + +实例代码2: + +```java +public static int sum2(int n){ + int sum = 0; + for (int i = 1; i < n; ++i) { + for (int j = 1; j < n; ++j) { + sum = sum + i * j; + } + } + return sum; +} +``` + +这个代码的执行行数为:O( 3n^2 + 3n + 3 ),不过,依据大O表示的规则:**常量、系数、低阶,可以忽略** + +所以这个代码最终的时间复杂度为:O(n^2) + +#### 1.2.6 时间复杂度O(logn) + +对数复杂度非常的常见,但相对比较难以分析,实例代码: + +```java +public void test04(int n){ + int i=1; + while(i<=n){ + i = i * 2; + } +} +``` + +分析这个代码的复杂度,我们必须要再强调一个前提:**复杂度分析就是要弄清楚代码的执行次数和数据规模n之间的关系** + +以上代码最关键的一行是:`i = i * 2`,这行代码可以决定这个while循环执行代码的行数,`i`的值是可以无限接近`n`的值的。如果`i` 一旦大于等于了`n`则循环条件就不满足了。也就说达到了最大的行数。我们可以分析一下`i`这个值变化的过程 + +分析过程如下: + +![image-20230427174832858](img\image-20230427174832858.png) + +由此可知,代码的时间复杂度表示为O(log n) + +#### 1.2.7 时间复杂度O(n * log n) + +分析完O( log n ),那O( n * log n )就很容易理解了,比如下列代码: + +```java +public void test05(int n){ + int i=0; + for(;i<=n;i++){ + test04(n); + } +} + +public void test04(int n){ + int i=1; + while(i<=n){ + i = i * 2; + } +} +``` + +### 1.3 空间复杂度 + +空间复杂度全称是渐进空间复杂度,表示算法占用的额外**存储空间**与**数据规模**之间的增长关系 + +看下面代码 + +```java +public void test(int n){ + int i=0; + int sum=0; + for(;i= 0; --i) { + System.out.println(a[i]); + } +} +``` + +传入一个变量n,决定申请多少的int数组空间内存,此段代码的空间复杂度为O(n) + +我们常见的空间复杂度就是O(1),O(n),O(n ^2),其他像对数阶的复杂度几乎用不到,因此空间复杂度比时间复杂度分析要简单的多。 + +## 2 List相关面试题 + +### 2.1 数组 + +#### 2.1.1 数组概述 + +数组(Array)是一种用**连续的内存空间**存储**相同数据类型**数据的线性数据结构。 + +```java +int[] array = {22,33,88,66,55,25}; +``` + +![image-20230427175545402](img\image-20230427175545402.png) + +我们定义了这么一个数组之后,在内存的表示是这样的: + +![image-20230427175633253](img\image-20230427175633253.png) + +现在假如,我们通过`arrar[1]`,想要获得下标为1这个元素,但是现在栈内存中指向的堆内存数组的首地址,它是如何获取下标为1这个数据的? + +![image-20230427175849493](img\image-20230427175849493.png) + +#### 2.1.2 寻址公式 + +为了方便大家理解,我们把数组的内存地址稍微改了一下,都改成了数字,如下图 + +![image-20230427180056509](img\image-20230427180056509.png) + +在数组在内存中查找元素的时候,是有一个寻址公式的,如下: + +```java +arr[i] = baseAddress + i * dataTypeSize +``` + +>baseAddress:数组的首地址,目前是10 +> +>dataTypeSize:代表数组中元素类型的大小,目前数组重存储的是int型的数据,dataTypeSize=4个字节 +> +>arr:指的是数组 +> +>i:指的是数组的下标 + +有了寻址公式以后,我们再来获取一下下标为1的元素,这个是原来的数组 + +```java +int[] array = {22,33,88,66,55,25}; +``` + +套入公式: + +```java +array[1] =10 + i * 4 = 14 +``` + +获取到14这个地址,就能获取到下标为1的这个元素了。 + +#### 2.1.3 操作数组的时间复杂度 + +**1.随机查询(根据索引查询)** + +数组元素的访问是通过下标来访问的,计算机通过数组的**首地址**和**寻址公式**能够很快速的找到想要访问的元素 + +```java +public int test01(int[] a,int i){ + return a[i]; + // a[i] = baseAddress + i \* dataSize +} +``` + +代码的执行次数并不会随着数组的数据规模大小变化而变化,是常数级的,所以查询数据操作的时间复杂度是O(1) + + + +**2. 未知索引查询O(n)或O(log2n)** + +情况一:查找数组内的元素,查找55号数据,遍历数组时间复杂度为O(n) + +![image-20221007101831281](img\image-20221007101831281.png) + +情况二:查找排序后数组内的元素,通过二分查找算法查找55号数据时间复杂度为O(logn) + +![image-20221007101811885](img\image-20221007101811885.png) + +**3.插入O(n)** + +数组是一段连续的内存空间,因此为了保证数组的连续性会使得数组的插入和删除的效率变的很低。 + +假设数组的长度为 n,现在如果我们需要将一个数据插入到数组中的第 k 个位置。为了把第 k 个位置腾出来给新来的数据,我们需要将第 k~n 这部分的元素都顺序地往后挪一位。如下图所示: + +![image-20220820104903422](img\image-20220820104903422.png) + +新增之后的数据变化,如下 + +![image-20220820104950846](img\image-20220820104950846.png) + +所以: + +插入操作,最好情况下是O(1)的,最坏情况下是O(n)的,**平均情况下的时间复杂度是O(n)**。 + +**4.删除O(n)** + +同理可得:如果我们要删除第 k 个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了,时间复杂度仍然是O(n)。 + +### 2.2 ArrayList源码分析 + +分析ArrayList源码主要从三个方面去翻阅:成员变量,构造函数,关键方法 + +>以下源码都来源于jdk1.8 + +#### 2.2.1 成员变量 + +![image-20230427192118259](img\image-20230427192118259.png) + +>*DEFAULT_CAPACITY* = 10; 默认初始的容量**(CAPACITY) +> +>*EMPTY_ELEMENTDATA* = {}; 用于空实例的共享空数组实例 +> +>*DEFAULTCAPACITY_EMPTY_ELEMENTDATA* = {};用于默认大小的空实例的共享空数组实例 +> +>Object[] elementData; 存储元素的数组缓冲区 +> +>int size; ArrayList的大小(它包含的元素数量) + +#### 2.2.2 构造方法 + +![image-20230427192154014](img\image-20230427192154014.png) + +>- 第一个构造是带初始化容量的构造函数,可以按照指定的容量初始化数组 +> +>- 第二个是无参构造函数,默认创建一个空集合 + +![image-20230427192200918](img\image-20230427192200918.png) + +>将collection对象转换成数组,然后将数组的地址的赋给elementData + +#### 2.2.3 ArrayList源码分析 + +添加数据的流程 + +![image-20230427192644244](img\image-20230427192644244.png) + +**结论:** + +- 底层数据结构 + +ArrayList底层是用动态的数组实现的 + +- 初始容量 + +ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10 + +- 扩容逻辑 + +ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组 + +- 添加逻辑 + + - 确保数组已使用长度(size)加1之后足够存下下一个数据 + - 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍) + + - 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。 + - 返回添加成功布尔值。 + + + +#### 2.2.4 面试题-ArrayList list=new ArrayList(10)中的list扩容几次 + +>难易程度:☆☆☆ +> +>出现频率:☆☆ + +![image-20230428185505677](img\image-20230428185505677.png) + +参考回答: + + 该语句只是声明和实例了一个 ArrayList,指定了容量为 10,未扩容 + +#### 2.2.4 面试题-如何实现数组和List之间的转换 + +>难易程度:☆☆☆ +> +>出现频率:☆☆ + +如下代码: + +![image-20230428185600918](img\image-20230428185600918.png) + +参考回答: + +- 数组转List ,使用JDK中java.util.Arrays工具类的asList方法 + +- List转数组,使用List的toArray方法。无参toArray方法返回 Object数组,传入初始化长度的数组对象,返回该对象数组 + +面试官再问: + +1,用Arrays.asList转List后,如果修改了数组内容,list受影响吗 + +2,List用toArray转数组后,如果修改了List内容,数组受影响吗 + +![image-20230428185657791](img\image-20230428185657791.png) + +>数组转List受影响 +> +>List转数组不受影响 + +再答: + +1,用Arrays.asList转List后,如果修改了数组内容,list受影响吗 + +Arrays.asList转换list之后,如果修改了数组的内容,list会受影响,因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,最终指向的都是同一个内存地址 + +2,List用toArray转数组后,如果修改了List内容,数组受影响吗 + +list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层是它是进行了数组的拷贝,跟原来的元素就没啥关系了,所以即使list修改了以后,数组也不受影响 + +### 2.3 链表 + +#### 2.3.1 单向链表 + +- 链表中的每一个元素称之为结点(Node) + +- 物理存储单元上,非连续、非顺序的存储结构 + +- 单向链表:每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。记录下个结点地址的指针叫作后继指针 next + +![image-20230428185922776](img\image-20230428185922776.png) + +代码实现参考: + +![image-20230428185945929](img\image-20230428185945929.png) + +链表中的某个节点为B,B的下一个节点为C 表示: B.next==C + +#### 2.3.2 单向链表时间复杂度分析 + +(1)查询操作 + +![image-20230428190130901](img\image-20230428190130901.png) + +- 只有在查询头节点的时候不需要遍历链表,时间复杂度是O(1) + +- 查询其他结点需要遍历链表,时间复杂度是O(n) + +(2)插入和删除操作 + +![image-20230428190210915](img\image-20230428190210915.png) + +- 只有在添加和删除头节点的时候不需要遍历链表,时间复杂度是O(1) +- 添加或删除其他结点需要遍历链表找到对应节点后,才能完成新增或删除节点,时间复杂度是O(n) + +#### 2.3.3 双向链表 + +而双向链表,顾名思义,它支持两个方向 + +- 每个结点不止有一个后继指针 next 指向后面的结点 + +- 有一个前驱指针 prev 指向前面的结点 + +参考代码 + +![image-20230428190324752](img\image-20230428190324752.png) + +![image-20230428190353286](img\image-20230428190353286.png) + +对比单链表: + +- 双向链表需要额外的两个空间来存储后继结点和前驱结点的地址 + +- 支持双向遍历,这样也带来了双向链表操作的灵活性 + +#### 2.3.4 双向链表时间复杂度分析 + +![image-20230428190450517](img\image-20230428190450517.png) + +(1)查询操作 + +- 查询头尾结点的时间复杂度是O(1) + +- 平均的查询时间复杂度是O(n) + +- 给定节点找前驱节点的时间复杂度为O(1) + +(2)增删操作 + +- 头尾结点增删的时间复杂度为O(1) + +- 其他部分结点增删的时间复杂度是 O(n) + +- 给定节点增删的时间复杂度为O(1) + +#### 2.3.5 面试题-ArrayList和LinkedList的区别是什么? + +- 底层数据结构 + + - ArrayList 是动态数组的数据结构实现 + + - LinkedList 是双向链表的数据结构实现 + +- 操作数据效率 + - ArrayList按照下标查询的时间复杂度O(1)【内存是连续的,根据寻址公式】, LinkedList不支持下标查询 + - 查找(未知索引): ArrayList需要遍历,链表也需要链表,时间复杂度都是O(n) + - 新增和删除 + - ArrayList尾部插入和删除,时间复杂度是O(1);其他部分增删需要挪动数组,时间复杂度是O(n) + - LinkedList头尾节点增删时间复杂度是O(1),其他都需要遍历链表,时间复杂度是O(n) + +- 内存空间占用 + + - ArrayList底层是数组,内存连续,节省内存 + + - LinkedList 是双向链表需要存储数据,和两个指针,更占用内存 + +- 线程安全 + - ArrayList和LinkedList都不是线程安全的 + - 如果需要保证线程安全,有两种方案: + - 在方法内使用,局部变量则是线程安全的 + - 使用线程安全的ArrayList和LinkedList + +## 3 HashMap相关面试题 + +![image-20230428194715016](img\image-20230428194715016.png) + +### 3.1 二叉树 + +#### 3.1.1 二叉树概述 + +二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。 + +二叉树每个节点的左子树和右子树也分别满足二叉树的定义。 + +![image-20230428194831426](img\image-20230428194831426.png) + +Java中有两个方式实现二叉树:数组存储,链式存储。 + +基于链式存储的树的节点可定义如下: + +![image-20230428194904383](img\image-20230428194904383.png) + +![image-20230428194931132](img\image-20230428194931132.png) + +#### 3.1.2 二叉搜索树 + +在二叉树中,比较常见的二叉树有: + +- 满二叉树 + +- 完全二叉树 + +- **二叉搜索树** + +- **红黑树** + +我们重点讲解二叉搜索树和红黑树 + +(1)二叉搜索树概述 + +二叉搜索树(Binary Search Tree,BST)又名二叉查找树,有序二叉树或者排序二叉树,是二叉树中比较常用的一种类型 + +二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值 + +![image-20230428195206422](img\image-20230428195206422.png) + +(2)二叉搜索树-时间复杂度分析 + +实际上由于二叉查找树的形态各异,时间复杂度也不尽相同,我画了几棵树我们来看一下插入,查找,删除的时间复杂度 + +![image-20230428195341917](img\image-20230428195341917.png) + +插入,查找,删除的时间复杂度**O(logn)** + + + +极端情况下二叉搜索的时间复杂度 + +![image-20230428195449799](img\image-20230428195449799.png) + +对于图中这种情况属于最坏的情况,二叉查找树已经退化成了链表,左右子树极度不平衡,此时查找的时间复杂度肯定是O(n)。 + +#### 3.1.3 红黑树 + +(1)概述 + +**红黑树(Red Black Tree)**:也是一种自平衡的二叉搜索树(BST),之前叫做平衡二叉B树(Symmetric Binary B-Tree) + +![image-20230428195832724](img\image-20230428195832724.png) + +(2)红黑树的特质 + +性质1:节点要么是**红色**,要么是**黑色** + +性质2:根节点是**黑色** + +性质3:叶子节点都是黑色的空节点 + +性质4:红黑树中红色节点的子节点都是黑色 + +性质5:从任一节点到叶子节点的所有路径都包含相同数目的黑色节点 + +**在添加或删除节点的时候,如果不符合这些性质会发生旋转,以达到所有的性质,保证红黑树的平衡** + + + +(3)红黑树的复杂度 + +- 查找: + - 红黑树也是一棵BST(二叉搜索树)树,查找操作的时间复杂度为:O(log n) + +- 添加: + - 添加先要从根节点开始找到元素添加的位置,时间复杂度O(log n) + - 添加完成后涉及到复杂度为O(1)的旋转调整操作 + - 故整体复杂度为:O(log n) + +- 删除: + - 首先从根节点开始找到被删除元素的位置,时间复杂度O(log n) + - 删除完成后涉及到复杂度为O(1)的旋转调整操作 + - 故整体复杂度为:O(log n) + +### 3.2 散列表 + +在HashMap中的最重要的一个数据结构就是散列表,在散列表中又使用到了红黑树和链表 + +#### 3.2.1 散列表(Hash Table)概述 + +散列表(Hash Table)又名哈希表/Hash表,是根据键(Key)直接访问在内存存储位置值(Value)的数据结构,它是由数组演化而来的,利用了数组支持按照下标进行随机访问数据的特性 + +举个例子: + +![image-20230428200919454](img\image-20230428200919454.png) + +假设有100个人参加马拉松,编号是1-100,如果要编程实现根据选手的编号迅速找到选手信息? + + + +可以把选手信息存入数组中,选手编号就是数组的下标,数组的元素就是选手的信息。 + +当我们查询选手信息的时候,只需要根据选手的编号到数组中查询对应的元素就可以快速找到选手的信息,如下图: + +![image-20230428201000814](img\image-20230428201000814.png) + +现在需求升级了: + +假设有100个人参加马拉松,不采用1-100的自然数对选手进行编号,编号有一定的规则比如:2023ZHBJ001,其中2023代表年份,ZH代表中国,BJ代表北京,001代表原来的编号,那此时的编号2023ZHBJ001不能直接作为数组的下标,此时应该如何实现呢? + +![image-20230428201321607](img\image-20230428201321607.png) + +我们目前是把选手的信息存入到数组中,不过选手的编号不能直接作为数组的下标,不过,可以把选手的选号进行转换,转换为数值就可以继续作为数组的下标了? + +转换可以使用散列函数进行转换 + +#### 3.2.2 散列函数和散列冲突 + +将键(key)映射为数组下标的函数叫做散列函数。可以表示为:hashValue = hash(key) + +散列函数的基本要求: + +- 散列函数计算得到的散列值必须是大于等于0的正整数,因为hashValue需要作为数组的下标。 + +- 如果key1==key2,那么经过hash后得到的哈希值也必相同即:hash(key1) == hash(key2) + +- **如果key1 != key2,那么经过hash后得到的哈希值也必不相同即:hash(key1) != hash(key2)** + + + +实际的情况下想找一个散列函数能够做到对于不同的key计算得到的散列值都不同几乎是不可能的,即便像著名的MD5,SHA等哈希算法也无法避免这一情况,这就是散列冲突(或者哈希冲突,哈希碰撞,**就是指多个key映射到同一个数组下标位置**) + +![image-20230428203219225](img\image-20230428203219225.png) + +#### 3.2.3 散列冲突-链表法(拉链) + +在散列表中,数组的每个下标位置我们可以称之为桶(bucket)或者槽(slot),每个桶(槽)会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。 + +![image-20230428203437910](img\image-20230428203437910.png) + +简单就是,如果有多个key最终的hash值是一样的,就会存入数组的同一个下标中,下标中挂一个链表存入多个数据 + +#### 3.2.4 时间复杂度-散列表 + +1,插入操作,通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,插入的时间复杂度是 O(1) + +![image-20230428203711269](img\image-20230428203711269.png) + +>通过计算就可以找到元素 + +2,当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除 + +- 平均情况下基于链表法解决冲突时查询的时间复杂度是O(1) + +- 散列表可能会退化为链表,查询的时间复杂度就从 O(1) 退化为 O(n) + +![image-20230428203858903](img\image-20230428203858903.png) + +- 将链表法中的链表改造为其他高效的动态数据结构,比如红黑树,查询的时间复杂度是 O(logn) + +![image-20230428203924816](img\image-20230428203924816.png) + +将链表法中的链表改造红黑树还有一个非常重要的原因,可以防止DDos攻击 + +>DDos 攻击: +> +>分布式拒绝服务攻击(英文意思是Distributed Denial of Service,简称DDoS) +> +>指处于不同位置的多个攻击者同时向一个或数个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。由于攻击的发出点是分布在不同地方的,这类攻击称为分布式拒绝服务攻击,其中的攻击者可以有多个 + +### 3.3 面试题-说一下HashMap的实现原理? + +HashMap的数据结构: 底层使用hash表数据结构,即数组和链表或红黑树 + +1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 + +2. 存储时,如果出现hash值相同的key,此时有两种情况。 + + a. 如果key相同,则覆盖原始值; + + b. 如果key不同(出现冲突),则将当前的key-value放入链表或红黑树中 + +3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。 + +![image-20230428204902016](img\image-20230428204902016.png) + +面试官追问:HashMap的jdk1.7和jdk1.8有什么区别 + +- JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 + +- jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8) 时并且数组长度达到64时,将链表转化为红黑树,以减少搜索时间。扩容 resize( ) 时,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表 + +### 3.4 面试题-HashMap的put方法的具体流程 + +#### 3.4.1 hashMap常见属性 + +![image-20230428210404117](img\image-20230428210404117.png) + +#### 3.4.2 源码分析 + +![image-20230428210450744](img\image-20230428210450744.png) + +- HashMap是懒惰加载,在创建对象时并没有初始化数组 + +- 在无参的构造函数中,设置了默认的加载因子是0.75 + +添加数据流程图 + +![image-20230428210624847](img\image-20230428210624847.png) + +具体的源码: + +```java +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} + +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + //判断数组是否未初始化 + if ((tab = table) == null || (n = tab.length) == 0) + //如果未初始化,调用resize方法 进行初始化 + n = (tab = resize()).length; + //通过 & 运算求出该数据(key)的数组下标并判断该下标位置是否有数据 + if ((p = tab[i = (n - 1) & hash]) == null) + //如果没有,直接将数据放在该下标位置 + tab[i] = newNode(hash, key, value, null); + //该数组下标有数据的情况 + else { + Node e; K k; + //判断该位置数据的key和新来的数据是否一样 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + //如果一样,证明为修改操作,该节点的数据赋值给e,后边会用到 + e = p; + //判断是不是红黑树 + else if (p instanceof TreeNode) + //如果是红黑树的话,进行红黑树的操作 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + //新数据和当前数组既不相同,也不是红黑树节点,证明是链表 + else { + //遍历链表 + for (int binCount = 0; ; ++binCount) { + //判断next节点,如果为空的话,证明遍历到链表尾部了 + if ((e = p.next) == null) { + //把新值放入链表尾部 + p.next = newNode(hash, key, value, null); + //因为新插入了一条数据,所以判断链表长度是不是大于等于8 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + //如果是,进行转换红黑树操作 + treeifyBin(tab, hash); + break; + } + //判断链表当中有数据相同的值,如果一样,证明为修改操作 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + //把下一个节点赋值为当前节点 + p = e; + } + } + //判断e是否为空(e值为修改操作存放原数据的变量) + if (e != null) { // existing mapping for key + //不为空的话证明是修改操作,取出老值 + V oldValue = e.value; + //一定会执行 onlyIfAbsent传进来的是false + if (!onlyIfAbsent || oldValue == null) + //将新值赋值当前节点 + e.value = value; + afterNodeAccess(e); + //返回老值 + return oldValue; + } + } + //计数器,计算当前节点的修改次数 + ++modCount; + //当前数组中的数据数量如果大于扩容阈值 + if (++size > threshold) + //进行扩容操作 + resize(); + //空方法 + afterNodeInsertion(evict); + //添加操作时 返回空值 + return null; +} +``` + +1. 判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化) + +2. 根据键值key计算hash值得到数组索引 + +3. 判断table[i]==null,条件成立,直接新建节点添加 + +4. 如果table[i]==null ,不成立 + + 4.1 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value + + 4.2 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对 + + 4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现key已经存在直接覆盖value + +5. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容。 + + + +### 3.5 面试题-讲一讲HashMap的扩容机制 + +![image-20230428210844694](img\image-20230428210844694.png) + +扩容的流程: + +![image-20230428211031968](img\image-20230428211031968.png) + +源码: + +```java +//扩容、初始化数组 +final Node[] resize() { + Node[] oldTab = table; + //如果当前数组为null的时候,把oldCap老数组容量设置为0 + int oldCap = (oldTab == null) ? 0 : oldTab.length; + //老的扩容阈值 + int oldThr = threshold; + int newCap, newThr = 0; + //判断数组容量是否大于0,大于0说明数组已经初始化 + if (oldCap > 0) { + //判断当前数组长度是否大于最大数组长度 + if (oldCap >= MAXIMUM_CAPACITY) { + //如果是,将扩容阈值直接设置为int类型的最大数值并直接返回 + threshold = Integer.MAX_VALUE; + return oldTab; + } + //如果在最大长度范围内,则需要扩容 OldCap << 1等价于oldCap*2 + //运算过后判断是不是最大值并且oldCap需要大于16 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold 等价于oldThr*2 + } + //如果oldCap<0,但是已经初始化了,像把元素删除完之后的情况,那么它的临界值肯定还存在, 如果是首次初始化,它的临界值则为0 + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + //数组未初始化的情况,将阈值和扩容因子都设置为默认值 + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + //初始化容量小于16的时候,扩容阈值是没有赋值的 + if (newThr == 0) { + //创建阈值 + float ft = (float)newCap * loadFactor; + //判断新容量和新阈值是否大于最大容量 + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + //计算出来的阈值赋值 + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + //根据上边计算得出的容量 创建新的数组 + Node[] newTab = (Node[])new Node[newCap]; + //赋值 + table = newTab; + //扩容操作,判断不为空证明不是初始化数组 + if (oldTab != null) { + //遍历数组 + for (int j = 0; j < oldCap; ++j) { + Node e; + //判断当前下标为j的数组如果不为空的话赋值个e,进行下一步操作 + if ((e = oldTab[j]) != null) { + //将数组位置置空 + oldTab[j] = null; + //判断是否有下个节点 + if (e.next == null) + //如果没有,就重新计算在新数组中的下标并放进去 + newTab[e.hash & (newCap - 1)] = e; + //有下个节点的情况,并且判断是否已经树化 + else if (e instanceof TreeNode) + //进行红黑树的操作 + ((TreeNode)e).split(this, newTab, j, oldCap); + //有下个节点的情况,并且没有树化(链表形式) + else { + //比如老数组容量是16,那下标就为0-15 + //扩容操作*2,容量就变为32,下标为0-31 + //低位:0-15,高位16-31 + //定义了四个变量 + // 低位头 低位尾 + Node loHead = null, loTail = null; + // 高位头 高位尾 + Node hiHead = null, hiTail = null; + //下个节点 + Node next; + //循环遍历 + do { + //取出next节点 + next = e.next; + //通过 与操作 计算得出结果为0 + if ((e.hash & oldCap) == 0) { + //如果低位尾为null,证明当前数组位置为空,没有任何数据 + if (loTail == null) + //将e值放入低位头 + loHead = e; + //低位尾不为null,证明已经有数据了 + else + //将数据放入next节点 + loTail.next = e; + //记录低位尾数据 + loTail = e; + } + //通过 与操作 计算得出结果不为0 + else { + //如果高位尾为null,证明当前数组位置为空,没有任何数据 + if (hiTail == null) + //将e值放入高位头 + hiHead = e; + //高位尾不为null,证明已经有数据了 + else + //将数据放入next节点 + hiTail.next = e; + //记录高位尾数据 + hiTail = e; + } + + } + //如果e不为空,证明没有到链表尾部,继续执行循环 + while ((e = next) != null); + //低位尾如果记录的有数据,是链表 + if (loTail != null) { + //将下一个元素置空 + loTail.next = null; + //将低位头放入新数组的原下标位置 + newTab[j] = loHead; + } + //高位尾如果记录的有数据,是链表 + if (hiTail != null) { + //将下一个元素置空 + hiTail.next = null; + //将高位头放入新数组的(原下标+原数组容量)位置 + newTab[j + oldCap] = hiHead; + } + } + } + } + } + //返回新的数组对象 + return newTab; + } +``` + +- 在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度 * 0.75) + +- 每次扩容的时候,都是扩容之前容量的2倍; + +- 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中 + - 没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置 + - 如果是红黑树,走红黑树的添加 + - 如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上 + + + +### 3.6 面试题-hashMap的寻址算法 + +![image-20230428212501408](img\image-20230428212501408.png) + +在putVal方法中,有一个hash(key)方法,这个方法就是来去计算key的hash值的,看下面的代码 + +![image-20230428212601977](img\image-20230428212601977.png) + +首先获取key的hashCode值,然后右移16位 异或运算 原来的hashCode值,主要作用就是使原来的hash值更加均匀,减少hash冲突 + +有了hash值之后,就很方便的去计算当前key的在数组中存储的下标,看下面的代码: + +![image-20230428212729580](img\image-20230428212729580.png) + +(n-1)&hash : 得到数组中的索引,代替取模,性能更好,数组长度必须是2的n次幂 + + + +**关于hash值的其他面试题:为何HashMap的数组长度一定是2的次幂?** + +1. 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模 + +2. 扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap + + + +### 3.7 面试题-hashmap在1.7情况下的多线程死循环问题 + +jdk7的的数据结构是:数组+链表 + +在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能导致死循环 + +![image-20230428213115071](img\image-20230428213115071.png) + +- 变量e指向的是需要迁移的对象 + +- 变量next指向的是下一个需要迁移的对象 + +- Jdk1.7中的链表采用的头插法 + +- 在数据迁移的过程中并没有新的对象产生,只是改变了对象的引用 + + + +产生死循环的过程: + +线程1和线程2的变量e和next都引用了这个两个节点 + +![image-20230428213533483](img\image-20230428213533483.png) + +线程2扩容后,由于头插法,链表顺序颠倒,但是线程1的临时变量e和next还引用了这两个节点 + +![image-20230428214732877](img\image-20230428214732877.png) + +第一次循环 + +由于线程2迁移的时候,已经把B的next执行了A + +![image-20230428214806072](img\image-20230428214806072.png) + +第二次循环 + +![image-20230428214908652](img\image-20230428214908652.png) + +第三次循环 + +![image-20230428214937231](img\image-20230428214937231.png) + +参考回答: + +在jdk1.7的hashmap中在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能导致死循环 + +比如说,现在有两个线程 + +线程一:读取到当前的hashmap数据,数据中一个链表,在准备扩容时,线程二介入 + +线程二:也读取hashmap,直接进行扩容。因为是头插法,链表的顺序会进行颠倒过来。比如原来的顺序是AB,扩容后的顺序是BA,线程二执行结束。 + +线程一:继续执行的时候就会出现死循环的问题。 + +线程一先将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,B的next指向了A, + +所以B->A->B,形成循环。 + +当然,JDK 8 将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),**尾插法**,就避免了jdk7中死循环的问题。 + + + +### 3.8 面试题-HashSet与HashMap的区别 + +(1)HashSet实现了Set接口, 仅存储对象; HashMap实现了 Map接口, 存储的是键值对. + +(2)HashSet底层其实是用HashMap实现存储的, HashSet封装了一系列HashMap的方法. 依靠HashMap来存储元素值,(利用hashMap的key键进行存储), 而value值默认为Object对象. 所以HashSet也不允许出现重复值, 判断标准和HashMap判断标准相同, 两个元素的hashCode相等并且通过equals()方法返回true. + +![image-20221007110404375](img\image-20221007110404375.png) + +### 3.9 面试题-HashTable与HashMap的区别 + +>难易程度:☆☆ +> +>出现频率:☆☆ + +主要区别: + +| **区别** | **HashTable** | **HashMap** | +| -------------- | ------------------------------ | ---------------- | +| 数据结构 | 数组+链表 | 数组+链表+红黑树 | +| 是否可以为null | Key和value都不能为null | 可以为null | +| hash算法 | key的hashCode() | 二次hash | +| 扩容方式 | 当前容量翻倍 +1 | 当前容量翻倍 | +| 线程安全 | 同步(synchronized)的,线程安全 | 非线程安全 | + +在实际开中不建议使用HashTable,在多线程环境下可以使用ConcurrentHashMap类 + +## 3 真实面试还原 + +### 3.1 Java常见的集合类 + +>**面试官**:说一说Java提供的常见集合?(画一下集合结构图) +> +>**候选人**: +> +>嗯~~,好的。 +> +>在java中提供了量大类的集合框架,主要分为两类: +> +>第一个是Collection 属于单列集合,第二个是Map 属于双列集合 +> +>- 在Collection中有两个子接口List和Set。在我们平常开发的过程中用的比较多像list接口中的实现类ArrarList和LinkedList。 在Set接口中有实现类HashSet和TreeSet。 +>- 在map接口中有很多的实现类,平时比较常见的是HashMap、TreeMap,还有一个线程安全的map:ConcurrentHashMap + +### 3.2 List + +>**面试官**:ArrayList底层是如何实现的? +> +>**候选人**: +> +>嗯~,我阅读过arraylist的源码,我主要说一下add方法吧 +> +>第一:确保数组已使用长度(size)加1之后足够存下下一个数据 +> +>第二:计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍) +> +>第三:确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。 +> +>第四:返回添加成功布尔值。 +> +>**面试官**:ArrayList list=new ArrayList(10)中的list扩容几次 +> +>**候选人**: +> +>​ 是new了一个ArrarList并且给了一个构造参数10,对吧?(问题一定要问清楚再答) +> +>**面试官**:是的 +> +>**候选人**: +> +>​ 好的,在ArrayList的源码中提供了一个带参数的构造方法,这个参数就是指定的集合初始长度,所以给了一个10的参数,就是指定了集合的初始长度是10,这里面并没有扩容。 +> +>------------------------------------------- +> +>**面试官**:如何实现数组和List之间的转换 +> +>**候选人**: +> +>​ 嗯,这个在我们平时开发很常见 +> +>​ 数组转list,可以使用jdk自动的一个工具类Arrars,里面有一个asList方法可以转换为数组 +> +>​ List 转数组,可以直接调用list中的toArray方法,需要给一个参数,指定数组的类型,需要指定数组的长度。 +> +>**面试官**:用Arrays.asList转List后,如果修改了数组内容,list受影响吗?List用toArray转数组后,如果修改了List内容,数组受影响吗 +> +>**候选人**: +> +>Arrays.asList转换list之后,如果修改了数组的内容,list会受影响,因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,最终指向的都是同一个内存地址 +> +>list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层是它是进行了数组的拷贝,跟原来的元素就没啥关系了,所以即使list修改了以后,数组也不受影响 +> +>----- +> +>**面试官**:ArrayList 和 LinkedList 的区别是什么? +> +>**候选人**: +> +>嗯,它们两个主要是底层使用的数据结构不一样,ArrayList 是动态数组,LinkedList 是双向链表,这也导致了它们很多不同的特点。 +> +>1,从操作数据效率来说 +> +>ArrayList按照下标查询的时间复杂度O(1)【内存是连续的,根据寻址公式】, LinkedList不支持下标查询 +> +>查找(未知索引): ArrayList需要遍历,链表也需要链表,时间复杂度都是O(n) +> +>新增和删除 +> +>- ArrayList尾部插入和删除,时间复杂度是O(1);其他部分增删需要挪动数组,时间复杂度是O(n) +>- LinkedList头尾节点增删时间复杂度是O(1),其他都需要遍历链表,时间复杂度是O(n) +> +>2,从内存空间占用来说 +> +>ArrayList底层是数组,内存连续,节省内存 +> +>LinkedList 是双向链表需要存储数据,和两个指针,更占用内存 +> +>3,从线程安全来说,ArrayList和LinkedList都不是线程安全的 +> +>**面试官**:嗯,好的,刚才你说了ArrayList 和 LinkedList 不是线程安全的,你们在项目中是如何解决这个的线程安全问题的? +> +>**候选人**: +> +>嗯,是这样的,主要有两种解决方案: +> +>第一:我们使用这个集合,优先在方法内使用,定义为局部变量,这样的话,就不会出现线程安全问题。 +> +>第二:如果非要在成员变量中使用的话,可以使用线程安全的集合来替代 +> +>ArrayList可以通过Collections 的 synchronizedList 方法将 ArrayList 转换成线程安全的容器后再使用。 +> +>LinkedList 换成ConcurrentLinkedQueue来使用 + +### 3.4 HashMap + +>**面试官**:说一下HashMap的实现原理? +> +>**候选人**: +> +>​ 嗯。它主要分为了一下几个部分: +> +>1,底层使用hash表数据结构,即数组+(链表 | 红黑树) +> +>2,添加数据时,计算key的值确定元素在数组中的下标 +> +>​ key相同则替换 +> +>​ 不同则存入链表或红黑树中 +> +>3,获取数据通过key的hash计算数组下标获取元素 +> +>**面试官**:HashMap的jdk1.7和jdk1.8有什么区别 +> +>**候选人**: +> +>- JDK1.8之前采用的拉链法,数组+链表 +> +>- JDK1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转化为红黑树 +> +>**面试官**:好的,你能说下HashMap的put方法的具体流程吗? +> +>**候选人**: +> +>嗯好的。 +> +>1. 判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化) +> +>2. 根据键值key计算hash值得到数组索引 +> +>3. 判断table[i]==null,条件成立,直接新建节点添加 +> +>4. 如果table[i]==null ,不成立 +> +> 4.1 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value +> +> 4.2 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对 +> +> 4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现key已经存在直接覆盖value +> +>5. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容。 +> +>**面试官**:好的,刚才你多次介绍了hsahmap的扩容,能讲一讲HashMap的扩容机制吗? +> +>**候选人**: +> +>好的 +> +>- 在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度 * 0.75) +> +>- 每次扩容的时候,都是扩容之前容量的2倍; +> +>- 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中 +> - 没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置 +> - 如果是红黑树,走红黑树的添加 +> - 如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上 +> +>**面试官**:好的,刚才你说的通过hash计算后找到数组的下标,是如何找到的呢,你了解hashMap的寻址算法吗? +> +>**候选人**: +> +>这个哈希方法首先计算出key的hashCode值,然后通过这个hash值右移16位后的二进制进行按位**异或运算**得到最后的hash值。 +> +>在putValue的方法中,计算数组下标的时候使用hash值与数组长度取模得到存储数据下标的位置,hashmap为了性能更好,并没有直接采用取模的方式,而是使用了数组长度-1 得到一个值,用这个值按位与运算hash值,最终得到数组的位置。 +> +>**面试官**:为何HashMap的数组长度一定是2的次幂? +> +>**候选人**: +> +>嗯,好的。hashmap这么设计主要有两个原因: +> +>第一: +> +>计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模 +> +>第二: +> +>扩容时重新计算索引效率更高:在进行扩容是会进行判断 hash值按位与运算旧数组长租是否 == 0 +> +>如果等于0,则把元素留在原来位置 ,否则新位置是等于旧位置的下标+旧数组长度 +> +> +> +>**面试官**:好的,我看你对hashmap了解的挺深入的,你知道hashmap在1.7情况下的多线程死循环问题吗? +> +>**候选人**: +> +>嗯,知道的。是这样 +> +>jdk7的的数据结构是:数组+链表 +> +>在数组进行扩容的时候,因为链表是**头插法**,在进行数据迁移的过程中,有可能导致死循环 +> +>比如说,现在有两个线程 +> +>线程一:**读取**到当前的hashmap数据,数据中一个链表,在准备扩容时,线程二介入 +> +>线程二也读取hashmap,直接进行扩容。因为是头插法,链表的顺序会进行颠倒过来。比如原来的顺序是AB,扩容后的顺序是BA,线程二执行结束。 +> +>当线程一再继续执行的时候就会出现死循环的问题。 +> +>线程一先将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,B的next指向了A,所以B->A->B,形成循环。 +> +>当然,JDK 8 将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),**尾插法**,就避免了jdk7中死循环的问题。 +> +>**面试官**:好的,hashmap是线程安全的吗? +> +>**候选人**:不是线程安全的 +> +>**面试官**:那我们想要使用线程安全的map该怎么做呢? +> +>**候选人**:我们可以采用ConcurrentHashMap进行使用,它是一个线程安全的HashMap +> +>**面试官**:那你能聊一下ConcurrentHashMap的原理吗? +> +>**候选人**:好的,请参考《多线程相关面试题》中的ConcurrentHashMap部分的讲解 +> +>----- +> +>**面试官**:HashSet与HashMap的区别? +> +>**候选人**:嗯,是这样。 +> +>HashSet底层其实是用HashMap实现存储的, HashSet封装了一系列HashMap的方法. 依靠HashMap来存储元素值,(利用hashMap的key键进行存储), 而value值默认为Object对象. 所以HashSet也不允许出现重复值, 判断标准和HashMap判断标准相同, 两个元素的hashCode相等并且通过equals()方法返回true. +> +>**面试官**:HashTable与HashMap的区别 +> +>**候选人**: +> +>嗯,他们的主要区别是有几个吧 +> +>第一,数据结构不一样,hashtable是数组+链表,hashmap在1.8之后改为了数组+链表+红黑树 +> +>第二,hashtable存储数据的时候都不能为null,而hashmap是可以的 +> +>第三,hash算法不同,hashtable是用本地修饰的hashcode值,而hashmap经常了二次hash +> +>第四,扩容方式不同,hashtable是当前容量翻倍+1,hashmap是当前容量翻倍 +> +>第五,hashtable是线程安全的,操作数据的时候加了锁synchronized,hashmap不是线程安全的,效率更高一些 +> +>在实际开中不建议使用HashTable,在多线程环境下可以使用ConcurrentHashMap类 + + + + + diff --git a/面试/面试/MySQL面试题-参考回答.md b/面试/面试/MySQL面试题-参考回答.md new file mode 100755 index 0000000..f877f49 --- /dev/null +++ b/面试/面试/MySQL面试题-参考回答.md @@ -0,0 +1,169 @@ +### MySQL面试题-文稿 + +>**面试官:**MySQL中,如何定位慢查询? +> +>**候选人:** +> +>嗯~,我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了2秒以上,因为我们当时的系统部署了运维的监控系统Skywalking ,在展示的报表中可以看到是哪一个接口比较慢,并且可以分析这个接口哪部分比较慢,这里可以看到SQL的具体的执行时间,所以可以定位是哪个sql出了问题 +> +>如果,项目中没有这种运维的监控系统,其实在MySQL中也提供了慢日志查询的功能,可以在MySQL的系统配置文件中开启这个慢日志的功能,并且也可以设置SQL执行超过多少时间来记录到一个日志文件中,我记得上一个项目配置的是2秒,只要SQL执行的时间超过了2秒就会记录到日志文件中,我们就可以在日志文件找到执行比较慢的SQL了。 +> +>**面试官:**那这个SQL语句执行很慢, 如何分析呢? +> +>**候选人:**如果一条sql执行很慢的话,我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况,比如在这里面可以通过key和key_len检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况,第二个,可以通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描,第三个可以通过extra建议来判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复 +> +>**面试官:**了解过索引吗?(什么是索引) +> +>**候选人:**嗯,索引在项目中还是比较常见的,它是帮助MySQL高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的IO成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了CPU的消耗 +> +>**面试官:**索引的底层数据结构了解过嘛 ? +> +>**候选人:**MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引,选择B+树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是B+树便于扫库和区间查询,叶子节点是一个双向链表 +> +>**面试官:**B树和B+树的区别是什么呢? +> +>**候选人**:第一:在B树中,非叶子节点和叶子节点都会存放数据,而B+树的所有的数据都会出现在叶子节点,在查询的时候,B+树查找效率更加稳定 +> +>第二:在进行范围查询的时候,B+树效率更高,因为B+树都在叶子节点存储,并且叶子节点是一个双向链表 +> +>**面试官:**什么是聚簇索引什么是非聚簇索引 ? +> +>**候选人:** +> +>好的~,聚簇索引主要是指数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚簇索引的 +> +>非聚簇索引值的是数据与索引分开存储,B+树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚簇索引 +> +>**面试官:**知道什么是回表查询嘛 ? +> +>**候选人:**嗯,其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表 +> +>【**备注**:如果面试官直接问回表,则需要先介绍聚簇索引和非聚簇索引】 +> +>**面试官:**知道什么叫覆盖索引嘛 ? +> +>**候选人:**嗯~,清楚的 +> +>覆盖索引是指select查询语句使用了索引,在返回的列,必须在索引中全部能够找到,如果我们使用id查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。 +> +>如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *,尽量在返回的列中都包含添加索引的字段 +> +>**面试官:**MYSQL超大分页怎么处理 ? +> +>**候选人:**嗯,超大分页一般都是在数据量比较大时,我们使用了limit分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决 +> +>先分页查询数据的id字段,确定了id之后,再用子查询来过滤,只查询这个id列表中的数据就可以了 +> +>因为查询id的时候,走的覆盖索引,所以效率可以提升很多 +> +>**面试官:**索引创建原则有哪些? +> +>**候选人:**嗯,这个情况有很多,不过都有一个大前提,就是表中的数据要超过10万以上,我们才会创建索引,并且添加索引的字段是查询比较频繁的字段,一般也是像作为查询条件,排序字段或分组的字段这些。 +> +>还有就是,我们通常创建索引的时候都是使用复合索引来创建,一条sql的返回值,尽量使用覆盖索引,如果字段的区分度不高的话,我们也会把它放在组合索引后面的字段。 +> +>如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。 +> +>**面试官:**什么情况下索引会失效 ? +> +>**候选人:**嗯,这个情况比较多,我说一些自己的经验,以前遇到过的 +> +>比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果%号在前面也会导致索引失效。如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。 +> +>我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效 +> +>所以,通常情况下,想要判断出这条sql是否有索引失效的情况,可以使用explain执行计划来分析 +> +>**面试官:**sql的优化的经验 +> +>**候选人:**嗯,这个在项目还是挺常见的,当然如果直说sql优化的话,我们会从这几方面考虑,比如 +> +>建表的时候、使用索引、sql语句的编写、主从复制,读写分离,还有一个是如果量比较大的话,可以考虑分库分表 +> +>**面试官:**创建表的时候,你们是如何优化的呢? +> +>**候选人:**这个我们主要参考的阿里出的那个开发手册《嵩山版》,就比如,在定义字段的时候需要结合字段的内容来选择合适的类型,如果是数值的话,像tinyint、int 、bigint这些类型,要根据实际情况选择。如果是字符串类型,也是结合存储的内容来选择char和varchar或者text类型 +> +>**面试官:**那在使用索引的时候,是如何优化呢? +> +>**候选人:**【参考索引创建原则 进行描述】 +> +>**面试官:**你平时对sql语句做了哪些优化呢? +> +>**候选人:**嗯,这个也有很多,比如SELECT语句务必指明字段名称,不要直接使用select * ,还有就是要注意SQL语句避免造成索引失效的写法;如果是聚合查询,尽量用union all代替union ,union会多一次过滤,效率比较低;如果是表关联的话,尽量使用innerjoin ,不要使用用left join right join,如必须使用 一定要以小表为驱动 +> +>**面试官:**事务的特性是什么?可以详细说一下吗? +> +>**候选人:**嗯,这个比较清楚,ACID,分别指的是:原子性、一致性、隔离性、持久性;我举个例子: +> +>A向B转账500,转账成功,A扣除500元,B增加500元,原子操作体现在要么都成功,要么都失败 +> +>在转账的过程中,数据要一致,A扣除了500,B必须增加500 +> +>在转账的过程中,隔离性体现在A像B转账,不能受其他事务干扰 +> +>在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作) +> +>**面试官**:并发事务带来哪些问题? +> +>**候选人**: +> +>我们在项目开发中,多个事务并发进行是经常发生的,并发也是必然的,有可能导致一些问题 +> +>第一是脏读, 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。 +> +>第二是不可重复读:比如在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。 +> +>第三是幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。 +> +>**面试官**:怎么解决这些问题呢?MySQL的默认隔离级别是? +> +>**候选人**:解决方案是对事务进行隔离 +> +>MySQL支持四种隔离级别,分别有: +> +>第一个是,未提交读(read uncommitted)它解决不了刚才提出的所有问题,一般项目中也不用这个。第二个是读已提交(read committed)它能解决脏读的问题的,但是解决不了不可重复读和幻读。第三个是可重复读(repeatable read)它能解决脏读和不可重复读,但是解决不了幻读,这个也是mysql默认的隔离级别。第四个是串行化(serializable)它可以解决刚才提出来的所有问题,但是由于让是事务串行执行的,性能比较低。所以,我们一般使用的都是mysql默认的隔离级别:可重复读 +> +>**面试官**:undo log和redo log的区别 +> +>**候选人**:好的,其中redo log日志记录的是数据页的物理变化,服务宕机可用来同步数据,而undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在undo log日志文件中新增一条delete语句,如果发生回滚就执行逆操作; +> +>redo log保证了事务的持久性,undo log保证了事务的原子性和一致性 +> +>**面试官**:事务中的隔离性是如何保证的呢?(你解释一下MVCC) +> +>**候选人**:事务的隔离性是由锁和mvcc实现的。 +> +>其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是undo log日志,第三个是readView读视图 +> +>隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址 +> +>undo log主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表 +> +>readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快照读时生成ReadView,如果是rr隔离级别仅在事务中第一次执行快照读时生成ReadView,后续复用 +> +>**面试官**:MySQL主从同步原理 +> +>**候选人**:MySQL主从复制的核心就是二进制日志(DDL(数据定义语言)语句和 DML(数据操纵语言)语句),它的步骤是这样的: +> +>第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。 +> +>第二:从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。 +> +>第三:从库重做中继日志中的事件,将改变反映它自己的数据 +> +>**面试官**:你们项目用过MySQL的分库分表吗? +> +>**候选人**: +> +>嗯,因为我们都是微服务开发,每个微服务对应了一个数据库,是根据业务进行拆分的,这个其实就是垂直拆分。 +> +>**面试官**:那你之前使用过水平分库吗? +> +>**候选人**: +> +>嗯,这个是使用过的,我们当时的业务是(xxx),一开始,我们也是单库,后来这个业务逐渐发展,业务量上来的很迅速,其中(xx)表已经存放了超过1000万的数据,我们做了很多优化也不好使,性能依然很慢,所以当时就使用了水平分库。 +> +>我们一开始先做了3台服务器对应了3个数据库,由于库多了,需要分片,我们当时采用的mycat来作为数据库的中间件。数据都是按照id(自增)取模的方式来存取的。 +> +>当然一开始的时候,那些旧数据,我们做了一些清洗的工作,我们也是按照id取模规则分别存储到了各个数据库中,好处就是可以让各个数据库分摊存储和读取的压力,解决了我们当时性能的问题 diff --git a/面试/面试/Redis面试题-参考回答.md b/面试/面试/Redis面试题-参考回答.md new file mode 100755 index 0000000..3647761 --- /dev/null +++ b/面试/面试/Redis面试题-参考回答.md @@ -0,0 +1,278 @@ +# Redis相关面试题 + +>**面试官**:什么是缓存穿透 ? 怎么解决 ? +> +>**候选人**: +> +>嗯~~,我想一下 +> +>缓存穿透是指查询一个一定**不存在**的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。 +> +>解决方案的话,我们通常都会用布隆过滤器来解决它 + + + +>**面试官**:好的,你能介绍一下布隆过滤器吗? +> +>**候选人**: +> +>嗯,是这样~ +> +>布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是redisson实现的布隆过滤器。 +> +>它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。 +> +>当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。 +> + +> +>**面试官**:什么是缓存击穿 ? 怎么解决 ? +> +>**候选人**: +> +>嗯!! +> +>缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。 +> +>解决方案有两种方式: +> +>第一可以使用互斥锁:当缓存失效时,不立即去load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db的操作并回设缓存,否则重试get缓存的方法 +> +>第二种方案可以设置当前key逻辑过期,大概是思路如下: +> +>①:在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间 +> +>②:当查询的时候,从redis取出数据后判断时间是否过期 +> +>③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新 +> +>当然两种方案各有利弊: +> +>如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么高,锁需要等,也有可能产生死锁的问题 +> +>如果选择key的逻辑删除,则优先考虑的高可用性,性能比较高,但是数据同步这块做不到强一致。 + + +>**面试官**:什么是缓存雪崩 ? 怎么解决 ? +> +>**候选人**: +> +>嗯!! +> +>缓存雪崩意思是设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。 +> +>解决方案主要是可以将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。 + + +>**面试官**:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性) +> +>**候选人**:嗯!就说我最近做的这个项目,里面有xxxx(**根据自己的简历上写**)的功能,需要让数据库与redis高度保持一致,因为要求时效性比较高,我们当时采用的读写锁保证的强一致性。 +> +>我们采用的是redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。 + + +>**面试官**:那这个排他锁是如何保证读写、读读互斥的呢? +> +>**候选人**:其实排他锁底层使用也是setnx,保证了同时只能有一个线程操作锁住的方法 + + +>**面试官**:你听说过延时双删吗?为什么不用它呢? +> +>**候选人**:延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。 +> + + + +>**面试官**:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性) +> +>**候选人**:嗯!就说我最近做的这个项目,里面有xxxx(**根据自己的简历上写**)的功能,数据同步可以有一定的延时(符合大部分业务) +> +>我们当时采用的阿里的canal组件实现数据同步:不需要更改业务代码,部署一个canal服务。canal服务把自己伪装成mysql的一个从节点,当mysql数据更新以后,canal会读取binlog数据,然后在通过canal的客户端获取到数据,更新缓存即可。 + + +>**面试官**:redis做为缓存,数据的持久化是怎么做的? +> +>**候选人**:在Redis中提供了两种数据持久化的方式:1、RDB 2、AOF + + +>**面试官**:这两种持久化方式有什么区别呢? +> +>**候选人**:RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。 +> +>AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据 + + +>**面试官**:这两种方式,哪种恢复的比较快呢? +> +>**候选人**:RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令 + + +>**面试官**:Redis的数据过期策略有哪些 ? +> +>**候选人**: +> +>嗯~,在redis中提供了两种数据过期删除策略 +> +> +>第一种是惰性删除,在设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。 +> +>第二种是 定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除里面过期的key +> +>定期清理的两种模式: +> +>- SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的 **hz** 选项来调整这个次数 +>- FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms +> +>Redis的过期删除策略:**惰性删除 + 定期删除**两种策略进行配合使用。 + + +>**面试官**:Redis的数据淘汰策略有哪些 ? +> +>**候选人**: +> +>嗯,这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足直接报错 +> +>是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU +> +>LRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。 +> +>LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高 +> +>我们在项目设置的allkeys-lru,挑选最近最少使用的数据淘汰,把一些经常访问的key留在redis中 + + +>**面试官**:数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ? +> +>**候选人**: +> +>嗯,我想一下~~ +> +>可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据 + + +>**面试官**:Redis的内存用完了会发生什么? +> +>**候选人**: +> +>嗯~,这个要看redis的数据淘汰策略是什么,如果是默认的配置,redis内存用完以后则直接报错。我们当时设置的 allkeys-lru 策略。把最近最常访问的数据留在缓存中。 + + +>**面试官**:Redis分布式锁如何实现 ? +> +>**候选人**:嗯,在redis中提供了一个命令setnx(SET if not exists) +> +>由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候是其他客户端是不能设置这个key的 + + +>**面试官**:好的,那你如何控制Redis实现分布式锁有效时长呢? +> +>**候选人**:嗯,的确,redis的setnx指令不好控制这个问题,我们当时采用的redis的一个框架redisson实现的。 +> +>在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了 +> +>还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自旋不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。 + + +>**面试官**:好的,redisson实现的分布式锁是可重入的吗? +> +>**候选人**:嗯,是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数 + + +>**面试官**:redisson实现的分布式锁能解决主从一致性的问题吗 +> +>**候选人**:这个是不能的,比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。 +> +>我们可以利用redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,并且要求在大多数redis节点上都成功创建锁,红锁中要求是redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。 +> +>但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变的很低了,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁 + + +>**面试官**:好的,如果业务非要保证数据的强一致性,这个该怎么解决呢? +> +>**候选人:**嗯~,redis本身就是支持高可用的,做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用zookeeper实现的分布式锁,它是可以保证强一致性的。 + + +>**面试官**:Redis集群有哪些方案, 知道嘛 ? +> +>**候选人**:嗯~~,在Redis中提供的集群方案总共有三种:主从复制、哨兵模式、Redis分片集群 + + +>**面试官**:那你来介绍一下主从同步 +> +>**候选人**:嗯,是这样的,单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中 + + +>**面试官**:能说一下,主从同步数据的流程 +> +>**候选人**:嗯~~,好!主从同步分为了两个阶段,一个是全量同步,一个是增量同步 +> +>全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的: +> +>第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。 +> +>第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。 +> +>第三:在同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致 +> +>当然,如果在rdb生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步 +> +>增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步 + + +>**面试官**:怎么保证Redis的高并发高可用 +> +>**候选人**:首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用 + + +>**面试官**:你们使用redis是单点还是集群,哪种集群 +> +>**候选人**:嗯!,我们当时使用的是主从(1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务 + + +>**面试官**:redis集群脑裂,该怎么解决呢? +> +>**候选人**:嗯! 这个在项目很少见,不过脑裂的问题是这样的,我们现在用的是redis的哨兵模式集群的 +> +>有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old master降为salve,这时再从新master同步数据,这会导致old master中的大量数据丢失。 +> +>关于解决的话,我记得在redis的配置中可以设置:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失 + + +>**面试官**:redis的分片集群有什么作用 +> +>**候选人**:分片集群主要解决的是,海量数据存储的问题,集群中有多个master,每个master保存不同数据,并且还可以给每个master设置多个slave节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点 + + +>**面试官**:Redis分片集群中数据是怎么存储和读取的? +> +>**候选人**: +> +>嗯~,在redis集群中是这样的 +> +>Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。 +> +>取值的逻辑是一样的 + + +>**面试官**:Redis是单线程的,但是为什么还那么快? +> +>**候选人**: +> +>嗯,这个有几个原因吧~~~ +> +>1、完全基于内存的,C语言编写 +> +>2、采用单线程,避免不必要的上下文切换可竞争条件 +> +>3、使用多路I/O复用模型,非阻塞IO +> +>例如:bgsave 和 bgrewriteaof 都是在**后台**执行操作,不影响主线程的正常使用,不会产生阻塞 + + +>**面试官**:能解释一下I/O多路复用模型? +> +>**候选人**:嗯~~,I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。 +> +>其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器; +> +>在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程 diff --git a/面试/面试/img/20161222153407_471.png b/面试/面试/img/20161222153407_471.png new file mode 100755 index 0000000..0cd635e Binary files /dev/null and b/面试/面试/img/20161222153407_471.png differ diff --git a/面试/面试/img/20161222153407_691.png b/面试/面试/img/20161222153407_691.png new file mode 100755 index 0000000..900b7f3 Binary files /dev/null and b/面试/面试/img/20161222153407_691.png differ diff --git a/面试/面试/img/ReentrantLock加锁和解锁过程.jpg b/面试/面试/img/ReentrantLock加锁和解锁过程.jpg new file mode 100755 index 0000000..5ee3e86 Binary files /dev/null and b/面试/面试/img/ReentrantLock加锁和解锁过程.jpg differ diff --git a/面试/面试/img/image-20200802001502483.png b/面试/面试/img/image-20200802001502483.png new file mode 100755 index 0000000..0dd2bb8 Binary files /dev/null and b/面试/面试/img/image-20200802001502483.png differ diff --git a/面试/面试/img/image-20200802001515438.png b/面试/面试/img/image-20200802001515438.png new file mode 100755 index 0000000..ed64c06 Binary files /dev/null and b/面试/面试/img/image-20200802001515438.png differ diff --git a/面试/面试/img/image-20200802001533126.png b/面试/面试/img/image-20200802001533126.png new file mode 100755 index 0000000..ea1a953 Binary files /dev/null and b/面试/面试/img/image-20200802001533126.png differ diff --git a/面试/面试/img/image-20200802123228945.png b/面试/面试/img/image-20200802123228945.png new file mode 100755 index 0000000..bd2aebe Binary files /dev/null and b/面试/面试/img/image-20200802123228945.png differ diff --git a/面试/面试/img/image-20200802123241536.png b/面试/面试/img/image-20200802123241536.png new file mode 100755 index 0000000..142f842 Binary files /dev/null and b/面试/面试/img/image-20200802123241536.png differ diff --git a/面试/面试/img/image-20200802123304797.png b/面试/面试/img/image-20200802123304797.png new file mode 100755 index 0000000..a439b98 Binary files /dev/null and b/面试/面试/img/image-20200802123304797.png differ diff --git a/面试/面试/img/image-20200802123315514.png b/面试/面试/img/image-20200802123315514.png new file mode 100755 index 0000000..e973a53 Binary files /dev/null and b/面试/面试/img/image-20200802123315514.png differ diff --git a/面试/面试/img/image-20200802124108240.png b/面试/面试/img/image-20200802124108240.png new file mode 100755 index 0000000..e8cea30 Binary files /dev/null and b/面试/面试/img/image-20200802124108240.png differ diff --git a/面试/面试/img/image-20200802124119931.png b/面试/面试/img/image-20200802124119931.png new file mode 100755 index 0000000..1bd1dc7 Binary files /dev/null and b/面试/面试/img/image-20200802124119931.png differ diff --git a/面试/面试/img/image-20200802214415076.png b/面试/面试/img/image-20200802214415076.png new file mode 100755 index 0000000..08055d5 Binary files /dev/null and b/面试/面试/img/image-20200802214415076.png differ diff --git a/面试/面试/img/image-20200802215543670.png b/面试/面试/img/image-20200802215543670.png new file mode 100755 index 0000000..3c04e77 Binary files /dev/null and b/面试/面试/img/image-20200802215543670.png differ diff --git a/面试/面试/img/image-20200802222734870.png b/面试/面试/img/image-20200802222734870.png new file mode 100755 index 0000000..d05aab9 Binary files /dev/null and b/面试/面试/img/image-20200802222734870.png differ diff --git a/面试/面试/img/image-20200825231704058.png b/面试/面试/img/image-20200825231704058.png new file mode 100755 index 0000000..6199178 Binary files /dev/null and b/面试/面试/img/image-20200825231704058.png differ diff --git a/面试/面试/img/image-20210824164717055.png b/面试/面试/img/image-20210824164717055.png new file mode 100755 index 0000000..6ac0ebb Binary files /dev/null and b/面试/面试/img/image-20210824164717055.png differ diff --git a/面试/面试/img/image-20210824164853535.png b/面试/面试/img/image-20210824164853535.png new file mode 100755 index 0000000..3e9b4a8 Binary files /dev/null and b/面试/面试/img/image-20210824164853535.png differ diff --git a/面试/面试/img/image-20210831093204388.png b/面试/面试/img/image-20210831093204388.png new file mode 100755 index 0000000..8446d61 Binary files /dev/null and b/面试/面试/img/image-20210831093204388.png differ diff --git a/面试/面试/img/image-20220204222010008.png b/面试/面试/img/image-20220204222010008.png new file mode 100755 index 0000000..780b00d Binary files /dev/null and b/面试/面试/img/image-20220204222010008.png differ diff --git a/面试/面试/img/image-20220205092454233.png b/面试/面试/img/image-20220205092454233.png new file mode 100755 index 0000000..90339ed Binary files /dev/null and b/面试/面试/img/image-20220205092454233.png differ diff --git a/面试/面试/img/image-20220205094004602.png b/面试/面试/img/image-20220205094004602.png new file mode 100755 index 0000000..9e66190 Binary files /dev/null and b/面试/面试/img/image-20220205094004602.png differ diff --git a/面试/面试/img/image-20220205094926130.png b/面试/面试/img/image-20220205094926130.png new file mode 100755 index 0000000..e36f737 Binary files /dev/null and b/面试/面试/img/image-20220205094926130.png differ diff --git a/面试/面试/img/image-20220819132839511.png b/面试/面试/img/image-20220819132839511.png new file mode 100755 index 0000000..f5c5931 Binary files /dev/null and b/面试/面试/img/image-20220819132839511.png differ diff --git a/面试/面试/img/image-20220819133305480.png b/面试/面试/img/image-20220819133305480.png new file mode 100755 index 0000000..bf83f0b Binary files /dev/null and b/面试/面试/img/image-20220819133305480.png differ diff --git a/面试/面试/img/image-20220820104903422.png b/面试/面试/img/image-20220820104903422.png new file mode 100755 index 0000000..2f222ff Binary files /dev/null and b/面试/面试/img/image-20220820104903422.png differ diff --git a/面试/面试/img/image-20220820104950846.png b/面试/面试/img/image-20220820104950846.png new file mode 100755 index 0000000..91c72c4 Binary files /dev/null and b/面试/面试/img/image-20220820104950846.png differ diff --git a/面试/面试/img/image-20220821003816845.png b/面试/面试/img/image-20220821003816845.png new file mode 100755 index 0000000..70875d0 Binary files /dev/null and b/面试/面试/img/image-20220821003816845.png differ diff --git a/面试/面试/img/image-20220826235336379.png b/面试/面试/img/image-20220826235336379.png new file mode 100755 index 0000000..d12fa08 Binary files /dev/null and b/面试/面试/img/image-20220826235336379.png differ diff --git a/面试/面试/img/image-20220831195353786.png b/面试/面试/img/image-20220831195353786.png new file mode 100755 index 0000000..fb217db Binary files /dev/null and b/面试/面试/img/image-20220831195353786.png differ diff --git a/面试/面试/img/image-20220901110122451.png b/面试/面试/img/image-20220901110122451.png new file mode 100755 index 0000000..f97528d Binary files /dev/null and b/面试/面试/img/image-20220901110122451.png differ diff --git a/面试/面试/img/image-20220901112811374.png b/面试/面试/img/image-20220901112811374.png new file mode 100755 index 0000000..9bfe016 Binary files /dev/null and b/面试/面试/img/image-20220901112811374.png differ diff --git a/面试/面试/img/image-20220901121814290.png b/面试/面试/img/image-20220901121814290.png new file mode 100755 index 0000000..00b1375 Binary files /dev/null and b/面试/面试/img/image-20220901121814290.png differ diff --git a/面试/面试/img/image-20220901121822268.png b/面试/面试/img/image-20220901121822268.png new file mode 100755 index 0000000..00b1375 Binary files /dev/null and b/面试/面试/img/image-20220901121822268.png differ diff --git a/面试/面试/img/image-20220901123003719.png b/面试/面试/img/image-20220901123003719.png new file mode 100755 index 0000000..2d84187 Binary files /dev/null and b/面试/面试/img/image-20220901123003719.png differ diff --git a/面试/面试/img/image-20220901123045003.png b/面试/面试/img/image-20220901123045003.png new file mode 100755 index 0000000..1359964 Binary files /dev/null and b/面试/面试/img/image-20220901123045003.png differ diff --git a/面试/面试/img/image-20220901123201248.png b/面试/面试/img/image-20220901123201248.png new file mode 100755 index 0000000..d6bcba6 Binary files /dev/null and b/面试/面试/img/image-20220901123201248.png differ diff --git a/面试/面试/img/image-20220901140857623.png b/面试/面试/img/image-20220901140857623.png new file mode 100755 index 0000000..8d1690a Binary files /dev/null and b/面试/面试/img/image-20220901140857623.png differ diff --git a/面试/面试/img/image-20220901183856096.png b/面试/面试/img/image-20220901183856096.png new file mode 100755 index 0000000..2ce3cc3 Binary files /dev/null and b/面试/面试/img/image-20220901183856096.png differ diff --git a/面试/面试/img/image-20220901184120726.png b/面试/面试/img/image-20220901184120726.png new file mode 100755 index 0000000..18ece0c Binary files /dev/null and b/面试/面试/img/image-20220901184120726.png differ diff --git a/面试/面试/img/image-20220902135259000.png b/面试/面试/img/image-20220902135259000.png new file mode 100755 index 0000000..102dd20 Binary files /dev/null and b/面试/面试/img/image-20220902135259000.png differ diff --git a/面试/面试/img/image-20220902171032898.png b/面试/面试/img/image-20220902171032898.png new file mode 100755 index 0000000..2e8e146 Binary files /dev/null and b/面试/面试/img/image-20220902171032898.png differ diff --git a/面试/面试/img/image-20220902171426738.png b/面试/面试/img/image-20220902171426738.png new file mode 100755 index 0000000..5f49f8d Binary files /dev/null and b/面试/面试/img/image-20220902171426738.png differ diff --git a/面试/面试/img/image-20220902172229567.png b/面试/面试/img/image-20220902172229567.png new file mode 100755 index 0000000..cb8c53a Binary files /dev/null and b/面试/面试/img/image-20220902172229567.png differ diff --git a/面试/面试/img/image-20220902222630304.png b/面试/面试/img/image-20220902222630304.png new file mode 100755 index 0000000..7387a41 Binary files /dev/null and b/面试/面试/img/image-20220902222630304.png differ diff --git a/面试/面试/img/image-20220902222715863.png b/面试/面试/img/image-20220902222715863.png new file mode 100755 index 0000000..15ccb01 Binary files /dev/null and b/面试/面试/img/image-20220902222715863.png differ diff --git a/面试/面试/img/image-20220902223846871.png b/面试/面试/img/image-20220902223846871.png new file mode 100755 index 0000000..20c615f Binary files /dev/null and b/面试/面试/img/image-20220902223846871.png differ diff --git a/面试/面试/img/image-20220902224028438.png b/面试/面试/img/image-20220902224028438.png new file mode 100755 index 0000000..269b43a Binary files /dev/null and b/面试/面试/img/image-20220902224028438.png differ diff --git a/面试/面试/img/image-20220903144547378.png b/面试/面试/img/image-20220903144547378.png new file mode 100755 index 0000000..0f3cc66 Binary files /dev/null and b/面试/面试/img/image-20220903144547378.png differ diff --git a/面试/面试/img/image-20220903144622508.png b/面试/面试/img/image-20220903144622508.png new file mode 100755 index 0000000..88a918c Binary files /dev/null and b/面试/面试/img/image-20220903144622508.png differ diff --git a/面试/面试/img/image-20220903144652067.png b/面试/面试/img/image-20220903144652067.png new file mode 100755 index 0000000..6a8b7b8 Binary files /dev/null and b/面试/面试/img/image-20220903144652067.png differ diff --git a/面试/面试/img/image-20220903215611606.png b/面试/面试/img/image-20220903215611606.png new file mode 100755 index 0000000..52f7865 Binary files /dev/null and b/面试/面试/img/image-20220903215611606.png differ diff --git a/面试/面试/img/image-20220903222719878.png b/面试/面试/img/image-20220903222719878.png new file mode 100755 index 0000000..4027ce5 Binary files /dev/null and b/面试/面试/img/image-20220903222719878.png differ diff --git a/面试/面试/img/image-20220903233627146.png b/面试/面试/img/image-20220903233627146.png new file mode 100755 index 0000000..7666d7a Binary files /dev/null and b/面试/面试/img/image-20220903233627146.png differ diff --git a/面试/面试/img/image-20220904003215993.png b/面试/面试/img/image-20220904003215993.png new file mode 100755 index 0000000..1470481 Binary files /dev/null and b/面试/面试/img/image-20220904003215993.png differ diff --git a/面试/面试/img/image-20220904003730785.png b/面试/面试/img/image-20220904003730785.png new file mode 100755 index 0000000..cf477a5 Binary files /dev/null and b/面试/面试/img/image-20220904003730785.png differ diff --git a/面试/面试/img/image-20220904004306359.png b/面试/面试/img/image-20220904004306359.png new file mode 100755 index 0000000..b8d5426 Binary files /dev/null and b/面试/面试/img/image-20220904004306359.png differ diff --git a/面试/面试/img/image-20220904005802025.png b/面试/面试/img/image-20220904005802025.png new file mode 100755 index 0000000..c86b44d Binary files /dev/null and b/面试/面试/img/image-20220904005802025.png differ diff --git a/面试/面试/img/image-20220904010012624.png b/面试/面试/img/image-20220904010012624.png new file mode 100755 index 0000000..3c7ff41 Binary files /dev/null and b/面试/面试/img/image-20220904010012624.png differ diff --git a/面试/面试/img/image-20220904010201496.png b/面试/面试/img/image-20220904010201496.png new file mode 100755 index 0000000..ba90907 Binary files /dev/null and b/面试/面试/img/image-20220904010201496.png differ diff --git a/面试/面试/img/image-20220904010634153.png b/面试/面试/img/image-20220904010634153.png new file mode 100755 index 0000000..9b06b3c Binary files /dev/null and b/面试/面试/img/image-20220904010634153.png differ diff --git a/面试/面试/img/image-20220904104739581.png b/面试/面试/img/image-20220904104739581.png new file mode 100755 index 0000000..6bd77e5 Binary files /dev/null and b/面试/面试/img/image-20220904104739581.png differ diff --git a/面试/面试/img/image-20220904110804287.png b/面试/面试/img/image-20220904110804287.png new file mode 100755 index 0000000..a0ff534 Binary files /dev/null and b/面试/面试/img/image-20220904110804287.png differ diff --git a/面试/面试/img/image-20220904111059602.png b/面试/面试/img/image-20220904111059602.png new file mode 100755 index 0000000..ca876ae Binary files /dev/null and b/面试/面试/img/image-20220904111059602.png differ diff --git a/面试/面试/img/image-20220904114511854.png b/面试/面试/img/image-20220904114511854.png new file mode 100755 index 0000000..29f3d19 Binary files /dev/null and b/面试/面试/img/image-20220904114511854.png differ diff --git a/面试/面试/img/image-20220904115157363.png b/面试/面试/img/image-20220904115157363.png new file mode 100755 index 0000000..f3c84f4 Binary files /dev/null and b/面试/面试/img/image-20220904115157363.png differ diff --git a/面试/面试/img/image-20220904115936095.png b/面试/面试/img/image-20220904115936095.png new file mode 100755 index 0000000..d8519a7 Binary files /dev/null and b/面试/面试/img/image-20220904115936095.png differ diff --git a/面试/面试/img/image-20220904120057211.png b/面试/面试/img/image-20220904120057211.png new file mode 100755 index 0000000..1738b44 Binary files /dev/null and b/面试/面试/img/image-20220904120057211.png differ diff --git a/面试/面试/img/image-20220904120256011.png b/面试/面试/img/image-20220904120256011.png new file mode 100755 index 0000000..37e1785 Binary files /dev/null and b/面试/面试/img/image-20220904120256011.png differ diff --git a/面试/面试/img/image-20220904120356174.png b/面试/面试/img/image-20220904120356174.png new file mode 100755 index 0000000..2076a84 Binary files /dev/null and b/面试/面试/img/image-20220904120356174.png differ diff --git a/面试/面试/img/image-20220904132000870.png b/面试/面试/img/image-20220904132000870.png new file mode 100755 index 0000000..d17334b Binary files /dev/null and b/面试/面试/img/image-20220904132000870.png differ diff --git a/面试/面试/img/image-20220904132011289.png b/面试/面试/img/image-20220904132011289.png new file mode 100755 index 0000000..d17334b Binary files /dev/null and b/面试/面试/img/image-20220904132011289.png differ diff --git a/面试/面试/img/image-20220904132134095.png b/面试/面试/img/image-20220904132134095.png new file mode 100755 index 0000000..d51bcb1 Binary files /dev/null and b/面试/面试/img/image-20220904132134095.png differ diff --git a/面试/面试/img/image-20220904132346495.png b/面试/面试/img/image-20220904132346495.png new file mode 100755 index 0000000..8983e21 Binary files /dev/null and b/面试/面试/img/image-20220904132346495.png differ diff --git a/面试/面试/img/image-20220904132925812.png b/面试/面试/img/image-20220904132925812.png new file mode 100755 index 0000000..5db8b7a Binary files /dev/null and b/面试/面试/img/image-20220904132925812.png differ diff --git a/面试/面试/img/image-20220904133722905.png b/面试/面试/img/image-20220904133722905.png new file mode 100755 index 0000000..fd49229 Binary files /dev/null and b/面试/面试/img/image-20220904133722905.png differ diff --git a/面试/面试/img/image-20220904151948778.png b/面试/面试/img/image-20220904151948778.png new file mode 100755 index 0000000..8e9bfc4 Binary files /dev/null and b/面试/面试/img/image-20220904151948778.png differ diff --git a/面试/面试/img/image-20220904161818255.png b/面试/面试/img/image-20220904161818255.png new file mode 100755 index 0000000..8be46f6 Binary files /dev/null and b/面试/面试/img/image-20220904161818255.png differ diff --git a/面试/面试/img/image-20220904162117022.png b/面试/面试/img/image-20220904162117022.png new file mode 100755 index 0000000..0756811 Binary files /dev/null and b/面试/面试/img/image-20220904162117022.png differ diff --git a/面试/面试/img/image-20220904162654928.png b/面试/面试/img/image-20220904162654928.png new file mode 100755 index 0000000..6e61164 Binary files /dev/null and b/面试/面试/img/image-20220904162654928.png differ diff --git a/面试/面试/img/image-20220904162941977.png b/面试/面试/img/image-20220904162941977.png new file mode 100755 index 0000000..e90f202 Binary files /dev/null and b/面试/面试/img/image-20220904162941977.png differ diff --git a/面试/面试/img/image-20220907174130993.png b/面试/面试/img/image-20220907174130993.png new file mode 100755 index 0000000..06f881a Binary files /dev/null and b/面试/面试/img/image-20220907174130993.png differ diff --git a/面试/面试/img/image-20220907174151925.png b/面试/面试/img/image-20220907174151925.png new file mode 100755 index 0000000..32be4bc Binary files /dev/null and b/面试/面试/img/image-20220907174151925.png differ diff --git a/面试/面试/img/image-20220907174308262.png b/面试/面试/img/image-20220907174308262.png new file mode 100755 index 0000000..fb763c0 Binary files /dev/null and b/面试/面试/img/image-20220907174308262.png differ diff --git a/面试/面试/img/image-20220913110351052.png b/面试/面试/img/image-20220913110351052.png new file mode 100755 index 0000000..84598fe Binary files /dev/null and b/面试/面试/img/image-20220913110351052.png differ diff --git a/面试/面试/img/image-20220913110915155.png b/面试/面试/img/image-20220913110915155.png new file mode 100755 index 0000000..bd872e6 Binary files /dev/null and b/面试/面试/img/image-20220913110915155.png differ diff --git a/面试/面试/img/image-20220913115948157.png b/面试/面试/img/image-20220913115948157.png new file mode 100755 index 0000000..44739c3 Binary files /dev/null and b/面试/面试/img/image-20220913115948157.png differ diff --git a/面试/面试/img/image-20220913123809949.png b/面试/面试/img/image-20220913123809949.png new file mode 100755 index 0000000..6e6d7a5 Binary files /dev/null and b/面试/面试/img/image-20220913123809949.png differ diff --git a/面试/面试/img/image-20220913124542154.png b/面试/面试/img/image-20220913124542154.png new file mode 100755 index 0000000..415f352 Binary files /dev/null and b/面试/面试/img/image-20220913124542154.png differ diff --git a/面试/面试/img/image-20220913125116591.png b/面试/面试/img/image-20220913125116591.png new file mode 100755 index 0000000..34d8de9 Binary files /dev/null and b/面试/面试/img/image-20220913125116591.png differ diff --git a/面试/面试/img/image-20220913125131383.png b/面试/面试/img/image-20220913125131383.png new file mode 100755 index 0000000..3506a51 Binary files /dev/null and b/面试/面试/img/image-20220913125131383.png differ diff --git a/面试/面试/img/image-20220913125209804.png b/面试/面试/img/image-20220913125209804.png new file mode 100755 index 0000000..1d4a0f5 Binary files /dev/null and b/面试/面试/img/image-20220913125209804.png differ diff --git a/面试/面试/img/image-20221007101811885.png b/面试/面试/img/image-20221007101811885.png new file mode 100755 index 0000000..47d3b98 Binary files /dev/null and b/面试/面试/img/image-20221007101811885.png differ diff --git a/面试/面试/img/image-20221007101831281.png b/面试/面试/img/image-20221007101831281.png new file mode 100755 index 0000000..4e1fe8c Binary files /dev/null and b/面试/面试/img/image-20221007101831281.png differ diff --git a/面试/面试/img/image-20221007110404375.png b/面试/面试/img/image-20221007110404375.png new file mode 100755 index 0000000..5137bf6 Binary files /dev/null and b/面试/面试/img/image-20221007110404375.png differ diff --git a/面试/面试/img/image-20221007151910345.png b/面试/面试/img/image-20221007151910345.png new file mode 100755 index 0000000..e75ea00 Binary files /dev/null and b/面试/面试/img/image-20221007151910345.png differ diff --git a/面试/面试/img/image-20221007151919542.png b/面试/面试/img/image-20221007151919542.png new file mode 100755 index 0000000..8bcf50e Binary files /dev/null and b/面试/面试/img/image-20221007151919542.png differ diff --git a/面试/面试/img/image-20221026105350827.png b/面试/面试/img/image-20221026105350827.png new file mode 100755 index 0000000..8c2dc5e Binary files /dev/null and b/面试/面试/img/image-20221026105350827.png differ diff --git a/面试/面试/img/image-20221026105442158.png b/面试/面试/img/image-20221026105442158.png new file mode 100755 index 0000000..22c558f Binary files /dev/null and b/面试/面试/img/image-20221026105442158.png differ diff --git a/面试/面试/img/image-20221026105607248.png b/面试/面试/img/image-20221026105607248.png new file mode 100755 index 0000000..a6a298e Binary files /dev/null and b/面试/面试/img/image-20221026105607248.png differ diff --git a/面试/面试/img/image-20221026105641323.png b/面试/面试/img/image-20221026105641323.png new file mode 100755 index 0000000..b97c5b2 Binary files /dev/null and b/面试/面试/img/image-20221026105641323.png differ diff --git a/面试/面试/img/image-20221026110310947.png b/面试/面试/img/image-20221026110310947.png new file mode 100755 index 0000000..00f59bb Binary files /dev/null and b/面试/面试/img/image-20221026110310947.png differ diff --git a/面试/面试/img/image-20221026110452264.png b/面试/面试/img/image-20221026110452264.png new file mode 100755 index 0000000..fb9a4f5 Binary files /dev/null and b/面试/面试/img/image-20221026110452264.png differ diff --git a/面试/面试/img/image-20221026111154135.png b/面试/面试/img/image-20221026111154135.png new file mode 100755 index 0000000..ca2cf23 Binary files /dev/null and b/面试/面试/img/image-20221026111154135.png differ diff --git a/面试/面试/img/image-20221026111227136.png b/面试/面试/img/image-20221026111227136.png new file mode 100755 index 0000000..e895b94 Binary files /dev/null and b/面试/面试/img/image-20221026111227136.png differ diff --git a/面试/面试/img/image-20221026111246089.png b/面试/面试/img/image-20221026111246089.png new file mode 100755 index 0000000..f07df89 Binary files /dev/null and b/面试/面试/img/image-20221026111246089.png differ diff --git a/面试/面试/img/image-20221026111313057.png b/面试/面试/img/image-20221026111313057.png new file mode 100755 index 0000000..6247a84 Binary files /dev/null and b/面试/面试/img/image-20221026111313057.png differ diff --git a/面试/面试/img/image-20221026111429301.png b/面试/面试/img/image-20221026111429301.png new file mode 100755 index 0000000..ee2767b Binary files /dev/null and b/面试/面试/img/image-20221026111429301.png differ diff --git a/面试/面试/img/image-20221026111506307.png b/面试/面试/img/image-20221026111506307.png new file mode 100755 index 0000000..e50909d Binary files /dev/null and b/面试/面试/img/image-20221026111506307.png differ diff --git a/面试/面试/img/image-20230427162524322.png b/面试/面试/img/image-20230427162524322.png new file mode 100755 index 0000000..1ef0230 Binary files /dev/null and b/面试/面试/img/image-20230427162524322.png differ diff --git a/面试/面试/img/image-20230427173120668.png b/面试/面试/img/image-20230427173120668.png new file mode 100755 index 0000000..2648452 Binary files /dev/null and b/面试/面试/img/image-20230427173120668.png differ diff --git a/面试/面试/img/image-20230427173742389.png b/面试/面试/img/image-20230427173742389.png new file mode 100755 index 0000000..f79164d Binary files /dev/null and b/面试/面试/img/image-20230427173742389.png differ diff --git a/面试/面试/img/image-20230427173922705.png b/面试/面试/img/image-20230427173922705.png new file mode 100755 index 0000000..11cc3d2 Binary files /dev/null and b/面试/面试/img/image-20230427173922705.png differ diff --git a/面试/面试/img/image-20230427173937663.png b/面试/面试/img/image-20230427173937663.png new file mode 100755 index 0000000..3a71472 Binary files /dev/null and b/面试/面试/img/image-20230427173937663.png differ diff --git a/面试/面试/img/image-20230427174832858.png b/面试/面试/img/image-20230427174832858.png new file mode 100755 index 0000000..641dc26 Binary files /dev/null and b/面试/面试/img/image-20230427174832858.png differ diff --git a/面试/面试/img/image-20230427175545402.png b/面试/面试/img/image-20230427175545402.png new file mode 100755 index 0000000..f729b9f Binary files /dev/null and b/面试/面试/img/image-20230427175545402.png differ diff --git a/面试/面试/img/image-20230427175633253.png b/面试/面试/img/image-20230427175633253.png new file mode 100755 index 0000000..0e6f17e Binary files /dev/null and b/面试/面试/img/image-20230427175633253.png differ diff --git a/面试/面试/img/image-20230427175849493.png b/面试/面试/img/image-20230427175849493.png new file mode 100755 index 0000000..009deaa Binary files /dev/null and b/面试/面试/img/image-20230427175849493.png differ diff --git a/面试/面试/img/image-20230427180056509.png b/面试/面试/img/image-20230427180056509.png new file mode 100755 index 0000000..1afe1f1 Binary files /dev/null and b/面试/面试/img/image-20230427180056509.png differ diff --git a/面试/面试/img/image-20230427192118259.png b/面试/面试/img/image-20230427192118259.png new file mode 100755 index 0000000..487face Binary files /dev/null and b/面试/面试/img/image-20230427192118259.png differ diff --git a/面试/面试/img/image-20230427192154014.png b/面试/面试/img/image-20230427192154014.png new file mode 100755 index 0000000..bab2484 Binary files /dev/null and b/面试/面试/img/image-20230427192154014.png differ diff --git a/面试/面试/img/image-20230427192200918.png b/面试/面试/img/image-20230427192200918.png new file mode 100755 index 0000000..bab2484 Binary files /dev/null and b/面试/面试/img/image-20230427192200918.png differ diff --git a/面试/面试/img/image-20230427192620292.png b/面试/面试/img/image-20230427192620292.png new file mode 100755 index 0000000..cee546b Binary files /dev/null and b/面试/面试/img/image-20230427192620292.png differ diff --git a/面试/面试/img/image-20230427192644244.png b/面试/面试/img/image-20230427192644244.png new file mode 100755 index 0000000..9081c44 Binary files /dev/null and b/面试/面试/img/image-20230427192644244.png differ diff --git a/面试/面试/img/image-20230428185505677.png b/面试/面试/img/image-20230428185505677.png new file mode 100755 index 0000000..1f5668f Binary files /dev/null and b/面试/面试/img/image-20230428185505677.png differ diff --git a/面试/面试/img/image-20230428185600918.png b/面试/面试/img/image-20230428185600918.png new file mode 100755 index 0000000..0177fe6 Binary files /dev/null and b/面试/面试/img/image-20230428185600918.png differ diff --git a/面试/面试/img/image-20230428185657791.png b/面试/面试/img/image-20230428185657791.png new file mode 100755 index 0000000..11caf9d Binary files /dev/null and b/面试/面试/img/image-20230428185657791.png differ diff --git a/面试/面试/img/image-20230428185922776.png b/面试/面试/img/image-20230428185922776.png new file mode 100755 index 0000000..cf607bf Binary files /dev/null and b/面试/面试/img/image-20230428185922776.png differ diff --git a/面试/面试/img/image-20230428185945929.png b/面试/面试/img/image-20230428185945929.png new file mode 100755 index 0000000..45b009c Binary files /dev/null and b/面试/面试/img/image-20230428185945929.png differ diff --git a/面试/面试/img/image-20230428190130901.png b/面试/面试/img/image-20230428190130901.png new file mode 100755 index 0000000..d199a2c Binary files /dev/null and b/面试/面试/img/image-20230428190130901.png differ diff --git a/面试/面试/img/image-20230428190210915.png b/面试/面试/img/image-20230428190210915.png new file mode 100755 index 0000000..a1826a4 Binary files /dev/null and b/面试/面试/img/image-20230428190210915.png differ diff --git a/面试/面试/img/image-20230428190324752.png b/面试/面试/img/image-20230428190324752.png new file mode 100755 index 0000000..2760fd3 Binary files /dev/null and b/面试/面试/img/image-20230428190324752.png differ diff --git a/面试/面试/img/image-20230428190353286.png b/面试/面试/img/image-20230428190353286.png new file mode 100755 index 0000000..289e4c5 Binary files /dev/null and b/面试/面试/img/image-20230428190353286.png differ diff --git a/面试/面试/img/image-20230428190450517.png b/面试/面试/img/image-20230428190450517.png new file mode 100755 index 0000000..c669ad6 Binary files /dev/null and b/面试/面试/img/image-20230428190450517.png differ diff --git a/面试/面试/img/image-20230428194641694.png b/面试/面试/img/image-20230428194641694.png new file mode 100755 index 0000000..adc68f0 Binary files /dev/null and b/面试/面试/img/image-20230428194641694.png differ diff --git a/面试/面试/img/image-20230428194715016.png b/面试/面试/img/image-20230428194715016.png new file mode 100755 index 0000000..9c0bbe2 Binary files /dev/null and b/面试/面试/img/image-20230428194715016.png differ diff --git a/面试/面试/img/image-20230428194831426.png b/面试/面试/img/image-20230428194831426.png new file mode 100755 index 0000000..56a79d9 Binary files /dev/null and b/面试/面试/img/image-20230428194831426.png differ diff --git a/面试/面试/img/image-20230428194904383.png b/面试/面试/img/image-20230428194904383.png new file mode 100755 index 0000000..674084d Binary files /dev/null and b/面试/面试/img/image-20230428194904383.png differ diff --git a/面试/面试/img/image-20230428194931132.png b/面试/面试/img/image-20230428194931132.png new file mode 100755 index 0000000..61fdc20 Binary files /dev/null and b/面试/面试/img/image-20230428194931132.png differ diff --git a/面试/面试/img/image-20230428195206422.png b/面试/面试/img/image-20230428195206422.png new file mode 100755 index 0000000..1ad61fe Binary files /dev/null and b/面试/面试/img/image-20230428195206422.png differ diff --git a/面试/面试/img/image-20230428195341917.png b/面试/面试/img/image-20230428195341917.png new file mode 100755 index 0000000..b939eb8 Binary files /dev/null and b/面试/面试/img/image-20230428195341917.png differ diff --git a/面试/面试/img/image-20230428195449799.png b/面试/面试/img/image-20230428195449799.png new file mode 100755 index 0000000..ee45cf9 Binary files /dev/null and b/面试/面试/img/image-20230428195449799.png differ diff --git a/面试/面试/img/image-20230428195832724.png b/面试/面试/img/image-20230428195832724.png new file mode 100755 index 0000000..bf808f3 Binary files /dev/null and b/面试/面试/img/image-20230428195832724.png differ diff --git a/面试/面试/img/image-20230428200919454.png b/面试/面试/img/image-20230428200919454.png new file mode 100755 index 0000000..dbfc018 Binary files /dev/null and b/面试/面试/img/image-20230428200919454.png differ diff --git a/面试/面试/img/image-20230428201000814.png b/面试/面试/img/image-20230428201000814.png new file mode 100755 index 0000000..ca0ecaa Binary files /dev/null and b/面试/面试/img/image-20230428201000814.png differ diff --git a/面试/面试/img/image-20230428201321607.png b/面试/面试/img/image-20230428201321607.png new file mode 100755 index 0000000..8d2cf45 Binary files /dev/null and b/面试/面试/img/image-20230428201321607.png differ diff --git a/面试/面试/img/image-20230428203219225.png b/面试/面试/img/image-20230428203219225.png new file mode 100755 index 0000000..44a0411 Binary files /dev/null and b/面试/面试/img/image-20230428203219225.png differ diff --git a/面试/面试/img/image-20230428203437910.png b/面试/面试/img/image-20230428203437910.png new file mode 100755 index 0000000..63bad96 Binary files /dev/null and b/面试/面试/img/image-20230428203437910.png differ diff --git a/面试/面试/img/image-20230428203711269.png b/面试/面试/img/image-20230428203711269.png new file mode 100755 index 0000000..ccc79bc Binary files /dev/null and b/面试/面试/img/image-20230428203711269.png differ diff --git a/面试/面试/img/image-20230428203858903.png b/面试/面试/img/image-20230428203858903.png new file mode 100755 index 0000000..8213df9 Binary files /dev/null and b/面试/面试/img/image-20230428203858903.png differ diff --git a/面试/面试/img/image-20230428203924816.png b/面试/面试/img/image-20230428203924816.png new file mode 100755 index 0000000..c96f4d1 Binary files /dev/null and b/面试/面试/img/image-20230428203924816.png differ diff --git a/面试/面试/img/image-20230428204902016.png b/面试/面试/img/image-20230428204902016.png new file mode 100755 index 0000000..79547bf Binary files /dev/null and b/面试/面试/img/image-20230428204902016.png differ diff --git a/面试/面试/img/image-20230428210404117.png b/面试/面试/img/image-20230428210404117.png new file mode 100755 index 0000000..1d8b785 Binary files /dev/null and b/面试/面试/img/image-20230428210404117.png differ diff --git a/面试/面试/img/image-20230428210424304.png b/面试/面试/img/image-20230428210424304.png new file mode 100755 index 0000000..807bb69 Binary files /dev/null and b/面试/面试/img/image-20230428210424304.png differ diff --git a/面试/面试/img/image-20230428210450744.png b/面试/面试/img/image-20230428210450744.png new file mode 100755 index 0000000..ed98d8d Binary files /dev/null and b/面试/面试/img/image-20230428210450744.png differ diff --git a/面试/面试/img/image-20230428210624847.png b/面试/面试/img/image-20230428210624847.png new file mode 100755 index 0000000..fe035a4 Binary files /dev/null and b/面试/面试/img/image-20230428210624847.png differ diff --git a/面试/面试/img/image-20230428210844694.png b/面试/面试/img/image-20230428210844694.png new file mode 100755 index 0000000..f99d900 Binary files /dev/null and b/面试/面试/img/image-20230428210844694.png differ diff --git a/面试/面试/img/image-20230428211031968.png b/面试/面试/img/image-20230428211031968.png new file mode 100755 index 0000000..c355126 Binary files /dev/null and b/面试/面试/img/image-20230428211031968.png differ diff --git a/面试/面试/img/image-20230428211304013.png b/面试/面试/img/image-20230428211304013.png new file mode 100755 index 0000000..6d0598e Binary files /dev/null and b/面试/面试/img/image-20230428211304013.png differ diff --git a/面试/面试/img/image-20230428212501408.png b/面试/面试/img/image-20230428212501408.png new file mode 100755 index 0000000..a429e20 Binary files /dev/null and b/面试/面试/img/image-20230428212501408.png differ diff --git a/面试/面试/img/image-20230428212553393.png b/面试/面试/img/image-20230428212553393.png new file mode 100755 index 0000000..a429e20 Binary files /dev/null and b/面试/面试/img/image-20230428212553393.png differ diff --git a/面试/面试/img/image-20230428212601977.png b/面试/面试/img/image-20230428212601977.png new file mode 100755 index 0000000..fa1aaff Binary files /dev/null and b/面试/面试/img/image-20230428212601977.png differ diff --git a/面试/面试/img/image-20230428212729580.png b/面试/面试/img/image-20230428212729580.png new file mode 100755 index 0000000..553de0b Binary files /dev/null and b/面试/面试/img/image-20230428212729580.png differ diff --git a/面试/面试/img/image-20230428213115071.png b/面试/面试/img/image-20230428213115071.png new file mode 100755 index 0000000..b4b5c55 Binary files /dev/null and b/面试/面试/img/image-20230428213115071.png differ diff --git a/面试/面试/img/image-20230428213513669.png b/面试/面试/img/image-20230428213513669.png new file mode 100755 index 0000000..38ba861 Binary files /dev/null and b/面试/面试/img/image-20230428213513669.png differ diff --git a/面试/面试/img/image-20230428213533483.png b/面试/面试/img/image-20230428213533483.png new file mode 100755 index 0000000..61bac38 Binary files /dev/null and b/面试/面试/img/image-20230428213533483.png differ diff --git a/面试/面试/img/image-20230428214732877.png b/面试/面试/img/image-20230428214732877.png new file mode 100755 index 0000000..0e3c72f Binary files /dev/null and b/面试/面试/img/image-20230428214732877.png differ diff --git a/面试/面试/img/image-20230428214806072.png b/面试/面试/img/image-20230428214806072.png new file mode 100755 index 0000000..eb988c9 Binary files /dev/null and b/面试/面试/img/image-20230428214806072.png differ diff --git a/面试/面试/img/image-20230428214908652.png b/面试/面试/img/image-20230428214908652.png new file mode 100755 index 0000000..a6dd35c Binary files /dev/null and b/面试/面试/img/image-20230428214908652.png differ diff --git a/面试/面试/img/image-20230428214937231.png b/面试/面试/img/image-20230428214937231.png new file mode 100755 index 0000000..14166f4 Binary files /dev/null and b/面试/面试/img/image-20230428214937231.png differ diff --git a/面试/面试/img/image-20230503203246348.png b/面试/面试/img/image-20230503203246348.png new file mode 100755 index 0000000..4f8d6db Binary files /dev/null and b/面试/面试/img/image-20230503203246348.png differ diff --git a/面试/面试/img/image-20230503203330700.png b/面试/面试/img/image-20230503203330700.png new file mode 100755 index 0000000..8031206 Binary files /dev/null and b/面试/面试/img/image-20230503203330700.png differ diff --git a/面试/面试/img/image-20230503203629212.png b/面试/面试/img/image-20230503203629212.png new file mode 100755 index 0000000..dd9d973 Binary files /dev/null and b/面试/面试/img/image-20230503203629212.png differ diff --git a/面试/面试/img/image-20230504165342501.png b/面试/面试/img/image-20230504165342501.png new file mode 100755 index 0000000..b615d26 Binary files /dev/null and b/面试/面试/img/image-20230504165342501.png differ diff --git a/面试/面试/img/image-20230504165833809.png b/面试/面试/img/image-20230504165833809.png new file mode 100755 index 0000000..cf6cf19 Binary files /dev/null and b/面试/面试/img/image-20230504165833809.png differ diff --git a/面试/面试/img/image-20230504172253826.png b/面试/面试/img/image-20230504172253826.png new file mode 100755 index 0000000..e676084 Binary files /dev/null and b/面试/面试/img/image-20230504172253826.png differ diff --git a/面试/面试/img/image-20230504172414210.png b/面试/面试/img/image-20230504172414210.png new file mode 100755 index 0000000..7f82e73 Binary files /dev/null and b/面试/面试/img/image-20230504172414210.png differ diff --git a/面试/面试/img/image-20230504172541922.png b/面试/面试/img/image-20230504172541922.png new file mode 100755 index 0000000..c99a09f Binary files /dev/null and b/面试/面试/img/image-20230504172541922.png differ diff --git a/面试/面试/img/image-20230504172957271.png b/面试/面试/img/image-20230504172957271.png new file mode 100755 index 0000000..b232400 Binary files /dev/null and b/面试/面试/img/image-20230504172957271.png differ diff --git a/面试/面试/img/image-20230504173414009.png b/面试/面试/img/image-20230504173414009.png new file mode 100755 index 0000000..6a9b70f Binary files /dev/null and b/面试/面试/img/image-20230504173414009.png differ diff --git a/面试/面试/img/image-20230504173520412.png b/面试/面试/img/image-20230504173520412.png new file mode 100755 index 0000000..dd1b947 Binary files /dev/null and b/面试/面试/img/image-20230504173520412.png differ diff --git a/面试/面试/img/image-20230504173611219.png b/面试/面试/img/image-20230504173611219.png new file mode 100755 index 0000000..b6460ca Binary files /dev/null and b/面试/面试/img/image-20230504173611219.png differ diff --git a/面试/面试/img/image-20230504173711041.png b/面试/面试/img/image-20230504173711041.png new file mode 100755 index 0000000..3769750 Binary files /dev/null and b/面试/面试/img/image-20230504173711041.png differ diff --git a/面试/面试/img/image-20230504173922343.png b/面试/面试/img/image-20230504173922343.png new file mode 100755 index 0000000..fc9cf55 Binary files /dev/null and b/面试/面试/img/image-20230504173922343.png differ diff --git a/面试/面试/img/image-20230504173955680.png b/面试/面试/img/image-20230504173955680.png new file mode 100755 index 0000000..1251db9 Binary files /dev/null and b/面试/面试/img/image-20230504173955680.png differ diff --git a/面试/面试/img/image-20230504174045458.png b/面试/面试/img/image-20230504174045458.png new file mode 100755 index 0000000..7b2d330 Binary files /dev/null and b/面试/面试/img/image-20230504174045458.png differ diff --git a/面试/面试/img/image-20230504174505031.png b/面试/面试/img/image-20230504174505031.png new file mode 100755 index 0000000..f463251 Binary files /dev/null and b/面试/面试/img/image-20230504174505031.png differ diff --git a/面试/面试/img/image-20230504174525256.png b/面试/面试/img/image-20230504174525256.png new file mode 100755 index 0000000..2460fa2 Binary files /dev/null and b/面试/面试/img/image-20230504174525256.png differ diff --git a/面试/面试/img/image-20230504174736226.png b/面试/面试/img/image-20230504174736226.png new file mode 100755 index 0000000..766e8b8 Binary files /dev/null and b/面试/面试/img/image-20230504174736226.png differ diff --git a/面试/面试/img/image-20230504181638237.png b/面试/面试/img/image-20230504181638237.png new file mode 100755 index 0000000..36989a2 Binary files /dev/null and b/面试/面试/img/image-20230504181638237.png differ diff --git a/面试/面试/img/image-20230504181827330.png b/面试/面试/img/image-20230504181827330.png new file mode 100755 index 0000000..45a37a9 Binary files /dev/null and b/面试/面试/img/image-20230504181827330.png differ diff --git a/面试/面试/img/image-20230504181947319.png b/面试/面试/img/image-20230504181947319.png new file mode 100755 index 0000000..1f708b4 Binary files /dev/null and b/面试/面试/img/image-20230504181947319.png differ diff --git a/面试/面试/img/image-20230504182129820.png b/面试/面试/img/image-20230504182129820.png new file mode 100755 index 0000000..d3dddf6 Binary files /dev/null and b/面试/面试/img/image-20230504182129820.png differ diff --git a/面试/面试/img/image-20230504182447552.png b/面试/面试/img/image-20230504182447552.png new file mode 100755 index 0000000..73f1a68 Binary files /dev/null and b/面试/面试/img/image-20230504182447552.png differ diff --git a/面试/面试/img/image-20230504182737931.png b/面试/面试/img/image-20230504182737931.png new file mode 100755 index 0000000..ddc6dbe Binary files /dev/null and b/面试/面试/img/image-20230504182737931.png differ diff --git a/面试/面试/img/image-20230504182838426.png b/面试/面试/img/image-20230504182838426.png new file mode 100755 index 0000000..a9f2d33 Binary files /dev/null and b/面试/面试/img/image-20230504182838426.png differ diff --git a/面试/面试/img/image-20230504182958703.png b/面试/面试/img/image-20230504182958703.png new file mode 100755 index 0000000..0fb0853 Binary files /dev/null and b/面试/面试/img/image-20230504182958703.png differ diff --git a/面试/面试/img/image-20230505082441116.png b/面试/面试/img/image-20230505082441116.png new file mode 100755 index 0000000..77fee9f Binary files /dev/null and b/面试/面试/img/image-20230505082441116.png differ diff --git a/面试/面试/img/image-20230505082835588.png b/面试/面试/img/image-20230505082835588.png new file mode 100755 index 0000000..647355e Binary files /dev/null and b/面试/面试/img/image-20230505082835588.png differ diff --git a/面试/面试/img/image-20230505082923729.png b/面试/面试/img/image-20230505082923729.png new file mode 100755 index 0000000..401f5ec Binary files /dev/null and b/面试/面试/img/image-20230505082923729.png differ diff --git a/面试/面试/img/image-20230505083124159.png b/面试/面试/img/image-20230505083124159.png new file mode 100755 index 0000000..b800386 Binary files /dev/null and b/面试/面试/img/image-20230505083124159.png differ diff --git a/面试/面试/img/image-20230505083159269.png b/面试/面试/img/image-20230505083159269.png new file mode 100755 index 0000000..6ff06f1 Binary files /dev/null and b/面试/面试/img/image-20230505083159269.png differ diff --git a/面试/面试/img/image-20230505083217904.png b/面试/面试/img/image-20230505083217904.png new file mode 100755 index 0000000..ba6f781 Binary files /dev/null and b/面试/面试/img/image-20230505083217904.png differ diff --git a/面试/面试/img/image-20230505083840046.png b/面试/面试/img/image-20230505083840046.png new file mode 100755 index 0000000..dae6bce Binary files /dev/null and b/面试/面试/img/image-20230505083840046.png differ diff --git a/面试/面试/img/image-20230505084451193.png b/面试/面试/img/image-20230505084451193.png new file mode 100755 index 0000000..cf302ca Binary files /dev/null and b/面试/面试/img/image-20230505084451193.png differ diff --git a/面试/面试/img/image-20230505091736569.png b/面试/面试/img/image-20230505091736569.png new file mode 100755 index 0000000..ab81f7b Binary files /dev/null and b/面试/面试/img/image-20230505091736569.png differ diff --git a/面试/面试/img/image-20230505091827720.png b/面试/面试/img/image-20230505091827720.png new file mode 100755 index 0000000..8f21a66 Binary files /dev/null and b/面试/面试/img/image-20230505091827720.png differ diff --git a/面试/面试/img/image-20230505091833629.png b/面试/面试/img/image-20230505091833629.png new file mode 100755 index 0000000..0d9115c Binary files /dev/null and b/面试/面试/img/image-20230505091833629.png differ diff --git a/面试/面试/img/image-20230505092151244.png b/面试/面试/img/image-20230505092151244.png new file mode 100755 index 0000000..ef3dc5e Binary files /dev/null and b/面试/面试/img/image-20230505092151244.png differ diff --git a/面试/面试/img/image-20230505092340431.png b/面试/面试/img/image-20230505092340431.png new file mode 100755 index 0000000..35c9a1e Binary files /dev/null and b/面试/面试/img/image-20230505092340431.png differ diff --git a/面试/面试/img/image-20230505092654811.png b/面试/面试/img/image-20230505092654811.png new file mode 100755 index 0000000..b998fe1 Binary files /dev/null and b/面试/面试/img/image-20230505092654811.png differ diff --git a/面试/面试/img/image-20230505093055382.png b/面试/面试/img/image-20230505093055382.png new file mode 100755 index 0000000..118b36a Binary files /dev/null and b/面试/面试/img/image-20230505093055382.png differ diff --git a/面试/面试/img/image-20230505093507265.png b/面试/面试/img/image-20230505093507265.png new file mode 100755 index 0000000..0bc0bc7 Binary files /dev/null and b/面试/面试/img/image-20230505093507265.png differ diff --git a/面试/面试/img/image-20230505205200628.png b/面试/面试/img/image-20230505205200628.png new file mode 100755 index 0000000..29d1b63 Binary files /dev/null and b/面试/面试/img/image-20230505205200628.png differ diff --git a/面试/面试/img/image-20230505210853493.png b/面试/面试/img/image-20230505210853493.png new file mode 100755 index 0000000..3aaccc7 Binary files /dev/null and b/面试/面试/img/image-20230505210853493.png differ diff --git a/面试/面试/img/image-20230505211002252.png b/面试/面试/img/image-20230505211002252.png new file mode 100755 index 0000000..c4190bf Binary files /dev/null and b/面试/面试/img/image-20230505211002252.png differ diff --git a/面试/面试/img/image-20230505211209336.png b/面试/面试/img/image-20230505211209336.png new file mode 100755 index 0000000..e1b3ec5 Binary files /dev/null and b/面试/面试/img/image-20230505211209336.png differ diff --git a/面试/面试/img/image-20230505220514872.png b/面试/面试/img/image-20230505220514872.png new file mode 100755 index 0000000..261cd7f Binary files /dev/null and b/面试/面试/img/image-20230505220514872.png differ diff --git a/面试/面试/img/image-20230505220701835.png b/面试/面试/img/image-20230505220701835.png new file mode 100755 index 0000000..95bb4ee Binary files /dev/null and b/面试/面试/img/image-20230505220701835.png differ diff --git a/面试/面试/img/image-20230505221424359.png b/面试/面试/img/image-20230505221424359.png new file mode 100755 index 0000000..96ea475 Binary files /dev/null and b/面试/面试/img/image-20230505221424359.png differ diff --git a/面试/面试/img/image-20230505221837189.png b/面试/面试/img/image-20230505221837189.png new file mode 100755 index 0000000..7a861a8 Binary files /dev/null and b/面试/面试/img/image-20230505221837189.png differ diff --git a/面试/面试/img/image-20230505221959259.png b/面试/面试/img/image-20230505221959259.png new file mode 100755 index 0000000..cac64ef Binary files /dev/null and b/面试/面试/img/image-20230505221959259.png differ diff --git a/面试/面试/img/image-20230505222050294.png b/面试/面试/img/image-20230505222050294.png new file mode 100755 index 0000000..0ced56a Binary files /dev/null and b/面试/面试/img/image-20230505222050294.png differ diff --git a/面试/面试/img/image-20230505222126391.png b/面试/面试/img/image-20230505222126391.png new file mode 100755 index 0000000..0393581 Binary files /dev/null and b/面试/面试/img/image-20230505222126391.png differ diff --git a/面试/面试/img/image-20230505222203615.png b/面试/面试/img/image-20230505222203615.png new file mode 100755 index 0000000..004a5be Binary files /dev/null and b/面试/面试/img/image-20230505222203615.png differ diff --git a/面试/面试/img/image-20230505222319526.png b/面试/面试/img/image-20230505222319526.png new file mode 100755 index 0000000..4264668 Binary files /dev/null and b/面试/面试/img/image-20230505222319526.png differ diff --git a/面试/面试/img/image-20230505223014946.png b/面试/面试/img/image-20230505223014946.png new file mode 100755 index 0000000..7969cc6 Binary files /dev/null and b/面试/面试/img/image-20230505223014946.png differ diff --git a/面试/面试/img/image-20230505223219951.png b/面试/面试/img/image-20230505223219951.png new file mode 100755 index 0000000..810cbcd Binary files /dev/null and b/面试/面试/img/image-20230505223219951.png differ diff --git a/面试/面试/img/image-20230505223246059.png b/面试/面试/img/image-20230505223246059.png new file mode 100755 index 0000000..f9e36d7 Binary files /dev/null and b/面试/面试/img/image-20230505223246059.png differ diff --git a/面试/面试/img/image-20230505223442924.png b/面试/面试/img/image-20230505223442924.png new file mode 100755 index 0000000..060ce07 Binary files /dev/null and b/面试/面试/img/image-20230505223442924.png differ diff --git a/面试/面试/img/image-20230505223533130.png b/面试/面试/img/image-20230505223533130.png new file mode 100755 index 0000000..c4250da Binary files /dev/null and b/面试/面试/img/image-20230505223533130.png differ diff --git a/面试/面试/img/image-20230505223536657.png b/面试/面试/img/image-20230505223536657.png new file mode 100755 index 0000000..c4250da Binary files /dev/null and b/面试/面试/img/image-20230505223536657.png differ diff --git a/面试/面试/img/image-20230505223640038.png b/面试/面试/img/image-20230505223640038.png new file mode 100755 index 0000000..556a830 Binary files /dev/null and b/面试/面试/img/image-20230505223640038.png differ diff --git a/面试/面试/img/image-20230505224057228.png b/面试/面试/img/image-20230505224057228.png new file mode 100755 index 0000000..c4c471a Binary files /dev/null and b/面试/面试/img/image-20230505224057228.png differ diff --git a/面试/面试/img/image-20230505224341410.png b/面试/面试/img/image-20230505224341410.png new file mode 100755 index 0000000..d9c0097 Binary files /dev/null and b/面试/面试/img/image-20230505224341410.png differ diff --git a/面试/面试/img/image-20230505224626253.png b/面试/面试/img/image-20230505224626253.png new file mode 100755 index 0000000..9523c1e Binary files /dev/null and b/面试/面试/img/image-20230505224626253.png differ diff --git a/面试/面试/img/image-20230505224715087.png b/面试/面试/img/image-20230505224715087.png new file mode 100755 index 0000000..393c2ae Binary files /dev/null and b/面试/面试/img/image-20230505224715087.png differ diff --git a/面试/面试/img/image-20230505224755797.png b/面试/面试/img/image-20230505224755797.png new file mode 100755 index 0000000..aab795d Binary files /dev/null and b/面试/面试/img/image-20230505224755797.png differ diff --git a/面试/面试/img/image-20230505224812015.png b/面试/面试/img/image-20230505224812015.png new file mode 100755 index 0000000..4736e1e Binary files /dev/null and b/面试/面试/img/image-20230505224812015.png differ diff --git a/面试/面试/img/image-20230505224857538.png b/面试/面试/img/image-20230505224857538.png new file mode 100755 index 0000000..a002a0e Binary files /dev/null and b/面试/面试/img/image-20230505224857538.png differ diff --git a/面试/面试/img/image-20230506094254360.png b/面试/面试/img/image-20230506094254360.png new file mode 100755 index 0000000..7c53d5f Binary files /dev/null and b/面试/面试/img/image-20230506094254360.png differ diff --git a/面试/面试/img/image-20230506094411247.png b/面试/面试/img/image-20230506094411247.png new file mode 100755 index 0000000..011c80e Binary files /dev/null and b/面试/面试/img/image-20230506094411247.png differ diff --git a/面试/面试/img/image-20230506094602329.png b/面试/面试/img/image-20230506094602329.png new file mode 100755 index 0000000..f8e1942 Binary files /dev/null and b/面试/面试/img/image-20230506094602329.png differ diff --git a/面试/面试/img/image-20230506094735014.png b/面试/面试/img/image-20230506094735014.png new file mode 100755 index 0000000..5b0b15c Binary files /dev/null and b/面试/面试/img/image-20230506094735014.png differ diff --git a/面试/面试/img/image-20230506094803545.png b/面试/面试/img/image-20230506094803545.png new file mode 100755 index 0000000..ac2a776 Binary files /dev/null and b/面试/面试/img/image-20230506094803545.png differ diff --git a/面试/面试/img/image-20230506094938843.png b/面试/面试/img/image-20230506094938843.png new file mode 100755 index 0000000..9ee73e5 Binary files /dev/null and b/面试/面试/img/image-20230506094938843.png differ diff --git a/面试/面试/img/image-20230506095140595.png b/面试/面试/img/image-20230506095140595.png new file mode 100755 index 0000000..da74272 Binary files /dev/null and b/面试/面试/img/image-20230506095140595.png differ diff --git a/面试/面试/img/image-20230506095306061.png b/面试/面试/img/image-20230506095306061.png new file mode 100755 index 0000000..b610e7b Binary files /dev/null and b/面试/面试/img/image-20230506095306061.png differ diff --git a/面试/面试/img/image-20230506095401637.png b/面试/面试/img/image-20230506095401637.png new file mode 100755 index 0000000..c339d0d Binary files /dev/null and b/面试/面试/img/image-20230506095401637.png differ diff --git a/面试/面试/img/image-20230506095504213.png b/面试/面试/img/image-20230506095504213.png new file mode 100755 index 0000000..80efb01 Binary files /dev/null and b/面试/面试/img/image-20230506095504213.png differ diff --git a/面试/面试/img/image-20230506095634842.png b/面试/面试/img/image-20230506095634842.png new file mode 100755 index 0000000..00c3478 Binary files /dev/null and b/面试/面试/img/image-20230506095634842.png differ diff --git a/面试/面试/img/image-20230506100142724.png b/面试/面试/img/image-20230506100142724.png new file mode 100755 index 0000000..641ce91 Binary files /dev/null and b/面试/面试/img/image-20230506100142724.png differ diff --git a/面试/面试/img/image-20230506100501905.png b/面试/面试/img/image-20230506100501905.png new file mode 100755 index 0000000..f7baefc Binary files /dev/null and b/面试/面试/img/image-20230506100501905.png differ diff --git a/面试/面试/img/image-20230506100548455.png b/面试/面试/img/image-20230506100548455.png new file mode 100755 index 0000000..9554aa6 Binary files /dev/null and b/面试/面试/img/image-20230506100548455.png differ diff --git a/面试/面试/img/image-20230506100621146.png b/面试/面试/img/image-20230506100621146.png new file mode 100755 index 0000000..fad6754 Binary files /dev/null and b/面试/面试/img/image-20230506100621146.png differ diff --git a/面试/面试/img/image-20230506100746624.png b/面试/面试/img/image-20230506100746624.png new file mode 100755 index 0000000..826fcd5 Binary files /dev/null and b/面试/面试/img/image-20230506100746624.png differ diff --git a/面试/面试/img/image-20230506100848497.png b/面试/面试/img/image-20230506100848497.png new file mode 100755 index 0000000..951a67e Binary files /dev/null and b/面试/面试/img/image-20230506100848497.png differ diff --git a/面试/面试/img/image-20230506100920042.png b/面试/面试/img/image-20230506100920042.png new file mode 100755 index 0000000..fe34b57 Binary files /dev/null and b/面试/面试/img/image-20230506100920042.png differ diff --git a/面试/面试/img/image-20230506101032605.png b/面试/面试/img/image-20230506101032605.png new file mode 100755 index 0000000..dae6cc6 Binary files /dev/null and b/面试/面试/img/image-20230506101032605.png differ diff --git a/面试/面试/img/image-20230506101115674.png b/面试/面试/img/image-20230506101115674.png new file mode 100755 index 0000000..908e5bb Binary files /dev/null and b/面试/面试/img/image-20230506101115674.png differ diff --git a/面试/面试/img/image-20230506101213373.png b/面试/面试/img/image-20230506101213373.png new file mode 100755 index 0000000..23440c5 Binary files /dev/null and b/面试/面试/img/image-20230506101213373.png differ diff --git a/面试/面试/img/image-20230506101420202.png b/面试/面试/img/image-20230506101420202.png new file mode 100755 index 0000000..fc71c47 Binary files /dev/null and b/面试/面试/img/image-20230506101420202.png differ diff --git a/面试/面试/img/image-20230506101445898.png b/面试/面试/img/image-20230506101445898.png new file mode 100755 index 0000000..3948da6 Binary files /dev/null and b/面试/面试/img/image-20230506101445898.png differ diff --git a/面试/面试/img/image-20230506101504632.png b/面试/面试/img/image-20230506101504632.png new file mode 100755 index 0000000..9d6a196 Binary files /dev/null and b/面试/面试/img/image-20230506101504632.png differ diff --git a/面试/面试/img/image-20230506101625087.png b/面试/面试/img/image-20230506101625087.png new file mode 100755 index 0000000..a6b467c Binary files /dev/null and b/面试/面试/img/image-20230506101625087.png differ diff --git a/面试/面试/img/image-20230506101641837.png b/面试/面试/img/image-20230506101641837.png new file mode 100755 index 0000000..aa2f755 Binary files /dev/null and b/面试/面试/img/image-20230506101641837.png differ diff --git a/面试/面试/img/image-20230506101824622.png b/面试/面试/img/image-20230506101824622.png new file mode 100755 index 0000000..96f35ef Binary files /dev/null and b/面试/面试/img/image-20230506101824622.png differ diff --git a/面试/面试/img/image-20230506102311951.png b/面试/面试/img/image-20230506102311951.png new file mode 100755 index 0000000..2b1a80d Binary files /dev/null and b/面试/面试/img/image-20230506102311951.png differ diff --git a/面试/面试/img/image-20230506104954777.png b/面试/面试/img/image-20230506104954777.png new file mode 100755 index 0000000..4cc9a9e Binary files /dev/null and b/面试/面试/img/image-20230506104954777.png differ diff --git a/面试/面试/img/image-20230506111102825.png b/面试/面试/img/image-20230506111102825.png new file mode 100755 index 0000000..2e4d50d Binary files /dev/null and b/面试/面试/img/image-20230506111102825.png differ diff --git a/面试/面试/img/image-20230506111136231.png b/面试/面试/img/image-20230506111136231.png new file mode 100755 index 0000000..1ce9a70 Binary files /dev/null and b/面试/面试/img/image-20230506111136231.png differ diff --git a/面试/面试/img/image-20230506111255401.png b/面试/面试/img/image-20230506111255401.png new file mode 100755 index 0000000..47dcf7c Binary files /dev/null and b/面试/面试/img/image-20230506111255401.png differ diff --git a/面试/面试/img/image-20230506111327590.png b/面试/面试/img/image-20230506111327590.png new file mode 100755 index 0000000..27eb43c Binary files /dev/null and b/面试/面试/img/image-20230506111327590.png differ diff --git a/面试/面试/img/image-20230506111512450.png b/面试/面试/img/image-20230506111512450.png new file mode 100755 index 0000000..fa94fd6 Binary files /dev/null and b/面试/面试/img/image-20230506111512450.png differ diff --git a/面试/面试/img/image-20230506111801764.png b/面试/面试/img/image-20230506111801764.png new file mode 100755 index 0000000..595c101 Binary files /dev/null and b/面试/面试/img/image-20230506111801764.png differ diff --git a/面试/面试/img/image-20230506111817938.png b/面试/面试/img/image-20230506111817938.png new file mode 100755 index 0000000..64cfaf1 Binary files /dev/null and b/面试/面试/img/image-20230506111817938.png differ diff --git a/面试/面试/img/image-20230506111834758.png b/面试/面试/img/image-20230506111834758.png new file mode 100755 index 0000000..3932b35 Binary files /dev/null and b/面试/面试/img/image-20230506111834758.png differ diff --git a/面试/面试/img/image-20230506111919008.png b/面试/面试/img/image-20230506111919008.png new file mode 100755 index 0000000..d6a4dcd Binary files /dev/null and b/面试/面试/img/image-20230506111919008.png differ diff --git a/面试/面试/img/image-20230506111957793.png b/面试/面试/img/image-20230506111957793.png new file mode 100755 index 0000000..7248fb8 Binary files /dev/null and b/面试/面试/img/image-20230506111957793.png differ diff --git a/面试/面试/img/image-20230506112047190.png b/面试/面试/img/image-20230506112047190.png new file mode 100755 index 0000000..88e363c Binary files /dev/null and b/面试/面试/img/image-20230506112047190.png differ diff --git a/面试/面试/img/image-20230506131229649.png b/面试/面试/img/image-20230506131229649.png new file mode 100755 index 0000000..6387bca Binary files /dev/null and b/面试/面试/img/image-20230506131229649.png differ diff --git a/面试/面试/img/image-20230506131308654.png b/面试/面试/img/image-20230506131308654.png new file mode 100755 index 0000000..ebc9790 Binary files /dev/null and b/面试/面试/img/image-20230506131308654.png differ diff --git a/面试/面试/img/image-20230506131415418.png b/面试/面试/img/image-20230506131415418.png new file mode 100755 index 0000000..28d57be Binary files /dev/null and b/面试/面试/img/image-20230506131415418.png differ diff --git a/面试/面试/img/image-20230506131442503.png b/面试/面试/img/image-20230506131442503.png new file mode 100755 index 0000000..b6c1bd9 Binary files /dev/null and b/面试/面试/img/image-20230506131442503.png differ diff --git a/面试/面试/img/image-20230506131544447.png b/面试/面试/img/image-20230506131544447.png new file mode 100755 index 0000000..a48b378 Binary files /dev/null and b/面试/面试/img/image-20230506131544447.png differ diff --git a/面试/面试/img/image-20230506131607645.png b/面试/面试/img/image-20230506131607645.png new file mode 100755 index 0000000..d3830f1 Binary files /dev/null and b/面试/面试/img/image-20230506131607645.png differ diff --git a/面试/面试/img/image-20230506131640893.png b/面试/面试/img/image-20230506131640893.png new file mode 100755 index 0000000..dfa187a Binary files /dev/null and b/面试/面试/img/image-20230506131640893.png differ diff --git a/面试/面试/img/image-20230506154006266.png b/面试/面试/img/image-20230506154006266.png new file mode 100755 index 0000000..8e98f5b Binary files /dev/null and b/面试/面试/img/image-20230506154006266.png differ diff --git a/面试/面试/img/image-20230506154034531.png b/面试/面试/img/image-20230506154034531.png new file mode 100755 index 0000000..95259cf Binary files /dev/null and b/面试/面试/img/image-20230506154034531.png differ diff --git a/面试/面试/img/image-20230506154042673.png b/面试/面试/img/image-20230506154042673.png new file mode 100755 index 0000000..6d3808b Binary files /dev/null and b/面试/面试/img/image-20230506154042673.png differ diff --git a/面试/面试/img/image-20230506154107944.png b/面试/面试/img/image-20230506154107944.png new file mode 100755 index 0000000..2ca86e5 Binary files /dev/null and b/面试/面试/img/image-20230506154107944.png differ diff --git a/面试/面试/img/image-20230506154117857.png b/面试/面试/img/image-20230506154117857.png new file mode 100755 index 0000000..8ea94bd Binary files /dev/null and b/面试/面试/img/image-20230506154117857.png differ diff --git a/面试/面试/img/image-20230506154323950.png b/面试/面试/img/image-20230506154323950.png new file mode 100755 index 0000000..e142e0f Binary files /dev/null and b/面试/面试/img/image-20230506154323950.png differ diff --git a/面试/面试/img/image-20230506154542687.png b/面试/面试/img/image-20230506154542687.png new file mode 100755 index 0000000..6777865 Binary files /dev/null and b/面试/面试/img/image-20230506154542687.png differ diff --git a/面试/面试/img/image-20230506154607558.png b/面试/面试/img/image-20230506154607558.png new file mode 100755 index 0000000..eefb2da Binary files /dev/null and b/面试/面试/img/image-20230506154607558.png differ diff --git a/面试/面试/img/image-20230506154633118.png b/面试/面试/img/image-20230506154633118.png new file mode 100755 index 0000000..7773398 Binary files /dev/null and b/面试/面试/img/image-20230506154633118.png differ diff --git a/面试/面试/img/image-20230506154705088.png b/面试/面试/img/image-20230506154705088.png new file mode 100755 index 0000000..2836893 Binary files /dev/null and b/面试/面试/img/image-20230506154705088.png differ diff --git a/面试/面试/img/image-20230506154759809.png b/面试/面试/img/image-20230506154759809.png new file mode 100755 index 0000000..083a829 Binary files /dev/null and b/面试/面试/img/image-20230506154759809.png differ diff --git a/面试/面试/img/image-20230506154826981.png b/面试/面试/img/image-20230506154826981.png new file mode 100755 index 0000000..ac42e12 Binary files /dev/null and b/面试/面试/img/image-20230506154826981.png differ diff --git a/面试/面试/img/image-20230506154859985.png b/面试/面试/img/image-20230506154859985.png new file mode 100755 index 0000000..9e0f1c1 Binary files /dev/null and b/面试/面试/img/image-20230506154859985.png differ diff --git a/面试/面试/img/image-20230506155000503.png b/面试/面试/img/image-20230506155000503.png new file mode 100755 index 0000000..15175f6 Binary files /dev/null and b/面试/面试/img/image-20230506155000503.png differ diff --git a/面试/面试/img/image-20230506155047765.png b/面试/面试/img/image-20230506155047765.png new file mode 100755 index 0000000..4d34bff Binary files /dev/null and b/面试/面试/img/image-20230506155047765.png differ diff --git a/面试/面试/img/image-20230506155116267.png b/面试/面试/img/image-20230506155116267.png new file mode 100755 index 0000000..ea7df6d Binary files /dev/null and b/面试/面试/img/image-20230506155116267.png differ diff --git a/面试/面试/img/image-20230506155146370.png b/面试/面试/img/image-20230506155146370.png new file mode 100755 index 0000000..732f127 Binary files /dev/null and b/面试/面试/img/image-20230506155146370.png differ diff --git a/面试/面试/img/image-20230506155341703.png b/面试/面试/img/image-20230506155341703.png new file mode 100755 index 0000000..4cea5b7 Binary files /dev/null and b/面试/面试/img/image-20230506155341703.png differ diff --git a/面试/面试/img/image-20230506155416293.png b/面试/面试/img/image-20230506155416293.png new file mode 100755 index 0000000..5ea4043 Binary files /dev/null and b/面试/面试/img/image-20230506155416293.png differ diff --git a/面试/面试/img/image-20230506155501557.png b/面试/面试/img/image-20230506155501557.png new file mode 100755 index 0000000..7132544 Binary files /dev/null and b/面试/面试/img/image-20230506155501557.png differ diff --git a/面试/面试/img/image-20230506155518510.png b/面试/面试/img/image-20230506155518510.png new file mode 100755 index 0000000..878a288 Binary files /dev/null and b/面试/面试/img/image-20230506155518510.png differ diff --git a/面试/面试/img/image-20230506155552693.png b/面试/面试/img/image-20230506155552693.png new file mode 100755 index 0000000..38eac56 Binary files /dev/null and b/面试/面试/img/image-20230506155552693.png differ diff --git a/面试/面试/img/image-20230506155704119.png b/面试/面试/img/image-20230506155704119.png new file mode 100755 index 0000000..914f5f1 Binary files /dev/null and b/面试/面试/img/image-20230506155704119.png differ diff --git a/面试/面试/img/image-20230521101639915.png b/面试/面试/img/image-20230521101639915.png new file mode 100755 index 0000000..babed99 Binary files /dev/null and b/面试/面试/img/image-20230521101639915.png differ diff --git a/面试/面试/img/image-20230521102022928.png b/面试/面试/img/image-20230521102022928.png new file mode 100755 index 0000000..7044242 Binary files /dev/null and b/面试/面试/img/image-20230521102022928.png differ diff --git a/面试/面试/img/image-20230521102122950.png b/面试/面试/img/image-20230521102122950.png new file mode 100755 index 0000000..bd8e8f5 Binary files /dev/null and b/面试/面试/img/image-20230521102122950.png differ diff --git a/面试/面试/img/image-20230521102135897.png b/面试/面试/img/image-20230521102135897.png new file mode 100755 index 0000000..e396fea Binary files /dev/null and b/面试/面试/img/image-20230521102135897.png differ diff --git a/面试/面试/img/image-20230521102156863.png b/面试/面试/img/image-20230521102156863.png new file mode 100755 index 0000000..bb0f1f9 Binary files /dev/null and b/面试/面试/img/image-20230521102156863.png differ diff --git a/面试/面试/img/image-20230521102319997.png b/面试/面试/img/image-20230521102319997.png new file mode 100755 index 0000000..44a00f8 Binary files /dev/null and b/面试/面试/img/image-20230521102319997.png differ diff --git a/面试/面试/img/image-20230521102452501.png b/面试/面试/img/image-20230521102452501.png new file mode 100755 index 0000000..07c8d2b Binary files /dev/null and b/面试/面试/img/image-20230521102452501.png differ diff --git a/面试/面试/img/image-20230521104457397.png b/面试/面试/img/image-20230521104457397.png new file mode 100755 index 0000000..cd410ec Binary files /dev/null and b/面试/面试/img/image-20230521104457397.png differ diff --git a/面试/面试/img/image-20230521104504491.png b/面试/面试/img/image-20230521104504491.png new file mode 100755 index 0000000..cd410ec Binary files /dev/null and b/面试/面试/img/image-20230521104504491.png differ diff --git a/面试/面试/img/image-20230521104952085.png b/面试/面试/img/image-20230521104952085.png new file mode 100755 index 0000000..27905ba Binary files /dev/null and b/面试/面试/img/image-20230521104952085.png differ diff --git a/面试/面试/img/image-20230521105012350.png b/面试/面试/img/image-20230521105012350.png new file mode 100755 index 0000000..004f353 Binary files /dev/null and b/面试/面试/img/image-20230521105012350.png differ diff --git a/面试/面试/img/image-20230521105346337.png b/面试/面试/img/image-20230521105346337.png new file mode 100755 index 0000000..2b6ebc1 Binary files /dev/null and b/面试/面试/img/image-20230521105346337.png differ diff --git a/面试/面试/img/image-20230521111455359.png b/面试/面试/img/image-20230521111455359.png new file mode 100755 index 0000000..2986779 Binary files /dev/null and b/面试/面试/img/image-20230521111455359.png differ diff --git a/面试/面试/img/image-20230521111943231.png b/面试/面试/img/image-20230521111943231.png new file mode 100755 index 0000000..12bfb43 Binary files /dev/null and b/面试/面试/img/image-20230521111943231.png differ diff --git a/面试/面试/img/image-20230521112108550.png b/面试/面试/img/image-20230521112108550.png new file mode 100755 index 0000000..b6ad922 Binary files /dev/null and b/面试/面试/img/image-20230521112108550.png differ diff --git a/面试/面试/img/image-20230521112731617.png b/面试/面试/img/image-20230521112731617.png new file mode 100755 index 0000000..1710193 Binary files /dev/null and b/面试/面试/img/image-20230521112731617.png differ diff --git a/面试/面试/img/image-20230521112802886.png b/面试/面试/img/image-20230521112802886.png new file mode 100755 index 0000000..12d623c Binary files /dev/null and b/面试/面试/img/image-20230521112802886.png differ diff --git a/面试/面试/img/image-20230521112831065.png b/面试/面试/img/image-20230521112831065.png new file mode 100755 index 0000000..5d38cfb Binary files /dev/null and b/面试/面试/img/image-20230521112831065.png differ diff --git a/面试/面试/img/image-20230521113500488.png b/面试/面试/img/image-20230521113500488.png new file mode 100755 index 0000000..d1f1aed Binary files /dev/null and b/面试/面试/img/image-20230521113500488.png differ diff --git a/面试/面试/img/image-20230521113544219.png b/面试/面试/img/image-20230521113544219.png new file mode 100755 index 0000000..8780787 Binary files /dev/null and b/面试/面试/img/image-20230521113544219.png differ diff --git a/面试/面试/img/image-20230521113906521.png b/面试/面试/img/image-20230521113906521.png new file mode 100755 index 0000000..91f55f1 Binary files /dev/null and b/面试/面试/img/image-20230521113906521.png differ diff --git a/面试/面试/img/image-20230521113941467.png b/面试/面试/img/image-20230521113941467.png new file mode 100755 index 0000000..9abe645 Binary files /dev/null and b/面试/面试/img/image-20230521113941467.png differ diff --git a/面试/面试/img/image-20230521114305463.png b/面试/面试/img/image-20230521114305463.png new file mode 100755 index 0000000..ce79fa2 Binary files /dev/null and b/面试/面试/img/image-20230521114305463.png differ diff --git a/面试/面试/img/image-20230521114432028.png b/面试/面试/img/image-20230521114432028.png new file mode 100755 index 0000000..c948f67 Binary files /dev/null and b/面试/面试/img/image-20230521114432028.png differ diff --git a/面试/面试/img/image-20230521124717749.png b/面试/面试/img/image-20230521124717749.png new file mode 100755 index 0000000..9fddea7 Binary files /dev/null and b/面试/面试/img/image-20230521124717749.png differ diff --git a/面试/面试/img/image-20230521125012727.png b/面试/面试/img/image-20230521125012727.png new file mode 100755 index 0000000..ea18eba Binary files /dev/null and b/面试/面试/img/image-20230521125012727.png differ diff --git a/面试/面试/img/image-20230521125136717.png b/面试/面试/img/image-20230521125136717.png new file mode 100755 index 0000000..2cc8245 Binary files /dev/null and b/面试/面试/img/image-20230521125136717.png differ diff --git a/面试/面试/img/image-20230521232030067.png b/面试/面试/img/image-20230521232030067.png new file mode 100755 index 0000000..7c24c01 Binary files /dev/null and b/面试/面试/img/image-20230521232030067.png differ diff --git a/面试/面试/img/image-20230521232726959.png b/面试/面试/img/image-20230521232726959.png new file mode 100755 index 0000000..202c9f8 Binary files /dev/null and b/面试/面试/img/image-20230521232726959.png differ diff --git a/面试/面试/img/image-20230521232850921.png b/面试/面试/img/image-20230521232850921.png new file mode 100755 index 0000000..4182d86 Binary files /dev/null and b/面试/面试/img/image-20230521232850921.png differ diff --git a/面试/面试/img/image-20230521232913086.png b/面试/面试/img/image-20230521232913086.png new file mode 100755 index 0000000..ec616a9 Binary files /dev/null and b/面试/面试/img/image-20230521232913086.png differ diff --git a/面试/面试/img/image-20230521233150276.png b/面试/面试/img/image-20230521233150276.png new file mode 100755 index 0000000..82ef524 Binary files /dev/null and b/面试/面试/img/image-20230521233150276.png differ diff --git a/面试/面试/img/image-20230521233220905.png b/面试/面试/img/image-20230521233220905.png new file mode 100755 index 0000000..c859371 Binary files /dev/null and b/面试/面试/img/image-20230521233220905.png differ diff --git a/面试/面试/img/image-20230521233554657.png b/面试/面试/img/image-20230521233554657.png new file mode 100755 index 0000000..07b1ead Binary files /dev/null and b/面试/面试/img/image-20230521233554657.png differ diff --git a/面试/面试/img/image-20230521233600556.png b/面试/面试/img/image-20230521233600556.png new file mode 100755 index 0000000..ceaa602 Binary files /dev/null and b/面试/面试/img/image-20230521233600556.png differ diff --git a/面试/面试/img/image-20230521233715574.png b/面试/面试/img/image-20230521233715574.png new file mode 100755 index 0000000..7229a2e Binary files /dev/null and b/面试/面试/img/image-20230521233715574.png differ diff --git a/面试/面试/img/image-20230521233926897.png b/面试/面试/img/image-20230521233926897.png new file mode 100755 index 0000000..bd2eaf7 Binary files /dev/null and b/面试/面试/img/image-20230521233926897.png differ diff --git a/面试/面试/img/image-20230521233934644.png b/面试/面试/img/image-20230521233934644.png new file mode 100755 index 0000000..3eb8be6 Binary files /dev/null and b/面试/面试/img/image-20230521233934644.png differ diff --git a/面试/面试/img/简单工厂.jpg b/面试/面试/img/简单工厂.jpg new file mode 100755 index 0000000..14a6c24 Binary files /dev/null and b/面试/面试/img/简单工厂.jpg differ diff --git a/面试/面试/img/线程池的执行原理.jpg b/面试/面试/img/线程池的执行原理.jpg new file mode 100755 index 0000000..afbaca8 Binary files /dev/null and b/面试/面试/img/线程池的执行原理.jpg differ diff --git a/面试/面试/多线程相关面试题.md b/面试/面试/多线程相关面试题.md new file mode 100755 index 0000000..5fedfd9 --- /dev/null +++ b/面试/面试/多线程相关面试题.md @@ -0,0 +1,2597 @@ +```文档说明``` + +>在文档中对所有的面试题都进行了**难易程度**和**出现频率**的等级说明 +> +>星数越多代表权重越大,最多五颗星(☆☆☆☆☆) 最少一颗星(☆) + +# Java多线程相关面试题 + +## 1.线程的基础知识 + +### 1.1 线程和进程的区别? + +>难易程度:☆☆ +> +>出现频率:☆☆☆ + +程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。 + +**当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。** + +![image-20221026105350827](img/image-20221026105350827.png) + +一个进程之内可以分为一到多个线程。 + +一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行 + +Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。在 windows 中进程是不活动的,只是作为线程的容器 + +![image-20221026105442158](img/image-20221026105442158.png) + +**二者对比** + +- 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务 +- 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间 +- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程) + +### 1.2 并行和并发有什么区别? + +>难易程度:☆ +> +>出现频率:☆ + +单核CPU + +- 单核CPU下线程实际还是串行执行的 + +- 操作系统中有一个组件叫做任务调度器,将cpu的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于cpu在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。 + +- 总结为一句话就是: 微观串行,宏观并行 + +一般会将这种线程轮流使用CPU的做法称为并发(concurrent) + +![image-20230503203246348](img/image-20230503203246348.png) + + + +![image-20221026105607248](img/image-20221026105607248.png) + +多核CPU + +每个核(core)都可以调度运行线程,这时候线程可以是并行的。 + +![image-20230503203330700](img/image-20230503203330700.png) + +**并发(concurrent)是同一时间应对(dealing with)多件事情的能力** + +**并行(parallel)是同一时间动手做(doing)多件事情的能力** + +>举例: +> +>- 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发 +> +>- 家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待) +> +>- 雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行 + +### 1.3 创建线程的四种方式 + +>难易程度:☆☆ +> +>出现频率:☆☆☆☆ + +参考回答: + +共有四种方式可以创建线程,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程 + +详细创建方式参考下面代码: + +① **继承Thread类** + +```java +public class MyThread extends Thread { + + @Override + public void run() { + System.out.println("MyThread...run..."); + } + + + public static void main(String[] args) { + + // 创建MyThread对象 + MyThread t1 = new MyThread() ; + MyThread t2 = new MyThread() ; + + // 调用start方法启动线程 + t1.start(); + t2.start(); + + } + +} +``` + +② **实现runnable接口** + +```java +public class MyRunnable implements Runnable{ + + @Override + public void run() { + System.out.println("MyRunnable...run..."); + } + + public static void main(String[] args) { + + // 创建MyRunnable对象 + MyRunnable mr = new MyRunnable() ; + + // 创建Thread对象 + Thread t1 = new Thread(mr) ; + Thread t2 = new Thread(mr) ; + + // 调用start方法启动线程 + t1.start(); + t2.start(); + + } + +} +``` + +③ **实现Callable接口** + +```java +public class MyCallable implements Callable { + + @Override + public String call() throws Exception { + System.out.println("MyCallable...call..."); + return "OK"; + } + + public static void main(String[] args) throws ExecutionException, InterruptedException { + + // 创建MyCallable对象 + MyCallable mc = new MyCallable() ; + + // 创建F + FutureTask ft = new FutureTask(mc) ; + + // 创建Thread对象 + Thread t1 = new Thread(ft) ; + Thread t2 = new Thread(ft) ; + + // 调用start方法启动线程 + t1.start(); + + // 调用ft的get方法获取执行结果 + String result = ft.get(); + + // 输出 + System.out.println(result); + + } + +} +``` + +④ **线程池创建线程** + +```java +public class MyExecutors implements Runnable{ + + @Override + public void run() { + System.out.println("MyRunnable...run..."); + } + + public static void main(String[] args) { + + // 创建线程池对象 + ExecutorService threadPool = Executors.newFixedThreadPool(3); + threadPool.submit(new MyExecutors()) ; + + // 关闭线程池 + threadPool.shutdown(); + + } + +} +``` + +### 1.4 runnable 和 callable 有什么区别 + +>难易程度:☆☆ +> +>出现频率:☆☆☆ + +参考回答: + +1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果 +2. Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。 +3. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛 + +### 1.5 线程的 run()和 start()有什么区别? + +>难易程度:☆☆ +> +>出现频率:☆☆ + +start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。 + +run(): 封装了要被线程执行的代码,可以被调用多次。 + +### 1.6 线程包括哪些状态,状态之间是如何变化的 + +> 难易程度:☆☆☆ +> +> 出现频率:☆☆☆☆ + +线程的状态可以参考JDK中的Thread类中的枚举State + +```java +public enum State { + /** + * 尚未启动的线程的线程状态 + */ + NEW, + + /** + * 可运行线程的线程状态。处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自 * 操作系统的其他资源,例如处理器。 + */ + RUNNABLE, + + /** + * 线程阻塞等待监视器锁的线程状态。处于阻塞状态的线程正在等待监视器锁进入同步块/方法或在调 * 用Object.wait后重新进入同步块/方法。 + */ + BLOCKED, + + /** + * 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态: + * Object.wait没有超时 + * 没有超时的Thread.join + * LockSupport.park + * 处于等待状态的线程正在等待另一个线程执行特定操作。 + * 例如,一个对对象调用Object.wait()的线程正在等待另一个线程对该对象调用Object.notify() * 或Object.notifyAll() 。已调用Thread.join()的线程正在等待指定线程终止。 + */ + WAITING, + + /** + * 具有指定等待时间的等待线程的线程状态。由于以指定的正等待时间调用以下方法之一,线程处于定 * 时等待状态: + * Thread.sleep + * Object.wait超时 + * Thread.join超时 + * LockSupport.parkNanos + * LockSupport.parkUntil + * + */ + TIMED_WAITING, + + /** + * 已终止线程的线程状态。线程已完成执行 + */ + TERMINATED; + } +``` + +状态之间是如何变化的 + +![image-20230503203629212](img/image-20230503203629212.png) + +分别是 + +* 新建 + * 当一个线程对象被创建,但还未调用 start 方法时处于**新建**状态 + * 此时未与操作系统底层线程关联 +* 可运行 + * 调用了 start 方法,就会由**新建**进入**可运行** + * 此时与底层线程关联,由操作系统调度执行 +* 终结 + * 线程内代码已经执行完毕,由**可运行**进入**终结** + * 此时会取消与底层线程关联 +* 阻塞 + * 当获取锁失败后,由**可运行**进入 Monitor 的阻塞队列**阻塞**,此时不占用 cpu 时间 + * 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的**阻塞**线程,唤醒后的线程进入**可运行**状态 +* 等待 + * 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从**可运行**状态释放锁进入 Monitor 等待集合**等待**,同样不占用 cpu 时间 + * 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的**等待**线程,恢复为**可运行**状态 +* 有时限等待 + * 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从**可运行**状态释放锁进入 Monitor 等待集合进行**有时限等待**,同样不占用 cpu 时间 + * 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的**有时限等待**线程,恢复为**可运行**状态,并重新去竞争锁 + * 如果等待超时,也会从**有时限等待**状态恢复为**可运行**状态,并重新去竞争锁 + * 还有一种情况是调用 sleep(long) 方法也会从**可运行**状态进入**有时限等待**状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为**可运行**状态 + + + + + +### 1.7 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行? + +>难易程度:☆☆ +> +>出现频率:☆☆☆ + +在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的**join**()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。 + +代码举例: + +为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成 + +```java +public class JoinTest { + + public static void main(String[] args) { + + // 创建线程对象 + Thread t1 = new Thread(() -> { + System.out.println("t1"); + }) ; + + Thread t2 = new Thread(() -> { + try { + t1.join(); // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程 + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("t2"); + }) ; + + + Thread t3 = new Thread(() -> { + try { + t2.join(); // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程 + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("t3"); + }) ; + + // 启动线程 + t1.start(); + t2.start(); + t3.start(); + + } + +} +``` + + + +### 1.8 notify()和 notifyAll()有什么区别? + +>难易程度:☆☆ +> +>出现频率:☆☆ + +notifyAll:唤醒所有wait的线程 + +notify:只随机唤醒一个 wait 线程 + +```java +package com.itheima.basic; + +public class WaitNotify { + + static boolean flag = false; + static Object lock = new Object(); + + public static void main(String[] args) { + + Thread t1 = new Thread(() -> { + synchronized (lock){ + while (!flag){ + System.out.println(Thread.currentThread().getName()+"...wating..."); + try { + lock.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + System.out.println(Thread.currentThread().getName()+"...flag is true"); + } + }); + + Thread t2 = new Thread(() -> { + synchronized (lock){ + while (!flag){ + System.out.println(Thread.currentThread().getName()+"...wating..."); + try { + lock.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + System.out.println(Thread.currentThread().getName()+"...flag is true"); + } + }); + + Thread t3 = new Thread(() -> { + synchronized (lock) { + System.out.println(Thread.currentThread().getName() + " hold lock"); + lock.notifyAll(); + flag = true; + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + t1.start(); + t2.start(); + t3.start(); + + } + +} +``` + +### 1.9 在 java 中 wait 和 sleep 方法的不同? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +参考回答: + +共同点 + +* wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态 + +不同点 + +* 方法归属不同 + * sleep(long) 是 Thread 的静态方法 + * 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有 + +* 醒来时机不同 + * 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来 + * wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去 + * 它们都可以被打断唤醒 + +* 锁特性不同(重点) + * wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制 + * wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用) + * 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了) + +代码示例: + +```java +public class WaitSleepCase { + + static final Object LOCK = new Object(); + + public static void main(String[] args) throws InterruptedException { + sleeping(); + } + + private static void illegalWait() throws InterruptedException { + LOCK.wait(); + } + + private static void waiting() throws InterruptedException { + Thread t1 = new Thread(() -> { + synchronized (LOCK) { + try { + get("t").debug("waiting..."); + LOCK.wait(5000L); + } catch (InterruptedException e) { + get("t").debug("interrupted..."); + e.printStackTrace(); + } + } + }, "t1"); + t1.start(); + + Thread.sleep(100); + synchronized (LOCK) { + main.debug("other..."); + } + + } + + private static void sleeping() throws InterruptedException { + Thread t1 = new Thread(() -> { + synchronized (LOCK) { + try { + get("t").debug("sleeping..."); + Thread.sleep(5000L); + } catch (InterruptedException e) { + get("t").debug("interrupted..."); + e.printStackTrace(); + } + } + }, "t1"); + t1.start(); + + Thread.sleep(100); + synchronized (LOCK) { + main.debug("other..."); + } + } +} +``` + + + +### 1.10 如何停止一个正在运行的线程? + +>难易程度:☆☆ +> +>出现频率:☆☆ + +参考回答: + +有三种方式可以停止线程 + +- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止 +- 使用stop方法强行终止(不推荐,方法已作废) +- 使用interrupt方法中断线程 + +代码参考如下: + +① **使用退出标志,使线程正常退出**。 + +```java +public class MyInterrupt1 extends Thread { + + volatile boolean flag = false ; // 线程执行的退出标记 + + @Override + public void run() { + while(!flag) { + System.out.println("MyThread...run..."); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static void main(String[] args) throws InterruptedException { + + // 创建MyThread对象 + MyInterrupt1 t1 = new MyInterrupt1() ; + t1.start(); + + // 主线程休眠6秒 + Thread.sleep(6000); + + // 更改标记为true + t1.flag = true ; + + } +} +``` + +② **使用stop方法强行终止** + +```java +public class MyInterrupt2 extends Thread { + + volatile boolean flag = false ; // 线程执行的退出标记 + + @Override + public void run() { + while(!flag) { + System.out.println("MyThread...run..."); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static void main(String[] args) throws InterruptedException { + + // 创建MyThread对象 + MyInterrupt2 t1 = new MyInterrupt2() ; + t1.start(); + + // 主线程休眠2秒 + Thread.sleep(6000); + + // 调用stop方法 + t1.stop(); + + } +} +``` + +③ **使用interrupt方法中断线程**。 + +```java +package com.itheima.basic; + +public class MyInterrupt3 { + + public static void main(String[] args) throws InterruptedException { + + //1.打断阻塞的线程 + /*Thread t1 = new Thread(()->{ + System.out.println("t1 正在运行..."); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }, "t1"); + t1.start(); + Thread.sleep(500); + t1.interrupt(); + System.out.println(t1.isInterrupted());*/ + + + //2.打断正常的线程 + Thread t2 = new Thread(()->{ + while(true) { + Thread current = Thread.currentThread(); + boolean interrupted = current.isInterrupted(); + if(interrupted) { + System.out.println("打断状态:"+interrupted); + break; + } + } + }, "t2"); + t2.start(); + Thread.sleep(500); +// t2.interrupt(); + + } +} +``` + + + +## 2.线程中并发锁 + +### 2.1 讲一下synchronized关键字的底层原理? + +>难易程度:☆☆☆☆☆ +> +>出现频率:☆☆☆ + +#### 2.1.1 基本使用 + +如下抢票的代码,如果不加锁,就会出现超卖或者一张票卖给多个人 + +Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住 + +```java + +public class TicketDemo { + + static Object lock = new Object(); + int ticketNum = 10; + + + public synchronized void getTicket() { + synchronized (this) { + if (ticketNum <= 0) { + return; + } + System.out.println(Thread.currentThread().getName() + "抢到一张票,剩余:" + ticketNum); + // 非原子性操作 + ticketNum--; + } + } + + public static void main(String[] args) { + TicketDemo ticketDemo = new TicketDemo(); + for (int i = 0; i < 20; i++) { + new Thread(() -> { + ticketDemo.getTicket(); + }).start(); + } + } + + +} +``` + +#### 2.1.2 Monitor + +Monitor 被翻译为监视器,是由jvm提供,c++语言实现 + +在代码中想要体现monitor需要借助javap命令查看clsss的字节码,比如以下代码: + +```java +public class SyncTest { + + static final Object lock = new Object(); + static int counter = 0; + public static void main(String[] args) { + synchronized (lock) { + counter++; + } + } +} +``` + +找到这个类的class文件,在class文件目录下执行`javap -v SyncTest.class`,反编译效果如下: + +![image-20230504165342501](img/image-20230504165342501.png) + +>- monitorenter 上锁开始的地方 +>- monitorexit 解锁的地方 +>- 其中被monitorenter和monitorexit包围住的指令就是上锁的代码 +>- 有两个monitorexit的原因,第二个monitorexit是为了防止锁住的代码抛异常后不能及时释放锁 + +在使用了synchornized代码块时需要指定一个对象,所以synchornized也被称为对象锁 + +monitor主要就是跟这个对象产生关联,如下图 + +![image-20230504165833809](img/image-20230504165833809.png) + +Monitor内部具体的存储结构: + +- Owner:存储当前获取锁的线程的,只能有一个线程可以获取 + +- EntryList:关联没有抢到锁的线程,处于Blocked状态的线程 + +- WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程 + +具体的流程: + +- 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有 +- 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功 +- 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平) +- 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待 + +参考回答: + +- Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】 + +- 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor + +- 在monitor内部有三个属性,分别是owner、entrylist、waitset + +- 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程 + +### 2.2 synchronized关键字的底层原理-进阶 + +Monitor实现的锁属于重量级锁,你了解过锁升级吗? + +- Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。 + +- 在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。 + +#### 2.2.1 对象的内存结构 + +在HotSpot虚拟机中,对象在内存中存储的布局可分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充 + +![image-20230504172253826](img/image-20230504172253826.png) + +我们需要重点分析MarkWord对象头 + +#### 2.2.2 MarkWord + +![image-20230504172541922](img/image-20230504172541922.png) + +>- hashcode:25位的对象标识Hash码 +> +>- age:对象分代年龄占4位 +> +>- biased_lock:偏向锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁 +> +>- thread:持有偏向锁的线程ID,占23位 +> +>- epoch:偏向时间戳,占2位 +> +>- ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针,占30位 +> +>- ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针,占30位 + +我们可以通过lock的标识,来判断是哪一种锁的等级 + +- 后三位是001表示无锁 +- 后三位是101表示偏向锁 +- 后两位是00表示轻量级锁 +- 后两位是10表示重量级锁 + +#### 2.2.3 再说Monitor重量级锁 + +每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,**该对象头的Mark Word 中就被设置指向 Monitor 对象的指针** + +![image-20230504172957271](img/image-20230504172957271.png) + +简单说就是:每个对象的对象头都可以设置monoitor的指针,让对象与monitor产生关联 + +#### 2.2.4 轻量级锁 + +在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。 + +```java +static final Object obj = new Object(); + +public static void method1() { + synchronized (obj) { + // 同步块 A + method2(); + } +} + +public static void method2() { + synchronized (obj) { + // 同步块 B + } +} +``` + +**加锁的流程** + +1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。 + +![image-20230504173520412](img/image-20230504173520412.png) + +2.通过CAS指令将Lock Record的地址存储在对象头的mark word中(数据进行交换),如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。 + +![image-20230504173611219](img/image-20230504173611219.png) + + + +3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。 + +![image-20230504173922343](img/image-20230504173922343.png) + +4.如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。 + +**解锁过程** + +1.遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。 + +2.如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后continue。 + +![image-20230504173955680](img/image-20230504173955680.png) + +3.如果Lock Record的 Mark Word不为null,则利用CAS指令将对象头的mark word恢复成为无锁状态。如果失败则膨胀为重量级锁。 + +![image-20230504174045458](img/image-20230504174045458.png) + +#### 2.2.5 偏向锁 + +轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。 + +Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 + +这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有 + +```java +static final Object obj = new Object(); + +public static void m1() { + synchronized (obj) { + // 同步块 A + m2(); + } +} + +public static void m2() { + synchronized (obj) { + // 同步块 B + m3(); + } +} + +public static void m3() { + synchronized (obj) { + + } +} +``` + +**加锁的流程** + +1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。 + +![image-20230504174525256](img/image-20230504174525256.png) + +2.通过CAS指令将Lock Record的**线程id**存储在对象头的mark word中,同时也设置偏向锁的标识为101,如果对象处于无锁状态则修改成功,代表该线程获得了偏向锁。 + +![image-20230504174505031](img/image-20230504174505031.png) + +3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。与轻量级锁不同的时,这里不会再次进行cas操作,只是判断对象头中的线程id是否是自己,因为缺少了cas操作,性能相对轻量级锁更好一些 + +![image-20230504174736226](img/image-20230504174736226.png) + +解锁流程参考轻量级锁 + +#### 2.2.6 参考回答 + +Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。 + +| | **描述** | +| -------- | ------------------------------------------------------------ | +| 重量级锁 | 底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。 | +| 轻量级锁 | 线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性 | +| 偏向锁 | 一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令 | + +**一旦锁发生了竞争,都会升级为重量级锁** + +### 2.3你谈谈 JMM(Java 内存模型) + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型。 + +Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。 + +![image-20230504181638237](img/image-20230504181638237.png) + +特点: + +1. 所有的共享变量都存储于主内存(计算机的RAM)这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。 + +2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。 +3. 线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成。 + +### 2.4 CAS 你知道吗? + +>难易程度:☆☆☆ +> +>出现频率:☆☆ + +#### 2.4.1 概述及基本工作流程 + +CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性。 + +在JUC( java.util.concurrent )包下实现的很多类都用到了CAS操作 + +- AbstractQueuedSynchronizer(AQS框架) + +- AtomicXXX类 + +例子: + +我们还是基于刚才学习过的JMM内存模型进行说明 + +- 线程1与线程2都从主内存中获取变量int a = 100,同时放到各个线程的工作内存中 + +![image-20230504181947319](img/image-20230504181947319.png) + +>一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当旧的预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。如果CAS操作失败,通过自旋的方式等待并再次尝试,直到成功 + +- 线程1操作:V:int a = 100,A:int a = 100,B:修改后的值:int a = 101 (a++) + - 线程1拿A的值与主内存V的值进行比较,判断是否相等 + - 如果相等,则把B的值101更新到主内存中 + +![image-20230504182129820](img/image-20230504182129820.png) + +- 线程2操作:V:int a = 100,A:int a = 100,B:修改后的值:int a = 99(a--) + - 线程2拿A的值与主内存V的值进行比较,判断是否相等(目前不相等,因为线程1已更新V的值99) + - 不相等,则线程2更新失败 + +![image-20230504181827330](img/image-20230504181827330.png) + +- 自旋锁操作 + + - 因为没有加锁,所以线程不会陷入阻塞,效率较高 + + - 如果竞争激烈,重试频繁发生,效率会受影响 + +![image-20230504182447552](img/image-20230504182447552.png) + +需要不断尝试获取共享内存V中最新的值,然后再在新的值的基础上进行更新操作,如果失败就继续尝试获取新的值,直到更新成功 + +#### 2.4.2 CAS 底层实现 + +CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令 + +![image-20230504182737931](img/image-20230504182737931.png) + +都是native修饰的方法,由系统提供的接口执行,并非java代码实现,一般的思路也都是自旋锁实现 + +![image-20230504182838426](img/image-20230504182838426.png) + +在java中比较常见使用有很多,比如ReentrantLock和Atomic开头的线程安全类,都调用了Unsafe中的方法 + +- ReentrantLock中的一段CAS代码 + +![image-20230504182958703](img/image-20230504182958703.png) + +#### 2.4.3 乐观锁和悲观锁 + +- CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。 + +- synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。 + +### 2.5 请谈谈你对 volatile 的理解 + +> 难易程度:☆☆☆ +> +> 出现频率:☆☆☆ + +一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: + +#### 2.5.1 保证线程间的可见性 + +保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。 + +一个典型的例子:永不停止的循环 + +```java +package com.itheima.basic; + + +// 可见性例子 +// -Xint +public class ForeverLoop { + static boolean stop = false; + + public static void main(String[] args) { + new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + stop = true; + System.out.println("modify stop to true..."); + }).start(); + foo(); + } + + static void foo() { + int i = 0; + while (!stop) { + i++; + } + System.out.println("stopped... c:"+ i); + } +} +``` + +当执行上述代码的时候,发现foo()方法中的循环是结束不了的,也就说读取不到共享变量的值结束循环。 + +主要是因为在JVM虚拟机中有一个JIT(即时编辑器)给代码做了优化。 + +>上述代码 +> +>```java +>while (!stop) { +>i++; +>} +>``` +> +>在很短的时间内,这个代码执行的次数太多了,当达到了一个阈值,JIT就会优化此代码,如下: +> +>```java +>while (true) { +>i++; +>} +>``` +> +>当把代码优化成这样子以后,及时`stop`变量改变为了`false`也依然停止不了循环 + +解决方案: + +第一: + +在程序运行的时候加入vm参数`-Xint`表示禁用即时编辑器,不推荐,得不偿失(其他程序还要使用) + +第二: + +在修饰`stop`变量的时候加上`volatile`,表示当前代码禁用了即时编辑器,问题就可以解决,代码如下: + +```java +static volatile boolean stop = false; +``` + +#### 2.5.2 禁止进行指令重排序 + +用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果 + +![image-20230505082441116](img/image-20230505082441116.png) + +在去获取上面的结果的时候,有可能会出现4种情况 + +情况一:先执行actor2获取结果--->0,0(正常) + +情况二:先执行actor1中的第一行代码,然后执行actor2获取结果--->0,1(正常) + +情况三:先执行actor1中所有代码,然后执行actor2获取结果--->1,1(正常) + +情况四:先执行actor1中第二行代码,然后执行actor2获取结果--->1,0(发生了指令重排序,影响结果) + +**解决方案** + +在变量上添加volatile,禁止指令重排序,则可以解决问题 + +![image-20230505082835588](img/image-20230505082835588.png) + +屏障添加的示意图 + +![image-20230505082923729](img/image-20230505082923729.png) + +- 写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下 +- 读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上 + +**其他补充** + +我们上面的解决方案是把volatile加在了int y这个变量上,我们能不能把它加在int x这个变量上呢? + +下面代码使用volatile修饰了x变量 + +![image-20230505083124159](img/image-20230505083124159.png) + +屏障添加的示意图 + +![image-20230505083217904](img/image-20230505083217904.png) + +这样显然是不行的,主要是因为下面两个原则: + +- 写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下 +- 读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上 + +所以,现在我们就可以总结一个volatile使用的小妙招: + +- 写变量让volatile修饰的变量的在代码最后位置 +- 读变量让volatile修饰的变量的在代码最开始位置 + +### 2.6 什么是AQS? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +#### 2.6.1 概述 + +全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,它是构建锁或者其他同步组件的基础框架 + +AQS与Synchronized的区别 + +| **synchronized** | **AQS** | +| ------------------------------ | -------------------------------------- | +| 关键字,c++ 语言实现 | java 语言实现 | +| 悲观锁,自动释放锁 | 悲观锁,手动开启和关闭 | +| 锁竞争激烈都是重量级锁,性能差 | 锁竞争激烈的情况下,提供了多种解决方案 | + +AQS常见的实现类 + +- ReentrantLock 阻塞式锁 + +- Semaphore 信号量 + +- CountDownLatch 倒计时锁 + +#### 2.6.2 工作机制 + +- 在AQS中维护了一个使用了volatile修饰的state属性来表示资源的状态,0表示无锁,1表示有锁 +- 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList +- 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet + +![image-20230505083840046](img/image-20230505083840046.png) + +>- 线程0来了以后,去尝试修改state属性,如果发现state属性是0,就修改state状态为1,表示线程0抢锁成功 +>- 线程1和线程2也会先尝试修改state属性,发现state的值已经是1了,有其他线程持有锁,它们都会到FIFO队列中进行等待, +>- FIFO是一个双向队列,head属性表示头结点,tail表示尾结点 + +**如果多个线程共同去抢这个资源是如何保证原子性的呢?** + +![image-20230505084451193](img/image-20230505084451193.png) + +在去修改state状态的时候,使用的cas自旋锁来保证原子性,确保只能有一个线程修改成功,修改失败的线程将会进入FIFO队列中等待 + +**AQS是公平锁吗,还是非公平锁?** + +- 新的线程与队列中的线程共同来抢资源,是非公平锁 + +- 新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁 + +>比较典型的AQS实现类ReentrantLock,它默认就是非公平锁,新的线程与队列中的线程共同来抢资源 + + + +### 2.5 ReentrantLock的实现原理 + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆ + +#### 2.5.1 概述 + +ReentrantLock翻译过来是可重入锁,相对于synchronized它具备以下特点: + +- 可中断 + +- 可以设置超时时间 + +- 可以设置公平锁 + +- 支持多个条件变量 + +- 与synchronized一样,都支持重入 + +![image-20230505091736569](img/image-20230505091736569.png) + +#### 2.5.2 实现原理 + +ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似 + +构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。 + +查看ReentrantLock源码中的构造方法: + +![image-20230505091827720](img/image-20230505091827720.png) + +提供了两个构造方法,不带参数的默认为非公平 + +如果使用带参数的构造函数,并且传的值为true,则是公平锁 + + + +其中NonfairSync和FairSync这两个类父类都是Sync + +![image-20230505092151244](img/image-20230505092151244.png) + +而Sync的父类是AQS,所以可以得出ReentrantLock底层主要实现就是基于AQS来实现的 + +![image-20230505091833629](img/image-20230505091833629.png) + +**工作流程** + +![image-20230505092340431](img/image-20230505092340431.png) + +- 线程来抢锁后使用cas的方式修改state状态,修改状态成功为1,则让exclusiveOwnerThread属性指向当前线程,获取锁成功 + +- 假如修改状态失败,则会进入双向队列中等待,head指向双向队列头部,tail指向双向队列尾部 + +- 当exclusiveOwnerThread为null的时候,则会唤醒在双向队列中等待的线程 + +- 公平锁则体现在按照先后顺序获取锁,非公平体现在不在排队的线程也可以抢锁 + +### 2.6 synchronized和Lock有什么区别 ? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆☆ + +参考回答 + +* 语法层面 + * synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现 + * Lock 是接口,源码由 jdk 提供,用 java 语言实现 + * 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁 +* 功能层面 + * 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能 + * Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量 + * Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock +* 性能层面 + * 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖 + * 在竞争激烈时,Lock 的实现通常会提供更好的性能 + +### 2.7 死锁产生的条件是什么? + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆ + +**死锁**:一个线程需要同时获取多把锁,这时就容易发生死锁 + +>例如: +> +>t1 线程获得A对象锁,接下来想获取B对象的锁 +> +>t2 线程获得B对象锁,接下来想获取A对象的锁 + +代码如下: + +```java +package com.itheima.basic; + +import static java.lang.Thread.sleep; + +public class Deadlock { + + public static void main(String[] args) { + Object A = new Object(); + Object B = new Object(); + Thread t1 = new Thread(() -> { + synchronized (A) { + System.out.println("lock A"); + try { + sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + synchronized (B) { + System.out.println("lock B"); + System.out.println("操作..."); + } + } + }, "t1"); + + Thread t2 = new Thread(() -> { + synchronized (B) { + System.out.println("lock B"); + try { + sleep(500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + synchronized (A) { + System.out.println("lock A"); + System.out.println("操作..."); + } + } + }, "t2"); + t1.start(); + t2.start(); + } +} +``` + +控制台输出结果 + +![image-20220902171032898](img/image-20220902171032898.png) + +此时程序并没有结束,这种现象就是死锁现象...线程t1持有A的锁等待获取B锁,线程t2持有B的锁等待获取A的锁。 + +### 2.8 如何进行死锁诊断? + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack + +步骤如下: + +第一:查看运行的线程 + +![image-20220902171426738](img/image-20220902171426738.png) + +第二:使用jstack查看线程运行的情况,下图是截图的关键信息 + +运行命令:`jstack -l 46032` + +![image-20220902172229567](img/image-20220902172229567.png) + +**其他解决工具,可视化工具** + +- jconsole + +用于对jvm的内存,线程,类 的监控,是一个基于 jmx 的 GUI 性能监控工具 + +打开方式:java 安装目录 bin目录下 直接启动 jconsole.exe 就行 + +- VisualVM:故障处理工具 + +能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈 + +打开方式:java 安装目录 bin目录下 直接启动 jvisualvm.exe就行 + + + +### 2.10 ConcurrentHashMap + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +ConcurrentHashMap 是一种线程安全的高效Map集合 + +底层数据结构: + +- JDK1.7底层采用分段的数组+链表实现 + +- JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。 + +#### (1) JDK1.7中concurrentHashMap + +数据结构 + +![image-20230505092654811](img/image-20230505092654811.png) + +>- 提供了一个segment数组,在初始化ConcurrentHashMap 的时候可以指定数组的长度,默认是16,一旦初始化之后中间不可扩容 +>- 在每个segment中都可以挂一个HashEntry数组,数组里面可以存储具体的元素,HashEntry数组是可以扩容的 +>- 在HashEntry存储的数组中存储的元素,如果发生冲突,则可以挂单向链表 + +存储流程 + +![image-20230505093055382](img/image-20230505093055382.png) + +- 先去计算key的hash值,然后确定segment数组下标 +- 再通过hash值确定hashEntry数组中的下标存储数据 +- 在进行操作数据的之前,会先判断当前segment对应下标位置是否有线程进行操作,为了线程安全使用的是ReentrantLock进行加锁,如果获取锁是被会使用cas自旋锁进行尝试 + +#### (2) JDK1.8中concurrentHashMap + +在JDK1.8中,放弃了Segment臃肿的设计,数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表 + +采用 CAS + Synchronized来保证并发安全进行实现 + +- CAS控制数组节点的添加 + +- synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升 + +![image-20230505093507265](img/image-20230505093507265.png) + + + +### 2.11 导致并发程序出现问题的根本原因是什么 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +Java并发编程三大特性 + +- 原子性 + +- 可见性 + +- 有序性 + +#### (1)原子性 + +一个线程在CPU中操作不可暂停,也不可中断,要不执行完成,要不不执行 + +比如,如下代码能保证原子性吗? + +![image-20230505205200628](img/image-20230505205200628.png) + +以上代码会出现超卖或者是一张票卖给同一个人,执行并不是原子性的 + +解决方案: + +1.synchronized:同步加锁 + +2.JUC里面的lock:加锁 + +![image-20230505210853493](img/image-20230505210853493.png) + +#### (3)内存可见性 + +内存可见性:让一个线程对共享变量的修改对另一个线程可见 + +比如,以下代码不能保证内存可见性 + +![image-20230505211002252](img/image-20230505211002252.png) + +解决方案: + +- synchronized + +- volatile(推荐) + +- LOCK + +#### (3)有序性 + +指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的 + +还是之前的例子,如下代码: + +![image-20230505211209336](img/image-20230505211209336.png) + +解决方案: + +- volatile + +## 3.线程池 + +### 3.1 说一下线程池的核心参数(线程池的执行原理知道嘛) + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +线程池核心参数主要参考ThreadPoolExecutor这个类的7个参数的构造函数 + +![image-20230505220514872](img/image-20230505220514872.png) + +- corePoolSize 核心线程数目 + +- maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目) + +- keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放 + +- unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等 + +- workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务 + +- threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等 + +- handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略 + +**工作流程** + +![image-20230505220701835](img/image-20230505220701835.png) + +>1,任务在提交的时候,首先判断核心线程数是否已满,如果没有满则直接添加到工作线程执行 +> +>2,如果核心线程数满了,则判断阻塞队列是否已满,如果没有满,当前任务存入阻塞队列 +> +>3,如果阻塞队列也满了,则判断线程数是否小于最大线程数,如果满足条件,则使用临时线程执行任务 +> +>如果核心或临时线程执行完成任务后会检查阻塞队列中是否有需要执行的线程,如果有,则使用非核心线程执行任务 +> +>4,如果所有线程都在忙着(核心线程+临时线程),则走拒绝策略 + +拒绝策略: + +1.AbortPolicy:直接抛出异常,默认策略; + +2.CallerRunsPolicy:用调用者所在的线程来执行任务; + +3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; + +4.DiscardPolicy:直接丢弃任务; + +参考代码: + +```java +public class TestThreadPoolExecutor { + + static class MyTask implements Runnable { + private final String name; + private final long duration; + + public MyTask(String name) { + this(name, 0); + } + + public MyTask(String name, long duration) { + this.name = name; + this.duration = duration; + } + + @Override + public void run() { + try { + LoggerUtils.get("myThread").debug("running..." + this); + Thread.sleep(duration); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public String toString() { + return "MyTask(" + name + ")"; + } + } + + public static void main(String[] args) throws InterruptedException { + AtomicInteger c = new AtomicInteger(1); + ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2); + ThreadPoolExecutor threadPool = new ThreadPoolExecutor( + 2, + 3, + 0, + TimeUnit.MILLISECONDS, + queue, + r -> new Thread(r, "myThread" + c.getAndIncrement()), + new ThreadPoolExecutor.AbortPolicy()); + showState(queue, threadPool); + threadPool.submit(new MyTask("1", 3600000)); + showState(queue, threadPool); + threadPool.submit(new MyTask("2", 3600000)); + showState(queue, threadPool); + threadPool.submit(new MyTask("3")); + showState(queue, threadPool); + threadPool.submit(new MyTask("4")); + showState(queue, threadPool); + threadPool.submit(new MyTask("5",3600000)); + showState(queue, threadPool); + threadPool.submit(new MyTask("6")); + showState(queue, threadPool); + } + + private static void showState(ArrayBlockingQueue queue, ThreadPoolExecutor threadPool) { + try { + Thread.sleep(300); + } catch (InterruptedException e) { + e.printStackTrace(); + } + List tasks = new ArrayList<>(); + for (Runnable runnable : queue) { + try { + Field callable = FutureTask.class.getDeclaredField("callable"); + callable.setAccessible(true); + Object adapter = callable.get(runnable); + Class clazz = Class.forName("java.util.concurrent.Executors$RunnableAdapter"); + Field task = clazz.getDeclaredField("task"); + task.setAccessible(true); + Object o = task.get(adapter); + tasks.add(o); + } catch (Exception e) { + e.printStackTrace(); + } + } + LoggerUtils.main.debug("pool size: {}, queue: {}", threadPool.getPoolSize(), tasks); + } + +} +``` + +### 3.2 线程池中有哪些常见的阻塞队列 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务 + +比较常见的有4个,用的最多是ArrayBlockingQueue和LinkedBlockingQueue + +1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。 + +2.LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。 + +3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的 + +4.SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。 + +**ArrayBlockingQueue的LinkedBlockingQueue区别** + +| **LinkedBlockingQueue** | **ArrayBlockingQueue** | +| -------------------------------- | ---------------------- | +| 默认无界,支持有界 | 强制有界 | +| 底层是链表 | 底层是数组 | +| 是懒惰的,创建节点的时候添加数据 | 提前初始化 Node 数组 | +| 入队会生成新 Node | Node需要是提前创建好的 | +| 两把锁(头尾) | 一把锁 | + +左边是LinkedBlockingQueue加锁的方式,右边是ArrayBlockingQueue加锁的方式 + +- LinkedBlockingQueue读和写各有一把锁,性能相对较好 +- ArrayBlockingQueue只有一把锁,读和写公用,性能相对于LinkedBlockingQueue差一些 + +![image-20230505221424359](img/image-20230505221424359.png) + +### 3.3 如何确定核心线程数 + +>难易程度:☆☆☆☆ +> +>出现频率:☆☆☆ + +在设置核心线程数之前,需要先熟悉一些执行线程池执行任务的类型 + +- IO密集型任务 + +一般来说:文件读写、DB读写、网络请求等 + +推荐:核心线程数大小设置为2N+1 (N为计算机的CPU核数) + + + +- CPU密集型任务 + +一般来说:计算型代码、Bitmap转换、Gson转换等 + +推荐:核心线程数大小设置为N+1 (N为计算机的CPU核数) + + + +java代码查看CPU核数 + +![image-20230505221837189](img/image-20230505221837189.png) + +**参考回答:** + +① 高并发、任务执行时间短 -->( CPU核数+1 ),减少线程上下文的切换 + +② 并发不高、任务执行时间长 + +- IO密集型的任务 --> (CPU核数 * 2 + 1) + +- 计算密集型任务 --> ( CPU核数+1 ) + +③ 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2) + + + +### 3.4 线程池的种类有哪些 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +在java.util.concurrent.Executors类中提供了大量创建连接池的静态方法,常见就有四种 + +1. 创建使用固定线程数的线程池 + + ![image-20230505221959259](img/image-20230505221959259.png) + + - 核心线程数与最大线程数一样,没有救急线程 + + - 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE + + - 适用场景:适用于任务量已知,相对耗时的任务 + + - 案例: + + ```java + public class FixedThreadPoolCase { + + static class FixedThreadDemo implements Runnable{ + @Override + public void run() { + String name = Thread.currentThread().getName(); + for (int i = 0; i < 2; i++) { + System.out.println(name + ":" + i); + } + } + } + + public static void main(String[] args) throws InterruptedException { + //创建一个固定大小的线程池,核心线程数和最大线程数都是3 + ExecutorService executorService = Executors.newFixedThreadPool(3); + + for (int i = 0; i < 5; i++) { + executorService.submit(new FixedThreadDemo()); + Thread.sleep(10); + } + + executorService.shutdown(); + } + + } + ``` + + + +2. 单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO)执行 + + ![image-20230505222050294](img/image-20230505222050294.png) + + - 核心线程数和最大线程数都是1 + + - 阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE + + - 适用场景:适用于按照顺序执行的任务 + + - 案例: + + ```java + public class NewSingleThreadCase { + + static int count = 0; + + static class Demo implements Runnable { + @Override + public void run() { + count++; + System.out.println(Thread.currentThread().getName() + ":" + count); + } + } + + public static void main(String[] args) throws InterruptedException { + //单个线程池,核心线程数和最大线程数都是1 + ExecutorService exec = Executors.newSingleThreadExecutor(); + + for (int i = 0; i < 10; i++) { + exec.execute(new Demo()); + Thread.sleep(5); + } + exec.shutdown(); + } + + } + ``` + + + +3. 可缓存线程池 + + ![image-20230505222126391](img/image-20230505222126391.png) + + - 核心线程数为0 + + - 最大线程数是Integer.MAX_VALUE + + - 阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。 + + - 适用场景:适合任务数比较密集,但每个任务执行时间较短的情况 + + - 案例: + + ```java + public class CachedThreadPoolCase { + + static class Demo implements Runnable { + @Override + public void run() { + String name = Thread.currentThread().getName(); + try { + //修改睡眠时间,模拟线程执行需要花费的时间 + Thread.sleep(100); + + System.out.println(name + "执行完了"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static void main(String[] args) throws InterruptedException { + //创建一个缓存的线程,没有核心线程数,最大线程数为Integer.MAX_VALUE + ExecutorService exec = Executors.newCachedThreadPool(); + for (int i = 0; i < 10; i++) { + exec.execute(new Demo()); + Thread.sleep(1); + } + exec.shutdown(); + } + + } + ``` + + + +4. 提供了“延迟”和“周期执行”功能的ThreadPoolExecutor。 + + ![image-20230505222203615](img/image-20230505222203615.png) + + - 适用场景:有定时和延迟执行的任务 + + - 案例: + + ```java + public class ScheduledThreadPoolCase { + + static class Task implements Runnable { + @Override + public void run() { + try { + String name = Thread.currentThread().getName(); + + System.out.println(name + ", 开始:" + new Date()); + Thread.sleep(1000); + System.out.println(name + ", 结束:" + new Date()); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static void main(String[] args) throws InterruptedException { + //按照周期执行的线程池,核心线程数为2,最大线程数为Integer.MAX_VALUE + ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2); + System.out.println("程序开始:" + new Date()); + + /** + * schedule 提交任务到线程池中 + * 第一个参数:提交的任务 + * 第二个参数:任务执行的延迟时间 + * 第三个参数:时间单位 + */ + scheduledThreadPool.schedule(new Task(), 0, TimeUnit.SECONDS); + scheduledThreadPool.schedule(new Task(), 1, TimeUnit.SECONDS); + scheduledThreadPool.schedule(new Task(), 5, TimeUnit.SECONDS); + + Thread.sleep(5000); + + // 关闭线程池 + scheduledThreadPool.shutdown(); + + } + + } + ``` + +### 3.5 为什么不建议用Executors创建线程池 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆ + +参考阿里开发手册《Java开发手册-嵩山版》 + +![image-20220821003816845](img/image-20220821003816845.png) + +## 4.线程使用场景问题 + +### 4.1 线程池使用场景CountDownLatch、Future(你们项目哪里用到了多线程) + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +#### 4.1.1 CountDownLatch + +CountDownLatch(闭锁/倒计时锁)用来进行线程同步协作,等待所有线程完成倒计时(一个或者多个线程,等待其他多个线程完成某件事情之后才能执行) + +- 其中构造参数用来初始化等待计数值 + +- await() 用来等待计数归零 + +- countDown() 用来让计数减一 + +![image-20230505223014946](img/image-20230505223014946.png) + +案例代码: + +```java +public class CountDownLatchDemo { + + public static void main(String[] args) throws InterruptedException { + //初始化了一个倒计时锁 参数为 3 + CountDownLatch latch = new CountDownLatch(3); + + new Thread(() -> { + System.out.println(Thread.currentThread().getName()+"-begin..."); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + //count-- + latch.countDown(); + System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount()); + }).start(); + new Thread(() -> { + System.out.println(Thread.currentThread().getName()+"-begin..."); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + //count-- + latch.countDown(); + System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount()); + }).start(); + new Thread(() -> { + System.out.println(Thread.currentThread().getName()+"-begin..."); + try { + Thread.sleep(1500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + //count-- + latch.countDown(); + System.out.println(Thread.currentThread().getName()+"-end..." +latch.getCount()); + }).start(); + String name = Thread.currentThread().getName(); + System.out.println(name + "-waiting..."); + //等待其他线程完成 + latch.await(); + System.out.println(name + "-wait end..."); + } + +} +``` + +#### 4.1.2 案例一(es数据批量导入) + +在我们项目上线之前,我们需要把数据库中的数据一次性的同步到es索引库中,但是当时的数据好像是1000万左右,一次性读取数据肯定不行(oom异常),当时我就想到可以使用线程池的方式导入,利用CountDownLatch来控制,就能避免一次性加载过多,防止内存溢出 + +整体流程就是通过CountDownLatch+线程池配合去执行 + +![image-20230505223219951](img/image-20230505223219951.png) + +详细实现流程: + +![image-20230505223246059](img/image-20230505223246059.png) + +>详细实现代码,请查看当天代码 + +#### 4.1.3 案例二(数据汇总) + +在一个电商网站中,用户下单之后,需要查询数据,数据包含了三部分:订单信息、包含的商品、物流信息;这三块信息都在不同的微服务中进行实现的,我们如何完成这个业务呢? + +![image-20230505223442924](img/image-20230505223442924.png) + +>详细实现代码,请查看当天代码 + +- 在实际开发的过程中,难免需要调用多个接口来汇总数据,如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能 + +- 报表汇总 + + ![image-20230505223536657](img/image-20230505223536657.png) + + + +#### 4.1.4 案例二(异步调用) + +![image-20230505223640038](img/image-20230505223640038.png) + +在进行搜索的时候,需要保存用户的搜索记录,而搜索记录不能影响用户的正常搜索,我们通常会开启一个线程去执行历史记录的保存,在新开启的线程在执行的过程中,可以利用线程提交任务 + +### 4.1 如何控制某个方法允许并发访问线程的数量? + +>难易程度:☆☆☆ +> +>出现频率:☆☆ + +Semaphore [ˈsɛməˌfɔr] 信号量,是JUC包下的一个工具类,我们可以通过其限制执行的线程数量,达到限流的效果 + +当一个线程执行时先通过其方法进行获取许可操作,获取到许可的线程继续执行业务逻辑,当线程执行完成后进行释放许可操作,未获取达到许可的线程进行等待或者直接结束。 + +Semaphore两个重要的方法 + +lsemaphore.acquire(): 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量) + +lsemaphore.release():释放一个信号量,此时信号量个数+1 + +线程任务类: + +```java +public class SemaphoreCase { + public static void main(String[] args) { + // 1. 创建 semaphore 对象 + Semaphore semaphore = new Semaphore(3); + // 2. 10个线程同时运行 + for (int i = 0; i < 10; i++) { + new Thread(() -> { + + try { + // 3. 获取许可 + semaphore.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + try { + System.out.println("running..."); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("end..."); + } finally { + // 4. 释放许可 + semaphore.release(); + } + }).start(); + } + } + +} +``` + + + +## 5.其他 + +### 5.1 谈谈你对ThreadLocal的理解 + +>难易程度:☆☆☆ +> +>出现频率:☆☆☆☆ + +#### 5.1.1 概述 + +ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享 + +案例:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的 Connection 上进行数据库的操作,避免A线程关闭了B线程的连接。 + +![image-20230505224057228](img/image-20230505224057228.png) + +#### 5.1.2 ThreadLocal基本使用 + +三个主要方法: + +- set(value) 设置值 + +- get() 获取值 + +- remove() 清除值 + +```java +public class ThreadLocalTest { + static ThreadLocal threadLocal = new ThreadLocal<>(); + + public static void main(String[] args) { + new Thread(() -> { + String name = Thread.currentThread().getName(); + threadLocal.set("itcast"); + print(name); + System.out.println(name + "-after remove : " + threadLocal.get()); + }, "t1").start(); + new Thread(() -> { + String name = Thread.currentThread().getName(); + threadLocal.set("itheima"); + print(name); + System.out.println(name + "-after remove : " + threadLocal.get()); + }, "t2").start(); + } + + static void print(String str) { + //打印当前线程中本地内存中本地变量的值 + System.out.println(str + " :" + threadLocal.get()); + //清除本地内存中的本地变量 + threadLocal.remove(); + } + +} +``` + +#### 5.1.3 ThreadLocal的实现原理&源码解析 + +ThreadLocal本质来说就是一个线程内部存储类,从而让多个线程只操作自己内部的值,从而实现线程数据隔离 + +![image-20230505224341410](img/image-20230505224341410.png) + +在ThreadLocal中有一个内部类叫做ThreadLocalMap,类似于HashMap + +ThreadLocalMap中有一个属性table数组,这个是真正存储数据的位置 + +**set方法** + +![image-20230505224626253](img/image-20230505224626253.png) + +**get方法/remove方法** + +![image-20230505224715087](img/image-20230505224715087.png) + +#### 5.1.4 ThreadLocal-内存泄露问题 + +Java对象中的四种引用类型:强引用、软引用、弱引用、虚引用 + +- 强引用:最为普通的引用方式,表示一个对象处于有用且必须的状态,如果一个对象具有强引用,则GC并不会回收它。即便堆中内存不足了,宁可出现OOM,也不会对其进行回收 + +![image-20230505224755797](img/image-20230505224755797.png) + +- 弱引用:表示一个对象处于可能有用且非必须的状态。在GC线程扫描内存区域时,一旦发现弱引用,就会回收到弱引用相关联的对象。对于弱引用的回收,无关内存区域是否足够,一旦发现则会被回收 + +![image-20230505224812015](img/image-20230505224812015.png) + +每一个Thread维护一个ThreadLocalMap,在ThreadLocalMap中的Entry对象继承了WeakReference。其中key为使用弱引用的ThreadLocal实例,value为线程变量的副本 + +![image-20230505224857538](img/image-20230505224857538.png) + +在使用ThreadLocal的时候,强烈建议:**务必手动remove** + +## 6 真实面试还原 + +### 6.1 线程的基础知识 + +>**面试官**:聊一下并行和并发有什么区别? +> +>**候选人:** +> +>是这样的~~ +> +>现在都是多核CPU,在多核CPU下 +> +>并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU +> +>并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程 +> +>------ +> +>**面试官**:说一下线程和进程的区别? +> +>**候选人:** +> +>嗯,好~ +> +>- 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务 +>- 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间 +>- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程) +> +>---- +> +>**面试官**:如果在java中创建线程有哪些方式? +> +>**候选人:** +> +>在java中一共有四种常见的创建方式,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程。通常情况下,我们项目中都会采用线程池的方式创建线程。 +> +>**面试官**:好的,刚才你说的runnable 和 callable 两个接口创建线程有什么不同呢? +> +>**候选人:** +> +>是这样的~ +> +>最主要的两个线程一个是有返回值,一个是没有返回值的。 +> +>Runnable 接口run方法无返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果 +> +>还有一个就是,他们异常处理也不一样。Runnable接口run方法只能抛出运行时异常,也无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息 +> +>在实际开发中,如果需要拿到执行的结果,需要使用Callalbe接口创建线程,调用FutureTask.get()得到可以得到返回值,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。 +> +>------ +> +>**面试官**:线程包括哪些状态,状态之间是如何变化的? +> +>**候选人:** +> +>在JDK中的Thread类中的枚举State里面定义了6中线程的状态分别是:新建、可运行、终结、阻塞、等待和有时限等待六种。 +> +>关于线程的状态切换情况比较多。我分别介绍一下 +> +>当一个线程对象被创建,但还未调用 start 方法时处于**新建**状态,调用了 start 方法,就会由**新建**进入**可运行**状态。如果线程内代码已经执行完毕,由**可运行**进入**终结**状态。当然这些是一个线程正常执行情况。 +> +>如果线程获取锁失败后,由**可运行**进入 Monitor 的阻塞队列**阻塞**,只有当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的**阻塞**线程,唤醒后的线程进入**可运行**状态 +> +>如果线程获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从**可运行**状态释放锁**等待**状态,当其它持锁线程调用 notify() 或 notifyAll() 方法,会恢复为**可运行**状态 +> +>还有一种情况是调用 sleep(long) 方法也会从**可运行**状态进入**有时限等待**状态,不需要主动唤醒,超时时间到自然恢复为**可运行**状态 +> +>**面试官**:嗯,好的,刚才你说的线程中的 wait 和 sleep方法有什么不同呢? +> +>**候选人:** +> +>它们两个的相同点是都可以让当前线程暂时放弃 CPU 的使用权,进入阻塞状态。 +> +>不同点主要有三个方面: +> +>第一:方法归属不同 +> +>sleep(long) 是 Thread 的静态方法。而 wait(),是 Object 的成员方法,每个对象都有 +> +>第二:线程醒来时机不同 +> +>线程执行 sleep(long) 会在等待相应毫秒后醒来,而 wait() 需要被 notify 唤醒,wait() 如果不唤醒就一直等下去 +> +>第三:锁特性不同 +> +>wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制 +> +>wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(相当于我放弃 cpu,但你们还可以用) +> +>而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(相当于我放弃 cpu,你们也用不了) +> +>**面试官**:好的,我现在举一个场景,你来分析一下怎么做,新建 T1、T2、T3 三个线程,如何保证它们按顺序执行? +> +>**候选人:** +> +>嗯~~,我思考一下 (适当的思考或想一下属于正常情况,脱口而出反而太假[背诵痕迹]) +> +>可以这么做,在多线程中有多种方法让线程按特定顺序执行,可以用线程类的**join**()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。 +> +>比如说: +> +>使用join方法,T3调用T2,T2调用T1,这样就能确保T1就会先完成而T3最后完成 +> +>**面试官**:在我们使用线程的过程中,有两个方法。线程的 run()和 start()有什么区别? +> +>**候选人:** +> +>start方法用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。run方法封装了要被线程执行的代码,可以被调用多次。 +> +>**面试官**:那如何停止一个正在运行的线程呢? +> +>**候选人**: +> +>有三种方式可以停止线程 +> +>第一:可以使用退出标志,使线程正常退出,也就是当run方法完成后线程终止,一般我们加一个标记 +> +>第二:可以使用线程的stop方法强行终止,不过一般不推荐,这个方法已作废 +> +>第三:可以使用线程的interrupt方法中断线程,内部其实也是使用中断标志来中断线程 +> +>我们项目中使用的话,建议使用第一种或第三种方式中断线程 + +### 6.2 线程中并发锁 + +>**面试官**:讲一下synchronized关键字的底层原理? +> +>**候选人**: +> +>嗯~~好的, +> +>synchronized 底层使用的JVM级别中的Monitor 来决定当前线程是否获得了锁,如果某一个线程获得了锁,在没有释放锁之前,其他线程是不能或得到锁的。synchronized 属于悲观锁。 +> +>synchronized 因为需要依赖于JVM级别的Monitor ,相对性能也比较低。 +> +>**面试官**:好的,你能具体说下Monitor 吗? +> +>**候选人**: +> +>monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因 +> +>monitor内部维护了三个变量 +> +>- WaitSet:保存处于Waiting状态的线程 +> +>- EntryList:保存处于Blocked状态的线程 +> +>- Owner:持有锁的线程 +> +>只有一个线程获取到的标志就是在monitor中设置成功了Owner,一个monitor中只能有一个Owner +> +>在上锁的过程中,如果有其他线程也来抢锁,则进入EntryList 进行阻塞,当获得锁的线程执行完了,释放了锁,就会唤醒EntryList 中等待的线程竞争锁,竞争的时候是非公平的。 +> +>**面试官**:好的,那关于synchronized 的锁升级的情况了解吗? +> +>**候选人**: +> +>嗯,知道一些(要谦虚) +> +>Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。 +> +>重量级锁:底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。 +> +>轻量级锁:线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性 +> +>偏向锁:一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令 +> +>一旦锁发生了竞争,都会升级为重量级锁 +> +>**面试官**:好的,刚才你说了synchronized它在高并发量的情况下,性能不高,在项目该如何控制使用锁呢? +> +>**候选人**: +> +>嗯,其实,在高并发下,我们可以采用ReentrantLock来加锁。 +> +>**面试官**:嗯,那你说下ReentrantLock的使用方式和底层原理? +> +>**候选人**: +> +>好的, +> +>ReentrantLock是一个可重入锁:,调用 lock 方 法获取了锁之后,再次调用 lock,是不会再阻塞,内部直接增加重入次数 就行了,标识这个线程已经重复获取一把锁而不需要等待锁的释放。 +> +>ReentrantLock是属于juc报下的类,属于api层面的锁,跟synchronized一样,都是悲观锁。通过lock()用来获取锁,unlock()释放锁。 +> +>它的底层实现原理主要利用**CAS+AQS队列**来实现。它支持公平锁和非公平锁,两者的实现类似 +> +>构造方法接受一个可选的公平参数(**默认非公平锁**),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高。 +> +>**面试官**:好的,刚才你说了CAS和AQS,你能介绍一下吗? +> +>**候选人**: +> +>好的。 +> +>CAS的全称是: Compare And Swap(比较再交换);它体现的一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性。 +> +>- CAS使用到的地方很多:AQS框架、AtomicXXX类 +> +>- 在操作共享变量的时候使用的自旋锁,效率上更高一些 +> +>- CAS的底层是调用的Unsafe类中的方法,都是操作系统提供的,其他语言实现 +> +> +> +>AQS的话,其实就一个jdk提供的类AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。 +> +>内部有一个属性 state 属性来表示资源的状态,默认state等于0,表示没有获取锁,state等于1的时候才标明获取到了锁。通过cas 机制设置 state 状态 +> +>在它的内部还提供了基于 FIFO 的等待队列,是一个双向列表,其中 +> +>- tail 指向队列最后一个元素 +> +>- head 指向队列中最久的一个元素 +> +>其中我们刚刚聊的ReentrantLock底层的实现就是一个AQS。 +> +>**面试官**:synchronized和Lock有什么区别 ? +> +>**候选人**: +> +>嗯~~,好的,主要有三个方面不太一样 +> +>第一,语法层面 +> +>* synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现,退出同步代码块锁会自动释放 +>* Lock 是接口,源码由 jdk 提供,用 java 语言实现,需要手动调用 unlock 方法释放锁 +> +>第二,功能层面 +> +>* 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能 +>* Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量,同时Lock 可以实现不同的场景,如 ReentrantLock, ReentrantReadWriteLock +> +>第三,性能层面 +> +>* 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖 +>* 在竞争激烈时,Lock 的实现通常会提供更好的性能 +> +>统合来看,需要根据不同的场景来选择不同的锁的使用。 +> +>----- +> +>**面试官**:死锁产生的条件是什么? +> +>**候选人**: +> +>嗯,是这样的,一个线程需要同时获取多把锁,这时就容易发生死锁,举个例子来说: +> +>t1 线程获得A对象锁,接下来想获取B对象的锁 +> +>t2 线程获得B对象锁,接下来想获取A对象的锁 +> +>这个时候t1线程和t2线程都在互相等待对方的锁,就产生了死锁 +> +>**面试官**:那如果产出了这样的,如何进行死锁诊断? +> +>**候选人**: +> +>这个也很容易,我们只需要通过jdk自动的工具就能搞定 +> +>我们可以先通过jps来查看当前java程序运行的进程id +> +>然后通过jstack来查看这个进程id,就能展示出来死锁的问题,并且,可以定位代码的具体行号范围,我们再去找到对应的代码进行排查就行了。 +> +>------- +> +>**面试官**:请谈谈你对 volatile 的理解 +> +>**候选人**: +> +>嗯~~ +> +>volatile 是一个关键字,可以修饰类的成员变量、类的静态成员变量,主要有两个功能 +> +>第一:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。 +> +>第二: 禁止进行指令重排序,可以保证代码执行有序性。底层实现原理是,添加了一个**内存屏障**,通过插入内存屏障禁止在内存屏障**前后**的指令执行重排序优化 +> +>-------- +> +>**本文作者**:接《集合相关面试题》 +> +>**面试官**:那你能聊一下ConcurrentHashMap的原理吗? +> +>**候选人**: +> +>嗯好的, +> +>ConcurrentHashMap 是一种线程安全的高效Map集合,jdk1.7和1.8也做了很多调整。 +> +>- JDK1.7的底层采用是**分段的数组**+**链表** 实现 +>- JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。 +> +>在jdk1.7中 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一 种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构 的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修 改时,必须首先获得对应的 Segment的锁。 +> +>Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元 素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁 +> +>在jdk1.8中的ConcurrentHashMap 做了较大的优化,性能提升了不少。首先是它的数据结构与jdk1.8的hashMap数据结构完全一致。其次是放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保 证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲 突,就不会产生并发 , 效率得到提升 + +### 6.3 线程池 + +>**面试官**:线程池的种类有哪些? +> +>**候选人**: +> +>嗯!是这样 +> +>在jdk中默认提供了4中方式创建线程池 +> +>第一个是:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回 收空闲线程,若无可回收,则新建线程。 +> +>第二个是:newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列 中等待。 +> +>第三个是:newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 +> +>第四个是:newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任 务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 +> +>**面试官**:线程池的核心参数有哪些? +> +>**候选人**: +> +>在线程池中一共有7个核心参数: +> +>1. corePoolSize 核心线程数目 - 池中会保留的最多线程数 +> +>2. maximumPoolSize 最大线程数目 - 核心线程+救急线程的最大数目 +> +>3. keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放 +> +>4. unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等 +> +>5. workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务 +> +>6. threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等 +> +>7. handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略 +> +> 在拒绝策略中又有4中拒绝策略 +> +> 当线程数过多以后,第一种是抛异常、第二种是由调用者执行任务、第三是丢弃当前的任务,第四是丢弃最早排队任务。默认是直接抛异常。 +> +>**面试官**:如何确定核心线程池呢? +> +>**候选人**: +> +>是这样的,我们公司当时有一些规范,为了减少线程上下文的切换,要根据当时部署的服务器的CPU核数来决定,我们规则是:CPU核数+1就是最终的核心线程数。 +> +>**面试官**:线程池的执行原理知道吗? +> +>**候选人**: +> +>嗯~,它是这样的 +> +>首先判断线程池里的核心线程是否都在执行任务,如果不是则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队 列里。如果工作队列满了,则判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任 务。如果已经满了,则交给拒绝策略来处理这个任务。 +> +>**面试官**:为什么不建议使用Executors创建线程池呢? +> +>**候选人**: +> +>好的,其实这个事情在阿里提供的最新开发手册《Java开发手册-嵩山版》中也提到了 +> +>主要原因是如果使用Executors创建线程池的话,它允许的请求队列默认长度是Integer.MAX_VALUE,这样的话,有可能导致堆积大量的请求,从而导致OOM(内存溢出)。 +> +>所以,我们一般推荐使用ThreadPoolExecutor来创建线程池,这样可以明确规定线程池的参数,避免资源的耗尽。 + +### 6.4 线程使用场景问题 + +>**面试官**:如果控制某一个方法允许并发访问线程的数量? +> +>**候选人**: +> +>嗯~~,我想一下 +> +>在jdk中提供了一个Semaphore[seməfɔːr]类(信号量) +> +>它提供了两个方法,semaphore.acquire() 请求信号量,可以限制线程的个数,是一个正数,如果信号量是-1,就代表已经用完了信号量,其他线程需要阻塞了 +> +>第二个方法是semaphore.release(),代表是释放一个信号量,此时信号量的个数+1 +> +> +> +>**面试官**:好的,那该如何保证Java程序在多线程的情况下执行安全呢? +> +>**候选人**: +> +>嗯,刚才讲过了导致线程安全的原因,如果解决的话,jdk中也提供了很多的类帮助我们解决多线程安全的问题,比如: +> +>- JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题 +>- synchronized、volatile、LOCK,可以解决可见性问题 +>- Happens-Before 规则可以解决有序性问题 +> +>--- +> +>**面试官**:你在项目中哪里用了多线程? +> +>**候选人**: +> +>嗯~~,我想一下当时的场景[根据自己简历上的模块设计多线程场景] +> +>参考场景一: +> +>es数据批量导入 +> +>在我们项目上线之前,我们需要把数据量的数据一次性的同步到es索引库中,但是当时的数据好像是1000万左右,一次性读取数据肯定不行(oom异常),如果分批执行的话,耗时也太久了。所以,当时我就想到可以使用线程池的方式导入,利用CountDownLatch+Future来控制,就能大大提升导入的时间。 +> +>参考场景二: +> +>在我做那个xx电商网站的时候,里面有一个数据汇总的功能,在用户下单之后需要查询订单信息,也需要获得订单中的商品详细信息(可能是多个),还需要查看物流发货信息。因为它们三个对应的分别三个微服务,如果一个一个的操作的话,互相等待的时间比较长。所以,我当时就想到可以使用线程池,让多个线程同时处理,最终再汇总结果就可以了,当然里面需要用到Future来获取每个线程执行之后的结果才行 +> +>参考场景三: +> +>《黑马头条》项目中使用的 +> +>我当时做了一个文章搜索的功能,用户输入关键字要搜索文章,同时需要保存用户的搜索记录(搜索历史),这块我设计的时候,为了不影响用户的正常搜索,我们采用的异步的方式进行保存的,为了提升性能,我们加入了线程池,也就说在调用异步方法的时候,直接从线程池中获取线程使用 + +### 6.5 其他 + +>**面试官**:谈谈你对ThreadLocal的理解 +> +>**候选人**: +> +>嗯,是这样的~~ +> +>ThreadLocal 主要功能有两个,第一个是可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题,第二个是实现了线程内的资源共享 +> +>**面试官**:好的,那你知道ThreadLocal的底层原理实现吗? +> +>**候选人**: +> +>嗯,知道一些~ +> +>在ThreadLocal内部维护了一个一个 ThreadLocalMap 类型的成员变量,用来存储资源对象 +> +>当我们调用 set 方法,就是以 ThreadLocal 自己作为 key,资源对象作为 value,放入当前线程的 ThreadLocalMap 集合中 +> +>当调用 get 方法,就是以 ThreadLocal 自己作为 key,到当前线程中查找关联的资源值 +> +>当调用 remove 方法,就是以 ThreadLocal 自己作为 key,移除当前线程关联的资源值 +> +>**面试官**:好的,那关于ThreadLocal会导致内存溢出这个事情,了解吗? +> +>**候选人**: +> +>嗯,我之前看过源码,我想一下~~ +> +>是应为ThreadLocalMap 中的 key 被设计为弱引用,它是被动的被GC调用释放key,不过关键的是只有key可以得到内存释放,而value不会,因为value是一个强引用。 +> +>在使用ThreadLocal 时都把它作为静态变量(即强引用),因此无法被动依靠 GC 回收,建议主动的remove 释放 key,这样就能避免内存溢出。 + + + diff --git a/面试/面试/常见技术场景.md b/面试/面试/常见技术场景.md new file mode 100755 index 0000000..fc154a5 --- /dev/null +++ b/面试/面试/常见技术场景.md @@ -0,0 +1,354 @@ +## 常见技术场景 + +### 1.单点登录这块怎么实现的 + +#### 1.1 概述 + +单点登录的英文名叫做:Single Sign On(简称**SSO**),只需要登录一次,就可以访问所有信任的应用系统 + +在**以前**的时候,一般我们就**单系统**,所有的功能都在同一个系统上。 + +![image-20230521113500488](img/image-20230521113500488.png) + +单体系统的session共享 + +- **登录**:将用户信息保存在Session对象中 + +- - 如果在Session对象中能查到,说明已经登录 + - 如果在Session对象中查不到,说明没登录(或者已经退出了登录) + +- **注销(退出登录)**:从Session中删除用户的信息 + +后来,我们为了**合理利用资源和降低耦合性**,于是把单系统**拆分**成多个子系统。 + +![image-20230521113544219](img/image-20230521113544219.png) + +多系统即可能有多个Tomcat,而Session是依赖当前系统的Tomcat,所以系统A的Session和系统B的Session是**不共享**的。 + +解决系统之间Session不共享问题有一下几种方案: + +- Tomcat集群Session全局复制(最多支持5台tomcat,不推荐使用) +- JWT(常见) +- Oauth2 +- CAS +- 自己实现(redis+token) + +#### 1.2 JWT解决单点登录 + +现在有一个微服务的简单架构,如图: + +![image-20230521113906521](img/image-20230521113906521.png) + +使用jwt解决单点登录的流程如下: + +![image-20230521113941467](img/image-20230521113941467.png) + +#### 1.3 回答要点 + +1,先解释什么是单点登录 + +单点登录的英文名叫做:Single Sign On(简称**SSO**) + +2,介绍自己项目中涉及到的单点登录(即使没涉及过,也可以说实现的思路) + +3,介绍单点登录的解决方案,以JWT为例 + +​ I. 用户访问其他系统,会在网关判断token是否有效 + +​ II. 如果token无效则会返回401(认证失败)前端跳转到登录页面 + +​ III. 用户发送登录请求,返回浏览器一个token,浏览器把token保存到cookie + +​ IV. 再去访问其他服务的时候,都需要携带token,由网关统一验证后路由到目标服务 + +### 2.权限认证是如何实现的 + +#### 2.1 概述 + +后台的管理系统,更注重权限控制,最常见的就是RBAC模型来指导实现权限 + +RBAC(Role-Based Access Control)基于角色的访问控制 + +- 3个基础部分组成:用户、角色、权限 + +- 具体实现 + - 5张表(用户表、角色表、权限表、用户角色中间表、角色权限中间表) + - 7张表(用户表、角色表、权限表、菜单表、用户角色中间表、角色权限中间表、权限菜单中间表) + +#### 2.2 RBAC权限模型 + +最常见的5张表的关系 + +![image-20230521114305463](img/image-20230521114305463.png) + +**数据流转** + +张三具有什么权限呢? + +流程:张三登录系统---> 查询张三拥有的角色列表--->再根据角色查询拥有的权限 + +![image-20230521114432028](img/image-20230521114432028.png) + +在实际的开发中,也会使用权限框架完成权限功能的实现,并且设置多种粒度,常见的框架有: + +- Apache shiro +- Spring security(推荐) + +#### 2.3 回答要点 + +- 后台管理系统的开发经验 + +- 介绍RBAC权限模型5张表的关系(用户、角色、权限) + +- 权限框架:Spring security + +### 3.上传数据的安全性你们怎么控制? + +#### 3.1 概述 + +![image-20230521124717749](img/image-20230521124717749.png) + +>这里的安全性,主要说的是,浏览器访问后台,需要经过网络传输,有可能会出现安全的问题 + +解决方案:使用非对称加密(或对称加密),给前端一个公钥让他把数据加密后传到后台,后台负责解密后处理数据 + +#### 3.2 对称加密 + +文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥 + +![image-20230521125012727](img/image-20230521125012727.png) + +- 数据发信方将明文和加密密钥一起经过特殊的加密算法处理后,使其变成复杂的加密密文发送出去, + +- 收信方收到密文后,若想解读出原文,则需要使用加密时用的密钥以及相同加密算法的逆算法对密文进行解密,才能使其回复成可读明文。 +- 在对称加密算法中,使用的密钥只有一个,收发双方都使用这个密钥,这就需要解密方事先知道加密密钥。 + +**优点:** 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。 + +**缺点:** 没有非对称加密安全. + +**用途:** 一般用于保存用户手机号、身份证等敏感但能解密的信息。 + +**常见的对称加密算法有**: `AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256 ` + +#### 3.3 非对称加密 + +两个密钥:公开密钥(publickey)和私有密钥,公有密钥加密,私有密钥解密 + +![image-20230521125136717](img/image-20230521125136717.png) + +解释: 同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端. + +加密与解密: + +- 私钥加密,持有公钥才可以解密 +- 公钥加密,持有私钥才可解密 + +签名: + +- 私钥签名, 持有公钥进行验证是否被篡改过. + +**优点: ** 非对称加密与对称加密相比,其安全性更好; + +**缺点:** 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。 +**用途:** 一般用于签名和认证。私钥服务器保存, 用来加密, 公钥客户拿着用于对于令牌或者签名的解密或者校验使用. + +**常见的非对称加密算法有:** RSA、DSA(数字签名用)、ECC(移动设备用)、RS256 (采用SHA-256 的 RSA 签名) + +#### 3.4 回答要点 + +- 使用非对称加密(或对称加密),给前端一个公钥让他把数据加密后传到后台,后台解密后处理数据 + - 传输的数据很大建议使用对称加密,不过不能保存敏感信息 + - 传输的数据较小,要求安全性高,建议采用非对称加密 + +### 4.你负责项目的时候遇到了哪些比较棘手的问题 + +这个面试题主要考察的是, + +- 你是否有过开发经验 +- 是否是核心开发人员 + +有4个方面可以回答,只要挑出一个回答就行了 + +**(1)设计模式** + +- 工厂模式+策略 +- 责任链模式 + +>回答思路 +> +>1,什么背景(技术问题) +> +>2,过程(解决问题的过程) +> +>3,最终落地方案 + +举例: + +①:介绍登录业务(一开始没有用设计模式,所有的登录方式都柔和在一个业务类中,不过,发现需求经常改) + +②:登录方式经常会增加或更换,每次都要修改业务层代码,所以,经过我的设计,使用了工厂设计模式和策略模式,解决了,经常修改业务层代码的问题 + +③:详细介绍一下工厂模式和策略模式(参考前面设计模式的课程) + +**(2)线上BUG** + +- CPU飙高 +- 内存泄漏 +- 线程死锁 +- .... + +回答方式参考上面的回答思路,具体问题可以参考前面的课程(JVM和多线程相关的面试题) + +**(3)调优** + +- 慢接口 +- 慢SQL +- 缓存方案 + +**(4)组件封装** + +- 分布式锁 +- 接口幂等 +- 分布式事务 +- 支付通用 + + + +### 5.你们项目中日志怎么采集的 + +#### 5.1 问题 + +1,为什么要采集日志? + +日志是定位系统问题的重要手段,可以根据日志信息快速定位系统中的问题 + +2,采集日志的方式有哪些? + +- ELK:即Elasticsearch、Logstash和Kibana三个软件的首字母 + +- 常规采集:按天保存到一个日志文件![image-20230521232726959](img/image-20230521232726959.png) + +#### 5.2 ELK基本架构 + +ELK即Elasticsearch、Logstash和Kibana三个开源软件的缩写 + +![image-20230521232913086](img/image-20230521232913086.png) + +- Elasticsearch + Elasticsearch 全文搜索和分析引擎,对大容量的数据进行接近实时的存储、搜索和分析操作。 + +- Logstash + Logstash是一个数据收集引擎,它可以动态的从各种数据源搜集数据,并对数据进行过滤、分析和统一格式等操作,并将输出结果存储到指定位置上 + +- Kibana + Kibana是一个数据分析和可视化平台,通常与Elasticsearch配合使用,用于对其中的数据进行搜索、分析,并且以统计图标的形式展示。 + +#### 5.3 参考回答 + +- 我们搭建了ELK日志采集系统 + +- 介绍ELK的三个组件: + - Elasticsearch是全文搜索分析引擎,可以对数据存储、搜索、分析 + - Logstash是一个数据收集引擎,可以动态收集数据,可以对数据进行过滤、分析,将数据存储到指定的位置 + - Kibana是一个数据分析和可视化平台,配合Elasticsearch对数据进行搜索,分析,图表化展示 + +### 6.查看日志的命令 + +目前采集日志的方式:按天保存到一个日志文件 + +![image-20230521233150276](img/image-20230521233150276.png) + +也可以在logback配置文件中设置日志的目录和名字 + +![image-20230521233220905](img/image-20230521233220905.png) + + + +需要掌握的Linux中的日志: + +- 实时监控日志的变化 + + 实时监控某一个日志文件的变化:tail -f xx.log;实时监控日志最后100行日志: tail –n 100 -f xx.log + +- 按照行号查询 + + - 查询日志尾部最后100行日志:tail – n 100 xx.log + + - 查询日志头部开始100行日志:head –n 100 xx.log + + - 查询某一个日志行号区间:cat -n xx.log | tail -n +100 | head -n 100 (查询100行至200行的日志) + +- 按照关键字找日志的信息 + + 查询日志文件中包含debug的日志行号:cat -n xx.log | grep "debug" + +- 按照日期查询 + + sed -n '/2023-05-18 14:22:31.070/,/ 2023-05-18 14:27:14.158/p’xx.log + +- 日志太多,处理方式 + + - 分页查询日志信息:cat -n xx.log |grep "debug" | more + + - 筛选过滤以后,输出到一个文件:cat -n xx.log | grep "debug" >debug.txt + +### 7.生产问题怎么排查 + +已经上线的bug排查的思路: + +1,先分析日志,通常在业务中都会有日志的记录,或者查看系统日志,或者查看日志文件,然后定位问题 + +2,远程debug(通常公司的正式环境(生产环境)是不允许远程debug的。一般远程debug都是公司的测试环境,方便调试代码) + +**远程debug配置** + +前提条件:**远程的代码和本地的代码要保持一致** + +1.远程代码需要配置启动参数,把项目打包放到服务器后启动项目的参数: + +```shell +java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 project-1.0-SNAPSHOT.jar +``` + +>**-agentlib:jdwp** 是通知JVM使用(java debug wire protocol)来运行调试环境 +> +>**transport=dt_socket** 调试数据的传送方式 +> +>**server=y** 参数是指是否支持在server模式 +> +>**suspend=n** 是否在调试客户端建立起来后,再执行JVM。 +> +>**address=5005** 调试端口设置为5005,其它端口也可以 + +2.idea中设置远程debug,找到idea中的 Edit Configurations... + +![image-20230521233554657](img/image-20230521233554657.png) + +![image-20230521233600556](img/image-20230521233600556.png) + +3. idea中启动远程debug![image-20230521233715574](img/image-20230521233715574.png) +4. 访问远程服务器,在本地代码中打断点即可调试远程 + +### 8.怎么快速定位系统的瓶颈 + +- 压测(性能测试),项目上线之前测评系统的压力 + - 压测目的:给出系统当前的性能状况;定位系统性能瓶颈或潜在性能瓶颈 + - 指标:响应时间、 QPS、并发数、吞吐量、 CPU利用率、内存使用率、磁盘IO、错误率 + - 压测工具:LoadRunner、Apache Jmeter … + - 后端工程师:根据压测的结果进行解决或调优(接口慢、代码报错、并发达不到要求…) + +- 监控工具、链路追踪工具,项目上线之后监控 + - 监控工具:Prometheus+Grafana + - 链路追踪工具:skywalking、Zipkin + +- 线上诊断工具Arthas(阿尔萨斯),项目上线之后监控、排查 + + - 官网:https://arthas.aliyun.com/ + + - 核心功能: + + ![image-20230521233926897](img/image-20230521233926897.png) + + ![image-20230521233934644](img/image-20230521233934644.png) + diff --git a/面试/面试/微服务面试题-参考回答.md b/面试/面试/微服务面试题-参考回答.md new file mode 100755 index 0000000..0ad282e --- /dev/null +++ b/面试/面试/微服务面试题-参考回答.md @@ -0,0 +1,231 @@ +## 微服务面试题 + +>**面试官:**Spring Cloud 5大组件有哪些? +> +>**候选人:** +> +>早期我们一般认为的Spring Cloud五大组件是 +> +>- Eureka : 注册中心 +>- Ribbon : 负载均衡 +>- Feign : 远程调用 +>- Hystrix : 服务熔断 +>- Zuul/Gateway : 网关 +> +>随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件 +> +>- 注册中心/配置中心 Nacos +> +>- 负载均衡 Ribbon +> +>- 服务调用 Feign +> +>- 服务保护 sentinel +> +>- 服务网关 Gateway +> +>**面试官:**服务注册和发现是什么意思?Spring Cloud 如何实现服务注册发现? +> +>**候选人:** +> +>我理解的是主要三块大功能,分别是服务注册 、服务发现、服务状态监控 +> +>我们当时项目采用的eureka作为注册中心,这个也是spring cloud体系中的一个核心组件 +> +>**服务注册**:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等等 +> +>**服务发现**:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用 +> +>**服务监控**:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除 +> +>**面试官:**我看你之前也用过nacos、你能说下nacos与eureka的区别? +> +>**候选人:** +> +>我们当时xx项目就是采用的nacos作为注册中心,选择nacos还要一个重要原因就是它支持配置中心,不过nacos作为注册中心,也比eureka要方便好用一些,主要相同不同点在于几点: +> +>- 共同点 +> +>Nacos与eureka都支持服务注册和服务拉取,都支持服务提供者心跳方式做健康检测 +> +>- Nacos与Eureka的区别 +> +>①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式 +> +>②临时实例心跳不正常会被剔除,非临时实例则不会被剔除 +> +>③Nacos支持服务列表变更的消息推送模式,服务列表更新更及时 +> +>④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式 +> +>**面试官:**你们项目负载均衡如何实现的 ? +> +>**候选人:** +> +>是这样~~ +> +>在服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 , Feign的底层已经自动集成了Ribbon , 使用起来非常简单 +> +>当发起远程调用时,ribbon先从注册中心拉取服务地址列表,然后按照一定的路由策略选择一个发起远程调用,一般的调用策略是轮询 +> +>**面试官:**Ribbon负载均衡策略有哪些 ? +> +>**候选人:** +> +>我想想啊,有很多种,我记得几个: +> +>- RoundRobinRule:简单轮询服务列表来选择服务器 +> +>- WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小 +> +>- RandomRule:随机选择一个可用的服务器 +> +>- ZoneAvoidanceRule:区域敏感策略,以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询(默认) +> +>**面试官:**如果想自定义负载均衡策略如何实现 ? +> +>**候选人:** +> +>提供了两种方式: +> +>1,创建类实现IRule接口,可以指定负载均衡策略,这个是全局的,对所有的远程调用都起作用 +> +>2,在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略,只是对配置的这个服务生效远程调用 +> +>**面试官:**什么是服务雪崩,怎么解决这个问题? +> +>**候选人:** +> +> +> +>服务雪崩是指一个服务失败,导致整条链路的服务都失败的情形,一般我们在项目解决的话就是两种方案,第一个是服务降级,第二个是服务熔断,如果流量太大的话,可以考虑限流 +> +>服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑 +> +>服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求 +> +>**面试官:**你们的微服务是怎么监控的? +> +>**候选人:** +> +>我们项目中采用的skywalking进行监控的 +> +>1,skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的分析和优化。 +> +>2,我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的bug情况,第一时间修复 +> +>**面试官:**你们项目中有没有做过限流 ? 怎么做的 ? +> +>**候选人:** +> +>我当时做的xx项目,采用就是微服务的架构,因为xx因为,应该会有突发流量,最大QPS可以达到2000,但是服务支撑不住,我们项目都通过压测最多可以支撑1200QPS。因为我们平时的QPS也就不到100,为了解决这些突发流量,所以采用了限流。 +> +>【版本1】 +> +>我们当时采用的nginx限流操作,nginx使用的漏桶算法来实现过滤,让请求以固定的速率处理请求,可以应对突发流量,我们控制的速率是按照ip进行限流,限制的流量是每秒20 +> +>【版本2】 +> +>我们当时采用的是spring cloud gateway中支持局部过滤器RequestRateLimiter来做限流,使用的是令牌桶算法,可以根据ip或路径进行限流,可以设置每秒填充平均速率,和令牌桶总容量 +> +>**面试官:**限流常见的算法有哪些呢? +> +>**候选人:** +> +>比较常见的限流算法有漏桶算法和令牌桶算法 +> +>漏桶算法是把请求存入到桶中,以固定速率从桶中流出,可以让我们的服务做到绝对的平均,起到很好的限流效果 +> +>令牌桶算法在桶中存储的是令牌,按照一定的速率生成令牌,每个请求都要先申请令牌,申请到令牌以后才能正常请求,也可以起到很好的限流作用 +> +>它们的区别是,漏桶和令牌桶都可以处理突发流量,其中漏桶可以做到绝对的平滑,令牌桶有可能会产生突发大量请求的情况,一般nginx限流采用的漏桶,spring cloud gateway中可以支持令牌桶算法 +> +>**面试官**:什么是CAP理论? +> +>**候选人**: +> +>CAP主要是在分布式项目下的一个理论。包含了三项,一致性、可用性、分区容错性 +> +>- 一致性(Consistency)是指更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。 +> +>- 可用性(Availability) 是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 +> +>- 分区容错性(Partition tolerance) 是指分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。 +> +>**面试官**:为什么分布式系统中无法同时保证一致性和可用性? +> +>**候选人**: +> +>嗯,是这样的~~ +> +>首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。 +> +>如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。 +> +>如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。 +> +>**面试官**:什么是BASE理论? +> +>**候选人**: +> +>嗯,这个也是CAP分布式系统设计理论 +> +>BASE是CAP理论中AP方案的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。它的思想包含三方面: +> +>1、Basically Available(基本可用):基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。 +> +>2、Soft state(软状态):即是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。 +> +>3、Eventually consistent(最终一致性):强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。 +> +>**面试官:**你们采用哪种分布式事务解决方案? +> +>**候选人:** +> +>我们当时是xx项目,主要使用到的seata的at模式解决的分布式事务 +> +>seata的AT模型分为两个阶段: +> +>1、阶段一RM的工作:① 注册分支事务 ② 记录undo-log(数据快照)③ 执行业务sql并提交 ④报告事务状态 +> +>2、阶段二提交时RM的工作:删除undo-log即可 +> +>3、阶段二回滚时RM的工作:根据undo-log恢复数据到更新前 +> +>at模式牺牲了一致性,保证了可用性,不过,它保证的是最终一致性 +> +>**面试官:**分布式服务的接口幂等性如何设计? +> +>**候选人:** +> +>嗯,我们当时有一个xx项目的下单操作,采用的token+redis实现的,流程是这样的 +> +>第一次请求,也就是用户打开了商品详情页面,我们会发起一个请求,在后台生成一个唯一token存入redis,key就是用户的id,value就是这个token,同时把这个token返回前端 +> +>第二次请求,当用户点击了下单操作会后,会携带之前的token,后台先到redis进行验证,如果存在token,可以执行业务,同时删除token;如果不存在,则直接返回,不处理业务,就保证了同一个token只处理一次业务,就保证了幂等性 +> +>**面试官:**xxl-job路由策略有哪些? +> +>**候选人:** +> +>xxl-job提供了很多的路由策略,我们平时用的较多就是:轮询、故障转移、分片广播… +> +>**面试官:**xxl-job任务执行失败怎么解决? +> +>**候选人:** +> +>有这么几个操作 +> +>第一:路由策略选择故障转移,优先使用健康的实例来执行任务 +> +>第二,如果还有失败的,我们在创建任务时,可以设置重试次数 +> +>第三,如果还有失败的,就可以查看日志或者配置邮件告警来通知相关负责人解决 +> +>**面试官:**如果有大数据量的任务同时都需要执行,怎么解决? +> +>**候选人:** +> +>我们会让部署多个实例,共同去执行这些批量的任务,其中任务的路由策略是分片广播 +> +>在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行就可以了 \ No newline at end of file diff --git a/面试/面试/框架篇面试题-参考回答.md b/面试/面试/框架篇面试题-参考回答.md new file mode 100755 index 0000000..899cc59 --- /dev/null +++ b/面试/面试/框架篇面试题-参考回答.md @@ -0,0 +1,276 @@ +## 框架篇面试题-参考回答 + +>##### **面试官**:Spring框架中的单例bean是线程安全的吗? +> +>**候选人**: +> +>嗯! +> +>不是线程安全的,是这样的 +> +>当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。 +> +>Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。 +> +>比如:我们通常在项目中使用的Spring bean都是不可可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。 +> +>如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由“**singleton**”变更为“**prototype**”。 +> +>**面试官**:什么是AOP +> +>**候选人**: +> +>aop是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等 +> +>**面试官**:你们项目中有没有使用到AOP +> +>**候选人**: +> +>我们当时在后台管理系统中,就是使用aop来记录了系统的操作日志 +> +>主要思路是这样的,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库 +> +>**面试官**:Spring中的事务是如何实现的 +> +>**候选人**: +> +>spring实现的事务本质就是aop完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 +> +>**面试官**:Spring中事务失效的场景有哪些 +> +>**候选人**: +> +>嗯!这个在项目中之前遇到过,我想想啊 +> +>第一个,如果方法上异常捕获处理,自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了跑出去就行了 +> +>第二个,如果方法抛出检查异常,如果报错也会导致事务失效,最后在spring事务的注解上,就是@Transactional上配置rollbackFor属性为Exception,这样别管是什么异常,都会回滚事务 +> +>第三,我之前还遇到过一个,如果方法上不是public修饰的,也会导致事务失效 +> +>嗯,就能想起来那么多 +> +>**面试官**:Spring的bean的生命周期 +> +>**候选人**: +> +>嗯!,这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的 +> +>首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等这些信息 +> +>在创建bean的时候,第一步是调用构造函数实例化bean +> +>第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowire都是这一步完成 +> +>第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法执行 +> +>第四步是bean的后置处理器BeanPostProcessor,这个是前置处理器 +> +>第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct +> +>第六步是执行了bean的后置处理器BeanPostProcessor,主要是对bean进行增强,有可能在这里产生代理对象 +> +>最后一步是销毁bean +> +>**面试官**:Spring中的循环引用 +> +>**候选人**: +> +>嗯,好的,我来解释一下 +> +>循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A +> +>循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖 +> +>①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象 +> +>②二级缓存:缓存早期的bean对象(生命周期还没走完) +> +>③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的 +> +>**面试官**:那具体解决流程清楚吗? +> +>**候选人**: +> +>第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories +> +>第二,A在初始化的时候需要B对象,这个走B的创建的逻辑 +> +>第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories +> +>第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键 +> +>第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects +> +>第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects +> +>第七,二级缓存中的临时对象A清除 +> +>**面试官**:构造方法出现了循环依赖怎么解决? +> +>**候选人**: +> +>由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建 +> +>**面试官**:SpringMVC的执行流程知道嘛 +> +>**候选人**: +> +>嗯,这个知道的,它分了好多步骤 +> +>1、用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心 +> +>2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。 +> +>3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。 +> +>4、DispatcherServlet调用HandlerAdapter(处理器适配器)。 +> +>5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。 +> +>6、Controller执行完成返回ModelAndView对象。 +> +>7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。 +> +>8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。 +> +>9、ViewReslover解析后返回具体View(视图)。 +> +>10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。 +> +>11、DispatcherServlet响应用户。 +> +>当然现在的开发,基本都是前后端分离的开发的,并没有视图这些,一般都是handler中使用Response直接结果返回 +> +>**面试官**:Springboot自动配置原理 +> +>**候选人**: +> +>嗯,好的,它是这样的。 +> +>在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是: +> +>- @SpringBootConfiguration +> +>- @EnableAutoConfiguration +> +>- @ComponentScan +> +>其中`@EnableAutoConfiguration`是实现自动化配置的核心注解。 +> +>该注解通过`@Import`注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下**META-INF/spring.factories**文件中的所配置的类的全类名。 +> +>在这些配置类中所定义的Bean会根据条件注解所**指定的条件来决定**是否需要将其导入到Spring容器中。 +> +>一般条件判断会有像`@ConditionalOnClass`这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。 +> +>**面试官**:Spring 的常见注解有哪些? +> +>**候选人**: +> +>嗯,这个就很多了 +> +>第一类是:声明bean,有@Component、@Service、@Repository、@Controller +> +>第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse +> +>第三类是:设置作用域 @Scope +> +>第四类是:spring配置相关的,比如@Configuration,@ComponentScan 和 @Bean +> +>第五类是:跟aop相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut +> +>**面试官**:SpringMVC常见的注解有哪些? +> +>**候选人**: +> +>嗯,这个也很多的 +> +>有@RequestMapping:用于映射请求路径; +> +>@RequestBody:注解实现接收http请求的json数据,将json转换为java对象; +> +>@RequestParam:指定请求参数的名称; +> +>@PathViriable:从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数;@ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户端。@RequestHeader:获取指定的请求头数据,还有像@PostMapping、@GetMapping这些。 +> +>**面试官**:Springboot常见注解有哪些? +> +>**候选人**: +> +>嗯~~ +> +>Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 : +> +>- @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能; +>- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项 +>- @ComponentScan:Spring组件扫描 +> +>**面试官**:MyBatis执行流程 +> +>**候选人**: +> +>好,这个知道的,不过步骤也很多 +> +>①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件 +> +>②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由spring进行管理 +> +>③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法 +> +>④操作数据库的接口,Executor执行器,同时负责查询缓存的维护 +> +>⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息 +> +>⑥输入参数映射 +> +>⑦输出结果映射 +> +>**面试官**:Mybatis是否支持延迟加载? +> +>**候选人**: +> +>是支持的~ +> +>延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。 +> +>Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载 +> +>在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false,默认是关闭的 +> +>**面试官**:延迟加载的底层原理知道吗? +> +>**候选人**: +> +>嗯,我想想啊 +> +>延迟加载在底层主要使用的CGLIB动态代理完成的 +> +>第一是,使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了延迟加载的mapper +> +>第二个是当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,再执行sql查询 +> +>第三个是获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了 +> +>**面试官**:Mybatis的一级、二级缓存用过吗? +> +>**候选人**: +> +>嗯~~,用过的~ +> +>mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存 +> +>关于二级缓存需要单独开启 +> +>二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。 +> +>如果想要开启二级缓存需要在全局配置文件和映射文件中开启配置才行。 +> +>**面试官**:Mybatis的二级缓存什么时候会清理缓存中的数据 +> +>**候选人**: +> +>嗯!! +> +>当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。 + diff --git a/面试/面试/消息中间件面试题-参考回答.md b/面试/面试/消息中间件面试题-参考回答.md new file mode 100755 index 0000000..b52578f --- /dev/null +++ b/面试/面试/消息中间件面试题-参考回答.md @@ -0,0 +1,170 @@ +## 消息中间件面试题-参考回答 + +>**面试官**:RabbitMQ-如何保证消息不丢失 +> +>**候选人**: +> +>嗯!我们当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的,这里面就要求了消息的高可用性,我们要保证消息的不丢失。主要从三个层面考虑 +> +>第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据 +> +>第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化 +> +>第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,我们当时设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理 +> +>**面试官**:RabbitMQ消息的重复消费问题如何解决的 +> +>**候选人**: +> +>嗯,这个我们还真遇到过,是这样的,我们当时消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了 +> +>因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了 +> +>**面试官**:那你还知道其他的解决方案吗? +> +>**候选人**: +> +>嗯,我想想~ +> +>其实这个就是典型的幂等的问题,比如,redis分布式锁、数据库的锁都是可以的 +> +>**面试官**:RabbitMQ中死信交换机 ? (RabbitMQ延迟队列有了解过嘛) +> +>**候选人**: +> +>嗯!了解过! +> +>我们当时的xx项目有一个xx业务,需要用到延迟队列,其中就是使用RabbitMQ来实现的。 +> +>延迟队列就是用到了死信交换机和TTL(消息存活时间)实现的。 +> +>如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实现了延迟队列的功能了。 +> +>我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机+TTL要省略了一些步骤 +> +>**面试官**:如果有100万消息堆积在MQ , 如何解决 ? +> +>**候选人**: +> +>我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的 +> +>第一:提高消费者的消费能力 ,可以使用多线程消费任务 +> +>第二:增加更多消费者,提高消费速度 +> +>​ 使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息 +> +>第三:扩大队列容积,提高堆积上限 +> +>可以使用RabbitMQ惰性队列,惰性队列的好处主要是 +> +>①接收到消息后直接存入磁盘而非内存 +> +>②消费者要消费消息时才会从磁盘中读取并加载到内存 +> +>③支持数百万条的消息存储 +> +>**面试官**:RabbitMQ的高可用机制有了解过嘛 +> +>**候选人**: +> +>嗯,熟悉的~ +> +>我们当时项目在生产环境下,使用的集群,当时搭建是镜像模式集群,使用了3台机器。 +> +>镜像队列结构是一主多从,所有操作都是主节点完成,然后同步给镜像节点,如果主节点宕机后,镜像节点会替代成新的主节点,不过在主从同步完成前,主节点就已经宕机,可能出现数据丢失 +> +>**面试官**:那出现丢数据怎么解决呢? +> +>**候选人**: +> +>我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Raft协议,强一致。 +> +>并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定这个是仲裁队列即可 +> +>**面试官**:Kafka是如何保证消息不丢失 +> +>**候选人**: +> +>嗯,这个保证机制很多,在发送消息到消费者接收消息,在每个阶段都有可能会丢失消息,所以我们解决的话也是从多个方面考虑 +> +>第一个是生产者发送消息的时候,可以使用异步回调发送,如果消息发送失败,我们可以通过回调获取失败后的消息信息,可以考虑重试或记录日志,后边再做补偿都是可以的。同时在生产者这边还可以设置消息重试,有的时候是由于网络抖动的原因导致发送不成功,就可以使用重试机制来解决 +> +>第二个在broker中消息有可能会丢失,我们可以通过kafka的复制机制来确保消息不丢失,在生产者发送消息的时候,可以设置一个acks,就是确认机制。我们可以设置参数为all,这样的话,当生产者发送消息到了分区之后,不仅仅只在leader分区保存确认,在follwer分区也会保存确认,只有当所有的副本都保存确认以后才算是成功发送了消息,所以,这样设置就很大程度了保证了消息不会在broker丢失 +> +>第三个有可能是在消费者端丢失消息,kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了 +> +>**面试官**:Kafka中消息的重复消费问题如何解决的 +> +>**候选人**: +> +>kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了 +> +>为了消息的幂等,我们也可以设置唯一主键来进行区分,或者是加锁,数据库的锁,或者是redis分布式锁,都能解决幂等的问题 +> +>**面试官**:Kafka是如何保证消费的顺序性 +> +>**候选人**: +> +>kafka默认存储和消费消息,是不能保证顺序性的,因为一个topic数据可能存储在不同的分区中,每个分区都有一个按照顺序的存储的偏移量,如果消费者关联了多个分区不能保证顺序性 +> +>如果有这样的需求的话,我们是可以解决的,把消息都存储同一个分区下就行了,有两种方式都可以进行设置,第一个是发送消息时指定分区号,第二个是发送消息时按照相同的业务设置相同的key,因为默认情况下分区也是通过key的hashcode值来选择分区的,hash值如果一样的话,分区肯定也是一样的 +> +> +> +>**面试官**:Kafka的高可用机制有了解过嘛 +> +>**候选人**: +> +>嗯,主要是有两个层面,第一个是集群,第二个是提供了复制机制 +> +>kafka集群指的是由多个broker实例组成,即使某一台宕机,也不耽误其他broker继续对外提供服务 +> +>复制机制是可以保证kafka的高可用的,一个topic有多个分区,每个分区有多个副本,有一个leader,其余的是follower,副本存储在不同的broker中;所有的分区副本的内容是都是相同的,如果leader发生故障时,会自动将其中一个follower提升为leader,保证了系统的容错性、高可用性 +> +>**面试官**:解释一下复制机制中的ISR +> +>**候选人**: +> +>ISR的意思是in-sync replica,就是需要同步复制保存的follower +> +>其中分区副本有很多的follower,分为了两类,一个是ISR,与leader副本同步保存数据,另外一个普通的副本,是异步同步数据,当leader挂掉之后,会优先从ISR副本列表中选取一个作为leader,因为ISR是同步保存数据,数据更加的完整一些,所以优先选择ISR副本列表 +> +>**面试官**:Kafka数据清理机制了解过嘛 +> +>**候选人**: +> +>嗯,了解过~~ +> +>Kafka中topic的数据存储在分区上,分区如果文件过大会分段存储segment +> +>每个分段都在磁盘上以索引(xxxx.index)和日志文件(xxxx.log)的形式存储,这样分段的好处是,第一能够减少单个文件内容的大小,查找数据方便,第二方便kafka进行日志清理。 +> +>在kafka中提供了两个日志的清理策略: +> +>第一,根据消息的保留时间,当消息保存的时间超过了指定的时间,就会触发清理,默认是168小时( 7天) +> +>第二是根据topic存储的数据大小,当topic所占的日志文件大小大于一定的阈值,则开始删除最久的消息。这个默认是关闭的 +> +>这两个策略都可以通过kafka的broker中的配置文件进行设置 +> +>**面试官**:Kafka中实现高性能的设计有了解过嘛 +> +>**候选人**: +> +>Kafka 高性能,是多方面协同的结果,包括宏观架构、分布式存储、ISR 数据同步、以及高效的利用磁盘、操作系统特性等。主要体现有这么几点: +> +>消息分区:不受单台服务器的限制,可以不受限的处理更多的数据 +> +>顺序读写:磁盘顺序读写,提升读写效率 +> +>页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问 +> +>零拷贝:减少上下文切换及数据拷贝 +> +>消息压缩:减少磁盘IO和网络IO +> +>分批发送:将消息打包批量发送,减少网络开销 + + + diff --git a/面试/面试/设计模式.md b/面试/面试/设计模式.md new file mode 100755 index 0000000..6fc0f49 --- /dev/null +++ b/面试/面试/设计模式.md @@ -0,0 +1,1015 @@ +# 面试专题-设计模式 + +## 前言 + +在平时的开发中,涉及到设计模式的有两块内容,第一个是我们平时使用的框架(比如spring、mybatis等),第二个是我们自己开发业务使用的设计模式。 + +面试官一般比较关心的是你在开发过程中,有没有使用过设计模式,或者你在简历上写了关于设计模式的描述,那么这样我们就需要重点关心自己开发中用过的设计模式。 + +在平时的业务开发中,其实真正使用设计模式的场景并不多,虽然设计号称有23种之多(不同的纬度可能会更多),但是在项目最常使用的也就几种而已,在面试的过程中,我们主要介绍一种或两种就可以,重点要说的是:在什么业务场景下使用了设计模式,什么设计模式? + +![image-20230521101639915](img/image-20230521101639915.png) + +这次面试部分,我们主要介绍三种设计模式: + +- 工厂方法模式(简单工厂、工厂方法、抽象工厂) +- 策略模式 +- 责任链模式 + + + +## 1 工厂方法模式 + +#### 1.1 概述 + +需求:设计一个咖啡店点餐系统。 + +设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。 + +具体类的设计如下: + +![](img/简单工厂.jpg) + +>1.类图中的符号 +> +>* +:表示public +>* -:表示private +>* #:表示protected +> +>2.泛化关系(继承)用带空心三角箭头的实线来表示 +> +>3.依赖关系使用带箭头的虚线来表示 + +```java +package com.itheima.factory.simple; + +public class CoffeeStore { + + public static void main(String[] args) { + Coffee coffee = orderCoffee("latte"); + System.out.println(coffee.getName()); + } + + + public static Coffee orderCoffee(String type){ + Coffee coffee = null; + if("american".equals(type)){ + coffee = new AmericanCoffee(); + }else if ("latte".equals(type)){ + coffee = new LatteCoffee(); + } + + //添加配料 + coffee.addMilk(); + coffee.addSuqar(); + return coffee; + } +} + +``` + + + +在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的**开闭原则**。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:**解耦**。 + +>开闭原则:**对扩展开放,对修改关闭**。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。 + +三种工厂 + +* 简单工厂模式 +* 工厂方法模式 +* 抽象工厂模式 + +#### 1.2 简单工厂模式 + +简单工厂不是一种设计模式,反而比较像是一种编程习惯。 + +##### 1.2.1 结构 + +简单工厂包含如下角色: + +* 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。 +* 具体产品 :实现或者继承抽象产品的子类 +* 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。 + +##### 1.2.2 实现 + +现在使用简单工厂对上面案例进行改进,类图如下: + +![image-20230521102022928](img/image-20230521102022928.png) + +工厂类代码如下: + +```java +public class SimpleCoffeeFactory { + + public Coffee createCoffee(String type) { + Coffee coffee = null; + if("americano".equals(type)) { + coffee = new AmericanoCoffee(); + } else if("latte".equals(type)) { + coffee = new LatteCoffee(); + } + return coffee; + } +} +``` + +咖啡店 + +```java +package com.itheima.factory.simple; + +public class CoffeeStore { + + public Coffee orderCoffee(String type){ + //通过工厂获得对象,不需要知道对象实现的细节 + SimpleCoffeeFactory factory = new SimpleCoffeeFactory(); + Coffee coffee = factory.createCoffee(type); + //添加配料 + coffee.addMilk(); + coffee.addSuqar(); + return coffee; + } +} +``` + +工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。 + +后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。 + +##### 1.2.3 优缺点 + +**优点:** + +封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。 + +**缺点:** + +增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。 + +##### 1.2.4 扩展 + +**静态工厂** + +在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。代码如下: + +```java +public class SimpleCoffeeFactory { + + public static Coffee createCoffee(String type) { + Coffee coffee = null; + if("americano".equals(type)) { + coffee = new AmericanoCoffee(); + } else if("latte".equals(type)) { + coffee = new LatteCoffee(); + } + return coffe; + } +} +``` + + + + + +#### 1.3 工厂方法模式 + +针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。 + +##### 1.3.1 概念 + +定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。 + +##### 1.3.2 结构 + +工厂方法模式的主要角色: + +* 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。 +* 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。 +* 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。 +* 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。 + +##### 1.3.3 实现 + +使用工厂方法模式对上例进行改进,类图如下: + +![image-20230521102122950](img/image-20230521102122950.png) + +流程: + +![image-20230521102156863](img/image-20230521102156863.png) + +代码如下: + +抽象工厂: + +```java +public interface CoffeeFactory { + + Coffee createCoffee(); +} +``` + +具体工厂: + +```java +public class LatteCoffeeFactory implements CoffeeFactory { + + public Coffee createCoffee() { + return new LatteCoffee(); + } +} + +public class AmericanCoffeeFactory implements CoffeeFactory { + + public Coffee createCoffee() { + return new AmericanCoffee(); + } +} +``` + +咖啡店类: + +```java +public class CoffeeStore { + + private CoffeeFactory factory; + + public CoffeeStore(CoffeeFactory factory) { + this.factory = factory; + } + + public Coffee orderCoffee(String type) { + Coffee coffee = factory.createCoffee(); + coffee.addMilk(); + coffee.addsugar(); + return coffee; + } +} +``` + +从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。 + +工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。 + +##### 1.3.4 优缺点 + +**优点:** + +- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程; +- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则; + +**缺点:** + +* 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。 + +#### 1.4 抽象工厂模式 + +前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。 + +这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。 + +本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示 + +- 产品族:一个品牌下面的所有产品;例如华为下面的电脑、手机称为华为的产品族; +- 产品等级:多个品牌下面的同种产品;例如华为和小米都有手机电脑为一个产品等级; + +![image-20220913115948157](img/image-20220913115948157.png) + +##### 1.4.1 概念 + +是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。 + +抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。 + +**一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂** + +##### 1.4.2 结构 + +抽象工厂模式的主要角色如下: + +* 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。 +* 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。 +* 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。 +* 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。 + +##### 1.4.3 实现 + +现咖啡店业务发生改变,不仅要生产**咖啡**还要生产**甜点** + +- 同一个产品等级(产品分类) + - 咖啡:拿铁咖啡、美式咖啡 + - 甜点:提拉米苏、抹茶慕斯 +- 同一个风味,就是同一个产品族(相当于同一个品牌) + - 美式风味:美式咖啡、抹茶慕斯 + - 意大利风味:拿铁咖啡、提拉米苏 + +要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。 + +所以这个案例可以使用抽象工厂模式实现。类图如下: + +![image-20230521102319997](img/image-20230521102319997.png) + +>实现关系使用带空心三角箭头的虚线来表示 + +整体调用思路: + +![image-20220913124542154](img/image-20220913124542154.png) + +##### 1.4.4 优缺点 + +**优点:** + +当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 + +**缺点:** + +当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。 + +##### 1.4.5 使用场景 + +* 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。 + +* 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。 + +* 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。 + +如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。 + + + +## 2 策略模式 + +### 2.1 概述 + +先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。 + +![image-20230521102452501](img/image-20230521102452501.png) + +作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。 + +![image-20220913125131383](img/image-20220913125131383.png) + +**定义:** + +​ 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。 + + + +### 2.2 结构 + +策略模式的主要角色如下: + +* 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。 +* 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。 +* 环境(Context)类:持有一个策略类的引用,最终给客户端调用。 + + + +### 2.3 案例实现 + +【例】促销活动 + +一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下: + +![image-20220913125209804](img/image-20220913125209804.png) + +>聚合关系可以用带空心菱形的实线来表示 + +代码如下: + +定义百货公司所有促销活动的共同接口 + +```java +public interface Strategy { + void show(); +} +``` + +定义具体策略角色(Concrete Strategy):每个节日具体的促销活动 + +```java +//为春节准备的促销活动A +public class StrategyA implements Strategy { + + public void show() { + System.out.println("买一送一"); + } +} + +//为中秋准备的促销活动B +public class StrategyB implements Strategy { + + public void show() { + System.out.println("满200元减50元"); + } +} + +//为圣诞准备的促销活动C +public class StrategyC implements Strategy { + + public void show() { + System.out.println("满1000元加一元换购任意200元以下商品"); + } +} +``` + +定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员 + +```java +public class SalesMan { + //持有抽象策略角色的引用 + private Strategy strategy; + + public SalesMan(Strategy strategy) { + this.strategy = strategy; + } + + //向客户展示促销活动 + public void salesManShow(){ + strategy.show(); + } +} +``` + +### 2.4 综合案例 + +#### 2.4.1 概述 + +下图是gitee的登录的入口,其中有多种方式可以进行登录 + +- 用户名密码登录 + +- 短信验证码登录 + +- 微信登录 + +- QQ登录 + +- .... + +![image-20230521104504491](img/image-20230521104504491.png) + +#### 2.4.2 目前已实现的代码 + +(1)登录接口 + +| | 说明 | +| -------- | --------------- | +| 请求方式 | POST | +| 路径 | /api/user/login | +| 参数 | LoginReq | +| 返回值 | LoginResp | + +请求参数:LoginReq + +```java +@Data +public class LoginReq { + + private String name; + private String password; + + private String phone; + private String validateCode;//手机验证码 + + private String wxCode;//用于微信登录 + /** + * account : 用户名密码登录 + * sms : 手机验证码登录 + * we_chat : 微信登录 + */ + private String type; +} +``` + +响应参数:LoginResp + +```java +@Data +public class LoginResp{ + private Integer userId; + private String userName; + private String roleCode; + private String token; //jwt令牌 + private boolean success; + +} +``` + +控制层LoginController + +```java +@RestController +@RequestMapping("/api/user") +public class LoginController { + + @Autowired + private UserService userService; + + + @PostMapping("/login") + public LoginResp login(@RequestBody LoginReq loginReq){ + return userService.login(loginReq); + } +} +``` + +业务层UserService + +```java +@Service +public class UserService { + + public LoginResp login(LoginReq loginReq){ + + if(loginReq.getType().equals("account")){ + System.out.println("用户名密码登录"); + + //执行用户密码登录逻辑 + + return new LoginResp(); + + }else if(loginReq.getType().equals("sms")){ + System.out.println("手机号验证码登录"); + + //执行手机号验证码登录逻辑 + + return new LoginResp(); + }else if (loginReq.getType().equals("we_chat")){ + System.out.println("微信登录"); + + //执行用户微信登录逻辑 + + return new LoginResp(); + } + LoginResp loginResp = new LoginResp(); + loginResp.setSuccess(false); + System.out.println("登录失败"); + return loginResp; + } +} +``` + +>注意:我们重点讲的是设计模式,并不是登录的逻辑,所以以上代码并没有真正的实现登录功能 + +(2)问题分析 + +- 业务层代码大量使用到了if...else,在后期阅读代码的时候会非常不友好,大量使用if...else性能也不高 +- 如果业务发生变更,比如现在新增了QQ登录方式,这个时候需要修改业务层代码,违反了开闭原则 + +解决: + +使用**工厂方法设计模式+策略模式**解决 + +#### 2.4.3 代码改造(工厂+策略) + +(1)整体思路 + +改造之后,不在service中写业务逻辑,让service调用工厂,然后通过service传递不同的参数来获取不同的登录策略(登录方式) + +![image-20230521105346337](img/image-20230521105346337.png) + +(2)具体实现 + +抽象策略类:UserGranter + +```java +/** + * 抽象策略类 + */ +public interface UserGranter{ + + /** + * 获取数据 + * @param loginReq 传入的参数 + * @return map值 + */ + LoginResp login(LoginReq loginReq); +} +``` + +具体的策略:AccountGranter、SmsGranter、WeChatGranter + +```java +/** + * 策略:账号登录 + **/ +@Component +public class AccountGranter implements UserGranter{ + + @Override + public LoginResp login(LoginReq loginReq) { + + System.out.println("登录方式为账号登录" + loginReq); + // TODO + // 执行业务操作 + + return new LoginResp(); + } +} +/** + * 策略:短信登录 + */ +@Component +public class SmsGranter implements UserGranter{ + + @Override + public LoginResp login(LoginReq loginReq) { + + System.out.println("登录方式为短信登录" + loginReq); + // TODO + // 执行业务操作 + + return new LoginResp(); + } +} +/** + * 策略:微信登录 + */ +@Component +public class WeChatGranter implements UserGranter{ + + @Override + public LoginResp login(LoginReq loginReq) { + + System.out.println("登录方式为微信登录" + loginReq); + // TODO + // 执行业务操作 + + return new LoginResp(); + } +} +``` + +工程类:UserLoginFactory + +```java +/** + * 操作策略的上下文环境类 工具类 + * 将策略整合起来 方便管理 + */ +@Component +public class UserLoginFactory implements ApplicationContextAware { + + private static Map granterPool = new ConcurrentHashMap<>(); + + @Autowired + private LoginTypeConfig loginTypeConfig; + + /** + * 从配置文件中读取策略信息存储到map中 + * { + * account:accountGranter, + * sms:smsGranter, + * we_chat:weChatGranter + * } + * + * @param applicationContext + * @throws BeansException + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + loginTypeConfig.getTypes().forEach((k, y) -> { + granterPool.put(k, (UserGranter) applicationContext.getBean(y)); + }); + } + + /** + * 对外提供获取具体策略 + * + * @param grantType 用户的登录方式,需要跟配置文件中匹配 + * @return 具体策略 + */ + public UserGranter getGranter(String grantType) { + UserGranter tokenGranter = granterPool.get(grantType); + return tokenGranter; + } + +} +``` + +在application.yml文件中新增自定义配置 + +```yaml +login: + types: + account: accountGranter + sms: smsGranter + we_chat: weChatGranter +``` + +新增读取数据配置类 + +```java +Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "login") +public class LoginTypeConfig { + + private Map types; + +} + +``` + +改造service代码 + +```java +@Service +public class UserService { + + @Autowired + private UserLoginFactory factory; + + public LoginResp login(LoginReq loginReq){ + + UserGranter granter = factory.getGranter(loginReq.getType()); + if(granter == null){ + LoginResp loginResp = new LoginResp(); + loginResp.setSuccess(false); + return loginResp; + } + LoginResp loginResp = granter.login(loginReq); + return loginResp; + } +} +``` + +大家可以看到我们使用了设计模式之后,业务层的代码就清爽多了,如果后期有新的需求改动,比如加入了QQ登录,我们只需要添加对应的策略就可以,无需再改动业务层代码。 + +#### 2.4.4 举一反三 + +其实像这样的需求,在日常开发中非常常见,场景有很多,以下的情景都可以使用工厂模式+策略模式解决比如: + +- 订单的支付策略 + - 支付宝支付 + - 微信支付 + - 银行卡支付 + - 现金支付 +- 解析不同类型excel + - xls格式 + - xlsx格式 +- 打折促销 + - 满300元9折 + - 满500元8折 + - 满1000元7折 +- 物流运费阶梯计算 + - 5kg以下 + - 5kg-10kg + - 10kg-20kg + - 20kg以上 + +一句话总结:**只要代码中有冗长的 if-else 或 switch 分支判断都可以采用策略模式优化** + +## 3 责任链设计模式 + +### 3.1 概述 + +在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。 + +**定义:** + +又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。 + +比较常见的springmvc中的拦截器,web开发中的filter过滤器 + +![image-20230521111455359](img/image-20230521111455359.png) + +### 3.2 结构 + +职责链模式主要包含以下角色: + +* 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。 +* 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。 +* 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。 + + + +### 3.2 案例实现 + +处理订单的操作 + +![image-20230521111943231](img/image-20230521111943231.png) + +类图: + +![image-20230521112108550](img/image-20230521112108550.png) + +代码: + +抽象处理者 + +```java +package com.itheima.designpattern.chain; + +/** + * 抽象处理者 + */ +public abstract class Handler { + + protected Handler handler; + + public void setNext(Handler handler) { + this.handler = handler; + } + + /** + * 处理过程 + * 需要子类进行实现 + */ + public abstract void process(OrderInfo order); +} +``` + +订单信息类: + +```java +package com.itheima.designpattern.chain; + + +import java.math.BigDecimal; + +public class OrderInfo { + + private String productId; + private String userId; + + private BigDecimal amount; + + public String getProductId() { + return productId; + } + + public void setProductId(String productId) { + this.productId = productId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } +} +``` + + + +具体处理者: + +```java +/** + * 订单校验 + */ +public class OrderValidition extends Handler { + + @Override + public void process(OrderInfo order) { + System.out.println("校验订单基本信息"); + //校验 + handler.process(order); + } + +} + +/** + * 补充订单信息 + */ +public class OrderFill extends Handler { + @Override + public void process(OrderInfo order) { + System.out.println("补充订单信息"); + handler.process(order); + } + +} + +/** + * 计算金额 + */ +public class OrderAmountCalcuate extends Handler { + @Override + public void process(OrderInfo order) { + System.out.println("计算金额-优惠券、VIP、活动打折"); + handler.process(order); + } + +} + +/** + * 订单入库 + */ +public class OrderCreate extends Handler { + @Override + public void process(OrderInfo order) { + System.out.println("订单入库"); + } +} + +``` + +客户类: + +```java +public class Application { + + public static void main(String[] args) { + //检验订单 + Handler orderValidition = new OrderValidition(); + //补充订单信息 + Handler orderFill = new OrderFill(); + //订单算价 + Handler orderAmountCalcuate = new OrderAmountCalcuate(); + //订单落库 + Handler orderCreate = new OrderCreate(); + + //设置责任链路 + orderValidition.setNext(orderFill); + orderFill.setNext(orderAmountCalcuate); + orderAmountCalcuate.setNext(orderCreate); + + //开始执行 + orderValidition.process(new OrderInfo()); + } + +} +``` + + + +### 3.3 优缺点 + +**优点** + +* 降低了对象之间的耦合度 + + 该模式降低了请求发送者和接收者的耦合度。 + +* 增强了系统的可扩展性 + + 可以根据需要增加新的请求处理类,满足开闭原则。 + +* 增强了给对象指派职责的灵活性 + + 当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。 + +* 责任链简化了对象之间的连接 + + 一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。 + +* 责任分担 + + 每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。 + +**缺点:** + +* 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。 +* 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。 +* 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。 + +### 3.4 举一反三 + +- 内容审核(视频、文章、课程….) + + ![image-20230521112731617](img/image-20230521112731617.png) + +- 订单创建 + + ![image-20230521112802886](img/image-20230521112802886.png) + +- 简易流程审批 + + ![image-20230521112831065](img/image-20230521112831065.png) + + + + + + + + + + + + + + + + + + + + +