Yii 框架之高效篇

Yii 的延迟加载

类的延迟加载

sql_autoload_register

Yii 如何使用 sql_autoload_register

自动加载文件路径:vendor/yiisoft/yii2/Yii.php
入口文件路径:web/index.php

类的映射表机制

yii2快速的机制:使用类的映射表,把常用的类放入映射表中,加快加载速度

映射表文件:vendor/yiisoft/yii2/classes.php
类的映射变量:Yii::$classMap
vendor/yiisoft/yii2/BaseYii.php

可以通过使用Yii::$classMap,对延迟加载机制进行优化,是典型的空间换时间的做法,所以不建议往classMap中放入太多不常用的内容,避免内存占用过多

只建议将常用的高频率class放进‘类的映射表’,不常用的就不要放,否则映射表大了,不但降低查询效率,还将占用不必要的内存。

组件的延迟加载

  • index.php
    • app
      • application class
      • components
        • vendor/yiisoft/yii2/web/Application.php
        • vendor/yiisoft/yii2/base/Application.php
      • controller

数据缓存的使用

数据缓存的基础使用

如何配置缓存服务器

如何将数据写入缓存

如何将缓存数据读出

如何修改缓存的数据

如何删除缓存的数据

如何清空缓存中数据

如何设置缓存有效期

数据缓存的依赖关系详解

可以用来解决缓存实时更新的问题

文件依赖
yii框架缓存的DB依赖,跟其他缓存依赖一样,如果依赖中的sql取出的数据发生了改变,相关联的缓存就会消失

表达式依赖

1、文件依赖(FileDependency):一旦文件改变,缓存将失效
$dependency=new \yii\caching\FileDependency([‘filename’=>’hw.txt’]);
$cache->add(‘file_key’,’hello word!’,3000,$dependency);
2、表达式依赖(ExpressionDependency):一旦表达式改变,缓存将失效,即参数发生了变化
$dependency=new \yii\caching\ExpressionDependency([‘expression’=>’\YII::$app->request->get[“name”]’]);
3、DB依赖(DbDependency):一旦数据改变,缓存将失效
$dependency=new \yii\caching\ExpressionDependency(
[‘sql’=>’select count(*) from user’]);

4.链式依赖
5.组依赖

Yii 框架是如何监听文件改变、表达式改变和数据改变?

片段缓存的使用

//片段缓存介绍(主要负责把前端界面的一些区域[不会经常变动的区域:如京东商品分类]缓存起来[缓存到内存或文件中],下次访问时直接从缓存里把数据拿出来,而不用再从数据库抓取信息,提高了程序的执行效率)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 使用视图组件($this)里的beginCache('缓存数据的名字')方法把cache_div给缓存起来;这个方法开启会检查当前有没有缓存,如果没有返回false -->
<!-- 如何证明cache_div的代码片段被缓存了呢? 在两个div里都添加上相同的内容。如果cache_div被缓存起来将会使用缓存里的内容,而不会使用修改后的内容。提高了程序的运行效率。 -->
<?php if($this->beginCache('cache_div')){?>
<div id='cache_div'>
<div>这里待会会被缓存(这里是缓存过后添加的)</div>
</div>
<?php
$this->endCache();//结束缓存
}
?>
<div id='no_cache_div'>
<div>这里不会被缓存(这里是缓存过后添加的)</div>
</div>

片段缓存的设置

片段缓存的时间
$this->beginCache(‘cache_div’,[‘duration’=>$duration]);
$this->endCache();

片段缓存的依赖
$this->beginCache(‘cache_div’,[‘dependency’=>$dependency]);
$this->endCache();

片段缓存的开关
$this->beginCache(‘cache_div’,[‘enabled’=>$enabled’]);
$this->endCache();

片段缓存的嵌套使用

谨慎使用:外层的失效时间应该短于内层,外层的依赖条件应该低于内层,以确保最小的片段,返回的是最新的数据。

片段嵌套缓存 外面的缓存时间大于里面的 那么里面的数据即使超过了缓存时间后有修改也不会被执行

页面缓存的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function actionIndex()
{
}
//页面访问index()方法之前会先访问behavior()方法行为。
public function behavior()
{
return [
[
'class' => 'yii\filters\PageCache',//页面缓存类
'duration' => 1000,//缓存时间
'only' => ['index', 'test'],//仅仅缓存index和test方法的数据]
'dependency' => ['class' => 'yii\caching\FileDependency', 'fileName' => 'hw.txt']//文件缓存依赖
],
];
}

HTTP 缓存的使用

如何校验服务器与浏览器的数据一致

