SharedCache由3个主要的项目组成MergeSystem.Indexus.WinServiceCommon、MergeSystem.Indexus.WinService和MergeSystem.Indexus.Notify。WinService可以以Windows服务方式加载,也可以以控制台方式运行,如果注册为Windows服务,则可以通过MergeSystem.Indexus.Notify程序来了解其状态,若是以控制台方式运行,则运行时的信息会在控制台窗口中显示。当然也可以配置通过NLog.dll记录日志。
大部分的功能都封装在WinServiceCommon项目中,WinService项目只负责监听和数据中转,这个项目中只有几个文件,如图1。
Indexus是个windows服务,也是程序的入口点,其类图如图2。

| 
|
图1 | 图2 |

|
图3 ShareCache服务端程序主流程 |
Indexus可以接受几个命令行参数/install /i /uninstall /u来安装或卸载windows服务,若想以控制台方式启动程序,可以使用/local参数,如:
MergeSystem.Indexus.WinService.exe /local
或直接使用相同目录中的几个批处理文件来执行。来看一下main函数:
public static void Main(string[] args)


{

Access Log#region Access Log
COM.Handler.LogHandler.Tracking(
"Access Method: " + ((object)MethodBase.GetCurrentMethod()).ToString() + " ;"
);
#endregion Access Log

string optionalArgs = string.Empty;
if (args.Length > 0)

{
optionalArgs = args[0];
}

args Handling#region args Handling
if (!string.IsNullOrEmpty(optionalArgs))

{
if("/?".Equals(optionalArgs.ToLower()))

{
Console.WriteLine("Help Menu");
Console.ReadLine();
return;
}
else if("/local".Equals(optionalArgs.ToLower()))

{
Console.Title = "Indexus.Net Shared Cache - Server";
Console.BackgroundColor = ConsoleColor.DarkBlue;
Console.ForegroundColor = ConsoleColor.White;
// running as cmd appliacation
Indexus SrvIndexus = new Indexus();
SrvIndexus.StartService();
Console.ReadLine();
SrvIndexus.StopService();
return;
}
else if(@"/verbose".Equals(optionalArgs.ToLower()))

{
// Console.SetOut(this);
// Console.SetIn(Console.Out);
// Console.ReadLine();
return;
}
TransactedInstaller ti = new TransactedInstaller();
IndexusInstaller ii = new IndexusInstaller();
ti.Installers.Add(ti);
string path = string.Format("/assemblypath={0}", System.Reflection.Assembly.GetExecutingAssembly().Location);

string[] cmd =
{ path };
InstallContext context = new InstallContext(string.Empty, cmd);
ti.Context = context;

if ("/install".Equals(optionalArgs.ToLower()) || "/i".Equals(optionalArgs.ToLower()))

{
ti.Install(new Hashtable());
}
else if ("/uninstall".Equals(optionalArgs.ToLower()) || "/u".Equals(optionalArgs.ToLower()))

{
ti.Uninstall(null);
}
else

{
StringBuilder sb = new StringBuilder();
sb.Append(@"Your provided Argument is not available." + Environment.NewLine);
sb.Append(@"Use one of the following once:" + Environment.NewLine);
sb.AppendFormat(@"To Install the service '{0}': '/install' or '/i'" + Environment.NewLine, @"IndeXus.Net");
sb.AppendFormat(@"To Un-Install the service'{0}': '/uninstall' or '/u'" + Environment.NewLine, @"IndeXus.Net");
Console.WriteLine(sb.ToString());
}
}
else

{
// nothing received as input argument
ServiceBase[] servicesToRun;

servicesToRun = new ServiceBase[]
{ new Indexus() };
ServiceBase.Run(servicesToRun);
}
#endregion args Handling
}


这里可以看到,加/local参数启动时,直接调用StartService()方法,然后就是和安装windows服务后启动服务一样了。
StartService中调用ServiceLogic.Init()方法进行初始化。代码注释比较少,但已经够我们理解这段代码了。

/**//// <summary>
/// Inits this instance. This method used at startup to initialize
/// all required server components
/// </summary>
public void Init()


