我们已经将数据从xls,csv,txt等文件中的解析出来,进行必须的数据验证, 然后将正确的数据以XML格式保存到磁盘,并将事务型数据更新到DB, 比如生成的磁盘文件名,CheckResult,CheckMemo等。
我们再一起回顾下数据库表设计:
USE
SSB3
GO
CREATE
TABLE
[
BatchInventoryQueue
]
(
TransactionNumber
INT
IDENTITY
(
1
,
1
)
NOT
NULL
,
BatchFileID
INT
NOT
NULL
,
RowIndex
INT
NOT
NULL
,
ItemNumber
INT
NOT
NULL
,
[
FileName
]
NVARCHAR
(
256
)
NULL
,
HasCheck
CHAR
(
1
)
NULL
,
CheckResult
CHAR
(
1
)
NULL
,
CheckMemo
NVARCHAR
(
2000
)
NULL
,
HasSendSSB
CHAR
(
1
)
NULL
,
--
是否尝试发送过SSB
SSBSendResult
CHAR
(
1
)
NULL
,
--
发送SSB是否成功
SSBMemo
NVARCHAR
(
2000
)
NULL
,
--
SSB处理结果
CONSTRAINT
PK_TransactionNumber_BatchInventoryQueue
PRIMARY
KEY
CLUSTERED
(
TransactionNumber
ASC
)
)
下面我们要将正确的数据以XML格式发送到数据库中。因为我们面临的数据量非常大,
所以我们需要考虑负载均衡,比如多台服务器部署。那么就可能会面临数据冲突的问题。
我这里的情况是要求多台服务器部署,那么如何给服务器分数据,又不造成冲突呢?
方案一:用表的主键TransactionNumber与服务器数据取模
1
SELECT
TOP
(
@BatchSize
) TransactionNumber,
2
[
FileName
]
3
FROM
dbo.BatchInventoryQueue
4
WHERE
HasCheck
=
'
Y
'
AND
CheckResult
=
'
S
'
5
AND
[
FileName
]
IS
NOT
NULL
6
AND
HasSendSSB
IS
NULL
AND
TransactionNumber
%
@Throtting
=
@TrottingMod
优点:实现简单。
缺点:如果其中一台服务器失败,那么应该属于它处理的数据将一直得不到处理。
方案二:结合SQL Server的锁特性,在查询数据时避免冲突如下:
1
UPDATE
TOP
(
@BatchSize
) dbo.BatchInventoryQueue
2
SET
HasSendSSB
=
'
I
'
--
inprocessing
3
OUTPUT DELETED.TransactionNumber,
4
DELETED.
[
FileName
]
5
WHERE
HasCheck
=
'
Y
'
AND
CheckResult
=
'
S
'
6
AND
[
FileName
]
IS
NOT
NULL
7
AND
HasSendSSB
IS
NULL
将HasSendSSB更新为I,标示正在发送。由于在更新数据时,进程会获取UPDLOCK,那么下
一个服务器再查询数据时就必须等待直到第一个进程更新完毕。并发执行情况下,难免会有问题,
因此需要考虑容错机制。即用另一个Job定时监视(SSBSendResult IS NULL OR SSBSendResult='N')
AND HasSendSSB='I'的数据,并将状态清空(SET HasSendSSB=NULL,SSBSendResult=NULL),
等待程序下次再次处理。不过这种情况应该非常少。
1
UPDATE
dbo.BatchInventoryQueue
2
SET
HasSendSSB
=
NULL
,
3
SSBSendResult
=
NULL
4
WHERE
(SSBSendResult
IS
NULL
OR
SSBSendResult
=
'
N
'
)
5
AND
HasSendSSB
=
'
I
'
接着我们要生成SSBMessage,这里我使用VTemplate模版引擎来生成.代码如下:
SSBMessageBase:
1
public
abstract
class
SSBMessageBase
2
{
3
public
string
Subject {
get
;
set
; }
4
5
public
string
FromService {
get
;
set
; }
6
7
public
string
ToService {
get
;
set
; }
8
9
public
SSBMessageHead Head {
get
;
set
; }
10
}
SSBMessageHead:
1
public
class
SSBMessageHead
2
{
3
public
string
Action {
get
;
set
; }
4
5
public
string
TransactionCode {
get
;
set
; }
6
}
SSBMessageFromFile:
1
public
class
SSBMessageFromFile : SSBMessageBase
2
{
3
public
string
FileName {
get
;
set
; }
4
}
VTemplate:
1
<
vt:template
>
2
<
Publish
>
3
<
Subject
>
/Subject>
4
<
FromService
>
</
FromService
>
5
<
ToService
>
</
ToService
>
6
<
Message
>
7
<
Head
>
8
<
Action
>
</
Action
>
9
<
TransactionCode
>
</
TransactionCode
>
10
</
Head
>
11
<
Body
>
12
<
vt:output
file
="$ssb.FileName"
charset
="utf-8"
/>
13
</
Body
>
14
</
Message
>
15
</
Publish
>
16
</
vt:template
>
SSBUtility:
1
public
class
SSBUtility
2
{
3
private
string
VtSSBMessage(SSBMessageBase ssb)
4
{
5
string
fileName
=
Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
@"
Templates\SSB.vt
"
);
6
7
TemplateDocument doc
=
new
TemplateDocument(fileName, Encoding.UTF8);
8
doc.SetValue(
"
ssb
"
, ssb);
9
10
StringBuilder sb
=
new
StringBuilder();
11
StringWriter sw
=
new
StringWriter(sb);
12
doc.Render(sw);
13
sw.Close();
14
15
return
sb.ToString();
16
}
17
18
public
void
SendSSB(SSBMessageBase ssb)
19
{
20
string
msg
=
VtSSBMessage(ssb);
21
using
(SqlConnection conn
=
new
SqlConnection(JobConfigs.SSBConnectionString))
22
{
23
using
(SqlCommand cmd
=
new
SqlCommand())
24
{
25
cmd.Connection
=
conn;
26
cmd.CommandType
=
CommandType.StoredProcedure;
27
cmd.CommandText
=
JobConfigs.SSBSendProc;
//
dbo.[UP_Send_Inventory]
28
SqlParameter p
=
new
SqlParameter(
"
@Message
"
, SqlDbType.Xml);
29
p.Value
=
msg;
30
cmd.Parameters.Add(p);
31
conn.Open();
32
cmd.ExecuteNonQuery();
33
conn.Close();
34
}
35
}
36
}
37
}
再接着就是SSB创建部分,下面列举代码示例:
1
USE [master]
2
GO
3
4
IF EXISTS(SELECT
*
FROM sys.databases
where
[name]
=
'
SSB
'
)
5
DROP DATABASE SSB
6
7
CREATE DATABASE SSB
8
GO
9
10
--
enable service broker on database
11
ALTER DATABASE SSB
12
SET TRUSTWORTHY ON
13
GO
14
15
USE SSB
16
GO
17
18
--
create a test table
19
CREATE TABLE dbo.Inventory
20
(
21
[ItemNumber] INT IDENTITY(
1
,
1
) PRIMARY KEY,
22
[Inventory] INT NOT NULL
23
)
24
25
INSERT INTO dbo.Inventory([Inventory])
26
VALUES(
1
)
27
28
GO
29
30
--
create message type and contract
31
CREATE MESSAGE TYPE [Message_Type_Inventory]
32
VALIDATION
=
WELL_FORMED_XML;
33
34
CREATE CONTRACT [Contract_Inventory]
35
(
36
[Message_Type_Inventory] SENT BY INITIATOR
37
)
38
GO
39
40
--
create queue, send service and receive
41
CREATE QUEUE [Queue_Inventory]
42
WITH STATUS
=
ON,
43
RETENTION
=
OFF;
44
45
CREATE SERVICE [Service_Send_Inventory]
46
ON QUEUE [Queue_Inventory]([Contract_Inventory])
47
GO
48
49
CREATE SERVICE [Service_Receive_Inventory]
50
ON QUEUE [Queue_Inventory]([Contract_Inventory])
51
GO
52
53
--
receive procedure
54
CREATE PROCEDURE dbo.[UP_Receive_Inventory]
55
AS
56
BEGIN
57
SET NOCOUNT ON;
58
DECLARE @ConversionHandle UNIQUEIDENTIFIER,
59
@MessageType SYSNAME,
60
@Message XML,
61
@ItemNumber CHAR(
50
),
62
@Inventory INT
63
64
WHILE(
1
=
1
)
65
BEGIN
66
WAITFOR(
67
RECEIVE TOP(
1
) @ConversionHandle
=
conversation_handle,
68
@MessageType
=
message_type_name,
69
@Message
=
CAST(message_body AS XML)
70
FROM [Queue_Inventory]
71
),TIMEOUT
1000
72
IF(@@ROWCOUNT
=
0
)
73
BREAK;
74
75
SELECT @ItemNumber
=
@Message.value(
'
(/Publish/Message/Body/Inventory/ItemNumber/text())[1]
'
,
'
INT
'
),
76
@Inventory
=
@Message.value(
'
(/Publish/Message/Body/Inventory/Inventory/text())[1]
'
,
'
INT
'
)
77
78
UPDATE dbo.Inventory
79
SET Inventory
=
@Inventory
80
WHERE ItemNumber
=
@ItemNumber
81
END
82
END
83
GO
84
85
--
activate queue
86
ALTER QUEUE [Queue_Inventory]
87
WITH ACTIVATION
88
(
89
PROCEDURE_NAME
=
[UP_Receive_Inventory],
90
MAX_QUEUE_READERS
=
5
,
91
EXECUTE AS OWNER
92
)
93
GO
94
95
--
send procedure
96
CREATE PROCEDURE dbo.[UP_Send_Inventory]
97
(
98
@Message XML
99
)
100
AS
101
BEGIN
102
SET NOCOUNT ON;
103
DECLARE @ConversationHandle UNIQUEIDENTIFIER
104
105
BEGIN DIALOG CONVERSATION @ConversationHandle
106
FROM SERVICE [Service_Send_Inventory]
107
TO SERVICE
'
Service_Receive_Inventory
'
108
ON CONTRACT [Contract_Inventory]
109
WITH ENCRYPTION
=
OFF;
110
111
SEND ON CONVERSATION @ConversationHandle
112
MESSAGE TYPE [Message_Type_Inventory](@Message)
113
114
END CONVERSATION @ConversationHandle WITH CLEANUP;
115
END
116
GO
117
118
--
test data
119
DECLARE @Message XML
120
SELECT @Message
=
'
121
<
Publish
>
122
<
Subject
>
BatchInventory
</
Subject
>
123
<
FromService
>
Service_Send_Inventory
</
FromService
>
124
<
ToService
>
Service_Receive_Inventory
</
ToService
>
125
<
Message
>
126
<
Head
>
127
<
Action
>
UpdateInventory
</
Action
>
128
<
TransactionCode
>
123
</
TransactionCode
>
129
</
Head
>
130
<
Body
>
131
<
Inventory
>
132
<
ItemNumber
>
1
</
ItemNumber
>
133
<
Inventory
>
200
</
Inventory
>
134
</
Inventory
>
135
</
Body
>
136
</
Message
>
137
</
Publish
>
'
138
139
SELECT
*
FROM dbo.[Inventory]
140
--
send
141
EXEC dbo.[UP_Send_Inventory] @Message
142
143
WAITFOR DELAY
'
00:00:30
'
144
SELECT
*
FROM Inventory