33、生鲜电商平台-定时器,定时任务quartz的设计与架构

说明:任何业务有时候需要系统在某个定点的时刻执行某些任务,比如:凌晨2点统计昨天的报表,早上6点抽取用户下单的佣金。

对于Java开源生鲜电商平台而言,有定时推送客户备货,定时计算卖家今日的收益,定时提醒每日的提现金额等等

          对于Java定时器而言,我们采用spring+quartz来进行技术解决方案:

对于业务而言,需要满足以下几个方面:

1.  对定时任务需要可以手动启动,手动停止,手动删除等。

2. 对定时任务的结果需要记录,什么时候执行,执行的结果如何,是否存在异常,是否有异常的记录。

3. 对定时器的错误,如果是级别很重要,是否有短信提醒来让运营人员或者技术人员手工处理呢?

类似这样:

为了满足业务的需求,根据quartz的技术选型,设计一下基础架构:

1. 任务记录信息表:

CREATE TABLE `t_job` (
  `JOB_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘任务id‘,
  `BEAN_NAME` varchar(100) NOT NULL COMMENT ‘spring bean名称‘,
  `METHOD_NAME` varchar(100) NOT NULL COMMENT ‘方法名‘,
  `PARAMS` varchar(200) DEFAULT NULL COMMENT ‘参数‘,
  `CRON_EXPRESSION` varchar(100) NOT NULL COMMENT ‘cron表达式‘,
  `STATUS` char(2) NOT NULL COMMENT ‘任务状态  0:正常  1:暂停‘,
  `REMARK` varchar(200) DEFAULT NULL COMMENT ‘备注‘,
  `CREATE_TIME` datetime DEFAULT NULL COMMENT ‘创建时间‘,
  PRIMARY KEY (`JOB_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

2. 任务操作日志记录表:

CREATE TABLE `t_job_log` (
  `LOG_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘任务日志id‘,
  `JOB_ID` bigint(20) NOT NULL COMMENT ‘任务id‘,
  `BEAN_NAME` varchar(100) NOT NULL COMMENT ‘spring bean名称‘,
  `METHOD_NAME` varchar(100) NOT NULL COMMENT ‘方法名‘,
  `PARAMS` varchar(200) DEFAULT NULL COMMENT ‘参数‘,
  `STATUS` char(2) NOT NULL COMMENT ‘任务状态    0:成功    1:失败‘,
  `ERROR` text COMMENT ‘失败信息‘,
  `TIMES` decimal(11,0) DEFAULT NULL COMMENT ‘耗时(单位:毫秒)‘,
  `CREATE_TIME` datetime DEFAULT NULL COMMENT ‘创建时间‘,
  PRIMARY KEY (`LOG_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2476 DEFAULT CHARSET=utf8;

说明:整个业务很简单,整个表设计与架构也很简单。

最终运营截图如下:

相关的系统设置与配置说明:

相关的核心代码如下:(贴些核心的,不算很核心的,大家可以去我github下面下载即可)

/**
 * 定时任务
 *
 * @author Administrator
 *
 */
public class ScheduleJob extends QuartzJobBean {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private ExecutorService service = Executors.newSingleThreadExecutor();

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        Job scheduleJob = (Job) context.getMergedJobDataMap().get(Job.JOB_PARAM_KEY);

        // 获取spring bean
        JobLogService scheduleJobLogService = (JobLogService) SpringContextUtils.getBean("JobLogService");

        JobLog log = new JobLog();
        log.setJobId(scheduleJob.getJobId());
        log.setBeanName(scheduleJob.getBeanName());
        log.setMethodName(scheduleJob.getMethodName());
        log.setParams(scheduleJob.getParams());
        log.setCreateTime(new Date());

        long startTime = System.currentTimeMillis();

        try {
            // 执行任务
            logger.info("任务准备执行,任务ID:" + scheduleJob.getJobId());
            ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getBeanName(), scheduleJob.getMethodName(),
                    scheduleJob.getParams());
            Future<?> future = service.submit(task);
            future.get();
            long times = System.currentTimeMillis() - startTime;
            log.setTimes(times);
            // 任务状态 0:成功 1:失败
            log.setStatus("0");

            logger.info("任务执行完毕,任务ID:" + scheduleJob.getJobId() + "  总共耗时:" + times + "毫秒");
        } catch (Exception e) {
            logger.error("任务执行失败,任务ID:" + scheduleJob.getJobId(), e);
            long times = System.currentTimeMillis() - startTime;
            log.setTimes(times);
            // 任务状态 0:成功 1:失败
            log.setStatus("1");
            log.setError(StringUtils.substring(e.toString(), 0, 2000));
        } finally {
            scheduleJobLogService.saveJobLog(log);
        }
    }
}

/**
 * 执行定时任务
 *
 * @author Administrator
 *
 */
