MySQL中的常用函数

以下示例均操作user表

mysql> show create table user\G
*************************** 1. row ***************************
       Table: user
Create Table: CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` char(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

表中数据

mysql> select * from user;
+----+---------------+
| id | name          |
+----+---------------+
|  1 | sujianhui     |
|  2 | zhaojianwei   |
|  3 | zhnangwenyuan |
|  4 | fuqiang       |
|  5 | sujianhui_1   |
|  6 | sujianhui_2   |
|  7 |               |
|  8 | NULL          |
+----+---------------+
8 rows in set (0.00 sec)

字符串函数

CONCAT(name1,name2,…)

mysql> select concat(1,name),name from user;
+----------------+---------------+
| concat(1,name) | name          |
+----------------+---------------+
| 1sujianhui     | sujianhui     |
| 1zhaojianwei   | zhaojianwei   |
| 1zhnangwenyuan | zhnangwenyuan |
| 1fuqiang       | fuqiang       |
| 1sujianhui_1   | sujianhui_1   |
| 1sujianhui_2   | sujianhui_2   |
| 1              |               |
| NULL           | NULL          |
+----------------+---------------+
8 rows in set (0.00 sec)

mysql> select concat(1,id,name),name from user;
+-------------------+---------------+
| concat(1,id,name) | name          |
+-------------------+---------------+
| 11sujianhui       | sujianhui     |
| 12zhaojianwei     | zhaojianwei   |
| 13zhnangwenyuan   | zhnangwenyuan |
| 14fuqiang         | fuqiang       |
| 15sujianhui_1     | sujianhui_1   |
| 16sujianhui_2     | sujianhui_2   |
| 17                |               |
| NULL              | NULL          |
+-------------------+---------------+
8 rows in set (0.00 sec)

注意:CONCAT的参数操作项中含有NULL值,那么返回值也为NULL

concat_ws()
使用指定分隔符连接,这个函数多用在多字段联合组成唯一ID的场景,比如在组织架构的数据表中,子级部门可能会存在重名现象,此时就需要创建唯一ID

concat_ws('_',business_name,department_1,department_2,department_3,department_4) as unique_id

注意:concat_ws的参数操作项中含有NULL 、’’ 值时的区别

*      concat_ws( '_', business_name, department_1, department_2 ) AS unique_code
    *      department_2 为 ''     =>   墨丘利事业部_墨丘利产研_
    *      department_2 为 NULL   =>   墨丘利事业部_墨丘利产研    
    * 
    *      两种方式产生的 unique_code 相差一个 "_" 字符

LEFT(str,n)
RIGHT(str,n)

mysql> select left(name,2),right(name,2) from user where id=1;
+--------------+---------------+
| left(name,2) | right(name,2) |
+--------------+---------------+
| su           | ui            |
+--------------+---------------+
1 row in set (0.00 sec)

LTRIM(str)
RTRIM(str)
TRIM(str)

SUBSTRING(str,x,y):从第x位开始y个字符

mysql> select substring(name,1,3) from user where id=1;
+---------------------+
| substring(name,1,3) |
+---------------------+
| suj                 |
+---------------------+
1 row in set (0.00 sec)

REPLACE(str,a,b) 将字符串str中所有的字符串a替换为字符串b

mysql> select replace(name,'zhao','su') from user;
+---------------------------+
| replace(name,'zhao','su') |
+---------------------------+
| sujianhui                 |
| sujianwei                 |
| zhnangwenyuan             |
| fuqiang                   |
| sujianhui_1               |
| sujianhui_2               |
|                           |
| NULL                      |
+---------------------------+
8 rows in set (0.00 sec)

INSERT(str,x,y,a):将指定字符串str从第x字符开始,y长度的字符子串替换为字符串a

mysql> select insert(name,1,4,'su') from user;
+-----------------------+
| insert(name,1,4,'su') |
+-----------------------+
| suanhui               |
| sujianwei             |
| sungwenyuan           |
| suang                 |
| suanhui_1             |
| suanhui_2             |
|                       |
| NULL                  |
+-----------------------+
8 rows in set (0.00 sec)

LPAD(str,n,pad)
RPAD(str,n,pad):将字符串str以pad填充到指定长度n

mysql> select lpad(name,5,'a') from user where id=7;
+------------------+
| lpad(name,5,'a') |
+------------------+
| aaaaa            |
+------------------+
1 row in set (0.00 sec)

REPEAT(str,n)

mysql> select repeat('su',11);
+------------------------+
| repeat('su',11)        |
+------------------------+
| sususususususususususu |
+------------------------+
1 row in set (0.00 sec)

LOWER(str)
UPPER(str)
STRCMP(str1,str2):比较字符串str1与str2的ANSII码值大小。相等返回0,s1>s2 返回1,s1<s2 返回-1
LOCATE 返回字符在字段中第一次出现的位置 没有出现则返回0 select LOCATE(‘第一军团’,name) from region

Tips:php中有一个函数preg_replace(),该函数的作用是对字符串中符合正则匹配的部分进行替换

<?php

    $name="sujianhui-A9"
    preg_replace('/-[A-Z0-9]+$/',"",$name);

在数据分析时,难免会遇到数据不规则的情况,但是mysql中又没有与preg_replace()一般作用的函数,此时可以几个mysql函数的配合使用,
达到正则匹配替换的作用

name字段的值类似 sujianhui-A9,sujianhui-B9A3格式,想要过滤掉中横线以及其后部分
mysql> select replace(name, SUBSTR(name, LOCATE('-', name), length(name)), '') AS name from user

依照这种思路可以组合出很多功能丰富的自定义函数

数值函数

  • RAND() 返回小于1的随机数
  • MOD(a,b) 求模
  • FLOOR(x) 舍去取整
  • CEIL(x) 进一取整
  • ROUND(m,n) 四舍五入
  • TRUNCATE(m,n) 截断保存小数

时间函数

CURDATE() 返回当前年月日
CURTIME() 返回当前时间
NOW() 返回当前完整时间

mysql> select curdate(),curtime(),now();
+------------+-----------+---------------------+
| curdate()  | curtime() | now()               |
+------------+-----------+---------------------+
| 2018-12-29 | 12:18:24  | 2018-12-29 12:18:24 |
+------------+-----------+---------------------+
1 row in set (0.05 sec)

UNIX_TIMESTAMP(‘2018-12-12’): 将日期格式字符串转化为时间戳
FROM_UNIXTIME(1543981584) :将时间戳转化为日期格式字符串

mysql> select UNIX_TIMESTAMP('2018-12-12'),FROM_UNIXTIME(1543981584);
+------------------------------+---------------------------+
| UNIX_TIMESTAMP('2018-12-12') | FROM_UNIXTIME(1543981584) |
+------------------------------+---------------------------+
|                   1544544000 | 2018-12-05 11:46:24       |
+------------------------------+---------------------------+
1 row in set (0.00 sec)

MONTHNAME(date) 返回月份的英文名
WEEK(date)
YEAR(date)
HOUR(date)

mysql> select week('2018-12-12'),year('2018-12-12'),hour('2018-12-12 12:00:00');
+--------------------+--------------------+-----------------------------+
| week('2018-12-12') | year('2018-12-12') | hour('2018-12-12 12:00:00') |
+--------------------+--------------------+-----------------------------+
|                 49 |               2018 |                          12 |
+--------------------+--------------------+-----------------------------+
1 row in set (0.00 sec)

DATE_FORMATE(date,fmt)

DATE_ADD():计算当前时间指定时间段后日期。。
DATE_DIFF():计算两个时间点之间相差天数

mysql> select date_add(now(),INTERVAL 31 day) afterdays,now();
+---------------------+---------------------+
| afterdays           | now()               |
+---------------------+---------------------+
| 2019-01-29 13:33:18 | 2018-12-29 13:33:18 |
+---------------------+---------------------+
1 row in set (0.00 sec)

mysql> select date_diff('2018-12-29','2019-02-04');
ERROR 1305 (42000): FUNCTION qq.date_diff does not exist
mysql> select datediff('2018-12-29','2019-02-04');
+-------------------------------------+
| datediff('2018-12-29','2019-02-04') |
+-------------------------------------+
|                                 -37 |
+-------------------------------------+
1 row in set (0.00 sec)

流程函数

IF(ifvalue,value1,value2)

mysql> select if(name='','my name is empty',name) from user;
+-------------------------------------+
| if(name='','my name is empty',name) |
+-------------------------------------+
| sujianhui                           |
| zhaojianwei                         |
| zhnangwenyuan                       |
| fuqiang                             |
| sujianhui_1                         |
| sujianhui_2                         |
| my name is empty                    |
| NULL                                |
+-------------------------------------+
8 rows in set (0.00 sec)

IFNULL(ifvalues,value1)

mysql> select ifnull(name,'my name is null') from user;

+--------------------------------+
| ifnull(name,'my name is null') |
+--------------------------------+
| sujianhui                      |
| zhaojianwei                    |
| zhnangwenyuan                  |
| fuqiang                        |
| sujianhui_1                    |
| sujianhui_2                    |
|                                |
| my name is null                |
+--------------------------------+
8 rows in set (0.00 sec)

IF系列流程函数在进行数据分析时使用的频率非常高,比如现在有一个job_details,表中对每个销售人员按天维度进行流水记录

| date       | tuition  | em | 
| 2019-04-20 | -3237.00 | xo | 
| 2019-04-19 | 225.25   | xo | 
| 2019-04-18 | 0.00     | xo | 
| 2019-04-17 | 0.00     | xo | 
| 2019-04-16 | 5582.81  | xo | 
| 2019-04-15 | 9685.21  | xo | 
| 2019-04-14 | 0.00     | xo | 
| 2019-04-13 | 0.00     | xo | 
 ... 

现想按月汇总每个销售的流水,如下

SELECT `em`
, ceil(SUM(IF(`date` BETWEEN '2019-04-01' AND '2019-04-31', `tuition`, 0))) AS tuition_month_4
, ceil(SUM(IF(`date` BETWEEN '2019-03-01' AND '2019-03-31', `tuition`, 0))) AS tuition_month_3
, ceil(SUM(IF(`date` BETWEEN '2019-02-01' AND '2019-02-28', `tuition`, 0))) AS tuition_month_2
, ceil(SUM(IF(`date` BETWEEN '2019-01-01' AND '2019-01-31', `tuition`, 0))) AS tuition_month_1
FROM `job_details`
WHERE `date` BETWEEN '2019-01-01' AND '2019-04-31'
GROUP BY `em`    

case when exp then … else … end

mysql> select case when name is null then 'name is null' when name='' then 'name is empty' else name end from user;
+--------------------------------------------------------------------------------------------+
| case when name is null then 'name is null' when name='' then 'name is empty' else name end |
+--------------------------------------------------------------------------------------------+
| sujianhui                                                                                  |
| zhaojianwei                                                                                |
| zhnangwenyuan                                                                              |
| fuqiang                                                                                    |
| sujianhui_1                                                                                |
| sujianhui_2                                                                                |
| name is empty                                                                              |
| name is null                                                                               |
+--------------------------------------------------------------------------------------------+
8 rows in set (0.01 sec)

case … when exp then … else … end,这个函数在进行数据拆分重组时非常有用。举个例子:我上学那会,六年级属于初中的一年级,五年级为小学的最后一级,创建数据表存储层级关系:

mysql> create table origanization(
    id int(11) auto_increment primary key,
    org1 char(50),
    org2 char(50),
    org3 char(50)
);
Query OK, 0 rows affected (0.26 sec)

mysql> desc origanization;
+-------+----------+------+-----+---------+----------------+
| Field | Type     | Null | Key | Default | Extra          |
+-------+----------+------+-----+---------+----------------+
| id    | int(11)  | NO   | PRI | NULL    | auto_increment |
| org1  | char(50) | YES  |     | NULL    |                |
| org2  | char(50) | YES  |     | NULL    |                |
| org3  | char(50) | YES  |     | NULL    |                |
+-------+----------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

将阶段、年级、班级数据存入表
mysql> insert into origanization(org1,org2,org3)
values(‘小学’,’五年级’,’一班’),
(‘小学’,’五年级’,’2班’),
(‘初中’,’六年级’,’一班’),
(‘初中’,’六年级’,’2班’),
(‘高中’,’高一’,’一班’),
(‘高中’,’高一’,’2班’);
Query OK, 6 rows affected (0.18 sec)
Records: 6 Duplicates: 0 Warnings: 0

mysql> select * from origanization;
+----+--------+-----------+--------+
| id | org1   | org2      | org3   |
+----+--------+-----------+--------+
|  1 | 小学   | 五年级    | 一班   |
|  2 | 小学   | 五年级    | 2班    |
|  3 | 初中   | 六年级    | 一班   |
|  4 | 初中   | 六年级    | 2班    |
|  5 | 高中   | 高一      | 一班   |
|  6 | 高中   | 高一      | 2班    |
+----+--------+-----------+--------+
6 rows in set (0.00 sec)

但是等我弟弟上学时,六年级被划分成为小学的最后一级,因为种种原因,不能更改原数据,但是需要查看新的划分关系,可以使用case函数生成中间表:

mysql> select case org2 when '六年级' then '小学' else org1 end as new_org1,org2,org3 from origanization;
+----------+-----------+--------+
| new_org1 | org2      | org3   |
+----------+-----------+--------+
| 小学     | 五年级    | 一班   |
| 小学     | 五年级    | 2班    |
| 小学     | 六年级    | 一班   |
| 小学     | 六年级    | 2班    |
| 高中     | 高一      | 一班   |
| 高中     | 高一      | 2班    |
+----------+-----------+--------+
6 rows in set (0.00 sec)

特别注意,case后的字段不能直接在where条件中使用,如需使用需要以中间表的形式引用

mysql> select case org2 when '六年级' then '小学' else org1 end as new_org1,org2,org3 from origanization where new_org1='小学';
ERROR 1054 (42S22): Unknown column 'new_org1' in 'where clause'

其它函数

  • DATABASE()
  • VERSION()
  • USER()
  • PASSWORD(str)
  • MD5(str);

聚合函数

sum()

sum()函数不单可以用来求和,有时也可以用来计数

select ...
// 在职人数
"sum(if(a.info_write_time <= :end_time and (a.last_working_day>=:start_time or a.last_working_day is null),1,0)) as now_work",
// 待离职人数
"sum(if(a.last_working_day >= :end_time and a.last_working_day is not null,1,0)) as ready_to_quit",
...
group by 部门

count()

count()通常用来对符合条件的记录进行计数

avg()

根据分组时各自检索到的数据行数进行求平均数

select avg(work_hour) as avg_work_hour,em from compile_add_on_work_hour group by em

小结

MySQL有很多内置函数,功能实用且性能高效,在空闲之余应该经常看一看MySQL的官方手册

php实用函数及其使用场景

提取数组中的指定列的值,获取列值的集合

array_column($array,$column_name)

提取快速提取某个字段作为key值

$people = [
    ['name' => 'sujianhui', 'age' => 19],
    ['name' => 'sujianhui1','age' => 19],
    ['name' => 'sujianhui2','age' => 19],
    ['name' => 'sujianhui3','age' => 19],
    ['name' => 'sujianhui4','age' => 19],
    ['name' => 'sujianhui5','age' => 19],
    ['name' => 'sujianhui6','age' => 19],
];

$a = array_column($people,null,'name');

Tips:如果同一值出现了多次,则保留最后一个

$a = array_column($people,null,'age'); 

array (size=1)
  19 => 
    array (size=2)
      'name' => string 'sujianhui6' (length=10)
      'age' => int 19

array_column($a,'name','gender') 提取 gender字段值为数组键 name 字段值 为数组值的数组

交换一维数组的键值(如果同一值出现了多次,则最后一个键名将作为它的值)

array_flip() 

对二维数组进行排序

// $reference : 以多维数组中的哪个字段为排序参考
$reference = array_column($list,$orderByField);
array_multisort($reference,$orderByOrder === 'ASC' ? SORT_ASC : SORT_DESC,$list);

对数组进行分页

$chunkResult = array_chunk($list,$pageSize);
return $chunkResult[$_GET['page]];

数组分段

// 1:1:3:3:2
$interval_1_length = (int)ceil($count * 0.1);

$offset = 0;
$labelS = array_slice($result,$offset,$interval_1_length);
$offset += $interval_1_length;

$labelA = array_slice($result,$offset ,$interval_1_length);
$offset += $interval_1_length;

$labelB = array_slice($result,$offset,$interval_1_length * 3);
$offset += $interval_1_length*3;

$labelC = array_slice($result,$offset,$interval_1_length * 3);
$offset += $interval_1_length*3;

$labelD = array_slice($result,$offset);

计算数组的交集

array_intersect($arr1,$arr2,...)     

计算数组的差集

array_diff($arr1,$arr2...)    

Tips:array_intersect(),array_diff()的返回值会保留原key

<?php
$a = [
    'sujianhui',
    'zhaojianwei',
    'fuqiang',
    'zhangwenyuan',
    'baoxinyu'
];

$b = [
    'sujianhiu',
    'fuqiang',
    'zhangyuteng'
];

var_dump(array_merge($a,$b));
var_dump(array_diff($a,$b));
var_dump(array_intersect($a,$b));

结果

/home/www/sll/level7.php:16:
array (size=8)
  0 => string 'sujianhui' (length=9)
  1 => string 'zhaojianwei' (length=11)
  2 => string 'fuqiang' (length=7)
  3 => string 'zhangwenyuan' (length=12)
  4 => string 'baoxinyu' (length=8)
  5 => string 'sujianhiu' (length=9)
  6 => string 'fuqiang' (length=7)
  7 => string 'zhangyuteng' (length=11)
/home/www/sll/level7.php:17:
array (size=4)
  0 => string 'sujianhui' (length=9)
  1 => string 'zhaojianwei' (length=11)
  3 => string 'zhangwenyuan' (length=12)
  4 => string 'baoxinyu' (length=8)
/home/www/sll/level7.php:18:
array (size=1)
  2 => string 'fuqiang' (length=7)

array_diff`array_intersect会保留原键值的索引,array_merge`不会

批量设置一维数组的值,常用户生成数组的模版

$a = [
    'department_1' => 'sdfdddd1',
    'department_2' => 'sdfdddd2',
    'department_3' => 'sdfdddd3',
    'department_4' => 'sdfdddd4',
    'department_5' => 'sdfdddd5',
];

// array_combine 要求两个参数的元素个数相同
$fillResult = array_combine(array_keys($a),array_fill(0,count($a),0));

打印结果

/Users/www/SLL/level7.php:20:
array(5) {
  'department_1' =>
  int(0)
  'department_2' =>
  int(0)
  'department_3' =>
  int(0)
  'department_4' =>
  int(0)
  'department_5' =>
  int(0)
}

获取关联数组的第一个元素

$tpl = empty($addOnList) ? [] : $addOnList[key($addOnList)];

瞎叨叨一句

下午碰见一个问题 Division by zero
问题很简单 ,除数为0 ,但我已经对0进行过滤,那么问题应该出现在 输入格式为 0 的变种格式,导致过滤效果不完全,
但是如何抓取异常的变种数据呢,在报错之前进行日志记录,这样当异常出现后,去日志中查看最后一条数据及问引发异常的数据。例如

/**
 * "0.00" 是一个字符串 同时也是一个 数组格式字符串 但是它不为空
 * @param $u1
 * @param $u2
 * @return string
 */
public static function ringRatio($u1,$u2)
{
    if(is_numeric($u1) && is_numeric($u2)){
        $u1 += 0;
        $u2 += 0;
        return empty($u2) ? "-%" : (round(($u1 - $u2) / $u2,4) * 100)."%";
    }else{
        return '-';
    }
}

SQL基础

SQL简介

SQL(structure query language) 结构化查询语言,是一种对 基于关系模型构建的数据库 进行数据检索的语言工具,MySQL 是标准SQL的扩展。

SQL分类

  • DDL语句 data defintion language 数据定义语句 create、drop alter
  • DML语句 data manipolution language 数据操纵语句 insert delete update select(增删改查)
  • DCL语句 data control language 数据控制语句 控制访问级别等 grant revoke

DDL语句

创建数据库

create database db_name

删除数据库

drop database db_name

创建数据表

create table table_name

删除数据表

drop table table_name

创建索引

create [index,upique index] 索引名 on 表名(列名)
mysql> create index `index` on ttttt(name);

修改数据表中列名

alter table `table_name` change column `old_column_name` `new_column_name` INT(11) NOT NULL DEFAULT '0' after `id`;

数据表中新加列

alter table `table_name` add 列名 列类型 [first,after 列名]

数据表中删除列

alter table `table_name` drop 列名;

DML语句

插入单条数据

insert into xxoo1(`name`,`sex`) values('machine',1)

批量插入

insert into xxoo1(`name`,`sex`) values('machine1',1),('machine2',2),(),()...

使用结果集插入

insert into xxoo1(`name`,`sex`) select `name`,`sex` from xxoo1

REPLACE INTO

replace into xxoo1(`name`,`sex`) values('machine',2)

replace into 语句实质为 删除原数据,插入新数据 两个操作结合,通过对比下面column_2值为0的数据项可验证

mysql> select * from replace_table;
+----+----------+----------+
| id | column_2 | name     |
+----+----------+----------+
|  1 |        0 | NULL     |
|  3 |     3333 | machine3 |
|  4 |      222 | machine4 |
+----+----------+----------+
3 rows in set (0.00 sec)

mysql> replace into replace_table(name) values('machine4');
Query OK, 2 rows affected (0.03 sec)

mysql> select * from replace_table;
+----+----------+----------+
| id | column_2 | name     |
+----+----------+----------+
|  3 |     3333 | machine3 |
|  4 |      222 | machine4 |
|  5 |        0 | machine4 |
+----+----------+----------+
3 rows in set (0.00 sec)

删除表中数据,不归零主键

delete from `table_name` 条件

清空表中数据 ,重新归零主键

truncate table 表名

例如

mysql> show create table ttttt\G
*************************** 1. row ***************************
       Table: ttttt
Create Table: CREATE TABLE `ttttt` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `column_2` int(11) NOT NULL DEFAULT '0',
  `name` char(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `column_2` (`column_2`),
  KEY `index` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)


mysql> select * from ttttt;
+----+----------+----------+
| id | column_2 | name     |
+----+----------+----------+
|  6 |      222 | machine4 |
|  7 |     3333 | machine3 |
+----+----------+----------+
2 rows in set (0.00 sec)

mysql> truncate table ttttt;
Query OK, 0 rows affected (0.14 sec)

mysql> select * from ttttt;
Empty set (0.00 sec)

更新字段值

update `table_name` set 列名=列值  条件

查询

select `id`,`colunm_2` from `replace_table` 条件

聚合函数

select * from compile_foo where date=201811 group by businessUnit

非严格模式下:使用聚合函数对结果集进行分类时,后面的子字段保留第一次匹配的结果.严格模式下sql_mode=only_full_group_by。对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中,所以对于设置了这个mode的数据库,在使用group by 的时候,就要用MAX(),SUM(),ANT_VALUE()这种聚合函数,才能完成GROUP BY 的聚合操作。

连接查询分为内连接与外链接

内连接 只显示相互匹配的数据

select qa_type.name,qa_content_new.title from qa_type,qa_content_new where qa_content_new.type_id=qa_type.id

外链接 会显示未匹配的部分

左查询  left join on
右查询  rignt join on
       inner join

结果集联合 union (去重 distinct)| union all

select type_id from qa_content_new union all select id from qa_type
select type_id from qa_content_new union select id from qa_type

DCL语句

查看帮助

在命令行中使用 ? + 关键字可快速检索mysql自带帮助文档

eg.1

mysql> ? contents
You asked for help about help category: "Contents"
For more information, type 'help <item>', where <item> is one of the following
categories:
   Account Management
   ...

eg.2

mysql> ? insert
Name: 'INSERT'
Description:
Syntax:
    ...

eg.3

mysql> ? int
Name: 'INT'
Description:
INT[(M)] [UNSIGNED] [ZEROFILL]

A normal-size integer. The signed range is -2147483648 to 2147483647.
The unsigned range is 0 to 4294967295.

URL: http://dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html

其它常用语句

查看当前所在数据库

mysql> select database();
+------------+
| database() |
+------------+
| qq         |
+------------+

切换数据库

use db_name;

查看建表语句

create table table_name 或 desc table_name

复制一个数据表,只复制表结构

create table user_copy like user

复制一个数据表,包括结构、数据

create table user_copy select * from user [ 后面可以接条件 where id > 1000 ...]

表数据复制插入(前提是 user_copy 与 select 子句后跟字段排列一致)

insert into user_copy name,id_card ... select name,id_card ... from user

ffmpeg安装过程中涉及到的知识点

昨天协助同事安装 ffmpeg 编码mp3文件 遇到几个问题,记录一下

1 configure时无法通过,依赖问题主要依赖 lame、x264。

这个没什么好说的,依赖什么装什么,可以使用yum安装就用yum ,无法使用yum就 download 、configure、make && make install 素质三连
有些工具安装完事后需要自己手动创建软链,注意安装完依赖后which一下
注意下载时的三个点,这几点的相关信息一般在configure的错误提示或者日志里都有记录,出现问题优先从这里寻找帮助,然后是 baidu google

    安装目录的指定  
    尽量去官方网站下载源码包
    注意源码包的版本要相互匹配

    ffmpeg 4.1
    lame 3.100
    x264 最新版    

./configure --prefix=/usr/local/ffmpeg --enable-libmp3lame --enable-libx264 --enable-gpl --disable-x86asm

2 安装完依赖,安装 ffmpeg,这个过程没碰见什么大问题,问题出现在运行ffmpeg时

[root@iz2zeiaj6kd5kgjklk2er2z ~]# ffmpeg -i 075b352cd6b628c1b964ec661127d4fa.mp3 self.mp3
ffmpeg: error while loading shared libraries: libmp3lame.so.0: cannot open shared object file: No such file or directory

别慌,这个是路径问题,翻译过来就是不能在共享库中找到 libmp3lame.so.0 文件.共享库也叫动态链接库
(什么是动态链接库与静态链接库:https://blog.csdn.net/li1914309758/article/details/82145023#comments)
这个问题是怎么产生的呢?

这里需要了解加一个文件 /etc/ld.so.conf

/etc/ld.so.conf 此文件记录了编译时使用的动态库的路径,也就是加载so库的路径。默认情况下,编译器只会使用/lib和/usr/lib这两个目录下的库文件,而通常通过源码包进行安装时,如果不
指定–prefix会将库安装在/usr/local目录下,而又没有在文件/etc/ld.so.conf中添加/usr/local/lib这个目录>。这样虽然安装了源码包,但是使用时仍然找不到相关的.so库,就会报错。也就是说系统不知道安装了源码包。
对于此种情况有2种解决办法:

(1)在用源码安装时,用--prefix指定安装路径为/usr/lib。这样的话也就不用配置PKG_CONFIG_PATH
 (2) 直接将路径/usr/local/lib路径加入到文件/etc/ld.so.conf文件的中。在文件/etc/ld.so.conf中末尾直接添加:/usr/local/lib(这个方法给力!编辑完后注意使用 ldconfig 刷新缓存)
 (3) 如果想重新编译 可以创建软链

ldconfig

ldconfig 这个程序,位于/sbin下,它的作用是将文件/etc/ld.so.conf列出的路径下的库文件缓存到/etc/ld.so.cache以供使用,因此当安装完一些库文件,或者修改/etc/ld.so.conf增加了库的新的搜索路径,需要运>行一下ldconfig,使所有的库文件都被缓存到文件/etc/ld.so.cache中,如果没做,可能会找不到刚安装的库。

所以,我们看一下/etc/ld.so.conf文件

root@iz2zeiaj6kd5kgjklk2er2z ~]# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
/lib
/usr/lib
/usr/lib64
/usr/local/lib64

我们使用ldd查看一下 ffmpeg 动态链接库的依赖关系

[root@iz2zeiaj6kd5kgjklk2er2z ~]# ldd `which ffmpeg`
    linux-vdso.so.1 =>  (0x00007ffc3b9a0000)
    libm.so.6 => /usr/lib64/libm.so.6 (0x00007f34d9d94000)
    libxcb.so.1 => /usr/lib64/libxcb.so.1 (0x00007f34d9b6b000)
    libxcb-shm.so.0 => /usr/lib64/libxcb-shm.so.0 (0x00007f34d9967000)
    libxcb-shape.so.0 => /usr/lib64/libxcb-shape.so.0 (0x00007f34d9763000)
    libxcb-xfixes.so.0 => /usr/lib64/libxcb-xfixes.so.0 (0x00007f34d955a000)
    libbz2.so.1 => /usr/lib64/libbz2.so.1 (0x00007f34d934a000)
    libz.so.1 => /usr/lib64/libz.so.1 (0x00007f34d9134000)
    libiconv.so.2 => /usr/local/lib/libiconv.so.2 (0x00007f34d8e4d000)
    liblzma.so.5 => /usr/lib64/liblzma.so.5 (0x00007f34d8c27000)
    libmp3lame.so.0 => /lib64/libmp3lame.so.0 (0x00007f34d8999000)
    、、、
    libx264.so.157 => not found
    、、、
    libpthread.so.0 => /usr/lib64/libpthread.so.0 (0x00007f34d877c000)
    libc.so.6 => /usr/lib64/libc.so.6 (0x00007f34d83b9000)
    /lib64/ld-linux-x86-64.so.2 (0x0000563a704e3000)
    libXau.so.6 => /usr/lib64/libXau.so.6 (0x00007f34d81b4000)

看到没 ffmpeg在链接动态库时去usr/lib64/目录下寻找,但是 libmp3lame.so.0 被安装在了 /usr/local/lib/
创建一个软链

ln -s /usr/local/lib/libmp3lame.so.0.0.0 /usr/lib64/libmp3lame.so.0

然后运行 ffmpeg
ffmpeg: error while loading shared libraries: libx264.so.157: cannot open shared object file: No such file or directory
再创建

ln -s /usr/local/lib/libx264. /usr/lib64/libx264.so.157

再运行

[root@iz2zeiaj6kd5kgjklk2er2z ~]# ffmpeg
ffmpeg version 4.1 Copyright (c) 2000-2018 the FFmpeg developers
  built with gcc 4.8.5 (GCC) 20150623 (Red Hat 4.8.5-36)
  configuration: --prefix=/usr/local/ffmpeg --enable-libmp3lame --enable-libx264 --enable-gpl --disable-x86asm
  libavutil      56. 22.100 / 56. 22.100
  libavcodec     58. 35.100 / 58. 35.100
  libavformat    58. 20.100 / 58. 20.100
  libavdevice    58.  5.100 / 58.  5.100
  libavfilter     7. 40.101 /  7. 40.101
  libswscale      5.  3.100 /  5.  3.100
  libswresample   3.  3.100 /  3.  3.100
  libpostproc    55.  3.100 / 55.  3.100
Hyper fast Audio and Video encoder
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...            

收工

参考资料

ldd 命令简介  https://www.cnblogs.com/zhangjxblog/p/7776556.html
解决运行 ffmpeg 时动态库 not found 问题 https://www.cnblogs.com/joshua317/articles/5478622.html
ffmpeg 依赖的安装 http://blog.chinaunix.net/uid-11344913-id-3930867.html
ffmpeg 官方下载地址 http://www.ffmpeg.org/releases/

ffmpeg 依赖安装x264 https://www.cnblogs.com/cxchanpin/p/6943221.html

司龄的计算

司龄的计算策略

2019-05-31  入职 到 2019-06-30 司龄 0 2019-07-01 为 1
2019-01-29  入职 到 2019-02-28 司龄 0 2019-03-01 为 1
2019-05-12  入职 到 2019-06-11 司龄 0 2019-06-12 为 1

分析问题

主要分以下几种情况

跨年跨月``
跨年不跨月
不跨年跨月
不跨年不跨月

处理闰月、大小月

将这几种情况处理完成后,合并代码

$entryDt   = '2019-01-29';
$currentDt = '2019-05-23';

$workMonth = 0;

list($entryYear,$entryMonth,$entryDay) = explode('-',$entryDt);
list($currentYear,$currentMonth,$currentDay) = explode('-',$currentDt);

$entryMonth    = (int)$entryMonth;
$entryDay      = (int)$entryDay;
$currentMonth  = (int)$currentMonth;
$currentDay    = (int)$currentDay;

if($entryYear < $currentYear ){

    $diff_y = $currentYear - $entryYear;

    if($diff_y > 0 ){
        $workMonth += ($diff_y - 1) * 12 + (12 - $entryMonth) + ($currentMonth - 1);
    } else {
        $workMonth += $currentMonth - $entryMonth - 1;
    }

    /*

     */
    if($currentDay >= $entryDay){
        $workMonth += 1;
    }

}

使用socket实现网络进程间通信

环境介绍

CentOS release 6.10 (Final)
gcc version 4.4.7 20120313 (Red Hat 4.4.7-23) (GCC) Thread model: posix
Linux host 2.6.32-431.3.1.el6.x86_64 #1 SMP Fri Jan 3 21:39:27 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

实现目标

采用C/S架构,采用多进程的方式 可同时处理多个来自于不同客户端的socket连接
客户端可以通过终端进行信息发送

代码

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h> // 网络地址转换
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h> // 信号集
#include <string.h>

int main(int argc, const char * argv[]) {

    int server_fd;
    struct sockaddr_in server_addr;

    /*
     参数说明
        SOCK_STREAM  tcp
        SOCK_DGRAM udp    视频 音频 (允许部分失真)

     函数介绍

     socket函数可以创建一个套接字。默认情况,内核会认为socket函数创建的套接字是
     主动套接字(active socket),它存在于一个连接的客户端。
     而服务器调用listen函数告诉内核,该套接字是被服务器而不是客户端使用的,
     即listen函数将一个主动套接字转化为监听套接字(下文以 listenfd 表示)。监听套接字可以接受来自客户端的连接请求。

     如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

     */
    server_fd = socket(AF_INET,SOCK_STREAM,0);

    // 申请内存 地址清零/初始化
    memset(&server_addr,0,sizeof(server_addr));

    /*
        获取IPV4的地址信息;
     */
    server_addr.sin_family = AF_INET;

    /*
        将监听端口的整型变量从主机字节顺序转变成网络字节顺序

        字节序问题只有在读取的时候才会出现

        大端法  人类习惯的表示方式。12345 一万两千三百四十五
        小端法  计算机电路从低位开始处理效率高 五万四千三百二十一

     在设定地址之前务必对地址 进行 主机字节序到网络字节序的转换,否则会引起
     todo 网络上进行 字符串/文件传输时 是否需要进行字节序转换 ? 我理解的是 需要 ,这样 不论数据的发送或者接收 都需要进行字节序转换

     */
    server_addr.sin_port   = htons(9100);

    /*
     如果你的服务器有多个网卡(每个网卡上有不同的IP地址),而你的服务(不管是在udp端口上侦听,还是在tcp端口上侦听),出于某种原因:
     可能是你的服务器操作系统可能随时增减IP地址,也有可能是为了省去确定服务器上有什么网络端口(网卡)的麻烦 —— 可以要在调用bind()的时候,告诉操作系统:“我需要在 yyyy 端口上侦听,所有发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都是我处理的。”这时候,服务器程序则在0.0.0.0这个地址上进行侦听。例如:

          回环地址 127.0.0.1 : 80 端口
     局域网地址 172.16.225.67 : 80 端口
     外网接口 111.198.190.132 :80 端口

     不论外部信息发送到上述三个IP中任意一个的 80端口 都可以被 server接收

     127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。

     因为 tcp udp 都具有自己的端口号(范围均为 0-2^16-1) 所以 udp :80 是不同于 tcp:80 的地址

     所以 利用三元组(ip地址,协议,端口)就可以标识唯一的网络的进程

     */

    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    /* todo 为什么这里不能用 (struct sockaddr_in *) &server_addr
     Incompatible pointer types passing 'struct sockaddr_in *' to parameter of type 'const struct sockaddr *'

     由调度程序
     目标进程 发送进程

     将ip+port 与 进程的fd之间的映射关系注册到 管理进程维护的表中


 注意 bind 调用主要完成了两件事 

    1  ip + port + fd 注册到内核空间中, 为进程声明唯一的通信标识
    2  bind 成功以后, 进程就可以向外进行信息发送了 如果作为客户端 从下一步开始就可以进行信息发送
    但是如果需要作为服务端 需要在接下来调用 listen 函数, 将端口注册监听队列中 ,由内核代为接收信息
    一般情况下 操作系统只对用于信息的端口进行安全防护 而对于发送则显的很宽容 

     */
    if( bind(server_fd,(struct sockaddr *) &server_addr,sizeof(struct sockaddr_in) ) < 0 ){
        perror("socket bind error");
    }

    /*

     sockfd 一个已绑定未被连接的套接字描述符
     backlog 连接请求队列(queue of pending connections)的最大长度

        listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务进程。

         (1) 执行listen 之后套接字进入被动模式。
         (2) 队列满了以后,将拒绝新的连接请求。客户端将出现连接D 错误WSAECONNREFUSED。
         (3) 在正在listen的套接字上执行listen不起作用

     */
    if(listen(server_fd,4) < 0 ){
        perror("listen op fail");
    }

    /*
        一个进程最多允许操作 1024个文件,一个fd代表一个文件
         并且 fd1 与fd2 默认分配给 stdin stdout
         所以 server fd 一般会从 3 开始

         [root@vagrant-centos65 IMserver]$>t server.c
          server start success fd is : 3
          wait connect ...

    */
    printf(" server start success fd is : %d \n",server_fd);

    // todo 如何使该进程作为daemon运行
    /*
        采用多进程的方式 可同时处理多个来自于不同客户端的 socket 连接。

        父进程进行监听,分配 子进程进行实际的 交互

         一般的,父进程在生成子进程之后会有两种情况:一是父进程继续去做别的事情,类似上面举的例子;另一是父进程啥都不做,一直在wait子进程退出。
         SIGCHLD信号就是为这第一种情况准备的,它让父进程去做别的事情,而只要父进程注册了处理该信号的函数,在子进程退出时就会调用该函数,
         在函数中wait子进程得到终止状态之后再继续做父进程的事情。

         最后,我们来明确以下二点:
         1)凡父进程不调用wait函数族获得子进程终止状态,回收子进程,子进程在退出后都会变成僵尸进程。
         2)SIGCHLD信号可以异步的通知父进程有子进程终止

        在进程树中 子进程终止后,会向父进程发送一个信号,通知父进程回收子进程的进程标示符。进程如何处理信号有三种选择。

     1)忽略该信号。有些信号表示硬件异常,例如,除以0或访问进程地址空间以外的单元等,因为这些异常产生的后果不确定,所以不推荐使用这种处理方式。

     2)按系统默认处理方式。

     3)提供一个函数,信号发生时调用这个函数,成为捕捉该信号。以实现按用户自定义的方式来处理信号。

        具体有以下几种情况

         父进程在子进程退出时仍在运行

            父进程wait() 子进程退出信号,回收子进程
            父进程提前注册 信号处理函数, 异步处理子进程
            父进程不负责对子进程进行回收,转交由内核(init进程 ,pid 为 1 ) 代为处理

        父进程先于子进程终止

            子进程退出后无进程负责回收,变成僵尸进程,占据一个进程id号

     */
    // linux 下 SIGCLD 就是 SIGCHLD
    signal(SIGCHLD,SIG_IGN);

    socklen_t client_len;
    int client_fd;
    struct sockaddr_in client_addr;
    char buffer[140];
    pid_t pid; // 子进程的 pid

    // todo 如何使 daemon 运行
    while(1){

        printf("wait connect  \n");
        client_len = sizeof(client_addr);

        /*
            主动阻塞主进程的执行 直至监听端口接收到客户端数据  唤醒主进程
            ?? client_fd 才是真正与客户端进行通讯的socket文件标示 server_fd socket文件中应该保存的是 客户端连接请求队列
            两点确定一条通信通道

            s:标识一个未连接socket
            name:指向要连接套接字的sockaddr结构体的指针
            namelen:sockaddr结构体的字节长度


         */
        if( (client_fd = accept(server_fd,(struct sockaddr *) &client_addr,&client_len)) == -1 ){
            perror("get connect from queue faild");
        }

        /*
            子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
            无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设
         */

        pid = fork();

        // 子进程
        if(pid == 0){

            // inet_ntoa 将一个32位网络字节序的二进制IP地址转换成相应的点分十进制的IP地址
            printf("server:get connect from %s port: %d \n",inet_ntoa(client_addr.sin_addr),client_addr.sin_port);
            send(client_fd,"hello \n",13,0);

             /*

               如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,

               recv先检查套接字s的接收缓冲区,

               1 如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待(阻塞、中断),直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓 冲中的数据copy到buf中

               2 当协议把数据接收完毕,s接收缓冲区中数据已经可读,recv函数就把s的接收缓冲中的数据copy到buf中(s的接收缓冲属于系统临界资源,使用完毕后要立即释放)
                (
                      注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把 s的接收缓冲中的数据全部copy完。
                      recv函数仅仅是copy数据,真正的接收数据是协议来完成的
                  )
                 (如果copy不完是不是意味着不会释放这缓冲区)

                    recv函数返回 实际copy的字节数。
                    如果recv在copy时出错,那么它返回SOCKET_ERROR;
                    如果recv函数在等待协议接收数据时网络中断了,那么它返回0

               3  (这种recv()的调用方式是阻塞调用)

               todo 如何区分阻塞套接字 与 非阻塞套接字






             */

            int  continueRecv = 1; // socket 对话是否需要继续
            char buff[100];        // 应用的数据接收缓存区
            size_t  buflen;        // 缓存区最大容量

            while(continueRecv)
            {

                buflen = recv(client_fd, buff, sizeof(buff), 0);

                //  SOCKET_ERROR 未定义 这个Linux的库函数里没有? win 下使用 ?
                if(buflen < 0 ){

                    continueRecv = 0;
                    perror("SOCKET_ERROR");

                } else if(buflen == 0){

                    // 这里表示对端的socket已正常关闭.
                    continueRecv = 0;
                    perror("network outage");


                } else {

                    if(buflen != sizeof(buff)){
                        printf("数据接收完毕 \n");
                    } else {
                        printf("数据未完全接收 \n");
                    }

                    printf("receive message: %s \n",buff);

                }

            }

            close(client_fd);
            printf("child process exit \n");
            exit(0);

        }else if(pid > 0){
            // 父进程
            printf("fork once \n");
        } else {
            printf("fork fail \n");
        }


    }

}

客户端实现

user.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>

#define BACKLOG 5
#define REMOTE_PORT 9100
#define MAX_RECEIVE 14
#define MAX_SEND 50
#define REMOTE_ADDR "172.16.124.67"

char * s_gets(char * pst,int n);

int main(void)
{
    int fd;
    int cfd; // client fd
    struct sockaddr_in serv_addr, client_addr;
    char buf[MAX_RECEIVE];
    char inputFrame[MAX_SEND];


    fd = socket(AF_INET,SOCK_STREAM,0);


    if(fd == -1){
        perror("server socket create error");
    } else {
        printf("server socket fd is %d \n",fd);
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_port        = htons(REMOTE_PORT);
    serv_addr.sin_addr.s_addr = inet_addr(REMOTE_ADDR);

    if( connect( fd,(struct sockaddr*) &serv_addr, sizeof(struct sockaddr)) == -1 ){
        printf("connect failed \n");
    } else {

        printf("connect success \n");
        recv(fd,buf,MAX_RECEIVE,0);  // 将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。  recv 会阻塞 、但是 send 不会阻塞
        printf("Received:%s \n",buf);
//
        while( s_gets(inputFrame,MAX_SEND) != NULL && inputFrame[0] != '\0' )
        {

            printf("----------entry------------\n");
            send(fd,inputFrame,MAX_SEND,0);
        }

    }

    // 停止 socket server
    if( close(fd) == 0 )
    {
        printf("socket close successful\n");
        exit(0);
    }
    else
    {
        printf("socket close fail\n");
        exit(1);
    }

    return 0;

}


char * s_gets(char * pst,int n)
{
    char * result_val;
    char * find;

    result_val = fgets(pst,n,stdin);
    if( result_val )
    {
        find = strchr(pst,'\n');

        if(find)
        {
            *find = '\0';
        }
        else
        {
            while( getchar() != '\n')
            {
                continue;
            }
        }
    }

    return result_val;
}
编译程序

因为开发环境虚拟机共享目录的权限问题,所以我自己写了一个简单shell,命令名为 t ,可以把编译生成的可执行文件输出到指定文件夹,并执行

[root@vagrant-centos65 ~]# which t
/root/bin/t
[root@vagrant-centos65 ~]# cat /root/bin/t
#! /bin/bash
gcc -o /home/machine_su/"$1".out $1
/home/machine_su/"$1".out
运行程序

首先,启动服务端程序,监听 9006 端口

[root@vagrant-centos65 webServer]# t main.c
socket fd is 3
PID:3506 PPID:3500 wait connect ...

检查端口是否监听成功

[root@vagrant-centos65 ~]# netstat -ntpl | grep 9100
tcp        0      0 0.0.0.0:9100                0.0.0.0:*                   LISTEN      3506/main.c.out

启动客户端程序,向服务端发起连接请求

[root@vagrant-centos65 webServer]# t user.c
server socket fd is 3
connect success: Success
Received:hello

进程的基本概念

进程

进程存在于操作系统之中

进程的状态

就绪状态:ready当一个进程获得了除CPU以外的一切所需资源,一旦得到CPU即可运行
执行状态:获得CPU程序正在执行,多CPU系统可同时存在多个当前进程
阻塞状态:block进程由于某种原因无法继续执行,放弃CPU变为暂停状态(例如等待IO、申请缓存空间等)
创建状态:create进程拥有了自己的PCB,但是还未进主存,不能被调度
终止状态:进程结束后,清除PCB,返还PCB空间

挂起状态:suspend()原语
激活状态:active()原语

进程状态的切换

1.进程阻塞过程

正在执行的进程,当发现上述某事件时,由于无法继续执行,于是进程便通过调用阻 塞原语 block 把自己阻塞。
可见,进程的阻塞是进程自身的一种主动行为。
进入 block 过程 后,由于此时该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状 态由“执行”改为“阻塞”,
并将 PCB 插入阻塞队列。
如果系统中设置了因不同事件而阻塞 的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列
最后,转调度程序 进行重新调度,将处理机分配给另一就绪进程并进行切换,亦即,
保留被阻塞进程的处理 机状态(在 PCB 中),再按新进程的 PCB 中的处理机状态设置 CPU 的环境。

2.进程唤醒过程

首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。block 原语和 wakeup 原语是一对作用刚好相反的原语。
因此,如果在某进程中调用了阻塞原语(进程本身自己主动调用block原语 阻塞自己,因为对临界资源是否使用完毕 由进程自己决定,调度程序无法感知),
则必须在与之相合作的另一进程中或其他相关的进程中安排唤醒原语,以能唤醒阻塞进程;
否则,被阻塞进程将会因不能被唤醒而长久地处于阻塞状态,从而再无机会继续运行。

3.进程的挂起

当出现了引起进程挂起的事件时,比如,用户进程请求将自己挂起,或父进程请求将自己的某个子进程挂起,
系统将利用挂起原语 suspend( )将指定进程挂起。挂起原语的执行过程是:首先检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪;对于活动阻塞状态的进程,则将之改为静止阻塞。

4.进程的激活过程

当发生激活进程的事件时,例如,父进程或用户进程请求激活指定进程,若该进程驻留在外存而内存中已有足够的空间时,则可将在外存上处于静止就绪状态的该进程换入内存。
这时,系统将利用激活原语 active( )将指定进程激活。激活原语先将进程从外存调入内存,
检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;
若为静止阻塞,便将之改为活动阻塞。
假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,应检查是否要进行重新调度,即由调度程序将被激活进程与当前进程进行优先级的比较,
如果被激活进程的优先级更低,就不必重新调度;否则,立即剥夺当前进程的运行,把处理机分配给刚被激活的进程。

进程的调度

进程的调度:进程在不停的排队,如何从队列中选取下一个执行对象取决于调度算法。

进程同步

进程同步的主要任务是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地[共享资源]和相互合作,从而使程序的执行具有可再现性。

临界资源
    在任意时刻,只允许一个进程正在使用

信号量 => 信号量集

    1 整型信号量  只可以进行P/V 操作(wait signal)即对缓存区资源数量的 +1 / -1 操作
    2 记录 型信号量 (数据结构中包含一个链表  用于实现让权等待)
    3 信号量集   (AND 信号量机制 的扩充 ,进程所需要的资源可能是多种,当多种资源全部都空闲时,一次性分配,否则不分配,避免死锁现象

管程机制

虽然信号量机制是一种既方便、又有效的进程同步机制,但每个要访问临界资源的进 程都必须自备同步操作 wait(S)和 signal(S)。
这就使大量的同步操作分散在各个进程中。这不仅给系统的管理带来了麻烦,而且还会因同步操作的使用不当而导致系统死锁。
这样,在解决上述问题的过程中,便产生了一种新的进程同步工具——管程(Monitors)。
(如果对程序不了解的开发人员为严格按照 临界资源的访问规则编写程序,那么很容易造成死锁)
这有点类似有几队人进行排队,信号量只提供一个排队参考,让每一队自己定义排队的规则,而管程则 倾向于强制提供排队规则,

管程的定义:
系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对该资源所执行的操作来表征该资源,而忽略了它们的内部结构和实现细节。
例 如,对一台电传机,可用与分配该资源有关的状态信息(busy 或 free)和对它执行请求与释放的操作,
以及等待该资源的进程队列来描述。又如,一个FIFO队列,可用其队长、队首和队尾以及在该队列上执行的一组操作来描述。

进程间通信(IPC)

本地进程间通信(单机中进程间通信)
    本地通过进程PID唯一标识一个进程

网络进程间通信
    三元组(ip地址,协议,端口)唯一标识一个进程,网络中的进程通信就可以利用这个标志与其它进程进行交互

按照通信的机制分类(3类)

    1 共享存储器系统

        共享数据结构 : 进程共用某些数据结构
        共享存储区   : 进程共享存储区

    2 消息传递系统(应用最为广泛的进程间通信机制,又称报文传递系统,优点:对分布式、网络、多处理机系统支持良好)

        根据实现方式的不同分为以下两种:
        直接通信
            发送进程直接调用OS提供的发送原语(原子性语句)send(Receiver,message)将信息发送至接收进程,接收进程调用接收原语receive(Sender,message)接收发送进程的信息
        间接通信
            发送进程调用OS提供的发送原语将消息发送至信箱(信箱是发送进程与接收进程约定共享的数据结构的一个实体,该实体用来暂存发送进程发送给目标进程的消息)
            接收进程从信箱中提取信息

    3 管道通信
        使用一个共享文件(俗称 pipe文件、管道文件)连接一对读写进程,实现进程间通信

        cat access.log | grep '2019-06-01'

        cat access.log 命令将输出信息写入pipe文件,写入完成后,管道符右边的 grep命令读取pipe文件内容,并将符合匹配规则的行输出到终端界面

    4 消息缓冲队列通信机制(广泛的应用在本地进程间通信)

        发送进程与接收进程之间存在一个消息队列(长度为N,最多容纳N条消息)
            发送进程可以向消息队列连续发送消息而不必等待(队列满时阻塞)
            接收进程可以直接从消息队列提取信息而不必等待(队列空时阻塞)

        由于该队列属于临界资源,所以执行insert原语前后,都要执行wait/signal操作
  • 互斥信号量 mutex
  • 资源信号量 sm
  • 消息队列 mq

Nginx负载均衡配置

测试环境
由于没有服务器,所以本次测试直接host指定域名,然后在VMware里安装了三台CentOS。

测试域名 :a.com

A服务器IP :192.168.5.149 (主)

B服务器IP :192.168.5.27

C服务器IP :192.168.5.126

部署思路
A服务器做为主服务器,域名直接解析到A服务器(192.168.5.149)上,由A服务器负载均衡到B服务器(192.168.5.27)与C服务器(192.168.5.126)上。

域名解析

由于不是真实环境,域名就随便使用一个a.com用作测试,所以a.com的解析只能在hosts文件设置。

打开:C:WindowsSystem32driversetchosts

在末尾添加

192.168.5.149 a.com

保存退出,然后启动命令模式ping下看看是否已设置成功

从截图上看已成功将a.com解析到192.168.5.149IP

A服务器nginx.conf设置
打开nginx.conf,文件位置在nginx安装目录的conf目录下。

在http段加入以下代码

upstream a.com {
server 192.168.5.126:80;
server 192.168.5.27:80;
}

server{
listen 80;
server_name a.com;
location / {
proxy_pass http://a.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

保存重启nginx

B、C服务器nginx.conf设置
打开nginx.confi,在http段加入以下代码

server{
listen 80;
server_name a.com;
index index.html;
root /data0/htdocs/www;
}

保存重启nginx

测试
当访问a.com的时候,为了区分是转向哪台服务器处理我分别在B、C服务器下写一个不同内容的index.html文件,以作区分。

打开浏览器访问a.com结果,刷新会发现所有的请求均分别被主服务器(192.168.5.149)分配到B服务器(192.168.5.27)与C服务器(192.168.5.126)上,实现了负载均衡效果。

B服务器处理页面

C服务器处理页面

假如其中一台服务器宕机会怎样?
当某台服务器宕机了,是否会影响访问呢?

我们先来看看实例,根据以上例子,假设C服务器192.168.5.126这台机子宕机了(由于无法模拟宕机,所以我就把C服务器关机)然后再来访问看看。

访问结果:

我们发现,虽然C服务器(192.168.5.126)宕机了,但不影响网站访问。这样,就不会担心在负载均衡模式下因为某台机子宕机而拖累整个站点了。

如果b.com也要设置负载均衡怎么办?
很简单,跟a.com设置一样。如下:

假设b.com的主服务器IP是192.168.5.149,负载均衡到192.168.5.150和192.168.5.151机器上

现将域名b.com解析到192.168.5.149IP上。

在主服务器(192.168.5.149)的nginx.conf加入以下代码:

upstream b.com {
server 192.168.5.150:80;
server 192.168.5.151:80;
}

server{
listen 80;
server_name b.com;
location / {
proxy_pass http://b.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
保存重启nginx

在192.168.5.150与192.168.5.151机器上设置nginx,打开nginx.conf在末尾添加以下代码:

server{
listen 80;
server_name b.com;
index index.html;
root /data0/htdocs/www;
}

保存重启nginx

完成以后步骤后即可实现b.com的负载均衡配置。

主服务器不能提供服务吗?
以上例子中,我们都是应用到了主服务器负载均衡到其它服务器上,那么主服务器本身能不能也加在服务器列表中,这样就不会白白浪费拿一台服务器纯当做转发功能,而是也参与到提供服务中来。

如以上案例三台服务器:

A服务器IP :192.168.5.149 (主)

B服务器IP :192.168.5.27

C服务器IP :192.168.5.126

我们把域名解析到A服务器,然后由A服务器转发到B服务器与C服务器,那么A服务器只做一个转发功能,现在我们让A服务器也提供站点服务。

我们先来分析一下,如果添加主服务器到upstream中,那么可能会有以下两种情况发生:

1、主服务器转发到了其它IP上,其它IP服务器正常处理;

2、主服务器转发到了自己IP上,然后又进到主服务器分配IP那里,假如一直分配到本机,则会造成一个死循环。

怎么解决这个问题呢?因为80端口已经用来监听负载均衡的处理,那么本服务器上就不能再使用80端口来处理a.com的访问请求,得用一个新的。于是我们把主服务器的nginx.conf加入以下一段代码:

server{
listen 8080;
server_name a.com;
index index.html;
root /data0/htdocs/www;
}

重启nginx,在浏览器输入a.com:8080试试看能不能访问。结果可以正常访问

既然能正常访问,那么我们就可以把主服务器添加到upstream中,但是端口要改一下,如下代码:

upstream a.com {
server 192.168.5.126:80;
server 192.168.5.27:80;
server 127.0.0.1:8080;
}

由于这里可以添加主服务器IP192.168.5.149或者127.0.0.1均可以,都表示访问自己。

重启Nginx,然后再来访问a.com看看会不会分配到主服务器上。

todo 这里完善一下负载分配策略

主服务器也能正常加入服务了。

最后
一、负载均衡不是nginx独有,著名鼎鼎的apache也有,但性能可能不如nginx。

二、多台服务器提供服务,但域名只解析到主服务器,而真正的服务器IP不会被ping下即可获得,增加一定安全性。

三、upstream里的IP不一定是内网,外网IP也可以。不过经典的案例是,局域网中某台IP暴露在外网下,域名直接解析到此IP。然后又这台主服务器转发到内网服务器IP中。

四、某台服务器宕机、不会影响网站正常运行,Nginx不会把请求转发到已宕机的IP上

Nginx配置文件实例

直接看注释

server {

        listen       80; 
        server_name  ~^host\.(?<project>[^\.]+?)\.com$;

    # 网站的根目录 未做特殊处理的情况下 用户可以根据相对路径访问网站根目录下的任意文件 跨过你所设置的核心入口 index.php 所以一般项目会将web根目录隐藏在核心文件的下层 
        root /Users/www/$project;
        index index.html; 

    # 隐藏响应头中的有关操作系统和Nginx服务器版本号的信息,保障安全性
    server_tokens off;

    location = / {
         proxy_pass http://127.0.0.1:80;
        }    

        access_log  logs/host.$project.access.log;
        error_page   500 502 503 504  /50x.html;

        location = /50x.html {
            root   html;
        }   

    location ~* (jpg|jpeg|png|gif|txt|pdf|css|js|ico)$
    {

        expires 30d;

        # 取消对静态文件的日志记录
        access_log off;    
    }

    # 禁止访问 根目录下app目录中的所有内容 向客户端返回 403 forbidden
    location ~* /app
    {
        allow 127.0.0.1;
        deny all;
    }


    location ~* /break/
    {
        # rewrite 重写之后,终止本模块中向下执行(即下面的return 402 不会执行) 直接去进行资源定位 不会再次进行 location 匹配 
        rewrite ^/break/(.*) /test/$1 break;
        return 402; 
    }

    location ~* /last/
    {
       # rewrite 之后 停止本模块语句的向下执行 重新进行location 匹配
       rewrite ^/last/(.*) /test/$1 last;
       return 502;
    }

    location ~* /test/
    {
       return 508;
    }

    # http://host.sll.com/nginx_status  可以通过这种访问方式查看 nginx 状态
    location /nginx_status{
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }



}

location 中可用的表达式

-f和!-f用来判断是否存在文件
-d和!-d用来判断是否存在目录
-e和!-e用来判断是否存在文件或目录
-x和!-x用来判断文件是否可执行

location 匹配规则

语法规则: location [=|~|~*|^~] /uri/ { … }

= 开头表示精确匹配
^~ 开头表示uri以某个常规字符串开头,理解为匹配 url路径即可。nginx不对url做编码,因此请求为/static/20%/aa,可以被规则^~ /static/ /aa匹配到(注意是空格)。
~ 开头表示区分大小写的正则匹配
~*  开头表示不区分大小写的正则匹配
!~和!~*分别为区分大小写不匹配及不区分大小写不匹配 的正则
/ 通用匹配,任何请求都会匹配到。
多个location配置优先级:按照精准度由高到低 = > ~ > / , 同级的按照在文件中的排列顺序, 匹配成功后停止匹配

如何查看nginx已经安装了哪些模块

nginx -V  

如果有一个模块没有安装 那么可以编译追加,注意 不要使用 make install 这样是覆盖安装

Yii的RESTful API的应用解析

Tips:本文为笔记,内容多引用他人。侵删

Yii 提供了一整套用来简化实现 RESTful 风格的 Web Service 服务的 API

RESTful API是关于资源的操作,什么是资源呢,资源就是存储在服务器上各式各样的数据。
这些数据资源有的存储在数据库中,如最基本user表,还有一些不适合在数据库中存储。

在如何代表一个资源没有固定的限定,在 Yii 中通常使用 yii\base\Model 或它的子类(如 yii\db\ActiveRecord) 代表资源,是为以下原因:

  • yii\base\Model 实现了 yii\base\Arrayable 接口, 它允许你通过 RESTful API 自定义你想要公开的资源数据。
  • yii\base\Model 支持 输入验证, 在你的 RESTful API 需要支持数据输入时非常有用。
  • yii\db\ActiveRecord 提供了强大的数据库访问和操作方面的支持, 如资源数据需要存到数据库它提供了完美的支持。

RESTful API在Yii中的实现

例如,如果我们想要提供一套操作admin表的RESTful API,我们需要如下操作

Step.1 创建控制器

<?php

namespace frontend\controllers;

use yii\rest\ActiveController;

class AdminController extends ActiveController
{
    public $modelClass = 'frontend\models\Admin';
}

Step.2 创建控制器生成 frontend\models\Admin AR类

<?php
namespace frontend\models;

use yii\db\ActiveRecord;

class Admin extends ActiveRecord
{
    public static function tableName()
    {
        return 'admin';
    }

}        

frontend\models\Admin是基于admin表的ActiveRecord类。

Step.3 配置文件中启用Url美化(nginx中先开启rewrite)

'urlManager' => [
        'enablePrettyUrl'     => true,
        'enableStrictParsing' => false, // 这个推荐设为 false 严格解析代表如果 规则没有预定义则拒绝访问
        'showScriptName'      => false,
        'rules' => [
            ['class' => 'yii\rest\UrlRule', 'controller' => 'admin'],
        ],
    ],    

urlManager组件会在路由解析之前调用,按照预定义的规则进行匹配

public function parseRequest($request)
{
    if ($this->enablePrettyUrl) {

        ...
        return [$pathInfo, []];
    }

    Yii::debug('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
    $route = $request->getQueryParam($this->routeParam, '');
    if (is_array($route)) {
        $route = '';
    }

    return [(string) $route, []];
}

解析优先级为 预定义规则 > 默认规则

只需要这三步,可以通过以下调用方式实现对admin表的CRUD操作。

GET      /users:       逐页列出所有用户
HEAD     /users:       显示用户列表的概要信息
POST     /users:       创建一个新用户
GET      /users/123:   返回用户 123 的详细信息
HEAD     /users/123:   显示用户 123 的概述信息
PATCH    /users/123:   更新用户123
PUT      /users/123:   更新用户123
DELETE   /users/123:   删除用户123
OPTIONS  /users:       显示关于末端 /users 支持的动词
OPTIONS  /users/123:   显示有关末端 /users/123 支持的动词

现在如果要在后台管理系统中开发一个管理员模块,分分钟搞定。

但是在实际工作中,单表操纵可谓少之又少,很多时候资源都是多表数据汇总之后经过聚合之后的结果,
并且,查询操作的调用频率、查询维度的多样性都要远大于其它三种操作,依靠AR提供的通用操作接口显然远远不够
这时我们就需要自定义RESTful资源操作接口。

<?php

namespace frontend\controllers;

use yii\filters\RateLimiter;
use yii\rest\Controller;
use yii\web\Response;

class FooController extends Controller
{
    public $modelClass = 'frontend\models\FooForm';

    public function behaviors() {

        $behaviors = parent::behaviors();
        $behaviors['rateLimiter'] = [
            'class'                  => RateLimiter::className(),
            'enableRateLimitHeaders' => true,
        ];

        // 如果你将 RESTful APIs 作为应用开发,可以设置应用配置中 Response 组件的相应格式
        // 如果将 RESTful APIs 作为模块开发,可以在模块的 init() 方法中设置响应格式
        $behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_JSON;

        return $behaviors;

    }

    public function actionView($id)
    {
        echo __FUNCTION__;
    }

    public function actionSearch()
    {
        echo __FUNCTION__;
    }

    ...

}

urlManager中添加规则

'urlManager' => [
        'enablePrettyUrl'     => true,
        'enableStrictParsing' => false,
        'showScriptName'      => false,
        'rules' => [
            [
                'class' => 'yii\rest\UrlRule',
                'controller' => 'foo',
                'extraPatterns' => [
                    'GET  search' => 'search',
                ]
            ],
            [
                'class' => 'yii\rest\UrlRule',
                'controller' => 'admin'
            ],
        ],
    ],

然后就可以通过get方式访问http://hostname/foos/search 接口

限流 Rate Limiting

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流

  • 缓存 缓存的目的是提升系统访问速度和增大系统处理容量
  • 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
  • 限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队、降级等处理

Yii默认为RESTful API提供了限流功能,该限流针对于用户.User表中使用两列来记录容差和时间戳信息

allowance              剩余的允许请求数(原英文意思为限量,限额)
allowance_updated_at   最后一次接口访问时间戳          

User中需要先实现RateLimitInterface中的三个方法(建议换到NoSQL存储,这里只为说明限流的实现算法,并未切换),实现对这两个字段的读写

public function getRateLimit($request, $action)
{
    return [$this->rateLimit, 1]; // 预定义 每个User最大每秒请求数
}

public function loadAllowance($request, $action)
{
    return [$this->allowance, $this->allowance_updated_at];
}

public function saveAllowance($request, $action, $allowance, $timestamp)
{
    $this->allowance = $allowance;
    $this->allowance_updated_at = $timestamp;
    $this->save();
}

然后RateLimite过滤器中会按照如下规则进行调用,

/**
 * Checks whether the rate limit exceeds.
 * @param RateLimitInterface $user the current user
 * @param Request $request
 * @param Response $response
 * @param \yii\base\Action $action the action to be executed
 * @throws TooManyRequestsHttpException if rate limit exceeds
 */
public function checkRateLimit($user, $request, $response, $action)
{
    list($limit, $window) = $user->getRateLimit($request, $action);
    list($allowance, $timestamp) = $user->loadAllowance($request, $action);

    $current = time();

    // 这一句是核心 使用令牌桶算法 $limit 为令牌桶的最大容量 
    $allowance += (int) (($current - $timestamp) * $limit / $window);   
    if ($allowance > $limit) {
        $allowance = $limit;
    }

    if ($allowance < 1) {
        $user->saveAllowance($request, $action, 0, $current);
        $this->addRateLimitHeaders($response, $limit, 0, $window);
        throw new TooManyRequestsHttpException($this->errorMessage);
    }

    $user->saveAllowance($request, $action, $allowance - 1, $current);
    $this->addRateLimitHeaders($response, $limit, $allowance - 1, (int) (($limit - $allowance + 1) * $window / $limit));
}

常用的限流算法有

  • 计数器
  • 滑动窗口
  • 令牌桶
  • 漏桶

Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。

漏桶算法

把请求比作是水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就会导致水直接溢出,请求被丢弃,即拒绝服务。

漏斗有一个进水口 和 一个出水口,出水口以一定速率出水,并且有一个最大出水速率:

在漏斗中没有水的时候,

  • 如果进水速率小于等于最大出水速率,那么,出水速率等于进水速率,此时,不会积水
  • 如果进水速率大于最大出水速率,那么,漏斗以最大速率出水,此时,多余的水会积在漏斗中

在漏斗中有水的时候,出水口以最大速率出水

  • 如果漏斗未满,且有进水的话,那么这些水会积在漏斗中
  • 如果漏斗已满,且有进水的话,那么这些水会溢出到漏斗之外

令牌桶算法

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;
当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。

令牌桶算法VS漏桶算法

漏桶

漏桶的出水速度是恒定的,那么意味着如果瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。

令牌桶

生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情。

Tips:不论是对于令牌桶拿不到令牌被拒绝,还是漏桶的水满了溢出,都是为了保证大部分流量的正常使用,而牺牲掉了少部分流量,这是合理的,
如果因为极少部分流量需要保证的话,那么就可能导致系统达到极限而挂掉,得不偿失。分布式环境下可用使redis限流。

认证(authorization)与授权(authorize)

认证

RESTful APIs 通常是无状态的, 也就意味着不应使用 sessions 或 cookies,
因此每个请求应附带某种授权凭证,因为用户授权状态可能没通过 sessions 或 cookies 维护,
常用的做法是每个请求都发送一个秘密的 access token 来认证用户, 由于 access token 可以唯一识别和认证用户,所以api尽量使用https协议。
如何在RESTful api中设置认证方式呢?

Step.1 RESTful的主调控制器中设置认证方式

DefaultController extends \yii\rest\Controller
...

 public function behaviors()
{
    $behaviors = parent::behaviors();
    $behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_JSON;
    $behaviors['authenticator'] = [
        'class' => HttpBasicAuth::className(),
    ];

    return $behaviors;
}
...

Step.2 在User components将findIdentityByAccessToken()方法实现

public static function findIdentityByAccessToken($token, $type = null)
{
    // 可以将 token 与 user 的映射关系在redis中维护
    return static::findOne(['access_token' => $token]);
}

Yii常见的认证方式有三种

  • HttpBasicAuth header头中传递username/password
  • HttpBearerAuth 通过Bear token在header中搭载token
  • QueryParamAuth 通过url参数”access-token=ASKJFDjjdd_dD3DDFDFDWDDDD”传递token

版本化

Yii建议api采用增量发布的方式,这样可以向后兼容

api/
common/
    controllers/
        UserController.php
        PostController.php
    models/
        User.php
        Post.php
modules/
    v1/
        controllers/
            UserController.php
            PostController.php
        models/
            User.php
            Post.php
        Module.php
    v2/
        controllers/
            UserController.php
            PostController.php
        models/
            User.php
            Post.php
        Module.php

Yii的模块时可以重复嵌套的,所以可以对一个模块下的接口增量发布

car/
    v1/
        controllers/
            UserController.php
            PostController.php
        models/
            User.php
            Post.php
        Module.php
    v2/
        controllers/
            UserController.php
            PostController.php
        models/
            User.php
            Post.php
        Module.php

参考资料

Yiichian https://www.yiichina.com/doc/guide/2.0/rest-quick-start

限流算法 搜云库技术团队 https://www.jianshu.com/p/dd1071e08469