用矩形L代表浏览器,矩形F代表服务器。
1.当用户到F浏览网站或一个页面时,就会用L给F发送一个请求,然后F就会处理这个请求,处理完之后会把处理的结果作为一个响应返回给L,然后L从这个响应里拿数据,再显示出来,这样用户就可以看到他想看到的页面了。
2.但这时会遇到一个小问题,如果用户从F拿过来的数据不是他想要的数据(比如12306订票页面想订的票没有了),这时用户猛刷新L,那么L就会给F发送很多的请求,实际上F的数据是没有发生变化的,F收到这么多请求后都会照常的处理,但处理的结果没有发生变化,所以处理的结果跟第一次返回出去的数据是一样的。所以这样对F来说,很浪费资源、时间。
3.F想了一个办法,当F第一次把数据(小圆表示)发送给L时,它让L把数据缓存到L里面,也就是把它缓存起来,缓存起来以后如果下一次L发送了请求到了F这边之后,F会对比F这边的数据和L那边的数据是不是一样的(F是如何校验的呢?),如果是一样的那就没必要再把这个数据发送回给L了,就让L使用缓存里的数据就行了。
4.F如何校验F和L的数据是一样的,F就想了一个办法。当F第一次发送数据给L时,会在数据中塞入一个last-modified字段(记录了F这边数据的修改时间),那么L也有了这个字段。那么当L下一次发送请求时,会把这个修改时间也带上,也就是把lm(last-modified字段)发送回给F,F会校验lm和F里的那个数据的修改时间是否是一样的,如果是一样的那么这个数据肯定没有被修改过,那就没必要再发送回给L了,直接告诉L去使用缓存里的数据就行了。
5.另一种校验方式是基于last-modified的小缺陷设计出来的(开发人员把F的数据@改为#,然后又改回@,那么数据@虽然没变化,但它的修改时间改变了。那么L发送过来的lm和数据@的修改时间是对不上的,然后F又把@发送给L,但实际上@没有发生变化,这样也会造成一些问题)。所以F想了一个办法,F第一次发送@数据包时,会根据@里的内容生成一个标志etag,通过etag可以大概了解数据包@里的内容。F会把etag和last-modified一起发送给L,那么L下一次访问F时,会传lm和etag。那么F先验证lm(两边数据修改时间是否一样),一样则使用L的缓存;如果不一样,F则验证etag是否一样,如果一样证明@没有被修改,则让L使用缓存

服务器响应304,not modifired

服务器怎么告诉浏览器缓存起来?

1
2
3
4
5
6
7
8
9
10
11
12
13
public function behaviors(){
return [
[
'class'=>'yii\filters\HttpCache',
'lastModified'=>function(){
return 1432817564;
},
'etagSeed'=>function(){
return 'etagseed2f;'
}
]
];
}

//使用 HttpCache 之后再请求头部header会多了一个Cache-Control:last-modified 来作为标志

在服务器修改数据之后如何让浏览器更新缓存

httpCache的依赖:
lastModified: 时间标识
etagSeed:内容标识
当二者同时存时,需要两个均发生变化才会发送200并返回新数据

gii 工具简介

  • Model Generator(数据模型的代码生成器)
  • Form Generator(表单的代码生成器)
  • Module Generator(模块的代码生成器)
  • CRUD Generator(增删改查的代码生成器)
  • Controller Generator(控制器的代码生成器)
  • Extension Generator(扩展的代码生成器)

打造扛得住的 MySQL 数据库架构

第1章 实例和故事

1-1 什么决定了电商双11大促的成败

电商公司的服务器能够承受住双十一期间高并发的访问量,为用户提供稳定高速的购物体验。

1-2 在双11大促中的数据库服务器

数据库架构:

  • 1 台 MySQL 主服务器
  • 15 台 MySQL 从服务器

当时没有部署主从复制组件,一旦主服务器出现故障,很难自动进行故障切换,必须由 DBA 进行手动操作。过多的从服务器,对主服务器的网卡容量也是一个不小的挑战。

数据库性能依据参数:

  • QPS
  • TPS
  • 并发量
  • CPU 使用率
  • 磁盘 IO

1-3 在大促中什么影响了数据库性能

影响数据库性能的因素

  • SQL 查询速度
  • 服务器硬件
  • 网卡流量
  • 磁盘 IO

数据库性能常见的风险:

  • 效率低下的 SQL
    • QPS:每秒钟处理的查询量
      • 10ms 处理 1个 SQL,1s 处理 100个 SQL,QPS <= 100
      • 100ms 处理 1个 SQL,1s 处理 10个 SQL,QPS <= 10
    • TPS:每秒钟处理的事务数
  • 大量的并发和超高的 CPU 使用率
    • 大量的并发会导致数据库连接数被占满
    • 超高的 CPU 使用率会导致服务器因 CPU 资源耗尽而出现宕机
  • 磁盘 IO
    • 磁盘 IO 性能突然下降(使用更快的磁盘设备)
    • 其它大量消耗磁盘性能的计划任务(调整计划任务,做好磁盘维护)
  • 网卡 IO
    • 当网卡 IO 被占满时,(千兆网卡 1000 Mb/8=100 MB)会出现无法连接数据库的情况。
    • 如何避免无法连接数据库的情况
      • 减少从服务器的数量
      • 进行分级缓存
      • 避免使用 select * 进行查询
      • 分离业务网络和服务器网络
  • 大表
    • 什么样的表可以称之为大表
      • 记录行数巨大,单表超过千万行
      • 表数据文件巨大,表数据文件超过 10 G
    • 大表对查询的影响
      • 慢查询:很难在一定的时间内过滤出所需要的数据
        • 区分度低的数据在查询的时候会产生大量的磁盘 IO,会降低磁盘的效率,导致查询效率低下,出现大量的慢查询。
    • 大表对 DDL 操作的影响
      • 建立索引需要很长的时间
        • MySQL 版本 < 5.5,建立索引会锁表
        • MySQL 版本 >= 5.5,虽然不会锁表但会引起主从延迟
      • 修改表结构需要长时间锁表
        • 会造成长时间的主从延迟
        • 影响正常的数据操作
    • 解决方案
      • 分库分表,把一张大表分成多个小表
        • 难点
          • 分表主键的选择
          • 分表后跨分区数据的查询和统计
      • 大表的历史数据归档(减少对前后端业务的影响)
        • 难点
          • 归档时间点的选择
          • 如何进行归档操作
  • 大事务
    • 事务的概念
      • 事务是数据库系统区别于其它一切文件系统的重要特性之一
      • 事务是一组具有原子性的 SQL 语句,或是一个独立的工作单元
    • 事务的特性
      • 原子性
        • 一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败,对于一个事务来说,不可能只执行其中的一部分操作。
      • 一致性
        • 一致性是指事务将数据库从一种一致性状态转换到另外一种一致性状态。在事务开始之前和事务结束后,数据库中的数据完整性没有被破坏。
      • 隔离性
        • 隔离性要求一个事务对数据库中数据的修改,在未提交完成前对于其它事务是不可见的。
        • 隔离性的类型
          • 未提交读(Read Uncommited)
          • 已提交读(Read Commited)
          • 可重复读(Repeatable Read)
          • 可串行化(Serialization)
      • 持久性
        • 一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,已经提交的修改数据也不会丢失
    • 什么是大事务
      • 定义:运行时间比较长,操作的数据比较多的事务
      • 风险:
        • 锁定太多的数据,造成大量的阻塞和锁超时
        • 回滚时所需时间比较长
        • 执行时间长,容易造成主从延迟
      • 处理:
        • 避免一次处理太多的数据
        • 移出不必要在事务中的 SELECT 操作

第2章 什么影响了MySQL性能

第3章 MySQL基准测试

第4章 MySQL数据库结构优化

第5章 MySQL高可用架构设计

第6章 数据库索引优化

第7章 SQL查询优化

第8章 数据库的分库分表

第9章 数据库监控

LNMP 性能优化之 MySQL 性能优化

目录

数据准备

打开链接下载数据

1
http://downloads.mysql.com/docs/sakila-db.zip

打开终端,执行以下命令

1
2
3
4
5
6
7
8
9
10
11
# 登录 MySQL Cli 模式
mysql -uroot -p
# 创建数据库
SOURCE /Users/LuisEdware/Downloads/sakila-db/sakila-schema.sql
# 填充数据到数据库
SOURCE /Users/LuisEdware/Downloads/sakila-db/sakila-data.sql
# 使用 sakila 数据库
USE sakila;

数据库结构优化

选择合适的数据类型

  • 使用可以存下你的数据的最小的数据类型
  • 使用简单的数据类型。Integer 要比 Varchar 类型在 MySQL 中处理更高效
  • 尽可能使用 not null 定义字段
  • 尽量少用 text 类型,非用不可时最好考虑分表

数据库表的范式化优化与反范式化优化

为了避免冗余数据,可以根据数据库表设计第三范式对数据的储存进行优化设计;也可以为了提高性能,采用数据库表设计反范式化设计去冗余数据,使得数据库的查询更加的快速。

数据库表垂直拆分

垂直拆分,就是把原来一个有很多列的表拆分成多个表,这解决了表的宽度问题。通常垂直拆分就可以按以下原则进行:

  • 把不太常用的字段单独存放到一个表中
  • 把大字段独立存放到一个表中
  • 把经常一起使用的字段放到一起

数据库表垂直拆分实践

TODO

数据库表水平拆分

当单表的数据量过大,导致增删查改等操作过慢,这时候需要对表进行水平拆分。水平拆分的表,每一张表的结构都是完全一致的。

常用的水平拆分方法为:

  1. 对 customer_id 进行 hash 运算,如果要拆分成 5 个表则使用 mod(customer_id,5) 取出 0-4 个值
  2. 针对不同的 hashID 把数据存到不同的表中

挑战:

  • 跨分区表进行数据查询
  • 统计及后台报表操作

数据库表水平拆分实践

TODO

MySQL 慢查询日志

慢查询日志配置

如何发现有问题的 SQL?答案是使用 MySQL 慢查询日志对有效率问题的 SQL 进行监控,执行命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查看是否开启慢查询日志
show variables like "slow_query_log";
# 查看是否设置了把没有索引的记录到慢查询日志
show variables like "log_queries_not_using_indexes";
# 查看是否设置慢查询的 SQL 执行时间
show variables like "long_query_time";
# 查看慢查询日志记录位置
show variables like "slow_query_log_file";
# 开启慢查询日志
set global slow_query_log=on
# 设置没有索引的记录到慢查询日志
set global log_queries_not_using_indexes=on
# 设置到慢查询日志的 SQL 执行时间(1 代表 1 秒)
set global long_query_time=1
# 查看慢查询日志(在 Linux 终端下执行)
tail -50 /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;

慢查询日志所存内容

  • SQL 的执行时间:# Time: 2016-10-13T10:01:45.914267Z
  • SQL 的执行主机:# User@Host: root[root] @ localhost [] Id: 949
  • SQL 的执行信息:# Query_time: 0.000227 Lock_time: 0.000099 Rows_sent: 2 Rows_examined: 2
  • SQL 的执行时间:SET timestamp=1476352905;
  • SQL 的执行内容:select * from store;

慢查询日志分析工具

  • mysqldumpslow
    • 安装:MySQL 数据库自带
    • 使用:mysqldumpslow /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;
    • 详解:点击查看详情
  • pt-query-digest
    • 安装:brew install brew install percona-toolkit
    • 使用:pt-query-digest /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log | more;
    • 详解:TODO
  • mysqlsla
    • 安装:TODO
    • 使用:TODO
    • 详解:TODO

慢查询日志判断问题 SQL 的依据

  • 查询次数多且每次查询占用时间长的 SQL
  • 未命中索引的 SQL
  • IO 大的 SQL

EXPLAIN 分析 SQL 的执行计划

使用 EXPLAIN 分析 SQL

1
EXPLAIN SELECT * FROM staff;

使用 EXPLAIN 分析 SQL 后各个参数含义如下:

  • id:SQL 语句执行顺序编号
  • select_type:SQL 语句执行的类型,主要区别普通查询、联合查询和子查询之类的复杂查询
  • table:SQL 语句执行所引用的数据表
  • type:显示连接使用的类型
  • possible_keys:指出 MySQL 能在该数据表中使用哪些索引有助于查询
  • key:SQL 语句执行时所使用的索引
  • key_len:SQL 语句执行时所使用的索引的长度。在不损失精确性的情况下,长度越短越好
  • ref:显示索引的哪一列被使用了
  • rows:MySQL 认为必须检查的用来返回请求数据的行数
  • Extra:提供 MySQL 优化器一系列额外信息

执行 EXPLAIN 分析 SQL 返回结果如下:

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
mysql> EXPLAIN select * from z_order left join z_league on z_order.league_id = z_league.id limit 10000\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: z_order
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 740339
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: z_league
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: bingoshuiguo.z_order.league_id
rows: 1
filtered: 100.00
Extra: Using where
2 rows in set, 1 warning (0.00 sec)

SQL 优化

MAX() 优化

分析 SQL 语句:使用 MAX() 方法查询最后一笔交易的时间

1
EXPLAIN SELECT MAX(payment_date) FROM payment_date

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: payment
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 16086
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)

