今天是wcf系列的第四天,也该出手压轴戏了。嗯,现在的大型架构,都是神马的,

nginx鸡群,iis鸡群,wcf鸡群,DB鸡群,由一个人作战变成了群殴.......

今天我就分享下wcf鸡群,高性能架构中一种常用的手法就是在内存中维护一个叫做“索引”的内存数据库,

在实战中利用“索引”这个概念做出"海量数据“的秒杀。

好,先上图:

这个图明白人都能看得懂吧。因为我的系列偏重于wcf,所以我重点说下”心跳检测“的实战手法。

第一步:上一下项目的结构,才能做到心中有数。

第二步:“LoadDBService”这个是控制台程序,目的就是从数据库抽出关系模型加载在内存数据库中,因为这些东西会涉及一些算法的知识,

在这里就不写算法了,就简单的模拟一下。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Web.Script.Serialization;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using Common; namespace LoadDBData
{
class Program
{
static void Main(string[] args)
{
//模拟从数据库加载索引到内存中,形成内存中的数据库
//这里的 "Dictionary" 用来表达“一个用户注册过多少店铺“,即UserID与ShopID的一对多关系
SerializableDictionary<int, List<int>> dic = new SerializableDictionary<int, List<int>>(); List<int> shopIDList = new List<int>(); for (int shopID = ; shopID < ; shopID++)
shopIDList.Add(shopID); int UserID = ; //假设这里已经维护好了UserID与ShopID的关系
dic.Add(UserID, shopIDList); XmlSerializer xml = new XmlSerializer(dic.GetType()); var memoryStream = new MemoryStream(); xml.Serialize(memoryStream, dic); memoryStream.Seek(, SeekOrigin.Begin); //将Dictionary持久化,相当于模拟保存在Mencache里面
File.AppendAllText("F://1.txt", Encoding.UTF8.GetString(memoryStream.ToArray())); Console.WriteLine("数据加载成功!"); Console.Read();
}
}
}

因为Dictionary不支持序列化,所以我从网上拷贝了一份代码让其执行序列化

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.Runtime.Serialization; namespace Common
{
///<summary>
/// 标题:支持 XML 序列化的 Dictionary
///</summary>
///<typeparam name="TKey"></typeparam>
///<typeparam name="TValue"></typeparam>
[XmlRoot("SerializableDictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{ public SerializableDictionary()
: base()
{
}
public SerializableDictionary(IDictionary<TKey, TValue> dictionary)
: base(dictionary)
{
} public SerializableDictionary(IEqualityComparer<TKey> comparer)
: base(comparer)
{
} public SerializableDictionary(int capacity)
: base(capacity)
{
}
public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer)
: base(capacity, comparer)
{
}
protected SerializableDictionary(SerializationInfo info, StreamingContext context)
: base(info, context)
{
} public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
///<summary>
/// 从对象的 XML 表示形式生成该对象
///</summary>
///<param name="reader"></param>
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
this.Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
} /**/
///<summary>
/// 将对象转换为其 XML 表示形式
///</summary>
///<param name="writer"></param>
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
} }
}

第三步: "HeartBeatService"也做成了一个控制台程序,为了图方便,把Contract和Host都放在一个控制台程序中,

