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

C#using System;
using System.Text;
using LightningDB;
namespace AppLightningDB
{
internal class Program
{
static void Main(string[] args)
{
// 创建环境和数据库
using var env = new LightningEnvironment("./mydata");
env.MapSize = 1024 * 1024 * 100; // 100MB
env.Open();
// 写入数据
using (var tx = env.BeginTransaction())
{
using var 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))
{
using var 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,这是数据库的最大容量限制
C#using LightningDB;
using System;
using System.Text;
using System.Text.Json;
namespace AppLightningDB
{
public class LightningCache<T> : IDisposable
{
private readonly LightningEnvironment _env;
public LightningCache(string path)
{
_env = new LightningEnvironment(path);
_env.MapSize = 1024 * 1024 * 500; // 500MB缓存空间
_env.Open();
}
public void Set(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);
using var tx = _env.BeginTransaction();
using var db = tx.OpenDatabase();
tx.Put(db, keyBytes, valueBytes);
tx.Commit();
}
public T Get(string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
using var tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
using var db = tx.OpenDatabase();
var (resultCode, _, value) = tx.Get(db, keyBytes);
if (resultCode != MDBResultCode.Success)
return default(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); // 清理过期数据
return default(T);
}
return cacheEntry.Value;
}
public bool Remove(string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
using var tx = _env.BeginTransaction();
using var db = tx.OpenDatabase();
var result = tx.Delete(db, keyBytes);
if (result == MDBResultCode.Success)
{
tx.Commit();
return true;
}
return false;
}
public bool ContainsKey(string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
using var tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
using var db = tx.OpenDatabase();
var (resultCode, _, _) = tx.Get(db, keyBytes);
return resultCode == MDBResultCode.Success;
}
public void Dispose()
{
_env?.Dispose();
}
private class CacheEntry<TValue>
{
public TValue Value { get; set; }
public DateTime? ExpiryTime { get; set; }
}
}
}
C#using System;
using System.Text;
using LightningDB;
namespace AppLightningDB
{
internal class Program
{
static void Main(string[] args)
{
using var 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机制
C#using LightningDB;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace AppLightningDB
{
public class OrderService : IDisposable
{
private readonly LightningEnvironment _env;
public OrderService(string dbPath)
{
_env = new LightningEnvironment(dbPath);
_env.MapSize = 1024 * 1024 * 200; // 200MB
_env.Open();
}
public bool CreateOrder(Order order, List<OrderItem> items)
{
try
{
using var tx = _env.BeginTransaction();
using var 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)
{
throw new 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} 创建成功");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"订单创建失败: {ex.Message}");
return false;
}
}
public Order? GetOrder(string orderId)
{
try
{
using var tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
using var db = tx.OpenDatabase();
var orderKey = Encoding.UTF8.GetBytes($"order:{orderId}");
var (resultCode, _, value) = tx.Get(db, orderKey);
if (resultCode != MDBResultCode.Success)
return null;
var orderJson = Encoding.UTF8.GetString(value.AsSpan());
return JsonSerializer.Deserialize<Order>(orderJson);
}
catch (Exception ex)
{
Console.WriteLine($"获取订单失败: {ex.Message}");
return null;
}
}
public List<OrderItem> GetOrderItems(string orderId)
{
var items = new List<OrderItem>();
try
{
using var tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
using var db = tx.OpenDatabase();
using var 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;
}
public bool InitializeStock(string productId, int initialStock)
{
try
{
using var tx = _env.BeginTransaction();
using var 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}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"初始化库存失败: {ex.Message}");
return false;
}
}
public int GetCurrentStock(string productId)
{
try
{
using var tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
using var 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, out var stock))
{
return stock;
}
}
return 0;
}
catch (Exception ex)
{
Console.WriteLine($"获取库存失败: {ex.Message}");
return 0;
}
}
public void Dispose()
{
_env?.Dispose();
}
}
// 数据模型类
public class Order
{
public string OrderId { get; set; } = string.Empty;
public string CustomerId { get; set; } = string.Empty;
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public string Status { get; set; } = "Created";
}
public class OrderItem
{
public string ProductId { get; set; } = string.Empty;
public string ProductName { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal TotalPrice { get; set; }
}
}

💡 实际应用场景:电商库存管理、金融交易记录、日志系统
⚠️ 常见坑点:LMDB是单写多读模式,同时只能有一个写事务
C#using LightningDB;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading;
namespace AppLightningDB
{
public class MetricsCollector : IDisposable
{
private readonly LightningEnvironment _env;
private readonly Timer _flushTimer;
private readonly ConcurrentQueue<MetricEntry> _buffer = new();
public MetricsCollector(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));
}
public void RecordMetric(string name, double value, 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);
}
private void FlushMetrics(object? state)
{
var batch = new List<MetricEntry>();
// 取出缓冲区中的数据
while (_buffer.TryDequeue(out var entry) && batch.Count < 1000)
{
batch.Add(entry);
}
if (batch.Count == 0) return;
try
{
using var tx = _env.BeginTransaction();
using var db = tx.OpenDatabase();
foreach (var entry in batch)
{
var key = $"metric:{entry.Name}:{entry.Timestamp}";
var value = 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>();
using var tx = _env.BeginTransaction(TransactionBeginFlags.ReadOnly);
using var db = tx.OpenDatabase();
using var 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;
}
// 添加清理过期数据的方法
public void CleanupOldMetrics(long olderThanTimestamp)
{
using var tx = _env.BeginTransaction();
using var db = tx.OpenDatabase();
using var 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], out var 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} 条过期指标数据");
}
}
public void Dispose()
{
_flushTimer?.Dispose();
// 最后一次刷新缓冲区
FlushMetrics(null);
_env?.Dispose();
}
public class MetricEntry
{
public string Name { get; set; } = string.Empty;
public double Value { get; set; }
public Dictionary<string, string> Tags { get; set; } = new();
public long Timestamp { get; set; }
}
}
}
C#using System;
using System.Text;
using LightningDB;
namespace AppLightningDB
{
internal class Program
{
static void Main(string[] args)
{
using var 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("指标收集完成!");
}
}
}

💡 实际应用场景:应用性能监控、用户行为分析、系统日志存储
⚠️ 常见坑点:大量小写入会影响性能,建议使用批量写入策略
Lightning.NET不仅仅是一个数据存储库,更是提升C#应用性能的利器。 无论是桌面应用的本地缓存、Web服务的会话存储,还是IoT设备的指标收集,它都能为你的项目带来显著的性能提升。
💬 互动时间:你在项目中遇到过哪些数据存储性能瓶颈?Lightning.NET的哪个特性最吸引你?欢迎在评论区分享你的使用经验或技术问题!
🚀 觉得有用请转发给更多同行,让更多C#开发者了解这个高性能的存储解决方案!
#C#开发 #编程技巧 #数据存储 #性能优化
相关信息
通过网盘分享的文件:AppLightningDB.zip 链接: https://pan.baidu.com/s/1811B3x_Ovt5jC1WhTkqpOQ?pwd=tnqi 提取码: tnqi --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!