摘要: 公司简介: 福州掌中云文化传媒有限公司是位于省会城市福州,坐落于福州繁华商业区五四路环球广场。公司主要从事互娱业务,通过拥抱粉丝经济与内容经济,形成全新互联网营销商业模式,借助微信、新浪微博等自媒体平台账号矩阵,覆盖多个泛娱乐领域。旗下布局掌中云阅、掌中云漫、掌中云游三个平台,目前掌中云阅日UV数百 阅读全文
posted @ 2018-01-13 12:18 水言木 阅读(201) 评论(2) 推荐(0) 编辑

1. RavenDB中的两种查询方式

在RavenDB中,查询可分为两种,一种是通过IDocumentSession的Load方法按Id直接查询,另一种是通过IDocumentSession的Query<T>方法查询索引。如下所示:

// 使用Load方法查询Id为users/1的User
var user = session.Load<User>("users/1");

// 使用Query<T>方法查询索引,索引名为UserIndex
var users = session.Query<User>("UserIndex");

这两种查询的主要区别在于后者是查询索引,而前者不是。RavenDB的索引是在后台计算的,这也就意味着查询索引得到的很可能是脏数据,例如,我们添加一个用户并成功保存,然后通过索引查询用户,这时是有可能查询不到刚刚保存的用户记录的。而对于Load方法,只要用户保存成功了,调用Load<User>(userId)则一定会查询到相应的用户记录,且不为脏数据。

2. 业务流程处理中对强一致性的要求

显然,RavenDB的高性能正是来源于索引查询的这种“最终一致性”,因为对于大部分应用来说(尤其互联网应用),几乎都是读多写少,而且可以接受临时的数据不一致。但对于业务流程的处理来说,往往要求数据是强一致的,例如,某系统中有User和Account两个类,前者表示用户,后者表示该用户相关联的帐号(Account中有Balance(表示帐户余额)这样的属性),User和Account是一对一的关系,并且这两个文档独立存储,也就是说不把Account存储为User文档的内嵌对象。现假设有一个用户购买商品的流程,购买商品需要支付商品费用,此时该用户(User)的帐户(Account)需要扣除相应的金额,用伪代码可表示为:

var user = ...;
var account = GetAccount(user);
account.Decrease(50); // 假设需要支付50元

上面代码的问题就在于GetAccount()方法要怎么写,对于这个业务流程来讲,调用GetAccount()时必须得到该用户最新的帐户信息,不能得到脏数据,如果我们在GetAccount()中通过查询索引来获取Account实例,则可能查不到Account,或查到的Account的Balance是脏数据,这是无法接受的。不过,前面说的其实并不完全正确,在RavenDB中查询索引,是可以强制不返回脏数据的(即等待索引计算结束后再返回),用代码来表示就是:

var user = ...;
var account = session.Query<Account>("AccountIndex")
                     .Where(x => x.UserId == user.Id)
                     .Customize(x => x.WaitForNonStaleResultsAsOfNow())
                     .First();

上面代码中的Customize(x => x.WaitForNonStaleResultsAsOfNow())会等待索引计算结束后再返回,这样得到的Account就可以保证是最新的,但这里有一个大问题:这个等待可能很快,也可能很慢。这也相当于把两个本来并行的操作改成同步执行,性能杀手!对于一个设计良好的RavenDB应用来说,WaitForNonStatleResultsAsOfNow()这个方法应该基本不使用,如果我们发现代码中大量调用了该方法,那我们的代码一定是有问题的(当然,在单元测试中则可能会大量用到该方法)。现在问题有了,就要找解决方案。

3. 强一致性和高性能兼得的方案

解决思路其实很明显,如果我们可以通过Load来加载Account,那问题便迎刃而解,而如果想通过Load来加载Account,那就需要一种通过User实例来计算Account的Id的办法,因为User是已知的,只要可以根据User中的信息来计算出Account的Id,那就可以通过Load来加载Account了,所以,解决方案就是:将Account的Id格式设计成UserId和/account的拼接。用代码表示如下:

创建用户:

// 创建User
var user = new User();
// ...
session.Store(user);
                
// 创建Account,注意Id的格式
var account = new Account();
account.Id = user.Id + "/account";
session.Store(account);

查询Account:

var user = session.Load<User>("已知的UserId");

// 通过User计算Account的Id
var accountId = user.Id + "/account";
// 通过计算出来的Account Id直接加载Account
var account = session.Load<Account>(accountId);

