浅谈微服务架构

微服务组成部分

  • 服务描述
  • 注册中心
  • 服务框架
  • 服务监控
  • 服务追踪
  • 服务治理

###服务描述
常见的服务描述方式包括RESTful API、XML配置以及IDL文件三种。

  • RESTful API方式通常用于HTTP协议的服务描述
  • XML配置方式多用作RPC协议的服务描述,通过*.xml配置文件来定义接口名、参数以及返回值类型等。
  • IDL文件方式通常用作Thrift和gRPC这类跨语言服务调用框架

注册中心

服务提供者将自己提供的服务以及地址登记到注册中心,服务消费者则从注册中心查询所需要调用的服务的地址,然后发起请求。

工作流程

  1. 服务提供者在启动时,根据服务发布文件中配置的发布信息向注册中心注册自己的服务。
  2. 服务消费者在启动时,根据消费者配置文件中配置的服务信息向注册中心订阅自己所需要的服务。
  3. 注册中心返回服务提供者地址列表给服务消费者。
  4. 当服务提供者发生变化,比如有节点新增或者销毁,注册中心将变更通知给服务消费者。

服务框架

服务消费者从注册中心获取服务提供者的地址,有了地址就可以发起调用。

  • 服务通信采用协议的选择? TCP、UDP or HTTP协议
  • 数据传输采用方式? 同步 or 异步 单连接传输 or 多路复用

什么是单体应用

概念

什么是单体应用

一个归档包包含所有功能的应用程序,比如一些LAMP(Linux+Apache+MySQL+PHP)+和MVC(Spring + iBatis/Hibernate + Tomcat)两大流派。

单体应用架构图

###单体应用出现的问题

  1. 部署效率低下:单体应用代码和依赖的资源越来越多,应用打包和部署测试,耗时久。
  2. 团队协作开发成本高:
  3. 系统高可用差

服务化拆分的两种姿势

  1. 纵向拆分,是从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务。
  2. 横向拆分,是从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务耦合。

服务化拆分的前置条件

服务如何定义?
对于单体应用来说,不同功能模块之前相互交互时,通常是以类库的方式来提供各个模块的功能。对于微服务来说,每个服务都运行在各自的进程之中,应该以何种形式向外界传达自己的信息呢?答案就是接口,无论采用哪种通讯协议,是HTTP还是RPC,服务之间的调用都通过接口描述来约定,约定内容包括接口名、接口参数以及接口返回值

服务如何发布和订阅
单体应用由于部署在同一个WAR包里,接口之间的调用属于进程内的调用。而拆分为微服务独立部署后,服务提供者该如何对外暴露自己的地址,服务调用者该如何查询所需要调用的服务的地址呢?这个时候你就需要一个类似登记处的地方,能够记录每个服务提供者的地址以供服务调用者查询,在微服务架构里,这个地方就是注册中心。

服务如何监控
通常对于一个服务,我们最关心的是QPS(调用量)、AvgTime(平均耗时)以及P999(99.9%的请求性能在多少毫秒以内)这些指标。这时候你就需要一种通用的监控方案,能够覆盖业务埋点、数据收集、数据处理,最后到数据展示的全链路功能。

服务如何治理
可以想象,拆分为微服务架构后,服务的数量变多了,依赖关系也变复杂了。比如一个服务的性能有问题时,依赖的服务都势必会受到影响。可以设定一个调用性能阈值,如果一段时间内一直超过这个值,那么依赖服务的调用可以直接返回,这就是熔断,也是服务治理最常用的手段之一。

故障如何定位
在单体应用拆分为微服务之后,一次用户调用可能依赖多个服务,每个服务又部署在不同的节点上,如果用户调用出现问题,你需要有一种解决方案能够将一次用户请求进行标记,并在多个依赖的服务系统中继续传递,以便串联所有路径,从而进行故障定位。

委托模式

概念

通过分配或委托至其他对象,委托模式能够去除核心对象中的判决和复杂的功能性。

实现场景

添加音乐、并且根据音乐类型获取不同的音乐列表(返回字段形式均不一样)

使用委托模式 VS 基本实现 (UML)


