[orleans2.1]这是你没玩过的船新版本
不知不觉orleans就发布到2.1版本的,但是说也奇怪orleans越是完善我发现园子相关的博客就越少,大概是大佬都在美滋滋用在生产环境,不屑于玩demo了吧。
但是小弟不才还是只会玩demo,所以只能简单的介绍介绍2.1版本的新玩法了。
1.新建一个asp.net core的webapi项目,然后引用下面几个nuget包:
1 Microsoft.Orleans.OrleansRuntime 2 Microsoft.Orleans.CodeGenerator.MSBuild 3 Microsoft.Orleans.Transactions 4 Orleans.Providers.MongoDB 5 OrleansDashboard
2.包装一下orleans的silobuilder类,并且继承IHostedService直接和asp.net core运行在一起
1 public class SiloWrapper : IHostedService 2 { 3 private readonly ISiloHost _silo; 4 public readonly IClusterClient Client; 5 6 public SiloWrapper() 7 { 8 _silo = new SiloHostBuilder() 9 .UseLocalhostClustering() 10 .ConfigureApplicationParts(parts => 11 parts.AddApplicationPart(typeof(Grains.IUserGrain).Assembly).WithReferences()) 12 .EnableDirectClient()//2.1新增的功能,单个Host可以直接使用SiloHost的Client,不需要再用ClientBuilder建Client了 13 .AddMongoDBGrainStorageAsDefault(options => 14 { 15 options.ConnectionString = "mongodb://localhost/OrleansTestApp"; 16 })//配置数据库 17 .ConfigureLogging(x => 18 { 19 x.AddConsole(); 20 x.SetMinimumLevel(LogLevel.Warning); 21 }) 22 .UseDashboard(x => 23 { 24 x.HostSelf = false; 25 })//HostSelf设置为false 26 .UseTransactions()//2.1的事务配置简化了 27 .Build(); 28 29 Client = _silo.Services.GetRequiredService<IClusterClient>();//把sliohost的IClusterClient暴露出去。 30 } 31 32 public async Task StartAsync(CancellationToken cancellationToken) 33 { 34 await _silo.StartAsync(cancellationToken); 35 } 36 37 public async Task StopAsync(CancellationToken cancellationToken) 38 { 39 await _silo.StopAsync(cancellationToken); 40 } 41 }
3.Startup类配置:
1 public class Startup 2 { 3 public Startup(IConfiguration configuration) 4 { 5 Configuration = configuration; 6 } 7 8 public IConfiguration Configuration { get; } 9 10 public void ConfigureServices(IServiceCollection services) 11 { 12 services.AddSingleton<SiloWrapper>();//注入SiloWrapper 13 services.AddSingleton<IHostedService>(x=>x.GetRequiredService<SiloWrapper>());//同时把SiloWrapper注入为IHostedService 14 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 15 services.AddSingleton(x => x.GetRequiredService<SiloWrapper>().Client);//注入SiloWrapper的Client 16 services.AddServicesForSelfHostedDashboard();//注入orleans的dashboard 17 } 18 19 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 20 { 21 if (env.IsDevelopment()) 22 { 23 app.UseDeveloperExceptionPage(); 24 } 25 26 app.UseOrleansDashboard(new OrleansDashboard.DashboardOptions { BasePath = "/dashboard"});//设置一下dashboard的访问路径 27 app.UseMvc(); 28 } 29 }
4.新建一些Grain类,这里只给出一个后面我会贴代码地址出来。
public class UserGrain:Grain<UserInfo>,IUserGrain { public ValueTask<UserInfo> GetInfo()//同步代码可以返回ValueTask { return new ValueTask<UserInfo>(State); } public async Task<UserInfo> UpdateInfo(UserInfo info) { State = info; await WriteStateAsync();//更新数据才需要数据库相关的操作 return State; } public async Task<uint> GetBalance() { var account = this.GrainFactory.GetGrain<IAccountGrain>(this.GetPrimaryKeyLong());//通过GrainFactory访问其他grain return await account.GetBalance(); } }
[StatelessWorker] public class ATMGrain : Grain, IATMGrain//转账事务的专用grain { Task IATMGrain.Transfer(long fromAccount, long toAccount, uint amountToTransfer) { return Task.WhenAll( this.GrainFactory.GetGrain<IAccountGrain>(fromAccount).Withdraw(amountToTransfer), this.GrainFactory.GetGrain<IAccountGrain>(toAccount).Deposit(amountToTransfer)); } } public class AccountGrain : Grain, IAccountGrain//加钱,减钱,查钱啦 { private readonly ITransactionalState<Balance> _balance; public AccountGrain( [TransactionalState("balance")] ITransactionalState<Balance> balance) { _balance = balance ?? throw new ArgumentNullException(nameof(balance)); } async Task IAccountGrain.Deposit(uint amount) { await _balance.PerformUpdate(x => x.Value += amount); } async Task IAccountGrain.Withdraw(uint amount) { await _balance.PerformUpdate(x => { if (x.Value < amount) { throw new InvalidOperationException( "The transferred amount was greater than the balance."); } return x.Value -= amount; }); } Task<uint> IAccountGrain.GetBalance() { return _balance.PerformRead(x => x.Value); } }
5.controller相关的代码,这里也是照旧只贴一部分
[Route("api/[controller]")] public class ValuesController : Controller { private readonly IClusterClient _client; public ValuesController(IClusterClient client) { _client = client; } [HttpGet("[action]/{id}")] public async Task<object> GetInfo(long id) { var userGrain = _client.GetGrain<IUserGrain>(id); return await userGrain.GetInfo(); } }
代码地址:https://github.com/iJzFan/orleansdemo
可以看到2.1之后配置真的简单了很多,简单几步之后你就能快乐的进行无数据库设计无并发考虑的编程啦。
最后面是我用jmeter做的一个小测试(不是特别严谨,日志都是开着的,不要太纠结数据),配置嘛就是那个1核两G的腾讯云垃圾主机啦,上面跑了一个两个docker,一个是前面的orleansdemo,一个是mongodb。
测试条件就是用户1和用户2相互转账( ̄︶ ̄)↗ ,10个线程,分别转1000次(对应的URL:/api/values/atm?from=1&to=2&amount=1和/api/values/atm?from=2&to=1&amount=1)。
测试条件就是1转2,2转3,3转4,4转1,10个线程,分别转500次(url参考上面)。
时延还是挺低的,平均才55~61ms,腾讯云那个垃圾主机一秒都能处理150~160的事务请求。
最最后面贴几个orleans相关的代码库,毕竟我上面的demo还是太小儿科了,
https://github.com/RayTale/Ray 分布式、高性能、事件溯源、事件驱动、最终一致性框架
https://github.com/Squidex/squidex Headless CMS and Content Managment Hub