php进程通信-消息队列

PHP进程通信-消息队列

php多进程通信,有各种各样的方法(进程信号、消息队列、管道、共享内存、socket等等)
本文主要讲php利用linux消息对列的通信方法

注意:多进程系列文章,都建立在linux环境下,php-cli运行模式下。

一、消息队列通信介绍

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。

消息队列的最佳定义是:内核地址空间中的内部链表。消息可以顺序地发送到队列中,并以几种不同的方式从队列中获取。当然,每个消息队列都是由 IPC标识符 所唯一标识的。

二、php消息队列扩展

php如果要使用linux的消息队列,需要安装sysvmsg扩展,官方文档地址:https://www.php.net/manual/zh/book.sem.php

三、php使用消息队列

1、获取一个 IPC 标识符 ftok();

ftok 可将项目路径与文件标识符转换成一个 IPC 标识符,该标识符可用于创建消息队列。
pcntl

2、获取/创建一个消息队列msg_get_queue()

pcntl

使用linux命令ipcs -q可查看系统当前的消息队列数
pcntl

3、插入数据到队列(发送一个消息到消息队列) msg_send()

1
2
3
4
5
6
7
8
msg_send(
resource $queue, # 消息队列资源句柄
int $msgtype, # 插入数据的类型,用来标识该队列自己的消息类型,自己自定义,必须大于0
mixed $message # 插入的数据,可以为数组,下一个参数可以序列化数据
[,bool $serialize = TRUE # 是否序列化数据,默认是
[,bool $blocking = TRUE # 如果消息太大而无法放入队列(linux消息队列限制),则脚本将等到另一个进程从队列读取消息,并释放足够的空间以发送消息。这被称为阻塞;您可以通过设置可选 blocking 参数来防止阻塞FALSE,在这种情况下,如果消息对于队列来说太大,msg_send() 将立即返回,并将可选参数FALSE设置为 errorcode 为 MSG_EAGAIN,表示您应稍后尝试再次发送消息。
[,int &$errcode # 错误标识
);

pcntl

插入成功之后,ipcs 可查看 message 多了一条:
pcntl

4、取出一条数据 msg_receive

1
2
3
4
5
6
7
8
9
10
msg_receive(
resource $queue, # 消息队列资源句柄
int $desiremsgtype, # 要取出的消息队列类型,如果为0,则不筛选类型,直接返回最先插入的那条,大于0,则筛选类型,返回最先插入的类型数据,小于0,则返回小于等于绝对值的数据,如果消息队列暂无满足要求的数据,则阻塞或者返回false,由flags参数配置
int &$msgtype, # 当取出数据时,该变量会赋值为该数据的类型
int $maxsize, # 消息的最大大小被指定的被接受 maxsize;如果队列中的消息大于此大小,则该功能将失败(除非 flags 按照以下说明设置)该参数较迷,没有理解
mixed &$message, # 当取出数据时,该变量会赋值为该数据
[, bool $unserialize = TRUE] # 是否反序列化数据
[, int $flags = 0 # 该选项 flags 允许您将标志传递给低级msgrcv 系统调用。它默认为,但您可以指定一个或多个以下值(通过他们相加或相减)。见下表
[, int &$errorcode # 如果该函数失败,errorcode 则可选项将被设置为系统 errno 变量的值。
]]]);

msg_receive的标志值:

msg_receive的标志值 含义
MSG_IPC_NOWAIT 如果没有消息 desiremsgtype,立即返回,不要等待。该函数将失败并返回对应的整数值 MSG_ENOMSG
MSG_EXCEPT 将此标志与desiredmsgtype大于0结合使用会导致函数接收到不等于的第一条信息desiremsgtype
MSG_NOERROR 如果消息长于maxsize,则设置此标志将截断消息,maxsize并且不会发出错误信号。

5、删除队列

1
msg_remove_queue(resource $queue)

顾名思义,该函数可删除一个消息队列。

四、linux相关操作

在linux中吗,主要用ipcs(查看) ipcrm(删除)

1、ipcs

ipcs -h,可查看帮助
pcntl

主要需要记住的是:
ipcs -q(查看消息队列)
pcntl

ipcs -l(查看系统配置)
pcntl

2、ipcrm

ipcrm -h:
pcntl

ipcrm,只要能删除就ok了。
ipcrm -q id(删除指定消息队列)
pcntl

3、注意!

在使用消息队列时,请注意消息队列的默认限制(限制消息队列数和消息队列大小),
当达到上限时,会使得写入消息队列操作阻塞(默认阻塞)

五、封装类

创建队列方法,好像有点问题(创建后无法正确使用队列,估计是__FILE__常量问题),暂时没查

使用封装类方法:

1
2
3
4
5
6
7
<?php
$message_queue_key = ftok(__FILE__, 'a');
if (msg_queue_exists($message_queue_key)) { // 如果有该消息队列,则删除,用于清空之前队列的无用数据
msg_remove_queue(msg_get_queue($message_queue_key, 0666));
}
$message_queue = msg_get_queue($message_queue_key, 0666);
$msg_queue = new MsgQueue($message_queue); // MsgQueue类具体看下面:
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
<?php
/**
* Created by PhpStorm.
* User: huanghui
* Date: 2019/12/24
* Time: 15:13
*/
class MsgQueue
{
public $queue;

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

public function push($data, $type = 1)
{
$result = msg_send($this->queue, $data);
return $result;
}

public function pop($type = 0, $flags = MSG_IPC_NOWAIT)
{
msg_receive($this->queue, $type, $message_type, 1024, $message, true, $flags);
// var_dump($message_type);
// msg_receive($this->queue, $type, $message_type, 1024, $message);
return $message;
}

public function close()
{
return msg_remove_queue($this->queue);
}

/**
* 创建一个队列(TODO:疑问待解决)
* @param string $path_name
* @param string $prop
* @param string $perms
* @return array
*/
public static function getQueue($path_name = __FILE__, $prop = '1', $perms = '0666')
{
$data = array();
$data['queue_key'] = ftok($path_name, $prop);
$data['queue'] = msg_get_queue($data['queue_key'], $perms);
return $data;
}
}

七、使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include_once 'new/MsgQueue.php';
$message_queue_key = ftok(__FILE__, 'a');
$message_queue = msg_get_queue($message_queue_key, 0666);
$queue_obj = new MsgQueue($message_queue);
$pid = pcntl_fork();
if ($pid >= 0) { // 主进程入列
while (1) {
$msg = $queue_obj->push((array('a' => 321312, 'v' => 'casd')), 12456);
sleep(2);
}
} else { // 子进程入列
while (1) {
$message = $queue_obj->pop();
var_dump($message);
sleep(1);
}
}

以上内容转自:http://www.php20.cn/article/137

微服务架构简单介绍

微服务架构简单介绍

一、为什么需要分布式服务

1.1、早期单体架构带来的问题

单体架构在规模比较小的情况下工作情况良好,但是随着系统规模的扩大,它暴露出来的问题也越来越多,主要有以下几点:

1、复杂性组件变高

比如有的项目有几十万行代码,各个模块之间区别比较模糊,逻辑比较混乱,代码越多复杂性越高,越难解决遇到的问题。

2、技术债务逐渐上升

公司的人员流动是再正常不过的事情,有的员工在离职之前,疏于代码质量的自我管束,导致留下来的很多坑,由于单体项目代码量庞大的惊人,留下的坑很难被发觉,这就给新来的员工带来很大的烦恼,人员流动越大所留下的坑越多,也就是所谓的技术债务越来越多。

3、阻碍技术创新

比如以前的某个项目使用tp3.2写的,由于各个模块之间有着千丝万缕的联系,代码量大,逻辑不够清楚,如果现在想用tp5来重构这个项目将是非常困难的,付出的成本将非常大,所以更多的时候公司不得不硬着头皮继续使用老的单体架构,这就阻碍了技术的创新。

4、无法按需伸缩

比如说电影模块是CPU密集型(也就是非常耗费cpu,但是不怎么耗io)的模块,而订单模块是IO密集型(非常耗费io,但是不怎me耗费cpu)的模块,假如我们要提升订单模块的性能,比如加大内存、增加硬盘,但是由于所有的模块都在一个架构下,我们在扩展订单模块的性能时不得不考虑其他模块的因素,因为我们不能因为扩展某个模块的性能而损害其他模块的性能,从而无法按需进行伸缩。

5、系统高可用性差

因为所有的功能开发最后都部署到同一个框架里,运行在同一个进程之中,一旦某一功能涉及的代码或者资源有问题,那就会影响整个框架中部署的功能。

二、注解机制

一般而言,在编程界中注解是一种和注释平行的概念,在解释注解之前我们需要先定义一下注解与注释的区别:

  • 注释:给程序员看,帮助理解代码,对代码起到解释、说明的作用。
  • 注解:给应用程序看,注解往往充当着对代码的声明和配置的作用,为可执行代码提供机器可用的额外信息,在特定的环境下会影响程序的执行。

框架可以基于这些元信息为代码提供各种额外功能,本质上注解就是理解注解只是配置的另一种展现方式:

  • 比如通过注解的方式实现权限的控制,就比在配置文件当中配置更加地方便。
  • 比如通过利用注解的方式配置路由、配置定时任务、配置服务限流。

三、RPC接口

3.1、什么是RPC?

RPC(Remote Rrocedure Call)——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

比如说两台服务器A, B,一个应用部署到 A 服务器上,想要调用 B 服务器上应用的提供的函数/方法,由于不在一个内存空间,不能直接调用,就需要通过网络来表达调用的语义和传达调用的数据,而这种方式就是RPC。

RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简结性。为实现该目标,RPC框架需提供一种透明调用机制让使用者不必显示的区分本地调用和远程调用。

RPC 隐藏了通讯的细节,调用远程的服务就像本地的代码一样,其调用协议通讯包含传输协议和编码协议。

  • 传输协议:可以是自定义的TCP 协议,可以是http、websocket。
  • 编码协议:如基于文本编码的xml、json等,也有二进制编码的protobuf、binpack等。

3.2、使用什么协议?

RPC 是一个软件结构概念,是构建分布式应用的理论基础。就好比为啥你家可以用到发电厂发出来的电?

是因为电是可以传输的。至于用铜线还是铁丝还是其他种类的导线,也就是用 http 还是其他协议的问题了。这个要看什么场景,对性能要求怎么样。

3.3、rpc就只是接口调用?

一个完善的 RPC 其实还包含另一块内容,除通信协议外还有“服务注册发现”,错误重试,服务限流,服务调用的负载均衡等等,RPC 不仅仅是一套设计规范,还包含了服务治理。

常用的vim技巧

Linux生产环境上,最常用的一套”vim”技巧

最常用系列:

《Linux生产环境上,最常用的一套 “Sed” 技巧》

《Linux生产环境上,最常用的一套 “AWK” 技巧》

《”Sed”高级功能》

引子

研发线上使用最多的编辑器,就是vi。无论是最快查看某个文件内容,还是快速编辑某个文件,vi都能帮上忙。

软件世界貌似有一些非常长寿的东西,vi算是一个。本篇文章聚焦的是研发线上最常用的一些功能。至于安装插件,写一些脚本,那一般是在开发机上玩的,生产环境没有条件、也没有时间忍受你做这些增强。希望看完本文,能够对这款神器有一个大体印象。当然,熟练的使用还需要日常有意识的培养。

vimvi的增强版,一般现代 linux 都不缺那几兆空间,所以预装的都是增强版,本文默认使用vim

养成习惯

vim最大的贡献就是它的按键系统。这也是为什么chrome、idea、atom等编辑器都会提供一个vim mode笔者见过很多资深的程序员,包括架构师,习惯使用方向键去控制光标的移动。 这不能说不对,但这也抛弃了vim最大的精华所在,效率上低了一大截。坚持使用hjkl,你会感谢你今天的纠正。大脑和手指真的是有记忆,当你用的足够多,这也就成了你约定俗成的设定。

vim另外一个特点就是带模式的。一共四种模式,我们不需要记忆,只需要使用例子去理解即可。

不要添乱

不要使用vim打开大文件,vim会一次性读取所有内容到内存,容易造成宿主机内存溢出。 打开文件前,可以使用du -h命令查看文件大小。一般,100MB以下为宜。

常用操作

以下操作在普通模式下执行,连续按键

漫游操作

j 向下
30j 向下移动30行
k 向上
h 向左
l 向右
0 到行首
^ 到行首第一个字符,如果前面有空格的话
$ 到行尾
gg 快速到文件头
G 快速到文件尾
100G 跳转到第100行

1
2
3
4
5
6
7
8
9
10
11
j   向下
30j 向下移动30行
k 向上
h 向左
l 向右
0 到行首
^ 到行首第一个字符,如果前面有空格的话
$ 到行尾
gg 快速到文件头
G 快速到文件尾
100G 跳转到第100行

不建议在插入模式下进行光标移动,这很低效

复制操作: y

yy 复制一行
10yy 向下复制10行
yw 复制光标开始的一个单词
y$ 复制光标到行尾
yfB 复制光标到第一个大写B中间的内容
y2fB 复制光标到第二个大写B中间的内容

1
2
3
4
5
6
yy    复制一行
10yy 向下复制10行
yw 复制光标开始的一个单词
y$ 复制光标到行尾
yfB 复制光标到第一个大写B中间的内容
y2fB 复制光标到第二个大写B中间的内容

黏贴操作: p

p 黏贴复制或剪切的内容
3p 将复制或剪切的内容黏贴三次

1
2
p    黏贴复制或剪切的内容
3p 将复制或剪切的内容黏贴三次

剪切操作: x

x 向后剪切一个字符,如果是在行尾,则为向前剪切一个字符
3x 向后剪切3个字符,如果在行尾,则为向前剪切一个字符
xp 非行尾交换两个字符,如从 bs 变成 sb

1
2
3
x   向后剪切一个字符,如果是在行尾,则为向前剪切一个字符
3x 向后剪切3个字符,如果在行尾,则为向前剪切一个字符
xp 非行尾交换两个字符,如从 bs 变成 sb

删除操作: d

删除的内容会放到剪贴板,按p即可粘贴到其他地方

dd 删除一行
200dd 删除200行
dw 删除一个单词
df” 删除到出现的第一个引号

1
2
3
4
dd     删除一行
200dd 删除200行
dw 删除一个单词
df" 删除到出现的第一个引号

可视化模式

v表示行模式,选择一些内容

可视化模式是非常有用的一种模式,在普通模式下按 v 即可进入。 使用hjkl进行漫游,选中相应的内容。

例如,vim a.txt 打开文件,然后按v,选中一部分想要的内容,然后按d即可删除。


ctrl+v 表示块模式

演示:将文件中的每一行添加到ArrayList中:
1) 在命令模式下,执行%s/$/“);/g,在行尾追加数据
2) 按ESC进入普通模式,并使用gg回到行首
3) 按ctrl+v进入可视化模式,然后按G到文件尾
4) 不要理会编辑器反应,按I进入插入模式,输入list.add(“
5) 按ESC回到普通模式,可以发现以上输入已经在每一行生效了

块模式还可以完成列的呼唤,貌似在UE里见过此神技。

命令模式

上面的例子里已经展示了命令模式的进入模式。在普通模式下,输入:即可进入。

%s/$/sth/ 在行尾追加sth
%s/^M//g 替换掉dos换行符,^M使用ctrl+v + Enter即可输入
:g/^\s$/d* 删除空行以及只有空格的行
%s/#.*//g 删除#之后的字符

1
2
3
4
%s/$/sth/     在行尾追加sth
%s/^M//g 替换掉dos换行符,\^M使用ctrl+v + Enter即可输入
:g/^\s$/d* 删除空行以及只有空格的行
%s/#.*//g 删除#之后的字符

没错,命令模式用的是正则,这些经验是通用的

你已经发现了,这大概就是针对编辑器窗口的sed命令。

查找字符串

同样的,正则的知识也可以应用*

在普通模式下,按下/直接进入查找,输入相应的字符串按确定即可。

n 查找下一个匹配
N 查找上一个匹配
2n 查找下面第二个匹配

1
2
3
n     查找下一个匹配
N 查找上一个匹配
2n 查找下面第二个匹配

如果觉得跳来跳去晕头转向,可以在命令模式下输入set nu开启行号。

宏录制

这可以说是vim的一个杀手锏了。拿上面的例子来说。 将文件中的每一行添加到ArrayList中。