使用基本实现方式调用时,需要if-else的判断,并且音乐类型不断增加,会导致PlayList类无限扩大。但是使用委托模式,在初始化类时,已经声明音乐类型$type,例如M3U,根据类型找到M3UPlaySong类,其余的查询列表均在委托类中进行实现。

代码实现

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
<?php

class Playlist
{
private $_song;

public function __construct()
{
$this->_song = [];
}

public function addSong($location, $title)
{
$song = ['loc'=>$location,'title'=>$title];
$this->_song[] = $song;
}

public function getM3UList() {
$m3u = "#M3U#\n";

foreach ($this->_song as $song) {
$m3u .= "音乐位置".$song['loc'];
$m3u .= "音乐名称".$song['title'];
}

return $m3u;
}

public function getPLSList() {
$m3u = "#PLS#\n";

foreach ($this->_song as $song) {
$m3u .= "music loc".$song['loc'];
$m3u .= "music title".$song['title'];
}

return $m3u;
}
}

$obj = new Playlist();
$obj->addSong('ssss','every');
$obj->addSong('dsdsda','one');

$ext = 'm3u';
if ($ext == 'm3u') {
var_dump($obj->getM3UList());
} else {
var_dump($obj->getPLSList());
}
echo "\n";
class NewPlaySong {
private $_song;
private $_SongType;
public function __construct($type)
{
$this->_song =[];
$song_type = strtoupper($type).'PlaySong';
$this->_SongType = new $song_type ();
}

public function addSong($location, $title)
{
$song = ['loc'=>$location,'title'=>$title];
$this->_song[] = $song;
}

public function getPlayList() {
$play_list = $this->_SongType->getPlayList($this->_song);
return $play_list;
}
}

class M3UPlaySong {
public function getPlayList($song_list) {
$m3u = "#M3U#\n";

foreach ($song_list as $song) {
$m3u .= "音乐位置".$song['loc'];
$m3u .= "音乐名称".$song['title'];
}
return $m3u;
}
}

$obj = new NewPlaySong('m3u');
$obj->addSong('ssss','every');
$obj->addSong('dsdsda','one');

var_dump($obj->getPlayList());

适配器模式(Adapter Design Pattern)

概念

适配器设计模式只是将某个对象的接口适配为另一个对象所期望的接口。

UML图


###代码实现

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
class errorObject
{
private $_error;
public function __construct($error)
{
$this->_error = $error;
}

public function getError() {
return $this->_error;
}
}

class LogToCsvErrorObject extends errorObject{
private $error_num;
private $error_msg;

public function __construct($error)
{
parent::__construct($error);
$error = $this->getError();
$parts = explode(":",$error);
$this->error_num = $parts[0];
$this->error_msg = $parts[1];
}
public function getErrorNum() {
return $this->error_num;
}

public function getErrorMsg() {
return $this->error_msg;
}
}

class LogToConsole {
private $_errorObject;

public function __construct(errorObject $errorObject)
{
$this->_errorObject = $errorObject;
}

public function write() {
fwrite(STDERR,$this->_errorObject->getError());
}
}

class LogToCSV {
private $_errorObject;

public function __construct(LogToCsvErrorObject $errorObject)
{
$this->_errorObject = $errorObject;
}

public function write() {
$error_num = $this->_errorObject->getErrorNum();
$error_msg = $this->_errorObject->getErrorMsg();

fwrite(STDERR,$error_msg."错误码:".$error_num);
}
}

$error_obj = new LogToCsvErrorObject("404:NOT FOUND");

(new LogToCSV($error_obj))->write();

快速排序

概念

快速排序基于分治思想来实现。

  1. 分解:数组A[start,end]被划分为两个(可能为空)子数组A[start,postion-1]和A[postion+1,end]中的,使A[start,postion-1]的每一个元素都小于等于A[postion],A[postion+1,end]的每一个元素都大于等于A[postion]。
  2. 解决:通过递归调用快速排序,对子数组A[start,postion-1]和A[postion+1,end]进行排序。
  3. 合并:因为数组是原址排序,因此A[start,end]是有序的。

数组的划分(解决)实现思想图

