前言
大家是否还在为应用程序的数据存储性能而苦恼?SQLite太重,内存存储又不够持久化,Redis需要额外的服务器资源……传统的数据存储方案似乎总是在性能、资源占用和易用性之间艰难权衡。
今天给大家介绍 Lightning.NET,可以解决这一痛点!它是 OpenLDAP LMDB 的 .NET 包装器,提供了内存级别的读取速度、零配置的嵌入式部署,以及事务级别的数据安全保障。
如果大家想找一个轻量级、高性能的本地数据存储解决方案,这篇文章将彻底改变你的技术选型思路。
问题分析:传统键值存储的三大痛点
性能瓶颈
传统的 SQLite 在大量读写操作下性能不佳,而内存数据库又面临数据持久化问题。经常需要在性能和数据安全之间做出妥协。
部署复杂度
Redis、MongoDB 等解决方案虽然性能优秀,但需要独立的服务器进程,增加了部署和运维的复杂度,对于桌面应用或边缘计算场景并不友好。
资源消耗
许多数据库解决方案消耗大量内存和 CPU 资源,对于资源受限的环境(如 IoT 设备、移动应用)来说负担过重。
Lightning.NET:终极解决方案
什么是 Lightning.NET?
Lightning.NET 是 OpenLDAP LMDB 的 .NET 包装库,LMDB(Lightning Memory-Mapped Database)是一个超快、超小的键值存储引擎。它使用内存映射文件技术,实现了读取性能接近内存数据库,同时保证数据持久化的完美平衡。
核心优势:
Nuget 安装库

实战演练:五大应用场景
快速入门:基础键值操作
using System;
using System.Text;
using LightningDB;
namespaceAppLightningDB
{
internalclassProgram
{
staticvoidMain(string[] args)
{
// 创建环境和数据库
usingvar env = new LightningEnvironment("./mydata");
env.MapSize = 1024 * 1024 * 100; // 100MB
env.Open();
// 写入数据
using (var tx = env.BeginTransaction())
{
usingvar db = tx.OpenDatabase();
var key1 = Encoding.UTF8.GetBytes("user:1001");
var value1 = Encoding.UTF8.GetBytes("张三");
tx.Put(db, key1, value1);
var key2 = Encoding.UTF8.GetBytes("user:1002");
var value2 = Encoding.UTF8.GetBytes("李四");
tx.Put(db, key2, value2);
tx.Commit();
}
// 读取数据
using (var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly))
{
usingvar db = tx.OpenDatabase();
var keyBytes = Encoding.UTF8.GetBytes("user:1001");
var (resultCode, _, value) = tx.Get(db, keyBytes);
if (resultCode == MDBResultCode.Success)
{
var userName = Encoding.UTF8.GetString(value.AsSpan());
Console.WriteLine($"用户姓名: {userName}");
}
}
}
}
}

