嗅探连接池Sniffing connection pools
和静态连接池Static connection pools
在活动节点上轮询,以便将请求均匀分布到所有已知节点上。
创建视图方法(CreateView)
CreateView()
是IConnectionPool
接口提供的一个公共方法,该方法创建一个客户端已知集群中所有活动节点的视图。不同的连接池实现可以决定返回的不同的视图,比如:
SingleNodeConnectionPool
连接池只能返回一个节点;StickyConnectionPool
连接池可以返回与请求对应的最后一个活动节点的起始位置相同的活动节点的视图;SniffingConnectionPool
连接池返回起始位置在每次调用时都在变化的视图。
CreateView
方法是以无锁线程安全的方式实现的,这意味着每个被调用者都会返回自己的游标,以在内部节点列表上前进。这样做是为了保证每个失败的请求都尝试所有节点,而不会影响全局游标。
以下示例设置了一个包含10个节点的静态连接池,在启动时强制随机化为false,这样我们就可以测试返回的节点是否符合我们预期的顺序。
var uris = Enumerable.Range(9200, NumberOfNodes).Select(p => new Uri("http://localhost:" + p));
var staticPool = new StaticConnectionPool(uris, randomize: false);
var sniffingPool = new SniffingConnectionPool(uris, randomize: false);
this.AssertCreateView(staticPool);
this.AssertCreateView(sniffingPool);
那么,我们期望的顺序是怎么样的呢?让我们来设想一下:
- 线程A首先调用
CreateView()
,但不使用本地游标,并从内部全局游标获取当前值,即0 - 接着,线程B在没有本地游标的情况下调用
CreateView()
,因此从1开始 - 在此之后,每个线程应该使用它们的本地游标依次遍历节点。例如,线程A可能得到0、1、2、3、5,而线程B将得到1、2、3、4、0。
var startingPositions = Enumerable.Range(0, NumberOfNodes)
.Select(i => pool.CreateView().First())
.Select(n => n.Uri.Port)
.ToList();
var expectedOrder = Enumerable.Range(9200, NumberOfNodes);
startingPositions.Should().ContainInOrder(expectedOrder);
上面的代码证明,每次调用CreateView()
都会分配下一个可用节点。
重新调整测试策略:
- 通过
NumberOfNodex * 2
个线程来调用CreateView()
方法 - 在每个线程上,使用本地游标调用
CreateView()
方法NumberOfNodes * 10
次。
验证每个线程是否看到所有节点以及它们是否被包裹。例如,在节点9209之后,又出现了节点9200:
var threadedStartPositions = new ConcurrentBag<int>();
var threads = Enumerable.Range(0, 20)
.Select(i => CreateThreadCallingCreateView(pool, threadedStartPositions))
.ToList();
foreach (var t in threads) t.Start();
foreach (var t in threads) t.Join();
每个线程都报告了它启动的第一个节点,因为我们启动了NumberOfNodes * 2
个线程,确保每个节点可以看到两次:
var grouped = threadedStartPositions.GroupBy(p => p).ToList();
grouped.Count.Should().Be(NumberOfNodes);
grouped.Select(p => p.Count()).Should().OnlyContain(p => p == 2);
发表评论
登录用户才能发表评论, 请 登 录 或者 注册