1 实现

CustomDbContext扩展了DbContext,其构造函数带有形式参nameOrConnectionString,可以在使用CustomDbContext时指定数据库连接字符串。

DbContextFactory包含两个属性MasterDbContext和SlaveDbContext,MasterDbContext为主库上下文,SlaveDbContext为从库上下文。DbContextFactory还包含了一个方法:UpdateSlaves用于实现对SlaveDbContext的更新,因为SlaveDbContext是从多个配置的从库随机取出一个,因此定时检测不可用从库,将其从从库集合中剔除。

JobScheduler为定时任务规划器,使用Quartz实现。quartz.config和quartz_jobs.xml为定时任务配置文件。

为了使定时任务工作,在WebApiApplication类的Application_Start()函数应添加:

  1. JobScheduler jobScheduler = new JobScheduler();
  2. jobScheduler.log4netPath = AppSettings.Log4netPathForWeb;
  3. jobScheduler.OnStart();

关键代码

  1. /// <summary>
  2. /// 自定义上下文
  3. /// </summary>
  4. [DbConfigurationType(typeof(MySqlEFConfiguration))]
  5. public class CustomDbContext:DbContext
  6. {
  7. public CustomDbContext(string nameOrConnectionString)
  8. : base(nameOrConnectionString)
  9. {
  10. Database.SetInitializer<CustomDbContext>(null);
  11. }
  12.  
  13. ......
  14.  
  15. public DbSet<Collection> Collections { get; set; }
  16. public DbSet<CollectionUser> CollectionUsers { get; set; }
  17.  
  18. ......
  19.  
  20. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  21. {
  22. base.OnModelCreating(modelBuilder);
  23. EntityConfiguration.Set(modelBuilder);
  24. }
  25. }
  26.  
  27. public class EntityConfiguration
  28. {
  29. public static void Set(DbModelBuilder modelBuilder)
  30. {
  31. modelBuilder.Entity<Collection>().Property(c => c.FileName)
  32. .IsUnicode(false)
  33. .IsRequired()
  34. .HasMaxLength();
  35. modelBuilder.Entity<Collection>().Property(c => c.TableName)
  36. .IsUnicode(false)
  37. .IsRequired()
  38. .HasMaxLength();
  39. modelBuilder.Entity<Collection>().Property(c => c.Title)
  40. .IsUnicode(false)
  41. .IsRequired()
  42. .HasMaxLength();
  43. modelBuilder.Entity<Collection>().Property(c => c.Author)
  44. .IsUnicode(false)
  45. .IsOptional()
  46. .HasMaxLength();
  47. modelBuilder.Entity<Collection>().Property(c => c.PublicationName)
  48. .IsUnicode(false)
  49. .IsOptional()
  50. .HasMaxLength();
  51. modelBuilder.Entity<Collection>().Property(c => c.DiscNo)
  52. .IsUnicode(false)
  53. .IsOptional()
  54. .HasMaxLength();
  55. modelBuilder.Entity<Collection>().Property(c => c.ResourceType)
  56. .IsUnicode(false)
  57. .IsOptional()
  58. .HasMaxLength();
  59. modelBuilder.Entity<Collection>().Property(c => c.PublisherUnit)
  60. .IsUnicode(false)
  61. .IsOptional()
  62. .HasMaxLength();
  63. modelBuilder.Entity<Collection>().Property(c => c.Year)
  64. .IsUnicode(false)
  65. .IsOptional()
  66. .HasMaxLength();
  67. modelBuilder.Entity<Collection>().Property(c => c.Period)
  68. .IsUnicode(false)
  69. .IsOptional()
  70. .HasMaxLength();
  71.  
  72. modelBuilder.Entity<Collection>().Property(c => c.PublicationDate)
  73. .IsOptional();
  74. modelBuilder.Entity<Collection>().Property(c => c.Downloads)
  75. .IsOptional();
  76. modelBuilder.Entity<Collection>().Property(c => c.CitationNumber)
  77. .IsOptional();
  78. }
  79. }
  80.  
  81. /// <summary>
  82. /// db上下文工厂
  83. /// </summary>
  84. public class DbContextFactory
  85. {
  86. private static List<string> allSlaves = GetAllSlaves();
  87. private DbContextFactory() { }
  88. /// <summary>
  89. /// 主
  90. /// </summary>
  91. public static CustomDbContext MasterDbContext
  92. {
  93. get
  94. {
  95. return new CustomDbContext("name=Master");
  96. }
  97. }
  98.  
  99. /// <summary>
  100. /// 从
  101. /// </summary>
  102. public static CustomDbContext SlaveDbContext
  103. {
  104. get
  105. {
  106. Random rm = new Random();
  107. if (allSlaves.Count > )
  108. {
  109. int i = rm.Next(allSlaves.Count);
  110. string name = string.Format("name={0}", allSlaves.ElementAt(i));
  111. return new CustomDbContext(name);
  112. }
  113. else
  114. {
  115. return MasterDbContext;
  116. }
  117. }
  118. }
  119. /// <summary>
  120. /// 获得所有可用连接
  121. /// </summary>
  122. /// <returns></returns>
  123. private static List<string> GetAllSlaves()
  124. {
  125. List<string> connNames = new List<string>();
  126. var conns = ConfigurationManager.ConnectionStrings;
  127. if (conns == null)
  128. {
  129. throw new Exception("ConfigurationManager.ConnectionStrings 是空值,请检查Web.config");
  130. }
  131. var masterConn = conns["Master"];
  132. if (masterConn == null)
  133. {
  134. throw new Exception("名称为Master的连接配置不存在,请检查Web.config");
  135. }
  136.  
  137. //conn中必然包含master,还有一个默认的LocalSqlServer
  138. int connCount = conns.Count - ;
  139. if (connCount == )
  140. {
  141. throw new Exception("连接配置中只包含Master,不包含任何Slave,请检查Web.config,并配置Slave");
  142. }
  143.  
  144. for (int i = ; i < connCount; i++)
  145. {
  146. string connName = string.Format("Slave{0}", i);
  147. var conn = ConfigurationManager.ConnectionStrings[connName];
  148. if (conn == null)
  149. {
  150. string msg = string.Format("{0}不存在,请检查配置Web.config", connName);
  151. throw new Exception(msg);
  152. }
  153. //检测是否可连接
  154. bool canConn = CanConnect(connName);
  155. if (canConn)
  156. {
  157. connNames.Add(connName);
  158. }
  159. }
  160. return connNames;
  161. }
  162. public static void UpdateSlaves()
  163. {
  164. allSlaves = GetAllSlaves();
  165. if (allSlaves.Count == )
  166. {
  167. allSlaves.Add("Master");
  168. }
  169. }
  170.  
  171. private static bool CanConnect(string connName)
  172. {
  173. bool ret = false;
  174. DbConnection dbConnection = null;
  175. try
  176. {
  177. string connStr = ConfigurationManager.ConnectionStrings[connName].ToString();
  178. MySqlConnectionFactory factory = new MySqlConnectionFactory();
  179. dbConnection = factory.CreateConnection(connStr);
  180. dbConnection.Open();//打不开会抛异常
  181. ret = true;
  182. }
  183. catch (Exception ex)
  184. {
  185. }
  186. finally
  187. {
  188. if (dbConnection != null && dbConnection.State == System.Data.ConnectionState.Open) dbConnection.Close();
  189. }
  190. return ret;
  191. }
  192. }
  193.  
  194. public class JobScheduler
  195. {
  196. /// <summary>
  197. /// log4net配置文件位置
  198. /// </summary>
  199. public string log4netPath { get; set; }
  200. private IScheduler scheduler;
  201. public JobScheduler() { }
  202.  
  203. public void OnStart()
  204. {
  205. //构造函数自动加载Quartz.config,并通过quartz.plugin.xml.fileNames加载~/quartz_jobs.xml
  206. try
  207. {
  208.  
  209. if (string.IsNullOrWhiteSpace(log4netPath))
  210. {
  211. log4netPath = AppSettings.Log4netPathForApp;
  212. }
  213. //加载日志
  214. LogConfigLoading.Load(log4netPath);
  215.  
  216. ISchedulerFactory sf = new StdSchedulerFactory();
  217. scheduler = sf.GetScheduler();
  218. scheduler.Start();
  219. }
  220. catch (Exception ex)
  221. {
  222. LogHelper.LogError(ex, "JobScheduler");
  223. }
  224. }
  225.  
  226. public void OnStop()
  227. {
  228. if (scheduler != null && scheduler.IsStarted)
  229. {
  230. scheduler.Shutdown(false);
  231. }
  232. }
  233.  
  234. public void OnPause()
  235. {
  236. if (scheduler != null && scheduler.IsStarted)
  237. {
  238. scheduler.PauseAll();
  239. }
  240. }
  241.  
  242. public void OnContinue()
  243. {
  244. if (scheduler != null)
  245. {
  246. bool isJobGroupPaused = false;
  247. var groups = scheduler.GetJobGroupNames();
  248. foreach (var group in groups)
  249. {
  250. isJobGroupPaused = scheduler.IsJobGroupPaused(group);
  251. if (!isJobGroupPaused)
  252. {
  253. break;
  254. }
  255. }
  256. if (isJobGroupPaused)
  257. {
  258. scheduler.ResumeAll();
  259. }
  260. }
  261. }
  262. }
  263.  
  264. public class DbMonitorJob : IJob
  265. {
  266.  
  267. public void Execute(IJobExecutionContext context)
  268. {
  269. DbContextFactory.UpdateSlaves();
  270. }
  271. }