PHP实现快速排序

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
function quickSort(&$array,$start,$end)
{
if ($start < $end) {
$postion = partition($array, $start, $end);
quickSort($array,$start,$postion-1);
quickSort($array,$postion+1,$end);
}
}

function partition(&$arr,$start,$end) {
$flag = $arr[$end];
$i = $start-1;
for ( $j=$start; $j<= $end-1; $j++) {
if($arr[$j] <= $flag) {
$i = $i+1;
swap($arr,$i,$j);
}
}
swap($arr,$i+1,$end);

return $i+1;
}

function swap (&$arr,$exist,$replace) {
$temp = $arr[$exist];
$arr[$exist] = $arr[$replace];
$arr[$replace] = $temp;
}

$array = array(1,6,5,4);
quickSort($array,0,3);
var_dump($array);

优先队列(PriorityQueue)

概述

优先队列(priority queue)是一种用来维护由一组元素构成的集合S的数据结构,其中每一个元素都有一个相关的值,称为关键字(key)。
队列的定义
队列属于先进先出型,Frist in Frist out(FIFO)
优先队列基于堆排序的方法进行实现的,堆排序每次都要进行建立最大堆,第一个元素为整个队列中的最大值,优先队列也是利用了堆排序这个性质达到优先队列中权值最大的先出的效果。

优先队列的方法

  1. HeapMaximum方法实现了返回最大值
  2. HeapExtractMax方法实现删除队列中的最大值并返回最大值
  3. HeapIncreaseKey方法实现更改某个值。
  4. MaxHeapInsert方法实现将元素插入到队列队尾

PHP实现优先队列

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
public function HeapMaximum($arr) {
return $arr[0];
}

public function HeapExtractMax(&$arr,$length) {
if($length < 1) {
return false;
}

$max = $arr[0];
$arr[0] = $arr[$length-1];
$length = $length - 1;
$this->MaxHeapify($arr,1,$length);
return $max;
}

public function HeapIncreaseKey(&$arr,$i,$key) {
if ($key < $arr[$i]) {
return false;
}
$arr[$i] = $key;
$flag = $this->parent($i);
while ($i > 1 && $arr[$flag] < $arr[$i]) {
$this->swap($arr,$flag,$i);
$i = $this->parent($i);
}
}

public function MaxHeapInsert(&$arr,$key) {
$length = count($arr) + 1;
$arr[$length] = 0;
$this->HeapIncreaseKey($arr,$length,$key);
}

基于堆排序的PHP的部分代码

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
class PriorityQueue
{
public function __construct(&$arr) {
$arr_length = count($arr)-1;
$this->BuildMaxHeap($arr,$arr_length);
}


public function BuildMaxHeap(&$arr,$arr_length) {
$count = count($arr)-1;
for ($i = floor($count/2); $i >=0; $i--) {
$this->MaxHeapify($arr,$i,$arr_length);
}
}
public function MaxHeapify(&$arr,$i,$arr_length) {
$left = $this->left($i);
$right = $this->right($i);
if($left <= $arr_length && $arr[$left] >= $arr[$i]) {
$this->swap($arr,$i,$left);
$largest = $left;
} else {
$largest = $i;
}
if ($right <= $arr_length && $arr[$right] >= $arr[$largest]) {
$this->swap($arr,$largest,$right);
$largest = $right;
}
if ($largest != $i) {
$this->MaxHeapify($arr,$largest);
}
}

public function swap(&$arr,$exist,$largest) {
$temp = $arr[$exist];
$arr[$exist] = $arr[$largest];
$arr[$largest] = $temp;
}
private function left($i) {
return 2*$i+1;
}

private function right($i) {
return 2*$i+2;
}
private function parent($i) {
return floor($i/2);
}

//以下是队列操作
}

网络分层TCP/IP

概述

互联网分为五层,自下而上分为应用层、传输层、网络层、链接层、实体层。

实体层

实体层就是把电脑连接在一起的物理手段。它主要规定了网络的一些电气特性,作用是负责传送0和1的电信号。

链接层

拥有唯一的MAC地址进行标识,有了数据包和网卡MAC地址、广播的发送方式,链路层就可以在多台计算机之间传送数据。

网络层

