JavaWeb_LeadNews_Day10-Xxljob, Redis实现定时热文章
- xxl-job概述
- windows部署调度中心
- docker部署调度中心
- xxl-job入门案例
- xxl-job分片广播
- 热点文章定时计算
- 思路分析
- 具体实现
- 热文章计算
- 定时计算
- 查询文章接口改造
- 来源
- Gitee
xxl-job概述
windows部署调度中心
- 运行 xxl-job\doc\db\tables_xxl_job.sql
- 修改 xxl-job-admin子模块下的application.properties
spring.datasource.password=1234
- 启动 xxl-job-admin子模块下的启动程序
- 访问localhost:8080/xxl-job-admin, 账号: admin, 密码:123456
docker部署调度中心
- 创建mysql容器
docker run -p 3306:3306 --name mysql57 \
-v /opt/mysql/conf:/etc/mysql \
-v /opt/mysql/logs:/var/log/mysql \
-v /opt/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7.25
- 拉取镜像
docker pull xuxueli/xxl-job-admin:2.3.0
- 创建xxl-job-admin容器
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://192.168.174.133:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \
--spring.datasource.username=root \
--spring.datasource.password=root" \
-p 8888:8080 -v /tmp:/data/applogs \
--name xxl-job-admin -d xuxueli/xxl-job-admin:2.3.0
xxl-job入门案例
- 调度中心新建示例任务
- 依赖
<dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.3.0</version> </dependency>
- 配置
application.yml
XxlJobConfig.javaserver: port: 8881 xxl: job: admin: addresses: http://192.168.174.133:8888/xxl-job-admin executor: appname: xxl-job-executor-sample port: 9999
@Configuration public class XxlJobConfig { private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); @Value("${xxl.job.admin.addresses}") private String adminAddresses; @Value("${xxl.job.executor.appname}") private String appname; @Value("${xxl.job.executor.port}") private int port; @Bean public XxlJobSpringExecutor xxlJobExecutor() { logger.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(adminAddresses); xxlJobSpringExecutor.setAppname(appname); xxlJobSpringExecutor.setPort(port); return xxlJobSpringExecutor; } /** * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP; * * 1、引入依赖: * <dependency> * <groupId>org.springframework.cloud</groupId> * <artifactId>spring-cloud-commons</artifactId> * <version>${version}</version> * </dependency> * * 2、配置文件,或者容器启动变量 * spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.' * * 3、获取IP * String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); */ }
- 任务代码
@Component public class HelloJob { @XxlJob("demoJobHandler") public void demoJobHandler() { XxlJobHelper.log("XXL-JOB, Hello World."); System.out.println("简单任务执行..."); } }
xxl-job分片广播
- 创建分片执行器
xxl-job-sharding-sample
- 创建任务, 路由策略为分片广播
- 分片广播任务代码, 创建多个实例
@XxlJob("shardingJobHandler") public void shardingJobHandler() { // 分片的参数 int shardIndex = XxlJobHelper.getShardIndex(); // 实例在集群中的序号 int shardTotal = XxlJobHelper.getShardTotal(); // 集群总量 // 业务逻辑 List<Integer> list = getList(); for (Integer integer : list) { if(integer % shardTotal == shardIndex){ System.out.println("当前分片: "+shardIndex+", 当前任务项: "+integer); } } } public List<Integer> getList() { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { list.add(i); } return list; }
热点文章定时计算
思路分析
具体实现
热文章计算
@Slf4j
@Service
@Transactional
public class HotArticleServiceImpl implements HotArticleService {
@Autowired
private ApArticleMapper apArticleMapper;
/**
* 计算热门文章
*/
@Override
public void computeHotArticle() {
// 1. 查询前5天的文章数据
Date dayParam = DateTime.now().minusDays(5).toDate();
List<ApArticle> articleList = apArticleMapper.findArticleListByLast5days(dayParam);
// 2. 计算文章的分值
List<HotArticleVo> hotArticleVoList = getHotArticleVoList(articleList);
// 3. 为每个频道缓存30条分值较高的文章
cacheTagToRedis(hotArticleVoList);
}
@Autowired
private IWemediaClient wemediaClient;
@Autowired
private CacheService cacheService;
/**
* 为每个频道缓存30条分值较高的文章
* @param hotArticleVoList
*/
private void cacheTagToRedis(List<HotArticleVo> hotArticleVoList) {
// 为每个频道缓存30条分值较高的文章
ResponseResult responseResult = wemediaClient.getChannels();
if(responseResult.getCode().equals(200)){
String jsonString = JSON.toJSONString(responseResult.getData());
List<WmChannel> wmChannelList = JSON.parseArray(jsonString, WmChannel.class);
// 检索出每个频道的文章
if(wmChannelList!=null){
for (WmChannel wmChannel : wmChannelList) {
List<HotArticleVo> hotArticleVos = hotArticleVoList.stream().filter(item ->
item.getChannelId().equals(wmChannel.getId())
).collect(Collectors.toList());
// 给文章排序, 取30条分值较高的文章存入redis key: 频道id value: 30条分值较高的文章
sortAndCache(hotArticleVos, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + wmChannel.getId());
}
}
}
// 设置推荐数据
// 给文章排序, 取30条分值较高的文章存入redis key: 频道id value: 30条分值较高的文章
sortAndCache(hotArticleVoList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
}
/**
* 排序并且缓存数据
* @param hotArticleVos
* @param key
*/
private void sortAndCache(List<HotArticleVo> hotArticleVos, String key) {
hotArticleVos = hotArticleVos.stream().sorted(
Comparator.comparing(HotArticleVo::getScore).reversed()
).collect(Collectors.toList());
if(hotArticleVos.size() > 30){
hotArticleVos = hotArticleVos.subList(0, 30);
}
cacheService.set(key, JSON.toJSONString(hotArticleVos));
}
/**
* 获取热文章列表
* @param articleList
* @return
*/
private List<HotArticleVo> getHotArticleVoList(List<ApArticle> articleList) {
List<HotArticleVo> articleVoList = new ArrayList<>();
if(articleList!=null) {
for (ApArticle apArticle : articleList) {
HotArticleVo hotArticleVo = new HotArticleVo();
BeanUtils.copyProperties(apArticle, hotArticleVo);
Integer score = computeArticleScore(apArticle);
hotArticleVo.setScore(score);
articleVoList.add(hotArticleVo);
}
}
return articleVoList;
}
/**
* 计算文章分数
* @param apArticle
* @return
*/
private Integer computeArticleScore(ApArticle apArticle) {
Integer score = 0;
if(apArticle.getLikes() != null){
score += ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT*apArticle.getLikes();
}
if(apArticle.getComment() != null){
score += ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT*apArticle.getComment();
}
if(apArticle.getCollection() != null){
score += ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT*apArticle.getCollection();
}
if(apArticle.getViews() != null){
score += apArticle.getViews();
}
return score;
}
}
// ApArticleMapper.java
List<ApArticle> findArticleListByLast5days(@Param("dayParam") Date dayParam);
// ApArticleMapper.xml
<select id="findArticleListByLast5days" resultType="com.heima.model.article.pojos.ApArticle">
SELECT
aa.*
FROM
`ap_article` aa
LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
<where>
and aac.is_delete != 1
and aac.is_down != 1
<if test="dayParam != null">
and aa.publish_time <![CDATA[>=]]> #{dayParam}
</if>
</where>
</select>
总结:
ApArticleMapper.java
中的形参必须添加@Param注解, 否则在ApArticleMapper.xml
中会将dayParam解释为Date的属性然后报错.
定时计算
- 新建热文章计算执行器
leadnews-hot-article-executor
- 新建定时任务
- 依赖和配置
- 任务代码
@Component @Slf4j public class ComputeHotArticleJob { @Autowired private HotArticleService hotArticleService; @XxlJob("computeHotArticleJob") public void handle() { log.info("热文章分值计算调度任务开始执行..."); hotArticleService.computeHotArticle(); log.info("热文章分值计算调度任务执行结束..."); } }
查询文章接口改造
// article-Controller
public class ArticleHomeController {
...
public ResponseResult load(@RequestBody ArticleHomeDto articleHomeDto)
{
// return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE);
return apArticleService.load2(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE, true);
}
...
}
// article-Service
public ResponseResult load2(ArticleHomeDto dto, Short type, boolean firstPage) {
if(firstPage==true){
String jsonString = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
if(StringUtils.isNotBlank(jsonString)){
List<HotArticleVo> hotArticleVoList = JSON.parseArray(jsonString, HotArticleVo.class);
return ResponseResult.okResult(hotArticleVoList);
}
}
return load(dto, type);
}
来源
黑马程序员. 黑马头条
Gitee
https://gitee.com/yu-ba-ba-ba/leadnews