Orleans8.2入门测试

微软官方文档:快速入门:使用 ASP.NET Core 生成第一个 Orleans 应用 - .NET | Microsoft Learn

项目及引入的nuget库:

1、接口项目;2、接口实现项目;3、silo项目;4、客户端项目

其中Microsoft.Orleans.Streaming,为测试流功能额外添加。 

//IHello.cs代码
using System.Reflection;

namespace GrainInterfaces
{
    /// <summary>
    /// Grain接口
    /// 从IGrainWithStringKey继承可以使用字符串作为Grain的唯一标识,便于理解
    /// 从IGrainObserver继承为了支持订阅方式双向通信
    /// </summary>
    public interface IHello : IGrainWithStringKey, IGrainObserver
    {
        /// <summary>
        /// 默认测试接口
        /// </summary>
        /// <param name="greeting"></param>
        /// <returns></returns>
        ValueTask<string> SayHello(string greeting);
        /// <summary>
        /// 传递复杂对象测试
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        ValueTask<ResultA> GetResultA(ParamA param);
        #region 用订阅方式实现双向通信
        Task NotifyOtherGrain(string grainID, string message);
        /// <summary>
        /// 客户端订阅Grain上的通知消息
        /// 需要额外的一个IGrainObserver类型对象IChat支持
        /// </summary>
        /// <param name="observer"></param>
        /// <returns></returns>
        Task Subscribe(IChat observer);
        /// <summary>
        /// 解除订阅
        /// </summary>
        /// <param name="observer"></param>
        /// <returns></returns>
        Task Unsubscribe(IChat observer);
        /// <summary>
        /// Grains直接通信,只要有Grains的id,就可以很方便的从客户端或其他grains中拿到grains的引用
        /// </summary>
        /// <param name="sender">目标grains的id</param>
        /// <param name="message">要转发的消息</param>
        /// <returns></returns>
        Task ReceiveBrotherMessage(string sender, string message);
        #endregion
    }
    #region 订阅方式支持类定义
    /// <summary>
    /// Grain类不允许出现属性,为了方便客户端订阅,额外定义一个带Action委托的接口让Chat对象实现
    /// </summary>
    public interface IChatNotify
    {
        /// <summary>
        /// 收到服务端通知触发事件,客户端指定处理事件
        /// </summary>
        Action<string> ReceiveAct { get; set; }
    }

    /// <summary>
    /// 消息订阅支持接口
    /// </summary>
    [Alias("GrainInterfaces.IChat")]
    public interface IChat : IGrainObserver
    {
        /// <summary>
        /// 定义订阅模式消息接收函数
        /// 注意IChat在客户端实例化,此函数在客户端执行
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        [Alias("ReceiveMessage")]
        Task ReceiveMessage(string message);
    }

    /// <summary>
    /// 消息订阅类实现(在客户端实例化)
    /// </summary>
    public class Chat : IChat, IChatNotify
    {
        public Action<string> ReceiveAct { get; set; } = null;
        public Task ReceiveMessage(string message)
        {
            var log = $"===={message}   {Assembly.GetEntryAssembly()?.FullName}";
            Console.WriteLine(log);
            ReceiveAct?.Invoke(message);
            return Task.CompletedTask;
        }
    }
    #endregion

    /// <summary>
    /// 定义复杂函数参数类型
    /// 注解GenerateSerializer:告诉Orleans使用其默认的序列化反序列化方式
    /// </summary>
    [GenerateSerializer]
    public class ParamA
    {
        [Id(0)]
        public int ID { get; set; }
        [Id(1)]
        public int DataLen { get; set; }
        [Id(2)]
        public required string Name { get; set; }
    }

    /// <summary>
    /// 定义复杂函数返回类型
    /// 注解GenerateSerializer:告诉Orleans使用其默认的序列化反序列化方式
    /// </summary>
    [GenerateSerializer]
    public class ResultA
    {
        [Id(0)]
        public int ID { get; set; }
        [Id(1)]
        public required string Name { get; set; }
        [Id(2)]
        public required byte[] Data { get; set; }
    }

    /// <summary>
    /// 测试用全局数据定义
    /// </summary>
    public static class GlobalValueDefinition
    {
        public const string StreamProviderName = "StreamProvider";
        public const string GrainStorageName = "PubSubStore";
        public const string ImplicitStreamSubscriptionName = "RANDOMDATA";
    }
}
//HelloGrains.cs
using GrainInterfaces;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Streams;
using Orleans.Utilities;
using System.Net.NetworkInformation;

namespace Grains
{
    /// <summary>
    /// Hello Grain定义
    /// [ImplicitStreamSubscription(GlobalValueDefinition.ImplicitStreamSubscriptionName)]
    /// 这个注解告诉Orleans使用隐式订阅流(简化测试)
    /// </summary>
    [ImplicitStreamSubscription(GlobalValueDefinition.ImplicitStreamSubscriptionName)]
    public class HelloGrains :Grain, IHello
    {
        private readonly ILogger _logger;