网络层关心的是如何把一个数据从一台设备发送到另一台设备。是主机到主机之间的通信。

传输层

有了MAC地址和IP地址,我们可以在互联网任意两个主机上建立通信。区分一台主机中的接收的数据包属于哪个程序使用,是靠端口判断的。传输层的功能是从端口到端口的通信。因此Unix系统就把主机和端口叫作套接字(socket)

UDP协议

UDP数据包,也是由”标头”和”数据”两部分组成。
“标头”部分主要定义了发出端口和接收端口,”数据”部分就是具体的内容。然后,把整个UDP数据包放入IP数据包的”数据”部分,而前面说过,IP数据包又是放在以太网数据包之中的,所以整个以太网数据包现在变成了下面这样:

TCP协议

UDP协议的优点是比较简单,容易实现,但是缺点是可靠性差,一旦数据发出后,无法知道对方是否收到。
为了解决这个问题,TCP协议诞生。TCP协议可以理解为有确认机制的UDP协议。如果发送一个数据包遗失,就收不到确认,发送方就知道有必要重新发送数据包。
而且TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络效率通常TCP数据包的长度不会超过IP数据包长度,以保证单个TCP数据包不被分割。

应用层

应用层的作用就是规定应用程序的数据格式。
TCP协议可以为各种各样的程序传递数据,比如Email、WWW、FTP。必须有不同的协议规定电子邮件、网页、FTP数据格式,这些应用程序协议构成了“应用层”。

堆排序

堆排序概念

堆排序是利用堆这种数据结构而设计的一种排序算法。时间复杂度为O(nlogn)。

堆排序具有如下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为最大堆;或每个节点都小于或者等于其左右孩子节点被称为最小堆。


对堆中的节点进行编号,将这种逻辑映射到数组中,如下:

堆排序的基本性质:
最大堆:左右子节点小于父节点
最小堆:左右子节点大于父节点

堆排序流程

1.构建最大堆
构建最大堆之前呈现效果如下

构建最大堆之后呈现效果如下:

保证了堆排序中的最大堆的性质。
2.堆排序算法实现
取出构建最大堆中的最大值,放在尾部
然后重新构建最大堆。

以此类推最终达到从小到大的排序效果,完成堆排序。

PHP实现堆排序

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
<?php
/**
*
*/
class HeapSort
{
public function __construct(&$arr) {
$arr_length = count($arr)-1;
$this->HeapMaxSort($arr,$arr_length);
}
private function HeapMaxSort(&$arr,$arr_length) {;
$this->BuildMaxHeap($arr,$arr_length);

for($i = $arr_length;$i >= 0; $i--) {
$this->swap($arr,$i,0);
$arr_length--;
$this->MaxHeapify($arr,0,$arr_length);
}
}
private function BuildMaxHeap(&$arr,$arr_length) {
$count = count($arr)-1;
for ($i = floor($count/2); $i >=0; $i--) {
$this->MaxHeapify($arr,$i,$arr_length);
}
}

public function MaxHeapify(&$arr,$i,$arr_length) {
$left = $this->left($i);
$right = $this->right($i);
if($left <= $arr_length && $arr[$left] >= $arr[$i]) {
$this->swap($arr,$i,$left);
$largest = $left;
} else {
$largest = $i;
}
if ($right <= $arr_length && $arr[$right] >= $arr[$largest]) {
$this->swap($arr,$largest,$right);
$largest = $right;
}
if ($largest != $i) {
$this->MaxHeapify($arr,$largest);
}

}
public function swap(&$arr,$exist,$largest) {
$temp = $arr[$exist];
$arr[$exist] = $arr[$largest];
$arr[$largest] = $temp;
}
private function left($i) {
return 2*$i+1;
}

private function right($i) {
return 2*$i+2;
}


}
$array = array(5,2,3,1,4,6);
$Heap_Model = new HeapSort($array);
var_dump($array);

最大子数组

暴力求解最大子数组

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
function violentMax($array,$count,&$start,&$end) {
$sum = 0;
$max = 0;

for($i=0; $i <= $count-1; $i++) {
for ($j = $i; $j <= $count-1 ; $j++) {
$sum = 0;
for($k = $i; $k <= $j; $k++) {
$sum += $array[$k];
}
if ($sum > $max) {
$start = $i;
$end = $j;
$max = $sum;
}
}
}

return $max;
}

