在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 {
tatic 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() + "个");
} }; imer.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里面的语法查询条数不同,我们将改一下,还有就是在此服务已有一个,并且内存数据库实现的方式是通过本地化文件操作,很显然是费时费力的工作,就性能均摊,可扩展性等方面,无疑该框架是利器...好了先解析到此..
|