        private readonly ObserverManager<IChat> _subManager;

        public HelloGrains(ILogger<HelloGrains> logger)
        {
            _logger = logger;
            _subManager = new ObserverManager<IChat>(TimeSpan.FromMinutes(5), logger);
            Console.WriteLine($"====={GrainContext.GrainId}");
        }

        private IAsyncStream<string> _demoStream;

        public override Task OnActivateAsync(CancellationToken cancellationToken)
        {
            #region 流的支持
            CreateStreamSubscribe().GetAwaiter().GetResult();
            #endregion
            return base.OnActivateAsync(cancellationToken);
        }

        private async Task CreateStreamSubscribe()
        {
            var guid = this.GetPrimaryKeyString();
            var streamProvider = this.GetStreamProvider(GlobalValueDefinition.StreamProviderName);
            var streamId = StreamId.Create(GlobalValueDefinition.ImplicitStreamSubscriptionName, guid);
            _demoStream = streamProvider.GetStream<string>(streamId);
            await _demoStream.SubscribeAsync<string>(
                async (data, token) => { 
                    Console.WriteLine($"*****Hello {data}");
                    await Task.CompletedTask;
                });
        }

        #region 订阅支持
        public Task Subscribe(IChat observer) 
        {
            _subManager.Subscribe(observer, observer);
            return Task.CompletedTask;
        }

        public Task Unsubscribe(IChat observer) 
        { 
            _subManager.Unsubscribe(observer); 
            return Task.CompletedTask; 
        }
        //向订阅者发送消息
        public Task SendUpdateMessage(string message)
        {
            _subManager.Notify(s=>s.ReceiveMessage(message));
            return Task.CompletedTask;
        }
        #endregion
        public ValueTask<string> SayHello(string greeting)
        {
            var response = $"Client {GrainContext.GrainId} said:{greeting}, so HelloGrain says: Hello";
            _logger.LogInformation(response);
            SendUpdateMessage("SendUpdateMessage:" + response);
            return ValueTask.FromResult(response);
        }

        public ValueTask<ResultA> GetResultA(ParamA param)
        {
            _logger.LogInformation($"Invoke GetResultA({param.ID}  {param.Name})");
            var res = new ResultA
            {
                ID = param.ID + 1,
                Name = param.Name,
                Data = new byte[param.DataLen],
            };
            return ValueTask.FromResult(res);
        }

        public Task NotifyOtherGrain(string otherGrainID, string message)
        {
            var otherGrainHello = GrainFactory.GetGrain<IHello>(otherGrainID);
            otherGrainHello.ReceiveBrotherMessage(this.GetPrimaryKeyString(), message);
            return Task.CompletedTask;
        }

        public Task ReceiveBrotherMessage(string sender, string message)
        {
            SendUpdateMessage($"GetBrotherMessage({sender},{message})");
            return Task.CompletedTask;
        }
    }
}
//Silo服务启动源码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans.Configuration;
using Orleans.Serialization;
using System.Net;
using GrainInterfaces;