1) 按下gg到行首
2) 按下qa进行宏录制,a是我们起的一个标记名称
3) 按I进入插入模式,输入list.add(“
4) 按ESC进入普通模式,然后按$跳到行尾
5) 按j进入下一行,然后按^回到行首
6) 再次按下q结束宏录制
7) 输入@a触发宏测试一下录制效果
8) 输入100@a重复宏100次,也就是影响下面的100行

可以录制不同的多个宏,方面的进行批量操作

其他

u 撤销更改(恢复更改)
r 替换字符
ggVG 全选
J 合并下一行
gU 光标处转大写
ggguG 整篇文章大写转化为小写
% 跳转到下一个匹配,如在

上按%,则跳转到相应的

:e /tmp/a 在同一个编辑器内打开/tmp/a文件。同一个编辑器的缓冲区是剪贴板是共享的,可以方便在多个文件中复制
bp 跳转到上一个缓冲区
bn 跳转到下一个缓冲区

1
2
3
4
5
6
7
8
9
10
r       替换字符
ggVG 全选
u 恢复更改
J 合并下一行
gU 光标处转大写
ggguG 整篇文章大写转化为小写
% 跳转到下一个匹配,如在<div>上按%,则跳转到相应的</div>
:e /tmp/a 在同一个编辑器内打开/tmp/a文件。同一个编辑器的缓冲区是剪贴板是共享的,可以方便在多个文件中复制
bp 跳转到上一个缓冲区
bn 跳转到下一个缓冲区

