今天是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. kvm-GT

    REF: http://los-vmm.sc.intel.com/wiki/How-to-setup-kvmgthttp://xenvgt.sh.intel.com/image/bdw-h/ Host ...

  2. Mac 安装Qt5.1编译出现的错误解决

    错误提示: :-1: 错误:Xcode is not installed in /Volumes/Xcode/Xcode.app/Contents/Developer. Please use xcod ...

  3. Android解析中国天气网的Json数据

    在Android开发中.一般的APP都是通过获取server端的数据来更新UI.从server获取到的数据能够是Json.它的数据量要比XML要小,这里解析中国天气网上获取的数据,尽管已经不再更新了. ...

  4. RMAN备份之非归档模式下的备份

    Backing Up a Database in NOARCHIVELOG Mode:1.Log into RMAN2.Shutdown immediate from RMAN3.Startup mo ...

  5. JeeSite 企业信息化快速开发平台

    平台简介 JeeSite是基于多个优秀的开源项目,高度整合封装而成的高效,高性能,强安全性的开源Java EE快速开发平台. JeeSite本身是以Spring Framework为核心容器,Spri ...

  6. LinQ学习手册

    LinQ : Language Integrated Query(语言集成查询); 1.以统一方式操作各种数据源,减少数据访问的复杂性. 优点在于封装了SQL语句,只对对象进行操作(增删改查),代码量 ...

  7. 集合框架-TreeSet

    TreeSet是Set集合的常见子类. TreeSet:底层结构是 二叉树 元素是有排序的,但是不可以有重复元素. 相关代码演练: /* TreeSet ;元素是有序的,但是不可以元素重复. */ i ...

  8. IOS支付宝支付出现6002问题的解决办法

    运行支付宝官方demo进行支付测试,会出现6002-网络连接错误,是因为以iOS9 SDK编译的工程会默认以SSL安全协议进行网络传输,即HTTPS,如果依然使用HTTP协议请求网络会报系统异常并中断 ...

  9. CSS3中轻松实现渐变效果

    background: -moz-linear-gradient(top, #8fa1ff, #3757fa); /* Firefox */ background: -webkit-gradient( ...

  10. 南阳师范学院ACM集训队博客使用方法

    南阳师范学院ACM集训队博客使用方法 为方便大家交流,我们使用的是同一个用户名和密码,所以请不要随意修改用户名和密码,不然大家都登不上了,谢谢! 首先进入主页:http://www.cnblogs.c ...