今天主要是讨论分布式应用系统下服务器间缓存是如何同步,此篇文章主要是参考了Discuz!NT,Peter,以及微软的解决方案.
主要着重讲Discuz!NT和Peter的实现方式
Discuz!NT和Peter两者的解决方案基本思路其实是一致的.
先说下相同点:
都是将分布式布署的应用看成是一个个"客户端",在其中一台"客户端"发生更新时,需要实时更新其它"客户端",并以推送消息的方式去通知各个客户端,发出Request;
之后接受各个客户端的Response,如果得到的Response为OK,则同步成功,反之则通知同步失败.
具体实现代码:
WebRequest request = WebRequest.Create(new Uri("url")); //首先发送web请求
//之后通过WebResponse接受对方的请求回复,
WebResponse response = request.GetResponse()
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)
//对方如果顺利进行解析并Sync缓存数据成功就会Response.Write("OK");本方如果接受到OK,说明一个完整的流程结束,否则给出异常信息.
再说下不同点:
一、request请求方式不同一个是POST,一个是GET。
二、Peter是定义CacheControlItem对象,对象中指定它的缓存策略(优先级,缓存生存时间)等信息,并将CacheControlItem对象序列化传到各个分布式服务器端,再由每个服务器端自己解析
并同步缓存数据.
Discuz!NT是通过GET的方法通过URL提交数据,提交的数据有两个,一个是Cache的key,一个是PassKey,这个Passkey是用来加密的,在发送通知的请求时,对请求进行加密,以免该功能被其它恶意代码利用.
对方接受到数据后,再进行处理。这里对方是使用ashx文件来接受信息,可以通过它来调用HttpHandler类,直接处理http的请求,更方便.
我把主要是代码贴出来:
Discuz!NT
/// <summary>
/// 同步远程缓存信息
/// </summary>
/// <param name="cacheKey"></param>
public static void SyncRemoteCache(string cacheKey)
{
foreach (string webSite in syncCacheUrlList)
{
string url = string.Format("{0}?cacheKey={1}&passKey={2}",
webSite,
cacheKey,
Discuz.Common.Utils.UrlEncode(Discuz.Common.DES.Encode(cacheKey, loadBalanceConfigInfo.AuthCode)));
ThreadSyncRemoteCache src = new ThreadSyncRemoteCache(url);
new Thread(new ThreadStart(src.Send)).Start();
}
}
public void Send()
{
try
{
//设置循环三次,如果某一次更新成功("OK"),则跳出循环
for (int count = 0; count < 3; count++)
{
if (this.SendWebRequest(_url) == "OK")
break;
else
Thread.Sleep(5000); //如果更新不成功,则暂停5秒后再次更新
}
}
catch { }
finally
{
if (Thread.CurrentThread.IsAlive)
Thread.CurrentThread.Abort();
}
}
/// <summary>
/// 发送web请求
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public string SendWebRequest(string url)
{
StringBuilder builder = new StringBuilder();
try
{
WebRequest request = WebRequest.Create(new Uri(url));
request.Method = "GET";
request.Timeout = 15000;
request.ContentType = "Text/XML";
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
builder.Append(reader.ReadToEnd());
}
}
}
catch
{
builder.Append("Process Failed!");
}
return builder.ToString();
}
/// <summary>
/// 同步本地缓存
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class SyncLocalCache : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string cacheKey = context.Request.QueryString["cacheKey"];
string passKey = context.Request.QueryString["passKey"];
if (Utils.StrIsNullOrEmpty(cacheKey))
{
context.Response.Write("CacheKey is not null!");
return;
}
if (!cacheKey.StartsWith("/Forum"))
{
context.Response.Write("CacheKey is not valid!");
return;
}
if (passKey != Discuz.Common.DES.Encode(cacheKey, Discuz.Config.LoadBalanceConfigs.GetConfig().AuthCode))
{
context.Response.Write("AuthCode is not valid!");
return;
}
Discuz.Cache.DNTCache cache = Discuz.Cache.DNTCache.GetCacheService();
cache.LoadCacheStrategy(new DefaultCacheStrategy());
cache.RemoveObject(cacheKey);
cache.LoadDefaultCacheStrategy();
context.Response.Write("OK");
}
}
Peter的代码:
public enum CacheControlAction
{
AddItem =1,
RemoveItem=2
}
/// <summary>
/// Represents a serializable wrapper class to hold a Cache item
/// </summary>
[Serializable]
public class CacheControlItem
{
public string Key;
public object Item;
public DateTime AbsoluteExpiration;
public TimeSpan SlidingExpiration;
public System.Web.Caching.CacheItemPriority Priority;
// sorry, Dependency not marked as Serializable...class is sealed. Too bad!
//public System.Web.Caching.CacheDependency Dependency;
public int Action;
public CacheControlItem(string Key, object Item, DateTime AbsoluteExpiration,
TimeSpan SlidingExpiration,CacheItemPriority Priority, CacheControlAction Action)
{
this.Key=Key;
this.Item=Item;
this.AbsoluteExpiration=AbsoluteExpiration;
this.SlidingExpiration=SlidingExpiration;
this.Priority=Priority;
this.Action=(int)Action;
}
}
public static MemoryStream Serialize(CacheControlItem item)
{
try
{
MemoryStream ms = new MemoryStream();
BinaryFormatter b = new BinaryFormatter();
b.Serialize(ms, item);
return ms;
}
catch (Exception ex)
{
throw new ApplicationException("Formatter error", ex);
}
}
public static CacheControlItem Deserialize(MemoryStream msInput)
{
try
{
BinaryFormatter b = new BinaryFormatter();
CacheControlItem c = (CacheControlItem)b.Deserialize(msInput);
return c;
}
catch (Exception ex)
{
throw new ApplicationException("Deserialize error", ex);
}
}
public static void AddCacheControlItem(string key, object value,
System.Web.Caching.CacheDependency dependsOn, DateTime absoluteExpiration,
TimeSpan slidingExpiration, CacheItemPriority priority)
{
CacheItemRemovedCallback CacheOut = new CacheItemRemovedCallback(OnCacheOut);
HttpContext.Current.Cache.Add(key, value, dependsOn, absoluteExpiration, slidingExpiration, priority, CacheOut);
CacheControlItem cci = new CacheControlItem(key, value, absoluteExpiration, slidingExpiration, priority, CacheControlAction.AddItem);
MemoryStream ms = Serialize(cci);
byte[] dataToSend = ms.ToArray();
for (int i = 0; i < sServers.Length; i++)
{
//webrequest here to send update to server list
if (!sendUpdate(dataToSend, "http://" + sServers[i] + "/CacheControl.aspx"))
throw new ApplicationException("Update Error");
}
}
public static bool sendUpdate(byte[] dataToSend, string sFullUri)
{
try
{
HttpWebRequest req = (System.Net.HttpWebRequest)WebRequest.Create(sFullUri);
WebRequest request = WebRequest.Create(new Uri(sFullUri));
req.Method = "POST";
Stream stm = req.GetRequestStream();
stm.Write(dataToSend, 0, dataToSend.Length);
stm.Close();
System.Net.WebResponse resp;
resp = req.GetResponse();
stm = resp.GetResponseStream();
StreamReader r = new StreamReader(stm);
byte[] bytRes = System.Text.Encoding.UTF8.GetBytes(r.ReadToEnd());
string res = System.Text.Encoding.ASCII.GetString(bytRes);
Debug.Write(res);
if (res.StartsWith("OK"))
{
return true;
}
else
{
return false;
}
}
catch (Exception ex)
{
throw new HttpException("Update Error", ex);
}
}
public static void OnCacheOut(string key, object val, CacheItemRemovedReason r)
{
// a cache item was removed - expired, etc.
// do WebRequest to other servers from servers[] array to remove item
CacheControlItem cci = new CacheControlItem(key, val, System.DateTime.MaxValue, new TimeSpan(0, 0, 0, 0, 0), CacheItemPriority.Low, CacheControlAction.RemoveItem);
MemoryStream ms = Serialize(cci);
byte[] dataToSend = ms.ToArray();
for (int i = 0; i < sServers.Length; i++)
{
// webrequest here to send update to server list
throw new ApplicationException("Update Error");
}
}