项目中是有多个集群的,现在存在一个是:在切换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. Clear Writer v1.7 更新

    拖更了这么久了的我终于来更新了--这可能是今年上半年最后一次更新了-- 这次我打算把 Clear Writer 公开发布了. 下载链接 下载链接在这里. (这次用蓝奏,不用奶牛快传了) Clear W ...

  2. matplotlib作图一例

    知识点都在这个例子里面: plt.figure(figsize=(10,10)) for i in range(25): plt.subplot(5,5,i+1) plt.xticks([]) plt ...

  3. HashSet扩容机制在时间和空间上的浪费,远大于你的想象

    一:背景 1. 讲故事 自从这个纯内存项目进了大客户之后,搞得我现在对内存和CPU特别敏感,跑一点数据内存几个G的上下,特别没有安全感,总想用windbg抓几个dump看看到底是哪一块导致的,是我的代 ...

  4. Vue中hash模式和history模式的区别

    vue-router 中hash模式和history模式. 在vue的路由配置中有mode选项,最直观的区别就是在hash模式下的地址栏里的URL夹杂着‘#’号 ,而history模式下没有.vue默 ...

  5. SLS编写规范

    SLS编写规范 规范要点说明 首先,状态的执行不可回滚,执行完了就是执行完了,并不会中断回滚,其次,状态的执行,可以反复执行,也就是说一个状态文件,可以多次来进行调用. 在编写状态文件过程中,有以下几 ...

  6. Linux上TCP的几个内核参数调优

    Linux作为一个强大的操作系统,提供了一系列内核参数供我们进行调优.光TCP的调优参数就有50多个.在和线上问题斗智斗勇的过程中,笔者积累了一些在内网环境应该进行调优的参数.在此分享出来,希望对大家 ...

  7. 并发06--JAVA中的并发工具类

    1.等待多线程完成的CountDownLatch CountDownLatch允许一个或多个线程等待其他线程完成操作. 使用join也可以完成这个操作,代码示例如下: package com.exam ...

  8. Windows Defender might be impacting your build performance

    由于换了SSD, 昨天安装了最新的 Idea 2019.2+ , 然后发现每次导入项目都有如下提示: 处理方法就是在Windows安全中心排除目录 处理方式参考: 官方 Known issues An ...

  9. java方法中开启一个线程

    很多业务场景下需要你在一个方法中去开启一个线程,去跑一些处理时间较长的代码,这样调用方就不必经过长时间的等待了.好了 话不多说  先上代码: package test; public class Th ...

  10. 【部分】Asp.Net Mvc 控制器与视图的数据传递

    原文:https://www.cnblogs.com/lsgsanxiao/p/5105639.html 数据传递也就是控制器和视图之间的交互,比如在视图中提交的数据,在控制器怎么获取,或者控制器从业 ...