前提
最近做了一个手机网站,进入首页会有六个模块的内容需要访问后台。去获取对应的数据。这样是很占带宽和占用cpu,频繁的查询也很消耗数据库的性能。
解决办法
解决办法就是把多条请求合并成一条,这样就节约了带宽浪费的问题。因为java是线程同步的,所以多个请求一起的话,会增加查询数据库的时间。解决办法就是使用多线程,多个线程同时去查询数据库,减少等待时间。因为首页数据是经常访问的。所以会频繁的访问数据库。这个问题的解决办法是使用缓存数据库,redis。先查询redis,看是不是有数据,有的话就返回,没有的话就查询数据库,再把结果存入redis,就可以加快首页访问的响应速度。spring整合redis+redisTemplate教程的链接:添加链接描述
1.新建多线程配置类ThreadConfig
代码如下:
package com.bbkj.common.thread;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* @author JJ
* @version 1.0
* @description: TODO
* @date 2022/1/4 20:20
*/
@Configuration
@ComponentScan("com.bbkj.common.thread")
@EnableAsync //启用一步任务
public class ThreadConfig {
// 这里声明一个bean,类似于xml中的<bean>标签。
// Executor就是一个线程池
@Bean("asyncTaskExecutor")
public Executor getExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(7);
executor.setQueueCapacity(18);
executor.initialize();
return executor;
}
}
这里我是按照并发任务的数量去设置核心线程数和最大线程数的,使用的时候可以按照自己项目的情况去设定。
2.使用@Async去执行异步方法,注入spring容器。
package com.bbkj.service.impl;
import com.bbkj.common.utils.Page;
import com.bbkj.domain.Carousel;
import com.bbkj.domain.Catalog;
import com.bbkj.service.CarouselService;
import com.bbkj.service.CatalogService;
import com.bbkj.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.criterion.DetachedCriteria;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
/**
* @author JJ
* @version 1.0
* @description: TODO 新建异步方法
* @date 2022/1/5 9:28
*/
@Slf4j
@Service
public class AsynTaskService {
@Autowired
private CarouselService carouselService;
@Autowired
private ProductService productService;
@Autowired
private CatalogService catalogService;
/*
这里可以注入spring中管理的其他bean,这也是使用spring来实现多线程的一大优势
@Async 标注此任务为异步任务,在执行此方法的时候,会单独开启线程来执行
*/
@Async
public Future<Page> getCarousel() {
DetachedCriteria criteria = DetachedCriteria.forClass(Carousel.class);
Page page = carouselService.pageList(criteria, 1, 8);
log.info("轮播图获取完成");
return new AsyncResult<>(page);
}
@Async
public Future<Page> getRecommend() throws Exception {
//page为取出的那页数据
try {
Page page = productService.findByRecommend(1, 1, 5);
log.info("推荐商品获取完成");
return new AsyncResult<>(page);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* @return java.util.concurrent.Future<com.bbkj.common.utils.Page>
* @author JJ
* @Description 有返回值
* @Date 2022/1/6 15:57
**/
@Async
public Future<Page> getCatalog() throws Exception {
try {
List result = new ArrayList();
//带分页器
Page page = catalogService.findFather(1, 5);
List<Catalog> catalogs = page.getPageList();
setSort(catalogs, result);
log.info("商品分类获取完成");
return new AsyncResult<>(page);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//包装分类,sorts1是二级分类,sort是操作的一级分类
public List setSort(List<Catalog> catalogs, List result) {
for (Catalog item : catalogs) {
List<Catalog> sorts1 = catalogService.findChild(item.getId());
Map map = new HashMap();
map.put("catalogs", sorts1);
map.put("icon", item.getIcon());
map.put("catalog_name", item.getCatalog_name());
map.put("paren_id", item.getParen_id());
map.put("id", item.getId());
result.add(map);
}
return result;
}
}
注意:这里的@Async注解作用的方法。需要返回值的话,只能是用Future<希望返回的类型>来返回,直接返回其他类型没用。
还可以使用其他方式提交异步任务,如下
@Autowired
AsyncTaskExecutor asyncTaskExecutor;//注入线程池对象
//通过线程池对象提交异步任务
asyncTaskExecutor.submit(() -> {
log.info("异步任务开始");
//省略异步任务业务逻辑...
log.info("异步任务结束");
});
3.调用异步方法
package com.bbkj.action;
import com.alibaba.fastjson.JSON;
import com.bbkj.common.BaseAction;
import com.bbkj.common.redis.RedisUtil;
import com.bbkj.service.impl.AsynTaskService;
import com.bbkj.common.utils.Page;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
/**
* @author JJ
* @version 1.0
* @description: TODO
* @date 2022/1/4 20:19
*/
@Getter
@Setter
@Controller
@ParentPackage("post")
@Namespace("/base")
@Slf4j
public class BaseActionSync extends BaseAction {
@Autowired
private AsynTaskService asynTaskService;
@Autowired
private RedisUtil redisUtil;
@Action("index-data")
public String indexData() {
Map map = new HashMap();
try {
String indexData = redisUtil.get("indexData");
if (indexData != null && indexData.length() > 0 && !indexData.isEmpty()) {
// redis缓存里面有值,使用缓存的值
log.info("redis缓存里有值: " + indexData);
Map result = JSON.parseObject(indexData, Map.class);
returnData(200, "true", result);
return null;
}
log.info("redis没有缓存数据,把数据找出来,再缓存到redis,方便下次快速获取!");
Future<Page> carouselPage = asynTaskService.getCarousel();
Future<Page> recommendPage = asynTaskService.getRecommend();
Future<Page> catalogPage = asynTaskService.getCatalog();
map.put("carousel", carouselPage.get().getPageList());
map.put("recommend", recommendPage.get().getPageList());
map.put("catalog", catalogPage.get().getPageList());
JSONObject result = JSONObject.fromObject(map);
redisUtil.set("indexData", result.toString(), 1, "d");
returnData(200, "true", result);
//log.info("全部数据获取完成");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}