开具一张图,展开来聊天。有从单个服务、consul集群和orleans来展开高并发测试一个小小数据库并发实例。

首先介绍下场景,创建一个order,同时去product表里面减掉一个库存。很简单的业务但是遇到并发问题在项目中就很头痛。

由于内容比较多,简单介绍了。

对外的接口很简单,客户端代码如下,通过不同的方法去控制并发问题,当然者都是在单个服务跑起来的时候。下面介绍下怎么去测试。

 [Route("api/[controller]/[action]")]
[ApiController]
public class OrderController : ControllerBase
{
private readonly IClusterClient orderService;
public OrderController(IClusterClient orderService)
{
this.orderService = orderService;//500请求 并发50 . 100库存
} [HttpPost]
public async Task Create([FromServices] Channel<CreateOrderDto> channel, string sku, int count)
{
await channel.Writer.WriteAsync(new CreateOrderDto(sku, count)); //高并发高效解决方案 并发测试工具postjson_windows 10s
} [HttpPost]
public async Task CreateTestLock(string sku, int count)//非阻塞锁
{
await orderService.GetGrain<IOrderGrains>(Random.Shared.Next()).CreateTestLock(sku, count); //执行时间快,库存少量扣减 10s
}
[HttpPost]
public async Task CreateBlockingLock(string sku, int count)//阻塞锁
{
await orderService.GetGrain<IOrderGrains>(Random.Shared.Next()).CreateBlockingLock(sku, count); //卖不完,时间长 50s
}
[HttpPost]
public async Task CreateDistLock(string sku, int count) //colder组件 分布式锁
{
await orderService.GetGrain<IOrderGrains>(Random.Shared.Next()).CreateDistLock(sku, count); //库存扣完,时间长 50s
} [HttpPost]
public async Task CreateNetLock(string sku, int count) //netlock.net锁
{
await orderService.GetGrain<IOrderGrains>(Random.Shared.Next()).CreateNetLock(sku, count); //库存扣完,时间长 50s
} static System.Threading.SpinLock semaphore = new SpinLock(false);
[HttpPost]
public async Task CreateLock(string sku, int count) //卖不完
{
bool lockTaken = false;
try
{
semaphore.Enter(ref lockTaken);
await orderService.GetGrain<IOrderGrains>(0).CreateLock(sku, count);
}
finally
{
if (lockTaken)
semaphore.Exit();
}
} [HttpPost]
public void CreateLocalLock(string sku, int count) //能卖完
{
orderService.GetGrain<IOrderGrains>(Random.Shared.Next()).CreateLocalLock(sku, count); //
} [HttpPost]
public async Task CreateNoLock(string sku, int count)
{
await orderService.GetGrain<IOrderGrains>(Random.Shared.Next()).CreateNoLock(sku, count); //乱的
}
[HttpGet]
public async Task ChangeOrderStatus(int orderId, OrderStatus status)
{
switch (status)
{
case OrderStatus.Shipment:
await orderService.GetGrain<IOrderGrains>(0).Shipment(orderId);
break;
case OrderStatus.Completed:
await orderService.GetGrain<IOrderGrains>(0).Completed(orderId);
break;
case OrderStatus.Rejected:
await orderService.GetGrain<IOrderGrains>(0).Rejected(orderId);
break;
default:
break;
}
}
}

redis必不可少,有用到分布式锁。

高并发测试工具我用的是postjson_windows,自行百度。很方便。

下面简单说一下单机测试吧,上面的代码注释不多但是简单总结了结果。通过测试我个人认为channel是最理想的方式,速度快,而且结果很完美,lock锁其次。其他结果或多或少有瑕疵。

但是如果部署成集群会怎样呢,因为lock锁和channel是进程内安全的,多开几个就以为这多开了几个进程,这样的话分布式锁是大家最先想到的,但是结果呢

下面简单介绍下consul集群,以前有写过例子,这次拿来真的是很快就跑起来了。第一个就封装的consul注册中心和心跳检查的类库,第二个是我们的服务,第三个是我们的网关。具体代码后面有链接。consul自行安装,配置什么的默认。

这里部署了三个服务,把生成的代码拷贝了三份,分别执行。再启动网关。然后按照对应的方法名调用并发测试。(这里拷贝三份有点麻烦,后面orleans再看新的执行方法)

多开服务

dotnet eapi.dll --urls="http://*:5007" --ip="127.0.0.1" --port="5007" --weight=1
dotnet eapi.dll --urls="http://*:5008" --ip="127.0.0.1" --port="5008" --weight=2
dotnet eapi.dll --urls="http://*:5009" --ip="127.0.0.1" --port="5009" --weight=5

consul启动走命令,应对闪退
Consul.exe agent -dev

dotnet eapi.gateway.dll --urls="https://*:5000" --ip="127.0.0.1" --port="5000"

简单说下结果吧,集群下只有分布式锁能解决问题,但是只生成了50条数据,扣减库存也是50。channel和本地锁结果都跟想的差不多,是不对的。

