/**
 * Copyright (c) 2015, www.cubbery.com. All rights reserved.
 */
package com.cubbery.event.retry;

import com.cubbery.event.EventBus;
import com.cubbery.event.EventStorage;
import com.cubbery.event.conf.Configurable;
import com.cubbery.event.conf.ConfigureKeys;
import com.cubbery.event.conf.Context;
import com.cubbery.event.event.RetryEvent;
import com.cubbery.event.handler.EventHandler;
import com.cubbery.event.utils.ThreadFactories;
import com.cubbery.event.utils.Threads;
import com.cubbery.event.worker.RetryWorker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <b>类描述</b>: 重试服务,管理重试标记、重试分发线程、重试队列等<br>
 * <b>创建人</b>: <a href="mailto:cubber.zh@gmail.com">百墨</a> <br>
 * <b>创建时间</b>:9:46 2016/2/25 <br>
 * @version 1.0.0 <br>
 */
public class RetryService implements Configurable {
    private final static Logger LOG = LoggerFactory.getLogger("Retry-Service");

    //master优先权,单位为秒(s)
    private long priority;
    //重试线程数
    private int retryCount;
    //新master上线等待时间
    private int masterWaitCount;
    //lease 周期
    private long leasePeriod;

    //Lease 持久化引用
    private EventBus eventBus;
    //当前JVM是否为master
    private AtomicBoolean isMaster;
    //是否启动重试服务
    private AtomicBoolean started;
    //重试服务名称
    private String name;
    //lease 线程
    private LeaseTask leaseTask;
    //重试分发 线程
    private ScheduledExecutorService retryDispatchService;
    //重试线程池
    private ExecutorService retryService;

    public RetryService(EventBus eventBus) {
        this(eventBus,10,3,2,60);//默认重试线程数为3,master优先权为10个时间单位
    }

    public RetryService(EventBus eventBus,long priority,int retryCount,int masterWaitCount,long leasePeriod) {
        this.priority = priority;
        this.retryCount = retryCount;
        this.masterWaitCount = masterWaitCount;
        this.leasePeriod = leasePeriod;

        this.eventBus = eventBus;
        this.isMaster = new AtomicBoolean(false);
        this.started = new AtomicBoolean(false);
        this.name = masterInfo();

    }

    public synchronized void start() {
        if(started.get()) {
            LOG.warn("Retry Service is started!");
            return;
        }
        LOG.info("Try to Start Retry Service !");
        init();
        startLease();
        startRetry(false);
        started.compareAndSet(false, true);
        LOG.info("Retry Service Started !");
    }

    public synchronized void stop() {
        LOG.info("Try To Stop Retry Service !");
        this.setMaster(false);
        leaseTask.stop();
        started.set(false);
        LOG.info("Try To Stop Retry Service !");
    }

    private synchronized void init() {
        //init Lease
        try {
            eventBus.getStorage().initLease(leasePeriod);
        } catch (Exception e) {
            if(LOG.isWarnEnabled()) {
                LOG.warn("Init Lease Error! Ignore the DuplicateKeyException !",e);
            }
        }
        //init ...
    }

    public synchronized void setMaster(boolean isNewMaster) {
        if(this.isMaster() && isNewMaster) {
            return;//保留现在的状态
        }
        final boolean confirmOfflineIfNecessary = this.isMaster() && !isNewMaster;
        final boolean waitOnlineIfNecessary = !this.isMaster() && isNewMaster;

        this.isMaster.set(isNewMaster);//先改,然后启动
        if(this.isMaster()) {
            startRetry(waitOnlineIfNecessary);
        } else {
            stopRetry(confirmOfflineIfNecessary);
        }
    }

