跳转至

2017

一种应用于云平台负载的PID非线性控制系统设计

本文的实现效率尚有待考证,极有可能沦为扯淡文,但如果在网络资源部分可以快速应用测试。

众所周知,很多计算机系统里的设计都可以描述为线性模型,但正如金融系统的发展,计算机系统直接面向大众以后,也会呈现出非线性的特征,比如典型的DDoS即是在系统设计之外。接下来,笔者将使用自控知识来设计一种自适应负载的云计算控制器,不仅适用于计算、也会适用于网络、存储等服务资源。

以OpenStack平台的计算(虚拟机)为例,当用户的计算需求被量化后,那么我们就能根据其需求直接给出相应数量的计算节点。假如用户的计算需求是变化的,其值为R,且我们的程序员也是个直肠子,给出相应的计算能力为C,那么他设计的程序很有可能就是这个公式:

N=10, R=nN, C=(n+2)N

每台虚拟机的计算能力N为10,虚拟机数量为n。

可以看出他给了两台的冗余量,啊哈,还不错。所以他期望的场景应该是这样的。

其中黄色为实际需求,蓝色为平台提供,绿色为虚拟机数量。

但是,假如需求呈现出短时间大量波动的话,比如下图。

这个时候事情就不是那么美妙了,平台在即时响应的同时,伴随着大量虚拟机的上线/下线,从而造成一定的资源请求拥堵,降低控制性能。

接下来,我们尝试引入PID回馈控制器,就是这个样子的。

PID的Python代码实现如下:

!/usr/bin/python

import time

class PID: def init(self, P=0.2, I=0.0, D=0.0):

 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
    self.Kp = P
    self.Ki = I
    self.Kd = D

    self.sample_time = 0.00
    self.current_time = time.time()
    self.last_time = self.current_time

    self.clear()

def clear(self):
    #Clears PID computations and coefficients
    self.SetPoint = 0.0

    self.PTerm = 0.0
    self.ITerm = 0.0
    self.DTerm = 0.0
    self.last_error = 0.0

    # Windup Guard
    self.int_error = 0.0
    self.windup_guard = 20.0

    self.output = 0.0

def update(self, feedback_value):
    # Calculates PID value for given reference feedback

    error = self.SetPoint - feedback_value

    self.current_time = time.time()
    delta_time = self.current_time - self.last_time
    delta_error = error - self.last_error

    if (delta_time >= self.sample_time):
        self.PTerm = self.Kp * error
        self.ITerm += error * delta_time

        if (self.ITerm < -self.windup_guard):
            self.ITerm = -self.windup_guard
        elif (self.ITerm > self.windup_guard):
            self.ITerm = self.windup_guard

        self.DTerm = 0.0
        if delta_time > 0:
            self.DTerm = delta_error / delta_time

        # Remember last time and last error for next calculation
        self.last_time = self.current_time
        self.last_error = error

        self.output = self.PTerm + (self.Ki * self.ITerm) + (self.Kd * self.DTerm)

def setKp(self, proportional_gain):
    # Determines how aggressively the PID reacts to the current error with setting Proportional Gain
    self.Kp = proportional_gain

def setKi(self, integral_gain):
    # Determines how aggressively the PID reacts to the current error with setting Integral Gain
    self.Ki = integral_gain

def setKd(self, derivative_gain):
    # Determines how aggressively the PID reacts to the current error with setting Derivative Gain
    self.Kd = derivative_gain

def setWindup(self, windup):
    # unwound
    self.windup_guard = windup

def setSampleTime(self, sample_time):
    # PID that should be updated at a regular interval.
    self.sample_time = sample_time

然后在上图条件下进行PID控制,代码如下:

import PID import time import matplotlib.pyplot as plt import numpy as np from scipy.interpolate import spline

def test_pid(P = 0.2, I = 0.0, D= 0.0, L=100): """Self-test PID class

 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
.. note::
    ...
    for i in range(1, END):
        pid.update(feedback)
        output = pid.output
        if pid.SetPoint > 0:
            feedback += (output - (1/i))
        if i>9:
            pid.SetPoint = 1
        time.sleep(0.02)
    ---
"""
pid = PID.PID(P, I, D)
pid.clear()
pid.SetPoint=0.0
pid.setSampleTime(0.01)

END = L
feedback = 0

feedback_list = []
time_list = []
setpoint_list = []

for i in range(1, END):
    pid.update(feedback)
    output = pid.output
    if pid.SetPoint > 0:
        feedback += (output - (1/i))
    if i<10:
        pid.SetPoint = 20
    if i>20:
        pid.SetPoint = 50
    if i>22:
        pid.SetPoint = 100
    if i>24:
        pid.SetPoint = 10
    if i>28:
        pid.SetPoint = 200
    if i>30:
        pid.SetPoint = 200
    if i>70:
        pid.SetPoint = 20

    feedback_list.append(feedback)
    setpoint_list.append(pid.SetPoint)
    time_list.append(i)
    time.sleep(0.02)

time_sm = np.array(time_list)
time_smooth = np.linspace(time_sm.min(), time_sm.max(), 300)
feedback_smooth = spline(time_list, feedback_list, time_smooth)

plt.plot(time_smooth, feedback_smooth)
plt.plot(time_list, setpoint_list)
plt.xlim((0, L))
plt.ylim((min(feedback_list)-0.5, max(feedback_list)+0.5))
plt.xlabel('time (s)')
plt.ylabel('PID C-R')

plt.grid(True)
plt.show()

if name == "main": test_pid(1.01, 1, 0.001, L=100)

然后看看现在是什么样呢?

嗯,没错,多了一些调节量(超调量),且变化较之前平稳了一些。这些调节量是否适用于大批量的云计算环境还有待验证,但是以Web应用来看,这些调节量理应工作。

另外,考虑到虚拟机在创建后某些应用可能短时间内不接受下调,所以我们可以动态地调节C的值,即PID的输出仅用作参考。