实际应用场景:用户会话管理、配置存储、临时数据缓存
常见坑点:记得设置合适的 MapSize,这是数据库的最大容量限制
高性能缓存层:替代 MemoryCache
using LightningDB;
using System;
using System.Text;
using System.Text.Json;
namespaceAppLightningDB
{
publicclassLightningCache<T> : IDisposable
{
privatereadonly LightningEnvironment _env;
publicLightningCache(string path)
{
_env = new LightningEnvironment(path);
_env.MapSize = 1024 * 1024 * 500; // 500MB缓存空间
_env.Open();
}
publicvoidSet(string key, T value, TimeSpan? expiry = null)
{
var cacheEntry = new CacheEntry<T>
{
Value = value,
ExpiryTime = expiry.HasValue ? DateTime.UtcNow.Add(expiry.Value) : null
};
var serialized = JsonSerializer.Serialize(cacheEntry);
var keyBytes = Encoding.UTF8.GetBytes(key);
var valueBytes = Encoding.UTF8.GetBytes(serialized);
usingvar tx = _env.BeginTransaction();
usingvar db = tx.OpenDatabase();
tx.Put(db, keyBytes, valueBytes);
tx.Commit();
}
public T Get(string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
usingvar tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
usingvar db = tx.OpenDatabase();
var (resultCode, _, value) = tx.Get(db, keyBytes);
if (resultCode != MDBResultCode.Success)
returndefault(T);
var data = Encoding.UTF8.GetString(value.AsSpan());
var cacheEntry = JsonSerializer.Deserialize<CacheEntry<T>>(data);
// 检查过期时间
if (cacheEntry.ExpiryTime.HasValue && cacheEntry.ExpiryTime < DateTime.UtcNow)
{
Remove(key); // 清理过期数据
returndefault(T);
}
return cacheEntry.Value;
}
publicboolRemove(string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
usingvar tx = _env.BeginTransaction();
usingvar db = tx.OpenDatabase();
var result = tx.Delete(db, keyBytes);
if (result == MDBResultCode.Success)
{
tx.Commit();
returntrue;
}
returnfalse;
}
publicboolContainsKey(string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
usingvar tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
usingvar db = tx.OpenDatabase();
var (resultCode, _, _) = tx.Get(db, keyBytes);
return resultCode == MDBResultCode.Success;
}
publicvoidDispose()
{
_env?.Dispose();
}
privateclassCacheEntry<TValue>
{
public TValue Value { get; set; }
public DateTime? ExpiryTime { get; set; }
}
}
}
using System;
using System.Text;
using LightningDB;
namespaceAppLightningDB
{
internalclassProgram
{
staticvoidMain(string[] args)
{
usingvar cache = new LightningCache<string>("./cache");
// 设置缓存
cache.Set("user:1001", "张三", TimeSpan.FromMinutes(30));
cache.Set("user:1002", "李四"); // 永不过期
// 获取缓存
var user1 = cache.Get("user:1001");
var user2 = cache.Get("user:1002");
Console.WriteLine($"用户1: {user1}");
Console.WriteLine($"用户2: {user2}");
// 检查键是否存在
Console.WriteLine($"包含user:1001: {cache.ContainsKey("user:1001")}");
// 删除缓存
cache.Remove("user:1001");
Console.WriteLine($"删除后包含user:1001: {cache.ContainsKey("user:1001")}");
}
}
}