    private void startRetry(boolean waitOnlineIfNecessary) {
        LOG.info("Try to Start Inner Retry Service !");
        //先读取是否可以上线
        int waitCount = 0;
        while(waitOnlineIfNecessary && (++waitCount) < masterWaitCount) {
            if(eventBus.getStorage().selectOfflineInHalfPeriod() != 1) {
                LOG.info("Wait for Pre-Master Release the Lease...");
                Threads.sleep(1000);
            }
        }
        //在老的master发送了下线通知,或者超时未收到的情况下(存在风险),新的master上线
        if(started.get() && isMaster()) {
            LOG.info("Start to Retry !");
            //懒初始化,配置加载
            if(retryService == null) {
                this.retryService = Executors.newFixedThreadPool(this.retryCount,new ThreadFactories("Retry-Consumer"));
            }
            if(retryDispatchService == null) {
                retryDispatchService = Executors.newScheduledThreadPool(1,new ThreadFactories("Retry-Dispatcher"));
            }
            //默认10s处理一次,如果10s之内没有处理完,那么到下一个执行窗口再拉取数据
            retryDispatchService.scheduleWithFixedDelay(new RetryTask(), 0, 10, TimeUnit.SECONDS);
        }
        LOG.info("Inner Retry Service Started !");
    }

    private void stopRetry(boolean confirmOfflineIfNecessary) {
        LOG.info("Try To Stop Inner Retry Service !");
        if(retryDispatchService != null && !retryDispatchService.isShutdown()) {
            retryDispatchService.shutdownNow();//不等待,立刻关闭。在执行的runnable中处理interrupted异常!
        }
        if(retryService != null && !retryService.isShutdown()) {
            retryService.shutdownNow();//不等待,立刻关闭。在执行的runnable中处理interrupted异常!
        }
        try {
            retryDispatchService.awaitTermination(Long.MAX_VALUE,TimeUnit.DAYS);
            retryService.awaitTermination(Long.MAX_VALUE,TimeUnit.DAYS);
        } catch (InterruptedException e) {
            Thread.interrupted();
            LOG.error("Wait Retry Service Terminal Be Interrupted!",e);
        }
        //发送下线确认
        if(confirmOfflineIfNecessary) {
            this.eventBus.getStorage().confirmOffline(new Lease().setMaster(name));
        }
        LOG.info("Stop Inner Retry Service Success !");
    }

    private void startLease() {
        LOG.info("Try To Start Lease Service !");
        if(started.get()) return;
        if(leaseTask == null) {
            leaseTask = new LeaseTask(this, false);
        }
        Thread leaseThread  = new ThreadFactories("Lease").newThread(leaseTask);
        leaseThread.start();
        LOG.info("Start Lease Service Success !");
    }

    public boolean isMaster() {
        return this.isMaster.get();
    }

    public long getPriority() {
        return priority;
    }

    public String getName() {
        return name;
    }

    public void setPriority(long priority) {
        this.priority = priority;
    }

    public void setRetryCount(int retryCount) {
        this.retryCount = retryCount;
    }

    public EventStorage getLeaseDao() {
        return eventBus.getStorage();
    }

    class RetryTask implements Runnable {
        @Override
        public void run() {
            List<RetryEvent> retries = eventBus.getStorage().selectRetryEvents();
            int size = retries.size();
            LOG.info("Read {} items to retry!",size);
            if(size < 1) return;

            for (int a = 0; (a < size && started.get()); a++ ) {
                try {
                    RetryEvent entity = retries.get(a);
                    consumeEvent(entity);
                } catch (Throwable throwable) {
                    LOG.error("Consumer Error!",throwable);
                    break;
                }
            }
        }

        private void consumeEvent(RetryEvent entity) {
            EventHandler handler = eventBus.getHandlerClassByType(entity.getType(),entity.getExpression());
            if(handler == null) return;
            retryService.submit(new RetryWorker(eventBus,handler, entity));
        }
    }

    private String masterInfo() {
        StringBuffer sb = new StringBuffer();
        try {
            sb.append(InetAddress.getLocalHost().getHostAddress());
        } catch (UnknownHostException e) {
            sb.append("127.0.0.1");
        }
        sb.append("_").append(Thread.currentThread().getName());
        sb.append("_").append(new Random().nextInt(100));
        return sb.toString();
    }