代码中加入了注释,看一下就会懂的。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text; namespace HeartBeatService
{
//CallbackContract:这个就是Client实现此接口,方便服务器端通知客户端
[ServiceContract(CallbackContract = typeof(ILiveAddressCallback))]
public interface IAddress
{
///<summary>
/// 此方法用于Search启动后,将Search地址插入到此处
///</summary>
///<param name="address"></param>
[OperationContract(IsOneWay = true)]
void AddSearch(string address); ///<summary>
/// 此方法用于IIS端获取search地址
///</summary>
///<param name="address"></param>
[OperationContract(IsOneWay = true)]
void GetService(string address);
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Timers;
using System.IO;
using System.Collections.Concurrent;
using SearhService;
using ClientService; namespace HeartBeatService
{
//InstanceContextMode:主要是管理上下文的实例,此处是single,也就是单体
//ConcurrencyMode: 主要是用来控制实例中的线程数,此处是Multiple,也就是多线程
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Address : IAddress
{
static List<string> search = new List<string>(); static object obj = new object(); ///<summary>
/// 此静态构造函数用来检测存活的Search个数
///</summary>
static Address()
{
Timer timer = new Timer();
timer.Interval = ;
timer.Elapsed += (sender, e) =>
{ Console.WriteLine("\n***************************************************************************");
Console.WriteLine("当前存活的Search为:"); lock (obj)
{
//遍历当前存活的Search
foreach (var single in search)
{
ChannelFactory<IProduct> factory = null; try
{
//当Search存在的话,心跳服务就要定时检测Search是否死掉,也就是定时的连接Search来检测。
factory = new ChannelFactory<IProduct>(new NetTcpBinding(SecurityMode.None), new EndpointAddress(single));
factory.CreateChannel().TestSearch();
factory.Close(); Console.WriteLine(single); }
catch (Exception ex)
{
Console.WriteLine(ex.Message); //如果抛出异常,则说明此search已经挂掉
search.Remove(single);
factory.Abort();
Console.WriteLine("\n当前时间:" + DateTime.Now + " ,存活的Search有:" + search.Count() + "个");
}
}
} //最后统计下存活的search有多少个
Console.WriteLine("\n当前时间:" + DateTime.Now + " ,存活的Search有:" + search.Count() + "个");
};
timer.Start();
} public void AddSearch(string address)
{ lock (obj)
{
//是否包含相同的Search地址
if (!search.Contains(address))
{
search.Add(address); //search添加成功后就要告诉来源处,此search已经被成功载入。
var client = OperationContext.Current.GetCallbackChannel<ILiveAddressCallback>();
client.LiveAddress(address);
}
}
} public void GetService(string address)
{
Timer timer = new Timer();
timer.Interval = ;
timer.Elapsed += (obj, sender) =>
{
try
{
//这个是定时的检测IIS是否挂掉
var factory = new ChannelFactory<IServiceList>(new NetTcpBinding(SecurityMode.None),
new EndpointAddress(address)); factory.CreateChannel().AddSearchList(search); factory.Close(); timer.Interval = ;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
};
timer.Start();
}
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel; namespace HeartBeatService
{
///<summary>
/// 等客户端实现后,让客户端约束一下,只能是这个LiveAddress方法
///</summary>
public interface ILiveAddressCallback
{
[OperationContract(IsOneWay = true)]
void LiveAddress(string address);
}
}

第四步: 我们开一下心跳,预览下效果:

是的,心跳现在正在检测是否有活着的Search。

第五步:"SearhService" 这个Console程序就是WCF的search,主要用于从MemerCache里面读取索引。

记得要添加一下对“心跳服务”的服务引用。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text; namespace SearhService
{
// 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。
[ServiceContract]
public interface IProduct
{
[OperationContract]
List<int> GetShopListByUserID(int userID); [OperationContract]
void TestSearch();
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using Common;
using System.Xml;
using System.IO;
using System.Xml.Serialization; namespace SearhService
{
public class Product : IProduct
{
public List<int> GetShopListByUserID(int userID)
{
//模拟从MemCache中读取索引
SerializableDictionary<int, List<int>> dic = new SerializableDictionary<int, List<int>>(); byte[] bytes = Encoding.UTF8.GetBytes(File.ReadAllText("F://1.txt", Encoding.UTF8)); var memoryStream = new MemoryStream(); memoryStream.Write(bytes, , bytes.Count()); memoryStream.Seek(, SeekOrigin.Begin); XmlSerializer xml = new XmlSerializer(dic.GetType()); var obj = xml.Deserialize(memoryStream) as Dictionary<int, List<int>>; return obj[userID];
} public void TestSearch() { }
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Configuration;
using System.Timers;
using SearhService.HeartBeatService; namespace SearhService
{
public class SearchHost : IAddressCallback
{
static DateTime startTime; public static void Main()
{
ServiceHost host = new ServiceHost(typeof(Product)); host.Open(); AddSearch(); Console.Read(); } static void AddSearch()
{
startTime = DateTime.Now; Console.WriteLine("Search服务发送中.....\n\n*************************************************\n"); try
{
var heartClient = new AddressClient(new InstanceContext(new SearchHost())); string search = ConfigurationManager.AppSettings["search"]; heartClient.AddSearch(search);
}
catch (Exception ex)
{
Console.WriteLine("Search服务发送失败:" + ex.Message);
}
} public void LiveAddress(string address)
{
Console.WriteLine("恭喜你," + address + "已被心跳成功接收!\n");
Console.WriteLine("发送时间:" + startTime + "\n接收时间:" + DateTime.Now);
}
}
}

第六步:此时Search服务已经建好,我们可以测试当Search开启获取关闭对心跳有什么影响:

Search开启时:

Search关闭时:

对的,当Search关闭时,心跳检测该Search已经死掉,然后只能从集群中剔除。

当然,我们可以将Search拷贝N份,部署在N台机器中,只要修改一下endpoint地址就OK了,这一点明白人都会。

第七步:"ClientService" 这里也就指的是IIS,此时我们也要添加一下对心跳的服务引用。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel; namespace ClientService
{
[ServiceContract]
public interface IServiceList
{
[OperationContract]
void AddSearchList(List<string> search);
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Configuration;
using System.Timers;
using System.Threading; namespace ClientService
{
public class ServiceList : IServiceList
{
public static List<string> searchList = new List<string>(); static object obj = new object(); public static string Search
{
get
{
lock (obj)
{
//如果心跳没及时返回地址,客户端就在等候
if (searchList.Count == )
Thread.Sleep(1);
return searchList[new Random().Next(, searchList.Count)];
}
}
set
{ }
} public void AddSearchList(List<string> search)
{
lock (obj)
{
searchList = search; Console.WriteLine("************************************");
Console.WriteLine("当前存活的Search为:"); foreach (var single in searchList)
{
Console.WriteLine(single);
}
}
}
}
}
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Configuration;
using System.Threading;
using ClientService.HeartBeatService;
using SearhService;
using BaseClass;
using System.Data;
using System.Diagnostics; namespace ClientService
{
class Program : IAddressCallback
{
static void Main(string[] args)
{ ServiceHost host = new ServiceHost(typeof(ServiceList)); host.Open(); var client = new AddressClient(new InstanceContext(new Program())); //配置文件中获取iis的地址
var iis = ConfigurationManager.AppSettings["iis"]; //将iis的地址告诉心跳
client.GetService(iis); //从集群中获取search地址来对Search服务进行调用
var factory = new ChannelFactory<IProduct>(new NetTcpBinding(SecurityMode.None), new EndpointAddress(ServiceList.Search)); //根据userid获取了shopID的集合
var shopIDList = factory.CreateChannel().GetShopListByUserID(); //.......................... 后续就是我们将shopIDList做连接数据库查询(做到秒杀) Console.Read();
} public void LiveAddress(string address)
{ }
}
}

然后我们开启Client,看看效果咋样:

当然,search集群后,client得到search的地址是随机的,也就分担了search的负担,实现有福同享,有难同当的效果了。

最后: 我们做下性能检测,看下“秒杀”和“毫秒杀”的效果。

首先在数据库的User表和Shop插入了180万和20万的数据用于关联。

ClientService改造后的代码:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Timers;
using System.Diagnostics;
using BaseClass;
using ClientService;
using ClientService.HeartBeatService;
using System.Configuration;
using SearhService; namespace ClientService
{
class Program : IAddressCallback
{
static void Main(string[] args)
{ ServiceHost host = new ServiceHost(typeof(ServiceList)); host.Open(); var client = new AddressClient(new InstanceContext(new Program())); //配置文件中获取iis的地址
var iis = ConfigurationManager.AppSettings["iis"]; //将iis的地址告诉心跳
client.GetService(iis); //从集群中获取search地址来对Search服务进行调用
var factory = new ChannelFactory<IProduct>(new NetTcpBinding(SecurityMode.None), new EndpointAddress(ServiceList.Search)); //根据userid获取了shopID的集合
//比如说这里的ShopIDList是通过索引交并集获取的分页的一些shopID
var shopIDList = factory.CreateChannel().GetShopListByUserID(); var strSql = string.Join(",", shopIDList); Stopwatch watch = new Stopwatch(); watch.Start();
SqlHelper.Query("select s.ShopID,u.UserName,s.ShopName from [User] as u ,Shop as s where s.ShopID in(" + strSql + ")");
watch.Stop(); Console.WriteLine("通过wcf索引获取的ID >>>花费时间:" + watch.ElapsedMilliseconds); //普通的sql查询花费的时间
StringBuilder builder = new StringBuilder(); builder.Append("select * from ");
builder.Append("(select ROW_NUMBER() over(order by s.ShopID) as NumberID, ");
builder.Append(" s.ShopID, u.UserName, s.ShopName ");
builder.Append("from Shop s left join [User] as u on u.UserID=s.UserID ");
builder.Append("where s.UserID=15) as array ");
builder.Append("where NumberID>300000 and NumberID<300050"); watch.Start();
SqlHelper.Query(builder.ToString());
watch.Stop(); Console.WriteLine("普通的sql分页 >>>花费时间:" + watch.ElapsedMilliseconds); Console.Read();
} public void LiveAddress(string address)
{ }
}
}

性能图:

对的,一个秒杀,一个是毫秒杀,所以越复杂越能展示出“内存索引”的强大之处。

wcf系列学习5天速成——第四天 wcf之分布式架构的更多相关文章

  1. wcf系列学习5天速成——第五天 服务托管

    今天是系列的终结篇,当然要分享一下wcf的托管方面的知识. wcf中托管服务一般有一下四种: Console寄宿:             利于开发调试,但不是生产环境中的最佳实践. winform寄 ...

  2. wcf系列学习5天速成——第三天 分布性事务的使用 有时间再验证下 不同库的操作

    原文地址:http://www.cnblogs.com/huangxincheng/archive/2011/11/06/2238273.html 今天是速成的第三天,再分享一下WCF中比较常用的一种 ...

  3. wcf系列学习5天速成——第三天 事务的使用

    今天是速成的第三天,再分享一下WCF中比较常用的一种技术,也就是”事务“. 在B2B的项目中,一般用户注册后,就有一个属于自己的店铺,此时,我们就要插入两张表, User和Shop表. 当然,要么插入 ...

  4. WCF系列学习5天速成

    看到一篇比较好的基础wcf学习博客,分享给大家:http://www.cnblogs.com/huangxincheng/archive/2011/10/23/2221845.html

  5. [转]wcf系列学习——服务托管

    今天是系列的终结篇,当然要分享一下wcf的托管方面的知识. wcf中托管服务一般有一下四种: Console寄宿:             利于开发调试,但不是生产环境中的最佳实践. winform寄 ...

  6. WCF学习系列四--【WCF Interview Questions – Part 4 翻译系列】

    WCF Interview Questions – Part 4   This WCF service tutorial is part-4 in series of WCF Interview Qu ...

  7. WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?

    原文:WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的? 服务端只有抛出FaultException异常才能被正常地序列化成Fault消息,并实现向客户 ...

  8. wcf系列5天速成——第一天 binding的使用(1)

    原文:wcf系列5天速成--第一天 binding的使用(1) 作为WCF速成系列,只介绍些项目开发中常用到的实战知识. 学习wcf,还是对其中的几个术语要了解一下.wcf中有一个ABC的概念,就是 ...

  9. WCF服务二:创建一个简单的WCF服务程序

    在本例中,我们将实现一个简单的计算服务,提供基本的加.减.乘.除运算,通过客户端和服务端运行在同一台机器上的不同进程实现. 一.新建WCF服务 1.新建一个空白解决方案,解决方案名称为"WC ...

随机推荐

  1. DB操作用法总结。

    用到了慢慢总结.用到了随时更新. 其实可以看手册了.但是看了完了手册之后,还是记不住. 1. mysql select * from table where id in(1,2,3,3,4) 怎么能显 ...

  2. 【LeetCode练习题】First Missing Positive

    First Missing Positive Given an unsorted integer array, find the first missing positive integer. For ...

  3. firefox必备扩展

    1.首先,当然要推荐 Adblock Plus 安装地址:https://adblockplus.org/zh_CN/firefox 作用:屏蔽广告,从此和该死的广告说拜拜吧(不论是百度推广,还是别的 ...

  4. Seeding(dfs)

    Seeding Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 65536/32768K (Java/Other) Total Submi ...

  5. lua的学习

    lua是什么 Lua是一中嵌入式的脚本语言. Lua语言可以独立进行编程,但这不是其主要的使用方式.Lua虽然有动态.灵活的语法提供强大的功能,但并不像 Java.Python 等一样有一个完善的库( ...

  6. 关于 HTML5 的 11 个让人难以接受的事实

    HTML5为Web开发者提供了很多强大的新特性,但是它的一些特定的限制会让它无法和本地应用匹敌. HTML5整合进了很多新的特性,并且有可能提升Web编程模式.和每一个阅读技术资讯的人所知道的一样,没 ...

  7. linux-telnet服务配置

    Telnet服务的配置:一.安装telnet软件包(通常要两个)1. telnet-client (或 telnet),这个软件包提供的是 telnet 客户端程序: 2. telnet-server ...

  8. Fiddler 跟踪 手机页面数据包

    随着 HTML5 的急速增长,现在越来越多的人,开始涉及到移动终端的 Web 开发领域,但手机端始终没有 PC 端这么多的调试工具.即使 PC 端浏览器模拟 user-agent 进行开发,也可能会发 ...

  9. 【转】Android的线程使用来更新UI----Thread、Handler、Looper、TimerTask

    方法一:(java习惯,在android不推荐使用) 刚刚开始接触android线程编程的时候,习惯好像java一样,试图用下面的代码解决问题 new Thread( new Runnable() { ...

  10. 利用Oracle数据库的UTL_SMTP发送HTML 邮件

    Ok, that looks hard, but if you use this procedure I wrote, its really quite easy, it does all of th ...