实际应用场景:API 响应缓存、计算结果缓存、会话状态存储
常见坑点:Lightning.NET 不支持自动过期,需要手动实现 TTL 机制
事务处理:确保数据一致性
using LightningDB;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespaceAppLightningDB
{
publicclassOrderService : IDisposable
{
privatereadonly LightningEnvironment _env;
publicOrderService(string dbPath)
{
_env = new LightningEnvironment(dbPath);
_env.MapSize = 1024 * 1024 * 200; // 200MB
_env.Open();
}
publicboolCreateOrder(Order order, List<OrderItem> items)
{
try
{
usingvar tx = _env.BeginTransaction();
usingvar db = tx.OpenDatabase();
// 检查库存
foreach (var item in items)
{
var stockKey = Encoding.UTF8.GetBytes($"stock:{item.ProductId}");
var (resultCode, _, value) = tx.Get(db, stockKey);
var currentStock = 0;
if (resultCode == MDBResultCode.Success)
{
var stockValue = Encoding.UTF8.GetString(value.AsSpan());
int.TryParse(stockValue, out currentStock);
}
if (currentStock < item.Quantity)
{
thrownew InvalidOperationException($"库存不足: 产品 {item.ProductId}, 需要 {item.Quantity}, 当前库存 {currentStock}");
}
}
// 创建订单记录
var orderKey = Encoding.UTF8.GetBytes($"order:{order.OrderId}");
var orderJson = JsonSerializer.Serialize(order);
var orderValue = Encoding.UTF8.GetBytes(orderJson);
tx.Put(db, orderKey, orderValue);
// 减少库存并创建订单项
foreach (var item in items)
{
// 更新库存
var stockKey = Encoding.UTF8.GetBytes($"stock:{item.ProductId}");
var (resultCode, _, value) = tx.Get(db, stockKey);
var currentStock = 0;
if (resultCode == MDBResultCode.Success)
{
var stockValue = Encoding.UTF8.GetString(value.AsSpan());
int.TryParse(stockValue, out currentStock);
}
var newStock = currentStock - item.Quantity;
var newStockValue = Encoding.UTF8.GetBytes(newStock.ToString());
tx.Put(db, stockKey, newStockValue);
// 创建订单项
var itemKey = Encoding.UTF8.GetBytes($"order_item:{order.OrderId}:{item.ProductId}");
var itemJson = JsonSerializer.Serialize(item);
var itemValue = Encoding.UTF8.GetBytes(itemJson);
tx.Put(db, itemKey, itemValue);
Console.WriteLine($"处理订单项: 产品 {item.ProductId}, 数量 {item.Quantity}, 剩余库存 {newStock}");
}
// 提交事务
tx.Commit();
Console.WriteLine($"订单 {order.OrderId} 创建成功");
returntrue;
}
catch (Exception ex)
{
Console.WriteLine($"订单创建失败: {ex.Message}");
returnfalse;
}
}
public Order? GetOrder(string orderId)
{
try
{
usingvar tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
usingvar db = tx.OpenDatabase();
var orderKey = Encoding.UTF8.GetBytes($"order:{orderId}");
var (resultCode, _, value) = tx.Get(db, orderKey);
if (resultCode != MDBResultCode.Success)
returnnull;
var orderJson = Encoding.UTF8.GetString(value.AsSpan());
return JsonSerializer.Deserialize<Order>(orderJson);
}
catch (Exception ex)
{
Console.WriteLine($"获取订单失败: {ex.Message}");
returnnull;
}
}
public List<OrderItem> GetOrderItems(string orderId)
{
var items = new List<OrderItem>();
try
{
usingvar tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
usingvar db = tx.OpenDatabase();
usingvar cursor = tx.CreateCursor(db);
var prefix = $"order_item:{orderId}:";
var prefixKey = Encoding.UTF8.GetBytes(prefix);
// 使用游标查找所有订单项
var result = cursor.SetRange(prefixKey);
if (result == MDBResultCode.Success)
{
do
{
var current = cursor.GetCurrent();
var currentKey = Encoding.UTF8.GetString(current.key.AsSpan());
// 检查是否仍然匹配前缀
if (!currentKey.StartsWith(prefix))
break;
var itemJson = Encoding.UTF8.GetString(current.value.AsSpan());
var item = JsonSerializer.Deserialize<OrderItem>(itemJson);
if (item != null)
{
items.Add(item);
}
var nextResult = cursor.Next();
if (nextResult.resultCode != MDBResultCode.Success)
break;
}
while (true);
}
}
catch (Exception ex)
{
Console.WriteLine($"获取订单项失败: {ex.Message}");
}
return items;
}
publicboolInitializeStock(string productId, int initialStock)
{
try
{
usingvar tx = _env.BeginTransaction();
usingvar db = tx.OpenDatabase();
var stockKey = Encoding.UTF8.GetBytes($"stock:{productId}");
var stockValue = Encoding.UTF8.GetBytes(initialStock.ToString());
tx.Put(db, stockKey, stockValue);
tx.Commit();
Console.WriteLine($"初始化库存: 产品 {productId}, 数量 {initialStock}");
returntrue;
}
catch (Exception ex)
{
Console.WriteLine($"初始化库存失败: {ex.Message}");
returnfalse;
}
}
publicintGetCurrentStock(string productId)
{
try
{
usingvar tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
usingvar db = tx.OpenDatabase();
var stockKey = Encoding.UTF8.GetBytes($"stock:{productId}");
var (resultCode, _, value) = tx.Get(db, stockKey);
if (resultCode == MDBResultCode.Success)
{
var stockValue = Encoding.UTF8.GetString(value.AsSpan());
if (int.TryParse(stockValue, outvar stock))
{
return stock;
}
}
return0;
}
catch (Exception ex)
{
Console.WriteLine($"获取库存失败: {ex.Message}");
return0;
}
}
publicvoidDispose()
{
_env?.Dispose();
}
}
// 数据模型类
publicclassOrder
{
publicstring OrderId { get; set; } = string.Empty;
publicstring CustomerId { get; set; } = string.Empty;
public DateTime OrderDate { get; set; }
publicdecimal TotalAmount { get; set; }
publicstring Status { get; set; } = "Created";
}
publicclassOrderItem
{
publicstring ProductId { get; set; } = string.Empty;
publicstring ProductName { get; set; } = string.Empty;
publicint Quantity { get; set; }
publicdecimal UnitPrice { get; set; }
publicdecimal TotalPrice { get; set; }
}
}