    @Override
    public void configure(Context context) {
        this.priority = context.getLong(ConfigureKeys.RETRY_MASTER_PRIORITY,this.priority);
        this.retryCount = context.getInt(ConfigureKeys.RETRY_PARALLEL_COUNT,this.retryCount);
        this.masterWaitCount = context.getInt(ConfigureKeys.RETRY_MASTER_WAIT,this.masterWaitCount);
        this.leasePeriod = context.getLong(ConfigureKeys.RETRY_LEASE_PERIOD,this.leasePeriod);
    }
}
/**
 * Copyright (c) 2015, www.cubbery.com. All rights reserved.
 */
package com.cubbery.event.retry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Lease 选举实现
 */
final class LeaseTask implements Runnable {
    private final static Logger LOG = LoggerFactory.getLogger("Lease-Task");

    private RetryService retryService;
    private boolean leaseStop;

    public LeaseTask(RetryService retryService,boolean leaseStop) {
        this.retryService = retryService;
        this.leaseStop = leaseStop;
    }

    @Override
    public void run() {
        do {
            try {
                //1、查询Lease表,获取Lease信息
                Lease leaseEntity = retryService.getLeaseDao().selectLease();
                LOG.info("Read Lease Master Info ..." + leaseEntity.getMaster());
                long interval = leaseEntity.getNow().getTime() - leaseEntity.getModifiedTime().getTime();
                long milliseconds = leaseEntity.getPeriod() * 1000;
                long wait = waitTime(interval,milliseconds);
                if(wait > 0) {
                    LOG.info("Lease Wait for ..." + wait);
                    Thread.sleep(wait);
                    continue;
                }
                //试着去抢占,拿到锁
                int row = retryService.getLeaseDao().updateLease(retryService.getName(),leaseEntity.getVersion());
                LOG.info("Compete Lease Master ..." + row);
                retryService.setMaster(row == 1);//不关心前一次的状态,不用cas。按照最后最新的数据去覆盖。
            } catch (Throwable e) {
                LOG.error("Lease is terminal !",e);
                retryService.setMaster(false);//处理异常无条件设置为普通节点。
                sleep();//此时被打断,不再care,进入下一个循环。
            }
        } while (!leaseStop);
    }

    private long waitTime(long interval,long period) {
        if(retryService.isMaster()) {
            return period - interval - retryService.getPriority();
        }
        return period - interval;
    }

    private void sleep() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.interrupted();
            LOG.error("Lease is Interrupted !", e);
        }
    }

    public synchronized void stop() {
        LOG.info("Try To Lease Service !");
        leaseStop = true;
        retryService.setMaster(false);
        LOG.info("Stop Retry Service Success !");
    }
}

项目地址:https://github.com/cncduLee/async-event