退出编辑器

wq 保存当前文件并退出
wqa 保存所有文件并退出
q! 不保存,直接退出
qa! 有多个文件被打开,同时退出

1
2
3
4
wq   保存当前文件并退出
wqa 保存所有文件并退出
q! 不保存,直接退出
qa! 有多个文件被打开,同时退出

CentOS设置自定义时间

CentOS date设置自定义时间

手动修改

设置日期

1
date -s 20191128

设置时间

1
date -s 12:30:00

设置日期和时间

1
date -s "20191128 12:30:00"

自动同步

安装ntpdate工具

1
yum -y install ntp ntpdate

设置系统时间与网络时间同步

1
ntpdate cn.pool.ntp.org

将系统时间写入硬件时间

1
hwclock --systohc

强制系统时间写入CMOS中防止重启失效

1
hwclock -w 或 clock -w

查看当前时间

1
date

MySQL数据库的锁有多少种和怎么编写加锁的sql语句

一、概述

数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问时变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计的,所以各存储引擎的锁定机制也有较大区别。

MySQL各存储引擎使用了三种类型(级别)的锁定机制:表级锁定、行级锁定和页级锁定。

1.表级锁定(table-level)

表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。

当然,锁定颗粒度大所带来的最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。

使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎

2.行级锁定(row-level)

