-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathalgolia.json
1 lines (1 loc) · 701 KB
/
algolia.json
1
[{"categories":["k8s"],"content":"目前主流的搭建 k8s 集群的方式有 minikube、二进制包以及 kubeadm。 ","date":"2023-01-11","objectID":"/k8s-install/:0:0","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"minikube 方式安装 minikube 一般用于本地开发、测试和学习,不能用于生产环境,是一个工具,minikube 快速搭建一个运行在本地的单节点的 Kubernetes。 需要提前安装docker docker 需要运行在非 root 用户下 把运行用户添加到 docker 用户组下,sudo usermod -aG docker ubuntu \u0026\u0026 newgrp docker 安装 minikube curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 sudo install minikube-linux-amd64 /usr/local/bin/minikube 启动minikube start 交互 kubectl get po -A minikube kubectl -- get po -A alias kubectl=\"minikube kubectl --\" minikube dashboard ","date":"2023-01-11","objectID":"/k8s-install/:1:0","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"kubeadm 方式安装 ","date":"2023-01-11","objectID":"/k8s-install/:2:0","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"禁用 swap # 临时禁止 sudo swapoff -a # 永久禁止 # /etc/fstab 文件 注释掉 /swapfile 行 ","date":"2023-01-11","objectID":"/k8s-install/:2:1","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"安装 CRI 容器运行时 安装containerd tar Cxzvf /usr/local containerd-1.6.2-linux-amd64.tar.gz bin/ bin/containerd-shim-runc-v2 bin/containerd-shim bin/ctr bin/containerd-shim-runc-v1 bin/containerd bin/containerd-stress 拷贝 https://raw.githubusercontent.com/containerd/containerd/main/containerd.service 内容至 /etc/systemd/system/containerd.service systemctl daemon-reload systemctl enable --now containerd 安装 runc install -m 755 runc.amd64 /usr/local/sbin/runc ","date":"2023-01-11","objectID":"/k8s-install/:2:2","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"安装和配置先决条件 转发 IPv4 并让 iptables 看到桥接流量 cat \u003c\u003cEOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # 设置所需的 sysctl 参数,参数在重新启动后保持不变 cat \u003c\u003cEOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF # 应用 sysctl 参数而不重新启动 sudo sysctl --system 配置 systemd cgroup 驱动 containerd config default \u003e /etc/containerd/config.toml # 修改 [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options] # SystemdCgroup = true # 修改 sandbox_image = \"registry.aliyuncs.com/google_containers/pause:3.9\" systemctl daemon-reload systemctl restart containerd ","date":"2023-01-11","objectID":"/k8s-install/:2:3","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"安装 kubeadm apt-get update \u0026\u0026 apt-get install -y apt-transport-https curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - cat \u003c\u003cEOF \u003e/etc/apt/sources.list.d/kubernetes.list deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main EOF apt-get update apt-get install -y kubelet kubeadm kubectl ","date":"2023-01-11","objectID":"/k8s-install/:2:4","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"初始化集群 kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers kubeadm init --pod-network-cidr=10.244.0.0/16 --image-repository registry.aliyuncs.com/google_containers --apiserver-advertise-address=IP --control-plane-endpoint=IP mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config ","date":"2023-01-11","objectID":"/k8s-install/:2:5","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"添加 worker node # 以下命令参见 init 成功之后的提示 kubeadm join 10.0.12.17:6443 --token llcvt5.3cd0pw2stgxrmbui \\ --discovery-token-ca-cert-hash sha256:b501bac3e340e833f726ccc9e2520c90a8cdb23e03424173bafa83c857e48f0b ","date":"2023-01-11","objectID":"/k8s-install/:2:6","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"添加网络组件 kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml ","date":"2023-01-11","objectID":"/k8s-install/:2:7","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"验证 # 后面加上 -v 9,可查看每条命令执行的日志 kubectl run nginx --image=nginx ","date":"2023-01-11","objectID":"/k8s-install/:2:8","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"FAQ 使用公网 IP 搭建 k8s 集群 创建虚拟网卡 # 所有主机都要创建虚拟网卡,并绑定对应的公网 ip # 该设置方式在重启服务器后失效,持久化需要将配置写入/etc/network/interfaces或/etc/netplan/50-cloud-init.yaml sudo ifconfig eth0:1 139.198.108.103 x509: certificate is valid for kubeadm reset # 需要删除配置再重新生成 rm -rf $HOME/.kube mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 加上参数:--apiserver-cert-extra-sans=IP kubeadm init --pod-network-cidr=10.244.0.0/16 --image-repository registry.aliyuncs.com/google_containers --apiserver-cert-extra-sans=IP kubeadm init 查看日志 systemctl status kubelet journalctl -f -u kubelet ","date":"2023-01-11","objectID":"/k8s-install/:2:9","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["k8s"],"content":"参考 https://minikube.sigs.k8s.io/docs/start/ https://developer.aliyun.com/mirror/kubernetes?spm=a2c6h.13651102.0.0.472e1b11kbg5Ix https://kubernetes.io/zh-cn/docs/setup/ https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ https://cloud.tencent.com/developer/article/2164600 ","date":"2023-01-11","objectID":"/k8s-install/:3:0","tags":["k8s"],"title":"k8s 安装","uri":"/k8s-install/"},{"categories":["分布式"],"content":"简介 Raft 协议是一种分布式一致性算法(共识算法),共识就是多个节点对某一个事件达成一致的算法,即使出现部分节点故障,网络延时等情况,也不影响各节点,进而提高系统的整体可用性。Raft 是使用较为广泛的分布式协议,我们熟悉的 etcd 注册中心就采用了这个算法; Raft 算法将分布式一致性分解为多个子问题,包括 Leader 选举(Leader election)、日志复制(Log replication)、安全性(Safety)、日志压缩(Log compaction)等。raft 将系统中的角色分为领导者(Leader)、跟从者(Follower)和候选者(Candidate)。 Leader:接受客户端请求,并向 Follower 同步请求日志,当日志同步到大多数节点上后高速 Follower 提交日志。 Follower:接受并持久化 Leader 同步的日志,在 Leader 告知日志可以提交后,提交日志。当 Leader 出现故障时,主动推荐自己为候选人。 Candidate:Leader 选举过程中的临时角色。向其他节点发送请求投票信息,如果获得大多数选票,则晋升为 Leader。 Raft 要求系统在任意时刻最多只有一个 Leader,正常工作期间只有 Leader 和 Follower,Raft 算法将时间划分为任意不同长度的任期(Term),每一任期的开始都是一次选举,一个或多个候选人会试图称为 Leader,在成功选举 Leader 后,Leader 会在整个任期内管理整个集群,如果 Leader 选举失败,该任期就会因为没有 Leader 而结束,开始下一任期,并立刻开始下一次选举。 ","date":"2022-12-26","objectID":"/raft/:1:0","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"Leader 选举 Raft 使用心跳机制来触发领导者选举,当服务器启动时,初始化都是 Follower 身份, 由于没有 Leader,Followers 无法与 Leader 保持心跳,因此,Followers 会认为 Leader 已经下线,进而转为 Candidate 状态, 然后 Candidate 向集群其他节点请求投票,同意自己成为 Leader,如果 Candidate 收到超过半数节点的投票(N/2 +1),它将获胜成为 Leader。 Leader 向所有 Follower 周期性发送 heartbeat,如果 Follower 在选举超时时间(150 到 300 毫秒随机值)内没有收到 Leader 的 heartbeat,就会等待一段随机的时间后发起一次 Leader 选举。 ","date":"2022-12-26","objectID":"/raft/:2:0","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"日志同步 Raft 算法实现日志同步的具体过程如下: Leader 收到来自客户端的请求,将之封装成 log entry 并追加到自己的日志中; Leader 并行地向系统中所有节点发送日志复制消息; 接收到消息的节点确认消息没有问题,则将 log entry 追加到自己的日志中,并向 Leader 返回 ACK 表示接收成功; Leader 若在随机超时时间内收到大多数节点的 ACK(N/2 +1),则将该 log entry 应用到状态机并向客户端返回成功。 ","date":"2022-12-26","objectID":"/raft/:3:0","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"安全性 ","date":"2022-12-26","objectID":"/raft/:4:0","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"选举安全性 选举安全性,即任一任期内最多一个 leader 被选出。这一点非常重要,在一个复制集中任何时刻只能有一个 leader。系统中同时有多余一个 leader,被称之为脑裂(brain split),这是非常严重的问题,会导致数据的覆盖丢失。在 raft 中,两点保证了这个属性: 一个节点某一任期内最多只能投一票; 只有获得 majority 投票的节点才会成为 leader。 因此,某一任期内一定只有一个 leader。 ","date":"2022-12-26","objectID":"/raft/:4:1","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"log matching 很有意思,log 匹配特性, 就是说如果两个节点上的某个 log entry 的 log index 相同且 term 相同,那么在该 index 之前的所有 log entry 应该都是相同的。 leader 在某一 term 的任一位置只会创建一个 log entry,且 log entry 是 append-only。 consistency check。leader 在 AppendEntries 中包含最新 log entry 之前的一个 log 的 term 和 index,如果 follower 在对应的 term index 找不到日志,那么就会告知 leader 不一致。 ","date":"2022-12-26","objectID":"/raft/:4:2","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"Leader 完整性(Leader Completeness) 指 Leader 日志的完整性,当 Log 在任期 Term1 被 Commit 后,那么以后任期 Term2、Term3…等的 Leader 必须包含该 Log; Raft 在选举阶段就使用 Term 的判断用于保证完整性:当请求投票的该 Candidate 的 Term 较大或 Term 相同 Index 更大则投票,否则拒绝该请求。 ","date":"2022-12-26","objectID":"/raft/:4:3","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"如何解决脑裂问题 当 raft 在集群中遇见网络分区的时候,集群就会因此而相隔开,在不同的网络分区里会因为无法接收到原来的 leader 发出的心跳而超时选主,这样就会造成多 leader 现象,在网络分区 1 和网络分区 2 中,出现了两个 leaderA 和 D,假设此时要更新分区 2 的值,因为分区 2 无法得到集群中的大多数节点的 ACK,会复制失败。而网络分区 1 会成功,因为分区 1 中的节点更多,leaderA 能得到大多数回应。 当网络恢复的时候,集群不再是双分区,raft 会有如下操作: leaderD 发现自己的 Term 小于 LeaderA,会自动下台(step down)成为 follower,leaderA 保持不变依旧是集群中的主 leader 角色。 分区中的所有节点会回滚 roll back 自己的数据日志,并匹配新 leader 的 log 日志,然后实现同步提交更新自身的值。 最终集群达到整体一致,集群存在唯一 leader(节点 A)。 ","date":"2022-12-26","objectID":"/raft/:5:0","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["分布式"],"content":"参考 https://thesecretlivesofdata.com/raft/ ","date":"2022-12-26","objectID":"/raft/:6:0","tags":["raft","分布式"],"title":"Raft 协议","uri":"/raft/"},{"categories":["k8s"],"content":"介绍 ","date":"2022-12-18","objectID":"/etcd/:1:0","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"简介 Etcd 是 CoreOS 基于 Raft 开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。 在分布式系统中,如何管理节点间的状态一直是一个难题,etcd 像是专门为集群环境的服务发现和注册而设计,它提供了数据 TTL 失效、数据改变监视、多值、目录监听、分布式锁原子操作等功能,可以方便的跟踪并管理集群节点的状态。 etcd 包含以下特点: 简单:基于 HTTP+JSON 的 API 让你用 curl 就可以轻松使用。 安全:支持 SSL 证书认证机制。 快速: 单实例每秒 1000 次写操作,2000+次读操作 可靠:采用 Raft 算法,实现分布式系统数据的可用性和一致性。 数据持久化:默认数据一更新就进行持久化。 监测变更:监测特定的键或目录以进行更改,并对值的更改做出反应 主要功能: 基本的 key-value 存储 监听机制 key 的过期及续约机制,用于监控和服务发现 原子 Compare And Swap 和 Compare And Delete,用于分布式锁和 leader 选举 Raft 算法:工程上使用较为广泛的强一致性、去中心化、高可用的分布式协议。Raft 是一个共识算法(consensus algorithm),所谓共识,就是多个节点对某个事情达成一致的看法,即使是在部分节点故障、网络延时、网络分割的情况下。 ","date":"2022-12-18","objectID":"/etcd/:1:1","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"基础知识 每个 etcd cluster 都是有若干个 member 组成的,每个 member 是一个独立运行的 etcd 实例,单台机器上可以运行多个 member。 在正常运行的状态下,集群中会有一个 leader,其余的 member 都是 followers。leader 向 followers 同步日志,保证数据在各个 member 都有副本。leader 还会定时向所有的 member 发送心跳报文,如果在规定的时间里 follower 没有收到心跳,就会重新进行选举。 客户端所有的请求都会先发送给 leader,leader 向所有的 followers 同步日志,等收到超过半数的确认后就把该日志存储到磁盘,并返回响应客户端。 每个 etcd 服务有三大主要部分组成:raft 实现、WAL 日志存储、数据的存储和索引。WAL 会在本地磁盘(就是之前提到的 –data-dir)上存储日志内容(wal file)和快照(snapshot)。 ","date":"2022-12-18","objectID":"/etcd/:1:2","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"核心组件 从 etcd 的架构图中我们可以看到,etcd 主要分为四个部分。 HTTP Server:用于处理用户发送的 API 请求以及其它 etcd 节点的同步与心跳信息请求。 Store:用于处理 etcd 支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是 etcd 对用户提供的大多数 API 功能的具体实现。 Raft:Raft 强一致性算法的具体实现,是 etcd 的核心。 WAL:Write Ahead Log(预写式日志),是 etcd 的数据存储方式。除了在内存中存有所有数据的状态以及节点的索引以外,etcd 就通过 WAL 进行持久化存储。WAL 中,所有的数据提交前都会事先记录日志。Snapshot 是为了防止数据过多而进行的状态快照;Entry 表示存储的具体日志内容。 通常,一个用户的请求发送过来,会经由 HTTP Server 转发给 Store 进行具体的事务处理,如果涉及到节点的修改,则交给 Raft 模块进行状态的变更、日志的记录,然后再同步给别的 etcd 节点以确认数据提交,最后进行数据的提交,再次同步。 ","date":"2022-12-18","objectID":"/etcd/:1:3","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"读写数据 为了保证数据的强一致性,etcd 集群中所有的数据流向都是一个方向,从 Leader (主节点)流向 Follower,也就是所有 Follower 的数据必须与 Leader 保持一致,如果不一致会被覆盖。 简单点说就是,用户可以对 etcd 集群中的所有节点进行读写,读取非常简单因为每个节点保存的数据是强一致的。对于写入来说,如果写入请求来自 Leader 节点即可直接写入然后 Leader 节点会把写入分发给所有 Follower;如果写入请求来自其他 Follower 节点,那么写入请求会给转发给 Leader 节点,由 Leader 节点写入之后再分发给集群上的所有其他节点。 ","date":"2022-12-18","objectID":"/etcd/:1:4","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"存储机制 etcd v3 store 分为两部分,一部分是内存中的索引,kvindex,是基于 Google 开源的一个 Golang 的 btree 实现的,另外一部分是后端存储。按照它的设计,backend 可以对接多种存储,当前使用的 boltdb。boltdb 是一个单机的支持事务的 kv 存储,etcd 的事务是基于 boltdb 的事务实现的。etcd 在 boltdb 中存储的 key 是 reversion,value 是 etcd 自己的 key-value 组合,也就是说 etcd 会在 boltdb 中把每个版本都保存下,从而实现了多版本机制。 reversion 主要由两部分组成,第一部分 main rev,每次事务进行加一,第二部分 sub rev,同一个事务中的每次操作加一。 etcd 提供了命令和设置选项来控制 compact,同时支持 put 操作的参数来精确控制某个 key 的历史版本数。 内存 kvindex 保存的就是 key 和 reversion 之前的映射关系,用来加速查询。 ","date":"2022-12-18","objectID":"/etcd/:1:5","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"Watch 机制 etcd v3 的 watch 机制支持 watch 某个固定的 key,也支持 watch 一个范围(可以用于模拟目录的结构的 watch),所以 watchGroup 包含两种 watcher,一种是 key watchers,数据结构是每个 key 对应一组 watcher,另外一种是 range watchers, 数据结构是一个 IntervalTree,方便通过区间查找到对应的 watcher。 同时,每个 WatchableStore 包含两种 watcherGroup,一种是 synced,一种是 unsynced,前者表示该 group 的 watcher 数据都已经同步完毕,在等待新的变更,后者表示该 group 的 watcher 数据同步落后于当前最新变更,还在追赶。 当 etcd 收到客户端的 watch 请求,如果请求携带了 revision 参数,则比较请求的 revision 和 store 当前的 revision,如果大于当前 revision,则放入 synced 组中,否则放入 unsynced 组。同时 etcd 会启动一个后台的 goroutine 持续同步 unsynced 的 watcher,然后将其迁移到 synced 组。也就是这种机制下,etcd v3 支持从任意版本开始 watch,没有 v2 的 1000 条历史 event 表限制的问题(当然这是指没有 compact 的情况下) ","date":"2022-12-18","objectID":"/etcd/:1:6","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"容量管理 单个对象不建议超过 1.5M 默认容量 2G 不建议超过 8G ","date":"2022-12-18","objectID":"/etcd/:1:7","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"常用命令 # 查看集群成员状态 etcdctl member list --write-out=table # 写入数据 etcdctl --endpoints=localhost:12379 put /a b # 读取数据 etcdctl --endpoints=localhost:12379 get /a # 按key的前缀查询数据 etcdctl --endpoints=localhost:12379 get --prefix / # 只显示键值 etcdctl --endpoints=localhost:12379 get --prefix / --keys-only --debug # 查询历史版本值 etcdctl get x --rev=2 # 创建Snapshot etcdctl --endpoints https://127.0.0.1:3379 --cert /tmp/etcd-certs/certs/127.0.0.1.pem -- key /tmp/etcd-certs/certs/127.0.0.1-key.pem --cacert /tmp/etcd-certs/certs/ca.pem snapshot save snapshot.db # 恢复数据 etcdctl snapshot restore snapshot.db \\ --name infra2 \\ --data-dir=/tmp/etcd/infra2 \\ --initial-cluster infra0=http://127.0.0.1:3380,infra1=http://127.0.0.1:4380,infra2=http://127.0.0.1:5380 \\ --initial-cluster-token etcd-cluster-1 \\ --initial-advertise-peer-urls http://127.0.0.1:5380 ","date":"2022-12-18","objectID":"/etcd/:2:0","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"安装 ","date":"2022-12-18","objectID":"/etcd/:3:0","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"docker 方式安装 # 启动 docker run -it -d -p 2379:2379 -p 2380:2380 --name etcd quay.io/coreos/etcd # 查询 docker exec -it etcd etcdctl member list ","date":"2022-12-18","objectID":"/etcd/:3:1","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"shell 脚本方式安装 编写以下脚本,并运行 ETCD_VER=v3.5.7 # choose either URL GOOGLE_URL=https://storage.googleapis.com/etcd GITHUB_URL=https://github.com/etcd-io/etcd/releases/download DOWNLOAD_URL=${GOOGLE_URL} rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz rm -rf /tmp/etcd-download-test \u0026\u0026 mkdir -p /tmp/etcd-download-test curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1 rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz /tmp/etcd-download-test/etcd --version /tmp/etcd-download-test/etcdctl version /tmp/etcd-download-test/etcdutl version 运行以下命令 # start a local etcd server /tmp/etcd-download-test/etcd # write,read to etcd /tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 put foo bar /tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 get foo ","date":"2022-12-18","objectID":"/etcd/:3:2","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"二进制方式安装 访问GitHub releases page上,根据自己的系统下载对应的软件包,下载完成后解压。 进入到解压的目录,将 etcd 和 etcdctl 可执行文件移动到$GOPATH/bin 目录下。然后执行命令 etcd –version,同样会看到版本信息。 $ etcd --version etcd Version: 3.3.13 Git SHA: 98d3084 Go Version: go1.10.8 Go OS/Arch: linux/amd64 ","date":"2022-12-18","objectID":"/etcd/:3:3","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"常用参数说明 –name:方便理解的节点名称,默认为 default,在集群中应该保持唯一,可以使用 hostname。 –data-dir:数据保存的目录,默认为${name}.etcd。 –snapshot-count:最大快照次数,指定有多少事务(transaction)被提交时,触发截取快照保存到磁盘,默认值 100000。 –max-snapshots: 最大保留快照数,默认 5 个。 –heartbeat-interval:心跳周期,leader 多久发送一次心跳到 followers。默认值是 100ms。 –eletion-timeout:重新投票的超时时间,如果 follow 在该时间间隔没有收到心跳包,会触发重新投票,默认为 1000ms。 –listen-peer-urls:和同伴通信的地址,比如 http://ip:2380,如果有多个,使用逗号分隔。需要所有节点都能够访问,所以不要使用 localhost! –initial-advertise-peer-urls:该节点同伴监听地址,这个值会告诉集群中其他节点。 –listen-client-urls:对外提供服务的地址:比如 http://ip:2379,http://127.0.0.1:2379,客户端会连接到这里和etcd交互。 –advertise-client-urls:对外公告的该节点客户端监听地址,这个值会告诉集群中其他节点。 –initial-cluster:集群中所有节点的信息,格式为 node1=http://ip1:2380,node2=http://ip2:2380,…。注意:这里的 node1 是节点的 –name 指定的名字;后面的 ip1:2380 是 –initial-advertise-peer-urls 指定的值。 –initial-cluster-state:新建集群的时候,这个值为 new;假如已经存在的集群,这个值为 existing。 –initial-cluster-token:创建集群的 token,这个值每个集群保持唯一。这样的话,如果你要重新创建集群,即使配置和之前一样,也会再次生成新的集群和节点 uuid;否则会导致多个集群之间的冲突,造成未知的错误。 –log-level: 日志等级。info, warn, error, panic, or fatal。 所有以 –init 开头的配置都是在 bootstrap 集群的时候才会用到,后续节点的重启会被忽略。 ","date":"2022-12-18","objectID":"/etcd/:4:0","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["k8s"],"content":"参考 https://etcd.io/docs/v3.5/install/ https://github.com/cncamp/101/tree/master/module5/etcd-ha-demo ","date":"2022-12-18","objectID":"/etcd/:5:0","tags":["etcd","k8s"],"title":"etcd","uri":"/etcd/"},{"categories":["mysql"],"content":"Ubuntu 查看 Ubuntu 版本 cat /etc/lsb-release 添加源 vim /etc/apt/sources.list.d/mysql-community.list # Ubuntu 20.04 LTS deb https://mirrors.tuna.tsinghua.edu.cn/mysql/apt/ubuntu focal mysql-5.7 mysql-8.0 mysql-tools # Ubuntu 18.04 LTS deb https://mirrors.tuna.tsinghua.edu.cn/mysql/apt/ubuntu bionic mysql-5.7 mysql-8.0 mysql-tools # Ubuntu 16.04 LTS deb https://mirrors.tuna.tsinghua.edu.cn/mysql/apt/ubuntu xenial mysql-5.6 mysql-5.7 mysql-8.0 mysql-tools # Ubuntu 14.04 LTS deb https://mirrors.tuna.tsinghua.edu.cn/mysql/apt/ubuntu trusty mysql-5.6 mysql-5.7 mysql-8.0 mysql-tools 导入 GPG 密钥 # apt-key adv --keyserver pgp.mit.edu --recv-keys 3A79BD29 apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3A79BD29 更新库 apt update 查看具体版本 apt-cache policy mysql-server apt 安装 # 安装5.7 apt install mysql-community-client=5.7.41-1ubuntu18.04 mysql-client=5.7.41-1ubuntu18.04 apt install mysql-community-server=5.7.41-1ubuntu18.04 mysql-server=5.7.41-1ubuntu18.04 # 安装8.0 apt install mysql-client mysql-server 验证安装是否成功 mysql --version service mysql start netstat -lnp | grep mysql ","date":"2022-06-01","objectID":"/mysql-install/:1:0","tags":["mysql"],"title":"mysql 安装","uri":"/mysql-install/"},{"categories":["mysql"],"content":"参考 https://blog.csdn.net/simplemurrina/article/details/80088479 https://www.cnblogs.com/immaxfang/p/16804455.html https://blog.csdn.net/weixin_44129085/article/details/105403674 https://downloads.mysql.com/archives/community/ ","date":"2022-06-01","objectID":"/mysql-install/:2:0","tags":["mysql"],"title":"mysql 安装","uri":"/mysql-install/"},{"categories":["工具"],"content":"开发效率 Eclipse keymap,eclipse 快捷键。 Translation,翻译插件。 Key Promoter X,快捷键提示。 String Manipulation,字符串处理,选中需要处理的内容后,按快捷键 Alt + M,即可弹出工具功能列表。 Tabnine AI 代码补全 Protocol Buffers GitToolBox,自动 fetching 项目最新代码,显示编辑页光标行的 git 日志 Goctl,go-zero 框架插件 ","date":"2022-04-19","objectID":"/goland-plugins/:1:0","tags":["工具"],"title":"Goland 插件推荐","uri":"/goland-plugins/"},{"categories":["工具"],"content":"编程美化 Gopher,进度条。 Rainbow Brackets,通过颜色区分括号嵌套层级,便于阅读,能够更快地对错误代码进行定位和调整。 Chinese (Simplified) Language Pack / 中文语言包 ","date":"2022-04-19","objectID":"/goland-plugins/:2:0","tags":["工具"],"title":"Goland 插件推荐","uri":"/goland-plugins/"},{"categories":["工具"],"content":"Intellij IDEA 的插件主页地址是https://plugins.jetbrains.com。打开网站测速 - 站长工具找到最快的IP,配置到hosts文件中,可以提高插件安装的速度。 ","date":"2022-04-19","objectID":"/idea-plugins/:0:0","tags":["工具"],"title":"IDEA 插件推荐","uri":"/idea-plugins/"},{"categories":["工具"],"content":"开发效率 Eclipse keymap,eclipse 快捷键。 Lombok,以简单的注解形式来简化 java 代码,提高开发人员的开发效率。 MybatisX,mapper 和 xml 跳转支持。 Jrebel,热加载。 MyBatisCodeHelperPro,mybatis 代码辅助。 Maven Helper,maven 辅助。 JUnitGenerator V2.0,单元测试。 GenerateAllSetter,对象之间快速赋值。 Translation,翻译插件。 Key Promoter X,快捷键提示。 String Manipulation,字符串处理,选中需要处理的内容后,按快捷键 Alt + M,即可弹出工具功能列表。 RESTfultoolkit,根据 url 查找 controller,快捷键 Ctrl + \\。 ","date":"2022-04-19","objectID":"/idea-plugins/:1:0","tags":["工具"],"title":"IDEA 插件推荐","uri":"/idea-plugins/"},{"categories":["工具"],"content":"代码生成 GsonFormat,粘贴一段 Json 文本,能自动生成对象的嵌套结构代码。 EasyCode,代码生成插件 ","date":"2022-04-19","objectID":"/idea-plugins/:2:0","tags":["工具"],"title":"IDEA 插件推荐","uri":"/idea-plugins/"},{"categories":["工具"],"content":"编程美化 Mybatis Log Free,mybatis 日志优化。 Background Image Plus,背景图片。 Rainbow Brackets,通过颜色区分括号嵌套层级,便于阅读,能够更快地对错误代码进行定位和调整。 GrepConsole,控制台日志显示优化。 Chinese (Simplified) Language Pack / 中文语言包 ","date":"2022-04-19","objectID":"/idea-plugins/:3:0","tags":["工具"],"title":"IDEA 插件推荐","uri":"/idea-plugins/"},{"categories":["工具"],"content":"代码质量 Alibaba Java Coding Guidelines,扫描代码中可能存在的问题。 CheckStyle-IDEA,提醒注意无用导入、注释、语法错误、代码冗余等等。 FindBugs,静态代码分析。 ","date":"2022-04-19","objectID":"/idea-plugins/:4:0","tags":["工具"],"title":"IDEA 插件推荐","uri":"/idea-plugins/"},{"categories":["工具"],"content":"主题 Material Theme UI Vuesion Theme ","date":"2022-04-19","objectID":"/idea-plugins/:5:0","tags":["工具"],"title":"IDEA 插件推荐","uri":"/idea-plugins/"},{"categories":["工具"],"content":"修仙插件 Leetcode,刷题神器。 ","date":"2022-04-19","objectID":"/idea-plugins/:6:0","tags":["工具"],"title":"IDEA 插件推荐","uri":"/idea-plugins/"},{"categories":["工具"],"content":"开发效率 EditorConfig for VS Code,代码格式统一。 vscode-json,操作美化 json。 快捷键 cmd+alt+v,验证 cmd+alt+b,格式化 cmd+alt+u,压缩 cmd+alt+’,加转义字符 cmd+alt+;,去转义字符 Eclipse Keymap,习惯 eclipse 快捷键的可以装这个。 Markdown All in One,markdown 插件。 Tabnine AI Autocomplete,智能代码补全。 IntelliCode,自动补全。 ","date":"2022-04-19","objectID":"/vscode-plugins/:1:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["工具"],"content":"编程美化 Bracket Pair Colorizer,给匹配的括号上色,可以自定义配置。 Highlight Matching Tag,突出显示匹配的开始和/或结束标签。 Indent-Rainbow,使得对齐更加具有可读性。 ","date":"2022-04-19","objectID":"/vscode-plugins/:2:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["工具"],"content":"主题及图标 GitHub Theme Material Theme vscode-icons ","date":"2022-04-19","objectID":"/vscode-plugins/:3:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["工具"],"content":"功能强化 settings sync,同步所有设置和插件。 Chinese (Simplified) Language Pack for Visual Studio Code,中文语言包。 ","date":"2022-04-19","objectID":"/vscode-plugins/:4:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["工具"],"content":"Git Git Graph,Git 图形化显示和操作。 GitHub Pull requests,在 Visual Studio Code 中查看和管理 GitHub 拉取请求和问题。 GitLens,源代码管理 ","date":"2022-04-19","objectID":"/vscode-plugins/:5:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["工具"],"content":"前端开发 Auto Rename Tag,自动重命名 HTML 或 XML 标签。 Auto Close Tag,自动添加 HTML/XML 关闭标签。 Path Intellisense,智能路径提示。 npm Intellisense,可以在导入语句自动补全 npm 模块名称。 CSS Peek,查看 css 定义。 Prettier - Code formatter,各种代码或者文档格式化。 ESLint,静态代码分析工具。 ","date":"2022-04-19","objectID":"/vscode-plugins/:6:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["工具"],"content":"修仙插件 LeetCode,刷题利器。 ","date":"2022-04-19","objectID":"/vscode-plugins/:7:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["工具"],"content":"参考 https://blog.csdn.net/u011262253/article/details/113879997 ","date":"2022-04-19","objectID":"/vscode-plugins/:8:0","tags":["工具"],"title":"VSCODE 插件推荐","uri":"/vscode-plugins/"},{"categories":["Windows"],"content":"启用相关功能 首先搜索【启用或关闭 Winsows 功能】 然后勾选【适用于 linux 的 Windows 子系统】和【虚拟平台】 ","date":"2022-03-19","objectID":"/wsl/:1:0","tags":["Windows"],"title":"Windows11 安装 Linux 子系统","uri":"/wsl/"},{"categories":["Windows"],"content":"下载安装 Linux 内核更新包 wsl_update_x64.msi ","date":"2022-03-19","objectID":"/wsl/:2:0","tags":["Windows"],"title":"Windows11 安装 Linux 子系统","uri":"/wsl/"},{"categories":["Windows"],"content":"设置默认版本为 WSL 2 wsl --set-default-version 2 ","date":"2022-03-19","objectID":"/wsl/:3:0","tags":["Windows"],"title":"Windows11 安装 Linux 子系统","uri":"/wsl/"},{"categories":["Windows"],"content":"安装 Linux 最后打开应用商店,选择你喜欢的 linux 分发版本安装。 安装成功之后,打开相关 Linux 版本。 设置默认 root 账号打开。 ubuntu2204.exe config --default-user root ","date":"2022-03-19","objectID":"/wsl/:4:0","tags":["Windows"],"title":"Windows11 安装 Linux 子系统","uri":"/wsl/"},{"categories":["Windows"],"content":"Windows 访问 Linux 文件 通过 \\wsl$ 访问 Linux 文件时将使用 WSL 分发版的默认用户。 如果修改没有权限,可以更改文件或目录权限以支持修改。 chmod 777 -R /data ","date":"2022-03-19","objectID":"/wsl/:5:0","tags":["Windows"],"title":"Windows11 安装 Linux 子系统","uri":"/wsl/"},{"categories":["Windows"],"content":"Linux 访问 Windows 文件 在从 WSL 访问 Windows 文件时,可以直接使用/mnt/{Windows 盘符}进入对应的盘中。 ","date":"2022-03-19","objectID":"/wsl/:6:0","tags":["Windows"],"title":"Windows11 安装 Linux 子系统","uri":"/wsl/"},{"categories":["Windows"],"content":"参考 https://zhuanlan.zhihu.com/p/386385348 ","date":"2022-03-19","objectID":"/wsl/:7:0","tags":["Windows"],"title":"Windows11 安装 Linux 子系统","uri":"/wsl/"},{"categories":["Windows"],"content":" 关闭系统透明效果,设置 -\u003e 个性化 -\u003e 颜色 视觉效果调整为最佳性能,高级系统设置 -\u003e 性能 -\u003e 设置 -\u003e 调整为最佳性能 禁用游戏模式,设置 -\u003e 游戏 -\u003e 游戏模式 关闭启动的应用,cmd 打开 msconfig 关闭设备安全性,设置 -\u003e 隐私和安全性 -\u003e Windows 安全中心 -\u003e 设备安全性 -\u003e 内核隔离 -\u003e 关闭内存完整性 禁用锁屏背景,设置 -\u003e 锁屏界面 更改电源计划为高性能,控制面板 -\u003e 电源选项 清理临时文件,设置 -\u003e 系统 -\u003e 存储 -\u003e 临时文件 开启存储感知,设置 -\u003e 系统 -\u003e 存储 -\u003e 存储管理 禁用系统通知与建议,设置 -\u003e 系统 -\u003e 通知 不需要安装杀毒软件 开启系统更新,设置 -\u003e Windows 更新 关闭动画效果,设置 -\u003e 辅助功能 -\u003e 动画效果 选择刷新了,设置 -\u003e 系统 -\u003e 屏幕 -\u003e 高级显示器设置 -\u003e 选择最高的刷新率 优化隐私设置,设置 -\u003e 隐私和安全性 -\u003e 常规 -\u003e 关闭相关设置 进行磁盘碎片整理,搜索磁盘碎片整理 字体:控制面板 -\u003e 字体 -\u003e 调整 ClearType 文本 -\u003e 开启 ","date":"2022-03-19","objectID":"/win11-optimization/:0:0","tags":["Windows"],"title":"Windows11 优化","uri":"/win11-optimization/"},{"categories":["Windows"],"content":"参考 https://zhuanlan.zhihu.com/p/386385348 ","date":"2022-03-19","objectID":"/win11-optimization/:1:0","tags":["Windows"],"title":"Windows11 优化","uri":"/win11-optimization/"},{"categories":["docker"],"content":"Compose 简介 Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。 Compose 使用的三个步骤: 使用 Dockerfile 定义应用程序的环境。 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。 最后,执行 docker-compose up 命令来启动并运行整个应用程序。 ","date":"2022-02-28","objectID":"/docker-compose/:1:0","tags":["docker","容器"],"title":"Docker Compose","uri":"/docker-compose/"},{"categories":["docker"],"content":"Compose 安装 Linux 上我们可以从 Github 上下载它的二进制包来使用,最新发行的版本地址:https://github.com/docker/compose/releases。 运行以下命令 sudo curl -L \"https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose # 将可执行权限应用于二进制文件: sudo chmod +x /usr/local/bin/docker-compose # 创建软连接 sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose docker-compose version ","date":"2022-02-28","objectID":"/docker-compose/:2:0","tags":["docker","容器"],"title":"Docker Compose","uri":"/docker-compose/"},{"categories":["docker"],"content":"使用 创建 Go 项目 mkdir docker-compose-demo cd docker-compose-demo go mod init docker-compose-demo go get -u github.com/go-redis/redis 编辑 main.go package main import ( \"fmt\" \"github.com/go-redis/redis\" \"net/http\" ) func main() { http.HandleFunc(\"/\", func(writer http.ResponseWriter, request *http.Request) { rdb := NewRedisClient() pong, err := rdb.Ping().Result() if err != nil { panic(err) } fmt.Fprintf(writer, pong) }) err := http.ListenAndServe(\":5000\", nil) if err != nil { panic(err) } } func NewRedisClient() *redis.Client { rdb := redis.NewClient(\u0026redis.Options{ // ip 需要用 ifconfig 列出来的 Addr: \"127.0.0.1:6379\", Password: \"\", DB: 0, PoolSize: 100, }) return rdb } 编辑 Dockerfile #源镜像 FROM alpine:latest #设置工作目录 WORKDIR / #将服务器的go工程代码加入到docker容器中 ADD docker-compose-demo /docker-compose-demo #暴露端口 EXPOSE 5000 #最终运行docker的命令 ENTRYPOINT [\"/docker-compose-demo\"] 编辑 docker-compose.yml # yaml 配置 version: '3' services: web: build: . depends_on: - redis ports: - \"5000:5000\" redis: image: \"redis:alpine\" ports: - \"6379:6379\" networks: some-network: # Use a custom driver driver: custom-driver-1 other-network: # Use a custom driver which takes special options driver: custom-driver-2 运行以下命令 # 如果docker基础镜像不支持cgo的话,需要加上CGO_ENABLED=0 CGO_ENABLED=0 go build -o docker-compose-demo main.go docker-compose up docker-compose 常用命令 # 后台运行 docker-compose up -d # 列出项目中所有容器 docker-compose ps # 停止和删除容器和网络,会清楚数据 docker-compose down ","date":"2022-02-28","objectID":"/docker-compose/:3:0","tags":["docker","容器"],"title":"Docker Compose","uri":"/docker-compose/"},{"categories":["docker"],"content":"yml 配置解析 version 指定本 yml 依从的 compose 哪个版本制定的。 services 配置服务。 build 指定为构建镜像上下文路径: cap_add,cap_drop 添加或删除容器拥有的宿主机的内核功能。 cgroup_parent 为容器指定父 cgroup 组,意味着将继承该组的资源限制。 command 覆盖容器启动的默认命令。 container_name 指定自定义容器名称,而不是生成的默认名称。 depends_on 设置依赖关系。 image 指定容器运行的镜像 expose 暴露端口,但不映射到宿主机,只被连接的服务访问。 volumes 将主机的数据卷或着文件挂载到容器里。 networks 配置容器连接的网络,引用顶级 networks 下的条目 。 ","date":"2022-02-28","objectID":"/docker-compose/:4:0","tags":["docker","容器"],"title":"Docker Compose","uri":"/docker-compose/"},{"categories":["docker"],"content":"参考 https://www.runoob.com/docker/docker-compose.html ","date":"2022-02-28","objectID":"/docker-compose/:5:0","tags":["docker","容器"],"title":"Docker Compose","uri":"/docker-compose/"},{"categories":["docker"],"content":"什么是 Dockerfile Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:1:0","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"Dockerfile 构建镜像的过程 从 base 镜像运行一个容器。 执行一条指令,对容器做修改。 执行类似 docker commit 的操作,生成一个新的镜像层。 Docker 再基于刚刚提交的镜像运行一个新容器。 重复 2 ~ 4 步,直到 Dockerfile 中的所有指令执行完毕。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:2:0","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"使用 Dockerfile 定制镜像 这里仅讲解如何运行 Dockerfile 文件来定制一个镜像,具体 Dockerfile 文件内指令详解,将在下一节中介绍,这里你只要知道构建的流程即可。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:3:0","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"下面以定制一个 nginx 镜像(构建好的镜像内会有一个 /usr/share/nginx/html/index.html 文件) 在一个空目录下,新建一个名为 Dockerfile 文件,并在文件内添加以下内容: FROM nginx RUN echo ‘这是一个本地构建的 nginx 镜像’ \u003e /usr/share/nginx/html/index.html ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:3:1","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"FROM 和 RUN 指令的作用 FROM:定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。 RUN:用于执行后面跟着的命令行命令。 注意:Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大。例如: FROM centos RUN yum -y install wget RUN wget -O redis.tar.gz \"http://download.redis.io/releases/redis-5.0.3.tar.gz\" RUN tar -xvf redis.tar.gz 以上执行会创建 3 层镜像。可简化为以下格式: FROM centos RUN yum -y install wget \\ \u0026\u0026 wget -O redis.tar.gz \"http://download.redis.io/releases/redis-5.0.3.tar.gz\" \\ \u0026\u0026 tar -xvf redis.tar.gz 如上,以 \u0026\u0026 符号连接命令,这样执行后,只会创建 1 层镜像。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:3:2","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"构建镜像 在 Dockerfile 文件的存放目录下,执行docker build构建动作。 以下示例,通过目录下的 Dockerfile 构建一个 nginx:v3(镜像名称:镜像标签)。 注意:最后的 . 代表本次执行的上下文路径。 $ docker build -t nginx:v3 . ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:3:3","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"Dockerfile 指令详解 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:0","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"FROM 指定 base 镜像。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:1","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"MAINTAINER 设置镜像的作者,可以是任意字符串。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:2","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"COPY 将文件从 build context 复制到镜像。COPY 支持两种形式: COPY src dest 与 COPY [“src”, “dest”]。 注意:src 只能指定 build context 中的文件或目录。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:3","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"ADD 与 COPY 类似(同样需求下,官方推荐使用 COPY),从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar、zip、tgz、xz 等),文件会被自动解压到 dest,同时 ADD 也支持 URL 而 COPY 不支持。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:4","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"WORKDIR 为后面的 RUN、CMD、ENTRYPOINT、ADD 或 COPY 指令设置镜像中的当前工作目录。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:5","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"RUN 在容器中运行指定的命令。有以下俩种格式: shell 格式: RUN \u003c命令行命令\u003e # \u003c命令行命令\u003e 等同于,在终端操作的 shell 命令。 exec 格式: RUN [\"可执行文件\", \"参数1\", \"参数2\"] # 例如: # RUN [\"./test.php\", \"dev\", \"offline\"] 等价于 RUN ./test.php dev offline ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:6","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"CMD 类似于 RUN 指令,用于运行程序,但二者运行的时间点不同: CMD 在 docker run 时运行。 RUN 是在 docker build。 作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。 注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。 格式: CMD \u003cshell 命令\u003e CMD [\"\u003c可执行文件或命令\u003e\",\"\u003cparam1\u003e\",\"\u003cparam2\u003e\",...] CMD [\"\u003cparam1\u003e\",\"\u003cparam2\u003e\",...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数 推荐使用第二种格式,执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是 sh。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:7","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"ENTRYPOINT 类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。 但是, 如果运行 docker run 时使用了 –entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。 优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。 注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。 格式: ENTRYPOINT [\"\u003cexecuteable\u003e\",\"\u003cparam1\u003e\",\"\u003cparam2\u003e\",...] 可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。 示例: 假设已通过 Dockerfile 构建了 nginx:test 镜像: FROM nginx ENTRYPOINT [\"nginx\", \"-c\"] # 定参 CMD [\"/etc/nginx/nginx.conf\"] # 变参 RUN、CMD、ENTRYPOINT 三者用法最佳实践 RUN:使用 RUN 指令安装应用和软件包,创建新的镜像层。 CMD:如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。 ENTRYPOINT:如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:8","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"ENV 设置环境变量,环境变量可被后面的指令使用。 格式: ENV \u003ckey\u003e \u003cvalue\u003e ENV \u003ckey1\u003e=\u003cvalue1\u003e \u003ckey2\u003e=\u003cvalue2\u003e... 以下示例设置 NODE_VERSION = 7.2.0 , 在后续的指令中可以通过 $NODE_VERSION 引用: ENV NODE_VERSION 7.2.0 RUN curl -SLO \"https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz\" \\ \u0026\u0026 curl -SLO \"https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc\" ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:9","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"ARG 构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:10","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"EXPOSE 仅仅只是声明端口。 作用: 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。 格式: EXPOSE \u003c端口1\u003e [\u003c端口2\u003e...] ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:11","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"VOLUME 定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。 作用: 避免重要的数据,因容器重启而丢失,这是非常致命的。 避免容器不断变大。 格式: VOLUME [\"\u003c路径1\u003e\", \"\u003c路径2\u003e\"...] VOLUME \u003c路径\u003e 在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:12","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"ONBUILD 用于延迟构建命令的执行。简单的说,就是 Dockerfile 里用 ONBUILD 指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ,这时执行新镜像的 Dockerfile 构建时候,会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:13","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"USER 用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。 格式: USER \u003c用户名\u003e[:\u003c用户组\u003e] ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:14","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"LABEL LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式,语法格式如下: LABEL \u003ckey\u003e=\u003cvalue\u003e \u003ckey\u003e=\u003cvalue\u003e \u003ckey\u003e=\u003cvalue\u003e ... 比如我们可以添加镜像的作者: LABEL org.opencontainers.image.authors=\"ubuntu\" ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:4:15","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"多阶段构建 编译、测试、打包等同时放到一个 Dockerfile 文件,会带来一些问题: 镜像层次多,镜像体积较大,部署时间变长 源代码存在泄露的风险 分散到多个 Dockerfile 文件又太复杂。 Docker v17.05 开始支持多阶段构建 (multistage builds)。使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个 Dockerfile: FROM golang:alpine AS builder LABEL stage=gobuilder ENV CGO_ENABLED 0 ENV GOPROXY https://goproxy.cn,direct RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories RUN apk update --no-cache \u0026\u0026 apk add --no-cache tzdata WORKDIR /build ADD go.mod . ADD go.sum . RUN go mod download COPY . . COPY user-api/etc /app/etc RUN go build -ldflags=\"-s -w\" -o /app/user user-api/user.go FROM scratch COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai ENV TZ Asia/Shanghai WORKDIR /app COPY --from=builder /app/user /app/user COPY --from=builder /app/etc /app/etc CMD [\"./user\", \"-f\", \"etc/user-api.yaml\"] ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:5:0","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"只构建某一阶段的镜像 我们可以使用 as 来为某一阶段命名,例如 FROM golang:alpine as builder 例如当我们只想构建 builder 阶段的镜像时,增加 –target=builder 参数即可 $ docker build --target builder -t username/imagename:tag . ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:5:1","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"构建时从其他镜像复制文件 可以使用 COPY --from=0 /go/src/github.com/go/helloworld/app . 从上一阶段的镜像中复制文件,我们也可以复制任意镜像中的文件。 $ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:5:2","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"最佳实践 不要安装无效软件包。 应简化镜像中同时运行的进程数,理想状况下,每个镜像应该只有一个进程。 当无法避免同一镜像运行多进程时,应选择合理的初始化进程(init process)。 最小化层级数 最新的 docker 只有 RUN, COPY,ADD 创建新层,其他指令创建临时层,不会增加镜像大小。 比如 EXPOSE、VOLUME 指令就不会生成新层。 多条 RUN 命令可通过连接符连接成一条指令集以减少层数。 通过多段构建减少镜像层数。 把多行参数按字母排序,可以减少可能出现的重复参数,并且提高可读性。 编写 dockerfile 的时候,应该把变更频率低的编译指令优先构建以便放在镜像底层以有效利用 build cache。 复制文件时,每个文件应独立复制,这确保某个文件变更时,只影响该文件对应的缓存。 ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:6:0","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"参考 《每天 5 分钟玩转 Docker 容器技术》 https://www.runoob.com/docker/docker-dockerfile.html https://docs.docker.com/engine/reference/builder/ https://yeasy.gitbook.io/docker_practice/image/multistage-builds ","date":"2022-02-28","objectID":"/dokcer-dockerfile/:7:0","tags":["docker","容器"],"title":"Docker Dockerfile","uri":"/dokcer-dockerfile/"},{"categories":["docker"],"content":"完整示例请移步 Github ","date":"2022-02-28","objectID":"/docker-network-communications/:0:0","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"docker 网卡介绍 $ docker network ls NETWORK ID NAME DRIVER SCOPE 60ca2bcc3925 bridge bridge local 4fba5fc9c823 host host local 15b262a2e321 none null local bridge:默认网卡,类似于 VMware 的 NAT 模式,如果需要访问容器内部的端口需要进行端口映射。 host:直接使用主机网络,类似于 VMware 的桥接模式,访问容器内部的端口时不需要进行端口映射,直接访问即可,但是可能会与主机的端口号冲突。 none:禁止所有联网,没有网络驱动。 ","date":"2022-02-28","objectID":"/docker-network-communications/:1:0","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"docker 自定义网络 使用 docker network create custom-local-net 创建一个名为 custom-local-net 的 Docker 网卡,这个网卡是基于 bridge 模式的,但是和 bridge 模式又有一定的区别。 用户自定义的网络是基于 bridge 的,并且容器间可以通过容器名或别名进行自动 DNS 相互解析的功能! 使用默认的 bridge 网络仅仅能通过各自的 IP 地址来进行通信,除非使用 –link 选项 –link 选项实现的容器相互访问功能已经被官方认定是过时的 ","date":"2022-02-28","objectID":"/docker-network-communications/:2:0","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"link 方式共享 ","date":"2022-02-28","objectID":"/docker-network-communications/:3:0","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"docker run 使用 link 参数 在不使用 Docker Compose 的时候,在启动命令中加入 –link 参数,就可以实现容器之间的访问 # 启动 prometheus 容器 docker run --rm --name prometheus -p 9090:9090 -d quay.io/prometheus/prometheus # 启动 grafana 容器 docker run --rm --name grafana -p 3000:3000 -d grafana/grafana # 进入 grafana 容器,查看是否可以 ping 通 docker exec -it 60ca2bcc3925 bash ping prometheus ","date":"2022-02-28","objectID":"/docker-network-communications/:3:1","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"docker compose 使用 links 参数 相比使用 docker run 命令启动容器,显然 docker compose 才是更为推荐的那一个方式,把启动所需的镜像、环境等全都写到一份 docker-compose.yaml 文件中,方便使用。 如果使用 docker compose 的方式,就是把两个容器的启动信息都写到同一份文件中,再在需要依赖另一个容器的容器启动信息中加入 depends_on、links 参数,这样 grafana 也可以使用 prometheus 解析到其容器的 IP 地址,得到的结果和上面使用 docker run 的方式得到的结果是一样的。 version: '3.4' services: prometheus: image: quay.io/prometheus/prometheus container_name: prometheus hostname: prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - ./rules:/etc/prometheus/rules grafana: image: grafana/grafana container_name: grafana hostname: grafana ports: - 3000:3000 volumes: - ./grafana.ini:/etc/grafana/grafana.ini depends_on: - prometheus links: - prometheus ","date":"2022-02-28","objectID":"/docker-network-communications/:3:2","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"自定义网络共享 在上面我们简单地介绍了一下如何自定义网络,并且知道使用 link 参数的方式已经是过时的,那通过自定义网络,让 grafana 容器和 prometheus 容器处于同一个网络,并且他们可以互相进行 DNS 解析,就可以让 grafana 访问到 prometheus 容器了。 ","date":"2022-02-28","objectID":"/docker-network-communications/:4:0","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"docker run 使用自定义网络 docker run 命令也可以手动指定容器连接的网络,使用 network 参数。 # 自定义网络 custom-local-net docker network create custom-local-net # 启动 prometheus 容器 docker run --rm --name prometheus -p 9090:9090 --network custom-local-net -d quay.io/prometheus/prometheus # 启动 grafana 容器 docker run --rm --name grafana -p 3000:3000 --network custom-local-net -d grafana/grafana # 进入 grafana 容器中 ping prometheus 查看是否能ping 通 docker exec -it 60ca2bcc3925 bash ping prometheus ","date":"2022-02-28","objectID":"/docker-network-communications/:4:1","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"docker compose 使用自定义网络 docker-compose.yaml 文件中有两种使用自定义网络的方式: 创建并使用,如果没有手动使用 docker network create 命令,需要在使用前创建 version: '3.4' services: prometheus: image: quay.io/prometheus/prometheus container_name: prometheus hostname: prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - ./rules:/etc/prometheus/rules networks: - custom-local-net-1 grafana: image: grafana/grafana container_name: grafana hostname: grafana ports: - 3000:3000 volumes: - ./grafana.ini:/etc/grafana/grafana.ini depends_on: - prometheus networks: - custom-local-net-1 networks: custom-local-net-1: # 声明使用的网络是使用 bridge 驱动来创建的 driver: bridge ipam: # 网络配置 config: # 分配的子网网段 - subnet: 172.25.64.0/18 # 网关地址 gateway: 172.25.64.1 声明并使用,如果已经手动创建了网络,在 docker-compose.yaml 文件中只需声明一下即可 version: '3.4' services: prometheus: image: quay.io/prometheus/prometheus container_name: prometheus hostname: prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - ./rules:/etc/prometheus/rules networks: - custom-local-net grafana: image: grafana/grafana container_name: grafana hostname: grafana ports: - 3000:3000 volumes: - ./grafana.ini:/etc/grafana/grafana.ini depends_on: - prometheus networks: - custom-local-net networks: custom-local-net: # 声明这个网络是外部定义的 external: true ","date":"2022-02-28","objectID":"/docker-network-communications/:4:2","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"总结 Docker 容器之间相互访问是实际生产中难以绕开的一道坎,诚然可以使用桥接模式,但是桥接模式不太利于环境的迁移。显然使用能自动完成 DNS 解析的网络模式会更为灵活,也更为优雅。 ","date":"2022-02-28","objectID":"/docker-network-communications/:5:0","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"参考 https://juejin.cn/post/7143883774818795527 ","date":"2022-02-28","objectID":"/docker-network-communications/:6:0","tags":["docker","容器"],"title":"Docker 网络互通","uri":"/docker-network-communications/"},{"categories":["docker"],"content":"Dcoker 基本概念 Docker 包括三个基本概念: 镜像(Image):Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 示例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。 仓库(Repository):仓库(Repository)类似 Git 的远程仓库,集中存放镜像文件。 三者之间的关系如下: ","date":"2022-02-26","objectID":"/docker-command/:1:0","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"服务 查看 Docker 版本信息 docker version 启动 Docker service docker start 重启 docker 服务 service docker restart 关闭 docker 服务 service docker stop 查看 Docker 信息 docker info 查看命令帮助 docker help docker help run ","date":"2022-02-26","objectID":"/docker-command/:2:0","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"容器 ","date":"2022-02-26","objectID":"/docker-command/:3:0","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"容器生命周期管理 docker run 创建一个新的容器并运行一个命令,语法 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] OPTIONS 说明: -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项; -d: 后台运行容器,并返回容器 ID; -i: 以交互模式运行容器,通常与 -t 同时使用; -P: 随机端口映射,容器内部端口随机映射到主机的端口 -p: 指定端口映射,格式为:主机(宿主)端口:容器端口 -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用; –name=“nginx-lb”: 为容器指定一个名称; –dns 8.8.8.8: 指定容器使用的 DNS 服务器,默认和宿主一致; –dns-search example.com: 指定容器 DNS 搜索域名,默认和宿主一致; -h “mars”: 指定容器的 hostname; -e username=“ritchie”: 设置环境变量; –env-file=[]: 从指定文件读入环境变量; –cpuset=“0-2” or –cpuset=“0,1,2”: 绑定容器到指定 CPU 运行; -m :设置容器使用内存最大值; –net=“bridge”: 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型; –link=[]: 添加链接到另一个容器; –expose=[]: 开放一个端口或一组端口; –volume , -v: 绑定一个卷 示例 使用 docker 镜像 nginx:latest 以后台模式启动一个容器,并将容器命名为 mynginx。 docker run --name mynginx -d nginx:latest 使用镜像 nginx:latest 以后台模式启动一个容器,并将容器的 80 端口映射到主机随机端口。 docker run -P -d nginx:latest 使用镜像 nginx:latest,以后台模式启动一个容器,将容器的 80 端口映射到主机的 80 端口,主机的目录 /data 映射到容器的 /data。 docker run -p 80:80 -v /data:/data -d nginx:latest 绑定容器的 8080 端口,并将其映射到本地主机 127.0.0.1 的 80 端口上。 $ docker run -p 127.0.0.1:80:8080/tcp ubuntu bash 使用镜像 nginx:latest 以交互模式启动一个容器,在容器内执行/bin/bash 命令。 ubuntu@ubuntu:~$ docker run -it nginx:latest /bin/bash root@b8573233d675:/# docker start 启动一个或多个已经被停止的容器,语法docker start [OPTIONS] CONTAINER [CONTAINER...] docker stop 停止一个运行中的容器,语法docker stop [OPTIONS] CONTAINER [CONTAINER...] docker restart 重启容器,语法docker restart [OPTIONS] CONTAINER [CONTAINER...] docker kill 杀掉一个运行中的容器,语法docker kill [OPTIONS] CONTAINER [CONTAINER...] OPTIONS 说明: -s :向容器发送一个信号 示例 杀掉运行中的容器 mynginx ubuntu@ubuntu:~$ docker kill -s KILL mynginx mynginx docker rm 删除一个或多个容器,语法docker rm [OPTIONS] CONTAINER [CONTAINER...] OPTIONS 说明: -f :通过 SIGKILL 信号强制删除一个运行中的容器。 -l :移除容器间的网络连接,而非容器本身。 -v :删除与容器关联的卷。 示例 强制删除容器 db01、db02: docker rm -f db01 db02 docker pause 暂停容器中所有的进程,语法docker pause CONTAINER [CONTAINER...] docker unpause 恢复容器中所有的进程,语法docker unpause CONTAINER [CONTAINER...] 示例 暂停数据库容器 db01 提供服务。 docker pause db01 恢复数据库容器 db01 提供服务。 docker unpause db01 docker create 创建一个新的容器但不启动它,用法同 docker run 语法docker create [OPTIONS] IMAGE [COMMAND] [ARG...],语法同 docker run 示例 使用 docker 镜像 nginx:latest 创建一个容器,并将容器命名为 myubuntu ubuntu@ubuntu:~$ docker create --name myubuntu nginx:latest 09b93464c2f75b7b69f83d56a9cfc23ceb50a48a9db7652ee4c27e3e2cb1961f docker exec 在运行的容器中执行命令,语法docker exec [OPTIONS] CONTAINER COMMAND [ARG...] OPTIONS 说明: -d :分离模式: 在后台运行 -i :即使没有附加也保持 STDIN 打开 -t :分配一个伪终端 示例 在容器 mynginx 中以交互模式执行容器内 /root/ubuntu.sh 脚本: ubuntu@ubuntu:~$ docker exec -it mynginx /bin/sh /root/ubuntu.sh http://www.ubuntu.com/ 在容器 mynginx 中开启一个交互模式的终端: ubuntu@ubuntu:~$ docker exec -i -t mynginx /bin/bash root@b1a0703e41e7:/# ","date":"2022-02-26","objectID":"/docker-command/:3:1","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"容器操作 docker ps 列出容器,语法docker ps [OPTIONS] OPTIONS 说明: -a :显示所有的容器,包括未运行的。 -f :根据条件过滤显示的内容。 –format :指定返回值的模板文件。 -l :显示最近创建的容器。 -n :列出最近创建的 n 个容器。 –no-trunc :不截断输出。 -q :静默模式,只显示容器编号。 -s :显示总的文件大小。 示例 列出所有在运行的容器信息。 ubuntu@ubuntu:~$ docker ps CONTAINER ID IMAGE COMMAND ... PORTS NAMES 09b93464c2f7 nginx:latest \"nginx -g 'daemon off\" ... 80/tcp, 443/tcp myubuntu 96f7f14e99ab mysql:5.6 \"docker-entrypoint.sh\" ... 0.0.0.0:3306-\u003e3306/tcp mymysql 输出详情介绍: CONTAINER ID: 容器 ID。 IMAGE: 使用的镜像。 COMMAND: 启动容器时运行的命令。 CREATED: 容器的创建时间。 STATUS: 容器状态。状态有 7 种: created(已创建) restarting(重启中) running(运行中) removing(迁移中) paused(暂停) exited(停止) dead(死亡) PORTS: 容器的端口信息和使用的连接类型(tcp\\udp)。 NAMES: 自动分配的容器名称。 docker inspect 获取容器/镜像的元数据,语法docker inspect [OPTIONS] NAME|ID [NAME|ID...] OPTIONS 说明: -f :指定返回值的模板文件。 -s :显示总的文件大小。 –type :为指定类型返回 JSON。 docker top 查看容器中运行的进程信息,支持 ps 命令参数,语法docker top [OPTIONS] CONTAINER [ps OPTIONS] 容器运行时不一定有/bin/bash 终端来交互执行 top 命令,而且容器还不一定有 top 命令,可以使用 docker top 来实现查看 container 中正在运行的进程。 docker attach 连接到正在运行中的容器,语法docker attach [OPTIONS] CONTAINER 要 attach 上去的容器必须正在运行,可以同时连接上同一个 container 来共享屏幕(与 screen 命令的 attach 类似)。 docker events 从服务器获取实时事件,语法docker events [OPTIONS] OPTIONS 说明: -f :根据条件过滤事件; –since :从指定的时间戳后显示所有事件; –until :流水时间显示到指定的时间为止; docker logs 获取容器的日志,语法docker logs [OPTIONS] CONTAINER OPTIONS 说明: -f : 跟踪日志输出 –since :显示某个开始时间的所有日志 -t : 显示时间戳 –tail :仅列出最新 N 条容器日志 docker wait 阻塞运行直到容器停止,然后打印出它的退出代码,语法docker wait [OPTIONS] CONTAINER [CONTAINER...] docker export 将文件系统作为一个 tar 归档文件导出到 STDOUT,语法docker export [OPTIONS] CONTAINER OPTIONS 说明: -o :将输入内容写到文件。 示例 将 id 为 a404c6c174a2 的容器按日期保存为 tar 文件。 ubuntu@ubuntu:~$ docker export -o mysql-`date +%Y%m%d`.tar a404c6c174a2 ubuntu@ubuntu:~$ ls mysql-`date +%Y%m%d`.tar mysql-20160711.tar docker port 用于列出指定的容器的端口映射,或者查找将 PRIVATE_PORT NAT 到面向公众的端口。 语法docker port [OPTIONS] CONTAINER [PRIVATE_PORT[/PROTO]] docker stats 显示容器资源的使用情况,包括:CPU、内存、网络 I/O 等。 语法docker stats [OPTIONS] [CONTAINER...] OPTIONS 说明: –all , -a :显示所有的容器,包括未运行的。 –format :指定返回值的模板文件。 –no-stream :展示当前状态就直接退出了,不再实时更新。 –no-trunc :不截断输出。 示例 列出所有在运行的容器信息。 ubuntu@ubuntu:~$ docker stats CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS b95a83497c91 awesome_brattain 0.28% 5.629MiB / 1.952GiB 0.28% 916B / 0B 147kB / 0B 9 67b2525d8ad1 foobar 0.00% 1.727MiB / 1.952GiB 0.09% 2.48kB / 0B 4.11MB / 0B 2 输出详情介绍: CONTAINER ID 与 NAME: 容器 ID 与名称。 CPU % 与 MEM %: 容器使用的 CPU 和内存的百分比。 MEM USAGE / LIMIT: 容器正在使用的总内存,以及允许使用的内存总量。 NET I/O: 容器通过其网络接口发送和接收的数据量。 BLOCK I/O: 容器从主机上的块设备读取和写入的数据量。 PIDs: 容器创建的进程或线程数。 以 JSON 格式输出: ubuntu@ubuntu:~$ docker stats nginx --no-stream --format \"{{ json . }}\" {\"BlockIO\":\"0B / 13.3kB\",\"CPUPerc\":\"0.03%\",\"Container\":\"nginx\",\"ID\":\"ed37317fbf42\",\"MemPerc\":\"0.24%\",\"MemUsage\":\"2.352MiB / 982.5MiB\",\"Name\":\"nginx\",\"NetIO\":\"539kB / 606kB\",\"PIDs\":\"2\"} ","date":"2022-02-26","objectID":"/docker-command/:3:2","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"容器 rootfs 命令 docker commit 从容器创建一个新的镜像,语法docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] OPTIONS 说明: -a :提交的镜像作者; -c :使用 Dockerfile 指令来创建镜像; -m :提交时的说明文字; -p :在 commit 时,将容器暂停。 示例 将容器 a404c6c174a2 保存为新的镜像,并添加提交人信息和说明信息。 ubuntu@ubuntu:~$ docker commit -a \"ubuntu.com\" -m \"my apache\" a404c6c174a2 mymysql:v1 sha256:37af1236adef1544e8886be23010b66577647a40bc02c0885a6600b33ee28057 ubuntu@ubuntu:~$ docker images mymysql:v1 REPOSITORY TAG IMAGE ID CREATED SIZE mymysql v1 37af1236adef 15 seconds ago 329 MB docker cp 用于容器与主机之间的数据拷贝。 语法 docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|- docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH OPTIONS 说明: -L :保持源目标中的链接 示例 将主机/www/ubuntu 目录拷贝到容器 96f7f14e99ab 的/www 目录下。 docker cp /www/ubuntu 96f7f14e99ab:/www/ 将主机/www/ubuntu 目录拷贝到容器 96f7f14e99ab 中,目录重命名为 www。 docker cp /www/ubuntu 96f7f14e99ab:/www 将容器 96f7f14e99ab 的/www 目录拷贝到主机的/tmp 目录中。 docker cp 96f7f14e99ab:/www /tmp/ docker diff 检查容器里文件结构的更改,语法docker diff [OPTIONS] CONTAINER ","date":"2022-02-26","objectID":"/docker-command/:3:3","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"镜像仓库 ","date":"2022-02-26","objectID":"/docker-command/:4:0","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker login 登陆到一个 Docker 镜像仓库,如果未指定镜像仓库地址,默认为官方仓库 Docker Hub 语法docker login [OPTIONS] [SERVER] OPTIONS 说明: -u :登陆的用户名 -p :登陆的密码 示例 登陆到 Docker Hub docker login -u 用户名 -p 密码 # 登出 Docker Hub # docker logout ","date":"2022-02-26","objectID":"/docker-command/:4:1","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker logout 登出一个 Docker 镜像仓库,如果未指定镜像仓库地址,默认为官方仓库 Docker Hub 语法docker logout [OPTIONS] [SERVER] ","date":"2022-02-26","objectID":"/docker-command/:4:2","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker pull 从镜像仓库中拉取或者更新指定镜像,语法docker pull [OPTIONS] NAME[:TAG|@DIGEST] OPTIONS 说明: -a :拉取所有 tagged 镜像 –disable-content-trust :忽略镜像的校验,默认开启 ","date":"2022-02-26","objectID":"/docker-command/:4:3","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker push 将本地的镜像上传到镜像仓库,要先登陆到镜像仓库 语法docker push [OPTIONS] NAME[:TAG] OPTIONS 说明: –disable-content-trust :忽略镜像的校验,默认开启 ","date":"2022-02-26","objectID":"/docker-command/:4:4","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker search 从 Docker Hub 查找镜像,语法docker search [OPTIONS] TERM OPTIONS 说明: –automated :只列出 automated build 类型的镜像; –no-trunc :显示完整的镜像描述; -f \u003c过滤条件\u003e:列出收藏数不小于指定值的镜像。 示例 从 Docker Hub 查找所有镜像名包含 java,并且收藏数大于 10 的镜像 ubuntu@ubuntu:~$ docker search -f stars=10 java NAME DESCRIPTION STARS OFFICIAL AUTOMATED java Java is a concurrent, class-based... 1037 [OK] anapsix/alpine-java Oracle Java 8 (and 7) with GLIBC ... 115 [OK] develar/java 46 [OK] 参数说明: NAME: 镜像仓库源的名称 DESCRIPTION: 镜像的描述 OFFICIAL: 是否 docker 官方发布 stars: 类似 Github 里面的 star,表示点赞、喜欢的意思。 AUTOMATED: 自动构建。 ","date":"2022-02-26","objectID":"/docker-command/:4:5","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"本地镜像管理 ","date":"2022-02-26","objectID":"/docker-command/:5:0","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker images 列出本地镜像,语法docker images [OPTIONS] [REPOSITORY[:TAG]] OPTIONS 说明: -a :列出本地所有的镜像(含中间映像层,默认情况下,过滤掉中间映像层); –digests :显示镜像的摘要信息; -f :显示满足条件的镜像; –format :指定返回值的模板文件; –no-trunc :显示完整的镜像信息; -q :只显示镜像 ID。 ","date":"2022-02-26","objectID":"/docker-command/:5:1","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker rmi 删除本地一个或多个镜像,语法docker rmi [OPTIONS] IMAGE [IMAGE...] OPTIONS 说明: -f :强制删除; –no-prune :不移除该镜像的过程镜像,默认移除; ","date":"2022-02-26","objectID":"/docker-command/:5:2","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker tag 标记本地镜像,将其归入某一仓库。 语法docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] 示例 将镜像 ubuntu:15.10 标记为 ubuntu/ubuntu:v3 镜像。 root@ubuntu:~# docker tag ubuntu:15.10 ubuntu/ubuntu:v3 root@ubuntu:~# docker images ubuntu/ubuntu:v3 REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu/ubuntu v3 4e3b13c8a266 3 months ago 136.3 MB ","date":"2022-02-26","objectID":"/docker-command/:5:3","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker build 命令用于使用 Dockerfile 创建镜像。 语法docker build [OPTIONS] PATH | URL | - OPTIONS 说明: –build-arg=[] :设置镜像创建时的变量; –cpu-shares :设置 cpu 使用权重; –cpu-period :限制 CPU CFS 周期; –cpu-quota :限制 CPU CFS 配额; –cpuset-cpus :指定使用的 CPU id; –cpuset-mems :指定使用的内存 id; –disable-content-trust :忽略校验,默认开启; -f :指定要使用的 Dockerfile 路径; –force-rm :设置镜像过程中删除中间容器; –isolation :使用容器隔离技术; –label=[] :设置镜像使用的元数据; -m :设置内存最大值; –memory-swap :设置 Swap 的最大值为内存+swap,\"-1\"表示不限 swap; –no-cache :创建镜像的过程不使用缓存; –pull :尝试去更新镜像的新版本; –quiet, -q :安静模式,成功后只输出镜像 ID; –rm :设置镜像成功后删除中间容器; –shm-size :设置/dev/shm 的大小,默认值是 64M; –ulimit :Ulimit 配置。 –squash :将 Dockerfile 中所有的操作压缩为一层。 –tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。 –network: 默认 default。在构建期间设置 RUN 指令的网络模式 示例 使用当前目录的 Dockerfile 创建镜像,标签为 ubuntu/ubuntu:v1。 docker build -t ubuntu/ubuntu:v1 . 使用 URL github.com/creack/docker-firefox 的 Dockerfile 创建镜像。 docker build github.com/creack/docker-firefox 也可以通过 -f Dockerfile 文件的位置: $ docker build -f /path/to/a/Dockerfile . ","date":"2022-02-26","objectID":"/docker-command/:5:4","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker history 查看指定镜像的创建历史,语法docker history [OPTIONS] IMAGE OPTIONS 说明: -H :以可读的格式打印镜像大小和日期,默认为 true; –no-trunc :显示完整的提交记录; -q :仅列出提交记录 ID。 ","date":"2022-02-26","objectID":"/docker-command/:5:5","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker save 将指定镜像保存成 tar 归档文件,语法docker save [OPTIONS] IMAGE [IMAGE...] OPTIONS 说明: -o :输出到的文件。 示例 将镜像 ubuntu/ubuntu:v3 生成 my_ubuntu_v3.tar 文档 ubuntu@ubuntu:~$ docker save -o my_ubuntu_v3.tar ubuntu/ubuntu:v3 ubuntu@ubuntu:~$ ll my_ubuntu_v3.tar -rw------- 1 ubuntu ubuntu 142102016 Jul 11 01:37 my_ubuntu_v3.ta ","date":"2022-02-26","objectID":"/docker-command/:5:6","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker load 导入使用 docker save 命令导出的镜像,语法docker load [OPTIONS] OPTIONS 说明: –input , -i : 指定导入的文件,代替 STDIN。 –quiet , -q : 精简输出信息。 示例 $ docker load \u003c busybox.tar.gz $ docker load --input fedora.tar ","date":"2022-02-26","objectID":"/docker-command/:5:7","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"docker import 从归档文件中创建镜像,语法docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] OPTIONS 说明: -c :应用 docker 指令创建镜像; -m :提交时的说明文字; 示例 从镜像归档文件 my_ubuntu_v3.tar 创建镜像,命名为 ubuntu/ubuntu:v4 ubuntu@ubuntu:~$ docker import my_ubuntu_v3.tar ubuntu/ubuntu:v4 sha256:63ce4a6d6bc3fabb95dbd6c561404a309b7bdfc4e21c1d59fe9fe4299cbfea39 ubuntu@ubuntu:~$ docker images ubuntu/ubuntu:v4 REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu/ubuntu v4 63ce4a6d6bc3 20 seconds ago 142.1 MB ","date":"2022-02-26","objectID":"/docker-command/:5:8","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"参考 https://cloud.tencent.com/developer/article/1772136 https://www.runoob.com/docker/docker-command-manual.html ","date":"2022-02-26","objectID":"/docker-command/:6:0","tags":["docker","容器"],"title":"Docker 常用命令","uri":"/docker-command/"},{"categories":["docker"],"content":"容器的实现原理 从本质上,容器其实就是一种沙盒技术。就好像把应用隔离在一个盒子内,使其运行。因为有了盒子边界的存在,应用于应用之间不会相互干扰。并且像集装箱一样,拿来就走,随处运行。 实现容器的核心,就是要生成限制应用运行时的边界。我们知道,编译后的可执行代码加上数据,叫做程序。而把程序运行起来后,就变成了进程,也就是所谓的应用。如果能在应用启动时,给其加上一个边界,这样不就能实现期待的沙盒吗? 在 Linux 中,实现容器的边界,主要有两种技术 Cgroups 和 Namespace。 Cgroups 用于对运行的容器进行资源的限制,Namespace 则会将容器隔离起来,实现边界。 这样看来,容器只是一种被限制的了特殊进程而已。 ","date":"2022-02-22","objectID":"/docker-implementation/:1:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"Docker 容器 基于 Linux 内核的 Cgroup,Namespace,以及 Union FS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术,由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。 最初实现是基于 LXC,从 0.7 以后开始去除 LXC,转而使用自行开发的 Libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 Containerd。 Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护,使得 Docker 技术比虚拟机技术更为轻便、快捷。 ","date":"2022-02-22","objectID":"/docker-implementation/:2:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"为什么要用 Docker 更高效地利用系统资源 更快速的启动时间 一致的运行环境 持续交付和部署 更轻松地迁移 更轻松地维护和扩展 ","date":"2022-02-22","objectID":"/docker-implementation/:3:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"虚拟机和容器 性能对比: 特性 容器 虚拟机 启动速度 秒级 分钟级 硬盘使用 一般为 MB 一般为 GB 性能 接近原生 弱于原生 系统支持量 单机支持上千个容器 一般几十个 ","date":"2022-02-22","objectID":"/docker-implementation/:4:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"Namespace Linux Namespace 是一种 Linux Kernel 提供的资源隔离方案: 系统可以为进程分配不同的 Namespace; 并保证不同的 Namespace 资源独立分配、进程彼此隔离,即不同的 Namespace 下的进程互不干扰 。 ","date":"2022-02-22","objectID":"/docker-implementation/:5:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"Pid namespace 不同用户的进程就是通过 Pid namespace 隔离开的,且不同 namespace 中可以有相同 Pid。 有了 Pid namespace, 每个 namespace 中的 Pid 能够相互隔离。 ","date":"2022-02-22","objectID":"/docker-implementation/:5:1","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"net namespace 网络隔离是通过 net namespace 实现的, 每个 net namespace 有独立的 network devices, IP addresses, IP routing tables, /proc/net 目录。 Docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge: docker0 连接在一起。 ","date":"2022-02-22","objectID":"/docker-implementation/:5:2","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"ipc namespace Container 中进程交互还是采用 linux 常见的进程间交互方法 (interprocess communication – IPC), 包括常见的信号量、消息队列和共享内存。 container 的进程间交互实际上还是 host 上 具有相同 Pid namespace 中的进程间交互,因此需要在 IPC 资源申请时加入 namespace 信息 - 每个 IPC 资源有一个唯一的 32 位 ID。 ","date":"2022-02-22","objectID":"/docker-implementation/:5:3","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"mnt namespace mnt namespace 允许不同 namespace 的进程看到的文件结构不同,这样每个 namespace 中的进程所看到的文件目录就被隔离开了。 ","date":"2022-02-22","objectID":"/docker-implementation/:5:4","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"uts namespace UTS(“UNIX Time-sharing System”) namespace 允许每个 container 拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 Host 上的一个进程。 ","date":"2022-02-22","objectID":"/docker-implementation/:5:5","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"user namespace 每个 container 可以有不同的 user 和 group id, 也就是说可以在 container 内部用 container 内部的用户执行程序而非 Host 上的用户。 ","date":"2022-02-22","objectID":"/docker-implementation/:5:6","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"Cgroups Cgroups (Control Groups)是 Linux 下用于对一个或一组进程进行资源控制和监控的机制; 可以对诸如 CPU 使用时间、内存、磁盘 I/O 等进程所需的资源进行限制; 不同资源的具体管理工作由相应的 Cgroup 子系统(Subsystem)来实现 ; 针对不同类型的资源限制,只要将限制策略在不同的的子系统上进行关联即可 ; Cgroups 在不同的系统资源管理子系统中以层级树(Hierarchy)的方式来组织管理:每个 Cgroup 都可以包含其他的子 Cgroup,因此子 Cgroup 能使用的资源除了受本 Cgroup 配置的资源参数限制,还受到父 Cgroup 设置的资源限制 。 ","date":"2022-02-22","objectID":"/docker-implementation/:6:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"Union FS Union Fs 有以下特性: 将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)的文件系统 支持为每一个成员目录(类似 Git Branch)设定 readonly、readwrite 和 whiteout-able 权限 文件系统分层, 对 readonly 权限的 branch 可以逻辑上进行修改(增量地, 不影响 readonly 部分的)。 通常 Union FS 有两个用途, 一方面可以将多个 disk 挂到同一个目录下, 另一个更常用的就是将一个 readonly 的 branch 和一个 writeable 的 branch 联合在一起。 Linux 的命名空间和控制组分别解决了不同资源隔离的问题,前者解决了进程、网络以及文件系统的隔离,后者实现了 CPU、内存等资源的隔离,但是在 Docker 中还有另一个非常重要的问题需要解决 - 也就是镜像。 镜像实现就是依靠的 Union Fs。 Docker 镜像其实本质就是一个压缩包,我们可以使用下面的命令将一个 Docker 镜像中的文件导出: $ docker export $(docker create busybox) | tar -C rootfs -xvf - $ ls bin dev etc home proc root sys tmp usr var 可以看到这个 busybox 镜像中的目录结构与 Linux 操作系统的根目录中的内容并没有太多的区别,可以说 Docker 镜像就是一个文件。 Docker 中的每一个镜像都是由一系列只读的层组成的,Dockerfile 中的每一个命令都会在已有的只读层上创建一个新的层: FROM ubuntu:15.04 COPY . /app RUN make /app CMD python /app/app.py 容器中的每一层都只对当前容器进行了非常小的修改,上述的 Dockerfile 文件会构建一个拥有四层 layer 的镜像: 当镜像被 docker run 命令创建时就会在镜像的最上层添加一个可写的层,也就是容器层,所有对于运行时容器的修改其实都是对这个容器读写层的修改。 容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。 ","date":"2022-02-22","objectID":"/docker-implementation/:7:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"参考 https://www.cnblogs.com/michael9/p/13039700.html https://draveness.me/docker/ https://blog.csdn.net/crazymakercircle/article/details/120747767 ","date":"2022-02-22","objectID":"/docker-implementation/:8:0","tags":["docker","容器"],"title":"Docker 实现原理","uri":"/docker-implementation/"},{"categories":["docker"],"content":"使用官方安装脚本自动安装 注意:脚本安装适合在测试和开发环境使用。 安装命令如下: curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh --mirror Aliyun 也可以使用国内 daocloud 一键安装命令: curl -sSL https://get.daocloud.io/docker | sh ","date":"2022-02-19","objectID":"/docker-install/:1:0","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"手动安装 ","date":"2022-02-19","objectID":"/docker-install/:2:0","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"更新库和安装依赖 apt-get update apt-get install \\ ca-certificates \\ curl \\ gnupg \\ lsb-release ","date":"2022-02-19","objectID":"/docker-install/:2:1","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"添加 docker 的官方 GPG key mkdir -p /etc/apt/keyrings # 官方源 # curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod -R 0755 /etc/apt/keyrings ","date":"2022-02-19","objectID":"/docker-install/:2:2","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"将 Docker 的源添加到 /etc/apt/sources.list.d # 官方源 # echo \\ # \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \\ # $(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list \u003e /dev/null echo \\ \"deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \\ $(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list \u003e /dev/null ","date":"2022-02-19","objectID":"/docker-install/:2:3","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"安装 docker apt-get update apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin ","date":"2022-02-19","objectID":"/docker-install/:3:0","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"启动 docker service enable docker service start docker # wsl2 ubuntu 22.04 启动报错,执行以下命令修复 # update-alternatives --set iptables /usr/sbin/iptables-legacy # update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy ","date":"2022-02-19","objectID":"/docker-install/:4:0","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"配置国内镜像源 创建或修改 /etc/docker/daemon.json 文件,修改为如下形式 { \"registry-mirrors\": [ \"http://hub-mirror.c.163.com\", \"https://docker.mirrors.ustc.edu.cn\", \"https://registry.docker-cn.com\" ] } 重启 docker,service docker restart。 查看是否修改成功,docker info。 ","date":"2022-02-19","objectID":"/docker-install/:5:0","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["docker"],"content":"参考 https://docs.docker.com/engine/install/ubuntu/ https://www.runoob.com/docker/ubuntu-docker-install.html https://yeasy.gitbook.io/docker_practice/install/ubuntu ","date":"2022-02-19","objectID":"/docker-install/:6:0","tags":["docker","容器"],"title":"Ubuntu 安装 Docker","uri":"/docker-install/"},{"categories":["Git"],"content":"简介 GitLab 是一个基于 Git 的平台,提供对 Git 存储库的远程访问,并通过创建用于管理代码的私有和公共存储库,有助于软件开发周期。 GitLab 支持不同类型的操作系统,例如:Windows,Ubuntu,Debian,CentOS 等。 ","date":"2021-06-08","objectID":"/gitlab-install/:1:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"安装 apt 安装 # 安装依赖 sudo apt update sudo apt-get upgrade sudo apt install ca-certificates curl openssh-server ca-certificates postfix # 执行脚本 cd /tmp curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh |sudo bash # 修改源 # sudo vim /etc/apt/sources.list.d/gitlab_gitlab-ce.list # 修改成以下内容 # deb [signed-by=/usr/share/keyrings/gitlab_gitlab-ce-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu focal main # deb-src [signed-by=/usr/share/keyrings/gitlab_gitlab-ce-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu focal main # 更新 # sudo apt update # 安装 sudo apt install gitlab-ce 调整防火墙规则 sudo ufw allow http sudo ufw allow https sudo ufw allow OpenSSH 编辑 Gitlab 配置文件 vim /etc/gitlab/gitlab.rb 修改 external_url external_url 'https://example.com' // 此处修改为您的域名或ip地址 开机启动和禁止 sudo systemctl enable gitlab-runsvdir.service sudo systemctl disable gitlab-runsvdir.service ","date":"2021-06-08","objectID":"/gitlab-install/:2:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"常用命令 常用命令 说明 sudo gitlab-ctl reconfigure 重新加载配置,每次修改/etc/gitlab/gitlab.rb 文件之后执行 sudo gitlab-ctl status 查看 GitLab 状态 sudo gitlab-ctl start 启动 GitLab sudo gitlab-ctl stop 停止 GitLab sudo gitlab-ctl restart 重启 GitLab sudo gitlab-ctl tail 查看所有日志 sudo gitlab-ctl tail nginx/gitlab_acces.log 查看 nginx 访问日志 sudo gitlab-ctl tail postgresql 查看 postgresql 日志 ","date":"2021-06-08","objectID":"/gitlab-install/:3:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"配置密码 在 Web 浏览器中访问 GitLab 服务器的域名: https://example.com // 您external_url配置的地址 启动成功后,默认有个管理员账号 登录名:root 登录密码:初始密码在这个文件中/etc/gitlab/initial_root_password (可更改) ","date":"2021-06-08","objectID":"/gitlab-install/:4:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"设置克隆地址 进入 vim /opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml 设置 gitlab:host ","date":"2021-06-08","objectID":"/gitlab-install/:5:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"邮箱配置 开启 qq 邮箱的 POP3/SMTP 服务并保存好授权码 这一步在 qq 邮箱的设置 -\u003e 账户中 点击开启按照提示步骤操作会获得相应的授权码 修改配置文件 sudo vim /etc/gitlab/gitlab.rb #配置邮箱来源, 与展示的名称 gitlab_rails['gitlab_email_enabled'] = true gitlab_rails['gitlab_email_from'] = '您的qq邮箱地址' gitlab_rails['gitlab_email_display_name'] = '您的邮箱显示名称' #smtp配置 gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = \"smtp.qq.com\" gitlab_rails['smtp_port'] = 465 gitlab_rails['smtp_user_name'] = \"您的qq邮箱地址\" gitlab_rails['smtp_password'] = \"您的授权码\" gitlab_rails['smtp_domain'] = \"smtp.qq.com\" gitlab_rails['smtp_authentication'] = \"login\" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_tls'] = true # 重新加载配置 sudo gitlab-ctl reconfigure 发送测试邮件 sudo gitlab-rails console #进入控制台,然后发送邮件 Notify.test_email('测试邮箱地址', '邮件标题', '邮件正文').deliver_now ","date":"2021-06-08","objectID":"/gitlab-install/:6:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"修改端口 sudo vim /etc/gitlab/gitlab.rb nginx['listen_port'] = 9091 // GitLab端口,默认80端口 unicorn['port'] = 9092 // 可不修改,默认监听8080端口 sudo gitlab-ctl reconfigure ","date":"2021-06-08","objectID":"/gitlab-install/:7:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"Git Pages 设置 sudo vim /etc/gitlab/gitlab.rb gitlab_pages[‘enable’] = true; 开启 Pages 服务 pages_external_url ‘您的GitLab Pages域名地址'; 替换成你自己的域名 gitlab_pages[‘inplace_chroot’] = true; 以Docker container 方式运行的 Gitlab 必须开启此项 pages_nginx[‘enable’] = true; 开启 Pages 服务的 vhost,该项开启后将会在 /var/opt/gitlab/nginx/conf 目录下生成独立的名为 gitlab-pages.conf Nginx 配置文件。 gitlab_pages['access_control'] = true 开启 Pages 访问控制。 sudo gitlab-ctl reconfigure ","date":"2021-06-08","objectID":"/gitlab-install/:8:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Git"],"content":"参考 https://about.gitlab.com/install/#ubuntu https://www.cnblogs.com/bymo/p/9046586.html https://blog.csdn.net/OldDirverHelpMe/article/details/106536972 https://www.cnblogs.com/bymo/p/9046586.html https://cloud.tencent.com/developer/article/1711802 https://www.webfunny.com/blog/post/207 ","date":"2021-06-08","objectID":"/gitlab-install/:9:0","tags":["Git","Gitlab"],"title":"Gitlab Ubuntu 安装","uri":"/gitlab-install/"},{"categories":["Go常用库"],"content":"简介 logrus 是一个结构化、插件化的日志记录库。完全兼容 golang 标准库中的日志模块。 它有以下特点: 完全兼容标准日志库,拥有七种日志级别:Trace, Debug, Info, Warning, Error, Fataland Panic。 可扩展的 Hook 机制,允许使用者通过 Hook 的方式将日志分发到任意地方,如本地文件系统,logstash,elasticsearch 或者 mq 等,或者通过 Hook 定义日志内容和格式等 可选的日志输出格式,内置了两种日志格式 JSONFormater 和 TextFormatter,还可以自定义日志格式 Field 机制,通过 Filed 机制进行结构化的日志记录 线程安全 ","date":"2021-06-06","objectID":"/go-logrus/:1:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"安装 go get github.com/sirupsen/logrus ","date":"2021-06-06","objectID":"/go-logrus/:2:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"简单示例 package main import ( log \"github.com/sirupsen/logrus\" ) func main() { log.WithFields(log.Fields{ \"animal\": \"walrus\", }).Info(\"a walrus appears\") } 输出: time=\"2021-11-11T17:41:48+08:00\" level=info msg=\"a walrus appears\" animal=walrus ","date":"2021-06-06","objectID":"/go-logrus/:3:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"日志级别 Logrus 有七个日志级别:Trace, Debug, Info, Warning, Error, Fataland Panic。 log.Trace(\"Something very low level.\") log.Debug(\"Useful debugging information.\") log.Info(\"Something noteworthy happened!\") log.Warn(\"You should probably take a look at this.\") log.Error(\"Something failed but I'm not quitting.\") // Calls os.Exit(1) after logging log.Fatal(\"Bye.\") // Calls panic() after logging log.Panic(\"I'm bailing.\") ","date":"2021-06-06","objectID":"/go-logrus/:4:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"设置日志级别 你可以在 Logger 上设置日志记录级别,然后它只会记录具有该级别或以上级别任何内容的条目: // 会记录info及以上级别 (warn, error, fatal, panic) log.SetLevel(log.InfoLevel) ","date":"2021-06-06","objectID":"/go-logrus/:4:1","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"字段 Logrus 鼓励通过日志字段进行谨慎的结构化日志记录,而不是冗长的、不可解析的错误消息。 例如,区别于使用 log.Fatalf(“Failed to send event %s to topic %s with key %d”),你应该使用如下方式记录更容易发现的内容: log.WithFields(log.Fields{ \"event\": event, \"topic\": topic, \"key\": key, }).Fatal(\"Failed to send event\") WithFields 的调用是可选的。 ","date":"2021-06-06","objectID":"/go-logrus/:5:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"默认字段 通常,将一些字段始终附加到应用程序的全部或部分的日志语句中会很有帮助。例如,你可能希望始终在请求的上下文中记录 request_id 和 user_ip。 区别于在每一行日志中写上 log.WithFields(log.Fields{“request_id”: request_id, “user_ip”: user_ip}),你可以向下面的示例代码一样创建一个 logrus.Entry 去传递这些字段。 requestLogger := log.WithFields(log.Fields{\"request_id\": request_id, \"user_ip\": user_ip}) requestLogger.Info(\"something happened on that request\") # will log request_id and user_ip requestLogger.Warn(\"something not great happened\") ","date":"2021-06-06","objectID":"/go-logrus/:6:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"日志条目 除了使用 WithField 或 WithFields 添加的字段外,一些字段会自动添加到所有日志记录事中: time:记录日志时的时间戳 msg:记录的日志信息 level:记录的日志级别 ","date":"2021-06-06","objectID":"/go-logrus/:7:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"设置输出方式 // 输出到 Stdout log.SetOutput(os.Stdout) // 输出到文件里 logfile, _ := os.OpenFile(\"./logrus.log\", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) logrus.SetOutput(logfile) // 输出到多个地方 logrus.SetOutput(io.MultiWriter(os.Stdout, logfile)) ","date":"2021-06-06","objectID":"/go-logrus/:8:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"格式化 logrus 内置以下两种日志格式化程序: logrus.TextFormatter logrus.JSONFormatter log.SetFormatter(\u0026log.JSONFormatter{}) 可以根据 Formatter 接口自定义日志格式。 还支持一些第三方的格式化程序,详见项目首页。 ","date":"2021-06-06","objectID":"/go-logrus/:9:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"记录函数名 如果你希望将调用的函数名添加为字段,请通过以下方式设置: log.SetReportCaller(true) 这会将调用者添加为”method”,如下所示: {\"animal\":\"penguin\",\"level\":\"fatal\",\"method\":\"github.com/sirupsen/arcticcreatures.migrate\",\"msg\":\"a penguin swims by\",\"time\":\"2014-03-10 19:57:38.562543129 -0400 EDT\"} 注意:,开启这个模式会增加性能开销。 ","date":"2021-06-06","objectID":"/go-logrus/:10:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"切分日志文件 如果日志文件太大了,想切分成小文件,但是 logrus 没有提供这个功能。 一种是借助 linux 系统的 logrotate 命令来切分 logrus 生成的日志文件。 另外一种是用 logrus 的 hook 功能,做一个切分日志的插件。 我们使用lumberjack切分。 package main import ( log \"github.com/sirupsen/logrus\" \"gopkg.in/natefinch/lumberjack.v2\" ) func main() { logger := \u0026lumberjack.Logger{ Filename: \"./testlogrus.log\", MaxSize: 500, // 日志文件大小,单位是 MB MaxBackups: 3, // 最大过期日志保留个数 MaxAge: 28, // 保留过期文件最大时间,单位 天 Compress: true, // 是否压缩日志,默认是不压缩。这里设置为true,压缩日志 } log.SetOutput(logger) // logrus 设置日志的输出方式 } ","date":"2021-06-06","objectID":"/go-logrus/:11:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"Hooks 你可以添加日志级别的钩子(Hook)。例如,向异常跟踪服务发送 Error、Fatal 和 Panic、信息到 StatsD 或同时将日志发送到多个位置,例如 syslog。 Logrus 配有内置钩子。在 init 中添加这些内置钩子或你自定义的钩子: import ( log \"github.com/sirupsen/logrus\" \"gopkg.in/gemnasium/logrus-airbrake-hook.v2\" // the package is named \"airbrake\" logrus_syslog \"github.com/sirupsen/logrus/hooks/syslog\" \"log/syslog\" ) func init() { // Use the Airbrake hook to report errors that have Error severity or above to // an exception tracker. You can create custom hooks, see the Hooks section. log.AddHook(airbrake.NewHook(123, \"xyz\", \"production\")) hook, err := logrus_syslog.NewSyslogHook(\"udp\", \"localhost:514\", syslog.LOG_INFO, \"\") if err != nil { log.Error(\"Unable to connect to local syslog daemon\") } else { log.AddHook(hook) } } 意:Syslog 钩子还支持连接到本地 syslog(例如. “/dev/log” or “/var/run/syslog” or “/var/run/log”)。有关详细信息,请查看syslog hook README。 ","date":"2021-06-06","objectID":"/go-logrus/:12:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"线程安全 默认的 logger 在并发写的时候是被 mutex 保护的,比如当同时调用 hook 和写 log 时 mutex 就会被请求,有另外一种情况,文件是以 appending mode 打开的, 此时的并发操作就是安全的,可以用 logger.SetNoLock()来关闭它。 ","date":"2021-06-06","objectID":"/go-logrus/:13:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Go常用库"],"content":"参考 https://github.com/sirupsen/logrus ","date":"2021-06-06","objectID":"/go-logrus/:14:0","tags":["Go常用库","logrus"],"title":"Go常用库-logrus","uri":"/go-logrus/"},{"categories":["Git"],"content":"1. 起步 下载地址,各平台安装方式不一样,可以点进去细看下 中文文档 ","date":"2021-06-05","objectID":"/git-guide/:1:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"2. 运行前配置 git config --list --show-origin # 查看所有配置 git config --global user.name \"luzhifang\" # 配置用户名 git config --global user.email \"[email protected]\" # 配置邮件地址 git config --global core.editor vim # 配置文本编辑器 git config --global color.ui auto # git config --global color.ui auto git help config # 查看config的命令手册 cat ~/.gitconfig # 查看配置文件 ","date":"2021-06-05","objectID":"/git-guide/:2:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"3. 获取帮助 git help config # git help \u003cverb\u003e,查看命令手册, 和git \u003cverb\u003e --help 、 man git-\u003cverb\u003e 等价 git add -h # git \u003cverb\u003e -h,不需要全面的手册,可以用这个查看可用选项的快速参考 ","date":"2021-06-05","objectID":"/git-guide/:3:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"4. 基础 ","date":"2021-06-05","objectID":"/git-guide/:4:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"4.1 获取仓库 在已存在目录中初始化仓库 mkdir -p /home/user/my_project cd /home/user/my_project git init # 初始化 .git 文件夹 克隆现有的仓库 git clone https://github.com/luzhifang/luzhifang.github.io ","date":"2021-06-05","objectID":"/git-guide/:4:1","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"4.2 基础命令 touch README.md touch .gitignore # 添加忽略文件 git add . # 添加到暂存区 git mv README.md README # git mv 需要添加到版本控制中才能重命名 git rm --cached README # 从暂存区移除 git rm README # 非暂存区移除 git status # 检查当前文件状态 git diff # 列出具体哪个文件哪一行修改了,加--staged将比对已暂存文件与最后一次提交的文件差异 git commit # 不带-m则可以添加换行的commit message,建议使用,Linux默认会进去vim编辑模式,按crtl+x再按y进行保存 git commit -m 'initial project version' # 提交到本地仓库,git commit -a等同于git add + git commit git add forgotten_file git commit --amend # 修正提交,如果上次有提交则覆盖为一次 git reset HEAD forgotten_file # 取消暂存 git checkout -- forgotten_file # 撤销文件的修改 git remote # 查看已经配置的远程仓库,-v会显示URL git remote add origin https://github.com/luzhifang/luzhifang.github.io # git remote add \u003cshortname\u003e \u003curl\u003e 添加一个新的远程 Git 仓库,同时指定一个方便使用的简写,可添加多个 git push origin main # git push \u003cremote\u003e \u003cbranch\u003e 推送到远程仓库 git remote show origin # git remote show \u003cremote\u003e 查看某个远程仓库 git remote remove origin # 删除远程仓库 git remote rename origin new # 远程仓库的重命名 git config --global alias.br branch # 设置别名 ","date":"2021-06-05","objectID":"/git-guide/:4:2","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"4.3 标签 git tag # 列出所有标签 git tag -l \"v1.8.5\" # 通配符匹配标签 git tag -a v1.5 -m \"标签信息\" # 打附注标签,没有-a 打轻量标签 git show tagname # 查看标签信息和与之对应的提交信息 git tag -a tagname 9fceb02 # 指定提交ID打标签 git push origin v1.5 # 推送指定标签 git push origin --tags # 推送所有标签 git tag -d tagname # 删除本地标签 git push origin --delete \u003ctagname\u003e # 删除远程标签 git checkout tagname # 检出标签 git checkout -b branch tagname # 检出标签到分支 ","date":"2021-06-05","objectID":"/git-guide/:4:3","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"4.4 文件修改 git log # 查看commit的历史 git log --pretty=format:\"%h - %an, %ar : %s\" --graph # 图标显示并格式化,更多的细节可以查看官方文档和手册 git log -p # 查看某个文件的修改历史 git log -p -2 # 查看最近2次的更新内容 git log --name-status # 每次修改的文件列表, 显示状态 git log --name-only # 每次修改的文件列表 git log --stat # 每次修改的文件列表, 及文件修改的统计 git whatchanged # 每次修改的文件列表 git whatchanged –stat # 每次修改的文件列表, 及文件修改的统计 git show commitid # 查看某次commit的修改内容 git show # 显示最后一次的文件改变的具体内容 ","date":"2021-06-05","objectID":"/git-guide/:4:4","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"5. 分支 git branch # 列出本地所有分支,--merged 与 --no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支 git checkout branch # 切换到已存在的分支 git checkout - # 切回上一个分支 git checkout -b branch # 新建分支并检出,等于 git branch branch + git checkout branch git checkout -b branch origin/branch # 建立在远程跟踪分支之上 git branch -u origin/branch # 设置上游分支 git merge branch # 合并分支,注意合并前需要切换到你想合并入的分支 git branch -d branch # 删除分支 git push origin --delete branch # 删除远程分支 git branch -vv # 查看设置的所有跟踪分支 git branch -m oldbranch newbranch # 修改本地分支名称 git fetch origin # 拉取 origin 的仓库中有但你没有的信息 git pull # 大多数情况下它的含义是一个 git fetch 紧接着一个 git merge 命令,推荐用fetch + merge 代替pull ","date":"2021-06-05","objectID":"/git-guide/:5:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"6. 更改提交 git reset --hard 9fceb02 # 强制回退到某个提交ID,慎用 git push origin HEAD --force # 远端同步回滚 git rebase -i HEAD~2 # 可以选定当前分支中包含HEAD(最新提交)在内的两个最新历史记录为对象,并。 # 变基,推荐只对尚未推送或未分享给别人的本地修改执行变基操作清理历史,不对已推送的提交执行变基操作 git rebase -i # 在编辑器中打开,加上HEAD~2修正最新提交两次提交并在,可以指定commit id指定修改开始和截止区段 git rebase --continue # 修改提交信息 git push # 推送 ","date":"2021-06-05","objectID":"/git-guide/:6:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"7. 提交规范 遵循 Conventional Commits 来规范化 commit message \u003ctype\u003e[optional scope]: \u003cdescription\u003e [optional body] [optional footer(s)] ","date":"2021-06-05","objectID":"/git-guide/:7:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"7.1 type 提交的 commit 类型主要有以下几种: 主要类型 fix 修复 bug feat 新增功能 deps 依赖修改 break 不兼容修改 其他类型 perf 性能优化 docs 文档修改 refactor 重构 style 代码格式 test 测试用例 chore 日常工作,如文档修改,示例等 ci 构建脚本 ","date":"2021-06-05","objectID":"/git-guide/:7:1","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"7.2 scope 提交的代码修改的代码文件范围: transport examples middleware config cmd etc. ","date":"2021-06-05","objectID":"/git-guide/:7:2","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"7.3 description 用简短的话语清晰的描述提交的代码做了什么事。 ","date":"2021-06-05","objectID":"/git-guide/:7:3","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"7.4 body 补充说明,用于描述原因、目的、实现逻辑等可以省略。 ","date":"2021-06-05","objectID":"/git-guide/:7:4","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"7.5 footer 当存在不兼容(breaking change)更新时,需要描述原因以及影响范围。 关联相关的 issue,如 Refs #133。 可能涉及到的文档更新和其他模块的更新的 PR 关联。 Commit Examples 只有提交信息 fix: The log debug level should be -1 包含全部结构 fix(log): [BREAKING-CHANGE] unable to meet the requirement of log Library Explain the reason, purpose, realization method, etc. Close #777 Doc change on doc/#111 BREAKING CHANGE: Breaks log.info api, log.log should be used instead ","date":"2021-06-05","objectID":"/git-guide/:7:5","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"8. 版本规范 格式为 x.y.z x 代表主版本号,在重大功能变更或新版本不向下兼容时加 1,此时 y 与 z 的数字归 0 y 代表次版本号,在添加新功能或者删除已有功能时加 1,此时 z 的数字归 0 z 代表修订号,z 只在进行内部修改后加 1 ","date":"2021-06-05","objectID":"/git-guide/:8:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Git"],"content":"9. Git Flow /add-user /1.0.0 /1.0.1 feature develop release hotfix master | ● | | | | / | | | | | / | | | | | / | | | | |/ | | | | start ● | | | | ↓ | | | | ● | | | | ↓ | | | | finish ●\\ | | | | | \\ | | | | |PR \\ | | | | |merge \\ | | | | | ● | | | | | \\ | | | | | \\ | | | | | \\ | | | | | \\ | | | | | ● start | | | | ↓ | | | | ● | | | | ↓ | | | | ●finish | | | | / | \\ | | | | / | \\ | | | | / | \\ | | | | / | \\ | | ● | | \\ | | | | | \\ | | | | | \\ | | | | | ● tag 1.0.0 | | | | / | | | | | / | | | | | / | | | | start ● | | | | ↓ | | | | ● | | | | ↓ | | | | finish● | | | | / | \\ | | | | / | \\ | | | | / | \\ | | | / | ● tag 1.0.1 | | / | | | | | / | | | | | / | | | | | / | | | | ● | | | | | | | | ","date":"2021-06-05","objectID":"/git-guide/:9:0","tags":["Git","Git Flow","版本"],"title":"Git使用指北","uri":"/git-guide/"},{"categories":["Linux"],"content":"Linux 文本三剑客 awk、grep、sed 是 linux 操作文本的三大利器,合称文本三剑客,也是必须掌握的 linux 命令之一。 三者的功能都是处理文本,但侧重点各不相同,其中属 awk 功能最强大,但也最复杂。 grep 更适合单纯的查找或匹配文本。 sed 更适合编辑匹配到的文本。 awk 更适合格式化文本,对文本进行较复杂格式处理。 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:1:0","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"grep ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:2:0","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"介绍 grep 全称是 Global Regular Expression Print。grep 命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来(匹配到的标红)。 egrep = grep -E:扩展的正则表达式 (除了\u003c , \u003e , \\b 使用其他正则都可以去掉\\) 命令格式:grep [option] pattern file… ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:2:1","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"命令参数 参数 释义 -A\u003c显示行数\u003e 除了显示符合范本样式的那一列之外,并显示该行之后的内容。 -B\u003c显示行数\u003e 除了显示符合样式的那一行之外,并显示该行之前的内容。 -C\u003c显示行数\u003e 除了显示符合样式的那一行之外,并显示该行之前后的内容。 -c 统计匹配的行数 -e 实现多个选项间的逻辑 or 关系 -E 扩展的正则表达式 -f FILE 从 FILE 获取 PATTERN 匹配 -F 相当于 fgrep,搜索字符串而不会匹配正则 -i –ignore-case 忽略字符大小写的差别。 -n 显示匹配的行号 -o 仅显示匹配到的字符串 -q 静默模式,不输出任何信息 -s 不显示错误信息。 -v 显示不被 pattern 匹配到的行,相当于[^] 反向匹配 -w 匹配 整个单词 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:2:2","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"grep 实战演示 [root@along ~]# cat test aaa bbbbb AAAaaa BBBBASDABBDA [root@along ~]# grep -A2 b test bbbbb AAAaaa BBBBASDABBDA [root@along ~]# grep -B1 b test aaa bbbbb [root@along ~]# grep -C1 b test aaa bbbbb AAAaaa [root@along ~]# grep -c b test 2 [root@along ~]# grep -e AAA -e bbb test bbbbb AAAaaa [root@along ~]# grep -in b test 2:bbbbb 4:BBBBASDABBDA [root@along ~]# grep -o ASDA test1 ASDA [root@along ~]# grep -q aa test1 [root@along ~]# grep -v aaa test1 bbbbb BBBBASDABBDA [root@along ~]# grep -w aaa test1 aaa [root@along ~]# cat grep.txt aaa [root@along ~]# grep -f grep.txt test aaa AAAaaa ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:2:3","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"sed ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:0","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"介绍 sed 英文全称是 stream editor,是一种流编辑器,它一次处理一行内容。 处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(patternspace ),接着用 sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。 除非使用重定向存储输出或-i,文件内容才会改变,这个参数可以用来操作文件。 命令格式:sed [options] ‘[地址定界] command’ file(s) ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:1","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"常用选项 options 参数 释义 -n 不输出模式空间内容到屏幕,即不自动打印,只打印匹配到的行 -e 多点编辑,对每行处理时,可以有多个 Script -f 把 Script 写到文件当中,在执行 sed 时-f 指定文件路径,如果是多个 Script,换行写 -r 支持扩展的正则表达式 -i 直接将处理的结果写入文件 -i.bak 在将处理的结果写入文件之前备份一份 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:2","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"常用选项 options 演示 [root@along ~]# cat demo aaa bbbb AABBCCDD [root@along ~]# sed \"/aaa/p\" demo #匹配到的行会打印一遍,不匹配的行也会打印 aaa aaa bbbb AABBCCDD [root@along ~]# sed -n \"/aaa/p\" demo #-n不显示没匹配的行 aaa [root@along ~]# sed -e \"s/a/A/\" -e \"s/b/B/\" demo #-e多点编辑 Aaa Bbbb AABBCCDD [root@along ~]# cat sedscript.txt s/A/a/g [root@along ~]# sed -f sedscript.txt demo #-f使用文件处理 aaa bbbb aaBBCCDD [root@along ~]# sed -i.bak \"s/a/A/g\" demo #-i直接对文件进行处理 [root@along ~]# cat demo AAA bbbb AABBCCDD [root@along ~]# cat demo.bak aaa bbbb AABBCCDD ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:3","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"地址定界 地址 释义 不给地址 对全文处理 # 指定的行 /pattern/ 被此处模式所能够匹配到的每一行 #,# 从#行,到#行 #,+n 从#行开始,一直到向下的 n 行 /pat1/,/pat2/ 从第一次被 pat1 匹配到的行开始,到第一次被 pat2 匹配到的行结束,中间的所有行 #,/pat1/ 从#行开始,到第一次被 pat1 匹配到的行结束,中间的所有行 ~ 步进,指定起始行及步长 1~2 所有奇数行 2~2 所有偶数行 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:4","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"地址界定演示 [root@along ~]# cat demo aaa bbbb AABBCCDD [root@along ~]# sed -n \"p\" demo #不指定行,打印全文 aaa bbbb AABBCCDD [root@along ~]# sed \"2s/b/B/g\" demo #替换第2行的b-\u003eB aaa BBBB AABBCCDD [root@along ~]# sed -n \"/aaa/p\" demo aaa [root@along ~]# sed -n \"1,2p\" demo #打印1-2行 aaa bbbb [root@along ~]# sed -n \"/aaa/,/DD/p\" demo aaa bbbb AABBCCDD [root@along ~]# sed -n \"2,/DD/p\" demo bbbb AABBCCDD [root@along ~]# sed \"1~2s/[aA]/E/g\" demo #将奇数行的a或A替换为E EEE bbbb EEBBCCDD ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:5","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"编辑命令 command 参数 释义 d 删除模式空间匹配的行,并立即启用下一轮循环 p 打印当前模式空间内容,追加到默认输出之后 a 在指定行后面追加文本,支持使用\\n 实现多行追加 i 在行前面插入文本,支持使用\\n 实现多行追加 c 替换行为单行或多行文本,支持使用\\n 实现多行追加 w 保存模式匹配的行至指定文件 r 读取指定文件的文本至模式空间中匹配到的行后 = 为模式空间中的行打印行号 ! 模式空间中匹配行取反处理 s/// 查找替换,支持使用其它分隔符,如:s@@@,s###。加 g 表示行内全局替换 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:6","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"编辑演示 [root@along ~]# cat demo aaa bbbb AABBCCDD [root@along ~]# sed \"2d\" demo #删除第2行 aaa AABBCCDD [root@along ~]# sed -n \"2p\" demo #打印第2行 bbbb [root@along ~]# sed \"2a123\" demo #在第2行后加123 aaa bbbb 123 AABBCCDD [root@along ~]# sed \"1i123\" demo #在第1行前加123 123 aaa bbbb AABBCCDD [root@along ~]# sed \"3c123\\n456\" demo #替换第3行内容 aaa bbbb 123 456 [root@along ~]# sed -n \"3w/root/demo3\" demo #保存第3行的内容到demo3文件中 [root@along ~]# cat demo3 AABBCCDD [root@along ~]# sed \"1r/root/demo3\" demo #读取demo3的内容到第1行后 aaa AABBCCDD bbbb AABBCCDD [root@along ~]# sed -n \"=\" demo #=打印行号 1 2 3 [root@along ~]# sed -n '2!p' demo #打印除了第2行的内容 aaa AABBCCDD [root@along ~]# sed 's@[a-z]@\\u\u0026@g' demo #将全文的小写字母替换为大写字母 AAA BBBB AABBCCDD ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:3:7","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"awk ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:0","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"介绍 awk 是 Aho Weinberger kernaighan 三个人的首字母缩写 awk 是一种编程语言,用于在 linux/unix 下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。 支持用户自定义函数和动态正则表达式等先进功能,是 linux/unix 下的一个强大编程工具。 可以在命令行中使用,但更多是作为脚本来使用 awk 有很多内建的功能,比如数组、函数等。 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:1","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"语法 awk [options] 'program' var=value file… awk [options] -f programfile var=value file… awk [options] 'BEGIN{ action;… } pattern{ action;… } END{ action;… }' file ... ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:2","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"常用命令选项 选项 释义 -F fs fs 指定输入分隔符,fs 可以是字符串或正则表达式,如-F: -v var=value 赋值一个用户定义变量,将外部变量传递给 awk -f scripfile 从脚本文件中读取 awk 命令 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:3","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"内置变量 内置变量 释义 FS 输入字段分隔符,默认为空白字符 OFS 输出字段分隔符,默认为空白字符 RS 输入记录分隔符,指定输入时的换行符,原换行符仍有效 ORS 输出记录分隔符,输出时用指定符号代替换行符 NF 字段数量,共有多少字段, $NF引用最后一列,$(NF-1)引用倒数第 2 列 NR 行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始 FNR 各文件分别计数, 行号,后跟一个文件和 NR 一样,跟多个文件,第二个文件行号从 1 开始 FILENAME 当前文件名 ARGC 命令行参数的个数 ARGV 数组,保存的是命令行所给定的各参数,查看参数 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:4","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"实战演示 [root@along ~]# cat awkdemo hello:world linux:redhat:lalala:hahaha along:love:youou [root@along ~]# awk -v FS=':' '{print $1,$2}' awkdemo #FS指定输入分隔符 hello world linux redhat along love [root@along ~]# awk -v FS=':' -v OFS='---' '{print $1,$2}' awkdemo #OFS指定输出分隔符 hello---world linux---redhat along---love [root@along ~]# awk -v RS=':' '{print $1,$2}' awkdemo hello world linux redhat lalala hahaha along love you [root@along ~]# awk -v FS=':' -v ORS='---' '{print $1,$2}' awkdemo hello world---linux redhat---along love--- [root@along ~]# awk -F: '{print NF}' awkdemo 2 4 3 [root@along ~]# awk -F: '{print $(NF-1)}' awkdemo #显示倒数第2列 hello lalala love [root@along ~]# awk '{print NR}' awkdemo awkdemo1 1 2 3 4 5 [root@along ~]# awk 'END {print NR}' awkdemo awkdemo1 5 [root@along ~]# awk '{print FNR}' awkdemo awkdemo1 1 2 3 1 2 [root@along ~]# awk '{print FILENAME}' awkdemo awkdemo awkdemo awkdemo [root@along ~]# awk 'BEGIN {print ARGC}' awkdemo awkdemo1 3 [root@along ~]# awk 'BEGIN {print ARGV[0]}' awkdemo awkdemo1 awk [root@along ~]# awk 'BEGIN {print ARGV[1]}' awkdemo awkdemo1 awkdemo [root@along ~]# awk 'BEGIN {print ARGV[2]}' awkdemo awkdemo1 awkdemo1 ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:5","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"自定义变量 [root@along ~]# awk -v name=\"along\" -F: '{print name\":\"$0}' awkdemo along:hello:world along:linux:redhat:lalala:hahaha along:along:love:you ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:6","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["Linux"],"content":"统计分析 where 条件过滤 # select * from table; awk 1 table_file # select * from table where cost \u003e 100; awk '$2\u003e100' table_file 对某个字段去重,或者按记录去重 # select distinct(date) from table; awk '!a[$3]++{print $3}' table_file # select distinct(*) from table; awk '!a[$0]++' table_file 记录按序输出 # select id from table order by id; awk '{a[$1]}END{asorti(a);for(i=1;i\u003c=length(a);i++){print a[i]}}' table_file 取前多少条记录 # select * from table limit 2; awk 'NR\u003c=2' table_file awk 'NR\u003e2{exit}1' table_file # performance is better 分组求和统计,关键词:group by、having、sum、count # select id, count(1), sum(cost) from table group by id having count(1) \u003e 2; awk '{a[$1]=a[$1]==\"\"?$2:a[$1]\",\"$2}END{for(i in a){c=split(a[i],b,\",\");if(c\u003e2){sum=0;for(j in b){sum+=b[j]};print i\"\\t\"c\"\\t\"sum}}}' table_file 模糊查询 # select name from table where name like 'wang%'; awk '$2 ~/^wang/{print $2}' table_file # select addr from table where addr like '%bei'; awk '/.*bei$/{print $3}' table_file # select addr from table where addr like '%bei%'; awk '$3 ~/bei/{print $3}' table_file 多表 join 关联查询 # select a._ , b._ from table a inner join table b on a.id = b.id and b.id = 2; awk 'ARGIND==1{a[$1]=$0;next}{if(($1 in a)\u0026\u0026$1==2){print a[$1]\"\\t\"$2\"\\t\"$3}}' table_file table_file 多表 union all # select a._ from table a union all select b._ from table b; awk 1 table_file table_file # select a._ from table a union select b._ from table b; awk '!a[$0]++' table_file table_file 随机抽样统计,关键词:order by rand() # select * FROM table ORDER BY RAND() LIMIT 2; awk 'BEGIN{srand();while(i\u003c2){k=int(rand()*10)+1;if(!(k in a)){a[k];i++}}}(NR in a)' table_file awk 还支持 if else while for do while 自定义函数.awk 脚本.awk ","date":"2021-05-19","objectID":"/linux-grep-sed-awk/:4:7","tags":["Linux","grep","awk","sed"],"title":"Linux 文本三剑客","uri":"/linux-grep-sed-awk/"},{"categories":["regexp"],"content":"简介 正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为\"元字符\")。 正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。 正则表达式是繁琐的,但它是强大的,学会之后的应用会让你除了提高效率外。 许多程序设计语言都支持利用正则表达式进行字符串操作。 ","date":"2020-11-26","objectID":"/regexp/:1:0","tags":["正则","regexp","Linux"],"title":"正则表达式","uri":"/regexp/"},{"categories":["regexp"],"content":"应用 测试字符串内的模式。 例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。 替换文本。 可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。 基于模式匹配从字符串中提取子字符串。 可以查找文档内或输入域内特定的文本。 ","date":"2020-11-26","objectID":"/regexp/:2:0","tags":["正则","regexp","Linux"],"title":"正则表达式","uri":"/regexp/"},{"categories":["regexp"],"content":"应用领域 目前,正则表达式已经在很多软件中得到广泛的应用,包括 *nix(Linux, Unix 等)、HP 等操作系统,PHP、C#、Java 等开发环境,以及很多的应用软件中,都可以看到正则表达式的影子。 ","date":"2020-11-26","objectID":"/regexp/:3:0","tags":["正则","regexp","Linux"],"title":"正则表达式","uri":"/regexp/"},{"categories":["regexp"],"content":"匹配字符释义 字符 描述 \\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\\n’ 匹配一个换行符。序列 ‘\\\\’ 匹配 “\\” 而 “\\(” 则匹配 “(\"。 ^ 匹配输入字符串的开始位置。 $ 匹配输入字符串的结束位置。 * 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。 + 匹配前面的子表达式一次或多次。例如,‘zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。 ? 匹配前面的子表达式零次或一次。例如,“do(es)?” 可以匹配 “do” 或 “does” 。? 等价于 {0,1}。 {n} n 是一个非负整数。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。 {n,} n 是一个非负整数。至少匹配 n 次。例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。‘o{1,}’ 等价于 ‘o+’。‘o{0,}’ 则等价于 ‘o*’。 {n,m} m 和 n 均为非负整数,其中 n \u003c= m。最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o。‘o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。 ? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,‘o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。 . 匹配除换行符(\\n、\\r)之外的任何单个字符。要匹配包括 ‘\\n’ 在内的任何字符,请使用像```(. \\n)```的模式。 (pattern) 匹配 pattern 并获取这一匹配。使用 $0…$9 属性进行获取。要匹配圆括号字符,请使用 ‘(’ 或 ‘)’。 (?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分时很有用。例如, ‘industr(?:y|ies) 就是一个比 ‘industry|industries’ 更简略的表达式。 (?=pattern) 正向肯定预查(look ahead positive assert),在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)“能匹配\"Windows2000\"中的\"Windows”,但不能匹配\"Windows3.1\"中的\"Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 (?!pattern) 正向否定预查(negative assert),在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如\"Windows(?!95|98|NT|2000)“能匹配\"Windows3.1\"中的\"Windows”,但不能匹配\"Windows2000\"中的\"Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 (?\u003c=pattern) 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,\"(?\u003c=95|98|NT|2000)Windows“能匹配”2000Windows“中的”Windows\",但不能匹配\"3.1Windows“中的”Windows\"。 (?\u003c!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。例如\"(?\u003c!95|98|NT|2000)Windows“能匹配”3.1Windows“中的”Windows\",但不能匹配\"2000Windows“中的”Windows\"。 x|y 匹配 x 或 y。例如,‘z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。 [xyz] 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。 [^xyz] 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’、’l’、‘i’、’n’。 [a-z] 字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。 [^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,’[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。 \\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ’er\\b’ 可以匹配\"never\" 中的 ’er’,但不能匹配 “verb” 中的 ’er’。 \\B 匹配非单词边界。’er\\B’ 能匹配 “verb” 中的 ’er’,但不能匹配 “never” 中的 ’er’。 \\cx 匹配由 x 指明的控制字符。例如, \\cI 匹配一个 Control+I,等价于\\t。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。 \\d 匹配一个数字字符。等价于 [0-9]。 \\D 匹配一个非数字字符。等价于 [^0-9]。 \\f 匹配一个换页符。等价于 \\x0c 和 \\cL。 \\n 匹配一个换行符。等价于 \\x0a 和 \\cJ。 \\r 匹配一个回车符。等价于 \\x0d 和 \\cM。 \\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \\f\\n\\r\\t\\v]。 \\S 匹配任何非空白字符。等价于 [^ \\f\\n\\r\\t\\v]。 \\t 匹配一个制表符。等价于 \\x09 和 \\cI。 \\v 匹配一个垂直制表符。等价于 \\x0b 和 \\cK。 \\w 匹配字母、数字、下划线。等价于’[A-Za-z0-9_]’。 \\W 匹配非字母、数字、下划线。等价于 ‘[^a-za-z0-9_]’。 \\xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\\x41’ 匹配 “A”。’\\x041’ 则等价于 ‘\\x04’ \u0026 “1”。正则表达式中可以使用 ASCII 编码。 \\num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,’(.)\\1’ 匹配两个连续的相同字符。 \\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \\u0041 匹配字母 A。 ","date":"2020-11-26","objectID":"/regexp/:4:0","tags":["正则","regexp","Linux"],"title":"正则表达式","uri":"/regexp/"},{"categories":["regexp"],"content":"Linux 用到的命令 grep cat find vim grep egrep awk sed cp mv rm 等等 ","date":"2020-11-26","objectID":"/regexp/:5:0","tags":["正则","regexp","Linux"],"title":"正则表达式","uri":"/regexp/"},{"categories":["监控告警"],"content":"Grafana 介绍 Grafana 是一个跨平台的开源的度量分析和可视化工具,可以通过将采集的数据查询然后可视化的展示,并及时通知。它主要有以下六大特点: 展示方式:快速灵活的客户端图表,面板插件有许多不同方式的可视化指标和日志,官方库中具有丰富的仪表盘插件,比如热图、折线图、图表等多种展示方式; 数据源:Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch 和 KairosDB 等; 通知提醒:以可视方式定义最重要指标的警报规则,Grafana 将不断计算并发送通知,在数据达到阈值时通过 Slack、PagerDuty 等获得通知; 混合展示:在同一图表中混合使用不同的数据源,可以基于每个查询指定数据源,甚至自定义数据源; 注释:使用来自不同数据源的丰富事件注释图表,将鼠标悬停在事件上会显示完整的事件元数据和标记; 过滤器:Ad-hoc 过滤器允许动态创建新的键/值过滤器,这些过滤器会自动应用于使用该数据源的所有查询。 ","date":"2020-11-02","objectID":"/grafana/:1:0","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"安装 ","date":"2020-11-02","objectID":"/grafana/:2:0","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"下载 官网下载地址:Grafana 可以根据需要选择二进制安装和 docker 安装。 ","date":"2020-11-02","objectID":"/grafana/:2:1","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"启动 启动服务,打开浏览器,输入 IP+端口,3000 为 Grafana 的默认侦听端口。 系统默认用户名和密码为 admin/admin,第一次登陆系统会要求修改密码,修改密码后登陆。 ","date":"2020-11-02","objectID":"/grafana/:2:2","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"使用 ","date":"2020-11-02","objectID":"/grafana/:3:0","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"添加数据源 依次点击设置图标 -\u003e Data sources -\u003e Add data source -\u003e Prometheus ","date":"2020-11-02","objectID":"/grafana/:3:1","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"添加面板 依次点击 Add panel -\u003e Add a new panel ","date":"2020-11-02","objectID":"/grafana/:3:2","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"工作原理 exporter 负责收集数据,本实例中是 node-exporter 这个服务,会查询你的本地电脑的信息,比如内存还有多少、CPU 负载之类。 Prometheus 定时从 exporter 里拉取存储的统计数据。 Grafana 每次要展现一个仪表盘的时候,会向 Prometheus 发送一个查询请求。 ","date":"2020-11-02","objectID":"/grafana/:4:0","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"docker-compose 示例 完整示例请看Demo ","date":"2020-11-02","objectID":"/grafana/:5:0","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"参考 https://cloud.tencent.com/developer/article/1807679 ","date":"2020-11-02","objectID":"/grafana/:6:0","tags":["监控告警","Grafana"],"title":"Grafana","uri":"/grafana/"},{"categories":["监控告警"],"content":"Alertmanager 简介 告警能力在 Prometheus 的架构中被划分成两个独立的部分。如下所示,通过在 Prometheus 中定义 AlertRule(告警规则),Prometheus 会周期性的对告警规则进行计算,如果满足告警触发条件就会向 Alertmanager 发送告警信息。 在 Prometheus 中一条告警规则主要由以下几部分组成: 告警名称:用户需要为告警规则命名,当然对于命名而言,需要能够直接表达出该告警的主要内容 告警规则:告警规则实际上主要由 PromQL 进行定义,其实际意义是当表达式(PromQL)查询结果持续多长时间(During)后出发告警 在 Prometheus 中,还可以通过 Group(告警组)对一组相关的告警进行统一定义。当然这些定义都是通过 YAML 文件来统一管理的。 Alertmanager 作为一个独立的组件,负责接收并处理来自 Prometheus Server(也可以是其它的客户端程序)的告警信息。Alertmanager 可以对这些告警信息进行进一步的处理,比如当接收到大量重复告警时能够消除重复的告警信息,同时对告警信息进行分组并且路由到正确的通知方,Prometheus 内置了对邮件,Slack 等多种通知方式的支持,同时还支持与 Webhook 的集成,以支持更多定制化的场景。例如,企业微信和钉钉。 ","date":"2020-11-02","objectID":"/alertmanager/:1:0","tags":["监控告警","Alertmanager"],"title":"Prometheus Alertmanager","uri":"/alertmanager/"},{"categories":["监控告警"],"content":"特性 Alertmanager 除了提供基本的告警通知能力以外,还主要提供了如:分组、抑制以及静默等告警特性: ","date":"2020-11-02","objectID":"/alertmanager/:1:1","tags":["监控告警","Alertmanager"],"title":"Prometheus Alertmanager","uri":"/alertmanager/"},{"categories":["监控告警"],"content":"自定义 Prometheus 告警规则 Prometheus 中的告警规则允许你基于 PromQL 表达式定义告警触发条件,Prometheus 后端对这些触发规则进行周期性计算,当满足触发条件后则会触发告警通知。默认情况下,用户可以通过 Prometheus 的 Web 界面查看这些告警规则以及告警的触发状态。当 Promthues 与 Alertmanager 关联之后,可以将告警发送到外部服务如 Alertmanager 中并通过 Alertmanager 可以对这些告警进行进一步的处理。 修改 prometheus.yml,添加 rule_files: - rules/*.yml 创建告警文件 rules/hoststats-alert.yml 内容如下: groups: - name: hostStatsAlert rules: - alert: hostCpuUsageAlert expr: sum(avg without (cpu)(irate(node_cpu_seconds_total{mode!='idle'}[5m]))) by (instance) \u003e 0.85 for: 1m labels: severity: page annotations: summary: \"Instance {{ $labels.instance }} CPU usgae high\" description: \"{{ $labels.instance }} CPU usage above 85% (current value: {{ $value }})\" - alert: hostMemUsageAlert expr: (node_memory_MemTotal - node_memory_MemAvailable)/node_memory_MemTotal \u003e 0.85 for: 1m labels: severity: page annotations: summary: \"Instance {{ $labels.instance }} MEM usgae high\" description: \"{{ $labels.instance }} MEM usage above 85% (current value: {{ $value }})\" - name: example rules: # Alert for any instance that is unreachable for \u003e5 minutes. - alert: InstanceDown expr: up == 0 for: 5m labels: severity: page annotations: summary: \"Instance {{ $labels.instance }} down\" description: \"{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes.\" # Alert for any instance that has a median request latency \u003e1s. - alert: APIHighRequestLatency expr: api_http_request_latencies_second{quantile=\"0.5\"} \u003e 1 for: 10m annotations: summary: \"High request latency on {{ $labels.instance }}\" description: \"{{ $labels.instance }} has a median request latency above 1s (current value: {{ $value }}s)\" 在告警规则文件中,我们可以将一组相关的规则设置定义在一个 group 下。在每一个 group 中我们可以定义多个告警规则(rule)。一条告警规则主要由以下几部分组成: alert:告警规则的名称。 expr:基于 PromQL 表达式告警触发条件,用于计算是否有时间序列满足该条件。 for:评估等待时间,可选参数。用于表示只有当触发条件持续一段时间后才发送告警。在等待期间新产生告警的状态为 pending。 labels:自定义标签,允许用户指定要附加到告警上的一组附加标签。 annotations:用于指定一组附加信息,比如用于描述告警详细信息的文字等,annotations 的内容在告警产生时会一同作为参数发送到 Alertmanager。 summary:描述告警的概要信息 description:用于描述告警的详细信息 summary 和 description 以及 labels 均支持模板化 编辑好之后,重启 Prometheus 后访问 Prometheus UIhttp://127.0.0.1:9090/rules可以查看当前以加载的规则文件。 切换到 Alerts 标签http://127.0.0.1:9090/alerts可以查看当前告警的活动状态。 此时,我们可以手动拉高系统的 CPU 使用率,验证 Prometheus 的告警流程,在主机上运行以下命令: cat /dev/zero\u003e/dev/null 运行命令后查看 CPU 使用率情况 Prometheus 首次检测到满足触发条件后,hostCpuUsageAlert 显示由一条告警处于活动状态。由于告警规则中设置了 1m 的等待时间,当前告警状态为 PENDING 如果 1 分钟后告警条件持续满足,则会实际触发告警并且告警状态为 FIRING ","date":"2020-11-02","objectID":"/alertmanager/:2:0","tags":["监控告警","Alertmanager"],"title":"Prometheus Alertmanager","uri":"/alertmanager/"},{"categories":["监控告警"],"content":"部署 Alertmanager Alertmanager 最新版本的下载地址可以从 Prometheus 官方网站 https://prometheus.io/download/ 获取。 cd /usr/local wget https://github.com/prometheus/alertmanager/releases/download/v0.15.2/alertmanager-0.15.2.linux-amd64.tar.gz tar -zxvf alertmanager-0.15.2.linux-amd64/ cd alertmanager-0.15.2.linux-amd64/ ./alertmanager 查看运行状态:Alertmanager 启动后可以通过 9093 端口访问,http://localhost:9093 ","date":"2020-11-02","objectID":"/alertmanager/:3:0","tags":["监控告警","Alertmanager"],"title":"Prometheus Alertmanager","uri":"/alertmanager/"},{"categories":["监控告警"],"content":"关联 Prometheus 与 Alertmanager: 在 Prometheus 的架构中被划分成两个独立的部分。Prometheus 负责产生告警,而 Alertmanager 负责告警产生后的后续处理。因此 Alertmanager 部署完成后,需要在 Prometheus 中设置 Alertmanager 相关的信息。 编辑 Prometheus 配置文件 prometheus.yml,并添加以下内容 alerting: alertmanagers: - static_configs: - targets: ['localhost:9093'] ","date":"2020-11-02","objectID":"/alertmanager/:3:1","tags":["监控告警","Alertmanager"],"title":"Prometheus Alertmanager","uri":"/alertmanager/"},{"categories":["监控告警"],"content":"配置解析 Alertmanager 主要负责对 Prometheus 产生的告警进行统一处理,因此在 Alertmanager 配置中一般会包含以下几个主要部分: 全局配置(global):用于定义一些全局的公共参数,如全局的 SMTP 配置,Slack 配置等内容; 模板(templates):用于定义告警通知时的模板,如 HTML 模板,邮件模板等; 告警路由(route):根据标签匹配,确定当前告警应该如何处理; 接收人(receivers):接收人是一个抽象的概念,它可以是一个邮箱也可以是微信,Slack 或者 Webhook 等,接收人一般配合告警路由使用; 抑制规则(inhibit_rules):合理设置抑制规则可以减少垃圾告警的产生 global: resolve_timeout: 5m route: group_by: ['alertname'] group_wait: 10s group_interval: 10s repeat_interval: 1h receiver: 'web.hook' receivers: - name: 'web.hook' webhook_configs: - url: 'http://127.0.0.1:5001/' inhibit_rules: - source_match: severity: 'critical' target_match: severity: 'warning' equal: ['alertname', 'dev', 'instance'] 配置好 webhook,再启动一个 go http 服务就可以接收到 alertmanager 发过来的告警信息了,我们可以对接公司 IM 或者短信平台都可以。 import ( \"fmt\" \"io\" \"net/http\" ) func main() { http.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) { s, _ := io.ReadAll(r.Body) fmt.Fprintf(w, \"%s\", s) }) http.ListenAndServe(\":5001\", nil) } ","date":"2020-11-02","objectID":"/alertmanager/:4:0","tags":["监控告警","Alertmanager"],"title":"Prometheus Alertmanager","uri":"/alertmanager/"},{"categories":["监控告警"],"content":"参考 https://prometheus.io/docs/alerting/latest/ https://yunlzheng.gitbook.io/prometheus-book/ ","date":"2020-11-02","objectID":"/alertmanager/:5:0","tags":["监控告警","Alertmanager"],"title":"Prometheus Alertmanager","uri":"/alertmanager/"},{"categories":["监控告警"],"content":"Prometheus 简介 Prometheus 最开始是由 SoundCloud 开发的开源监控告警系统,是 Google BorgMon 监控系统的开源版本。在 2016 年,Prometheus 加入 CNCF,成为继 Kubernetes 之后第二个被 CNCF 托管的项目。随着 Kubernetes 在容器编排领头羊地位的确立,Prometheus 也成为 Kubernetes 容器监控的标配。本文接下来将会对 Prometheus 做一个介绍。 ","date":"2020-11-01","objectID":"/prometheus/:1:0","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"特性 通过指标名称和标签(key/value 对)区分的多维度、时间序列数据模型 灵活的查询语法 PromQL 不需要依赖额外的存储,一个服务节点就可以工作 利用 http 协议,通过 pull 模式来收集时间序列数据 需要 push 模式的应用可以通过中间件 gateway 来实现 监控目标支持服务发现和静态配置 支持各种各样的图表和监控面板组件 ","date":"2020-11-01","objectID":"/prometheus/:1:1","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"核心组件 Prometheus Server, 主要用于抓取数据和存储时序数据,另外还提供查询和 Alert Rule 配置管理。 client libraries,用于对接 Prometheus Server, 可以查询和上报数据。 push gateway ,用于批量,短期的监控数据的汇总节点,主要用于业务数据汇报等。 各种汇报数据的 exporters ,例如汇报机器数据的 node_exporter, 汇报 MongoDB 信息的 MongoDB exporter 等等。 用于告警通知管理的 alertmanager 。 各种支持工具。 ","date":"2020-11-01","objectID":"/prometheus/:1:2","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"架构 从这个架构图,也可以看出 Prometheus 的主要模块包含, Server, Exporters, Pushgateway, PromQL, Alertmanager, WebUI 等。 Retrieval:是负责定时去暴露的目标页面上去抓取采样指标数据。 Storage:是负责将采样数据写入指定的时序数据库存储。 PromQL:是 Prometheus 提供的查询语言模块。可以和一些 webui 比如 grfana 集成。 Jobs / Exporters:Prometheus 可以从 Jobs 或 Exporters 中拉取监控数据。Exporter 以 Web API 的形式对外暴露数据采集接口。 Prometheus Server:Prometheus 还可以从其他的 Prometheus Server 中拉取数据。 Pushgateway:对于一些以临时性 Job 运行的组件,Prometheus 可能还没有来得及从中 pull 监控数据的情况下,这些 Job 已经结束了,Job 运行时可以在运行时将监控数据推送到 Pushgateway 中,Prometheus 从 Pushgateway 中拉取数据,防止监控数据丢失。 Service discovery:是指 Prometheus 可以动态的发现一些服务,拉取数据进行监控,如从 DNS,Kubernetes,Consul 中发现, file_sd 是静态配置的文件。 AlertManager:是一个独立于 Prometheus 的外部组件,用于监控系统的告警,通过配置文件可以配置一些告警规则,Prometheus 会把告警推送到 AlertManager。 ","date":"2020-11-01","objectID":"/prometheus/:1:3","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"工作流程 Prometheus server 定期从配置好的 jobs 或者 exporters 中拉 metrics,或者接收来自 Pushgateway 发过来的 metrics,或者从其他的 Prometheus server 中拉 metrics。 Prometheus server 在本地存储收集到的 metrics,并运行已定义好的 alert.rules,记录新的时间序列或者向 Alertmanager 推送警报。 Alertmanager 根据配置文件,对接收到的警报进行处理,发出告警。 在图形界面中,可视化采集数据。 ","date":"2020-11-01","objectID":"/prometheus/:1:4","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"监控系统对比 ","date":"2020-11-01","objectID":"/prometheus/:2:0","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"Prometheus vs Zabbix Zabbix 使用的是 C 和 PHP, Prometheus 使用 Golang, 整体而言 Prometheus 运行速度更快一点。 Zabbix 属于传统主机监控,主要用于物理主机,交换机,网络等监控,Prometheus 不仅适用主机监控,还适用于 Cloud, SaaS, Openstack,Container 监控。 Zabbix 在传统主机监控方面,有更丰富的插件。 Zabbix 可以在 WebGui 中配置很多事情,但是 Prometheus 需要手动修改文件配置。 ","date":"2020-11-01","objectID":"/prometheus/:2:1","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"总结 Prometheus 属于一站式监控告警平台,依赖少,功能齐全。 Prometheus 支持对云或容器的监控,其他系统主要对主机监控。 Prometheus 数据查询语句表现力更强大,内置更强大的统计函数。 Prometheus 在数据存储扩展性以及持久性上没有 InfluxDB,OpenTSDB,Sensu 好。 ","date":"2020-11-01","objectID":"/prometheus/:2:2","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"安装 ","date":"2020-11-01","objectID":"/prometheus/:3:0","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"二进制包安装 下载请到 https://prometheus.io/download/ cd /usr/local wget https://github.com/prometheus/prometheus/releases/download/v1.6.2/prometheus-1.6.2.linux-amd64.tar.gz tar -zxvf prometheus-1.6.2.linux-amd64.tar.gz cd prometheus-1.6.2.linux-amd64 ./prometheus --version ./prometheus --config.file=prometheus.yml 打开浏览器访问 http://localhost:9090/graph ","date":"2020-11-01","objectID":"/prometheus/:3:1","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"docker 安装 docker run --name prometheus -d -p 9090:9090 quay.io/prometheus/prometheus 运行 docker start prometheus 启动服务 运行 docker stats prometheus 查看 prometheus 状态 运行 docker stop prometheus 停止服务 ","date":"2020-11-01","objectID":"/prometheus/:3:2","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"基本概念 ","date":"2020-11-01","objectID":"/prometheus/:4:0","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"数据模型 Prometheus 存储的是时序数据, 即按照相同时序(相同的名字和标签),以时间维度存储连续的数据的集合。 格式上由 metric 的名字和一系列的标签(键值对)唯一标识而成。 不同的标签代表不同的时间序列。 ","date":"2020-11-01","objectID":"/prometheus/:4:1","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"metric types(指标类型) Counter Counter 表示收集的数据是按照某个趋势(增加/减少)一直变化的,我们往往用它记录服务请求总量、错误总数等。 例如 Prometheus server 中 http_requests_total, 表示 Prometheus 处理的 http 请求总数,我们可以使用 delta, 很容易得到任意区间数据的增量,这个会在 PromQL 一节中细讲。 Gauge Gauge 表示搜集的数据是一个瞬时的值,与时间没有关系,可以任意变高变低,往往可以用来记录内存使用率、磁盘使用率等。 例如 Prometheus server 中 go_goroutines, 表示 Prometheus 当前 goroutines 的数量。 Histogram Histogram 由 _bucket{le=\"\"},_bucket{le=\"+Inf\"}, _sum,_count 组成,主要用于表示一段时间范围内对数据进行采样(通常是请求持续时间或响应大小),并能够对其指定区间以及总数进行统计,通常它采集的数据展示为直方图。 例如 Prometheus server 中 prometheus_local_storage_series_chunks_persisted, 表示 Prometheus 中每个时序需要存储的 chunks 数量,我们可以用它计算待持久化的数据的分位数。 Summary Summary 和 Histogram 类似,由 {quantile=\"\u003cφ\u003e\"},_sum,_count 组成,主要用于表示一段时间内数据采样结果(通常是请求持续时间或响应大小),它直接存储了 quantile 数据,而不是根据统计区间计算出来的。 例如 Prometheus server 中 prometheus_target_interval_length_seconds。 ","date":"2020-11-01","objectID":"/prometheus/:4:2","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"作业和实例 Prometheus 中,将任意一个独立的数据源(target)称之为实例(instance)。包含相同类型的实例的集合称之为作业(job)。 ","date":"2020-11-01","objectID":"/prometheus/:4:3","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"Exporter Exporter 是 prometheus 监控中重要的组成部分,负责数据指标的采集。广义上讲所有可以向 Prometheus 提供监控样本数据的程序都可以被称为一个 Exporter。而 Exporter 的一个实例称为 target。官方给出的插件有 blackbox_exporter、node_exporter、mysqld_exporter、snmp_exporter 等,第三方的插件有 redis_exporter,cadvisor 等。 ","date":"2020-11-01","objectID":"/prometheus/:5:0","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"node_exporter node_exporter 主要用来采集机器的性能指标数据,包括 cpu,内存,磁盘,io 等基本信息。 GitHub 地址:https://github.com/prometheus/node_exporter 官方教程:https://prometheus.io/docs/guides/node-exporter/ ","date":"2020-11-01","objectID":"/prometheus/:5:1","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"mysqld_exporter mysql_exporter 是用来收集 MysQL 或者 Mariadb 数据库相关指标的,mysql_exporter 需要连接到数据库并有相关权限。 GitHub 地址:https://github.com/prometheus/mysqld_exporter ","date":"2020-11-01","objectID":"/prometheus/:5:2","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"redis_exporter redis_exporter 是用来收集 Redis 数据库相关指标的。 GitHub 地址:https://github.com/oliver006/redis_exporter 除了直接使用社区提供的 Exporter 程序以外,用户还可以基于 Prometheus 提供的 Client Library 创建自己的 Exporter 程序,目前 Promthues 社区官方提供了对以下编程语言的支持:Go、Java/Scala、Python、Ruby。同时还有第三方实现的如:Bash、C++、Common Lisp、Erlang,、Haskeel、Lua、Node.js、PHP、Rust 等。 ","date":"2020-11-01","objectID":"/prometheus/:5:3","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["监控告警"],"content":"参考 https://prometheus.io/docs https://songjiayang.gitbooks.io/prometheus https://yunlzheng.gitbook.io/prometheus-book/ ","date":"2020-11-01","objectID":"/prometheus/:6:0","tags":["监控告警","Prometheus"],"title":"Prometheus","uri":"/prometheus/"},{"categories":["链路追踪"],"content":"简介 受 Dapper 和 OpenZipkin 启发的 Jaeger 是由 Uber 开源的分布式跟踪系统,兼容 OpenTracing 标准。它用于监视和诊断基于微服务的分布式系统,功能包括: 分布式上下文传播 分布式交易监控 根本原因分析 服务依赖性分析 性能/延迟优化 ","date":"2020-10-28","objectID":"/jaeger/:1:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"特性 兼容 OpenTracing 的数据模型和工具库 多语言支持 对每个服务/端点概率使用一致的前期采样 多种存储后端支持:Cassandra,Elasticsearch,内存。 系统拓扑图 自适应采样 收集后数据处理管道 ","date":"2020-10-28","objectID":"/jaeger/:2:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"架构 jaeger 架构如下: Collector 直接写入存储 Collecter 写入 Kafka 作为初始缓冲区 Jaeger 主要包括以下这些组件:(每一个组件都支持单独部署) jaeger-client:Jaeger 的客户端,实现了 OpenTracing 的 API,支持主流编程语言。客户端直接集成在目标 Application 中,其作用是记录和发送 Span 到 Jaeger Agent。在 Application 中调用 Jaeger Client Library 记录 Span 的过程通常被称为埋点。 jaeger-agent:暂存 Jaeger Client 发来的 Span,并批量向 Jaeger Collector 发送 Span,一般每台机器上都会部署一个 Jaeger Agent。官方的介绍中还强调了 Jaeger Agent 可以将服务发现的功能从 Client 中抽离出来,不过从架构角度讲,如果是部署在 Kubernetes 或者是 Nomad 中,Jaeger Agent 存在的意义并不大。 jaeger-collector:接受 Jaeger Agent 发来的数据,并将其写入存储后端,目前支持采用 Cassandra 和 Elasticsearch 作为存储后端。比较推荐用 Elasticsearch,既可以和日志服务共用同一个 ES,又可以使用 Kibana 对 Trace 数据进行额外的分析。架构图中的存储后端是 Cassandra,旁边还有一个 Spark,讲的就是可以用 Spark 等其他工具对存储后端中的 Span 进行直接分析。 jaeger-query \u0026 jaeger-ui:读取存储后端中的数据,以直观的形式呈现。 ingester:从 Kafka Topic 中读取数据并写入到另一个存储后端(Cassandra, Elasticsearch)。 ","date":"2020-10-28","objectID":"/jaeger/:3:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"快速部署本地环境 ","date":"2020-10-28","objectID":"/jaeger/:4:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"All in one 多合一是用于快速本地测试的可执行文件,具有内存存储组件,可启动 Jaeger UI,收集器,查询和代理。开始多合一的最简单方法是使用发布到 DockerHub 的预构建映像(单个命令行)。 docker run -d --name jaeger \\ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \\ -p 5775:5775/udp \\ -p 6831:6831/udp \\ -p 6832:6832/udp \\ -p 5778:5778 \\ -p 16686:16686 \\ -p 14268:14268 \\ -p 9411:9411 \\ jaegertracing/all-in-one:1.14 容器需要暴露的端口 端口 协议 所属模块 功能 6831 UDP agent accept jaeger.thrift over Thrift-compact protocol (used by most SDKs) 6832 UDP agent accept jaeger.thrift over Thrift-binary protocol (used by Node.js SDK) 5775 UDP agent (deprecated) accept zipkin.thrift over compact Thrift protocol (used by legacy clients only) 5778 HTTP agent serve configs (sampling, etc.) 16686 HTTP query serve frontend 14268 HTTP collector accept jaeger.thrift directly from clients 14250 HTTP collector accept model.proto 9411 HTTP collector Zipkin compatible endpoint (optional) ","date":"2020-10-28","objectID":"/jaeger/:4:1","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"手动安装 根据不同系统,下载安装包 运行 jaeger-all-in-one[.exe] jaeger-all-in-one --collector.zipkin.http-port=9411 安装好之后可以通过 http://localhost:16686 访问 Jaeger UI。 ","date":"2020-10-28","objectID":"/jaeger/:4:2","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"采样 Jaeger 库实现了一致的前期(或基于头)的采样。例如,假设我们有一个简单的调用图,其中服务 A 调用服务 B,服务 B 调用服务 C:A-\u003e B-\u003eC。当服务 A 收到不包含跟踪信息的请求时,Jaeger 跟踪器将开始新的跟踪,为其分配一个随机跟踪 ID,然后根据当前安装的采样策略做出采样决定。采样决策将与请求一起传播到 B 和 C,因此这些服务将不会再次做出采样决策,而是会尊重顶级服务 A 做出的决策。这种方法保证了,如果对跟踪进行了采样,则所有其跨度将记录在后端。如果每个服务都做出自己的抽样决定,那么我们很少会在后端获得完整的跟踪。 支持设置采样率是 Jaeger 的一个亮点,在生产环境中,如果对每个请求都开启 Trace,必然会对系统性能带来一定压力,除此之外,数量庞大的 Span 也会占用大量的存储空间。为了尽量消除分布式追踪采样对系统带来的影响,设置采样率是一个很好的办法。 ","date":"2020-10-28","objectID":"/jaeger/:5:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"客户端采样配置 当使用配置对象来实例化 tracer 时,可以通过 sampler.type 和 sampler.param 属性选择采样类型。Jaeger 支持下面四种采样策略: Constant(sampler.type=const):const 意为常量,采样器始终对所有 traces 做出相同的决定。sample.param=1 则采样所有 tracer,sample.param=0 则都不采样。 Probabilistic (sampler.type=probabilistic):概率采样,采样概率介于 0-1 之间,通过 sample.param 属性进行配置,例如,在 sampler.param=0.1 的情况下,将在 10 条 traces 中大约采样 1 条。 Rate Limiting (sampler.type=ratelimiting):设置每秒的采样次数上限。当 sampler.param=2 的时候,将以每秒 2 条 traces 的速率对请求进行采样。 Remote (sampler.type=remote):默认配置,client 将从 jaeger-agent 中获取当前服务使用的采样策略,这允许 Client 从 Jaeger Agent 中动态获取采样率设置。 ","date":"2020-10-28","objectID":"/jaeger/:5:1","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"自适应采样器 自适应采样器是一个组合了两个功能的复合采样器: 它基于每个操作(即基于 span 操作名称)做出抽样决策。这在 API 服务中特别有用,这些 API 服务的端点的流量可能非常不同,并且对整个服务使用单个概率采样器可能会使某些低 QPS 端点饿死(从不采样)。 它支持最低的保证采样率,例如始终允许每秒最多 N 条 traces,然后以一定的概率采样所有高于此值的采样率(一切都是针对每个操作,而不是针对每个服务)。 ","date":"2020-10-28","objectID":"/jaeger/:5:2","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"Jaeger 采样配置示例 收集器可以通过 –sampling.strategies-file 选项通过静态采样策略实例化(如果使用 Remote sample r 配置, 则将传播到相应的服务)。该选项需要一个已定义采样策略的 json 文件的路径。 如果未提供任何配置,则收集器将为所有服务返回默认概率抽样策略,概率为 0.001(0.1%) { \"service_strategies\": [ { \"service\": \"foo\", \"type\": \"probabilistic\", \"param\": 0.8, \"operation_strategies\": [ { \"operation\": \"op1\", \"type\": \"probabilistic\", \"param\": 0.2 }, { \"operation\": \"op2\", \"type\": \"probabilistic\", \"param\": 0.4 } ] }, { \"service\": \"bar\", \"type\": \"ratelimiting\", \"param\": 5 } ], \"default_strategy\": { \"type\": \"probabilistic\", \"param\": 0.5, \"operation_strategies\": [ { \"operation\": \"/health\", \"type\": \"probabilistic\", \"param\": 0.0 }, { \"operation\": \"/metrics\", \"type\": \"probabilistic\", \"param\": 0.0 } ] } } service_strategies 元素定义特定于服务的采样策略,而 operation_strategies 元素定义特定于操作的采样策略。可能有两种策略:概率策略和速率限制,如上所述(注意:operation_strategies 不支持速率限制)。如果服务不是 service_strategies 定义在内的操作,则采用 default_strategy 定义的采样策略。 ","date":"2020-10-28","objectID":"/jaeger/:5:3","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"Go 简单示例 package main import ( \"github.com/opentracing/opentracing-go\" \"github.com/uber/jaeger-client-go\" \"github.com/uber/jaeger-client-go/config\" \"io\" \"time\" ) func main() { tracer, closer, err := NewTracer(\"demo-service\", \"127.0.0.1:6831\") if err != nil { panic(err) } defer closer.Close() // 创建第一个 span A ASpan := tracer.StartSpan(\"A\") // 可手动调用 Finish() defer ASpan.Finish() CallBService(tracer, ASpan) } // CallBService 调用B方法,可以假设是另外一个服务 func CallBService(tracer opentracing.Tracer, parentSpan opentracing.Span) { // 继承上下文关系,创建子 span BSpan := tracer.StartSpan(\"B\", opentracing.ChildOf(parentSpan.Context())) BSpan.Finish() } // NewTracer 新建tracer func NewTracer(serviceName, addr string) (opentracing.Tracer, io.Closer, error) { cgf := config.Configuration{ // 服务名称 ServiceName: serviceName, // 采样配置 Sampler: \u0026config.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, // 上报配置 Reporter: \u0026config.ReporterConfig{ BufferFlushInterval: 1 * time.Second, LogSpans: true, LocalAgentHostPort: addr, // 直接上报到collector // CollectorEndpoint: \"http://127.0.0.1:14268/api/traces\", }, } return cgf.NewTracer() } ","date":"2020-10-28","objectID":"/jaeger/:6:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"Go HTTP 示例 客户端 package main import ( \"fmt\" \"github.com/opentracing/opentracing-go\" \"github.com/opentracing/opentracing-go/ext\" \"github.com/opentracing/opentracing-go/log\" \"github.com/uber/jaeger-client-go\" \"github.com/uber/jaeger-client-go/config\" \"io\" \"net/http\" \"time\" ) func main() { tracer, closer, err := NewClientTracer(\"demo-service\", \"127.0.0.1:6831\") if err != nil { panic(err) } defer closer.Close() // 创建第一个 span A ASpan := tracer.StartSpan(\"A\") // 可手动调用 Finish() defer ASpan.Finish() CallUserInfo(tracer, ASpan) } // CallUserInfo 请求远程服务,获得用户信息 func CallUserInfo(tracer opentracing.Tracer, parentSpan opentracing.Span) { // 继承上下文关系,创建子 span childSpan := tracer.StartSpan( \"B\", opentracing.ChildOf(parentSpan.Context()), ) url := \"http://127.0.0.1:8081/Get?username=jeff\" req, _ := http.NewRequest(\"GET\", url, nil) // 设置 tag ext.SpanKindRPCClient.Set(childSpan) ext.HTTPUrl.Set(childSpan, url) ext.HTTPMethod.Set(childSpan, \"GET\") err := tracer.Inject(childSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) if err != nil { log.Error(err) } resp, _ := http.DefaultClient.Do(req) log.Event(fmt.Sprintf(\"%v\", resp)) defer childSpan.Finish() } // NewClientTracer 新建tracer func NewClientTracer(serviceName, addr string) (opentracing.Tracer, io.Closer, error) { cgf := config.Configuration{ // 服务名称 ServiceName: serviceName, // 采样配置 Sampler: \u0026config.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, // 上报配置 Reporter: \u0026config.ReporterConfig{ BufferFlushInterval: 1 * time.Second, LogSpans: true, LocalAgentHostPort: addr, // 直接上报到collector // CollectorEndpoint: \"http://127.0.0.1:14268/api/traces\", }, } return cgf.NewTracer() } 服务端 package main import ( \"github.com/gin-gonic/gin\" \"github.com/opentracing/opentracing-go\" \"github.com/opentracing/opentracing-go/ext\" \"github.com/opentracing/opentracing-go/log\" \"github.com/uber/jaeger-client-go\" \"github.com/uber/jaeger-client-go/config\" \"io\" \"net/http\" \"time\" ) func main() { r := gin.Default() // 插入中间件处理 r.Use(UseOpenTracing()) r.GET(\"/Get\", GetUserInfo) err := r.Run(\"0.0.0.0:8081\") // listen and serve on 0.0.0.0:8080 (for windows \"localhost:8080\") if err != nil { panic(err) } } func GetUserInfo(ctx *gin.Context) { userName := ctx.Param(\"username\") log.Event(\"收到请求,用户名称为:\" + userName) ctx.String(http.StatusOK, \"他的博客是 https://luzhifang.github.io/\") } func UseOpenTracing() gin.HandlerFunc { handler := func(c *gin.Context) { tracer, closer, _ := NewServerTracer(\"userInfoWebService\", \"127.0.0.1:6831\") spanContext, err := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header)) if err != nil { log.Error(err) } defer closer.Close() // 生成依赖关系,并新建一个 span、 // 这里很重要,因为生成了 References []SpanReference 依赖关系 startSpan := tracer.StartSpan(c.Request.URL.Path, ext.RPCServerOption(spanContext)) defer startSpan.Finish() // 记录 tag // 记录请求 Url ext.HTTPUrl.Set(startSpan, c.Request.URL.Path) // Http Method ext.HTTPMethod.Set(startSpan, c.Request.Method) // 记录组件名称 ext.Component.Set(startSpan, \"Gin-Http\") // 在 header 中加上当前进程的上下文信息 c.Request = c.Request.WithContext(opentracing.ContextWithSpan(c.Request.Context(), startSpan)) // 传递给下一个中间件 c.Next() // 继续设置 tag ext.HTTPStatusCode.Set(startSpan, uint16(c.Writer.Status())) } return handler } // NewServerTracer 新建tracer func NewServerTracer(serviceName, addr string) (opentracing.Tracer, io.Closer, error) { cgf := config.Configuration{ // 服务名称 ServiceName: serviceName, // 采样配置 Sampler: \u0026config.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, // 上报配置 Reporter: \u0026config.ReporterConfig{ BufferFlushInterval: 1 * time.Second, LogSpans: true, LocalAgentHostPort: addr, // 直接上报到collector // CollectorEndpoint: \"http://127.0.0.1:14268/api/traces\", }, } return cgf.NewTracer() } ","date":"2020-10-28","objectID":"/jaeger/:7:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"参考 https://juejin.cn/post/6844903971010641934 https://xiaoming.net.cn/2021/03/25/Opentracing%E6%A0%87%E5%87%86%E5%92%8CJaeger%E5%AE%9E%E7%8E%B0 ","date":"2020-10-28","objectID":"/jaeger/:8:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列3-jaeger快速入门","uri":"/jaeger/"},{"categories":["链路追踪"],"content":"分布式链路追踪系统其中最著名的是 Google Dapper 论文所介绍的 Dapper。源于 Google 为了解决可能由不同团队,不同语言,不同模块,部署在不同服务器,不同数据中心的所带来的软件复杂性(很难去分析,无法做定位),构建了一个的分布式跟踪系统。 Tracing 介于 Logging 和 Metric 之间, 以请求的维度来串联服务间的调用关系并记录调用耗时,即保留了必要的信息,又将分散的日志事件通过 Span 串联,帮助我们更好的理解系统的行为、辅助调试和排查性能问题。 ","date":"2020-10-26","objectID":"/tracing-tech-selection/:0:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列2-技术选型","uri":"/tracing-tech-selection/"},{"categories":["链路追踪"],"content":"分布式链路追踪系统设计目标 低侵入性:代码侵入性 灵活的应用策略:收集数据的范围和粒度 时效性:从 agent 采样,到 collect、storage 和 display 尽可能快 决策支持:策略是否可配置 可视化 低消耗:在 Web 请求链路中,对请求的响应影响尽可能小 延展性:随着业务量的增长,分布式追踪系统依然具有高可用和高性能表现 ","date":"2020-10-26","objectID":"/tracing-tech-selection/:1:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列2-技术选型","uri":"/tracing-tech-selection/"},{"categories":["链路追踪"],"content":"对比 名称 厂商 开发语言 OpenTracing 兼容 侵入性 时效性 可视化 消耗 Jaeger Uber Go 是 中 高 中 低 Zipkin twitter Java 是 高 高 中 低 Pinpoint NAVER Java 否 低 - 高 低 CAT 大众点评 Java 否 高 中 高 低 Appdash sourcegraph Go 是 低 高 低 不支持大规模部署 SkyWalking 华为 Java 是 低 中 高 低 ","date":"2020-10-26","objectID":"/tracing-tech-selection/:2:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列2-技术选型","uri":"/tracing-tech-selection/"},{"categories":["链路追踪"],"content":"小结 一般选择的时候会选择语言亲和性比较强的追踪系统,比如 Go 选择 jaeger,Java 选择 Zipkin 和 Skywalking。 jaeger 兼容 Zipkin,在这点上 Jaeger 完胜。 ","date":"2020-10-26","objectID":"/tracing-tech-selection/:3:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列2-技术选型","uri":"/tracing-tech-selection/"},{"categories":["链路追踪"],"content":"参考 https://jckling.github.io/2021/04/02/Jaeger/%E5%85%A8%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA%E4%B8%8E%20Jaeger%20%E5%85%A5%E9%97%A8/ ","date":"2020-10-26","objectID":"/tracing-tech-selection/:4:0","tags":["OpenTracing","jaeger"],"title":"分布式链路追踪系列2-技术选型","uri":"/tracing-tech-selection/"},{"categories":["链路追踪"],"content":"简介 OpenTracing 是一个中立的(厂商无关、平台无关)分布式追踪的 API 规范,提供了统一接口方便开发者在自己的服务中集成一种或者多种分布式追踪的实现。 ","date":"2020-10-22","objectID":"/opentracing/:1:0","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"OpenTracing 数据模型 Trace:OpenTracing 中的Trace(调用链)通过归属于此调用链的Span来隐性的定义,一条Trace可以被认为是一个由多个Span组成的有向无环图(DAG 图)。 References:Span 与 Span 的关系被命名为References。 Span:可以被翻译为跨度,可以被理解为一次方法调用, 一个程序块的调用, 或者一次 RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个 span。 例如:下面的示例Trace就是由 8 个Span组成: 单个Trace中,span间的因果关系 [Span A] ←←←(the root span) | +------+------+ | | [Span B] [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf) | | [Span D] +---+-------+ | | [Span E] [Span F] \u003e\u003e\u003e [Span G] \u003e\u003e\u003e [Span H] ↑ ↑ ↑ (Span G 在 Span F 后被调用, FollowsFrom) 有些时候,使用下面这种,基于时间轴的时序图可以更好的展现Trace(调用链): 单个Trace中,span间的时间关系 ––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–\u003e time [Span A···················································] [Span B··············································] [Span D··········································] [Span C········································] [Span E·······] [Span F··] [Span G··] [Span H··] 每个Span包含以下的状态: An operation name,操作名称 A start timestamp,起始时间 A finish timestamp,结束时间 Span Tag:一组键值对构成的 Span 标签集合。键值对中,键必须为 string,值可以是字符串,布尔,或者数字类型。 Span Log:一组 span 的日志集合。 每次 log 操作包含一个键值对,以及一个时间戳。 键值对中,键必须为 string,值可以是任意类型。 但是需要注意,不是所有的支持 OpenTracing 的 Tracer,都需要支持所有的值类型。 SpanContext:上下文对象。SpanContext 携带着一些用于跨服务通信的(跨进程)数据,主要包含: 任何一个 OpenTracing 的实现,都需要将当前调用链的状态(例如:trace 和 span 的 id),依赖一个独特的 Span 去跨进程边界传输。 Baggage Items:Trace 的随行数据,是一个键值对集合,它存在于 trace 中,也需要跨进程边界传输。 References:Span 间的关系,相关的零个或者多个 Span(Span间通过SpanContext建立这种关系) ","date":"2020-10-22","objectID":"/opentracing/:2:0","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"Span 间关系 一个 Span 可以与一个或者多个SpanContexts存在因果关系。OpenTracing 目前定义了两种关系:ChildOf(父子) 和 FollowsFrom(跟随)。这两种关系明确的给出了两个父子关系的 Span 的因果模型。 将来,OpenTracing 可能提供非因果关系的 span 间关系。(例如:span 被批量处理,span 被阻塞在同一个队列中,等等)。 ChildOf 引用: 一个 span 可能是一个父级 span 的孩子,即\"ChildOf\"关系。在\"ChildOf\"引用关系下,父级 span 某种程度上取决于子 span。下面这些情况会构成\"ChildOf\"关系: 一个 RPC 调用的服务端的 span,和 RPC 服务客户端的 span 构成 ChildOf 关系 一个 sql insert 操作的 span,和 ORM 的 save 方法的 span 构成 ChildOf 关系 很多 span 可以并行工作(或者分布式工作)都可能是一个父级的 span 的子项,他会合并所有子 span 的执行结果,并在指定期限内返回 下面都是合理的表述一个\"ChildOf\"关系的父子节点关系的时序图。 [-Parent Span---------] [-Child Span----] [-Parent Span--------------] [-Child Span A----] [-Child Span B----] [-Child Span C----] [-Child Span D---------------] [-Child Span E----] FollowsFrom 引用: 一些父级节点不以任何方式依赖他们子节点的执行结果,这种情况下,我们说这些子 span 和父 span 之间是\"FollowsFrom\"的因果关系。“FollowsFrom\"关系可以被分为很多不同的子类型,未来版本的 OpenTracing 中将正式的区分这些类型 下面都是合理的表述一个\"FollowFrom\"关系的父子节点关系的时序图。 [-Parent Span-] [-Child Span-] [-Parent Span--] [-Child Span-] [-Parent Span-] [-Child Span-] ","date":"2020-10-22","objectID":"/opentracing/:2:1","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"OpenTracing API OpenTracing 标准中有三个重要的相互关联的类型,分别是Tracer, Span 和 SpanContext。每种类型的行为都会在各语言实现层面上,会演变成一个方法。 ","date":"2020-10-22","objectID":"/opentracing/:3:0","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"Tracer Tracer接口用来创建Span,以及处理如何Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递。它具有如下能力: 创建一个新Span 必填参数 operation name, 操作名, 一个具有可读性的字符串,代表这个 span 所做的工作(例如:RPC 方法名,方法名,或者一个大型计算中的某个阶段或子任务)。操作名应该是一个抽象、通用,明确、具有统计意义的名称。因此,\"get_user\" 作为操作名,比 \"get_user/314159\"更好。 例如,假设一个获取账户信息的 span 会有如下可能的名称: 操作名 指导意见 get 太抽象 get_account/792 太明确 get_account 正确的操作名,关于account_id=792的信息应该使用Tag操作 可选参数 零个或者多个关联(references)的SpanContext,如果可能,同时快速指定关系类型,ChildOf 还是 FollowsFrom。 一个可选的显性传递的开始时间;如果忽略,当前时间被用作开始时间。 零个或者多个tag。 返回值,返回一个已经启动Span实例。 将SpanContext上下文 Inject(注入)到 carrier 必填参数 SpanContext 实例 format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何对SpanContext进行编码放入到 carrier 中。 carrier,根据format确定。Tracer实现根据format声明的格式,将SpanContext序列化到 carrier 对象中。 将SpanContext上下文从 carrier 中 Extract(提取) 必填参数 format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何从 carrier 中解码SpanContext。 carrier,根据format确定。Tracer实现根据format声明的格式,从 carrier 中解码SpanContext。 返回值,返回一个SpanContext实例,可以使用这个SpanContext实例,通过Tracer创建新的Span。 注意,对于 Inject(注入)和 Extract(提取),format是必须的。 Inject(注入)和 Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数\"carrier\"的类型,同时约束了\"carrier\"中SpanContext是如何编码的。所有的 Tracer 实现,都必须支持下面的format。 Text Map: 基于字符串:字符串的 map,对于 key 和 value 不约束字符集。 HTTP Headers: 适合作为 HTTP 头信息的,基于字符串:字符串的 map。(RFC 7230.在工程实践中,如何处理 HTTP 头具有多样性,强烈建议 tracer 的使用者谨慎使用 HTTP 头的键值空间和转义符) Binary: 一个简单的二进制大对象,记录SpanContext的信息。 ","date":"2020-10-22","objectID":"/opentracing/:3:1","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"Span 当Span结束后(span.finish()),除了通过Span获取SpanContext外,下列其他所有方法都不允许被调用。 获取Span的SpanContext 不需要任何参数。 返回值,Span构建时传入的SpanContext。这个返回值在Span结束后(span.finish()),依然可以使用。 复写操作名(operation name) 必填参数 新的操作名operation name,覆盖构建Span时,传入的操作名。 结束Span 可选参数 一个明确的完成时间;如果省略此参数,使用当前时间作为完成时间。 为Span设置 tag 必填参数 tag key,必须是 string 类型 tag value,类型为字符串,布尔或者数字 注意,OpenTracing 标准包含**“standard tags,标准 Tag”**,此文档中定义了 Tag 的标准含义。 Log 结构化数据 必填参数 一个或者多个键值对,其中键必须是字符串类型,值可以是任意类型。某些 OpenTracing 实现,可能支持更多的 log 值类型。 可选参数 一个明确的时间戳。如果指定时间戳,那么它必须在 span 的开始和结束时间之内。 注意,OpenTracing 标准包含**“standard log keys,标准 log 的键”**,此文档中定义了这些键的标准含义。 设置一个baggage(随行数据)元素 Baggage 元素是一个键值对集合,将这些值设置给给定的Span,Span的SpanContext,以及所有和此Span有直接或者间接关系的本地Span。 也就是说,baggage 元素随 trace 一起保持在带内传递。(译者注:带内传递,在这里指,随应用程序调用过程一起传递) Baggage 元素具有强大的功能,使得 OpenTracing 能够实现全栈集成(例如:任意的应用程序数据,可以在移动端创建它,显然的,它会一直传递了系统最底层的存储系统),同时他也会产生巨大的开销,请小心使用此特性。 必填参数 baggage key, 字符串类型 baggage value, 字符串类型 获取一个baggage元素 必填参数 baggage key, 字符串类型 返回值,相应的baggage value,或者可以标识元素值不存在的返回值(译者注:如 Null)。 ","date":"2020-10-22","objectID":"/opentracing/:3:2","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"SpanContext 相对于 OpenTracing 中其他的功能,SpanContext更多的是一个“概念”。也就是说,OpenTracing 实现中,需要重点考虑,并提供一套自己的 API。 OpenTracing 的使用者仅仅需要,在创建 span、向传输协议 Inject(注入)和从传输协议中 Extract(提取)时,使用SpanContext和references。 OpenTracing 要求,SpanContext是不可变的,目的是防止由于Span的结束和相互关系,造成的复杂生命周期问题。 遍历所有的 baggage 元素 遍历模型依赖于语言,实现方式可能不一致。在语义上,要求调用者可以通过给定的SpanContext实例,高效的遍历所有的 baggage 元素 ","date":"2020-10-22","objectID":"/opentracing/:3:3","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"NoopTracer 所有的 OpenTracing API 实现,必须提供某种方式的NoopTracer实现。NoopTracer可以被用作控制或者测试时,进行无害的 inject 注入(等等)。例如,在 OpenTracing-Java 实现中,NoopTracer在他自己的模块中。 ","date":"2020-10-22","objectID":"/opentracing/:3:4","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"可选 API 元素 有些语言的 OpenTracing 实现,为了在串行处理中,传递活跃的Span或SpanContext,提供了一些工具类。例如,opentracing-go中,通过context.Context机制,可以设置和获取活跃的Span。 ","date":"2020-10-22","objectID":"/opentracing/:3:5","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["链路追踪"],"content":"参考 https://opentracing.io/docs/overview/ https://github.com/opentracing-contrib/opentracing-specification-zh/blob/master/specification.md https://github.com/opentracing/specification/blob/master/specification.md https://github.com/opentracing/specification/blob/master/semantic_conventions.md ","date":"2020-10-22","objectID":"/opentracing/:4:0","tags":["OpenTracing"],"title":"分布式链路追踪系列1-OpenTracing 规范","uri":"/opentracing/"},{"categories":["Linux"],"content":"安装 meld (如果缺少的话) cd /usr/local wget https://pypi.python.org/packages/0f/5e/3a57c223d8faba2c3c2d69699f7e6cfdd1e5cc31e79cdd0dd48d44580b50/meld3-1.0.1.tar.gz#md5=2f045abe0ae108e3c8343172cffbe21d tar -zxvf meld3-1.0.1.tar.gz cd meld3-1.0.1 python setup.py install ","date":"2020-08-06","objectID":"/centos6.6-supervisor3.x/:1:0","tags":["Linux","supervisor"],"title":"centos6.6安装supervisor3.x方法","uri":"/centos6.6-supervisor3.x/"},{"categories":["Linux"],"content":"安装 supervisor3 wget https://pypi.python.org/packages/source/s/supervisor/supervisor-3.0b2.tar.gz --no-check-certificate tar -zxvf supervisor-3.0b2.tar.gz cd supervisor-3.0b2 python setup.py install yum install python-setuptools ","date":"2020-08-06","objectID":"/centos6.6-supervisor3.x/:2:0","tags":["Linux","supervisor"],"title":"centos6.6安装supervisor3.x方法","uri":"/centos6.6-supervisor3.x/"},{"categories":["Linux"],"content":"创建日志文件 touch /var/log/supervisord.log chmod 755 /var/log/supervisord.log ","date":"2020-08-06","objectID":"/centos6.6-supervisor3.x/:3:0","tags":["Linux","supervisor"],"title":"centos6.6安装supervisor3.x方法","uri":"/centos6.6-supervisor3.x/"},{"categories":["Linux"],"content":"创建默认的配置文件 echo_supervisord_conf \u003e /etc/supervisord.conf ","date":"2020-08-06","objectID":"/centos6.6-supervisor3.x/:4:0","tags":["Linux","supervisor"],"title":"centos6.6安装supervisor3.x方法","uri":"/centos6.6-supervisor3.x/"},{"categories":["Linux"],"content":"修改/etc/supervisord.conf # 添加到末尾 [include] files = /etc/supervisord.d/\\*.ini ","date":"2020-08-06","objectID":"/centos6.6-supervisor3.x/:5:0","tags":["Linux","supervisor"],"title":"centos6.6安装supervisor3.x方法","uri":"/centos6.6-supervisor3.x/"},{"categories":["ELK"],"content":"单节点安装 修改相关配置文件 # 或者执行 sysctl -w vm.max_map_count=262144 echo \"vm.max_map_count=262144\" \u003e\u003e /etc/sysctl.conf echo \"* soft nproc 131072\" \u003e\u003e /etc/security/limits.conf echo \"* hard nproc 131072\" \u003e\u003e /etc/security/limits.conf echo \"* soft nofile 131072\" \u003e\u003e /etc/security/limits.conf echo \"* hard nofile 131072\" \u003e\u003e /etc/security/limits.conf ","date":"2020-08-06","objectID":"/elasticsearch-install/:1:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 安装","uri":"/elasticsearch-install/"},{"categories":["ELK"],"content":"二进制方式安装 官网下载链接 cd /usr/local wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-xxx-linux-x86_64.tar.gz wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-xxx-linux-x86_64.tar.gz.sha512 shasum -a 512 -c elasticsearch-xxx-linux-x86_64.tar.gz.sha512 tar -xzf elasticsearch-xxx-linux-x86_64.tar.gz cd elasticsearch-xxx/ ./bin/elasticsearch -d -p pid # 验证是否启动成功 curl --cacert $ES_HOME/config/certs/http_ca.crt -u elastic https://localhost:9200 ","date":"2020-08-06","objectID":"/elasticsearch-install/:1:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 安装","uri":"/elasticsearch-install/"},{"categories":["ELK"],"content":"docker 方式安装 docker pull docker.elastic.co/elasticsearch/elasticsearch:8.6.0 docker network create elastic docker run --name es01 --net elastic -p 9200:9200 -it docker.elastic.co/elasticsearch/elasticsearch:8.6.0 # 重置相关密码 docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-reset-password ","date":"2020-08-06","objectID":"/elasticsearch-install/:1:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 安装","uri":"/elasticsearch-install/"},{"categories":["ELK"],"content":"集群 首先按照单节点安装能跑起来先 然后修改各自配置文件 config/elasticsearch.yml 下面是三份配置示例,具体配置可能不同版本稍有不同,请参照官方文档 node-1 # 集群名称,三台集群,要配置相同的集群名称!!! cluster.name: my-es-cluster # 节点名称 node.name: node-1 # 是不是有资格主节点 node.master: true # 是否存储数据 node.data: true # ⽹关地址 network.host: 0.0.0.0 network.publish_host: 192.168.0.1 # http端⼝ http.port: 9200 # 内部节点之间沟通端⼝ transport.port: 9300 # es7.x 之后新增的配置,写⼊候选主节点的设备地址,在开启服务后可以被选为主节点 discovery.seed_hosts: [\"192.168.0.1:9300\",\"192.168.0.2:9300\",\"192.168.0.3:9300\"] # es7.x 之后新增的配置,初始化⼀个新的集群时需要此配置来选举master cluster.initial_master_nodes: [\"node-1\", \"node-2\",\"node-3\"] # 数据和存储路径 path.data: /home/ubuntu/es/data path.logs: /home/ubuntu/es/logs node-2 # 集群名称,三台集群,要配置相同的集群名称!!! cluster.name: my-es-cluster # 节点名称 node.name: node-2 # 是不是有资格主节点 node.master: true # 是否存储数据 node.data: true # ⽹关地址 network.host: 0.0.0.0 network.publish_host: 192.168.0.2 # http端⼝ http.port: 9200 # 内部节点之间沟通端⼝ transport.port: 9300 # es7.x 之后新增的配置,写⼊候选主节点的设备地址,在开启服务后可以被选为主节点 discovery.seed_hosts: [\"192.168.0.1:9300\",\"192.168.0.2:9300\",\"192.168.0.3:9300\"] # es7.x 之后新增的配置,初始化⼀个新的集群时需要此配置来选举master cluster.initial_master_nodes: [\"node-1\", \"node-2\",\"node-3\"] # 数据和存储路径 path.data: /home/ubuntu/es/data path.logs: /home/ubuntu/es/logs node-3 # 集群名称,三台集群,要配置相同的集群名称!!! cluster.name: my-es-cluster # 节点名称 node.name: node-3 # 是不是有资格主节点 node.master: true # 是否存储数据 node.data: true # ⽹关地址 network.host: 0.0.0.0 network.publish_host: 192.168.0.3 # http端⼝ http.port: 9200 # 内部节点之间沟通端⼝ transport.port: 9300 # es7.x 之后新增的配置,写⼊候选主节点的设备地址,在开启服务后可以被选为主节点 discovery.seed_hosts: [\"192.168.0.1:9300\",\"192.168.0.2:9300\",\"192.168.0.3:9300\"] # es7.x 之后新增的配置,初始化⼀个新的集群时需要此配置来选举master cluster.initial_master_nodes: [\"node-1\", \"node-2\",\"node-3\"] # 数据和存储路径 path.data: /home/ubuntu/es/data path.logs: /home/ubuntu/es/logs 修改好配置之后,重启三台 elasticsearch ","date":"2020-08-06","objectID":"/elasticsearch-install/:2:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 安装","uri":"/elasticsearch-install/"},{"categories":["ELK"],"content":"参考 https://blog.csdn.net/carefree2005/article/details/118027877 ","date":"2020-08-06","objectID":"/elasticsearch-install/:3:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 安装","uri":"/elasticsearch-install/"},{"categories":["ELK"],"content":"状态查询 ","date":"2020-08-06","objectID":"/elasticsearch-command/:1:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"获取所有 _cat 系列的操作 curl http://localhost:9200/_cat =^.^= /_cat/allocation /_cat/shards /_cat/shards/{index} /_cat/master /_cat/nodes /_cat/tasks /_cat/indices /_cat/indices/{index} /_cat/segments /_cat/segments/{index} /_cat/count /_cat/count/{index} /_cat/recovery /_cat/recovery/{index} /_cat/health /_cat/pending_tasks /_cat/aliases /_cat/aliases/{alias} /_cat/thread_pool /_cat/thread_pool/{thread_pools} /_cat/plugins /_cat/fielddata /_cat/fielddata/{fields} /_cat/nodeattrs /_cat/repositories /_cat/snapshots/{repository} /_cat/templates ","date":"2020-08-06","objectID":"/elasticsearch-command/:1:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"集群状态 curl -X GET \"localhost:9200/_cluster/health?pretty\" ","date":"2020-08-06","objectID":"/elasticsearch-command/:1:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"节点状态 节点简要信息 curl -X GET \"localhost:9200/_cat/nodes?pretty\u0026v\" ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name 172.25.64.5 38 97 6 0.18 0.14 0.56 cdfhilmrstw - es02 172.25.64.3 54 97 6 0.18 0.14 0.56 cdfhilmrstw - es03 172.25.64.2 52 97 6 0.18 0.14 0.56 cdfhilmrstw * es01 节点详细信息 curl -X GET \"localhost:9200/_nodes/stats/http?pretty\" 后面的 http 是查看的属性,另外还有 indices, fs, http, jvm, os, process, thread_pool, discovery 等,支持组合(如 indices,fs,http) ","date":"2020-08-06","objectID":"/elasticsearch-command/:1:3","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"分片状态 curl -X GET \"localhost:9200/_cat/shards?v\u0026pretty\" index shard prirep state docs store ip node elk-demo-2023.01.14 0 p STARTED 98 64.2kb 172.25.64.5 es02 elk-demo-2023.01.14 0 r STARTED 98 31.2kb 172.25.64.3 es03 分片中如果存在未分配的分片, 可以查看未分片的原因:_cat/shards?h=index,shard,prirep,state,unassigned.reason\u0026v ","date":"2020-08-06","objectID":"/elasticsearch-command/:1:4","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"索引 ","date":"2020-08-06","objectID":"/elasticsearch-command/:2:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"索引管理 索引列表 curl -X GET \"localhost:9200/_cat/indices?v\" health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open elk-demo-2023.01.13 _NX6po1HSs6-z_j0wVqBOw 1 1 6 0 40.2kb 20.1kb 条件过滤:_cat/indices?v\u0026health=yellow 排序:_cat/indices?v\u0026health=yellow\u0026s=docs.count:desc 索引详细信息 curl -X GET \"localhost:9200/elk-demo-2023.01.14/_stats?pretty\" 数据量 curl -X GET \"localhost:9200/_cat/count/elk-demo-2023.01.14?v\u0026pretty\" 新建索引 curl -X PUT -H \"content-type:application/json\" \"localhost:9200/my_index\" -d ' { \"settings\" : { \"index\" : { \"number_of_shards\" : 3, \"number_of_replicas\" : 2 } } }' 删除索引 curl -X DELETE \"localhost:9200/my_index\" 分词搜索 curl -X POST -H \"content-type:application/json\" \"localhost:9200/elk-demo-2023.01.14/_search\" -d ' { \"query\": { \"match\": { \"message\": \"测试\" } } }' 完全匹配搜索 curl -X POST -H \"content-type:application/json\" \"localhost:9200/elk-demo-2023.01.14/_search?pretty\" -d ' { \"query\": { \"match_phrase\": { \"message\": \"测试\" } } }' ","date":"2020-08-06","objectID":"/elasticsearch-command/:2:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"别名 查看别名 curl -X GET \"localhost:9200/_alias/elk-demo-2023.01.14?pretty\" 增加别名 curl -X PUT \"localhost:9200/elk-demo-2023.01.14/_alias/elk-demo-2023.01.14_alias?pretty\" 删除别名 curl -X POST -H \"content-type:application/json\" 'http://localhost:9200/_aliases' -d ' { \"actions\": [ {\"remove\": {\"index\": \"elk-demo-2023.01.14\", \"alias\": \"elk-demo-2023.01.14_alias\"}} ] }' 别名重新绑定 curl -X POST -H \"content-type:application/json\" 'http://localhost:9200/_aliases' -d ' { \"actions\" : [ { \"remove\" : { \"index\" : \"elk-demo-2023.01.14\", \"alias\" : \"elk-demo-2023.01.14_alias\" } }, { \"add\" : { \"index\" : \"elk-demo-2023.01.14_v2\", \"alias\" : \"elk-demo-2023.01.14_alias\" } } ] }' ","date":"2020-08-06","objectID":"/elasticsearch-command/:3:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"参考 ","date":"2020-08-06","objectID":"/elasticsearch-command/:4:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 常用命令","uri":"/elasticsearch-command/"},{"categories":["ELK"],"content":"硬件配置优化 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:1:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"CPU 配置 一般说来,CPU 繁忙的原因有以下几个: 线程中有无限空循环、无阻塞、正则匹配或者单纯的计算; 发生了频繁的 GC; 多线程的上下文切换; 大多数 Elasticsearch 部署往往对 CPU 要求不高(写入的时候比较耗 CPU)。因此,相对其它资源,具体配置多少个(CPU)不是那么关键。你应该选择具有多个内核的现代处理器,常见的集群使用 2 到 8 个核的机器。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:1:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"内存配置 如果有一种资源是最先被耗尽的,它可能是内存。排序和聚合都很耗内存,所以有足够的堆空间来应付它们是很重要的。即使堆空间是比较小的时候,也能为操作系统文件缓存提供额外的内存。因为 Lucene 使用的许多数据结构是基于磁盘的格式,Elasticsearch 利用操作系统缓存能产生很大效果。 64 GB 内存的机器是非常理想的,但是 32 GB 和 16 GB 机器也是很常见的。少于 8 GB 会适得其反(你最终需要很多很多的小机器),大于 64 GB 的机器也会有问题。 由于 ES 构建基于 lucene,而 lucene 设计强大之处在于 lucene 能够很好的利用操作系统内存来缓存索引数据,以提供快速的查询性能。lucene 的索引文件 segements 是存储在单文件中的,并且不可变,对于 OS 来说,能够很友好地将索引文件保持在 cache 中,以便快速访问;因此,我们很有必要将一半的物理内存留给 lucene;另一半的物理内存留给 ES(JVM heap)。 内存分配 当机器内存小于 64G 时,遵循通用的原则,50% 给 ES,50% 留给 lucene。 当机器内存大于 64G 时,遵循以下原则:Jvm heap 大小不要超过物理内存的 50%,最大也不要超过 32GB(compressed oop),它可用于其内部缓存的内存就越多,但可供操作系统用于文件系统缓存的内存就越少,heap 过大会导致 GC 时间过长。 禁止 swap 禁止 swap,一旦允许内存与磁盘的交换,会引起致命的性能问题。可以通过在 elasticsearch.yml 中 bootstrap.memory_lock: true,以保持 JVM 锁定内存,保证 ES 的性能。 GC 设置 修改 jvm.options 文件 -XX:+UseG1GC -XX:MaxGCPauseMillis=50 其中 -XX:MaxGCPauseMillis 是控制预期的最高 GC 时长,默认值为 200ms,如果线上业务特性对于 GC 停顿非常敏感,可以适当设置低一些。但是 这个值如果设置过小,可能会带来比较高的 cpu 消耗。 G1 对于集群正常运作的情况下减轻 G1 停顿对服务时延的影响还是很有效的,但是如果是你描述的 GC 导致集群卡死,那么很有可能换 G1 也无法根本上解决问题。 通常都是集群的数据模型或者 Query 需要优化。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:1:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"磁盘 在经济压力能承受的范围下,尽量使用固态硬盘(SSD)。 使用 RAID0 是提高硬盘速度的有效途径,对机械硬盘和 SSD 来说都是如此。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:1:3","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"操作系统 #设置虚拟内存大小,这个是MMPFILE存储内存需要;禁用内存交换 echo \"vm.max_map_count=262144\" \u003e\u003e /etc/sysctl.conf echo \"vm.swappiness=1\" \u003e\u003e /etc/sysctl.conf sysctl -p #设置文件句柄数与线程限制,因为ES的索引会产生和调用大量文件数 echo \"* soft nproc 131072\" \u003e\u003e /etc/security/limits.conf echo \"* hard nproc 131072\" \u003e\u003e /etc/security/limits.conf echo \"* soft nofile 131072\" \u003e\u003e /etc/security/limits.conf echo \"* hard nofile 131072\" \u003e\u003e /etc/security/limits.conf #内存锁定交换 echo \"* hard memlock unlimited\" \u003e\u003e /etc/security/limits.conf echo \"* soft memlock unlimited\" \u003e\u003e /etc/security/limits.conf ","date":"2020-08-05","objectID":"/elasticsearch-perf/:2:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"写入速度优化 配置参考 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"调整配置项 translog 的持久化策略 index.translog.durability index.translog.durability: request,这是影响 ES 写入速度的最大因素。但是只有这样,写操作才有可能是可靠的。 index.translog.durability: async,设置为 async 表示 translog 的刷盘策略按 sync_interval 配置指定的时间周期进行。 index.translog.sync_interval: 120s,加大 translog 刷盘间隔时间。默认为 5s,不可低于 100ms。 index.translog.flush_threshold_size: 1024mb,超过这个大小会导致 refresh 操作,产生新的 Lucene 分段。默认值为 512MB。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"调整配置项索引刷新间隔 index.refresh_interval 默认情况下索引的 refresh_interval 为 1 秒,这意味着数据写 1 秒后就可以被搜索到,每次索引的 refresh 会产生一个新的 Lucene 段,这会导致频繁的 segment merge 行为,如果不需要这么高的搜索实时性,应该降低索引 refresh 周期,例如:index.refresh_interval: 120s ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"段合并优化 segment merge 操作对系统 I/O 和内存占用都比较高,从 ES 2.0 开始,merge 行为不再由 ES 控制,而是由 Lucene 控制。 index.merge.scheduler.max_thread_count 最大线程数 max_thread_count 的默认值如下:Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))以上是一个比较理想的值,如果只有一块硬盘并且非 SSD,则应该把它设置为 1,因为在旋转存储介质上并发写,由于寻址的原因,只会降低写入速度。 index.merge.policy merge 策略 index.merge.policy 有三种: tiered(默认策略,推荐); log_byte_size; log_doc。 索引创建时合并策略就已确定,不能更改,但是可以动态更新策略参数,可以不做此项调整。 如果堆栈经常有很多 merge,则可以尝试调整以下策略配置:index.merge.policy.segments_per_tier 该属性指定了每层分段的数量,取值越小则最终 segment 越少,因此需要 merge 的操作更多,可以考虑适当增加此值。默认为 10,其应该大于等于 index.merge.policy.max_merge_at_once。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:3","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"减少副本数量 Elasticsearch 默认副本数量为 3 个,虽然这样会提高集群的可用性,增加搜索的并发数,但是同时也会影响写入索引的效率。 在索引过程中,需要把更新的文档发到副本节点上,等副本节点生效后在进行返回结束。使用 Elasticsearch 做业务搜索的时候,建议副本数目还是设置为 3 个,但是像内部 ELK 日志系统、分布式跟踪系统中,完全可以将副本数目设置为 1 个。 index.merge.policy.max_merged_segment 指定了单个 segment 的最大容量,默认为 5GB,可以考虑适当降低此值。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:4","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"indexing buffer indexing buffer 在为 doc 建立索引时使用,当缓冲满时会刷入磁盘,生成一个新的 segment,这是除 refresh_interval 刷新索引外,另一个生成新 segment 的机会。每个 shard 有自己的 indexing buffer。 在执行大量的索引操作时,indices.memory.index_buffer_size的默认设置可能不够,这和可用堆内存、单节点上的 shard 数量相关,可以考虑适当增大该值。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:5","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"使用 bulk 请求 建立索引的过程属于计算密集型任务,应该使用固定大小的线程池配置,来不及处理的任务放入队列。线程池最大线程数量应配置为 CPU 核心数+1,这也是 bulk 线程池的默认设置,可以避免过多的上下文切换。队列大小可以适当增加,但一定要严格控制大小,过大的队列导致较高的 GC 压力,并可能导致 FGC 频繁发生。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:6","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"索引过程调整和优化 自动生成 doc ID,通过 ES 写入流程可以看出,写入 doc 时如果外部指定了 id,则 ES 会先尝试读取原来 doc 的版本号,以判断是否需要更新。这会涉及一次读取磁盘的操作,通过自动生成 doc ID 可以避免这个环节。 调整字段 Mappings 减少字段数量,对于不需要建立索引的字段,不写入 ES。 将不需要建立索引的字段 index 属性设置为 not_analyzed 或 no。对字段不分词,或者不索引,可以减少很多运算操作,降低 CPU 占用。尤其是 binary 类型,默认情况下占用 CPU 非常高,而这种类型进行分词通常没有什么意义。 减少字段内容长度,如果原始数据的大段内容无须全部建立索引,则可以尽量减少不必要的内容。 使用不同的分析器(analyzer),不同的分析器在索引过程中运算复杂度也有较大的差异。 调整_source 字段,_source 字段用于存储 doc 原始数据,对于部分不需要存储的字段,可以通过 includes excludes 过滤,或者将_source 禁用,一般用于索引和数据分离。 对 Analyzed 的字段禁用 Norms,Norms 用于在搜索时计算 doc 的评分,如果不需要评分,则可以将其禁用:"title": {"type": "string","norms": {"enabled": false}} index_options 设置,index_options 用于控制在建立倒排索引过程中,哪些内容会被添加到倒排索引,例如,doc 数量、词频、positions、offsets 等信息,优化这些设置可以一定程度降低索引过程中的运算任务,节省 CPU 占用率。不过在实际场景中,通常很难确定业务将来会不会用到这些信息,除非一开始方案就明确是这样设计的。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:3:7","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"搜索速度优化 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"为文件系统 cache 预留足够的内存 cache 保存在系统物理内存中(线上应该禁用 swap),命中 cache 可以降低对磁盘的直接访问频率。搜索很依赖对系统 cache 的命中,如果某个请求需要从磁盘读取数据,则一定会产生相对较高的延迟。应该至少为系统 cache 预留一半的可用物理内存,更大的内存有更高的 cache 命中率。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"使用更快的硬件 写入性能对 CPU 的性能更敏感,而搜索性能在一般情况下更多的是在于 I/O 能力,使用 SSD 会比旋转类存储介质好得多。尽量避免使用 NFS 等远程文件系统,如果 NFS 比本地存储慢 3 倍,则在搜索场景下响应速度可能会慢 10 倍左右。这可能是因为搜索请求有更多的随机访问。 如果搜索类型属于计算比较多,则可以考虑使用更快的 CPU。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"文档模型 为了让搜索时的成本更低,文档应该合理建模。特别是应该避免 join 操作,嵌套(nested)会使查询慢几倍,父子(parent-child)关系可能使查询慢数百倍。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:3","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"字段映射 mappings 有些字段的内容是数值,但并不意味着其总是应该被映射为数值类型,例如,一些标识符,将它们映射为 keyword 可能会比 integer 或 long 更好。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:4","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"为只读索引执行 force-merge 为不再更新的只读索引执行 force merge,将 Lucene 索引合并为单个分段,可以提升查询速度。当一个 Lucene 索引存在多个分段时,每个分段会单独执行搜索再将结果合并,将只读索引强制合并为一个 Lucene 分段不仅可以优化搜索过程,对索引恢复速度也有好处。 基于日期进行轮询的索引的旧数据一般都不会再更新。此前的章节中说过,应该避免持续地写一个固定的索引,直到它巨大无比,而应该按一定的策略,例如,每天生成一个新的索引,然后用别名关联,或者使用索引通配符。这样,可以每天选一个时间点对昨天的索引执行 force-merge、Shrink 等操作。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:5","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"预索引 利用查询中的模式来优化数据的索引方式。例如,如果所有文档都有一个 price 字段,并且大多数查询 range 在固定的范围列表上运行聚合,可以通过将范围预先索引到索引中并使用聚合来加快聚合速度。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:6","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"使用 filter 代替 query query 和 filter 的主要区别在: filter 是结果导向的而 query 是过程导向。query 倾向于“当前文档和查询的语句的相关度”而 filter 倾向于“当前文档和查询的条件是不是相符”。即在查询过程中,query 是要对查询的每个结果计算相关性得分的,而 filter 不会。另外 filter 有相应的缓存机制,可以提高查询效率。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:7","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"避免深度分页 避免单页数据过大,可以参考百度或者淘宝的做法。es 提供两种解决方案 scroll search 和 search after。关于深度分页的详细原理,推荐阅读:详解 Elasticsearch 深度分页问题 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:8","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"使用 Keyword 类型 并非所有数值数据都应映射为数值字段数据类型。Elasticsearch 为 查询优化数字字段,例如 integeror long。如果不需要范围查找,对于 term 查询而言,keyword 比 integer 性能更好。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:9","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"避免使用脚本 Scripting 是 Elasticsearch 支持的一种专门用于复杂场景下支持自定义编程的强大的脚本功能。相对于 DSL 而言,脚本的性能更差,DSL 能解决 80% 以上的查询需求,如非必须,尽量避免使用 Script ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:10","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"预热文件系统 cache 如果 ES 主机重启,则文件系统缓存将为空,此时搜索会比较慢。可以使用 index.store.preload 设置,通过指定文件扩展名,显式地告诉操作系统应该将哪些文件加载到内存中, ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:11","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"调节搜索请求中的 batched_reduce_size 该字段是搜索请求中的一个参数。默认情况下,聚合操作在协调节点需要等所有的分片都取回结果后才执行,使用 batched_reduce_size 参数可以不等待全部分片返回结果,而是在指定数量的分片返回结果之后就可以先处理一部分(reduce)。这样可以避免协调节点在等待全部结果的过程中占用大量内存,避免极端情况下可能导致的 OOM。该字段的默认值为 512,从 ES 5.4 开始支持。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:12","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"限制搜索请求的分片数 一个搜索请求涉及的分片数量越多,协调节点的 CPU 和内存压力就越大。默认情况下,ES 会拒绝超过 1000 个分片的搜索请求。我们应该更好地组织数据,让搜索请求的分片数更少。如果想调节这个值,则可以通过 action.search.shard_count 配置项进行修改。 虽然限制搜索的分片数并不能直接提升单个搜索请求的速度,但协调节点的压力会间接影响搜索速度,例如,占用更多内存会产生更多的 GC 压力,可能导致更多的 stop-the-world 时间等,因此间接影响了协调节点的性能。 ","date":"2020-08-05","objectID":"/elasticsearch-perf/:4:13","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"参考 https://weread.qq.com/web/reader/f9c32dc07184876ef9cdeb6k2023270027b202cb962a56f https://blog.csdn.net/wlei0618/article/details/124104738 https://pdai.tech/md/db/nosql-es/elasticsearch-y-peformance.html ","date":"2020-08-05","objectID":"/elasticsearch-perf/:5:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 性能优化","uri":"/elasticsearch-perf/"},{"categories":["ELK"],"content":"简介 Elasticsearch 是一款基于 Lucene 的高扩展性分布式全文搜索引擎,是整个 ELK STACK 的核心。通过 RESTful API(一种接口设计规范,让接口更易懂)将 Lucene 原本的复杂性做了隐藏。 ES 支持对数据进行分布式、实时分析,并且可以进行搜索补全、关键词高亮、自动纠错、分词搜索等功能。相比使用 SQL 语句在数据库中进行查询,ES 通过倒排索引和分词功能快速对数据页进行定位,然后再用正排索引通过数据页进行查找,搜索速度更快,就算是 TB、PB 级别的数据也可以快速搜索。 正排索引:从文档到关键字的映射(已知文档求关键字),正排表是以文档的 ID 为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。 倒排索引:从关键字到文档的映射(已知关键字求文档),倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的 ID 和字符在该文档中出现的位置情况。 ES 本身也是一个分布式存储系统,如同其他分布式系统一样,我们经常关注的一些特性如下。 数据可靠性:通过分片副本和事务日志机制保障数据安全。 服务可用性:在可用性和一致性的取舍方面,默认情况下 ES 更倾向于可用性,只要主分片可用即可执行写入操作。 一致性:笔者认为是弱一致性。只要主分片写成功,数据就可能被读取。因此读取操作在主分片和副分片上可能会得到不同结果。 原子性:索引的读写、别名更新是原子操作,不会出现中间状态。但 bulk 不是原子操作,不能用来实现事务。 扩展性:主副分片都可以承担读请求,分担系统负载。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:1:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"术语 节点(node):站在用户的角度,ES 并没有主从节点概念,对用户来说每个节点都可以响应请求。但是对于集群来说会通过一个主节点来管理每个节点的状态、决定分片(Shared)的分布方式以及周期性检查其他节点是否可用并进行修复。各节点是通过集群名称来判断是否属于同一节点 索引(index):存放 ES 数据的集合,类似 MySQL 中的库、Index 里的 Types 相当于表,从 ES6 开始一个 Index 下只能包含一个 Type,从 ES 7.X 开始 Type 的概念已经被删除了 Document(文档): 是 index 中的行,许多条 Document 构成了一个 Index。 分片(shared):Elasticsearch 7 以前在创建索引时如果没有指定分片数量(number_of_shards),默认会分为 5 个分片和一个副本(number_of_replicas),这些分片会依据内部算法划分到不同的节点中去。从 ES7 以后默认分片数修改为了 1 个。分片可以带来并行执行的好处,能提高效率。文档数据是根据 HASH 算法存储到分片,由于算法中涉及到了分片的数量,所以分片创建好以后不可修改,否则 HASH 值就变了。每一个分片都是一个独立完整的索引,分布在不同的节点上。如果发现有分片没有被分配到节点上,可以看下是否磁盘没有空间。如果节点数\u003e分片数并不能起到优化作用,所以在初期就要规划好分片数 副本(Replication):副本的存在是为了提高数据的安全性,副本数量通常设置为集群节点数-1,有了副本的存在就能保证不会因为某个节点的故障而丢失数据,当有节点发生故障时,会从副本中选取一个分片提升为主分片。在使用 head 或 cerebro 插件查看集群状态时,如果是虚线边框就代表是副本分片,实线边框是主分片 字段(Field):相当于是数据表的字段,对文档数据根据不同属性进行的分类标识 映射 mapping:mapping 是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理 es 里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。相当于 mysql 中的创建表的过程,设置主键外键、字段类型等等。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:2:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"模块 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"Cluster Cluster 模块是主节点执行集群管理的封装实现,管理集群状态,维护集群层面的配置信息。主要功能如下: 管理集群状态,将新生成的集群状态发布到集群所有节点。 调用 allocation 模块执行分片分配,决策哪些分片应该分配到哪个节点 在集群各节点中直接迁移分片,保持数据平衡。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"Allocation 封装了分片分配相关的功能和策略,包括主分片的分配和副分片的分配,本模块由主节点调用。创建新索引、集群完全重启都需要分片分配的过程。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"Discovery 发现模块负责发现集群中的节点,以及选举主节点。当节点加入或退出集群时,主节点会采取相应的行动。从某种角度来说,发现模块起到类似 ZooKeeper 的作用,选主并管理集群拓扑。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:3","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"Gateway 负责对收到 Master 广播下来的集群状态(cluster state)数据的持久化存储,并在集群完全重启时恢复它们。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:4","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"Indices 索引模块管理全局级的索引设置,不包括索引级的(索引设置分为全局级和每个索引级)。它还封装了索引数据恢复功能。集群启动阶段需要的主分片恢复和副分片恢复就是在这个模块实现的。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:5","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"HTTP HTTP 模块允许通过 JSON over HTTP 的方式访问 ES 的 API,HTTP 模块本质上是完全异步的,这意味着没有阻塞线程等待响应。使用异步通信进行 HTTP 的好处是解决了 C10k 问题(10k 量级的并发连接)。 在部分场景下,可考虑使用 HTTP keepalive 以提升性能。注意:不要在客户端使用 HTTP chunking。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:6","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"Transport 传输模块用于集群内节点之间的内部通信。从一个节点到另一个节点的每个请求都使用传输模块。 如同 HTTP 模块,传输模块本质上也是完全异步的。 传输模块使用 TCP 通信,每个节点都与其他节点维持若干 TCP 长连接。内部节点间的所有通信都是本模块承载的。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:7","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"Engine Engine 模块封装了对 Lucene 的操作及 translog 的调用,它是对一个分片读写操作的最终提供者。 ES 使用 Guice 框架进行模块化管理。Guice 是 Google 开发的轻量级依赖注入框架(IoC)。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:3:8","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"读写原理 建立索引和执行搜索的原理如下图所示: ","date":"2020-08-04","objectID":"/elasticsearch-conception/:4:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"写入流程 客户端向 NODE1 发送写请求。 NODE1 使用文档 ID 来确定文档属于分片 0,通过集群状态中的内容路由表信息获知分片 0 的主分片位于 NODE3,因此请求被转发到 NODE3 上。 NODE3 上的主分片执行写操作。如果写入成功,则它将请求并行转发到 NODE1 和 NODE2 的副分片上,等待返回结果。当所有的副分片都报告成功,NODE3 将向协调节点报告成功,协调节点再向客户端报告成功。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:4:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"读取流程 ES 的读取分为 GET 和 Search 两种操作,这两种读取操作有较大的差异,GET/MGET 必须指定三元组:_index、_type、_id。 也就是说,根据文档 id 从正排索引中获取内容。而 Search 不指定_id,根据关键词从倒排索引中获取内容。 GET 流程 客户端向 NODE1 发送读请求。 NODE1 使用文档 ID 来确定文档属于分片 0,通过集群状态中的内容路由表信息获知分片 0 有三个副本数据,位于所有的三个节点中,此时它可以将请求发送到任意节点,这里它将请求转发到 NODE2。 NODE2 将文档返回给 NODE1,NODE1 将文档返回给客户端。 NODE1 作为协调节点,会将客户端请求轮询发送到集群的所有副本来实现负载均衡。 在读取时,文档可能已经存在于主分片上,但还没有复制到副分片。在这种情况下,读请求命中副分片时可能会报告文档不存在,但是命中主分片可能成功返回文档。一旦写请求成功返回给客户端,则意味着文档在主分片和副分片都是可用的。 Search 流程 在协调节点,搜索任务被执行成一个两阶段过程,即 query then fetch。真正执行搜索任务的节点称为数据节点。 需要两个阶段才能完成搜索的原因是,在查询的时候不知道文档位于哪个分片,因此索引的所有分片(某个副本)都要参与搜索,然后协调节点将结果合并,再根据文档 ID 获取文档内容。例如,有 5 个分片,查询返回前 10 个匹配度最高的文档,那么每个分片都查询出当前分片的 TOP 10,协调节点将 5×10 = 50 的结果再次排序,返回最终 TOP 10 的结果给客户端。 Query 阶段 客户端发送 search 请求到 NODE 3。 Node 3 将查询请求转发到索引的每个主分片或副分片中。 每个分片在本地执行查询,并使用本地的 Term/Document Frequency 信息进行打分,添加结果到大小为 from + size 的本地有序优先队列中。 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,协调节点合并这些值到自己的优先队列中,产生一个全局排序后的列表。 协调节点广播查询请求到所有相关分片时,可以是主分片或副分片,协调节点将在之后的请求中轮询所有的分片副本来分摊负载。 Fetch 阶段 协调节点向相关 NODE 发送 GET 请求。 分片所在节点向协调节点返回数据。 协调节点等待所有文档被取得,然后返回给客户端。 分片所在节点在返回文档数据时,处理有可能出现的_source 字段和高亮参数。 协调节点首先决定哪些文档“确实”需要被取回,例如,如果查询指定了{ "from": 90, "size":10 },则只有从第 91 个开始的 10 个结果需要被取回。为了避免在协调节点中创建的 number_of_shards * (from + size)优先队列过大,应尽量控制分页深度。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:4:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"一致性 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:5:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"索引恢复 索引恢复过程可分为主分片恢复流程和副分片恢复流程。 主分片从 translog 中自我恢复,尚未执行 flush 到磁盘的 Lucene 分段可以从 translog 中重建; 副分片需要从主分片中拉取 Lucene 分段和 translog 进行恢复。但是有机会跳过拉取 Lucene 分段的过程。 索引恢复的触发条件包括从快照备份恢复、节点加入和离开、索引的_open 操作等。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:5:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"主副分片一致 假设在第一阶段执行期间,有客户端索引操作要求将 docA 的内容写为 1,主分片执行了这个操作,而副分片由于尚未就绪所以没有执行。第二阶段期间客户端索引操作要求写 docA 的内容为 2,此时副分片已经就绪,先执行将 docA 写为 2 的新增请求,然后又收到了从主分片所在节点发送过来的 translog 重复写 docA 为 1 的请求该如何处理?具体流程如下图所示。 答案是在写流程中做异常处理,通过版本号来过滤掉过期操作。写操作有三种类型:索引新文档、更新、删除。索引新文档不存在冲突问题,更新和删除操作采用相同的处理机制。每个操作都有一个版本号,这个版本号就是预期 doc 版本,它必须大于当前 Lucene 中的 doc 版本号,否则就放弃本次操作。对于更新操作来说,预期版本号是 Lucene doc 版本号+1。主分片节点写成功后新数据的版本号会放到写副本的请求中,这个请求中的版本号就是预期版本号。 这样,时序上存在错误的操作被忽略,对于特定 doc,只有最新一次操作生效,保证了主副分片一致。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:5:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"集群 分布式系统的集群方式大致可以分为主从(Master-Slave)模式和无主模式。ES、HDFS、HBase 使用主从模式,Cassandra 使用无主模式。主从模式可以简化系统设计,Master 作为权威节点,部分操作仅由 Master 执行,并负责维护集群元信息。缺点是 Master 节点存在单点故障,需要解决灾备问题,并且集群规模会受限于 Master 节点的管理能力。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:6:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"集群角色 生产环境架构中每个节点配置单一职责,具体为 Master、Data、Ingest、Coordinate、Tribe 的一个,有利于每个角色可以使用不同的机器配置。 Master 主节点 负责集群层面的相关操作,管理集群变更。通过配置 node.master: true(默认)使节点具有被选举为 Master 的资源。主节点是全局唯一的。主节点也可以是数据节点,不太推荐。 生产环境通常配置 3 台,低配置(低 CPU 核数、小内存、低磁盘) Data 数据节点 负责保存数据、执行数据相关操作:CRUD、搜索、聚合等。通过配置 node.data: true。(一般情况下,数据读写流程只和数据节点交互,不会和主节点打交道,异常情况除外) 高配置(高 CPU 核数、大内存、SSD 盘) Ingest 预处理节点 负责写入和查询的数据进行预处理,在写入数据之前,通过事先定义好的一系列的 processors(处理器)和 pipeline(管道),对数据进行某种转换、富化。processors 和 pipeline 拦截 bulk 和 index 请求,在应用相关操作后将文档传回给 index 或 bulk API。 中配置(高 CPU 核数、中内存、低磁盘) Coordinate 协调节点 通常在 es 大集群中配置,降低 Master 和 Data Nodes 的负载,负责接受请求、分发请求、汇总结果。应对客户的未知查询请求,深度聚合可能导致 OOM 中高配置(中高 CPU 核数、中高内存、低磁盘) Tribe 部落节点 它不做主节点,也不做数据节点,仅用于路由请求,本质上是一个智能负载均衡器(从负载均衡器的定义来说,智能和非智能的区别在于是否知道访问的内容存在于哪个节点),从 5.0 版本开始,这个角色被协调节点(Coordinating only node)取代。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:6:1","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"集群状态 集群健康状态分为三种: Green,所有的主分片和副分片都正常运行。 Yellow,所有的主分片都正常运行,但不是所有的副分片都正常运行。这意味着存在单点故障风险。 Red,有主分片没能正常运行。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:6:2","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"集群扩容 当扩容集群、添加节点时,分片会均衡地分配到集群的各个节点,从而对索引和搜索过程进行负载均衡,这些都是系统自动完成的。 分片分配过程中除了让节点间均匀存储,还要保证不把主分片和副分片分配到同一节点,避免单个节点故障引起数据丢失。 当主节点异常时,集群会重新选举主节点。当某个主分片异常时,会将副分片提升为主分片。 ","date":"2020-08-04","objectID":"/elasticsearch-conception/:6:3","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"参考 https://weread.qq.com/web/reader/f9c32dc07184876ef9cdeb6 https://www.linuxe.cn/post-295.html https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html https://www.51cto.com/article/705682.html ","date":"2020-08-04","objectID":"/elasticsearch-conception/:7:0","tags":["ELK","Elasticsearch"],"title":"Elasticsearch 基本概念和原理","uri":"/elasticsearch-conception/"},{"categories":["ELK"],"content":"简介 Kibana 可以为 Elasticsearch 提供一个可视化平台,将收集到的数据进行图形化展示。Kibana 7 开始默认支持中文,配图更为方便,推荐使用 ","date":"2020-08-03","objectID":"/kibana/:1:0","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"安装 二进制安装 cd /usr/local wget https://artifacts.elastic.co/downloads/kibana/kibana-7.17.6-linux-x86_64.tar.gz tar zxvf kibana-7.17.6-linux-x86_64.tar.gz cd kibana-7.17.6 ./bin/kibana docker 方式安装 docker pull docker.elastic.co/kibana/kibana:7.17.6 docker run -p 5601:5601 docker.elastic.co/kibana/kibana:7.17.6 ","date":"2020-08-03","objectID":"/kibana/:2:0","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"配置 Kibana 配置只需要修改主配置文件 config/kibana.yml 里的以下信息即可 server.port: 5601 #kibana端口 server.host: \"127.0.0.1\" #kibana地址 server.maxPayload: 10485760 elasticsearch.hosts: [\"http://172.20.1.221:9200\"] kibana.index: \".kibana\" #kibana的元数据也会存放在ES的索引中,这里配置的就是索引的名字,一般不用修改 i18n.locale: \"zh-CN\" #修改为中文 ","date":"2020-08-03","objectID":"/kibana/:3:0","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"使用 ","date":"2020-08-03","objectID":"/kibana/:4:0","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"登录 首次登录要用上 elasticsearch 用户 kibana_system 和 elastic 的用户名密码 # 交互设置所有密码 ./bin/elasticsearch-setup-passwords interactive # 重置 elastic 密码 ./bin/elasticsearch-reset-password -u elastic # 重置 kibana_system 密码 ./bin/elasticsearch-reset-password -u kibana_system ","date":"2020-08-03","objectID":"/kibana/:4:1","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"Discover 从发现页可以交互地探索 ES 的数据。可以访问与所选索引模式相匹配的每一个索引中的每一个文档。您可以提交搜索查询、筛选搜索结果和查看文档数据。还可以看到匹配搜索查询和获取字段值统计的文档的数量。如果一个时间字段被配置为所选择的索引模式,则文档的分布随着时间的推移显示在页面顶部的直方图中。 ","date":"2020-08-03","objectID":"/kibana/:4:2","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"Visualize 可视化能使你创造你的 Elasticsearch 指标数据的可视化。然后你可以建立仪表板显示相关的可视化。Kibana 的可视化是基于 Elasticsearch 查询。通过一系列的 Elasticsearch 聚合提取和处理您的数据,您可以创建图表显示你需要知道的关于趋势,峰值和骤降。您可以从搜索保存的搜索中创建可视化或从一个新的搜索查询开始。 ","date":"2020-08-03","objectID":"/kibana/:4:3","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"Dashboard 一个仪表板显示 Kibana 保存的一系列可视化。你可以根据需要安排和调整可视化,并保存仪表盘,可以被加载和共享。 ","date":"2020-08-03","objectID":"/kibana/:4:4","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"Monitoring 默认 Kibana 是没有该选项的。其实,Monitoring 是由 X-Pack 集成提供的。 该 X-pack 监控组件使您可以通过 Kibana 轻松地监控 ElasticSearch。您可以实时查看集群的健康和性能,以及分析过去的集群、索引和节点度量。此外,您可以监视 Kibana 本身性能。当你安装 X-pack 在群集上,监控代理运行在每个节点上收集和指数指标从 Elasticsearch。安装在 X-pack 在 Kibana 上,您可以查看通过一套专门的仪表板监控数据。 ","date":"2020-08-03","objectID":"/kibana/:4:5","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"Timelion Timelion 是一个时间序列数据的可视化,可以结合在一个单一的可视化完全独立的数据源。它是由一个简单的表达式语言驱动的,你用来检索时间序列数据,进行计算,找出复杂的问题的答案,并可视化的结果。 这个功能由一系列的功能函数组成,同样的查询的结果,也可以通过 Dashboard 显示查看。 ","date":"2020-08-03","objectID":"/kibana/:4:6","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"Management 管理中的应用是在你执行你的运行时配置 kibana,包括初始设置和指标进行配置模式,高级设置,调整自己的行为和 Kibana,各种“对象”,你可以查看保存在整个 Kibana 的内容如发现页,可视化和仪表板。 ","date":"2020-08-03","objectID":"/kibana/:4:7","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"Dev Tools 使用户方便的通过浏览器直接与 Elasticsearch 进行交互。从 Kibana 5 开始改名并直接内建在 Kibana,就是 Dev Tools 选项。 ","date":"2020-08-03","objectID":"/kibana/:4:8","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"参考 https://www.elastic.co/guide/cn/kibana/current/getting-started.html https://www.linuxe.cn/post-309.html ","date":"2020-08-03","objectID":"/kibana/:5:0","tags":["ELK","Kibana"],"title":"Kibana","uri":"/kibana/"},{"categories":["ELK"],"content":"简介 Logstash 可以收集日志数据并对数据进行处理后传递给 ES,但是现阶段通常使用它做数据分析和过滤工作,采集工作交由 filebeat 完成。 因为使用它采集日志的话需要在所有需要收集日志的服务器上安装 Logstash,由于 Logstash 偏重量级,现在通常不会这样做,而是交给各种 beat 去完成这个工作,然后最后交给 Logstash 处理后再传递给 ES。 如果数据量太大可以在 agent 与 server 端之前部署消息队列,一般使用 Redis 或者 Kafka。 ","date":"2020-08-02","objectID":"/logstash/:1:0","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"安装运行 二进制方式安装 最新包到官网下载 运行以下命令: cd /usr/local wget https://artifacts.elastic.co/downloads/logstash/logstash-7.17.6-linux-x86_64.tar.gz tar zxvf logstash-7.17.6-linux-x86_64.tar.gz chown -R elastic. /usr/local/logstash-7.17.6/ su elastic /usr/local/logstash-7.17.6/bin/logstash -f /usr/local/logstash-7.17.6/config/logstash-sample.conf docker 方式安装 docker run -it docker.elastic.co/logstash/logstash:7.17.6 ","date":"2020-08-02","objectID":"/logstash/:2:0","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"工作原理 logstash 处理流程有三步:inputs -\u003e filters -\u003e outputs。 inputs:负责生产数据 filters:修改数据 outputs:搬运数据到其他地方 ","date":"2020-08-02","objectID":"/logstash/:3:0","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"inputs inputs 负责获取数据到 logstash,以下是一些通用的 inputs: file:从文件系统上的文件读取,很像 UNIX 命令 tail -0F syslog:监听知名端口 514 上的 syslog 消息,并根据 RFC3164 格式进行解析 redis:读取 redis channels 或者 lists beats:由 Beats 发送过来,比如 Filebeat kafka:从 kafka topic 读取 更多 inputs,请访问链接 ","date":"2020-08-02","objectID":"/logstash/:3:1","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"filters filters 是 Logstash 管道中的中间处理设备。如果事件符合某些条件,可以将筛选器与条件组合在一起对其执行操作。常用的 filters 包括: grok: 转换和构造任意文本。Grok 是目前 Logstash 中将非结构化日志数据解析为结构化可查询数据的最佳方法。Logstash 内置了 120 种模式,您很可能会找到满足您需求的模式! 文档见链接 mutate: 对事件字段执行通用转换。您可以重命名、删除、替换和修改事件中的字段。 drop: 完全删除事件,如 debug 事件。 clone: 复制一个事件,可能添加或删除字段。 geoip: 添加 IP 地址的地理位置信息(也可以在 Kibana 中显示惊人的图表!) json: 处理 json 格式的文本,文档见链接 更多 filters,请访问链接 ","date":"2020-08-02","objectID":"/logstash/:3:2","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"outputs outputs 是 Logstash 管道的最后阶段。一个事件可以通过多个输出,但是一旦所有输出处理完成,事件就完成了它的执行。常用的 outputs 包括: elasticsearch: 将事件数据发送到 elasticsearch。如果你计划将你的数据以一种高效、方便、易于查询的格式保存,Elasticsearch 就是你的选择。 file: 将事件数据写入磁盘上的文件。 Graphite: 将事件数据发送到 Graphite,这是一种流行的用于存储和绘制指标的开源工具。http://graphite.readthedocs.io/en/latest/ statsd: 将事件数据发送到 statsd,该服务“侦听统计数据,如计数器和计时器,通过 UDP 发送,并将聚合发送到一个或多个可插入的后端服务”。如果您已经在使用 statsd,这可能对您很有用! 更多 outputs,请访问链接 ","date":"2020-08-02","objectID":"/logstash/:3:3","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"配置文件 在正式的生产环境中要启动 Logstash 需要配置一系列复杂的规则以满足需求,所以就需要通过配置文件来自定义这些规则。 Logstash 和 ElasticSearch 一样有 2 个配置文件需要调整,其中 jvm.options 和 Elasticsearch 中的一样的用于优化堆栈内存,另一个 config/logstash.yml,通过该配置文件来进行采集和过滤数据等操作。 自定义配置文件(logstash-sample.conf)时主要配置的内容包括 input(用于指定输入源,包含本地日志、redis、stdin 标准输入等多种方式)、filter(进行规则过滤和处理)、output(指定输出目标)三个区。在 Logstash 中输入的数据可以通过很多种方式获取,比如本地日志文件、filebeat、数据库等。而输出也可以指定到需要的容器中,如 Elasticsearch 配置示例如下: input { beats { port =\u003e 5044 } } output { elasticsearch { hosts =\u003e [\"http://localhost:9200\"] index =\u003e \"%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}\" #user =\u003e \"elastic\" #password =\u003e \"changeme\" } } ","date":"2020-08-02","objectID":"/logstash/:4:0","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"参考 https://www.elastic.co/guide/en/logstash/current/pipeline.html https://www.linuxe.cn/post-309.html ","date":"2020-08-02","objectID":"/logstash/:5:0","tags":["ELK","Logstash"],"title":"Logstash","uri":"/logstash/"},{"categories":["ELK"],"content":"简介 首先 filebeat 是 Beats 中的一员。 Beats 在是一个轻量级日志采集器,其实 Beats 家族有 6 个成员,早期的 ELK 架构中使用 Logstash 收集、解析日志,但是 Logstash 对内存、cpu、io 等资源消耗比较高。相比 Logstash,Beats 所占系统的 CPU 和内存几乎可以忽略不计。 目前 Beats 包含六种工具: Packetbeat:网络数据(收集网络流量数据) Metricbeat:指标(收集系统、进程和文件系统级别的 CPU 和内存使用情况等数据) Filebeat:日志文件(收集文件数据) Winlogbeat:windows 事件日志(收集 Windows 事件日志数据) Auditbeat:审计数据(收集审计日志) Heartbeat:运行时间监控(收集系统运行时的数据) ","date":"2020-08-01","objectID":"/filebeat/:1:0","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"工作原理 Filebeat 是用于转发和集中日志数据的轻量级传送工具。Filebeat 监视您指定的日志文件或位置,收集日志事件,并将它们转发到 Elasticsearch 或 Logstash 进行索引。 Filebeat 的工作方式如下:启动 Filebeat 时,它将启动一个或多个输入,这些输入将在为日志数据指定的位置中查找。对于 Filebeat 所找到的每个日志,Filebeat 都会启动收集器。每个收集器都读取单个日志以获取新内容,并将新日志数据发送到 libbeat,libbeat 将聚集事件,并将聚集的数据发送到为 Filebeat 配置的输出。 工作的流程图如下: ","date":"2020-08-01","objectID":"/filebeat/:2:0","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"Filebeat 的构成 Filebeat 由两个组件构成,分别是 input(输入)和 harvester(收集器) input(输入):负责管理 harvester 并找到所有读取源。如果 input 类型是 log,则 input 将查找驱动器上与定义的路径匹配的所有文件,并为每个文件启动一个 harvester。 harvester(收集器):负责读取单个文件内容,每个文件启动一个 ","date":"2020-08-01","objectID":"/filebeat/:2:1","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"Filebeat 如何保存文件的状态 Filebeat 保留每个文件的状态,并经常将状态刷新到磁盘中的注册表文件中(默认在/var/lib/filebeat/registry)。 该状态用于记住 harvester 读取的最后一个偏移量,并确保发送所有日志行。如果无法访问输出(如 Elasticsearch 或 Logstash),Filebeat 将跟踪最后发送的行,并在输出再次可用时继续读取文件。 当 Filebeat 运行时,每个输入的状态信息也保存在内存中。当 Filebeat 重新启动时,来自注册表文件的数据用于重建状态,Filebeat 在最后一个已知位置继续每个 harvester。 对于每个输入,Filebeat 都会保留它找到的每个文件的状态。由于文件可以重命名或移动,文件名和路径不足以标识文件。对于每个文件,Filebeat 存储唯一的标识符,以检测文件是否以前被捕获。 ","date":"2020-08-01","objectID":"/filebeat/:2:2","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"Filebeat 何如保证至少一次数据消费 Filebeat 保证事件将至少传递到配置的输出一次,并且不会丢失数据。是因为它将每个事件的传递状态存储在注册表文件中。 在已定义的输出被阻止且未确认所有事件的情况下,Filebeat 将继续尝试发送事件,直到输出确认已接收到事件为止。 如果 Filebeat 在发送事件的过程中关闭,它不会等待输出确认所有事件后再关闭。当 Filebeat 重新启动时,将再次将 Filebeat 关闭前未确认的所有事件发送到输出。 这样可以确保每个事件至少发送一次,但最终可能会有重复的事件发送到输出。通过设置 shutdown_timeout 选项,可以将 Filebeat 配置为在关机前等待特定时间 ","date":"2020-08-01","objectID":"/filebeat/:2:3","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"安装与运行 压缩包方式安装,点击链接查看最新版本,运行以下命令 cd /usr/local curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-xxx-linux-x86_64.tar.gz tar xzvf filebeat-xxx-linux-x86_64.tar.gz docker 方式安装 docker run \\ docker.elastic.co/beats/filebeat:8.5.3 \\ setup -E setup.kibana.host=kibana:5601 \\ -E output.elasticsearch.hosts=[\"elasticsearch:9200\"] 编辑配置文件 filebeat.yml filebeat.inputs: #采集系统日志 - type: log #通常采集日志文件的话type就为log,还有stdin、redis、docker等其它类型 enabled: true #启用开关 paths: - /var/log/message.log #include_lines: ['^ERR','^WARN'] #只采集正则所包含的日志 tags: [\"system\"] #定义标签,采集多个日志的时候可以用标签来创建索引,也可以用logstash的type来区分 启动 Filebeat ./filebeat -e -c filebeat.yml ","date":"2020-08-01","objectID":"/filebeat/:3:0","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"filebeat 模块 Filebeat 内置有大量 module,比如 Nginx module、MySQL module。使用这些 module 可以简化日志收集的过程,包括 Kibana 出图,甚至可以通过官方 module 学习出图。 开启 Filebeat 模块相关配置 filebeat.config.modules: path: ${path.config}/modules.d/*.yml #模块配置文件路径,查看支持的模块时就会从该目录去寻找配置文件 reload.enabled: true ##开启模块功能 reload.period: 10s 查看当前支持的 module,filebeat modules list 启动指定的 module,filebeat modules enable mysql 配置 module,只需要修改日志路径 modules.d/mysql.yml - module: mysql error: enabled: true var.paths: [\"/var/log/mysql/log/slowlog/mysql.log\"] slowlog: enabled: true var.paths: [\"/var/log/mysql/log/error.log\"] 初始化环境,如果安装有多个 module 的话只用初始化一次。filebeat setup -e 重启 filebeat,需要注意的是索引名是自动创建好了的,不要自定义,然后到 Kibana 中看图即可 ","date":"2020-08-01","objectID":"/filebeat/:4:0","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"各种组合架构 ","date":"2020-08-01","objectID":"/filebeat/:5:0","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"Elasticsearch+Logstash+Filebeat 架构 如果 Filebeat 直接把日志传给 ES 的话,由于其不支持正则、无法移除字段等,没办法实现数据分析,所以更优的配置是将 Filebeat 收集到的日志输出给一台 Logstash 服务器,由 Logstash 处理后再给 ES。为了实现这个要求,就需要修改 Filebeat 的 output 段以及 Logstash 的 input 段。 修改 filebeat.yml 的 output.logstash 配置 output.logstash: hosts: [\"127.0.0.1:5044\"] 修改 logstash 相关配置 ","date":"2020-08-01","objectID":"/filebeat/:5:1","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"Elasticsearch+Logstash+Kafka+Filebeat 架构(推荐架构) 当生产环境中有多台 Filebeat 同时向 Logstash 传输数据再交给 ES 入库时,后端服务器的负载会比较大,为了减轻这种压力可以在 Filebeat 和 Logstash 之间加上一层消息队列,比如 Redis、Kafka。由于 Redis 太吃内存,而 Kafka 是基于磁盘存储,所以数据量很大的时候更推荐 Kafka 修改 Filebeat 输出段的配置,将输出传送给 Kafka output.kafka hosts: [\"127.0.0.1:9092\", \"127.0.0.1:9093\"] topic: system 修改 Logstash input 相关配置,从 Kafka 读取数据 ","date":"2020-08-01","objectID":"/filebeat/:5:2","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["ELK"],"content":"参考 https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html https://www.cnblogs.com/zsql/p/13137833.html https://www.linuxe.cn/post-422.html ","date":"2020-08-01","objectID":"/filebeat/:6:0","tags":["ELK","Filebeat"],"title":"Filebeat","uri":"/filebeat/"},{"categories":["Go"],"content":"目录结构 ├── api ├── assets ├── build │ ├── ci │ └── package ├── cmd │ └── _your_app_ ├── configs ├── deployments ├── docs ├── examples ├── githooks ├── init ├── internal │ ├── app │ │ └── _your_app_ │ └── pkg │ └── _your_private_lib_ ├── pkg │ └── _your_public_lib_ ├── scripts ├── test ├── third_party ├── tools ├── vendor ├── web │ ├── app │ ├── static │ └── template ├── website ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md └── go.mod ","date":"2020-06-16","objectID":"/go-project-layout/:1:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"开发 ","date":"2020-06-16","objectID":"/go-project-layout/:2:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/cmd 项目主要的应用程序。 对于每个应用程序来说这个目录的名字应该和项目可执行文件的名字匹配(例如,/cmd/myapp)。 有可能有多个可执行文件,如果是微服务,那么通常是只有一个可执行文件的。 通常来说,项目都应该拥有一个小的main函数,并在main函数中导入或者调用/internal和/pkg目录中的代码。 更多详情,请看/cmd目录中的例子。 ","date":"2020-06-16","objectID":"/go-project-layout/:2:1","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/internal 私有的应用程序代码库。这些是不希望被其他人导入的代码。请注意:这种模式是 Go 编译器强制执行的。再次注意,在项目的目录树中的任意位置都可以有internal目录,而不仅仅是在顶级目录中。 可以在内部代码包中添加一些额外的结构,来分隔共享和非共享的内部代码。这不是必选项(尤其是在小项目中),但是有一个直观的包用途是很棒的。应用程序实际的代码可以放在/internal/app目录(如,internal/app/myapp),而应用程序的共享代码放在/internal/pkg目录(如,internal/pkg/myprivlib)中。 ","date":"2020-06-16","objectID":"/go-project-layout/:2:2","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/pkg 外部应用程序可以使用的库代码(如,/pkg/mypubliclib)。其他项目将会导入这些库来保证项目可以正常运行,所以在将代码放在这里前,一定要三思而行。请注意,internal目录是一个更好的选择来确保项目私有代码不会被其他人导入,因为这是 Go 强制执行的。使用/pkg目录来明确表示代码可以被其他人安全的导入仍然是一个好方式。 如果项目确实很小并且嵌套的层次并不会带来多少价值(除非你就是想用它),那么就不要使用它。请仔细思考这种情况,当项目变得很大,并且根目录中包含的内容相当繁杂(尤其是有很多非 Go 的组件)。 微服务建议尽量不使用 pkg,公用的代码建议做成私有库(go module),除非公用的代码只在有限几个项目中可公用。 ","date":"2020-06-16","objectID":"/go-project-layout/:2:3","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/api OpenAPI/Swagger 规范,JSON 模式文件,协议定义文件,比如 proto 文件。 更多样例查看/api目录。 ","date":"2020-06-16","objectID":"/go-project-layout/:2:4","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/configs 配置文件模板或默认配置。 将confd或者consul-template文件放在这里。 ","date":"2020-06-16","objectID":"/go-project-layout/:2:5","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/third_party 外部辅助工具,fork 的代码(可以魔改)和其他第三方工具(例如 Swagger UI)。 ","date":"2020-06-16","objectID":"/go-project-layout/:2:6","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/vendor 应用程序的依赖关系(通过手动或者使用喜欢的依赖管理工具,如新增的内置Go Modules特性)。执行go mod vendor命令将会在项目中创建/vendor目录,注意,如果使用的不是 Go 1.14 版本,在执行go build进行编译时,需要添加-mod=vendor命令行选项,因为它不是默认选项。 构建库文件时,不要提交应用程序依赖项。 请注意,从1.13开始,Go 也启动了模块代理特性(使用https://proxy.golang.org作为默认的模块代理服务器)。点击这里阅读有关它的更多信息,来了解它是否符合所需要求和约束。如果Go Module满足需要,那么就不需要vendor目录。 ","date":"2020-06-16","objectID":"/go-project-layout/:2:7","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"测试 ","date":"2020-06-16","objectID":"/go-project-layout/:3:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/test 外部测试应用程序和测试数据。随时根据需要构建/test目录。对于较大的项目,有一个数据子目录更好一些。例如,如果需要 Go 忽略目录中的内容,则可以使用/test/data或/test/testdata这样的目录名字。请注意,Go 还将忽略以“.”或“_”开头的目录或文件,因此可以更具灵活性的来命名测试数据目录。 更多样例查看/test。 ","date":"2020-06-16","objectID":"/go-project-layout/:3:1","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"打包构建 ","date":"2020-06-16","objectID":"/go-project-layout/:4:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/scripts 用于执行各种构建,安装,分析等操作的脚本。 这些脚本使根级别的 Makefile 变得更小更简单(例如 https://github.com/hashicorp/terraform/blob/master/Makefile )。 更多样例查看/scripts。 ","date":"2020-06-16","objectID":"/go-project-layout/:4:1","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/build 打包和持续集成。 将云(AMI),容器(Docker),操作系统(deb,rpm,pkg)软件包配置和脚本放在/build/package目录中。 将 CI(travis、circle、drone)配置文件和就脚本放在build/ci目录中。请注意,有一些 CI 工具(如,travis CI)对于配置文件的位置有严格的要求。尝试将配置文件放在/build/ci目录,然后链接到 CI 工具想要的位置。 ","date":"2020-06-16","objectID":"/go-project-layout/:4:2","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"部署 ","date":"2020-06-16","objectID":"/go-project-layout/:5:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/deployments IaaS,PaaS,系统和容器编排部署配置和模板(docker-compose,kubernetes/helm,mesos,terraform,bosh)。请注意,在某些存储库中(尤其是使用 kubernetes 部署的应用程序),该目录的名字是/deploy。 ","date":"2020-06-16","objectID":"/go-project-layout/:5:1","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"Web 相关 ","date":"2020-06-16","objectID":"/go-project-layout/:6:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/web Web 应用程序特定的组件:静态 Web 资源,服务器端模板和单页应用(Single-Page App,SPA)。 ","date":"2020-06-16","objectID":"/go-project-layout/:6:1","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/assets 项目中使用的其他资源(图像,Logo 等)。 ","date":"2020-06-16","objectID":"/go-project-layout/:6:2","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/website 如果不使用 Github pages,则在这里放置项目的网站数据。 更多样例查看/website。 ","date":"2020-06-16","objectID":"/go-project-layout/:6:3","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"文档和示例 ","date":"2020-06-16","objectID":"/go-project-layout/:7:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/docs 设计和用户文档(除了 godoc 生成的文档)。 更多样例查看/docs。 ","date":"2020-06-16","objectID":"/go-project-layout/:7:1","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/ examples 应用程序或公共库的示例。 更多样例查看/examples。 ","date":"2020-06-16","objectID":"/go-project-layout/:7:2","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"其他 ","date":"2020-06-16","objectID":"/go-project-layout/:8:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/init 系统初始化(systemd、upstart、sysv)和进程管理(runit、supervisord)配置。 ","date":"2020-06-16","objectID":"/go-project-layout/:8:1","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/tools 此项目的支持工具。请注意,这些工具可以从/pkg和/internal目录导入代码。 更多样例查看/tools。 ","date":"2020-06-16","objectID":"/go-project-layout/:8:2","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"/githooks Git 的钩子。 ","date":"2020-06-16","objectID":"/go-project-layout/:8:3","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"参考 https://github.com/golang-standards/project-layout ","date":"2020-06-16","objectID":"/go-project-layout/:9:0","tags":["Go","目录规范"],"title":"Go项目目录规范","uri":"/go-project-layout/"},{"categories":["Go"],"content":"Makefile 简介 Makefile 是一个工程文件的编译规则,描述了整个工程的编译和链接等规则,这些规则里包含了这些内容: 工程中的哪些源文件需要编译,以及如何编译; 需要创建哪些库文件,以及如何创建; 如何最终生成我们想要的可执行文件。 默认情况下,make 命令会在当前目录下按如下顺序查找 Makefile 文件:“GNUmakefile”、“makefile”、“Makefile”的文件,一旦找到,就开始读取这个文件并执行。 建议使用“Makefile”文件名,因为这个文件名第一个字符大写,这样有一种显目的感觉。还有一些 make 只对全小写的“makefile”文件名敏感。大多数的 make 都支持“makefile”和“Makefile”这两种默认文件名。make 也支持-f 和–file 参数来指定其它文件名,比如:make -f golang.mk 或者 make –file golang.mk。 ","date":"2020-06-10","objectID":"/go-makefile/:1:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"如何学习 Makefile 上面,我们简单介绍了 Makefile。那么如何学习 Makefile 呢?要回答这个问题,我们先来看一下 Makefile 的组成部分,Makefile 脚本文件内容由以下三部分组成: 一系列规则来指定源文件编译的先后顺序。规则是 makefile 中的重要概念,它一般由目标、依赖和命令组成。 特定的语法规则,支持变量、函数和函数调用等。 操作系统中的各种命令。 学习 Makefile,其实也就是对这 3 个部分的学习,分别对应于: Makefile 规则 Makefile 语法 Shell 脚本、Linux 命令等 ","date":"2020-06-10","objectID":"/go-makefile/:2:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"Makefile 的规则 target ... : prerequisites ... command ... ... target:可以是一个 object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。 prerequisites:生成该 target 所依赖的文件和/或 target command:该 target 要执行的命令(任意的 shell 命令) 这是一个文件的依赖关系,也就是说,target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。说白一点就是说: prerequisites 中如果有一个以上的文件比 target 文件要新的话,command 所定义的命令就会被执行。 ","date":"2020-06-10","objectID":"/go-makefile/:3:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"make 的工作方式 读入所有的 Makefile。 读入被 include 的其它 Makefile。 初始化文件中的变量。 推导隐晦规则,并分析所有规则。 为所有的目标文件创建依赖关系链。 根据依赖关系,决定哪些目标要重新生成。 执行生成命令。 ","date":"2020-06-10","objectID":"/go-makefile/:4:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"伪目标 GO 项目下 Makefile 的管理能力都是通过伪目标来实现的,要执行的功能在 Makefile 中以伪目标的形式存在。 在上面的 Makefile 示例中,我们定义了一个 clean 目标,这个其实是一个伪目标,也就是说我们不会为该目标生成任何文件。因为伪目标不是文件,make 无法生成它的依赖关系和决定是否要执行它,通常我们需要显式地指明这个目标为伪目标。为了避免和文件重名,在 Makefile 中可以使用.PHONY 来标识一个目标为伪目标: .PHONY: clean clean: rm hello.o 伪目标可以有依赖文件,也可以作为“默认目标”,例如: .PHONY: all all: lint test build 因为伪目标总是会被执行,所以其依赖总是会被决议,通过这种方式,可以达到同时执行所有依赖项的目的。 ","date":"2020-06-10","objectID":"/go-makefile/:5:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"Makefile 语法 ","date":"2020-06-10","objectID":"/go-makefile/:6:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"书写命令 每条规则中的命令和操作系统 Shell 的命令行是一致的。make 会一按顺序一条一条的执行命令,每条命令的开头必须以 Tab 键开头,除非,命令是紧跟在依赖规则后面的分号后的。 # 是注释符 ","date":"2020-06-10","objectID":"/go-makefile/:6:1","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"显示命令 # 当我们用 @ 字符在命令行前,那么,这个命令将不被make显示出来 @echo 正在编译XXX模块...... # 如果没有“@”,将输出命令并且换行显示echo后面的内容 echo 正在编译XXX模块...... 如果 make 执行时,带入 make 参数 -n 或 --just-print ,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。 而 make 参数 -s 或 --silent 或 --quiet 则是全面禁止命令的显示。 ","date":"2020-06-10","objectID":"/go-makefile/:6:2","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"命令执行 如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。 # 当我们执行 make exec 时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录 exec: cd /home/hchen pwd # cd起作用,pwd会打印出“/home/hchen”。 exec: cd /home/hchen; pwd ","date":"2020-06-10","objectID":"/go-makefile/:6:3","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"命令出错 如果一个规则中的某个命令出错了(命令退出码非零),那么 make 就会终止执行当前规则,这将有可能终止所有规则的执行。 给 make 加上 -i 或是 --ignore-errors 参数,那么,Makefile 中所有命令都会忽略错误。 还有一个要提一下的 make 的参数的是 -k 或是 --keep-going ,这个参数的意思是,如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。 可以通过在命令行前加-符号,来让 make 忽略命令的出错,比如 clean: -rm hello.o ","date":"2020-06-10","objectID":"/go-makefile/:6:4","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"引入其它的 Makefile include \u003cfilename\u003e ","date":"2020-06-10","objectID":"/go-makefile/:6:5","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"变量赋值 通过变量声明来声明一个变量,变量在声明时需要赋予一个初值,比如:GO=go,引用变量时可以通过$()或者${}方式引用,建议用$()方式引用变量:$(GO),也建议整个 makefile 的变量引用方式要保持一致。变量会像 bash 变量一样,在使用它的地方展开。 Makefile 中一共有 4 种变量赋值方法。 = 最基本的赋值方法。 # B最后的值为:c b,而不是a b。也就是说,在用变量给变量赋值时,右边变量的取值取的是最终的变量值。 A = a B = $(A) b A = c := 直接赋值,赋予当前位置的值。 # B最后的值为:a b。通过:=可以避免=赋值带来的一些潜在的不一致。 A = a B := $(A) b A = c ?= 表示如果该变量没有被赋值,则赋予等号后的值。 += 表示将等号后面的值追加到前面的变量上。 ","date":"2020-06-10","objectID":"/go-makefile/:6:6","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"多行变量 define 变量名 变量内容 ... endef ","date":"2020-06-10","objectID":"/go-makefile/:6:7","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"环境变量 Makefile 也像 Linux 一样支持环境变量,在 Makefile 中有 2 种环境变量:Makefile 预定义的环境变量和自定义的环境变量。 其中自定义的环境变量可以覆盖 Makefile 预定义的环境变量。默认情况下 Makefile 中定义的环境变量只在当前 Makefile 有效,如果想向下层传递(Makefile 中调用另一个 Makefile),需要使用 export 关键字来声明,如下声明了一个环境变量,并可以在下层 Makefile 中使用: export USAGE_OPTIONS ","date":"2020-06-10","objectID":"/go-makefile/:6:8","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"特殊变量 特殊变量是 make 提前定义好的,可以在 makefile 中直接引用,特殊变量列表如下: 变量 含义 MAKE 当前 make 解释器的文件名 MAKECMDGOALS 命令行中指定的目标名(make 的命令行参数) CURDIR 当前 make 解释器的工作目录 MAKE_VERSION 当前 make 解释器的版本 MAKEFILE_LIST make 所需要处理的 makefile 文件列表,当前 makefile 的文件名总是位于列表的最后,文件名之间以空格进行分隔 .DEFAULT_GOAL 指定如果在命令行中未指定目标,应该构建哪个目标,即使这个目标不是在第一行 .VARIABLES 所有已经定义的变量名列表(预定义变量和自定义变量) .FEATURES 列出本版本支持的功能,以空格隔开 .INCLUDE_DIRS make 查询 makefile 的路径,以空格隔开 ","date":"2020-06-10","objectID":"/go-makefile/:6:9","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"条件语句 语法: \u003cconditional-directive\u003e \u003ctext-if-true\u003e else \u003ctext-if-false\u003e endif 其中 \u003cconditional-directive\u003e 表示条件关键字,如 ifeq 。这个关键字有四个,如下 关键字 释义 ifeq 相等 ifneq 不相等 ifdef 不为空 ifndef 为空 示例 ifeq ($(strip $(foo)),) \u003ctext-if-empty\u003e endif ifeq (\u003carg1\u003e, \u003carg2\u003e) \u003ctext-if-true\u003e endif ifdef \u003cvariable-name\u003e \u003ctext-if-true\u003e endif ","date":"2020-06-10","objectID":"/go-makefile/:6:10","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"函数 函数定义 define 函数名 函数体 endef 例如,如下是一个自定义函数: define Foo @echo \"my name is $(0)\" @echo \"param is $(1)\" endef define 本质上是定义一个多行变量,可以在 call 的作用下当作函数来使用,在其它位置使用只能作为多行变量的使用,例如: var := $(call Foo) new := $(Foo) 预定义函数 make 编译器也定义了很多函数,这些函数叫做预定义函数,调用语法和变量类似,语法为: $(\u003cfunction\u003e \u003carguments\u003e) 或者 ${\u003cfunction\u003e \u003carguments\u003e} \u003cfunction\u003e是函数名,\u003carguments\u003e是函数参数,参数间以逗号(,)分割。函数的参数也可以为变量。 常用的函数,罗列如下: 函数名 功能描述 $(origin ) 告诉变量的“出生情况”,有如下返回值: undefined: 从来没有定义过default: 是一个默认的定义environment: 是一个环境变量file: 这个变量被定义在 Makefile 中command line: 这个变量是被命令行定义的override: 是被 override 指示符重新定义的automatic: 是一个命令运行中的自动化变量 $(addsuffix ,\u003cnames…\u003e) 把后缀加到中的每个单词后面,并返回加过后缀的文件名序列。 $(addprefix ,\u003cnames…\u003e) 把前缀加到中的每个单词后面,并返回加过前缀的文件名序列。 $(wildcard ) 扩展通配符,例如:$(wildcard ${ROOT_DIR}/build/docker/*) $(word ,) 取字符串中第个单词(从一开始),并返回字符串中第个单词。如 比中的单词数要大,那么返回空字符串 $(subst ,,) 把字串 中的 字符串替换成 ,并返回被替换后的字符串 $(eval ) 将的内容将作为 makefile 的一部分而被 make 解析和执行。 $(firstword ) 取字符串 中的第一个单词,并返回字符串 的第一个单词 $(lastword ) 取字符串 中的最后一个单词,并返回字符串 的最后一个单词 $(abspath ) 将中的各路径转换成绝对路径,并将转换后的结果返回 $(shell cat foo) 执行操作系统命令,并返回操作结果 $(info \u003ctext …\u003e) 输出一段信息 $(warning \u003ctext …\u003e) 出一段警告信息,而 make 继续执行 $(error \u003ctext …\u003e) 产生一个致命的错误,\u003ctext …\u003e 是错误信息 $(filter \u003cpattern…\u003e,) 以模式过滤字符串中的单词,保留符合模式的单词。可以有多个模式。返回符合模式的字串 $(filter-out \u003cpattern…\u003e,) 以模式过滤字符串中的单词,去除符合模式的单词。可以有多个模式,并返回不符合模式的字串 $(dir \u003cnames…\u003e) 从文件名序列中取出非目录部分。非目录部分是指最後一个反斜杠(/)之后的部分。返回文件名序列的非目录部分。 $(notdir \u003cnames…\u003e) 从文件名序列中取出非目录部分。非目录部分是指最後一个反斜杠(/)之后的部分。返回文件名序列的非目录部分。 $(strip ) 去掉字串中开头和结尾的空字符,并返回去掉空格后的字符串 $(suffix \u003cnames…\u003e) 从文件名序列中取出各个文件名的后缀。返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串。 $(foreach ,,) 把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。每一次 会返回一个字符串,循环过程中的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。 ","date":"2020-06-10","objectID":"/go-makefile/:6:11","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"Makefile 常见管理内容 静态代码检查(lint):推荐用 golangci-lint。 单元测试(test):运行 go test ./…。 编译(build):编译源码,支持不同的平台,不同的 CPU 架构。 镜像打包和发布(image/image.push):现在的系统比较推荐用 Docker/Kubernetes 进行部署,所以一般也要有镜像构建功能。 清理(clean):清理临时文件或者编译后的产物。 代码生成(gen):比如要编译生成 protobuf pb.go 文件。 部署(deploy,可选):一键部署功能,方便测试。 发布(release):发布功能,比如:发布到 Docker Hub、github 等。 帮助(help):告诉 Makefile 有哪些功能,如何执行这些功能。 版权声明(add-copyright):如果是开源项目,可能需要在每个文件中添加版权头,这可以通过 Makefile 来添加。 API 文档(swagger):如果使用 swagger 来生成 API 文档,这可以通过 Makefile 来生成。 ","date":"2020-06-10","objectID":"/go-makefile/:7:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"参考 https://github.com/marmotedu/geekbang-go/tree/master/makefile https://seisman.github.io/how-to-write-makefile/Makefile.pdf ","date":"2020-06-10","objectID":"/go-makefile/:8:0","tags":["Makefile"],"title":"Go Makefile使用","uri":"/go-makefile/"},{"categories":["Go"],"content":"windows 下载多版本管理工具 g,地址:https://github.com/voidint/g/releases,选择g*.windows-amd64.zip 解压 g 压缩包到自己喜好的目录,并将该目录添加到 PATH 环境变量 设置环境变量:G_MIRROR 值为https://gomirrors.org/ 设置/修改环境变量 GOROOT 值为 C:\\Users\\个人账户.g\\go,C:\\Users\\个人账户.g\\目录下 downloads 目录为已下载包、version 目录为解压的版本 将之前环境变量 PATH 里面 go 的 bin 目录去掉 重要:右键以管理员身份打开 dos 窗口 g ls 查询已安装的 go 版本 g ls-remote 查询可供安装的所有 go 版本 g ls-remote stable 查询当前可供安装的 stable 状态的 go 版本 g install 1.14.6 安装目标 go 版本 1.14.6 g use 1.14.6 切换至 1.14.6 版本 g uninstall 1.14.6 卸载一个已安装的 go 版本 ","date":"2020-06-10","objectID":"/go-multi-version/:1:0","tags":["Go"],"title":"Go多版本管理器","uri":"/go-multi-version/"},{"categories":["Go"],"content":"linux curl -sSL https://raw.githubusercontent.com/voidint/g/master/install.sh | bash 没有 curl 命令可以把 install.sh 下载下来直接运行 source ~/.bashrc 修改~/.bashrc 文件最下面 G_MIRROR 值为https://gomirrors.org/ 将之前环境变量 PATH 里面 go 的 bin 目录去掉 g ls 查询已安装的 go 版本 g ls-remote 查询可供安装的所有 go 版本 g ls-remote stable 查询当前可供安装的 stable 状态的 go 版本 g install 1.14.6 安装目标 go 版本 1.14.6 g use 1.14.6 切换至 1.14.6 版本 g uninstall 1.14.6 卸载一个已安装的 go 版本 ","date":"2020-06-10","objectID":"/go-multi-version/:2:0","tags":["Go"],"title":"Go多版本管理器","uri":"/go-multi-version/"},{"categories":["Go"],"content":"概述 输入 go 命令我们可以看到全部命令,使用 go help 可以查看某个命令的详细解释和用法 root@luzhifang:~# go Go is a tool for managing Go source code. Usage: go \u003ccommand\u003e [arguments] The commands are: bug start a bug report build compile packages and dependencies clean remove object files and cached files doc show documentation for package or symbol env print Go environment information fix update packages to use new APIs fmt gofmt (reformat) package sources generate generate Go files by processing source get add dependencies to current module and install them install compile and install packages and dependencies list list packages or modules mod module maintenance run compile and run Go program test test packages tool run specified go tool version print Go version vet report likely mistakes in packages Use \"go help \u003ccommand\u003e\" for more information about a command. ","date":"2020-06-08","objectID":"/go-command/:1:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go bug 输入此命名后会直接打开默认浏览器,显示 go 的 github 页面进行 bug 报告,并会自动添加系统的信息。 ","date":"2020-06-08","objectID":"/go-command/:2:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go build Build 会编译导入路径命名的包及其依赖项,但不会安装结果。 用法:go build [-o output] [-i] [build flags] [packages] 如果构建的参数是.go 文件的列表,则 build 会将它们视为指定单个包的源文件列表。 编译单个主程序包时,build 会将生成的可执行文件写入以第一个源文件命名的输出文件(‘go build ed.go rx.go’write’ed’或’ed.exe’)或源代码目录( ‘go build unix / sam’写’sam’或’sam.exe’)。编写 Windows 可执行文件时会添加“.exe”后缀。 在编译多个包或单个非主包时,build 会编译包但丢弃生成的对象,仅用于检查是否可以构建包。 编译包时,构建会忽略以“_test.go”结尾的文件。 -o 标志仅在编译单个包时允许,强制构建将结果可执行文件或对象写入命名输出文件,而不是最后两段中描述的默认行为。 -i 标志安装作为目标依赖项的软件包。 查看 Plan9 汇编代码:go build -gcflags -S main.go 逃逸分析:go build -gcflags=\"-m\" main.go 构建标志由构建,清理,获取,安装,列表,运行和测试命令共享: -a 强制重建已经是最新的软件包。 -n 打印命令但不运行它们。 go build -n 查看打包执行过程 compile.exe -\u003e buildid.exe -\u003e link.exe -p n 可以并行运行 的程序数,例如构建命令或测试二进制文件。 默认值是可用的CPU数。 -race 启用数据竞争检测。仅支持linux / amd64,freebsd / amd64,darwin / amd64和windows / amd64。 -msan 支持与内存清理程序的互操作。 仅支持在linux / amd64,linux / arm64上, 并且仅支持Clang / LLVM作为主机C编译器。 -v 在编译时打印包的名称。 -work 打印临时工作目录的名称,退出时不要删除它。 -x 打印命令。 -asmflags '[pattern =] arg list' 传递每个go工具asm调用的参数。 -buildmode mode 构建模式使用。有关更多信息,请参阅“go help buildmode”。 -compiler 要使用的编译器名称,如runtime.Compiler(gccgo或gc)。 -gccgoflags '[pattern =] arg list' 传递每个gccgo编译器/链接器调用的参数。 -gcflags '[pattern =] arg list' 传递每个go工具编译调用的参数。 -installsuffix suffix 在软件包安装目录的名称中使用后缀, 为了使输出与默认构建分开。 如果使用-race标志,则安装后缀会自动设置为race, 或者,如果明确设置,则会附加_race。同样对于-msan 标志。使用需要非默认编译标志的-buildmode选项 具有类似的效果。 -ldflags '[pattern =] arg list' 传递每个go工具链接调用的参数。 -linkshared 链接以前使用 -buildmode = shared 创建的共享库。 -mod mode 模块下载模式使用:readonly或vendor。 有关更多信息,请参阅“go help modules”。 -pkgdir dir 从dir 安装并加载所有包,而不是通常的位置。 例如, 使用非标准配置构建时,请使用-pkgdir将生成的包保留在单独的位置。 -tags '标记列表' 构建期间要考虑满足的以空格分隔的构建标记列表。有关构建标记的更多信息,请参阅 go / build包文档中的构建约束说明。 -toolexec 'cmd args' 用于调用vet和asm等工具链程序的程序。 例如,go命令不是运行asm,而是运行 'cmd args / path / to / asm \u003casm\u003e的参数'。 ","date":"2020-06-08","objectID":"/go-command/:3:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go clean 删除目标文件和缓存的文件 用法:go clean [clean flags] [build flags] [packages] Clean 从包源目录中删除目标文件。go 命令在临时目录中构建大多数对象,因此 go clean 主要关注其他工具留下的目标文件或 go build 的手动调用。 这些文件包括: _obj/ 旧的 object 目录,由 Makefiles 遗留 _test/ 旧的 test 目录,由 Makefiles 遗留 _testmain.go 旧的 gotest 文件,由 Makefiles 遗留 test.out 旧的 test 记录,由 Makefiles 遗留 build.out 旧的 test 记录,由 Makefiles 遗留 *.[568ao] object 文件,由 Makefiles 遗留 DIR(.exe) 由 go build 产生 DIR.test(.exe) 由 go test -c 产生 MAINFILE(.exe) 由 go build MAINFILE.go 产生 *.so 由 SWIG 产生 参数包括: -i 清除关联的安装的包和可运行文件,也就是通过 go install 安装的文件 -n 把需要执行的清除命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的 -r 循环的清除在 import 中引入的包 -x 打印出来执行的详细命令,其实就是-n 打印的执行版本 ","date":"2020-06-08","objectID":"/go-command/:4:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go doc Doc 打印与其参数(包,const,func,类型,var,方法或结构字段)标识的项目相关联的文档注释。 用法:go doc [-u] [-c] [package | [package.] symbol [.methodOrField]] 参数列表: -all 显示包的所有文档。 -c 在匹配符号时尊重大小写。 -cmd 将命令(包main)视为常规包。 否则,在显示程序包的顶级文档时,将隐藏程序包主导出的符号。 -src 显示符号的完整源代码。 这将显示其声明和定义的完整Go源,例如函数定义(包括 正文),类型声明或封闭const块。因此输出可能包括未导出的细节。 -u 显示未导出的符号,方法和字段的文档。 ","date":"2020-06-08","objectID":"/go-command/:5:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go env Env 打印 Go 环境信息。go env -w GOPROXY=https://goproxy.cn,direct -w 参数修改环境信息。 用法:go env [-json] [var …] 默认情况下,env 将信息打印为 shell 脚本(在 Windows 上,即批处理文件)。如果给出一个或多个变量名作为参数,则 env 在其自己的行上打印每个命名变量的值。 -json 标志以 JSON 格式而不是 shell 脚本打印环境。 有关环境变量的更多信息,请参阅“go help environment”。 ","date":"2020-06-08","objectID":"/go-command/:6:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go fix 更新包以使用新 API 用法:go fix [packages] Fix 在导入路径命名的包上运行 Go fix 命令。 有关修复的更多信息,请参阅“go doc cmd/fix”。有关指定包的更多信息,请参阅“go help packages”。 要使用特定选项运行修复,请运行“go tool fix”。 ","date":"2020-06-08","objectID":"/go-command/:7:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go fmt Gofmt(重新格式化)包源 用法:go fmt [-n] [-x] [packages] Fmt 在导入路径命名的包上运行命令’gofmt -l -w’。它打印修改的文件的名称。 有关 gofmt 的更多信息,请参阅“go doc cmd/gofmt”。有关指定包的更多信息,请参阅“go help packages”。 -n 标志打印将要执行的命令。-x 标志在执行时打印命令。 要使用特定选项运行 gofmt,请运行 gofmt 本身。 参数说明: -l 显示那些需要格式化的文件 -w 把改写后的内容直接写入到文件中,而不是作为结果打印到标准输出。 -r 添加形如“a[b:len(a)] -\u003e a[b:]”的重写规则,方便我们做批量替换 -s 简化文件中的代码 -d 显示格式化前后的 diff 而不是写入文件,默认是 false -e 打印所有的语法错误到标准输出。如果不使用此标记,则只会打印不同行的前 10 个错误。 -cpuprofile 支持调试模式,写入相应的 cpufile 到指定的文件 ","date":"2020-06-08","objectID":"/go-command/:8:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go generate 通过处理源生成 Go 文件 用法:go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go … | 包] 生成由现有文件中的指令描述的运行命令。这些命令可以运行任何进程,但目的是创建或更新 Go 源文件。 Go generate 永远不会通过 go build,go get,go test 等自动运行。它必须明确运行。 Go 生成扫描文件中的指令,这些指令是表单的行, // go:生成命令参数... ","date":"2020-06-08","objectID":"/go-command/:9:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go get 下载并安装包和依赖项 旧版本执行命令后一般会下载在 GOPATH 的 src 目录下。go mod 方式下载的包源码在 GOPATH 目录下的 pkg/mod 目录下。 go get github.com/PuerkitoBio/goquery 用法:go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages] -d 标志指示在下载软件包后停止; 也就是说,它指示不安装软件包。 -f 标志仅在设置-u 时有效,强制 get -u 不验证每个包是否已从其导入路径隐含的源控制存储库中检出。如果源是原始的本地分支,这可能很有用。 -fix 标志指示 get 在解析依赖项或构建代码之前在下载的包上运行修复工具。 -insecure 标志允许从存储库中提取并使用不安全的方案(如 HTTP)解析自定义域。谨慎使用。 -t 标志指示 get 还下载构建指定包的测试所需的包。 -u 标志指示 get 使用网络更新命名包及其依赖项。默认情况下,get 使用网络检出丢失的包,但不使用它来查找现有包的更新。 -v 标志启用详细进度和调试输出。 ","date":"2020-06-08","objectID":"/go-command/:10:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go install 编译并安装包和依赖项,执行后可执行文件自动安装到 GOPATH/bin 目录下。 go install main.go 用法:go install [-i] [build flags] [packages] 安装编译并安装导入路径命名的包。 -i 标志也会安装命名包的依赖项。 ","date":"2020-06-08","objectID":"/go-command/:11:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go list 列出包或模块 用法:go list [-f format] [-json] [-m] [list flags] [build flags] [packages] 列表列出了命名包,每行一个。最常用的标志是-f 和-json,它们控制为每个包打印的输出形式。 ","date":"2020-06-08","objectID":"/go-command/:12:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go mod go mod 是 go modules 的简写,用于对 go 包的管理。详细信息可以看go 包管理 从 go1.11 开始实现了 modules 管理,mod 相比以前的方式,优点主要体现在: 项目不需要放在 GOPATH 下的 src 目录了,可以在任意目录。 自动下载第三方包和依赖包。 第三方包会指定版本号。 项目内会生成一个 go.mod 文件,文件内指定包依赖关系。 有一些第三方包不存在了或转移到其他地方,不需要改代码,只需用 replace 命令替换即可。 ","date":"2020-06-08","objectID":"/go-command/:13:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go run go run 用于编译并运行源码文件,由于包含编译步骤,所以 go build 参数都可用于 go run,在 go run 中只接受 go 源码文件而不接受代码包。 go run main.go ","date":"2020-06-08","objectID":"/go-command/:14:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go test 测试包 用法:go test [build / test flags] [packages] [build / test flags&test binary flags] go test 重新编译每个包以及名称与文件模式“* _test.go”匹配的任何文件。 声明具有后缀“_test”的包的测试文件将被编译为单独的包,然后链接并与主测试二进制文件一起运行。 将忽略名为“testdata”的目录,使其可用于保存测试所需的辅助数据。 Go 测试以两种不同的模式运行: 第一种称为本地目录模式,在没有包参数的情况下调用 go test 时发生(例如,‘go test’或’go test -v’)。在此模式下,go test 将编译当前目录中的包源和测试,然后运行生成的测试二进制文件。在此模式下,禁用缓存(下面讨论)。包测试完成后,go test 打印一条摘要行,显示测试状态(‘ok’或’FAIL’),包名称和已用时间。 第二种叫做包列表模式,在使用显式包参数调用 go test 时发生(例如’go test math’,‘go test。/ …’,甚至’go test。’)。在此模式下,go test 编译并测试命令行中列出的每个包。如果包测试通过,则 go test 仅打印最终的’ok’摘要行。如果包测试失败,则 go test 打印完整的测试输出。如果使用-bench 或-v 标志调用,则即使传递包测试,go test 也会打印完整输出,以显示请求的基准测试结果或详细日志记录。 参数列表: -args 将命令行的其余部分(-args之后的所有内容) 传递给测试二进制文件,取消解释并保持不变。 由于此标志占用命令行的其余部分, 因此包列表(如果存在)必须出现在此标志之前。 -c 将测试二进制文件编译为pkg.test但不运行它 (其中pkg是包的导入路径的最后一个元素)。 可以使用-o标志更改文件名。 -exec xprog 使用xprog运行测试二进制文件。行为与 'go run'中的行为相同。有关详细信息,请参阅“go help run”。 -i 安装作为测试依赖项的包。 不要运行测试。 -json 将测试输出转换为适合自动处理的JSON。 有关编码详细信息,请参阅“go doc test2json”。 -o file 将测试二进制文件编译为指定文件。 测试仍然运行(除非指定了-c或-i)。 ","date":"2020-06-08","objectID":"/go-command/:15:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go version go version 可以查看当前 go 的版本 ","date":"2020-06-08","objectID":"/go-command/:16:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go tool 运行指定的 go 工具 用法:go tool [-n] command [args…] Tool 运行由参数标识的 go 工具命令。没有参数,它打印已知工具列表。 -n 标志使工具打印将要执行但不执行它的命令。 ","date":"2020-06-08","objectID":"/go-command/:17:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"go vet 检查包中可能出现的错误 用法:go vet [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages] -n 标志打印将要执行的命令。-x 标志在执行时打印命令。 go vet 支持的构建标志是控制包解析和执行的构建标志,例如-n,-x,-v,-tags 和-toolexec。有关这些标志的更多信息,请参阅“go help build”。 ","date":"2020-06-08","objectID":"/go-command/:18:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["Go"],"content":"参考 https://zhuanlan.zhihu.com/p/161494871 https://www.cnblogs.com/sunsky303/p/10788982.html ","date":"2020-06-08","objectID":"/go-command/:19:0","tags":["Go"],"title":"Go命令详解","uri":"/go-command/"},{"categories":["规范"],"content":"panic 在程序启动的时候,如果有强依赖的服务出现故障时 panic 退出 在程序启动的时候,如果发现有配置明显不符合要求, 可以 panic 退出(防御编程) 其他情况下只要不是不可恢复的程序错误,都不应该直接 panic 应该返回 error // panic 恢复,需要在报错之前声明 defer func(){ if err := recover(); err != nil { log.Printf(\"panic: %+v\", err) } }() ","date":"2020-06-06","objectID":"/go-error/:1:0","tags":["错误码","日志"],"title":"Go错误处理","uri":"/go-error/"},{"categories":["规范"],"content":"error 我们在应用程序中使用 github.com/pkg/errors 处理应用错误,注意在公共库当中,我们一般不使用这个 error 应该是函数的最后一个返回值,当 error 不为 nil 时,函数的其他返回值是不可用的状态,不应该对其他返回值做任何期待 错误处理的时候应该先判断错误(使用卫语句), if err != nil 出现错误及时返回,使代码是一条流畅的直线,避免过多的嵌套 func foo() error { err := A() if err != nil { return err } // ... 其他逻辑 return nil } 在应用程序中出现错误需要自定义错误时,使用 errors.New() 或者 errors.Errorf() 返回错误 如果是调用本应用程序的其他函数出现错误,请直接返回,如果需要携带信息,请使用 errors.WithMessage errors.WithMessage(err, \"其他附加信息\") 如果是调用其他库(标准库、企业公共库、开源第三方库等)出现错误时,请使用 errors.Wrap 添加堆栈信息 不要每个地方都是用 errors.Wrap 只需要在错误第一次出现时进行 errors.Wrap 即可 根据场景进行判断是否需要将其他库的原始错误吞掉 编写基础库一般不用 errors.Wrap 避免堆栈信息重复 errors.Wrap(err, \"其他附加信息\") 禁止每个出错的地方都打日志,只需要在进程的最开始(最外层)的地方使用 %+v 进行统一打印 log.Errorf(\"%+v\", err) 错误判断使用 errors.Is 进行比较 func foo() error { err := A() if errors.Is(err, io.EOF){ return nil } // 其他逻辑 return nil } 错误类型判断,使用 errors.As 进行赋值 func foo() error { err := A() var errA errorA if errors.As(err, \u0026errA){ // ... } // 其他逻辑 return nil } 获取原始错误,使用 errors.Cause(err error) 处理错误的时候,需要释放已分配的资源,使用 defer 进行清理,例如文件句柄 对于业务错误,推荐在一个统一的地方创建一个错误字典,错误字典里面应该包含错误的 code,并且在日志中作为独立字段打印,方便做业务告警的判断,错误必须有清晰的错误文档 ","date":"2020-06-06","objectID":"/go-error/:2:0","tags":["错误码","日志"],"title":"Go错误处理","uri":"/go-error/"},{"categories":["规范"],"content":"错误码设计 纯数字表示,不同部位代表不同的服务,不同的模块。错误代码示例:100101 10: 服务。 01: 某个服务下的某个模块。 01: 模块下的错误码序号,每个模块可以注册 100 个错误。 注意:如果每个模块的错误码超过 100 个,要么说明这个模块太大了,建议拆分;要么说 明错误码设计得不合理,共享性差,需要重新设计。 ","date":"2020-06-06","objectID":"/go-error/:3:0","tags":["错误码","日志"],"title":"Go错误处理","uri":"/go-error/"},{"categories":["规范"],"content":"参考 https://lailin.xyz/post/go-training-03.html https://github.com/pkg/errors ","date":"2020-06-06","objectID":"/go-error/:4:0","tags":["错误码","日志"],"title":"Go错误处理","uri":"/go-error/"},{"categories":["Go"],"content":"包管理演进 ","date":"2020-06-03","objectID":"/go-mod/:1:0","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"GOPATH 模式 版本:最初版本就有了,1.13 之后不推荐使用了 用法:GOPATH 是配置 Go 开发环境时所设置的一个环境变量。历史版本的 go 语言开发时,需要将代码放在 GOPATH 目录的 src 文件夹下。go get 命令获取依赖,也会自动下载到 GOPATH 的 src 下。 缺点:go get 命令使用时,没有版本选择机制,拉下来的依赖代码都会默认当前最新版本 ","date":"2020-06-03","objectID":"/go-mod/:1:1","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"vendor 机制 版本:1.5 之后 用法:每个项目都可以有一个 vendor/ 目录来存放项目所需版本依赖的拷贝(vendor 目录需在 GOPATH 之内)。 缺点:基于该机制的管理工具虽然丰富,但是不同版本工具之间不兼容,无法协作,各种工具还都有学习成本。 ","date":"2020-06-03","objectID":"/go-mod/:1:2","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"Go Mod 版本:Go 1.11 和 1.12 需要设置 GO111MODULE=on 才能强制使用 Go Mod,1.13 之后默认在 GOPATH 之外开始使用 Go Mod 用法:后面细讲 ","date":"2020-06-03","objectID":"/go-mod/:1:3","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"Go Mod ","date":"2020-06-03","objectID":"/go-mod/:2:0","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"Go Mod 环境变量 GO111MODULE=\"auto\" # auto/on/off三种值,可通过go env -w GO111MODULE=on设置 GOPROXY=\"https://goproxy.io,direct\" # 设置 Go 模块代理,go env -w GOPROXY=https://goproxy.cn,direct GONOPROXY=\"\" # 禁用代理的模块,逗号隔开,不建议使用 GOSUMDB=\"sum.golang.org\" # 默认值为:sum.golang.org,设置了代理会走代理的,设置为off则禁用校验模块版本 GONOSUMDB=\"\" # 禁用校验的模块,逗号隔开,不建议使用 GOPRIVATE=\"\" # 私有模块,禁用代理和校验,go env -w GOPRIVATE=\"*.test.com,github.com/test/pkg\" ","date":"2020-06-03","objectID":"/go-mod/:2:1","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"Go Mod 操作命令 download # 下载依赖包到本地缓存GOPATH/src/pkg/mod edit #编辑 go.mod 文件一般直接用 ide 编辑就行 graph # 打印模块依赖图 init # 初始化go.mod文件,例:go mod init test.com/demo1 tidy # 拉取缺少的模块,移除不用的模块 vendor # 从mod cache中拷贝到项目的vendor,使用vendor:go build -mod vendor verify # 验证依赖是否正确 why # 解释为什么需要依赖 ","date":"2020-06-03","objectID":"/go-mod/:2:2","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"go.mod 文件 # 指定当前项目的模块导入路径为 test.com/demo1 module test.com/demo1 # 指定go版本 go 1.14 # 引入包 require ( github.com/jinzhu/gorm v1.0.0 ) # 排除一个特定的模块版本 exclude ( github.com/google/uuid v1.1.0 ) # 替换一个无法下载的包,或者引入一个本地的包 replace ( github.com/google/uuid v1.1.1 =\u003e ../uuid ) ","date":"2020-06-03","objectID":"/go-mod/:2:3","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"依赖的下载和更新 # go get 默认下载最新版本 # go get 命令使用的版本是 git 中的 tag # go get 之后会在go.mod的require中增加一项,并会更新go.sum go get github.com/jinzhu/[email protected] # 使用-u表示更新版本 go get -u github.com/jinzhu/[email protected] ","date":"2020-06-03","objectID":"/go-mod/:2:4","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"go.sum 文件 用途:防止下载依赖包被恶意篡改 生成过程:如果本地计算出的依赖包版本哈希值与 GOSUMDB 服务器给出的哈希值不一致,go 命令将拒绝向下执行,也不会更新 go.sum 文件。 ","date":"2020-06-03","objectID":"/go-mod/:2:5","tags":["包管理"],"title":"Go包管理","uri":"/go-mod/"},{"categories":["Go"],"content":"1. 代码风格 ","date":"2020-06-02","objectID":"/go-code-style/:1:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"1.1 代码格式 代码都必须用 gofmt 进行格式化。 运算符和操作数之间要留空格。 建议一行代码不超过 120 个字符,超过部分,请采用合适的换行方式换行。但也有些例外场景,例如 import 行、工具自动生成的代码、带 tag 的 struct 字段。 文件长度不能超过 800 行。 函数长度不能超过 80 行。 import 规范 代码都必须用goimports进行格式化(建议将代码 Go 代码编辑器设置为:保存时运行 goimports)。 不要使用相对路径引入包,例如 import ../util/net 。 包名称与导入路径的最后一个目录名不匹配时,或者多个相同包名冲突时,则必须使用导入别名。 // bad \"github.com/dgrijalva/jwt-go/v4\" //good jwt \"github.com/dgrijalva/jwt-go/v4\" - 导入的包建议进行分组,匿名包的引用使用一个新的分组,并对匿名包引用进行说明。\rimport ( // go 标准包 \"fmt\" // 第三方包 \"github.com/jinzhu/gorm\" \"github.com/spf13/cobra\" \"github.com/spf13/viper\" // 匿名包单独分组,并对匿名包引用进行说明 // import mysql driver _ \"github.com/jinzhu/gorm/dialects/mysql\" // 内部包 v1 \"github.com/marmotedu/api/apiserver/v1\" metav1 \"github.com/marmotedu/apimachinery/pkg/meta/v1\" \"github.com/marmotedu/iam/pkg/cli/genericclioptions\" ) ","date":"2020-06-02","objectID":"/go-code-style/:1:1","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"1.2 声明、初始化和定义 当函数中需要使用到多个变量时,可以在函数开始处使用var声明。在函数外部声明必须使用 var ,不要采用 := ,容易踩到变量的作用域的问题。 var ( Width int Height int ) 在初始化结构引用时,请使用\u0026T{}代替new(T),以使其与结构体初始化一致。 // bad sptr := new(T) sptr.Name = \"bar\" // good sptr := \u0026T{Name: \"bar\"} struct 声明和初始化格式采用多行,定义如下。 type User struct{ Username string Email string } user := User{ Username: \"colin\", Email: \"[email protected]\", } 相似的声明放在一组,同样适用于常量、变量和类型声明。 // bad import \"a\" import \"b\" // good import ( \"a\" \"b\" ) 尽可能指定容器容量,以便为容器预先分配内存,例如: v := make(map[int]string, 4) v := make([]string, 0, 4) 在顶层,使用标准 var 关键字。请勿指定类型,除非它与表达式的类型不同。 // bad var _s string = F() func F() string { return \"A\" } // good var _s = F() // 由于 F 已经明确了返回一个字符串类型,因此我们没有必要显式指定_s 的类型 // 还是那种类型 func F() string { return \"A\" } 对于未导出的顶层常量和变量,使用_作为前缀。 // bad const ( defaultHost = \"127.0.0.1\" defaultPort = 8080 ) // good const ( _defaultHost = \"127.0.0.1\" _defaultPort = 8080 ) 嵌入式类型(例如 mutex)应位于结构体内的字段列表的顶部,并且必须有一个空行将嵌入式字段与常规字段分隔开。 // bad type Client struct { version int http.Client } // good type Client struct { http.Client version int } ","date":"2020-06-02","objectID":"/go-code-style/:1:2","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"1.3 错误处理 error作为函数的值返回,必须对error进行处理,或将返回值赋值给明确忽略。对于defer xx.Close()可以不用显式处理。 func load() error { // normal code } // bad load() // good _ = load() error作为函数的值返回且有多个返回值的时候,error必须是最后一个参数。 // bad func load() (error, int) { // normal code } // good func load() (int, error) { // normal code } 尽早进行错误处理,并尽早返回,减少嵌套。 // bad if err != nil { // error code } else { // normal code } // good if err != nil { // error handling return err } // normal code 如果需要在 if 之外使用函数调用的结果,则应采用下面的方式。 // bad if v, err := foo(); err != nil { // error handling } // good v, err := foo() if err != nil { // error handling } 错误要单独判断,不与其他逻辑组合判断。 // bad v, err := foo() if err != nil || v == nil { // error handling return err } // good v, err := foo() if err != nil { // error handling return err } if v == nil { // error handling return errors.New(\"invalid value v\") } 如果返回值需要初始化,则采用下面的方式。 v, err := f() if err != nil { // error handling return // or continue. } 错误描述建议 错误描述用小写字母开头,结尾不要加标点符号,例如: 告诉用户他们可以做什么,而不是告诉他们不能做什么。 当声明一个需求时,用must 而不是should。例如,must be greater than 0、must match regex '[a-z]+'。 当声明一个格式不对时,用must not。例如,must not contain。 当声明一个动作时用may not。例如,may not be specified when otherField is empty、only name may be specified。 引用文字字符串值时,请在单引号中指示文字。例如,ust not contain '..'。 当引用另一个字段名称时,请在反引号中指定该名称。例如,must be greater than request。 指定不等时,请使用单词而不是符号。例如,must be less than 256、must be greater than or equal to 0。 指定数字范围时,请尽可能使用包含范围。 建议 Go 1.13 以上,error 生成方式为 fmt.Errorf(\"module xxx: %w\", err)。 ","date":"2020-06-02","objectID":"/go-code-style/:1:3","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"1.4 panic 处理 在业务逻辑处理中禁止使用 panic。 在 main 包中,只有当程序完全不可运行时使用 panic,例如无法打开文件、无法连接数据库导致程序无法正常运行。 在 main 包中,使用 log.Fatal 来记录错误,这样就可以由 log 来结束程序,或者将 panic 抛出的异常记录到日志文件中,方便排查问题。 可导出的接口一定不能有 panic。 包内建议采用 error 而不是 panic 来传递错误。 ","date":"2020-06-02","objectID":"/go-code-style/:1:4","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"1.5 单元测试 单元测试文件名命名规范为 example_test.go。 每个重要的可导出函数都要编写测试用例。 因为单元测试文件内的函数都是不对外的,所以可导出的结构体、函数等可以不带注释。 如果存在 func (b *Bar) Foo ,单测函数可以为 func TestBar_Foo。 ","date":"2020-06-02","objectID":"/go-code-style/:1:5","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"1.6 类型断言失败处理 type assertion 的单个返回值针对不正确的类型将产生 panic。请始终使用 “comma ok”的惯用法。 // bad t := n.(int) // good t, ok := n.(int) if !ok { // error handling } ","date":"2020-06-02","objectID":"/go-code-style/:1:6","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2. 命名规范 命名规范是代码规范中非常重要的一部分,一个统一的、短小的、精确的命名规范可以大大提高代码的可读性,也可以借此规避一些不必要的 Bug。 ","date":"2020-06-02","objectID":"/go-code-style/:2:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.1 包命名 包名必须和目录名一致,尽量采取有意义、简短的包名,不要和标准库冲突。 包名全部小写,没有大写或下划线,使用多级目录来划分层级。 项目名可以通过中划线来连接多个单词。 包名以及包所在的目录名,不要使用复数,例如,是net/url,而不是net/urls。 不要用 common、util、shared 或者 lib 这类宽泛的、无意义的包名。 包名要简单明了,例如 net、time、log。 ","date":"2020-06-02","objectID":"/go-code-style/:2:1","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.2 函数命名 函数名采用驼峰式,首字母根据访问控制决定使用大写或小写,例如:MixedCaps或者mixedCaps。 代码生成工具自动生成的代码(如xxxx.pb.go)和为了对相关测试用例进行分组,而采用的下划线(如TestMyFunction_WhatIsBeingTested)排除此规则。 ","date":"2020-06-02","objectID":"/go-code-style/:2:2","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.3 文件命名 文件名要简短有意义。 文件名应小写,并使用下划线分割单词。 ","date":"2020-06-02","objectID":"/go-code-style/:2:3","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.4 结构体命名 采用驼峰命名方式,首字母根据访问控制决定使用大写或小写,例如MixedCaps或者mixedCaps。 结构体名不应该是动词,应该是名词,比如 Node、NodeSpec。 避免使用 Data、Info 这类无意义的结构体名。 结构体的声明和初始化应采用多行,例如: // User 多行声明 type User struct { Name string Email string } // 多行初始化 u := User{ UserName: \"colin\", Email: \"[email protected]\", } ","date":"2020-06-02","objectID":"/go-code-style/:2:4","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.5 接口命名 接口命名的规则,基本和结构体命名规则保持一致: 单个函数的接口名以 “er\"”作为后缀(例如 Reader,Writer),有时候可能导致蹩脚的英文,但是没关系。 两个函数的接口名以两个函数名命名,例如 ReadWriter。 三个以上函数的接口名,类似于结构体名。 例如: // Seeking to an offset before the start of the file is an error. // Seeking to any positive offset is legal, but the behavior of subsequent // I/O operations on the underlying object is implementation-dependent. type Seeker interface { Seek(offset int64, whence int) (int64, error) } // ReadWriter is the interface that groups the basic Read and Write methods. type ReadWriter interface { Reader Writer } ","date":"2020-06-02","objectID":"/go-code-style/:2:5","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.6 变量命名 变量名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。 在相对简单(对象数量少、针对性强)的环境中,可以将一些名称由完整单词简写为单个字母,例如: user 可以简写为 u; userID 可以简写 uid。 特有名词时,需要遵循以下规则: 如果变量为私有,且特有名词为首个单词,则使用小写,如 apiClient。 其他情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID。 下面列举了一些常见的特有名词。 // A GonicMapper that contains a list of common initialisms taken from golang/lint var LintGonicMapper = GonicMapper{ \"API\": true, \"ASCII\": true, \"CPU\": true, \"CSS\": true, \"DNS\": true, \"EOF\": true, \"GUID\": true, \"HTML\": true, \"HTTP\": true, \"HTTPS\": true, \"ID\": true, \"IP\": true, \"JSON\": true, \"LHS\": true, \"QPS\": true, \"RAM\": true, \"RHS\": true, \"RPC\": true, \"SLA\": true, \"SMTP\": true, \"SSH\": true, \"TLS\": true, \"TTL\": true, \"UI\": true, \"UID\": true, \"UUID\": true, \"URI\": true, \"URL\": true, \"UTF8\": true, \"VM\": true, \"XML\": true, \"XSRF\": true, \"XSS\": true, } 若变量类型为 bool 类型,则名称应以 Has,Is,Can 或 Allow 开头,例如: var hasConflict bool var isExist bool var canManage bool var allowGitHook bool 局部变量应当尽可能短小,比如使用 buf 指代 buffer,使用 i 指代 index。 代码生成工具自动生成的代码可排除此规则(如xxx.pb.go里面的 Id) ","date":"2020-06-02","objectID":"/go-code-style/:2:6","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.7 常量命名 常量名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。 如果是枚举类型的常量,需要先创建相应类型: // Code defines an error code type. type Code int // Internal errors. const ( // ErrUnknown - 0: An unknown error occurred. ErrUnknown Code = iota // ErrFatal - 1: An fatal error occurred. ErrFatal ) ","date":"2020-06-02","objectID":"/go-code-style/:2:7","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"2.8 Error 的命名 Error 类型应该写成 FooError 的形式。 type ExitError struct { // .... } Error 变量写成 ErrFoo 的形式。 var ErrFormat = errors.New(\"unknown format\") ","date":"2020-06-02","objectID":"/go-code-style/:2:8","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"3. 注释规范 每个可导出的名字都要有注释,该注释对导出的变量、函数、结构体、接口等进行简要介绍。 全部使用单行注释,禁止使用多行注释。 和代码的规范一样,单行注释不要过长,禁止超过 120 字符,超过的请使用换行展示,尽量保持格式优雅。 注释必须是完整的句子,以需要注释的内容作为开头,句点作为结尾,格式为 // 名称 描述.。例如: // bad // logs the flags in the flagset. func PrintFlags(flags *pflag.FlagSet) { // normal code } // good // PrintFlags logs the flags in the flagset. func PrintFlags(flags *pflag.FlagSet) { // normal code } 所有注释掉的代码在提交 code review 前都应该被删除,否则应该说明为什么不删除,并给出后续处理建议。 在多段注释之间可以使用空行分隔加以区分,如下所示: // Package superman implements methods for saving the world. // // Experience has shown that a small number of procedures can prove // helpful when attempting to save the world. package superman ","date":"2020-06-02","objectID":"/go-code-style/:3:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"3.1 包注释 每个包都有且仅有一个包级别的注释。 包注释统一用 // 进行注释,格式为 // Package 包名 包描述,例如: // Package genericclioptions contains flags which can be added to you command, bound, completed, and produce // useful helper functions. package genericclioptions ","date":"2020-06-02","objectID":"/go-code-style/:3:1","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"3.2 变量/常量注释 每个可导出的变量/常量都必须有注释说明,格式为// 变量名 变量描述,例如: // ErrSigningMethod defines invalid signing method error. var ErrSigningMethod = errors.New(\"Invalid signing method\") 出现大块常量或变量定义时,可在前面注释一个总的说明,然后在每一行常量的前一行或末尾详细注释该常量的定义,例如: // Code must start with 1xxxxx. const ( // ErrSuccess - 200: OK. ErrSuccess int = iota + 100001 // ErrUnknown - 500: Internal server error. ErrUnknown // ErrBind - 400: Error occurred while binding the request body to the struct. ErrBind // ErrValidation - 400: Validation failed. ErrValidation ) ","date":"2020-06-02","objectID":"/go-code-style/:3:2","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"3.3 结构体注释 每个需要导出的结构体或者接口都必须有注释说明,格式为 // 结构体名 结构体描述.。 结构体内的可导出成员变量名,如果意义不明确,必须要给出注释,放在成员变量的前一行或同一行的末尾。例如: // User represents a user restful resource. It is also used as gorm model. type User struct { // Standard object's metadata. metav1.ObjectMeta `json:\"metadata,omitempty\"` Nickname string `json:\"nickname\" gorm:\"column:nickname\"` Password string `json:\"password\" gorm:\"column:password\"` Email string `json:\"email\" gorm:\"column:email\"` Phone string `json:\"phone\" gorm:\"column:phone\"` IsAdmin int `json:\"isAdmin,omitempty\" gorm:\"column:isAdmin\"` } ","date":"2020-06-02","objectID":"/go-code-style/:3:3","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"3.4 方法注释 每个需要导出的函数或者方法都必须有注释,格式为// 函数名 函数描述.,例如: // BeforeUpdate run before update database record. func (p *Policy) BeforeUpdate() (err error) { // normal code return nil } ","date":"2020-06-02","objectID":"/go-code-style/:3:4","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"3.5 类型注释 每个需要导出的类型定义和类型别名都必须有注释说明,格式为 // 类型名 类型描述.,例如: // Code defines an error code type. type Code int ","date":"2020-06-02","objectID":"/go-code-style/:3:5","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"4. 类型 ","date":"2020-06-02","objectID":"/go-code-style/:4:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"4.1 字符串 空字符串判断。 // bad if s == \"\" { // normal code } // good if len(s) == 0 { // normal code } []byte/string相等比较。 // bad var s1 []byte var s2 []byte ... bytes.Equal(s1, s2) == 0 bytes.Equal(s1, s2) != 0 // good var s1 []byte var s2 []byte ... bytes.Compare(s1, s2) == 0 bytes.Compare(s1, s2) != 0 复杂字符串使用 raw 字符串避免字符转义。 // bad regexp.MustCompile(\"\\\\.\") // good regexp.MustCompile(`\\.`) ","date":"2020-06-02","objectID":"/go-code-style/:4:1","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"4.2 切片 空 slice 判断。 // bad if len(slice) = 0 { // normal code } // good if slice != nil \u0026\u0026 len(slice) == 0 { // normal code } 上面判断同样适用于 map、channel。 声明 slice。 // bad s := []string{} s := make([]string, 0) // good var s []string slice 复制。 // bad var b1, b2 []byte for i, v := range b1 { b2[i] = v } for i := range b1 { b2[i] = b1[i] } // good copy(b2, b1) slice 新增。 // bad var a, b []int for _, v := range a { b = append(b, v) } // good var a, b []int b = append(b, a...) ","date":"2020-06-02","objectID":"/go-code-style/:4:2","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"4.3 结构体 struct 初始化。 struct 以多行格式初始化。 type user struct { Id int64 Name string } u1 := user{100, \"Colin\"} u2 := user{ Id: 200, Name: \"Lex\", } ","date":"2020-06-02","objectID":"/go-code-style/:4:3","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"5. 控制结构 ","date":"2020-06-02","objectID":"/go-code-style/:5:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"5.1 if if 接受初始化语句,约定如下方式建立局部变量。 if err := loadConfig(); err != nil { // error handling return err } if 对于 bool 类型的变量,应直接进行真假判断。 var isAllow bool if isAllow { // normal code } ","date":"2020-06-02","objectID":"/go-code-style/:5:1","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"5.2 for 采用短声明建立局部变量。 sum := 0 for i := 0; i \u003c 10; i++ { sum += 1 } 不要在 for 循环里面使用 defer,defer 只有在函数退出时才会执行。 // bad for file := range files { fd, err := os.Open(file) if err != nil { return err } defer fd.Close() // normal code } // good for file := range files { func() { fd, err := os.Open(file) if err != nil { return err } defer fd.Close() // normal code }() } ","date":"2020-06-02","objectID":"/go-code-style/:5:2","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"5.3 range 如果只需要第一项(key),就丢弃第二个。 for key := range keys { // normal code } 如果只需要第二项,则把第一项置为下划线。 sum := 0 for _, value := range array { sum += value } ","date":"2020-06-02","objectID":"/go-code-style/:5:3","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"5.4 switch 必须要有 default。 switch os := runtime.GOOS; os { case \"linux\": fmt.Println(\"Linux.\") case \"darwin\": fmt.Println(\"OS X.\") default: fmt.Printf(\"%s.\\n\", os) } ","date":"2020-06-02","objectID":"/go-code-style/:5:4","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"5.5 goto 业务代码禁止使用 goto 。 框架或其他底层源码尽量不用。 ","date":"2020-06-02","objectID":"/go-code-style/:5:5","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"6. 函数 传入变量和返回变量以小写字母开头。 函数参数个数不能超过 5 个。 函数分组与顺序 函数应按粗略的调用顺序排序。 同一文件中的函数应按接收者分组。 尽量采用值传递,而非指针传递。 传入参数是 map、slice、chan、interface ,不要传递指针。 ","date":"2020-06-02","objectID":"/go-code-style/:6:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"6.1 函数参数 如果函数返回相同类型的两个或三个参数,或者如果从上下文中不清楚结果的含义,使用命名返回,其他情况不建议使用命名返回,例如: func coordinate() (x, y float64, err error) { // normal code } 传入变量和返回变量都以小写字母开头。 尽量用值传递,非指针传递。 参数数量均不能超过 5 个。 多返回值最多返回三个,超过三个请使用 struct。 ","date":"2020-06-02","objectID":"/go-code-style/:6:1","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"6.2 defer 当存在资源创建时,应紧跟 defer 释放资源(可以大胆使用 defer,defer 在 Go1.14 版本中,性能大幅提升,defer 的性能损耗即使在性能敏感型的业务中,也可以忽略)。 先判断是否错误,再 defer 释放资源,例如: rep, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() ","date":"2020-06-02","objectID":"/go-code-style/:6:2","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"6.3 方法的接收器 推荐以类名第一个英文首字母的小写作为接收器的命名。 接收器的命名在函数超过 20 行的时候不要用单字符。 接收器的命名不能采用 me、this、self 这类易混淆名称。 ","date":"2020-06-02","objectID":"/go-code-style/:6:3","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"6.4 嵌套 嵌套深度不能超过 4 层。 ","date":"2020-06-02","objectID":"/go-code-style/:6:4","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"6.5 变量命名 变量声明尽量放在变量第一次使用的前面,遵循就近原则。 如果魔法数字出现超过两次,则禁止使用,改用一个常量代替,例如: // PI ... const Prise = 3.14 func getAppleCost(n float64) float64 { return Prise * n } func getOrangeCost(n float64) float64 { return Prise * n } ","date":"2020-06-02","objectID":"/go-code-style/:6:5","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"7. GOPATH 设置规范 Go 1.11 之后,弱化了 GOPATH 规则,已有代码(很多库肯定是在 1.11 之前建立的)肯定符合这个规则,建议保留 GOPATH 规则,便于维护代码。 建议只使用一个 GOPATH,不建议使用多个 GOPATH。如果使用多个 GOPATH,编译生效的 bin 目录是在第一个 GOPATH 下。 ","date":"2020-06-02","objectID":"/go-code-style/:7:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"8. 依赖管理 Go 1.11 以上必须使用 Go Modules。 使用 Go Modules 作为依赖管理的项目时,不建议提交 vendor 目录。 使用 Go Modules 作为依赖管理的项目时,必须提交 go.sum 文件。 ","date":"2020-06-02","objectID":"/go-code-style/:8:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"9. 最佳实践 尽量少用全局变量,而是通过参数传递,使每个函数都是“无状态”的。这样可以减少耦合,也方便分工和单元测试。 在编译时验证接口的符合性,例如: type LogHandler struct { h http.Handler log *zap.Logger } var _ http.Handler = LogHandler{} 服务器处理请求时,应该创建一个 context,保存该请求的相关信息(如 requestID),并在函数调用链中传递。 ","date":"2020-06-02","objectID":"/go-code-style/:9:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"9.1 性能 string 表示的是不可变的字符串变量,对 string 的修改是比较重的操作,基本上都需要重新申请内存。所以,如果没有特殊需要,需要修改时多使用 []byte。 优先使用 strconv 而不是 fmt。 ","date":"2020-06-02","objectID":"/go-code-style/:9:1","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"9.2 注意事项 append 要小心自动分配内存,append 返回的可能是新分配的地址。 如果要直接修改 map 的 value 值,则 value 只能是指针,否则要覆盖原来的值。 map 在并发中需要加锁。 编译过程无法检查 interface{} 的转换,只能在运行时检查,小心引起 panic。 ","date":"2020-06-02","objectID":"/go-code-style/:9:2","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"参考 可直接套用的Go编码规范 ","date":"2020-06-02","objectID":"/go-code-style/:10:0","tags":["编码","规范"],"title":"Go编码规范","uri":"/go-code-style/"},{"categories":["Go"],"content":"卸载 如果以前安装过版本,可以先卸载 如果是用 Ubuntu 用 apt 安装的,用以下命令卸载 sudo apt-get remove golang-go sudo apt-get remove --auto-remove golang-go 如果是手动下载安装的,删除相应目录即可 ","date":"2020-05-31","objectID":"/go-install/:1:0","tags":["Go"],"title":"Go 安装、升级、卸载","uri":"/go-install/"},{"categories":["Go"],"content":"安装 下载 go 方法一:官网下载:https://go.dev/dl/ 方法二:打开 ubuntu 输入: wget https://dl.google.com/go/go1.15.3.linux-amd64.tar.gz 解压安装包 sudo tar -C /usr/local -xzf go1.15.3.linux-amd64.tar.gz 设置环境变量 建立软链接 sudo ln -s /usr/local/go/bin/* /usr/bin/ 修改/etc/profile # 打开/etc/profile,添加以下内容 export GOPATH=\"$HOME/go\" export PATH=\"$PATH:/usr/local/go/bin:$GOPATH/bin\" # 重新加载文件 source /etc/profile 验证是否生效 go version ","date":"2020-05-31","objectID":"/go-install/:2:0","tags":["Go"],"title":"Go 安装、升级、卸载","uri":"/go-install/"},{"categories":["Go"],"content":"升级 先卸载再安装 ","date":"2020-05-31","objectID":"/go-install/:3:0","tags":["Go"],"title":"Go 安装、升级、卸载","uri":"/go-install/"},{"categories":["hugo"],"content":"1. 注册 Algolia 注册 algolia 添加索引 data sources 点击 Settings -\u003e API Keys,查看自己的 Application ID 和 Search-Only API Key ","date":"2020-05-08","objectID":"/hugo-loveit-algolia/:1:0","tags":["hugo","algolia"],"title":"hugo添加Algolia搜索系统","uri":"/hugo-loveit-algolia/"},{"categories":["hugo"],"content":"2. 生成索引文件 修改 config.toml 注意 outputs 下面 home 的末尾要有\"Algolia\",漏了生成的名字就不一样了 [outputs] home = [\"HTML\", \"RSS\", \"Algolia\"] [outputFormats.Algolia] baseName = \"algolia\" isPlainText = true mediaType = \"application/json\" notAlternative = true [params.algolia] appId = \"Application ID\" indexName = \"索引名字\" searchOnlyKey = \"Search-Only API Key\" 根目录下 layouts/_default (没有就新建) 文件夹中新建 list.algolia.json 文件,内容如下 注意如果索引文件太多,则需要过滤一些内容作为索引 // 把内容也添加索引 {{- range $index, $entry := .Site.RegularPages }} {{- if $index }}, {{ end }} { \"objectID\": {{ .File.TranslationBaseName }}, \"url\": {{ .Permalink | jsonify }}, \"title\": {{ .Title | jsonify }}, \"summary\": {{ .Summary | jsonify }}, \"content\": {{ .Plain | jsonify }}, \"pubDate\": {{ .PublishDate | jsonify }} } {{- end }} // 过滤内容 {{- $.Scratch.Add \"index\" slice -}} {{- $section := $.Site.GetPage \"section\" .Section}} {{- range .Site.AllPages -}} {{- if or (and (.IsDescendant $section) (and (not .Draft) (not .Params.private))) $section.IsHome -}} {{- $.Scratch.Add \"index\" (dict \"objectID\" .UniqueID \"date\" .Date.UTC.Unix \"description\" .Description \"dir\" .Dir \"expirydate\" .ExpiryDate.UTC.Unix \"fuzzywordcount\" .FuzzyWordCount \"keywords\" .Keywords \"kind\" .Kind \"lang\" .Lang \"lastmod\" .Lastmod.UTC.Unix \"permalink\" .Permalink \"publishdate\" .PublishDate \"readingtime\" .ReadingTime \"relpermalink\" .RelPermalink \"summary\" .Summary \"title\" .Title \"type\" .Type \"url\" .URL \"weight\" .Weight \"wordcount\" .WordCount \"section\" .Section \"tags\" .Params.Tags \"categories\" .Params.Categories \"authors\" .Params.Authors)}} {{- end -}} {{- end -}} {{- $.Scratch.Get \"index\" | jsonify -}} 执行hugo命令,在 public 目录下会生成algolia.json文件 ","date":"2020-05-08","objectID":"/hugo-loveit-algolia/:2:0","tags":["hugo","algolia"],"title":"hugo添加Algolia搜索系统","uri":"/hugo-loveit-algolia/"},{"categories":["hugo"],"content":"3. 上传索引文件 手动上传,在 Algolia 网站内,点左下侧边栏 Data Sources -\u003e Indices,再点 Upload record 按钮上传生成的 algolia.json 文件。 自动上传 安装 node 和 npm,执行 npm init,再执行npm install atomic-algolia --save,再执行 npm install 修改 package.json 文件,scripts 下添加 \"algolia\": \"atomic-algolia\" \"scripts\": { \"test\": \"echo \\\"Error: no test specified\\\" \u0026\u0026 exit 1\", \"algolia\": \"atomic-algolia\" }, 根目录下新建 .env 文件,内容如下: ALGOLIA_APP_ID=你的Application ID ALGOLIA_INDEX_NAME=你的索引名字 ALGOLIA_INDEX_FILE=public/algolia.json ALGOLIA_ADMIN_KEY=你的Admin API Key 执行 npm run algolia ","date":"2020-05-08","objectID":"/hugo-loveit-algolia/:3:0","tags":["hugo","algolia"],"title":"hugo添加Algolia搜索系统","uri":"/hugo-loveit-algolia/"},{"categories":["Hugo"],"content":"1. 注册 Github 账号并创建仓库 注册账号和创建仓库此不赘述,说一下需要注意的点 仓库的名字必须以 github.io 结尾 Repository name 需要与 Owner 一致,不一致的话会多一个层级。例:owner.github.io 仓库可见性必须是 public ","date":"2020-05-06","objectID":"/hugo-loveit/:1:0","tags":["Hugo","Github Pages","LoveIt"],"title":"基于Hugo + Github Pages搭建LoveIt主题个人博客","uri":"/hugo-loveit/"},{"categories":["Hugo"],"content":"2. 安装 hugo windows 下载安装包 解压并发执行文件所在目录添加到环境变量 linux 下载安装包 解压并发执行文件所在目录添加到环境变量 /etc/profile 文件添加一行(export 解压目录),执行命令 source /etc/profile 使配置文件生效 ","date":"2020-05-06","objectID":"/hugo-loveit/:2:0","tags":["Hugo","Github Pages","LoveIt"],"title":"基于Hugo + Github Pages搭建LoveIt主题个人博客","uri":"/hugo-loveit/"},{"categories":["Hugo"],"content":"3. 安装git ","date":"2020-05-06","objectID":"/hugo-loveit/:3:0","tags":["Hugo","Github Pages","LoveIt"],"title":"基于Hugo + Github Pages搭建LoveIt主题个人博客","uri":"/hugo-loveit/"},{"categories":["Hugo"],"content":"4. 创建站点和配置主题 选定一个站点放置目录执行一下命令 hugo new site myblog cd myblog git init git submodule add https://github.com/dillonzq/LoveIt.git themes/LoveIt cp myblog/themes/LoveIt/exampleSite/config.toml . hugo serve 浏览器打开 http://localhost:1313/ 修改 config.toml 以满足自己需要,里面有详细注释 ","date":"2020-05-06","objectID":"/hugo-loveit/:4:0","tags":["Hugo","Github Pages","LoveIt"],"title":"基于Hugo + Github Pages搭建LoveIt主题个人博客","uri":"/hugo-loveit/"},{"categories":["Hugo"],"content":"5. 发布内容 执行 hugo new posts/first_post.md 添加头部内容 --- title: 标题 author: 作者 date: 日期(格式:yyyy-MM-dd) // 添加分类 categories: - Hugo // 添加标签 tags: - Hugo - Github Pages - LoveIt --- 接下来就是编写的 markdown 文档了 ","date":"2020-05-06","objectID":"/hugo-loveit/:5:0","tags":["Hugo","Github Pages","LoveIt"],"title":"基于Hugo + Github Pages搭建LoveIt主题个人博客","uri":"/hugo-loveit/"},{"categories":["Hugo"],"content":"6. 将博客内容上传至 Github 在 myblog 目录下执行一下命令 hugo cd public git init git add . git commit -m \"first post\" git remote add origin https://github.com/yourusername/yourusername.github.io git push -u origin main 进入你的 yourusername.github.io 仓库看文件是否上传成功 访问 https://yourusername.github.io ","date":"2020-05-06","objectID":"/hugo-loveit/:6:0","tags":["Hugo","Github Pages","LoveIt"],"title":"基于Hugo + Github Pages搭建LoveIt主题个人博客","uri":"/hugo-loveit/"},{"categories":["Node"],"content":"下载 Node 进入 Node 最新版下载 https://nodejs.org/en/download/current/ 例:https://nodejs.org/dist/v16.17.1/node-v16.17.1-linux-x64.tar.xz ","date":"2020-02-16","objectID":"/node-install-upgrade/:1:0","tags":["Node","Linux"],"title":"Linux安装node和升级node","uri":"/node-install-upgrade/"},{"categories":["Node"],"content":"安装 Node cd /usr/local wget https://nodejs.org/dist/v16.17.1/node-v16.17.1-linux-x64.tar.xz tar -xvf node-v16.17.1-linux-x64.tar.xz cd node-v16.17.1-linux-x64/ ln -s /usr/local/node-v16.17.1-linux-x64/bin/node /usr/bin/node ln -s /usr/local/node-v16.17.1-linux-x64/bin/npm /usr/bin/npm node -v npm -v ","date":"2020-02-16","objectID":"/node-install-upgrade/:2:0","tags":["Node","Linux"],"title":"Linux安装node和升级node","uri":"/node-install-upgrade/"},{"categories":["Node"],"content":"加速 npm 使用淘宝的 npm npm install cnpm -g --registry=https://registry.npm.taobao.org 使用 cnpm 去代替 npm 来执行,比如 cnpm install xxx 不想安装 cnpm,也可以每次使用npm install xxx --registry=https://registry.npm.taobao.org直接安装 ","date":"2020-02-16","objectID":"/node-install-upgrade/:3:0","tags":["Node","Linux"],"title":"Linux安装node和升级node","uri":"/node-install-upgrade/"},{"categories":["Node"],"content":"升级 Node 清除缓存信息 sudo npm cache clean -f 下载 node 安装包 sudo npm install -g n 升级到 nodejs 最新稳定版本 sudo n stable 查看当前版本 node -v ","date":"2020-02-16","objectID":"/node-install-upgrade/:4:0","tags":["Node","Linux"],"title":"Linux安装node和升级node","uri":"/node-install-upgrade/"},{"categories":["规范","API","RESTful"],"content":"1. RESTful API 介绍 REST 本身并没有创造新的技术、组件或服务,它代表的是一种软件架构风格,是一组架构约束条件和原则,而不是技术框架。REST 它是 Representational State Transfer 的简称,中文的含义是: “表征状态转移” 或 “表现层状态转化”。它是基于 HTTP、URI、XML、JSON 等标准和协议,支持轻量级、跨平台、跨语言的架构设计。 REST 是一种规范,而 RESTful API 则是满足这种规范的 API 接口。 REST 风格虽然适用于很多传输协议,但在实际开发中,由于 REST 天生和 HTTP 协议相辅相成,因此 HTTP 协议已经成了实现 RESTful API 事实上的标准。 ","date":"2020-02-06","objectID":"/restful/:1:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"2. REST 设计原则 每一个 URI 代表一种资源。 同一种资源有多种表现形式(xml/json)。 规范统一接口。 返回一致的数据格式。 所有的操作都是无状态的。 可缓存(客户端可以缓存响应的内容)。 ","date":"2020-02-06","objectID":"/restful/:2:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"3. URL 及参数设计规范 在 uri 末尾不需要出现斜杠/ 在 uri 中使用斜杠/是表达层级关系的。层级太多可以考虑使用 querystring 参数。 在 uri 中可以使用连接符-, 来提升可读性。 在 uri 中不允许出现下划线字符_。 在 uri 中尽量使用小写字符。 在 uri 中不允许出现文件扩展名. 比如接口为 /xxx/test, 不要写成 /xxx/api.json 这样的是不合法的。 在 uri 中使用名词复数形式。例:/students/3248234/courses/physics,集合-\u003e成员-\u003e集合-\u003e成员。 具体可以看:https://blog.restcase.com/7-rules-for-rest-api-uri-design/ ","date":"2020-02-06","objectID":"/restful/:3:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"4. REST 资源操作映射为 HTTP 方法 HTTP 方法 操作 是否安全(是否修改状态) 幂等 GET 查询,取出资源 是 是 POST 新增,新建一个资源 否 否 PUT 更新,更新资源(客户端提供改变后的完整资源) 否 是 PATCH 更新,更新部分资源(客户端提供改变的属性) 否 是 DELETE 删除,删除资源(批量/users?ids=1,2,3) 否 是 ","date":"2020-02-06","objectID":"/restful/:4:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"5. API 版本管理 API 版本有不同的标识方法,在 RESTful API 开发中,通常将版本标识放在如下 3 个位 置: URL 中,比如/v1/users。 HTTP Header 中,比如 Accept: vnd.example-com.foo+json; version=1.0。 querystring 参数中,比如/users?version=v1 没有严格限制,推荐放在 URL 中的,比如/v1/users,这样做的好处是很直观,GitHub、Kubernetes、Etcd 等很多优秀的 API 均采用这种方式。 ","date":"2020-02-06","objectID":"/restful/:5:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"6. 统一分页 / 过滤 / 排序 / 搜索功能 分页:在列出一个 Collection 下所有的 Member 时,应该提供分页功能,例如/users?offset=0\u0026limit=20(limit,指定返回记录的数量;offset,指定返回记录的开始位置)。引入分页功能可以减少 API 响应的延时,同时可以避免返回太多条目,导致服务器 / 客户端响应特别慢,甚至导致服务器 / 客户端 crash 的情况。 过滤:如果用户不需要一个资源的全部状态属性,可以在 URI 参数里指定返回哪些属性,例如/users?fields=email,username,address。 排序:用户很多时候会根据创建时间或者其他因素,列出一个 Collection 中前 100 个 Member,这时可以在 URI 参数中指明排序参数,例如/users?sort=age,desc。 搜索:当一个资源的 Member 太多时,用户可能想通过搜索,快速找到所需要的 Member,或者想搜下有没有名字为 xxx 的某类资源,这时候就需要提供搜索功能。搜索建议按模糊匹配来搜索。例如/users?name=xxx。 ","date":"2020-02-06","objectID":"/restful/:6:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"7. 统一返回格式 RESTful 规范中的请求应该返回统一的数据格式。对于返回的数据,一般会包含如下字段: code:http 响应的状态码(或者自定义的错误码)。 message: data:当请求成功的时候, 返回的数据信息。 返回成功的响应 JSON 格式一般为如下: { \"code\": 200, \"message\": \"success\", \"data\": [{ \"name\": \"tugenhua\", \"age\": 31 }] } 返回失败的响应 JSON 格式一般为如下: { \"code\": 401, \"message\": \"用户没有权限\", \"data\": null } ","date":"2020-02-06","objectID":"/restful/:7:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"8. 状态码 各类型状态码 1xx: 信息,请求收到了,继续处理。 2xx: 代表成功. 行为被成功地接收、理解及采纳。 3xx: 重定向。 4xx: 客户端错误,请求包含语法错误或请求无法实现。 5xx: 服务器端错误. 2xx 状态码 200 OK [GET]: 服务器端成功返回用户请求的数据。 201 CREATED [POST/PUT/PATCH]: 用户新建或修改数据成功。 202 Accepted 表示一个请求已经进入后台排队(一般是异步任务)。 204 NO CONTENT -[DELETE]: 用户删除数据成功。 4xx 状态码 400:Bad Request - [POST/PUT/PATCH]: 用户发出的请求有错误,服务器不理解客户端的请求,未做任何处理。 401: Unauthorized; 表示用户没有权限(令牌、用户名、密码错误)。 403:Forbidden: 表示用户得到授权了,但是访问被禁止了, 也可以理解为不具有访问资源的权限。 404:Not Found: 所请求的资源不存在,或不可用。 405:Method Not Allowed: 用户已经通过了身份验证, 但是所用的 HTTP 方法不在它的权限之内。 406:Not Acceptable: 用户的请求的格式不可得(比如用户请求的是 JSON 格式,但是只有 XML 格式)。 410:Gone - [GET]: 用户请求的资源被转移或被删除。且不会再得到的。 415: Unsupported Media Type: 客户端要求的返回格式不支持,比如,API 只能返回 JSON 格式,但是客户端要求返回 XML 格式。 422:Unprocessable Entity: 客户端上传的附件无法处理,导致请求失败。 429:Too Many Requests: 客户端的请求次数超过限额。 5xx 状态码 500:INTERNAL SERVER ERROR; 服务器发生错误。 502:网关错误。 503: Service Unavailable 服务器端当前无法处理请求。 504:网关超时。 ","date":"2020-02-06","objectID":"/restful/:8:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"9. 域名 应该尽量将 API 部署在专用域名之下。例:https://api.example.com 如果确定 API 很简单,不会有进一步扩展,可以考虑放在主域名下。例:https://example.com/api/ ","date":"2020-02-06","objectID":"/restful/:9:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["规范","API","RESTful"],"content":"10. 参考 https://blog.restcase.com ","date":"2020-02-06","objectID":"/restful/:10:0","tags":["规范","API","RESTful"],"title":"RESTful API 设计规范","uri":"/restful/"},{"categories":["Linux"],"content":"详细命令 ssh-keygen \\ -t rsa \\ -m PEM \\ -b 4096 \\ -C \"user@server\" \\ -f /home/username/.ssh/keys/privatekey \\ -N passphrase ","date":"2020-01-15","objectID":"/linux-ssh-kengen/:1:0","tags":["Linux","ssh","免密登录"],"title":"Linux使用ssh-kengen生成公私钥和免密登录","uri":"/linux-ssh-kengen/"},{"categories":["Linux"],"content":"命令释义 ssh-keygen = 用于创建密钥的程序 -m PEM = 将密钥的格式设为 PEM -t rsa = 要创建的密钥类型,本例中为 RSA 格式 -b 4096 = 密钥的位数,本例中为 4096 -C “user@server” = 追加到公钥文件末尾以便于识别的注释。 通常以电子邮件地址用作注释,但也可以使用任何最适合你基础结构的事物。 -f /home/username/.ssh/keys/privatekey = 私钥文件的文件名(如果选择不使用默认名称)。 追加了 .pub 的相应公钥文件在相同目录中生成。 该目录必须存在。 -N passphrase = 用于访问私钥文件的其他密码。 ","date":"2020-01-15","objectID":"/linux-ssh-kengen/:2:0","tags":["Linux","ssh","免密登录"],"title":"Linux使用ssh-kengen生成公私钥和免密登录","uri":"/linux-ssh-kengen/"},{"categories":["Linux"],"content":"使用示例: username@hostname:~# ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/home/username/.ssh/id_rsa): // 这里输入要生成的文件名 Created directory '/home/username/.ssh'. // 这里输入密码 Enter passphrase (empty for no passphrase): // 这里重复输入密码 Enter same passphrase again: Your identification has been saved in /home/username/.ssh/id_rsa. Your public key has been saved in /home/username/.ssh/id_rsa.pub. The key fingerprint is: SHA256:LIGNrf7jteWJtB6quaJgOwT4yLkbyIBhCTUOCW58Em0 /home/username/@hostname The key's randomart image is: +---[RSA 2048]----+ |=++ | |=ooE = | |oBo.o + | |*.o . o | |=o. . . S | |=+.. . | |++. . + . | |oo+ o.+ B . | |.+o.+++o= o | +----[SHA256]-----+ ","date":"2020-01-15","objectID":"/linux-ssh-kengen/:3:0","tags":["Linux","ssh","免密登录"],"title":"Linux使用ssh-kengen生成公私钥和免密登录","uri":"/linux-ssh-kengen/"},{"categories":["Linux"],"content":"免密登录 复制 A 机公钥到 B 机/home/username/.ssh 目录下的 authorized_keys 文件里。 scp /home/username/.ssh/id_rsa.pub [email protected]:/home/username/.ssh/authorized_keys B 机中给 authorized_keys 文件 600 权限 chmod 600 authorized_keys 、A 机登录 B 机 ssh 192.168.1.100 ","date":"2020-01-15","objectID":"/linux-ssh-kengen/:4:0","tags":["Linux","ssh","免密登录"],"title":"Linux使用ssh-kengen生成公私钥和免密登录","uri":"/linux-ssh-kengen/"},{"categories":["消息队列"],"content":" 维度 Kafka RabbitMQ ZeroMQ RocketMQ ActiveMQ 资料文档 中等 多 少 少 多 开发语言 Scala Erlang C Java Java 支持的协议 自定义基于 TCP AMQP TCP、UDP 自定义 OpenWire、STOMP、REST、XMPP、AMQP 消息存储 内存、磁盘、数据库。支持大量堆积。 内存、磁盘。支持少量堆积。 消息发送端的内存或者磁盘中。不支持持久化。 磁盘。支持大量堆积。 内存、磁盘、数据库。支持少量堆积。 消息事务 支持 支持 不支持 支持 支持 负载均衡 支持 支持不好 不支持 支持 支持(基于 zookeeper) 集群方式 支持 支持简单集群 不支持 支持 支持 管理界面 一般 好 无 有,非自带 一般 可用性 非常高(分布式) 高(主从) 高 非常高(分布式) 高(主从) 消息重复 支持 at least once、at most once 支持 at least once、at most once 有重传,没有持久化 支持 at least once 支持 at least once 吞吐量 TPS 极大 比较大 极大 大 比较大 消费推拉模式 拉 推 推 拉 推 订阅形式和消息分发 基于 topic 及按 topic 正则匹配的发布订阅模式 direct、topic、Headers 和 fanout。 点对点(p2p) 基于 topic/messageTag 及按消息类型、属性正则匹配的发布订阅 点对点(p2p)、广播(发布-订阅) 顺序消息 支持 不支持 不支持 支持 不支持 消息确认 支持 支持 支持 支持 支持 消息回溯 支持指定分区 offset 位置的回溯 不支持 不支持 支持指定时间点的回溯 不支持 消息重试 不支持,但是可以实现 不支持,可以利用消息确认机制实现 不支持 支持 不支持 并发度 高 极高 高 高 高 延迟队列 不支持 支持 不支持 支持 不支持 死信队列 不支持 支持 不支持 支持 不支持 优先级队列 不支持 支持 不支持 不支持 不支持 ","date":"2020-01-12","objectID":"/message-queue-selection/:0:0","tags":["消息队列"],"title":"消息队列选型","uri":"/message-queue-selection/"},{"categories":["消息队列"],"content":"参考 https://cloud.tencent.com/developer/article/1449951 https://cloud.tencent.com/developer/article/1944357 ","date":"2020-01-12","objectID":"/message-queue-selection/:1:0","tags":["消息队列"],"title":"消息队列选型","uri":"/message-queue-selection/"},{"categories":["Kafka","消息队列"],"content":"Broker 配置参数 属性 默认值 释义 broker.id -1 该参数用来指定 Kafka 集群中 broker 的唯一标识,默认值为-1。如果没有设置,那么 Kafka 会自动生成一个。 listeners null 该参数指明 broker 监听客户端连接的地址列表,即为客户端要连接 broker 的入口地址列表,配置格式为 protocol1://hostname1:port1,protocol2://hostname2:port2,其中 protocol 代表协议类型,Kafka 当前支持的协议类型有 PLAINTEXT、SSL、SASL_SSL 等,如果未开启安全认证,则使用简单的 PLAINTEXT 即可。hostname 代表主机名,port 代表服务端口,此参数的默认值为 null。比如此参数配置为 PLAINTEXT://198.162.0.2:9092,如果有多个地址,则中间以逗号隔开。如果不指定主机名,则表示绑定默认网卡,注意有可能会绑定到 127.0.0.1,这样无法对外提供服务,所以主机名最好不要为空;如果主机名是 0.0.0.0,则表示绑定所有的网卡。 advertised.listeners null 作用和 listeners 类似,绑定公网 IP 供外部客户端使用 listener.security.protocol.map null listener 支持的协议集 num.network.threads 3 server 用来处理网络请求的网络线程数目;一般你不需要更改这个属性。 num.io.threads 8 server 用来处理请求的 I/O 线程的数目;这个线程数目至少要等于硬盘的个数。 socket.send.buffer.bytes 100 * 1024 SO_SNDBUFF 缓存大小,server 进行 socket 连接所用 socket.receive.buffer.bytes 100 * 1024 SO_RCVBUFF 缓存大小,server 进行 socket 连接时所用 socket.request.max.bytes 100 * 1024 * 1024 server 允许的最大请求尺寸; 这将避免 server 溢出,它应该小于 Java heap size log.dirs /tmp/kafka-logs kafka 存放数据的路径。这个路径并不是唯一的,可以是多个,路径之间只需要使用逗号分隔即可;每当创建新 partition 时,都会选择在包含最少 partitions 的路径下进行。 num.partitions 1 如果创建 topic 时没有给出划分 partitions 个数,这个数字将是 topic 下 partitions 数目的默认数值。 num.recovery.threads.per.data.dir 1 每个数据目录用来日志恢复的线程数目 offsets.topic.replication.factor 1 topic 的 offset 的备份份数。建议设置更高的数字保证更高的可用性 transaction.state.log.replication.factor 1 事务主题的复制因子(设置更高以确保可用性) transaction.state.log.min.isr 1 覆盖事务主题的 min.insync.replicas 配置 log.flush.interval.messages 1000 log 文件“sync”到磁盘之前累积的消息条数。 log.flush.interval.ms 1000 用来控制”fsync“的时间间隔,如果消息量始终没有达到固化到磁盘的消息数,但是离上次磁盘同步的时间间隔达到阈值,也将触发磁盘同步。 log.retention.hours 168 日志文件保留小时数 log.retention.bytes -1 每个 topic 下每个 partition 保存数据的总量;注意,这是每个 partitions 的上限,因此这个数值乘以 partitions 的个数就是每个 topic 保存的数据总量。同时注意:如果 log.retention.hours 和 log.retention.bytes 都设置了,则超过了任何一个限制都会造成删除一个段文件。 log.segment.bytes 1024 * 1024 * 1024 topic partition 的日志存放在某个目录下诸多文件中,这些文件将 partition 的日志切分成一段一段的;这个属性就是每个文件的最大尺寸;当尺寸达到这个数值时,就会创建新文件。此设置可以由每个 topic 基础设置时进行覆盖。 log.retention.check.interval.ms 300000:5 分钟 检查日志分段文件的间隔时间,以确定是否文件属性是否到达删除要求。 zookeeper.connect null 该参数指明 broker 要连接的 ZooKeeper 集群的服务地址(包含端口号),没有默认值,且此参数为必填项。可以配置为 localhost:2181,如果 ZooKeeper 集群中有多个节点,则可以用逗号将每个节点隔开,类似于 localhost1:2181,localhost2:2181,localhost3:2181 这种格式。最佳的实践方式是再加一个 chroot 路径,这样既可以明确指明该 chroot 路径下的节点是为 Kafka 所用的,也可以实现多个 Kafka 集群复用一套 ZooKeeper 集群,这样可以节省更多的硬件资源。包含 chroot 路径的配置类似于 localhost1:2181,localhost2:2181,localhost3:2181/kafka 这种,如果不指定 chroot,那么默认使用 ZooKeeper 的根路径。 zookeeper.connection.timeout.ms 6000 客户端等待和 zookeeper 建立连接的最大时间 group.initial.rebalance.delay.ms 3 秒 这个参数的主要效果就是让 coordinator 推迟空消费组接收到成员加入请求后本应立即开启的 rebalance。在实际使用时,假设你预估你的所有 consumer 组成员加入需要在 10s 内完成,那么你就可以设置该参数=10000。 ","date":"2020-01-10","objectID":"/kafka-config/:1:0","tags":["Kafka","消息队列"],"title":"kafka 配置参数详解","uri":"/kafka-config/"},{"categories":["Kafka","消息队列"],"content":"生产者配置参数 属性 默认值 释义 boostrap.servers null 该参数用来指定生产者客户端连接 Kafka 集群所需的 broker 地址清单,具体的内容格式为 host1:port1,host2:port2,可以设置一个或多个地址,中间以逗号隔开,此参数的默认值为“”。注意这里并非需要所有的 broker 地址,因为生产者会从给定的 broker 里查找到其他 broker 的信息。不过建议至少要设置两个以上的 broker 地址信息,当其中任意一个宕机时,生产者仍然可以连接到 Kafka 集群上。 acks 1 producer 需要 server 接收到数据之后发出的确认接收的信号,此项配置就是指 procuder 需要多少个这样的确认信号。此配置实际上代表了数据备份的可用性。以下设置为常用选项:(1)acks=0: 设置为 0 表示 producer 不需要等待任何确认收到的信息。副本将立即加到 socket buffer 并认为已经发送。没有任何保障可以保证此种情况下 server 已经成功接收数据,同时重试配置不会发生作用(因为客户端不知道是否失败)回馈的 offset 会总是设置为-1;(2)acks=1: 这意味着至少要等待 leader 已经成功将数据写入本地 log,但是并没有等待所有 follower 是否成功写入。这种情况下,如果 follower 没有成功备份数据,而此时 leader 又挂掉,则消息会丢失。(3)acks=all: 这意味着 leader 需要等待所有备份都成功写入日志,这种策略会保证只要有一个备份存活就不会丢失数据。这是最强的保证。(4)其他的设置,例如 acks=2 也是可以的,这将需要给定的 acks 数量,但是这种策略一般很少用。 max.request.size 1028576:1M 请求的最大字节数。这也是对最大记录尺寸的有效覆盖。注意:server 具有自己对消息记录尺寸的覆盖,这些尺寸和这个设置不同。此项设置将会限制 producer 每次批量发送请求的数目,以防发出巨量的请求。 compression.type null producer 用于压缩数据的压缩类型。默认是无压缩。正确的选项值是 none、gzip、snappy。压缩最好用于批量处理,批量处理消息越多,压缩性能越好。 retries 0 设置大于 0 的值将使客户端重新发送任何数据,一旦这些数据发送失败。注意,这些重试与客户端接收到发送错误时的重试没有什么不同。允许重试将潜在的改变数据的顺序,如果这两个消息记录都是发送到同一个 partition,则第一个消息失败第二个发送成功,则第二条消息会比第一条消息出现要早。 retry.backoff.ms 100 在试图重试失败的 produce 请求之前的等待时间。避免陷入发送-失败的死循环中。 connections.max.idle.ms 540000ms 这个参数用来指定在多久之后关闭限制的连接 linger.ms 0 这个参数用来指定生产者发送 ProducerBatch 之前等待更多消息(ProducerRecord)加入 ProducerBatch 的时间,默认值为 0。生产者客户端会在 ProducerBatch 被填满或等待时间超过 linger.ms 值时发送出去。增大这个参数的值会增加消息的延迟,但是同时能提升一定的吞吐量。这个 linger.ms 参数与 TCP 协议中的 Nagle 算法有异曲同工之妙。 receive.buffer.bytes 32768 这个参数用来设置 Socket 接收消息缓冲区(SO_RECBUF)的大小,默认值为 32768(B),即 32KB。如果设置为-1,则使用操作系统的默认值。如果 Producer 与 Kafka 处于不同的机房,则可以适地调大这个参数值。 send.buffer.bytes 131072 这个参数用来设置 Socket 发送消息缓冲区(SO_SNDBUF)的大小,默认值为 131072(B),即 128KB。与 receive.buffer.bytes 参数一样,如果设置为-1,则使用操作系统的默认值。 buffer.memory 33554432 producer 可以用来缓存数据的内存大小。如果数据产生速度大于向 broker 发送的速度,producer 会阻塞或者抛出异常,以“block.on.buffer.full”来表明。 batch.size 16384 producer 将试图批处理消息记录,以减少请求次数。这将改善 client 与 server 之间的性能。这项配置控制默认的批量处理消息字节数。不会试图处理大于这个字节数的消息字节数。发送到 brokers 的请求将包含多个批量处理,其中会包含对每个 partition 的一个请求。较小的批量处理数值比较少用,并且可能降低吞吐量(0 则会仅用批量处理)。较大的批量处理数值将会浪费更多内存空间,这样就需要分配特定批量处理数值的内存大小。 client.id null 当向 server 发出请求时,这个字符串会发送给 server。目的是能够追踪请求源头,以此来允许 ip/port 许可列表之外的一些应用可以发送信息。这项应用可以设置任意字符串,因为没有任何功能性的目的,除了记录和跟踪 partitioner.class kafka.producer.DefaultPartitioner partitioner 类,用于在 subtopics 之间划分消息。默认 partitioner 基于 key 的 hash 表 request.timeout.ms 30000 这个参数用来配置 Producer 等待请求响应的最长时间,默认值为 30000(ms)。请求超时之后可以选择进行重试。注意这个参数需要比 broker 端参数 replica.lag.time.max.ms 的值要大,这样可以减少因客户端重试而引起的消息重复的概率。 max.block.ms 60000 用来控制 KafkaProducer 中 send()方法和 partitionsFor()方法的阻塞时间。当生产者的发送缓冲区已满,或者没有可用的元数据时,这些方法就会阻塞 timeout.ms 30000 此配置选项控制 server 等待来自 followers 的确认的最大时间。如果确认的请求数目在此时间内没有实现,则会返回一个错误。这个超时限制是以 server 端度量的,没有包含请求的网络延迟 block.on.buffer.full true 当我们内存缓存用尽时,必须停止接收新消息记录或者抛出错误。默认情况下,这个设置为真,然而某些阻塞可能不值得期待,因此立即抛出错误更好。设置为 false 则会这样:如果记录已经发送同时缓存已满,producer 会抛出一个异常错误:BufferExhaustedException metadata.fetch.timeout.ms 60000 是指我们所获取的一些元素据的第一个时间数据。元素据包含:topic,host,partitions。此项配置是指当等待元素据 fetch 成功完成所需要的时间,否则会跑出异常给客户端。 metadata.max.age.ms 300000 以微秒为单位的时间,是在我们强制更新 metadata 的时间间隔。即使我们没有看到任何 partition leadership 改变。 metric.reporters [] 类的列表,用于衡量指标。实现 MetricReporter 接口,将允许增加一些类,这些类在新的衡量指标产生时就会改变。JmxReporter 总会包含用于注册 JMX 统计 metrics.num.samples 2 用于维护 metrics 的样本数 metrics.sample.window.ms 30000 metrics 系统维护可配置的样本数量,在一个可修正的 window size。这项配置配置了窗口大小,例如。我们可能在 30s 的期间维护两个样本。当一个窗口推出后,我们会擦除并重写最老的窗口 recoonect.backoff.ms 10 连接失败时,当我们重新连接时的等待时间。这避免了客户端反复重连 ","date":"2020-01-10","objectID":"/kafka-config/:2:0","tags":["Kafka","消息队列"],"title":"kafka 配置参数详解","uri":"/kafka-config/"},{"categories":["Kafka","消息队列"],"content":"消费者配置参数 属性 默认值 释义 bootstrap.servers null 该参数的释义和生产者客户端 KafkaProducer 中的相同,用来 指 定 连 接 Kafka 集 群 所 需 的 broker 地 址 清 单,具 体 内 容 形 式 为 host1:port1,host2:post,可以设置一个或多个地址,中间用逗号隔开,此参数的默认值为“”。注意这里并非需要设置集群中全部的 broker 地址,消费者会从现有的配置中查找到全部的 Kafka 集群成员。这里设置两个以上的 broker 地址信息,当其中任意一个宕机时,消费者仍然可以连接到 Kafka 集群上。 group.id null 消费者隶属的消费组的名称,默认值为“”。如果设置为空,则会报出异常:Exception in thread “main\"org.apache.kafka.common.errors.InvalidGroupIdException:The configured groupId is invalid。一般而言,这个参数需要设置成具有一定的业务意义的名称。 key.deserializer null 用来指定消息中 key 所需反序列化操作的反序列化器,无默认值。 value.deserializer null 用来指定消息中 value 所需反序列化操作的反序列化器,无默认值。 client.id null 这个参数用来设定 KafkaConsumer 对应的客户端 id,默认值也为“”。如果客户端不设置,则 KafkaConsumer 会自动生成一个非空字符串,内容形式如“consumer-1”“consumer-2”,即字符串“consumer-”与数字的拼接。 consumer.id null 不需要设置,一般自动产生 socket.timeout.ms 30*100 网络请求的超时限制。真实的超时限制是 max.fetch.wait+socket.timeout.ms socket.receive.buffer.bytes 64*1024 socket 用于接收网络请求的缓存大小 fetch.message.max.bytes 1024*1024 每次 fetch 请求中,针对每次 fetch 消息的最大字节数。这些字节将会督导用于每个 partition 的内存中,因此,此设置将会控制 consumer 所使用的 memory 大小。这个 fetch 请求尺寸必须至少和 server 允许的最大消息尺寸相等,否则,producer 可能发送的消息尺寸大于 consumer 所能消耗的尺寸。 num.consumer.fetchers 1 用于 fetch 数据的 fetcher 线程数 auto.commit.enable true 如果为真,consumer 所 fetch 的消息的 offset 将会自动的同步到 zookeeper。这项提交的 offset 将在进程挂掉时,由新的 consumer 使用 auto.commit.interval.ms 60*1000 consumer 向 zookeeper 提交 offset 的频率,单位是秒 queued.max.message.chunks 2 用于缓存消息的最大数目,以供 consumption。每个 chunk 必须和 fetch.message.max.bytes 相同 rebalance.max.retries 4 当新的 consumer 加入到 consumer group 时,consumers 集合试图重新平衡分配到每个 consumer 的 partitions 数目。如果 consumers 集合改变了,当分配正在执行时,这个重新平衡会失败并重入 fetch.min.bytes 1 每次 fetch 请求时,server 应该返回的最小字节数。如果没有足够的数据返回,请求会等待,直到足够的数据才会返回。 fetch.wait.max.ms 100 如果没有足够的数据能够满足 fetch.min.bytes,则此项配置是指在应答 fetch 请求之前,server 会阻塞的最大时间。 rebalance.backoff.ms 2000 在重试 reblance 之前 backoff 时间 refresh.leader.backoff.ms 200 在试图确定某个 partition 的 leader 是否失去他的 leader 地位之前,需要等待的 backoff 时间 auto.offset.reset largest zookeeper 中没有初始化的 offset 时,如果 offset 是以下值的回应:smallest:自动复位 offset 为 smallest 的 offsetlargest:自动复位 offset 为 largest 的 offsetanything else:向 consumer 抛出异常 consumer.timeout.ms -1 如果没有消息可用,即使等待特定的时间之后也没有,则抛出超时异常 exclude.internal.topics true 是否将内部 topics 的消息暴露给 consumer paritition.assignment.strategy range 选择向 consumer 流分配 partitions 的策略,可选值:range,roundrobin client.id group id value 是用户特定的字符串,用来在每次请求中帮助跟踪调用。它应该可以逻辑上确认产生这个请求的应用 zookeeper.session.timeout.ms 6000 zookeeper 会话的超时限制。如果 consumer 在这段时间内没有向 zookeeper 发送心跳信息,则它会被认为挂掉了,并且 reblance 将会产生 zookeeper.connection.timeout.ms 6000 客户端在建立通 zookeeper 连接中的最大等待时间 zookeeper.sync.time.ms 2000 ZK follower 可以落后 ZK leader 的最大时间 offsets.storage zookeeper 用于存放 offsets 的地点: zookeeper 或者 kafka offset.channel.backoff.ms 1000 重新连接 offsets channel 或者是重试失败的 offset 的 fetch/commit 请求的 backoff 时间 offsets.channel.socket.timeout.ms 10000 当读取 offset 的 fetch/commit 请求回应的 socket 超时限制。此超时限制是被 consumerMetadata 请求用来请求 offset 管理 offsets.commit.max.retries 5 重试 offset commit 的次数。这个重试只应用于 offset commits 在 shut-down 之间。他 dual.commit.enabled true 如果使用“kafka”作为 offsets.storage,你可以二次提交 offset 到 zookeeper(还有一次是提交到 kafka)。在 zookeeper-based 的 offset storage 到 kafka-based 的 offset storage 迁移时,这是必须的。对任意给定的 consumer group 来说,比较安全的建议是当完成迁移之后就关闭这个选项 partition.assignment.strategy range 在“range”和“roundrobin”策略之间选择一种作为分配 partitions 给 consumer 数据流的策略; 循环的 partition 分配器分配所有可用的 partitions 以及所有可用 consumer 线程。它会将 partition 循环的分配到 consumer 线程上。如果所有 consumer 实例的订阅都是确定的,则 partitions 的划分是确定的分布。循环分配策略只有在以下条件满足时才可以:(1)每个 topic 在每个 consumer 实力上都有同样数量的数据流。(2)订阅的 topic 的集合对于 consumer group 中每个 consumer 实例来说都是确定的。 ","date":"2020-01-10","objectID":"/kafka-config/:3:0","tags":["Kafka","消息队列"],"title":"kafka 配置参数详解","uri":"/kafka-config/"},{"categories":["Kafka","消息队列"],"content":"参考 《深入理解 Kafka:核心设计与实践原理》 朱忠华 ","date":"2020-01-10","objectID":"/kafka-config/:4:0","tags":["Kafka","消息队列"],"title":"kafka 配置参数详解","uri":"/kafka-config/"},{"categories":["Kafka","消息队列"],"content":"架构 一个典型的 Kafka 体系架构包括若干 Producer、若干 Broker、若干 Consumer,以及一个 ZooKeeper 集群。其中 ZooKeeper 是 Kafka 用来负责集群元数据的管理、控制器的选举等操作的。Producer 将消息发送到 Broker,Broker 负责将收到的消息存储到磁盘中,而 Consumer 负责从 Broker 订阅并消费消息。 Producer:生产者,也就是发送消息的一方。生产者负责创建消息,然后将其投递到 Kafka 中。 Consumer:消费者,也就是接收消息的一方。消费者连接到 Kafka 上并接收消息,进而进行相应的业务逻辑处理。 Broker:服务代理节点。对于 Kafka 而言,Broker 可以简单地看作一个独立的 Kafka 服务节点或 Kafka 服务实例。大多数情况下也可以将 Broker 看作一台 Kafka 服务器,前提是这台服务器上只部署了一个 Kafka 实例。一个或多个 Broker 组成了一个 Kafka 集群。一般而言,我们更习惯使用首字母小写的 broker 来表示服务代理节点。 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:1:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"基本概念 主题(Topic):Kafka 中的消息以主题为单位进行归类,生产者负责将消息发送到特定的主题(发送到 Kafka 集群中的每一条消息都要指定一个主题),而消费者负责订阅主题并进行消费。一个主题可以横跨多个 broker,以此来提供比单个 broker 更强大的性能。 分区(Partition):一个主题可以细分为多个分区,一个分区只属于单个主题,很多时候也会把分区称为主题分区(Topic-Partition)。同一主题下的不同分区包含的消息是不同的,分区在存储层面可以看作一个可追加的日志(Log)文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)。 偏移量(offset):offset 是消息在分区中的唯一标识,Kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说,Kafka 保证的是分区有序而不是主题有序。 副本(Replica):Kafka 为分区引入了多副本(Replica)机制,通过增加副本数量可以提升容灾能力。副本之间是“一主多从”的关系,其中 leader 副本负责处理读写请求,follower 副本只负责与 leader 副本的消息同步。副本处于不同的 broker 中,当 leader 副本出现故障时,从 follower 副本中重新选举新的 leader 副本对外提供服务。 AR(Assigned Replicas):所有副本。 ISR(In-Sync Replicas):所有与 leader 副本保持一定程度同步的副本(包括 leader 副本在内)组成 ISR。只有在 ISR 集合中的副本才有资格被选举为新的 leader(不过这个原则也可以通过修改相应的参数配置来改变)。 OSR(Out-of-Sync Replicas):与 leader 副本同步滞后过多的副本(不包括 leader 副本)组成 OSR。 HW:是 High Watermark 的缩写,俗称高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个 offset 之前的消息。 LEO 是:Log End Offset 的缩写,它标识当前日志文件中下一条待写入消息的 offset。LEO 的大小相当于当前日志分区中最后一条消息的 offset 值加 1。分区 ISR 集合中的每个副本都会维护自身的 LEO,而 ISR 集合中最小的 LEO 即为分区的 HW。 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:2:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"前置条件 需要先安装 Java 环境 需要先安装 ZooKeeper 并启动 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:3:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"下载 打开链接 https://kafka.apache.org/downloads 下载 Kafka 的 tgz 安装包 然后解压并进入 kafka 路径下 cd /usr/local wget https://downloads.apache.org/kafka/3.1.2/kafka_2.12-3.1.2.tgz tar -zxvf kafka_2.12-3.1.2.tgz cd kafka_2.12-3.1.2 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:4:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"启动 配置文件放在主目录下 config/server.properties,主要需要注意的配置项如下 # The id of the broker. This must be set to a unique integer for each broker. broker.id=1 # The address the socket server listens on. It will get the value returned from # java.net.InetAddress.getCanonicalHostName() if not configured. # FORMAT: # listeners = listener_name://host_name:port # EXAMPLE: # listeners = PLAINTEXT://your.host.name:9092 listeners=PLAINTEXT://127.0.0.1:9092 # Zookeeper connection string (see zookeeper docs for details). # This is a comma separated host:port pairs, each corresponding to a zk # server. e.g. \"127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002\". # You can also append an optional chroot string to the urls to specify the # root directory for all kafka znodes. zookeeper.connect=127.0.0.1:2181/kafka # The maximum size of a request that the socket server will accept (protection against OOM) socket.request.max.bytes=104857600 注意:确保集群中每个 broker 的 broker.id 配置参数的值不一样,以及 listeners 配置参数也需要修改为与 broker 对应的 IP 地址或域名,之后就可以各自启动服务。 修改好配置之后,启动命令如下(后台运行) bin/kafka-server-start.sh -daemon config/server.properties ","date":"2020-01-08","objectID":"/kafka-quick-tour/:5:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"Topic 操作 kafka-topics.sh 脚本有 5 种指令类型:create、list、describe、alter 和 delete。 kafka-configs.sh 脚本是专门用来对配置进行操作的,这里的操作是指在运行状态下修改原有的配置,如此可以达到动态变更的目的。 kafka-reassign-partitions.sh 脚本来执行分区重分配的工作 创建 Topic bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test 查看 Topic 列表 bin/kafka-topics.sh --list --bootstrap-server localhost:9092 查看 Topic 详细信息 bin/kafka-topics.sh --describe --bootstrap-server localhost:9092 --topic test 修改 Topic # 分区数只能增加不能减小 bin/kafka-topics.sh --alter --bootstrap-server localhost:9092 --partitions 2 --topic test 删除 Topic bin/kafka-topics.sh --delete --bootstrap-server localhost:9092 --topic test ","date":"2020-01-08","objectID":"/kafka-quick-tour/:6:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"消息发送 # 输入命令后,会出现消息输入提示符,输入消息后按回车可发送消息 bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test ","date":"2020-01-08","objectID":"/kafka-quick-tour/:7:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"消息消费 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning ","date":"2020-01-08","objectID":"/kafka-quick-tour/:8:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"消息防丢 Consumer 使用拉(Pull)模式从服务端拉取消息,并且保存消费的具体位置,当消费者宕机后恢复上线时可以根据之前保存的消费位置重新拉取需要的消息进行消费,这样就不会造成消息丢失。 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:9:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"复制机制 Kafka 的复制机制既不是完全的同步复制,也不是单纯的异步复制。事实上,同步复制要求所有能工作的 follower 副本都复制完,这条消息才会被确认为已成功提交,这种复制方式极大地影响了性能。而在异步复制方式下,follower 副本异步地从 leader 副本中复制数据,数据只要被 leader 副本写入就被认为已经成功提交。在这种情况下,如果 follower 副本都还没有复制完而落后于 leader 副本,突然 leader 副本宕机,则会造成数据丢失。Kafka 使用的这种 ISR 的方式则有效地权衡了数据可靠性和性能之间的关系。 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:10:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"Kafka 为何如此之快 Kafka 实现了零拷贝原理来快速移动数据,避免了内核之间的切换。Kafka 可以将数据记录分批发送,从生产者到文件系统(Kafka 主题日志)到消费者,可以端到端的查看这些批次的数据。 批处理能够进行更有效的数据压缩并减少 I/O 延迟,Kafka 采取顺序写入磁盘的方式,避免了随机磁盘寻址的浪费。 总结一下其实就是四个要点 顺序读写 零拷贝 消息压缩 分批发送 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:11:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["Kafka","消息队列"],"content":"参考 《深入理解 Kafka:核心设计与实践原理》 朱忠华 ","date":"2020-01-08","objectID":"/kafka-quick-tour/:12:0","tags":["Kafka","消息队列"],"title":"kafka 快速入门","uri":"/kafka-quick-tour/"},{"categories":["markdown"],"content":"Markdown 简介 Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档。 Markdown 语言在 2004 由约翰·格鲁伯(英语:John Gruber)创建。 Markdown 编写的文档可以导出 HTML 、Word、图像、PDF、Epub 等多种格式的文档。 Markdown 编写的文档后缀为 .md, .markdown。 ","date":"2019-08-05","objectID":"/markdown/:1:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 应用 Markdown 能被使用来撰写电子书,如:Gitbook。 当前许多网站都广泛使用 Markdown 来撰写帮助文档或是用于论坛上发表消息。例如:GitHub、简书、reddit、Diaspora、Stack Exchange、OpenStreetMap 、SourceForge 等。 ","date":"2019-08-05","objectID":"/markdown/:2:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 编辑器 Typora 编辑器:Typora 支持 MacOS 、Windows、Linux 平台,且包含多种主题,编辑后直接渲染出效果。支持导出 HTML、PDF、Word、图片等多种类型文件。 官网:https://typora.io/ 在线编辑器 其他的文本编辑器(例:VSCODE + 插件),IDE(idea + 插件) ","date":"2019-08-05","objectID":"/markdown/:3:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 标题 使用 # 号可表示 1-6 级标题,一级标题对应一个 # 号,二级标题对应两个 # 号,以此类推。 # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题 ","date":"2019-08-05","objectID":"/markdown/:4:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 换行和段落 段落的换行是使用两个以上空格加上回车 在段落后面使用一个空行来表示重新开始一个段落 段落段落段落段落段落段落段落段落段落段落段落段落段落段落段落段落段落段落段落段落 段落 段落 ","date":"2019-08-05","objectID":"/markdown/:5:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 字体样式 _斜体文本_ _斜体文本_ **粗体文本** **粗体文本** **_粗斜体文本_** **_粗斜体文本_** 斜体文本 斜体文本 粗体文本 粗体文本 粗斜体文本 粗斜体文本 ","date":"2019-08-05","objectID":"/markdown/:6:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 分隔线 你可以在一行中用三个以上的星号、减号、下划线来建立一个分隔线,行内不能有其他东西。你也可以在星号或是减号中间插入空格。 *** --- ___ ","date":"2019-08-05","objectID":"/markdown/:7:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 删除线,两边加~~ ~~删除线~~ 删除线 ","date":"2019-08-05","objectID":"/markdown/:8:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 下划线 \u003cu\u003e下划线\u003c/u\u003e 下划线 ","date":"2019-08-05","objectID":"/markdown/:9:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 列表 1 无序列表使用星号(*)、加号(+)或是减号(-)作为列表标记,这些标记后面要添加一个空格 - 第一项 - 第二项 * 第一项 * 第二项 - 第一项 - 第二项 第一项 第二项 第一项 第二项 第一项 第二项 2 有序列表使用数字并加上 . 号 1. 第一项 2. 第二项 3. 第三项 第一项 第二项 第三项 3 列表嵌套只需在子列表中的选项前面添加四个空格即可: 1. 第一项: - 第一项嵌套的第一个元素 - 第一项嵌套的第二个元素 2. 第二项: - 第二项嵌套的第一个元素 - 第二项嵌套的第二个元素 第一项: 第一项嵌套的第一个元素 第一项嵌套的第二个元素 第二项: 第二项嵌套的第一个元素 第二项嵌套的第二个元素 ","date":"2019-08-05","objectID":"/markdown/:10:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 区块 Markdown 区块引用是在段落开头使用 \u003e 符号 ,然后后面紧跟一个空格符号: \u003e 区块引用 \u003e 区块引用 \u003e 区块引用 \u003e 最外层 \u003e \u003e \u003e 第一层嵌套 \u003e \u003e \u003e \u003e \u003e 第二层嵌套 区块引用 区块引用 区块引用 最外层 第一层嵌套 第二层嵌套 ","date":"2019-08-05","objectID":"/markdown/:11:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 代码 段落上的一个函数或片段的代码可以用反引号把它包起来(`) `printf()`函数 printf()函数 你也可以用 ``` 包裹一段代码,并指定一种语言(也可以不指定) $(document).ready(function () { alert(\"Hello world!\"); }); ","date":"2019-08-05","objectID":"/markdown/:12:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 链接 [百度](https://baidu.com) \u003chttps://baidu.com\u003e 百度 https://baidu.com 这个链接用 a 作为网址变量 [百度][a],然后在文档的结尾为变量赋值(网址) [a]: http://www.baidu.com/ 这个链接用 a 作为网址变量 百度,然后在文档的结尾为变量赋值(网址) ","date":"2019-08-05","objectID":"/markdown/:13:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 图片 ![alt 属性文本](https://demo.png \"可选标题\") 这个链接用 mark 作为网址变量 ![Markdown][mark] 然后在文档的结尾为变量赋值(网址) [mark]: https://demo.png \u003cimg src=\"https://demo.png\" width=\"50%\" /\u003e ","date":"2019-08-05","objectID":"/markdown/:14:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 表格 Markdown 制作表格使用 | 来分隔不同的单元格,使用 - 来分隔表头和其他行。 | 表头 | 表头 | | ------ | ------ | | 单元格 | 单元格 | | 单元格 | 单元格 | 我们可以设置表格的对齐方式: -: 设置内容和标题栏居右对齐。 :- 设置内容和标题栏居左对齐。 :-: 设置内容和标题栏居中对齐。 | 左对齐左对齐 | 右对齐右对齐 | 居中对齐居中对齐 | | :----------- | -----------: | :--------------: | | 单元格 | 单元格 | 单元格 | | 单元格 | 单元格 | 单元格 | 表头 表头 单元格 单元格 单元格 单元格 我们可以设置表格的对齐方式: -: 设置内容和标题栏居右对齐。 :- 设置内容和标题栏居左对齐。 :-: 设置内容和标题栏居中对齐。 左对齐左对齐 右对齐右对齐 居中对齐居中对齐 单元格 单元格 单元格 单元格 单元格 单元格 ","date":"2019-08-05","objectID":"/markdown/:15:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["markdown"],"content":"Markdown 高级技巧 支持的 HTML 元素 不在 Markdown 涵盖范围之内的标签,都可以直接在文档里面用 HTML 撰写。 目前支持的 HTML 元素有:\u003ckbd\u003e \u003cb\u003e \u003ci\u003e \u003cem\u003e \u003csup\u003e \u003csub\u003e \u003cbr\u003e \u003chr\u003e \u003cp\u003e \u003cimg\u003e \u003cdiv\u003e \u003ch1\u003e \u003cvideo\u003e等 ,如: 使用 \u003ckbd\u003eCtrl\u003c/kbd\u003e+\u003ckbd\u003eAlt\u003c/kbd\u003e+\u003ckbd\u003eDel\u003c/kbd\u003e 重启电脑 视频 \u003cvideo controls width=\"250\"\u003e \u003csource src=\"/media/examples/flower.mp4\" type=\"video/mp4\"\u003e Sorry, your browser doesn't support embedded videos. \u003c/video\u003e 使用 Ctrl+Alt+Del 重启电脑 视频 Sorry, your browser doesn't support embedded videos.\r转义 Markdown 支持以下这些符号前面加上反斜杠来帮助插入普通的符号 \\\\ 反斜线 \\` 反引号 \\* 星号 \\_ 下划线 \\{\\} 花括号 \\[\\] 方括号 \\(\\) 小括号 \\# 井字号 \\+ 加号 \\- 减号 \\. 英文句点 \\! 感叹号 \\``` \\``` \\ 反斜线 ` 反引号 * 星号 _ 下划线 {} 花括号 [] 方括号 () 小括号 # 井字号 + 加号 - 减号 . 英文句点 ! 感叹号 ``` ``` ","date":"2019-08-05","objectID":"/markdown/:16:0","tags":["markdown","vi"],"title":"Markdown指北","uri":"/markdown/"},{"categories":["Linux"],"content":"1. vi 和 vim 的区别 vim 是进阶版的 vi vim 不但可以用不同颜色显示文字内容 vim 能够进行如 shell script, C program 等程序编辑功能, 你可以将 vim 作为一种程序编辑器 ","date":"2019-06-05","objectID":"/vi-editor/:1:0","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["Linux"],"content":"2. vi 编辑器的三种模式 ","date":"2019-06-05","objectID":"/vi-editor/:2:0","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["Linux"],"content":"2.1. 一般指令模式 使用 vi[m] filename 进入一般指令模式 在这个模式中, 你可以使用“上下左右”按键来移动光标,你可以使用“删除字符”或“删除整列”来处理文件内容, 也可以使用“复制、贴上”来处理你的文件数据。 快捷键 h(←) j(↓) k(↑) l(→),n+[hjkl↑↓←→],n+Enter 向下移动 n 行 Ctrl+f 向下一页,Ctrl+b 向上一页,Ctrl+d 向下半页,Ctrl+u 向上半页 n+space 光标处向后移动 n 个字符 0/Home 键移至行首,$/End 键移至行尾 H 移至屏幕第一行第一个字符,M 移至屏幕中央,L 移至屏幕最后一行 G 移至文件最后一行,nG 移至文件第 n 行,gg 移至文件第一行,相当于 1G n+backspace 键退格几个字符 n+dd 删除 n 行,d1G 删除光标至第一行,dG 删除光标至最后一行,d$删除光标至行尾,d0 删除光标至行首 n+yy 向下复制 n 行,y1G 复制光标至第一行,yG 复制光标至最后一行,y$复制光标至行尾,y0 复制光标至行首 p 光标处下一行或下一个字符粘贴,P 光标处上一行或上一个字符粘贴 J 将光标所在列与下一列的数据结合成同一列 u 撤销,Ctrl+r 恢复,.(小数点)重复前一个动作 ","date":"2019-06-05","objectID":"/vi-editor/:2:1","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["Linux"],"content":"2.2 编辑模式 i 光标处插入,I 该行第一个非空白字符处插入 a 光标所在的下一个字符处插入,A 该行最后一个字符处插入 o 光标处向下插入空行,O 光标处向上插入空行 r 只会取代光标处的那一个字符,R 会一直取代光标所在的文字,直到按下 ESC 为止 ESC 退出编辑模式 ","date":"2019-06-05","objectID":"/vi-editor/:2:2","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["Linux"],"content":"2.3. 命令行命令模式 在一般模式中,输入[: / ? ]三个中的任何一个按钮,就可以将光标移动到最下面那一列 提供查找、存盘、替换、离开 vi 、显示行号等等的 快捷键 /word 光标之下查找,?word 光标之上查找。n 重复前一个查找,N 与 n 相反 :n1,n2s/word1/word2/g 替换 n1 与 n2 行之间的 word1 为 word2 :1,$s/word1/word2/g 替换所有行 :1,$s/word1/word2/gc 二次确认替换 :w 写入,:w!强制写入 :q 退出,:q!强制退出 :wq 保存后离开,:wq!强制保存后离开 ZZ 文件无修改不保存退出,文件有修改保存后退出 :w filename 另存为,:r filename 读入文件数据 :n1,n2 w filename 将 n1 到 n2 行另存为 :! [shell command] 暂时离开,可接命令 :set nu 显示行号,:set nonu 取消行号 ","date":"2019-06-05","objectID":"/vi-editor/:2:3","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["Linux"],"content":"3. 区块选择 v 字符选择,V 行选择 Ctrl+v 方块选择 y 将选中地方复制 d 将选中地方删除 p 将选中地方在光标处粘贴 ","date":"2019-06-05","objectID":"/vi-editor/:3:0","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["Linux"],"content":"4. 多窗口编辑 :sp [filename] 打开一个新窗口 Ctrl+w+[j/↓]移动到下一个窗口 Ctrl+w+[k/↑]移动到下一个窗口 Ctrl+w+q 关闭当前窗口,同:q ","date":"2019-06-05","objectID":"/vi-editor/:4:0","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["Linux"],"content":"5. 多文件编辑 vi[m] filename1 filename2… 编辑多个文件 :n 编辑下一个文件,:N 编辑上一个文件 :files 列出所有编辑的文件 ","date":"2019-06-05","objectID":"/vi-editor/:5:0","tags":["Linux","vi"],"title":"vi编辑器","uri":"/vi-editor/"},{"categories":["微服务"],"content":"简介 gRPC 是一个高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers) 序列化协议开发,且支持众多开发语言。 gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等等。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。 ","date":"2019-02-18","objectID":"/grpc/:1:0","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"RPC 原理 服务调用方(client)以本地调用方式调用服务; client stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; client stub 找到服务地址,并将消息发送到服务端; server 端接收到消息; server stub 收到消息后进行解码; server stub 根据解码结果调用本地的服务; 本地服务执行并将结果返回给 server stub; server stub 将返回结果打包成能够进行网络传输的消息体; 按地址将消息发送至调用方; client 端接收到消息; client stub 收到消息并进行解码; 调用方得到最终结果。 ","date":"2019-02-18","objectID":"/grpc/:2:0","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"环境准备 首先需要安装 protoc 和 protoc-gen-go,详见Protocol Buffers。 然后安装 protoc-gen-go-grpc。 # 安装好之后将 $GOPATH/bin 添加到环境变量中 # export PATH=\"$PATH:$(go env GOPATH)/bin\" go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest ","date":"2019-02-18","objectID":"/grpc/:3:0","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"helloworld 案例 ","date":"2019-02-18","objectID":"/grpc/:4:0","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"定义服务 创建 helloworld.proto 文件 syntax = \"proto3\"; //name 表示生成的go文件所属的包名 option go_package=\"./;proto\"; // 定义包名 package proto; // 定义Greeter服务 service Greeter { // 定义SayHello方法,接受HelloRequest消息, 并返回HelloReply消息 rpc SayHello (HelloRequest) returns (HelloReply) {} } // 定义HelloRequest消息 message HelloRequest { string name = 1; } // 定义HelloReply消息 message HelloReply { string message = 1; } ","date":"2019-02-18","objectID":"/grpc/:4:1","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"生成代码 protoc --go_out=. --go-grpc_out=. helloworld.proto ","date":"2019-02-18","objectID":"/grpc/:4:2","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"实现服务端代码 创建文件:helloworld/server.go package main import ( \"log\" \"net\" \"golang.org/x/net/context\" // 导入grpc包 \"google.golang.org/grpc\" \"google.golang.org/grpc/reflection\" // 导入刚才我们生成的代码所在的proto包。 pb \"grpc-demo/proto\" ) // 定义server,用来实现proto文件,里面实现的Greeter服务里面的接口 type server struct { pb.UnimplementedGreeterServer } // 实现SayHello接口 // 第一个参数是上下文参数,所有接口默认都要必填 // 第二个参数是我们定义的HelloRequest消息 // 返回值是我们定义的HelloReply消息,error返回值也是必须的。 func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { // 创建一个HelloReply消息,设置Message字段,然后直接返回。 return \u0026pb.HelloReply{Message: \"Hello \" + in.Name}, nil } func main() { // 监听127.0.0.1:50051地址 lis, err := net.Listen(\"tcp\", \"127.0.0.1:50051\") if err != nil { log.Fatalf(\"failed to listen: %v\", err) } // 实例化grpc服务端 s := grpc.NewServer() // 注册Greeter服务 pb.RegisterGreeterServer(s, \u0026server{}) // 往grpc服务端注册反射服务 reflection.Register(s) // 启动grpc服务 if err := s.Serve(lis); err != nil { log.Fatalf(\"failed to serve: %v\", err) } } 运行: # 切换到项目根目录,运行命令 go run server.go ","date":"2019-02-18","objectID":"/grpc/:4:3","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"实现客户端代码 创建文件:helloworld/client.go package main import ( \"log\" \"time\" \"golang.org/x/net/context\" // 导入grpc包 \"google.golang.org/grpc\" // 导入刚才我们生成的代码所在的proto包。 pb \"grpc-demo/proto\" ) func main() { // 连接grpc服务器 conn, err := grpc.Dial(\"localhost:50051\", grpc.WithInsecure()) if err != nil { log.Fatalf(\"did not connect: %v\", err) } // 延迟关闭连接 defer conn.Close() // 初始化Greeter服务客户端 c := pb.NewGreeterClient(conn) // 初始化上下文,设置请求超时时间为1秒 ctx, cancel := context.WithTimeout(context.Background(), time.Second) // 延迟关闭请求会话 defer cancel() // 调用SayHello接口,发送一条消息 r, err := c.SayHello(ctx, \u0026pb.HelloRequest{Name: \"world\"}) if err != nil { log.Fatalf(\"could not greet: %v\", err) } // 打印服务的返回的消息 log.Printf(\"Greeting: %s\", r.Message) } 运行: # 切换到项目根目录,运行命令 go run client.go ","date":"2019-02-18","objectID":"/grpc/:4:4","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"gRPC 的服务类型 gRPC 主要有 4 种请求和响应模式,分别是简单模式(Simple RPC)、服务端流式(Server-side streaming RPC)、客户端流式(Client-side streaming RPC)、和双向流式(Bidirectional streaming RPC)。 简单模式(Simple RPC):客户端发起请求并等待服务端响应。 服务端流式(Server-side streaming RPC):客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。 客户端流式(Client-side streaming RPC):与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。 双向流式(Bidirectional streaming RPC):双方使用读写流去发送一个消息序列,两个流独立操作,双方可以同时发送和同时接收。 在服务定义的请求参数前面加 stream 表示客户端流式 RPC,在返回值前面加 stream 表示服务端流式,都加表示双向流式 //双向流式rpc,只要在请求的参数前和响应参数前都添加stream即可 service Stream { // 双向流式 rpc ,同时在请求参数前和响应参数前加上 stream rpc Conversations(stream StreamRequest) returns(stream StreamResponse){}; } ","date":"2019-02-18","objectID":"/grpc/:5:0","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"参考 https://grpc.io/docs/languages/go/quickstart/ https://doc.oschina.net/grpc?t=58008 https://www.jianshu.com/p/17d02fe0841a https://zhuanlan.zhihu.com/p/411317961 https://www.liwenzhou.com/posts/Go/rpc/ ","date":"2019-02-18","objectID":"/grpc/:6:0","tags":["gRPC"],"title":"Go 使用 gRPC","uri":"/grpc/"},{"categories":["微服务"],"content":"基本概念 aggregate (聚合):聚合就是一组相关对象的集合,我们把聚合作为数据修改的单元。外部对象只能引用聚合中的一个成员,我们把它称为根。在聚合的边界之内应用一组一致的规则。 bounded context(限界上下文):特定模型的限界应用。限界上下文使团队所有成员能够明确地知道什么必须保持一致,什么必须独立开发。 context(上下文):一个单词或句子出现的环境,它决定了其含义。参见 bounded context 。 context map(上下文图):项目所涉及的限界上下文以及它们与模型之间的关系的一种表示。 core domain(核心领域):模型的独特部分,是用户的核心目标,它使得应用程序与众不同并且有价值。 deep model(深层模型):领域专家们最关心的问题以及与这些问题最相关的知识的清晰表示。深层模型不停留在领域的表层和粗浅的理解上。 distillation(精炼):精炼是把一堆混杂在一起的组件分开的过程,从中提取出最重要的内容,使得它更有价值,也更有用。在软件设计中,精炼就是对模型中的关键方面进行抽象,或者是对大系统进行划分,从而把核心领域提取出来。 领域(domain):知识、影响或活动的范围。 领域专家(domain expert):软件项目的成员之一,精通的是软件的应用领域而不是软件开发。并非软件的任何使用者都是领域专家,领域专家需要具备深厚的专业知识。 domain layer(领域层):在分层架构中负责领域逻辑的那部分设计和实现。领域层是在软件中用来表示领域模型的地方。 entity(实体):一种对象,它不是由属性来定义的,而是通过一连串的连续事件和标识定义的。 factory(工厂):一种封装机制,把复杂的创建逻辑封装起来,并为客户抽象出所创建的对象的类型。 implicit concept(隐式概念):一种为了理解模型和设计的意义而必不可少的概念,但它从未被提及。 layered architecture(分层架构):一种用于分离软件系统关注点的技术,它把领域层与其他层分开。 model(模型):一个抽象的系统,描述了领域的所选方面,可用于解决与该领域有关的问题。 model driven design (模型驱动的设计):软件元素的某个子集严格对应于模型的元素。也代表一种合作开发模型和实现以便互相保持一致的过程。 repository (存储库):一种把存储、检索和搜索行为封装起来的机制,它类似于一个对象集合。 service (服务):一种作为接口提供的操作,它在模型中是独立的,没有封装的状态。 strategic design(战略设计):一种针对系统整体的建模和设计决策。这样的决策影响整个项目,而且必须由团队来制定。 ubiquitous language (通用语言):围绕领域模型建立的一种语言,团队所有成员都使用这种语言把团队的所有活动与软件联系起来。 value object (值对象):一种描述了某种特征或属性但没有概念标识的对象。 ","date":"2019-02-16","objectID":"/ddd/:1:0","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"领域建模 模型是对领域的抽象,建模是针对特定问题建立领域的合理模型 领域 -\u003e 模型(领域知识/业务需求) -\u003e 代码(技术实现) 模型分解(领域划分/限界上下文)解决面向对象的复杂性(一个对象太臃肿) 复杂性来源于业务本身的复杂性和技术引入的额外复杂性 领域驱动设计通过分解模型和模型驱动设计控制复杂性 ","date":"2019-02-16","objectID":"/ddd/:2:0","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"有效建模的要素 模型和实现的绑定。 建立了一种基于模型的通用语言。 开发一个蕴含丰富知识的模型。 提炼模型。 头脑风暴和实验。 ","date":"2019-02-16","objectID":"/ddd/:2:1","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"领域建模容易产生问题的应对方法 不要单纯以角色的行为(活动)为中心进行沟通和建模 领域沟通过程中,研发人员发挥主动性,以便发现深层模型 场景走查 ","date":"2019-02-16","objectID":"/ddd/:2:2","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"常用建模方法 Domain Storytelling (领域故事陈述法)本地工具 在线工具 Event Storming (事件风暴法) 4C (四色建模法) ","date":"2019-02-16","objectID":"/ddd/:2:3","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"DDD 与其他开发方法的区别 ","date":"2019-02-16","objectID":"/ddd/:3:0","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"DDD 与面向对象 OOAD 的区别 区别 联系 OOAD 没有战略设计,DDD 通过战略设计划分领域和模型 都是建模和设计方法 OOAD 仅用对象描述世界,DDD 的描述更细致(实体/值对象/聚合/聚合根) 部分建模方法和工具可复用 ","date":"2019-02-16","objectID":"/ddd/:3:1","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"DDD 与敏捷 区别 联系 敏捷:关注流程和文化,DDD:关注建模和设计方法 敏捷:重人员轻文档,DDD:重视统一语言的建立 都是软件工程领域的思想,解决软件工程中的不同问题,一般可结合应用 ","date":"2019-02-16","objectID":"/ddd/:3:2","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"战略战术设计 建模和设计的整体流程:挖掘用户故事 -\u003e 建立通用语言 -\u003e 战略设计 -\u003e 战术设计 ","date":"2019-02-16","objectID":"/ddd/:4:0","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"战略设计 DDD 中对问题空间和解决方案空间进行分解的过程 目的是分解模型以控制复杂性 战略设计是与传统建模和设计方法的核心区别之一 战略设计包含三部分:领域划分 -\u003e 寻找限界上下文(BC) -\u003e 确定上下文映射 领域划分 领域划分是以分离关注点为原则对问题空间的划分 子域是领域中某个方面的问题和解决它所设计的一切 基于领域划分进行分工协作而非基于需求 传统模式的问题:模块分给不同开发人员导致模型重叠 基于用户故事分解可以让领域划分清晰化 核心域 领域 -\u003e 精炼(分离和萃取) -\u003e (核心域,通用子域,支撑子域) 团队尽量减少非核心域的投入 从个人发展角度看,程序员尽量投入到核心域的工作 限界上下文 限界上下文:是一种语义上的上下文边界。意思是在这个边界里的软件模型组件都有它特定的含义并且做特定的事。一个限界上下文内的组件都是上下文特定的并且语义明确的。 为什么需要限界上下文 自然语言有歧义,同一个事物在不同场景的含义不一样 控制复杂性,便于分工协作 限界上下文的划分方式 领域故事陈述法 事件风暴法 基于子域概念提取 微服务是限界上下文的一种实现方式,一般一个限界上下文对应一个服务 聚合是限界上下文粒度的下限 上下文映射 上下文映射是指限界上下文之间的模型映射关系 描述团队之间的协作关系以及上下文之间的集成关系 决定上下文之间如何集成以及如何设置防腐层 上下文映射模式:合伙人、共享内核、客户/供应商、顺从者、防腐层、分道扬镳、开放主机服务、公开语言、大泥球 ","date":"2019-02-16","objectID":"/ddd/:4:1","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"战术设计 对各个 BC 的细节设计过程 BC 内部的模型结构与完整技术方案 战术设计是包括编码的,设计人员和架构师是需要深度编码的 类图无法描述跨域限界上下文之间关系的,会淹没在细节之中 ","date":"2019-02-16","objectID":"/ddd/:4:2","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"分层架构 架构分层能够避免模型在实现中被省略或者污染 四层架构 用户界面层 应用层 领域层 基础设施层 六边形架构 端口和适配器 应用程序 领域模型 ","date":"2019-02-16","objectID":"/ddd/:5:0","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"领域模型 ","date":"2019-02-16","objectID":"/ddd/:6:0","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"实体和值对象 实体 值对象 ID 相等性 属性相等性 要跟踪状态变化 不变性,可互换 是什么:具有 ID 相等性且需要跟踪状态变化的是实体,否则是值对象 为什么:值对象的实现更简单,技术负担更小,尽可能使用值对象 怎么做:基于对象的技术特征、基于更直观的模型特点 ","date":"2019-02-16","objectID":"/ddd/:6:1","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"聚合和聚合根 聚合就是一组相关对象的集合(比如汽车包含轮胎、方向盘、发动机),我们把它作为数据修改的单元。每个聚合都有一个根和一个边界。 聚合根是聚合所包含的一个特定实体。对聚合而言,外部对象只引用根,而边界内部的对象之间则可以互相引用。 聚合是拥有事务一致性(强一致性)的领域对象组合 聚合的原则 聚合内的实体适用事务一致性 聚合之间适用最终一致性 不脱离聚合根修改聚合内部对象 聚合根有全局唯一标识,聚合内部实体只有局部标识 聚合根可以从资源库获取,聚合内部实体不能 聚合的识别 实体是否在所有活动中都协同变更 ","date":"2019-02-16","objectID":"/ddd/:6:2","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"对象构造 工厂方法、抽象工厂、Builder 模式 生成实体 ID 的方式:程序自动生成、数据库自增 ID、ID 生成器(推荐) ","date":"2019-02-16","objectID":"/ddd/:6:3","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"资源库(数据访问层) 资源库放到适配层更好 资源库最好是依赖接口,可以方便更换存储层实现,比如是数据库还是缓存 ","date":"2019-02-16","objectID":"/ddd/:6:4","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"领域服务 当领域中的某个重要的过程或转换操作不是实体或值对象的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为领域服务。定义接口时要使用模型语言,并确保操作名称是通用语言中的术语。此外,应该使领域服务成为无状态的。 领域服务只包含业务逻辑(不包含事务管理、访问授权、数据格式转换) 不属于实体或值对象的逻辑,才可以放到领域服务 领域服务是无状态的 ","date":"2019-02-16","objectID":"/ddd/:6:5","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"应用层 定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道。 应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使他们互相协作。 应用层的职责 事务控制 身份认证和访问权限 定时任务调度 事件订阅(事件监听(适配层)、事件处理(应用层)) 调用领域层 ","date":"2019-02-16","objectID":"/ddd/:6:6","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"领域事件 是什么: 领域中发生的任何领域专家感兴趣的事情 领域事件一般由聚合产生 领域事件不是技术概念,是通用语言 为什么: 领域事件能够驱动建模 领域事件和很多重要思想相关,比如 CQRS、Event Sourcing 领域事件和大数据处理和分析相关 发布和订阅方式 外部系统 内部系统 API 定向通知(回调 URL) 观察者模式 API 定时拉取 数据库流水 消息队列(安全性:SSL) 消息队列 事件存储 直接使用消息中间件的存储,需要做好冗余备份 基于数据库,mongoDB、postgresql、mysql,做好分布式、按时间分区 事件处理的要求 顺序性:聚合 ID、存储分片、消费分组 幂等性:代替分布式事务(状态判断或者去重) ","date":"2019-02-16","objectID":"/ddd/:6:7","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"事件风暴法 核心词汇 便利贴颜色 角色 黄色 策略 深紫 决策命令 蓝色 聚合 淡黄 领域事件 橙色 读模型 绿色 外部系统 粉色 问题/热点 紫红 正向驱动 列出领域事件 -\u003e 选定领域事件 -\u003e 确定决策命令 -\u003e 确定用户或者策略 -\u003e 确定聚合 -\u003e 选定一个事件 反向驱动 选定领域事件 -\u003e 确定命令的读模型 -\u003e 由读模型驱动出上游领域事件 ","date":"2019-02-16","objectID":"/ddd/:6:8","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"参考 《领域驱动设计:软件核心复杂性应对之道》 Eric Evans 《实现领域驱动设计》 沃恩.弗农 《领域驱动设计 Thoughtworks 洞见》 https://github.com/go-kratos/beer-shop https://github.com/e-commerce-sample/order-backend ","date":"2019-02-16","objectID":"/ddd/:7:0","tags":["微服务","DDD"],"title":"DDD","uri":"/ddd/"},{"categories":["微服务"],"content":"简介 protocol buffers (ProtoBuf)是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。 Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10 倍)、更快(20 ~ 100 倍)、更为简单。 json\\xml 都是基于文本格式,protobuf 是二进制格式。 你可以通过 ProtoBuf 定义数据结构,然后通过 ProtoBuf 工具生成各种语言版本的数据结构类库,用于操作 ProtoBuf 协议数据。 ","date":"2019-02-16","objectID":"/protobuf/:1:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"protoc 下载安装 Protobuf 的 release 版本,下载可以移步:Protobuf release版本。 如果是 Linux 操作系统下,可以直接下载:protoc-xxx-linux-x86_64.zip。 这个版本包含了 protoc 二进制文件以及与 protobuf 一起分发的一组标准.proto 文件。 进入 bin 文件夹,查看 protoc 的版本信息: # 如果打印出了protoc的版本信息,就表示没有任何问题。 # 可以加到环境变量,方便使用 ./protoc --version ","date":"2019-02-16","objectID":"/protobuf/:2:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"protoc 使用 protoc --java_out=. test.proto protoc --go_out=. test.proto go 使用 protoc 的话需要额外安装 protoc-gen-go go install google.golang.org/protobuf/cmd/protoc-gen-go@latest ","date":"2019-02-16","objectID":"/protobuf/:3:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"使用 docker 生成代码 docker run --rm -v $(pwd):$(pwd) -w $(pwd) rvolosatovs/protoc --go_out=. --go-grpc_out=. --grpc-gateway_out=. --openapiv2_out=. -I=. test.proto ","date":"2019-02-16","objectID":"/protobuf/:4:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"proto 文件解析 // 定义proto的版本 syntax = \"proto3\"; // 引入其它proto文件 import \"other.proto\"; import \"other2.proto\"; // 定义proto的包名,包名可以避免对message 类型之间的名字冲突,同名的Message可以通过package进行区分。 // 在没有为特定语言定义option xxx_package的时候,它还可以用来生成特定语言的包名,比如Java package, go package。 package foo.bar; // option可以用在proto的scope中,或者message、enum、service的定义中。 // 可以是Protobuf定义的option,或者自定义的option。 option java_package = \"com.example.foo\"; // protoc-gen-go 版本大于1.4.0, proto文件需要加上go_package,否则无法生成 option go_package = \"main\"; // 定义消息 message SearchRequest { string query = 1; } message SearchResponse { string answer = 1; } // 定义服务 // 需要与协议缓冲区搭配使用,最直接的 RPC 系统是 gRPC service SearchService { rpc Search(SearchRequest) returns (SearchResponse); } ","date":"2019-02-16","objectID":"/protobuf/:5:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"数据类型 ","date":"2019-02-16","objectID":"/protobuf/:6:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"定义消息 消息(message),在 protobuf 中指的就是我们要定义的数据结构。 syntax = \"proto3\"; message SearchRequest { // 字段类型 字段名称 = 标识号 string query = 1; int32 page_number = 2; int32 result_per_page = 3; } 保留标识号(Reserved) 如果你想保留一些标识号,留给以后用,可以使用下面语法: message Foo { reserved 2, 15, 9 to 11; // 保留2,15,9到11这些标识号 } ","date":"2019-02-16","objectID":"/protobuf/:6:1","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"基本类型 .proto Type Notes C++ Type Java Type Python Type Go Type Ruby Type C# Type PHP Type double double double float float64 Float double float float float float float float32 Float float float int32 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用 sint64 替代 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer uint32 使用变长编码 uint32 int int/long uint32 Fixnum 或者 Bignum(根据需要) uint integer uint64 使用变长编码 uint64 long int/long uint64 Bignum ulong integer/string sint32 使用变长编码,这些编码在负值时比 int32 高效的多 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer sint64 使用变长编码,有符号的整型值。编码时比通常的 int64 高效。 int64 long int/long int64 Bignum long integer/string fixed32 总是 4 个字节,如果数值总是比总是比 228 大的话,这个类型会比 uint32 高效。 uint32 int int uint32 Fixnum 或者 Bignum(根据需要) uint integer fixed64 总是 8 个字节,如果数值总是比总是比 256 大的话,这个类型会比 uint64 高效。 uint64 long int/long uint64 Bignum ulong integer/string sfixed32 总是 4 个字节 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer sfixed64 总是 8 个字节 int64 long int/long int64 Bignum long integer/string bool bool boolean bool bool TrueClass/FalseClass bool boolean string 一个字符串必须是 UTF-8 编码或者 7-bit ASCII 编码的文本。 string String str/unicode string String (UTF-8) string string bytes 可能包含任意顺序的字节数据。 string ByteString str []byte String (ASCII-8BIT) ByteString string ","date":"2019-02-16","objectID":"/protobuf/:6:2","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"枚举类型 syntax = \"proto3\";//指定版本信息,不指定会报错 enum PhoneType //枚举消息类型,使用enum关键词定义,一个电话类型的枚举类型 { MOBILE = 0; //proto3版本中,首成员必须为0,成员不应有相同的值 HOME = 1; WORK = 2; } // 定义一个电话消息 message PhoneNumber { string number = 1; // 电话号码字段 PhoneType type = 2; // 电话类型字段,电话类型使用PhoneType枚举类型 } ","date":"2019-02-16","objectID":"/protobuf/:6:3","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"数组类型 在 protobuf 消息中定义数组类型,是通过在字段前面增加 repeated 关键词实现,标记当前字段是一个数组。 message Msg { // 只要使用repeated标记类型定义,就表示数组类型。 repeated int32 arrays = 1; } ","date":"2019-02-16","objectID":"/protobuf/:6:4","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"map 类型 protocol buffers 支持 map 类型定义。 syntax = \"proto3\"; message Product { string name = 1; // 商品名 // 定义一个k/v类型,key是string类型,value也是string类型 map\u003cstring, string\u003e attrs = 2; // 商品属性,键值对 } ","date":"2019-02-16","objectID":"/protobuf/:6:5","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"消息嵌套 我们在各种语言开发中类的定义是可以互相嵌套的,也可以使用其他类作为自己的成员属性类型。 在 protobuf 中同样支持消息嵌套,可以在一个消息中嵌套另外一个消息,字段类型可以是另外一个消息类型。 // 定义Result消息 message Result { string url = 1; string title = 2; repeated string snippets = 3; // 字符串数组类型 } // 定义SearchResponse消息 message SearchResponse { // 引用上面定义的Result消息类型,作为results字段的类型 repeated Result results = 1; // repeated关键词标记,说明results字段是一个数组 } ","date":"2019-02-16","objectID":"/protobuf/:6:6","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"编码原理 Protocol buffers 核心就是对单个数据的编码(Varint 编码)以及对数据整体的编码(Message Structure 编码)。 Protocol Buffer 利用 Varint 原理压缩数据,同时使用Tag - Value (Tag - Length - Value)的编码结构的实现,减少了分隔符的使用,数据存储更加紧凑。 ","date":"2019-02-16","objectID":"/protobuf/:7:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"Varint 编码 Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。 Varint 中的每个字节(最后一个字节除外)都设置了最高有效位 most significant bit(MSB),这一位表示是否还会有更多字节出现。每个字节的低 7 位用于以 7 位组的形式存储数字的二进制补码表示,最低有效组首位。 最高位为 1 代表后面 7 位仍然表示数字;最高位为 0,后面 7 位用原码补齐。 如果用不到 1 个字节,那么最高有效位设为 0 ,如下面这个例子,1 用一个字节就可以表示,所以 msb 为 0. 0000 0001 如果需要多个字节表示,msb 就应该设置为 1 。例如 300,如果用 Varint 表示的话: 1010 1100 0000 0010 编码方式: 将被编码数转换为二进制表示 从低位到高位按照 7 位 一组进行划分 将大端序转为小端序,即以分组为单位进行首尾顺序交换。因为 protobuf 使用是小端序,所以需要转换一下 给每组加上最高有效位(最后一个字节高位补 0,其余各字节高位补 1)组成编码后的数据。 最后转成 10 进制。 比如对数字 123456 进行 varint 编码: 123456 用二进制表示为1 11100010 01000000, 每次从低向高取 7 位 变成111 1000100 1000000 大端序转为小端序,即交换字节顺序变成1000000 1000100 111 然后加上最高有效位(即:最后一个字节高位补 0,其余各字节高位补 1)变成11000000 11000100 00000111 最后再转成 10 进制,所以经过 varint 编码后 123456 占用三个字节分别为192 196 7。 解码 解码的过程就是将字节依次取出,去掉最高有效位,因为是小端排序所以先解码的字节要放在低位,之后解码出来的二进制位继续放在之前已经解码出来的二进制的高位最后转换为 10 进制数完成 varint 编码的解码过程。 缺点 负数需要 10 个字节显示(因为计算机定义负数的符号位为数字的最高位)。 具体是先将负数是转成了 long 类型,再进行 varint 编码,这就是占用 10 个 字节的原因了。 protobuf 采取的解决方式:使用 sint32/sint64 类型表示负数,通过先采用 Zigzag 编码,将正数、负数和 0 都映射到无符号数,最后再采用 varint 编码。 具体实现 github.com/golang/protobuf ","date":"2019-02-16","objectID":"/protobuf/:7:1","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"ZigZag 编码 ZigZag 是将符号数统一映射到无符号号数的一种编码方案,具体映射函数为: Zigzag(n) = (n \u003c\u003c 1) ^ (n \u003e\u003e 31), n 为 sint32 时 Zigzag(n) = (n \u003c\u003c 1) ^ (n \u003e\u003e 63), n 为 sint64 时 比如:对于 0 -1 1 -2 2 映射到无符号数 0 1 2 3 4。 原始值 映射值 0 0 -1 1 1 2 2 3 -2 4 ","date":"2019-02-16","objectID":"/protobuf/:7:2","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"Message Structure 编码 message 的每个字段 field 在序列化时,一个 field 对应一个 key-value 对,整个二进制文件就是一连串紧密排列的 key-value 对,key 也称为 tag。 采用这种 key-value 对的结构无需使用分隔符来分割不同的 field。对于可选的 field,如果消息中不存在该 field,那么在最终的 message 中就没有该 field,这些特性都有助于节约消息本身的大小。 key 由 wire type 和 FieldNumber 两部分编码而成,具体地说,key=(field_number\u003c\u003c3)|wire_type。 key 的最低 3 个 bit 为 wire type,wire type 类型如下表所示: Type Meaning Used For 0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum 1 64-bit fixed64, sfixed64, double 2 Length-delimi string, bytes, embedded messages, packed repeated fields 3 Start group Groups (deprecated) 4 End group Groups (deprecated) 5 32-bit fixed32, sfixed32, float 由于 key 的第三位最多表示 8 个值,而 wire type 目前的种类是 6 种。由于采用 varint 的编码方式,只剩下 4 位的空闲存放 field_number,因此之前在定义每个字段的标识号的时候建议不要超过 15。 wire type 被如此设计,主要是为了解决一个问题,如何知道接下来 value 部分的长度(字节数),如果: wire type=0、1、5,编码为 key+数据,只有一个数据,可能占数个字节,数据在编码时自带终止标记; wire type=2,编码为 key+length+数据,length 指示了数据长度,可能有多个数据,顺序排序。 需要注意的是: 如果出现嵌套 message,直接将嵌套 message 部分的编码接在 length 后即可; **repeated 后面接的字段,如果是个 message,它重复出现多少次,编码时其 key 就会出现几次;如果接的是 proto 定义的字段,且以 packed = true 压缩存储时,只会出现 1 个 key;如果不以压缩方式存储,其 key 也会出现多次。**在 proto3 中,默认以压缩方式进行存储,proto2 中则需要显式地声明。 没有压缩 float、double 这些浮点类型,说 Protocol Buffer 压缩数据没有到极限,原因就在这里 ","date":"2019-02-16","objectID":"/protobuf/:7:3","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"参考 https://www.tizi365.com/archives/367.html https://zhuanlan.zhihu.com/p/478299451 https://learnku.com/articles/55924 https://colobu.com/2019/10/03/protobuf-ultimate-tutorial-in-go/ https://www.lixueduan.com/posts/protobuf/02-encode-core/ https://blog.csdn.net/qq_38410730/article/details/103702827 https://developers.google.com/protocol-buffers/docs/proto3?hl=zh-cn#services ","date":"2019-02-16","objectID":"/protobuf/:8:0","tags":["protobuf","Protocol Buffers"],"title":"Protocol Buffers 详解","uri":"/protobuf/"},{"categories":["微服务"],"content":"什么是微服务 随着领域驱动设计、持续交付、按需虚拟化、基础设施自动化、小型自治团队、大型集群系统这些实践的流行,微服务也应运而生。 微服务:把应用程序功能性分解为一组协同工作的小而自治的服务的架构风格。每个服务是一组专注的、内聚的功能职责组成。 每个服务是松耦合的,有自己的私有数据库,通过 API 通信。每个服务可以独立设计、开发、测试、部署和扩展。 ","date":"2019-02-15","objectID":"/microservice/:1:0","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务特点 很小,专注于做好一件事,一个微服务应该可以在两周内完全重写。 自治性:修改一个服务并对其进行部署,而不影响其他任何服务。 ","date":"2019-02-15","objectID":"/microservice/:1:1","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务优势 技术异构性:开发成本低,支持不同技术栈服务,开发人员可使用自己擅长的技术,不同服务场景可以选择最合适的技术实现 容错性:可靠性高,去中心化、集群化,降低了单点故障及性能瓶颈 灵活扩展:只对需要扩展的服务进行扩展,这样就可以把那些不需要扩展的服务运行在更小的、性能稍差的硬件上 硬件投入少:高峰期自动扩容、低谷时自动缩减,可实现对资源恰到好处的利用 简化部署:各个服务的部署是独立的,这样就可以更快地对特定部分的代码进行部署 团队自治:与组织结构匹配,避免出现过大的代码库,从而获得理想的团队大小及生产力 可组合性:代码复用率高,更小的粒度意味着更多的可复用性,避免重复造轮子 方便替代和升级:业务响应快,按细粒度业务单元拆分,新增或业务变更只需要修改小部分服务即可提测、发布 ","date":"2019-02-15","objectID":"/microservice/:1:2","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务劣势 服务拆分和定义是一项挑战(糟糕的是搞成了分布式单体应用) 分布式系统带来的各种复杂性,使开发、部署和测试变得更困难 跨服务开发需要协调多个团队。服务部署可能要按照依赖关系排序 什么阶段使用微服务?初创公司几乎都是从单体应用开始 复杂性从代码转移到基础设施 ","date":"2019-02-15","objectID":"/microservice/:1:3","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务的挑战 微服务并不能消除风险,而是将这个成本移到了系统生命周期的后半阶段:降低了开发过程中的冲突,但是增加了运维阶段系统部署、验证以及监控的复杂度。 识别和划定微服务范围需要大量专业的业务领域知识。 正确识别服务间的边界和契约是很困难的,而且一旦确定,是很难对它们进行改动的。 微服务是分布式系统,所以需要对状态、一致性和网络可靠性这些内容做出不同的假设。 跨网络分发系统组件以及不断增长的技术差异性,会导致微服务出现新的故障形式。 越来越难以了解和验证在正常运行过程中会发生什么事情(可观测性)。 不断增加的服务使得故障点增多。 ","date":"2019-02-15","objectID":"/microservice/:1:4","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务主流方案 微服务 SDK 服务网格 ","date":"2019-02-15","objectID":"/microservice/:1:5","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务组件 服务注册和发现 服务监控 熔断降级 流量控制 安全性 配置管理 网关 容器化 ","date":"2019-02-15","objectID":"/microservice/:1:6","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务设计 微服务设计可以参考领域驱动设计 ","date":"2019-02-15","objectID":"/microservice/:2:0","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务架构 微服务应用的 4 层架构如下 平台层——微服务平台提供了工具、基础架构和一些高级的基本部件,以支持微服务的快速开发、运行和部署。一个成熟的平台层会让技术人员把重心放在功能开发而非一些底层的工作上。 平台层需要提供以下支持: 服务运行的部署目标,包括基础设施的基本元件,如负载均衡器和虚拟机。 日志聚合和监控聚合用于观测服务运行情况。 一致且可重复的部署流水线,用于测试和发布新服务或者新版本。 支持安全运行,如网络控制、涉密信息管理和应用加固。 通信通道和服务发现方案,用于支持服务间交互。 服务层——在这一层,开发的各个服务会借助下层的平台层的支持来相互作用,以提供业务和技术功能。 边界层——客户端会通过定义好的边界和应用进行交互。这个边界会暴露底层的各个功能,以满足外部消费者的需求。 边界层可以实现一些其他面向客户端的功能: SSL 终止 认证和授权——验证 API 客户端的身份和权限; 限流——对客户端的滥用进行防卫; 缓存——降低后端整体的负载; 日志和指标收集——可以对客户端的请求进行分析和监控。 边界层 3 种相关但又不同的应用边界模式: API 网关,(缺点:承担的职责就会越来越多) 服务于前端的后端(BFF),会为每种客户端类型提供一个 API 网关 消费者驱动的网关,可以只构建一个“超级”API 来让消费者决定他们所需要的响应数据的样子。 客户端层——与微服务后端交互的客户端应用,如网站和移动应用。 ","date":"2019-02-15","objectID":"/microservice/:2:1","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务拆分 为了处理不确定性,服务应该从粗粒度开始,再慢慢的进一步分解,之后先扩张再收缩(下线和迁移)。 拆分的指导原则 单一职责: 改变类应该只有一个理由 闭包原则: 包中包含的所有类应该是对同类变化的一个集合。 按照业务能力和限界上下文(bounded context)划分——服务将对应于粒度相对粗一些但又紧密团结成一个整体的业务功能领域。 按用例(使用案例)划分——这种服务应该是一个“动词”型,它反映了系统中将发生的动作。 按易变性划分——服务会将那些未来可能发生变化的领域封装在内部。 按技术能力划分 在面向业务的服务中包含这个功能会使得服务过于复杂、增加未来替换的复杂度。 许多服务都需要的技术能力——比如,发送邮件通知。 可以独立于业务能力进行修改的技术能力——比如,重要的第三方系统集成。 ","date":"2019-02-15","objectID":"/microservice/:2:2","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务事务与查询 分布式事务的成本很高,服务拆分尽量避免产生跨服务事务,能合则合。如无法合并则优先考虑 TCC 或基于 MQ 的柔性事务,尽可能规避 2PC 等对性能影响很大的事务方案。TCC 可完全替换 2PC,但开发成本偏高,需要调用各方都同步修改以支持 Try、Confirm 和 Cancel 操作,某些场景会调用三方服务,其代码不受我们控制,此时可以考虑使用 MQ 实现异步消息和补偿性事务。 基于事件的最终一致性 异步事件能够帮助我们解除服务之间的耦合和提高系统整体的可用性,但是这也促使服务的开发者开始思考最终一致性(eventual consistency)。 当从单体应用向微服务应用迁移时,面向事件的服务通信方案是非常出色的。单体应用发出事件消息,而开发者在那些并行开发的微服务中消费这些消息。通过这种方式,开发者就可以在新的服务上开发新的功能,而不用担心新服务与原有的单体应用耦合太紧。 使用 Saga 管理事务 Saga 模式是一个本地事务序列,其每个事务在一个单独的微服务内更新数据存储并发布一个事件或消息。 Saga 中的首个事务是由外部请求(事件或动作)初始化的,一旦本地事务完成(数据已保存在数据存储且消息或事件已发布),那么发布的消息或事件则会触发 Saga 中的下一个本地事务。 如任一正向操作执行失败,则事务会执行之前各参与者(发布给之前的参与者消息或事件)的逆向回滚操作,回滚已提交的参与者,直至事务退回至其初始状态。 CQRS CQRS 将服务分成了命令和查询两部分,每一部分分别由不同的数据存储来提供支持。 查询服务也可通过其他服务发出的事件消息来组建复合型的数据视图。 ","date":"2019-02-15","objectID":"/microservice/:2:3","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务可靠性 故障主要发生在四大领域中: 硬件——服务运行所依赖的底层物理基础设施和虚拟化的基础设施; 主机 数据中心 主机配置 物理网络 操作系统和资源隔离 通信——不同服务之间的协作以及服务与外部第三方之间的协作; 网络:网络连接会中断 防火墙 DNS 错误:主机名不能在应用中被正确的传播或解析 消息传输 健康检查不充分:健康检查不能正确体现实例的状态,导致请求被路由到出现问题的实例 依赖——服务自身所依赖的服务的故障; 超时:请求超时 功能下线或者向后不兼容 内部组件故障:数据库或者缓存服务出现问题导致服务不能正常工作 外部依赖:外部系统(第三方 API)不能正常运行或者执行不符合预期 内部——服务本身的代码错误,比如工程师引入的代码缺陷。 可靠性设计 重试 永远要限制重试的总次数; 使用带抖动的指数退避策略来均匀地分配重试请求和避免进一步加剧负载; 仔细考虑哪些错误情况应该触发重试,以及哪些重试不大可能成功、哪些重试永远不会成功; 需要考虑幂等性。 加乐观锁 使用去重索引 使用 token 机制 后备方案 优雅降级 缓存 功能冗余 桩数据 超时:合理设置请求超时 断路器 如果在一定时间窗口内失败数或者失败率超过某个阈值,这时断路器就会被断开。这种情况下,我们的服务就不再尝试向协作服务发起请求了,而是会绕过这个请求,并在可能的情况下执行适当的后备方案——返回一个桩消息、路由到其他服务或者返回缓存的数据。 异步通信 使用类似消息队列这样的通信代理来设计异步的服务交互是提高可靠性的另一大技术。 负载均衡与服务健康 负载均衡器负责执行健康检查并会利用到检查的结果。 限流 丢弃超过容量的请求 关键数据请求优先 丢弃不常见的客户端 限制并行请求量 验证可靠性和容错性 压力测试 混沌测试 混沌测试会倒逼着微服务应用在生产环境中出现故障。通过引入不稳定性因素以及故障,混沌测试可以精确模拟真实系统的故障,同时也让工程团队得到训练,使得他们能够处理这些故障。 ","date":"2019-02-15","objectID":"/microservice/:2:4","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务底座 从一开始就支持在容器调度器中部署服务(CI/CD)。 可观测性 支持日志聚合。 收集度量指标数据。 错误报告。 具备同步和异步通信机制。 服务注册和发现。 配置获取。 数据存储设置。 负载均衡和健康检查。 ","date":"2019-02-15","objectID":"/microservice/:2:5","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"微服务部署 微服务极大地增加了系统中活动部件的数量,从而增大了部署的复杂性。在部署微服务时,开发者将面临四大挑战: 面对大量的发布和组件变更时应保持稳定性; 避免会导致组件在构建阶段或者发布阶段产生依赖关系的紧耦合; 服务 API 发布不兼容的变更可能会对客户端产生非常大的负面影响; 服务下线。 微服务生产环境具备以下六大基础功能。 部署目标或者运行平台,也就是服务所运行的地方,比如虚拟机[理想情况下,工程师可以使用 API 来配置、部署和更新服务配置。开发者还可以将这种 API 称为控制面板(control pane)]、容器。 运行时管理,比如自动愈合和自动扩容。这样服务环境就可以动态地响应失败或者负载变化,而不需要人为干预(如果某个服务实例出现故障,它应该会被自动替换掉)。 日志和监控,用来监测服务的运行情况并方便工程师对服务执行的过程有深入了解。 支持安全运维,比如网络控制、密码凭据管理以及应用加固。 负载均衡、DNS 以及其他路由组件可将用户侧的请求以及微服务之间的请求路由分发出去。 部署流水线,安全地将服务从代码交付到生产环境中运行。 这些组件是微服务架构栈中平台层的组成部分。 不停机部署有 3 种常见的部署模式。 滚动部署:在启动新实例(版本为 N+1)时,逐步将旧实例(版本为 N)从服务中剔除,确保在部署期间最小比例的负载容量得到保证。 金丝雀部署:开发者在服务中添加一个新实例来验证 N+1 版本的可靠性,然后再全面推出。这种模式在常规滚动部署之外提供了附加安全措施。 蓝绿部署:创建一个运行新版本代码的并行服务组(绿色集合),开发者逐步将请求从旧版本(蓝色集合)中转移出去。在服务消费者对错误率高度敏感、无法接受不健康的金丝雀风险的情况下,这种方法比金丝雀部署模式更有效。 ","date":"2019-02-15","objectID":"/microservice/:3:0","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"部署模式:基于语言特定的发布包格式 可以执行的 JAR/WAR 文件 好处: 快速部署 高效的资源利用 缺点: 缺乏技术栈的封装 无法约束服务实例消耗的资源 服务之间缺少隔离 难以自动判定放置服务实例的位置 尽量避免使用,除非所获效率的价值远在其他所有考量之上 ","date":"2019-02-15","objectID":"/microservice/:3:1","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"部署模式:将服务部署为虚拟机 作为虚拟机镜像打包的服务部署到生产环境中,每个服务实例都是一个虚拟机。 优点:封装了技术栈;服务实例隔离;使用成熟云计算基础设施; 缺点:资源利用率低;部署速度慢;系统管理开销 ","date":"2019-02-15","objectID":"/microservice/:3:2","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"服务部署为容器 将作为容器镜像打包的服务部署到生产环境,每个服务实例都是一个容器。 容器镜像:由应用程序和运行服务所需的依赖软件组成的文件系统镜像 好处:封装技术栈;实例隔离;资源受限 弊端:大量容器镜像管理工作 ","date":"2019-02-15","objectID":"/microservice/:3:3","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"使用 Kubernets 部署 docker 编排框架,是 docker 之上的一个软件层,将一组计算机硬件资源转变为用于运行服务的单一资源池。 资源管理: 一组计算机视为 cpu、内存和存储卷构成的资源池,将计算机集群视为一台计算机 调度: 选择要运行容器的机器 服务管理:负载均衡,滚动升级等 Istio 服务网格 ","date":"2019-02-15","objectID":"/microservice/:3:4","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"Serverless 部署 AWS Lambda ","date":"2019-02-15","objectID":"/microservice/:3:5","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"可观测性 利用度量指标、链路追踪和日志信息构建一套监控系统,以更加丰富全面地监测微服务应用,并且收集的信号来设置告警。 ","date":"2019-02-15","objectID":"/microservice/:4:0","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"监控 应该把客户端、边界层、服务层和平台层这 4 层都监控起来。把各层的度量指标都收集到统一的地方以便观测。 利用 Prometheus 和 Grafana 搭建监控平台。 ","date":"2019-02-15","objectID":"/microservice/:4:1","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"度量指标 从面向用户的系统中收集度量指标时,开发者应该关注四大黄金标志:时延、错误量、通信量和饱和度。 度量指标的类型:计数器、计量器、直方图。 ","date":"2019-02-15","objectID":"/microservice/:4:2","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"告警 系统出错时哪些人需要知悉 触发告警的应该是症状,而非原因,比如用户会遇到的错误。 尽可能减少告警通知的数量并保持这些通知的可操作性来避免出现告警疲劳。系统正常行为的每次偏差都生成一条告警通知很快就会导致人们不再关心这些告警或者认为这些告警并不重要。这样会导致一些重要告警被忽视。 ","date":"2019-02-15","objectID":"/microservice/:4:3","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"日志 生成一致的、结构化的、人类可读的日志,可以使用 ELK 或 EFK 进行收集、存储、展示。 日志中的有用信息: 时间戳,最好以毫秒为单位。时间戳还应该包含时区,建议开发者尽可能使用 GMT/UTC 来收集数据。 标识符,请求 ID、用户 ID 和其他唯一标识符是非常重要的。 来源,开发者可以使用的典型数据来源包括主机名、类名或模块名、函数名和文件名。 日志等级或类别 ","date":"2019-02-15","objectID":"/microservice/:4:4","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"链路追踪 分布式链路追踪系统以可视化形式展示各个服务间的执行流程,同时展示每步操作所耗费的时间。这是非常有价值的,不仅有助于了解请求在各个服务间的流动顺序,还有助于发现可能的系统瓶颈。 OpenTracing API 是一个与供应商无关的分布式链路追踪开放标准。许多分布式跟踪系统(如 Dapper、Zipkin、HTrace、X-Trace)都提供了链路追踪功能,但使用的是互不兼容的 API。选择其中一个系统通常意味着可能要与使用不同编程语言的系统紧密耦合到一起,从而形成一个解决方案。OpenTracing 的目的是为链路追踪的信息收集提供一组约定的、标准化的 API。类库可用于不同的语言和框架。 请求关联:trace 和 span trace 是由单个或多个 span 组成的有向无环图(DAG),这些 span 的边称为 reference。trace 用于聚合和关联整个系统的执行流。为此,需要传播一些信息。一个 trace 记录整个流程。 每个 span 包含如下信息:操作名称、起始时间戳和完成时间戳、零个或者多个 span 标签(键值对)、零个或多个 span 日志(带时间戳的键值对)、span 上下文(context)以及引用零个或多个 span 的参考(通过 span 上下文)。 这些 span 可以由同一个应用触发,也可以由不同的应用触发。唯一的要求是在触发新的 span 时,要传递父 span 的 ID,这样新的 span 就拥有了对父 span 的引用。 ","date":"2019-02-15","objectID":"/microservice/:4:5","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["微服务"],"content":"参考 《微服务设计》 《微服务实战》 https://zhuanlan.zhihu.com/p/34862889 https://pegasuswang.readthedocs.io/zh/latest/%E7%BD%91%E7%AB%99%E6%9E%B6%E6%9E%84%E5%BE%AE%E6%9C%8D%E5%8A%A1/microservices_patterns_%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/book/ https://www.infoq.cn/article/kdw69bdimlx6fsgz1bg3 http://autumn200.com/2019/04/24/Micro-service-architecture-design/ ","date":"2019-02-15","objectID":"/microservice/:5:0","tags":["微服务"],"title":"微服务","uri":"/microservice/"},{"categories":["Linux"],"content":"性能指标 通常用带宽、吞吐量、延时、PPS(Packet Per Second)等指标衡量网络的性能。 带宽,表示链路的最大传输速率,单位通常为 b/s (比特 / 秒)。 吞吐量,表示单位时间内成功传输的数据量,单位通常为 b/s(比特 / 秒)或者 B/s(字节 / 秒)。吞吐量受带宽限制,而吞吐量 / 带宽,也就是该网络的使用率。 延时,表示从网络请求发出后,一直到收到远端响应,所需要的时间延迟。在不同场景中,这一指标可能会有不同含义。比如,它可以表示,建立连接需要的时间(比如 TCP 握手延时),或一个数据包往返所需的时间(比如 RTT)。 PPS,是 Packet Per Second(包 / 秒)的缩写,表示以网络包为单位的传输速率。PPS 通常用来评估网络的转发能力,比如硬件交换机,通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值)。而基于 Linux 服务器的转发,则容易受网络包大小的影响。 除了这些指标,网络的可用性(网络能否正常通信)、并发连接数(TCP 连接数量)、丢包率(丢包百分比)、重传率(重新传输的网络包比例)等也是常用的性能指标。 ","date":"2019-02-09","objectID":"/linux-perf-net/:1:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"性能工具 ","date":"2019-02-09","objectID":"/linux-perf-net/:2:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"性能优化 ","date":"2019-02-09","objectID":"/linux-perf-net/:3:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"应用程序 使用长连接取代短连接,可以显著降低 TCP 建立连接的成本。在每秒请求次数较多时,这样做的效果非常明显。 使用内存等方式,来缓存不常变化的数据,可以降低网络 I/O 次数,同时加快应用程序的响应速度。 使用 Protocol Buffer 等序列化的方式,压缩网络 I/O 的数据量,可以提高应用程序的吞吐。 使用 DNS 缓存、预取、HTTPDNS 等方式,减少 DNS 解析的延迟,也可以提升网络 I/O 的整体速度。 ","date":"2019-02-09","objectID":"/linux-perf-net/:3:1","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"套接字 网络配置项大部分在 /etc/sysctl.conf 文件。 为了提高网络的吞吐量,通常需要调整缓冲区的大小。比如: 增大每个套接字的缓冲区大小 net.core.optmem_max; 增大套接字接收缓冲区大小 net.core.rmem_max 和发送缓冲区大小 net.core.wmem_max; 增大 TCP 接收缓冲区大小 net.ipv4.tcp_rmem 和发送缓冲区大小 net.ipv4.tcp_wmem。 除此之外,套接字接口还提供了一些配置选项,用来修改网络连接的行为: 为 TCP 连接设置 TCP_NODELAY 后,就可以禁用 Nagle 算法; 为 TCP 连接开启 TCP_CORK 后,可以让小包聚合成大包后再发送(注意会阻塞小包的发送); 使用 SO_SNDBUF 和 SO_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大小。 ","date":"2019-02-09","objectID":"/linux-perf-net/:3:2","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"传输层 ","date":"2019-02-09","objectID":"/linux-perf-net/:3:3","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"网络层 在需要转发的服务器中,比如用作 NAT 网关的服务器或者使用 Docker 容器时,开启 IP 转发,即设置 net.ipv4.ip_forward = 1。 调整数据包的生存周期 TTL,比如设置 net.ipv4.ip_default_ttl = 64。注意,增大该值会降低系统性能。 开启数据包的反向地址校验,比如设置 net.ipv4.conf.eth0.rp_filter = 1。这样可以防止 IP 欺骗,并减少伪造 IP 带来的 DDoS 问题。 调整 MTU(Maximum Transmission Unit)的大小。 为了避免 ICMP 主机探测、ICMP Flood 等各种网络问题,你可以通过内核选项,来限制 ICMP 的行为。 比如,你可以禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1。这样,外部主机就无法通过 ICMP 来探测主机。 或者,你还可以禁止广播 ICMP,即设置 net.ipv4.icmp_echo_ignore_broadcasts = 1。 ","date":"2019-02-09","objectID":"/linux-perf-net/:3:4","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"网络配置 可以使用 ifconfig 或者 ip 命令,来查看网络的配置。 # 也可使用 ip -s addr show dev eth0 $ ifconfig eth0 eth0: flags=4163\u003cUP,BROADCAST,RUNNING,MULTICAST\u003e mtu 1500 inet 172.29.16.241 netmask 255.255.240.0 broadcast 172.29.31.255 inet6 fe80::215:5dff:feaa:48ac prefixlen 64 scopeid 0x20\u003clink\u003e ether 00:15:5d:aa:48:ac txqueuelen 1000 (Ethernet) RX packets 403 bytes 78032 (78.0 KB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 16 bytes 1216 (1.2 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 这些具体指标的含义,在文档中都有详细的说明,不过,这里有几个跟网络性能密切相关的指标,需要你特别关注一下。 第一,网络接口的状态标志。ifconfig 输出中的 RUNNING ,或 ip 输出中的 LOWER_UP ,都表示物理网络是连通的,即网卡已经连接到了交换机或者路由器中。如果你看不到它们,通常表示网线被拔掉了。 第二,MTU 的大小。MTU 默认大小是 1500,根据网络架构的不同(比如是否使用了 VXLAN 等叠加网络),你可能需要调大或者调小 MTU 的数值。 第三,网络接口的 IP 地址、子网以及 MAC 地址。这些都是保障网络功能正常工作所必需的,你需要确保配置正确。 第四,网络收发的字节数、包数、错误数以及丢包情况,特别是 TX 和 RX 部分的 errors、dropped、overruns、carrier 以及 collisions 等指标不为 0 时,通常表示出现了网络 I/O 问题。其中: errors 表示发生错误的数据包数,比如校验错误、帧同步错误等; dropped 表示丢弃的数据包数,即数据包已经收到了 Ring Buffer,但因为内存不足等原因丢包; overruns 表示超限数据包数,即网络 I/O 速度过快,导致 Ring Buffer 中的数据包来不及处理(队列满)而导致的丢包; carrier 表示发生 carrirer 错误的数据包数,比如双工模式不匹配、物理电缆出现问题等; collisions 表示碰撞数据包数。 ","date":"2019-02-09","objectID":"/linux-perf-net/:4:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"套接字信息 用 netstat 或者 ss 查看套接字、网络栈、网络接口以及路由表的信息。 # head -n 3 表示只显示前面 3 行 # -l 表示只显示监听套接字 # -n 表示显示数字地址和端口 (而不是名字) # -p 表示显示进程信息 $ netstat -lnp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:3415 0.0.0.0:* LISTEN 29/./server # -l 表示只显示监听套接字 # -t 表示只显示 TCP 套接字 # -n 表示显示数字地址和端口 (而不是名字) # -p 表示显示进程信息 $ ss -ltnp | head -n 3 State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:* users:((\"systemd-resolve\",pid=840,fd=13)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:((\"sshd\",pid=1459,fd=3)) 当套接字处于连接状态(Established)时, Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数(即接收队列长度)。 而 Send-Q 表示还没有被远端主机确认的字节数(即发送队列长度)。 当套接字处于监听状态(Listening)时, Recv-Q 表示 syn backlog 的当前值。 而 Send-Q 表示最大的 syn backlog 值。 ","date":"2019-02-09","objectID":"/linux-perf-net/:5:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"协议栈统计信息 使用 netstat 或 ss ,可以查看协议栈的信息: $ netstat -s ... Tcp: 3244906 active connection openings 23143 passive connection openings 115732 failed connection attempts 2964 connection resets received 1 connections established 13025010 segments received 17606946 segments sent out 44438 segments retransmitted 42 bad segments received 5315 resets sent InCsumErrors: 42 ... $ ss -s ","date":"2019-02-09","objectID":"/linux-perf-net/:6:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"网络吞吐和 PPS 给 sar 增加 -n 参数就可以查看网络的统计信息,比如网络接口(DEV)、网络接口错误(EDEV)、TCP、UDP、ICMP 等等。执行下面的命令,你就可以得到网络接口统计信息: # 数字 1 表示每隔 1 秒输出一组数据 $ sar -n DEV 1 Linux 4.15.0-1035-azure (ubuntu) 01/06/19 _x86_64_ (2 CPU) 13:21:40 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil 13:21:41 eth0 18.00 20.00 5.79 4.25 0.00 0.00 0.00 0.00 13:21:41 docker0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 13:21:41 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 rxpck/s 和 txpck/s 分别是接收和发送的 PPS,单位为包 / 秒。 rxkB/s 和 txkB/s 分别是接收和发送的吞吐量,单位是 KB/ 秒。 rxcmp/s 和 txcmp/s 分别是接收和发送的压缩数据包数,单位是包 / 秒。 %ifutil 是网络接口的使用率,即半双工模式下为 (rxkB/s+txkB/s)/Bandwidth,而全双工模式下为 max(rxkB/s, txkB/s)/Bandwidth。 ","date":"2019-02-09","objectID":"/linux-perf-net/:7:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"性能测试和评估 ","date":"2019-02-09","objectID":"/linux-perf-net/:8:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"TCP/UDP 性能 iperf 和 netperf 都是最常用的网络性能测试工具,测试 TCP 和 UDP 的吞吐量。 # 安装 $ apt-get install iperf3 # -s 表示启动服务端,-i 表示汇报间隔,-p 表示监听端口 $ iperf3 -s -i 1 -p 10000 # -c 表示启动客户端,192.168.0.30 为目标服务器的 IP # -b 表示目标带宽 (单位是 bits/s) # -t 表示测试时间 # -P 表示并发数,-p 表示目标服务器监听端口 $ iperf3 -c 192.168.0.30 -b 1G -t 15 -P 2 -p 10000 ","date":"2019-02-09","objectID":"/linux-perf-net/:8:1","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"HTTP 性能 ab 是 Apache 自带的 HTTP 压测工具,主要测试 HTTP 服务的每秒请求数、请求延迟、吞吐量以及请求延迟的分布情况等。 # 安装 $ apt-get install -y apache2-utils # -c 表示并发请求数为 1000,-n 表示总的请求数为 10000 $ ab -c 1000 -n 10000 http://192.168.0.30/ ","date":"2019-02-09","objectID":"/linux-perf-net/:8:2","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"应用负载性能 # -c 表示并发连接数 1000,-t 表示线程数为 2 $ wrk -c 1000 -t 2 http://192.168.0.30/ ","date":"2019-02-09","objectID":"/linux-perf-net/:8:3","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"参考 性能之巅-洞悉系统、企业与云计算 ","date":"2019-02-09","objectID":"/linux-perf-net/:9:0","tags":["Linux","网络"],"title":"Linux性能优化-网络","uri":"/linux-perf-net/"},{"categories":["Linux"],"content":"磁盘性能指标 说到磁盘性能的衡量标准,必须要提到五个常见指标,也就是我们经常用到的,使用率、饱和度、IOPS、吞吐量以及响应时间等。这五个指标,是衡量磁盘性能的基本指标。 使用率,是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味着磁盘 I/O 存在性能瓶颈。 饱和度,是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。 IOPS(Input/Output Per Second),是指每秒的 I/O 请求数。 吞吐量,是指每秒的 I/O 请求大小。 响应时间,是指 I/O 请求从发出到收到响应的间隔时间。 ","date":"2019-02-08","objectID":"/linux-perf-io/:1:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"性能工具 ","date":"2019-02-08","objectID":"/linux-perf-io/:2:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"I/O 快速分析步骤 从 I/O 延迟的案例到 MySQL 和 Redis 的案例,就会发现,虽然这些问题千差万别,但从 I/O 角度来分析,最开始的分析思路基本上类似,都是: 先用 iostat 发现磁盘 I/O 性能瓶颈; 再借助 pidstat ,定位出导致瓶颈的进程; 随后分析进程的 I/O 行为(比如用 strace 和 lsof,strace -p 12280 2\u003e\u00261 | grep write lsof -p 12280); 最后,结合应用程序的原理,分析这些 I/O 的来源。 为了缩小排查范围,我通常会先运行那几个支持指标较多的工具,如 top、iostat、vmstat、pidstat 等。 ","date":"2019-02-08","objectID":"/linux-perf-io/:3:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"I/O 性能优化 ","date":"2019-02-08","objectID":"/linux-perf-io/:4:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"应用程序优化 可以用追加写代替随机写,减少寻址开销,加快 I/O 写的速度。 可以借助缓存 I/O ,充分利用系统缓存,降低实际 I/O 的次数。 可以在应用程序内部构建自己的缓存,或者用 Redis 这类外部缓存系统。这样,一方面,能在应用程序内部,控制缓存的数据和生命周期;另一方面,也能降低其他应用程序使用缓存对自身的影响。 在需要频繁读写同一块磁盘空间时,可以用 mmap 代替 read/write,减少内存的拷贝次数。 在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync() 取代 O_SYNC。 在多个应用程序共享相同磁盘时,为了保证 I/O 不被某个应用完全占用,推荐你使用 cgroups 的 I/O 子系统,来限制进程 / 进程组的 IOPS 以及吞吐量。 ","date":"2019-02-08","objectID":"/linux-perf-io/:4:1","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"文件系统优化 可以根据实际负载场景的不同,选择最适合的文件系统。比如 Ubuntu 默认使用 ext4 文件系统,而 CentOS 7 默认使用 xfs 文件系统。 在选好文件系统后,还可以进一步优化文件系统的配置选项,包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等等。 可以优化文件系统的缓存。 ","date":"2019-02-08","objectID":"/linux-perf-io/:4:2","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"磁盘优化 最简单有效的优化方法,就是换用性能更好的磁盘,比如用 SSD 替代 HDD。 可以使用 RAID ,把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列。这样做既可以提高数据的可靠性,又可以提升数据的访问性能。 针对磁盘和应用程序 I/O 模式的特征,我们可以选择最适合的 I/O 调度算法。比方说,SSD 和虚拟机中的磁盘,通常用的是 noop 调度算法。而数据库应用,我更推荐使用 deadline 算法。 可以对应用程序的数据,进行磁盘级别的隔离。比如,我们可以为日志、数据库等 I/O 压力比较重的应用,配置单独的磁盘。 在顺序读比较多的场景中,我们可以增大磁盘的预读数据,比如,你可以通过下面两种方法,调整 /dev/sdb 的预读大小。 可以优化内核块设备 I/O 的选项。比如,可以调整磁盘队列的长度 /sys/block/sdb/queue/nr_requests,适当增大队列长度,可以提升磁盘的吞吐量(当然也会导致 I/O 延迟增大)。 最后,磁盘本身出现硬件错误,也会导致 I/O 性能急剧下降,所以发现磁盘性能急剧下降时,你还需要确认,磁盘本身是不是出现了硬件错误。 ","date":"2019-02-08","objectID":"/linux-perf-io/:4:3","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"磁盘容量 对文件系统来说,最常见的一个问题就是空间不足。当然,你可能本身就知道,用 df 命令,就能查看文件系统的磁盘空间使用情况。比如: $ df /dev/sda1 Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 30308240 3167020 27124836 11% / ","date":"2019-02-08","objectID":"/linux-perf-io/:5:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"磁盘 I/O 观测 iostat 是最常用的磁盘 I/O 性能观测工具,它提供了每个磁盘的使用率、IOPS、吞吐量等各种常见的性能指标,当然,这些指标实际上来自 /proc/diskstats。 iostat 的输出界面如下: # -d 表示显示 I/O 性能指标,-x 表示显示扩展统计(即所有 I/O 指标) $ iostat -d -x 1 Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util sda 0.01 1.02 0.03 510.55 0.00 0.02 0.00 1.99 7.56 1.09 0.00 3.95 500.45 2.74 0.28 sdb 1.39 0.12 33.91 7.73 0.50 0.39 26.29 76.99 6.65 41.15 0.01 24.44 65.65 5.90 0.89 # %util ,就是我们前面提到的磁盘 I/O 使用率; # r/s+ w/s ,就是 IOPS; # rkB/s+wkB/s ,就是吞吐量; # r_await+w_await ,就是响应时间。 ","date":"2019-02-08","objectID":"/linux-perf-io/:6:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"进程 I/O 观测 观察进程的 I/O 情况,可以使用 pidstat 和 iotop 这两个工具。 # -d 显示IO数据,-t显示线程,不加-t默认显示各进程 $ pidstat -td 1 13:39:51 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command 13:39:52 102 916 0.00 4.00 0.00 0 rsyslogd iotop 是一个类似于 top 的工具,你可以按照 I/O 大小对进程排序,然后找到 I/O 较大的那些进程。 $ iotop Total DISK READ : 0.00 B/s | Total DISK WRITE : 7.85 K/s Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 0.00 B/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO\u003e COMMAND 15055 be/3 root 0.00 B/s 7.85 K/s 0.00 % 0.00 % systemd-journald ","date":"2019-02-08","objectID":"/linux-perf-io/:7:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"参考 https://www.eet-china.com/mp/a117035.html 性能之巅-洞悉系统、企业与云计算 ","date":"2019-02-08","objectID":"/linux-perf-io/:8:0","tags":["Linux","IO"],"title":"Linux性能优化-IO","uri":"/linux-perf-io/"},{"categories":["Linux"],"content":"内存性能指标 ","date":"2019-01-29","objectID":"/linux-perf-memory/:1:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"系统内存使用情况 已用内存和剩余内存很容易理解,就是已经使用和还未使用的内存。 共享内存是通过 tmpfs 实现的,所以它的大小也就是 tmpfs 使用的内存大小。tmpfs 其实也是一种特殊的缓存。 可用内存是新进程可以使用的最大内存,它包括剩余内存和可回收缓存。 缓存包括两部分,一部分是磁盘读取文件的页缓存,用来缓存从磁盘读取的数据,可以加快以后再次访问的速度。另一部分,则是 Slab 分配器中的可回收内存。 缓冲区是对原始磁盘块的临时存储,用来缓存将要写入磁盘的数据。这样,内核就可以把分散的写集中起来,统一优化磁盘写入。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:1:1","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"进程内存使用情况 虚拟内存,包括了进程代码段、数据段、共享内存、已经申请的堆内存和已经换出的内存等。这里要注意,已经申请的内存,即使还没有分配物理内存,也算作虚拟内存。 常驻内存是进程实际使用的物理内存,不过,它不包括 Swap 和共享内存。 共享内存,既包括与其他进程共同使用的真实的共享内存,还包括了加载的动态链接库以及程序的代码段等。 Swap 内存,是指通过 Swap 换出到磁盘的内存。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:1:2","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"缺页异常 可以直接从物理内存中分配时,被称为次缺页异常。 需要磁盘 I/O 介入(比如 Swap)时,被称为主缺页异常。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:1:3","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"Swap 的使用情况 已用空间和剩余空间很好理解,就是字面上的意思,已经使用和没有使用的内存空间。 换入和换出速度,则表示每秒钟换入和换出内存的大小。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:1:4","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"内存性能工具 为了迅速定位内存问题,我通常会先运行几个覆盖面比较大的性能工具,比如 free、top、vmstat、pidstat 等。 具体的分析思路主要有这几步。 先用 free 和 top,查看系统整体的内存使用情况。 再用 vmstat 和 pidstat,查看一段时间的趋势,从而判断出内存问题的类型。 最后进行详细分析,比如内存分配分析、缓存 / 缓冲区分析、具体进程的内存使用分析等。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:2:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"查看内存使用情况 $ free -w total used free shared buffers cache available Mem: 8113464 214652 7745064 64 14180 139568 7689660 Swap: 2097152 0 2097152 # total 是总内存大小; # used 是已使用内存的大小,包含了共享内存; # free 是未使用内存的大小; # shared 是共享内存的大小; # buffers 是对磁盘数据的缓存,会用在读和写请求上 # cache 是文件数据的缓存,会用在读和写请求上 # available 是新进程可用内存的大小。 $ top top - 17:13:55 up 29 min, 0 users, load average: 0.01, 0.00, 0.00 Tasks: 9 total, 1 running, 8 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.1 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 8113464 total, 7745304 free, 213904 used, 154256 buff/cache KiB Swap: 2097152 total, 2097152 free, 0 used. 7690156 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 40 root 20 0 836056 140424 34740 S 0.3 1.7 0:27.98 hugo # VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。 # RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。 # SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。 # %MEM 是进程使用物理内存占系统总内存的百分比。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:3:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"缓存命中率 Buffer 和 Cache 的设计目的,是为了提升系统的 I/O 性能。它们利用内存,充当起慢速磁盘与快速 CPU 之间的桥梁,可以加速 I/O 的访问速度。 Buffer 和 Cache 分别缓存的是对磁盘和文件系统的读写数据。 缓存命中率是指直接通过缓存获取数据的请求次数,占所有请求次数的百分比。命中率越高说明缓存带来的收益越高,应用程序的性能也就越好。 安装 bcc 包后可以通过 cachestat 和 cachetop 来监测缓存的读写命中情况。 # 可以把bcc工具加到环境变量中,export PATH=$PATH:/usr/share/bcc/tools sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD echo \"deb https://repo.iovisor.org/apt/xenial xenial main\" | sudo tee /etc/apt/sources.list.d/iovisor.list sudo apt-get update sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r) # cachestat 提供了整个操作系统缓存的读写命中情况。 # TOTAL ,表示总的 I/O 次数; # MISSES ,表示缓存未命中的次数; # HITS ,表示缓存命中的次数; # DIRTIES, 表示新增到缓存中的脏页数; # BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位; # CACHED_MB 表示 Cache 的大小,以 MB 为单位。 $ cachestat 1 3 TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB 2 0 2 1 17 279 # cachetop 提供了每个进程的缓存命中情况。 # READ_HIT 和 WRITE_HIT ,分别表示读和写的缓存命中率。 $ cachetop 11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 13029 root python 1 0 0 100.0% 0.0% ","date":"2019-01-29","objectID":"/linux-perf-memory/:4:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"内存泄漏 虚拟内存分布从低到高分别是只读段,数据段,堆,内存映射段,栈五部分。其中会导致内存泄漏的是: 堆:由应用程序自己来分配和管理,除非程序退出这些堆内存不会被系统自动释放。 内存映射段:包括动态链接库和共享内存,其中共享内存由程序自动分配和管理 内存泄漏的危害比较大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。内存泄漏不断累积甚至会耗尽系统内存。 bcc 软件包中的 memleak 工具可以用来检测内存泄漏 # 可以根据内存分配的调用栈,定位内存的分配位置,从而释放不再访问的内存。 $ /usr/share/bcc/tools/memleak -a -p $(pidof app) 避免内存泄漏,最重要的一点就是养成良好的编程习惯,比如分配内存后,一定要先写好内存释放的代码,再去开发其他逻辑。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:5:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"Swap 对于程序自动分配的堆内存,也就是我们在内存管理中的匿名页,虽然这些内存不能直接释放,但是 Linux 提供了 Swap 机制将不常访问的内存写入到磁盘来释放内存,再次访问时从磁盘读取到内存即可。 Swap 本质就是把一块磁盘空间或者一个本地文件当作内存来使用,包括换入和换出两个过程: 换出:将进程暂时不用的内存数据存储到磁盘中,并释放这些内存 换入:进程再次访问内存时,将它们从磁盘读到内存中 当 Swap 变高时,你可以用 sar、/proc/zoneinfo、/proc/pid/status 等方法,查看系统和进程的内存使用情况,进而找出 Swap 升高的根源和受影响的进程。 Swap 优化: 禁止 Swap,现在服务器的内存足够大,所以除非有必要,禁用 Swap 就可以了。随着云计算的普及,大部分云平台中的虚拟机都默认禁止 Swap。 如果实在需要用到 Swap,可以尝试降低 swappiness 的值,减少内存回收时 Swap 的使用倾向。 响应延迟敏感的应用,如果它们可能在开启 Swap 的服务器中运行,你还可以用库函数 mlock() 或者 mlockall() 锁定内存,阻止它们的内存换出。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:6:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"内存优化思路 最好禁止 Swap。如果必须开启 Swap,降低 swappiness 的值,减少内存回收时 Swap 的使用倾向。 减少内存的动态分配。比如,可以使用内存池、大页(HugePage)等。 尽量使用缓存和缓冲区来访问数据。比如,可以使用堆栈明确声明内存空间,来存储需要缓存的数据;或者用 Redis 这类的外部缓存组件,优化数据的访问。 使用 cgroups 等方式限制进程的内存使用情况。这样,可以确保系统内存不会被异常进程耗尽。 通过 /proc/pid/oom_adj ,调整核心应用的 oom_score。这样,可以保证即使内存紧张,核心应用也不会被 OOM 杀死。 ","date":"2019-01-29","objectID":"/linux-perf-memory/:7:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"参考 https://www.eet-china.com/mp/a117035.html 性能之巅-洞悉系统、企业与云计算 ","date":"2019-01-29","objectID":"/linux-perf-memory/:8:0","tags":["Linux","内存"],"title":"Linux性能优化-内存","uri":"/linux-perf-memory/"},{"categories":["Linux"],"content":"各种延时 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:1:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"CPU 性能指标 CPU 使用率 用户 CPU 使用率, 包括用户态(user)和低优先级用户态(nice). 该指标过高说明应用程序比较繁忙. 系统 CPU 使用率, CPU 在内核态运行的时间百分比(不含中断). 该指标高说明内核比较繁忙. 等待 I/O 的 CPU 使用率, iowait, 该指标高说明系统与硬件设备 I/O 交互时间比较长. 软/硬中断 CPU 使用率, 该指标高说明系统中发生大量中断. steal CPU / guest CPU, 表示虚拟机占用的 CPU 百分比. 平均负载 理想情况下平均负载等于逻辑 CPU 个数,表示每个 CPU 都被充分利用. 若大于则说明系统负载较重. 进程上下文切换 包括无法获取资源的自愿切换和系统强制调度时的非自愿切换. 上下文切换本身是保证 Linux 正常运行的一项核心功能. 过多的切换则会将原本运行进程的 CPU 时间消耗在寄存器,内核占及虚拟内存等数据保存和恢复上 CPU 缓存命中率 CPU 缓存的复用情况,命中率越高性能越好. 其中 L1/L2 常用在单核,L3 则用在多核中 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:2:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"性能工具 平均负载案例 先用 uptime 查看系统平均负载 判断负载在升高后再用 mpstat 和 pidstat 分别查看每个 CPU 和每个进程 CPU 使用情况.找出导致平均负载较高的进程. 上下文切换案例 先用 vmstat 查看系统上下文切换和中断次数 再用 pidstat 观察进程的自愿和非自愿上下文切换情况 最后通过 pidstat 观察线程的上下文切换情况 进程 CPU 使用率高案例 先用 top 查看系统和进程的 CPU 使用情况,定位到进程 再用 perf top 观察进程调用链,定位到具体函数 系统 CPU 使用率高案例 先用 top 查看系统和进程的 CPU 使用情况,top/pidstat 都无法找到 CPU 使用率高的进程 重新审视 top 输出 从 CPU 使用率不高,但是处于 Running 状态的进程入手 perf record/report 发现短时进程导致 (execsnoop 工具) 不可中断和僵尸进程案例 先用 top 观察 iowait 升高,发现大量不可中断和僵尸进程 strace 无法跟踪进程系统调用 perf 分析调用链发现根源来自磁盘直接 I/O 软中断案例 top 观察系统软中断 CPU 使用率高 查看/proc/softirqs 找到变化速率较快的几种软中断 sar 命令发现是网络小包问题 tcpdump 找出网络帧的类型和来源,确定 SYN FLOOD 攻击导致 根据不同的性能指标来找合适的工具: 为了缩小排查范围,我通常会先运行几个支持指标较多的工具,如 top、vmstat 和 pidstat 。看看下面三者的关系: ","date":"2019-01-26","objectID":"/linux-perf-cpu/:3:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"平均负载和 CPU 使用率 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:4:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"平均负载 平均负载是指单位时间内,系统处于可运行状态(包括正在运行)和不可中断状态的平均进程数,也就是平均活跃进程数,它和 CPU 使用率并没有直接关系。 当平均负载高于 CPU 数量 70% 的时候,你就应该分析排查负载高的问题了。 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:4:1","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"CPU 使用率 CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。 CPU 使用率描述了非空闲时间占总 CPU 时间的百分比,根据 CPU 上运行任务的不同,又被分为用户 CPU、系统 CPU、等待 I/O CPU、软中断和硬中断等。 CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的; I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高; 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:4:2","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"排查步骤 # -d 参数表示高亮显示变化的区域 $ watch -d uptime 11:34:42 up 2:02, 0 users, load average: 0.06, 1.39, 1.94 # -P ALL 表示监控所有 CPU,后面数字 5 表示间隔 5 秒后输出一组数据 $ mpstat -P ALL 2 11:35:18 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 11:35:20 all 0.00 0.00 0.12 0.00 0.00 0.00 0.00 0.00 0.00 99.88 11:35:20 0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 11:35:20 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 # 间隔 5 秒后输出一组数据,-u 表示 CPU 指标 $ pidstat -u 5 1 11:38:16 UID PID %usr %system %guest %wait %CPU CPU Command 11:38:21 0 7137 50.00 0.00 0.00 50.00 50.00 2 stress 11:38:21 0 7138 49.80 0.00 0.00 50.20 49.80 3 stress 如果%usr 和%nice 比较高,%iowait 比较低则表明是 CPU 密集型 如果%usr 和%nice 比较低,%iowait 比较高则表明是 IO 密集型 如果%usr 和%nice 比较低,%iowait 比较低但是%wait 比较高说明进程太多了 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:4:3","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"上下文切换 CPU 的上下文切换就可以分为几个不同的场景,也就是进程上下文切换、线程上下文切换以及中断上下文切换。 由于上下文切换需要陷入内核,所以上下文切换属于系统 CPU 使用率。 根据上下文切换的类型,再做具体分析。 自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O、内存等其他问题; 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈; 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。 # 每隔 5 秒输出 1 组数据 $ vmstat 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 7846784 32708 148268 0 0 12 208 33 86 0 0 99 0 0 # cs(context switch)是每秒上下文切换的次数。 # in(interrupt)则是每秒中断的次数。 # r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。 # b(Blocked)则是处于不可中断睡眠状态的进程数。 # -w参数表示输出进程切换指标,-t参数表示输出线程的上下文切换指标,-u 参数则表示输出 CPU 使用指标 $ pidstat -wtu 5 11:25:28 UID TGID TID %usr %system %guest %wait %CPU CPU Command 11:25:33 0 20 - 0.00 0.20 0.00 0.00 0.20 2 fsnotifier-wsl 11:25:33 0 - 20 0.00 0.20 0.00 0.00 0.20 2 |__fsnotifier-wsl 11:25:28 UID TGID TID cswch/s nvcswch/s Command 11:25:33 0 20 - 2.00 0.00 fsnotifier-wsl 11:25:33 0 - 20 2.00 0.00 |__fsnotifier-wsl # cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数, # 自愿上下文切换是指进程无法获取所需资源,导致的上下文切换。 # nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数, # 非自愿上下文切换是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:5:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"具体某个应用 CPU 飙高 碰到 CPU 使用率升高的问题,你可以借助 top、pidstat 等工具,确认引发 CPU 性能问题的来源; 用户(%user) CPU 和 Nice(%nice) CPU 高,说明用户态进程占用了较多的 CPU,所以应该着重排查进程的性能问题。 系统(%system) CPU 高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。 I/O(%iowait) 等待 CPU 高,说明等待 I/O 的时间比较长,所以应该着重排查系统存储是不是出现了 I/O 问题。 软中断(%softirq)和硬中断(%irq)高,说明软中断或硬中断的处理程序占用了较多的 CPU,所以应该着重排查内核中的中断服务程序。 再使用 perf 等工具,排查出引起性能问题的具体函数。 $ top $ pidstat -u 5 1 $ vmstat 5 # -g 开启调用关系分析,-p 指定应用的的进程号 12096 $ perf top -g -p 21515 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:6:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"CPU 使用率整体高,但各个应用使用率都很低 碰到常规问题无法解释的 CPU 使用率情况时,首先要想到有可能是短时应用导致的问题,比如有可能是下面这两种情况。 应用里直接调用了其他二进制程序,这些程序通常运行时间比较短,通过 top 等工具也不容易发现。 应用本身在不停地崩溃重启,而启动过程的资源初始化,很可能会占用相当多的 CPU。 对于这类进程,我们可以用 pstree 或者 execsnoop 找到它们的父进程,再从父进程所在的应用入手,排查问题的根源。 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:7:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"iowait 高 碰到 iowait 升高时,需要先用 dstat、pidstat 等工具,确认是不是磁盘 I/O 的问题,然后再找是哪些进程导致了 I/O。 等待 I/O 的进程一般是不可中断状态,所以用 ps 命令找到的 D(Disk Sleep) 状态(即不可中断状态)的进程,多为可疑进程。 可以用 strace -p [pid] 直接分析这个进程的系统调用。 strace 没查到可使用 perf record -g 记录,perf report 再输出报告 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:8:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"僵尸进程 僵尸进程表示进程已经退出,但它的父进程还没有回收子进程占用的资源。短暂的僵尸状态我们通常不必理会,但进程长时间处于僵尸状态,就应该注意了,可能有应用程序没有正常处理子进程的退出。 使用 pstree 找出父进程后,去查看父进程的代码,检查 wait() / waitpid() 的调用,或是 SIGCHLD 信号处理函数的注册就行了。 # -a 表示输出命令行选项 # p 表 PID # s 表示指定进程的父进程 $ pstree -aps 12757 init,1 └─init,6604 └─init,6605 └─bash,6606 └─go,12757 run main.go ├─main,12809 │ ├─{main},12810 │ ├─{main},12811 ├─{go},12758 ├─{go},12759 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:9:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"中断 中断是系统用来响应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。 Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部: 上半部用来快速处理中断,也就是我们常说的硬中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。 下半部则是由内核触发,也就是我们常说的软中断,下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。 上半部会打断 CPU 正在执行的任务,然后立即执行中断处理程序。而下半部以内核线程的方式执行,并且每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/CPU 编号”,比如说, 0 号 CPU 对应的软中断内核线程的名字就是 ksoftirqd/0。 软中断不只包括了刚刚所讲的硬件设备中断处理程序的下半部,一些内核自定义的事件也属于软中断,比如网络收发、定时、内核调度和 RCU 锁(Read-Copy Update 的缩写,RCU 是 Linux 内核中最常用的锁之一)等。 # 查看软中断 cat /proc/softirqs # 查看硬中断 cat /proc/interrupts 软中断 CPU 使用率(softirq)升高是一种很常见的性能问题。虽然软中断的类型很多,但实际生产中,我们遇到的性能瓶颈大多是网络收发类型的软中断,特别是网络接收的软中断。 在碰到这类问题时,你可以借用 sar、tcpdump 等工具,做进一步分析。 # -i eth0 只抓取 eth0 网卡,-n 不解析协议名和主机名 # tcp port 80 表示只抓取 tcp 协议并且端口号为 80 的网络帧 $ tcpdump -i eth0 -n tcp port 80 # -n DEV 表示显示网络收发的报告,间隔 1 秒输出一组数据 $ sar -n DEV 1 20:52:29 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil 20:52:30 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:10:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["Linux"],"content":"参考 https://www.eet-china.com/mp/a117035.html 性能之巅-洞悉系统、企业与云计算 ","date":"2019-01-26","objectID":"/linux-perf-cpu/:11:0","tags":["Linux","CPU"],"title":"Linux性能优化-CPU","uri":"/linux-perf-cpu/"},{"categories":["流程","规范"],"content":"1. 需求阶段 市场调研 可行性分析 需求分析 需求文档 需求评审 ","date":"2018-09-06","objectID":"/development-workflow/:1:0","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"2. 设计阶段 产品设计 交互设计 视觉设计 技术设计 技术评审 需求排期 ","date":"2018-09-06","objectID":"/development-workflow/:2:0","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"3. 开发阶段 ","date":"2018-09-06","objectID":"/development-workflow/:3:0","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"3.1 开发 Git Flow 工作流 生成代码 版权检查 编码 静态代码检查 单元测试 编译 自测 Code Review Merge ","date":"2018-09-06","objectID":"/development-workflow/:3:1","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"3.2 构建(CI) 代码扫描 单元测试 编译打包 归档 镜像仓库 制品库 ","date":"2018-09-06","objectID":"/development-workflow/:3:2","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"4. 测试阶段 功能测试 性能测试 集成测试 系统测试 ","date":"2018-09-06","objectID":"/development-workflow/:4:0","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"5. 发布阶段 ","date":"2018-09-06","objectID":"/development-workflow/:5:0","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"5.1 代码发布 合并到主干 生成版本号 打标签 代码扫描 单元测试 编译 发布构建产物 ","date":"2018-09-06","objectID":"/development-workflow/:5:1","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"5.2 发布审批 资源申请 创建发布计划 创建发布单 发布单审批 ","date":"2018-09-06","objectID":"/development-workflow/:5:2","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"5.3 服务发布 预发部署 预发验证 现网部署 现网验证 ","date":"2018-09-06","objectID":"/development-workflow/:5:3","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"6. 运营阶段 运维 产品运营 ","date":"2018-09-06","objectID":"/development-workflow/:6:0","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["流程","规范"],"content":"7. 参考 https://time.geekbang.org/column/intro/100079601 ","date":"2018-09-06","objectID":"/development-workflow/:7:0","tags":["流程","规范"],"title":"开发流程","uri":"/development-workflow/"},{"categories":["软件工程"],"content":"1. 什么是软件生命周期 软件生命周期又称为软件生存周期或系统开发生命周期,是指从软件的产生直到报废的整个过程,它包括问题定义、可行性分析、总体描述、系统设计、编码、调试和测试、验收与运行、维护升级到废弃等阶段。每一个阶段都有确定的任务,并产生一定规格的文档(资料),提交给下一个周期作为继续工作的依据。 ","date":"2018-09-04","objectID":"/software-life-cycle/:1:0","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"2. 软件生命周期过程包括哪些方面 问题定义:用户需要计算机解决的问题是什么? 可行性分析:用户需要计算机解决的问题是否可行? 市场可行性分析,是否有市场价值。 技术可行性分析,使用什么技术解决用户提出的问题。 需求分析(重点):将用户提出的问题进行细化,先确定大模块,对每一个大模块进行细化,直到细化到不能细化为止。 设计(次重点):确定细化问题的实现方法(比如:要设计什么接口,设计功能什么技术实现……) 编码:去解决问题,依据需求和设计文档进行开发 测试:验证是否已经解决用户提出的问题。 单元测试(通过在开发阶段由开发人员进行测试) 集成测试(测试业务整体流程) 功能用例测试(对每个细化的功能点进行测试) 性能测试(使用专业工具进行压力和稳定性测试) 维护(占整个软件生命周期很大的比重) 修改性维护:前期没有测试出的问题,正式上线运行后 bug 显示出来了,对这些 bug 进行修复。 完善性维护:在现有功能的基础上增加或完善功能。 预防性维护:后期根据正式运行的情况对系统进行优化。 ","date":"2018-09-04","objectID":"/software-life-cycle/:2:0","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"3. 常用的开发模型 ","date":"2018-09-04","objectID":"/software-life-cycle/:3:0","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"3.1 瀑布模型 瀑布模型是软件开发的各个阶段是顺序执行,从系统需求分析开始知道产品发布和维护,每个阶段都会产生循环反馈,因此,如果有信息未被覆盖或者发现了问题,那么最好“返回”上一个阶段并进行适当的修改,项目开发进程从一个阶段“流动”到下一个阶段,这也是瀑布模型名称的由来。 适用场合:需求明确,解决方案明确,常在一些中小型项目中使用。 ┌─────────────┐ | │ ┌───────────┐ │ ┌──────────┐ │ 需求分析 │ ▼ | │ └───────────┘ ┌───────────┐ │ ┌──────────┐ │ 设计 │ ▼ | │ └───────────┘ ┌───────────┐ │ ┌──────────┐ │ 实施 │ ▼ | │ └───────────┘ ┌───────────┐ │ │ 测试 │ ▼ └───────────┘ ┌───────────┐ │ 维护 │ └───────────┘ ","date":"2018-09-04","objectID":"/software-life-cycle/:3:1","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"3.2 原型模型 原型模型即样品模型,先借用已有系统作为原型模型,通过“样品”不断改进,使得最后的产品就是用户所需要的。 原型模型采用逐步求精的方法完善原型,使得原型能够“快速”开发,避免了像瀑布模型一样在冗长的开发过程中难以对用户的反馈做出快速的响应。 强调:逐步求精(每一次修改对上次原型的完善)对原型进行修改优化,开发阶段围绕着原型(当成样品)进行实施。 适用场合:前期需求不确定,采用原型方法方便搜集需求。 ","date":"2018-09-04","objectID":"/software-life-cycle/:3:2","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"3.3 增量模型 增量模型:瀑布模型+原型模型,增量模型融合了瀑布模型的基本成分(重复应用)和原型实现的迭代特征。 每一次增量经过了开发的每一个阶段(瀑布模型经过的所有阶段)。 每一次增量对功能进行完善(原型模型的逐步求精)。 适用场合:大型项目,需求不明确。 ","date":"2018-09-04","objectID":"/software-life-cycle/:3:3","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"4. 需求分析 ","date":"2018-09-04","objectID":"/software-life-cycle/:4:0","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"4.1 什么是需求分析 需求分析是在问题定义以及可行性分析完成后细化用户对软件的功能和性能的要求,即用户希望软件做什么事情,完成什么样的功能,达到什么性能。 重要性:需求分析是软件工程的开端,我们设计的软件产品存在不完整性、不正确性大部分原因是需求分析错误导致的,因此,需求分析是软件生命周期中最重要的过程。 需求分析包括需求调研、需求描述、需求评审。 ","date":"2018-09-04","objectID":"/software-life-cycle/:4:1","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"4.2 需求调研 目标:挖掘用户的需求。 过程:首先确定目标用户,开发人员和目标用户确定一个问题领域,并定义一个描述问题的系统,用户在这个问题领域和系统下提出需求,需求类型包括:功能需求、质量需求、用户体验需求等。 需求调研方法:根据不同的项目、不同的用户群体采用不同的方法。 与客户交谈,向用户提问题 参观用户工作流程,观察用户操作 向用户发调查问卷(通常是以选择题和是非题为主) 与同行、专家交谈,听取他们的意见 分析已经存在的软件产品,提取需求 从行业标准、规划中提取需求 上网搜索相关资料 成果物:需求调研报告.doc ","date":"2018-09-04","objectID":"/software-life-cycle/:4:2","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"4.3 需求描述 通过需求调研对收集到的资料进行分析、鉴别、综合和建模,清除用户需求的模糊性、歧义性和不一致性,分析系统的数据要求,为原始问题及目标软件建立逻辑模型。分析人员需要编写“软件需求说明书”并与用户交流确认。 成果物:“软件需求说明书”或“软件需求规格说明书”。 重要性:软件开发人员需要根据“软件需求规格说明书”进行设计和编码。 软件需求规格说明书的内容也是根据公司的项目管理水平而定。 内容基本包括:系统概述、运行环境、功能模块图、功能用例、性能需求、接口需求、其他需求等。 ","date":"2018-09-04","objectID":"/software-life-cycle/:4:3","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"4.4 需求评审 由架构师、技术经理、需求分析人员、设计人员、开发人员对“软件需求规格说明书”进行审核。 审核内容: 看软件需求规格说明书是否符合文档规范 需求描述是否详细(设计人员可以依据需求说明书进行设计) ","date":"2018-09-04","objectID":"/software-life-cycle/:4:4","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"5. 设计 软件设计是从软件需求规格说明书出发,根据需求分析阶段确定的功能设计软件系统的整体结构、划分功能模块、确定每个模块的实现算法以及编写核心代码,形成软件的具体设计方案。软件设计一般分为总体设计(概要设计)和详细设计。 ","date":"2018-09-04","objectID":"/software-life-cycle/:5:0","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"5.1 概要设计 根据软件需求说明,建立目标系统的总体结构和模块间的关系;系统网络部署结构;核心业务流程;定义系统的外部接口、以及模块间的接口;编写概要文档;概要设计审查。 概要设计内容: 总体结构和模块间的关系; 系统架构包括:技术架构和功能架构(就是需求描述的功能框图) 系统网络部署结构 业务流程图 定义系统的外部接口、以及模块间的接口 接口数据描述(重点) ","date":"2018-09-04","objectID":"/software-life-cycle/:5:1","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"5.2 详细设计 详细设计内容:详细设计的主要任务是设计每个模块的类接口、所需的局部数据结构、物理数据模型、页面原型等。 注意:一般情况下根据公司人力现状不写详细设计文档的,可以在需求分析阶段和概要设计阶段对详细设计的内容进行描述。 每个模块的类接口:通过伪代码实现,描述接口参数、接口功能。 局部数据结构:需要自定义什么数据类型。 物理数据模型(重点):使用 powerDesigner 设计物理数据模型(根据公司要求可能在需求分析阶段或概要设计阶段实现)。 ","date":"2018-09-04","objectID":"/software-life-cycle/:5:2","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["软件工程"],"content":"6. 参考 https://www.bilibili.com/video/BV1Pt411b7yx ","date":"2018-09-04","objectID":"/software-life-cycle/:6:0","tags":["需求分析","软件设计"],"title":"软件生命周期之需求分析和软件设计","uri":"/software-life-cycle/"},{"categories":["敏捷","规范"],"content":"1. 起源 Scrum(司克兰)源自日本的“丰田生产系统”(Toyota Production System)和美国空军的 OODA 循环理论。 OODA: 又称为“博伊德循环”,是一种空战理论。OODA 指 observe-orient-decide-act,意为“观察-\u003e导向-\u003e决定-\u003e行动”。该理论认为双方都从观察开始,包括观察自己、环境和敌人,在观察的基础上获取相关外部信息,根据感知到的外部威胁,及时调整系统,做出应对决定,并采取相应行动。 PDCA 循环。PDCA 四个英文字母分别代表计划(Plan)、执行(Do)、检查(Check)与行动(Action)。 敏捷软件开发宣言:人胜过流程、可以使用的软件胜过面面俱到的文件、客户合作胜过合同谈判、应对变化胜过遵循计划。 ","date":"2018-09-02","objectID":"/scrum/:1:0","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"2. scrum 方法的本质 无论你什么时候启动一个项目(可以运用到各个行业),为什么不经常检验一下自己正在做的事情,看看是否朝着正确的方向前进?结果是不是大家真正希望看到的?是否有什么办法能改善目前正在做的事情?如何才能做得更快更好?存在哪些潜在的障碍? Scrum 是一种迭代式增量软件开发过程。所谓迭代,是指把一个复杂且开发周期很长的开发任务分解为很多短期可完成的任务,这样的一个周期就是一次迭代的过程;同时,每一次迭代都可以生产或开发出一款可以交付的产品。 ","date":"2018-09-02","objectID":"/scrum/:2:0","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3. scrum 实践步骤 ","date":"2018-09-02","objectID":"/scrum/:3:0","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.1 挑选一位产品负责人。 这个人必须知道自己带领的团队需要做什么、制造什么产品以及取得什么成果,必须全面考虑到风险与回报、什么具有可行性、什么能做以及他们对什么富有热情。(参见第八章) ","date":"2018-09-02","objectID":"/scrum/:3:1","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.2 挑选一个团队。 真正做事的是谁?这个团队必须能够落实产品负责人的愿景。团队规模宜小不宜大,一般 3~9 人较为合适。为一个延误的 IT(信息技术)项目增加人员,将导致更严重的延误。 ","date":"2018-09-02","objectID":"/scrum/:3:2","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.3 挑选 Scrum 主管。 主管为 Scrum 过程负责,负责培训团队其他成员,确保 Scrum 得到正确运用,帮助团队消除一切障碍。 ","date":"2018-09-02","objectID":"/scrum/:3:3","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.4 拟定待办事项清单,并确定优先顺序。 这个清单高屋建瓴地列出为了落实产品负责人的愿景而需要完成的所有事项。在产品的整个研发过程中,这个清单一直存在,并有所演变,相当于产品研发的“路线图”。无论在任何时间,要想知道一个团队要做的所有事项(按照优先顺序排列),待办事项清单都是唯一具有决定性的参考依据。待办事项清单只有一份,意味着产品负责人从头到尾必须不断地对优先顺序加以调整。产品负责人应该与所有利益相关者和团队进行协商,以确保产品待办事项清单既能反映用户的需求,又不会超出团队的能力范围。 ","date":"2018-09-02","objectID":"/scrum/:3:4","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.5 改进和评估待办事项清单。 让负责实际开发工作的团队对待办事项做出评估,是一个至关重要的环节。团队应该审视每个事项,看看是否切实可行。但要完成这些事项,现有的信息足够吗?该项目是否细分到了可以评估的程度?团队是否具有了每个成员都能接受、用于评定一个事项已完成的标准?一个事项能否带来显著的价值?各个事项在完成后必须产生能够用来展示的成果,如果这个成果能交付给客户试用会更好。不要用所需小时数去评估,因为人们根本不擅长做出这么精确的评估。要用相对难度去评估,比如,难度是小、中或大。更好的方式是采用斐波那契数列的数字(1,2,3,5,8,13,21……)。 ","date":"2018-09-02","objectID":"/scrum/:3:5","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.6 冲刺规划会。 这是第一场 Scrum 会议。团队成员、Scrum 主管以及产品负责人坐到一起,规划冲刺的内容。冲刺周期一般是固定的,不超过一个月,大部分是一至两周。团队要从待办事项清单的顶端着手(即从最重要的事项着手),看看一个冲刺阶段中能完成多少。如果团队已经开展过好几个冲刺,那就记录下每一个冲刺完成的事项的“点数”。这个数字相当于团队的速度。Scrum 主管与团队成员应努力在每一个冲刺阶段中提高这个数字。团队成员和产品负责人也可以借助“点数”确保每个人都能了解待办事项对于落实最终愿景的作用。对于冲刺目标,即在这一冲刺阶段完成哪些事项,所有人都应该形成共识。 Scrum 的基石之一在于,产品负责人告诉开发团队他需要完成产品订单中的哪些订单项。开发团队决定在下一次冲刺中他们能够承诺完成多少订单项。在冲刺的过程中,没有人能够变更冲刺内容。团队必须在冲刺阶段自主工作。 ","date":"2018-09-02","objectID":"/scrum/:3:6","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.7 工作透明化。 在 Scrum 中,最常见的做法是准备一块白板,上面分成三栏:待办事项、在办事项、完成事项。把待办事项写到便笺纸上,随着进度的推进,将相应的便笺纸转移到其他栏目。让工作透明化的另一个工具是燃尽图。在这张图中,一个轴代表工作量,另一个轴代表时间。每天,Scrum 主管都会记录待完成的剩余点数,而后画在燃尽图上。理想情况下,该图是一条向下的曲线,随着剩余工作的完成,“燃尽”至零。 ","date":"2018-09-02","objectID":"/scrum/:3:7","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.8 每日立会。 这是 Scrum 的活力源泉。团队每天在固定时间进行内部沟通,时间一般不超过 15 分钟,且站立进行,Scrum 主管向团队成员提出下列问题:(1)你昨天做了什么去帮助团队完成冲刺?(2)今天你打算做什么来帮助团队完成冲刺?(3)什么因素阻碍了团队的前进之路?Scrum 主管要问的问题就是这么多!整个会议的内容就是这么多! 如果会议时间超过 15 分钟,那就说明开会的方法存在问题。这样做的意义在于让整个团队清楚地知道在这一个冲刺周期内各项任务的进展。所有任务都能按时完成吗?有没有机会帮助其他团队成员克服障碍?团队的任务都不是自上而下分派的,而是自主决定、自主完成的,也不需要向上司做详细的汇报。Scrum 主管负责消除团队面临的障碍。 ","date":"2018-09-02","objectID":"/scrum/:3:8","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.9 冲刺评估或冲刺展示。 在冲刺结束前,给产品负责人展示成果,也就是展示哪些事项可以挪到“完成事项”那一栏,并接受评价。这是一场公开的会议,任何人都可以是参与者,不仅仅包括产品负责人、Scrum 主管及开发团队,还包括利益相关者、管理人员与客户。团队应该只展示那些符合“完成定义”的事项,也就是全部完成,不需要再做工作就能交付的成果。这个成果或许不是完整的产品,但至少是一项完整的、可以使用的功能。 ","date":"2018-09-02","objectID":"/scrum/:3:9","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.10 冲刺回顾。 团队展示之前冲刺中创造的成果,也就是展示已完成的事项,看看可以为顾客传递哪些价值,并征求反馈意见,大家就会坐下来想想哪些事执行得很顺利,哪些事应该做得更好,以及在下一个冲刺阶段中可以做出什么改善。 那么,如何发现流程中的哪个环节需要改善呢?要让这个冲刺回顾过程有效,团队需要相互信任。必须记住关键的一点,即大家不要从团队中找一个人当成责备的对象,而是要将注意力集中在流程上,认真分析以下几个问题:为什么会发生那件事?为什么我们当时忽略了?怎样才能加快工作进度?作为一个团队,大家要对自己的流程和结果负责,要集思广益,共同寻求问题解决之道。这一点是至关重要的。 与此同时,团队必须有勇气把真正的障碍摆到台面上来,这样做是为了解决问题,而不是为了指责某个成员。团队成员必须能认真探讨问题,并虚心接受他人反馈的意见和建议,以便寻求问题解决之道,而非只想着为自己辩解。然后就进入了关键环节。团队确定一个最值得改善的地方,将其设定为下一个冲刺阶段的首要任务,当然,改善的结果必须通过“验收测试”。你如何证明自己成功地完成了改善?你需要用具体的、可操作的方式界定什么是“成功”,这样,在下一个冲刺回顾会议中才能很快判断出是否已完成改善。 ","date":"2018-09-02","objectID":"/scrum/:3:10","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"3.11 上一个冲刺阶段结束之后,立即开始新的冲刺阶段。 利用在之前的冲刺过程中,团队在消除障碍、改善流程方面积累的经验。 ","date":"2018-09-02","objectID":"/scrum/:3:11","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["敏捷","规范"],"content":"参考 《敏捷革命》杰夫.萨瑟兰 ","date":"2018-09-02","objectID":"/scrum/:4:0","tags":["敏捷","规范"],"title":"scrum","uri":"/scrum/"},{"categories":["网络"],"content":"对 TCP 性能的考虑 ","date":"2018-08-26","objectID":"/net-perf/:1:0","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"握手时延 小的 HTTP 事务可能会在 TCP 建立上花费 50%,或更多的时间。后面的小节会讨论 HTTP 是如何通过重用现存连接,来减小这种 TCP 建立时延所造成的影响的。 ","date":"2018-08-26","objectID":"/net-perf/:1:1","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"延迟确认 延迟确认算法会在一个特定的窗口时间(通常是 100 ~ 200 毫秒)内将输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。 通常,延迟确认算法会引入相当大的时延。根据所使用操作系统的不同,可以调整或禁止延迟确认算法。 和延迟确认一样,Nagle 也没有直接提高性能,启用它的作用只是提高传输效率,减轻网络负担。 ","date":"2018-08-26","objectID":"/net-perf/:1:2","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"累计确认 发送方在一个窗口里发出 n 个包,是不是就能收到 n 个确认包?不一定,确认包一般会少一些。由于 TCP 可以累积起来确认,所以当收到多个包的时候,只需要确认最后一个就可以了。比如客户端用一个包(包号 49)确认了它收到的 10 个包(39 ~ 48 号包)。 ","date":"2018-08-26","objectID":"/net-perf/:1:3","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"超时重传 TCP 超时重传的间隔时间太长,设置一个较小的时间可以减少重传对性能的影响。 ","date":"2018-08-26","objectID":"/net-perf/:1:4","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"发送窗口 发送窗口决定了一口气能发多少字节,而 MSS 决定了这些字节要分多少个包发完。举个例子,在发送窗口为 16000 字节的情况下,如果 MSS 是 1000 字节,那就需要发送 16000/1000=16 个包;而如果 MSS 等于 8000,那要发送的包数就是 16000/8000=2 了。 该网络频繁拥塞,拥塞点大多在 32KB 以上。如果把发送窗口限制在 32KB,就可以避免触碰拥塞点。 ","date":"2018-08-26","objectID":"/net-perf/:1:5","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"TCP Window Scale 在 TCP 刚被发明的时候,全世界的网络带宽都很小,所以最大接收窗口被定义成 65535 字节。随着硬件的革命性进步,65535 字节已经成为性能瓶颈了,怎么样才能扩展呢?TCP 头中只给接收窗口值留了 16 bit,肯定是无法突破 65535(216−1)的。1992 年的 RFC 1323 中提出了一个解决方案,就是在三次握手时,把自己的 Window Scale 信息告知对方。由于 Window Scale 放在 TCP 头之外的 Options 中,所以不需要修改 TCP 头的设计。Window Scale 的作用是向对方声明一个 Shift count,我们把它作为 2 的指数,再乘以 TCP 头中定义的接收窗口,就得到真正的 TCP 接收窗口了。 ","date":"2018-08-26","objectID":"/net-perf/:1:6","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"SACK TCP SACK(Selective Acknowledgement)是一个 TCP 的选项,来允许 TCP 单独确认非连续的片段,用于告知真正丢失的包,只重传丢失的片段。要使用 SACK,2 个设备必须同时支持 SACK 才可以,建立连接的时候需要使用 SACK Permitted 的 option,如果允许,后续的传输过程中 TCP segment 中的可以携带 SACK option。 只要把“Ack=656925”和“SACK: 661857-663035”这两个因素结合起来,客户端就知道排在后面的数据段 661857-663035 已经送达,但排在前面的 656925-661856(共 4932 字节)反而丢失了,因此它需要重传这段数据。 ","date":"2018-08-26","objectID":"/net-perf/:1:7","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"临界窗口 那临界窗口应该如何取值才合理呢?我能想到的,就是在带宽大的环境中取得大一些,在带宽小的环境中取得小一些。RFC 2001 也是这样建议的,它把临界窗口值定义为发生丢包时拥塞窗口的一半大小。我们可以想象在带宽大的环境中,发生丢包时的拥塞窗口往往也比较大,所以临界窗口值自然会随之加大。 ","date":"2018-08-26","objectID":"/net-perf/:1:8","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"WEB 性能优化 大多数网站性能的瓶颈都是延迟,而不是带宽! ","date":"2018-08-26","objectID":"/net-perf/:2:0","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"性能检查清单 把服务器内核升级到最新版本(Linux:3.2+); 确保 cwnd(拥塞窗口) 大小为 10; 禁用空闲后的慢启动; 确保启动窗口缩放; 减少传输冗余数据; 压缩要传输的数据; 把服务器放到离用户近的地方以减少往返时间; 尽最大可能重用已经建立的 TCP 连接。 ","date":"2018-08-26","objectID":"/net-perf/:2:1","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"服务器配置调优 增大 TCP 的初始拥塞窗口 加大起始拥塞窗口可以让 TCP 在第一次往返就传输较多数据,而随后的速度提升也会很明显。对于突发性的短暂连接,这也是特别关键的一个优化。 慢启动重启 在连接空闲时禁用慢启动可以改善瞬时发送数据的长 TCP 连接的性能。 窗口缩放(RFC 1323) 启用窗口缩放可以增大最大接收窗口大小,可以让高延迟的连接达到更好吞吐量。 TCP 快速打开 在某些条件下,允许在第一个 SYN 分组中发送应用程序数据。TFO(TCP Fast Open,TCP 快速打开)是一种新的优化选项,需要客户端和服务器共同支持。为此,首先要搞清楚你的应用程序是否可以利用这个特性。 ","date":"2018-08-26","objectID":"/net-perf/:2:2","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"应用程序行为调优 再快也快不过什么也不用发送,能少发就少发。 我们不能让数据传输得更快,但可以让它们传输的距离更短。 重用 TCP 连接是提升性能的关键。 ","date":"2018-08-26","objectID":"/net-perf/:2:3","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"移动网络优化 轮询在移动网络中代价极高,少用; 尽可能使用推送和通知; 出站和入站请求应该合并和汇总; 非关键性请求应该推迟到无线模块活动时进行。 ","date":"2018-08-26","objectID":"/net-perf/:3:0","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"最佳实践 无论什么网络,也不管所用网络协议是什么版本,所有应用都应该致力于消除或减少不必要的网络延迟,将需要传输的数据压缩至最少。这两条标准是经典的性能优化最佳实践,是其他数十条性能准则的出发点。 减少 DNS 查找 每一次主机名解析都需要一次网络往返,从而增加请求的延迟时间,同时还会阻塞后续请求。 重用 TCP 连接 尽可能使用持久连接,以消除 TCP 握手和慢启动延迟;参见“慢启动”。 减少 HTTP 重定向 HTTP 重定向极费时间,特别是不同域名之间的重定向,更加费时;这里面既有额外的 DNS 查询、TCP 握手,还有其他延迟。最佳的重定向次数为零。 使用 CDN(内容分发网络) 把数据放到离用户地理位置更近的地方,可以显著减少每次 TCP 连接的网络延迟,增大吞吐量。这一条既适用于静态内容,也适用于动态内容。 去掉不必要的资源 任何请求都不如没有请求快。说到这,所有建议都无需解释。延迟是瓶颈,最快的速度莫过于什么也不传输。然而,HTTP 也提供了很多额外的机制,比如缓存和压缩,还有与其版本对应的一些性能技巧。 在客户端缓存资源 应该缓存应用资源,从而避免每次请求都发送相同的内容。 传输压缩过的内容 传输前应该压缩应用资源,把要传输的字节减至最少:确保对每种要传输的资源采用最好的压缩手段。 消除不必要的请求开销 减少请求的 HTTP 首部数据(比如 HTTP cookie),节省的时间相当于几次往返的延迟时间。 并行处理请求和响应 请求和响应的排队都会导致延迟,无论是客户端还是服务器端。这一点经常被忽视,但却会无谓地导致很长延迟。 针对协议版本采取优化措施 HTTP 1.x 支持有限的并行机制,要求打包资源、跨域分散资源,等等。相对而言,HTTP 2.0 只要建立一个连接就能实现最优性能,同时无需针对 HTTP 1.x 的那些优化方法。 ","date":"2018-08-26","objectID":"/net-perf/:4:0","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"参考 《HTTP 权威指南》 《WEB 性能权威指南》 ","date":"2018-08-26","objectID":"/net-perf/:5:0","tags":["网络"],"title":"网络性能优化","uri":"/net-perf/"},{"categories":["网络"],"content":"HTTPS 协议简介 HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer 或 Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版。即 HTTP 下加入 TLS 层,HTTPS 的安全基础是 TLS,因此加密的详细内容就需要 TLS 它是一个 URI scheme(抽象标识符体系),句法类同 http:体系。用于安全的 HTTP 数据传输。https:URL 表明它使用了 HTTP,但 HTTPS 存在不同于 HTTP 的默认端口及一个加密/身份验证层(在 HTTP 与 TCP 之间)。 注意:TLS 的早期版本叫做 SSL。SSL 的 1.0, 2.0, 3.0(对应 TLS1.0) 版本均已经被废弃,出于安全问题考虑广大浏览器也不再对老旧的 SSL 版本进行支持了,因此这里我们就统一使用 TLS 名称了。 ","date":"2018-08-23","objectID":"/https/:1:0","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"HTTP 与 HTTPS 区别 HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。 HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS 除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。 http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。 HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。 ","date":"2018-08-23","objectID":"/https/:2:0","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"HTTPS 原理解析 客户端请求 HTTPS 网址,然后连接到 server 的 443 端口 (HTTPS 默认端口,类似于 HTTP 的 80 端口)。 采用 HTTPS 协议的服务器必须要有一套数字 CA (Certification Authority)证书,证书是需要申请的,并由专门的数字证书认证机构(CA)通过非常严格的审核之后颁发的电子证书 (当然了是要钱的,安全级别越高价格越贵)。颁发证书的同时会产生一个私钥和公钥。私钥由服务端自己保存,不可泄漏。公钥则是附带在证书的信息中,可以公开的。证书本身也附带一个证书电子签名,这个签名用来验证证书的完整性和真实性,可以防止证书被篡改。 服务器响应客户端请求,将证书传递给客户端,证书包含公钥和大量其他信息,比如证书颁发机构信息,公司信息和证书有效期等。Chrome 浏览器点击地址栏的锁标志再点击证书就可以看到证书详细信息。 客户端解析证书并对其进行验证。如果证书不是可信机构颁布,或者证书中的域名与实际域名不一致,或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。如果证书没有问题,客户端就会从服务器证书中取出服务器的公钥 A。然后客户端还会生成一个随机码 KEY,并使用公钥 A 将其加密。 客户端把加密后的随机码 KEY 发送给服务器,作为后面对称加密的密钥。 服务器在收到随机码 KEY 之后会使用私钥 B 将其解密。经过以上这些步骤,客户端和服务器终于建立了安全连接,完美解决了对称加密的密钥泄露问题,接下来就可以用对称加密愉快地进行通信了。 服务器使用密钥 (随机码 KEY)对数据进行对称加密并发送给客户端,客户端使用相同的密钥 (随机码 KEY)解密数据。 双方使用对称加密愉快地传输所有数据。 ","date":"2018-08-23","objectID":"/https/:3:0","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"HTTPS 安全问题 ","date":"2018-08-23","objectID":"/https/:4:0","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"中间人攻击 基本原理就是中间人通过网络劫持等,将通信过程中的公钥替换成自己的,然后假装自己是服务器与客户端进行通信。从而对信息进行窃取或篡改。 我们知道,公私钥及证书都是可以自己进行生成的,虽然发起了 HTTPS 的请求,但如果证书和公私钥无法保证是否被替换,传输的安全性就无法保证。此时,就需要拿出终极武器:SSL 证书申购。也称作 CA 证书申购。 ","date":"2018-08-23","objectID":"/https/:4:1","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"CA 证书 CA 是证书的签发机构,它是公钥基础设施(Public Key Infrastructure,PKI)的核心。CA 是负责签发证书、认证证书、管理已颁发证书的机关。有这样一个权威机构来签发证书,就确保了证书的可信性(合法性)。 浏览器会对服务器返回 SSL 证书进行验证: 验证域名、有效期等信息是否正确; 判断证书来源是否合法:每份签发证书都可以根据验证链查找到对应的根证书,操作系统、浏览器会在本地存储权威机构的根证书,利用本地根证书可以对对应机构签发证书完成来源验证; 判断证书是否被篡改:需要与 CA 服务器进行校验; 判断证书是否已吊销,可用于第 3 步中,以减少与 CA 服务器的交互,提高验证效率。 上述条件完全满足时,才说明该证书合法。 此时,再回到“中间人”攻击的问题,会发现,当浏览器获取到假公钥时,通过比对验证就会发现不合法,进而在浏览器层面对用户进行风险提示。但浏览器只会进行风险提示,用户仍然可以授权信任证书继续操作。 ","date":"2018-08-23","objectID":"/https/:4:2","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"伪造证书攻击 假设我们想访问 www.google.com,但我们的 DNS 服务器被攻击了,指向的 IP 地址并非 Google 的服务器,而是攻击者的 IP。当攻击者的服务器也有合法的证书的时候,我们的浏览器就会认为对方是 Google 服务器,从而信任对方。这样,攻击者便可以监听我们和谷歌之前的所有通信了。 可以看到攻击者有两步需要操作,第一步是需要攻击 DNS 服务器。第二步是攻击者自己的证书需要被用户信任,这一步对于用户来说是很难控制的,需要证书颁发机构能够控制自己不滥发证书。 ","date":"2018-08-23","objectID":"/https/:4:3","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"SSL 剥离 SSL 剥离即阻止用户使用 HTTPS 访问网站。由于并不是所有网站都只支持 HTTPS,大部分网站会同时支持 HTTP 和 HTTPS 两种协议。用户在访问网站时,也可能会在地址栏中输入 http:// 的地址,第一次的访问完全是明文的,这就给了攻击者可乘之机。通过攻击 DNS 响应,攻击者可以将自己变成中间人。 ","date":"2018-08-23","objectID":"/https/:4:4","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"https 可以抓包吗 HTTPS 的数据是加密的,常规下抓包工具代理请求后抓到的包内容是加密状态,无法直接查看。 但是,我们可以通过抓包工具来抓包。它的原理其实是模拟一个中间人。 通常 HTTPS 抓包工具的使用方法是会生成一个证书,用户需要手动把证书安装到客户端中,然后终端发起的所有请求通过该证书完成与抓包工具的交互,然后抓包工具再转发请求到服务器,最后把服务器返回的结果在控制台输出后再返回给终端,从而完成整个请求的闭环。 有人可能会问了,既然 HTTPS 不能防抓包,那 HTTPS 有什么意义? HTTPS 可以防止用户在不知情的情况下通信链路被监听,对于主动授信的抓包操作是不提供防护的,因为这个场景用户是已经对风险知情。要防止被抓包,需要采用应用级的安全防护,例如采用私有的对称加密,同时做好移动端的防反编译加固,防止本地算法被破解。 ","date":"2018-08-23","objectID":"/https/:4:5","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"参考 https://www.runoob.com/w3cnote/http-vs-https.html https://segmentfault.com/a/1190000021494676 https://www.51cto.com/article/680520.html https://juejin.cn/post/6850418120629485582 《HTTP 权威指南》 《WEB 性能权威指南》 ","date":"2018-08-23","objectID":"/https/:5:0","tags":["网络"],"title":"HTTPS","uri":"/https/"},{"categories":["网络"],"content":"HTTP 协议简介 HTTP(Hypertext Transfer Protocol,超文本传输协议)是在万维网上进行通信时所使用的协议方案。HTTP 有很多应用,但最著名的是用于 Web 浏览器和 Web 服务器之间的双工通信。 ","date":"2018-08-22","objectID":"/http/:1:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"URI 与 URL URI(Uniform Resource Identifier):统一资源标识符 URL(Uniform Resource Locator):统一资源定位符;URL 是 URI 的子集 URN(Uniform Resource Name):URI 的第二种形式就是统一资源名(URN)。URN 是作为特定内容的唯一名称使用的,与目前的资源所在地无关。使用这些与位置无关的 URN,就可以将资源四处搬移。 URI 格式如下图: ","date":"2018-08-22","objectID":"/http/:2:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"WEB 结构组件 组件 功能 代理 位于客户端和服务器之间的 HTTP 中间实体。 缓存 HTTP 的仓库,使常用页面的副本可以保存在离客户端更近的地方。 网关 连接其他应用程序的特殊 Web 服务器,通常用于将 HTTP 流量转换成其他的协议。 隧道 对 HTTP 通信报文进行盲转发的特殊代理。 Agent 代理 发起自动 HTTP 请求的半智能 Web 客户端。 ","date":"2018-08-22","objectID":"/http/:3:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"媒体类型 HTTP 仔细地给每种要通过 Web 传输的对象都打上了名为 MIME 类型(MIME type)的数据格式标签。最初设计 MIME(Multipurpose Internet Mail Extension,多用途因特网邮件扩展)是为了解决在不同的电子邮件系统之间搬移报文时存在的问题。MIME 在电子邮件系统中工作得非常好,因此 HTTP 也采纳了它,用它来描述并标记多媒体内容。 常见的 MIME 类型 超文本标记语言文本 .html、.html:text/html 普通文本 .txt: text/plain RTF 文本 .rtf: application/rtf GIF 图形 .gif: image/gif JPEG 图形 .jpeg、.jpg: image/jpeg au 声音文件 .au: audio/basic MIDI 音乐文件 mid、.midi: audio/midi、audio/x-midi RealAudio 音乐文件 .ra、.ram: audio/x-pn-realaudio MPEG 文件 .mpg、.mpeg: video/mpeg AVI 文件 .avi: video/x-msvideo GZIP 文件 .gz: application/x-gzip TAR 文件 .tar: application/x-tar multipart 类型:multipart/form-data 上传文件时使用 ","date":"2018-08-22","objectID":"/http/:4:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP 报文 ","date":"2018-08-22","objectID":"/http/:5:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"请求报文 请求行,分别是 请求头 空行 数据主体(body)部分 \u003cmethod\u003e \u003crequest-URL\u003e \u003cversion\u003e \u003cheaders\u003e \u003centity-body\u003e ","date":"2018-08-22","objectID":"/http/:5:1","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"响应报文 状态行 响应头 空行 数据主体(body)部分 \u003cversion\u003e \u003cstatus\u003e \u003creason-phrase\u003e \u003cheaders\u003e \u003centity-body\u003e ","date":"2018-08-22","objectID":"/http/:5:2","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP 首部 ","date":"2018-08-22","objectID":"/http/:6:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"通用首部 header 解释 示例 Cache-Control 控制缓存的行为 no-cache/no-store/max-age=0 Connection 决定当前的事务完成后,是否会关闭网络连接 close/keep-alive Date 创建报文的日期时间 Date: Tue, 15 Nov 2010 08:12:31 GMT Keep-Alive 用来设置超时时长和最大请求数 Keep-Alive: timeout=5, max=100 Via 代理服务器的相关信息 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) Warning 错误通知,警告实体可能存在的问题 Warning: 199 Miscellaneous warning Trailer 允许发送方在分块发送的消息后面添加额外的元信息 Trailer: Max-Forwards Transfer-Encoding 指定报文主体的传输编码方式 Transfer-Encoding:chunked Upgrade 升级为其他协议 Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 ","date":"2018-08-22","objectID":"/http/:6:1","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"请求首部 header 解释 示例 Accept 客户端可以处理的内容类型 */*,text/* Accept-Charset 客户端可以处理的字符集类型 iso-latin-1 Accept-Encoding 客户端能够理解的内容编码方式 compress, gzip Accept-Language 客户端可以理解的自然语言 en,zh Authorization Web 认证信息 Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Cookie 通过 Set-Cookie 设置的值 $Version=1; Skin=new; DNT 表明用户对于网站追踪的偏好 From 用户的电子邮箱地址 [email protected] Host 请求资源所在服务器 www.zcmhi.com If-Match 比较实体标记(ETag) 737060cd8c284d8af7ad3082f209582d If-Modified-Since 比较资源的更新时间 Sat, 29 Oct 2010 19:43:31 GMT If-None-Match 比较实体标记(与 If-Match 相反) 737060cd8c284d8af7ad3082f209582d If-Range 资源未更新时发送实体 Byte 的范围请求 737060cd8c284d8af7ad3082f209582d If-Unmodified-Since 比较资源的更新时间(与 If-Modified-Since 相反) Sat, 29 Oct 2010 19:43:31 GMT Origin 表示当前请求资源所在页面的协议和域名 http://test.my.com Proxy-Authorization 代理服务器要求客户端的认证信息 Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Range 只请求实体的一部分,指定范围 Range: bytes=500-999 Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: http://www.zcmhi.com/archives/71.html TE 指定用户代理希望使用的传输编码类型 TE: trailers,deflate;q=0.5 Upgrade-Insecure-Requests 表示客户端优先选择加密及带有身份验证的响应 User-Agent 浏览器信息 User-Agent: Mozilla/5.0 (Linux; X11) ","date":"2018-08-22","objectID":"/http/:6:2","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"响应首部 header 解释 示例 Accept-Ranges 是否接受字节范围请求 none/bytes Age 消息对象在缓存代理中存贮的时长,以秒为单位 60 ETag 资源的匹配信息 737060cd8c284d8af7ad3082f209582d Location 令客户端重定向至指定 URI Location: http://www.zcmhi.com/archives/94.html Proxy-Authenticate 代理服务器对客户端的认证信息 Basic Public-Key-Pins 包含该 Web 服务器用来进行加密的 public key (公钥)信息 Public-Key-Pins-Report-Only 设置在公钥固定不匹配时,发送错误信息到 report-uri Referrer-Policy 用来监管哪些访问来源信息,会在 Referer 中发送 Server HTTP 服务器的安装信息 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux) Set-Cookie 服务器端向客户端发送 cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 Strict-Transport-Security 它告诉浏览器只能通过 HTTPS 访问当前资源 Timing-Allow-Origin 用于指定特定站点,以允许其访问 Resource Timing API 提供的相关信息 Tk 显示了对相应请求的跟踪情况 Vary 服务器缓存的管理信息 Vary: * WWW-Authenticate 定义了使用何种验证方式去获取对资源的连接 Basic X-XSS-Protection 当检测到跨站脚本攻击 (XSS)时,浏览器将停止加载页面 ","date":"2018-08-22","objectID":"/http/:6:3","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"实体首部 header 解释 示例 Allow 对某网络资源的有效的请求行为 GET, HEAD Content-Encoding 用于对特定媒体类型的数据进行压缩 gzip Content-Language 访问者希望采用的语言或语言组合 en,zh Content-Length 发送给接收方的消息主体的大小 348 Content-Location 请求资源可替代的备用的另一地址 /index.htm Content-Range 在整个返回体中本部分的字节位置 Content-Range: bytes 21010-47021/47022 Content-Type 告诉客户端实际返回的内容的内容类型 text/html; charset=utf-8 Expires 包含日期/时间, 即在此时候之后,响应过期 Expires: Thu, 01 Dec 2010 16:00:00 GMT Last-Modified 资源的最后修改日期时间 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT 注意:还有一些扩展首部,规范中没有定义的新首部。 ","date":"2018-08-22","objectID":"/http/:6:4","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP 请求方法 HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。 HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。 方法 描述 GET 请求指定的页面信息,并返回实体主体。 HEAD 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 PUT 从客户端向服务器传送的数据取代指定的文档的内容。 DELETE 请求服务器删除指定的页面。 CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 OPTIONS 允许客户端查看服务器的性能。 TRACE 回显服务器收到的请求,主要用于测试或诊断。 PATCH 是对 PUT 方法的补充,用来对已知资源进行局部更新 。 ","date":"2018-08-22","objectID":"/http/:7:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"GET 和 POST 的区别 get 是获取数据,post 是修改数据 get 把请求的数据放在 url 上, 以?分割 URL 和传输数据,参数之间以\u0026相连,所以 get 不太安全。而 post 把数据放在 HTTP 的包体内(request body 相对安全) get 提交的数据最大是 2k( 限制实际上取决于浏览器), post 理论上没有限制。 GET 产生一个 TCP 数据包,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据); POST 产生两个 TCP 数据包,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)。 GET 请求会被浏览器主动缓存,而 POST 不会,除非手动设置。 本质区别:GET 是幂等的,而 POST 不是幂等的 这里的幂等性:幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一 URL 的多个请求应该返回同样的结果。 正因为它们有这样的区别,所以不应该且不能用 get 请求做数据的增删改这些有副作用的操作。因为 get 请求是幂等的,在网络不好的隧道中会尝试重试。如果用 get 请求增数据,会有重复操作的风险,而这种重复操作可能会导致副作用(浏览器和操作系统并不知道你会用 get 请求去做增操作)。 ","date":"2018-08-22","objectID":"/http/:7:1","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP 状态码 HTTP 状态码分类 状态码 类型 说明 1xx Informational(信息性状态码) 接收的请求正在处理 2xx Success(成功) 请求正常处理完毕 3xx Redirection(重定向) 需要进行附加操作以完成请求 4xx Client Error(客户端错误) 服务器无法处理请求 5xx Server Error(服务端错误) 服务器处理请求出错 HTTP 状态码列表 状态码 状态码英文名称 中文描述 100 Continue 继续。客户端应继续其请求 101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到 HTTP 的新版本协议 200 OK 请求成功。一般用于 GET 与 POST 请求 201 Created 已创建。成功请求并创建了新的资源 202 Accepted 已接受。已经接受请求,但未处理完成 203 Non-Authoritative Information 非授权信息。请求成功。但返回的 meta 信息不在原始的服务器,而是一个副本 204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 206 Partial Content 部分内容。服务器成功处理了部分 GET 请求 300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 301 Moved Permanently 永久移动。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替 302 Found 临时移动。与 301 类似。但资源只是临时被移动。客户端应继续使用原有 URI 303 See Other 查看其它地址。与 301 类似。使用 GET 和 POST 请求查看 304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 305 Use Proxy 使用代理。所请求的资源必须通过代理访问 306 Unused 已经被废弃的 HTTP 状态码 307 Temporary Redirect 临时重定向。与 302 类似。使用 GET 请求重定向 400 Bad Request 客户端请求的语法错误,服务器无法理解 401 Unauthorized 请求要求用户的身份认证 402 Payment Required 保留,将来使用 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求 404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置\"您所请求的资源无法找到\"的个性页面 405 Method Not Allowed 客户端请求中的方法被禁止 406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求 407 Proxy Authentication Required 请求要求代理的身份认证,与 401 类似,但请求者应当使用代理进行授权 408 Request Time-out 服务器等待客户端发送的请求时间过长,超时 409 Conflict 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 410 Gone 客户端请求的资源已经不存在。410 不同于 404,如果资源以前有现在被永久删除了可使用 410 代码,网站设计人员可通过 301 代码指定资源的新位置 411 Length Required 服务器无法处理客户端发送的不带 Content-Length 的请求信息 412 Precondition Failed 客户端请求信息的先决条件错误 413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个 Retry-After 的响应信息 414 Request-URI Too Large 请求的 URI 过长(URI 通常为网址),服务器无法处理 415 Unsupported Media Type 服务器无法处理请求附带的媒体格式 416 Requested range not satisfiable 客户端请求的范围无效 417 Expectation Failed 服务器无法满足 Expect 的请求头信息 500 Internal Server Error 服务器内部错误,无法完成请求 501 Not Implemented 服务器不支持请求的功能,无法完成请求 502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的 Retry-After 头信息中 504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求 505 HTTP Version not supported 服务器不支持请求的 HTTP 协议的版本,无法完成处理 ","date":"2018-08-22","objectID":"/http/:8:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"Cookie 与 Session HTTP 作为无状态协议,必然需要在某种方式保持连接状态。保持状态可使用 Cookie 和 Session。 ","date":"2018-08-22","objectID":"/http/:9:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"Cookie Cookie 是客户端保持状态的方法。 Cookie 简单的理解就是存储由服务器发至客户端并由客户端保存的一段字符串。为了保持会话,服务器可以在响应客户端请求时将 Cookie 字符串放在 Set-Cookie 下,客户机收到 Cookie 之后保存这段字符串,之后再请求时候带上 Cookie 就可以被识别。 除了上面提到的这些,Cookie 在客户端的保存形式可以有两种,一种是会话 Cookie 一种是持久 Cookie,会话 Cookie 就是将服务器返回的 Cookie 字符串保持在内存中,关闭浏览器之后自动销毁,持久 Cookie 则是存储在客户端磁盘上,其有效时间在服务器响应头中被指定,在有效期内,客户端再次请求服务器时都可以直接从本地取出。需要说明的是,存储在磁盘中的 Cookie 是可以被多个浏览器代理所共享的。 ","date":"2018-08-22","objectID":"/http/:9:1","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"Session Session 是服务器保持状态的方法。 首先需要明确的是,Session 保存在服务器上,可以保存在数据库、文件或内存中,每个用户有独立的 Session 用户在客户端上记录用户的操作。我们可以理解为每个用户有一个独一无二的 Session ID 作为 Session 文件的 Hash 键,通过这个值可以锁定具体的 Session 结构的数据,这个 Session 结构中存储了用户操作行为。 当服务器需要识别客户端时就需要结合 Cookie 了。每次 HTTP 请求的时候,客户端都会发送相应的 Cookie 信息到服务端。实际上大多数的应用都是用 Cookie 来实现 Session 跟踪的,第一次创建 Session 的时候,服务端会在 HTTP 协议中告诉客户端,需要在 Cookie 里面记录一个 Session ID,以后每次请求把这个会话 ID 发送到服务器,我就知道你是谁了。如果客户端的浏览器禁用了 Cookie,会使用一种叫做 URL 重写的技术来进行会话跟踪,即每次 HTTP 交互,URL 后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户,这样就可以帮用户完成诸如用户名等信息自动填入的操作了。 ","date":"2018-08-22","objectID":"/http/:9:2","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP 安全 ","date":"2018-08-22","objectID":"/http/:10:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"XSS 攻击 跨站点脚本攻击,指攻击者通过篡改网页,嵌入恶意脚本程序,在用户浏览网页时,控制用户浏览器进行恶意操作的一种攻击方式。 防范措施: 前端,服务端,同时需要校验字符串输入的长度限制。 前端,服务端,同时需要对 HTML 转义处理。将其中的”\u003c”,”\u003e”等特殊字符进行转义编码。 防 XSS 的核心是必须对输入的数据做过滤处理。 ","date":"2018-08-22","objectID":"/http/:10:1","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"CSRF 攻击 跨站点请求伪造,指攻击者通过跨站请求,以合法的用户的身份进行非法操作。可以这么理解 CSRF 攻击:攻击者盗用你的身份,以你的名义向第三方网站发送恶意请求。CRSF 能做的事情包括利用你的身份发邮件,发短信,进行交易转账,甚至盗取账号信息。 防范措施: token 机制。在 HTTP 请求中进行 token 验证,如果请求中没有 token 或者 token 内容不正确,则认为 CSRF 攻击而拒绝该请求。 验证码。通常情况下,验证码能够很好的遏制 CSRF 攻击,但是很多情况下,出于用户体验考虑,验证码只能作为一种辅助手段,而不是最主要的解决方案。 referer 识别。在 HTTP Header 中有一个字段 Referer,它记录了 HTTP 请求的来源地址。如果 Referer 是其他网站,就有可能是 CSRF 攻击,则拒绝该请求。 ","date":"2018-08-22","objectID":"/http/:10:2","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"文件上传漏洞 文件上传漏洞,指的是用户上传一个可执行的脚本文件,并通过此脚本文件获得了执行服务端命令的能力。 许多第三方框架、服务,都曾经被爆出文件上传漏洞,比如很早之前的 Struts2,以及富文本编辑器等等,可被攻击者上传恶意代码,有可能服务端就被人黑了。 防范措施: 文件上传的目录设置为不可执行。 判断文件类型。在判断文件类型的时候,可以结合使用 MIME Type,后缀检查等方式。因为对于上传文件,不能简单地通过后缀名称来判断文件的类型,因为攻击者可以将可执行文件的后缀名称改为图片或其他后缀类型,诱导用户执行。 对上传的文件类型进行白名单校验,只允许上传可靠类型。 上传的文件需要进行重新命名,使攻击者无法猜想上传文件的访问路径,将极大地增加攻击成本,同时向 shell.php.rar.ara 这种文件,因为重命名而无法成功实施攻击。 限制上传文件的大小。 单独设置文件服务器的域名。 ","date":"2018-08-22","objectID":"/http/:10:3","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP 性能优化 并行连接:通过多条 TCP 连接发起并发的 HTTP 请求。 持久连接:重用 TCP 连接,以消除连接及关闭时延。为解决每进行一次 HTTP 通信就要断开一次 TCP 连接,HTTP/1.1 通过 keep-alive 持久连接,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。 管道化连接:通过共享的 TCP 连接发起并发的 HTTP 请求。 复用的连接:交替传送请求和响应报文(实验阶段)。 ","date":"2018-08-22","objectID":"/http/:11:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP1.0 和 HTTP1.1 的区别 长连接(Persistent Connection) HTTP1.1 支持长连接和请求的流水线处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启长连接 keep-alive,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。HTTP1.0 需要使用 keep-alive 参数来告知服务器端要建立一个长连接。 节约带宽 HTTP1.0 中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能。HTTP1.1 支持只发送 header 信息(不带任何 body 信息),如果服务器认为客户端有权限请求服务器,则返回 100,客户端接收到 100 才开始把请求 body 发送到服务器;如果返回 401,客户端就可以不用发送请求 body 了节约了带宽。 HOST 域 在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname),HTTP1.0 没有 host 域。随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都支持 host 域,且请求消息中如果没有 host 域会报告一个错误(400 Bad Request)。 缓存处理 在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。 错误通知的管理 在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 ","date":"2018-08-22","objectID":"/http/:12:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"HTTP1.1 和 HTTP2.0 的区别 多路复用 HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP1.1 大了好几个数量级。HTTP1.1 也可以多建立几个 TCP 连接,来支持处理更多并发的请求,但是创建 TCP 连接本身也是有开销的。 头部数据压缩 在 HTTP1.1 中,HTTP 请求和响应都是由状态行、请求/响应头部、消息主体三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输。随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。 HTTP1.1 不支持 header 数据的压缩,HTTP2.0 使用 HPACK 算法对 header 的数据进行压缩,这样数据体积小了,在网络上传输就会更快。 服务器推送 服务端推送是一种在客户端请求之前发送数据的机制。网页使用了许多资源:HTML、样式表、脚本、图片等等。在 HTTP1.1 中这些资源每一个都必须明确地请求。这是一个很慢的过程。浏览器从获取 HTML 开始,然后在它解析和评估页面的时候,增量地获取更多的资源。因为服务器必须等待浏览器做每一个请求,网络经常是空闲的和未充分使用的。 为了改善延迟,HTTP2.0 引入了 server push,它允许服务端推送资源给浏览器,在浏览器明确地请求之前,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。 ","date":"2018-08-22","objectID":"/http/:13:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"参考 《HTTP 权威指南》 https://www.huweihuang.com/linux-notes/tcpip/http-basics.html https://www.runoob.com/http/http-status-codes.html https://juejin.cn/post/6844903489596833800 ","date":"2018-08-22","objectID":"/http/:14:0","tags":["网络"],"title":"HTTP","uri":"/http/"},{"categories":["网络"],"content":"UDP 协议简介 UDP 用户数据报协议,是面向无连接的通讯协议,UDP 数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。 UDP 通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。 UDP 与 TCP 位于同一层,但它不管数据包的顺序、错误或重发。因此,UDP 不被应用于那些使用虚电路的面向连接的服务,UDP 主要用于那些面向查询—应答的服务,例如 NFS。相对于 FTP 或 Telnet,这些服务需要交换的信息量较小。 ","date":"2018-08-21","objectID":"/udp/:1:0","tags":["网络"],"title":"UDP","uri":"/udp/"},{"categories":["网络"],"content":"UDP 头部格式 每个 UDP 报文分 UDP 报头和 UDP 数据区两部分。报头由四个 16 位长(2 字节)字段组成,分别说明该报文的源端口、目的端口、报文长度以及校验值。UDP 报头由 4 个域组成,其中每个域各占用 2 个字节,具体如下: ","date":"2018-08-21","objectID":"/udp/:2:0","tags":["网络"],"title":"UDP","uri":"/udp/"},{"categories":["网络"],"content":"使用场景 使用 UDP 协议包括:TFTP(简单文件传输协议)、SNMP(简单网络管理协议)、DNS(域名解析协议)、NFS、BOOTP。 TCP 与 UDP 的区别:TCP 是面向连接的,可靠的字节流服务;UDP 是面向无连接的,不可靠的数据报服务。 适用一下数据: 不需要重发的数据用 UDP 发送更高效 控制用的短数据 音频和视频数据 ","date":"2018-08-21","objectID":"/udp/:3:0","tags":["网络"],"title":"UDP","uri":"/udp/"},{"categories":["网络"],"content":"UDP 和 TCP 的区别 TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不需要建立连接 TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付 TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文的 UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP 电话,实时视频会议等) 每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信 TCP 首部开销 20 字节;UDP 的首部开销小,只有 8 个字节 TCP 的逻辑通信信道是全双工的可靠信道;UDP 则是不可靠信道 UDP 是面向报文的,发送方的 UDP 对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层,不论应用层交给 UDP 多长的报文,它统统发送,一次发送一个。而对接收方,接到后直接去除首部,交给上面的应用层就完成任务了。因此,它需要应用层控制报文的大小 TCP 是面向字节流的,它把上面应用层交下来的数据看成无结构的字节流会发送,可以想象成流水形式的,发送方 TCP 会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着 TCP 会根据当前网络的拥塞状态来确定每个报文段的大小。 TCP 的分段机制可以把数据拆小后封装在多个包里,避免了被网络层分片。重传 TCP 包的效率可比重传分片高多了。 TCP 只需要重传丢失的那一个包,而不是所有包,所以效率比重传分片高多了。在传输过程中应用层也不用负责重传事宜,因为 TCP 是可靠的,能确保数据被安全送达。 ","date":"2018-08-21","objectID":"/udp/:4:0","tags":["网络"],"title":"UDP","uri":"/udp/"},{"categories":["网络"],"content":"RFC 对 UDP 应用程序的建议 RFC 5405 就是这么一份文档,它对设计单播 UDP 应用程序给出了很多设计建议,简述如下: 应用程序必须容忍各种因特网路径条件; 应用程序应该控制传输速度; 应用程序应该对所有流量进行拥塞控制; 应用程序应该使用与 TCP 相近的带宽; 应用程序应该准备基于丢包的重发计数器; 应用程序应该不发送大于路径 MTU 的数据报; 应用程序应该处理数据报丢失、重复和重排; 应用程序应该足够稳定以支持 2 分钟以上的交付延迟; 应用程序应该支持 IPv4 UDP 校验和,必须支持 IPv6 校验和; 应用程序可以在需要时使用 keep-alive(最小间隔 15 秒)。 ","date":"2018-08-21","objectID":"/udp/:5:0","tags":["网络"],"title":"UDP","uri":"/udp/"},{"categories":["网络"],"content":"参考 https://www.runoob.com/w3cnote/summary-of-network.html https://interviewguide.cn/notes/03-hunting_job/02-interview/03-05-net.html 《WEB 性能权威指南》 ","date":"2018-08-21","objectID":"/udp/:6:0","tags":["网络"],"title":"UDP","uri":"/udp/"},{"categories":["网络"],"content":"IP 协议简介 IP 协议位于 TCP/IP 协议的第三层——网络层。与传输层协议相比,网络层的责任是提供点到点(hop by hop)的服务,而传输层(TCP/UDP)则提供端到端(end to end)的服务。 ","date":"2018-08-19","objectID":"/ip/:1:0","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"IP 头部格式 ","date":"2018-08-19","objectID":"/ip/:2:0","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"IP 地址 IP 地址(IPv4 地址)由 32 位正整数来表示。IP 地址在计算机内部以二进制方式被处理,但习惯将 32 位的 IP 地址以 8 位为一组,分成 4 组,每组以“.”隔开,转换成 10 进制来表示。IPv4 地址为 32 位,最多允许 43 亿台计算机连接网络。 实际上,IP 地址并非根据主机台数来分配而是每一台主机上的每一块网卡都得设置 IP 地址,一块网卡可以设置一个或以上个 IP,路由器通常会配置两个以上的网卡。 IP 地址由“网络地址”和“主机地址”两部分组成。 ","date":"2018-08-19","objectID":"/ip/:3:0","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"IP 地址分类 IP 地址分为 A、B、C、D、E 四类。 IP 地址类别 地址开头 网络地址 主机地址 范围 一个网段内主机地址个数 备注 A 类地址 0 第 1-8 位 后 24 位 0.0.0.0~127.0.0.0 2^24-2=16777214 B 类地址 10 第 1-16 位 后 16 位 128.0.0.0~191.255.0.0 2^16-2=65534 C 类地址 110 第 1-24 位 后 8 位 192.0.0.0~239.255.255.0 2^8-2=254 D 类地址 1110 第 1-32 位 没有主机地址 224.0.0.0~239.255.255.255 常用于多播 E 类地址 1111 第 1-32 位 没有主机地址 240.0.0.0~255.255.255.255 保留地址,供以后使用 注意:同一个网段中的主机地址分配,主机地址全为 0 表示对应的网络地址,主机地址全为 1 通常用于广播地址。因此一个网段内主机的个数去掉 2 个(例如 2^8-2=254)。 ","date":"2018-08-19","objectID":"/ip/:3:1","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"网络地址 IP 地址由网络号(包括子网号)和主机号组成,网络地址的主机号为全 0,网络地址代表着整个网络。 ","date":"2018-08-19","objectID":"/ip/:3:2","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"广播地址 广播地址通常称为直接广播地址,是为了区分受限广播地址。 广播地址与网络地址的主机号正好相反,广播地址中,主机号为全 1。当向某个网络的广播地址发送消息时,该网络内的所有主机都能收到该广播消息。 ","date":"2018-08-19","objectID":"/ip/:3:3","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"多播地址 D 类地址就是多播地址,多播又叫组播。 ","date":"2018-08-19","objectID":"/ip/:3:4","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"255.255.255.255 该 IP 地址指的是受限的广播地址。受限广播地址与一般广播地址(直接广播地址)的区别在于,受限广播地址只能用于本地网络,路由器不会转发以受限广播地址为目的地址的分组;一般广播地址既可在本地广播,也可跨网段广播。例如:主机 192.168.1.1/30 上的直接广播数据包后,另外一个网段 192.168.1.5/30 也能收到该数据报;若发送受限广播数据报,则不能收到。 注:一般的广播地址(直接广播地址)能够通过某些路由器(当然不是所有的路由器),而受限的广播地址不能通过路由器。 ","date":"2018-08-19","objectID":"/ip/:3:5","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"0.0.0.0 常用于寻找自己的 IP 地址,例如在我们的 RARP,BOOTP 和 DHCP 协议中,若某个未知 IP 地址的无盘机想要知道自己的 IP 地址,它就以 255.255.255.255 为目的地址,向本地范围(具体而言是被各个路由器屏蔽的范围内)的服务器发送 IP 请求分组。 ","date":"2018-08-19","objectID":"/ip/:3:6","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"回环地址 127.0.0.0/8 被用作回环地址,回环地址表示本机的地址,常用于对本机的测试,用的最多的是 127.0.0.1。 ","date":"2018-08-19","objectID":"/ip/:3:7","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"A、B、C 类私有地址 私有地址(private address)也叫专用地址,它们不会在全球使用,只具有本地意义。 A 类私有地址:10.0.0.0/8,范围是:10.0.0.0~10.255.255.255 B 类私有地址:172.16.0.0/12,范围是:172.16.0.0~172.31.255.255 C 类私有地址:192.168.0.0/16,范围是:192.168.0.0~192.168.255.255 ","date":"2018-08-19","objectID":"/ip/:3:8","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"子网掩码及网络划分 随着互连网应用的不断扩大,原先的 IPv4 的弊端也逐渐暴露出来,即网络号占位太多,而主机号位太少,所以其能提供的主机地址也越来越稀缺,目前除了使用 NAT 在企业内部利用保留地址自行分配以外,通常都对一个高类别的 IP 地址进行再划分,以形成多个子网,提供给不同规模的用户群使用。 这里主要是为了在网络分段情况下有效地利用 IP 地址,通过对主机号的高位部分取作为子网号,从通常的网络位界限中扩展或压缩子网掩码,用来创建某类地址的更多子网。但创建更多的子网时,在每个子网上的可用主机地址数目会比原先减少。 什么是子网掩码? 子网掩码是标志两个 IP 地址是否同属于一个子网的,也是 32 位二进制地址,其每一个为 1 代表该位是网络位,为 0 代表主机位。它和 IP 地址一样也是使用点式十进制来表示的。如果两个 IP 地址在子网掩码的按位与的计算下所得结果相同,即表明它们共属于同一子网中。 在计算子网掩码时,我们要注意 IP 地址中的保留地址,即\" 0\"地址和广播地址,它们是指主机地址或网络地址全为\" 0\"或\" 1\"时的 IP 地址,它们代表着本网络地址和广播地址,一般是不能被计算在内的。 子网掩码的计算: 对于无须再划分成子网的 IP 地址来说,其子网掩码非常简单,即按照其定义即可写出:如某 B 类 IP 地址为 10.12.3.0,无须再分割子网,则该 IP 地址的子网掩码 255.255.0.0。如果它是一个 C 类地址,则其子网掩码为 255.255.255.0。其它类推,不再详述。下面我们关键要介绍的是一个 IP 地址,还需要将其高位主机位再作为划分出的子网网络号,剩下的是每个子网的主机号,这时该如何进行每个子网的掩码计算。 ","date":"2018-08-19","objectID":"/ip/:4:0","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"网关 网关实质上是一个网络通向其他网络的 IP 地址。比如有网络 A 和网络 B,网络 A 的 IP 地址范围为“192.168.1.1~192.168.1.254”,子网掩码为 255.255.255.0;网络 B 的 IP 地址范围为“192.168.2.1~192.168.2.254”,子网掩码为 255.255.255.0。 在没有路由器的情况下,两个网络之间是不能进行 TCP/IP 通信的,即使是两个网络连接在同一台交换机(或集线器)上,TCP/IP 协议也会根据子网掩码(255.255.255.0)判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信,则必须通过网关。如果网络 A 中的主机发现数据包的目标主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络 B 的网关,网络 B 的网关再转发给网络 B 的某个主机。网络 B 向网络 A 转发数据包的过程也是如此 所以说,只有设置好网关的 IP 地址,TCP/IP 协议才能实现不同网络之间的相互通信。那么这个 IP 地址是哪台机器的 IP 地址呢?网关的 IP 地址是具有路由功能的设备的 IP 地址,具有路由功能的设备有路由器、启用了路由协议的服务器(实质上相当于一台路由器)、代理服务器(也相当于一台路由器)。 ","date":"2018-08-19","objectID":"/ip/:5:0","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"IP 路由 IP 路由选择是逐跳进行的。IP 并不知道到达任何目的的完整路径(当然,除了那些与主机直接相连的)。所有的 IP 路由选择只为数据报传输提供下一站路由器的 IP 地址。它假定下一站路由器比发送数据报的主机更接近目的,而且下一站路由器与该主机是直接相连的。 IP 路由选择主要完成以下这些功能: 搜索路由表,寻找能与目的 IP 地址完全匹配的表目(网络号和主机号都要匹配)。如果找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标志字段的值)。 搜索路由表,寻找能与目的网络号相匹配的表目。如果找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标志字段的值)。目的网络上的所有主机都可以通过这个表目来处置。例如,一个以太网上的所有主机都是通过这种表目进行寻径的。这种搜索网络的匹配方法必须考虑可能的子网掩码。 搜索路由表,寻找标为“默认”的表目。如果找到,则把报文发送给该表目指定的下一站路由器。 如果上面这些步骤都没有成功,那么该数据报就不能被传送。如果不能传送的数据报来自本机,那么一般会向生成数据报的应用程序返回一个“主机不可达”或“网络不可达”的错误。 IP 路由选择是通过逐跳来实现的。数据报在各站的传输过程中目的 IP 地址始终不变,但是封装和目的链路层地址在每一站都可以改变。大多数的主机和许多路由器对于非本地网络的数据报都使用默认的下一站路由器。 IP 路由选择机制的两个特征: 完整主机地址匹配在网络号匹配之前执行 为网络指定路由,而不必为每个主机指定路由 ","date":"2018-08-19","objectID":"/ip/:6:0","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"路由控制 发送数据包除了有目标 IP 地址外,还需要指明路由器和主机的信息,即路由控制表。 路由控制表的形成方式有两种: 1、静态路由 由管理员手动设置 2、动态路由 路由器与其他路由器相互交互信息时自动刷新。为了让动态路由及时刷新路由表,在网络上互联的路由器之间需设置路由协议,保证正常读取路由控制信息。 ","date":"2018-08-19","objectID":"/ip/:6:1","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"路由选择协议 常见的路由选择协议有:RIP 协议、OSPF 协议。 RIP 协议 :底层是贝尔曼福特算法,它选择路由的度量标准(metric)是跳数,最大跳数是 15 跳,如果大于 15 跳,它就会丢弃数据包。 OSPF 协议 :Open Shortest Path First 开放式最短路径优先,底层是迪杰斯特拉算法,是链路状态路由选择协议,它选择路由的度量标准是带宽,延迟。 ","date":"2018-08-19","objectID":"/ip/:6:2","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"参考 《网络是怎样连接的》 https://www.runoob.com/w3cnote/summary-of-network.html https://tonydeng.github.io/sdn-handbook/basic/tcpip.html https://hit-alibaba.github.io/interview/basic/network/IP.html https://www.huweihuang.com/linux-notes/tcpip/ip.html ","date":"2018-08-19","objectID":"/ip/:7:0","tags":["网络"],"title":"IP","uri":"/ip/"},{"categories":["网络"],"content":"TCP 简介 传输层控制协议(Transport Control Protocol),TCP/IP 协议栈的核心之一。位于应用层与网络层之间,提供面向连接的、可靠的字节流服务。 记住关键词“面向连接”、“可靠”、“字节流”,这是学习掌握 TCP 的关键: 面向连接:客户端、服务端交换数据前,需要建立连接 可靠:通过特定机制,在不可靠的网络之上,确保报文准确送达 字节流:数据的最小单位为字节。至于字节中存储内容的含义,由于应用层的程序决定 ","date":"2018-08-18","objectID":"/tcp/:1:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"TCP 如何确保服务可靠性 TCP 花了大量的功夫在确保传输层服务的可靠性上,具体举措包括(但不限于)以下: 应用数据切割:应用数据被分隔成 TCP 认为最适合发送的多个报文段(由特定的算法和机制来确认) 接收端确认:接收端收到报文段后,会向发送端发送确认报文; 超时重传机制:发送端发送一个报文段后,会启动定时器,等待接收端确认收到这个报文;如果没有及时收到确认,发送端会重新发送报文; 数据校验和:发送端发送的报文首部中,有个叫做校验和(checksum)的特殊字段,它是根据报文的首部、数据计算出来的。这是一个端到端的校验和,用来检测传输过程数据的变化。接收端收到报文后会对校验和进行检查,如果校验和存在差错,则丢弃这个报文,且不确认收到此报文(等待发送端超时重发) 报文段排序:TCP 报文包裹在 IP 数据包里进行传输,而 IP 数据包的到达次序是不固定的。接收端会对接收到的报文段重新排序,这个对应用层是无感知的; 去重复:接收端丢弃重复的报文(比如,因某些原因,虽然接收端已经收到报文,且给发送端发送了接收确认,但接收端没有收到该确认,超时后重新发送了同样的报文); 流量控制:TCP 连接双方都有固定大小的缓冲空间,且只允许发送端发送缓冲空间能够容纳的数据,避免缓冲区溢出; ","date":"2018-08-18","objectID":"/tcp/:2:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"TCP 首部 TCP 首部格式如下图所示,在不包含可选字段(其后会有一些填充 Padding)的情况下,大小通常为 20 个字节。 ","date":"2018-08-18","objectID":"/tcp/:3:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"三次握手 ","date":"2018-08-18","objectID":"/tcp/:4:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"为啥需要三次 确认双方的收发能力 第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。 第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。 第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。 序列号可靠同步 如果是两次握手,服务端无法确定客户端是否已经接收到了自己发送的初始序列号,如果第二次握手报文丢失,那么客户端就无法知道服务端的初始序列号,那 TCP 的可靠性就无从谈起。 所以,只有三次握手才能确认双方的接收与发送能力是否正常。 阻止重复历史连接的初始化 客户端由于某种原因发送了两个不同序号的 SYN 包,我们知道网络环境是复杂的,旧的数据包有可能先到达服务器。如果是两次握手,服务器收到旧的 SYN 就会立刻建立连接,那么会造成网络异常。 如果是三次握手,服务器需要回复 SYN+ACK 包,客户端会对比应答的序号,如果发现是旧的报文,就会给服务器发 RST 报文,直到正常的 SYN 到达服务器后才正常建立连接。 所以三次握手才有足够的上下文信息来判断当前连接是否是历史连接。 安全问题 我们知道 TCP 新建连接时,内核会为连接分配一系列的内存资源,如果采用两次握手,就建立连接,那会放大 DDOS 攻击的。 ","date":"2018-08-18","objectID":"/tcp/:4:1","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"初始序列号(ISN) ISN 全称是 Initial Sequence Number,是 TCP 发送方的字节数据编号的原点,告诉对方我要开始发送数据的初始化序列号 ISN 不是固定的,如果固定,攻击者很容易猜出后续的确认序号,为了安全起见,避免被第三方猜到从而发送伪造的 RST 报文,因此 ISN 是动态生成的 ","date":"2018-08-18","objectID":"/tcp/:4:2","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"半连接队列 服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立连接。服务器会把这种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。 当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。 ","date":"2018-08-18","objectID":"/tcp/:4:3","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"第三次是否可以携带数据 第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。 假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,疯狂着重复发 SYN 报文,这会让服务器花费大量的内存空间来缓存这些报文,这样服务器就更容易被攻击了。 ","date":"2018-08-18","objectID":"/tcp/:4:4","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"四次挥手 ","date":"2018-08-18","objectID":"/tcp/:5:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"为啥需要四次 在主动关闭方发送 FIN 包后,被动方可能还要发送数据,不能立即关闭服务器端到客户端的数据通道,所以也就不能将服务器端的 FIN 包与对客户端的 ACK 包合并发送,只能先确认 ACK,然后服务器待无需发送数据时再发送 FIN 包,所以四次挥手时必须是四次数据包的交互。 ","date":"2018-08-18","objectID":"/tcp/:5:1","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"为什么 TIME_WAIT 状态需要经过 2MSL 才能返回到 CLOSE 状态 MSL 指的是报文在网络中最大生存时间。在客户端发送对服务器端的 FIN 的确认包 ACK 后,这个 ACK 包是有可能不可达的,服务器端如果收不到 ACK 的话需要重新发送 FIN 包。 所以客户端发送 ACK 后需要留出 2MSL 时间(ACK 到达服务器 + 服务器发送 FIN 重传包,一来一回)等待确认服务器端确实收到了 ACK 包。 也就是说客户端如果等待 2MSL 时间也没有收到服务器端的重传包 FIN,说明可以确认服务器已经收到客户端发送的 ACK。 避免新旧连接混淆。 在客户端发送完最后一个 ACK 报文段后,在经过 2MSL 时间,就可以使本连接持续的时间内所产生的所有报文都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文。 ","date":"2018-08-18","objectID":"/tcp/:5:2","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"TIME_WAIT 累积与端口耗尽 当某个 TCP 端点关闭 TCP 连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的 IP 地址和端口号。这类信息只会维持一小段时间,通常是所估计的最大分段使用期的两倍(称为 2MSL,通常为 2 分钟左右,以确保在这段时间内不会创建具有相同地址和端口号的新连接。 有些操作系统会将 2MSL 设置为一个较小的值,但修改此值时要特别小心。分组确实会被复制,如果来自之前连接的复制分组插入了具有相同连接值的新 TCP 流,会破坏 TCP 数据。 确保客户端和服务器在循环使用几个虚拟 IP 地址以增加更多的连接组合。 ","date":"2018-08-18","objectID":"/tcp/:5:3","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"服务器出现大量 close_wait 的连接 close_wait 状态是在 TCP 四次挥手的时候收到 FIN 但是没有发送自己的 FIN 时出现的,服务器出现大量 close_wait 状态的原因有两种: 服务器内部业务处理占用了过多时间,都没能处理完业务;或者还有数据需要发送;或者服务器的业务逻辑有问题,没有执行 close()方法 服务器的父进程派生出子进程,子进程继承了 socket,收到 FIN 的时候子进程处理但父进程没有处理该信号,导致 socket 的引用不为 0 无法回收 处理方法: 停止应用程序 修改程序里的 bug ","date":"2018-08-18","objectID":"/tcp/:5:4","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"流量控制 滑动窗口协议,是 TCP 使用的一种流量控制方法。该协议允许发送方在停止并等待确认前可以连续发送多个分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。 只有在接收窗口向前滑动时(与此同时也发送了确认),发送窗口才有可能向前滑动。收发两端的窗口按照以上规律不断地向前滑动,因此这种协议又称为滑动窗口协议。 TCP 头里有一个字段叫 Window,也就是窗口大小,用于告知对方(发送方)本方的缓冲还能接收多少字节数据。 这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。 所以,通常窗口的大小是由接收方的窗口大小(实际由发送端和接收端两个窗口的最小值)来决定的。 cwnd:发送端窗口( congestion window ) rwnd:接收端窗口(receiver window) ","date":"2018-08-18","objectID":"/tcp/:6:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"拥塞控制 发送端主动控制 cwnd,有慢启动(从 cwnd 初始为 1 开始启动,指数启动) 拥塞避免(到达 ssthresh(慢启动阈值) 后,为了避免拥塞开始尝试线性增长) 快重传(接收方每收到一个报文段都要回复一个当前最大连续位置的确认,发送方只要一连收到三个重复确认就知道接收方丢包了,快速重传丢包的报文,并且 TCP 马上把拥塞窗口 cwnd 减小到 1) 为什么要规定凑满 3 个呢?这是因为网络包有时会乱序,乱序的包一样会触发重复的 Ack,但是为了乱序而重传没有必要。由于一般乱序的距离不会相差太大,比如 2 号包也许会跑到 4 号包后面,但不太可能跑到 6 号包后面,所以限定成 3 个或以上可以在很大程度上避免因乱序而触发快速重传。 快恢复(当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把 ssthresh 门限减半(为了预防网络发生拥塞)。考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。) ","date":"2018-08-18","objectID":"/tcp/:7:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"差错控制 TCP 使用差错控制来提供可靠性。差错控制包括以下的一些机制:检测和重传受到损伤的报文段、重传丢失的报文段、保存失序到达的报文段直至缺失的报文到期,以及检测和丢弃重复的报文段。TCP 通过三个简单的工具来完成其差错控制:检验和、确认以及超时。 ","date":"2018-08-18","objectID":"/tcp/:8:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"SYN 攻击 客户端向服务端发送请求链接数据包,服务端向客户端发送确认数据包,客户端不向服务端发送确认数据包,服务器一直等待来自客户端的确认 没有彻底根治的办法,除非不使用 TCP SYN 攻击预防: 限制同时打开 SYN 半链接的数目 缩短 SYN 半链接的 Time out 时间 关闭不必要的服务 增加最大半连接数 过滤网关防护 SYN cookies 技术 ","date":"2018-08-18","objectID":"/tcp/:9:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"TCP KeepAlive TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。主流的操作系统基本都在内核里支持了这个特性。 ","date":"2018-08-18","objectID":"/tcp/:10:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"参考 《网络是怎样连接的》 https://www.cnblogs.com/chyingp/p/understanding-tcp.html https://www.eet-china.com/mp/a44399.html https://blog.csdn.net/u013256816/article/details/84001583 ","date":"2018-08-18","objectID":"/tcp/:11:0","tags":["网络"],"title":"TCP","uri":"/tcp/"},{"categories":["网络"],"content":"网络层次划分 为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在 1978 年提出了\"开放系统互联参考模型\",即著名的 OSI/RM 模型(Open System Interconnection/Reference Model)。 除了标准的 OSI 七层模型以外,常见的网络层次划分还有 TCP/IP 四层协议以及 TCP/IP 五层协议,它们之间的对应关系如下图所示: 在网络体系结构中网络通信的建立必须是在通信双方的对等层进行,不能交错。 在整个数据传输过程中,数据在发送端时经过各层时都要附加上相应层的协议头和协议尾(仅数据链路层需要封装协议尾)部分,也就是要对数据进行协议封装,以标识对应层所用的通信协议。 不同层的数据包如下 在四层,既传输层数据被称作段(Segments); 三层网络层数据被称做包(Packages); 二层数据链路层时数据被称为帧(Frames); 一层物理层时数据被称为比特流(Bits)。 ","date":"2018-08-16","objectID":"/summary-of-network-/:1:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"OSI 七层模型 在理论上,还有一个 OSI 七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。这是一个理想模型,由于其复杂性并没有被大家广泛采用。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"物理层(Physical Layer) 激活、维持、关闭通信端点之间的机械特性、电气特性、功能特性以及过程特性。该层为上层协议提供了一个传输数据的可靠的物理媒体。简单的说,物理层确保原始的数据可在各种物理媒体上传输。物理层记住两个重要的设备名称,中继器(Repeater,也叫放大器)和集线器。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:1","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"数据链路层(Data Link Layer) 数据链路层在物理层提供的服务的基础上向网络层提供服务,其最基本的服务是将源自网络层来的数据可靠地传输到相邻节点的目标机网络层。为达到这一目的,数据链路必须具备一系列相应的功能,主要有:如何将数据组合成数据块,在数据链路层中称这种数据块为帧(frame),帧是数据链路层的传送单位;如何控制帧在物理信道上的传输,包括如何处理传输差错,如何调节发送速率以使与接收方相匹配;以及在两个网络实体之间提供数据链路通路的建立、维持和释放的管理。数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。 有关数据链路层的重要知识点: 数据链路层为网络层提供可靠的数据传输; 基本数据单位为帧; 主要的协议:以太网协议; 两个重要设备名称:网桥和交换机。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:2","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"网络层(Network Layer) 网络层的目的是实现两个端系统之间的数据透明传送,具体功能包括寻址和路由选择、连接的建立、保持和终止等。它提供的服务使传输层不需要了解网络中的数据传输和交换技术。如果您想用尽量少的词来记住网络层,那就是\"路径选择、路由及逻辑寻址\"。 网络层中涉及众多的协议,其中包括最重要的协议,也是 TCP/IP 的核心协议——IP 协议。IP 协议非常简单,仅仅提供不可靠、无连接的传送服务。IP 协议的主要功能有:无连接数据报传输、数据报路由选择和差错控制。与 IP 协议配套使用实现其功能的还有地址解析协议 ARP、逆地址解析协议 RARP、因特网报文协议 ICMP、因特网组管理协议 IGMP。具体的协议我们会在接下来的部分进行总结,有关网络层的重点为: 网络层负责对子网间的数据包进行路由选择。此外,网络层还可以实现拥塞控制、网际互连等功能; 基本数据单位为 IP 数据报; 包含的主要协议: IP 协议(Internet Protocol,因特网互联协议); ICMP 协议(Internet Control Message Protocol,因特网控制报文协议); ARP 协议(Address Resolution Protocol,地址解析协议); RARP 协议(Reverse Address Resolution Protocol,逆地址解析协议)。 重要的设备:路由器。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:3","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"传输层(Transport Layer) 第一个端到端,即主机到主机的层次。传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输。此外,传输层还要处理端到端的差错控制和流量控制问题。 传输层的任务是根据通信子网的特性,最佳的利用网络资源,为两个端系统的会话层之间,提供建立、维护和取消传输连接的功能,负责端到端的可靠数据传输。在这一层,信息传送的协议数据单元称为段或报文。 网络层只是根据网络地址将源结点发出的数据包传送到目的结点,而传输层则负责将数据可靠地传送到相应的端口。 有关网络层的重点: 传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输以及端到端的差错控制和流量控制问题; 包含的主要协议:TCP 协议(Transmission Control Protocol,传输控制协议)、UDP 协议(User Datagram Protocol,用户数据报协议); 重要设备:网关。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:4","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"会话层 会话层管理主机之间的会话进程,即负责建立、管理、终止进程之间的会话。会话层还利用在数据中插入校验点来实现数据的同步。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:5","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"表示层 表示层对上层数据或信息进行变换以保证一个主机应用层信息可以被另一个主机的应用程序理解。表示层的数据转换包括数据的加密、压缩、格式转换等。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:6","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"应用层 为操作系统或网络应用程序提供访问网络服务的接口。 会话层、表示层和应用层重点: 数据传输基本单位为报文; 包含的主要协议:FTP(文件传送协议)、Telnet(远程登录协议)、DNS(域名解析协议)、SMTP(邮件传送协议),POP3 协议(邮局协议),HTTP 协议(Hyper Text Transfer Protocol)。 ","date":"2018-08-16","objectID":"/summary-of-network-/:2:7","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"ARP/RARP 协议 地址解析协议,即 ARP(Address Resolution Protocol),是根据 IP 地址获取物理地址的一个 TCP/IP 协议。主机发送信息时将包含目标 IP 地址的 ARP 请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该 IP 地址和物理地址存入本机 ARP 缓存中并保留一定时间,下次请求时直接查询 ARP 缓存以节约资源。地址解析协议是建立在网络中各个主机互相信任的基础上的,网络上的主机可以自主发送 ARP 应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机 ARP 缓存;由此攻击者就可以向某一主机发送伪 ARP 应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个 ARP 欺骗。ARP 命令可用于查询本机 ARP 缓存中 IP 地址和 MAC 地址的对应关系、添加或删除静态对应关系等。 ARP 工作流程举例: 主机 A 的 IP 地址为 192.168.1.1,MAC 地址为 0A-11-22-33-44-01; 主机 B 的 IP 地址为 192.168.1.2,MAC 地址为 0A-11-22-33-44-02; 当主机 A 要与主机 B 通信时,地址解析协议可以将主机 B 的 IP 地址(192.168.1.2)解析成主机 B 的 MAC 地址,以下为工作流程: 根据主机 A 上的路由表内容,IP 确定用于访问主机 B 的转发 IP 地址是 192.168.1.2。然后 A 主机在自己的本地 ARP 缓存中检查主机 B 的匹配 MAC 地址。 如果主机 A 在 ARP 缓存中没有找到映射,它将询问 192.168.1.2 的硬件地址,从而将 ARP 请求帧广播到本地网络上的所有主机。源主机 A 的 IP 地址和 MAC 地址都包括在 ARP 请求中。本地网络上的每台主机都接收到 ARP 请求并且检查是否与自己的 IP 地址匹配。如果主机发现请求的 IP 地址与自己的 IP 地址不匹配,它将丢弃 ARP 请求。 主机 B 确定 ARP 请求中的 IP 地址与自己的 IP 地址匹配,则将主机 A 的 IP 地址和 MAC 地址映射添加到本地 ARP 缓存中。 主机 B 将包含其 MAC 地址的 ARP 回复消息直接发送回主机 A。 当主机 A 收到从主机 B 发来的 ARP 回复消息时,会用主机 B 的 IP 和 MAC 地址映射更新 ARP 缓存。本机缓存是有生存期的,生存期结束后,将再次重复上面的过程。主机 B 的 MAC 地址一旦确定,主机 A 就能向主机 B 发送 IP 通信了。 逆地址解析协议,即 RARP,功能和 ARP 协议相对,其将局域网中某个主机的物理地址转换为 IP 地址。 比如局域网中有一台主机只知道物理地址而不知道 IP 地址,那么可以通过 RARP 协议发出征求自身 IP 地址的广播请求,然后由 RARP 服务器负责回答。 RARP 协议工作流程: 给主机发送一个本地的 RARP 广播,在此广播包中,声明自己的 MAC 地址并且请求任何收到此请求的 RARP 服务器分配一个 IP 地址; 本地网段上的 RARP 服务器收到此请求后,检查其 RARP 列表,查找该 MAC 地址对应的 IP 地址; 如果存在,RARP 服务器就给源主机发送一个响应数据包并将此 IP 地址提供给对方主机使用; 如果不存在,RARP 服务器对此不做任何的响应; ","date":"2018-08-16","objectID":"/summary-of-network-/:3:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"TCP/IP 协议 TCP/IP 协议是 Internet 最基本的协议、Internet 国际互联网络的基础,由网络层的 IP 协议和传输层的 TCP 协议组成。通俗而言:TCP 负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而 IP 是给因特网的每一台联网设备规定一个地址。 详情看TCP 协议 详情看IP 协议 ","date":"2018-08-16","objectID":"/summary-of-network-/:4:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"UDP 协议 详情看UDP 协议 ","date":"2018-08-16","objectID":"/summary-of-network-/:5:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"DNS 协议 DNS 是域名系统(DomainNameSystem)的缩写,该系统用于命名组织到域层次结构中的计算机和网络服务,可以简单地理解为将 URL 转换为 IP 地址。域名是由圆点分开一串单词或缩写组成的,每一个域名都对应一个惟一的 IP 地址,在 Internet 上域名与 IP 地址之间是一一对应的,DNS 就是进行域名解析的服务器。DNS 命名用于 Internet 等 TCP/IP 网络中,通过用户友好的名称查找计算机和服务。 域名服务器一般监听在端口 53 上。 ","date":"2018-08-16","objectID":"/summary-of-network-/:6:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"NAT 协议 NAT 网络地址转换(Network Address Translation)属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法 IP 地址的转换技术,它被广泛应用于各种类型 Internet 接入方式和各种类型的网络中。原因很简单,NAT 不仅完美地解决了 lP 地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。 ","date":"2018-08-16","objectID":"/summary-of-network-/:7:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"DHCP 协议 DHCP 动态主机设置协议(Dynamic Host Configuration Protocol)是一个局域网的网络协议,使用 UDP 协议工作,主要有两个用途:给内部网络或网络服务供应商自动分配 IP 地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。 ","date":"2018-08-16","objectID":"/summary-of-network-/:8:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"HTTP 协议 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。 详情看HTTP 协议 ","date":"2018-08-16","objectID":"/summary-of-network-/:9:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"网络性能指标 ","date":"2018-08-16","objectID":"/summary-of-network-/:10:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"速率 在计算机与计算机之间通信时传输数据位数的速率 单位 bit/s 1 bit 也就是 1 位 byte 与 bit 的区别和换算 byte:字节 bit:位 1byte = 8bit 按字节换算 1 k = 1024byte M 与 byte 之间的换算是 1024 1M = 1024K 按位换算 1kb = 1000bit 1Mb = 1000Kb 1Gb = 1000Mb 我们人们常说的我的网络是 8M 为什么下载速度只有 1M/S 呢? 这里的 8M,单位是 Mb,也就是按位来算的, 而下载速度是按字节算的 8M = 8000Kb = 8000,000bit = 1000,000 byte ≈1000kb ≈1M 所以一般都市直接除以 8,8M 的网络下载速度就是 1M/s 100M 的网下载速度也就 10M/s ","date":"2018-08-16","objectID":"/summary-of-network-/:10:1","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"带宽 接口支持最高的传输速率, 也就是我们的带宽是 8M, 带宽是 100M. 就是最高支持一次传输 8M,自己换算一下,能传多个位。 ","date":"2018-08-16","objectID":"/summary-of-network-/:10:2","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"吞吐量 单位时间内通过某个网络的数据量 简单的说,就是通过一根网线一次性传输的多少个位,最高位就是带宽,但不是每次传输都达到了带宽值,有可能通过的只有 3M,5M,都不一定,通过的量就是我们说的吞吐量 ","date":"2018-08-16","objectID":"/summary-of-network-/:10:3","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"时延 发送时延、传播时延、处理时延、排队时延 发送时延 = 数据块长度(bit)/信道宽度(bit/s) 过一个特定情景来讲解 A—————-路由器———————B A 发送 0101010101(10bit)数据到 B 带宽是 10bit/s A 距离 B 100 米 信号在信道上的传播速率 10 米/s 当从开始发送 1 到最后一个 0 从 A 的网卡中出来结束,发送时延 = 10bit/10bit/s = 1s 传播时延 = 信道长度(米)/信号在信道上传播速率(米/秒) 传播时延:从 A 发送 1 开始,到路由器,接受最后一个 0 结束,这一段也算是传播时延,路由器到 B 之间也是 100/10 = 10 秒。 在电线上花费的时间是 10 秒 排队时延:路由器接受数据,有一个缓冲区,相当于队列,数据到路由器,先到缓冲区排队等待路由器一个个接受数据,直到路由器开始接受第一个位,这就是排队时延,从路由器出来也需要排队时延 处理时延:路由器开始接受第一个位,这就开始处理数据了,到最后一个位接受完,这之间就是处理时延 ","date":"2018-08-16","objectID":"/summary-of-network-/:10:4","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"时延带宽积 其实也就是在信道中有多少位数据在其中,用的是带宽,也就是最高的数据。 传播时延 * 带宽 ","date":"2018-08-16","objectID":"/summary-of-network-/:10:5","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"往返时间 从 A 到 B,在 B 返回数据到 A,之间用了多少时间 使用命令 ping www.baidu.com 从我们浏览器到存放百度地址的主机,然后返回数据到我们计算机需要的时间 ","date":"2018-08-16","objectID":"/summary-of-network-/:10:6","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"参考 https://tonydeng.github.io/sdn-handbook/basic/tcpip.html https://www.runoob.com/w3cnote/summary-of-network.html https://www.cnblogs.com/zhangyinhua/p/7598603.html https://www.51cto.com/article/597961.html ","date":"2018-08-16","objectID":"/summary-of-network-/:11:0","tags":["网络","TCP/IP","ICMP"],"title":"网络总结","uri":"/summary-of-network-/"},{"categories":["网络"],"content":"生成 HTTP 请求消息 浏览器输入网址 浏览器先要解析 URL(有 http:、file:、ftp:、news:、mailto:等协议),获取域名 生成 HTTP 请求消息(请求行、请求头、空行、消息报文) 注意:1 条请求消息中只能写 1 个 URI。如果需要获取多个文件,必须对每个文件单独发送 1 条请求。 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:1:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"DNS 解析 IP 地址 查看浏览器缓存 查看系统缓存(hosts 文件里面的 ip) 查看路由器缓存 查看 ISP(也就是本地域名服务器,一般都是你的网络接入服务器商提供,比如中国电信,中国移动等) DNS 缓存 由 ISP 询问根域名服务器 询问顶级域名服务器 询问权威域名(主域名)服务器 保存结果至缓存:本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时将该结果反馈给客户端,客户端通过这个 IP 地址即可访问目标 Web 服务器。 域名和 IP 地址并用的理由: IP 地址不方便记忆,网址中用域名更好 路由器中的路由表会需要根据 IP 地址来传送到什么地方,如果用域名代替 IP 地址,占用空间会很大。 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:2:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"创建套接字 创建套接字创建套接字(创建套接字阶段),首先分配一个套接字所需的内存空间,然后向其中写入初始状态。 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:3:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"建立连接 将管道连接到服务器端的套接字上(连接阶段) 连接操作的第一步是在 TCP 模块处创建表示连接控制信息的头部。 通过 TCP 头部中的发送方和接收方端口号可以找到要连接的套接字。 IP 地址和端口号:客户端和服务器之间用来识别对方套接字的机制 描述符:应用程序用来识别套接字的机制,使用描述符来指代套接字的原因如下: 等待连接的套接字中没有客户端 IP 地址和端口号 使用描述符这一种信息比较简单 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:4:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"TCP 收发数据 将 HTTP 请求消息交给协议栈 对较大的数据进行拆分 使用 ACK 号确认网络包已收到 根据网络包平均往返时间调整 ACK 号等待时间 使用窗口有效管理 ACK 号 ACK 与窗口的合并 接收 HTTP 响应消息 MTU:一个网络包的最大长度,以太网中一般为 1500 字节。 MSS:除去头部之后,一个网络包所能容纳的 TCP 数据的最大长度。 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:5:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"IP 与以太网的包收发操作 生成包含接收方 IP 地址的 IP 头部 生成以太网用的 MAC 头部 通过 ARP 查询目标路由器的 MAC 地址 将 IP 包转换成电或光信号 给网络包再加 3 个控制数据(报头、起始帧分界符、帧校验序列) 向集线器发送网络包 接收返回包 将服务器的响应包从 IP 层传递给 TCP 层 IP 模块负责添加如下两个头部。 MAC 头部:以太网用的头部,包含 MAC 地址 IP 头部:IP 用的头部,包含 IP 地址 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:6:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"从网线到网络设备-探索集线器、交换机和路由器 信号在网线和集线器中传输 交换机根据地址表进行转发 交换机端口的 MAC 模块不具有 MAC 地址。 交换机根据 MAC 地址表查找 MAC 地址,然后将信号发送到相应的端口。 路由器的各个端口都具有 MAC 地址和 IP 地址。 路由器的包接收(路由器的端口都具有 MAC 地址,只接收与自身地址匹配的包,遇到不匹配的包则直接丢弃。) 路由器的包转发,路由器判断下一个转发目标的方法如下: 根据包的接收方 IP 地址查询路由表中的目标地址栏,以找到相匹配的记录。 这个匹配并不是匹配全部 32 个比特,而是根据子网掩码列中的值判断网络号的比特数,并匹配相应数量的比特。 会匹配到多条候选记录,路由器寻找网络号比特数最长的一条记录。 如果存在网络号长度相同的多条记录,则根据跃点数匹配,跃点计数越小说明该路由越近,因此应选择跃点计数较小的记录。 路由器也会使用 ARP 来查询下一个转发目标的 MAC 地址。 找不到匹配路由时选择默认路由(路由表中子网掩码为 0.0.0.0 的记录表示“默认路由”。) 如果在路由表中无法找到匹配的记录,路由器会丢弃这个包,并通过 ICMP 消息告知发送方。 注意:路由器也会使用 ARP 来查询下一个转发目标的 MAC 地址。 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:7:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"通过接入网进入互联网内部 ADSL 接入网 光纤接入网(FTTH) 网络包通过接入网之后,到达运营商 POP(接入点)的路由器。 接着进入 NOC(网络运营中心,某运营商的) 通过 IX(互联网交互中心)到达其他运营商 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:8:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"进入服务端的局域网 经过防火墙,过滤规则如下 发送方 MAC 地址 发送方 IP 地址、接收方 IP 地址、协议号 发送方端口号、接收方端口号、TCP 控制位、分片 ICMP 消息类型 通过 DNS 轮询将请求派发到不同的服务器 使用缓存服务器(CDN、反向代理 nginx 也可)分担负载 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:9:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"服务器接收 网卡将接收到的信号转换成数字信息 网卡的 MAC 模块将网络包从信号还原为数字信息,校验 FCS 并存入缓冲区。 网卡驱动会根据 MAC 头部判断协议类型,并将包交给相应的协议栈。 IP 模块的接收操作 协议栈的 IP 模块会检查 IP 头部,(1)判断是不是发给自己的;(2)判断网络包是否经过分片;(3)将包转交给 TCP 模块或 UDP 模块。 TCP 模块处理连接包 如果收到的是发起连接的包,则 TCP 模块会(1)确认 TCP 头部的控制位 SYN;(2)检查接收方端口号;(3)为相应的等待连接套接字复制一个新的副本;(4)记录发送方 IP 地址和端口号等信息。 断开操作完成后,套接字会在经过一段时间后被删除。 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:10:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"服务器响应 URI 指定的是文件(html、图片等)则直接返回文件 URI 指定的是 CGI 或者其他程序则由程序返回具体内容 返回响应消息 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:11:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"浏览器接收响应消息并显示内容 通过响应的数据类型判断其中的内容 如果是 html,浏览器需要解析 html,并请求 html 代码中的资源(如 js、css、图片等) 浏览器对页面进行渲染呈现给用户 断开连接,删除套接字 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:12:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"从服务器断开并删除套接字 数据发送完毕后断开连接 删除套接字 ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:13:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["网络"],"content":"参考 《网络是怎样连接的》 https://www.sfn.cn/news/technology/detail/222.html ","date":"2018-08-10","objectID":"/how-is-the-network-connected/:14:0","tags":["网络"],"title":"网络是怎样连接的","uri":"/how-is-the-network-connected/"},{"categories":["操作系统"],"content":"计算机 IO IO,英文全称是 Input/Output,翻译过来就是输入/输出。比如磁盘 IO,网络 IO。 输入设备是向计算机输入数据和信息的设备,键盘,鼠标都属于输入设备;输出设备是计算机硬件系统的终端设备,用于接收计算机数据的输出显示,一般显示器、打印机属于输出设备。 鼠标、显示器这只是直观表面的输入输出,回到计算机架构来说,涉及计算机核心与其他设备间数据迁移的过程,就是 IO。如磁盘 IO,就是从磁盘读取数据到内存,这算一次输入,对应的,将内存中的数据写入磁盘,就算输出。这就是 IO 的本质。 ","date":"2018-06-06","objectID":"/os-io/:1:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"操作系统 IO 将内存中的数据写入到磁盘的话,主体会可能是一个应用程序,比如一个 Java 进程或者 Go 进程。 以 32 位操作系统为例,它为每一个进程都分配了 4G(2 的 32 次方)的内存空间。这 4G 可访问的内存空间分为二部分,一部分是用户空间,一部分是内核空间。 内核空间是操作系统内核访问的区域,是受保护的内存空间,而用户空间是用户应用程序访问的内存区域。 应用程序是跑在用户空间的,它不存在实质的 IO 过程,真正的 IO 是在操作系统执行的。即应用程序的 IO 操作分为两种动作:IO 调用和 IO 执行。 IO 调用:应用程序进程向操作系统内核发起调用。 IO 执行:操作系统内核完成 IO 操作。 IO 调用是由进程(应用程序的运行态)发起,而 IO 执行是操作系统内核的工作。此时所说的 IO 是应用程序对操作系统 IO 功能的一次触发,即 IO 调用。 操作系统内核完成 IO 操作还包括两个过程: 准备数据阶段:内核等待 I/O 设备准备好数据。 拷贝数据阶段:将数据从内核缓冲区拷贝到用户进程缓冲区。 IO 就是把进程的内部数据转移到外部设备,或者把外部设备的数据迁移到进程内部。外部设备一般指硬盘、socket 通讯的网卡。一个完整的 IO 过程包括以下几个步骤: 应用程序进程向操作系统发起 IO 调用请求 操作系统准备数据,把 IO 外部设备的数据,加载到内核缓冲区 操作系统拷贝数据,即将内核缓冲区的数据,拷贝到用户进程缓冲区 ","date":"2018-06-06","objectID":"/os-io/:2:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"阻塞 IO 模型 用程序的进程发起 IO 调用,但是如果内核的数据还没准备好的话,那应用程序进程就一直在阻塞等待,一直等到内核数据准备好了,从内核拷贝到用户空间,才返回成功提示,此次 IO 操作,称之为阻塞 IO。 阻塞 IO 的缺点就是:如果内核数据一直没准备好,那用户进程将一直阻塞,浪费性能,可以使用非阻塞 IO 优化。 ","date":"2018-06-06","objectID":"/os-io/:3:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"非阻塞 IO 模型 如果内核数据还没准备好,可以先返回错误信息给用户进程,让它不需要等待,而是通过轮询的方式再来请求。这就是非阻塞 IO。 相对于阻塞 IO,虽然大幅提升了性能,但是它依然存在性能问题,即频繁的轮询,导致频繁的系统调用,同样会消耗大量的 CPU 资源。可以考虑 IO 复用模型,去解决这个问题。 ","date":"2018-06-06","objectID":"/os-io/:4:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"IO 多路复用模型 IO 复用模型核心思路:操作系统提供了一类函数(比如 select、poll、epoll 函数),它们可以同时监控多个 fd(socket 或者文件)的操作,任何一个返回内核数据就绪,应用进程再发起 recvfrom 系统调用。 ","date":"2018-06-06","objectID":"/os-io/:5:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"select 应用进程通过调用 select 函数,可以同时监控多个 fd,在 select 函数监控的 fd 中,只要有任何一个数据状态准备就绪了,select 函数就会返回可读状态,这时应用进程再发起 recvfrom 请求去读取数据。 非阻塞 IO 模型(NIO)中,需要 N(N\u003e=1)次轮询系统调用,然而借助 select 的 IO 多路复用模型,只需要发起一次询问就够了,大大优化了性能。 但是呢,select 有几个缺点: 监听的 IO 最大连接数有限,在 Linux 系统上一般为 1024。 select 函数返回后,是通过遍历 fdset,找到就绪的描述符 fd。(仅知道有 I/O 事件发生,却不知是哪几个流,所以遍历所有流) 因为存在连接数限制,所以后来又提出了 poll。与 select 相比,poll 解决了连接数限制问题。但是呢,select 和 poll 一样,还是需要通过遍历文件描述符来获取已经就绪的 socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。 ","date":"2018-06-06","objectID":"/os-io/:5:1","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"epoll epoll 先通过 epoll_ctl()来注册一个 fd(文件描述符),一旦基于某个 fd 就绪时,内核会采用回调机制,迅速激活这个 fd,当进程调用 epoll_wait()时便得到通知。这里去掉了遍历文件描述符的操作,而是采用监听事件回调的机制。这就是 epoll 的亮点。 select、poll、epoll 的区别 select poll epoll 底层数据结构 数组 链表 红黑树和双链表 获取就绪的 fd 遍历 遍历 事件回调 时间复杂度 O(n) O(n) O(1) 最大连接数 1024 无限制 无限制 fd 数据拷贝 每次调用 select,需要将 fd 数据从用户空间拷贝到内核空间 每次调用 poll,需要将 fd 数据从用户空间拷贝到内核空间 使用内存映射(mmap),不需要从用户空间频繁拷贝 fd 数据到内核空间 epoll 明显优化了 IO 的执行效率,但在进程调用 epoll_wait()时,仍然可能被阻塞。能不能不用老是去问你数据是否准备就绪,等发出请求后,数据准备好了通知程序就行了,这就诞生了信号驱动 IO 模型。 ","date":"2018-06-06","objectID":"/os-io/:5:2","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"信号驱动 IO 模型 信号驱动 IO 不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号(调用 sigaction 的时候建立一个 SIGIO 的信号),然后应用用户进程可以去做别的事,不用阻塞。当内核数据准备好后,再通过 SIGIO 信号通知应用进程,数据准备好后的可读状态。应用用户进程收到信号之后,立即调用 recvfrom,去读取数据。 信号驱动 IO 模型,在应用进程发出信号后,是立即返回的,不会阻塞进程。它已经有异步操作的感觉了。但是你细看上面的流程图,发现数据复制到应用缓冲的时候,应用进程还是阻塞的。回过头来看下,不管是 BIO,还是 NIO,还是信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的。 ","date":"2018-06-06","objectID":"/os-io/:6:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"异步 IO 模型(AIO) AIO 实现了 IO 全流程的非阻塞,就是应用进程发出系统调用后,是立即返回的,但是立即返回的不是处理结果,而是表示提交成功类似的意思。等内核数据准备好,将数据拷贝到用户进程缓冲区,发送信号通知用户进程 IO 操作执行完毕。 异步 IO 的优化思路很简单,只需要向内核发送一次请求,就可以完成数据状态询问和数据拷贝的所有操作,并且不用阻塞等待结果。日常开发中,有类似思想的业务场景: 比如发起一笔批量转账,但是批量转账处理比较耗时,这时候后端可以先告知前端转账提交成功,等到结果处理完,再通知前端结果即可。 ","date":"2018-06-06","objectID":"/os-io/:7:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"阻塞、非阻塞、同步、异步 IO 划分 IO 模型 阻塞 I/O 模型 同步阻塞 非阻塞 I/O 模型 同步非阻塞 I/O 多路复用模型 同步阻塞 信号驱动 I/O 模型 同步非阻塞 异步 IO(AIO)模型 异步非阻塞 ","date":"2018-06-06","objectID":"/os-io/:8:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"参考 https://www.51cto.com/article/693213.html ","date":"2018-06-06","objectID":"/os-io/:9:0","tags":["操作系统"],"title":"IO","uri":"/os-io/"},{"categories":["操作系统"],"content":"进程与线程的区别 每个线程都是一个轻量级进程(Light Weight Process),都有自己的唯一 PID 和一个 TGID(Thread group ID)。TGID 是启动整个进程的 thread 的 PID。 例如,当一个进程被创建的时候,它其实是一个 PID 和 TGID 数值相同线程。当线程 A 启动线程 B 时,线程 B 会有自己的唯一 PID,但它的 TGID 会从 A 继承而来。这样通过 PID 线程可以独立得到调度,而相同的 TGID 可以知道哪些线程属于同一个进程,这样可以共享资源(RAM,虚拟内存、文件等)。 从内核层面来看线程其实也是一种特殊的进程,它跟父进程共享了打开了的文件和文件系统信息,共享了地址空间和信号处理函数。 线程进程的区别主要体现在以下几个方面: 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。 资源开销:每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一进程的线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。两者均可并发执行。 上下文切换:线程切换依然需要内核进行进程切换,线程切换相比进程切换,主要节省了虚拟地址空间的切换。 通信方面:进程主要通过管道、套接字、消息队列、信号量、信号、共享内存进行通信,线程主要通过锁、信号、信号量进行通信。 ","date":"2018-06-06","objectID":"/process-thread-coroutine/:1:0","tags":["操作系统"],"title":"进程线程协程的区别","uri":"/process-thread-coroutine/"},{"categories":["操作系统"],"content":"协程与线程的区别 根本区别:协程并不是取代线程,而且抽象于线程之上。线程是被分割的 CPU 资源, 协程是组织好的代码流程, 协程需要线程来承载运行。 资源开销:协程需要用到的寄存器更少,相比线程,协程占用的内存空间更小。 包含关系:一个线程可以有多个协程。 同步异步:大多数业务场景下,线程进程可以看做是同步机制,而协程则是异步。 调度:线程是抢占式,而协程是非抢占式的,所以需要用户代码释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。 上下文切换:协程切换不需要陷入内核。 ","date":"2018-06-06","objectID":"/process-thread-coroutine/:2:0","tags":["操作系统"],"title":"进程线程协程的区别","uri":"/process-thread-coroutine/"},{"categories":["操作系统"],"content":"进程、线程和协程的区别和联系 进程 线程 协程 定义 资源分配和拥有的基本单位 程序执行的基本单位 用户态的轻量级线程,线程内部调度的基本单位 切换情况 进程 CPU 环境 (栈、寄存器、页表和文件句柄等)的保存以及新调度的进程 CPU 环境的设置 保存和设置程序计数器、少量寄存器和栈的内容 先将寄存器上下文和栈保存,等切换回来的时候再进行恢复 切换者 操作系统 操作系统 用户 切换过程 用户态-\u003e内核态-\u003e用户态 用户态-\u003e内核态-\u003e用户态 用户态(没有陷入内核) 调用栈 内核栈 内核栈 用户栈 拥有资源 CPU 资源、内存资源、文件资源和句柄等 程序计数器、寄存器、栈和状态字 拥有自己的寄存器上下文和栈 并发性 不同进程之间切换实现并发,各自占有 CPU 实现并行 一个进程内部的多个线程并发执行 同一时间只能执行一个协程,而其他协程处于休眠状态,适合对任务进行分时处理 系统开销 切换虚拟地址空间,切换内核栈和硬件上下文,CPU 高速缓存失效、页表切换,开销很大 切换时只需保存和设置少量寄存器内容,因此开销很小 直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快 通信方面 进程间通信需要借助操作系统 线程间可以直接读写进程数据段(如全局变量)来进行通信 共享内存、消息队列 1、进程是资源调度的基本单位,运行一个可执行程序会创建一个或多个进程,进程就是运行起来的可执行程序 2、线程是程序执行的基本单位,是轻量级的进程。每个进程中都有唯一的主线程,且只能有一个,主线程和进程是相互依存的关系,主线程结束进程也会结束。多提一句:协程是用户态的轻量级线程,线程内部调度的基本单位 ","date":"2018-06-06","objectID":"/process-thread-coroutine/:3:0","tags":["操作系统"],"title":"进程线程协程的区别","uri":"/process-thread-coroutine/"},{"categories":["操作系统"],"content":"文件系统 操作系统为磁盘提供的抽象就是:文件及文件系统,或者说,文件系统就是磁盘的抽象。当然文件系统不一定在磁盘上,也可以在光盘上。 文件系统的基本数据单位是文件,文件系统的目的是对磁盘上的文件进行组织管理,组织方式不同,就会形成不同的文件系统。 Linux 上所有东西都是文件,不仅普通的文件和目录,就连块设备、管道、socket 等,也都是统一交给文件系统管理的。 Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry),它们主要用来记录文件的元信息和目录层次结构。 索引节点,也就是 inode,用来记录文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。索引节点是文件的唯一标识,它们之间一一对应,也同样都会被存储在硬盘中,所以索引节点同样占用磁盘空间。 目录项,也就是 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。 由于索引节点唯一标识一个文件,而目录项记录着文件的名,所以目录项和索引节点的关系是多对一,也就是说,一个文件可以有多个别字。比如,硬链接的实现就是多个目录项中的索引节点指向同一个文件。 注意,目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存子目录或文件。 ","date":"2018-06-05","objectID":"/os-file-system/:1:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"虚拟文件系统 文件系统的种类众多,而操作系统希望对用户提供一个统一的接口,于是在用户层与文件系统层引入了中间层,这个中间层就称为虚拟文件系统(Virtual File System,VFS)。 Linux 文件系统中,用户空间、系统调用、虚拟机文件系统、缓存、文件系统以及存储之间的关系如下图: Linux 支持的文件系统也不少,根据存储位置的不同,可以把文件系统分为三类: 磁盘的文件系统,它是直接把数据存储在磁盘中,比如 Ext 2/3/4、XFS 等都是这类文件系统。 内存的文件系统,这类文件系统的数据不是存储在硬盘的,而是占用内存空间,我们经常用到的 /proc 和 /sys 文件系统都属于这一类,读写这类文件,实际上是读写内核中相关的数据数据。 网络的文件系统,用来访问其他计算机主机数据的文件系统,比如 NFS、SMB 等等。 文件系统首先要先挂载到某个目录才可以正常使用,比如 Linux 系统在启动时,会把文件系统挂载到根目录。 ","date":"2018-06-05","objectID":"/os-file-system/:2:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"文件系统布局 在计算机启动时,处于主板 ROM 里面的 BIOS 程序首先运行。BIOS 在进行一些基本的系统配置扫描后对磁盘的扇面 0 进行读操作,将 MBR 里面的程序读到内存并运行。MBR 程序接下来找到系统主分区,并将主分区里面的 Boot Record 加载并运行。Boot Record 里面的内容是一个小程序,该程序将负责找到操作系统映像,并加载到内存,从而启动操作系统。 在该记录块后面的磁盘内容布局因文件系统的不同可以不同。但在通常情况下,紧接着引导记录后面的是一个超级数据块(Super Block),该块里面存放的是关于该文件系统的各种参数,如文件系统类型、文件系统数据块尺寸等。在 Super Block 后面则是磁盘的自由空间,其后面是 I-NODE 区,再后面是文件系统根目录区。分区的最后存放的是用户文件和文件夹区。 文件系统的布局如下: ","date":"2018-06-05","objectID":"/os-file-system/:3:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"文件的存储 文件的数据是要存储在硬盘上面的,数据在磁盘上的存放方式,就像程序在内存中存放的方式那样,有以下两种: 连续空间存放方式(会造成碎片) 非连续空间存放方式 其中,非连续空间存放方式又可以分为「链表方式」(查询效率低)和「索引方式」。 ","date":"2018-06-05","objectID":"/os-file-system/:4:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"空闲空间管理 文件系统必须记录哪些 inode 和数据块是空闲的,哪些不是,这样在分配新文件或目录时,就可以为它找到空间。因此,空闲空间管理(free space management)对于所有文件系统都很重要。在 VSFS 中,我们用两个简单的位图来完成这个任务。 ","date":"2018-06-05","objectID":"/os-file-system/:5:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"软链接和硬链接 Linux 给文件别名的方式有两种:硬链接(Hard Link) 和软链接(Symbolic Link),它们都是比较特殊的文件,但是实现方式也是不相同的。 硬链接是多个目录项中的「索引节点」指向一个文件,也就是指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。 软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。 ","date":"2018-06-05","objectID":"/os-file-system/:6:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"读取和写入 ","date":"2018-06-05","objectID":"/os-file-system/:7:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"读取 所有遍历都从文件系统的根开始,即根目录(root directory),它就记为/。在大多数 UNIX 文件系统中,根的 inode 号为 2。因此,要开始该过程,文件系统会读入 inode 号 2 的块(第一个 inode 块)。 一旦 inode 被读入,文件系统可以在其中查找指向数据块的指针,数据块包含根目录的内容。因此,文件系统将使用这些磁盘上的指针来读取目录,在这个例子中,寻找 foo 的条目。通过读入一个或多个目录数据块,它将找到 foo 的条目。一旦找到,文件系统也会找到下一个需要的 foo 的 inode 号(假定是 44)。 下一步是递归遍历路径名,直到找到所需的 inode。在这个例子中,文件系统读取包含 foo 的 inode 及其目录数据的块,最后找到 bar 的 inode 号。open()的最后一步是将 bar 的 inode 读入内存。然后文件系统进行最后的权限检查,在每个进程的打开文件表中,为此进程分配一个文件描述符,并将它返回给用户。 ","date":"2018-06-05","objectID":"/os-file-system/:7:1","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"写入 写入文件是一个类似的过程。首先,文件必须打开(如上所述)。其次,应用程序可以发出 write()调用以用新内容更新文件。最后,关闭该文件。 与读取不同,写入文件也可能会分配(allocate)一个块(除非块被覆写)。当写入一个新文件时,每次写入操作不仅需要将数据写入磁盘,还必须首先决定将哪个块分配给文件,从而相应地更新磁盘的其他结构(例如数据位图和 inode)。 因此,每次写入文件在逻辑上会导致 5 个 I/O:一个读取数据位图(然后更新以标记新分配的块被使用),一个写入位图(将它的新状态存入磁盘),再有两次 I/O,其中一次是读取 inode,另一次是写 inode(为了更新块的位置),最后一次写入真正的数据块本身。 ","date":"2018-06-05","objectID":"/os-file-system/:7:2","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"缓存和缓冲 读取和写入文件可能是昂贵的,会导致(慢速)磁盘的许多 I/O。这显然是一个巨大的性能问题,为了弥补,大多数文件系统积极使用系统内存(DRAM)来缓存重要的块。 ","date":"2018-06-05","objectID":"/os-file-system/:8:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"文件操作 使用文件的目的是用来存储信息并方便以后的检索。对于存储和检索,不同的系统提供了不同的操作。以下是与文件有关的最常用的一些系统调用: Create,创建不包含任何数据的文件。调用的目的是表示文件即将建立,并对文件设置一些属性。 Delete,当文件不再需要,必须删除它以释放内存空间。为此总会有一个系统调用来删除文件。 Open,在使用文件之前,必须先打开文件。这个调用的目的是允许系统将属性和磁盘地址列表保存到主存中,用来以后的快速访问。 Close,当所有进程完成时,属性和磁盘地址不再需要,因此应关闭文件以释放表空间。很多系统限制进程打开文件的个数,以此达到鼓励用户关闭不再使用的文件。磁盘以块为单位写入,关闭文件时会强制写入最后一块,即使这个块空间内部还不满。 Read,数据从文件中读取。通常情况下,读取的数据来自文件的当前位置。调用者必须指定需要读取多少数据,并且提供存放这些数据的缓冲区。 Write,向文件写数据,写操作一般也是从文件的当前位置开始进行。如果当前位置是文件的末尾,则会直接追加进行写入。如果当前位置在文件中,则现有数据被覆盖,并且永远消失。 append,使用 append 只能向文件末尾添加数据。 seek,对于随机访问的文件,要指定从何处开始获取数据。通常的方法是用 seek 系统调用把当前位置指针指向文件中的特定位置。seek 调用结束后,就可以从指定位置开始读写数据了。 get attributes,进程运行时通常需要读取文件属性。 set attributes,用户可以自己设置一些文件属性,甚至是在文件创建之后,实现该功能的是 set attributes 系统调用。 rename,用户可以自己更改已有文件的名字,rename 系统调用用于这一目的。 ","date":"2018-06-05","objectID":"/os-file-system/:9:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"目录操作 不同文件中管理目录的系统调用的差别比管理文件的系统调用差别大。为了了解这些系统调用有哪些以及它们怎样工作,下面给出一个例子(取自 UNIX)。 Create,创建目录,除了目录项 . 和 .. 外,目录内容为空。 Delete,删除目录,只有空目录可以删除。只包含 . 和 .. 的目录被认为是空目录,这两个目录项通常不能删除 opendir,目录内容可被读取。例如,未列出目录中的全部文件,程序必须先打开该目录,然后读其中全部文件的文件名。与打开和读文件相同,在读目录前,必须先打开文件。 closedir,读目录结束后,应该关闭目录用于释放内部表空间。 readdir,系统调用 readdir 返回打开目录的下一个目录项。以前也采用 read 系统调用来读取目录,但是这种方法有一个缺点:程序员必须了解和处理目录的内部结构。相反,不论采用哪一种目录结构,readdir 总是以标准格式返回一个目录项。 rename,在很多方面目录和文件都相似。文件可以更换名称,目录也可以。 link,链接技术允许在多个目录中出现同一个文件。这个系统调用指定一个存在的文件和一个路径名,并建立从该文件到路径所指名字的链接。这样,可以在多个目录中出现同一个文件。有时也被称为硬链接(hard link)。 unlink,删除目录项。如果被解除链接的文件只出现在一个目录中,则将它从文件中删除。如果它出现在多个目录中,则只删除指定路径名的链接,依然保留其他路径名的链接。在 UNIX 中,用于删除文件的系统调用就是 unlink。 ","date":"2018-06-05","objectID":"/os-file-system/:10:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"参考 《操作系统之哲学原理第 2 版》邹恒明 《操作系统导论》 ","date":"2018-06-05","objectID":"/os-file-system/:11:0","tags":["操作系统"],"title":"文件系统","uri":"/os-file-system/"},{"categories":["操作系统"],"content":"磁盘的物理结构 硬盘内部主要部件为磁盘盘片、传动手臂、读写磁头和主轴马达。实际数据都是写在盘片上,读写主要是通过传动手臂上的读写磁头来完成。实际运行时,主轴让磁盘盘片转动,然后传动手臂可伸展让读取头在盘片上进行读写操作。磁盘物理结构如下图所示: 由于单一盘片容量有限,一般硬盘都有两张以上的盘片,每个盘片有两面,都可记录信息,所以一张盘片对应着两个磁头。 扇区:盘片被分为许多扇形的区域,每个区域叫一个扇区,硬盘中每个扇区的大小固定为 512 字节。 磁道:盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。 柱面:不同盘片相同半径的磁道所组成的圆柱称为柱面。 早期的硬盘每磁道扇区数相同(外圈的记录密度就要比内圈小使得浪费大量的存储空间),此时由磁盘基本参数可以计算出硬盘的容量:存储容量=磁头数磁道(柱面)数每道扇区数*每扇区字节数。 后期使用 ZBR(Zoned Bit Recording,区位记录)技术,盘片外圈区域磁道长扇区数目较多,内圈区域磁道短扇区数目较少,大体实现了等密度,从而获得了更多的存储空间。 ","date":"2018-06-04","objectID":"/os-disk/:1:0","tags":["操作系统"],"title":"磁盘","uri":"/os-disk/"},{"categories":["操作系统"],"content":"磁盘读取 操作系统在对磁盘进行管理时通常以磁盘块作为最小单位。而定位一个磁盘块则通过磁盘块地址进行。磁盘块通常为扇面数的整数倍,更为常见的是 2 的幂次方倍,如 32 个扇面或 64 个扇面等。 操作系统通常都会标明磁道(磁柱)、扇面、和盘面(磁头)3 个参数。以这种方式给出的地址称为逻辑块地址(Logical Block Address,LBA)。 读取过程如下: 操作系统将要读取的 LBA 传送给磁盘驱动器并启动读取命令。 磁盘驱动器通过将磁头移动到正确的位置,并启动处于指定盘面上的磁头来搜索指定的磁道。在磁头的移动过程中,读取磁头将不断地检查下面的扇面号直到找到所要求的扇面为止。 磁盘控制器将扇面数据和 ECC 信息传送到一个处于磁盘界面里的缓冲区。这里的 ECC 信息是在数据流动的动态过程中进行计算而得出的。 磁盘驱动器向操作系统发出“数据就绪”信号。 操作系统从磁盘界面里的缓冲区读取数据,既可以按一个字节一个字节的方式来读取,又可以启动 DMA 命令来读取。 ","date":"2018-06-04","objectID":"/os-disk/:2:0","tags":["操作系统"],"title":"磁盘","uri":"/os-disk/"},{"categories":["操作系统"],"content":"磁盘调度算法 先来先服务 短任务优先:短任务优先就是谁的磁盘读写数据量最少,谁就优先。 短寻道优先:短寻道优先则考虑当前磁头离谁的数据最近,谁就优先。 电梯算法:先满足一个方向的所有请求,再满足所有反方向的请求,这样循环往复。 提前查看电梯算法:每次往某个方向移动时必须确保该方向还有请求未满足。否则即刻调转方向。这样效率将得到提高。 ","date":"2018-06-04","objectID":"/os-disk/:3:0","tags":["操作系统"],"title":"磁盘","uri":"/os-disk/"},{"categories":["操作系统"],"content":"影响硬盘性能的因素 影响磁盘的关键因素是磁盘服务时间,即磁盘完成一个 I/O 请求所花费的时间,它由寻道时间、旋转延迟和数据传输时间三部分构成。 寻道时间 Tseek 是指将读写磁头移动至正确的磁道上所需要的时间。寻道时间越短,I/O 操作越快,目前磁盘的平均寻道时间一般在 3-15ms。 旋转延迟 Trotation 是指盘片旋转将请求数据所在的扇区移动到读写磁盘下方所需要的时间。旋转延迟取决于磁盘转速,通常用磁盘旋转一周所需时间的 1/2 表示。比如:7200rpm 的磁盘平均旋转延迟大约为 60*1000/7200/2 = 4.17ms,而转速为 15000rpm 的磁盘其平均旋转延迟为 2ms。 数据传输时间 Ttransfer 是指完成传输所请求的数据所需要的时间,它取决于数据传输率,其值等于数据大小除以数据传输率。目前 IDE/ATA 能达到 133MB/s,SATA II 可达到 300MB/s 的接口数据传输率,数据传输时间通常远小于前两部分消耗时间。简单计算时可忽略。 ","date":"2018-06-04","objectID":"/os-disk/:4:0","tags":["操作系统"],"title":"磁盘","uri":"/os-disk/"},{"categories":["操作系统"],"content":"衡量性能的指标 机械硬盘的连续读写性能很好,但随机读写性能很差,这主要是因为磁头移动到正确的磁道上需要时间,随机读写时,磁头需要不停的移动,时间都浪费在了磁头寻址上,所以性能不高。衡量磁盘的重要主要指标是 IOPS 和吞吐量。 IOPS IOPS(Input/Output Per Second)即每秒的输入输出量(或读写次数),即指每秒内系统能处理的 I/O 请求数量。随机读写频繁的应用,如小文件存储等,关注随机读写性能,IOPS 是关键衡量指标。可以推算出磁盘的 IOPS = 1000ms / (Tseek + Trotation + Transfer),如果忽略数据传输时间,理论上可以计算出随机读写最大的 IOPS。常见磁盘的随机读写最大 IOPS 为: 7200rpm 的磁盘 IOPS = 76 IOPS 10000rpm 的磁盘 IOPS = 111 IOPS 15000rpm 的磁盘 IOPS = 166 IOPS 吞吐量 吞吐量(Throughput),指单位时间内可以成功传输的数据数量。顺序读写频繁的应用,如视频点播,关注连续读写性能、数据吞吐量是关键衡量指标。它主要取决于磁盘阵列的架构,通道的大小以及磁盘的个数。不同的磁盘阵列存在不同的架构,但他们都有自己的内部带宽,一般情况下,内部带宽都设计足够充足,不会存在瓶颈。磁盘阵列与服务器之间的数据通道对吞吐量影响很大,比如一个 2Gbps 的光纤通道,其所能支撑的最大流量仅为 250MB/s。最后,当前面的瓶颈都不再存在时,硬盘越多的情况下吞吐量越大。 ","date":"2018-06-04","objectID":"/os-disk/:5:0","tags":["操作系统"],"title":"磁盘","uri":"/os-disk/"},{"categories":["操作系统"],"content":"磁盘阵列 分基于软件的软 RAID(如 mdadm)和基于硬件的硬 RAID(如 RAID 卡) 级别 特征 原理 单元 冗余 性能 利用率 最多坏 用途 缺陷 RAID0 条带 分片分散存入 2 块硬盘 2 否 读写速度 2 倍 100% 0/2 SWAP/TMP 不冗余,数据难恢复 RAID1 镜像 相同数据存入 2 块硬盘 2 是 写速度不变,读速度 2 倍 50% 1/2 数据备份 读写速度没加,利用率低 RAID4 校验 分片分散存入 2 块硬盘,校验码存入第 3 块硬盘 3 是 读写速度 2 倍 2/3=66% 1/3 用的很少 1. 坏盘时另外 2 块需要重新计算还原坏盘数据;2. 校验码盘压力大成为瓶颈 RAID5 校验 分片和校验码混合存储 3 是 读写速度 2 倍 2/3=66% 1/3 用的不多 坏盘时另外 2 块需要重新计算还原坏盘数据 RAID6 校验 分片盘校验码盘分别 2 个,数据分片校验码计算 2 次 4 是 读写速度 2 倍 2/4 2/4 1∈2 用的很少 “部队中有一半是搞后勤的,感觉还是不太爽。” RAID10 1+0 2 块硬盘 1 组先做 RAID1,多组 RAID1 再做 RAID0 4 是 读写速度 N 倍,N 为组数 2/4 2/4 1∈2 用的最多 - RAID50 5+0 3 块硬盘 1 组先做 RAID5,多组再做 RAID0 6 是 读写数读 2N 倍,N 为组数 4/6 2/6 1∈3 土豪用的 “好是好,就是贵!” ","date":"2018-06-04","objectID":"/os-disk/:6:0","tags":["操作系统"],"title":"磁盘","uri":"/os-disk/"},{"categories":["操作系统"],"content":"参考 《操作系统之哲学原理第 2 版》邹恒明 《操作系统导论》 ","date":"2018-06-04","objectID":"/os-disk/:7:0","tags":["操作系统"],"title":"磁盘","uri":"/os-disk/"},{"categories":["操作系统"],"content":"内存简介 内存架构:缓存 -\u003e 主存 -\u003e 磁盘 -\u003e 磁带 内存管理:该机制负责对内存架构进行管理,使程序在内存架构的任何一个层次上的存放对于用户来说都是一样的。用户无须担心自己的程序是存储在缓存、主存、磁盘还是磁带上,反正运行、计算、输出的结果都一样。而内存管理实现这种媒介透明的手段就是虚拟内存。 ","date":"2018-06-03","objectID":"/os-memory/:1:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"虚拟内存 虚拟内存系统负责为程序提供一个巨大的、稀疏的、私有的地址空间的假象,其中保存了程序的所有指令和数据。操作系统在专门硬件的帮助下,通过每一个虚拟内存的索引,将其转换为物理地址,物理内存根据获得的物理地址去获取所需的信息。 作为用户级程序的程序员,可以看到的任何地址都是虚拟地址。只有操作系统,通过精妙的虚拟化内存技术,知道这些指令和数据所在的物理内存的位置。 目标:透明、效率、保护(一个程序不能访问另一个程序地址空间。) ","date":"2018-06-03","objectID":"/os-memory/:2:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"地址空间 地址空间(address space)是一个非负整数 地址 的有序合集:{0,1,2,… } 在一个带虚拟内存的系统中,CPU 从一个有 N= 2 的 n 次方 个地址的地址空间中生成虚拟地址,这个地址空间就称为虚拟地址空间:{0,1,2,3,…., N-1}。 一个系统还有一个物理地址空间,对应于系统中物理内存的 M 个字节: {0,1,2,3,…, M-1}。 一个地址空间的大小通常是由表示最大地址所需要的位数来描述的,比如,一个包含 N = 2 的 n 次方 个地址的虚拟地址空间就叫做一个 n 位地址空间,现代操作系统通常支持 32 位或者 64 位虚拟地址空间。 ","date":"2018-06-03","objectID":"/os-memory/:3:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"虚拟内存空间分布 用户空间内存从低到高是五种不同的内存段: 只读段:代码和常量等 数据段:全局变量等 堆:动态分配的内存,从低地址开始向上增长 文件映射:动态库、共享内存等,从高地址开始向下增长 栈:包括局部变量和函数调用的上下文等,栈的大小是固定的。一般 8MB ","date":"2018-06-03","objectID":"/os-memory/:4:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"基址极限地址翻译 翻译过程非常简单:物理地址=虚拟地址+程序所在区域的起始地址。 程序所在区域的起始地址称为(程序)基址。 地址保护也变得非常简单,只要访问的地址满足下列条件即为合法访问:程序所在区域的起始地址≤有效地址≤程序所在区域的起始地址+程序长度。 由此可见,我们只需要设置两个端值:基址和极限,即可达到地址翻译和地址保护的目的。这两个端值可以由两个寄存器来存放,分别称为基址寄存器和极限寄存器。 基址极限(包括分段之后包含多个基址极限也存在)管理模式的问题: 空间浪费(外部碎片太多) 地址空间增长困难。这有两层意思:一是指空间增长效率低下;二是空间增长存在天花板限制。 ","date":"2018-06-03","objectID":"/os-memory/:5:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"分页 分页系统的核心就是将虚拟内存空间和物理内存空间皆划分为大小相同的页面,如 4KB、8KB 或 16KB 等,并以页面作为内存空间的最小分配单位,一个程序的一个页面可以存放在任意一个物理页面里。 在分页系统下,一个程序发出的虚拟地址由两部分组成:页面号和页内偏移值。例如,对于 32 位寻址的系统,如果页面大小为 4KB,则页面号占 20 位,页内偏移值占 12 位。 分页的优势: 物理空间是页面的整数倍,并且空间分配以页面为单位,将不会再产生外部碎片。 空间增长也容易解决:只需要分配额外的虚拟页面,并找到一个闲置的物理页面存放即可。 分页的缺点: 页表很大。(使用多级页表解决) 页表和多级页表都会使内存访问次数增加,导致翻译速度下降。(使用 TLB 解决) ","date":"2018-06-03","objectID":"/os-memory/:6:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"页表 为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表(page table)。 例如,对于 32 位寻址的虚拟地址,如果页面大小为 4KB,则虚拟页面数最多可以达到 220,即 1048576 个虚拟页面。那么页表的记录条数就为 1048576 条。 页面记录内容:缓存禁止位、访问位、修改位、保护标识区、在内存否、物理页面号。 内存管理单元(MMU)对虚拟地址的翻译只是对页面号的翻译,即将虚拟页面号翻译成物理页面号。而对于偏移值,则不进行任何操作。这是因为虚拟页表和物理页表大小完全一样,虚拟页面里的偏移值和物理页面里的偏移值完全一样,因此无须翻译。 ","date":"2018-06-03","objectID":"/os-memory/:6:1","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"内存交换(swap space) 交换空间就是在硬盘上开辟一部分空间用于物理页的移入和移出。交换空间让操作系统为多个并发运行的进程都提供巨大地址空间的假象。 内存交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。 内存交换技术主要是在不同进程(或作业)之间进行,而内存覆盖则用于同一程序或进程中。 ","date":"2018-06-03","objectID":"/os-memory/:6:2","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"缺页中断 在分页系统里,一个虚拟页面既有可能在物理内存,也有可能保存在磁盘上。如果 CPU 发出的虚拟地址对应的页面不在物理内存,就将产生一个缺页中断。 缺页中断服务程序首先根据虚拟地址计算出该地址在相应程序文件里面的位移量或偏移量(off-set),然后要求文件系统在这个偏移量的地方进行文件读操作(读一个页面)。 在最坏情况下,每次新的访问都是对一个不在内存的页面进行访问,即每次内存访问都产生一次缺页中断,这样每次内存访问皆变成一次磁盘访问,而由于磁盘访问速度比内存可以慢一百万倍,因此整个系统的效率急剧下降。这种现象就称为内存抖动。 外中断是指由 CPU 执行指令以外的事件引起,如 I/O 完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。 而异常时由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。 ","date":"2018-06-03","objectID":"/os-memory/:6:3","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"页面更换算法 缺页中断服务程序将负责把位于磁盘上的数据加载到物理内存来。如果物理内存还有空闲页面,那就直接使用空闲的页面。但如果物理内存已满,则需要挑选某个已经使用过的页面进行替换。 公平算法主要包括下面 4 种: 随机算法 先来先出(FIFO)算法 第二次机会算法 时钟算法 非公平算法则包括如下 5 种: 最优算法 NRU 算法 LRU 算法 工作集算法 还有一种混合算法,它既想保持公平,又含有区别对待的考虑:工作集时钟算法。 ","date":"2018-06-03","objectID":"/os-memory/:6:4","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"页表地址翻译过程 页表命中 CPU 硬件的执行步骤: CPU 生成一个虚拟地址,并把它传送给 MMU。 MMU 生成 PTE(页表条目) 地址。 命中 TLB 的 MMU 从 TLB 中取出对应的 PTE MMU 将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。 高速缓存/主存将所请求的数据字返回 CPU。 未命中 TLB 从高速缓存/主存中请求这个 PTE 高速缓存/主存向 MMU 返回 PTE。 未缺页 PTE 有效位为 1,MMU 构造物理地址,并把它传送给高速缓存/主存。 高速缓存/主存将所请求的数据字返回 CPU。 缺页 PTE 有效位为 0,触发一次中断,传递 CPU 中的控制到操作系统内核中的缺页中断处理程序。 缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘。 缺页处理程序调入新的页面,并更新内存中的 PTE。 缺页处理程序返回原来的进程,再次执行导致缺页的指令, CPU 将引起缺页的虚拟地址重新发送给 MMU , 因为虚拟页面现在存在主存中,所以会命中,主存将所请求的数据字返回 CPU。 ","date":"2018-06-03","objectID":"/os-memory/:7:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"内存操作 API ","date":"2018-06-03","objectID":"/os-memory/:8:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"栈内存 栈内存的申请和释放操作是编译器来隐式管理的,所以有时也称为自动(automatic)内存。 C 中申请栈内存很容易。比如,假设需要在 func()函数中为一个整形变量 x 申请空间。 void func() { int x; ... } ","date":"2018-06-03","objectID":"/os-memory/:8:1","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"分配堆内存 malloc 只需要一个 size_t 类型参数,该参数表示你需要多少个字节。 #include \u003cstdlib.h\u003e ... void *malloc(size_t size); ","date":"2018-06-03","objectID":"/os-memory/:8:2","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"堆内存释放 要释放不再使用的堆内存,程序员只需调用 free(): int *x = malloc(10 * sizeof(int)); ... free(x); free 和 malloc 都属于库调用,但是本身是建立在一些系统调用之上的。 访问堆的一个具体单元,需要两次访问内存,第一次得取得指针,第二次才是真正的数据,而栈只需访问一次。另外,堆的内容被操作系统交换到外存的概率比栈大,栈一般是不会被交换出去的。 ","date":"2018-06-03","objectID":"/os-memory/:8:3","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"参考 《操作系统之哲学原理第 2 版》邹恒明 《操作系统导论》 ","date":"2018-06-03","objectID":"/os-memory/:9:0","tags":["操作系统"],"title":"内存","uri":"/os-memory/"},{"categories":["操作系统"],"content":"线程简介 一个程序只有一个执行点(一个程序计数器,用来存放要执行的指令),但多线程(multi-threaded)程序会有多个执行点(多个程序计数器,每个都用于取指令和执行)。 换一个角度来看,每个线程类似于独立的进程,只有一点区别:它们共享地址空间,从而能够访问相同的数据。 单个线程的状态与进程状态非常类似。线程有一个程序计数器(PC),记录程序从哪里获取指令。每个线程有自己的一组用于计算的寄存器。 如果有两个线程运行在一个处理器上,从运行一个线程(T1)切换到另一个线程(T2)时,必定发生上下文切换(context switch)。线程之间的上下文切换类似于进程间的上下文切换。对于进程,我们将状态保存到进程控制块(Process Control Block,PCB)。现在,我们需要一个或多个线程控制块(Thread Control Block,TCB),保存每个线程的状态。但是,与进程相比,线程之间的上下文切换有一点主要区别:地址空间保持不变(即不需要切换当前使用的页表)。 在多线程的进程中,每个线程独立运行,当然可以调用各种例程来完成正在执行的任何工作。不是地址空间中只有一个栈,而是每个线程都有一个栈。 线程共享资源 线程独享资源 地址空间 程序计数器 全局变量 寄存器 打开的文件 栈 子进程 状态字 信号及信号服务程序 ","date":"2018-06-02","objectID":"/os-thread/:1:0","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"线程 API ","date":"2018-06-02","objectID":"/os-thread/:2:0","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"线程创建 #include \u003cpthread.h\u003e int pthread_create( pthread_t * thread, const pthread_attr_t * attr, void * (*start_routine)(void*), void * arg); 该函数有 4 个参数:thread、attr、start_routine 和 arg。 第一个参数 thread 是指向 pthread_t 结构类型的指针,我们将利用这个结构与该线程交互,因此需要将它传入 pthread_create(),以便将它初始化。 第二个参数 attr 用于指定该线程可能具有的任何属性。一些例子包括设置栈大小,或关于该线程调度优先级的信息。 第三个参数最复杂,但它实际上只是问:这个线程应该在哪个函数中运行?在 C 中,我们把它称为一个函数指针(function pointer),这个指针告诉我们需要以下内容:一个函数名称(start_routine)。 第四个参数 arg 就是要传递给线程开始执行的函数的参数。 ","date":"2018-06-02","objectID":"/os-thread/:2:1","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"线程完成 void *mythread(void *arg) { int m = (int) arg; printf(\"%d\\n\", m); return (void *) (arg + 1); } int main(int argc, char *argv[]) { pthread_t p; int rc, m; Pthread_create(\u0026p, NULL, mythread, (void *) 100); Pthread_join(p, (void **) \u0026m); printf(\"returned %d\\n\", m); return 0; } 该函数有两个参数。 第一个是 pthread_t 类型,用于指定要等待的线程。 第二个参数是一个指针,指向你希望得到的返回值。因为函数可以返回任何东西,所以它被定义为返回一个指向 void 的指针。 ","date":"2018-06-02","objectID":"/os-thread/:2:2","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"锁 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); ","date":"2018-06-02","objectID":"/os-thread/:2:3","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"条件变量 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_signal(pthread_cond_t *cond); ","date":"2018-06-02","objectID":"/os-thread/:2:4","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"线程的生命周期 线程被选中 ●-------\u003e 创建 -------\u003e 就绪 \u003c-------\u003e 运行 -------\u003e 退出 -------\u003e ● ↑ 时间片用尽 / \\ / 阻塞结束\\ /等待IO或其他任务 \\ / \\ ↓ 阻塞 创建:一个新的线程被创建,等待该线程被调用执行; 就绪:时间片已用完,此线程被强制暂停,等待下一个属于它的时间片到来; 运行:此线程正在执行,正在占用时间片; 阻塞:也叫等待状态,等待某一事件(如 IO 或另一个线程)执行完; 退出:一个线程完成任务或者其他终止条件发生,该线程终止进入退出状态,退出状态释放该线程所分配的资源。 ","date":"2018-06-02","objectID":"/os-thread/:3:0","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"线程通信 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。 ","date":"2018-06-02","objectID":"/os-thread/:4:0","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"互斥锁 互斥锁是最常见的线程同步方式,它是一种特殊的变量,它有 lock 和 unlock 两种状态,一旦获取,就会上锁,且只能由该线程解锁,期间,其他线程无法获取。 优点:使用简单; 缺点: 重复锁定和解锁,每次都会检查共享数据结构,浪费时间和资源; 繁忙查询的效率非常低; ","date":"2018-06-02","objectID":"/os-thread/:4:1","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"条件变量 条件变量的方法是,当线程在等待某些满足条件时使线程进入睡眠状态,一旦条件满足,就唤醒,这样不会占用宝贵的互斥对象锁,实现高效。 条件变量允许线程阻塞并等待另一个线程发送信号,一般和互斥锁一起使用。 条件变量被用来阻塞一个线程,当条件不满足时,线程会解开互斥锁,并等待条件发生变化。一旦其他线程改变了条件变量,将通知相应的阻塞线程,这些线程重新锁定互斥锁,然后执行后续代码,最后再解开互斥锁。 ","date":"2018-06-02","objectID":"/os-thread/:4:2","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"读写锁 读写锁 也称之为 共享-独占锁,一般用在读和写的次数有很大不同的场合。即对某些资源的访问会出现两种情况,一种是访问的排他性,需要独占,称之为写操作;还有就是访问可以共享,称之为读操作。 适用场景:读写锁最适用于对数据结构的读操作次数多于写操作次数的场合。 ","date":"2018-06-02","objectID":"/os-thread/:4:3","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"信号量 信号量 和互斥锁的区别在于:互斥锁只允许一个线程进入临界区,信号量允许多个线程同时进入临界区 可以这样理解,互斥锁使用对同一个资源的互斥的方式达到线程同步的目的,信号量可以同步多个资源以达到线程同步。 ","date":"2018-06-02","objectID":"/os-thread/:4:4","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"信号 类似进程间的信号处理 ","date":"2018-06-02","objectID":"/os-thread/:4:5","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"死锁 死锁的四个必要条件 资源互斥 持有并等待 不能抢占 循环等待 死锁的应对方法 顺其自然,不予理睬 死锁检测与修复 死锁的动态避免 死锁的静态防止 清除四个必要条件中的一个 资源共享 一次性抢到所有锁 允许抢占资源 顺序请求资源 ","date":"2018-06-02","objectID":"/os-thread/:5:0","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"参考 《操作系统之哲学原理第 2 版》邹恒明 《操作系统导论》 ","date":"2018-06-02","objectID":"/os-thread/:6:0","tags":["操作系统"],"title":"线程","uri":"/os-thread/"},{"categories":["操作系统"],"content":"进程简介 进程就是运行中的程序。操作系统运行程序必须做的第一件事是将代码和所有静态数据(例如初始化变量)加载(load)到内存中,加载到进程的地址空间中。 操作系统用于维护进程记录的结构就是进程表或进程控制块(Process Control Block,PCB)。 维护的资料信息应当包括寄存器、程序计数器、状态字、栈指针、优先级、进程 ID、信号、创建时间、所耗 CPU 时间、当前持有的各种句柄等。 ","date":"2018-06-01","objectID":"/os-process/:1:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"进程的内存占用 从高地址到低地址依次为 Stack 未分配内存 Heap BSS(未初始化数据) Data(初始化数据) Text(程序代码) Linux 可通过size 编译的二进制文件查看各个块的大小。 ","date":"2018-06-01","objectID":"/os-process/:2:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"内核态和用户态 当处理器执行到syscall指令时,察觉这是一个系统调用指令,将进行如下操作: 设置处理器至内核态。 保存当前寄存器(栈指针、程序计数器、通用寄存器)。 将栈指针设置指向内核栈地址。 将程序计数器设置为一个事先约定的地址上,该地址上存放的是系统调用处理程序的起始地址。 系统调用和过程调用之间的关键区别在于,系统调用将控制转移(跳转)到 OS 中,同时提高硬件特权级别。 用户应用程序以所谓的用户模式(user mode)运行,这意味着硬件限制了应用程序的功能。 例如,以用户模式运行的应用程序通常不能发起对磁盘的 I/O 请求,不能访问任何物理内存页或在网络上发送数据包。 在发起系统调用时 ,通常通过一个称为陷阱(trap)的特殊硬件指令,硬件将控制转移到预先指定的陷阱处理程序(trap handler)(即预先设置的操作系统),并同时将特权级别提升到内核模式(kernel mode)。在内核模式下,操作系统可以完全访问系统的硬件,因此可以执行诸如发起 I/O 请求或为程序提供更多内存等功能。 当操作系统完成请求的服务时,它通过特殊的陷阱返回(return-from-trap)指令将控制权交还给用户,该指令返回到用户模式,同时将控制权交还给应用程序,回到应用离开的地方。 ","date":"2018-06-01","objectID":"/os-process/:3:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"进程的创建过程 分配进程控制块。 初始化机器寄存器。 初始化页表。 将程序代码从磁盘读进内存。 将处理器状态设置为“用户态”。 跳转到程序的起始地址(设置程序计数器)。 ","date":"2018-06-01","objectID":"/os-process/:4:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"进程的 API fork() 创建新进程,父进程通过 fork 创建子进程,子进程并不是完全拷贝了父进程。具体来说,虽然它拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器等。 wait() 父进程需要等待子进程执行完毕。 exec() 让子进程执行与父进程不同的程序。 ","date":"2018-06-01","objectID":"/os-process/:5:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"进程调度算法 先来先服务 最短任务优先 最短完成时间优先 时间片轮转 优先级调度 多级反馈队列 ","date":"2018-06-01","objectID":"/os-process/:6:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"进程状态 进程分为 3 种状态:执行、阻塞和就绪,但是只支持四种转换。 执行 → 就绪 执行 → 阻塞 阻塞 → 就绪 就绪 → 执行 ","date":"2018-06-01","objectID":"/os-process/:7:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"进程通信方式 管道(记名管道和匿名管道) 管道所占的空间既可以是内存,也可以是磁盘。要创建一个管道,一个进程只需调用管道创建的系统调用即可。该系统调用所做的事情就是在某种存储介质上划出一片空间,赋给其中一个进程写的权利,另一个进程读的权利即可。 记名管道:如果要在两个不相关的进程(如两个不同进程里面的进程)之间进行管道通信,则需要使用记名管道。 管道只能一端读,一端写。 套接字 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。 信号 信号就是一个内核对象,或者说是一个内核数据结构。发送方将该数据结构的内容填好,并指明该信号的目标进程后,发出特定的软件中断。操作系统接收到特定的中断请求后,知道是有进程要发送信号,于是到特定的内核数据结构里查找信号接收方,并进行通知。接到通知的进程则对信号进行相应处理。 使用信号用来传输很小的信息,使用管道或套接字不划算。 信号量 信号量实际上就是一个简单整数。一个进程在信号变为 0 或者 1 的情况下推进,并且将信号变为 1 或 0 来防止别的进程推进。当进程完成任务后,则将信号再改变为 0 或 1,从而允许其他进程执行。需要注意的是,信号量不只是一种通信机制,更是一种同步机制。 共享内存 共享内存就是两个进程共同拥有同一片内存。对于这片内存中的任何内容,二者均可以访问。要使用共享内存进行通信,一个进程首先需要创建一片内存空间专门作为通信用,而其他进程则将该片内存映射到自己的(虚拟)地址空间。这样,读写自己地址空间中对应共享内存的区域时,就是在和其他进程进行通信。 主要用于共享大量数据。 消息队列 消息队列相比管道,它无需固定的读写进程,任何进程都可以读写(当然是有权限的进程)。其次,它可以同时支持多个进程,多个进程可以读写消息队列。即所谓的多对多,而不是管道的点对点。 linux 通过ipcs命令可以查看消息队列、共享内存、信号量的具体信息 ","date":"2018-06-01","objectID":"/os-process/:8:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"进程切换开销 直接开销 切换页表全局目录(PGD) 切换内核态堆栈 切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文) 刷新 TLB 系统调度器的代码执行 间接开销 CPU 缓存失效导致的进程需要到内存直接访问的 IO 操作变多 ","date":"2018-06-01","objectID":"/os-process/:8:1","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":["操作系统"],"content":"参考 《操作系统之哲学原理第 2 版》邹恒明 《操作系统导论》 ","date":"2018-06-01","objectID":"/os-process/:9:0","tags":["操作系统"],"title":"进程","uri":"/os-process/"},{"categories":null,"content":"关于我 一个爱学习,知上进,善沟通,重细节,懂感恩的人。 ","date":"0001-01-01","objectID":"/about/:1:0","tags":null,"title":"关于","uri":"/about/"},{"categories":null,"content":"技能列表 熟悉 Go 编程,使用过 Gin、Echo、Gorm、RediGo、gRPC 等框架 掌握 Java 核心概念、JVM 模型等等,使用过 Spring、Mybatis、SpringBoot、SpringCloud 等框架 掌握 MySQL、Oracle、SQLServer 等关系型数据库 掌握 Redis 非关系型数据库 掌握 Kafka 消息队列 掌握数据库结构与算法,LeetCode 持续刷题 熟悉 TCP/IP 协议栈、RESTful API 掌握 Linux 核心命令,会一些基本的性能调优和问题排查 掌握 Nginx、Tomcat 等 web 服务,了解反(正)向代理、负载均衡、水平扩展等 熟练使用 Git 版本控制工具、基本命令,Git 工作流 掌握基本正则表达式使用 掌握 Docker 容器化技术,正在学习 Kubernetes(k8s) 容器化编排技术 掌握前端基本技能,写过一些 Vue 项目和混合 APP 项目 ","date":"0001-01-01","objectID":"/about/:2:0","tags":null,"title":"关于","uri":"/about/"},{"categories":null,"content":"关于本站 本站由 Hugo, LoveIt 主题, Github Pages 等生成。 ","date":"0001-01-01","objectID":"/about/:3:0","tags":null,"title":"关于","uri":"/about/"}]