下面说写orleans下的表现,首先eapi是服务,gateway是网关,interfaces就是网关和服务侨链的接口。这里代码跟上面的有些差别,就是事务实现方式不一样。因为运行起来的时候发现每次只能成功第一次,后面一直报错数据库链接被占用,所以我改掉了事务实现方式。因为前面的没这问题所以很纳闷,实际项目中也是感觉到orleans对数据库的链接管理是有点问题的,所以不去深究。

下面跑起来看看orleans怎么样,dotnet命令我们是可以指定appsettings.json的配置的,所以配置文件指定好OrleansOptions,只需要在生产代码目录orleans多实例服务\eapi\bin\Debug\net7.0下面执行者三个服务命令就开了三服务,下面执行下网关。

{
"urls": "https://*:5005",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"distributedLock": {
"LockType": "Redis",
"RedisEndPoints": [ "127.0.0.1:6379" ]
},
"OrleansOptions": {
"GatewayPort": 30001,
"SiloPort": 1112
} }
  builder.Host.ConfigureDistributedLockDefaults();
var gport = int.Parse(builder.Configuration["OrleansOptions:GatewayPort"]);
var sport = int.Parse(builder.Configuration["OrleansOptions:SiloPort"]);
builder.Host.UseOrleans(b => b.UseLocalhostClustering(sport, gport));

先说下遇到的问题, ReposioryBase<T>下面的代码原来返回的是iquerable<T> 这是大佬推荐的做法返回iquerable,结果到多服务就出现占用,我把事务换成现在的写法发现还是徒劳,直接查询出来ToListAsync就可以了。

public async Task<IReadOnlyList<T>> FindByCondition(Expression<Func<T, bool>> expression)
{
return await DbSet.Where(expression).ToListAsync();
}

orderService下面的代码

var product = (await _productRepository.FindByCondition(x => x.Sku.Equals(sku))).SingleOrDefault(); //执行一尺order创建后此处链接就不释放了。

if (product == null || product.Count < count)
{
_logger.LogInformation("库存不足,稍后重试");
return;
}
else
{
product.Count -= count;
}

下面的问题就是负载的问题,单个服务的话GetGrain<IOrderGrains>(0)没问题,多个服务这样的话所有请求都只会落到一个服务上,改成随机的三个服务就都可以了。

 internal class NotificationDispatcher : BackgroundService
{
private readonly ILogger<NotificationDispatcher> logger;
private readonly Channel<CreateOrderDto> channel;
private readonly IServiceProvider serviceProvider;
public NotificationDispatcher( ILogger<NotificationDispatcher> logger, Channel<CreateOrderDto> channel, IServiceProvider serviceProvider)
{
this.logger = logger;
this.channel = channel;
this.serviceProvider = serviceProvider;
} protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!channel.Reader.Completion.IsCompleted)
{
var createOrderDto = await channel.Reader.ReadAsync();
try
{
using (var scope = serviceProvider.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<IClusterClient>();
var orderService = client.GetGrain<IOrderGrains>(Random.Shared.Next()); //设置为0导致指挥获取一个服务,随机的话就是多服务负载
//var orderService = client.GetGrain<IOrderGrains>(0);
await orderService.Create(createOrderDto.sku, createOrderDto.count);
}
}
catch (Exception ex)
{
logger.LogError(ex, "notification failed");
} }
}
}

结果发现channel竟然很迅速的完成500次请求,50并发数,只用了8秒钟。数据库库存扣减完毕,order生成100条。有去了解过orleans,理解它的设计思路。但是说不上来具体的。

这是启动的三兄弟:

下面只要启动了网关就会链接上服务,这里需要启动先后顺序,先服务后网关要不然网关会起不来。启动后服务掉线或者关掉都不会再影响网关,自动注册和发现。

通过swagger试了一条数据,服务正常,服务被服务端口5008,网关端口30002的执行了。

并发测试结果,看sql注释有提前把库存还原,order清空:

看看是哪些服务拿到了请求,因为500次请求只有100个库存所以后面就是一直库存不足,而且每个服务都参与了处理。

个人推荐用orleans做微服务集群,channel处理防止并发。其次是分布式锁redlock.net。

下面redlock.net执行结果,orleans集群上b表现还不错。在单机和consul只能完成50条。

consul不知道为什么特别慢,而且和单机一样遇到有些锁完全跑不完。对于channel和本地锁跑到consul上结果是乱的。

只有orlans表现相当突出。

结论,单机用本地锁或者channel。 consul和orleans我选orleans。 consul下只能分布式锁。orleans用channel就够了。

源代码:liuzhixin405/orleans-consul-cluster: orleans和consul并发测试 (github.com)

