Paul Jiang's Blog

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

List

发表于 2018-10-14 | 更新于 2020-05-17 | 分类于 数据结构与算法

MyArrayList

MyArrayList将保持基础数组,数组的容量,以及存储在MyArrayList中的当前项数。

MyArrayList将提供一种机制以改变基础数组的容量。通过获得一个新数组,将老数组拷贝到新数组中来改变数组的容量,允许虚拟机回收老数组。

MyArrayList将提供get和set的实现。

MyArrayList将提供基本的例程,如size、isEmpty和clear。还提供remove,以及两种不同版本的add。如果数组的大小和容量相同,那么这两个add例程将增加容量。

MyArrayList将提供一个实现Iterator接口的类。这个类将存储迭代序列中的下一项的下表,并提供next、hasNext和remove等方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
public class MyArrayList<AnyType> implements Iterable<AnyType>
{
private static final int DEFAULT_CAPACITY = 10;

private int theSize;
private AnyType[] theItems;

public int size()
{
return theSize;
}

public boolean isEmpty()
{
return size() == 0;
}

//调整容量符合大小
public void trimToSize()
{
ensureCapacity(size());
}
//确保数组大小足够大
public void ensureCapacity(int newCapacity)
{
if(newCapacity < theSize)
return;

//复制数据到新数组中
AnyType[] old = theItems;
theItems = (AnyType[]) new Object[newCapacity];
for(int i = 0; i <size(); i++)
{
theItems[i] = old[i];
}
}
public AnyType get(int index)
{
if(index < 0 || index >= size())
{
throw new ArrayIndexOutOfBoundsException();
}
return theItems[index];
}

public AnyType set(int index, AnyType newVal)
{
if(index < 0 || index >= size())
{
throw new ArrayIndexOutOfBoundsException();
}
AnyType old = theItems[index];
theItems[index] = newVal;
return old;
}

public void add(int index, AnyType x)
{
//数组不够大,则扩大数组
if(theItems.length == size())
{
ensureCapacity(size()*2 + 1);
}
//从index开始,元素往后移动一位
for(int i = theSize; i > index; i--)
{
theItems[i] = theItems[i - 1];
}
//index位置赋值x
theItems[index] = x;
theSize++;
}

public AanyType remove(int index)
{
AnyType removedItem = theItems[index];
for(int i = index; i < size(); i++)
{
//从index位置开始,所有元素都往前移动一位
theItems[i] = theItems[i + 1];
}
theSize--;
return removedItem;
}

public java.util.Iterator<AnyType> iterator()
{
return new ArrayListIterator<AnyType>();
}

private static class ArrayListIterator<AnyType> implements java.util.Iterator<AnyType>
{
private int current = 0;

public boolean hasNext()
{
return current < MyArrayList.this.size();
}

public AnyType next()
{
return MyArrayList.this.theItems[current++];
}

public void remove()
{
//防止迭代器的remove与MyArrayList的remove冲突
MyArrayList.this.remove(--current);
}
}
};

MyLinkedList

MyLinkedList类,包含两端的链、表的大小以及一些方法。

Node类,一个节点包含数据以及到前一个节点的链和到下一个节点的链,还有一些适当的构造方法。

LinkedListIterator类,该类抽象了位置的概念,并实现接口Iterator。它提供了方法next、hasNext和remove的实现

