Mark blog

知行合一 划水归档

全新启航 敬请期待

Linux服务器上经常遇到一些系统和应用上的问题,如何分析排查,需要利器,下面总结列表了一些常用工具、trace tool.

引用linux-performance-analysis-and-tools中图片,说明这些tool试用层次位置

OS系统命令

系统信息(RHEL/Fedora)

  • uname -a 或 cat /proc/version #print system information
    • Linux hadoopst2.cm6 2.6.18-164.el5 #1 SMP Tue Aug 18 15:51:48 EDT 2009 x86_64 x86_64 x86_64 GNU/Linux
  • uptime
    • 15:42:46 up 674 days, 6 min, 35 users, load average: 1.30, 5.97, 11.53
  • cat /etc/redhat-release
    • Red Hat Enterprise Linux Server release 5.4 (Tikanga)
  • lsb_release
    • LSB Version: :core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3.1-amd64:graphics-3.1-ia32:graphics-3.1-noarch
  • cat /proc/cpuinfo
  • cat /proc/meminfo
  • lspci - list all PCI devices
  • lsusb - list USB devices
  • last, lastb - show listing of last logged in users
  • lsmod — show the status of modules in the Linux Kernel
  • modprobe - add and remove modules from the Linux Kernel

常用命令/工具

  • ps
    • To print a process tree: ps -ejH / ps axjf
    • To get info about threads: ps -eLf / ps axms
  • ulimit -a
  • lsof - list open files, UNIX一切皆文件
    • lsof -p PID
  • rpm/yum
    • rpm -qf FILE #文件所属rpm包
    • rpm -ql RPM #rpm包含文件
    • /var/log/yum.log #yum 更新包日志
  • /etc/XXX #系统级程序配置目录, 如
    • /etc/yum.repos.d/ yum源配置
  • /var/log/XXX #日志目录, 如
    • /var/log/cron #crontab日志,可以查看调度执行情况
  • ntpd - Network Time Protocol (NTP) daemon,同步集群中机器时间
  • squid - proxy caching server,集群WebUI的代理

系统监控

  • mpstat - Report processors related statistics. 注意%sys %iowait值
  • vmstat - Report virtual memory statistics
  • iostat - Report Central Processing Unit (CPU) statistics and input/output statistics for devices and partitions.
  • netstat - Print network connections, routing tables, interface statistics, masquerade connections, and multicast memberships
    • netstat -atpn | grep PID
  • ganglia - a scalable distributed monitoring system for high-performance computing systems such as clusters and Grids.
  • sar/tsar - Collect, report, or save system activity information; tsar是淘宝自己改进的版本
    • 定时采样(每分钟),可查历史记录(默认5分钟),可弥补ganglia显示更详细信息
  • iftop - the “top” bandwidth consumers shown. iftop wiki
  • iotop
  • vmtouch, Portable file system cache diagnostics and control

网络相关

  • telnet/nc IP PORT - 确认目标端口是否可访问,只ping通不一定端口可访问,可能防火墙等禁止
  • ifconfig/ifup/ifdown - configure a network interface
  • traceroute - print the route packets trace to network host
  • nslookup - query Internet name servers interactively
  • tcpdump - dump traffic on a network, 类似开源工具 wireshark, netsniff-ng, 更多工具比较
  • lynx - a general purpose distributed information browser for the World Wide Web
  • tcpcp - allows cooperating applications to pass ownership of TCP connection endpoints from one Linux host to another one.

程序/进程相关

静态信息

  • ldconfig - configure dynamic linker run time bindings
    • ldconfig -p | grep SO 查看so是否在link cache中
  • ldd - print shared library dependencies, 查看exe或so依赖的so
  • nm - list symbols from object files,可grep查找是否存在相关的symbol,是否Undefined.
  • readelf - Displays information about ELF files. 可现实elf相关信息,如32/64位,适用的OS,处理器

动态信息

  • gdb
  • cat /proc/$PID/[cmdline|environ|limits|status|…] - 进程相关信息
  • pstack - print a stack trace of a running process
  • pmap - report memory map of a process

资料来源:

https://www.cnblogs.com/zengkefu/p/5642955.html

Shell脚本的调试方法

Shell提供了一些用于调试脚本的选项,如下所示:

  • -n

    读一遍脚本中的命令但不执行,用于检查脚本中的语法错误

  • -v

    一边执行脚本,一边将执行过的脚本命令打印到标准错误输出

  • -x

    提供跟踪执行信息,将执行的每一条命令和结果依次打印出来

使用这些选项有三种方法,一是在命令行提供参数

1
$ sh -x ./script.sh

二是在脚本开头提供参数

1
#! /bin/sh -x

第三种方法是在脚本中用set命令启用或禁用参数

1
2
3
4
5
6
7
#! /bin/sh
if [ -z "$1" ]; then
set -x
echo "ERROR: Insufficient Args."
exit 1
set +x
fi

set -xset +x分别表示启用和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。

跟踪脚本的执行

你可以让bash打印出你脚本执行的过程中的所有语句。这很简单,只需要使用bash的-x选项就可以做到,下面让我们来看一下。

下面的这段脚本,先是输出一个问候语句,然后输出当前的时间:

1
2
3
#!/bin/bash
echo "Hello $USER,"
echo "Today is $(date +'%Y-%m-%d')"

下面让我们使用-x选项来运行这段脚本:

1
2
3
4
5
6
$ bash -x example_script.sh  
+ echo 'Hello chenhao,'
Hello chenhao,
++ date +%Y-%m-%d
+ echo 'Today is 2009-08-31'
Today is 2009-08-31

这时,我们可以看到,bash在运行前打印出了每一行命令。而且每行前面的+号表明了嵌套。这样的输出可以让你看到命令执行的顺序并可以让你知道整个脚本的行为。
在跟踪里输出行号

在一个很大的脚本中,你会看到很多很多的执行跟踪的输出,阅读起来非常费劲,所以,你可以在每一行前加上文件的行号,这会非常有用。要做到这样,你只需要设置下面的环境变量:

1
export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '

让我们看看设置上了PS4这个环境变量后会是什么样的输出。

1
2
3
4
5
6
$ bash -x example_script.sh
+example_script.sh:2:: echo 'Hello chenhao,'
Hello chenhao,
++example_script.sh:3:: date +%Y-%m-%d
+example_script.sh:3:: echo 'Today is 2009-08-31'
Today is 2009-08-31

调试部份的脚本

有些时候,你并不想调试整个脚本,你只要调试其中的一部份,那么,你可以在你想要调试的脚本之前,调用“set -x”,结束的时候调用“set +x”就可以了。如下面的脚本所示:

1
2
3
4
5
#!/bin/bash
echo "Hello $USER,"
set -x
echo "Today is $(date %Y-%m-%d)"
set +x

让我们看看运行起来是啥样?