{

Access Log#region Access Log
COM.Handler.LogHandler.Tracking(
"Access Method: " + this.GetType().ToString()+ "->" + ((object)MethodBase.GetCurrentMethod()).ToString() + " ;"
);
#endregion Access Log

COM.Handler.LogHandler.Force("Initializing Settings" + COM.Enums.LogCategory.ServiceStart.ToString());
Console.WriteLine(@"Welcome to indeXus.Net Shared Cache");
Console.WriteLine();
Console.WriteLine(COM.Handler.Config.DisplayAppSettings());
COM.Handler.LogHandler.Info(COM.Handler.Config.DisplayAppSettings());
// needs to be instantiated before TCP, it needs an instance of CachExpire
cacheExpireInstance = new CacheExpire();
// TCP needs an instance of CacheExpire
tcpInstance = new TcpServer(cacheExpireInstance);

COM.Handler.LogHandler.Force("Init and Start Thread Tcp");
COM.Handler.LogHandler.Force("Init and Start Thread CacheExpire");
// Init all extenders
// an extender is a class which initializes its own logic around
// specific issue within its own thread;

/**////////////////////////////////////////////////////////////////////
this.workerTcp = new Thread(this.tcpInstance.Init);
this.workerTcp.Name = "TCP Handler";
this.workerTcp.IsBackground = true;
this.workerTcp.Priority = ThreadPriority.Normal;

/**////////////////////////////////////////////////////////////////////
this.workerCacheExpire = new Thread(this.cacheExpireInstance.Init);
this.workerCacheExpire.Name = "Cache Expire Handler";
this.workerCacheExpire.IsBackground = true;
this.workerCacheExpire.Priority = ThreadPriority.Lowest;

/**////////////////////////////////////////////////////////////////////
this.workerCacheExpire.Start();
this.workerTcp.Start();

// enable the search of replicaiton servers
if (this.enableServiceFamilyMode)

{
NetworkDistribution.Init();
}

string msgThreadInfo = Environment.NewLine +
"Main Thread Id: " + Thread.CurrentThread.ManagedThreadId.ToString() + Environment.NewLine +
"this.workerTcp: " + this.workerTcp.ManagedThreadId.ToString() + Environment.NewLine +

/**//*"this.workerTimer: " + this.workerTimer.ManagedThreadId.ToString() + Environment.NewLine +*/
"this.workerCacheExpire: " + this.workerCacheExpire.ManagedThreadId.ToString();

Console.WriteLine(msgThreadInfo + Environment.NewLine);
Console.WriteLine("+ + + + + + + + + + + + + + + + + + + + + + + + + + + + ");
Console.WriteLine("server is ready to receive data.");

COM.Handler.LogHandler.Force("IndeXus.Net Service Started " + COM.Enums.LogCategory.ServiceStart.ToString());
}

这里启动2个Thread,一个负责执行CacheExpire.Init()方法,负责定时轮查缓Cache中设置有过期策略的对象,如果有到期的,就从Cache中清除。另一个Thread负责监听TCP端口,然后每有一个新的客户端连接过来,又启动一个新的线程处理。
接下来转到MergeSystem.Indexus.WinService.TcpServer.Init()方法:
public void Init()