使用额外的头节点和尾节点的优点在于,通过排除许多特殊情形极大地简化了编码。例如,如果我不使用头节点,那么删除第1个节点就变成了一种特殊的情况,因为在删除期间我们必须重新调整链表到第1个节点的链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
public class MyLinkedList<AnyType> implements Iterable<AnyType>
{
private static class Node<AnyType>
{
public AnyType data;
public Node<AnyType> prev;
public Node<AnyType> next;
public Node(AnyType d, Node<AnyType> p, Node<AnyType> n)
{
data = d;
prev = p;
next = n;
}
}

private int theSize;
//自从链表构造以来对链表所做的改变次数
//与迭代器里的modCount进行比较
private int modCount = 0;
//头节点
private Node<AnyType> beginMarker;
//尾节点
private Node<AnyType> endMarker;

public boolean add(AnyType x)
{
add(size(), x);
return true;
}

public void add(int index, AnyType x)
{
//根据index获取节点,在该节点前添加x
addBefore(getNode(index, x));
}

public void addBefore(Node<AnyType> p, AnyTyoe x)
{
//在p和p之前插入新节点
Node<AnyType> newNode = new Node<AnyType>(x, p.prev, p);
//更新新节点前一节点的后一节点链接
newNode.prev.next = newNode;
//更新p的前一节点链接
p.prev = newNode;
theSize++;
modCount++;
}
public AnyType get(int index)
{
return getNode(index).data;
}

public AnyType set(int index, AnyType newVal)
{
Node<AnyType> p = getNode(index);
AnyType oldVal = p.data;
p.data = newVal;
return oldVal;
}

public AnyType remove(int index)
{
return remove(getNode(index));
}

private AnyType remove(Node<AnyType> p)
{
p.next.prev = p.prev;
p.prev.next = p.next;
theSize--;
modCount++;

return p.data;
}

private Node<AnyType> getNode(int index)
{
Node<AnyType> p;

if(index < 0 || index > size())
{
throw new IndexOutOfBoundsException();
}

//从头节点或者尾节点开始查找
if(index < size() / 2)
{
p = beginMarker.next;
for(int i = 0; i < index; i++)
{
p = p.next;
}
}
else
{
p = endMarker;
for(int i = size(); i > index; i--)
{
p = p.prev;
}
}

return p;
}

private class LinkedListIterator implements java.util.Iterator<AnyType>
{
private Node<AnyType> current = beginMarker.next;
private int expectedModCount = modCount;
private boolean okToRemove = false;

public boolean hasNext()
{
return current != endMarker;
}

public AnyType next()
{
if(modCount != expectedModCount)
{
throw new java.util.ConcurrentModificationException();
}
if(!hasNext())
{
throw new java.util.NoSuchElementException();
}

AnyType nextItem = current.data;
current = current.next;
okToRemove = true;
return nextItem;
}

public void remove()
{
if(modCount != expectedModCount)
{
throw new java.util.ConcurrentModificationException();
}
if(!okToRemove)
{
throw new IllegalStateException();
}

MyLinkedList.this.remove(current.prev);
okToRemove = false;
expectedModCount++;
}
}
}

Docker

发表于 2018-10-14 | 更新于 2020-05-17 | 分类于 虚拟化

Docker安装

修改YUM源

  1. 备份
    mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

  2. 下载对应版本repo文件
    wget http://mirrors.163.com/.help/CentOS7-Base-163.repo

  3. 运行以下命令生成缓存
    yum clean all
    yum makecache

  4. 安装docker
    yum install docker

Docker Registry搭建

1
2
3
4
5
6
7
docker run -d -p 5000:5000 -v /data/registry:/var/lib/registry --restart=always --name registry registry:2
# 配置非安全访问的仓库IP:端口号
/etc/docker/daemon.json
"insecure-registries":["10.0.2.15:5000"]

docker push 192.168.1.62:5000/xpanda/busybox
docker pull 192.168.1.62:5000/xpanda/busybox

常用Docker命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
docker search
docker ps
docker images
#删除镜像
docker rmi
#删除容器实例
docker rm
#下载镜像
docker pull quay.io/coreos/etcd:v3.2.16
#构建镜像
docker build -t xpanda/nginx:1.12.2 .
#后台运行
docker run -d -p 8080:8080 -p 443:443 --ip 10.20.0.1 -v /data/configs/nginx/conf.d:/etc/nginx/conf.d -v /data/configs/nginx/certs:/etc/nginx/certs xpanda/nginx:1.12.2
#进入容器
docker exec -it 5c61a04b14fc /bin/bash
#日志查看
docker logs -f --tail=100 6aa2172c8742
journalctl -u docker.service -n

边干边学LINUX内核指导-编译

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

环境准备

  1. 操作系统Fedora Core 5

配置内核

Linux-makefile

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

make命令和makefile文件

make命令和makefile文件的结合提供了一个在项目管理领域十分强大的工具。

makefile文件由一组依赖关系和规则构成。每个依赖关系由一个目标(即将要创建的文件)和一组该目标所依赖的源文件组成。而规则描述了如何通过这些依赖文件创建目标。一般来说,目标是一个单独的可执行文件。

