HubbleDotNet 推出一年多来,得到了大量用户的支持和使用,其中有些用户的搜索访问量较大,在使用过程中有时会因为同时并发访问过大,出现 Too many connects on server 这个错误,为了缓解这个问题,HubbleDotNet 的 V1.0.4.0 版本做了一些改进,本文将介绍这个改进即相关原理。
这篇文章写的比较早,现在解决Too many connects on server 的最佳办法是采用异步方式连接,请参见下面文章:
对于搜索系统大量并发访问的通常解决方案是分布式搜索和智能缓存相结合,这些功能HubbleDotNet 将在后续版本中开发。不过分布式搜索和智能缓存服务往往会提高系统的软硬件成本,很多中小型网站的站内搜索的访问量还没有达到大型门户网站的数量级别,他们往往希望采用单机系统来最大限度的满足自身系统的需要。为了这些系统可以最大限度的在低成本下运行,HubbleDotNet 的V1.0.4.0提供了一个折中的解决方案,可以在一定程度上缓解单机搜索系统大量并发的问题。
首先我们来看看 Too many connects on server 这个错误是怎么产生的。
HubbleDotNet 是以服务形式存在的,HubbleDotNet 提供的客户端组件 Hubble.SqlClient.dll 通过TCP 方式向HubbleDotNet 服务发出查询请求,并从HubbleDotNet 服务获得查询结果。每个HubbleConnection 实例都会保持一个和服务器之间的TCP连接,直到执行HubbleConnection.Close 方法关闭连接,这个原理和SQL SERVER 的访问原理是相似的。HubbleDotNet 在 Setting.xml 中有如下配置
<MaxConnectNum>32</MaxConnectNum>
这个配置用于设置HubbleDotNet 服务同时最多可以接受的连接数,默认为32个连接。如果同时连接数超过了这个设置指定的数,老版本就会报 Too many connects on server这样的错误。
那么是不是我们把这个连接数加大就可以解决大并发的问题了呢?回答是不一定。
这里面有两个问题
1. 单机的TCP连接数是有限的,曾经有个用户将这个值设置为10000,结果导致其机器上其他的TCP连接都无法连接了,为什么会造成这个问题,请看我以前写过的一篇文章
2. 机器的处理能力有限
机器的处理能力就像一个池子,如果流进的水一直比流出的水多,那么这个池子迟早会溢出。比如机器的处理能力为每秒可以搜索100次,那么当并发数量超过每秒100次时,哪怕最大连接数设置很大,溢出也是迟早会发生的,只是这个值设置的大比设置的小发生溢出所用时间会长。
下面谈谈 V1.0.4.0 所做的改进
等待机制就是当同时连接数超过最大连接数时,后面的连接会等待一段时间,而不是直接报错。在这个等待的时间内,HubbleConnection 会多次尝试和服务器连接,一旦服务器有空闲的连接,等待的连接就可以连接成功。
等待的时间由 HubbleConnection.ConnectionTimeout 这个参数设置,单位为秒,默认为300秒。如果超过这个等待时间依然无法连接成功,则会触发连接超时的错误。
这个改进主要是针对短时间内大量访问的情况。很多中小网站,搜索的访问量不会持续很大,但可能在某个瞬间很高,比如在100ms内同时有50个用户访问,最大连接数为32,这时即使机器的处理能力达到每秒500次,老版本依然可能出错,V1.0.4.0 以后版本,在出现这种问题时就不会再出错了,大量并发时访问时间可能会稍微长一点比如几秒钟,这个视机器性能和缓存设置情况而定。但不会出错,这样对于应用来说会更友好一些。
等待机制和数据缓存超时同时使用,会达到比较好的效果
HubbleDotNet 的数据缓存原理是将查询数据缓存在客户端组件中,查询时如果缓存未超时,则直接从客户端组件读取数据,这样就大大缓解了搜索服务器的压力。这个超时时间通过HubbleCommand.CacheTimeout 这个属性设置,单位为秒,默认为-1,超时时间小于0,表示不缓存。等于0表示每次都向服务器询问一下缓存是否还有效,如果在两次访问间服务器没有更新过,则服务器返回缓存依然有效,这样就继续使用客户端的缓存,否则服务器将重新查询一边。如果大于0,那么在缓存超时前将不再询问服务器是否有效,直到缓存超时后才询问。
HubbleCommand 还有一个属性 ResetDataCacheAfterTimeout 这个属性如果设置为 true ,则在缓存超时后强制刷新缓存,不管服务器是否更新过,这个设置一般用在被动模式下非索引字段更新问题的解决上,这里不做过多解释。
很显然,如果我们设置了较长的缓存超时时间,服务器的压力就会大大降低(除非每次搜索都是不同的关键字,这种应用不是特别多,大部分应用还是集中在一些热门关键字上,网站上也可以通过类似百度,google 的关键字提示功能来诱导用户使用热门关键字以提高缓存的命中率,减轻服务器的压力)
HubbleConnection 这个类新增了一个 TryConnectTimeout 属性,这个属性设置尝试连接的超时时长,单位为毫秒。当尝试连接时间超过这个设置时让暂时让这个连接通过,即HubbleConnection.Open 这个函数返回(此时并没有连接到服务器)。然后后面在HubbleCommand 执行 SQL 查询时,将判断当前是否有缓存,如果有,不管这个缓存是否超时,都强制使用这个缓存,如果没有将继续尝试连接,直到连接超时(这时不再判断这个 TryConnectTimeout 超时了,而是判断 ConnectionTimeout 超时了)。这个参数的默认值为0,就是不强制使用缓存。
这个设置的好处是在大并发时最大限度的减少响应时间。下面举个例子
比如我们设置数据缓存超时为300秒,某个搜索条件 A 在300秒前执行过一次,300秒后又要查询这个A,这时网站出现访问量洪峰,同时搜索的访问量超过32次(假设最大连接数为32),假设A为第33个连接,平均搜索时长为50毫秒,那么A最长会在 1.6 秒后才得到执行。如果设置TryConnectTimeout= 200ms 那么200ms后,A就直接返回300秒前的缓存数据了,这样就大大提高了服务器忙时的搜索响应时间。
应用这个参数时必须特别注意
由于数据缓存只针对 select 语句,对于存储过程是不会缓存的,那么根据我上面说的,如果在 select 语句前执行存储过程,那么这个设置就失效了,比如我写的那个asp.net 的例子,在select 执行之前会执行一个存储过程获取搜索关键字的分词结果,如果要用这个设置,那么我们就不能这样去获取搜索关键字的分词结果了,要用本地的分词组件在asp.net 代码中直接进行分词才行,也就是说如果要使用这个参数,我们只能在 HubbleCommand 中执行 select 语句,不能执行存储过程,否则这个参数就会失效,效果和不用这个参数的效果一样。