项目中是有多个集群的,现在存在一个是:在切换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.Authorizationhttps://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登录验证的更多相关文章

  1. Nginx集群之WCF分布式身份验证(支持Soap)

    目录 1       大概思路... 1 2       Nginx集群之WCF分布式身份验证... 1 3       BasicHttpBinding.ws2007HttpBinding. 2 4 ...

  2. Zookeeper 集群的安装及高可用性验证已完成!

    安装包 kafka_2.12-0.10.2.0.tgz zookeeper-3.3.5.tar.gz Java 环境 Zookeeper 和 Kafka 的运行都需要 Java 环境,Kafka 默认 ...

  3. 【原创】Linux服务器集群通过SSH无密码登录

    SSH 无密码授权访问slave集群机器 1. 安装SSH,所有集群机器,都要安装SSH环境介绍:  Master : CNT06BIG01 192.168.3.61 SLAVE 1: CNT06BI ...

  4. spring cloud: Hystrix(八):turbine集群监控(dashboard)

    turbine是聚合服务器发送事件流数据的一个工具,hystrix的监控中,只能监控单个节点,实际生产中都为集群, 因此可以通过turbine来监控集群下hystrix的metrics情况,通过eur ...

  5. [Kubernetes]集群配置免密登录Permission denied (publickey,password) 解决办法

    在用ansible部署Kubernetes集群是需要配置免密登录,但是遇到Permission denied (publickey,password)的问题 首先推断可能是sshd_config的配置 ...

  6. KingbaseES R3 受限dba影响集群切换

    ​ 一.受限dba功能说明(参考自官方文档) 受限DBA 受限DBA可以对当前DBA的权限进行一定限制.当功能开启后DBA将不能更改以下对象: Table Database Function(by n ...

  7. Spring Cloud项目之断路器集群监控Hystrix Dashboard

    微服务(Microservices Architecture)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独立部署,各个微服务之间是松耦合的.每个微服务仅关注于完 ...

  8. 【ssh免登录】设置集群环境ssh免登录步骤

    1.每台机器都需要执行,生成自己的密钥 # ssh-keygen -t rsa 过程中遇到选项,全部enter #cd ~/.ssh # cat id_rsa.pub > authorized_ ...

  9. centos7生成密钥及集群之间免密登录

    1.在本地生成密钥 命令:ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa 2.进入.ssh文件夹,创建保存密钥的文件 命令:touch authorized_keys ...

随机推荐

  1. 基于Python3.7的robotframework环境搭建步骤

    一.前言 Robot Framework作为公司能快速落地实现UI自动化测试的一款框架,同时也非常适合刚入门自动化测试的朋友们去快速学习自动化,笔者计划通过从搭建逐步到完成自动化测试的过程来整体描述它 ...

  2. 使用LaTeX输入矩阵

    当前各种文本编辑器支持的LaTeX数学公式库大多基于KaTeX,或者在Web中用MathJax的比较多,下面给出一种在Web中输入矩阵的例子 $$\left[ \begin{array}{cccc}a ...

  3. python批量发邮件

    如果有一天,老板过来给你一个很大的邮箱列表,要你给每个人发邮件,你该如何去做,最简单的就是写一个 python 程序 # coding:utf-8import smtplibfrom email.mi ...

  4. python_lesson1 数学与随机数 (math包,random包)

    math包 math包主要处理数学相关的运算.math包定义了两个常数: math.e   # 自然常数e math.pi  # 圆周率pi   此外,math包还有各种运算函数 (下面函数的功能可以 ...

  5. 【JMeter_06】JMeter逻辑控制器__If控制器<If Controller>

    If控制器<If Controller> 业务逻辑: 根据表达式的结果来决定是否执行控制器下的脚本内容,与编程语言中的if判断逻辑大致相同,表达式结果为布尔值 true或false; 当表 ...

  6. C++核心编程

    C++核心编程 本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓. 1 内存分区模型 C++程序在执行时,将内存大方向划分为4个区域 代码区:存放函数体的二进制代码,由操作系统 ...

  7. Java学习笔记4(多线程)

    多线程 多个程序块同时运行的现象被称作并发执行.多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一条线程,它们会交替执行,彼此间可以进行通信. 进程:在一个操作系统中,每个独立执行的程 ...

  8. Andrew Ng - 深度学习工程师 - Part 1. 神经网络和深度学习(Week 1. 深度学习概论)

     =================第1周 循环序列模型=============== ===1.1 欢迎来到深度学习工程师微专业=== 我希望可以培养成千上万的人使用人工智能,去解决真实世界的实际问 ...

  9. Javascript数组迭代精髓,拿去花

    数组迭代 数组迭代是处理各数组的利器,编写代码时常常会用到,为我们提供了大大的便利.如果还不知道,真的别告诉别人你知道js哈哈. 以下迭代方法均不会改变原数组,带*为必选对象. 1.arr.forEa ...

  10. LeetCode 79,这道走迷宫问题为什么不能用宽搜呢?

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题第48篇文章,我们一起来看看LeetCode当中的第79题,搜索单词(Word Search). 这一题官方给的难 ...