这样,我们的代码就不再需要WaitForNonStatleResultsAsOfNow()了,而且,上面的代码很容易进行进一步优化,例如将User和Account放在一个数据库请求中加载(Load方法中可以传入多个不同实体的Id同时加载),这样就可以减少一次数据库连接的性能消耗。

4. 一对多关联的处理

上面是一对一的情况,对于一对多的情况一样适用(当然这个“多”不能太多,一般就几条为宜),假设一个User可以关联多个Account(关联的Account总数不多),这时可以将Account的Id格式设计成: UserId + /account/ + 数字,例如Id为users/1的用户关联的Account Id可以有users/1/account/1, users/1/account/2等,在这种情况下,加载指定用户的Account可以利用IDocumentSession的Advanced.LoadStartingWith<T>()方法。

RavenDB服务端的Id默认是users/11、products/23、orders/12这样的格式,而客户端则可通过约定来支持整型的Id,也就是说,我们可以在程序中将User的Id定义为Int32类型(这只是约定,RavenDB的客户端类库会实现客户端Int32 Id到服务端的字符串Id的转换),但通过上面的例子也可以看出,在RavenDB中更推荐直接使用字符串格式的Id,因为我们可以在字符串Id的格式上做很多文章。

另外,通过上面关于查询的讨论也可以看到,想通过网上广传的Repository模式来让应用可同时支持RDBMS和RavenDB的做法是不可行的,RDBMS的查询可以返回强一致的结果,而RavenDB中的索引查询则是最终一致的,若要让Repository中的查询接口返回强一致的结果,则要使用WaitForNonStatleResults(),而这会对性能产生很大影响。

