欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)

程序员文章站 2024-02-23 16:14:22
...

在wcf集群中,高性能的构架中一种常用的手法就是在内存中维护一个叫做“索引”的内存数据库,在实战中利用“索引”这个概念做出“海量数据的”秒杀。

首先,先上架构图:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
大体讲解下该系统流程;所谓的Search集群就是图中WCF模块,它分布于不同的服务器上,为了提供高效率的数据检索,我们分步骤进行:

1、我们将数据库中的数据通过程序加载到内存数据库中

2、通过各个服务器中的WCFSearch服务为IIS提供内存数据中的数据检索

3、为了管理不同服务器中的WCFSearch服务,我们利用了“心跳检测”,实现WCFSearch和IIS的搭桥工作

4、在IIS中获取数据索引,然后向本地数据库中提取数据,实现客户端数据提取。

下面重点分析这里面的“心跳检测”的实战手法:

第一步:项目准备,为了显示出该系统构架的优越性我们先新建立个数据库,上图:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
类似电子商务中的用户和店铺的关系,一个用户可以开多个店:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
我们向这两张表中插入数据,插入百万级别的数据,晒下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)  ---重置标识列
----------------------------------------------------------

至此,数据库已经准备完毕,我们开始建立项目。

第二步:先晒项目结构:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
第三步:先解析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时候等候状态…(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
然后我们再运行Search…向“心跳”抛出橄榄枝…(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
嘿嘿,两者已经顺利握手…下面咱把IIS启动,看看“心跳检测”是否能将这个SearchService发送给它:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
同样成功,这说明我们的心跳服务还是能实时的为IIS提供连接地址的…

好,下面我们测试该框架性能如何,首先我们执行LoadDBService,将数据库加载到内存中…结果如下:

(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
我们用该框架测试下速度和普通的查询方式进行比较:

先晒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端进行加载检测:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
晒运行结果:

提示了一个错误:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
我们将此服务的连接时间该的长一点:(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
这个错误是因为和客户端验证模式不对,我们在这里面做了设置,将其设置为None,为了确保从内存数据库中读取完毕,我们将应答时间该的稍长点。我们下面晒结果(精华)2020年10月6日 高并发高可用 wcf分布式构架集群案例(百万并发秒杀系统)
:这里看样子也快不了多少,那是因为我们在wcf里查询的条数设置和SQL里面的语法查询条数不同,我们将改一下,还有就是在此服务已有一个,并且内存数据库实现的方式是通过本地化文件操作,很显然是费时费力的工作,就性能均摊,可扩展性等方面,无疑该框架是利器…好了先解析到此…

相关标签: 高并发高可用