namespace Silo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            //本地集群方式部署
            var primarySiloEndPoint = new IPEndPoint(IPAddress.Parse(textBox_IPAddr.Text), 11_111);
            var silo = new HostBuilder()
                .UseOrleans(builder =>
                {
                    builder.UseDevelopmentClustering(primarySiloEndPoint)
                    .Configure<ClusterOptions>(options =>
                    {
                        options.ClusterId = "cluster001";
                        options.ServiceId = "service001";
                    })
                    .ConfigureEndpoints(siloPort: 11_111, gatewayPort: 30_001)
                    .ConfigureLogging(logging => logging.AddConsole())
                    .AddMemoryStreams(GlobalValueDefinition.StreamProviderName) //unget引入 Microsoft.Orleans.Streaming
                    .AddMemoryGrainStorage(GlobalValueDefinition.GrainStorageName);
                }).Build();
            
            await silo.RunAsync();
        }

        //#region 本地测试模式
        ////IHostBuilder builder = Host.CreateDefaultBuilder(args)
        ////    .UseOrleans(silo =>
        ////    {
        ////        silo.UseLocalhostClustering(11111, 30001, serviceId: "service001", clusterId: "cluster001")
        ////        .ConfigureLogging(logging => logging.AddConsole());
        ////    })
        ////    .UseConsoleLifetime();

        ////using IHost host = builder.Build();
        ////await host.RunAsync();
        //#endregion
    }
}
//客户端代码
using GrainInterfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans.Configuration;
using Orleans.Serialization.WireProtocol;
using Orleans.Streams;
using System.Net;
using System.Security.AccessControl;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private IClusterClient client = null;
        private IHost host = null;
        private IHello friend1;
        private IHello friend2;
        private IChat chat;
        private async void button1_Click(object sender, EventArgs e)
        {
            #region 开发者模式连接
            //IHostBuilder builder = Host.CreateDefaultBuilder()
            //    .UseOrleansClient(client =>
            //    {
            //        client.UseLocalhostClustering(30001, "service001", "cluster001");
            //    })
            //    .ConfigureLogging(logging => logging.AddConsole())
            //    .UseConsoleLifetime();

            //host = builder.Build();
            //await host.StartAsync();

            //client = host.Services.GetRequiredService<IClusterClient>();
            #endregion
            var PRIMARY_SILO_IP_ADDRESS = IPAddress.Parse(textBox_IPAddr.Text);
            //var PRIMARY_SILO_IP_ADDRESS_1 = IPAddress.Parse("192.168.0.101");
            #region 专用服务器群集模式连接
            var gateways = new IPEndPoint[] {
                new IPEndPoint(PRIMARY_SILO_IP_ADDRESS, 30_001)
            };

            host = Host.CreateDefaultBuilder()
                .UseOrleansClient(clientBuilder =>
                    clientBuilder.UseStaticClustering(gateways)
                    .Configure<ClusterOptions>(options =>
                    {
                        options.ClusterId = "cluster001";
                        options.ServiceId = "service001";
                    })
                #region Stream测试
                    .AddMemoryStreams(GlobalValueDefinition.StreamProviderName)
                #endregion
                ).Build();
            await host.StartAsync();

            client = host.Services.GetRequiredService<IClusterClient>();
            #endregion

            #region 订阅
            friend1 = client.GetGrain<IHello>("user1");
            friend2 = client.GetGrain<IHello>("user2");
            Chat c = new Chat();
            (c as IChatNotify).ReceiveAct += ShowLog;
            chat = client.CreateObjectReference<IChat>(c);
            await friend1.Subscribe(chat);
            #endregion

            #region StreamTest
            await CreateStreamSubscribe();
            #endregion
        }

        private IAsyncStream<string> _demoStream;

        private async Task CreateStreamSubscribe()
        {
            var clientStreamName = "clientStream001";
            var streamProvider = client.GetStreamProvider(GlobalValueDefinition.StreamProviderName);
            var streamId = StreamId.Create(GlobalValueDefinition.ImplicitStreamSubscriptionName, clientStreamName);
            _demoStream = streamProvider.GetStream<string>(streamId);//流的内容可为容易可序列化类实例
            await _demoStream.SubscribeAsync<string>(
                async (data, token) =>
                {
                    ShowLog($"*****Hello {data}");
                    await Task.CompletedTask;
                });
        }

        private async void button2_Click(object sender, EventArgs e)
        {
            await host.StopAsync();
        }

        private async void button3_Click(object sender, EventArgs e)
        {
            string response = await friend1.SayHello("Hi friend!");
            ShowLog($"{friend1.GetPrimaryKeyString()}  {response}");


        }
        private void ShowLog(string log)
        {
            Invoke(new Action(() =>
            {
                textBox1.AppendText($"{log}\r\n");
            }));
        }

        private async void button4_Click(object sender, EventArgs e)
        {
            string response = await friend2.SayHello("Hi friend!");
            ShowLog($"{friend2.GetPrimaryKeyString()}  {response}");
        }

        private async void button5_Click(object sender, EventArgs e)
        {
            IHello friend = client.GetGrain<IHello>("user1");
            var dateLen = Convert.ToInt32(textBox_DataLen.Text);
            var result = await friend.GetResultA(new ParamA { ID = 1, Name = "henreash", DataLen = dateLen });
            ShowLog($"GetResultA({result.ID} {result.Name}  {result.Data.Length})");
        }

        private void button6_Click(object sender, EventArgs e)
        {
            friend2.NotifyOtherGrain(friend1.GetPrimaryKeyString(), "让user2转告user1");
        }

        private async void button7_Click(object sender, EventArgs e)
        {
            await _demoStream.OnNextAsync("Send test stream message!");
        }
    }
}

结论:Orleans部署不需要额外的平台,编译后的进程直接启动;通过配置即可做集群扩展.grain和grain间,grain和客户端间很方便实现双向tcp通信.关闭silo服务,客户端访问失败,启动soli服务后客户端可继续访问.

服务器增加Dashboard

silo引入nuget库OleansDashboard(8.2.0),配置增加如下内容

可方便查看silo服务器的性能数据 

 集群部署

ClusterID、ServiceID不变,多个服务端使用不同的IP和端口号;

客户端使用相同的ClusterID、ServiceID,连接时gateway指定多个IPEndPoint即可测试。

注意:这种方式仍旧是开发模式,测试发现服务端无法获取其他silo上的grain实例,而是在本地重新创建一个同名的grain实例,无法实现集群效果。为了完整实现orleans的效果,必须部署集群,下章使用MySql数据库配置集群。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值