当前位置:   article > 正文

.Net高并发解决思路【转载】_net高并发处理方案

net高并发处理方案

文本为转载文章

首先在windows上安装好Redis,RabbitMQ

这里写图片描述


Redis-cli使用示例

这里写图片描述

这里写图片描述


ModelContext.cs代码:

  1. public class ModelContext : DbContext
  2. {
  3. //您的上下文已配置为从您的应用程序的配置文件(App.config 或 Web.config)
  4. //连接字符串。
  5. public ModelContext()
  6. : base("name=default")
  7. {
  8. }
  9. public virtual DbSet<Person> Person { get; set; }
  10. }
  11. public class Person
  12. {
  13. public int Id { get; set; }
  14. public string Id2 { get; set; }
  15. public string Name { get; set; }
  16. }

在 Package Manager Console 下运行命令 Enable-Migrations
 这个命令将在项目下创建文件夹 Migrations

The Configuration class 这个类允许你去配置如何迁移,对于本文将使用默认的配置(在本文中因为只有一个 Context, Enable-Migrations 将自动对 context type 作出适配);
An InitialCreate migration (本文为201702220232375_20170222.cs)这个迁移之所以存在是因为我们之前用 Code First 创建了数据库, 在启用迁移前,scaffolded migration 里的代码表示在数据库中已经创建的对象,本文中即为表 Person(列 Id 和 Name). 文件名包含一个 timestamp 以便排序(如果之前数据库没有被创建,那么 InitialCreate migration 将不会被创建,相反,当我们第一次调用 Add-Migration 的时候所有表都将归集到一个新的 migration 中)

多个实体锁定同一数据库

当使用 EF6 之前的版本时,只会有一个 Code First Model 被用来生成/管理数据库的 Schema, 这将导致每个数据库只会有一张 __MigrationsHistory 表,从而无法辨别实体与模型的对应关系。

从 EF6 开始,Configuration 类将会包含一个 ContextKey 属性,它将作为每一个 Code First Model 的唯一标识符, __MigrationsHistory 表中一个相应地的列允许来自多个模型(multiple models)的实体共享表(entries),默认情况下这个属性被设置成 context 的完全限定名。

定制化迁移

在 Package Manager Console 中运行命令 Add-Migration XXXXXXXXX
生成的迁移如下

  1. public partial class _20170222 : DbMigration
  2. {
  3. public override void Up()
  4. {
  5. CreateTable(
  6. "dbo.People",
  7. c => new
  8. {
  9. Id = c.Int(nullable: false, identity: true),
  10. Id2 = c.String(),
  11. Name = c.String(),
  12. })
  13. .PrimaryKey(t => t.Id);
  14. }
  15. public override void Down()
  16. {
  17. DropTable("dbo.People");
  18. }
  19. }

Configuration.cs代码:

  1. internal sealed class Configuration : DbMigrationsConfiguration<EF.ModelContext>
  2. {
  3. public Configuration()
  4. {
  5. AutomaticMigrationsEnabled = false;
  6. }
  7. protected override void Seed(EF.ModelContext context)
  8. {
  9. }
  10. }

我们对迁移做些更改:

以下是本项目无关的其他示例:

  1. namespace MigrationsDemo.Migrations
  2. {
  3. using System;
  4. using System.Data.Entity.Migrations;
  5. public partial class AddPostClass : DbMigration
  6. {
  7. public override void Up()
  8. {
  9. CreateTable(
  10. "dbo.Posts",
  11. c => new
  12. {
  13. PostId = c.Int(nullable: false, identity: true),
  14. Title = c.String(maxLength: 200),
  15. Content = c.String(),
  16. BlogId = c.Int(nullable: false),
  17. })
  18. .PrimaryKey(t => t.PostId)
  19. .ForeignKey("dbo.Blogs", t => t.BlogId, cascadeDelete: true)
  20. .Index(t => t.BlogId)
  21. .Index(p => p.Title, unique: true);
  22. AddColumn("dbo.Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));
  23. }
  24. public override void Down()
  25. {
  26. DropIndex("dbo.Posts", new[] { "Title" });
  27. DropForeignKey("dbo.Posts", "BlogId", "dbo.Blogs");
  28. DropIndex("dbo.Posts", new[] { "BlogId" });
  29. DropColumn("dbo.Blogs", "Rating");
  30. DropTable("dbo.Posts");
  31. }
  32. }
  33. }

在 Package Manager Console 中运行命令 Update-Database –Verbose


消费者端,用来把消息队列里的数据写入数据库

MqHelper.cs代码:

  1. public class MqHelper
  2. {
  3. private static IConnection _connection;
  4. /// <summary>
  5. /// 获取连接对象
  6. /// </summary>
  7. /// <returns></returns>
  8. public static IConnection GetConnection()
  9. {
  10. if (_connection != null) return _connection;
  11. _connection = GetNewConnection();
  12. return _connection;
  13. }
  14. public static IConnection GetNewConnection()
  15. {
  16. //从工厂中拿到实例 本地host、用户admin
  17. var factory = new ConnectionFactory()
  18. {
  19. HostName = "localhost",
  20. UserName = "guest",
  21. Password = "guest",
  22. };
  23. _connection = factory.CreateConnection();
  24. return _connection;
  25. }
  26. }

Program.cs代码:

  1. internal class Program
  2. {
  3. private static void Main(string[] args)
  4. {
  5. using (var channel = MqHelper.GetConnection().CreateModel())
  6. {
  7. //参数有 queue名字 是否持久化 独占的queue(仅供此连接) 不使用时是否自动删除 其他参数
  8. channel.QueueDeclare("NET", true, false, false, null);
  9. //我们要告诉服务器从队列里推送消息,因为消息是异步发送的,所以我们需要提供一个回调事件EventingBasicConsumer,用于处理接收到的消息。
  10. //这就是 EventingBasicConsumer.Received 事件处理程序做的事。
  11. var consumber = new EventingBasicConsumer(channel);
  12. //QoS = quality-of-service, 顾名思义,服务的质量。
  13. //代码第一个参数是可接收消息的大小的,但是似乎在客户端2.8.6版本中它必须为0,即使:不受限制。
  14. //如果不输0,程序会在运行到这一行的时候报错,说还没有实现不为0的情况。
  15. //第二个参数是处理消息最大的数量。举个例子,如果输入1,那如果接收一个消息,但是没有应答,则客户端不会收到下一个消息,消息只会在队列中阻塞。
  16. //如果输入3,那么可以最多有3个消息不应答,如果到达了3个,则发送端发给这个接收方得消息只会在队列中,而接收方不会有接收到消息的事件产生。
  17. //总结说,就是在下一次发送应答消息前,客户端可以收到的消息最大数量。
  18. //第三个参数则设置了是不是针对整个Connection的,因为一个Connection可以有多个Channel,如果是false则说明只是针对于这个Channel的。
  19. //Fair dispatch 公平分发
  20. //通过 BasicQos 方法设置prefetchCount = 1。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。 设置方法如下:
  21. channel.BasicQos(0, 1, false);
  22. consumber.Received += (sender, e) =>
  23. {
  24. try
  25. {
  26. var user = JsonConvert.DeserializeObject<User>(Encoding.UTF8.GetString(e.Body));
  27. //Redis INCR命令用于将键的整数值递增1。如果键不存在,则在执行操作之前将其设置为0。 如果键包含错误类型的值或包含无法表示为整数的字符串,则会返回错误。此操作限于64位有符号整数。
  28. var flag = RedisHelper.GetRedisClient().Incr(user.Id.ToString());
  29. if (flag == 1)
  30. {
  31. //用户的第一次请求,为有效请求
  32. //下面开始入库,这里使用List做为模拟
  33. Console.WriteLine(string.Format("{0}标识为{1} {2}", user.Id, flag, user.Name));
  34. var dbContext = new ModelContext();
  35. dbContext.Person.Add(new Person() {Id2 = user.Id.ToString(), Name = user.Name});
  36. Task ts = dbContext.SaveChangesAsync();
  37. ts.Wait();
  38. //添加入库标识
  39. RedisHelper.GetRedisClient().Incr(string.Format("{0}入库", user.Id.ToString()));
  40. Console.WriteLine("入库成功");
  41. }
  42. //用户的N次请求,为无效请求
  43. channel.BasicAck(e.DeliveryTag, false); // 回发ACK 参数 tag 是否多个 //对message进行确认
  44. }
  45. catch (Exception ex)
  46. {
  47. File.AppendAllText(string.Format("{0}/bin/log.txt", System.AppDomain.CurrentDomain.BaseDirectory), ex.Message);
  48. }
  49. };
  50. Console.WriteLine("开始工作");
  51. // 如果 channel.BasicConsume 中参数 noAck 设置为 false,必须加上消息确认语句
  52. // Message acknowledgment(消息确认机制作用)
  53. //打开应答机制
  54. //no_ack 的用途:确保 message 被 consumer “成功”处理了。
  55. //这里“成功”的意思是,(在设置了 no_ack=false 的情况下)只要 consumer 手动应答了 Basic.Ack ,就算其“成功”处理了。
  56. //情况一:no_ack=true (此时为自动应答)
  57. //在这种情况下,consumer 会在接收到 Basic.Deliver + Content-Header + Content-Body 之后,立即回复 Ack 。
  58. //而这个 Ack 是 TCP 协议中的 Ack 。此 Ack 的回复不关心 consumer 是否对接收到的数据进行了处理,当然也不关心处理数据所需要的耗时。
  59. //情况二:no_ack=false (此时为手动应答)
  60. //在这种情况下,要求 consumer 在处理完接收到的 Basic.Deliver + Content-Header + Content-Body 之后才回复 Ack 。
  61. //而这个 Ack 是 AMQP 协议中的 Basic.Ack 。此 Ack 的回复是和业务处理相关的,所以具体的回复时间应该要取决于业务处理的耗时。
  62. channel.BasicConsume("NET", false, consumber);
  63. Console.ReadKey();
  64. }
  65. }
  66. }

模拟并发的MVC网站,写入队列

HomeController.cs代码

  1. public class HomeController : Controller
  2. {
  3. // GET: Controller
  4. public ActionResult Index()
  5. {
  6. return View();
  7. }
  8. /// <summary>
  9. /// 抢单接口
  10. /// </summary>
  11. /// <param name="user"></param>
  12. /// <returns></returns>
  13. [HttpPost]
  14. public ActionResult GrabSingle(User user)
  15. {
  16. //使用后台任务
  17. //BackgroundJob.Enqueue(() => MqPublish.AddQueue(user));
  18. MqPublish.AddQueue(user);
  19. //MqPublish.AddQueue(user);
  20. return Json(new { Status = "OK" });
  21. }
  22. /// <summary>
  23. /// 获取数量
  24. /// </summary>
  25. /// <returns></returns>
  26. [HttpGet]
  27. public async Task<ActionResult> GetCount()
  28. {
  29. using (var dbcontext = new ModelContext())
  30. {
  31. return Json(new { Count = await dbcontext.Person.CountAsync() }, JsonRequestBehavior.AllowGet);
  32. }
  33. }
  34. }

MqPublish.cs类

  1. /// <summary>
  2. /// 发布者
  3. /// </summary>
  4. public class MqPublish
  5. {
  6. public const string QueueName = "NET";
  7. public static IList<User> UserList = new List<User>();
  8. /// <summary>
  9. /// 添加到队列
  10. /// </summary>
  11. public static void AddQueue(User user)
  12. {
  13. //创建一个channel
  14. using (var channel = MqHelper.GetNewConnection().CreateModel())
  15. {
  16. //json序列化
  17. var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(user));
  18. //channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes);
  19. //参数说明:
  20. //要发出的交换机名字
  21. //路由关键字
  22. //是否强制(设置为true时,找不到收的人时可以通过returnListener返回)
  23. //是否立即(其实rabbitmq不支持)
  24. //其他属性
  25. //消息主体
  26. channel.BasicPublish(String.Empty, QueueName, null, bytes);
  27. }
  28. }
  29. }

