Tech-Paul

work hard, play hard

随着软件开发技术的不断发展,现在出现了很多种不同的开发模式,其实敏捷开发已经成为现在很多企业开发应用程序都想要选择的开发方案。

敏捷开发:

敏捷开发(Agile)是一种以人为核心、迭代、循序渐进的开发方法。在敏捷开发中,软件项目的构建被切分成多个子项目,各个子项目的成果都经过测试,具备集成和可运行的特征。简单地来说,敏捷开发并不追求前期完美的设计、完美编码,而是力求在很短的周期内开发出产品的核心功能,尽早发布出可用的版本。然后在后续的生产周期内,按照新需求不断迭代升级,完善产品。

敏捷软件开发特点:

首要任务是尽早地、持续地交付可评价的软件,以使客户满意。

乐于接受需求变更,即使在开发后期也是如此。敏捷软件开发能够驾驭需求的变化,从 而为客户赢得竞争优势。

频繁交付可使用的软件,交付的间隔越短越好,可以从几个月缩减到几个星期。

在整个项目开发期间,业务人员和开发人员必须朝夕工作在一起。

围绕那些有推动力的人们来构建项目,给予他们所需的环境和支持,并且相信他们能够把工作做好。

开发团队及在开发团队内部进行最快速、有效的传递信息的方法是面对面交谈。

可使用的软件是进度的主要衡量指标。

提倡可持续发展。出资人、开发人员及使用者应该共同维持稳定的开发速度。

为了增强敏捷能力,应持续关注技术上的杰出成果和良好的设计。

简洁,最小化那些没有必要投入的工作量是至关重要的。

最好的架构、需求和设计都源于自我组织的团队。

团队定期反思如何变得更有战斗力,然后相应地转变井调整其行为。

敏捷开发模式的分类

敏捷开发的实现主要包括 SCRUM、XP(极限编程)、Crystal Methods、FDD(特性驱动开发)等等。其中 SCRUM 与 XP 最为流行。

同样是敏捷开发,XP 极限编程 更侧重于实践,并力求把实践做到极限。这一实践可以是测试先行,也可以是结对编程等,关键要看具体的应用场景。

敏捷开发与 DevOps:

DevOps 是 Development 和 Operations 的合成词,其目标是要加强开发人员、测试人员、运维人员之间的沟通协调。如何实现这一目标呢?需要我们的项目做到持续集成、持续交付、持续部署。

敏捷开发是一种快速开发应用程序的方式,以最快的途径完成应用程序的开发,帮助企业提高应用程序的交付速度。

瀑布式开发

瀑布式开发是由 WW.Royce 在 1970 年提出的软件开发模型,是一种比较老的计算机软件开发模式, 也是典型的预见性的开发模式。在瀑布式开发中,开发严格遵循预先计划的需求分析、设计、编码、集成、测试、维护的步骤进行,步骤的成果作为衡量进度的方法,例如需求规格、设计文档、测试计划和代码审阅等。 瀑布式开发最早强调系统开发应有完整的周期,且 必须完整地经历每个周期内的每个开发阶段,井系统化地考量分析所涉及的技术、时间与资源投入等。

瀑布式开发的主要问题是它的严格分级导致自由度降低,项目早期即作出承诺会导致对后 期需求的变化难以调整且代价很大,这在需求不明晰并且在项目进行过程中可能有变化的情况 下基本上是不可行的。

迭代式开发

法代式开发也被称为迭代增量式开发,是一种与传统的瀑布式开发相反的软件开发过程, 它弥补了传统开发方式的一些弱点,有更高的成功率。在迭代式开发中,整个开发工作被组织 为一系列短小的、固定长度的小项目,每次选代都包括需求分析、设计、实现与测试。采用迭代式开发时, 工作可以在需求被完整地确定之前启动, 并在一次选代中完成系统的一部分功能 或业务,再通过客户的反馈来细化需求,并开始新一轮的迭代。

迭代式开发有如下特点:

每次只设计和实现产品的一部分;

一步一步地完成;

每次设计和实现一个阶段,这叫作一个迭代。

Dockerfile

Dockerfile 是用于定义一个 Docker 镜像(Image)的文本文件。它包含了从基础镜像开始,逐步构建和配置该镜像所需要的所有步骤和指令。它描述了如何构建一个容器镜像,并为该镜像提供了具体的环境配置。