如果数据表的数据非常大,查询频率又非常高,那么服务器的 IO 消耗也会非常高,所以这条 SQL 语句需要优化。可以通过建立索引进行优化。执行代码如下:

1
CREATE INDEX idx_paydate ON payment(payment_date);

然后再分析 SQL 语句,执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: NULL
partitions: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
filtered: NULL
Extra: Select tables optimized away
1 row in set, 1 warning (0.01 sec)

经过优化之后,由于索引是按顺序排列的,MySQL 不需要查询表中的数据,而是通过查询索引最后的一个数据,就可以得知执行结果了。而且这个时候,不管表的数据量多大,查询 MAX() 所需要的时间都是基本固定的,这样就尽可能地减少了 IO 操作。

COUNT() 优化

分析 SQL 语句:使用 COUNT() 函数在一条 SQL 中同时查出 2006 年和 2007 年电影的数量

1
2
3
4
5
SELECT
count(release_year = '2006' OR NULL) AS '2006 年电影数量' ,
count(release_year = '2007' OR NULL) AS '2007 年电影数量'
FROM
film;

count(*) 包含空值,count(id) 不包含空值。上述语句就是优化 Count() 函数取值

子查询优化

分析 SQL 语句:查询 sandra 出演的所有影片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT
title ,
release_year ,
LENGTH
FROM
film
WHERE
film_id IN(
SELECT
film_id
FROM
film_actor
WHERE
actor_id IN(
SELECT
actor_id
FROM
actor
WHERE
first_name = 'sandra'
)
)

通常情况下,需要把子查询优化为 join 查询,但在优化时要注意关联键是否有一对多的关系,要注意重复数据。

GROUP BY 优化

group by 可能会出现临时表、文件排序等,影响效率。可以通过关联的子查询,来避免产生临时表和文件排序,可以节省 IO。

group by 查询优化前:

1
2
3
4
5
6
7
8
9
EXPLAIN SELECT
actor.first_name ,
actor.last_name ,
Count(*)
FROM
sakila.film_actor
INNER JOIN sakila.actor USING(actor_id)
GROUP BY
film_actor.actor_id;

执行结果如下:

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
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: actor
partitions: NULL
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 200
filtered: 100.00
Extra: Using temporary; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: film_actor
partitions: NULL
type: ref
possible_keys: PRIMARY,idx_fk_film_id
key: PRIMARY
key_len: 2
ref: sakila.actor.actor_id
rows: 27
filtered: 100.00
Extra: Using index
2 rows in set, 1 warning (0.01 sec)

group by 查询优化后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EXPLAIN SELECT
actor.first_name ,
actor.last_name ,
c.cnt
FROM
sakila.actor
INNER JOIN(
SELECT
actor_id ,
count(*) AS cnt
FROM
sakila.film_actor
GROUP BY
actor_id
) AS c USING(actor_id);

执行结果如下:

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
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: actor
partitions: NULL
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 200
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: PRIMARY
table: <derived2>
partitions: NULL
type: ref
possible_keys: <auto_key0>
key: <auto_key0>
key_len: 2
ref: sakila.actor.actor_id
rows: 27
filtered: 100.00
Extra: NULL
*************************** 3. row ***************************
id: 2
select_type: DERIVED
table: film_actor
partitions: NULL
type: index
possible_keys: PRIMARY,idx_fk_film_id
key: PRIMARY
key_len: 4
ref: NULL
rows: 5462
filtered: 100.00
Extra: Using index
3 rows in set, 1 warning (0.00 sec)

LIMIT 优化

LIMIT 常用于分页处理,时常会伴随 ORDER BY 从句使用,因此大多时候会使用 Filesorts ,这样会造成大量的 IO 问题

优化步骤1:使用有索引的列或主键进行 Order By 操作
优化步骤2:记录上次返回的主键,在下次查询时使用主键过滤(保证主键是自增且有索引)

索引优化

为合适的列建立索引

  • 在 WHERE 从句,GROUP BY 从句,ORDER BY 从句,ON 从句中出现的字段
  • 索引字段越小越好
  • 离散度的字段放到联合索引的前面

例如:

1
SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 584;

上述 SQL 语句,是 index(staff_id,customer_id) 合理,还是 index(customer_id,staff_id) 合理。执行语句如下:

1
2
3
4
SELECT count(DISTINCT customer_id) , count(DISTINCT staff_id) FROM payment;
count(DISTINCT customer_id):599
count(DISTINCT staff_id):2

由于 customer_id 的离散度更大,所以应该使用 index(customer_id,staff_id)

找到重复和冗余的索引

之所以要找到重复和冗余的索引,是因为过多的索引不但影响写入,而且影响查询,索引越多,分析越慢。那么为何重复索引、冗余索引?概念如下:

重复索引是指相同的列以相同的顺序建立的同类型的索引,如下表中 primary key 和 ID 列上的索引就是重复索引,例子如下:

1
2
3
4
5
6
CREATE TABLE test(
id INT NOT NULL PRIMARY KEY ,
NAME VARCHAR(10) NOT NULL ,
title VARCHAR(50) NOT NULL ,
UNIQUE(id)
) ENGINE = INNODB;

UNIQUE(ID)PRIMARY KEY 重复了。
冗余索引是指多个索引的前缀列相同,或是在联合索引中包含了主键的索引,例子如下:

1
2
3
4
5
6
CREATE TABLE test(
id INT NOT NULL PRIMARY KEY ,
NAME VARCHAR(10) NOT NULL ,
title VARCHAR(50) NOT NULL ,
KEY(NAME , id)
) ENGINE = INNODB;