定时器配置文件quartz_jobs.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- This file contains job definitions in schema version 2.0 format -->
  3. <job-scheduling-data xmlns= "http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance " version ="2.0 ">
  4. <processing-directives>
  5. <overwrite-existing-data>true</overwrite-existing-data>
  6. </processing-directives>
  7. <schedule>
  8. <job>
  9. <name>DbMonitorJob</name>
  10. <group>DbMonitorJobGroup</group>
  11. <description>更新可用从库</description>
  12. <job-type>HY_WebApi.TaskScheduler.Jobs.DbMonitorJob, HY_WebApi.TaskScheduler</job-type>
  13. <durable>true</durable>
  14. <recover>false</recover>
  15. </job>
  16. <trigger>
  17. <simple>
  18. <name>DbMonitorJobTrigger</name>
  19. <group>DbMonitorJobTriggerGroup</group>
  20. <description>更新可用从库</description>
  21. <job-name>DbMonitorJob</job-name>
  22. <job-group>DbMonitorJobGroup</job-group>
  23. <misfire-instruction>SmartPolicy</misfire-instruction>
  24. <repeat-count>-1</repeat-count>
  25. <repeat-interval>10000</repeat-interval>
  26. </simple>
  27. </trigger>
  28. </schedule>
  29. </job-scheduling-data>