作用:
定义基础镜像:指定镜像的起始点,例如使用官方的 Node.js、Python、Java 等镜像作为基础。
安装依赖:在镜像中安装需要的软件包或依赖项。
复制项目文件:将本地的项目文件复制到容器中。
暴露端口:指定容器将要开放的端口,使其可以与外部通信。
指定启动命令:定义容器启动时的命令或进程。

如果是通过自定义镜像的方式安装数据库,可能会有 Dockerfile 文件。它通常包含以下内容:

  • 基础镜像选择:指定一个基础操作系统镜像作为起点,例如 FROM ubuntu:latest 等。
  • 安装数据库软件:通过运行一系列命令来安装数据库软件,如 RUN apt-get update && apt-get install -y mysql-server(以 MySQL 为例)。
  • 配置数据库:可以设置数据库的初始配置,如设置密码、字符集等。
  • 暴露端口:EXPOSE 3306(MySQL 默认端口为例),以便外部可以访问数据库。

docker-compose.yml (如果使用 Docker Compose 管理服务)

docker-compose.yml 是一个 YAML 文件,用于定义和运行多个 Docker 容器应用。它通过声明不同的服务、网络、卷等来帮助我们方便地配置和启动一个由多个容器组成的应用环境。

作用:
管理多个容器:当项目包含多个服务(如前端、后端、数据库等)时,docker-compose.yml 可以将它们统一配置和管理。
简化部署和启动:通过一个命令 docker-compose up 就能启动整个应用的所有服务,无需手动管理每个容器。
环境配置:通过 docker-compose.yml,可以轻松配置服务的端口、环境变量、依赖关系、网络等。

服务定义:

  • 定义数据库服务,如:
1
2
3
4
5
6
7
services:
db:
image: your_database_image
environment:
- MYSQL_ROOT_PASSWORD=your_password
ports:
- "3306:3306"
  • 可以设置环境变量来配置数据库,如设置密码、数据库名称等。
  • 网络配置:可以定义网络,使得不同的服务可以在同一个网络中通信。
  • 卷挂载:如果需要持久化数据,可以挂载外部存储卷,例如:
1
2
3
volumes:
- db_data:/var/lib/mysql
```

关系

Dockerfile 主要用于 构建容器镜像,它定义了单个容器的配置。
docker-compose.yml 主要用于 管理多个容器服务,它定义了如何运行多个容器,并且可以让你通过一个命令启动或停止所有相关的容器。

协作

在项目中,通常是这样的:

使用 Dockerfile 来定义如何构建每个服务的 Docker 镜像。
使用 docker-compose.yml 来定义和管理多个服务,甚至定义它们之间的依赖关系。

常用命令

1. 容器生命周期管理

启动/停止容器

1
2
3
4
5
6
docker start <容器名/ID>          # 启动已停止的容器
docker stop <容器名/ID> # 停止运行中的容器(优雅停止)
docker restart <容器名/ID> # 重启容器
docker kill <容器名/ID> # 强制停止容器
docker pause <容器名/ID> # 暂停容器
docker unpause <容器名/ID> # 恢复暂停的容器

创建/删除容器

1
2
3
4
5
docker create <镜像名>            # 创建容器但不启动
docker run <镜像名> # 创建并启动容器
docker rm <容器名/ID> # 删除已停止的容器
docker rm -f <容器名/ID> # 强制删除运行中的容器
docker container prune # 删除所有停止的容器

2. 容器操作

查看容器

1
2
3
4
docker ps                       # 查看运行中的容器
docker ps -a # 查看所有容器(包括停止的)
docker ps -q # 只显示容器ID
docker ps --filter "status=exited" # 过滤特定状态的容器

进入容器

1
2
docker exec -it <容器名/ID> /bin/bash   # 进入运行中的容器
docker attach <容器名/ID> # 附加到运行中的容器

容器日志

1
2
3
docker logs <容器名/ID>          # 查看容器日志
docker logs -f <容器名/ID> # 实时查看日志(类似tail -f)
docker logs --tail 100 <容器名/ID> # 查看最后100行日志

3. 镜像管理

查看镜像

1
2
3
docker images                   # 列出本地镜像
docker images -a # 列出所有镜像(包括中间层)
docker image ls # 同上(新语法)

镜像操作

1
2
3
4
5
docker pull <镜像名:标签>        # 拉取镜像
docker push <镜像名:标签> # 推送镜像到仓库
docker rmi <镜像名/ID> # 删除镜像
docker image prune # 删除未被使用的镜像
docker build -t <标签> . # 构建镜像(当前目录Dockerfile)

4. 网络管理

1
2
3
4
5
docker network ls               # 列出所有网络
docker network inspect <网络名> # 查看网络详情
docker network create <网络名> # 创建自定义网络
docker network connect <网络名> <容器名> # 连接容器到网络
docker network disconnect <网络名> <容器名> # 从网络断开容器

5. 数据卷管理

1
2
3
4
5
docker volume ls                # 列出数据卷
docker volume create <卷名> # 创建数据卷
docker volume inspect <卷名> # 查看数据卷详情
docker volume rm <卷名> # 删除数据卷
docker volume prune # 删除未使用的数据卷

6. 系统信息与维护

1
2
3
4
5
6
docker info                     # 显示Docker系统信息
docker version # 显示Docker版本信息
docker stats # 显示容器资源使用统计
docker top <容器名> # 查看容器内运行的进程
docker system df # 查看磁盘使用情况
docker system prune # 清理所有未使用的对象

7. Docker Compose 常用命令

1
2
3
4
5
6
7
docker-compose up               # 启动所有服务
docker-compose up -d # 后台启动服务
docker-compose down # 停止并移除所有容器
docker-compose ps # 列出所有服务容器
docker-compose logs # 查看服务日志
docker-compose build # 构建或重新构建服务
docker-compose restart # 重启所有服务

8. 实用组合命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 删除所有停止的容器和未使用的镜像
docker system prune -a

# 批量停止所有运行中的容器
docker stop $(docker ps -q)

# 批量删除所有容器(包括运行中的)
docker rm -f $(docker ps -aq)

# 查看容器资源使用情况(动态更新)
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

# 导出容器为镜像
docker export <容器ID> > container.tar

# 导入镜像
docker import container.tar new-image:tag

9. 容器与宿主机文件操作

1
2
docker cp <容器名>:<容器路径> <宿主机路径>  # 从容器复制文件到宿主机
docker cp <宿主机路径> <容器名>:<容器路径> # 从宿主机复制文件到容器

在 Docker Desktop 的容器管理界面中,每个 Tab 对应不同的容器操作和监控功能。以下是 6 个 Tab 的核心作用及使用场景详解:


1. Logs(日志)

作用:实时查看容器的标准输出(stdout)和标准错误(stderr)日志。
使用场景
• 调试应用程序运行时错误(如服务启动失败、异常报错)。
• 监控容器内服务的运行状态(如 HTTP 请求日志、数据库查询日志)。
关键操作
暂停/恢复:冻结日志滚动便于阅读。
时间戳:显示日志的生成时间。
过滤:根据关键词筛选日志内容。


2. Inspect(详细信息)

作用:以 JSON 格式展示容器的完整元数据(包括配置、网络、存储、状态等)。
核心信息
Config:环境变量(Env)、启动命令(Cmd)、工作目录(WorkingDir)。
NetworkSettings:IP 地址、端口映射(Ports)、网关(Gateway)。
Mounts:绑定挂载(Bind Mounts)和卷挂载(Volumes)的宿主机路径与容器路径映射。
使用场景
• 检查容器配置是否与预期一致(如端口是否暴露、环境变量是否正确)。
• 获取容器网络信息用于跨容器通信调试。


3. Bind Mounts(绑定挂载)

作用:管理容器与宿主机之间的文件或目录绑定挂载(Bind Mounts)。
核心功能
路径映射:显示宿主机路径(Source)与容器内路径(Destination)的对应关系。
读写模式:检查挂载是否为只读(ro)或可读写(rw)。
使用场景
• 验证配置文件(如 nginx.conf)是否正确挂载到容器内。
• 调试数据持久化问题(如容器重启后数据丢失)。


4. Exec(执行命令)

作用:在运行的容器内启动交互式命令行会话(类似 docker exec -it)。
使用场景
调试容器:手动执行命令检查服务状态(如 ps aux 查看进程、cat /etc/resolv.conf 检查 DNS)。
临时操作:修改配置文件或清理临时文件(如删除错误日志)。
注意事项
• 需要容器内包含 /bin/bash/bin/sh 等 Shell 环境。
• 避免在生产容器中执行高风险命令(如 rm -rf /)。


5. Files(文件)

作用:以图形化界面浏览和编辑容器内部的文件系统(类似 docker cp)。
核心功能
文件浏览:查看容器内的目录结构和文件内容(如 /var/log/nginx/error.log)。
上传/下载:在宿主机和容器之间传输文件。
编辑文件:直接修改容器内的配置文件(需谨慎操作)。
使用场景
• 快速检查容器内生成的文件(如日志、缓存)。
• 修复配置错误无需重建镜像。


6. Stats(统计信息)

作用:实时监控容器的资源使用情况(类似 docker stats)。
核心指标
CPU:使用率及核心占用比例。
内存:已用内存(Usage)与限制值(Limit)。
网络 I/O:上行/下行流量速率。
磁盘 I/O:读写速率。
使用场景
• 识别性能瓶颈(如内存泄漏、CPU 过载)。
• 调整资源限制(如通过 --memory 限制内存)。


总结:不同 Tab 的适用场景

Tab 核心用途 典型操作示例
Logs 调试服务运行状态 检查 ERROR 级别的日志定位崩溃原因
Inspect 验证容器配置 确认端口映射 "80/tcp": [{"HostPort": "8080"}]
Bind Mounts 管理持久化存储 检查数据卷是否挂载到 /data
Exec 容器内交互式操作 执行 curl localhost:8080/health 测试服务
Files 文件系统管理 编辑 /etc/mysql/my.cnf 修改数据库配置
Stats 监控资源消耗 发现内存使用率持续 90% 以上触发告警

补充说明

与 CLI 的对应关系
这些 Tab 的功能大部分可通过 docker logsdocker inspectdocker exec 等命令实现,但 Docker Desktop 提供了更直观的图形化操作。
安全性警告
• 避免在 ExecFiles 中直接修改生产容器,优先通过镜像重建和滚动更新。
Bind Mounts 的宿主机路径权限需与容器内用户权限匹配(防止权限错误)。

ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。

1
2
3
4
5
6
7
8
9
const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

ES6 中,Promise 是用于处理异步操作的一种机制,它允许你更加清晰地编写异步代码。Promise 对象有多个方法来处理异步操作,常用的方法包括:

1. Promise.resolve()

Promise.resolve() 返回一个已解决(fulfilled)状态的 Promise 对象,且其值为传入的参数。

  • 如果传入的参数是一个 Promise,Promise.resolve() 会返回该 Promise。
  • 如果传入的是一个非 Promise 的值,Promise.resolve() 会返回一个已解决的 Promise,并将这个值作为该 Promise 的返回值。

示例:

1
2
3
4
let p1 = Promise.resolve(42)
p1.then((value) => {
console.log(value) // 输出: 42
})
  • 如果传入的是一个 thenable(即一个有 then 方法的对象),它也会被处理为一个 Promise。

示例:

1
2
3
4
5
6
7
8
let p2 = Promise.resolve({
then: function (resolve, reject) {
resolve(100)
},
})
p2.then((value) => {
console.log(value) // 输出: 100
})

2. Promise.reject()

Promise.reject() 返回一个已拒绝(rejected)状态的 Promise 对象,且其拒绝原因为传入的参数。

示例:

1
2
3
4
let p3 = Promise.reject(new Error("Something went wrong"))
p3.catch((error) => {
console.log(error) // 输出: Error: Something went wrong
})

3. Promise.all()

Promise.all() 方法接受一个包含多个 Promise 的可迭代对象(通常是一个数组),并返回一个新的 Promise。这个新的 Promise 会等待所有传入的 Promise 都解决后再被解决。如果其中一个 Promise 被拒绝,Promise.all() 会立即拒绝。

  • 如果所有 Promise 都成功,返回一个包含每个 Promise 返回值的数组。
  • 如果其中一个 Promise 失败,返回第一个失败的错误。

示例:

1
2
3
4
5
6
7
8
let p4 = Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
])
p4.then((values) => {
console.log(values) // 输出: [1, 2, 3]
})

如果其中一个 Promise 被拒绝:

1
2
3
4
5
6
7
8
let p5 = Promise.all([
Promise.resolve(1),
Promise.reject("Error"),
Promise.resolve(3),
])
p5.catch((error) => {
console.log(error) // 输出: Error
})

4. Promise.allSettled() (ES2020)

Promise.allSettled() 方法接受一个包含多个 Promise 的可迭代对象,并返回一个新的 Promise,这个新的 Promise 会等待所有传入的 Promise 都完成,不论它们是成功还是失败。返回的结果是每个 Promise 的状态(fulfilled 或 rejected)和对应的值或拒绝原因。

  • 返回的数组中,每个元素都是一个对象,包含 statusvaluereason 属性。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let p6 = Promise.allSettled([
Promise.resolve(1),
Promise.reject("Error"),
Promise.resolve(3),
])
p6.then((results) => {
console.log(results)
// 输出:
// [
// { status: "fulfilled", value: 1 },
// { status: "rejected", reason: "Error" },
// { status: "fulfilled", value: 3 }
// ]
})

5. Promise.race()

Promise.race() 方法接受一个包含多个 Promise 的可迭代对象,并返回一个新的 Promise。这个新的 Promise 会在传入的 Promise 中 第一个解决或拒绝 时解决,并返回该 Promise 的值或拒绝原因。

示例:

1
2
3
4
5
6
7
8
9
let p7 = Promise.race([
new Promise((resolve) => setTimeout(resolve, 100, "one")),
new Promise((resolve) => setTimeout(resolve, 200, "two")),
new Promise((resolve) => setTimeout(resolve, 50, "three")),
])

p7.then((value) => {
console.log(value) // 输出: 'three' (因为它是最先解决的)
})

6. Promise.any() (ES2021)

Promise.any() 方法接受一个包含多个 Promise 的可迭代对象,并返回一个新的 Promise。这个新的 Promise 会在传入的 Promise 中 第一个成功 时解决。如果所有传入的 Promise 都被拒绝,返回一个 AggregateError(表示多个错误)。

示例:

1
2
3
4
5
6
7
8
9
let p8 = Promise.any([
Promise.reject("Error 1"),
Promise.resolve("Success 2"),
Promise.resolve("Success 3"),
])

p8.then((value) => {
console.log(value) // 输出: "Success 2" (因为它是第一个成功的 Promise)
})

如果所有 Promise 都被拒绝:

1
2
3
4
5
let p9 = Promise.any([Promise.reject("Error 1"), Promise.reject("Error 2")])

p9.catch((error) => {
console.log(error) // 输出: AggregateError: All promises were rejected
})

7. Promise.finally()

Promise.finally() 方法接受一个回调函数,这个回调函数会在 Promise 结束时(无论是解决还是拒绝)执行。finally() 不会影响 Promise 的最终结果,返回的 Promise 保持原有的状态。

示例:

1
2
3
4
5
6
7
8
9
let p10 = Promise.resolve("Success")

p10
.finally(() => {
console.log("Promise has been settled") // 不管成功还是失败,都会执行
})
.then((value) => {
console.log(value) // 输出: Success
})

finally() 常用于做清理工作,比如隐藏加载提示、关闭数据库连接等。


总结

ES6 的 Promise 对象提供了许多方法来处理异步操作,包括:

  • Promise.resolve():返回已解决的 Promise。
  • Promise.reject():返回已拒绝的 Promise。
  • Promise.all():等待多个 Promise 都成功并返回所有结果,或者在任何一个失败时拒绝。
  • Promise.allSettled():等待所有 Promise 完成(无论成功还是失败),返回每个 Promise 的状态。
  • Promise.race():返回第一个解决或拒绝的 Promise。
  • Promise.any():返回第一个成功的 Promise,所有 Promise 都失败时返回 AggregateError
  • Promise.finally():无论 Promise 成功还是失败,都会执行的回调。

实现 Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return new TypeError(" arugument must be a array")
}
let resolvedCounter = 0
let resolvedNum = promises.length
let resolvedResult = []
for (let i = 0; i < resolvedNum; i++) {
Promise.resolve(promises[i]).then(
(value) => {
resolvedCounter++
resolvedResult[i] = value
if (resolvedCounter === resolvedNum) {
return resolve(resolvedResult)
}
},
(error) => {
return reject(error)
}
)
}
})
}

Promise.race()

Promise.race() 方法用于在多个 Promise 中选择一个完成的 Promise。它会返回第一个完成的 Promise 的结果。如果所有的 Promise 都没有完成,它会等待所有的 Promise 完成后再返回。

1
const p = Promise.race([p1, p2, p3])

上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

Promise.allSettled()

Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是 fulfilled 还是 rejected),返回的 Promise 对象才会发生状态变更。

Promise.any()

只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态。

0%