查找重复及冗余索引的 SQL 语句如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
USE information_schema;
SELECT
a.TABLE_SCHEMA AS '数据名' ,
a.table_name AS '表名' ,
a.index_name AS '索引1' ,
b.INDEX_NAME AS '索引2' ,
a.COLUMN_NAME AS '重复列名'
FROM
STATISTICS a
JOIN STATISTICS b ON a.TABLE_SCHEMA = b.TABLE_SCHEMA
AND a.TABLE_NAME = b.table_name
AND a.SEQ_IN_INDEX = b.SEQ_IN_INDEX
AND a.COLUMN_NAME = b.COLUMN_NAME
WHERE
a.SEQ_IN_INDEX = 1
AND a.INDEX_NAME <> b.INDEX_NAME

也可以使用工具 pt-duplicate-key-checker 检查重复索引和冗余索引,使用例如:

1
pt-duplicate-key-checker -uroot -p '123456' -h 127.0.0.1 -d sakila

执行结果如下:

1
2
3
4
5
6
7
# ########################################################################
# Summary of indexes
# ########################################################################
# Size Duplicate Indexes 118425374
# Total Duplicate Indexes 24
# Total Indexes 1439

删除不用的索引

目前 MySQL 中还没有记录索引的使用情况,但是在 PerconMySQL 和 MariaDB 中可以通过 INDEX_STATISTICS 表来查看哪些索引未使用,但在 MySQL 中目前只能通过慢查询日志配合共组 pt-index-usage 来进行索引使用情况的分析。

1
pt-index-usage -uroot -p '123456' /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;

系统配置优化

数据库系统配置优化

数据库是基于操作系统的,目前大多数 MySQL 都是安装在Linux 系统之上,所以对于操作系统的一些参数配置也会影响到 MySQL 的性能,下面列举一些常用到的系统配置。

网络方面的配置,要修改文件 /etc/sysctl.conf

1
2
3
4
5
6
7
8
# 增加 tcp 支持的队列数
net.ipv4.tcp_max_syn_backlog = 65535
# 减少断开连接时,资源回收
net.ipv4.tcp_max_tw_buckets = 8000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 10

打开文件数的限制,可以使用 ulimit -a 查看目录的各项限制,可以修改文件 /etc/security/limits.conf ,增加以下内容以修改打开文件数量的限制

1
2
soft nofile 65535
hard nofile 65535

除此之外最好在 MySQL 服务器上关闭 iptables,selinux 等防火墙软件。

MySQL 配置文件

MySQL 可以通过启动时指定配置参数和使用配置文件两种方法进行配置,在一般情况下,配置文件位于 /etc/my.cnf 或是 /etc/mysql/my.cnf,MySQL 查询配置文件的顺序是可以通过以下方法过的

常用参数说明

  • innodb_buffer_pool_size:用于配置 Innodb 的缓冲池

    • 如果数据库中只有 Innodb 表,则推荐配置量为总内存的 75%
    • Innodb_buffer_pool_size >= Total MB
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      SELECT
      ENGINE ,
      round(
      sum(data_length + index_length) / 1024 / 1024 ,
      1
      ) AS 'Total MB'
      FROM
      information_schema. TABLES
      WHERE
      table_schema NOT IN(
      "information_schema" ,
      "performance_schema"
      )
      GROUP BY
      ENGINE;
  • innodb_buffer_pool_instances:MySQL 5.5 中新增参数,可以控制缓冲池的个数,默认情况下只有一个缓冲池。

  • innodb_log_buffer_size:Innodb 日志缓冲的大小,由于日志最长,每秒钟就会刷新,所以一般不用太大。
  • innodb_flush_log_at_trx_commit:对 Innodb 的 IO 效率影响很大。
  • innodb_file_per_table:控制 Innodb 每一个表都使用独立的表空间,默认为 OFF,也就是所有表都会建立在共享表空间中。
  • innodb_stats_on_metadata:决定 MySQL 在什么情况下会刷新 innodb 表的统计信息。

存储引擎及版本的选择

请选择 MySQL InnoDB 存储引擎及 MySQL 5.5+ 的版本,原因如下:

  • 更稳定可靠的数据存储和索引结构;整个存储引擎设计思想更可靠先进,接近于 Oracle、SQL Server 级别的数据库
  • 更多可靠特性支持,比如事务、外键等支持(支付等关键领域事务非常重要)
  • 运行更稳定,不论读写数据的量级,都能够保证比较稳定的性能响应
  • 更好地崩溃恢复机制,特别利用一些 Percona 的一些工具,更有效地运维 InnoDB
  • MySQL 5.5+ 对比 MySQL 5.1.x 总体功能和性能提升太多,改进太多

第三方配置工具使用

percona:https://tools.percona.com/

服务器硬件优化

  • CPU
    • 使用多核 CPU,能够充分发挥新版 MySQL 多核下的效果
    • MySQL 有一些工作只能使用到单核 CPU,选择高频
    • MySQL 对 CPU 核数的支持并不是越多越快,MySQL 5.5 版本不要超过 32 个核
  • 内存
    • 选择高频内存
    • 如果数据量比较大,建议使用不要低于 20 G 内存的服务器
  • 网络
    • 保证足够的吞吐量,建议千兆网卡
    • 增加带宽,加快网络通信速度
  • 硬盘 IO 优化
    • 使用 SSD 硬盘,提高 TPS,选择适合的 RAID 方案
    • RAID 级别简介
      • RAID 0:也称为条带,就是把多个磁盘链接成一个硬盘使用,这个级别 IO 最好
      • RAID 1:也成为镜像,要求至少两个磁盘,每组磁盘存储的数据相同
      • RAID 1 + 0:就是 RAID 1 和 RAID 0的结合。同时具备两个级别的优缺点。一般建议数据库使用这个级别。
      • RAID 5:把多个(最少 3 个)硬盘合并成 1 个逻辑盘使用,数据读写时会建立奇偶校验信息,并且奇偶校验信息和相对应的数据分别存储在不同的磁盘上。当 RAID 5 的一个磁盘数据发生损坏后,利用剩下的数据和相应的奇偶校验信息去恢复被损坏的数据。

MySQL 服务器集群

TODO

Linux Shell 基础之条件判断语句

Shell 条件判断式语句

按文件类型判断

测试选项 作用
-b 文件 判断该文件是否存在,并且是否为块设备文件
-c 文件 判断该文件是否存在,并且是否为字符设备文件
-d 文件 判断该文件是否存在,并且是否为目录文件
-e 文件 判断该文件是否存在(存在为真)
-f 文件 判断该文件是否存在,并且是否为普通文件
-L 文件 判断该文件是否存在,并且是否为符号链接文件
-p 文件 判断该文件是否存在,并且是否为管道文件
-s 文件 判断该文件是否存在,并且是否为非空
-S 文件 判断该文件是否存在,并且是否为套接字文件

两种判断格式

  • test -e /root/install.log
  • [ -e /root/install.log ]
  • [ -e /root/install.log ] && echo yes || echo no

按文件权限判断

测试选项 作用
-r 文件 判断该文件是否存在,并且是否该文件拥有读权限
-w 文件 判断该文件是否存在,并且是否该文件拥有写权限
-x 文件 判断该文件是否存在,并且是否该文件拥有执行权限
-u 文件 判断该文件是否存在,并且是否该文件拥有 SUID 权限
-g 文件 判断该文件是否存在,并且是否该文件拥有 SGID 权限
-k 文件 判断该文件是否存在,并且是否该文件拥有 SBit 权限

两个文件之间的比较

测试选项 作用
文件1 -nt 文件2 判断文件 1 的修改时间是否比文件 2 的新
文件1 -ot 文件2 判断文件 1 的修改时间是否比文件 2 的旧
文件1 -ef 文件2 判断文件 1 是否和文件 2 的 Inode 号一致,可以理解为两个文件是否为同一个文件,这个判断用于硬链接判断是很好的方法

例子

