使用Redis构建简单的ORM
Reids相关的资料引用
- http://www.tuicool.com/articles/bURJRj [Reids各种数据类型的应用场景]
- https://github.com/antirez/redis [Github Reids]
- https://github.com/StackExchange/StackExchange.Redis [Github StackExchangeReids]
目标
- 在Redis的基础上提供强类型的访问入口
- 分页支持
- 主键支持
几个方案[数据类型]的选择分析
为了实现上述目标,针对以下几种类型进行了思考:
[基于字符串类型]
使用字符串类型来存储集合对象。这种方式存在以下几个问题:
- 每次更新操作涉及到整个集合对象
- 序列化/反序列化会导致性能瓶颈
- 无法支持分页(仅支持内存分页,每次应用服务器都需要加载所有数据)
[基于集合类型]
使用集合类型(LIST/SET)来存储集合类型对象。相对于字符串而言,有如下改进:
- 每次更新操作不会影响到整个集合
- 序列化/反序列化不会导致性能瓶颈
- 支持分页,分页无需加载所有数据
但是仍然存在以下问题:
- 无法支持主键(无法根据Key来获取数据)
- 每次更新的粗细粒度为整个数据"行"
[基于HashSet类型]
使用HashSet来存储一个对象的每个FIELD,使用一个对应的KEY来访问对象。这种方式解决了以下问题:
- 为数据访问提供了键支持
- 可以根据指定字段来更新数据
但是无法提供集合支持。
[混合的方案]
使用一个SortedSet来记录数据集合的所有的KEY,使用不同的KEY指向的HashSet存储集合元素数据。这个方案满足了上述所有的需求,是目前采取的方式。但是仍然有以下问题:
- 每次读取一个对象就需要一次通信开销(访问一次HashSet)
KEY的设计
为了保证存储在Redis的键值对逻辑上的唯一性,在实现上述方案的时候使用了较长的KEY。一个KEY由以下几个部分组成:
- WellKnownReidsKeys,这是一个功能性的划分,表明这个key对应的值的用途
- TypeSpecifiedKey,这个部分反应了这个key对应的值被“结构化”之后的类型信息
- CustomizedKey,这个是一个自定义的Key,方便使用的时候扩展
在Redis中,一个KEY应该形如:[WellKnownReidsKeys][TypeSpecifiedKey][CustomizedKey]。其中,CustomizedKey可以将同类型的数据集合拆分成不同的区块,独立管理。
几个性能问题
[强类型对象转字典问题]
使用了运行时构造表达式目录树进行编译的方式来减少反射开销,代码如下:
public Func<T, IDictionary<string, string>> Compile(string key)
{
var outType = typeof (Dictionary<string, string>);
var func = ConcurrentDic.GetOrAdd(key, k =>
{
var tType = typeof (T);
var properties = tType.GetProperties();
var expressions = new List<Expression>();
//public T xxx(IDataReader reader){
var param = Expression.Parameter(typeof (T));
//var instance = new T();
var newExp = Expression.New(outType);
var varExp = Expression.Variable(outType, "instance");v
var varAssExp = Expression.Assign(varExp, newExp);
expressions.Add(varAssExp);
var indexProp = typeof (IDictionary<string, string>).GetProperties().Last(p => p.Name == "Item");
var strConvertMethod = typeof (object).GetMethod("ToString");
foreach (var property in properties)
{
var propExp = Expression.PropertyOrField(param, property.Name);
Expression indexAccessExp = Expression.MakeIndex(varExp, indexProp,
new Expression[] {Expression.Constant(property.Name)});
var strConvertExp = Expression.Condition(Expression.Equal(Expression.Constant(null), Expression.Convert(propExp,typeof(object))),
Expression.Constant(string.Empty), Expression.Call(propExp, strConvertMethod));
var valueAssignExp = Expression.Assign(indexAccessExp, strConvertExp);
expressions.Add(valueAssignExp);
}
//return instance;
var retarget = Expression.Label(outType);
var returnExp = Expression.Return(retarget, varExp);
expressions.Add(returnExp);
//}
var relabel = Expression.Label(retarget, Expression.Default(outType));
expressions.Add(relabel);
var blockExp = Expression.Block(new[] {varExp}, expressions);
var expression = Expression.Lambda<Func<T, IDictionary<string, string>>>(blockExp, param);
return expression.Compile();
});
return func;
}
对于单次转换,表达式的编译结果根据类型信息和字典的KEY信息做了缓存,从而提升性能。对于集合转换,对于每个集合的操作,每次使用的委托都是同一个从而减少了字典索引的开销。以下是一个以硬编码代码为了测试基准的性能比对:
public void ModelStringDicTransfer()
{
var customer = new ExpressionFuncTest.Customer
{
Id = Guid.NewGuid(),
Name = "TestMap",
Age = 25,
Nick = "Test",
Sex = 1,
Address = "Hello World Street",
Tel = "15968131264"
};
const int RunCount = 10000000;
GetDicByExpression(customer);
var time = StopwatchHelper.Timing(() =>
{
int count = RunCount;
while (count-- > 0)
{
GetDicByExpression(customer);
}
});
var baseTime = StopwatchHelper.Timing(() =>
{
int count = RunCount;
while (count-- > 0)
{
GetDicByHardCode(customer);
}
});
Console.WriteLine("time:{0}\tbasetime:{1}", time, baseTime);
Assert.IsTrue(baseTime * 3 >= time);
}
private Func<ExpressionFuncTest.Customer, IDictionary<string, string>> _dicMapper;
private IDictionary<string, string> GetDicByExpression(ExpressionFuncTest.Customer customer)
{
_dicMapper = _dicMapper ?? ModelStringDicTransfer<ExpressionFuncTest.Customer>.Instance.Compile(
typeof(ExpressionFuncTest.Customer).FullName);
return _dicMapper(customer);
}
private Dictionary<string, string> GetDicByHardCode(ExpressionFuncTest.Customer customer)
{
var dic = new Dictionary<string, string>();
dic.Add("Name", customer.Name);
dic.Add("Address", customer.Address);
dic.Add("Nick", customer.Nick);
dic.Add("Tel", customer.Tel);
dic.Add("Id", customer.Id.ToString());
dic.Add("Age", customer.Age.ToString());
dic.Add("Sex", customer.Sex.ToString());
return dic;
}
对于10M的转换量,硬编码耗时6s左右,动态转换耗时10s左右。
[整体的性能测试]
以下是一个针对已经完成的实现的测试:
public void PerformanceTest()
{
var amount = 1000000;
var key = "PerformanceTest";
Fill(amount, key);
PageGetFirst(1, key);
int i = 1;
while (i <= 100000)
{
var count = i;
var fTime = StopwatchHelper.Timing(() => PageGetFirst(count, key));
var lTime = StopwatchHelper.Timing(() => PageGetLast(count, key));
Console.WriteLine("{0}:第一页耗时:{1}\t最后一页耗时:{2}", count, fTime, lTime);
i = i*10;
}
}
private void Fill(int count,string partKey)
{
var codes = Enumerable.Range(1000, count).Select(i => i.ToString());
codes.Foreach(i =>
{
var customer = new Customer
{
Id = i == "1000" ? Guid.Empty : Guid.NewGuid(),
Name = "Customer" + i,
Code = i,
Address = string.Format("XX街{0}号", DateTime.Now.Millisecond),
Tel = "15968131264"
};
_pagableHashStore.UpdateOrInsertAsync(customer, customer.Code + "", partKey).Wait();
});
}
private void PageGetFirst(int count,string partKey)
{
var pageInfo = new PageInfo(count, 1);
_pagableHashStore.PageAsync(pageInfo, partKey).Result
.Foreach(i => i.Wait());
}
private void PageGetLast(int count, string partKey)
{
var pageInfo = new PageInfo(count, (100000 - 1)/count + 1);
_pagableHashStore.PageAsync(pageInfo, partKey).Result
.Foreach(i => i.Wait());
}
对于10M数据的分页测试(默认的插入时间排序,不同的页长)的结果(时间单位:毫秒):
- 1页长 第一页耗时:1, 最后一页耗时:1
- 10页长 第一页耗时:0, 最后一页耗时:0
- 100页长 第一页耗时:2, 最后一页耗时:5
- 1000页长 第一页耗时:33, 最后一页耗时:35
- 10000页长 第一页耗时:214, 最后一页耗时:316
- 100000页长 第一页耗时:3251, 最后一页耗时:3163
收获
- 打开了脑洞
- 开始编写单元测试
- 开始更新单元测试
所有的源码:
使用Redis构建简单的ORM的更多相关文章
- redis实战笔记(6)-第6章 使用 Redis构建应用程序组件
本章主要内容 1.构建两个前缀匹配自 动补全程序 2.通过构建分布式锁来提高性能 3.通过开发计数信号量来控制并发 4.构建两个不同用途的任务队列 5.通过消息拉取系统来实现延迟消息传递 6.学习 ...
- Redis构建全局并发锁
Redis构建全局并发锁 https://www.cnblogs.com/FG123/p/9990336.html 谈起Redis的用途,小伙伴们都会说使用它作为缓存,目前很多公司都用Redis作为缓 ...
- 《Redis官方文档》用Redis构建分布式锁
用Redis构建分布式锁 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简 ...
- 使用webstorm+webpack构建简单入门级“HelloWorld”的应用&&引用jquery来实现alert
使用webstorm+webpack构建简单入门级"HelloWorld"的应用&&构建使用jquery来实现 1.首先你自己把webstorm安装完成. 请参考这 ...
- 构建简单的Maven工程,使用测试驱动的方式开发项目
构建简单的Maven工程很简单,这里写这篇随笔的原因是希望自己能记住几个小点. 一.安装Maven 1.下载maven:https://maven.apache.org/download.cgi 2. ...
- 【译】用boosting构建简单的目标分类器
用boosting构建简单的目标分类器 原文 boosting提供了一个简单的框架,用来构建鲁棒性的目标检测算法.这里提供了必要的函数来实现它:100% MATLAB实现,作为教学工具希望让它简单易得 ...
- 一个简单的ORM制作(SQL帮助类)
一个简单的ORM制作大概需要以下几个类: SQL执行类 CURD操作类 其他酱油类 先从SQL执行类说起,可能会涉及数据库的迁移等问题,所以需要定义一个接口以方便迁移到其他数据库, 事务没提供命名,若 ...
- 三、使用Maven构建简单的java项目
前边,我刚搭建了Maven环境,还有给大家推荐了学习资源,这个小节,我们来就来,,简单的玩玩maven. 1.所需工具: 1.Eclipse 2.apache-maven-3.3.9 3. ...
- 构建简单的 C++ 服务组件,第 1 部分: 服务组件体系结构 C++ API 简介
构建简单的 C++ 服务组件,第 1 部分: 服务组件体系结构 C++ API 简介 熟悉将用于 Apache Tuscany SCA for C++ 的 API.您将通过本文了解该 API 的主要组 ...
随机推荐
- Linux 命令 - w: 显示登录的用户及其当前执行的任务
命令格式 w - [husfV] [user] 命令参数 -h 不打印头部信息 -u 当列出当前进程和 CPU 时间时忽略用户名,这主要是用于执行su命令后的情况. -s 使用简短的格式化,不打印 L ...
- 使用zipalign.exe工具优化APK安装包
zipalign.exe是Android自带的一个档案整理工具,它可以用于优化APK安装包,从而提升Android应用与系统之间的交互效率,提升应用程序的运行速度. 操作步骤: 1.在命令行窗口输入如 ...
- 自定义ListView适配器
继承BaseAdapter类 覆盖以下4个方法: @Override public int getCount() { return users.size(); } @Override public O ...
- sql中nvarchar(max)长度测试
nvarchar(max)长度测试:在使用convert强制类型转化之后 文本长度可以突破8000的上限.并且nvarchar(max)的最大长度可达到2^31以下为验证SQL: Declare @A ...
- hive 未初始化元数据库报错
启动hive-metastore和hive-server2 用beeline连接hive报错 [root@node04 hive]# beeline Beeline version 0.13.1-cd ...
- sqlserver2008 如何定时清理索引碎片
sqlserver2008 如何定时清理索引碎片 查询索引引起的表垃圾碎片sql脚本: SELECT object_name(a.object_id) [TableName] ,a.index_id ...
- Cisco中删除flash通过tftp服务器恢复
首先我们来看一下,Cisco路由器中的flash文件.通过show flash 命令来查看. 这时可以看到flash的版本是c1841-ipbasek9-mz.124-12.bin 下面我们删除这个f ...
- Cocos2d-x中背景音乐播放暂停与继续
背景音乐播放暂停与继续似乎我们很少使用,事实上也正是如此,背景音乐播放暂停与继续实例代码如下: SimpleAudioEngine::getInstance()->pauseBackground ...
- OpenGL1-创建窗口(建立Win32的OpenGL开发环境 )(代码上传)
视频教程请关注 http://edu.csdn.net/lecturer/lecturer_detail?lecturer_id=440 代码下载 #pragma once #include < ...
- button按钮在IE6、7、8、9、10中处理方式并不相同[转]
http://msdn.microsoft.com/en-us/library/ms534696%28v=vs.85%29.aspx 转自:http://my.oschina.net/fz04003/ ...