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

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

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

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

好,先上图:

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

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

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

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

LoadDBServcie

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Runtime.Serialization; 6 using System.Web.Script.Serialization; 7 using System.IO; 8 using System.Xml.Serialization; 9 using System.Xml;10 using Common;11 12 namespace LoadDBData13 {14     class Program15     {16         static void Main(string[] args)17         {18             //模拟从数据库加载索引到内存中,形成内存中的数据库19 //这里的 "Dictionary" 用来表达“一个用户注册过多少店铺“,即UserID与ShopID的一对多关系20             SerializableDictionary<int, List<int>> dic = new SerializableDictionary<int, List<int>>();21             22             List<int> shopIDList = new List<int>();23 24             for (int shopID = 300000; shopID < 300050; shopID++)25                 shopIDList.Add(shopID);26             27             int UserID = 15;28 29             //假设这里已经维护好了UserID与ShopID的关系30             dic.Add(UserID, shopIDList);31             32             XmlSerializer xml = new XmlSerializer(dic.GetType());33             34             var memoryStream = new MemoryStream();35             36             xml.Serialize(memoryStream, dic);37             38             memoryStream.Seek(0, SeekOrigin.Begin);39             40             //将Dictionary持久化,相当于模拟保存在Mencache里面41             File.AppendAllText("F://1.txt", Encoding.UTF8.GetString(memoryStream.ToArray()));42             43             Console.WriteLine("数据加载成功!");44             45             Console.Read();46         }47     }48 }

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

SerializableDictionary

  1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Text;  5 using System.Xml.Serialization;  6 using System.Xml;  7 using System.Xml.Schema;  8 using System.Runtime.Serialization;  9  10 namespace Common 11 { 12     ///<summary> 13 /// 标题:支持 XML 序列化的 Dictionary 14 ///</summary> 15 ///<typeparam name="TKey"></typeparam> 16 ///<typeparam name="TValue"></typeparam> 17     [XmlRoot("SerializableDictionary")] 18     public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable 19     { 20  21         public SerializableDictionary() 22             : base() 23         { 24         } 25         public SerializableDictionary(IDictionary<TKey, TValue> dictionary) 26             : base(dictionary) 27         { 28         } 29  30         public SerializableDictionary(IEqualityComparer<TKey> comparer) 31             : base(comparer) 32         { 33         } 34  35         public SerializableDictionary(int capacity) 36             : base(capacity) 37         { 38         } 39         public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) 40             : base(capacity, comparer) 41         { 42         } 43         protected SerializableDictionary(SerializationInfo info, StreamingContext context) 44             : base(info, context) 45         { 46         } 47  48  49         public System.Xml.Schema.XmlSchema GetSchema() 50         { 51             return null; 52         } 53         ///<summary> 54 /// 从对象的 XML 表示形式生成该对象 55 ///</summary> 56 ///<param name="reader"></param> 57         public void ReadXml(System.Xml.XmlReader reader) 58         { 59             XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); 60             XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); 61             bool wasEmpty = reader.IsEmptyElement; 62             reader.Read(); 63             if (wasEmpty) 64                 return; 65             while (reader.NodeType != System.Xml.XmlNodeType.EndElement) 66             { 67                 reader.ReadStartElement("item"); 68                 reader.ReadStartElement("key"); 69                 TKey key = (TKey)keySerializer.Deserialize(reader); 70                 reader.ReadEndElement(); 71                 reader.ReadStartElement("value"); 72                 TValue value = (TValue)valueSerializer.Deserialize(reader); 73                 reader.ReadEndElement(); 74                 this.Add(key, value); 75                 reader.ReadEndElement(); 76                 reader.MoveToContent(); 77             } 78             reader.ReadEndElement(); 79         } 80  81         /**/ 82         ///<summary> 83 /// 将对象转换为其 XML 表示形式 84 ///</summary> 85 ///<param name="writer"></param> 86         public void WriteXml(System.Xml.XmlWriter writer) 87         { 88             XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); 89             XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); 90             foreach (TKey key in this.Keys) 91             { 92                 writer.WriteStartElement("item"); 93                 writer.WriteStartElement("key"); 94                 keySerializer.Serialize(writer, key); 95                 writer.WriteEndElement(); 96                 writer.WriteStartElement("value"); 97                 TValue value = this[key]; 98                 valueSerializer.Serialize(writer, value); 99                 writer.WriteEndElement();100                 writer.WriteEndElement();101             }102         }103 104     }105 }

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

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

IAddress.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7  8 namespace HeartBeatService 9 {10     //CallbackContract:这个就是Client实现此接口,方便服务器端通知客户端11     [ServiceContract(CallbackContract = typeof(ILiveAddressCallback))]12     public interface IAddress13     {14         ///<summary>15 /// 此方法用于Search启动后,将Search地址插入到此处16 ///</summary>17 ///<param name="address"></param>18         [OperationContract(IsOneWay = true)]19         void AddSearch(string address);20 21         ///<summary>22 /// 此方法用于IIS端获取search地址23 ///</summary>24 ///<param name="address"></param>25         [OperationContract(IsOneWay = true)]26         void GetService(string address);27     }28 }

Address.cs

  1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Runtime.Serialization;  5 using System.ServiceModel;  6 using System.Text;  7 using System.Timers;  8 using System.IO;  9 using System.Collections.Concurrent; 10 using SearhService; 11 using ClientService; 12  13 namespace HeartBeatService 14 { 15     //InstanceContextMode:主要是管理上下文的实例,此处是single,也就是单体 16 //ConcurrencyMode:    主要是用来控制实例中的线程数,此处是Multiple,也就是多线程 17     [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)] 18     public class Address : IAddress 19     { 20         static List<string> search = new List<string>(); 21  22         static object obj = new object(); 23  24         ///<summary> 25 /// 此静态构造函数用来检测存活的Search个数 26 ///</summary> 27         static Address() 28         { 29             Timer timer = new Timer(); 30             timer.Interval = 6000; 31             timer.Elapsed += (sender, e) => 32             { 33  34                 Console.WriteLine("\n***************************************************************************"); 35                 Console.WriteLine("当前存活的Search为:"); 36  37                 lock (obj) 38                 { 39                     //遍历当前存活的Search 40                     foreach (var single in search) 41                     { 42                         ChannelFactory<IProduct> factory = null; 43  44                         try 45                         { 46                             //当Search存在的话,心跳服务就要定时检测Search是否死掉,也就是定时的连接Search来检测。 47                             factory = new ChannelFactory<IProduct>(new NetTcpBinding(SecurityMode.None), new EndpointAddress(single)); 48                             factory.CreateChannel().TestSearch(); 49                             factory.Close(); 50  51                             Console.WriteLine(single); 52  53                         } 54                         catch (Exception ex) 55                         { 56                             Console.WriteLine(ex.Message); 57  58                             //如果抛出异常,则说明此search已经挂掉 59                             search.Remove(single); 60                             factory.Abort(); 61                             Console.WriteLine("\n当前时间:" + DateTime.Now + " ,存活的Search有:" + search.Count() + "个"); 62                         } 63                     } 64                 } 65  66                 //最后统计下存活的search有多少个 67                 Console.WriteLine("\n当前时间:" + DateTime.Now + " ,存活的Search有:" + search.Count() + "个"); 68             }; 69             timer.Start(); 70         } 71  72         public void AddSearch(string address) 73         { 74  75             lock (obj) 76             { 77                 //是否包含相同的Search地址 78                 if (!search.Contains(address)) 79                 { 80                     search.Add(address); 81  82                     //search添加成功后就要告诉来源处,此search已经被成功载入。 83                     var client = OperationContext.Current.GetCallbackChannel<ILiveAddressCallback>(); 84                     client.LiveAddress(address); 85                 } 86             } 87         } 88  89         public void GetService(string address) 90         { 91             Timer timer = new Timer(); 92             timer.Interval = 1000; 93             timer.Elapsed += (obj, sender) => 94             { 95                 try 96                 { 97                     //这个是定时的检测IIS是否挂掉 98                     var factory = new ChannelFactory<IServiceList>(new NetTcpBinding(SecurityMode.None), 99                                                                    new EndpointAddress(address));100 101                     factory.CreateChannel().AddSearchList(search);102 103                     factory.Close();104 105                     timer.Interval = 10000;106                 }107                 catch (Exception ex)108                 {109                     Console.WriteLine(ex.Message);110                 }111             };112             timer.Start();113         }114     }115 }

ILiveAddressCallback.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6  7 namespace HeartBeatService 8 { 9     ///<summary>10 /// 等客户端实现后,让客户端约束一下,只能是这个LiveAddress方法11 ///</summary>12     public interface ILiveAddressCallback13     {14         [OperationContract(IsOneWay = true)]15         void LiveAddress(string address);16     }17 }

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

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

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

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

IProduct.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7  8 namespace SearhService 9 {10     // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。11     [ServiceContract]12     public interface IProduct13     {14         [OperationContract]15         List<int> GetShopListByUserID(int userID);16 17         [OperationContract]18         void TestSearch();19     }20 }

Product.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7 using Common; 8 using System.Xml; 9 using System.IO;10 using System.Xml.Serialization;11 12 namespace SearhService13 {14     public class Product : IProduct15     {16         public List<int> GetShopListByUserID(int userID)17         {18             //模拟从MemCache中读取索引19             SerializableDictionary<int, List<int>> dic = new SerializableDictionary<int, List<int>>();20 21             byte[] bytes = Encoding.UTF8.GetBytes(File.ReadAllText("F://1.txt", Encoding.UTF8));22 23             var memoryStream = new MemoryStream();24 25             memoryStream.Write(bytes, 0, bytes.Count());26 27             memoryStream.Seek(0, SeekOrigin.Begin);28 29             XmlSerializer xml = new XmlSerializer(dic.GetType());30 31             var obj = xml.Deserialize(memoryStream) as Dictionary<int, List<int>>;32 33             return obj[userID];34         }35 36         public void TestSearch() { }37     }38 }

SearchHost.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.Configuration; 7 using System.Timers; 8 using SearhService.HeartBeatService; 9 10 namespace SearhService11 {12     public class SearchHost : IAddressCallback13     {14         static DateTime startTime;15 16         public static void Main()17         {18             ServiceHost host = new ServiceHost(typeof(Product));19 20             host.Open();21 22             AddSearch();23 24             Console.Read();25 26         }27 28         static void AddSearch()29         {30             startTime = DateTime.Now;31 32             Console.WriteLine("Search服务发送中.....\n\n*************************************************\n");33 34             try35             {36                 var heartClient = new AddressClient(new InstanceContext(new SearchHost()));37 38                 string search = ConfigurationManager.AppSettings["search"];39 40                 heartClient.AddSearch(search);41             }42             catch (Exception ex)43             {44                 Console.WriteLine("Search服务发送失败:" + ex.Message);45             }46         }47 48         public void LiveAddress(string address)49         {50             Console.WriteLine("恭喜你," + address + "已被心跳成功接收!\n");51             Console.WriteLine("发送时间:" + startTime + "\n接收时间:" + DateTime.Now);52         }53     }54 }

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

Search开启时:

Search关闭时:

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

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

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

IServiceList.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6  7 namespace ClientService 8 { 9     [ServiceContract]10     public interface IServiceList11     {12         [OperationContract]13         void AddSearchList(List<string> search);14     }15 }

ServiceList.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.Configuration; 7 using System.Timers; 8 using System.Threading; 9 10 namespace ClientService11 {12     public class ServiceList : IServiceList13     {14         public static List<string> searchList = new List<string>();15 16         static object obj = new object();17 18         public static string Search19         {20             get21             {22                 lock (obj)23                 {24                     //如果心跳没及时返回地址,客户端就在等候25                     if (searchList.Count == 0)26                         Thread.Sleep(1000);27                     return searchList[new Random().Next(0, searchList.Count)];28                 }29             }30             set31             {32 33             }34         }35 36         public void AddSearchList(List<string> search)37         {38             lock (obj)39             {40                 searchList = search;41 42                 Console.WriteLine("************************************");43                 Console.WriteLine("当前存活的Search为:");44 45                 foreach (var single in searchList)46                 {47                     Console.WriteLine(single);48                 }49             }50         }51     }52 }

Program.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.Configuration; 7 using System.Threading; 8 using ClientService.HeartBeatService; 9 using SearhService;10 using BaseClass;11 using System.Data;12 using System.Diagnostics;13 14 namespace ClientService15 {16     class Program : IAddressCallback17     {18         static void Main(string[] args)19         {20 21             ServiceHost host = new ServiceHost(typeof(ServiceList));22 23             host.Open();24 25             var client = new AddressClient(new InstanceContext(new Program()));26 27             //配置文件中获取iis的地址28             var iis = ConfigurationManager.AppSettings["iis"];29 30             //将iis的地址告诉心跳31             client.GetService(iis);32 33             //从集群中获取search地址来对Search服务进行调用34             var factory = new ChannelFactory<IProduct>(new NetTcpBinding(SecurityMode.None), new EndpointAddress(ServiceList.Search));35 36             //根据userid获取了shopID的集合37             var shopIDList = factory.CreateChannel().GetShopListByUserID(15);38 39             //.......................... 后续就是我们将shopIDList做连接数据库查询(做到秒杀)40 41             Console.Read();42         }43 44         public void LiveAddress(string address)45         {46 47         }48     }49 }

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

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

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

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

ClientService改造后的代码:

Program.cs

 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.Timers; 7 using System.Diagnostics; 8 using BaseClass; 9 using ClientService;10 using ClientService.HeartBeatService;11 using System.Configuration;12 using SearhService;13 14 namespace ClientService15 {16     class Program : IAddressCallback17     {18         static void Main(string[] args)19         {20 21             ServiceHost host = new ServiceHost(typeof(ServiceList));22 23             host.Open();24 25             var client = new AddressClient(new InstanceContext(new Program()));26 27             //配置文件中获取iis的地址28             var iis = ConfigurationManager.AppSettings["iis"];29 30             //将iis的地址告诉心跳31             client.GetService(iis);32 33             //从集群中获取search地址来对Search服务进行调用34             var factory = new ChannelFactory<IProduct>(new NetTcpBinding(SecurityMode.None), new EndpointAddress(ServiceList.Search));35 36             //根据userid获取了shopID的集合37 //比如说这里的ShopIDList是通过索引交并集获取的分页的一些shopID38             var shopIDList = factory.CreateChannel().GetShopListByUserID(15);39 40             var strSql = string.Join(",", shopIDList);41 42             Stopwatch watch = new Stopwatch();43 44             watch.Start();45             SqlHelper.Query("select s.ShopID,u.UserName,s.ShopName  from [User] as u ,Shop as s where s.ShopID in(" + strSql + ")");46             watch.Stop();47 48             Console.WriteLine("通过wcf索引获取的ID >>>花费时间:" + watch.ElapsedMilliseconds);49 50             //普通的sql查询花费的时间51             StringBuilder builder = new StringBuilder();52 53             builder.Append("select * from ");54             builder.Append("(select  ROW_NUMBER() over(order by s.ShopID) as NumberID, ");55             builder.Append(" s.ShopID, u.UserName, s.ShopName ");56             builder.Append("from Shop s left join [User] as u on u.UserID=s.UserID ");57             builder.Append("where  s.UserID=15) as array ");58             builder.Append("where NumberID>300000 and NumberID<300050");59 60             watch.Start();61             SqlHelper.Query(builder.ToString());62             watch.Stop();63 64             Console.WriteLine("普通的sql分页 >>>花费时间:" + watch.ElapsedMilliseconds);65 66             Console.Read();67         }68 69         public void LiveAddress(string address)70         {71 72         }73     }74 }

性能图:

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

转载于:https://www.cnblogs.com/zpc870921/archive/2012/08/17/2643960.html

wcf系列学习5天速成——第四天 wcf之分布式架构(转载)相关推荐

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

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

  2. wcf系列5天速成——第二天 binding的使用(2)(转载)

    承接上一章,今天来讲MSMQ在实战项目中的应用.众所周知,放了防止订单丢失,我们都是采用Order过一下MSMQ. MSMQ的优点个人认为是:先天的异步消息发送和天生的自动负载均衡. 好了,看看MSM ...

  3. 走向.NET架构设计—第四章—业务层分层架构(中篇)

    走向.NET架构设计-第四章-业务层分层架构(中篇) 前言: 在上一篇文章中,我们讨论了两种组织业务逻辑的模式:Transaction Script和Active Record.在本篇中开始讲述Dom ...

  4. WCF技术剖析之二十: 服务在WCF体系中是如何被描述的?

    任何一个程序都需要运行于一个确定的进程中,进程是一个容器,其中包含程序实例运行所需的资源.同理,一个WCF服务的监听与执行同样需要通过一个进程来承载.我们将为WCF服务创建或指定一个进程的方式称为服务 ...

  5. [原创]WCF技术剖析之二十: 服务在WCF体系中是如何被描述的?

    任何一个程序都需要运行于一个确定的进程中,进程是一个容器,其中包含程序实例运行所需的资源.同理,一个WCF服务的监听与执行同样需要通过一个进程来承载.我们将为WCF服务创建或指定一个进程的方式称为服务 ...

  6. 一文了解四种软件架构:Serverless架构、微服务架构、分布式架构、单体架构

    如果一个软件开发人员,不了解软件架构的演进,会制约技术的选型和开发人员的生存.晋升空间.这里我列举了目前主要的四种软件架构以及他们的优缺点,希望能够帮助软件开发人员拓展知识面. 一.单体架构 单体架构 ...

  7. 《WCF技术内幕》翻译1:《WCF技术内幕》目录和作者简介

    翻译序言: 我现在推荐一本很好的WCF学习书籍:<Inside Microsoft Windows Communication Foundation>.Justin Smith先生所著.2 ...

  8. Java与WCF交互(一):Java客户端调用WCF服务

    最近开始了解WCF,写了个最简单的Helloworld,想通过java客户端实现通信.没想到以我的基础,居然花了整整两天(当然是工作以外的时间,呵呵),整个过程大费周折,特写下此文,以供有需要的朋友参 ...

  9. 四种主流的 API 架构风格对比

    来源 | InfoQ 本文讨论了四种主要的 API 架构风格,比较它们的优缺点,并重点介绍每种情况下最适合的 API 架构风格. 两个单独的应用程序需要中介程序才能相互通信.因此,开发人员经常需要搭建 ...

最新文章

  1. eeglab教程系列(5)-预处理工具
  2. 火车头下载文件并发布到木翼下载系统
  3. 13.3的MacBook air 8g内存还是有点吃力
  4. 第二阶段冲刺 站立会议03
  5. 只做macd二次金叉_【教你一招】MACD低位二次金叉
  6. git rollback代码都没了_git回滚线上代码
  7. Java加密与解密的艺术~数字证书详解
  8. 复杂的c语言,C语言:复杂数据类型
  9. 【华为大咖分享】12.从Change、merge到New Challenge--华为内源平台到研发云平台的发展历程(后附PPT下载地址)
  10. 百度地图 截图java_[Java教程]百度地图API 简单使用
  11. gevent开始学习-第一步
  12. mysql查看和调整最大连接数
  13. java的set和get方法实例化_java反射机制 调用get set 方法 | 学步园
  14. 计算机上的mrc功能,计算器上的MRC有什么功能?
  15. 【声源定位】基于matlab广义互相关声源定位【含Matlab源码 548期】
  16. c++ 调用meshlab程序慢_MeshLab中插件的添加过程
  17. python基础教程:face++与python实现人脸识别签到(考勤)功能
  18. 测绘——如何在win10环境下安装CAD2006+CASS7.0
  19. 软件开发常见英文单词
  20. 以周一为每周的第一天,计算周数

热门文章

  1. 分享EOS加拿大的文章《REX——从源代码做技术解析》
  2. 大学毕业没有实习经历_我是如何在大学毕业后没有实习的情况下获得第一份开发人员工作的...
  3. java编写一个通讯录_java写的通讯录(小玩意)
  4. linux用户在哪个文件夹,LINUX中用命令成功建立一个用户后信息会记录在哪个文件中...
  5. java登陆界面连接数据库_java 登陆界面怎么写,连接数据库后
  6. python中执行linux命令(调用linux命令)_Python调用Linux bash命令
  7. java整数常量区_在Java中,我可以用二进制格式定义一个整数常量吗?
  8. UI设计培训分享:ui的字体怎么正确设置?
  9. 新手参加java培训都学什么
  10. mysql innodb_data_file_path_关于innodb_data_file_path设置