RedisHelper.cs代码:

  1. /// <summary>
  2. /// Redis帮助类
  3. /// </summary>
  4. public class RedisHelper
  5. {
  6. public static RedisClient GetRedisClient()
  7. {
  8. return new RedisClient("127.0.0.1", 6379,"123456");
  9. }
  10. }

Index.cshtml内容:

  1. @{
  2. Layout = null;
  3. }
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <meta name="viewport" content="width=device-width" />
  8. <title>Index</title>
  9. <link href="//cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">
  10. <script src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
  11. <script>
  12. var uarray = [
  13. { Id: 1, Name: "小明" }, { Id: 2, Name: "张三" }, { Id: 3, Name: "李四" }, { Id: 4, Name: "王五" },
  14. { Id: 5, Name: "赵六" }, { Id: 6, Name: "钱八" }, { Id: 7, Name: "小红" }, { Id: 8, Name: "小紫" },
  15. { Id: 9, Name: "小蓝" }, { Id: 10, Name: "老王" }
  16. ];
  17. function btnClick() {
  18. //构造并发模拟
  19. var flag;
  20. for (var i = 0; i < 1000; i++) {
  21. flag = parseInt(Math.random() * (9 - 0 + 1) + 0);
  22. $.ajax({
  23. type: "post",
  24. url: "/Home/GrabSingle",
  25. data: uarray[flag]
  26. });
  27. }
  28. }
  29. function GetCount() {
  30. $.ajax({
  31. type: "get",
  32. url: "/Home/GetCount",
  33. dataType: "json",
  34. success: function (data) {
  35. alert(data.Count + "个");
  36. }
  37. });
  38. }
  39. </script>
  40. </head>
  41. <body>
  42. <h2>.Net高并发解决思路</h2>
  43. <hr />
  44. <button class="btn btn-primary" onclick="btnClick()">点击抢单</button>
  45. <button class="btn btn-primary" onclick="GetCount()">查看入库数量</button>
  46. </body>
  47. </html>