行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁

使用行级锁定的主要是InnoDB存储引擎

3.页级锁定(page-level)

页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁

在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。

使用页级锁定的主要是BerkeleyDB存储引擎

总的来说,MySQL这3种锁的特性可大致归纳如下

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
  • 页级锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统

二、表级锁定

由于MyISAM存储引擎使用的锁定机制完全是由MySQL提供的表级锁定实现,所以下面我们将以MyISAM存储引擎作为示例存储引擎。

1.MySQL 的表级锁的锁模式

MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。

锁模式的兼容性:

  • 对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
  • 对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;
  • MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。

2.如何加表锁

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

3.MyISAM 表锁优化建议

对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁定所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能地并发。

(1) 查询表级锁争用情况

MySQL内部有两组专门的状态变量记录系统内部锁资源争用情况:

1
2
3
4
5
6
7
mysql> show status like 'table%';
+----------------------------+---------+
| Variable_name | Value |
+----------------------------+---------+
| Table_locks_immediate | 100 |
| Table_locks_waited | 11 |
+----------------------------+---------+

这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:

  • Table_locks_immediate:产生表级锁定的次数;
  • Table_locks_waited:出现表级锁定争用而发生等待的次数;

两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加1。如果这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。

(2) 缩短锁定时间

如何让锁定时间尽可能的短呢?唯一的办法就是让我们的Query执行时间尽可能地短。

  • a) 尽量减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;
  • b) 尽可能地建立足够高效的索引,让数据检索更迅速;
  • c) 尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;
  • d) 利用合适的机会优化MyISAM表数据文件。

(3) 分离能并行的操作

说到MyISAM的表锁,而且是读写互相阻塞的表锁,可能有些人会认为在MyISAM存储引擎的表上就只能是完全的串行化,没办法再并行了。大家不要忘记了,MyISAM的存储引擎还有一个非常有用的特性,那就是ConcurrentInsert(并发插入)的特性。

MyISAM存储引擎有一个控制是否打开Concurrent Insert功能的参数选项:concurrent_insert,可以设置为0,1或者2。三个值的具体说明如下:

  • concurrent_insert=2,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录;
  • concurrent_insert=1,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置;
  • concurrent_insert=0,不允许并发插入。

可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。

(4) 合理利用读写优先级

MyISAM存储引擎的是读写互相阻塞的,那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?

答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前

原因分析:这是因为MySQL的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。所以,如果我们可以根据各自系统环境的差异决定读与写的优先级:

  • 通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接读比写的优先级高。如果我们的系统是一个以读为主,可以设置此参数,如果以写为主,则不用设置;
  • 通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

虽然上面方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。

另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。

这里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”,因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题,因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

三、行级锁定

PHP中的三大经典设计模式

PHP中的三大经典设计模式

单例模式

单例模式的含义:

作为对象的创建模式,单例模式确保某1个类只有1个对象实例,而且自行实例化并向整个系统全局地提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。

单例模式的三个要素:

  • 1、保存类唯一实例的静态变量
  • 2、构造函数和克隆函数必须是私有的,这样就不会创建实例副本。如果放在外部去实例化,这样就不存在单例模式的意义。
  • 3、提供一个可供外部访问的公共静态方法,这个方法返回该类的唯一实例。

单例模式的意义:

在 PHP 中的应用主要在于数据库应用,所以一个应用中会存在大量的数据库操作,在使用面向对象的方式开发时,如果使用单例模式,则可以避免大量的 new 操作消耗的资源。而且不完全是对系统资源的节省,还可以避免重复实例化,因为 PHP 每次实例化一个类之后都会清理掉对应的资源,当再次使用的时候又会在重新去实例化一次。

单例模式使用的场景:

  • 1、数据库操作,减少对数据库的 new 操作,从而减少内存资源和 系统资源的消耗
  • 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
<?php
class Single
{
public static $attribute = '';

public static $instance = '';

private function __construct($attribute = '个人技术')
{
self::$attribute = $attribute;
}

public static function getInstance($attribute = '我是编程浪子走四方1')
{
if (!(self::$instance instanceof self)) {
self::$instance = new self($attribute);
}
return self::$instance;
}

//克隆函数私有化,避免外部 clone (每clone一次,就不是同一个实例)
private function __clone()
{

}
}

单例模式和非单例模式的区别:

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
<?php
class Single {
public function index()
{
return '';
}
}

$single1 = new Single();
$single2 = new Single();
var_dump($single1);
// 结果:object(Single)#1 (0) { }

var_dump($single2);
// 结果:object(Single)#2 (0) { }

if ($single1 === $single2) {
echo '是同一个对象';
} else {
echo '不是同一个对象';
}
// 结果:不是同一个对象

class Single2 {
// 1、声明一个静态属性,用于保存类的实例
public static $instance;

// 2、将构造函数私有化,避免外部new (每new一次,就不是同一个实例)
private function __construct()
{

}
// 3、声明一个静态的公共方法,用于外部调用本类的实例
public static function getInstance()
{
if (!(self::$instance instanceof self)) {
self::$instance = new self();
}
return self::$instance;
}
// 4、克隆函数私有化,避免外部clone(每clone一次,就不是同一个实例)
private function __clone()
{
// TODO: Implement __clone() method.
}
}

$singleDemo1 = Single2::getInstance();
$singleDemo2 = Single2::getInstance();
var_dump($singleDemo1->getInstance());
// 结果:object(Single2)#3 (0) { }
var_dump($singleDemo2->getInstance());
// 结果:object(Single2)#3 (0) { }
if ($singleDemo1 === $singleDemo2) {
echo '是同一个对象';
} else {
echo '不是同一个对象';
}
// 结果:是同一个对象

工厂模式

工厂模式的含义:

负责生产其他对象的方法。简单的秒数就是通过一个工厂类,去实例化其他类或者方法。

工厂模式的意义:

通过使用工厂模式,减少因为多处 new 同一个类,当这个类发生变化时,则需要多处修改。

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Factory
{
public static function createDB()
{
echo '我生产了一个DB实例';
return new DB();
}
}

class DB
{
public function __construct()
{
echo __CLASS__ . PHP_EOL;
}
}

$db = Factory::createDB();
// 结果:
//我生产了一个DB实例
//DB

注册树模式

注册树含义:

注册树就是将多个对象注册在一个对象池中,当我们需要使用时,直接从对象池获取即可。

注册树模式的优点:

单例模式解决的是如何在整个项目中 创建唯一对象实例 的问题,工厂模式解决的是如何不通过 new 建立实例对象的方法。那么注册树模式想解决什么问题呢?

在考虑这个问题前,我们还是有必要考虑下前两种模式目前面临的局限。首先,单例模式创建唯一对象的过程本身还有一种判断,即判断对象是否存在。存在则返回对象,不存在则创建对象并返回。每次创建实例对象都要存在这么一层判断。工厂模式更多考虑的是扩展维护的问题。总的来说,单例模式和工厂模式可以产生更加合理的对象。怎么方便调用这些对象呢?而且在项目内如此建立的对象散兵游勇一样,不便统筹管理安排啊。

因而,注册树模式应运而生。不管你是通过单例模式还是工厂模式还是二者结合生成的对象,都统统给我“插到”注册树上。我用某个对象的时候,直接从注册树上取一下就好。这和我们使用全局变量一样的方便实用。而且注册树模式还为其他模式提供了一种非常好的想法。

综合代码演示:

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
<?php
/**
* 单例模式
*/
class Single
{
public static $attribute = '';
public static $instance = '';
private function __construct($attribute = '个人技术')
{
self::$attribute = $attribute;
}
public static function getInstance($attribute = '个人技术1')
{
if (!(self::$instance instanceof self)) {
self::$instance = new self($attribute);
}
return self::$instance;
}
private function __clone()
{
// TODO: Implement __clone() method.
}
}