实际应用场景:电商库存管理、金融交易记录、日志系统
常见坑点:LMDB 是单写多读模式,同时只能有一个写事务
高频数据存储:日志和指标收集
using LightningDB;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading;
namespaceAppLightningDB
{
publicclassMetricsCollector : IDisposable
{
privatereadonly LightningEnvironment _env;
privatereadonly Timer _flushTimer;
privatereadonly ConcurrentQueue<MetricEntry> _buffer = new();
publicMetricsCollector(string dbPath)
{
_env = new LightningEnvironment(dbPath);
_env.MapSize = 1024 * 1024 * 1024; // 1GB用于指标存储
_env.Open();
// 每10秒批量写入一次
_flushTimer = new Timer(FlushMetrics!, null,
TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));
}
publicvoidRecordMetric(string name, doublevalue, Dictionary<string, string>? tags = null)
{
var entry = new MetricEntry
{
Name = name,
Value = value,
Tags = tags ?? new Dictionary<string, string>(),
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
_buffer.Enqueue(entry);
}
privatevoidFlushMetrics(object? state)
{
var batch = new List<MetricEntry>();
// 取出缓冲区中的数据
while (_buffer.TryDequeue(outvar entry) && batch.Count < 1000)
{
batch.Add(entry);
}
if (batch.Count == 0) return;
try
{
usingvar tx = _env.BeginTransaction();
usingvar db = tx.OpenDatabase();
foreach (var entry in batch)
{
var key = $"metric:{entry.Name}:{entry.Timestamp}";
varvalue = JsonSerializer.Serialize(entry);
var keyBytes = Encoding.UTF8.GetBytes(key);
var valueBytes = Encoding.UTF8.GetBytes(value);
tx.Put(db, keyBytes, valueBytes);
}
tx.Commit();
Console.WriteLine($"已写入 {batch.Count} 条指标数据");
}
catch (Exception ex)
{
// 写入失败,将数据重新入队
foreach (var entry in batch)
{
_buffer.Enqueue(entry);
}
Console.WriteLine($"指标写入失败: {ex.Message}");
}
}
public List<MetricEntry> QueryMetrics(string name, long fromTimestamp, long toTimestamp)
{
var results = new List<MetricEntry>();
usingvar tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
usingvar db = tx.OpenDatabase();
usingvar cursor = tx.CreateCursor(db);
var startKey = $"metric:{name}:{fromTimestamp}";
var startKeyBytes = Encoding.UTF8.GetBytes(startKey);
var endKey = $"metric:{name}:{toTimestamp}";
// 使用 SetRange 定位到起始位置
var setRangeResult = cursor.SetRange(startKeyBytes);
if (setRangeResult == MDBResultCode.Success)
{
do
{
// 获取当前键值对
var current = cursor.GetCurrent();
var currentKeySpan = current.key.AsSpan();
var currentKeyString = Encoding.UTF8.GetString(currentKeySpan);
if (string.Compare(currentKeyString, endKey, StringComparison.Ordinal) > 0)
break;
var valueSpan = current.value.AsSpan();
var entryJson = Encoding.UTF8.GetString(valueSpan);
var entry = JsonSerializer.Deserialize<MetricEntry>(entryJson);
if (entry != null)
{
results.Add(entry);
}
// 移动到下一个记录
var nextResult = cursor.Next();
if (nextResult.resultCode != MDBResultCode.Success)
break;
}
while (true);
}
return results;
}
// 添加清理过期数据的方法
publicvoidCleanupOldMetrics(long olderThanTimestamp)
{
usingvar tx = _env.BeginTransaction();
usingvar db = tx.OpenDatabase();
usingvar cursor = tx.CreateCursor(db);
var keysToDelete = new List<byte[]>();
// 从第一个记录开始遍历
var firstResult = cursor.First();
if (firstResult.resultCode == MDBResultCode.Success)
{
do
{
var current = cursor.GetCurrent();
var keySpan = current.key.AsSpan();
var keyString = Encoding.UTF8.GetString(keySpan);
// 解析时间戳
if (keyString.StartsWith("metric:"))
{
var parts = keyString.Split(':');
if (parts.Length >= 3 && long.TryParse(parts[2], outvar timestamp))
{
if (timestamp < olderThanTimestamp)
{
keysToDelete.Add(keySpan.ToArray());
}
}
}
// 移动到下一个记录
var nextResult = cursor.Next();
if (nextResult.resultCode != MDBResultCode.Success)
break;
}
while (true);
}
// 删除过期数据
foreach (var keyBytes in keysToDelete)
{
tx.Delete(db, keyBytes);
}
if (keysToDelete.Count > 0)
{
tx.Commit();
Console.WriteLine($"清理了 {keysToDelete.Count} 条过期指标数据");
}
}
publicvoidDispose()
{
_flushTimer?.Dispose();
// 最后一次刷新缓冲区
FlushMetrics(null);
_env?.Dispose();
}
publicclassMetricEntry
{
publicstring Name { get; set; } = string.Empty;
publicdouble Value { get; set; }
public Dictionary<string, string> Tags { get; set; } = new();
publiclong Timestamp { get; set; }
}
}
}
using System;
using System.Text;
using LightningDB;
namespaceAppLightningDB
{
internalclassProgram
{
staticvoidMain(string[] args)
{
usingvar collector = new MetricsCollector("./metrics");
Console.WriteLine("开始收集指标数据...");
// 记录性能指标
collector.RecordMetric("cpu_usage", 75.2, new() { ["host"] = "web01" });
collector.RecordMetric("memory_usage", 68.5, new() { ["host"] = "web01" });
collector.RecordMetric("disk_io", 12.3, new() { ["host"] = "web01", ["disk"] = "sda" });
// 等待数据写入
Thread.Sleep(12000);
// 查询历史数据
var fromTime = DateTimeOffset.UtcNow.AddMinutes(-5).ToUnixTimeSeconds();
var toTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var metrics = collector.QueryMetrics("cpu_usage", fromTime, toTime);
Console.WriteLine($"查询到 {metrics.Count} 条 CPU 使用率数据");
// 清理老数据(1小时前的数据)
var oldTime = DateTimeOffset.UtcNow.AddHours(-1).ToUnixTimeSeconds();
collector.CleanupOldMetrics(oldTime);
Console.WriteLine("指标收集完成!");
}
}
}

