C# 在表存储上执行事件源预测

C# 在表存储上执行事件源预测,c#,.net,azure-table-storage,event-sourcing,C#,.net,Azure Table Storage,Event Sourcing,我正在创建一个小型的事件源类型函数应用程序,其中每次调用函数都会将事件写入表存储。此类事件的一个例子是: +------------+---------------+-----------------+ | Event | Timestamp | Destination | +------------+---------------+-----------------+ | Connect | 7/1/2019 4:52 | sftp.alex.com | |

我正在创建一个小型的事件源类型函数应用程序,其中每次调用函数都会将事件写入表存储。此类事件的一个例子是:

+------------+---------------+-----------------+
|   Event    |   Timestamp   |   Destination   |
+------------+---------------+-----------------+
| Connect    | 7/1/2019 4:52 | sftp.alex.com   |
| Disconnect | 7/1/2019 4:53 | sftp.liza.com   |
| Connect    | 7/1/2019 4:54 | sftp.yomama.com |
| Connect    | 7/1/2019 4:54 | sftp.alex.com   |
| Connect    | 7/1/2019 4:59 | sftp.liza.com   |
| Disconnect | 7/1/2019 4:59 | sftp.alex.com   |
| Disconnect | 7/1/2019 4:59 | sftp.yomama.com |
| Connect    | 7/1/2019 5:03 | sftp.alex.com   |
+------------+---------------+-----------------+
如何在此表上创建投影?

我需要回答的主要问题是:


每个目标当前有多少个连接?

基本思想是通过聚合重播事件以获取当前状态。下面是说明它的代码。警告:这不是一个生产代码,它甚至没有编译

公共类连接计数器
{
专用字典_计数器=新字典();
公共IEnumerable GetCounters()
{
返回_counters.Values;
}
公共无效句柄(ConnectionEvent@event)
{
var counter=GetOrCreateCounter(@event.Destination);
if(@event是ConnectEvent)
counter.ConnectionCount+=1;
if(@event是DisconnectEvent)
counter.ConnectionCount-=1;
}
专用连接计数器GetOrCreateCounter(字符串目标)
{
如果(_counters.ContainsKey(目的地))
返回计数器[目的地];
var counter=newconnectioncounter(){Destination=Destination};
_计数器[目的地]=计数器;
返回计数器;
}
}
公共类连接计数器
{
公共字符串目标{get;set;}
public int ConnectionCount{get;set;}
}
公共类ConnectEvent:ConnectionEvent{}
公共类DisconnectEvent:ConnectionEvent{}
公共类连接事件
{
公共字符串目标{get;set;}
}
// .....
私有连接计数器_ConnectionCounters=新连接计数器();
公共图书馆
{
var events=ReadEvents();//以某种方式读取事件
foreach(事件中的var@event)
{
_connectionCounters.Handle(@event);
}
foreach(在_connectionCounters.GetCounters()中的变量计数器)
Console.WriteLine($“{counter.Destination}有{counter.ConnectionCount}个连接。”)
}