{


IPEndPoint endpoint = COM.Handler.Network.GetServerAnyIPEndPoint(this.cacheIpPort);

serverSocket = new Socket(endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(endpoint);
serverSocket.Listen((int)SocketOptionName.MaxConnections);



// setup listener issues
acceptThread = new Thread(AcceptConnections);
acceptThread.IsBackground = true;
acceptThread.Priority = ThreadPriority.Normal;
acceptThread.Start();



}


这里启动一个新线程监听TCP 48888(默认值)端口,跟到AcceptConnections方法:
private void AcceptConnections()


{


while (true)

{
// Accept a connection
Socket socket = serverSocket.Accept();
ConnectionInfo connection = new ConnectionInfo();
connection.Socket = socket;

// Modified:06-01-2008 Merge System GmbH, rschuetz : for more information checkout the following link> http://netrsc.blogspot.com/2007/02/another-extract-from-scalenet-threading.html
ThreadPool.QueueUserWorkItem(this.ProcessConnection, connection);

// Store the socket in the open connections
lock (this.connections)

{
this.connections.Add(connection);
}

}
}


这个比较简单,只是在一个死循环中等待客户端连接,等到后要线程池中执行ProcessConnection方法来处理,此线程继续等待下一个请求。跟到ProcessConnection方法:
private void ProcessConnection(object stats)


{

ConnectionInfo connection = (ConnectionInfo)stats;

try

{


// read data from Socket and convert it to an IndeXusMessage
COM.IndexusMessage msg = new COM.Handler.NetworkMessage().ProcessNetworkMessage(connection.Socket);

if (msg != null)

{
this.ProcessMessage(msg, connection);
}
else

{
Console.WriteLine("Error appears due processing network message!");
COM.Handler.LogHandler.Error("Error appears due processing network message!");
}
}
catch (OutOfMemoryException ex)

{

}
catch (Exception ex)

{


}
finally

{
connection.Socket.Close();
lock (this.connections)
this.connections.Remove(connection);
}
}


这里只调用了COM.Handler.NetworkMessage().ProcessNetworkMessage()方法将客户端发过来的内容还原为IndexusMessage 对象,然后交给ProcessMessage()方法处理:
private void ProcessMessage(COM.IndexusMessage msg, ConnectionInfo connection)


{


// check status first [Request || Response]
switch (msg.Status)

{
case COM.IndexusMessage.StatusValue.Request:

{

request case#region request case
COM.IndexusMessage replicationMessage = null;

if (ServiceLogic.NetworkDistribution.ReplicationEnabled)

{
// create a new object of received msg. to broadcast on servers.
replicationMessage = new COM.IndexusMessage(new Random().Next(), msg.Action, COM.IndexusMessage.StatusValue.Request,new KeyValuePair<string,byte[]>(msg.Key, msg.Payload));
}


// switch over cache actions
switch (msg.Action)

{
case COM.IndexusMessage.ActionValue.Ping:

{

}
case COM.IndexusMessage.ActionValue.Add:

{

}
case COM.IndexusMessage.ActionValue.Get:

{

}
case COM.IndexusMessage.ActionValue.Remove:

{

}
case COM.IndexusMessage.ActionValue.RemoveAll:

{

}
case COM.IndexusMessage.ActionValue.GetAllKeys:

{

}
case COM.IndexusMessage.ActionValue.Statistic:

{

}
}
break;
#endregion request case
}
case COM.IndexusMessage.StatusValue.Response:

{

}
}

}


这里分类对消息进行处理,如添加到缓存或移除等。下面仔细看下Add分支:
case COM.IndexusMessage.ActionValue.Add:

{

Add Case#region Add Case
if (writeStats)
Console.WriteLine("Message Action: {0}", msg.Action.ToString());

Console.WriteLine(@"Adding new Item with Key: {0}", msg.Key);
lock (bulkObject)

{
LocalCache.Add(new KeyValuePair<string, byte[]>(msg.Key, msg.Payload));
// if the given msg expires is not MaxValue
// it will be listed sub-process which clean
// up in iterations the cache.
// QuickFix
if (msg.Expires != DateTime.MaxValue)

{
if (this.expire != null)

{
this.expire.Expire.DumpCacheItemAt(msg.Key, msg.Expires);
}
}
// update cleanup list with new object
CacheCleanup.Update(msg);
}

msg.Action = COM.IndexusMessage.ActionValue.Successful;


rschuetz: MODIFIED: 21-07-2007: distribute object over wire to other installations#region rschuetz: MODIFIED: 21-07-2007: distribute object over wire to other installations
// Question is if the client needs to wait until this happens,
// or should the client first get an answer and just then it
// will distribute it.
if (ServiceLogic.NetworkDistribution.ReplicationEnabled)

{
ServiceLogic.NetworkDistribution.Replicate(replicationMessage);
}
#endregion

// send object back throug the connection
connection.Socket.Send(msg.GetBytes());

// handle max size and purge issues
//DateTime startTime = DateTime.Now;
if (this.cacheAmountOfObjects != -1 && this.cacheAmountOfObjects <= LocalCache.CalculatedCacheSize && !CacheCleanup.PurgeIsRunning)

{
Console.WriteLine(@"Current Size of Cache: {0} ; {1} ", LocalCache.CalculatedCacheSize, LocalCache.CalculatedCacheSize <= 0 ? 0 : LocalCache.CalculatedCacheSize / (1024 * 1024));
List<string> remove = CacheCleanup.Purge(LocalCache.CalculatedCacheSize);
if (remove != null)

{
lock (bulkObject)

{
foreach (string s in remove)

{
LocalCache.Remove(s);
}
}
}
}
break;
#endregion Add Case
}


主要完成了4个功能:1.将数据保存到LocalCache中;2.如果要缓存的对象设置有过期时间,则在expire中保存一份存根;3.如果配置有复制服务器,则分发到各兄弟节点;4.如果缓存对象超出了最大值,则使用配置的策略靖仓。LocalCache是MergeSystem.Indexus.WinServiceCommon.Cache类型的静态成员,expire是WinServiceCommon.CacheExpire类型的静态成员。
分析到这里,ShartCache的大概思路已经很清晰了。
ShartCache的核心是MergeSystem.Indexus.WinServiceCommon.Cache和MergeSystem.Indexus.WinServiceCommon.CacheExpire这二个类。WinServiceCommon.Cache中使用一个字典对象readonly Dictionary<string, byte[]> dict;来存储需要缓存的数据,因为要缓存的数据都已经序列化通过TCP传输过来,这里就直接保存byte[]类型的了。WinServiceCommon.CacheExpire类中也有一个字典对象private static Dictionary<string, DateTime> expireTable = null;所有设置有过期时间的缓存对象,都在这里有个存根,过期的缓存有WinServiceCommon.CacheExpire负责清除。
来源:wuchang.cnblogs.com
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架