make命令会读取makefile文件的内容,它先确定目标文件或要创建的文件,然后比较该目标所依赖的源文件的日期和时间以决定采用哪条规则来构造目标。通常在创建最终的目标文件之前,它需要先创建一些中间目标。make命令会根据makefile文件来确定目标文件的创建顺序以及正确的规则调用顺序。

makefile语法

依赖关系

依赖关系定义了最终应用程序里的每个文件与源文件之间的关系。

规则的写法是:
目标的名称: [文件] [文件] …

以下规则表示目标myapp依赖于main.o、2.o和3.o,而main.o依赖于main.h和a.h,等等。

1
2
3
4
myapp: main.o 2.o 3.o
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h

如果想一次创建多个文件,可以利用伪目标all。如果未指定一个all目标,则make命令将只创建它在文件makefile中找到的第一个目标。

1
all: myapp myapp.1

规则

makefile文件的第二部分内容是规则,它们定义了目标的创建方式。规则必须以[tab]开头,不能是空格。

1
2
myapp: main.o 2.o 3.o
gcc -o myapp main.o 2.o 3.o

注释

makefile文件中的注释以#号开头。

宏

通过语句MACRONAME=value在makefile文件中定义宏,引用宏的方法是使用$(MACRONAME)或${MARCRONAME}。

Linux-内存管理

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

内存管理

简单的内存分配

1
2
3
4
#include <stdlib.h>

void *malloc(size_t size);
void free(void *ptr);

页

内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常为字,但是,内存管理单元(MMU,管理内存并把虚拟地址装换为物理地址的硬件)通常以页为单位进行处理。从虚拟内存的角度来看,页就是最小单位。

大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。这就意味着,在支持4KB页大小并有1GB物理内存的机器上,物理内存会被划分为262144个页。

区

由于硬件的限制,内核并不能对所有的页一视同仁。有些页位于内存中特定的物理地址上,所以不能将其用于一些特定的任务。由于存在这种限制,所以内核把页划分为不同的区(zone)。内核使用区对具有相似特性的页进行分组。Linux必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题。

  1. 一些硬件只能用某些特定的内存地址来执行DMA(直接内存访问)。
  2. 一些体系结构的内存的物理寻址范围比虚拟寻址范围大得多。这样,就有一些内存不能永久的映射到内核空间上。

Linux主要使用了四种区:

  • ZONE_DMA:这个区包含的页能用来执行DMA操作。
  • ZONE_DMA32:与ZONE_DMA不同之处在于这些页面只能被32位设备访问。
  • ZONE_NORMAL:这个区包含的都是能正常映射的页。
  • ZONE_HIGHMEM:这个区包含“高端内存”,其中的页并不能永久地映射到内核地址空间。

区的实际使用和分布是与体系结构相关的。区的划分没有任何物理意义,这只不过是内核为了管理页而采取的一种逻辑上的分组。

x86-32上的区

区 描述 物理内存
ZONE_DMA DMA使用的页 <16MB
ZONE_NORMAL 正常可寻址的页 16-896M
ZONE_HIGHMEM 动态映射的页 >896M

页的操作

内核是完全信赖自己的。这点与用户空间不同,如果你有非法操作,内核会开开心心的把自己挂起来,停止运行。

获得页:

1
释放页:```free_pages

Linux-虚拟文件系统

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

虚拟文件系统(VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。程序可以利用标准的Unix系统调用不同的文件系统,甚至是不同介质上的文件系统。

文件系统抽象层

之所以可以使用这种通用接口对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层。它定义了所有文件系统都支持的、基本的、概念上的接口和数据结构。

Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点。

安装点(Mount Point)

在Unix系统中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称作命名空间,所有的已安装文件系统都作为根文件系统树的枝叶出现在系统中。

文件

文件是简单的面向字节流的有序字节串。

目录项

文件通过目录组织起来。目录条目统称为目录项。在Unix中,目录属于普通文件,它列出包含在其中的所有文件。

索引节点

Unix系统将文件的相关信息和文件本身这两个概念加以区分,例如访问控制权限、大小、拥有者、创建时间等信息。文件相关信息,被存储在一个单独的数据结构中,该结构被称为索引节点。

超级块

超级块是一种包含文件系统信息的数据结构。

VFS对象及其数据结构

VFS采用的是面向对象的设计思路,使用一组数据结构来代表通用文件对象。

VFS中有四个主要的对象类型

  1. 超级块对象:代表一个具体的已安装文件系统
  2. 索引节点对象:代表一个具体文件
  3. 目录项对象:代表一个目录项,是路径的一个组成部分
  4. 文件对象:代表由进程打开的文件

因为VFS将目录作为一个文件来处理,所以不存在目录对象。

超级块对象

各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。对于并非基于磁盘的文件系统(如基于内存的文件系统,比如sysfs),它们会在使用现场创建超级块并将其保存到内存中。

1
2
3
4
struct super_block {
unsigned long long s_maxbytes;//文件大小上限
struct file_system_type s_type;//文件系统类型
}

索引节点对象

索引节点对象包含了内核在操作文件或目录时需要的全部信息。对于Unix风格的文件系统来说,这些信息可以从磁盘索引节点直接读入。如果一个文件系统没有索引节点,那么,不管这些相关信息在磁盘上是怎么存放的,文件系统都必须从中提取信息。没有索引节点的文件系统通常将文件的描述信息作为文件的一部分来存放。这些文件系统与Unix风格的文件系统不同,没有将数据与控制信息分开存放。有些现代文件系统使用数据库来存储文件的数据。不管哪种情况、采用哪种方式,索引节点对象必须在内存中创建,以便于文件系统使用。

一个索引节点代表文件系统中(但是索引节点仅当文件被访问时,才在内存中创建)的一个文件,它也可以是设备或管道这样的特殊文件。

1
2
3
4
5
6
7
8
struct inode {
unsigned long i_ino;//节点号
atomic_t i_count;//引用计数
loff_t i_size;//以字节为单位的文件大小
struct address_space *i_mapping;//相关的地址映射
struct address_space i_data;//设备地址映射
struct dquot *i_dquot[MAXQUOTAS];//索引节点的磁盘限额
}

目录项对象

为了方便查找操作,VFS引入了目录项的概念。每个dentry代表路径中的一个特定部分。在路径中(包括普通文件在内),每一个部分都是目录项对象。

1
2
3
4
5
6
7
// <linux/dcache.h>

struct dentry {
struct dentry *d_parent;//父目录的目录项对象
struct qstr d_name;//目录项名称
struct inode *d_inode;//相关联的索引节点
}

文件对象

文件对象表示进程已打开的文件。文件对象是已打开的文件在内存中的表示。因为多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已打开文件,它反过来指向目录项对象。

1
2
3
4

struct file {
struct path f_path;//包含目录项
}

Linux-shell

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

shell是一个作为用户与Linux系统间接口的程序,它允许用户向操作系统输入需要执行的命令。

名 路径 FreeBSD 5.2.1 Linux 2.4.22 Mac OS X 10.3 Solaris 9
Bourne shell /bin/sh . 链接至bash 链接至bash .
Bourne-again shell /bin/bash 可选的 . . .
C shell /bin/csh 链接至tcsh 链接至tcsh 链接至tcsh .
Korn shell /bin/ksh .
TENEX C shell /bin/tcsh . . . .

管道和重定向

  • 重定向输出
  • 重定向输入
  • 管道

脚本和语法

第一行

#!告诉系统同一行上紧跟在它后面的那个参数是用来执行本文件的程序。

1
#!/bin.sh

语法

变量

使用变量之前通常并不需要事先为它们做出声明。
变量赋值时,等号两边不能有空格。
通过在变量名前加一个$符号来访问它的内容。

1
2
a=123
echo $a

环境变量

当一个shell脚本程序开始执行时,一些变量会根据环境设置中的值进行初始化。这些变量通常用大写字母做名字,以便它们和用户在脚本程序里定义的变量区分开来。

环境变量 说明
$HOME 当前用户目录
$PATH 搜索地址
$PS1 命令提示符
$PS2 二级提示符
$IFS 输入域分隔符
$0 shell脚本的名字
$# 传递给脚本的参数个数
$$ shell脚本的进程号

参数变量

如果脚本程序在调用时带有参数,一些额外的变量就会被创建。

环境变量 说明
$1,$2,… 脚本程序的参数
$* 列出所有

条件

test或[

在一些老版本的UNIX shell中,test命令调用的是一个外部程序。可以使用which test来检查执行的是哪一个test命令。

1
2
3
4
5
6
7
8
9
if test -f fred.c
then
...
fi

if [ -f fred.c ]
then
...
fi
字符串比较 结果
string1 = string2
string1 != string2
-n strng 如果字符串不为空则结果为真
-z string 如果字符串为Null(一个空串)则结果为真
算术比较 结果
expression1 -eq expression2 如果两个表达式相等则结果为真
expression1 -ne expression2 如果两个表达式不等则结果为真
expression1 -gt expression2 如果expression1大于expression2则结果为真
expression1 -ge expression2 如果expression1大于等于expression2则结果为真
expression1 -lt expression2 如果expression1小于expression2则结果为真
expression1 -le expression2 如果expression1小于等于expression2则结果为真
!expression 如果表达式为假则结果为真
文件条件测试 结果
-d file 如果是目录
-e file 如果文件存在
-f file 如果是普通文件
-g file 如果set-group-id位被设置
-r file 如果文件刻度
-s file 如果文件大小不为0
-u file 如果set-user-id位被设置
-w file 如果文件可写
-x file 如果文件可执行

控制结构

if语句

1
2
3
4
5
6
7
8
if condition
then
statements
elif
statements
else
statements
fi

for语句

1
2
3
4
for variable in values
do
statements
done

while语句

1
2
3
while condition do
statements
done

until语句

1
2
3
4
until condition
do
statements
done

case语句

1
2
3
4
5
6
7
8
9
10
11
case variable in
pattern [|pattern]...) statements;;
pattern [|pattern]...) statements;;
...
esac

case "$timeofday" in
yes) echo "Good Morning";;
no ) echo "Good Afternonn";;
...
esac

AND和OR语句

1
2
statement1 && statement2 && statement3
statement1 || statement2 || statement3

语句块

1
2
3
get_confirm && {
grep -v "$cdcatnum" $tracks_file > $temp_file
}

函数

必须在调用一个函数之前先对它进行定义。当一个函数被调用时,脚本程序的位置参数($*,$@,$#,$1等)会被替换为函数的参数。当函数执行完毕后,这些参数会恢复为它们先前的值。

1
2
3
function_names(){
statements
}

命令

break命令

用于跳出循环

:冒号命令

冒号(:)命令是一个空命令。用于简化条件逻辑,相当于true的别名,比true快。

1
2
3
4
5
# 无限循环
while :

# 条件设置
: ${var:=value}

continue命令

继续循环

.命令

点(.)命令用于在当前shell中执行命令。点命令实在当前上下文中执行命令,所以可以改变当前脚本中的变量参数。

1
. ./shell_script

echo命令

输出字符串

1
echo -n "string to output"

eval命令

对参数进行求值

1
2
3
4
5
foo=10
x=foo
eval y='$'$x
echo $y
#输出10

命令的执行

1
2
3
4
5
# 旧的语法,反引号
`command`

# 新的语法
$(command)

调试脚本程序

跟踪脚本程序中复杂错误的主要方法是设置各种shell选项。为此,你可以在调用shell时加上命令行选项,或是使用set命令。

命令行选项 set选项 说明
sh -n set -n 只检查语法错误,不执行命令
sh -v set -v 在执行命令之前回显它们
sh -x set -x 在处理命令之后回显它们
sh -u set -u 如果使用了未定义的变量,就给出出错消息

CentOS-内核升级

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

EPEL的全称叫 Extra Packages for Enterprise Linux 。EPEL是由 Fedora 社区打造,为 RHEL 及衍生发行版如 CentOS、Scientific Linux 等提供高质量软件包的项目。装上了 EPEL之后,就相当于添加了一个第三方源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看当前内核版本
uname -r
cat /etc/redhat-release

# 安装内核包
yum install kernel-lt-4.4.147-1.el7.elrepo.x86_64.rpm
yum install kernel-lt-devel-4.4.147-1.el7.elrepo.x86_64.rpm

# 查看启动项
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg

# 设置启动项
grub2-set-default 0

# 检查启动项是否设置成功
grub2-editenv list

编译内核

https://wiki.centos.org/HowTos/Custom_Kernel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yum install -y rpm-build rpmdevtools

yum groupinstall -y "Development Tools"
yum install -y asciidoc bc m4 net-tools openssl-devel xmlto audit-libs-devel binutils-devel bison elfutils-devel java-1.8.0-openjdk-devel newt-devel numactl-devel perl-ExtUtils-Embed python-devel slang-devel xz-devel zlib-devel ncurses-devel pciutils pciutils-devel
# 生成目录
rpmdev-setuptree

cp kernel-lt-4.4.147-1.el7.elrepo.nosrc.rpm rpmbuild/SRPMS/
cp linux-4.4.147.tar.xz rpmbuild/SOURCES/

rpm -ivh ~/rpmbuild/SRPMS/kernel-lt-4.4.147-1.el7.elrepo.nosrc.rpm
# 使用rpmbuild就可以一键编出你的内核到 ~/rpmbuild/RPMS/ 目录中了
rpmbuild -bb --with baseonly --with firmware ~/rpmbuild/SPECS/kernel-lt-4.4.spec

#编译过程会用到大量随机种子,遇到 waiting for lock on `./random_seed'... 提示时请执行
rngd -r /dev/urandom