我想表中会有很多记录,对所有记录进行迭代是不可取的。
这里有几个想法:

  • 你就不能记录下连接的数量吗

    这将是最简单的解决办法。我不知道你的应用程序以及它是如何与Azure通信的,但至少有(不过,从时间上看,你需要使用一些额外的服务…比如队列存储)。在它们中,您应该能够在一个单独的表中存储到每个目的地的当前连接数,在
    Connect
    事件中递增,在
    Disconnect
    事件中递减

    但如果您只有一个编写器(一个与Azure通信的服务器),您就可以在代码内部跟踪连接

    此外,您还可以将表的当前连接数保存在一个额外字段中。作为奖励,您将能够在过去的任何给定时间立即获得大量连接(以内存为代价)

  • 当你谈论事件来源时。。。那也许你应该再用一次?想法仍然是一样的:您跟踪
    Connect
    Disconnect
    事件,但在一些外部接收器中。当你正在编写事件源风格的功能应用程序时,我相信创建一个应该很容易。而且你不必依赖额外的Azure服务

    那么,与第一个想法的唯一区别是,如果接收者死亡或断开连接或其他什么——只要记住它接收到的最后一个事件,当接收者重新联机时,只迭代较年轻的事件

    您应该记住的最后一个接收到的事件(加上计数器)本质上就是其他人在评论中谈论的快照


  • 投影应该与事件流分离,因为它们是业务驱动的,而事件流纯粹是一个技术方面

    我假设您将使用SQL来持久化预测以简化答案,但是任何键/值数据存储都可以

    您可以创建具有以下结构的
    DestinationEvents
    表:

    +------------------+-----------------+-------------------+
    |   Destination    |   Connections   |   Disconnections  |
    +------------------+-----------------+-------------------+
    | sftp.alex.com    |        3        |        1          |
    | sftp.liza.com    |        1        |        1          |
    +------------------+-----------------+-------------------+
    
    通过适当的索引,这将提供快速读取和写入。对于额外速度,考虑一些像ReDIS来缓存你的投影。

    棘手的一点是在解决方案设计中,您希望它能够扩展。 一种简单的方法可能是为事件流中的每次写入设置一个SQL触发器,但如果您有大量写入,这将降低您的速度

    如果您想要可伸缩性,您需要开始考虑预算(时间和金钱)和业务需求。预测是否需要实时可用

    • 如果没有,您可以有一个按一定间隔(每天、每小时、每周等)计算/更新预测的计划流程
    • 如果是,您需要开始查看队列/消息代理(RabbitMQ、Kafka等)。现在进入生产者/消费者逻辑。你的应用程序是制作人,它发布事件。EventStream和Projections存储是消费者,它们侦听、转换和持久化传入事件。队列/MessageBroker本身可以替换事件流表,使用Kafka很容易
    如果您只是想学习,请首先使用
    字典定义内存中的投影存储,其中Destination充当键,
    (int-Connections,int-Disconnections)
    是元组/类


    如果您想支持其他投影,内存中的存储可以是一个
    字典,其中外部字典键是投影名称。

    这是一个简单的计数器,可以在线程之间安全地共享,以计算每个事件目标的连接数,您可以将其作为服务注入到所有获得连接和断开连接事件的位置

    用法示例:

        static void Main(string[] args)
        {
            ConnectionsManager connectionsCounter = new ConnectionsManager();
    
            connectionsCounter.Connnect("sftp.alex.com");
            connectionsCounter.Connnect("sftp.liza.com");
            connectionsCounter.Connnect("sftp.alex.com");
            connectionsCounter.Disconnnect("sftp.alex.com");
            connectionsCounter.Connnect("sftp.alex.com");
    
            Console.WriteLine($"Count of {"sftp.alex.com"} is {connectionsCounter.GetConnectionCount("sftp.alex.com")}");
    
            Console.WriteLine(Environment.NewLine + "Count : " + Environment.NewLine);
            foreach (var kvp in connectionsCounter.GetAllConnectionsCount())
            {
                Console.WriteLine($"Count of {kvp.Key} is {kvp.Value}");
            }
        }
    
    输出:

    Count of sftp.alex.com is 2
    
    Count :
    
    Count of sftp.alex.com is 2
    Count of sftp.liza.com is 1
    
    连接管理器代码:

    public class ConnectionsManager
    {
        private ConcurrentDictionary<string, long> _destinationCounter;
    
        public ConnectionsManager()
        {
            _destinationCounter = new ConcurrentDictionary<string, long>();
        }
    
        public long Connnect(string destination)
        {
            long count = _destinationCounter.TryGetValue(destination, out long currentCount)
                ? currentCount + 1 : 1;
            _destinationCounter[destination] = count;
            return count;
        }
    
        public long Disconnnect(string destination)
        {
            if (_destinationCounter.TryGetValue(destination, out long count))
            {
                count--;
                if (count < 0) { } // Something went wrong
    
                _destinationCounter[destination] = count;
                return count;
            }
            throw new ArgumentException("Destionation not found", nameof(destination));
        }
    
        public long GetConnectionCount(string destination)
        {
            if (_destinationCounter.TryGetValue(destination, out long count))
                return count;
            throw new ArgumentException("Destionation not found", nameof(destination));
        }
    
        public Dictionary<string, long> GetAllConnectionsCount()
        {
            return new Dictionary<string, long>(_destinationCounter);
        }
    }
    
    pub
    
    public class ConnectionsManager
    {
        private ConcurrentDictionary<string, long> _destinationCounter;
    
        public ConnectionsManager()
        {
            _destinationCounter = new ConcurrentDictionary<string, long>();
        }
    
        public long Connnect(string destination)
        {
            long count = _destinationCounter.TryGetValue(destination, out long currentCount)
                ? currentCount + 1 : 1;
            _destinationCounter[destination] = count;
            return count;
        }
    
        public long Disconnnect(string destination)
        {
            if (_destinationCounter.TryGetValue(destination, out long count))
            {
                count--;
                if (count < 0) { } // Something went wrong
    
                _destinationCounter[destination] = count;
                return count;
            }
            throw new ArgumentException("Destionation not found", nameof(destination));
        }
    
        public long GetConnectionCount(string destination)
        {
            if (_destinationCounter.TryGetValue(destination, out long count))
                return count;
            throw new ArgumentException("Destionation not found", nameof(destination));
        }
    
        public Dictionary<string, long> GetAllConnectionsCount()
        {
            return new Dictionary<string, long>(_destinationCounter);
        }
    }