秒杀项目迭代流程
秒杀优化迭代
云端部署
在虚拟机上配置java环境,进入主目录, 使用vim ~/.bash_profile
,
JAVA_HOME=//user/java/1.8.0_121
PATH=$PATH:$JAVA_HOME/bin
安装mysql,yum install mysql*,centos7 安装mariadb yum install mariadb-server
发现启动不了mariadb,出现如下错误
Job for mariadb.service failed because the control process exited with error code. See "systemctl status mariadb.service" and "journalctl -xe" for details.
先关闭数据库:systemctl stop mariadb.service
,
复制/var/lib/mysql到/data/soft/mysql :cp -rp /var/lib/mysql/* /data/soft/mysql
查看当前目录是否有数据:ls -ld mysql/*
删除/var/lib/mysql目录,并创建对/data/soft/mysql的软链接
rm -rf mysql
ln -s /data/soft/mysql/ mysql
查看mysql ll mysql
查看mysql所有文件 ls -ld mysql/*
启动mariadb数据库
systemctl start mariadb.service
发现还是失败
查看/var/run/mariadb目录权限:ls -ld /var/run/mariadb
查一下/data/soft/mysql的权限:ls -ld /data/soft/mysql
,发现权限不是mysql,需要授权,chown mysql.mysql /data/soft/mysql
,再次查看修改权限,ls -ld /data/soft/mysql
再次启动发现没有报错,可以用lsof -i:3306
查看端口是否启动,也可以使用 ps-ef|grep mysql
,也可以使用 netstat -anp |grep 3306
查看。
设置数据库密码 mysqladmin -u root password root
登入数据库 mysql -uroot -proot
之后把数据库上传到服务器的tmp目录下,把数据导入到数据库中,mysql -uroot -proot <//tmp/miaosha.sql
,其次再登入数据库。
在本地用maven打包,在上传到服务器,把上传到服务器里面的文件移到自己创建的文件夹里mv //tmp/miaoshaProject-1.0-SNAPSHOT.jar ./miaoshaProject.jar
,在更改其属性,chmod -R 777 *
然后就可以启动项目了 ,在客服端上可以访问服务器,可以在项目目录下编辑一个application.properties的文件,把其端口改为80端口,java -jar miaoshaProject.jar --spring.config.addition-location=/www/miaosha/application.properties
用命令行启动配置文件里面的端口,可以访问到。
2.也可以使用脚本的方式配置启动,vim deploy.sh
配置启动文件的配置信息: nohup java -Xms400m -Xmx400m -XX:NewSize=200m -XX:MaxNewSize=200m -jar miaoshaProject.jar --spring.config.addition-location=/www/miaosha/application.properties
给文件赋予权限 chmod -R 777 *
开始启动 ./deploy.sh &
窗口会打印出新的文件 nohup.out,这是脚本的输出日志, 使用 tail -200f nohup.out
命令可以查看其日志输出,不会受到Ctrl + C 的影响。
编辑hosts,把服务器ip绑定到miaoshaserver,vim //etc/hosts
性能压测
使用性能测压工具--jmeter
压测工具的主要内容:线程组,Http请求,查看结果树,聚合报告。
刚开始主要测单个线程,10秒钟,一个循环,之后在慢慢压测。(例如变为20个线程)
使用 ps -ef|grep java 查看启动端口号,netstat -anp|grep 4181(监听端口号),pstree -p 4181(查看线程树) 查看线程数量是28个。
使用 pstree -p 4181 |wc -l 查看线程树数量,
top -H (查看整个CPU线程状态)
把线程数量加到100,循环次数增加到10次,
接着增加到500个线程数,
在用5000,10秒,100次循环压测,在查一遍线程数,发现最高可以达到84个,并发不能上去。
发现并发容量问题
server端并发数线程上不去,用4000线程,15秒钟,100次循环进行压测,
默认内嵌Tomcat配置
server.tomcat.accept-count:等待队列长度,默认100,
server.tomcat.max-connections:最大可被连接数,默认10000,
server.tomcat.max-threads:默认最大工作数200,
server.tomcat.min-spare-threads:最小工作线程数,默认10,
默认配置下,连接数超过10000后,出现拒接连接情况
默认配置下,触发的请求超过200+100后拒绝处理,
在服务器端配置application.properties
server.tomcat.accept-count:1000
server.tomcat.max-threads:800(4核8GB的配置)
server.tomcat.min-spare-threads:100
配置完成之后,杀掉进程,重新启动,再次查看该端口线程数总量,发现增加了。
定制化内嵌Tomcat开发
1.keepAliveTimeOut:多少毫秒后不响应的断开keepalive
2.maxKeepAliveRequests:多少次请求后keepalive断开后失效
3.使用WebServerFactoryCustomizer
在原来搭建的项目基础上,进行优化
添加WebServerConfiguration
//当spring容器里面没有TomcatEmbeddedServletContainerFactory这个bean时,会吧bean加载到spring容器中
public class WebServerConfiguration implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
//使用对应工厂类提供给我们的接口定制化我们的Tomcat connecor
((TomcatServletWebServerFactory)configurableWebServerFactory).addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
//转化为http11
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
//定制化keepalivetimeout
protocol.setKeepAliveTimeout(30000);
//当客户端发送超过10000个请求则自动断开keepalive链接
protocol.setMaxKeepAliveRequests(10000);
}
});
}
}
容量问题优化方向
单Web容器上限
线程数量:4核cpu 8G内存单进程调度线程数800-1000以上后即花费巨大的时间在cpu调度上(800是拐点)
等待队列长度:队列做缓冲池用,但也不能无限长,消耗内存,出队入队也耗cpu
MySql数据库QPS容量问题
主键查询:千万级别数据 = 1-10毫秒
唯一索引查询:千万级别数据 = 10-100毫秒
非唯一索引查询:千万级别数据 =100-1000 毫秒
无索引:百万条数据=1000毫秒 +
MySql数据库TPS容量问题
非插入更新删除操作:同查询
插入操作:1w~10w tps
分布式扩展
单机容量问题,水平扩展方案引入
表象:单机cpu使用率增高,memory占用增加,网络带宽使用增加
cpu us :用户空间的cpu使用情况
(用户层代码)
cpu sy :内核空间的cpu使用情况
(系统调用)
load average : 1,5,15分钟load平均值,跟着核数系数,0代表通常,1代表打满1+代表等待阻塞
memory:free空闲内存,used使用内存
mysql数据库开放远端连接
服务端水平对称部署
验证访问
把秒杀项目复制到两台客服端虚拟机,(服务器)
scp -r //var/www root@IP:/var/
修改application.properties
spring.datasource.url=jdbc:mysql://192.168.43.169:3306/miaosha?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai (我的数据库驱动是8系列的版本,虚拟机是5.6的,我需要重装mysql)
之后再telnet 192.168.43.169:3306 看是否ping通
没安装 Telnet yum install telnet
ping 不通报错,进入数据库服务器,登入修改
select host,user,password from user; 查看权限
如果没有授权给所有用户 使用grant all privileges on . to root'%' identified by 'root' ;
flush privileges;
如果没有安装java环境的,还需安装,
之后启动脚本,查看启动日志买就可以在浏览器中访问了。
修改前端资源用于部署nginx
编辑一个js页面,用于配置访问端口,gethost.js
var g_host = "localhost:8090";
部署NginxOpenResty
下载到服务器,解压并安装,
chmod -R 777 open~
tar -xvzf 解压
进入文件目录 进行配置 ./configure
可能会出现错误,需要安装PCRE
查看了官网,需要安装的一些支撑软件,yum install pcre-devel openssl-devel gcc curl
yum install PCRE ,安装之后再重新配置,接着make,之后 make install
cd /usr/local/local/openresty (进入安装文件夹目录)
nginx反向代理
使用nginx作为web服务器
使用nginx作为动静分离服务器
使用nginx作为反向代理服务器
进入nginx目录启动,sbin/nginx -c conf/nginx.conf
监听端口是否启动 netstat -an |grep 80
可以用客服端访问服务器ip地址,可以看到启动成功界面
location节点path :指定url映射key
location节点内容: root指定location path后对应的根路径,index指定默认的访问页
sbin/nginx -c conf/nginx.conf
启动
修改配置后直接sbin/nginx -s reload
无缝重启
前端资源部署
把前端得资源复制到nginx的服务器中,/usr/local/openresty/nginx/html/ 复制到这个目录下,访问nginx服务器ip/getotp.html 可以访问到前端界面,测试成功,
进入nginx服务器host配置文件,指定本地ip为miaoshaserver
编辑nginx中conf里面nginx.conf ,把server开面的 location 改为 location /resources/ {
alias /usr/local/openresty/nginx/html/resources/;(里面的第一行修改即可)
}
mkdir resources
mv*.html resources/
mv gethost.js resources/
cp -r static resources/
rm -rf static/
进入resources目录,查看文件
再次刷新网页,需要加上/resources 才能访问
使用nginx作为动静分离服务器
location节点path特定resources:静态资源路径
location节点其他路径∶动态资源用
nginx做反向代理服务器
设置upstream server(上行服务器)
设置动态请求location为proxy pass路径
开启tomcat access log验证
操作:编辑nginx config文件,在server上面加上
upstream backend_server{
server 客服端服务器1ip weight=1;
server 客服端服务器2ip weight=1;
}
在server里面加上 location/ {
proxy_pass http://backend_server;
proxy_set_header Host $http_host:$proxy_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
配置完成之后,之后再重新加载
进入日志目录,查看启动状态。
进入1号客服端服务器,进入秒杀文件夹,mkdir tomcat, chmod - R 777 tomcat/
重新编辑application.properties,(编辑内容: server.tomcat.accesslog.enable=true
server.tomcat.accesslog.directory=/var/www/miaosha/tomcat
server.tomcat.accesslog.pattern=%h %l %u %t ”%r“ %s %b %D)查看java启动端口号,kill,重启
进入tomcat文件夹,查看访问日志。
%h 代表远端的host,%l代表ident logic, %u代表user,%t代表时长,%r代表请求的url,%s带表请求的状态码,%b 代表response大小,%D代表处理请求的时长。
把application.properties复制到第2台秒杀客服端服务器上,scp application.properties root@第2台ip:/var/www
进入第2台客服端秒杀文件夹,查看配置,tomcat配置同上,再次刷新网页,查看日志,可以看见访问信息,用客服端访问,浏览器F12调试,打开Preserve log,可以看见请求地址是固定的IP,改下gethost脚本里面的ip改成miaoshaserver。上 传到nginx服务器, 路径: /usr/local/openresty/nginx/html/resources/
再次调试,发现请求URL变成了一个miaoshaserver
分布式扩展后的性能压测
连接数据库服务器ip,线程数1000,5秒,循环次数30次,
换成nginx服务器ip,同样测,发现tps容量比数据库的多,
默认的nginx服务器没有长链接,进入到nginx服务器(要进入nginx目录),查看80端口 ,netstat -an|grep 80,查看其数量 netstat -an|grep 80|wc -l,之后再测试客服端服务器的进程数量,编辑nginx.config,
开启keepalive 30;proxy_http_version 1.1;
proxy_set_header Connection "";之后再重启服务器,进行压测,查看establish, netstat -an|grep 客户端ip |grep Establish
回滚配置发现查看其ip地址一直在变,最后改回原来的配置,进行压测1000线程数,5秒,循环20次,可以测到极限。
nginx高性能的原因
1.epoll多路复用
java bio模型,阻塞进程式
linux select模型,变更触发轮训查找,有1024数量上限
epoll模型,变更触发回调直接读取,理论上无上限
(Linux2.6以下只能用select模型,以上能用epoll模型)
2.master worker进程模型
3.协程机制
1.依附于线程的内存模型,切换开销小
2.遇阻塞及归还执行权,代码同步
3.无需加锁
分布式会话管理
1.基于cookie传输sessionid : java tomcat容器session实现
2.基于token传输类似sessionid: java代码session实现
实现:(基于cookie)
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
2.编辑RedisCongfig
@Component
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisConfig {
}
3.下载Redis到对应得数据库服务器,下载到tmp目录,赋予权限chmod -R 777 redis压缩包,解压 tar -xzvf,./config,接着编译make,make install,之后就可以后台启动Redis -server &,启动客服端验证
4.在application.properties配置springboot对Redis的依赖
#配置springboot对redis的依赖
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=10
#spring.redis.password=
#设置jedis连接池
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.min-idle=20
在本地环境调试,把gethost.js 里面的参数改为localhost:8090,打开login界面,运行程序调试,发现UserModel没有实现序列化,select 10,key *,查看数据库发现生成了3个数据。
部署到2台秒杀服务器上,目录:/var/www/miaosha/
把原来的秒杀项目覆盖掉,mv 现在的jar包 miaosha.jar ,赋予权限,后台启动,第二台同上操作,
刚才安装了Redis,先 make clear,再make,编辑Redis -conf,bind 数据库的ip地址(注意要注释掉其他的ip,否则启动失败),以配置文件启动src/redis -server ./redis.conf &,查看redis是否启动,再监听端口是否绑定成功,netstat -an |grep 6379。
在秒杀2台服务器上修改Redis的ip :spring.redis.host=服务器ip
后台启动程序,查看日志是否启动成功,tail -f nohup,再次监听6379端口,(2台服务器同样的操作)
修改完成后,在浏览器中验证
2.实现(基于token)
@Autowired
private RedisTemplate redisTemplate;
//修改成若用户登录验证成功后将对应的登录信息和登录凭证一起存入redis中
//生成登录凭证token,UUID
String uuidToken = UUID.randomUUID().toString();
//建议token和用户登陆态之间的联系
redisTemplate.opsForValue().set(uuidToken,userModel);
redisTemplate.expire(uuidToken,1, TimeUnit.HOURS);
在前端修改相应的代码,修改getitem.html和login.html界面,接着在orderController里面优化实现token,完成之后在前端页面调试token,发现不能跳转前端页面,补上之后再次调试,window.localStorage,可以查看token的信息,去掉token信息里面的横杠, uuidToken = uuidToken.replace("-","");
清掉redis10号数据库,再次在前端调试,验证token。
查询优化与多级缓存
1.掌握多级缓存的定义
2.掌握redis缓存,本地缓存
3.掌握热点nginx lua缓存
用快速存取设备,用内存
将缓存推到离用户最近的地方
脏缓存清理
redis缓存
1.单机版
2.sentinal哨兵模式
3.集群cluster模式
Redis集中式缓存商品详情页接入
在ItemController添加Redis缓存,编写程序,
在商品详情页添加如下代码,
注入Redis模板类
ItemModel itemModel = null;
//根据商品的id到redis内获取
itemModel = (ItemModel) redisTemplate.opsForValue().get("item_"+id);
//若redis内不存在对应的itemModel,则访问下游service
if(itemModel == null){
itemModel = itemService.getItemById(id);
//设置itemModel到redis内
redisTemplate.opsForValue().set("item_"+id,itemModel);
redisTemplate.expire("item_"+id,10, TimeUnit.MINUTES);
}
调试程序之后,发现ItemModel和PromoModel没有实现序列化
之后,进给数据库查看key,发现是二进制的数据
再次编写RedisConfig,
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//首先解决key的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
//解决value的序列化方式
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
启动程序,清掉数据库,测试,查看数据信息,get item。。
优化序列化,编写JodaDateTimeJsonSerializer,JodaDateTimeJsonDeserializer,这两个类,之后也要把这个配置添加到RedisConfig中,清掉数据库,再次测试,发现刷新之后,报错,还需要在RedisConfig中加上一个配置,支持,缓存能反序列化,就完成了商品动态接入。
用压测工具压测,发现性能瓶颈明显提升。
热点内存本地缓存
1.热点数据
2.脏读不敏感
3.内存可控
Guava cache
1.可控制的大小和超时时间
2.可配置的lru策略
3.线程安全
实现:
1.引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
编写CacheService
//封装本地缓存操作类
public interface CacheService {
//存方法
void setCommonCache(String key,Object value);
//取方法
Object getFromCommonCache(String key);
}
其实现类
@Service
public class CacheServiceImpl implements CacheService {
private Cache<String,Object> commonCache = null;
@PostConstruct
public void init(){
commonCache = CacheBuilder.newBuilder()
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存中最大可以存储100个KEY,超过100个之后会按照LRU的策略移除缓存项
.maximumSize(100)
//设置写缓存后多少秒过期
.expireAfterWrite(60, TimeUnit.SECONDS).build();
}
@Override
public void setCommonCache(String key, Object value) {
commonCache.put(key,value);
}
@Override
public Object getFromCommonCache(String key) {
return commonCache.getIfPresent(key);
}
}
Item Controller补上其逻辑,优化整个逻辑,用IDEA DUBG可以验证其逻辑走向,有本地缓存直接取本地,没有直接取Redis。用压测工具压测发现其性能提升显著。
nginx proxy cache缓存
1.nginx反向代理前置
2.依靠文件系统存索引级的文件
3.依靠内存缓存文件地址
配置nginx conf
#申明一个cache缓存节点的路径
proxy_cache_path //usr/local/openresty/nginx/cache_temp levels=1:2 keys_zone=tmp_cache:100m inactive=7d max_siz# //usr/local/openresty/nginx/cache_temp把缓存文件放在哪里
#levels:目录设置两层结构用来缓存
#keys_zone指定了一个叫tmp_cache的缓存区,并且设置了100m的内存用来存储缓存key到文件路径的位置#inactive 缓存文件超过7天后自动释放淘汰
#max_size 缓存文件总大小超过100g后自动释放淘汰
location内加入
proxy_cache tmp_cache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 206 304 302 7d;
配置之后重启 进入nginx目录 sbin/nginx -s reload
查看tomcat日志,发现没有响应记录
进入nginx 临时缓存目录 cd 8; cd f6; cat 缓存信息;然后在进行压测,发现性能和平均消耗时间有所下降,所以还是回滚配置。
nginx lua缓存
lua协程机制
依附于线程的内存模型,切换开销小
遇阻塞及归还执行权,代码同步
无需加锁
nginx协程机制
nginx协程
1.nginx的每一个Worker进程都是在epoll或kqueue这种事件模型之上,封装成协程。
2.每一个请求都有一个协程进行处理。
3.即使ngx_lua须要运行Lua,相对C有一定的开销,但依旧能保证高并发能力
协程机制
1.nginx每个工作进程创建一个lua虚拟机
2.工作进程内的所有协程共享同一个vm
3.每个外部请求由一个lua协程处理,之间数据隔离
4.lua代码调用io等异步接口时,协程被挂起,上下文数据
5.自动保存,不阻塞工作进程
6.io异步操作完成后还原协程上下文,代码继续执行
nginx处理阶段
NGX_HTTP_POST_READ_PHASE= O,//读取请求头
NGX_HTTP_SERVER_REWRITE_PHASE,//执行rewrite rewrite_handler
NGX_HTTP_FIND_CONFIG_PHASE,//根据uri替换location
NGX_HTTP_REWRITE_PHASE,//根据替换结果继续执行rewrite>rewrite_handler
NGX_HTTP_POST_REWRITE_PHASE,//执行rewrite后处理
NGX_HTTP_PREACCESS_PHASE,//认证预处理 请求限制,连接限制→limit_conn_hander limit_req_handler
NGX_HTTP_ACCESS_PHASE,//认证处理→auth_basic_handler,access_handler
NGX_HTTP_POST_ACCESS_PHASE,//认证后处理,认证不通过,丢包
NGX_HTTP_TRY_FILES_PHASE,//尝试try标签
NGX_HTTP_CONTENT_PHASE,//内容处理→static_handler
NGX_HTTP_LOG_PHASE//日志处理→log_handler
nginx lua插载点
init_by_lua:系统启动时调用
init_worker_by_lua: worker进程启动时调用
set_by_lua: nginx变量用复杂lua return
rewrite_by_lua:重写url规则
access_by_lua :权限验证阶段
content_by_lua:内容输出节点
实战:
进入 //usr/local/openresty 目录,mkdir lua,cd lua,vim init.lua,
编辑内容:ngx.log(ngx.ERR, "init lua success");
进入nginx目录,编辑nginx.conf, init_by_lua_file ../lua/init.lua;
之后重启(以nginx配置文件方式),刚才配置仅是实例,回滚配置,
在server里面的location位置加上如下配置,location /staticitem/get{
default type "text/ html";
content_by_lua_file ../lua/ staticitem.lua;
}
在openresty/lua目录下,vim staticitem.lua, ngx. say ( "hello static item lua" ) ;
完成之后,重启访问验证
OpenResty
1.OpenResty由Nginx核心加很多第三方模块组成,默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用。
2.借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序。
3.OpenResty提供了大量组件如Mysql、Redis、Memcached等等使在Nginx上开发Web应用更方便更简单。
实践
1.openresty hello world
进入openresty/lua 目录,vim HelloWorld.lua, ngx.exec ( "/item/get?id=6");
进入nginx目录,编辑nginx.conf
location /helloworld{
content_by_lua_file ../lua/ helloworld.lua;
}
完成之后,重启访问验证
2.shared dic:共享内存字典,所有worker进程可见,Iru淘汰
编辑nginx.conf lua_shared_dict my_cache 128m;
编辑 vim itemsharedic. lua
function get_from_cache( key)
local cache ngx = ngx.shared. my_cache
local value = cache_ngx:get( key)
return value
end
function set_to_cache( key , value,exptime)
if not exptime then
exptime = 0
end
local cache_ngx =ngx.shared . my_cache
local succ,err,forcible =cache_ngx : set( key , value ,exptime)
return succ
end
local args = ngx.req-get_uri_args ( )
local id = args["id"]
local item_model = get_from_cache( "item_"..id)
if item_model == nil then
local resp = ngx.location.capture(" /item/get?id="..id)
item_model = resp. body
set_to_cache( "item_"..id,item_model,1*60)
end
ngx.say ( item_model)
再次编辑nginx.conf 在server里面加上 location /luaitem/get{
default_type "application/json";
content_by_lua_file ../lua/itemsharedic.lua;
}
配置完成之后 重启,再在一台秒杀服务器上面查看tomcat启动日志,验证其性能,用压测工具压测,发现其性能相比于 代理缓存有所提高,缺点更新nginx sharedictionary很麻烦。
3.openresty redis支持
进入 openresty/lua 目录,vim itemredis.lua
local args = ngx.req.get_uri_args ()
local id = args ["id"]
local redis = require "resty. redis"
local cache = redis : new( )
local ok,err = cache: connect( "172.31.49.157",6379)
local item_model = cache: get( "item_"..id)
if item_model == ngx.null or item_model == nil then
local resp = ngx.location.capture("/item/getid="..id)
item_model = resp. body
end
ngx. say (item_model)
修改nginx.conf 把刚才配置的 location/luaitem/get 里面的content_by_lua_file 改成
../lua/itemredis.lua
配置完成之后,重启测试,并用压测工具进行压测,发现性能提升明显。
查询性能优化之页面静态化
静态资源CDN
对静态资源进行压测,发现性能很低。
静态请求CDN
1.DNS 用CNAME解析到源站
2.回源缓存设置
3.强推失效
在阿里云上面创建CDN,域名为 miaoshaserver.test.com
业务类型 选者图片,源站信息类型 IP,IP地址为你nginx的地址,端口 80,加速地区中国大陆。
购买一个域名,添加一个解析,主机记录为miaoshaserver,记录值为CDN域名的cname。配置完成之后进行压测,发现性能提升明显。
cache control响应头
1.private :客户端可以缓存
2.public :客户端和代理服务器都可以缓存
3.max-age=XXX∶缓存的内容将在xXx秒后失效
4.no-cache :强制向服务端再验证一次
5.no-store :不缓存请求的任何返回内容
有效性判断
ETag :资源唯─标识
If-None-Match:客户端发送的匹配ETag标识符
Last-modified :资源最后被修改的时间
If-Modified-Since:客户端发送的匹配资源最后修改时间的标识符
流程图
浏览器三种刷新方式
1.回车刷新或a链接∶看cache-control对应的max-age是否仍然有效,有效则直接from cache,若cache-control中达no-cache ,则进入缓存协商逻辑
2.F5刷新或command+R刷新:去掉cache-control中的max-age或直接设置max-age为0,然后进入缓存协商逻辑
3.ctrl+ F5或commond+shift+R刷新:去掉cache-control和协商头,强制刷新
协商机制,比较Last-modified和ETag到服务端,若服务端判断没变化则304不返回数据,否则200返回数据
CDN自定义缓存策略
可自定义目录过期时间
可自定义后缀名过期时间
可自定义对应权重
可通过界面和api强制cdn对应目录刷新(不能保证成功)
静态资源部署策略(一)
1.css,js,img等元素使用带版本号部署,例如a.js?v=1.0不便利,且维护困难
2.css,js,img等元素使用带摘要部署,例如a.js?v=45edw存在先部署html还是先部署资源的覆盖问题
3.cssjs,img等元素使用摘要做文件名部署,例如45edw.js,新老版本并存且可回滚,资源部署完后再部署html
静态资源部署策略(二)
1.对应静态资源保持生命周期内不会变,max-age可设置的很长,无视失效更新周期
2.html文件设置no-cache或较短max age ,以便于更新
3.html文件仍然设置较长的max age,依靠动态的获取版本号请求发送到后端,异步下载最新的版本号的html后展示渲染在前端
策略:
动态请求也可以静态化成json资源推送到cdn上
依靠异步请求获取后端节点对应资源状态做紧急下架处理
可通过跑批紧急推送cdn内容以使其下架等操作
全页面静态化
1.html , css , js静态资源cdn化
2.js ajax动态请求cdn化
3.全页面静态化
定义∶在服务端完成html , css,甚至js的load渲染成纯html文件后直接以静态资源的方式部署到cdn上
phantomjs
无头浏览器,可以借助其模拟webkit js的执行
下载,使用 bin/phantomjs examples.hello.js 测试
在phantomjs目录下进入js目录,vim spiderbaidu.js,
var page = require ( 'webpage ' ) .create( ) ;
page.open('http: // www . baidu . com' , function(){
setTimeout(function( ) {
page. render( ' baidu. png ' ) ;
phantom. exit( );
},200);
});
启动 :bin/phantomjs js/spiderbaidu.js
phantomjs应用
1.修改需要全页面静态化的实现,采用initView和hasInit方式防止多次初始化
2.编写对应轮讯生成内容方式
3.将全静态化页面生成后推送到cdn
全页面静态化实现
进入phantomjs/ js目录,vim getitem.js;编辑内容如下
var page = require( "webpage" ).create( ) ;
var fs = require( "fs" );
page.open ( "http: // miaoshaserver/ resources/getitem. html?id=6" , function( status ){
console. log ( " status ="+status) ;
fs.write( "getitem. html" ,page. content , "w");
phantom.exit( );
});
启动脚本
bin/phantomjs js/getitem. js
把修改的getitem.html改名为getitemphantom.html保存到服务器中,然后把gethost.js改名为miaoshaserver,在调试,重新修改getitem.js,在启动 bin/phantomjs js/getitem.js
修改getitem.html
<input type="hidden" id="isInit" value="0"/>
function hasInit(){
var isInit =$("#isInit" ).val;
return isInit;
}
function setHasInit(){
$ ( "#isInit" ).val( "1");
}
function initView(){
var isInit = hasInit();
if(isInit ."1"){
return;
}
把ajax请求加过去。。。(获取商品详情)
}
修改 getitem.js
var isInit = "0" ;
setInterval( function( )
if( isInit != "1"){
page .evaluate( function( )
initView( ) ;
});
isInit = page.evaluate(function( ){
return hasInit( );
});
}else{
fs.write( "getitemphantom. html" ,page.content, "w");
phantom.exit( );
}
},1000 );
把getitem.html部署到nginx服务器上(nginx/html/resources/),启动脚本,在客服端验证一下静态页面,是否是全页面静态化。