基于Lease分布式系统重试服务选举的更多相关文章

  1. 通过Dapr实现一个简单的基于.net的微服务电商系统

    本来想在Dpar 1.0GA时发布这篇文章,由于其他事情耽搁了放到现在.时下微服务和云原生技术如何如荼,微软也不甘示弱的和阿里一起适时推出了Dapr(https://dapr.io/),园子里关于da ...

  2. 基于 Docker 的微服务架构实践

    本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 D ...

  3. 基于 OpenResty 的动态服务路由方案

    2019 年 5 月 11 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙武汉站,又拍云首席布道师在活动上做了< 基于 OpenResty ...

  4. 传统保险企业基于 Dubbo 的微服务实践

    本文整理自中国人寿保险(海外)股份有限公司深圳中心技术总监家黄晓彬在 Dubbo 社区开发者日深圳站的现场分享. 中国人寿保险(海外)股份有限公司负责香港.澳门.新加坡和印尼的业务开发,和国内业务不同 ...

  5. 一个基于Consul的.NET Leader选举类库

    前段时间有传言说Consul将不能在我国继续使用,后被查明是因法律问题Vault企业版产品不能在国内销售.Valut和Consul都是HashiCorp公司的产品,并且都推出了开源版本,继续使用开源版 ...

  6. 通过Dapr实现一个简单的基于.net的微服务电商系统(六)——一步一步教你如何撸Dapr之Actor服务

    我个人认为Actor应该是Dapr里比较重头的部分也是Dapr一直在讲的所谓"stateful applications"真正具体的一个实现(个人认为),上一章讲到有状态服务可能很 ...

  7. 通过Dapr实现一个简单的基于.net的微服务电商系统(八)——一步一步教你如何撸Dapr之链路追踪

    Dapr提供了一些开箱即用的分布式链路追踪解决方案,今天我们来讲一讲如何通过dapr的configuration来实现非侵入式链路追踪的 目录:一.通过Dapr实现一个简单的基于.net的微服务电商系 ...

  8. 通过Dapr实现一个简单的基于.net的微服务电商系统(十二)——istio+dapr构建多运行时服务网格

    多运行时是一个非常新的概念.在 2020 年,Bilgin Ibryam 提出了 Multi-Runtime(多运行时)的理念,对基于 Sidecar 模式的各种产品形态进行了实践总结和理论升华.那到 ...

  9. 基于thrift的微服务框架

    前一阵开源过一个基于spring-boot的rest微服务框架,今天再来一篇基于thrift的微服务加框,thrift是啥就不多了,大家自行百度或参考我之前介绍thrift的文章, thrift不仅支 ...

随机推荐

  1. 【FLUENT案例】06:与EDEM耦合计算

    折腾了很久才把耦合模块搞定,用的还是网上别人编译好的UDF,不完美.自己编译的时候,老是提示无法找到fluent中的一些头文件,个人怀疑是操作系统和visual studio的问题,有时间换个系统和V ...

  2. React构建单页应用方法与实例

    React作为目前最流行的前端框架之一,其受欢迎程度不容小觑,从这门框架上我们可以学到许多其他前端框架所缺失的东西,也是其创新性所在的地方,比如虚拟DOM.JSX等.那么接下来我们就来学习一下这门框架 ...

  3. S5PV210_时钟系统

    1.S5PV210的时钟获得:外部晶振+内部时钟发生器+内部PLL产生高频时钟+内部分频器分频 S5PV210外部有4个W晶振接口,可以根据需要来决定在哪里接晶振.接了晶振之后上电相应的模块就能产生振 ...

  4. java的会话管理:Cookie和Session

    java的会话管理:Cookie和Session 1.什么是会话 此处的是指客户端(浏览器)和服务端之间的数据传输.例如用户登录,购物车等 会话管理就是管理浏览器客户端和服务端之间会话过程产生的会话数 ...

  5. Linux下更换默认yum源为网易yum源的操作记录

    废话不多说,下面记录了centos下更换系统默认yum源为网易yum源的操作过程:1)备份原有的默认yum源[root@bastion-IDC ~]# cp /etc/yum.repos.d/Cent ...

  6. mysql 文件导入方法总结

    数据导入3三种方法 一.phpMyAdmin 限制大小:2M 1.创建数据库 2.导入.sql或.sql.zip文件 大数据导入方法一:http://jingyan.baidu.com/article ...

  7. ssh登录 The authenticity of host 192.168.0.xxx can't be established. 的问题

    scp免密码登录:Linux基础 - scp免密码登陆进行远程文件同步 执行scp一直是OK的,某天在本地生成了公钥私钥后,scp到某个IP报以下错误 The authenticity of host ...

  8. [LeetCode] Fraction to Recurring Decimal 分数转循环小数

    Given two integers representing the numerator and denominator of a fraction, return the fraction in ...

  9. BZOJ 3237: [Ahoi2013]连通图

    3237: [Ahoi2013]连通图 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1161  Solved: 399[Submit][Status ...

  10. 拷贝excel里的内容转为JSON的js代码

    <!DOCTYPE html> <html lang="en"> <head> <title>excel转json</titl ...