优化方案
定义大型网站
-
pv值(page views)
页面浏览量:一个网站所有页面在24小时内,被浏览的总的次数。达到千万级别/百万级别。 -
uv值(unique visitor)
独立访客:一个网站在24小时内,有多少个用户来访问网站。达到10w左右。 - 独立IP值:有多少个独立的IP地址来访问网站。达到10w左右。
uv值约等于独立IP值。考虑局域网,或者校内网,则独立IP小于uv值。
产生的问题
- 大并发(大访问量):在同一个时间点,有多少个用户来访问网站。 --> web吞吐量
- 大流量:用户访问网站时,需要的大的带宽,10G。 --> 流量
- 大存储:网站的数据库(表)数据流成海量趋势,表到了1T。 --> 如何快速查询数据库
常规解决方案
并发对应方案:对网站的架构重整,使用分层的结构,同时使用负载均衡/读写分离+集群。
负载均衡器(器,软件/硬件):
硬件:BIG-IP
,F5
,Net-Scaler
。硬件的特点:效率高,价格贵。
软件:LVS(Linux virtual server)
linux虚拟服务,Nginx
Web服务器+反向代理
读(select)写(update,insert,delete)分离:
写,一般把请求发送到master服务器上。
读,根据实际情况均衡到其它多个服务器上。(网站的程序80%都是读操作)
读写分离原理:
主服务器上操作完之后,会形成bin文件
,操作的行为会记录在bin文件
中。在从服务器中读取该文件,取在主服务器的行为,完成主从数据同步。(通过一个线程,不停的去读取bin文件)
通过配置完成读写分离。
集群
-
解决单点故障的恢复(冗余技术)
备用`apache`,`nginx`(处于休眠状态),通过心跳检测来检测`apache`,`nginx`(被检测的服务器属于激活状态)是否宕机。(备用服务器+服务器称之为集群)
- 不管一个集群中有多少个服务器,但是在同一时间,只有一个服务器处于激活/工作状态。
负载均衡的实现方案:
- 轮询技术(NAT实现)
- 静态实现
- 动态实现
- 端口复用
- 直接路由(DR)
大流量的解决方案:
- 网站的图片在保证使用情况下,尽可能小。
- 对数据压缩再传输 例如:
gzip
,deflate
- 把占用流量大的资源,放到专门的服务器上.例如:图片->图片服务器,视频->视频服务器
- 买带宽
大存储对应方案:
-
使用缓存:通过缓存来尽量减少或者不查询数据库。
常见的有:页面静态化(磁盘缓存),把动态页面转换成静态页面。 内存缓存`redis,memcached,mysql数据库的memory存储引擎`。
- 表的设计要满足3NF(3范式)
- 创建适当的索引(主键索引,唯一索引,普通索引,全文索引,空间索引)
- 创建适当存储过程,视图,函数,触发器
- 分表技术(水平分表和垂直分表):一个大表分成几个小表。从逻辑上分开。
- 分区技术:从物理上把数据分配到不同的磁盘空间
- 读写分离
- 优化程序中的SQL语句
- 优化apache的
my.ini
的配置,nginx的nginx.conf
的配置,比如配置最大并发,调整一些缓存大小。 - 硬件升级(使用64位机器,多个cpu)
页面静态化
页面静态化分为:
- 真静态:把一个动态的页面,转成一个静态的页面。
-
伪静态:所谓伪静态是从url地址上看是一个静态页面的,但是实际上还是对应一个动态页面。
伪静态的原理:
第一次访问的时候去查询数据库,并且生成静态页面。
第二次访问返回静态页面。
linxingzhang.com/index.html
-> 请求静态页面 <-- 首页对应的静态页面
伪静态的局部配置:
RewriteRule ^index\.html$ index\.php [L]
概念
动态网址:
一般来说去查询数据库,linxingzhang.com/news.php?id=1
特点:
- 查询数据库,速度慢
- 接口参数,安全性注意(SQL注入)
- 不利于SEO
只要有数据的接收,则要求对在客户端使用正则验证,同时当数据接收后,对数据再次通过正则验证。
SQL注入
SQL注入:利用现有应用程序,攻击者将精心构造的SQL语句注入到后台数据库,并使得数据库引擎成功执行。
SQL注入特点:
- 变种极多
- 攻击简单
- 危害极大
SQL注入攻击的危害性
- 未经授权情况下操作数据库中的数据
- 恶意篡改网页的内容
- 私自添加系统账号或者是数据库使用者账号
- 网页木马
SQL注入工具:
-
扫描检测
AWVS,web扫描器,APPScan Web扫描器等,抓包代理工具burpsuite
-
验证测试
sqlmap
手工探测SQL漏洞:
通过拼接SQL语句来判断和验证漏洞
// 测试语句
id=1'
and 1=1
and 1=2
order by num
union 联合查询
and 1=2 union select 1,2,3,4
user()
database()
group_coucat(table_name) from information_schema.tables where table_schema=tableName // table名十六进制
select * from downloads where id=1 and 1=2 union select 1,2,3,4,5,6,group_concat(username) from members;
?id=1 and 1=2 union select 1,2,3,4,5,6,load_file('/etc/passwd')
?id=1 and 1=2 union select 1,2,3,4,5,6,@@version
?id=1 and 1=2 union select 1,2,3,4,5,6,@@version_compile_os
?id=1 and 1=2 union select 1,2,3,4,5,6,@@basedir
?id=1 and 1=2 union select 1,2,3,4,5,6,@@datadir
SQL防御:
- 代码层防御
- 第三方安全程序及设备
代码层防御
编码阶段:
- 对输入进行验证
- 静态查询
- 最小权限
- 通用防注入脚本
- 安全函数(PHP程序:addslashes, mysql_real_escape_string等)
测试阶段:
- 代码验证
产品化阶段:
- Web应用安全网关
第三方安全程序
-
软件产品
mod_security 互联网安全防护产品(阿里云盾,安全宝等同类产品)
-
硬件
web应用防火墙
SQL语句验证
function CheckSql($db_string, $querytype = 'select') {
global $cfg_cookie_encode;
$clean = '';
$error = '';
$old_pos = 0;
$pos = -1;
$log_file = DEDEINC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';
$userIP = GetIP();
$getUrl = GetCurUrl();
// 如果是普通查询语句,直接过滤一些特殊语法
if ($querytype=='select') {
$notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";
// $notallow2 = "--|/\*";
if (eregi($notallow1,$db_string)) {
fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");
exit("Safe Alert: Request Error step 1 !");
}
}
// 完整的SQL检查
while (true) {
$pos = strpos($db_string, '\'', $pos + 1);
if ($pos === false) {
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (true) {
$pos1 = strpos($db_string, '\'', $pos + 1);
$pos2 = strpos($db_string, '\\', $pos + 1);
if ($pos1 === false) {
break;
} elseif ($pos2 == false || $pos2 > $pos1) {
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= '$s$';
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));
// 老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0) {
$fail = true;
$error="union detect";
}
// 发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false) {
$fail = true;
$error="comment detect";
}
// 这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0) {
$fail = true;
$error="slown down detect";
}
elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) {
$fail = true;
$error="slown down detect";
} elseif (strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0) {
$fail = true;
$error="file fun detect";
} elseif (strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0) {
$fail = true;
$error="file fun detect";
}
// 老版本的MYSQL不支持子查询,程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
elseif (preg_match('~\([^)]*?select~s', $clean) != 0) {
$fail = true;
$error="sub select detect";
}
}
静态网址:linxingzhang.com/a.html
静态的网址
特点:
- 一般不查询数据库,速度快
- 不接收参数,安全性比较高
- 利于SEO
伪静态网址:
从形式上看是一个静态的页面,但是实际上对应一个动态的页面。
特点:
- 本身需要查询数据库,速度慢
- 不接收参数,安全比较高
- 利于SEO
局部动态的方式:
-
直接嵌入JS
- ajax
- 如果动态的数据是因为用户某个操作引发的,则可以使用事件+ajax来处理
如何实现页面静态化
实现页面静态化(真静态),有两种方法:
- 使用PHP的OB机制
- 使用模板替换技术(正则)
OB缓存机制
OB就是output_buffering
,输出缓存,在请求一个PHP的过程中,实际上过三个缓存:1.程序缓存,2.OB缓存,3:浏览器缓存。
程序缓存,该缓存是PHP固有的,不能关闭。
每行代码,每个函数在程序缓存中,处理结果之后,拼接在一起通过HTTP响应返回给浏览器。
开启OB缓存:
- 在
php.ini
中开启:output_buffering = 4096
4096 缓存大小。作用于所有的php后缀文件. - 调用PHP函数
ob_start()
,只能作用于该页面
如果没有OB缓存,所有的缓存都放在程序缓存中。
header信息不管你是否开启ob,总是放入到程序缓存中。
ob_get_contents(); // 获取OB中的内容
ob_clean(); // 清除ob缓存,但不关闭ob缓存
ob_end_clean(); // 清除ob缓存,同时关闭ob缓存
ob_end_flush(); // 强制把ob缓存刷新到程序缓存,并关闭ob缓存
ob_flush(); // 强制把ob缓存刷新到程序缓存
flush(); // 把程序缓存刷新到浏览器缓存中
ob缓存细节
ob缓存
究竟可以存放什么样的数据?
静态数据:html,css,js,动态语言输出的结果.
ob放入的数据,从ob_start()
开始到ob_get_contents()
之间的返回给浏览器的静态页面
并发测试工具
常用工具
ab.exe
,winrunner
,loadrunner
ab.exe
基本用法:
切换到apache-bin
目录底下,然后运行ab.exe
命令
// ab.exe -n 总的请求次数 -c 并发量 请求页面地址
ab.exe -n 10000 -c 100 http://127.0.0.1 // 100个人完成10000次
测试结果:
> ab.exe -n 10000 -c 100 http://www.ting.com/index.html
This is ApacheBench, Version 2.3 <$Revision: 1554214 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking www.ting.com (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Server Software: Apache/2.4.9
Server Hostname: www.ting.com
Server Port: 80
Document Path: /index.html
Document Length: 6847 bytes
Concurrency Level: 100 // 并发数
Time taken for tests: 9.118 seconds // 完成的时间,值越小,服务器越好,越强悍。
Complete requests: 10000
Failed requests: 0
Total transferred: 71060000 bytes
HTML transferred: 68470000 bytes
Requests per second: 1096.69 [#/sec] (mean) // 一秒钟完成多少次请求
Time per request: 91.184 [ms] (mean) // 100个人并发一次花费的时间
Time per request: 0.912 [ms] (mean, across all concurrent requests) // 一个人并发花费的时间
Transfer rate: 7610.41 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 1.2 0 11
Processing: 26 90 23.8 84 424
Waiting: 26 76 24.4 72 410
Total: 27 90 23.9 85 425
Percentage of the requests served within a certain time (ms)
50% 85
66% 91
75% 96
80% 101
90% 109
95% 117
98% 128
99% 188
100% 425 (longest request)
Finished 10000 requests
调整apache的最大并发数
在默认情况下,apache的最大并发数为150
配置文件:httpd-mpm.conf
MPM(多路处理模块),就是apache处理并发的方式:
- perfork 预派生模式:预先创建配置的进程,等待浏览器的连接
- worker 工作者模式:有一定的进程,开了一些配置的线程,等待浏览器的连接(优于perfork模式,线程消耗更小)
- winnt模式 (windows下默认的模式):父进程-->子进程-->线程,等待浏览器的连接
确定mpm的指令
> httpd.exe -l
Compiled in modules:
core.c
mod_win32.c
mpm_winnt.c // MPM模式
http_core.c
mod_so.c
需要在httpd.conf
文件的打开mpm
从配置文件(Include conf/extra/httpd-mpm.conf
),然后在httpd-mpm.conf
文件中修改最大配置数
配置最大并发数,需要在合适数值之间,需要考虑服务器本身的性能问题
SEO
- url地址255字节
- 不要给静态页面带参数,否则
spider
不抓取 - 在前端尽量不使用frame,iframe
- 图片alt信息需要添加上
- 关键字,描述
移动端首屏优化
DNS预解析
DNS预解析与业务相关,
DNS缺点:
- 比较耗时
- 每个链接都需要重新建立链路
域名收敛
域名收敛:将静态资源只放在一个域名下面,而非发散情况下的多个域名下
作用:减少DNS解析的开销
performance对象
performance对象作用:查看网页性能数据
// 获取 performance 数据
var performance = {
// memory 是非标准属性,只在 Chrome 有
// 财富问题:我有多少内存
memory: {
usedJSHeapSize: 16100000, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize
totalJSHeapSize: 35100000, // 可使用的内存
jsHeapSizeLimit: 793000000 // 内存大小限制
},
// 哲学问题:我从哪里来?
navigation: {
redirectCount: 0, // 如果有重定向的话,页面通过几次重定向跳转而来
type: 0 // 0 即 TYPE_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)
// 1 即 TYPE_RELOAD 通过 window.location.reload() 刷新的页面
// 2 即 TYPE_BACK_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)
// 255 即 TYPE_UNDEFINED 非以上方式进入的页面
},
timing: {
// 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
navigationStart: 1441112691935,
// 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
unloadEventStart: 0,
// 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
unloadEventEnd: 0,
// 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
redirectStart: 0,
// 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0
redirectEnd: 0,
// 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
fetchStart: 1441112692155,
// DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupStart: 1441112692155,
// DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupEnd: 1441112692155,
// HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等
// 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
connectStart: 1441112692155,
// HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等
// 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
// 注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
connectEnd: 1441112692155,
// HTTPS 连接开始的时间,如果不是安全连接,则值为 0
secureConnectionStart: 0,
// HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存
// 连接错误重连时,这里显示的也是新建立连接的时间
requestStart: 1441112692158,
// HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
responseStart: 1441112692686,
// HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
responseEnd: 1441112692687,
// 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
domLoading: 1441112692690,
// 完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
// 注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源
domInteractive: 1441112693093,
// DOM 解析完成后,网页内资源加载开始的时间
// 在 DOMContentLoaded 事件抛出前发生
domContentLoadedEventStart: 1441112693093,
// DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
domContentLoadedEventEnd: 1441112693101,
// DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
domComplete: 1441112693214,
// load 事件发送给文档,也即 load 回调函数开始执行的时间
// 注意如果没有绑定 load 事件,值为 0
loadEventStart: 1441112693214,
// load 事件的回调函数执行完毕的时间
loadEventEnd: 1441112693215
}
};
链路复用
- TCP3次握手
- keep-alive(链路复用)提升的部分
- Server内存消耗,预选扩容
资源内联
在移动APP上面,在特定情况下会将CSS样式放在.html
中
组件化开发
- 按需/异步加载
- 异步渲染
- 结合服务端渲染
服务端渲染
- Ajax模式下,数据与页面资源串行
- 网络路径的增长,提升了网络异常的影响
- 使用
Node.js
进行服务端渲染 - 组件前后端同构