定时任务配置文件quartz.config

  1. # You can configure your scheduler in either <quartz> configuration section
  2. # or in quartz properties file
  3. # Configuration section has precedence
  4.  
  5. quartz.scheduler.instanceName = ServerScheduler
  6.  
  7. # configure thread pool info
  8. quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
  9. quartz.threadPool.threadCount = 1
  10. quartz.threadPool.threadPriority = Normal
  11.  
  12. # job initialization plugin handles our xml reading, without it defaults are used
  13. quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz
  14. quartz.plugin.xml.fileNames = ~\quartz_jobs.xml
  15.  
  16. # export this server to remoting context
  17. quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
  18. quartz.scheduler.exporter.port = 555
  19. quartz.scheduler.exporter.bindName = QuartzScheduler
  20. quartz.scheduler.exporter.channelType = tcp
  21. quartz.scheduler.exporter.channelName = httpQuartz

web项目配置文件Web.config

  1. <configuration>
  2. <connectionStrings>
  3. <clear/><!--清除默认的连接字符串,务必加上!!!-->
  4. <add name="Master" connectionString="Database=hy_webapi_n;Data Source=192.168.107.65;User Id=root;Password=cnki2016;CharSet=utf8;port=3306" providerName="MySql.Data.MySqlClient" />
  5. <add name="Slave0" connectionString="Database=hy_webapi_n;Data Source=192.168.107.62;User Id=root;Password=cnki2016;CharSet=utf8;port=3306" providerName="MySql.Data.MySqlClient" />
  6. <add name="Slave1" connectionString="Database=hy_webapi_n;Data Source=192.168.107.63;User Id=root;Password=cnki2016;CharSet=utf8;port=3306" providerName="MySql.Data.MySqlClient" />
  7. </connectionStrings>
  8.  
  9. ......
  10. </configuration>

