(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
在wcf集群中,高性能的构架中一种常用的手法就是在内存中维护一个叫做“索引”的内存数据库,在实战中利用“索引”这个概念做出“海量数据的”秒杀。
首先,先上架构图:
大体讲解下该系统流程;所谓的Search集群就是图中WCF模块,它分布于不同的服务器上,为了提供高效率的数据检索,我们分步骤进行:
1、我们将数据库中的数据通过程序加载到内存数据库中
2、通过各个服务器中的WCFSearch服务为IIS提供内存数据中的数据检索
3、为了管理不同服务器中的WCFSearch服务,我们利用了“心跳检测”,实现WCFSearch和IIS的搭桥工作
4、在IIS中获取数据索引,然后向本地数据库中提取数据,实现客户端数据提取。
下面重点分析这里面的“心跳检测”的实战手法:
第一步:项目准备,为了显示出该系统构架的优越性我们先新建立个数据库,上图:
类似电子商务中的用户和店铺的关系,一个用户可以开多个店:
我们向这两张表中插入数据,插入百万级别的数据,晒下SQL数据:
------------------------------------------------
declare @a int --定义变量
declare @m int
declare @ShopName char(50)
declare @ShopUrl nchar(50)
declare @UserName nchar(50)
declare @Password nchar(50)
set @a=0 --初始值
set @m=0
set @ShopName='淘宝店'
set @ShopUrl='www.baidu.com'
set @UserName='小吴'
set @Password='110'
while @a<=1800000 ---循环插入User表180W数据
begin
insert into [User] (UserName,Passwrod) values (@UserName,@Password)
declare @UserID int
set @UserID=(select @@identity)
while @m<=10
begin
insert into dbo.Shop (UserID,ShopName,ShopUrl,User_UserID) values (@UserID,@ShopName,@ShopUrl,null)
set @m=@m+1
continue
end
set @m=0 --内循环重置为0
set @a=@a+1
continue
end
---------------------------------------------------------
select * from Shop
select * from [User]
delete from Shop
delete from [User]
truncate table Shop --重置Shop标识列
dbcc checkident('[User]',reseed,1) ---重置标识列
----------------------------------------------------------
至此,数据库已经准备完毕,我们开始建立项目。
第二步:先晒项目结构:
第三步:先解析LoadDBService项目,该项目实现的是数据库内容的加载,不废话,晒代码:
/**
*心跳检测机制
*模拟数据库加载到内存中,形成内存中的数据库
*/
namespace xinTiaoTest
{
class Program
{
static void Main(string[] args)
{
//这里的Dicionary用来表示“一个注册用户用过了多少个店铺”,即UserID和ShopID的一对多关系
SerializableDictionary<int, List<int>> dic = new SerializableDictionary<int, List<int>>();
List<int> shopIDList = new List<int>();
for (int shopID = 3000; shopID < 3050; shopID++)
{
shopIDList.Add(shopID);
}
int UserID = 15;
//假设这里已经维护了UserID与shopID的关系
dic.Add(UserID, shopIDList);
XmlSerializer xml = new XmlSerializer(dic.GetType());
var memoryStrean = new MemoryStream();
xml.Serialize(memoryStrean, dic);//将dic对象写入ID流
memoryStrean.Seek(0, SeekOrigin.Begin); //从0开始读取
//将Dicrionary持久化,相当于模拟保存在Mencache里面
File.AppendAllText("E://1.txt", Encoding.UTF8.GetString(memoryStrean.ToArray()));
Console.WriteLine("将数据加载成功");
Console.Read();
}
}
}
为了序列化List列表,我们引入了该类的序列化:
///<summary>
///13 /// 标题:支持 XML 序列化的 Dictionary
///</summary>
///<typeparam name="TKey"></typeparam>
///16 ///<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> 54 /// 从对象的 XML 表示形式生成该对象 55 ///</summary>
///56 ///<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> 83 /// 将对象转换为其 XML 表示形式
///84 ///</summary> 85 ///<param name="writer"></param> 86
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();
}
}
}
}
这样我们就实现了数据库中索引保存为内存数据库中的,当然为了演示我们将其持久化,另存为txt文件。
第四步:新建WCFSearch,用于从内存数据中读取索引,为IIS提供服务,不废话,晒代码:
namespace SearhService
{
// 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IIProduct”。
[ServiceContract]
public interface IProduct
{
[OperationContract]
List<int> GetShopListByUserID(int userID);
[OperationContract]
void TestSrarch();
}
}
实现方法:
namespace SearhService
{
// 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的类名“IProduct”。
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("E://1.txt", Encoding.UTF8));
var memoryStream = new MemoryStream(); //新建缓存区域
memoryStream.Write(bytes, 0, bytes.Count()); //使用从缓冲区读取的数据将字节块写入当前流。
memoryStream.Seek(0, SeekOrigin.Begin);
XmlSerializer xml = new XmlSerializer(dic.GetType());
var obj = xml.Deserialize(memoryStream) as Dictionary<int, List<int>>; //反序列化生成对象
return obj[userID];
}
public void TestSrarch()
{
throw new NotImplementedException();
}
}
}
发布服务:
namespace SearhService
{
public class SearchHost : ServiceHeartBeat.IAddressCallback
{
static DateTime startTime;
public static void Main()
{
ServiceHost host = new ServiceHost(typeof(Product));
host.Open();
AddSearch();
Console.Read();
}
private static void AddSearch()
{
startTime = DateTime.Now;
Console.WriteLine("Search服务发送中.....\n\n*************************************************\n");
try
{
var heartClient = new ServiceHeartBeat.AddressClient(new InstanceContext(new SearchHost()));
string search = ConfigurationManager.AppSettings["search"]; //获取配置的本机service地址
heartClient.AddSearch(search); //添加到连接。
}
catch (Exception err)
{
Console.WriteLine("Search服务发送失败:" + err.Message);
}
}
//服务端回调函数
public void LiveAddress(string address)
{
Console.WriteLine("恭喜你,"+address+"已经被心跳成功接受");
Console.WriteLine("发送时间:"+startTime+"\n接收时间:"+DateTime.Now.ToString());
}
}
}
这里有几个点需要注意,在我们首先要引用“心跳服务”,然后将自己的发布的服务地址发送给"心跳服务“,在程序中也就是实现 ServiceHeartBeat.IAddressCallback接口,这是一个”心跳检测“中设置的一个回调接口,目的是实现告诉Search,我已经接受了你的服务,实现数据的推送…
下面是该服务的配置文件:
<?xml version="1.0"?>
<configuration>
<!--设置本service的地址-->
<appSettings>
<add key="search" value="net.tcp://localhost:8732/Design_Time_Addresses/SearhService/IProduct/"/>
</appSettings>
<system.serviceModel>
<!--添加服务引用生成的客户端的配置资料 -->
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IAddress"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:01:00"
transactionFlow="false"
transferMode="Buffered"
transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard"
listenBacklog="10"
maxBufferPoolSize="524288"
maxBufferSize="65536"
maxConnections="10"
maxReceivedMessageSize="65536">
<readerQuotas maxDepth="32"
maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096"
maxNameTableCharCount="16384"/>
<reliableSession ordered="true"
inactivityTimeout="00:10:00" enabled="false"/>
<security mode="Transport">
<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign"/>
<message clientCredentialType="Windows"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:8888/Heart" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IAddress" contract="ServiceHeartBeat.IAddress" name="NetTcpBinding_IAddress">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</client>
<!--End 添加服务引用生成的客户端的配置资料 -->
<!--添加本机服务配置 -->
<behaviors>
<serviceBehaviors>
<behavior name="IProductBehavior">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="IProductBehavior" name="SearhService.Product">
<endpoint address=""
binding="netTcpBinding"
contract="SearhService.IProduct">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint
address="mex"
binding="mexTcpBinding"
contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8732/Design_Time_Addresses/SearhService/IProduct/"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
这里面有个技巧是在我们在vs2010中新建服务时,添加的服务地址里面包含Design_Time_Addresses路径,这个是vs在安装的时候自己注册的一个安全配置,也就是说在这里面普通的的用户就能够发布服务…为了能发布任何服务,我们在运行vs2010的时候要选择“管理员权限”运行。
第五步:新建ClientService项目,该项目代表的是IIS端,实现的是从内存数据库中获取索引值,也就是连接Srerch,废话不多说,上代码:
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);
}
}
提供一个AddSearchList方法,目的是让“心跳检测”向IIS端发送Search的地址。
实现代码为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ClientService
{
public class ServiceList : IServiceList
{
public static List<string> serarchilist = new List<string>();
static object obj = new object();
public static string Search
{
get
{
//如果心跳没及时返回地址,客户端就在等候,如果有的话,就随机获取其地址
while (serarchilist.Count == 0)
{
Thread.Sleep(1000);
}
return serarchilist[new Random().Next(0, serarchilist.Count)];
}
set
{
}
}
public void AddSearchList(List<string> search)
{
lock (obj)
{
serarchilist = search;
Console.WriteLine("心跳发来searche信息************************************");
Console.WriteLine("当前存活的search为:");
foreach (var single in serarchilist)
{
Console.WriteLine(single);
}
}
}
}
}
配置服务,发布服务
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Configuration;
using SearhService;
using Common;
namespace ClientService
{
class Program:ServiceHeartBeat.IAddressCallback
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(ServiceList));
host.Opened += delegate
{
Console.WriteLine("IIS服务已经启动");
};
host.Open();
//向心跳添加链接,获取service。。
var client = new ServiceHeartBeat.AddressClient(new InstanceContext(new Program()));
//获取配置文件中的获取IIS的地址
var iis = ConfigurationManager.AppSettings["iis"];
//发送IIS地址给心跳
client.GetService(iis);
//从集群中获取search地址来对search服务进行调用
var factory = new ChannelFactory<SearhService.IProduct>(new NetTcpBinding(SecurityMode.None), new EndpointAddress(ServiceList.Search));
//根据userID获取了shopId的集合
//比如说这里的ShopIDList是通过索引交并集获取分页的一些shopID
var shopIDList = factory.CreateChannel().GetShopListByUserID(15);
var strSql = string.Join(",", shopIDList);
System.Diagnostics.Stopwatch watch = new System.Diagnostics.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)
{
Console.WriteLine("心跳检测到了你的IIS地址为:" + address );
Console.WriteLine("\n接收时间:" + DateTime.Now.ToString());
}
}
}
同样的实现ServiceHeartBeat.IAddressCallback,“心跳检测”随时的告诉IIS,已经连接的SearchService,当然在里面在获得了SearchService的时候我们对其进行了连接,然后进行了索引的查询…下面的内容是为了比较这种方法的速度如何,稍后晒测试结果。
下面是该服务的配置文件:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="iis" value="net.tcp://localhost:2345/ServiceList"/>
</appSettings>
<system.serviceModel>
<client>
<endpoint address="net.tcp://localhost:8888/Heart" binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_IAddress" contract="ServiceHeartBeat.IAddress"
name="NetTcpBinding_IAddress">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
<bindings>
<netTcpBinding>
<!--客户端验证模式为空-->
<binding name="ClientBinding">
<security mode="None" />
</binding>
<binding name="NetTcpBinding_IAddress"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:01:00"
transactionFlow="false"
transferMode="Buffered"
transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard"
listenBacklog="10"
maxBufferPoolSize="524288"
maxBufferSize="65536"
maxConnections="10"
maxReceivedMessageSize="65536">
<readerQuotas
maxDepth="32"
maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Transport">
<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
<message clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
</bindings>
<!--定义behaviors-->
<behaviors>
<serviceBehaviors>
<behavior name="IProductBehavior">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="ClientService.ServiceList">
<endpoint address="net.tcp://localhost:2345/ServiceList"
binding="netTcpBinding"
contract="ClientService.IServiceList"
bindingConfiguration="ClientBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>
</services>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
这里面的几个简单的配置需提醒,像客户端验证模式一定要匹配,当然在该项目中我们做了简单的配置,在实际应用中我们会对其作进一步详细配置,以确保服务的流畅性。
第六步:这时候该我们的重头戏出场了…心跳检测的的项目,实现两者的互联,上代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace HeartBeatService
{
//callbackContract:这个就是Client实现回调接口,方便服务器通知客户端
[ServiceContract(CallbackContract = typeof(ILiveAddressCallback))]
public interface IAddress
{
//此方法用于Search方法启动后,将Search地址插入此处
[OperationContract(IsOneWay=true)]
void AddSearch(string address);
//此方法用于IIS获取Search地址
[OperationContract(IsOneWay=true)]
void GetService(string address);
}
}
两个方法,第一个实现Search地址的获取,第二个实现IIS的检测,上面加了一个回调接口:ILiveAddressCallback方法,实现的是客户端数据的应答。方法如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace HeartBeatService
{
///<summary>10 /// 等客户端实现后,让客户端约束一下
///,只能是这个LiveAddress方法11 ///</summary>
public interface ILiveAddressCallback
{
[OperationContract(IsOneWay = true)]
void LiveAddress(string address);
}
}
下面是心跳检测的核心代码,上代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Timers;
using System.Configuration;
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();
//构造函数用来检测Search的个数
static Address()
{
Timer timer = new Timer();
timer.Interval = 6000;
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();
factory.Close();
Console.WriteLine(single);
}
catch (Exception err)
{
Console.WriteLine(err.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 = 1000;
timer.Elapsed += (obj, sender) =>
{
try
{
//这个是定时的检测IIS是否挂掉
var factory = new ChannelFactory<IServiceList>(new NetTcpBinding(SecurityMode.None),
new EndpointAddress(address));
factory.Opened += delegate
{
Console.WriteLine("IIS("+address+")已经启动..正在发送searchSerive地址...");
};
factory.CreateChannel().AddSearchList(search);
factory.Close();
timer.Interval = 20000;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
};
timer.Start();
}
}
}
为确保不丢掉任何的Search服务,我们采用多线程,InstanceContextMode:只要是管理上下文的实例,此处是single,也就是单体 ConcurrencyMode:主要是用来控制实例中的线程数,此处是Multiple,也就是多线程。
1、构造函数的静态方法实现当前服务的检测和显示,采用Timer定时检测…
2、AddSearch方法实现Search的添加,并回复客户端
3、GetService方法实现的是检测IIS是否等待连接,并且将Search集合发送给它,同样是定时发送。
下面晒配置文件:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true"/>
</system.web>
<!-- 部署服务库项目时,必须将配置文件的内容添加到
主机的 app.config 文件中。System.Configuration 不支持库的配置文件。-->
<system.serviceModel>
<services>
<service name="HeartBeatService.Address" behaviorConfiguration="myBehavior">
<endpoint address="net.tcp://localhost:8888/Heart"
binding="netTcpBinding"
contract="HeartBeatService.IAddress">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<!--定义引用地址元数据的接受方式,此处基地址(baseAddress)定义的协议Http所以binding同样为mexHttpBinding方式-->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost:9999/Heart"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="myBehavior">
<!-- 为避免泄漏元数据信息,
请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
<serviceMetadata httpGetEnabled="True"/>
<!-- 要接收故障异常详细信息以进行调试,
请将以下值设置为 true。在部署前设置为 false
以避免泄漏异常信息-->
<serviceDebug includeExceptionDetailInFaults="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
至此,我们已经成功顺利的搭建完心跳检测的流程,我们先运行下“心跳”项目,它将等待Search发送连接过来,并检查IIS时候等候状态…
然后我们再运行Search…向“心跳”抛出橄榄枝…
嘿嘿,两者已经顺利握手…下面咱把IIS启动,看看“心跳检测”是否能将这个SearchService发送给它:
同样成功,这说明我们的心跳服务还是能实时的为IIS提供连接地址的…
好,下面我们测试该框架性能如何,首先我们执行LoadDBService,将数据库加载到内存中…结果如下:
我们用该框架测试下速度和普通的查询方式进行比较:
先晒SQL语句,看两者差距性:
select * from
(
select ROW_NUMBER() over(order by s.ShopID) as NumberID,
s.ShopID,
u.UserName,
s.ShopName
from Shop s left join [User] u
on u.UserID=s.UserID
where s.UserID=150
) as array
where NumberID>0 and NumberID<50
-------------------分页查询-------------------------
select s.ShopID,u.UserName,s.ShopName
from [User] as u ,
Shop as s
where s.ShopID in(100,200,3000,5000,4000,201)
select * from shop
select * from [user]
我们在IIS端进行加载检测:
晒运行结果:
提示了一个错误:
我们将此服务的连接时间该的长一点:
这个错误是因为和客户端验证模式不对,我们在这里面做了设置,将其设置为None,为了确保从内存数据库中读取完毕,我们将应答时间该的稍长点。我们下面晒结果
:这里看样子也快不了多少,那是因为我们在wcf里查询的条数设置和SQL里面的语法查询条数不同,我们将改一下,还有就是在此服务已有一个,并且内存数据库实现的方式是通过本地化文件操作,很显然是费时费力的工作,就性能均摊,可扩展性等方面,无疑该框架是利器…好了先解析到此…