1
2
3
4
5
# 创建一个硬链接
ln /root/student.txt/tmp/stu.txt
# 测试
[/root/student.txt -ef /tmp/stu.txt] && echo "yes" || echo "no"

两个整数之间的比较

测试选项 作用
整数1 -eq 整数2 判断整数 1 是否和整数 2 相等
整数1 -ne 整数2 判断整数 1 是否和整数 2 不相等
整数1 -gt 整数2 判断整数 1 是否大于整数 2
整数1 -lt 整数2 判断整数 1 是否小于整数 2
整数1 -ge 整数2 判断整数 1 是否大于等于整数 2
整数1 -le 整数2 判断整数 1 是否小于等于整数 2

例子

1
2
[ 23 -ge 22 ] && echo "yes" || echo "no"
[ 23 -le 22 ] && echo "yes" || echo "no"

字符串的判断

测试选项 作用
-z 判断字符串是否为空
-n 判断字符串是否为非空
字符串1==字符串2 判断字符串 1 是否和字符串 2 相等
字符串1!=字符串2 判断字符串 1 是否和字符串 2 不相等

例子

1
2
3
4
5
6
7
8
9
10
11
12
# 给 name 变量赋值
name=fengj
# 判断 name 变量是否为空
[ -z "$name" ] && echo "yes" || echo "no"
# 给变量 aa 和变量 bb 赋值
aa=11
bb=22
# 判断两个变量的值是否相等
[ "$aa" == "$bb" ] && echo yes || echo no

多重条件判断

测试选项 作用
判断1 -a 判断2 逻辑与,判断 1 和判断 2 都成立,最终的结果才为真
判断1 -o 判断2 逻辑或,判断 1 和判断 2 有一个成立,最终的结果就为真
! 判断 逻辑非,使原始的判断式取反

例子

1
2
3
4
5
# 给变量 aa 赋值
aa=11
# 判断变量 aa 是否有值,同时判断变量 aa 是否大于 23
[ -n "$aa" -a "$aa" -gt 23 ] && echo "yes" || echo "no"

Shell 单分支if语句

概述

如何 “背” 程序

  1. 抄写目标程序并能正确运行
  2. 为目标程序补全注释
  3. 删掉注释,为代码重新加注释
  4. 删除代码,看注释写代码
  5. 删除注释和代码,从头开始写

单分支 if 语句

语法支持

1
2
3
4
5
6
7
8
if [ 条件判断式 ];then
程序
fi
if [ 条件判断式 ]
then
程序
fi

例子1:判断登录的用户是否为 root

1
2
3
4
5
6
7
8
#!/bin/bash
test=$(env|grep "USER" |cut -d "=" -f2)
if [ "$test" == root ]
then
echo "Current user is root."
fi

单分支 if 语句例子:判断分区使用率

1
2
3
4
5
6
7
8
9
#!/bin/bash
# 统计根分区使用率,把根分区使用率作为变量值赋予变量 rate
rate=$(df -h | grep "/dev/disk1" | awk '{print $5}' | cut -d "%" -f 1)
if [ $rate -ge 70 ]
then
echo "Warning! /dev/disk1 is full!!"
fi

Shell 双分支if语句

语法支持

1
2
3
4
5
6
if [ 条件判断式 ]
then
条件成立时,执行的程序
else
条件不成立时,执行的另一个程序
fi

双分支 if 语句例子:判断输入的是否是一个目录

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
read -t 30 -p "Please input a dir:" dir
if [ -d "$dir" ]
then
echo "is a Dir"
else
echo "is not a Dir"
fi

双分支 if 语句例子:判断Apache服务是否启动

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# 列出所有进程,选取 httpd 进程,反选 grep
test=$(ps aux | grep httpd | grep -v grep)
if [ -n "$test" ]
then
echo "$(date) httpd is ok!" >> /tmp/autostart-acc.log
else
sudo apachectl restart &> /dev/null
echo "$(date) restart httpd !!" >> /tmp/autostart-err.log
fi

Shell 多分支 if 语句

语言支持:

1
2
3
4
5
6
7
8
9
if [ 条件判断式 1 ]
then
条件成立 1 时,执行的程序
elif [ 条件判断式 2 ]
then
条件成立 2 时,执行的程序
else
条件不成立时,执行的另一个程序
fi

多分支 if 语句例子:判断用户输入的是什么文件

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
#!/bin/bash
# 判断用户输出的是什么文件
# 接受键盘的输入,并赋予变量file
read -p "Please input a filename:" file
# 判断变量是否为空
if [ -z "$file" ]
then
echo "Error,Please input a filename"
exit 1
# 判断 file 的值是否存在
elif [ ! -e "$file" ]
then
echo "Your input is not a file!"
exit 2
# 判断 file 的值是否为普通文件
elif [ -f "$file" ]
then
echo "$file is a regulare file!"
# 判断 file 的值是否为目录文件
then
echo "$file is a directory!"
else
echo "$file is an other file!"

Shell 多分支case语句

语法支持

1
2
3
4
5
6
7
8
9
10
case $变量名 in
"值1")
如果变量的值等于值 1,则执行程序1
;;
"值2")
如果变量的值等于值 2,则执行程序2
;;
*)
如果变量的值都不是以上的值,则执行此程序
;;

Linux Shell 基础之运算符

Shell 运算符

  • declare 声明变量
    • declare [+/-][选项] 变量名
      • - 给变量设定类型属性
        • -a 将变量声明为数组型
        • -i 将变量声明为整数型
        • -x 将变量声明为环境变量
        • -r 将变量声明为只读变量
        • -p 显示指定变量的被声明的类型
      • + 取消变量的类型属性
    • declare -x test=123 声明环境变量
  • declare -p 查询变量的属性
    • declare -p 查询所有变量的属性
    • declare -p 变量名 查询指定变量的属性
  • expr 或 let 数值运算工具

Shell 运算符 例子

Shell declare 例子

1
2
3
4
5
6
# 给变量 aa 和 bb 赋值
aa=11
bb=22
# 声明变量 cc 的类型是整数型,它的值是 aa 和 bb 的和
declare -i cc=$aa+$bb

声明数组变量例子

1
2
3
4
5
6
7
8
9
# 定义数组
movie[0]=zp
movie[1]=tp
declare -a movie[2]=live
# 查看数组
echo ${movie}
echo ${movie[2]}
echo ${movie[*]}

Shell 环境变量配置文件