加载定时器

  1. public class WebApiApplication : System.Web.HttpApplication
  2. {
  3. protected void Application_Start()
  4. {
  5. GlobalConfiguration.Configure(WebApiConfig.Register);
  6. JobScheduler jobScheduler = new JobScheduler();
  7. jobScheduler.log4netPath = AppSettings.Log4netPathForWeb;
  8. jobScheduler.OnStart();
  9. }
  10. }

2 代码分析

最核心的部分是DbContextFactory。下面详细分析其设计与实现。

获得web.config配置文件中的连接名称

使用静态私钥变量allSlaves来表示从库集合,这样做的好处是:静态私有变量只在使用前初始化一次,当第一次被allSlaves使用时初始化一次,即调用GetAllSlaves()方法获得所有可用的从库。当第二次使用allSlaves时,即当SlaveDbContext属性第二次被调用时,不在计算allSlaves。大部分时间都花费在测试数据库是否可用,因此不在重复计算allSlaves节省了时间。直接的效果就是由于检测数据库是否可用的影响可以忽略不计。

不可使用单例模式

由于检测数据库是否可用相对耗费时间的比例较大,于是想到通过单例模式来实现DbContextFactory,这样会导致系统报错:The operation cannot be completed because the DbContext has been disposed.其原因就在于使用DbContext时,慎重使用单例模式,全局的DbContext会引起第二次调用出错,即第一次调用后DbContext资源即被释放。

类似于单例模式的实现,即全局的DbContext,也是不可取的。

基于上述考虑设计实现SlaveDbContext,在每次被调用时,都会返回一个新的实例。

多从库随机选择

当配置了多个从库时,应随机从从库集合中选择一个。于是使用伪随机数生成器Random。

所有从库不可用时切换到主库

当所有从库都不可用时,SlaveDbContext值为MasterDbContext。这里还应该增加一个额外的监测服务,当有从库不可用时自动报警,供系统维护人员查看。

注意先写后读的操作

对于这种操作,若主从同步延迟稍大,那么会造成操作失败,解决的办法是:只操作主库。保守的做法就是只操作主库,一般主从分部在内网的两台机器上,网络通信延迟一旦较大时,就会造成数据无法同步的假象。

-----------------------------------------------------------------------------------------

转载与引用请注明出处。

时间仓促,水平有限,如有不当之处,欢迎指正。

