上节我们剩余Compare和Fix按钮没有讲,本节我们就来讲一下Compare按钮的功能,年底了,要开年会了,一个个积极的跟邱少云似得,给员工涨工资的时候能不能也积极点。不说了,说多了都是泪。
还记得我之前写的大数据实战之环境搭建,我们今天主要是拿DB的数据和solr的数据作比较,得出比较结果。首先,先启动centOS,启动tomcat,这样我们才能利用SolrNet调用solr公开的API。关于solr的启动再次我就不再赘述。
OK,我们将solr实例启动起来后,用firefox浏览管理界面,没问题。目前solr中并没有任何数据。
接下来我们就要看点击Compare按钮了,点击Compare按钮,Compare按钮在哪里呢,在Partial页。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
@
using
Bruce.GRLC.Model.ViewModel;
@model UserInfoViewModel
<div
class
=
"col-md-6 form-group"
>
<label
class
=
"control-label"
>
The Same:
</label>
<
select
id=
"selsamelist"
class
=
"form-control"
multiple=
"multiple"
style=
"max-height:500px;min-height:400px"
>
@
if
(Model!=
null
&&Model.SameWithSolrEntityList !=
null
&& Model.SameWithSolrEntityList.Count > 0)
{
foreach
(
var
entity
in
Model.SameWithSolrEntityList)
{
<option value=
"@entity.UserNo"
>@entity.Name</option>
}
}
</
select
>
<input id=
"btncompare"
type=
"button"
class
=
"btn btn-info"
value=
"Compare"
style=
"width: 80px;margin-top:5px"
/>
</div>
<div
class
=
"col-md-6 form-group"
>
<label
class
=
"control-label"
>
The Difference:
</label>
<
select
id=
"seldifflist"
class
=
"form-control"
multiple=
"multiple"
style=
"max-height: 500px;min-height: 400px"
>
@
if
(Model.DifferenceWithSolrEntityList !=
null
&& Model.DifferenceWithSolrEntityList.Count > 0)
{
foreach
(
var
entity
in
Model.DifferenceWithSolrEntityList)
{
<option value=
"@entity.UserNo"
>@entity.Name</option>
}
}
</
select
>
<input type=
"button"
class
=
"btn btn-info"
value=
"Fix"
style=
"width:80px;margin-top:5px"
/>
</div>
|
OK,我们看到了按钮在这里,我们设置按钮的样式为btn btn-info,这都是bootStrap提供的样式,我们直接拿来用就ok了。这个页面其实就是循环相同数据的List和不同数据的List,加载到一个可多选的下拉列表。大家注意到multiple="multiple",可以多选。另外"form-control"也是bootStrap提供的样式,用来装饰表单元素的样式。上面的代码没有什么,我们主要是看控制器一级一级往下是怎么处理的,我们先看Compare按钮的click事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
$(
"#btncompare"
).click(
function
() {
$(
"#selsamelist"
).empty();
$(
"#seldifflist"
).empty();
$.ajax({
url:
"/Home/GetCompareResult?pam="
+
new
Date().toTimeString(),
type:
"POST"
,
datatype:
"Html"
,
beforeSend:
function
() {
$(
"#divcompare"
).show();
},
complete:
function
() {
$(
"#divcompare"
).hide();
},
success:
function
(data) {
$(
"#divcompareresult"
).html(data);
},
error:
function
() {
alert(
"比较失败!"
);
}
});
|
首先我们先清除两个多选下拉列表的数据,因为我们这里并不是返回整个Partial页面来替换,所以我们必须先把两个多选下拉数据清除掉。OK,我们看一下控制器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
PartialViewResult GetCompareResult()
{
GRLCBiz instance = GRLCBiz.GetInstance();
instance.CompareDBAndSolr();
UserInfoViewModel userInfoViewModel =
new
UserInfoViewModel();
userInfoViewModel.DifferenceWithSolrEntityList = instance.differenceUserEntityList;
List<
string
> differenceUserIDList = userInfoViewModel.DifferenceWithSolrEntityList.Select(d => d.UserNo.Trim()).ToList();
userInfoViewModel.SameWithSolrEntityList = instance.userEntityList.Where(u => !differenceUserIDList.Contains(u.UserID.Trim()))
.Select((userDB, userSolr) =>
{
return
new
UserSolrEntity()
{
UserNo = userDB.UserID.Trim(),
Name = userDB.UserName ==
null
?
string
.Empty : userDB.UserName.Trim()
};
}).ToList();
return
PartialView(
"~/Views/Partial/DiffAndSameWithSolrPartial.cshtml"
, userInfoViewModel);
}
|
其实很简单,首先我们调用CompareDBAndSolr方法,这个方法是获取到比较结果的关键。那么比较的话,我们怎么比较呢,大家有经验的同学一定想到多线程。不错,就是多线程,可是有人要说了,多线程的话,你怎么知道线程都执行完了,因为只有多个线程都执行完了,我们才能拿到最终的比较结果,显示到页面上。说到这里,如果你是一个.net4.0以前的用户,你可能会想到AutoResetEvent。
说到这个AutoResetEvent,它可以有一个初始的状态,如果是true,则表明是一个终止状态,反之则是非终止状态,那么看一下我们的程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public
void
CompareDBAndSolr()
{
if
(userEntityList ==
null
)
{
userEntityList =
this
.GetAllDBUserList();
}
if
(userEntityList ==
null
|| userEntityList.Count == 0)
return
;
int
threadCount = 0;
int
totalCount = userEntityList.Count;
threadCount = totalCount % ConstValues.CONN_ComparePerThread == 0 ? totalCount / ConstValues.CONN_ComparePerThread : totalCount / ConstValues.CONN_ComparePerThread + 1;
if
(threadCount > ConstValues.CONN_CompareThreadCount)
{
threadCount = ConstValues.CONN_CompareThreadCount;
}
differenceUserEntityList =
new
List<UserSolrEntity>();
autoResetEvents =
new
AutoResetEvent[threadCount];
for
(
int
i = 0; i < threadCount; i++)
{
autoResetEvents[i] =
new
AutoResetEvent(
false
);
ThreadPool.QueueUserWorkItem(
new
WaitCallback(CompareUserInfoByThread), i);
Thread.Sleep(ConstValues.CONN_ThreadCreateInterval);
}
WaitHandle.WaitAll(
this
.autoResetEvents);
}
|
在这个方法中我们先拿到数据库的所有用户的数据
1
2
3
4
5
6
7
8
|
SELECT
A.UseNo,
ISNULL
(B.
Name
,
''
)
AS
Name
,
ISNULL
(B.Age,0)
AS
Age,
ISNULL
(B.Temper,
''
)
AS
Married
FROM
Bonus.dbo.[
User
] A
WITH
(NOLOCK)
INNER
JOIN
Bonus.dbo.UerInfo B
WITH
(NOLOCK)
ON
A.UseNo = B.UseNo
|
然后计算应该使用的线程数。CONN_ComparePerThread配置的是每个线程比较的数据量,所以我们先拿总数据对其进行求余,得到总线程数,然后再判断如果总线程数大于配置的线程数CONN_CompareThreadCount,则取配置的线程数。因为机器的资源有限,不可能开启成百上千个线程,那样CPU资源占用很大,机器肯能会卡死。所以设置最大线程数是必须的。当我们拿到线程数以后,我们实例化一个AutoResetEvent数组,来管理这些线程之间的通信。接下来循环创建AutoResetEvent,设置其初始状态为非终止。然后将线程要执行的方法加入线程池工作队列,并传递线程编号i作为方法参数。最后这句WaitHandle.WaitAll(this.autoResetEvents);意思是等待所有的线程任务结束(状态标记为终止状态)。
我们接下来看CompareUserInfoByThread方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
private
void
CompareUserInfoByThread(
object
userState)
{
int
threadIndex = (
int
)userState;
try
{
UserDBEntity[] copyUserDBEntityList =
null
;
while
(
this
.movePosition <
this
.userEntityList.Count)
{
lock
(
this
.userEntityList)
{
if
(
this
.movePosition >=
this
.userEntityList.Count)
{
break
;
}
if
(
this
.movePosition <=
this
.userEntityList.Count - ConstValues.CONN_ComparePerThread)
{
copyUserDBEntityList =
new
UserDBEntity[ConstValues.CONN_ComparePerThread];
userEntityList.CopyTo(
this
.movePosition, copyUserDBEntityList, 0, ConstValues.CONN_ComparePerThread);
}
else
{
//最后几个,count<CONN_ComparePerThread
copyUserDBEntityList =
new
UserDBEntity[userEntityList.Count -
this
.movePosition];
userEntityList.CopyTo(
this
.movePosition, copyUserDBEntityList, 0, copyUserDBEntityList.Length);
}
this
.movePosition += ConstValues.CONN_ComparePerThread;
}
this
.CompareUserInfoStart(copyUserDBEntityList, threadIndex);
}
}
catch
(Exception ex)
{
LogHelper.WriteExceptionLog(MethodBase.GetCurrentMethod(), ex);
}
finally
{
this
.autoResetEvents[threadIndex].Set();
}
}
|
这个方法其实就是多线程瓜分数据了,每个线程Copy出一批(CONN_ComparePerThread)DB的数据去和solr作对比,每个线程拿到自己的那批数据后,将计数器movePosition增加CONN_ComparePerThread。直到这些线程将这些数据瓜分完,大家注意到在finally语句块,我们对每个AutoResetEvent对象调用set方法,意思是告诉WaitHandle,我已经执行完了,即终止状态。这样当WaitHanlde收到每个线程执行完毕的信号后,结束等待,否则就会一直等待下去,这就是为什么Set方法的调用一定要放到finally块的原因。OK,继续看下一个方法CompareUserInfoStart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
private
void
CompareUserInfoStart(UserDBEntity[] userDBEntityList,
int
threadIndex)
{
List<
string
> userIDList = userDBEntityList.Select(u => u.UserID.Trim()).ToList();
StringBuilder solrFilter =
new
StringBuilder();
foreach
(
var
userID
in
userIDList)
{
solrFilter.Append(
"UserNo:"
);
solrFilter.Append(userID);
solrFilter.Append(
" OR "
);
}
solrFilter.Length = solrFilter.Length - 4;
List<UserSolrEntity> userSolrEntityList = SolrHelper.GetInstance().QueryByFilter<UserSolrEntity>(solrFilter.ToString());
List<UserSolrEntity> userDBConvertSolrEntityList = userDBEntityList.Select((userDB, userSolr) =>
{
return
new
UserSolrEntity()
{
UserNo = userDB.UserID.Trim(),
Age = userDB.Age,
Name = userDB.UserName.Trim(),
IsMarried = userDB.Married ==
"1"
};
}).ToList();
lock
(_lockObj)
{
differenceUserEntityList.AddRange(userDBConvertSolrEntityList.Except(userSolrEntityList,
new
UserSolrEntityCompare()));
}
}
}
|
我们拿到对比的DB数据实体List之后,得到userID,然后拼成solr的查询条件solrFilter,然后调用SolrHelper中的QueryByFilter<T>方法去查询出一个Solr的实体List,然后我们将DB的实体List通过Linq转化为Solr的实体List userDBConvertSolrEntityList。然后通过Except方法找出不同的实体List,放置到differenceUserEntityList。在这里注意IEqualityCompare接口的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
class
UserSolrEntityCompare : IEqualityComparer<UserSolrEntity>
{
public
bool
Equals(UserSolrEntity original, UserSolrEntity destination)
{
original.Name = original.Name ??
string
.Empty;
original.UserNo = original.UserNo ??
string
.Empty;
destination.Name = destination.Name ??
string
.Empty;
destination.UserNo = destination.UserNo ??
string
.Empty;
return
original.UserNo.Trim().Equals(destination.UserNo.Trim())
&& original.Age == destination.Age
&& original.Name.Trim().Equals(destination.Name.Trim())
&& original.IsMarried == destination.IsMarried;
}
public
int
GetHashCode(UserSolrEntity userSolrEntity)
{
return
userSolrEntity.UserNo.GetHashCode();
}
}
|
OK,到这里就全部结束了,我们在action中拿到了比较出的结果,然后组成viewModel,返回给Partial页面去绑定。看一下效果Comparing,please wait......
下面是比较出的结果,因为solr中没有数据,所以都是不相同的,因为取DB数据是INNER JOIN,所以只有四条数据。
OK,本节到此结束,下节我们看一下Fix功能的实现。
本文转自 BruceAndLee 51CTO博客,原文链接:http://blog.51cto.com/leelei/1588090,如需转载请自行联系原作者