• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
孙龙 程序员
少时总觉为人易,华年方知立业难
博客园    首页    新随笔    联系   管理    订阅  订阅
B端业务架构解决方案之分布式任务管理和异步任务分发管理

在B端业务中常见

系统模块分类为:cms系统,crm系统,会员系统,订单管理,采购管理,wms仓储管理,财务管理,供应商管理,质检系统等;

常见服务:文件图片上传服务,物流服务,商品搜索服务,订单服务,会员服务等;

 

在上述介绍的模块中,采用分布式部署方案的情况下(某个模块或者系统部署多台服务器),在公司业务发展壮大后,会遇到各个系统业务流转,获取数据,分发,同步数据等问题,常见的有:消息队列异步任务,定时任务。

1,异步任务分发(处理完订单或者某一个流程后需要异步通知其它系统或者发送消息),一般采用队列监听(异步任务消息即可);

2,像报表,统计等需要定时任务完成;

但是在系统、服务越来越多的情况下,服务器上运行的队列服务和定时任务将会很庞大,十几台乃至几十台服务器有各种不同的服务很难以管理

注:像一次性任务,比如golang任务发布,打包部署等可以采取jenkins来部署

 

1,定时任务采取方案就是找一套开源的或者自己开发一套分布式任务管理系统(一套后台管理几十上百台服务器上的定时任务和执行脚本)

  具备一下功能:

  1. 服务发现
  2. 服务注册
  3. 定时任务管理精确到秒
  4. 一次性任务管理
  5. 任务备份

 

2,任务分发

  像订单审核后同步订单到采购,采购单同步到wms   入库单同步wms    发货单,收款单,等各个系统衔接的数据对接如果按照普通api接口会影响业务性能;解决方案:根据业务开发一套异步任务分开系统

 

 

  1.  采购系统创建采购单 入库单同步wms仓储系统;
  2. 仓储系统入库通知采购系统和销售系统;
  3. 销售系统创建销售单同步采购系统;
  4. crm和销售系统发票关系同步;

 像上述只是简单列举了业务当中简单的任务同步过程;每个任务单独运行一个队列的话,那么在公司正常运转情况下将很难以管理这些任务,于是在此场景下应运而生的系统 任务分发系统

任务分发系统要具备一下功能:

  • 每个系统服务一个或者多个队列运行(防止任务阻塞),并且支持直接向队里插入任务和htttp形式插入任务;
  • 插入数据中包含了需要请求的接口地址,参数,是否需要返回数据通知对方;回调地址,消息唯一标识key,消息搜索关键词(用于elk搜索)等;
  • 支持任务失败了多次尝试(切换消息队列或者延时队列),固定次数失败后钉钉告警提醒;
  • 对于新增,修改、删除要支持,同一条数据不能重复请求插入(成功的任务),需要把任务存入elk中;
  • 对于修改数据,删除数据等要保证数据先手顺序;比如修改价格,库存等 第一条记录修改为80元,第一次修改为100;一定要保证数据顺序不能错乱,过时的消息要删除
  • 保证消息任务数据的幂等性,安全性,唯一性,可重复消费
  • 各个接口提供方要能保证接口支持重复增删改查后对数据没有太大影响,比如同一条数据插入,只能新增一条,第二次请求能返回插入成功;

 

 

1,分布式任务管理系统介绍:(开发语言golang)

 

 

 

 

 

 后台php,服务用golang开发(1,对接后台api接口,新增一次性任务和定时任务,2.处理任务),采用golang+etcd+mongodb

部署方案:

 

 

学习golang的分布式任务之前先学习协etcd的使用,重点需要学习etcd的  续租,watch监听key

etcd的安装

etcd的使用

 

 golang分布式任务主要分为

master 服务端   主要提供后台操作的api,添加定时任务和一次性任务到etcd中,对任务进行增删改查 日志  服务发现 服务注册等功能

worker 客户端   主要监听etcd中的任务(与自己服务器ip相关的任务) 一次性任务立即执行即可,定时任务解析表达式后,放入内存中,定时执行

 

woker执行流程:.

 

 

githu地址:https://github.com/sunlongv520/go-crontab

code可以手动向etcd添加任务 或者删除任务

package main

import (
    "context"
    "fmt"
    "github.com/coreos/etcd/clientv3"
    "time"
)