性能之巅-2文件系统

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

性能之巅-1基本概念

发表于 2018-10-01 | 更新于 2020-05-17 | 分类于 操作系统

系统性能

系统性能是对整个系统的研究,包括了所有的硬件组件和整个软件栈。对于分布式系统来说,这意味着多台服务器和多个应用。

人员

系统性能是一项需要多类人员参与的事务,其中包括系统管理员、技术支持人员、应用开发者、数据库管理员和网络管理员。对于他们的大多数来说,性能是一项兼职的事情,他们可能会有发掘性能的倾向,但仅限于本职工作范围内(网络团队检查网络、数据库团队检查数据库,如此等等)。然而,对于某些性能问题,要找到根本原因还需要这些团队一起协同工作才行。

一些公司会雇佣性能工程师,其主要任务就是维护系统性能。他们与多个团队协同工作,对环境做全局性的研究。

事情

视角

性能可以从不同的角度来审视。负载分析和资源分析。系统管理员作为系统资源的负责人,通常采用资源分析角度。应用程序开发人员,对最终实现的负载性能负责,通常采用负载缝隙角度。

延时

性能分析必须量化问题的重要程度。延时非常适合用来量化性能。

事件 延时 相对时间比例
1个CPU周期 0.3ns 1s
L1缓存访问 0.9ns 3s
L2缓存访问 2.8ns 9s
L3缓存访问 12.9ns 43s
主存访问(从CPU访问DRAM) 120ns 6分
固态硬盘I/O(闪存) 50-150us 2-6天
旋转磁盘I/O 1-10ms 1-12月
互联网:从旧金山到纽约 40ms 4年
互联网:从旧金山到英国 81ms 8年
互联网:从旧金山到澳大利亚 183ms 19年
TCP包重传 1-3s 105-317年
OS虚拟化系统重启 4s 423年
SCSI命令超时 30s 3千年
硬件虚拟化系统重启 40s 4千年
物理系统重启 5m 32千年