实际应用场景:应用性能监控、用户行为分析、系统日志存储
常见坑点:大量小写入会影响性能,建议使用批量写入策略
总结
性能优化技巧
- 合理设置 MapSize:根据实际需求设置,过小会限制容量,过大会浪费地址空间
- 批量操作:使用事务批量处理多个操作,减少磁盘同步次数
- 键设计优化:使用有序的键名,利用 LMDB 的 B+树结构提升范围查询性能
常见陷阱
1、单写多读限制:同时只能有一个写事务,高并发写入需要队列化处理
2、数据库大小不可缩减:删除数据后文件大小不会自动减小,需要定期压缩
3、跨平台兼容性:Windows 和 Linux 的文件系统差异可能影响性能
4、内存映射限制:在 32 位系统上地址空间有限,大型数据库需要 64 位环境
三个核心要点
1、性能革命:Lightning.NET 通过内存映射技术实现了读取性能的质的飞跃,特别适合读取密集型应用场景
2、简化架构:零配置的嵌入式设计让你告别复杂的数据库运维,专注于业务逻辑开发
3、生产就绪:ACID 事务支持和久经考验的 LMDB 内核,确保了数据的安全性和系统的稳定性
Lightning.NET 不仅仅是一个数据存储库,更是提升 C# 应用性能的神器。不管是桌面应用的本地缓存、Web 服务的会话存储,还是 IoT 设备的指标收集,它都能为项目带来显著的性能提升。
关键词
Lightning.NET、#LMDB、#嵌入式数据库、#键值存储、C#、#高性能、#事务、#内存映射、#本地存储、.NET
阅读原文:原文链接
该文章在 2026/1/7 16:50:12 编辑过