/**
* 工厂模式
*/
class Factory
{
public static function createObj()
{
return Single::getInstance('个人技术');
}
}

/**
* 注册树模式
* 含义:就是将对象放在一个对象池【简单理解就是一个数组】中,使用的时候直接去对象池查找
* 需要如下操作:
* 1、注册
* 2、存放对象池
* 3、获取
* 4、销毁
*/
class Register
{
// 用一个数组来当作对象池,键当作对象别名,值存储具体对象
public static $objTree = [];
// 将对象放在对象池
public static function set($key, $val)
{
return self::$objTree[$key] = $val;
}
// 通过对象别名在对象池中获取具体对象
public static function get($key)
{
return self::$objTree[$key];
}
// 通过对象别名将具体对象从对象池中注销
public static function _unset($key)
{
unset(self::$objTree[$key]);
}
}

Register::set('single', Factory::createObj());
$single = Register::get('single');
print_r($single);
// 结果:Single Object()

echo $single::$attribute;
// 结果:个人技术

PHP使用curl发送post请求json数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
function request($url,$params) {
$data = json_encode($params);
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POST,1);
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,60);
curl_setopt($ch,CURLOPT_HTTPHEADER,['Content-Type: application/json','Content-Length:' . strlen($data)]);
curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$output = curl_exec($ch);
curl_close($ch);
return json_decode($output,true);
}

CentOS查看日志常用命令

1
2
cat
tail -f

日志文件说明:

/var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一

/var/log/secure 与安全相关的日志信息

/var/log/maillog 与邮件相关的日志信息

/var/log/cron 与定时任务相关的日志信息

/var/log/spooler 与UUCP和news设备相关的日志信息

/var/log/boot.log 守护进程启动和停止相关的日志消息

系统:

1
2
3
4
5
6
7
8
9
# uname -a                # 查看内核/操作系统/CPU信息
# cat /etc/issue
# cat /etc/redhat-release # 查看操作系统版本
# cat /proc/cpuinfo # 查看CPU信息
# hostname # 查看计算机名
# lspci -tv # 列出所有PCI设备
# lsusb -tv # 列出所有USB设备
# lsmod # 列出加载的内核模块
# env # 查看环境变量

资源:

1
2
3
4
5
6
7
# free -m          # 查看内存使用量和交换区使用量
# df -h # 查看各分区使用情况
# du -sh <目录名> # 查看指定目录的大小
# grep MemTotal /proc/meminfo # 查看内存总量
# grep MemFree /proc/meminfo # 查看空闲内存量
# uptime # 查看系统运行时间、用户数、负载
# cat /proc/loadavg # 查看系统负载

磁盘和分区:

1
2
3
4
5
# mount | column -t  # 查看挂接的分区状态
# fdisk -l # 查看所有分区
# swapon -s # 查看所有交换分区
# hdparm -i /dev/hda # 查看磁盘参数(仅适用于IDE设备)
# dmesg | grep IDE # 查看启动时IDE设备检测状况

网络:

1
2
3
4
5
6
# ifconfig        # 查看所有网络接口的属性
# iptables -L # 查看防火墙设置
# route -n # 查看路由表
# netstat -lntp # 查看所有监听端口
# netstat -antp # 查看所有已经建立的连接
# netstat -s # 查看网络统计信息

进程:

1
2
# ps -ef   # 查看所有进程
# top # 实时显示进程状态(另一篇文章里面有详细的介绍)

用户:

1
2
3
4
5
6
# w             # 查看活动用户
# id <用户名> # 查看指定用户信息
# last # 查看用户登录日志
# cut -d: -f1 /etc/passwd # 查看系统所有用户
# cut -d: -f1 /etc/group # 查看系统所有组
# crontab -l # 查看当前用户的计划任务

服务:

1
2
# chkconfig --list           # 列出所有系统服务
# chkconfig --list | grep on # 列出所有启动的系统服务

程序:

1
# rpm -qa   # 查看所有安装的软件包

PHP7.0.x对比PHP5.6.x的新特性

PHP7.0.x对比PHP5.6.x的新特性

php在2015年12月03日发布了7.0正式版,带来了许多新的特性,以下是不完全列表:

  • 性能提升:PHP7比PHP5.6性能提升了两倍。 Improved performance: PHP 7 is up to twice as fast as PHP 5.6
  • 全面一致的64位支持。 Consistent 64-bit support
  • 以前的许多致命错误,现在改成抛出异常。Many fatal errors are now Exceptions
  • 移除了一些老的不在支持的SAPI(服务器端应用编程端口)和扩展。Removal of old and unsupported SAPIs and extensions
  • 新增了空接合操作符。The null coalescing operator (??)
  • 新增加了结合比较运算符。Combined comparison Operator (<=>)
  • 新增加了函数的返回类型声明。Return Type Declarations
  • 新增加了标量类型声明。Scalar Type Declarations
  • 新增加匿名类。Anonymous Classes