public class ScheduleRunnable implements Runnable {
    private Object target;
    private Method method;
    private String params;

    public ScheduleRunnable(String beanName, String methodName, String params)
            throws NoSuchMethodException, SecurityException {
        this.target = SpringContextUtils.getBean(beanName);
        this.params = params;

        if (StringUtils.isNotBlank(params)) {
            this.method = target.getClass().getDeclaredMethod(methodName, String.class);
        } else {
            this.method = target.getClass().getDeclaredMethod(methodName);
        }
    }

    @Override
    public void run() {
        try {
            ReflectionUtils.makeAccessible(method);
            if (StringUtils.isNotBlank(params)) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

/**
 * 执行定时任务
 *
 * @author Administrator
 *
 */
public class ScheduleRunnable implements Runnable {
    private Object target;
    private Method method;
    private String params;

    public ScheduleRunnable(String beanName, String methodName, String params)
            throws NoSuchMethodException, SecurityException {
        this.target = SpringContextUtils.getBean(beanName);
        this.params = params;

        if (StringUtils.isNotBlank(params)) {
            this.method = target.getClass().getDeclaredMethod(methodName, String.class);
        } else {
            this.method = target.getClass().getDeclaredMethod(methodName);
        }
    }

    @Override
    public void run() {
        try {
            ReflectionUtils.makeAccessible(method);
            if (StringUtils.isNotBlank(params)) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

POM文件

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.1</version>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <version>2.2.1</version>
</dependency>

org.quartz.scheduler.instanceName属性可为任何值,用在 JDBC JobStore 中来唯一标识实例,但是所有集群节点中必须相同。

org.quartz.scheduler.instanceId 属性为 AUTO即可,基于主机名和时间戳来产生实例 ID。

org.quartz.jobStore.class属性为 JobStoreTX,将任务持久化到数据中。因为集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用 Quartz 集群。这意味着你必须使用 JobStoreTX 或是 JobStoreCMT 作为 Job 存储;你不能在集群中使用 RAMJobStore。

org.quartz.jobStore.isClustered 属性为 true,你就告诉了 Scheduler 实例要它参与到一个集群当中。这一属性会贯穿于调度框架的始终,用于修改集群环境中操作的默认行为。

org.quartz.jobStore.clusterCheckinInterval 属性定义了Scheduler 实例检入到数据库中的频率(单位:毫秒)。Scheduler 检查是否其他的实例到了它们应当检入的时候未检入;这能指出一个失败的 Scheduler 实例,且当前 Scheduler 会以此来接管任何执行失败并可恢复的 Job。通过检入操作,Scheduler 也会更新自身的状态记录。clusterChedkinInterval 越小,Scheduler 节点检查失败的 Scheduler 实例就越频繁。默认值是 15000 (即15 秒)。

最终:系统架构设计图:

最后:很多人说系统功能很强大很好,但是我的一种思维方式是不一定,强大固然好,但是你需要通过这么多的系统数据中来分析出问题的关键,而不是所谓的代码堆积。

           你所需要的是思考,再思考,最终思考。

转载自-- https://www.cnblogs.com/jurendage/p/9153835.html

原文地址:https://www.cnblogs.com/lu-manman/p/10052418.html

时间: 2024-07-29 10:18:26

33、生鲜电商平台-定时器,定时任务quartz的设计与架构的相关文章

Java开源生鲜电商平台-RBAC系统权限的设计与架构(源码可下载)

Java开源生鲜电商平台-RBAC系统权限的设计与架构(源码可下载) 说明:根据上面的需求描述以及对需求的分析,我们得知通常的一个中小型系统对于权限系统所需实现的功能以及非功能性的需求,在下面我们将根据需求从技术角度上分析实现的策略以及基于目前两种比较流行的权限设计思想来讨论关于权限系统的实现. 1.1.       技术策略 l         身份认证 在B/S的系统中,为识别用户身份,通常使用的技术策略为将用户的身份记录在Session中,也就是当用户登录时即获取用户的身份信息,并将其记录

38、生鲜电商平台-会员积分系统的设计与架构

说明:互联网平台积分体系主要用于激励和回馈用户在平台的消费行为和活动行为,一个良好的积分体系可以很好的提升用户的粘性及活跃度. 一.互联网平台积分体系设计必要性 互联网平台积分体系是一个独立.完整的系统模块,主要用于激励和回馈用户在平台的消费行为和活动行为,通过积分体系可以激发与引导用户在平台的活跃行为,逐步形成用户对平台的依赖性和习惯性,提升用户对平台的黏度和重复下单率. 积分体系在保持系统独立性的同时,又与平台会员系统.商品系统.订单系统等具有紧密的关联性,积分体系的规划设计需与平台其他系统

26、生鲜电商平台-RBAC系统权限的设计与架构

说明:根据上面的需求描述以及对需求的分析,我们得知通常的一个中小型系统对于权限系统所需实现的功能以及非功能性的需求,在下面我们将根据需求从技术角度上分析实现的策略以及基于目前两种比较流行的权限设计思想来讨论关于权限系统的实现. 1.1.       技术策略 l         身份认证 在B/S的系统中,为识别用户身份,通常使用的技术策略为将用户的身份记录在Session中,也就是当用户登录时即获取用户的身份信息,并将其记录到Session里,当需要进行身份认证的时候通过从Session中获取

Java开源生鲜电商平台-Java后端生成Token架构与设计详解(源码可下载)

Java开源生鲜电商平台-Java后端生成Token架构与设计详解(源码可下载) 目的:Java开源生鲜电商平台-Java后端生成Token目的是为了用于校验客户端,防止重复提交. 技术选型:用开源的JWT架构. 1.概述:在web项目中,服务端和前端经常需要交互数据,有的时候由于网络相应慢,客户端在提交某些敏感数据(比如按照正常的业务逻辑,此份数据只能保存一份)时,如果前端多次点击提交按钮会导致提交多份数据,这种情况我们是要防止发生的. 2.解决方法: ①前端处理:在提交之后通过js立即将按钮

Java开源生鲜电商平台-OMS订单系统中并发问题和锁机制的探讨与解决方案(源码可下载)

Java开源生鲜电商平台-OMS订单系统中并发问题和锁机制的探讨与解决方案(源码可下载) 说明:Java开源生鲜电商中OMS订单系统中并发问题和锁机制的探讨与解决方案: 问题由来     假设在一个订单系统中(以火车票订单系统为例),用户A,用户B都要预定从成都到北京的火车票,A.B在不同的售票窗口均同时查询到了某车厢卧铺中.下铺位有空位.用户A正在犹豫订中铺还是下铺,这时用户B果断订购了下铺.当用户A决定订下铺时,系统提示下铺已经被预订,请重新选择铺位.在这个系统场景中,我们来探讨一下,火车票

24、生鲜电商平台-系统报表设计与架构

说明:任何一个运行的平台都需要一个很清楚的报表来显示,那么作为Java开源生鲜电商平台而言,我们应该如何设计报表呢?或者说我们希望报表来看到什么数据呢?           通过报表我们可以分析出目前整个公司的运营情况,以及下一步的调整方向,这样更加有理有据的掌握整个市场与决策. 设计基础维度:    1. 今日订单,今日营业额,总订单数,总营业额          2. 今日的注册买家,总的注册买家.          3. 实时的营收,实时的下单买家.          4. 今日下单买家,

9、生鲜电商平台-推荐系统模块的设计与架构

业务需求: 对于一个B2B的生鲜电商平台,对于买家而言,他需要更加快速的购买到自己的产品,跟自己的餐饮店不相关的东西,他是不关心的,而且过多无用的东西掺杂在一起,反而不便 于买家下单,用户体验也很差,严重的会因此丢了客户.(客户觉得太难用了.一般都就会放弃使用.) 对于卖家而言,他自己就调整下自己的商品的上架与下架,然后就是调整下自己商品的价格.(蔬菜类的商品会随着市场的供求关系会有相应的波动.) 业务分析: 推荐系统:根据买家的行为习惯以及购买行为来推荐些他可能需要的东西的一套算法系统. 对于

Java开源生鲜电商平台-系统简介

1.生鲜电商平台的价值与定位. 生鲜电商平台是一家致力于打造全国餐饮行业智能化.便利化.平台化与透明化服务的创新型移动互联网平台,连接买家与卖家之间的一个平台 看以下的图标:(商业模式) 名称解释: 买家:所有的大中小型餐馆,酒店等餐饮行业都属于我们常说的买家. 生鲜电商APP: 买家通过在APP上点菜,然后支付相应的费用的一种交易平台. 卖家:附近10公里内,在集贸市场有摊位的所有卖菜的商户 物流平台:公司平台运用自己的物流车辆把买家所需要的菜从卖家手里运输到买家手里的一种交通工具. 总体流程

Java开源生鲜电商平台-系统架构与技术选型(源码可下载)

Java开源生鲜电商平台-系统架构与技术选型(源码可下载) 1.  硬件环境 公司服务器 2.   软件环境 2.1  操作系统 Linux CentOS 6.8系列 2.2 反向代理/web服务器 Nginx 2.3 应用服务器 Jdk7+ Tomcat 7 2.4 数据库 Mysql 5.6.x 2.5 消息队列(可选) Rabbitmq/rocketmq 2.6 缓存(可选) Redis 3.x 3.工程构建和管理工具 1.Maven 开发人员已经很熟悉了.此处略 2.Jenkins Je