动态跟踪

动态跟踪技术把所有的软件变得可以监控,而且能用在真实的生产环境中。这项技术利用内存中的CPU指令并在这些指令之上动态构建监测数据。

指标

  • IOPS:每秒的I/O操作数。
  • 吞吐量:每秒数据量或操作量。
  • 使用率:给定的时间区间内资源的繁忙程度,有些组件达到100%后,还是能够正常工作。
  • 饱和度:随着工作量增加而对资源的请求超过资源所能处理的程度,100%后就无法处理,开始排队。
  • 命中率

方法

方法 类型
街灯讹方法 观测分析
随机变动讹方法 实验分析
责怪他人讹方法 假设分析
Ad Hoc核对清单法 观测与实验分析
问题陈述法 信息收集
科学法 观测分析
诊断循环 生命周期分析
工具法 观测分析
USE方法 观测分析
工作负载特征归纳 观测分析,容量规划
向下挖掘分析 观测分析
延时分析 观测分析
R方法 观测分析
事件跟踪 观测分析
基础线统计 观测分析,容量规划
性能监控 观测分析,容量规划
排队论 统计分析,容量规划
静态性能调整 观测分析,容量规划
缓存调优 观测分析,调优
微基准测试 实验分析
容量规划 容量规划,调优
1…19202122

Paul Jiang

212 日志
26 分类
26 标签
Links
  • 褚霸
  • 章亦春
  • Martin Fowler
© 2020 Paul Jiang
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Gemini v6.5.0