您如何使用 SSIS 2014 在企业 ETL 环境中大规模管理包配置?
How do you manage package configurations at scale in an enterprise ETL environment with SSIS 2014?
我正在将一些包从 SSIS 2008 迁移到 2014。MS 正在吹捧转向项目部署并使用 SSIS 环境进行配置,因为它更灵活,但我发现情况并非如此。
在以前的版本中,在配置方面,我使用了一系列技巧。现在,如果我想使用项目部署,我受限于环境。
对于所有包通用的那些变量,我可以设置一个环境,没问题。问题是那些对每个包都是唯一的配置设置。为每个包设置环境似乎很疯狂。
问题是:我有几十个包,其中有数百个配置值,这些配置值是包所独有的。如果我不能像 2008 年那样从 table 中存储和检索这些值,那么在 2014 年你是怎么做到的?
仅能使用环境并不一定正确。虽然您仅限于开箱即用的配置选项,但我正在与一个团队合作,我们已经能够利用一个直接的系统将变量值从 table 传递给包。环境包含一些连接信息,但任何需要在运行时设置的变量值都存储为行数据。
在变量值table中,除了对包的引用之外,一个字段包含变量名,另一个字段包含值。脚本任务调用存储过程和 returns 一组 name/value 对,包内的变量相应地分配传入的值。每个包都是相同的脚本代码。我们只需要确保 table 中的变量名与包中的变量名匹配即可。
事实证明,结合日志记录数据是使用项目部署模型管理包的一种非常有效的方法。
示例:
这里有一个模拟的简单包来展示这个过程。首先,创建一个包含变量值的 table 和一个存储过程,以 return 与您 运行 的包相关的集合。我选择将它放在 SSISDB 中,但您几乎可以使用任何数据库来存放这些对象。我也在使用 OLEDB 连接,这很重要,因为我在使用 OLEDB 库的脚本任务中引用了连接字符串。
create table dbo.PackageVariableValues
(PackageName NVARCHAR(200)
, VariableName NVARCHAR(200)
, VariableValue NVARCHAR(200)
)
create proc dbo.spGetVariableValues
@packageName NVARCHAR(200)
as
SELECT VariableName, VariableValue
FROM dbo.PackageVariableValues
WHERE PackageName = @packageName
insert into dbo.PackageVariableValues
select 'Package', 'strVariable1', 'NewValue'
union all select 'Package', 'intVariable2', '1000'
对于这个例子,包本身将只包含脚本任务和我们将在运行时设置的几个变量。
我有两个变量,strVariable1
和 intVariable2
。这些变量名称映射到我插入到 table.
中的行数据
在脚本任务中,我将 PackageName 和 TaskName 作为只读变量传递,并将设置为读写的变量。
脚本任务中的代码执行以下操作:
- 根据指定的连接管理器设置连接字符串
- 构建存储过程调用
- 执行存储过程并收集响应
- 遍历每一行,设置变量名称和值
- 使用 try/catch/finally,脚本 return 一些日志记录详细信息以及失败时的相关详细信息
正如我之前提到的,我正在使用 OLEDB 库连接到 SQL 和过程执行。
脚本任务代码如下:
public void Main()
{
string strPackageName;
strPackageName = Dts.Variables["System::PackageName"].Value.ToString();
string strCommand = "EXEC dbo.spGetVariableValues '" + strPackageName + "'";
bool bFireAgain = false;
OleDbDataReader readerResults;
ConnectionManager cm = Dts.Connections["localhost"];
string cmConnString = cm.ConnectionString.ToString();
OleDbConnection oleDbConn = new OleDbConnection(cmConnString);
OleDbCommand cmd = new OleDbCommand(strCommand);
cmd.Connection = oleDbConn;
Dts.Events.FireInformation(0, Dts.Variables["System::TaskName"].Value.ToString(), "All necessary values set. Package name: " + strPackageName + " Connection String: " + cmConnString, String.Empty, 0, ref bFireAgain);
try
{
oleDbConn.Open();
readerResults = cmd.ExecuteReader();
if (readerResults.HasRows)
{
while (readerResults.Read())
{
var VariableName = readerResults.GetValue(0);
var VariableValue = readerResults.GetValue(1);
Type VariableDataType = Dts.Variables[VariableName].Value.GetType();
Dts.Variables[VariableName].Value = Convert.ChangeType(VariableValue, VariableDataType);
}
Dts.Events.FireInformation(0, Dts.Variables["System::TaskName"].Value.ToString(), "Completed assigning variable values. Closing connection", String.Empty, 0, ref bFireAgain);
}
else
{
Dts.Events.FireError(0, Dts.Variables["System::TaskName"].Value.ToString(), "The query did not return any rows", String.Empty, 0);
}
}
catch (Exception e)
{
Dts.Events.FireError(0, Dts.Variables["System::TaskName"].Value.ToString(), "There was an error in the script. The messsage returned is: " + e.Message, String.Empty, 0);
}
finally
{
oleDbConn.Close();
}
}
设置值的部分有两个重要事项需要注意。首先,这被设置为查看结果集中每行的前两列。您可以更改此值或 return 其他值作为行的一部分,但您正在使用基于 0 的索引并且不想 return 一堆不必要的列,如果可以避免的话.
var VariableName = readerResults.GetValue(0);
var VariableValue = readerResults.GetValue(1);
其次,由于 table 中的 VariableValues 列可以包含在到达变量时需要不同类型的数据,我采用变量数据类型并对值执行转换以验证它匹配。由于这是在 try/catch 内完成的,因此失败将 return 一条我可以在输出中看到的转换消息。
Type VariableDataType = Dts.Variables[VariableName].Value.GetType();
Dts.Variables[VariableName].Value = Convert.ChangeType(VariableValue, VariableDataType);
现在,结果(通过 Watch window):
之前
之后
在脚本中,我使用 fireInformation
到 return 来自脚本任务的反馈以及 catch 块中的任何 fireError
。这使得在调试期间以及查看 SSISDB 执行消息 table(或执行报告)
时的输出可读
为了显示错误输出的示例,这是从将导致转换失败的过程传递的错误值。
希望这能让你继续下去。我们发现这非常灵活且易于管理。
配置 SSIS 包时,您有 3 个选项:使用设计时值、手动编辑值和使用环境。
方法一
我已经成功地结合了最后两个。我创建了一个文件夹:Configuration
和一个环境 Settings
。没有项目部署到配置。
我用可能跨项目共享的任何东西填充设置环境。数据库连接字符串、ftp 用户和密码、常用文件处理位置等
每个部署的项目,我们发现我们需要配置的东西是通过显式覆盖处理的。例如,文件名因环境而异,因此我们通过编辑器设置了值,但我们没有单击“确定”,而是单击顶部的“脚本”按钮。这会生成一个像
这样的调用
DECLARE @var sql_variant = N'DEV_Transpo*.txt';
EXEC SSISDB.catalog.set_object_parameter_value
@object_type = 20
, @parameter_name = N'FileMask'
, @object_name = N'LoadJobCosting'
, @folder_name = N'Accounting'
, @project_name = N'Costing'
, @value_type = V
, @parameter_value = @var;
我们存储脚本并 运行 它们作为迁移的一部分。它导致一些脚本看起来像
SELECT @var = CASE @@SERVERNAME
WHEN 'SQLSSISD01' THEN N'DEV_Transpo*.txt'
WHEN 'SQLSSIST01' THEN N'TEST_Transpo*.txt'
WHEN 'SQLSSISP01' THEN N'PROD_Transpo*.txt'
END
但这是一次性任务,所以我不认为它很繁重。关于我们的东西如何工作的假设是它是非常静态的,一旦我们弄明白了,所以一旦它开始工作就不会有太大的变动。供应商很少重新定义他们的命名标准。
方法二
如果您发现该方法不合理,那么或许可以继续使用 table 来配置动态的东西。我可以看到两个实现正在处理这个问题。
选项 A
第一个是外部演员设定的。基本上是上面的配置步骤,而不是存储静态脚本,一个简单的游标将去应用它们。
--------------------------------------------------------------------------------
-- Set up
--------------------------------------------------------------------------------
CREATE TABLE dbo.OptionA
(
FolderName sysname
, ProjectName sysname
, ObjectName sysname
, ParameterName sysname
, ParameterValue sql_variant
);
INSERT INTO
dbo.OptionA
(
FolderName
, ProjectName
, ObjectName
, ParameterName
, ParameterValue
)
VALUES
(
'MyFolder'
, 'MyProject'
, 'MyPackage'
, 'MyParameter'
, 100
);
INSERT INTO
dbo.OptionA
(
FolderName
, ProjectName
, ObjectName
, ParameterName
, ParameterValue
)
VALUES
(
'MyFolder'
, 'MyProject'
, 'MyPackage'
, 'MySecondParameter'
, 'Foo'
);
上面只是创建了一个 table 来标识所有应该应用的配置以及它们应该去的地方。
--------------------------------------------------------------------------------
-- You might want to unconfigure anything that matches the following query.
-- Use cursor logic from below substituting this as your source
--SELECT
-- *
--FROM
-- SSISDB.catalog.object_parameters AS OP
--WHERE
-- OP.value_type = 'V'
-- AND OP.value_set = CAST(1 AS bit);
--
-- Use the following method to remove existing configurations
-- in place of adding them
--
--EXECUTE SSISDB.catalog.clear_object_parameter_value
-- @folder_name = @FolderName
-- @project_name = @ProjectName
-- @object_type = 20
-- @object_name = @ObjectName
-- @parameter_name = @ParameterName
--------------------------------------------------------------------------------
至此开始应用配置
--------------------------------------------------------------------------------
-- Apply configurations
--------------------------------------------------------------------------------
DECLARE
@ProjectName sysname
, @FolderName sysname
, @ObjectName sysname
, @ParameterName sysname
, @ParameterValue sql_variant;
DECLARE Csr CURSOR
READ_ONLY FOR
SELECT
OA.FolderName
, OA.ProjectName
, OA.ObjectName
, OA.ParameterName
, OA.ParameterValue
FROM
dbo.OptionA AS OA
OPEN Csr;
FETCH NEXT FROM Csr INTO
@ProjectName
, @FolderName
, @ObjectName
, @ParameterName
, @ParameterValue;
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
EXEC SSISDB.catalog.set_object_parameter_value
-- 20 = project
-- 30 = package
@object_type = 30
, @folder_name = @FolderName
, @project_name = @ProjectName
, @parameter_name = @ParameterName
, @parameter_value = @ParameterValue
, @object_name = @ObjectName
, @value_type = V;
END
FETCH NEXT FROM Csr INTO
@ProjectName
, @FolderName
, @ObjectName
, @ParameterName
, @ParameterValue;
END
CLOSE Csr;
DEALLOCATE Csr;
你什么时候运行这个?每当需要时运行。您可以在 OptionA 上设置触发器以保持同步或将其作为 post 部署过程的一部分。真的,只要在您的组织中有意义。
选项 B
这与 Vinnie 的建议大体一致。我会设计一个 Parent/Orchestrator 包,负责为项目找到所有可能的配置,然后填充变量。然后,使用项目部署模型为子包传递更清晰的变量。
就我个人而言,我不喜欢这种方法,因为它让实施解决方案的开发人员承担更多责任,以确保编码正确。我发现它的维护成本更高,而且并非所有 BI 开发人员都对代码感到满意 table。而且该脚本需要跨大量父类型包实现,并且往往会导致复制和粘贴继承,没有人喜欢这样。
我正在将一些包从 SSIS 2008 迁移到 2014。MS 正在吹捧转向项目部署并使用 SSIS 环境进行配置,因为它更灵活,但我发现情况并非如此。
在以前的版本中,在配置方面,我使用了一系列技巧。现在,如果我想使用项目部署,我受限于环境。
对于所有包通用的那些变量,我可以设置一个环境,没问题。问题是那些对每个包都是唯一的配置设置。为每个包设置环境似乎很疯狂。
问题是:我有几十个包,其中有数百个配置值,这些配置值是包所独有的。如果我不能像 2008 年那样从 table 中存储和检索这些值,那么在 2014 年你是怎么做到的?
仅能使用环境并不一定正确。虽然您仅限于开箱即用的配置选项,但我正在与一个团队合作,我们已经能够利用一个直接的系统将变量值从 table 传递给包。环境包含一些连接信息,但任何需要在运行时设置的变量值都存储为行数据。
在变量值table中,除了对包的引用之外,一个字段包含变量名,另一个字段包含值。脚本任务调用存储过程和 returns 一组 name/value 对,包内的变量相应地分配传入的值。每个包都是相同的脚本代码。我们只需要确保 table 中的变量名与包中的变量名匹配即可。
事实证明,结合日志记录数据是使用项目部署模型管理包的一种非常有效的方法。
示例:
这里有一个模拟的简单包来展示这个过程。首先,创建一个包含变量值的 table 和一个存储过程,以 return 与您 运行 的包相关的集合。我选择将它放在 SSISDB 中,但您几乎可以使用任何数据库来存放这些对象。我也在使用 OLEDB 连接,这很重要,因为我在使用 OLEDB 库的脚本任务中引用了连接字符串。
create table dbo.PackageVariableValues
(PackageName NVARCHAR(200)
, VariableName NVARCHAR(200)
, VariableValue NVARCHAR(200)
)
create proc dbo.spGetVariableValues
@packageName NVARCHAR(200)
as
SELECT VariableName, VariableValue
FROM dbo.PackageVariableValues
WHERE PackageName = @packageName
insert into dbo.PackageVariableValues
select 'Package', 'strVariable1', 'NewValue'
union all select 'Package', 'intVariable2', '1000'
对于这个例子,包本身将只包含脚本任务和我们将在运行时设置的几个变量。
我有两个变量,strVariable1
和 intVariable2
。这些变量名称映射到我插入到 table.
在脚本任务中,我将 PackageName 和 TaskName 作为只读变量传递,并将设置为读写的变量。
脚本任务中的代码执行以下操作:
- 根据指定的连接管理器设置连接字符串
- 构建存储过程调用
- 执行存储过程并收集响应
- 遍历每一行,设置变量名称和值
- 使用 try/catch/finally,脚本 return 一些日志记录详细信息以及失败时的相关详细信息
正如我之前提到的,我正在使用 OLEDB 库连接到 SQL 和过程执行。
脚本任务代码如下:
public void Main()
{
string strPackageName;
strPackageName = Dts.Variables["System::PackageName"].Value.ToString();
string strCommand = "EXEC dbo.spGetVariableValues '" + strPackageName + "'";
bool bFireAgain = false;
OleDbDataReader readerResults;
ConnectionManager cm = Dts.Connections["localhost"];
string cmConnString = cm.ConnectionString.ToString();
OleDbConnection oleDbConn = new OleDbConnection(cmConnString);
OleDbCommand cmd = new OleDbCommand(strCommand);
cmd.Connection = oleDbConn;
Dts.Events.FireInformation(0, Dts.Variables["System::TaskName"].Value.ToString(), "All necessary values set. Package name: " + strPackageName + " Connection String: " + cmConnString, String.Empty, 0, ref bFireAgain);
try
{
oleDbConn.Open();
readerResults = cmd.ExecuteReader();
if (readerResults.HasRows)
{
while (readerResults.Read())
{
var VariableName = readerResults.GetValue(0);
var VariableValue = readerResults.GetValue(1);
Type VariableDataType = Dts.Variables[VariableName].Value.GetType();
Dts.Variables[VariableName].Value = Convert.ChangeType(VariableValue, VariableDataType);
}
Dts.Events.FireInformation(0, Dts.Variables["System::TaskName"].Value.ToString(), "Completed assigning variable values. Closing connection", String.Empty, 0, ref bFireAgain);
}
else
{
Dts.Events.FireError(0, Dts.Variables["System::TaskName"].Value.ToString(), "The query did not return any rows", String.Empty, 0);
}
}
catch (Exception e)
{
Dts.Events.FireError(0, Dts.Variables["System::TaskName"].Value.ToString(), "There was an error in the script. The messsage returned is: " + e.Message, String.Empty, 0);
}
finally
{
oleDbConn.Close();
}
}
设置值的部分有两个重要事项需要注意。首先,这被设置为查看结果集中每行的前两列。您可以更改此值或 return 其他值作为行的一部分,但您正在使用基于 0 的索引并且不想 return 一堆不必要的列,如果可以避免的话.
var VariableName = readerResults.GetValue(0);
var VariableValue = readerResults.GetValue(1);
其次,由于 table 中的 VariableValues 列可以包含在到达变量时需要不同类型的数据,我采用变量数据类型并对值执行转换以验证它匹配。由于这是在 try/catch 内完成的,因此失败将 return 一条我可以在输出中看到的转换消息。
Type VariableDataType = Dts.Variables[VariableName].Value.GetType();
Dts.Variables[VariableName].Value = Convert.ChangeType(VariableValue, VariableDataType);
现在,结果(通过 Watch window):
之前
之后
在脚本中,我使用 fireInformation
到 return 来自脚本任务的反馈以及 catch 块中的任何 fireError
。这使得在调试期间以及查看 SSISDB 执行消息 table(或执行报告)
为了显示错误输出的示例,这是从将导致转换失败的过程传递的错误值。
希望这能让你继续下去。我们发现这非常灵活且易于管理。
配置 SSIS 包时,您有 3 个选项:使用设计时值、手动编辑值和使用环境。
方法一
我已经成功地结合了最后两个。我创建了一个文件夹:Configuration
和一个环境 Settings
。没有项目部署到配置。
我用可能跨项目共享的任何东西填充设置环境。数据库连接字符串、ftp 用户和密码、常用文件处理位置等
每个部署的项目,我们发现我们需要配置的东西是通过显式覆盖处理的。例如,文件名因环境而异,因此我们通过编辑器设置了值,但我们没有单击“确定”,而是单击顶部的“脚本”按钮。这会生成一个像
这样的调用DECLARE @var sql_variant = N'DEV_Transpo*.txt';
EXEC SSISDB.catalog.set_object_parameter_value
@object_type = 20
, @parameter_name = N'FileMask'
, @object_name = N'LoadJobCosting'
, @folder_name = N'Accounting'
, @project_name = N'Costing'
, @value_type = V
, @parameter_value = @var;
我们存储脚本并 运行 它们作为迁移的一部分。它导致一些脚本看起来像
SELECT @var = CASE @@SERVERNAME
WHEN 'SQLSSISD01' THEN N'DEV_Transpo*.txt'
WHEN 'SQLSSIST01' THEN N'TEST_Transpo*.txt'
WHEN 'SQLSSISP01' THEN N'PROD_Transpo*.txt'
END
但这是一次性任务,所以我不认为它很繁重。关于我们的东西如何工作的假设是它是非常静态的,一旦我们弄明白了,所以一旦它开始工作就不会有太大的变动。供应商很少重新定义他们的命名标准。
方法二
如果您发现该方法不合理,那么或许可以继续使用 table 来配置动态的东西。我可以看到两个实现正在处理这个问题。
选项 A
第一个是外部演员设定的。基本上是上面的配置步骤,而不是存储静态脚本,一个简单的游标将去应用它们。
--------------------------------------------------------------------------------
-- Set up
--------------------------------------------------------------------------------
CREATE TABLE dbo.OptionA
(
FolderName sysname
, ProjectName sysname
, ObjectName sysname
, ParameterName sysname
, ParameterValue sql_variant
);
INSERT INTO
dbo.OptionA
(
FolderName
, ProjectName
, ObjectName
, ParameterName
, ParameterValue
)
VALUES
(
'MyFolder'
, 'MyProject'
, 'MyPackage'
, 'MyParameter'
, 100
);
INSERT INTO
dbo.OptionA
(
FolderName
, ProjectName
, ObjectName
, ParameterName
, ParameterValue
)
VALUES
(
'MyFolder'
, 'MyProject'
, 'MyPackage'
, 'MySecondParameter'
, 'Foo'
);
上面只是创建了一个 table 来标识所有应该应用的配置以及它们应该去的地方。
--------------------------------------------------------------------------------
-- You might want to unconfigure anything that matches the following query.
-- Use cursor logic from below substituting this as your source
--SELECT
-- *
--FROM
-- SSISDB.catalog.object_parameters AS OP
--WHERE
-- OP.value_type = 'V'
-- AND OP.value_set = CAST(1 AS bit);
--
-- Use the following method to remove existing configurations
-- in place of adding them
--
--EXECUTE SSISDB.catalog.clear_object_parameter_value
-- @folder_name = @FolderName
-- @project_name = @ProjectName
-- @object_type = 20
-- @object_name = @ObjectName
-- @parameter_name = @ParameterName
--------------------------------------------------------------------------------
至此开始应用配置
--------------------------------------------------------------------------------
-- Apply configurations
--------------------------------------------------------------------------------
DECLARE
@ProjectName sysname
, @FolderName sysname
, @ObjectName sysname
, @ParameterName sysname
, @ParameterValue sql_variant;
DECLARE Csr CURSOR
READ_ONLY FOR
SELECT
OA.FolderName
, OA.ProjectName
, OA.ObjectName
, OA.ParameterName
, OA.ParameterValue
FROM
dbo.OptionA AS OA
OPEN Csr;
FETCH NEXT FROM Csr INTO
@ProjectName
, @FolderName
, @ObjectName
, @ParameterName
, @ParameterValue;
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
EXEC SSISDB.catalog.set_object_parameter_value
-- 20 = project
-- 30 = package
@object_type = 30
, @folder_name = @FolderName
, @project_name = @ProjectName
, @parameter_name = @ParameterName
, @parameter_value = @ParameterValue
, @object_name = @ObjectName
, @value_type = V;
END
FETCH NEXT FROM Csr INTO
@ProjectName
, @FolderName
, @ObjectName
, @ParameterName
, @ParameterValue;
END
CLOSE Csr;
DEALLOCATE Csr;
你什么时候运行这个?每当需要时运行。您可以在 OptionA 上设置触发器以保持同步或将其作为 post 部署过程的一部分。真的,只要在您的组织中有意义。
选项 B
这与 Vinnie 的建议大体一致。我会设计一个 Parent/Orchestrator 包,负责为项目找到所有可能的配置,然后填充变量。然后,使用项目部署模型为子包传递更清晰的变量。
就我个人而言,我不喜欢这种方法,因为它让实施解决方案的开发人员承担更多责任,以确保编码正确。我发现它的维护成本更高,而且并非所有 BI 开发人员都对代码感到满意 table。而且该脚本需要跨大量父类型包实现,并且往往会导致复制和粘贴继承,没有人喜欢这样。