我正在创建一个微型的事件源样式的函数应用程序,该函数的每次调用都会在表存储中写入一个事件。这样的事件的一个例子是:
+------------+---------------+-----------------+
| 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 |
+------------+---------------+-----------------+
如何在此桌子上创建投影?
我需要回答的主要问题是:
每个目的地当前有多少个连接?
答案 0 :(得分:7)
我想表中会有很多记录,并且对所有记录进行迭代是不可能的。
因此,这里有一些想法:
您不能只跟踪连接数吗?
那将是最简单的解决方案。我不了解您的应用及其与 Azure 的通信方式,但至少有triggers(尽管从supported bindings table来看,您需要使用一些额外的服务...例如队列存储)。并且在它们中,您应该能够将与每个目标的当前连接数存储在单独的表中,在Connect
事件中增加,在Disconnect
中减少。
但是,如果您只有一个编写器(与 Azure 进行通信的单个服务器),则可以在代码内部跟踪连接。
您还可以在一个额外的字段中保存与表的当前连接数。另外,您可以在过去的任何给定时间立即获得大量连接(以内存成本计)。
当您谈论事件源时...那么也许您应该再次使用它?想法仍然是相同的:您可以跟踪Connect
和Disconnect
事件,但要在某些外部接收器中进行。在您编写事件源样式的功能应用程序时,我相信创建它应该很容易。而且您不必依赖额外的 Azure 服务。
然后,与第一个想法的唯一区别是,如果接收器死了,断开连接或发生某种事情-请记住接收到的最后事件,并且当接收器重新联机时,仅在较年轻的事件上进行迭代。
您应该记住的最后收到的事件(加上计数器)本质上是其他人在评论中谈论的快照。
答案 1 :(得分:5)
投影应该与事件流分离,因为它们是业务驱动的,而事件流纯粹是技术方面的。
我假设您将使用SQL保留预测以简化答案,但是任何键/值数据存储都可以。
您可以使用以下结构创建一个DestinationEvents
表:
+------------------+-----------------+-------------------+
| Destination | Connections | Disconnections |
+------------------+-----------------+-------------------+
| sftp.alex.com | 3 | 1 |
| sftp.liza.com | 1 | 1 |
+------------------+-----------------+-------------------+
通过适当的索引编制,这应该可以实现快速读取和写入。为了提高速度,请考虑使用Redis之类的东西来缓存您的投影。
棘手的问题在解决方案设计中,您希望它可以扩展。 幼稚的方法可能是为事件流中的每次写入都设置一个SQL触发器,但是如果您有大量写入操作,这会降低您的速度。
如果要扩展性,您需要开始考虑预算(时间和金钱)和业务需求。预测是否需要实时可用?
如果您只是想学习,请首先使用Dictionary<string, (int Connections, int Disconnections)>
定义内存中的投影存储,其中Destination作为键,而(int Connections, int Disconnections)
是一个元组/类。
如果要支持其他Projection,则内存中的存储可以是Dictionary<string, Dictionary<string, (int Connections, int Disconnections)>>
,其中外部词典Key是Projection名称。
答案 2 :(得分:0)
基本思想是在聚合上重播事件以获取当前状态。下面是说明它的代码。 警告:这不是生产代码,甚至无法编译。
public class ConnectionCounters
{
private Dictionary<string, ConnectionCounter> _counters = new Dictionary<string, ConnectionCounter>();
public IEnumerable<ConnectionCounter> GetCounters()
{
return _counters.Values;
}
public void Handle(ConnectionEvent @event)
{
var counter = GetOrCreateCounter(@event.Destination);
if (@event is ConnectEvent)
counter.ConnectionCount += 1;
if (@event is DisconnectEvent)
counter.ConnectionCount -= 1;
}
private ConnectionCounter GetOrCreateCounter(string destination)
{
if (_counters.ContainsKey(destination))
return _counters[destination];
var counter = new ConnectionCounter() { Destination = destination };
_counters[destination] = counter;
return counter;
}
}
public class ConnectionCounter
{
public string Destination { get; set; }
public int ConnectionCount { get; set; }
}
public class ConnectEvent : ConnectionEvent { }
public class DisconnectEvent : ConnectionEvent { }
public class ConnectionEvent
{
public string Destination { get; set; }
}
// .....
private ConnectionCounters _connectionCounters = new ConnectionCounters();
public void Main()
{
var events = ReadEvents(); // read events somehow
foreach (var @event in events)
{
_connectionCounters.Handle(@event);
}
foreach (var counter in _connectionCounters.GetCounters())
Console.WriteLine($"{counter.Destination} has {counter.ConnectionCount} connections.")
}
答案 3 :(得分:0)
这是一个简单的计数器,可以在线程之间安全地共享该计数器,以计数每个事件目标的连接,您可以将其作为服务注入所有获得连接和断开事件的位置
用法示例:
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
ConnectionsManager代码:
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);
}
}