$array = array(3,-1,2,5,-3,4,-6,-7,1,8,-3,5,9);
$count = count($array);
$start = 0;
$end = 0;
$max = violentMax($array,$count,$start,$end);
echo '<hr>';
echo $start;
echo '<hr>';
echo $end;
echo '<hr>';
echo $max;

上面方法的时间复杂度为O( n^2 );

分治策略实现最大子数组

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
<?php
class divideRule {
public $left_pos;
public $right_pos;
public function divide($arr,$start,$end) {
if ($start == $end) {
return $arr[$start];
} else {
$mid = floor(($start+$end)/2);
$left_max = $this->divide($arr,$start,$mid);
$right_max = $this->divide($arr,$mid+1,$end);
$middle_max = $this->middleMax($arr,$start,$mid,$end);
if ($left_max >= $right_max && $left_max >= $middle_max) {
return $left_max;
} else if($right_max >= $left_max && $right_max >= $middle_max) {
return $right_max;
} else {
return $middle_max;
}

}
}
public function middleMax($arr,$start,$mid,$end) {
$left_sum = 0;
$sum = 0;
for($i = $mid; $i >= 0; $i--) {
$sum = $sum + $arr[$i];
if ($sum > $left_sum) {
$left_sum = $sum;
$this->left_pos = $i;
}
}
$right_sum = 0;
$sum = 0;
for ($j = $mid+1; $j <= $end ; $j++) {
$sum = $sum + $arr[$j];
if ($sum > $right_sum) {
$right_sum = $sum;
$this->right_pos = $j;
}
}
$count_sum = $right_sum + $left_sum;
return $count_sum;
}
}


$array = array(3,-1,2,5,-3,4,-6,-7,1,8,-3,5,9);
$count = count($array)-1;
$rule_class = new divideRule();
$max = $rule_class->divide($array,0,$count);
echo $rule_class->left_pos;
echo $rule_class->right_pos;
var_dump($max);

归并排序

PHP实现归并排序算法

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
<?php
function merger_sort(&$arr,$frist,$end) {
if($frist < $end) {
$middle = floor(($frist+$end)/2);
merger_sort($arr,$frist,$middle);
merger_sort($arr,$middle+1,$end);
merger($arr,$frist,$middle,$end);
}
}
function merger(&$arr,$start,$mid,$end) {
$i = $start;
$j=$mid + 1;
$k = $start;
$temparr = array();

while($i!=$mid+1 && $j!=$end+1)
{
if($arr[$i] >= $arr[$j]){
$temparr[$k++] = $arr[$j++];
}
else{
$temparr[$k++] = $arr[$i++];
}
}
while($i != $mid+1){
$temparr[$k++] = $arr[$i++];
}

while($j != $end+1){
$temparr[$k++] = $arr[$j++];
}

for($i=$start; $i<=$end; $i++){
$arr[$i] = $temparr[$i];
}
}

function MergeSort(&$arr){
$start = 0;
$end = count($arr) - 1;
merger_sort($arr,$start,$end);
}
$array = array(5,2,7,4);
MergeSort($array);
var_dump($array);

分治策略的步骤

  1. 分解 原问题为若干个子问题,这些子问题是原问题的规模较小的实例。
  2. 解决 这些子问题,递归地求解各子问题。然而子问题的规模足够小,则直接求解。
  3. 合并 这些子问题的解成原问题的解。

归并排序遵从分治策略

  1. merger_sort函数完成了分解的步骤,将原数组array(5,2,7,4)最终分解为array(5,2)和array(7,4)
  2. merger函数完成了解决(排序)和合并的操作首先对分解的array(5,2)和array(7,4)进行排序,得到 array(2,5)和array(4,7),然后合并为array(2,5,4,7),然后继续merge进行解决,流程是将2与4进行比较拿出较小的放在原数组中,然后将4和5进行比较,然后比较5和7,完成归并排序。

总结

归并排序的时间复杂度是o(nlogn)