LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C# 高性能键值存储方案:用 Lightning.NET 替代 Redis 与 SQLite

admin
2026年1月7日 14:21 本文热度 488

前言

大家是否还在为应用程序的数据存储性能而苦恼?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)是一个超快、超小的键值存储引擎。它使用内存映射文件技术,实现了读取性能接近内存数据库,同时保证数据持久化的完美平衡。

核心优势:

  • 极致性能:读取速度接近内存访问

  • 零配置:嵌入式设计,无需额外服务

  • ACID 事务:完整的事务支持

  • 内存高效:使用系统虚拟内存,占用极小

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 { getset; }
            public DateTime? ExpiryTime { getset; }
        }
    }
}
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 { getset; } = string.Empty;
        publicstring CustomerId { getset; } = string.Empty;
        public DateTime OrderDate { getset; }
        publicdecimal TotalAmount { getset; }
        publicstring Status { getset; } = "Created";
    }

    publicclassOrderItem
    {
        publicstring ProductId { getset; } = string.Empty;
        publicstring ProductName { getset; } = string.Empty;
        publicint Quantity { getset; }
        publicdecimal UnitPrice { getset; }
        publicdecimal TotalPrice { getset; }
    }
}

实际应用场景:电商库存管理、金融交易记录、日志系统

常见坑点: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<stringstring>? tags = null)
        {
            var entry = new MetricEntry
            {
                Name = name,
                Value = value,
                Tags = tags ?? new Dictionary<stringstring>(),
                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 == 0return;

            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 { getset; } = string.Empty;
            publicdouble Value { getset; }
            public Dictionary<stringstring> Tags { getset; } = new();
            publiclong Timestamp { getset; }
        }
    }
}
using System;
using System.Text;
using LightningDB;

namespaceAppLightningDB
{
    internalclassProgram
    {
        staticvoidMain(string[] args)
        {
            usingvar collector = new MetricsCollector("./metrics");
            Console.WriteLine("开始收集指标数据...");
            // 记录性能指标
            collector.RecordMetric("cpu_usage"75.2new() { ["host"] = "web01" });
            collector.RecordMetric("memory_usage"68.5new() { ["host"] = "web01" });
            collector.RecordMetric("disk_io"12.3new() { ["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("指标收集完成!");
        }
    }
}

实际应用场景:应用性能监控、用户行为分析、系统日志存储

常见坑点:大量小写入会影响性能,建议使用批量写入策略

总结

性能优化技巧

  1. 合理设置 MapSize:根据实际需求设置,过小会限制容量,过大会浪费地址空间
  2. 批量操作:使用事务批量处理多个操作,减少磁盘同步次数
  3. 读写分离:利用只读事务提升并发读取性能
  4. 键设计优化:使用有序的键名,利用 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 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved