Natify 1.0.3

dotnet add package Natify --version 1.0.3
                    
NuGet\Install-Package Natify -Version 1.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Natify" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Natify" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="Natify" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Natify --version 1.0.3
                    
#r "nuget: Natify, 1.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Natify@1.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Natify&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=Natify&version=1.0.3
                    
Install as a Cake Tool

Natify — High-Performance Communication Framework

Built on NATS Core | Version 1.0.2 | netstandard2.0

Framework giao tiếp hiệu năng cao cho Microservice và Game Server (tối ưu Unity), xây trên nền NATS Core.

Dependencies

Package Version Purpose
Google.Protobuf 3.34.1 Serialization
NATS.Net (NATS.Client.Core) 2.7.3 Network transport
System.Threading.Channels built-in Lock-free queue

Không cần NATS JetStream.


Architecture Deep Dive

1. Queue Group & Horizontal Scaling

Đây là điểm quan trọng nhất — dễ bị hiểu nhầm.

Tham số groupName trong constructor chính là NATS queue group. Khi N instance cùng groupName subscribe cùng subject, NATS Core tự động giao mỗi message cho đúng 1 instance duy nhất:

// Trong OnMessage — NatifyClient.cs:290 và NatifyServer.cs:293
_connection.SubscribeAsync<byte[]>(subject, queueGroup: _groupName, ...)

Có thể scale N instance an toàn, không lo duplicate processing.

Ví dụ Router scale ngang:

// Router instance 1
new NatifyServer("nats://localhost:4222", "Router", "RouterGroup", "*");

// Router instance 2 — cùng serverName + groupName
new NatifyServer("nats://localhost:4222", "Router", "RouterGroup", "*");

// NATS chỉ gửi mỗi message từ Unity đến 1 trong 2 instance

2. Dedup Scope (Chống trùng lặp)

Dedup trong Natify (_processedMessages + TimedSortedSet, TTL 10s) dùng để chặn cùng 1 instance xử lý cùng 1 batch 2 lần do retransmission (at-least-once retry).

Đây KHÔNG phải cross-instance dedup. Cross-instance được NATS queue group xử lý.

Cùng 1 instance:         sender → NATS → receiver → ACK lost → retry → receiver
                                                                    ↓
                         dedup ngăn không xử lý batch lần 2       ✓

Nhiều instance:          sender → NATS → instance-1 (nhận) + instance-2 (không nhận)
                         queue group đảm bảo chỉ 1 instance nhận  ✓

3. Thread Model

Class Callback thread Cần Tick()?
NatifyClient (Unity) Main Thread (qua Tick())
NatifyClientFast (Backend) ThreadPool (ngay lập tức) KHÔNG
NatifyServer ThreadPool (ngay lập tức) KHÔNG
Trigger evaluation ThreadPool (mỗi 500ms) KHÔNG

Quan trọng: Trong Unity, NatifyClient đẩy callback vào ConcurrentQueue<Action>, Tick() lấy tối đa 100 action/frame xử lý trên Main Thread. Không gọi Tick() = callback không bao giờ chạy.

4. Topic Convention (NatifyTopics)

Tất cả subject được auto-generate — code chỉ cần dùng topic "clean", không prefix:

Direction Method Generated Subject
Client → Server (publish) GetClientPublishSubject NatifyServer.{server}.{client}.{region}.{topic}
Server → Client (publish) GetServerPublishSubject NatifyClient.{client}.{server}.{region}.{topic}
Server listen (wildcard region) GetServerListenSubject NatifyServer.{server}.{client}.*.{topic}
Client listen GetClientListenSubject NatifyClient.{client}.{server}.{region}.{topic}
  • * trong server listen subject là wildcard cho REGION, không phải clientName.
  • Server luôn nhận được regionId trong callback để biết message từ region nào.

5. Request-Reply Mechanism

Client gọi RequestAsync("Login", req, timeout)
    │
    ├── Publish("Login", req, "REQ", out reqId)
    │       → NATS: NatifyServer.Srv.Client.VN.Login
    │
    ├──_replyTasks[reqId] = (TCS, CTS)     // chờ reply
    │
    │   Server xử lý OnRequest:
    │       → return reply
    │       → Publish("Rep-{instanceId}", reply, "REP", repId=reqId)
    │           → NATS: NatifyClient.Client.Srv.VN.Rep-{instanceId}
    │
    └── Nhận reply → _replyTasks[reqId].SetResult(bytes)
            → Deserialize<TRes> → return

Server trả lời qua topic Rep-{instanceId} — instanceId là Guid.NewGuid() của client, đảm bảo reply về đúng client gửi request.

6. Micro-Batching

Message được gom vào Channel<(subject, payload, type, reqId, repId)>, background worker flush batch khi đạt:

  • 1000 messages hoặc
  • 50KB payload hoặc
  • 50ms kể từ message đầu tiên

Batch được serialize thành NatifyBatch protobuf, publish kèm Natify-BatchId header.

7. Reliability (ACK + Retry)

Mỗi batch gửi đi được lưu vào _unackedMessages. Retry loop quét mỗi 100ms:

  • Nếu now - LastSent > 100ms → retry (tối đa 10 lần)
  • Receiver gửi ACK về NatifyServer.{srv}.{client}.{region}.ACK.{batchId}
  • Khi nhận ACK → xóa khỏi _unackedMessages
  • Receiver dùng dedup để bỏ qua batch đã xử lý nếu retry đến

8. Graceful Shutdown (Dispose)

1. batchChannel.Writer.Complete()           // Khóa van đầu vào
2. Task.WaitAll(batchWorker, 2s)            // Chờ xả nốt batch cuối
3. Chờ ACK drain (2s)                       // Đợi đối tác xác nhận
4. _cts.Cancel()                            // Ngắt vòng lặp ngầm
5. Task.WaitAll(retryWorker, ackListener, 1s) // Chờ luồng tắt
6. _connection.DisposeAsync()               // Đóng kết nối
7. _messageTtlWheel.Dispose()               // Giải phóng time wheel

Complete API Reference

NatifyServer

public class NatifyServer : IDisposable
Member Signature
ctor NatifyServer(string url, string serverName, string groupName, string clientNameToConnect)
Publish void Publish<T>(string topic, string regionId, T msg) where T : IMessage
OnMessage void OnMessage<T>(string topic, Action<(string regionId, Data<T> data)> cb) where T : IMessage, new()
OnMessage void OnMessage<T>(string topic, Func<(string regionId, Data<T> data), Task> cb)
RequestAsync Task<TRes> RequestAsync<TReq, TRes>(string topic, string regionId, TReq data, TimeSpan timeout)
OnRequest void OnRequest<TReq, TRep>(string topic, Func<(string regionId, TReq req), TRep> handler)
OnRequest void OnRequest<TReq, TRep>(string topic, Func<(string regionId, TReq req), Task<TRep>> handlerAsync)
Trigger NatifyServerTriggers Trigger { get; }
Dispose void Dispose()

Tham số constructor:

  • url — NATS URL (vd: "nats://localhost:4222")
  • serverName — Định danh server này (xuất hiện trong NATS subject)
  • groupNameNATS queue group → dùng chung cho tất cả instance cùng loại để scale ngang
  • clientNameToConnect — Tên client pattern để listen. Dùng "*" để match mọi client.

NatifyClient (Unity)

public class NatifyClient : IDisposable
Member Signature
ctor NatifyClient(string url, string clientName, string groupName, string regionId, string serverNameToConnect)
Publish void Publish<T>(string topic, T msg) where T : IMessage
OnMessage void OnMessage<T>(string topic, Action<Data<T>> cb) where T : IMessage, new()
OnMessage void OnMessage<T>(string topic, Func<Data<T>, Task> cb)
RequestAsync Task<TRes> RequestAsync<TReq, TRes>(string topic, TReq data, TimeSpan timeout)
OnRequest void OnRequest<TReq, TRep>(string topic, Func<TReq, TRep> handler)
OnRequest void OnRequest<TReq, TRep>(string topic, Func<TReq, Task<TRep>> handlerAsync)
Tick void Tick()Bắt buộc gọi trong Update(), xử lý tối đa 100 callback/frame
Trigger NatifyClientTriggers Trigger { get; }
Dispose void Dispose()

NatifyClientFast (Backend / Console)

public class NatifyClientFast : IDisposable

