基于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不仅支 ...
随机推荐
- MYSQL 基本SQL语句
复制表结构 CREATE TABLE 新表 SELECT * FROM 旧表 where 1=2 复制表结构和数据CREATE TABLE 新表 SELECT * FROM 旧表 查询重复数据: se ...
- vim 在linux下中如何设置显示行数
在.vimrc(或/etc/vimrc)文件中输入如下文本: set tabstop=4 set softtabstop=4 set shiftwidth=4 set noexpandtab ...
- ubuntu 14.04 配置tomacat8
自己在虚拟机总安装tomcat8,主机访问,记下步骤方便以后查看. 1.将tomcat8安装包移动到/usr/local目录中(个人喜欢把自己安装的软件放到/usr/local文件夹中) 2.解压缩, ...
- [html/css]清除浮动的相关技巧
以前只了解得很浅显,转载了一篇不错的文,学习参考 浮动会使当前标签产生向上浮的效果,同时会影响到前后标签.父级标签的位置及 width height 属性.而且同样的代码,在各种浏览器中显示效果也有可 ...
- [No000096]程序员面试题集【上】
对几家的面试题凭记忆做个总结,基本全部拿到offer,由于时间比较长,题目只写大体意思,然后给出自己当时的答案(不保证一定正确): abstract类不可以被实例化 蛋糕算法: 平面分割空间:(n-1 ...
- 配置VSCode右键菜单
修改注册表,添加鼠标右键 选择文件 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\*\shell\VSCode]@="Ope ...
- mybatis: 利用多数据源实现分库存储
之前写过一篇mybatis 使用经验小结 提到过多数据源的处理方式,虽然简单但是姿势不太优雅,今天介绍一些更美观的办法: spring中有一个AbstractRoutingDataSource的抽象类 ...
- [LeetCode] Kth Smallest Element in a Sorted Matrix 有序矩阵中第K小的元素
Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth ...
- .net(C#)中this关键字
使用this关键字引用成员变量使用this关键字在自身构造方法内部引用其它构造方法使用this关键字代表自身类的对象使用this关键字引用成员方法 在一个类的方法或构造方法内部,可以使用"t ...
- static实现单例的隐患
1. 前言 Java的单例有多种实现方式:单线程下的简单版本.无法在指令重排序下正常工作的Double-Check.static.内部类+static.枚举--.这篇文章要讨论的,是在使用static ...