基于Lease分布式系统重试服务选举
/** * 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分布式系统重试服务选举的更多相关文章
- 通过Dapr实现一个简单的基于.net的微服务电商系统
本来想在Dpar 1.0GA时发布这篇文章,由于其他事情耽搁了放到现在.时下微服务和云原生技术如何如荼,微软也不甘示弱的和阿里一起适时推出了Dapr(https://dapr.io/),园子里关于da ...
- 基于 Docker 的微服务架构实践
本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 D ...
- 基于 OpenResty 的动态服务路由方案
2019 年 5 月 11 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙武汉站,又拍云首席布道师在活动上做了< 基于 OpenResty ...
- 传统保险企业基于 Dubbo 的微服务实践
本文整理自中国人寿保险(海外)股份有限公司深圳中心技术总监家黄晓彬在 Dubbo 社区开发者日深圳站的现场分享. 中国人寿保险(海外)股份有限公司负责香港.澳门.新加坡和印尼的业务开发,和国内业务不同 ...
- 一个基于Consul的.NET Leader选举类库
前段时间有传言说Consul将不能在我国继续使用,后被查明是因法律问题Vault企业版产品不能在国内销售.Valut和Consul都是HashiCorp公司的产品,并且都推出了开源版本,继续使用开源版 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(六)——一步一步教你如何撸Dapr之Actor服务
我个人认为Actor应该是Dapr里比较重头的部分也是Dapr一直在讲的所谓"stateful applications"真正具体的一个实现(个人认为),上一章讲到有状态服务可能很 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(八)——一步一步教你如何撸Dapr之链路追踪
Dapr提供了一些开箱即用的分布式链路追踪解决方案,今天我们来讲一讲如何通过dapr的configuration来实现非侵入式链路追踪的 目录:一.通过Dapr实现一个简单的基于.net的微服务电商系 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十二)——istio+dapr构建多运行时服务网格
多运行时是一个非常新的概念.在 2020 年,Bilgin Ibryam 提出了 Multi-Runtime(多运行时)的理念,对基于 Sidecar 模式的各种产品形态进行了实践总结和理论升华.那到 ...
- 基于thrift的微服务框架
前一阵开源过一个基于spring-boot的rest微服务框架,今天再来一篇基于thrift的微服务加框,thrift是啥就不多了,大家自行百度或参考我之前介绍thrift的文章, thrift不仅支 ...
随机推荐
- Excel公式 提取文件路径后缀
我们在代码中获取一个文件路径的后缀,是一个很简单的事. 如C#中,可以通过new FileInfo(filePath).Extension,或者Path.GetExtension(filePath)获 ...
- 前端少侠的ps故事
前端少侠的ps故事 正所谓,码在江湖,身不由己.自21世纪前后端分离,代码分工细化以来,前端与设计的合作也变得越来越重要.有人说,如果前端懂设计的话,工作会更快一点.倘若说我入前端半年能算半个前端少侠 ...
- 《WePayUI组件设计的秘密》——2016年第一届前端体验大会分享
本文是博主参加第一届前端体验大会 | 物勒工名做的分享<WePayUI组件设计的秘密>,内容主要分为2个部分: 一.浅析UI库/框架的未来 讨论的UI库或者框架,主要包含展示和交互的css ...
- 安装Oracle 10g
本文仅用于学习交流,商业用途请支持正版!转载请注明: http://www.cnblogs.com/mxbs/p/6217052.html 准备: Oracle 10g for Win(32-Bit) ...
- vs2013 error : cannot open source file "SDKDDKVer.h"
改: 项目属性--常规--工具集--Visual Studio 2013-WindowsXP(v120_xp)
- PHP的GD库
GD库 PHP通过GD库,可以对JPG.PNG.GIF.SWF等图片进行处理.GD库常用在图片加水印,验证码生成等方面. 绘制线条 要对图形进行操作,首先要新建一个画布,通过imagecreatetr ...
- HTML 学习笔记 (drag & drop)
拖放(Drag & Drop)是一种常见的特性,即抓取对象以后拖到另一个位置.在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放.过去,我们用监听鼠标的Mousedown.Mouseo ...
- javascript中的闭包
闭包一直是javascript中的难点,也比较不容易被初学者所掌握,"官方"的解释是:所谓"闭包",指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是 ...
- C#终于支持可选参数了!
今天偶然看了一下C#4.0的新特性, 第一个新特性就令我兴奋不已, 曾经一度令我使用C#很不习惯的"死参数"问题终于搞定了.实在太爽了! 过去用C++, VB.NET的时候都很爽, ...
- (原创)使用VMware安装Ubuntu,怎么无法使用startx进入桌面模式?
最近在VMware中安装Ubuntu时,发现VMware的快速安装后是文本模式,无法使用startx进入桌面模式,非常不方便.此问题为默认安装方式,需要设置安装方式. 操作系统:Windows 8.1 ...