API giống hệt NatifyClient nhưng:

  • Không có Tick() — callback chạy ngay trên ThreadPool
  • Callback truyền vào OnMessage nhận Data<T> (không qua hàng đợi main thread)

Data<T> — Message Envelope

public readonly struct Data<T>
{
    public T Value;           // Đã deserialize
    public string InstanceId; // Instance gửi
    public string ReqId;      // Request correlation ID
    public string RepId;      // Reply correlation ID
}

NatifyServer callbacks — Tuple pattern

server.OnMessage<MyMsg>("topic", tuple => {
    var regionId    = tuple.regionId;  // region của client gửi
    var data        = tuple.data;      // Data<MyMsg> envelope
    var msg         = data.Value;      // MyMsg đã deserialize
});

Trigger Telemetry

NatifyClientTriggers / NatifyServerTriggers — cả hai giống nhau:

Property Type Mô tả
BytesSent long Tổng bytes đã gửi
BytesReceived long Tổng bytes đã nhận
MessagesSent long Tổng messages riêng lẻ đã gửi
MessagesReceived long Tổng messages riêng lẻ đã nhận
BatchesSent / BatchesReceived long Tổng batch
ErrorsCount long Tổng lỗi
CurrentDedupCacheSize long Items trong dedup cache
TotalDedupExpired long Items đã hết hạn khỏi dedup
ProcessMemoryMB double RAM process (MB)
Method Signature
RegisterTrigger Guid RegisterTrigger(Func<T, bool> condition, Action<T> action, bool oneTime = false)
RemoveTrigger void RemoveTrigger(Guid ruleId)

Triggers được đánh giá mỗi 500ms trên ThreadPool.


Code Examples

Pub/Sub cơ bản

// Server
var server = new NatifyServer("nats://localhost:4222", "GameServer", "SrvGroup", "*");
server.OnMessage<StringValue>("Chat", tuple => {
    Console.WriteLine($"[{tuple.regionId}] {tuple.data.Value.Value}");
});

// Client (Backend)
var client = new NatifyClientFast("nats://localhost:4222", "Client1", "Grp1", "VN", "GameServer");
client.Publish("Chat", new StringValue { Value = "Hello" });

Request/Reply

// Server xử lý request
server.OnRequest<StringValue, StringValue>("Login", tuple => {
    return new StringValue { Value = tuple.request.Value + "_OK" };
});

// Client gọi request
var reply = await client.RequestAsync<StringValue, StringValue>(
    "Login", new StringValue { Value = "user123" }, TimeSpan.FromSeconds(5));
Console.WriteLine(reply.Value); // "user123_OK"

Unity Client

var client = new NatifyClient("nats://localhost:4222", "UnityClient", "GrpA", "VN", "GameServer");

client.OnMessage<Int32Value>("UpdateHealth", data => {
    healthBar.SetValue(data.Value.Value); // Chạy trên Main Thread sau Tick()
});

void Update() {
    client.Tick(); // Bắt buộc!
}

void OnDestroy() {
    client.Dispose();
}

Scale ngang (N instances cùng loại)

// Tất cả instance Router giống hệt constructor:
var router = new NatifyServer("nats://localhost:4222", "Router", "RouterGroup", "*");

// Tất cả instance Account giống hệt constructor:
var account = new NatifyClientFast("nats://localhost:4222", "AccountService", "AccountGroup", "ALL", "Router");

// Unity gửi event lên → NATS queue group "RouterGroup" → chỉ 1 Router instance nhận
// Router gửi request xuống Account → NATS queue group "AccountGroup" → chỉ 1 Account instance nhận

Giám sát

server.Trigger.RegisterTrigger(
    condition: t => t.ProcessMemoryMB > 1500,
    action: t => Console.WriteLine($"[OOM] RAM: {t.ProcessMemoryMB} MB!"),
    oneTime: false
);

Changelog

  • v1.0.2NatifyClientFast (Tick-free cho Backend). Cải thiện Request pipeline (200K RPS).
  • v1.0.1 — Fix request miss khi gửi nhiều request liên tục.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Natify:

Package Downloads
PubSubLib

Package Description

PubSubLib.Router

Package Description

XelerateV2

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.3 278 6/23/2026
1.0.2 358 4/22/2026
1.0.1 105 4/20/2026
1.0.0 101 4/19/2026