HangFire多集群切换及DashBoard登录验证
项目中是有多个集群的,现在存在一个是:在切换web集群时,如何切换HangFire的周期性任务。
先采取的解决办法是:
- 每个集群分一个队列,在周期性任务入队时分配当前web集群的集群id单做队列名称。
- 之前已存在的周期性任务,在其入队时修正到正确的集群执行
通过BackgroundJobServerOptions配置,只监听当前web集群的队列(ps:可参考文档:https://docs.hangfire.io/en/latest/background-processing/configuring-queues.html)
//只监听当前集群的队列
var options = new BackgroundJobServerOptions()
{
Queues = new[] { GlobalConfigSection.Current.WebClusterId }
};
HangfireAspNet.Use(() => new[] { new BackgroundJobServer(options) });
通过实现IElectStateFilter,重写OnStateElection方法,在任务入队前修正其入当前集群队列执行。
配置使用自定义熟悉
GlobalJobFilters.Filters.Add(new CustomJobFilterAttribute());
重写OnStateElection方法,通过修改enqueuedState的queue熟悉修正队列
/// <summary>
/// HangFire Filter
/// </summary>
public class CustomJobFilterAttribute : JobFilterAttribute, IElectStateFilter
{
public void OnStateElection(ElectStateContext context)
{
if (context.CandidateState is EnqueuedState enqueuedState)
{
var tenantConfigProvider = ObjectContainer.GetService<ITenantConfigProvider>();
var contextMessage = context.GetJobParameter<ContextMessage>("_ld_contextMessage");
var webClusterId = tenantConfigProvider.GetWebClusterIdByTenant(contextMessage.TenantId);
if (enqueuedState.Queue != webClusterId)//修正队列
{
enqueuedState.Queue = webClusterId;
}
}
} }
ps(更多的filter可以参考文档:https://docs.hangfire.io/en/latest/extensibility/using-job-filters.html)
附上HangFire执行失败记录日志实现

/// <summary>
/// HangFire Filter
/// </summary>
public class CustomJobFilterAttribute : JobFilterAttribute, IServerFilter
{ public void OnPerforming(PerformingContext filterContext)
{ } /// <summary>
/// 未成功执行的
/// </summary>
/// <param name="filterContext"></param>
public void OnPerformed(PerformedContext filterContext)
{
var error = filterContext.Exception;
if (error==null)
{
return;
}
//记录日志到后台
ILoggerFactory loggerFactory = ObjectContainer.GetService<ILoggerFactory>();
ILogger logger;
if (error.TargetSite != null && error.TargetSite.DeclaringType != null)
{
logger = loggerFactory.Create(error.TargetSite.DeclaringType.GetUnProxyType());
}
else
{
logger = loggerFactory.Create(GetType());
}
var contextMessage = filterContext.GetJobParameter<ContextMessage>("_ld_contextMessage");
var message = GetLogMessage(contextMessage, error.ToString(), filterContext.BackgroundJob.Id); var logLevel = ErrorLevelType.Fatal; if (error.InnerException is AppException ex)
{
logLevel = ex.ErrorLevel;
} switch (logLevel)
{
case ErrorLevelType.Info:
logger.Info(message, error);
break;
case ErrorLevelType.Warning:
logger.Warn(message, error);
break;
case ErrorLevelType.Error:
logger.Error(message, error);
break;
default:
logger.Fatal(message, error);
break;
}
} /// <summary>
/// 获取当前日志对象
/// </summary>
/// <returns></returns>
private LogMessage GetLogMessage(ContextMessage contextMessage, string errorMessage, string backgroundJobId)
{
var logMessage = new LogMessage(contextMessage, errorMessage)
{
RawUrl = backgroundJobId
};
return logMessage;
} }
现在还有一个问题是,HangFire DashBoard 默认只支持localhost访问,现在我需要可以很方便的在外网通过web集群就能访问到其对应的HangFire DashBoard。
通过文档https://docs.hangfire.io/en/latest/configuration/using-dashboard.html,可以知道其提供了一个登录验证的实现,但是由于其是直接写死了密码在代码中的,觉得不好。(ps:具体的实现可以参考:https://gitee.com/LucasDot/Hangfire.Dashboard.Authorization,https://www.cnblogs.com/lightmao/p/7910139.html)
我实现的思路是,在web集群界面直接打开标签页访问。在web集群后台生成token并在url中携带,在hangfire站点中校验token,验证通过则放行。同时校验url是否携带可修改的参数,如果有的话设置IsReadOnlyFunc放回false。(ps:可参考文档:https://docs.hangfire.io/en/latest/configuration/using-dashboard.html)
在startup页面配置使用dashboard,通过DashboardOptions选择配置我们自己实现的身份验证,以及是否只读设置。

public void Configuration(IAppBuilder app)
{
try
{ Bootstrapper.Instance.Start(); var dashboardOptions = new DashboardOptions()
{
Authorization = new[] { new HangFireAuthorizationFilter() },
IsReadOnlyFunc = HangFireIsReadOnly
};
app.UseHangfireDashboard("/hangfire", dashboardOptions); }
catch (Exception ex)
{
Debug.WriteLine(ex);
} } /// <summary>
/// HangFire仪表盘是否只读
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private bool HangFireIsReadOnly(DashboardContext context)
{
var owinContext = new OwinContext(context.GetOwinEnvironment());
if (owinContext.Request.Host.ToString().StartsWith("localhost"))
{
return false;
} try
{
var cookie = owinContext.Request.Cookies["Ld.HangFire"];
char[] spilt = { ',' };
var userData = FormsAuthentication.Decrypt(cookie)?.UserData.Split(spilt, StringSplitOptions.RemoveEmptyEntries);
if (userData != null)
{
var isAdmin = userData[].Replace("isAdmin:", "");
return !bool.Parse(isAdmin);
}
}
catch (Exception e)
{ } return true;
}
在HangFireAuthorizationFilter的具体实现中,先校验是否已存在验证后的cookie如果有就不再验证,或者如果是通过localhost访问也不进行校验,否则验证签名是否正确,如果正确就将信息写入cookie。
/// <summary>
/// HangFire身份验证
/// </summary>
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var owinContext = new OwinContext(context.GetOwinEnvironment()); if (owinContext.Request.Host.ToString().StartsWith("localhost")|| owinContext.Request.Cookies["Ld.HangFire"] != null)//通过localhost访问不校验,与cookie也不校验
{
return true;
} var cluster = owinContext.Request.Query.Get("cluster");//集群名称
var isAdminS = owinContext.Request.Query.Get("isAdmin");//是否管理员(是则允许修改hangfire)
var token = owinContext.Request.Query.Get("token");
var t = owinContext.Request.Query.Get("t");//时间戳
if (!string.IsNullOrEmpty(token) && bool.TryParse(isAdminS, out var isAdmin) && long.TryParse(t, out var timestamp))
{
try
{
var isValid = LicenceHelper.ValidSignature($"{cluster}_{isAdmin}", token, timestamp, TimeSpan.FromMinutes());//五分钟有效
if (isValid)
{
var ticket = new FormsAuthenticationTicket(, cluster, DateTime.Now, DateTime.Now + FormsAuthentication.Timeout, false, $"isAdmin:{isAdmin}");
var authToken = FormsAuthentication.Encrypt(ticket); owinContext.Response.Cookies.Append("Ld.HangFire", authToken, new CookieOptions()
{
HttpOnly = true,
Path = "/hangfire"
});
return true;
}
}
catch (Exception ex)
{ }
}
return false; } }
在web的管理后台具体实现为,同选中集群点击后台任务直接访问改集群的HangFire DashBoard


点击后台任务按钮,后台放回token相关信息,然后js打开一个新的标签页展示dashboard
public ActionResult GetHangFireToken(string clusterName)
{
var isAdmin=WorkContext.User.IsAdmin;
var timestamp = DateTime.UtcNow.Ticks;
var token = LicenceHelper.Signature($"{clusterName}_{isAdmin}", timestamp);
return this.Direct(new
{
isAdmin,
token,
timestamp=timestamp.ToString()
});
}
openHangFire:function() {
var me = this, rows = me.getGridSelection('查看后台任务的集群', true);
if (!rows) {
return;
}
if (rows.length > 1) {
me.alert('只能选择一行');
return;
}
var clusterName = rows[0].get('ClusterName');
var opts = {
actionName: 'GetHangFireToken',
extraParams: {
clusterName: clusterName
},
success: function (result) {
var data = result.result;
var url = Ext.String.format("{0}/hangfire?cluster={1}&isAdmin={2}&token={3}&t={4}",
rows[0].get("AccessUrl"),
clusterName,
data.isAdmin,
data.token,
data.timestamp);
window.open(url, clusterName);
}
};
me.directRequest(opts);
}
HangFire多集群切换及DashBoard登录验证的更多相关文章
- Nginx集群之WCF分布式身份验证(支持Soap)
目录 1 大概思路... 1 2 Nginx集群之WCF分布式身份验证... 1 3 BasicHttpBinding.ws2007HttpBinding. 2 4 ...
- Zookeeper 集群的安装及高可用性验证已完成!
安装包 kafka_2.12-0.10.2.0.tgz zookeeper-3.3.5.tar.gz Java 环境 Zookeeper 和 Kafka 的运行都需要 Java 环境,Kafka 默认 ...
- 【原创】Linux服务器集群通过SSH无密码登录
SSH 无密码授权访问slave集群机器 1. 安装SSH,所有集群机器,都要安装SSH环境介绍: Master : CNT06BIG01 192.168.3.61 SLAVE 1: CNT06BI ...
- spring cloud: Hystrix(八):turbine集群监控(dashboard)
turbine是聚合服务器发送事件流数据的一个工具,hystrix的监控中,只能监控单个节点,实际生产中都为集群, 因此可以通过turbine来监控集群下hystrix的metrics情况,通过eur ...
- [Kubernetes]集群配置免密登录Permission denied (publickey,password) 解决办法
在用ansible部署Kubernetes集群是需要配置免密登录,但是遇到Permission denied (publickey,password)的问题 首先推断可能是sshd_config的配置 ...
- KingbaseES R3 受限dba影响集群切换
一.受限dba功能说明(参考自官方文档) 受限DBA 受限DBA可以对当前DBA的权限进行一定限制.当功能开启后DBA将不能更改以下对象: Table Database Function(by n ...
- Spring Cloud项目之断路器集群监控Hystrix Dashboard
微服务(Microservices Architecture)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独立部署,各个微服务之间是松耦合的.每个微服务仅关注于完 ...
- 【ssh免登录】设置集群环境ssh免登录步骤
1.每台机器都需要执行,生成自己的密钥 # ssh-keygen -t rsa 过程中遇到选项,全部enter #cd ~/.ssh # cat id_rsa.pub > authorized_ ...
- centos7生成密钥及集群之间免密登录
1.在本地生成密钥 命令:ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa 2.进入.ssh文件夹,创建保存密钥的文件 命令:touch authorized_keys ...
随机推荐
- python下载及安装步骤
Python安装 1.浏览器打开网址:www.python.org 2.根据电脑系统选择下载 3.确定电脑系统属性,此处我们以win10的64位操作系统为例 4.安装python 3.6.3 双击下载 ...
- 弹出框Alert
selenium提供了三个处理alert的方法 注意:首先需要切换窗口到alert driver.switch_to.alert() (1)点击确定按钮 driver.switch_to.alert. ...
- Windows程序设计(2) - API-02 文件系统
一.磁盘分区的基本概念 1.磁盘分区(Patitions): 分区就是物理存储设备分割成多个不同的逻辑上的存储设备.分区从实质上说就是对硬盘的一种格式化.当我们创建分区时,就已经设置好了硬盘的各项物理 ...
- Java中的四种引用方式
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与"引用"有关.在Java语言中,将引用又分为强引用.软引用.弱引用 ...
- 动力节点 mysql 郭鑫 34道经典的面试题二
13.有3个表S(学生表),C(课程表),SC(学生选课表) S(SNO,SNAME)代表(学号,姓名) C(CNO,CNAME,CTEACHER)代表(课号,课名,教师) SC(SNO,CNO,SC ...
- mybatis面试入门
第一步创建一个java project 导入mybatis需要的jar包,创建与数据库一一对应的javabean对象 第二步:创建mybatis的配置文件 sqlMapconfig.xml 第三步:创 ...
- React实战教程之从零开始手把手教你使用 React 最新特性Hooks API 打造一款计算机知识测验App
项目演示地址 项目演示地址 项目代码结构 前言 React 框架的优雅不言而喻,组件化的编程思想使得React框架开发的项目代码简洁,易懂,但早期 React 类组件的写法略显繁琐.React Hoo ...
- 关于对Entity Framework 3.1的理解与总结
Entity Framework Core 是一个ORM,所谓ORM也是ef的一个框架之一吧,简单的说就是把C#一个类,映射到数据库的一个表,把类里面的属性映射到表中的字段.然后Entity Fram ...
- python基础知识扩展(一)
python课外笔记 1.print函数 print("helloworld")其实系统默认隐藏了一个参数end,完整的print()语句是 print("hellowo ...
- MongoDB副本集replica set(三)--添加删除成员
在上一篇文章中,我们搭建了3个节点的副本集,集群信息如下: rstest:PRIMARY> rs.config() { "_id" : "rstest", ...