Entity Framework——读写分离的更多相关文章

  1. 读写分离子系统 - C# SQL分发子系统 - Entity Framework支持

    A2D Framework增加了EF支持,加上原先支持ADO.NET: 支持EF方式 支持ADO.NET方式 这次来讲如何让Entity Framework变成nb的读写分离 1. 先设计EF模型, ...

  2. Entity Framework 6 Recipes 2nd Edition(9-1)译->用Web Api更新单独分离的实体

    第九章 在N层结构的应用程序中使用EF 不是所有的应用都能完全地写入到一个单个的过程中(就是驻留在一个单一的物理层中),实际上,在当今不断发展的网络世界,大量的应用程序的结构包含经典的表现层,应用程, ...

  3. Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合

    在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但 ...

  4. Programming Entity Framework 翻译(1)-目录

    1. Introducing the ADO.NET Entity Framework ado.net entity framework 介绍 1 The Entity Relationship Mo ...

  5. net Core 使用MyCat分布式数据库,实现读写分离

    net Core 使用MyCat分布式数据库,实现读写分离 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 MyCat2.0版本很快就发布了,关于MyCat的动态和一些问题, ...

  6. EF通用数据层封装类(支持读写分离,一主多从)

    浅谈orm 记得四年前在学校第一次接触到 Ling to Sql,那时候瞬间发现不用手写sql语句是多么的方便,后面慢慢的接触了许多orm框架,像 EF,Dapper,Hibernate,Servic ...

  7. Entity Framework 项目使用心得

    在博客园很久了,一直只看不说,这是发布本人的第一个博客. 总结一下在项目中,EntityFramework使用的一下经验拿来和大家分享,希望对大家有用~ 1.         在Entity Fram ...

  8. Entity Framework Core 1.1 升级通告

    原文地址:https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-entity-framework-core-1-1/ 翻译:杨晓东 ...

  9. Entity Framework 6 Recipes 2nd Edition 译 -> 目录 -持续更新

    因为看了<Entity Framework 6 Recipes 2nd Edition>这本书前面8章的翻译,感谢china_fucan. 从第九章开始,我是边看边译的,没有通读,加之英语 ...

随机推荐

  1. golang中的make与new

    golang 中有两个内存分配机制 :new和make,二者有明显区别. new:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值.其自身是一个指针.可用于初始化任何类 ...

  2. golang学习之regexp

    regexp是golang标准库自带的正则校验包,使用: re, _ := regexp.Compile(`(\d+)年(\d+)月`) //判断是否匹配category类别搜索 ismatch := ...

  3. django-admin管理后台高级自定义

    django自带的admin后台管理系统,在很多网站中被称为django的杀手级的应用.那么django-admin的适用情形倒底有哪些呢,一般 来说对于大型的商业性的项目通常不用采用django-a ...

  4. JavaWeb之JSP原理

    1.为什么需要JSP? 在很多动态网页中,绝大部分内容都是固定不变的,只有局部内容需要动态产生和改变.如果使用Servlet程序来输出只有局部内容需要改动的网页,其中所有的静态内容也需要程序员用jav ...

  5. Tomcat服务器配置https认证(使用keytool生成证书)

    一.证书生成 1.生成服务器证书 (1)打开打开命令控制台,进入jdk的bin目录 cd D:\Program Files\jdk1.6.0_45\bin (2)keytool为Tomcat生成证书( ...

  6. thinkphp引入头文件

    <include File="Public:regheader" />

  7. Django 模型层之多表操作

    一.创建模型 实例: 作者表: 拥有字段:姓名(name),性别(sex),该表与书籍表之间为多对多的关系 作者详情表: 拥有字段:地址(addr),手机号(phone),该表与作者表之间为一对一的关 ...

  8. 软件项目技术点(9)——如何将gif动态图拆分绘制

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 背景介绍 我们的软件支持插入gif图片,并且展示在软件里是动态的,例如插入下面这张gif图. 在软件里显示的同样是这样的动态效果: 那 ...

  9. 解决方案看起来是受源代码管理,但无法找到它的绑定信息。保存解决方案的源代码管理设置的MSSCCPRJ.SCC文件或其他项可能己被删除。

    Visual Studio 2015 + SVN 开发环境,今天打开项目,就报了下面这个错误,先前是好好的! 解决方案看起来是受源代码管理,但无法找到它的绑定信息.保存解决方案的源代码管理设置的MSS ...

  10. Distributed TensorFlow

    Distributed TensorFlow Todo list: Distributed TensorFlow简介 Distributed TensorFlow的部署与运行 对3个台主机做多卡GPU ...