环境变量配置文件中主要是定义对系统操作环境生效的系统默认环境变量,如 PATH 等

  • source 命令
    • source 配置文件
    • . 配置文件
  • 常用的环境变量配置文件
    • /etc/profile
      • USER 变量
      • LOGNAME 变量
      • MAIL 变量
      • PATH 变量
      • HOSTNAME 变量
      • HISTSIZE 变量
      • umask 查看系统默认权限
        • 文件最高权限为 666
        • 目录最高权限为 777
        • 权限不能使用数字进行换算,而必须使用字母
        • umask 定义的权限,是系统默认权限中准备丢弃的权限
      • 调用 /etc/profile.d/*sh 文件
    • /etc/profile.d/*sh
    • ~/.bash_profile
    • ~/.bashrc
    • /etc/bashrc
  • 其他配置文件
    • ~/.bash_logout 注销时生效的环境变量配置文件
  • Shell 登录信息
    • cat /etc/issue 本地终端欢迎信息
    • cat /etc/issue.net 远程终端欢迎信息
      • 转义符在 /etc/issue.net 文件中不能使用
      • 是否显示此欢迎信息,由 SSH 的配置文件 /etc/ssh/sshd_config 决定,加入 “Banner /etc/issue.net” 行才能显示(需要重启 SSH 服务)
    • /etc/motd 登陆后欢迎信息,不管是本地登录,还是远程登录,都可以显示此欢迎信息

Linux Shell 基础之变量

Shell 变量概述

Shell 与其他语言的对比

  • PHP 和 JAVA 主要实现功能
  • Shell 简化管理操作

什么是变量

  • 变量是计算机内存的单元,其中存放的值可以改变
  • 变量让你能够把程序中准备使用的每一段数据都赋给一个简短、易于记忆的名字,因此它们十分有用

变量命名规则

  • 变量名必须以字母或下划线开头,名字中间只能由字母、数字和下划线组成
  • 变量名的长度不得超过 255 个字符
  • 变量名在有效的范围内必须是唯一的
  • 在 Bash 中,变量的默认类型都是字符串型

变量存储类型

  • 整型
  • 浮点型
  • 日期型
  • 字符串型

变量定义分类

  • 用户自定义变量:用户自定义的变量
  • 环境变量:这种变量中主要保存的是和系统操作环境相关的数据。变量可以自定义,但是对系统生效的环境变量名和变量作用是固定的。
  • 位置参数变量:这种变量主要是用来向脚本当中传递参数或数据的。变量名不能自定义,变量作用固定。
  • 预定义变量:是 Bash 中已经定义好的变量,变量名不能自定义,变量作用也是固定的。

Shell 变量与变量分类

  • 用户自定义变量
    • 变量定义
      • 变量名=变量值
        • x=5
        • name="Luis"
      • 变量赋值时候,变量名与变量值之间不能有空格
    • 变量调用
      • echo $x
      • echo $name
    • 变量叠加
      • x=123
      • x=”$x”456
      • x=${x}789
    • 变量查看
      • set
        • -u 如果设定此选项,调用未声明变量时会报错(默认无任何显示)
    • 变量删除
      • unset 变量名
  • 环境变量
    • 用户自定义环境变量
      • 设置环境变量
        • export 变量名=变量值
        • 变量名=变量值;export 变量名
      • 查看环境变量
        • set 查看所有变量
        • env 查看环境变量
      • 删除环境变量
        • unset 变量名
    • 系统常用的环境变量
      • HOSTNAME 主机名
      • SHELL 当前的 Shell
      • TERM 终端环境
      • HISTSIZE 历史命令条数
      • SSH_CLIENT 当前操作环境是用 ssh 连接的,这里记录客户端 IP
      • SSH_TTY ssh 连接的终端时 pts/1
      • USER 当前登录的用户
      • PATH 系统查找命令的路径
        • echo $PATH 查看 PATH 环境变量
        • PATH="$PATH":/root/sh 增加 PATH 变量的值
      • PS1 变量 命令提示符设置
        • \d 显示日期,格式为”星期 月 日”
        • \H 显示完整的主机名,如默认主机名 “localhost.localdomain”
        • \t 显示 24 小时制时间,格式为 “HH:MM:SS”
        • \A 显示 24 小时制时间,格式为 “HH:MM”
        • \u 显示当前用户名
        • \w 显示当前所在目录的完整名称
        • \W 显示当前所在目录的最后一个目录
        • \$ 提示符。如果是 root 用户会显示提示符为 “#”,如果是普通用户会显示提示符为 “$”
        • 例子 PS1='[\u@\A \w]\$ '
    • 语系变量
      • locale 查询当前系统语系(echo $LANG
        • LANG 定义系统主语系的变量
        • LC_ALL 定义整体语系的变量
      • locale -a | more 查看 Linux 支持的所有语系
      • cat /etc/sysconfig/i18n 查询系统默认语系(下次开机语系变量)
  • 位置参数变量

    位置参数变量|作用
    —|—
    $n|n 为数字,$0 代表命令本身,$1-$9 代表第一到第九个参数,十以上的参数需要用大括号包含,如 ${10}
    $*|这个变量代表命令行中所有的参数,$* 把所有的参数看成一个整体
    $@|这个变量代表命令行中所有的参数,不过 $@ 把每个参数区分对待
    $#|这个变量代表命令行中所有参数的个数

  • 预定义变量

    预定义变量|作用
    —|—
    $? | 最后一次执行的命令的返回状态。如果这个变量的值为 0,证明上一个命令正确执行;如果这个变量的值为非 0(具体是哪个数,由命令自己决定,则证明上一个命令执行不正确)
    $$ | 当前进程的进程号
    $! | 后台运行的最后一个进程的进程号

  • 接收键盘输入

    • read [选项] [变量名]
      • -p 提示信息:在等待 read 输入时,输出提示信息
      • -t 秒数:read 命令会一直等待用户输入,使用此选项可以指定等待时间
      • -n 字符数:read 命令只接受指定的字符数,就会执行
      • -s 隐藏输入的数据,适用于机密信息的输入

Shell 变量例子

位置参数例子 1

1
2
3
4
5
6
7
8
9
#!/bin/bash
num1=$1
num2=$2
# 变量 sum 的和是 num1 加 num2
sum=$(($num1+$num2))
# 打印变量 sum 的值
echo $sum

位置参数例子 2

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# 使用 $# 代表所有参数的个数
echo "A total of $# parameters"
# 使用 $* 代表所有的参数
echo "The parameters is:$*"
# 使用 $@ 也代表所有参数
echo "The parameters is:$@"

位置参数例子 3($* 与 $@ 的区别)

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
for i in "$*"
# $* 中的所有参数看成是一个整体,所以这个 for 循环只会循环一次
do
echo "The parameters is: $i"
done
for y in "$@"
# $@ 中的每个参数都看成是独立的,所以 "$@" 中有几个参数,就会循环几次
do
echo "Parameter:$y"
done

接收键盘输入例子 1

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
read -p "Please input your name: " -t 30 name
echo $name
read -p "Please input your passwd: " -s passed
echo -e "\n"
echo $passwd
read -p "Please input your sex [M/F]: " -n 1 sex
echo -e "\n"
echo $sex

Linux Shell 实战

monitor_man.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
resettem=$(tput sgr0)
declare -A ssharray
i=0
numbers=""
for script_file in `ls -I "monitor_man.sh" ./`
do
echo -e "\e[1;35m" "The Script:" ${i} '==>' ${resettem} ${script_file}
ssharray[$i]=${script_file}
numbers="${numbers} | ${i}"
i=$((i+1))
done
while true
do
read -p "Please input a number [ ${numbers} ]:" execshell
if [[ ! ${execshell} =~ ^[0-9]+ ]];then
exit 0
fi
done

system_monitor.sh

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
#!/bin/bash
clear
if [[ $# -eq 0 ]]
then
# Define Variable reset_terminal
reset_terminal=$(tput sgr0)
# Check OS Type
os=$(uname -o)
echo -e '\E[32m' "Check OS Type" $reset_terminal $os
# Check OS Release Version and Name
os_name=$(cat /etc/issue|grep -e "Server")
echo -e '\E[32m' "Check OS Release Version and Name" $reset_terminal $os_name
# Check Architecture
architecture=$(uname -m)
echo -e '\E[32m' "Check Architecture" $reset_terminal $architecture
# Check Kernel Release
kernel_release=$(uname -r)
echo -e '\E[32m' "Check Kernel Release" $reset_terminal $kernel_release
# Check Hostname $HOSTNAME
host_name=$(uname -n)
echo -e '\E[32m' "Check Hostname" $reset_terminal $host_name
# Check Internal IP
internal_ip=$(hostname -I)
echo -e '\E[32m' "Check Internal IP" $reset_terminal $internal_ip
# Check External IP
external_ip=$(curl -s http://ipecho.net/plain)
echo -e '\E[32m' "Check External IP" $reset_terminal $external_ip
# Check DNS
name_servers=$(cat /etc/resolv.conf | grep -E "\<nameserver[ ]+" | awk '{print $NF}')
echo -e '\E[32m' "Check DNS" $reset_terminal $name_servers
# Check if connected to Internet or not
ping -c 2 baidu.com &> /dev/null && echo "Internet:Connected" || echo "Internet:Disconnected"
echo -e '\E[32m' "Check if connected to Internet or not" $reset_terminal $ping
# Check Logged In Users
who>/tmp/who
echo -e '\E[32m' "Logged In Users" && cat /tmp/who
rm -f /tmp/who
# Check System Memory Usages
system_men_usages=$(awk '/MemTotal/{total=$2}/MemFree/{free=$2}END{print (total-free)/1024}' /proc/meminfo)
apps_mem_usages=$(awk '/MemTotal/{total=$2}/MemFree/{free=$2}/^Cached/{cached=$2}/Buffers/{buffers=$2}END{print (total-free-cached-buffers)/1024}' /proc/meminfo)
echo -e '\E[32m' "System Memuserages" $reset_terminal $system_men_usages
echo -e '\E[32m' "Apps Memuserages" $reset_terminal $apps_mem_usages
load_average=$(top -n 1 -b | grep "load average:" | awk '{print $12 $13 $14}')
echo -e '\E[32m' "load averages" $reset_terminal $load_average
disk_average=$(df -hP | grep -vE 'Filesystem|tmpfs' | awk '{print $1 " " $5}')
echo -e '\E[32m' "disk averages" $reset_terminal $disk_average
fi

check_server_sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
resettem=$(tput sgr0)
nginx_server='http://10.170.22.217'
check_nginx_server()
{
status_code=curl -m 5 -s -w %{http_code} ${nginx_server} -o /dev/null
if [ $status_code -eq 000 -o $status_code -ge 500 ];then
echo -e '\E[32m' "Check http server error! Response status code is " $resettem $status_code
else
http_content=$(curl -s ${nginx_server})
echo -e '\E[32m' "Check http server ok! \n" $resettem $http_content
fi
}
check_nginx_server

Linux 权限管理

基本权限

  • drwxrw-r--
    • d 文件类型
    • r 读
    • w 写
    • x 执行
    • rwx 代表所有者权限
    • rw- 代表所属组权限
    • r– 代表其他人权限
  • 权限作用
    • 权限对文件的作用:
      • r 读取文件内容
      • w 编辑、新增、修改文件内容,但是不能删除文件
      • x 可执行
    • 权限对目录的作用:
      • r 可以查询目录下文件名(ls)
      • w 具有修改目录结构的权限。如新建文件和目录,删除此目录下文件和目录,重命名此目录下文件和目录,剪切(touch rm mv cp)
      • x 可以进入目录(cd)
    • 区别:
      • 对文件来讲,最高权限是 x
      • 对目录来讲,最高权限是 w
  • 权限命令
    • chmod
    • chmod [选项] 模式 文件名
      • 选项:
        • -R 递归
      • 模式:
        • [ugoa] [+.=] [rwx]
        • [mode=421]
      • 例子:
        • chmod u+x cangls.av
        • chmod a=rwx fengjie.av
        • chmod g+w,o+w furong.av
      • 权限的数字表示
        • r 4
        • w 2
        • x 1
    • chgrp
      • chgrp 组名 文件名 修改文件的所属组
    • chown
      • chown 用户名 文件名 修改文件的所有者

默认权限

  • umask 查看默认权限
    • 0022
      • 第一位 0:文件特殊权限
      • 022:文件默认权限
  • 修改 umask 值
    • 临时修改
      • umask 0002
    • 永久修改
      • vim /etc/profile

文件的默认权限特点

  • 文件默认不能建立为执行文件,必须手工赋予执行权限
  • 文件默认权限最大为 666
  • 默认权限需要换算成字母再相减
  • 建立文件之后的默认权限,为 666 减去 umask 值
    • 例子:
      • 文件默认最大权限 666,umask 值 022
      • -rw-rw-rw- 减去 -----w--w- 等于 -rw-r--r--,结果为 644
      • -rw-rw-rw- 键入 -----wx-wx 等于 -rw-r--r--,结果为 644

目录的默认权限特点

  • 目录默认权限最大为 777
  • 默认权限需要换算成字母再相减
  • 建立文件之后的默认权限,为 777 减去 umask 值
    • 例子:
      • 目录默认最大权限为 777,umask 值 022
      • -rwxrwxrwx 减去 -----w--w- 等于 -rwxr-xr-x,结果为 755

Linux 软件安装

软件包管理简介

  • 软件包分类
    • 源码包
      • 优点:
        • 开源,如果有足够的能力,可以修改代码
        • 可以自由选择所需的功能
        • 软件是编译安装,所以更加适合自己的系统,更加稳定也效率更高
        • 卸载方便
      • 缺点:
        • 安装步骤较多,尤其安装较大的软件集合时,容易出现拼写错误
        • 编译过程时间较长,安装比二进制安装时间长
        • 编译安装一旦报错,新手很难解决安装错误
    • 二进制包(RPM 包、系统默认包)
      • 优点:
        • 包管理系统简单,只通过几个命令就可以实现包的安装、升级、查询和卸载
        • 安装速度比源码包安装快得多
      • 缺点:
        • 经过编译,不再可以看到源代码
        • 功能选择不如源码包灵活
        • 依赖性
    • 脚本安装包(本质是源码包和二进制包)
      • 优点:安装简单、快捷
      • 缺点:完全丧失了自定义性

RPM 命令管理

RPM 包命令规则

  • httpd-2.2.15-15.el6.centos.1.i686.rpm
    • httpd:软件包名
    • 2.2.15:软件版本
    • 15:软件发布的次数
    • el6.centos:适合的 Linux 平台
    • i686:适合的硬件平台
    • rpm:rpm 包扩展名

RPM 包依赖性

  • 树形依赖:a->b->c
  • 环形依赖:a->b->c->a
  • 模块依赖:查询网站:www.rpmfind.net

RPM 安装命令

包全名与包名

  • 包全名:操作的包是没有安装的软件包时,使用包全名。而且要注意路径。
  • 包名:操作已经安装的软件包时,使用包名,是搜索 /var/lib/rpm/ 中的数据库

RPM 包安装

  • 安装
    • rpm [选项] 包全名
      • 选项:
        • -i(install)安装
        • -v(verbose)显示详细信息
        • -h(hash)显示进度
        • --nodeps 不检测依赖性
  • 升级
    • rpm -Uvh 包全名
      • 选项:
        • -U 升级
  • 卸载
    • rpm -e 包名
      • 选项:
        • -e 卸载
        • –nodeps 不检查依赖性
  • 查询
    • rpm -qa 查询所有已经安装的 RPM 包
    • rpm -q 包名 查询包是否安装
    • rpm -qi 包名 查询软件包详细信息
    • rpm -ql 包名 查询包中文件安装位置
      • RPM 包默认安装路径
        • /etc 配置文件安装目录
        • /usr/bin 可执行命令安装目录
        • /usr/lib 程序所使用的函数库保存位置
        • /usr/share/doc 基本的软件使用手册保存位置
        • /usr/share/man/ 帮助文件保存位置
    • rpm -qf 系统文件名
      • 选项:
        • -f 查询系统文件属于哪个软件包(file)
    • rpm -qR 包名
      • 选项:
        • -R 查询软件包的依赖性
        • -p 查询未安装包信息

Yum 命令管理

  • 查询
    • yum list 查询所有可用软件包列表
    • yum search 关键字 搜索服务器上所有和关键字相关的包
  • 安装
    • yum -y install 包名
      • 选项:
        • install 安装
        • -y 自动回答 yes
  • 升级
    • yum -y update 包名
      • 选项:
        • update 升级
        • -y 自动回答 yes
  • 卸载
    • yum -y remove 包名
      • 选项:
        • remove 卸载
        • -y 自动回答 yes
  • 组管理
    • yum grouplist 列出所有可用的软件组列表
    • yum groupinstall 软件组名 安装指定软件组,组名可以由 grouplist 查询出来
    • yum groupremove 软件组名 卸载指定软件组

源码包安装

源码包安装位置需要手动指定,一般安装在 /usr/local/软件名

安装源码包之前需要安装 C 语言编译器

源码包安装过程

  • 下载源码包
  • 解压缩下载的源码包
  • 进入解压缩目录
  • ./configure 软件配置与检查
    • 定义需要的功能选项
    • 检测系统环境是否符合安装要求
    • 把定义好的功能选项和检测系统环境的信息都写入 Makefile 文件,用于后续的编辑
  • make 编译
    • make clean 如果编译错误,尝试执行 make clean
  • make install 编译安装

源码包不需要卸载命令,直接删除安装目录即可。不会遗留垃圾文件

Linux 定时任务

Linux 定时任务

  • at 一次性定时任务
  • crontab 循环定时任务
  • crontab 设置
  • anacron 配置

at 一次性定时任务

1
2
3
4
5
# at 服务是否安装
chkconfig --list | grep atd
# at 服务的启动
service atd restart

at 的访问控制

  • 如果系统中有 /etc/at.allow 文件,那么只有写入 /etc/at.allow (白名单)文件中的用户可以使用 at 命令(/etc/at.deny 文件会被忽略)
  • 如果系统中没有 /etc/at.allow 文件,只有 /etc/at.deny 文件,那么写入 /etc/at.deny (黑名单)文件中的用户不能使用 at 命令。对 root 不起作用
  • 如果系统中这两个文件都不存在,那么只有 root 用户可以使用 at 命令

at 命令

  • at
    • at [选项] 时间
      • 选项:
        • -m 当 at 工作完成后,无论命令是否有输出,都用 email 通知执行 at 命令的用户
        • -c 工作号:显示该 at 工作的实际内容
      • 时间:
        • HH:MM 02:30
        • HH:MM YYYY-MM-DD 02:30 2013-07-25
        • HH:MM[am|pm] [month] [date] 02:30 July 25
        • HH:MM[am|pm] + [minutes|hours|days|weeks] now + 5 minutes
      • 例子:
        • at now + 2 minutes && /root/hello.sh >> /root/hello.log
        • at 02:00 2013-07-26 && /bin/sync && /sbin/shutdown -r now
      • 其他:
        • atq 查询当前服务器上的 at 工作
        • atrm [工作号] 删除指定的 at 任务

anncron

anacron 是用来保证在系统关机的时候错过的定时任务,可以在系统开机之后再执行

anacron 检测周期

  • anacron 会使用一天,七天,一个月作为检测周期
  • 在系统的 /var/spool/anacron/ 目录中存在 cron.{daily,weekly,monthly} 文件,用于记录上次执行的 cron 的时间
  • 和当前时间做比较,如果两个时间的差值超过了 anacron 的指定时间差值,证明有 cron 任务被漏执行

anacron 配置文件

  • vim /etc/anacrontab
    • RANDOM_DELAY=45 最大随机延迟
    • START_HOURS_RANGE=3-22 anacron 的执行时间范围是 3:00 - 22:00
    • 1 5 cron.daily nice run-parts /etc/cron.daily 天数 强制延迟(分钟) 工作名称 实际执行的命令

anacron 执行过程(以 cron.daily 为例)

  • 首先读取 /var/spool/anacron/cron.daily 中的上一次 anacron 执行的时间
  • 和当前时间比较,如果两个时间的差值超过 1 天,就执行 cron.daily 工作
  • 执行这个工作只能在 03:00-22:00 之间
  • 执行工作时强制延迟时间为 5 分钟,再随机延迟 0-45 分钟时间
  • 使用 nice 命令指定默认优先级,使用 run-parts 脚本执行 /etc/cron.daily 目录中的所有可执行文件

Crontab 简介

业务场景

  • 每分钟需要执行一个程序检查系统运行状态
  • 每天凌晨需要对过去一天的业务数据进行统计
  • 每个星期需要把日志文件备份
  • 每个月需要把数据库进行备份

概念理解

Crontab 是一个用于设置周期性被执行的任务的工具。

Crontab 实践

  • 相关工具
    • Mac OS 自带 SSH
  • 安装并检查 Crontab 服务
    • 检查 cron 服务
      • 检查 Crontab 工具是否安装:crontab -l
      • 检查 Crontab 可执行的命令:service crond
      • 检查 Crontab 服务是否启动:service crond status
      • 删除当前用户所有的定时任务: crontab -r
      • 打开定时任务列表,进行编辑:crontab -e
        • 实际上在修改目录 /var/spool/cron/root 下用户对应的文件
        • crontab 文件名 会把(crontab -e)文件里的内容都覆盖,使用时需要注意
      • 启动 Crontab 服务:service crond start
    • 安装 Crontab 服务(依次执行)
      • yum install vixie-cron
      • yum install crontabs
    • tail 命令
      • 作用:按照要求将指定的文件的最后部分输出到标准设备,如果文件有更新,tail 会主动刷新,确保你看到最新的文件内容
      • 选项
        • -f 循环读取
        • -q 不显示处理信息
        • -v 显示详细的处理信息
        • -c [数目] 显示的字节数
        • -n [行数] 显示行数
        • -s 与-f合用,表示在每次反复的间隔休眠 s 秒
        • -q 从不输出给出文件名的首部
  • Crontab 的基本组成
    • 配置文件
    • 系统服务 Crond
    • 配置工具 Crontab
  • Crontab 的配置文件格式
    • * * * * * Command
      • 第一个星号:分钟 0~59
      • 第二个星号:小时 0~23
      • 第三个星号:日期 1~31
      • 第四个星号:月份 1~12
      • 第五个星号:星期 0~7(0、7 都是星期天)
    • 用法:
      • 每晚的 21:30 重启 apache
        • 30 21 * * * service httpd restart
      • 每月 1 到 10 日的 4:45 重启 apache
        • 45 4 1-10 * * service httpd restart
      • 每月 1、10、22 日的 4:45 重启 apache
        • 45 4 1,10,22 * * service httpd restart
      • 每隔两分钟重启 apache 服务器
        • */2 * * * * service httpd restart
      • 晚上 11 点到早上 7 点之间,每隔一小时重启 apache
        • 0 23-7/1 * * * service httpd restart
      • 每天 18:00 至 23:00 之间每隔 30 分钟重启 apache
        • 0,30 18-23 * * * service httpd restart
        • 0-59/30 18-23 * * * service httpd restart
    • 总结:
      • * 表示任何时候都匹配
      • 可以用 “A,B,C” 表示 A 或者 B 或者 C 时执行命令
      • 可以用 “A-B” 表示 A 到 B 之间时执行命令
      • 可以用 “*/A” 表示每 A 时间执行一次命令
  • Crontab 配置文件
    • 全局配置文件
      • /etc/crontab 查看系统级别的 Crontab
    • 用户配置文件
      • /var/spool/cron/用户名称 查看用户级别的 Crontab
  • Crontab 的日志
    • 监听 Crontab 执行日志:tail -f /var/log/cron,可以看到用户级别和系统级别的 Crontab
    • 查看每天的 Crontab 日志:ls /var/log/cron*
  • Crontab 常见错误
    • 环境变量:涉及到文件路径时写全局路径。注意声明 #!/bin/bash 确保脚本中环境变量能够被识别
    • 第三和第五个域之间执行的是 “或” 操作
      • 四月的第一个星期日早晨 1 时 59 分运行 a.sh:59 1 1-7 4 * test 'date + \%w -eq 0 && /root/a.sh'
    • 两个小时运行一次
      • 错误例子:* 0,2,4,6,8,10,12,14,16,18,20,22 * * * date
      • 正确例子:0 */2 * * * date
  • Crontab 补充
    • 半分钟执行命令
      • date && sleep 0.5s && date
    • Crontab 最小时间是 1 分钟,控制 1 分钟执行多次,本应该是同时执行,但第二条被推迟了30s执行,效果就是1分钟执行了2次
      • */1 * * * * date >> /root/test/half.log
      • */1 * * * * sleep 30s; date >> /root/test/half.log