不知不覺orleans就發佈到2.1版本的,但是說也奇怪orleans越是完善我發現園子相關的博客就越少,大概是大佬都在美滋滋用在生產環境,不屑於玩demo了吧。 但是小弟不才還是只會玩demo,所以只能簡單的介紹介紹2.1版本的新玩法了。 1.新建一個asp.net core的webapi項目,然 ...
不知不覺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