Startup.cs代码:

  1. using System;
  2. using System.Threading.Tasks;
  3. using Hangfire;
  4. using Hangfire.MemoryStorage;
  5. using Microsoft.Owin;
  6. using NetHigh.RabbitMq;
  7. using Owin;
  8. [assembly: OwinStartup(typeof(NetHigh.Startup))]
  9. namespace NetHigh
  10. {
  11. public partial class Startup
  12. {
  13. public void Configuration(IAppBuilder app)
  14. {
  15. GlobalConfiguration.Configuration.UseMemoryStorage();
  16. app.UseHangfireServer();
  17. app.UseHangfireDashboard("/hangfire");
  18. //添加三个后台任务也就是三个consumer
  19. //BackgroundJob.Enqueue(() => MqConsumber.ConsumeQueue());
  20. //BackgroundJob.Enqueue(() => MqConsumber.ConsumeQueue());
  21. //BackgroundJob.Enqueue(() => MqConsumber.ConsumeQueue());
  22. }
  23. }
  24. }

Web.config

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <configuration>
  3. <configSections>
  4. <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  5. </configSections>
  6. <system.web>
  7. <compilation debug="true" targetFramework="4.5" />
  8. <httpRuntime targetFramework="4.5" />
  9. </system.web>
  10. <entityFramework>
  11. <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
  12. <parameters>
  13. <parameter value="mssqllocaldb" />
  14. </parameters>
  15. </defaultConnectionFactory>
  16. <providers>
  17. <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
  18. </providers>
  19. </entityFramework>
  20. <connectionStrings>
  21. <add name="default" connectionString="Server=.;User Id=sa;PassWord=123456;DataBase=Person" providerName="System.Data.SqlClient" />
  22. </connectionStrings>
  23. </configuration>

运行结果如图:

这里写图片描述


这里写图片描述


运行消费者端(控制台运用程序)
这里写图片描述


这里写图片描述

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/84254
推荐阅读
相关标签
  

闽ICP备14008679号