1
2
3
4
5
6
$ ./example_script.sh
Hello chenhao,
++example_script.sh:4:: date +%Y-%m-%d
+example_script.sh:4:: echo 'Today is 2009-08-31'
Today is 2009-08-31
+example_script.sh:5:: set +x

注意:我们在运行脚本的时候,不需要使用bash -x了。

日志输出

跟踪日志有时候太多了,多得都受不了,而且,输出的内容很难阅读。一般来说,我们很多时候只关心于条件表达式,变量值,或是函数调用,或是循环等。。在这种情况下,log一些感兴趣的特定的信息,可能会更好。

使用log前,我们先写一个函数:

1
2
3
4
_log() {
if [ "$_DEBUG" == "true" ]; then
echo 1>&2 "$@"
fi}

于是,你就可以在你的脚本中如下使用:

1
2
_log "Copying files..."
cp src/* dst/

我们可以看到,上面那个_log函数,需要检查一个_DEBUG 变量,只有这个变量是真,才会真正开发输出日志。这样,你就只需要控制这个开关,而不需要删除你的debug信息。

1
$ _DEBUG=true ./example_script.sh

参考原文作者:

陈皓

方法在Python中是如何工作的

方法就是一个函数,它作为一个类属性而存在,你可以用如下方式来声明、访问一个函数:

1
2
3
4
5
6
7
8
>>> class Pizza(object):
... def __init__(self, size):
... self.size = size
... def get_size(self):
... return self.size
...
>>> Pizza.get_size
<unbound method Pizza.get_size>

Python在告诉你,属性_get_size是类Pizza的一个未绑定方法。这是什么意思呢?很快我们就会知道答案:

1
2
3
4
>>> Pizza.get_size()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method get_size() must be called with Pizza instance as first argument (got nothing instead)

我们不能这么调用,因为它还没有绑定到Pizza类的任何实例上,它需要一个实例作为第一个参数传递进去(Python2必须是该类的实例,Python3中可以是任何东西),尝试一下:

1
2
>>> Pizza.get_size(Pizza(42))
42

太棒了,现在用一个实例作为它的的第一个参数来调用,整个世界都清静了,如果我说这种调用方式还不是最方便的,你也会这么认为的;没错,现在每次调用这个方法的时候我们都不得不引用这个类,如果不知道哪个类是我们的对象,长期看来这种方式是行不通的。

那么Python为我们做了什么呢,它绑定了所有来自类_Pizza的方法以及该类的任何一个实例的方法。也就意味着现在属性get_sizePizza的一个实例对象的绑定方法,这个方法的第一个参数就是该实例本身。

1
2
3
4
>>> Pizza(42).get_size
<bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>>
>>> Pizza(42).get_size()
42

和我们预期的一样,现在不再需要提供任何参数给_get_size,因为它已经是绑定的,它的self参数会自动地设置给Pizza实例,下面代码是最好的证明:

1
2
3
>>> m = Pizza(42).get_size
>>> m()
42

更有甚者,你都没必要使用持有Pizza对象的引用了,因为该方法已经绑定到了这个对象,所以这个方法对它自己来说是已经足够了。

也许,如果你想知道这个绑定的方法是绑定在哪个对象上,下面这种手段就能得知:

1
2
3
4
5
6
7
>>> m = Pizza(42).get_size
>>> m.__self__
<__main__.Pizza object at 0x7f3138827910>
>>> # You could guess, look at this:
...
>>> m == m.__self__.get_size
True

显然,该对象仍然有一个引用存在,只要你愿意你还是可以把它找回来。

在Python3中,依附在类上的函数不再当作是未绑定的方法,而是把它当作一个简单地函数,如果有必要它会绑定到一个对象身上去,原则依然和Python2保持一致,但是模块更简洁:

1
2
3
4
5
6
7
8
>>> class Pizza(object):
... def __init__(self, size):
... self.size = size
... def get_size(self):
... return self.size
...
>>> Pizza.get_size
<function Pizza.get_size at 0x7f307f984dd0>

静态方法

静态方法是一类特殊的方法,有时你可能需要写一个属于这个类的方法,但是这些代码完全不会使用到实例对象本身,例如:

1
2
3
4
5
6
7
class Pizza(object):
@staticmethod
def mix_ingredients(x, y):
return x + y

def cook(self):
return self.mix_ingredients(self.cheese, self.vegetables)

这个例子中,如果把_mix_ingredients作为非静态方法同样可以运行,但是它要提供self*参数,而这个参数在方法中根本不会被使用到。这里的@staticmethod*装饰器可以给我们带来一些好处:

  • Python不再需要为Pizza对象实例初始化一个绑定方法,绑定方法同样是对象,但是创建他们需要成本,而静态方法就可以避免这些。
1
2
3
4
5
6
>>> Pizza().cook is Pizza().cook
False
>>> Pizza().mix_ingredients is Pizza.mix_ingredients
True
>>> Pizza().mix_ingredients is Pizza().mix_ingredients
True
  • 可读性更好的代码,看到@staticmethod我们就知道这个方法并不需要依赖对象本身的状态。
  • 可以在子类中被覆盖,如果是把mix_ingredients作为模块的顶层函数,那么继承自Pizza的子类就没法改变pizza的mix_ingredients了如果不覆盖cook的话。

类方法

话虽如此,什么是类方法呢?类方法不是绑定到对象上,而是绑定在类上的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> class Pizza(object):
... radius = 42
... @classmethod
... def get_radius(cls):
... return cls.radius
...
>>>
>>> Pizza.get_radius
<bound method type.get_radius of <class '__main__.Pizza'>>
>>> Pizza().get_radius
<bound method type.get_radius of <class '__main__.Pizza'>>
>>> Pizza.get_radius is Pizza().get_radius
True
>>> Pizza.get_radius()
42

无论你用哪种方式访问这个方法,它总是绑定到了这个类身上,它的第一个参数是这个类本身(记住:类也是对象)。

什么时候使用这种方法呢?类方法通常在以下两种场景是非常有用的:

  • 工厂方法:它用于创建类的实例,例如一些预处理。如果使用@staticmethod代替,那我们不得不硬编码Pizza类名在函数中,这使得任何继承Pizza的类都不能使用我们这个工厂方法给它自己用。
1
2
3
4
5
6
7
class Pizza(object):
def __init__(self, ingredients):
self.ingredients = ingredients

@classmethod
def from_fridge(cls, fridge):
return cls(fridge.get_cheese() + fridge.get_vegetables())
  • 调用静态类:如果你把一个静态方法拆分成多个静态方法,除非你使用类方法,否则你还是得硬编码类名。使用这种方式声明方法,Pizza类名明永远都不会在被直接引用,继承和方法覆盖都可以完美的工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Pizza(object):
def __init__(self, radius, height):
self.radius = radius
self.height = height

@staticmethod
def compute_area(radius):
return math.pi * (radius ** 2)

@classmethod
def compute_volume(cls, height, radius):
return height * cls.compute_area(radius)

def get_volume(self):
return self.compute_volume(self.height, self.radius)

抽象方法

抽象方法是定义在基类中的一种方法,它没有提供任何实现,类似于Java中接口(Interface)里面的方法。

在Python中实现抽象方法最简单地方式是:

1
2
3
class Pizza(object):
def get_radius(self):
raise NotImplementedError

任何继承自_Pizza的类必须覆盖实现方法get_radius,否则会抛出异常。

这种抽象方法的实现有它的弊端,如果你写一个类继承Pizza,但是忘记实现get_radius,异常只有在你真正使用的时候才会抛出来。

1
2
3
4
5
6
7
>>> Pizza()
<__main__.Pizza object at 0x7fb747353d90>
>>> Pizza().get_radius()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in get_radius
NotImplementedError

还有一种方式可以让错误更早的触发,使用Python提供的abc模块,对象被初始化之后就可以抛出异常:

1
2
3
4
5
6
7
8
import abc

class BasePizza(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def get_radius(self):
"""Method that should do something."""

使用abc后,当你尝试初始化BasePizza或者任何子类的时候立马就会得到一个TypeError,而无需等到真正调用get_radius的时候才发现异常。

1
2
3
4
>>> BasePizza()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius

混合静态方法、类方法、抽象方法

当你开始构建类和继承结构时,混合使用这些装饰器的时候到了,所以这里列出了一些技巧。

记住,声明一个抽象的方法,不会固定方法的原型,这就意味着虽然你必须实现它,但是我可以用任何参数列表来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
import abc

class BasePizza(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def get_ingredients(self):
"""Returns the ingredient list."""

class Calzone(BasePizza):
def get_ingredients(self, with_egg=False):
egg = Egg() if with_egg else None
return self.ingredients + egg

这样是允许的,因为Calzone满足BasePizza对象所定义的接口需求。同样我们也可以用一个类方法或静态方法来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
import abc

class BasePizza(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def get_ingredients(self):
"""Returns the ingredient list."""

class DietPizza(BasePizza):
@staticmethod
def get_ingredients():
return None

这同样是正确的,因为它遵循抽象类BasePizza设定的契约。事实上get_ingredients方法并不需要知道返回结果是什么,结果是实现细节,不是契约条件。

因此,你不能强制抽象方法的实现是一个常规方法、或者是类方法还是静态方法,也没什么可争论的。从Python3开始(在Python2中不能如你期待的运行,见issue5867),在abstractmethod*方法上面使用@staticmethod@classmethod*装饰器成为可能。

1
2
3
4
5
6
7
8
9
10
11
12
import abc

class BasePizza(object):
__metaclass__ = abc.ABCMeta

ingredient = ['cheese']

@classmethod
@abc.abstractmethod
def get_ingredients(cls):
"""Returns the ingredient list."""
return cls.ingredients

别误会了,如果你认为它会强制子类作为一个类方法来实现get_ingredients那你就错了,它仅仅表示你实现的get_ingredientsBasePizza中是一个类方法。

可以在抽象方法中做代码的实现?没错,Python与Java接口中的方法相反,你可以在抽象方法编写实现代码通过super()来调用它。(译注:在Java8中,接口也提供的默认方法,允许在接口中写方法的实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import abc

class BasePizza(object):
__metaclass__ = abc.ABCMeta

default_ingredients = ['cheese']

@classmethod
@abc.abstractmethod
def get_ingredients(cls):
"""Returns the ingredient list."""
return cls.default_ingredients

class DietPizza(BasePizza):
def get_ingredients(self):
return ['egg'] + super(DietPizza, self).get_ingredients()

这个例子中,你构建的每个pizza都通过继承BasePizza的方式,你不得不覆盖get_ingredients方法,但是能够使用默认机制通过super()来获取ingredient列表。

感谢原文作者:

刘志军

今天电话面试被问到Python中多线程和多进程之间的关系和区别,说来丢人距上次使用Python时间久远,有些概念已经模糊了,检索查阅资料之后在此记录一下.

1 线程和进程的基本概念

现在的 PC 都是多核的,使用多线程能充分利用 CPU 来提供程序的执行效率。

1.1 线程

线程是一个基本的 CPU 执行单元。它必须依托于进程存活。一个线程是一个execution context(执行上下文),即一个 CPU 执行时所需要的一串指令。

1.2 进程

进程是指一个程序在给定数据集合上的一次执行过程,是系统进行资源分配和运行调用的独立单位。可以简单地理解为操作系统中正在执行的程序。也就说,每个应用程序都有一个自己的进程。

每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。

1.3 两者的区别

  • 线程必须在某个进程中执行。
  • 一个进程可包含多个线程,其中有且只有一个主线程。
  • 多线程共享同个地址空间、打开的文件以及其他资源。
  • 多进程共享物理内存、磁盘、打印机以及其他资源。

1.4 线程的类型

线程的因作用可以划分为不同的类型。大致可分为:

  • 主线程
  • 子线程
  • 守护线程(后台线程)
  • 前台线程

2 Python 多线程

2.1 GIL

其他语言,CPU 是多核时是支持多个线程同时执行。但在 Python 中,无论是单核还是多核,同时只能由一个线程在执行。其根源是 GIL 的存在。

GIL 的全称是 Global Interpreter Lock(全局解释器锁),来源是 Python 设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是“通行证”,并且在一个 Python 进程中,GIL 只有一个。拿不到通行证的线程,就不允许进入 CPU 执行。

而目前 Python 的解释器有多种,例如:

  • CPython:CPython 是用C语言实现的 Python 解释器。 作为官方实现,它是最广泛使用的 Python 解释器。
  • PyPy:PyPy 是用RPython实现的解释器。RPython 是 Python 的子集, 具有静态类型。这个解释器的特点是即时编译,支持多重后端(C, CLI, JVM)。PyPy 旨在提高性能,同时保持最大兼容性(参考 CPython 的实现)。
  • Jython:Jython 是一个将 Python 代码编译成 Java 字节码的实现,运行在JVM (Java Virtual Machine) 上。另外,它可以像是用 Python 模块一样,导入 并使用任何Java类。
  • IronPython:IronPython 是一个针对 .NET 框架的 Python 实现。它 可以用 Python 和 .NET framewor k的库,也能将 Python 代码暴露给 .NET 框架中的其他语言。

GIL 只在 CPython 中才有,而在 PyPy 和 Jython 中是没有 GIL 的。

每次释放 GIL锁,线程进行锁竞争、切换线程,会消耗资源。这就导致打印线程执行时长,会发现耗时更长的原因。

并且由于 GIL 锁存在,Python 里一个进程永远只能同时执行一个线程(拿到 GIL 的线程才能执行),这就是为什么在多核CPU上,Python 的多线程效率并不高的根本原因。

2.2 创建多线程

Python提供两个模块进行多线程的操作,分别是threadthreading
前者是比较低级的模块,用于更底层的操作,一般应用级别的开发不常用。

  • 方法1:直接使用threading.Thread()
1
2
3
4
5
6
7
8
9
10
11
import threading

# 这个函数名可随便定义
def run(n):
print("current task:", n)

if __name__ == "__main__":
t1 = threading.Thread(target=run, args=("thread 1",))
t2 = threading.Thread(target=run, args=("thread 2",))
t1.start()
t2.start()
  • 方法2:继承threading.Thread来自定义线程类,重写run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading

class MyThread(threading.Thread):
def __init__(self, n):
super(MyThread, self).__init__() # 重构run函数必须要写
self.n = n

def run(self):
print("current task:", n)

if __name__ == "__main__":
t1 = MyThread("thread 1")
t2 = MyThread("thread 2")

t1.start()
t2.start()

2.3 线程合并

Join函数执行顺序是逐个执行每个线程,执行完毕后继续往下执行。主线程结束后,子线程还在运行,join函数使得主线程等到子线程结束时才退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import threading

def count(n):
while n > 0:
n -= 1

if __name__ == "__main__":
t1 = threading.Thread(target=count, args=("100000",))
t2 = threading.Thread(target=count, args=("100000",))
t1.start()
t2.start()
# 将 t1 和 t2 加入到主线程中
t1.join()
t2.join()

2.4 线程同步与互斥锁

线程之间数据共享的。当多个线程对某一个共享数据进行操作时,就需要考虑到线程安全问题。threading模块中定义了Lock 类,提供了互斥锁的功能来保证多线程情况下数据的正确性。

用法的基本步骤:

1
2
3
4
5
6
#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([timeout])
#释放
mutex.release()

其中,锁定方法acquire可以有一个超时时间的可选参数timeout。如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。

具体用法见示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading
import time

num = 0
mutex = threading.Lock()

class MyThread(threading.Thread):
def run(self):
global num
time.sleep(1)

if mutex.acquire(1):
num = num + 1
msg = self.name + ': num value is ' + str(num)
print(msg)
mutex.release()

if __name__ == '__main__':
for i in range(5):
t = MyThread()
t.start()

2.5 可重入锁(递归锁)

为了满足在同一线程中多次请求同一资源的需求,Python 提供了可重入锁(RLock)。
RLock内部维护着一个Lock和一个counter变量,counter 记录了 acquire 的次数,从而使得资源可以被多次 require。直到一个线程所有的 acquire 都被 release,其他的线程才能获得资源。

具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
#创建 RLock
mutex = threading.RLock()

class MyThread(threading.Thread):
def run(self):
if mutex.acquire(1):
print("thread " + self.name + " get mutex")
time.sleep(1)
mutex.acquire()
mutex.release()
mutex.release()

2.6 守护线程

如果希望主线程执行完毕之后,不管子线程是否执行完毕都随着主线程一起结束。我们可以使用setDaemon(bool)函数,它跟join函数是相反的。它的作用是设置子线程是否随主线程一起结束,必须在start() 之前调用,默认为False

2.7 定时器

如果需要规定函数在多少秒后执行某个操作,需要用到Timer类。具体用法如下:

1
2
3
4
5
6
7
8
from threading import Timer

def show():
print("Pyhton")

# 指定一秒钟之后执行 show 函数
t = Timer(1, hello)
t.start()

3 Python 多进程

3.1 创建多进程

Python 要进行多进程操作,需要用到muiltprocessing库,其中的Process类跟threading模块的Thread类很相似。所以直接看代码熟悉多进程。

  • 方法1:直接使用Process, 代码如下:
1
2
3
4
5
6
7
8
9
from multiprocessing import Process  

def show(name):
print("Process name is " + name)

if __name__ == "__main__":
proc = Process(target=show, args=('subprocess',))
proc.start()
proc.join()
  • 方法2:继承Process来自定义进程类,重写run方法, 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from multiprocessing import Process
import time

class MyProcess(Process):
def __init__(self, name):
super(MyProcess, self).__init__()
self.name = name

def run(self):
print('process name :' + str(self.name))
time.sleep(1)

if __name__ == '__main__':
for i in range(3):
p = MyProcess(i)
p.start()
for i in range(3):
p.join()

3.2 多进程通信

进程之间不共享数据的。如果进程之间需要进行通信,则要用到Queue模块或者Pipi模块来实现。

  • Queue

Queue 是多进程安全的队列,可以实现多进程之间的数据传递。它主要有两个函数,putget

put() 用以插入数据到队列中,put 还有两个可选参数:blocked 和 timeout。如果 blocked 为 True(默认值),并且 timeout 为正值,该方法会阻塞 timeout 指定的时间,直到该队列有剩余的空间。如果超时,会抛出 Queue.Full 异常。如果 blocked 为 False,但该 Queue 已满,会立即抛出 Queue.Full 异常。

get()可以从队列读取并且删除一个元素。同样,get 有两个可选参数:blocked 和 timeout。如果 blocked 为 True(默认值),并且 timeout 为正值,那么在等待时间内没有取到任何元素,会抛出 Queue.Empty 异常。如果blocked 为 False,有两种情况存在,如果 Queue 有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出 Queue.Empty 异常。

具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
from multiprocessing import Process, Queue

def put(queue):
queue.put('Queue 用法')

if __name__ == '__main__':
queue = Queue()
pro = Process(target=put, args=(queue,))
pro.start()
print(queue.get())
pro.join()
  • Pipe

Pipe的本质是进程之间的用管道数据传递,而不是数据共享,这和socket有点像。pipe() 返回两个连接对象分别表示管道的两端,每端都有send() 和recv()函数。

如果两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据。

具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
from multiprocessing import Process, Pipe

def show(conn):
conn.send('Pipe 用法')
conn.close()

if __name__ == '__main__':
parent_conn, child_conn = Pipe()
pro = Process(target=show, args=(child_conn,))
pro.start()
print(parent_conn.recv())
pro.join()

3.3 进程池

创建多个进程,我们不用傻傻地一个个去创建。我们可以使用Pool模块来搞定。

Pool 常用的方法如下:

方法 含义
apply() 同步执行(串行)
apply_async() 异步执行(并行)
terminate() 立刻关闭进程池
join() 主进程等待所有子进程执行完毕。必须在close或terminate()之后使用
close() 等待所有进程结束后,才关闭进程池

具体用法见示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
from multiprocessing import Pool
def show(num):
print('num : ' + str(num))

if __name__=="__main__":
pool = Pool(processes = 3)
for i in xrange(6):
# 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
pool.apply_async(show, args=(i, ))
print('====== apply_async ======')
pool.close()
#调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
pool.join()

4 选择多线程还是多进程?

在这个问题上,首先要看下你的程序是属于哪种类型的。一般分为两种 CPU 密集型 和 I/O 密集型。

  • CPU 密集型:程序比较偏重于计算,需要经常使用 CPU 来运算。例如科学计算的程序,机器学习的程序等。
  • I/O 密集型:顾名思义就是程序需要频繁进行输入输出操作。爬虫程序就是典型的 I/O 密集型程序。

如果程序是属于 CPU 密集型,建议使用多进程。而多线程就更适合应用于 I/O 密集型程序。

感谢原文作者:

猴哥Yuri

进程间通信也是Linux中一个相对基础的内容,网上检索学习回顾一番之后将其记录下来加深记忆,共同学习.


进程间通信概述

进程通信的目的

数据传输

一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间

共享数据

多个进程想要操作共享数据,一个进程对共享数据

通知事件

一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程).

资源共享

多个进程之间共享同样的资源.为了作到这一点,需要内核提供锁和同步机制.

进程控制

有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变.

Linux 进程间通信(IPC)的发展

linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的.而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同.

前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;

后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制.

Linux则把两者继承了下来

早期UNIX进程间通信

基于System V进程间通信

基于Socket进程间通信

POSIX进程间通信.

UNIX进程间通信方式包括:管道、FIFO、信号.

System V进程间通信方式包括:System V消息队列、System V信号灯、System V共享内存

POSIX进程间通信包括:posix消息队列、posix信号灯、posix共享内存.

由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(PSOIX).现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循POSIX标准;

BSD并不是没有涉足单机内的进程间通信(socket本身就可以用于单机内的进程间通信).事实上,很多Unix版本的单机IPC留有BSD的痕迹,如4.4BSD支持的匿名内存映射、4.3+BSD对可靠信号语义的实现等等.

linux使用的进程间通信方式

1.管道(pipe),流管道(s_pipe)和有名管道(FIFO)

2.信号(signal)

3.消息队列

4.共享内存

5.信号量

6.套接字(socket)

管道( pipe )

管道这种通讯方式有两种限制,一是半双工的通信,数据只能单向流动,二是只能在具有亲缘关系的进程间使用.进程的亲缘关系通常是指父子进程关系.

流管道(s_pipe)

去除了第一种限制,可以双向传输.管道可用于具有亲缘关系进程间的通信,命名管道:name_pipe克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

信号量( semophore )

信号量是一个计数器,可以用来控制多个进程对共享资源的访问.它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源.因此,主要作为进程间以及同一进程内不同线程之间的同步手段.

信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);

消息队列( message queue )

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识.消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点.

消息队列是消息的链接表,包括Posix消息队列system V消息队列.有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息.消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点.

信号 ( singal )

信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生.

主要作为进程间以及同一进程不同线程之间的同步手段.

共享内存( shared memory )

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信.

使得多个进程可以访问同一块内存空间,是最快的可用IPC形式.是针对其他通信机制运行效率较低而设计的.往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥.

套接字( socket )

套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信

更为一般的进程间通信机制,可用于不同机器之间的进程间通信.起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字.

进程间通信各种方式效率比较

类型 无连接 可靠 流控制 优先级
普通PIPE N Y Y N
流PIPE N Y Y N
命名PIPE(FIFO) N Y Y N
消息队列 N Y Y Y
信号量 N Y Y Y
共享存储 N Y Y Y
UNIX流SOCKET N Y Y N
UNIX数据包SOCKET N Y N N

注:无连接: 指无需调用某种形式的OPEN,就有发送消息的能力流控制;如果系统资源短缺或者不能接收更多消息,则发送进程能进行流量控制

各种通信方式的比较和优缺点

管道:速度慢,容量有限,只有父子进程能通讯

FIFO:任何进程间都能通讯,但速度慢

消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

信号量:不能传递复杂消息,只能用来同步

共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

如果用户传递的信息较少或是需要通过信号来触发某些行为.前文提到的软中断信号机制不失为一种简捷有效的进程间通信方式.

但若是进程间要求传递的信息量比较大或者进程间存在交换数据的要求,那就需要考虑别的通信方式了.

无名管道简单方便.但局限于单向通信的工作方式.并且只能在创建它的进程及其子孙进程之间实现管道的共享:

有名管道虽然可以提供给任意关系的进程使用.但是由于其长期存在于系统之中,使用不当容易出错.所以普通用户一般不建议使用.

消息缓冲可以不再局限于父子进程,而允许任意进程通过共享消息队列来实现进程间通信,并由系统调用函数来实现消息发送和接收之间的同步,从而使得用户在使用消息缓冲进行通信时不再需要考虑同步问题,使用方便,但是信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合.

共享内存针对消息缓冲的缺点改而利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点.

但是共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,因此,这些进程之间的读写操作的同步问题操作系统无法实现.必须由各进程利用其他同步工具解决.另外,由于内存实体存在于计算机系统中,所以只能由处于同一个计算机系统中的诸进程共享.不方便网络通信.

共享内存块提供了在任意数量的进程之间进行高效双向通信的机制.每个使用者都可以读取写入数据,但是所有程序之间必须达成并遵守一定的协议,以防止诸如在读取信息之前覆写内存空间等竞争状态的出现.

不幸的是,Linux无法严格保证提供对共享内存块的独占访问,甚至是在您通过使用IPC_PRIVATE创建新的共享内存块的时候也不能保证访问的独占性. 同时,多个使用共享内存块的进程之间必须协调使用同一个键值.

感谢原文作者:

JeanCheng

在虚拟机中搭建KVM实验环境并实现KVM虚拟机之间的网络通信及分布式网站架构.实验中直接使用命令行界面实现虚拟机中系统的安装和配置,免去了图形化界面操作的步骤.系统安装完成之后配置虚拟网卡,配置虚拟网段及实现远程连接,分布式网站中跳板机的设置及ip过滤,模拟了一个小型网站的架构后期可以使用docker来替代KVM以实现更好的实验效果.

整个实验从配置环境到安装虚拟机,配置软件环境,配置网络通信等方面实现了KVM虚拟机环境配置的需求.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
1.检测是否支持KVM
KVM 是基于 x86 虚拟化扩展(Intel VT 或者 AMD-V) 技术的虚拟机软件,所以查看 CPU 是否支持 VT 技术,
就可以判断是否支持KVM。有返回结果,如果结果中有vmx(Intel)或svm(AMD)字样,就说明CPU的支持的。切记
在VMware CPU选项下点击CPU虚拟化
# cat /proc/cpuinfo | egrep 'vmx|svm'
关闭 Selinux 和 firewalld
# vi /etc/sysconfig/selinux
selinux=disabled

# systemctl stop firewalld
# systemctl disabled firewalld
# iptables -F
# iptables -vnL

2.安装KVM环境
yum -y install qemu-kvm python-virtinst libvirt libvirt-python virt-manager libguestfs-tools bridge-utils virt-install kvm acpid
装载 kvm 模块
# modprobe kvm
# lsmod | grep kvm
开启KVM服务,并设置开机自动启动
systemctl start acpid.service
systemctl enable acpid.service
systemctl start libvirtd.service
systemctl enable libvirtd.service

3.安装虚拟机
kvm创建虚拟机,特别注意.iso镜像文件一定放到/home 或者根目录重新创建目录,不然会因为权限报
错,无法创建虚拟机。
首先要创建一个虚拟网桥br0,无法创建的话就删除网卡原来的配置文件,然后重新生成(记得备份)
# virsh iface-bridge ens33 br0
然后开始创建虚拟机
创建虚拟机之前最好先把网桥设好并规划好网段,这样下次再创建虚拟机的时候可以直接连接到网桥上.


# virt-install \
--name=centos7C1 \
--vcpus=2 \
--memory=4096 \
--location=/tmp/CentOS-7-x86_64-Minimal-1511.iso \
--disk path=/home/vms/c1/centosC1.qcow2,size=40,format=qcow2 \
--network bridge=br0 \
--graphics none \
--extra-args='console=ttyS0' \
--force

选项参考:
name: 虚拟机名
vcpus: 虚拟cpu数量
memory: 虚拟内存大小
location: ios文件路径
没有的话可以手动在 ftp 服务器上下载:
# wget ftp://172.20.0.1/pub/ISOs/CentOS/CentOS-7-x86_64-Minimal-1511.iso
disk path: 虚拟机安装位置
network bridge: 网桥名称
graphics: 图形界面

4.命令行配置系统:
上面创建虚拟机命令最终需要你配置系统基础设置,带 [!] 基本都是要配置的,按照顺序往下配置,
按对用的数字以此进行设置。
Installation

1) [x] Language settings 2) [!] Timezone settings
(English (United States)) (Timezone is not set.)
3) [!] Installation source 4) [!] Software selection
(Processing...) (Processing...)
5) [!] Installation Destination 6) [x] Kdump
(No disks selected) (Kdump is enabled)
7) [ ] Network configuration 8) [!] Root password
(Not connected) (Password is not set.)
9) [!] User creation
(No user will be created)
Please make your choice from above ['q' to quit | 'b' to begin installation |
'r' to refresh]:

>2 Timezone settings 时区设置选择 5) Asia亚洲,再选择城市 62) Shanghai上海

Available regions
1) Africa 6) Atlantic 10) Pacific
2) America 7) Australia 11) US
3) Antarctica 8) Europe 12) Etc
4) Arctic 9) Indian
5) Asia
Please select the timezone.
Use numbers or type names directly [b to region list, q to quit]: 5
--------------------

8) Baghdad 35) Kathmandu 61) Seoul
9) Bahrain 36) Khandyga 62) Shanghai
10) Baku 37) Kolkata 63) Singapore
26) Hong_Kong 53) Pontianak
27) Hovd
Please select the timezone.
Use numbers or type names directly [b to region list, q to quit]: 62

Installation source 安装源输入数字2
Choose an installation source type.
1) CD/DVD
2) local ISO file
3) Network
Please make your choice from above ['q' to quit | 'c' to continue |
'r' to refresh]: 2

Software selection 软件选择:
Base environment
Software selection

Base environment

1) [x] Minimal Install
Please make your choice from above ['q' to quit | 'c' to continue |
'r' to refresh]:

Installation Destination 安装目的地
Installation Destination

[x] 1) : 40 GiB (vda)

1 disk selected; 40 GiB capacity; 40 GiB free ...

Please make your choice from above ['q' to quit | 'c' to continue |
'r' to refresh]: c


Autopartitioning Options 自动分区选项

[ ] 1) Replace Existing Linux system(s) 替换现有的Linux系统

[x] 2) Use All Space 使用所有空间

[ ] 3) Use Free Space 使用可用空间

================================================================================
Partition Scheme Options 分区方案选项

[ ] 1) Standard Partition 标准分区

[ ] 2) Btrfs Btrfs

[x] 3) LVM LVM(逻辑卷管理)

[ ] 4) LVM Thin Provisioning 精简配置

Select a partition scheme configuration.

Please make your choice from above ['q' to quit | 'c' to continue |
'r' to refresh]: c

然后输入8录入root账户的密码

此处也可以只设置 Root 密码和Installation Destination 安装目的地其它进入系统设置比如时区设置如下:
echo "TZ='Asia/Shanghai'; export TZ" >> /etc/profile

选择完成后输入 b 就可以开始安装系统了.
安装完成之后默认是在桌面环境的.直接登录并操作即可.

安装完成之后第一次启动网卡eth0是获取不到ip地址的,所以需要手动启动
# vi /etc/sysconfig/network-scripts/ifcfg-eth0
ONBOOT=yes

# systemctl restart network
# ip a
查看到ip地址之后最好将ip地址固定下来,方便以后使用
# vi /etc/sysconfig/network-scripts/ifcfg-eth0
BOOTPROTO=static
IPADDR=172.20.124.138


5.添加第一个网桥之后还需要对网桥进行控制划分其他的网段
# brctl show
# cd /etc/libvirt/qemu/networks
# cp default.xml mynet1.xml

拷贝完成之后需要对配置文件进行修改如修改网桥名称,划分子网范围
# vi mynet1.xml
<network>
<name>mynet1</name>
<bridge name='mybr1' stp='on' delay='0'/>
<ip address='12.20.20.40' netmask='255.255.255.0'>
<dhcp>
<range start='12.20.20.12' end='12.20.20.100'/>
</dhcp>
</ip>
</network>

配置文件修改完成之后即可创建网桥
# virsh net-create mynet1.xml
查看虚拟网桥信息
# virsh net-list

6.VM虚拟机添加网卡并配置网络
1>手动添加网桥并插入虚拟机
# virsh domiflist centos7C1
Interface Type Source Model MAC
-------------------------------------------------------
vnet0 bridge br0 virtio 52:54:00:8c:b8:60

2>成功附加接口
# virsh attach-interface centos7C1 --type bridge --source br0

永久添加网卡命令
# virsh attach-interface centos7C1 --type bridge --source br0 --config

3>查看添加网卡的信息
# virsh domiflist centos7C1
Interface Type Source Model MAC
-------------------------------------------------------
vnet0 bridge br0 virtio 52:54:00:8c:b8:60
vnet1 bridge br0 - 52:54:00:14:86:cf
在KVM中查看添加的信息:
ip a

4>命令行增加的网卡只保存在内存中,重启就失效,所以需要保存到配置文件中
# virsh dumpxml centos7C1 >/etc/libvirt/qemu/centos7C1.xml
# virsh define /etc/libvirt/qemu/centos7C1.xml

删除网卡命令

# virsh detach-interface centos7C1 --type bridge --mac 52:54:00:14:86:cf
成功分离接口

7.克隆KVM虚拟机,创建虚拟机镜像.
暂停原始虚拟机
# virsh shutdown centos72
# virt-clone -o centos72 -n centos.112 -f /home/vms/centos.112.qcow2 -m 00:00:00:00:00:01

复制第一次安装的干净系统镜像,作为基础镜像文件,
后面创建虚拟机使用这个基础镜像
cp /home/vms/centos.88.qcow2 /home/vms/centos7.base.qcow2

# 使用基础镜像文件,创建新的虚拟机镜像
cp /home/vms/centos7.base.qcow2 /home/vms/centos7.113.qcow2

8.热插拔网卡实现切换网桥
查看当前宿主机网卡的状态
# brctl show
查看KVM虚拟机的运行状态
# virsh list
查看KVM虚拟机网卡列表
# virsh domiflist centos7C1

添加一个网卡到物理桥myBr1上
# virsh attach-interface centos7C1 bridge myBr1
撤销网卡,撤销网卡前先关闭网卡
# ip link set dev ens10 down
注意:撤销某一块网卡要指定该网卡的MAC,要不会撤销该网卡所在网桥上所有的网卡
# virsh detach-interface centos7C1 bridge --mac 52:54:00:f1:22:5b

9.分布式部署LAMP
9.1 HTTPD的实现
# yum -y install httpd
修改主配置文件,把php的首页加上
# vi httpd.conf
<IfModule dir_module>
DirectoryIndex index.php index.html
</IfModule>

新建虚拟主机文件,对php文件做解析的服务器的地址可以暂时不写.等软件部署成功之后再补上.
[root@web-server conf.d]# vi www.conf
<VirtualHost *:80>
ServerName www.dklwj.com
DocumentRoot "/vhosts/www/htdocs"
<Directory "/vhosts/www/htdocs">
Options FollowSymLinks
AllowOverride None
Require all granted
</directory>
ProxyRequests Off
ProxyPassMatch ^/(.*\.php)$ fcgi://192.168.137.147:9000/vhosts/www/htdocs/$1
</VirtualHost>

创建虚拟主机的目录
# mkdir /vhosts/www/htdocs -p
修改目录的所属组的权限为apache
# chown -R apache.apache /vhosts/
# cd /vhosts/www/htdocs
# echo "<h1>It works!!</h1>" > ./index.html

启动http服务,查看80端口有没有被监听.
# systemctl start httpd
# ss -tnl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::80 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*

服务启动之后可以在本机上查看访问html网页的效果,这里就不修改 hosts 文件来实现域名的跳转了
# curl 192.168.100.233/index.html

9.2 php-fpm的实现
安装php-fpm 和php-mysql模块
# yum -y install php-fpm php-mysql

修改配置文件
# vim /etc/php-fpm.d/www.conf
#作为单独服务运行需要把监听端口改成所有接口,http后续会使用反代
listen = 0.0.0.0:9000
#只允许192.168.137.144这台服务器也就是http服务器
listen.allowed_clients = 192.168.100.233

创建php程序存放路径
# mkdir /vhosts/www/htdocs -p

进入目录然后创建一个php测试页面
# cd /vhosts/www/htdocs/
# vim info.php
<?php
phpinfo();
?>

启动PHP服务
# systemctl start php-fpm

测试对php网页的反向代理是否可以实现
# curl 192.168.100.233/info.php

9.3 MariaDB的实现
# yum -y install mariadb-server

#配置mariadb禁止解析反向IP地址
# vi /etc/my.cnf
[mysqld]
skip_name_resolve=on

修改mysql的root密码然后删除匿名用户
[root@mysql-server ~]# mysql
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 5.5.56-MariaDB MariaDB Server
Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> use mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [mysql]> update user set password=password('123456') where user='root';
Query OK, 4 rows affected (0.00 sec)
Rows matched: 4 Changed: 4 Warnings: 0
MariaDB [mysql]> flush privileges;
Query OK, 0 rows affected (0.00 sec)
MariaDB [mysql]> drop user ''@'localhost';
Query OK, 0 rows affected (0.00 sec)
MariaDB [mysql]> drop user ''@'mysql-server';
Query OK, 0 rows affected (0.00 sec)
MariaDB [mysql]> select user,host,password from user;
+------+--------------+-------------------------------------------+
| user | host | password |
+------+--------------+-------------------------------------------+
| root | localhost | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| root | mysql-server | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| root | 127.0.0.1 | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| root | ::1 | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+------+--------------+-------------------------------------------+
4 rows in set (0.00 sec)

在php服务器上创一个测试一个连接数据库的页面
[root@php-server /vhosts/www/htdocs]#vim mysql.php
<?php
$link=mysql_connect("192.168.137.148","root","123456");
if(!$link) echo "FAILD,Error!";
else echo "OK.......!";

测试对php访问数据库模块是否可以正常工作
# curl 192.168.100.233/mysql.php

9.4部署WordPress软件

在php服务器上下载WordPress包,这里需要强调一下,目录需要添加写权限要不然回出现文件无法写入的情况.
# cd /vhosts/www/
wordpress文件自行在官网下载
# tar xf wordpress-4.9.4-zh_CN.tar.gz
删除原有存放网页的 htdoc 文件夹并将其以软连接形式呈现
# rm -rf htdocs/
# ln -sv wordpress htdocs

在把软件包复制一份到http上去里面静态资源需要由HTTP来处理所以两台服务器得各放一份
# scp -r wordpress root@192.168.137.144:/vhosts/www/

在数据库服务器上创建所需要的库和帐号
MariaDB [(none)]> create database blog;
Query OK, 1 row affected (0.01 sec)
MariaDB [(none)]> grant all on blog.* to 'wpuser'@'%' identified by 'wppass';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.00 sec)

在HTTP服务器上配置
# cd /vhosts/www
# ls
htdocs index.html wordpress
# rm -rf htdocs/
# ln -s wordpress/ htdocs
# ll
lrwxrwxrwx. 1 root root 10 Oct 29 21:51 htdocs -> wordpress/
drwxr-xr-x. 5 root root 4096 Oct 29 21:50 wordpress
# cd ..
# chown -R apache.apache www/
# ll
lrwxrwxrwx. 1 apache apache 10 Oct 29 21:51 htdocs -> wordpress/
drwxr-xr-x. 5 apache apache 4096 Oct 29 21:50 wordpress

部署完成之后即可在浏览器上访问然后配置wordpress了,配置步骤不明白的可自行google,注意在php服务器上添加
/vhosts/www/wordpress/wp-config.php 这个文件,这是wordpress的配置文件.

10.隐藏真实主机网络访问控制
现在我们一共有4台KVM虚拟机,DB服务器和php服务器不能暴露在外网环境中而实现操控和访问的httpd服务器和跳板
机反而要对外实现通信,因此需要对网络进行设置
10.1 网桥的准备工作
之前已经设置好了可以访问外网桥街的br0,接下需要新建两个内网通信的网桥
# cd /etc/libvirt/qemu/networks/
# brctl show
# cp default.xml mynet1.xml
# vi mynet1.xml
<network>
<name>mynet1</name>
<bridge name='mybr1' stp='on' delay='0'/>
<ip address='10.10.10.1' netmask='255.255.255.0'>
<dhcp>
<range start='10.10.10.10' end='10.10.10.100'/>
</dhcp>
</ip>
</network>

# virsh net-create mynet1.xml
# cp mynet1.xml mynet2.xml
# vi mynet2.xml

<network>
<name>mynet2</name>
<bridge name='mybr2' stp='on' delay='0'/>
<ip address='20.20.20.1' netmask='255.255.255.0'>
<dhcp>
<range start='20.20.20.20' end='20.20.20.100'/>
</dhcp>
</ip>
</network>

# virsh net-create mynet2.xml
10.2为虚拟机配置网桥
主要是对网卡的插拔操作,上文中有提到如何热插拔网卡实现切换网桥
# virsh attach-interface centos7C1 --type bridge --source mybr1
# virsh attach-interface centos7C2 --type bridge --source mybr2
# virsh detach-interface centos7C2 --type bridge --mac 52:54:00:9c:0f:b4
# virsh attach-interface centos7C3 --type bridge --source mybr2
# virsh detach-interface centos7C3 --type bridge --mac 52:54:00:16:a8:a6
# virsh attach-interface centos7C4 --type bridge --source mybr2
# virsh attach-interface centos7C4 --type bridge --source mybr1

网桥插拔完成之后对虚拟机的网络接入情况进行查看:
[root@localhost vms]# virsh domiflist centos7C1
Interface Type Source Model MAC
-------------------------------------------------------
vnet0 bridge br0 virtio 52:54:00:13:29:30
vnet5 bridge mybr1 rtl8139 52:54:00:a9:b8:6c

[root@localhost vms]# virsh domiflist centos7C2
Interface Type Source Model MAC
-------------------------------------------------------
vnet1 bridge mybr1 virtio 52:54:00:f6:c9:d0
vnet6 bridge mybr2 rtl8139 52:54:00:87:f1:0d

[root@localhost vms]# virsh domiflist centos7C3
Interface Type Source Model MAC
-------------------------------------------------------
vnet2 bridge mybr2 rtl8139 52:54:00:1e:ab:d6

[root@localhost vms]# virsh domiflist centos7C4
Interface Type Source Model MAC
-------------------------------------------------------
vnet4 bridge br0 virtio 52:54:00:6f:fa:c7
vnet3 bridge mybr2 rtl8139 52:54:00:78:ea:44
vnet7 bridge mybr1 rtl8139 52:54:00:c4:61:9b

查看网桥及网卡的连接情况:
# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.000c29120c94 yes ens33
vnet0
vnet4
mybr1 8000.525400c30ff7 yes mybr1-nic
vnet1
vnet5
vnet7
mybr2 8000.525400dc598f yes mybr2-nic
vnet2
vnet3
vnet6
virbr0 8000.525400a975d4 yes virbr0-nic

然后按照下面定义的网络地址来配置虚拟机的网络,网卡名有改变,但是不影响操作.
Mysql(C3)
ens8:20.20.20.35
FPM(C2)
ens9:20.20.20.63
eth0:10.10.10.12
HTTPD(C1)
eth8:10.10.10.95
eth1:172.20.124.138
JUMPS(C4):16.40.40.80
ens9:10.10.10.73 e78fde6e-933c-4ee4-851b-4b4a5fe7e744
ens8:20.20.20.87 8bad1d1e-2247-4c54-a4e0-60094beddcdd
eth0:172.20.124.151
网桥设置:
mybr1(host only):
ip:10.10.10.1/24
dhcp range:10.10.10.10 10.10.10.100
mybr2(host only):
ip:20.20.20.1/24
dhcp range:20.20.20.20 20.20.20.100
br0(nat):
ip:192.168.122.1/24
dhcp range: 192.168.122.2 192.168.122.254
这样设置的话不同的主机就存在与不同的网域中,外界就没法直接访问敏感服务器咯.

跳板机的网络设置

mysql iptables rule
允许跳板机的ssh
iptables -A INPUT -d 10.10.20.20 -s 10.10.20.100 -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -s 10.10.20.20 -d 10.10.20.100 -p tcp --sport 22 -j ACCEPT
拒绝所有连接
iptables -A INPUT -j DROP
iptables -A OUTPUT -j DROP
允许php的IP连接数据库
iptables -I INPUT -d 10.10.20.20 -s 10.10.20.30 -p tcp --dport 3306 -j ACCEPT
iptables -I OUTPUT -s 10.10.20.20 -d 10.10.20.30 -p tcp --sport 3306 -j ACCEPT

PHP iptables rule
允许跳板机的ssh
iptables -A INPUT -d 172.30.20.30 -s 172.30.20.100 -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -s 172.30.20.30 -d 172.30.20.100 -p tcp --sport 22 -j ACCEPT

拒绝所有连接
iptables -A INPUT -j ACCEPT
iptables -A OUTPUT -j ACCEPT

允许httpd的反代端口9000通过
# iptables -I INPUT -d 172.30.20.30 -s 172.30.20.152 -p tcp --dport 9000 -j ACCEPT
# iptables -I OUTPUT -s 172.30.20.30 -d 172.30.20.152 -p tcp --sport 9000 -j ACCEPT

# iptables -I OUTPUT -d 10.10.20.20 -s 10.10.20.30 -p tcp --dport 3306 -j ACCEPT
# iptables -I INPUT -d 10.10.20.30 -s 10.10.20.20 -p tcp --sport 3306 -j ACCEPT


HTTPD iptables rule
允许跳板机的ssh
# iptables -A INPUT -d 172.30.20.152 -s 172.30.20.100 -p tcp --dport 22 -j ACCEPT
#iptables -A OUTPUT -s 172.30.20.152 -d 172.30.20.100 -p tcp --sport 22 -j ACCEPT
拒绝所有连接
# iptables -A INPUT -j DROP
# iptables -A OUTPUT -j DROP
允许客户端通过外网接口访问80和443接口
进口配置
# iptables -I INPUT -d 192.168.137.152 -i eth0 -p tcp -m multiport --dports 80,443 -j ACCEPT
出口设置
# iptables -I OUTPUT -s 192.168.137.152 -o eth0 -p tcp -m multiport --sports 80,443 -j ACCEPT

允许http能连接到后端的PHP服务器的9000端口
# iptables -I OUTPUT -d 172.30.20.30 -s 172.30.20.152 -p tcp --dport 9000 -j ACCEPT
# iptables -I INPUT -d 172.30.20.152 -s 172.30.20.30 -p tcp --sport 9000 -j ACCEPT


补充:
使用dhcp分配网络地址之后没有相应的网络配置文件
解决方案:
1.使用nmcli con show命令,查看网卡的UUID信息,记下UUID值
# nmcli con show
2.使用ip addr命令查看网卡信息,记下ens37网卡的MAC地址
3.将 /etc/sysconfig/network-scripts/目录中ifcfg-ens33文件复制一份,并命名为 ifcfg-ens37,
重新修改配置文件,注意修改必要的硬件信息
4.最后重启网络即可
# systemctl restart network