高并发解决方案orleans实践的更多相关文章

  1. PHP面试(二):程序设计、框架基础知识、算法与数据结构、高并发解决方案类

    一.程序设计 1.设计功能系统——数据表设计.数据表创建语句.连接数据库的方式.编码能力 二.框架基础知识 1.MVC框架基本原理——原理.常见框架.单一入口的工作原理.模板引擎的理解 2.常见框架的 ...

  2. java并发编程与高并发解决方案

    下面是我对java并发编程与高并发解决方案的学习总结: 1.并发编程的基础 2.线程安全—可见性和有序性 3.线程安全—原子性 4.安全发布对象—单例模式 5.不可变对象 6.线程封闭 7.线程不安全 ...

  3. 关于SQL SERVER高并发解决方案

    现在大家都比较关心的问题就是在多用户高并发的情况下,如何开发系统,这对我们程序员来说,确实是值得研究,最近找工作面试时也经常被问到,其实我早有去关心和了解这类问题,但一直没有总结一下,导致面试时无法很 ...

  4. 手把手让你实现开源企业级web高并发解决方案(lvs+heartbeat+varnish+nginx+eAccelerator+memcached)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://freeze.blog.51cto.com/1846439/677348 此文凝聚 ...

  5. 高并发解决方案--负载均衡(HTTP,DNS,反向代理服务器)(解决大流量,高并发)

    高并发解决方案--负载均衡(HTTP,DNS,反向代理服务器)(解决大流量,高并发) 一.总结 1.什么是负载均衡:当一台服务器的性能达到极限时,我们可以使用服务器集群来提高网站的整体性能.那么,在服 ...

  6. JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案

    JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案

  7. java系统高并发解决方案-转

    转载博客地址:http://blog.csdn.net/zxl333/article/details/8685157 一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图 ...

  8. java系统高并发解决方案(转载)

    转载博客地址:http://blog.csdn.net/zxl333/article/details/8454319 转载博客地址:http://blog.csdn.net/zxl333/articl ...

  9. 高并发解决方案之Actor——第一节

    还在为状态的并发控制而痛苦吗?   还在因为数据库瓶颈而痛苦吗?   还在因为缓存的实时性控制而痛苦吗?   还在为了想分布式,但又不知道怎么下手而痛苦吗?   Actor欢迎你!!!   一.什么是 ...

  10. java系统高并发解决方案(转载收藏)

    一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构.性能的要求都很简单,随着互联网业务的不断丰富,网站 ...

随机推荐

  1. Docker | 自定义网络(网关、子网地址)

    了解 docker network 通过下面的命令来获取帮助 docker network --help Commands: connect Connect a container to a netw ...

  2. 路由组件构建方案(分库分表)V1

    路由组件构建方案V1 实现效果:通过注解实现数据分散到不同库不同表的操作. 实现主要以下几部分: 数据源的配置和加载 数据源的动态切换 切点设置以及数据拦截 数据的插入 涉及的知识点: 分库分表相关概 ...

  3. 再有人说synchronized是重量级锁,就把这篇文章扔给他看

    synchronized作为Java程序员最常用同步工具,很多人却对它的用法和实现原理一知半解,以至于还有不少人认为synchronized是重量级锁,性能较差,尽量少用. 但不可否认的是synchr ...

  4. fun无处不在 搜索结果调用其他字段的办法

    一直有人要搜索结果调用其他字段的信息 比如图片等等 一直忙没顾上 官方其实已经内置了一个内容读取器. 我们打开搜索的结果模板template\index_style\你的模板目录\search\ind ...

  5. 浅谈ORM-对象关系映射

    目前.NET(C#)中比较流行的ORM框架: SqlSugar (国内) Dos.ORM (国内) Chloe (国内) StackExchange/Dapper (国外) Entity Framew ...

  6. 通过tkinter列出全部字体名称

    通过tkinter列出windows系统全部字体名称 通过 tkinter.font 的 families() 函数实现 import tkinter import tkinter.font # 把p ...

  7. 苹果 App Store 开始支持隐藏上架应用:只能通过链接下载

      据MacRumors报道,苹果公司最近宣布,正如其开发者网站上所概述的那样,App Store现在支持只能通过直接链接才能发现的隐藏应用.   图片来自 Apple 拥有不适合公开发布的应用的开发 ...

  8. 云数据库时代,DBA将走向何方?

    摘要:伴随云计算的迅猛发展,数据库也进入了云时代.云数据库不断涌现,产品越来越成熟和智能,作为数据库管理员的DBA将面临哪些机遇和挑战?又应该具备什么能力,才能应对未来的不确定性? 本文分享自华为云社 ...

  9. 论文笔记 - RETRIEVE: Coreset Selection for Efficient and Robust Semi-Supervised Learning

    Motivation 虽然半监督学习减少了大量数据标注的成本,但是对计算资源的要求依然很高(无论是在训练中还是超参搜索过程中),因此提出想法:由于计算量主要集中在大量未标注的数据上,能否从未标注的数据 ...

  10. Linux环境下执行脚本重启Weblogic控制台中部署的应用程序

    之前有写过一篇博文介绍切换登录方式的脚本,脚本中存在一个缺点:仍需手动去Weblogic控制台重启应用程序:本文即介绍如何在脚本中更新Weblogic控制台中部署的应用程序. 一.配置Weblogic ...