posted @ 2012-09-23 22:37 水言木 阅读(2473) 评论(0) 推荐(1) 编辑
摘要: 前几篇随笔中讨论了CQRS中的Command,本篇随笔中将讨论CQRS中的领域事件(Domain Event)。概念先回顾下CQRS中一个UI操作的执行过程:首先,用户在UI中点击一个按钮,继而UI层构造了一个相应的Command对象并放到CommandBus中执行,在Command的执行过程中,领域模型中的类和方法得到调用,而领域事件,正是在此时产生的,之所以称之为“领域”事件,也正是因为它产生于领域模型。这可以用下面这张图来说明(先忽略UnitOfWorkContext):从上图也可以看出,领域模型的调用被“包裹”在Command的执行上下文中,所以,UI层的所有操作都只是创建Comman 阅读全文
posted @ 2012-05-24 00:31 水言木 阅读(7098) 评论(22) 推荐(2) 编辑
摘要: 上篇随笔讨论了CQRS中Command的一种基本实现。面对UI中的各种命令,Controller会创建相应的Command对象,然后将其交给CommandBus,由CommandBus统一派发到相应的CommandExecutor中去执行,我们的ICommandBus的接口声明如下:public interface ICommandBus{ void Send<TCommand>(TCommand cmd) where TCommand : ICommand;}当在实际项目中应用CQRS时,我们会发现上面的做法存在一个问题:有时候我们希望Command在执行完后返回一些结果,但上面 阅读全文
posted @ 2012-03-29 22:43 水言木 阅读(6985) 评论(16) 推荐(5) 编辑
摘要: 概述继续引用上篇文章中的图片(来源于Udi Dahan博客),UI中的写入操作都将被封装为一个命令中,发送给Domain Model来处理。我们遵循Domain Driven Design的设计思想,因此所有的业务逻辑都只在Domain Model中处理,Command中将不会带有业务逻辑。Command中的代码无非是通过Repository获取某些个聚合根(Aggregate Root),然后将操作委托给相应的领域对象或领域服务来处理,仅此而已。实现实现上,我们会涉及三个东西:(1) Command对象Command对象的作用是用来封装命令数据,所以这类对象以属性为主,少量简单方法,但注意这 阅读全文
posted @ 2012-03-28 09:01 水言木 阅读(11430) 评论(8) 推荐(9) 编辑
摘要: 什么是CQRS?这个问题网上可以找到很多资料,未接触过的童鞋请先查看Udi Dahan, Grey Young,Rinat Abdullin,园子里dax.net,以及Jdon社区上的相关文章。例如下面几篇文章:1.http://www.cnblogs.com/daxnet/archive/2011/01/06/1929099.html1.http://www.udidahan.com/2009/12/09/clarified-cqrs/2.http://www.jdon.com/jivejdon/thread/37891这里只通过Udi Dahan的《Clarified CQRS》文章中的一 阅读全文
posted @ 2012-03-23 09:52 水言木 阅读(37848) 评论(27) 推荐(22) 编辑
摘要: 混乱的2011年过去了,现在也开始有点闲瑕时间了,所以准备开始重新写点东西。其实这两三年间也有写了一些,但都是放在自己的独立博客中,由于各种原因,这些博客现在也都关闭了,最后想来想去,还是在博客园上面开博客比较稳定,有时间写写,没时间就放着,也没人把它关了。重新回到在博客园的博客,第一眼见到的就是自己曾经写下的那些东西,大都是08年大三下学期时写的,现在看着它们偶尔会有删除的冲动,感觉写的比较幼稚,哈哈,但冲动却同时也伴随着纠结,它们毕竟也是历史,留着也挺有意义,另外,就算是现在写的东西,我也没有办法保证以后回头看时不会觉得它们幼稚,人都是不断成长的嘛,所以现在决定留着它们了,但愿其中写得有问 阅读全文
posted @ 2012-01-23 21:24 水言木 阅读(311) 评论(0) 推荐(0) 编辑
摘要: 在C#中写一个类的时候,你会毫不犹豫地为类的每个字段(field)写上get和set访问器吗?如果是在从前,我会这么做的,但现在知道,不能这么做。 阅读全文
posted @ 2008-11-28 18:40 水言木 阅读(0) 评论(2) 推荐(0) 编辑
摘要: [PoEAA] Learning Notes of Data Transfer Object 阅读全文
posted @ 2008-08-21 15:51 水言木 阅读(606) 评论(0) 推荐(0) 编辑
摘要: C++中的指针、引用,以及C#中的引用 阅读全文
posted @ 2008-08-16 23:10 水言木 阅读(336) 评论(0) 推荐(0) 编辑
摘要: 使用UrlRewriter进行Url重写的完整解决方案总结,包括配置步骤、postback问题 阅读全文
posted @ 2008-08-07 15:22 水言木 阅读(8021) 评论(11) 推荐(2) 编辑
摘要: 迅速搭建SVN环境, VisualSVN Server + TortoiseSVN 阅读全文
posted @ 2008-08-01 17:05 水言木 阅读(2629) 评论(5) 推荐(1) 编辑
摘要: 用js实现GridView中删除按钮被点击时的div提示框,同时保留GridView自带的通过CommandName="Delete"来执行删除的功能 阅读全文
posted @ 2008-07-29 01:02 水言木 阅读(1463) 评论(2) 推荐(0) 编辑
摘要: 下午考完试了,挺轻松的,不过现在不知道要干嘛了,随便写两句吧,也不知道要取个什么名字,就也时尚一下,叫成《工厂的那些事儿》吧 :) 阅读全文
posted @ 2008-07-06 20:36 水言木 阅读(3698) 评论(0) 推荐(0) 编辑
摘要: 这算是《关于ASP.NET的只言片语》的姐妹篇了,哈哈... 阅读全文
posted @ 2008-06-25 01:05 水言木 阅读(284) 评论(2) 推荐(0) 编辑
摘要: 今天开始,进一步深入学习asp.net,撇开ASP.NET MVC、LINQ等等,先把asp.net2.0给搞"精"咯。 阅读全文
posted @ 2008-06-20 01:55 水言木 阅读(1515) 评论(0) 推荐(0) 编辑
摘要: 大部分时候,引用类型和值类型的区别是显而易见的,但在用引用类型作为方法参数时,却多次引起了我的误解,每次都得对着电脑抓狂一下才又一次发现问题所在 阅读全文
posted @ 2008-06-18 23:23 水言木 阅读(314) 评论(0) 推荐(0) 编辑
摘要: 在学习的过程中,总是会有一些细节的问题,它很小,也很容易解决,但有时候它还真的挺有用,于是,便产生了写此随笔的念头。这将是一篇不断更新的随笔,记录一些平常碰到的ASP.NET的“小”问题。 阅读全文
posted @ 2008-06-10 20:04 水言木 阅读(402) 评论(0) 推荐(0) 编辑
摘要: 一点关于.ctor、.cctor以及对象构造过程的总结 阅读全文
posted @ 2008-05-18 03:38 水言木 阅读(8348) 评论(8) 推荐(5) 编辑
摘要: 以前上博客园的时候,对于首页的技术性文章都是最先关注的,但是这几天,我发现对技术性文章的激情少了许多,反而是那些关于地震的文章,每次从新闻、别人口中得知地震中的许多事迹时,总是会感动许久,也带有一丝惆怅... 阅读全文
posted @ 2008-05-15 12:20 水言木 阅读(480) 评论(2) 推荐(0) 编辑
点击右上角即可分享
微信分享提示