func main(){
    var (
        config clientv3.Config
        err error
        client *clientv3.Client
        kv clientv3.KV
        getResp *clientv3.GetResponse

    )
    //配置
    config = clientv3.Config{
        Endpoints:[]string{"192.168.2.232:2379"},
        DialTimeout:time.Second*5,
    }
    //连接 床见一个客户端
    if client,err = clientv3.New(config);err != nil{
        fmt.Println(err)
        return
    }


    //用于读写etcd的键值对
    kv = clientv3.NewKV(client)

    //删除key

    //kv.Delete(context.TODO(),"/cron/jobs/192.168.2.246/job1",clientv3.WithPrefix())
    //kv.Delete(context.TODO(),"/cron/jobs",clientv3.WithPrefix())
    //kv.Delete(context.TODO(),"/cron/oncejobs",clientv3.WithPrefix())
    //
    //return


    //新增定时任务
    //putResp, err := kv.Put(context.TODO(),"/cron/jobs/192.168.2.232/job2","{\"name\":\"job2\",\"command\":\"D:/phpstudy/PHPTutorial/php/php-5.6.27-nts/php E:/WWW/a.php\",\"cronExpr\":\"*/7 * * * * * *\"}",clientv3.WithPrevKV())
    //putResp, err := kv.Put(context.TODO(),"/cron/jobs/192.168.2.246/job2","{\"name\":\"job2\",\"command\":\" echo hello world\",\"cronExpr\":\"*/5 * * * * * *\"}",clientv3.WithPrevKV())
    //putResp, err := kv.Put(context.TODO(),"/cron/jobs/192.168.2.246/job3","{\"name\":\"job3\",\"command\":\" echo hello boy\",\"cronExpr\":\"*/10 * * * * * *\"}",clientv3.WithPrevKV())
//fmt.Println(putResp)
//fmt.Println(err)

    //新增一次性任务
    //putResp, err := kv.Put(context.TODO(),"/cron/oncejobs/192.168.2.232/job10","{\"name\":\"job10\",\"command\":\" echo hello world  \"}",clientv3.WithPrevKV())


    //强杀任务
    //putResp, err := kv.Put(context.TODO(),"/cron/killer/192.168.2.246/job10","")

    //
    //if err != nil{
    //    fmt.Println(err)
    //}else{
    //    fmt.Println("Revision:",putResp.Header.Revision)
    //    if putResp.PrevKv != nil{
    //        fmt.Println("key:",string(putResp.PrevKv.Key))
    //        fmt.Println("Value:",string(putResp.PrevKv.Value))
    //        fmt.Println("Version:",string(putResp.PrevKv.Version))
    //    }
    //}

    //查询
    getResp,err = kv.Get(context.TODO(),"/cron/workers",clientv3.WithPrefix())
    if err != nil {
        fmt.Println(err)
        return
    }
    for _, kvpair := range getResp.Kvs {
        fmt.Println(kvpair)
    }


    getResp,err = kv.Get(context.TODO(),"/cron/jobs",clientv3.WithPrefix())
    if err != nil {
        fmt.Println(err)
        return
    }
    for _, kvpair := range getResp.Kvs {
        fmt.Println(kvpair)
    }

    getResp,err = kv.Get(context.TODO(),"/cron/oncejobs",clientv3.WithPrefix())
    if err != nil {
        fmt.Println(err)
        return
    }
    for _, kvpair := range getResp.Kvs {
        fmt.Println(kvpair)
    }
}
View Code

** master:**

主要负责接收后台对任务的管理 提供api接口

go run .\master\main\master.go -config= .\master\main\master.json

** worker:**

监听任务,执行任务,任务调度

go run .\worker\main\worker.go -config=./worker\main\worker.json -logDir=./logs/worker

 

该套分布式任务管理开箱即用,如有疑问可以随时提问

项目框架解析: 供学习爱好者

#!/bin/bash
cd /data/gocode/golang_asynctask
git reset --hard HEAD
git pull origin master
filename="/data2/gocode/golang_asynctask/build/crm/sync_comuser_to_erp"
rm -f $filename
go build -o /data2/gocode/golang_asynctask/build/crm/sync_comuser_to_erp /data2/gocode/golang_asynctask/cmd/crm/sync_comuser_to_erp/main.go
supervisorctl restart crm_sync_comuser_to_erp:*

一次性任务脚本
一次性任务脚本

 

 

2,异步任务分发系统:

 

本文来自博客园,作者:孙龙-程序员,转载请注明原文链接:https://www.cnblogs.com/sunlong88/p/17119027.html

posted on 2023-02-14 11:27  孙龙-程序员  阅读(464)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3