本博客主要讲解 swoole 将用到或相关性的特性

致命错误将可用异常形式输出

在 PHP7 之后,大部分错误可通过异常形式抛出,并可使用 catch 拦截。例如:

1
2
3
4
5
6
7
8
9
10
<?php
try {
$a->test(); // 未定义该对象并没有该方法,抛出一个 Throwable 类
// Code that may throw an Exception or Error.
} catch (Throwable $t) {
var_dump($t->getMessage());
// Executed only in PHP7, will not match in PHP
} catch (Exception $e) {

}

运行之后将打印一条报错语句:

1
string(40) "Call to a member function test() on null"

?? null 合并运算符

由于日常使用中存在大量同时使用三元表达式和 isset() 的情况,PHP7 添加了 null 合并运算符(??)这个语法糖。如果变量存在且值不为 NULL,它就会返回自身的值,否则返回它的第二个操作数。例如:

1
2
3
<?php
$a = $b ?? 0;
// 如果当 $b 为 null,则返回0。如果$b 不为 null,则返回 $b;

标量类型声明

标量类型声明 有两种模式:强制(默认)和 严格模式。现在可以使用下列类型参数(无论用强制模式还是严格模式):字符串(string),整数(int),以及布尔值(bool)。它们扩充了 PHP5 中引入的其他类型:类名,接口,和 回调类型。例如:

1
2
3
4
5
6
7
<?php
function a(
?int $a /*参数必须是int或者null*/,
string $b /*参数必须是string*/,
Closure $function /*参数必须是匿名函数*/,
array $array /*参数必须是数组*/
){}

类名/接口限定都需要命名空间

返回值类型声明

PHP7 增加了对返回类型声明的支持。类似于参数类型声明,返回类型声明指明了函数返回值的类型。可用的类型与参数声明中可用的类型相同。例如:

1
2
3
4
5
6
7
<?php
function a():int { // 必须返回int类型,否则报错
return 1;
}
function b():?int { // 必须返回int类型或者null类型,否则报错
return 'das';
}

太空船操作符<=>(组合比较符)

太空船操作符用于比较两个表达式。当 $a 小于、等于或大于 $b 时返回-1、0或1。比较的原则是沿用 PHP 的常规比较规则进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

// Strings
echo 'a' <=> 'a'; // 0
echo 'a' <=> 'b'; // -1
echo 'b' <=> 'a'; // 1

怎样检测TCP/UDP端口的连通性

1、TCP端口的连通性

TCP端口的连通性,一般通过telnet检测:

TCP协议是面向连接的,可以直接通过telnet命令连接

1
2
telnet host-ip port
比如:telnet 127.0.0.1 80

2、UDP端口的连通性

因为公司网络防火墙一般对端口会有一些限制,通过公司网络访问外部服务器特定端口时,需要检测与目标服务器特定端口的连通性;

若无法连通,在确认目标服务器相关端口打开时,则很有可能是被公司网络(中间网络)屏蔽了。对于TCP端口,如上一般用telnet检测。

对于UDP端口,一般稍微复杂点:

因为UDP协议是无连接的,不需要握手建立连接,数据发送后,server端也不会返回确认信息。

一般可以使用netcat检测,这个命令被誉为是网络中的“瑞士军刀”,功能非常强大,测试udp只是其中的一个功能变通。

UDP 端口连通性测试:

在目标机器监听UDP端口port1, 在客户端机器向目标机器port1端口发送UDP数据报,看能否发送成功。发送成功,则表示可连通。

例如:

a机器上运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a机器上运行:

nc -ul 1080

或:netcat -ul -p 1080

#使用udp模式监听1080 端口

b机器上运行:

nc -u x.x.x.x 1080

或:netcat -u x.x.x.x 1080

#使用udp模式向该ip的1080端口发送信息。

效果如图,在任意一边输入内容,另一边则会收到相应内容,以此就可以测试该端口的udp连接是否通常。

udp

参考:

How does netcat know if a UDP port is open?

Linux Netcat 命令——网络工具中的瑞士军刀

Netcat–Wikipedia

How might one check if a UDP port is open to another server

本站总访问量 | 本页面被访问 | 您是第位小伙伴

© 炫彩信息科技有限公司 版权所有 备案号 : 赣ICP备19008485号