简单站内负载均衡
LoadBalancingPJ简单站内负载均衡
很多时候我们做项目时都会碰到实现一种相同功能需要对接多个接口的情况,目的需求是一旦现调用的接口服务出现异常时可方便切换到另外正常的接口,不影响正常的业务运营,保持运行稳定。一般这种需求的处理方法是部署这种同类接口的负载均衡,但是此种做法会导致在小型项目中增加运行维护成本、复杂系统结构。比如需要实现用户注册发送短信验证码功能,如果只购买一家短信服务提供商的服务,某些时候有可能会出现服务异常,欠费、网络瘫痪、服务宕机等问题导致短信无法发送。这里最简单的处理方式就是购买多家短信服务提供商!那么在小型项目中我们在不想增加运维成本的情况下,有没有什么办法实现站内调用服务失败的情况下实现自动切换正常的服务呢?答案是肯定有的!在此,本人想到的一个名字暂且叫“站内负载均衡”吧。
项目说明
本项目实现带有权重的基于简单站内负载均衡,自动轮寻实现EnterpriseInterfaceAbstract抽象类的所有类,约束返回相同model(模型)类的类为同一类负载均衡。
代码结构
Config 项目配置文件
EnterpriseInterfaceServerConfig.config 配置相应抽象实现类、权重和异常消息发送实现类
Documents 项目说明文档
EnterpriseInterface 简单负载均衡实现目录
Common 工具类
FlagSources 权重排序工工厂
ServerFactory 服务工厂,面向接口调用方法
EnterpriseInterfaceAbstract.cs 核心抽象类
IQueryBase.cs 查询接口
Models 项目模型(实现类返回模型)
SendSMSUtils 异常消息通知
ISendMessages.cs 消息通知接口
SendSMSByEmail 异常消息通知,邮件实现类
Tools.Common 实现工具类
ServerAPI 项目单件类
简单站内负载均衡实现思路
本项目实现简单站内负载均衡流程如下:
开始=>start: 开始
结束=>end
处理1=>operation: 检索EnerpriseInterfaceAbstract实现类
处理1_1=>operation: 降低异常实现类权重,检索提权其它EnerpriseInterfaceAbstract实现类
处理2=>operation: 调用实现类执行查询
选择1=>condition: 相同返回参数实现类权重最高且无错?
开始->处理1->选择1->处理2->结束
选择1(yes)->处理2
选择1(no)->处理1_1->选择1
简单核心代码及解析
遍历权重最高实现类执行查询及消息异常通知
public TOut Query<TOut>(string[] param) where TOut : class, new()
{
TOut IDCardQueryModel = default(TOut);
List<KeyValuePair<EnterpriseInterfaceAbstract, int>> myList = new List<KeyValuePair<EnterpriseInterfaceAbstract, int>>(ServerFlagDB.Db.GetFlagDBOrderDesc());
foreach (var item in myList)
{
try
{
IQueryBase<TOut> IQ = item.Key as IQueryBase<TOut>;
if (IQ != null)
{
IDCardQueryModel = IQ.Query(param);
break;
}
else
continue;
}
catch (Exception ex)
{
// 调用异常消息通知
// 更新权重
ServerFlagDB.Db.UpdateFlagWeight(item.Key);
}
}
if(IDCardQueryModel == null)
{
// 调用全部异常出错消息通知
}
return IDCardQueryModel;
}
更新权重方法
/// <summary>
/// 修改权重
/// </summary>
/// <param name="className"></param>
public void UpdateFlagWeight(EnterpriseInterfaceAbstract className)
{
if (m_Flag.ContainsKey(className))
{
m_Lock.WaitOne();
m_Flag[className] = 0;
int tempI = 1;
List<KeyValuePair<EnterpriseInterfaceAbstract, int>> myList2 = new List<KeyValuePair<EnterpriseInterfaceAbstract, int>>(m_Flag);
m_Flag.Clear();
foreach (var item in myList2)
{
if (!item.Key.Equals(className))
{
m_Flag.Add(item.Key, item.Value + (tempI++));
}
else
{
m_Flag.Add(item.Key, item.Value);
}
}
//其它的按高到低自动分权重
m_Lock.ReleaseMutex();
}
}
/// <summary>
/// 获取按权重降序排序
/// </summary>
/// <returns></returns>
public Dictionary<EnterpriseInterfaceAbstract, int> GetFlagDBOrderDesc()
{
return SortDictionary_Desc(m_Flag);
}
protected Dictionary<EnterpriseInterfaceAbstract, int> SortDictionary_Desc(Dictionary<EnterpriseInterfaceAbstract, int> dic)
{
List<KeyValuePair<EnterpriseInterfaceAbstract, int>> myList = new List<KeyValuePair<EnterpriseInterfaceAbstract, int>>(dic);
myList.Sort(delegate (KeyValuePair<EnterpriseInterfaceAbstract, int> s1, KeyValuePair<EnterpriseInterfaceAbstract, int> s2)
{
return s2.Value.CompareTo(s1.Value);
});
dic.Clear();
foreach (KeyValuePair<EnterpriseInterfaceAbstract, int> pair in myList)
{
dic.Add(pair.Key, pair.Value);
}
return dic;
}
EnterpriseInterfaceAbstract实现示例
比如我们要实现爬取百度首页的方法(这个只是示例而已),哪么引入该工程文件到项目中,继承 EnterpriseInterfaceAbstract 并实现 IQueryBase
public class QueryTest1 : EnterpriseInterfaceAbstract, IQueryBase<QueryTestModel1>
{
public QueryTestModel1 Query(string[] QueryString)
{
QueryTestModel1 retModel = new QueryTestModel1();
retModel.ret = "ok";
using (WebClient client = new WebClient())
{
retModel.ret = Encoding.UTF8.GetString(client.DownloadData("http://www.baidu.com"));
}
return retModel;
}
}
调用示例
把Config目录引入你的项目,在EnterpriseInterfaceServerConfig节点填写你实现的命名空间,FlagServerClass节点填写你的实现类。
比如我在ConsoleAppTest命名空间下有三个实现类:QueryTest1,QueryTest2和Test1那么我的配置文件如下:
<?xml version="1.0" encoding="utf-8"?>
<EnterpriseInterfaceServerConfig>
<!--需要反射的继承EnterpriseInterfaceAbstract实现类的命名空间-->
<EnterpriseInterfaceAbstractNamespace>ConsoleAppTest</EnterpriseInterfaceAbstractNamespace>
<!--权重X分钟一次恢复原状态(分钟)-->
<UpdataFlagDBTimer>180</UpdataFlagDBTimer>
<!--权重最大类名,多个以“,”号隔开,不需要按反射的方式填写,只需要写类名就可以自动去找了,以下QueryTest2,QueryTest1的权重都为100,test实现类因为没有填,所以为0,权重最低-->
<FlagServerClass>QueryTest2,QueryTest1</FlagServerClass>
<!--消息发送命名空间-->
<SendMessageNameSpace>LoadBalancingPJ</SendMessageNameSpace>
<!--消息发送实现类-->
<SendMessageImplementation>LoadBalancingPJ.SendSMSUtils.SendSMSByEmail.SendMessagesByEmail</SendMessageImplementation>
</EnterpriseInterfaceServerConfig>
在你要调用的地方通过单例这样调用:
// QueryTestModel1是返回实体
QueryTestModel1 QTReturn = ServerAPI.Interface.Query<QueryTestModel1>(new string[] { "1" });
Console.WriteLine(QTReturn.ret);
项目源码若有需要,可以留言@我,感谢关注!
shotting source code