一.页面静态化分析
涉及技术:fastdfs、velocity、redis、rabbitMQ、mysql、nginx…
1.页面静态化分析
1.1.什么是页面静态化
传统的页面的渲染方式是,请求某个页面的时候,后台需要查询数据 , 然后视图解析器会找到对应的页面的模板进行视图的渲染 ,然后写到浏览器 , 而页面静态化就是把前面的这个动作提前做好, 也就是说事先就把这个页面的内容渲染好,生成html,放到项目中 , 以后浏览器访问这个页面的时候就直接返回html即可,不要在走查询数据,渲染数据等流程。
注意:如果页面数据发生改变,那么页面需要重新触发页面静态化,重新渲染数据。所以那些页面数据经常变的,并发本身就不高的页面不适合做页面静态化。
1.2.为什么要做页面静态化
因为有些页面的并发比较高,那么这个页面所需要的数据的查询就会造成服务器(数据库)很大的压力 , 而且有些页面的数据不会经常变, 那么基于这种情况我就可以考虑做页面静态化来提升页面的查询数据,服务器的压力。
1.3.怎么做页面静态化、页面静态化的原理
使用模板技术:(JSP、Thymeleaf 3、Velocity 1.7、Freemarker 2.3.23) 把模板(页面)和数据进行合并成html
1.4.我们的hrm的页面静态化的需求
2.页面静态化步骤分析
2.1.页面静态化管理 pager
2.1.1.数据库hrm-page
2.1.2.页面静态化服务hrm-page-parent
2.1.3.完成页面的管理
添加:添加页面 , 上传模板到fastdfs
2.2.触发页面静态化
2.2.1.获取到pager,
2.2.2.查询填充数据,存储到Redis , 拿到key
2.2.3.调用页面静态化服务(数据:key ,pager )
2.3.页面静态化服务
2.3.1.下载模板 - fastdfs (解压工具ZipUtils)
2.3.2.根据key获取填充的数据
2.3.3.合并生成html ( 工具VelocityUtils)
2.3.4.把html上传到Fastdfs ,拿到fastdfs中html路径
2.3.5.往消息队列放入消息(routingkey , html的路径 ,html输出到nginx的路径 )
2.4.Nginx的代理服务
2.4.1.监听消息队列,获取到消息
2.4.2.从fastdfs获取html,下载
2.4.3.把html保存到nginx中(html输出到nginx的路径 )
发送消息到消息队列 流程图
注意:这里也可以使用一个队列,不同的Nginx监听不同的Routingkey也可以实现
二.页面静态化Page管理
1.创建Page服务
hrm-page-parent
hrm-page-service-2060
hrm-page-client
模板引擎
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
压缩工具 - ZipUtils
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.1</version>
</dependency>
2.生成Page服务基础代码
3.完成前端Page的管理
4.完成Page的添加功能 - 上传模板zip
三.主站主页的页面静态化
一.课程服务
触发主页页面静态化
private void doPageStaticForHome(){
//1.准备页面静态化的 page的name
String pageName = "home";
//2.准备好填充的数据
//模板路径:指的是,从fastdfs把模板的压缩包下载,解压到本地目录之后的那个本地目录路径
//模板路径交个pager服务去确定
//课程类型的层级结构的列表
//这里需要把有层级结构的课程类型的数据存储到redis
List<CourseType> courseTypes = treeData();
Map<String,Object> map = new HashMap<>();
map.put("courseTypes",courseTypes);
redisClient.set(RedisConstants.KEY_TREEDATA_COURSETYPE, JSON.toJSONString(map));
//3.调用pager服务做页面静态化
pageClient.page(pageName, RedisConstants.KEY_TREEDATA_COURSETYPE);
}
2.静态化服务
@Override
public void staticPage(String pageName, String dataKey) {
//1.根据PageName查询pager对象
Pager pager = baseMapper.selectByPageName(pageName);
//2.从Pager中拿到模板的路径(fastdfs)
String templateUrlInFastDfs = pager.getTemplateUrl();
//3.根据模板路径下载模板的zip
byte[] vmBytes = fastdfsClient.download(templateUrlInFastDfs);
if(null == vmBytes || vmBytes.length == 0){
throw new GlobalException("下载模板失败");
}
//4.解压模板到本地目录: 系统的临时目录
//4.1.确定模板压缩包的存储路径 C:\Users\0427\AppData\Local\Temp/index.zip
String basePath = System.getProperty("java.io.tmpdir")+"static/";
String vmZipPath = basePath+pageName+".zip";
try {
File zipFile = new File(vmZipPath);
//文件是否存在
if(!zipFile.exists()){
zipFile.getParentFile().mkdirs();
zipFile.createNewFile();
}
//把文件拷贝到本地 C:\Users\0427\AppData\Local\Temp/index.zip
FileCopyUtils.copy(vmBytes, zipFile );
//new FileOutputStream( new File(vmZipPath)).write(vmBytes);
//解压模板zip压缩包
ZipUtils.unZip(vmZipPath, basePath);
} catch (Exception e) {
e.printStackTrace();
}
//5.拿到解压目录的路径(系统的临时目录),作为填充数据 basePath
//6.根据dataKey从redis中获取到填充的数据
AjaxResult dataAjaxResult = redisClient.get(dataKey);
if(!dataAjaxResult.isSuccess() || dataAjaxResult.getResultObj() == null){
throw new GlobalException("填充数据获取失败");
}
//List<CourseType>
//转换填充的数据
Map<String,Object> map = JSON.parseObject(dataAjaxResult.getResultObj().toString(), Map.class);
map.put("staticRoot", basePath);
//7.使用模板引擎合并模板,生成html
//C:\Users\0427\AppData\Local\Temp\static\home.vm
String templatePath = basePath+pageName+".vm";
String htmlOutPath = basePath+pageName+".html";
//合并模板
VelocityUtils.staticByTemplate(map, templatePath,htmlOutPath );
//8.把生成好的html上传到fastdfs ,返回路径
try {
byte[] bytes = FileCopyUtils.copyToByteArray(new File(htmlOutPath));
AjaxResult htmlAjaxResult = fastdfsClient.uploadBytes(bytes, "html");
if(!htmlAjaxResult.isSuccess()){
throw new GlobalException("html上传到fastdfs失败");
}
//9.保存一个 page-config记录到mysql
PageConfig pageConfig = new PageConfig();
//redis的数据的key
pageConfig.setDataKey(dataKey);
//文件服务器的类型 1:fastdfs
pageConfig.setDfsType(1L);
//pager表的id
pageConfig.setPageId(pager.getId());
//html在fastdfs的路径
pageConfig.setPageUrl(htmlAjaxResult.getResultObj().toString());
//html需要拷贝到nginx的那个磁盘路径
pageConfig.setPhysicalPath(pager.getPhysicalPath());
pageConfig.setTemplateUrl(pager.getTemplateUrl());
//todo
pageConfigMapper.insert(pageConfig);
//10.发送一个Rabiitmq到队列中
} catch (Exception e) {
e.printStackTrace();
}
}
3.Fastdfs服务修改-增加下载方法
3.页面静态化服务集成MQ
5.1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
5.2.配置
......
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
5.3.配置类
@Configuration
public class MQConfig {
/**
* 交换机配置
* ExchangeBuilder提供了fanout、direct、topic、header交换机类型的配置
*/
@Bean(MQConstants.HRM_PAGE_EXCHANGE_FANOUT)
public Exchange HRM_PAGE_EXCHANGE_FANOUT() {
//durable(true)持久化,消息队列重启后交换机仍然存在
return ExchangeBuilder.fanoutExchange(MQConstants.HRM_PAGE_EXCHANGE_FANOUT).durable(true).build();
}
}
5.4.发送消息
//查询站点
String routingKey = siteMapper.selectById(pager.getSiteId()).getSn();
//10.发送一个Rabiitmq到队列中
Map<String,String> params = new HashMap<>();
//html页面在fastdfs的地址
params.put("pageUrl", pageConfig.getPageUrl());
//nginx的存放html的路径
params.put("physicalPath", pageConfig.getPhysicalPath());
params.put("siteSn", routingKey);
//发消息:交换机使用fanout广播的交换机
rabbitTemplate.convertAndSend(MQConstants.HRM_PAGE_EXCHANGE_FANOUT
, "", JSON.toJSONString(params));
4.Nginx代理服务
1.创建工程,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.itsource.hrm</groupId>
<artifactId>hrm-basic-utils</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>cn.itsource.hrm</groupId>
<artifactId>hrm-fastdfs-client</artifactId>
</dependency>
<!--spirngboot集成rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
2.主配置类/配置文件
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("cn.itsource.hrm.client")
public class NginxProxyServiceApplication2070 {
public static void main(String[] args) {
SpringApplication.run(NginxProxyServiceApplication2070.class);
}
}
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1010/eureka/ #注册中心地址
instance:
prefer-ip-address: true #使用ip地址注册
instance-id: hrm-nginx-proxy-service #指定服务的id
server:
port: 2070
max-http-header-size: 102400000
tomcat:
max-http-header-size: 102400000
spring:
application:
name: hrm-nginx-proxy-service
feign:
hystrix:
enabled: true #开启熔断支持
client:
config:
remote-service: #服务名,填写default为所有服务
connectTimeout: 30000
readTimeout: 30000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
3.MQ配置类
@Configuration
public class MQConfig {
//队列名字
public static final String QUEUE_NAME_HRMSITE = "queue_name_hrmsite";
/**
* 交换机配置
* ExchangeBuilder提供了fanout、direct、topic、header交换机类型的配置
*/
@Bean(MQConstants.HRM_PAGE_EXCHANGE_FANOUT)
public Exchange HRM_PAGE_EXCHANGE_FANOUT() {
//durable(true)持久化,消息队列重启后交换机仍然存在
return ExchangeBuilder.fanoutExchange(MQConstants.HRM_PAGE_EXCHANGE_FANOUT).durable(true).build();
}
//声明队列:针对sms的队列
@Bean(name=QUEUE_NAME_HRMSITE)
public Queue QUEUE_NAME_HRMSITE() {
Queue queue = new Queue(QUEUE_NAME_HRMSITE,true);
return queue;
}
/**
* 绑定队列到交换机 .
*/
@Bean
public Binding BINDING_QUEUE_NAME_HRMSITE() {
return BindingBuilder.bind(QUEUE_NAME_HRMSITE()).to(HRM_PAGE_EXCHANGE_FANOUT()).with("").noargs();
}
}
注意:这里使用的是广播fanout模式的交换机 , 所有的消息放入同一个队列 , 不同的代理服务配置了不同的站点,发送消息的时候,参数中需要带上目标站点 , 然后监听到消息后,要获取到消息参数中的站点和当前代理服务配置的站点做比对,如果一致,站点就对应上了,就实现html的拷贝 。 因为使用的是广播,所有的代理服务都会收到消息,所以要进行站点的判断。
4.接收消息
@Component
public class RevHtmlHandler {
@Value("${mq.routingkey}")
public String routingKey;
@Autowired
private FastdfsClient fastdfsClient ;
//监听页面静态化消息
@RabbitListener(queues = MQConfig.QUEUE_NAME_HRMSITE)
public void handler(String msg, Message message , Channel channel) throws IOException {
//1.获取消息
Map map = JSON.parseObject(msg, Map.class);
String pageUrl = map.get("pageUrl").toString();
String physicalPath = map.get("physicalPath").toString();
String siteSn = map.get("siteSn").toString();
//发布消息的站点要和当前服务配置的站点一致
if(StringUtils.hasLength(siteSn) && siteSn.equals(routingKey)){
//2.从fastdfs中下载html
byte[] htmlBytes = fastdfsClient.download(pageUrl);
if(htmlBytes == null || htmlBytes.length == 0){
throw new GlobalException("html下载失败");
}
//3.把html拷贝到 physicalPath路径
File file = new File(physicalPath);
if(!file.exists()){
file.getParentFile().mkdirs();
file.createNewFile();
}
FileCopyUtils.copy(htmlBytes,file);
}
}
}
5.手动签收
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
//消息ID
long deliveryTag = message.getMessageProperties().getDeliveryTag();
//手工ack
channel.basicAck(deliveryTag,true);