G
N
I
D
A
O
L

使用MapReduce实现矩阵相乘

实现代码

使用Hadoop的MapReduce实现矩阵相乘是可行的,但需要合理设计数据分发和处理逻辑。以下是基于两阶段MapReduce的实现思路:

实现思路

  1. 阶段一:计算每个k对应的部分乘积。
  2. 阶段二:聚合所有部分乘积,得到最终结果矩阵。

阶段一:部分乘积计算

Mapper

  • 处理矩阵A的元素,输出键为列号k,值为(A, 行号i, 值)。
  • 处理矩阵B的元素,输出键为行号k,值为(B, 列号j, 值)。

Reducer

  • 对每个k,收集所有A的行i的值和B的列j的值。
  • 计算笛卡尔积,输出键为(i,j),值为A[i][k] * B[k][j]。

示例代码

/**
 * 第一阶段Mapper:负责分发矩阵元素到Reducer
 */
public class Phase1Mapper extends Mapper<Object, Text, Text, Text> {

    // 输入值格式示例:
    // 矩阵A元素:A,行号i,列号k,值 如 "A,2,3,5.0"
    // 矩阵B元素:B,行号k,列号j,值 如 "B,3,4,2.0"
    public void map(Object key, Text value, Context context)
            throws IOException, InterruptedException {
        
        // 将输入文本按逗号分割
        String[] tokens = value.toString().split(",");
        String matrix = tokens[0]; // 获取矩阵标识(A或B)

        if (matrix.equals("A")) {
            // 处理矩阵A的元素
            int i = Integer.parseInt(tokens[1]); // 行号i
            int k = Integer.parseInt(tokens[2]); // 列号k(即中间维度)
            double aVal = Double.parseDouble(tokens[3]); // 元素值
            
            // 输出键为k,值为"A,行号i,值"(通过k进行分组)
            // 示例:key="3", value="A,2,5.0"
            context.write(
                new Text(String.valueOf(k)), 
                new Text("A," + i + "," + aVal)
            );

        } else if (matrix.equals("B")) {
            // 处理矩阵B的元素
            int k = Integer.parseInt(tokens[1]); // 行号k(即中间维度)
            int j = Integer.parseInt(tokens[2]); // 列号j
            double bVal = Double.parseDouble(tokens[3]); // 元素值
            
            // 输出键为k,值为"B,列号j,值"
            // 示例:key="3", value="B,4,2.0"
            context.write(
                new Text(String.valueOf(k)), 
                new Text("B," + j + "," + bVal)
            );
        }
    }
}

/**
 * 第一阶段Reducer:计算部分乘积
 */
public class Phase1Reducer extends Reducer<Text, Text, Text, DoubleWritable> {

    public void reduce(Text key, Iterable<Text> values, Context context)
            throws IOException, InterruptedException {
        
        // 存储来自矩阵A和B的元素
        List<String> aEntries = new ArrayList<>(); // 格式:i,value
        List<String> bEntries = new ArrayList<>(); // 格式:j,value

        // 分类处理来自不同矩阵的值
        for (Text val : values) {
            String[] parts = val.toString().split(",");
            if (parts[0].equals("A")) {
                // A矩阵元素:格式为 A,i,value
                aEntries.add(parts[1] + "," + parts[2]); // 保存i和值
            } else {
                // B矩阵元素:格式为 B,j,value
                bEntries.add(parts[1] + "," + parts[2]); // 保存j和值
            }
        }

        // 计算笛卡尔积(所有i和j的组合)
        for (String a : aEntries) {
            String[] aParts = a.split(",");
            int i = Integer.parseInt(aParts[0]); // 矩阵A的行号
            double aVal = Double.parseDouble(aParts[1]); // 矩阵A的值

            for (String b : bEntries) {
                String[] bParts = b.split(",");
                int j = Integer.parseInt(bParts[0]); // 矩阵B的列号
                double bVal = Double.parseDouble(bParts[1]); // 矩阵B的值

                // 计算乘积并输出到第二阶段
                double product = aVal * bVal;
                // 输出键为(i,j),值为乘积结果
                // 示例:key="2,4", value=10.0
                context.write(
                    new Text(i + "," + j), 
                    new DoubleWritable(product)
                );
            }
        }
    }
}

阶段二:聚合求和

Mapper:直接传递键值对。
Reducer:对相同(i,j)键的值求和。

示例代码

/**
 * 第二阶段Mapper(恒等映射):直接传递键值对
 * 输入格式:i,j    productValue
 */
public class Phase2Mapper extends Mapper<Object, Text, Text, DoubleWritable> {
    
    public void map(Object key, Text value, Context context)
            throws IOException, InterruptedException {
        
        // 将输入行分割为键值对(格式:i,j\tproduct)
        String[] parts = value.toString().split("\t");
        String outputKey = parts[0]; // 保持键不变
        double outputValue = Double.parseDouble(parts[1]);

        // 直接传递键值对
        context.write(
            new Text(outputKey), 
            new DoubleWritable(outputValue)
        );
    }
}

/**
 * 第二阶段Reducer:求和得到最终结果
 */
public class Phase2Reducer extends Reducer<Text, DoubleWritable, Text, DoubleWritable> {

    public void reduce(Text key, Iterable<DoubleWritable> values, Context context)
            throws IOException, InterruptedException {
        
        double sum = 0.0;
        // 对相同(i,j)的所有部分乘积求和
        for (DoubleWritable val : values) {
            sum += val.get(); // 累加值
        }

        // 输出最终结果
        context.write(key, new DoubleWritable(sum));
    }
}

输入格式要求

// 矩阵A的每个元素需要按以下格式存储:
A,i,k,value  
// 例如:A,0,2,3.14 表示A[0][2] = 3.14

// 矩阵B的每个元素需要按以下格式存储:
B,k,j,value  
// 例如:B,2,5,6.28 表示B[2][5] = 6.28

// 文件示例:
A,0,0,1.0
A,0,1,2.0
B,0,0,3.0
B,1,0,4.0

运行流程

  1. 输入格式:矩阵A和B以特定格式存储,如A,i,k,valueB,k,j,value
  2. 执行阶段一:计算每个k对应的部分乘积。
  3. 执行阶段二:对部分乘积求和,得到最终结果。

注意事项

  • 性能问题:阶段一的笛卡尔积可能导致中间数据量爆炸,适用于小规模矩阵或稀疏矩阵。
  • 优化:可通过矩阵分块、Combiner优化或调整数据分发策略来减少数据传输。

关键概念图解

矩阵相乘原理:
C[i][j] = Σ(A[i][k] * B[k][j]) ,对k从1到n求和

MapReduce数据流:
矩阵A元素 (i,k) → 通过k分发 → Reducer
矩阵B元素 (k,j) → 通过k分发 → Reducer
Reducer内计算所有i和j的组合乘积

阶段一输出:
(0,0) → 1 * 3 + 2 * 4 = 11
(0,1) → 1 * 5 + 2 * 6 = 17
...(其他元素同理)

阶段二输出:
(0,0) 11
(0,1) 17
...(最终结果矩阵)

通过上述方法,可以利用Hadoop的MapReduce实现矩阵相乘,虽然存在性能限制,但理论上是可行的。

具体例子详细跟踪程序的执行过程

让我们通过一个具体的2x2矩阵相乘示例,详细跟踪程序的执行过程。假设:

矩阵A:

[1 2]
[3 4]

矩阵B:

[5 6]
[7 8]

最终结果C = A×B 应该是:

[1×5+2×7=19, 1×6+2×8=22]
[3×5+4×7=43, 3×6+4×8=50]

输入文件格式

文件内容(假设存储为input/matrix.txt):

A,0,0,1.0
A,0,1,2.0
A,1,0,3.0
A,1,1,4.0
B,0,0,5.0
B,0,1,6.0
B,1,0,7.0
B,1,1,8.0

阶段一执行过程

Mapper处理流程

每个Mapper会处理一行输入:

输入行 输出键 (k) 输出值 (v) 说明
A,0,0,1.0 "0" "A,0,1.0" 矩阵A第0行第0列元素
A,0,1,2.0 "1" "A,0,2.0" 矩阵A第0行第1列元素
A,1,0,3.0 "0" "A,1,3.0" 矩阵A第1行第0列元素
A,1,1,4.0 "1" "A,1,4.0" 矩阵A第1行第1列元素
B,0,0,5.0 "0" "B,0,5.0" 矩阵B第0行第0列元素
B,0,1,6.0 "0" "B,1,6.0" 矩阵B第0行第1列元素
B,1,0,7.0 "1" "B,0,7.0" 矩阵B第1行第0列元素
B,1,1,8.0 "1" "B,1,8.0" 矩阵B第1行第1列元素

Reducer处理过程

按key分组后:

Reducer处理key="0"(k=0)时:

  • 收到的values列表:
    ["A,0,1.0", "A,1,3.0", "B,0,5.0", "B,1,6.0"]
    
  • 分类存储:
    aEntries = ["0,1.0", "1,3.0"]  // 矩阵A第0列元素(即k=0)
    bEntries = ["0,5.0", "1,6.0"]  // 矩阵B第0行元素(即k=0)
    
  • 计算笛卡尔积:
    (0,0) → 1.0*5.0 = 5.0
    (0,1) → 1.0*6.0 = 6.0
    (1,0) → 3.0*5.0 = 15.0
    (1,1) → 3.0*6.0 = 18.0
    

Reducer处理key="1"(k=1)时:

  • 收到的values列表:
    ["A,0,2.0", "A,1,4.0", "B,0,7.0", "B,1,8.0"]
    
  • 分类存储:
    aEntries = ["0,2.0", "1,4.0"]  // 矩阵A第1列元素(k=1)
    bEntries = ["0,7.0", "1,8.0"]  // 矩阵B第1行元素(k=1)
    
  • 计算笛卡尔积:
    (0,0) → 2.0*7.0 = 14.0
    (0,1) → 2.0*8.0 = 16.0
    (1,0) → 4.0*7.0 = 28.0
    (1,1) → 4.0*8.0 = 32.0
    

阶段一最终输出

0,0 → 5.0
0,1 → 6.0
1,0 → 15.0
1,1 → 18.0
0,0 → 14.0
0,1 → 16.0
1,0 → 28.0
1,1 → 32.0

阶段二执行过程

Mapper处理(直接传递)

输入来自阶段一的输出,Mapper不做处理,直接传递:

(0,0) → 5.0
(0,1) → 6.0
(1,0) → 15.0
...(其他值同理)

Reducer聚合求和

按(i,j)分组求和:

处理(0,0):

  • 值列表:[5.0, 14.0]
  • 求和:5.0 + 14.0 = 19.0

处理(0,1):

  • 值列表:[6.0, 16.0]
  • 求和:6.0 + 16.0 = 22.0

处理(1,0):

  • 值列表:[15.0, 28.0]
  • 求和:15.0 + 28.0 = 43.0

处理(1,1):

  • 值列表:[18.0, 32.0]
  • 求和:18.0 + 32.0 = 50.0

最终输出结果

0,0 → 19.0
0,1 → 22.0
1,0 → 43.0
1,1 → 50.0

可视化数据流向

矩阵A元素分布:
k=0 → A(0,0)=1.0, A(1,0)=3.0
k=1 → A(0,1)=2.0, A(1,1)=4.0

矩阵B元素分布:
k=0 → B(0,0)=5.0, B(0,1)=6.0
k=1 → B(1,0)=7.0, B(1,1)=8.0

阶段一Reducer处理:
k=0时:
   (i=0) * (j=0) → 1*5=5
   (i=0) * (j=1) → 1*6=6
   (i=1) * (j=0) → 3*5=15
   (i=1) * (j=1) → 3*6=18

k=1时:
   (i=0) * (j=0) → 2*7=14
   (i=0) * (j=1) → 2*8=16
   (i=1) * (j=0) → 4*7=28
   (i=1) * (j=1) → 4*8=32

阶段二聚合:
C[0][0] = 5 + 14 = 19
C[0][1] = 6 + 16 = 22
C[1][0] = 15 + 28 = 43
C[1][1] = 18 + 32 = 50

通过这个具体案例,可以清晰看到:

  1. 数据如何通过k进行分组
  2. Reducer如何计算部分乘积
  3. 两阶段MapReduce如何协作完成矩阵相乘

更常见的实现方式

从矩阵A中的元素aij开始,我们将其转化为p个<key, value>对的形式,其中key=“i,k”(其中k=1,2,…,p),value=“a:j,aij”。类似地,从矩阵B中的元素bij开始,我们将其转化为m个<key, value>对的形式,其中key=“k,j”(其中k=1,2,…,m),value=“b:i,bij”。

通过这样的处理,我们就能够得到具有相同key(“i,j”)的数据对,同时通过value中的"a:"和"b:"来区分元素是来自矩阵A还是矩阵B,以及它们在矩阵A的哪一列或矩阵B的哪一行。

我们可以通过以下分步解释来实现这种方法的矩阵相乘,并通过具体示例验证其可行性:

方法原理

该方法的本质是将矩阵元素按所有可能参与计算的维度预先分发,使得Reducer能直接获取到计算C[i][j] = Σ(A[i][k] * B[k][j])所需的全部数据。以下是具体实现步骤:

输入数据格式

矩阵A的行数为m,列数为n,aij为矩阵A第i行j列的元素;矩阵B的行数为n,列数为p,bij为矩阵B第i行j列的元素。

  • 矩阵A元素格式:(i,j,A[i][j])
  • 矩阵B元素格式:(k,j,B[k][j]) (注意此处应为B的行号为k)

处理流程

  1. Mapper阶段

    • 矩阵A的每个元素A[i][j]生成p个键值对(假设B的列数为p):
      • Key: i,k(k从1到p)
      • Value: a:j,A[i][j]
    • 矩阵B的每个元素B[k][j]生成m个键值对(假设A的行数为m):
      • Key: k,j
      • Value: b:i,B[k][j]
  2. Shuffle/Sort阶段

    • 系统自动将相同i,kk,j组合的键聚合到同一Reducer
  3. Reducer阶段

    • 对每个键i,k,收集所有a:jb:i的值
    • 计算对应元素的乘积,并输出中间结果
  4. 二次聚合阶段(可能需要第二趟MapReduce):

    • 按最终结果坐标i,j求和

具体实现步骤

阶段1:元素分发与初步计算

Mapper逻辑

// 处理矩阵A的元素
public void mapA(int i, int j, double value) {
    for (int k = 1; k <= p; k++) { // p为矩阵B的列数
        emit("i=" + i + ",k=" + k, "a:j=" + j + ",v=" + value);
    }
}

// 处理矩阵B的元素
public void mapB(int k, int j, double value) {
    for (int i = 1; i <= m; i++) { // m为矩阵A的行数
        emit("k=" + k + ",j=" + j, "b:i=" + i + ",v=" + value);
    }
}

Reducer逻辑

public void reduce(String key, Iterable<String> values) {
    Map<Integer, Double> aMap = new HashMap<>(); // 存储j->A值
    Map<Integer, Double> bMap = new HashMap<>(); // 存储i->B值
    
    // 分离A、B元素
    for (String val : values) {
        if (val.startsWith("a:")) {
            String[] parts = val.split("j=")[1].split(",v=");
            int j = Integer.parseInt(parts[0]);
            double aVal = Double.parseDouble(parts[1]);
            aMap.put(j, aVal);
        } else {
            String[] parts = val.split("i=")[1].split(",v=");
            int i = Integer.parseInt(parts[0]);
            double bVal = Double.parseDouble(parts[1]);
            bMap.put(i, bVal);
        }
    }

    // 计算笛卡尔积
    for (Entry<Integer, Double> aEntry : aMap.entrySet()) {
        int j = aEntry.getKey();
        double aVal = aEntry.getValue();
        for (Entry<Integer, Double> bEntry : bMap.entrySet()) {
            int i = bEntry.getKey();
            double bVal = bEntry.getValue();
            emit("i=" + i + ",j=" + j, aVal * bVal);
        }
    }
}

阶段2:结果聚合

二次Mapper(恒等映射):

public void map(String key, double value) {
    emit(key, value);
}

二次Reducer

public void reduce(String key, Iterable<Double> values) {
    double sum = 0;
    for (Double v : values) {
        sum += v;
    }
    emit(key, sum);
}

具体示例验证

假设:

矩阵A(2x2):
A = [[1, 2],
     [3, 4]]

矩阵B(2x2):
B = [[5, 6],
     [7, 8]]

阶段1处理流程

矩阵A元素分发

原始元素 生成键值对
A[0][0] ("i=0,k=0", "a:j=0,v=1")
("i=0,k=1", "a:j=0,v=1")
A[0][1] ("i=0,k=0", "a:j=1,v=2")
("i=0,k=1", "a:j=1,v=2")
A[1][0] ("i=1,k=0", "a:j=0,v=3")
("i=1,k=1", "a:j=0,v=3")
A[1][1] ("i=1,k=0", "a:j=1,v=4")
("i=1,k=1", "a:j=1,v=4")

矩阵B元素分发

原始元素 生成键值对
B[0][0] ("k=0,j=0", "b:i=0,v=5")
("k=0,j=0", "b:i=1,v=5")
B[0][1] ("k=0,j=1", "b:i=0,v=6")
("k=0,j=1", "b:i=1,v=6")
B[1][0] ("k=1,j=0", "b:i=0,v=7")
("k=1,j=0", "b:i=1,v=7")
B[1][1] ("k=1,j=1", "b:i=0,v=8")
("k=1,j=1", "b:i=1,v=8")

Reducer处理示例(以键"i=0,k=0"为例)

  • 接收到的values:
    "a:j=0,v=1" (来自A[0][0])
    "a:j=1,v=2" (来自A[0][1])
    "b:i=0,v=5" (来自B[0][0])
    "b:i=1,v=5" (来自B[0][0])
    
  • 计算过程:
    aMap = {0:1.0, 1:2.0}
    bMap = {0:5.0, 1:5.0}
    
    生成乘积:
    (j=0, i=0) → 1*5 = 5 → 输出("i=0,j=0",5)
    (j=0, i=1) → 1*5 = 5 → 输出("i=1,j=0",5)
    (j=1, i=0) → 2*5 = 10 → 输出("i=0,j=1",10)
    (j=1, i=1) → 2*5 = 10 → 输出("i=1,j=1",10)
    

阶段1输出结果

// 来自各个Reducer的输出:
("i=0,j=0",5), ("i=0,j=0",14) → 需要求和
("i=0,j=1",6), ("i=0,j=1",16)
("i=1,j=0",15), ("i=1,j=0",28)
("i=1,j=1",18), ("i=1,j=1",32)

阶段2最终结果

i=0,j=0 → 5+14=19
i=0,j=1 → 6+16=22
i=1,j=0 → 15+28=43
i=1,j=1 → 18+32=50

方法分析

优势:

  1. 并行度更高:允许不同k值的计算完全并行
  2. 灵活性:适合动态调整矩阵维度
  3. 容错性:细粒度任务划分

缺陷:

  1. 数据爆炸:矩阵A每个元素生成p个副本,B每个元素生成m个副本
    • 对于n×n矩阵,数据量膨胀到O(n³)
  2. 存储压力:中间结果需要大量磁盘空间
  3. 网络开销:Shuffle阶段传输数据量巨大

性能对比

指标 传统方法 本方法
Mapper输出 O(n²) O(n³)
Shuffle数据量 O(n²) O(n³)
Reducer计算 O(n³/p) O(n³/p)
适用场景 中小型矩阵 理论验证/特殊场景

优化建议

  1. 稀疏矩阵优化

    // 在Mapper中跳过零值
    if (value == 0) return;
    
  2. Combiner应用

    // 在阶段1后添加局部聚合
    job.setCombinerClass(Phase2Reducer.class);
    
  3. 分块处理

    将矩阵划分为b×b块:
    - 每个块生成b个副本而非n个
    - 数据量降为O(b·n²)
    
  4. 压缩技术

    <!-- 在mapred-site.xml中启用压缩 -->
    <property>
      <name>mapreduce.map.output.compress</name>
      <value>true</value>
    </property>
    

总结

该方法通过空间换时间的策略实现更高并行度,但需要谨慎处理数据膨胀问题。适用于以下场景:

  1. 理论研究需要极端并行化
  2. 特殊硬件环境(如超大内存集群)
  3. 稀疏矩阵计算(需配合零值优化)

实际生产环境中更推荐传统两阶段MapReduce方法,但在学术研究或特定优化场景下,这种多维分发策略可以提供新的思路。理解这种方法有助于深入掌握MapReduce的分发机制和矩阵计算的核心原理。

更复杂的例子

让我们通过一个具体的4×3矩阵A与3×2矩阵B相乘的例子,详细展示这种方法的完整执行过程。假设:

矩阵A(4行3列):

A = [
    [1,  2,  3],  // 行0
    [4,  5,  6],  // 行1
    [7,  8,  9],  // 行2
    [10, 11, 12]  // 行3
]

矩阵B(3行2列):

B = [
    [5, 6],  // 行0
    [7, 8],  // 行1
    [9, 10]  // 行2
]

预期计算结果

结果矩阵C = A×B 应该是:

C[0][0] = 1*5 + 2*7 + 3*9 = 5+14+27=46
C[0][1] = 1*6 + 2*8 + 3*10=6+16+30=52
C[1][0] = 4*5 +5*7 +6*9=20+35+54=109
C[1][1] = 4*6+5*8+6*10=24+40+60=124
...其他行同理

数据预处理

输入文件格式(假设存储为input/matrix.txt):

A,0,0,1.0
A,0,1,2.0
A,0,2,3.0
A,1,0,4.0
A,1,1,5.0
A,1,2,6.0
A,2,0,7.0
A,2,1,8.0
A,2,2,9.0
A,3,0,10.0
A,3,1,11.0
A,3,2,12.0

B,0,0,5.0
B,0,1,6.0
B,1,0,7.0
B,1,1,8.0
B,2,0,9.0
B,2,1,10.0

以下是用表格形式详细展示4x3矩阵A(行号i,列号j)与3x2矩阵B(行号k,列号j)相乘的完整处理过程:


矩阵定义

矩阵 维度 示例元素
A 4x3 A[0][0]=1.0
B 3x2 B[0][0]=5.0

阶段1:Mapper处理

矩阵A元素处理(以A[0][0]=1.0为例)

元素坐标 生成键值对 说明
(0,0) key="i=0,k=0" → value="a:j=0,1.0" 向所有k维度分发(B的列数=2)
key="i=0,k=1" → value="a:j=0,1.0"

矩阵B元素处理(以B[0][0]=5.0为例)

元素坐标 生成键值对 说明
(0,0) key="k=0,j=0" → value="b:i=0,5.0" 向所有i维度分发(A的行数=4)
key="k=0,j=0" → value="b:i=1,5.0"
key="k=0,j=0" → value="b:i=2,5.0"
key="k=0,j=0" → value="b:i=3,5.0"

阶段2:Shuffle过程

键分组示例(以key="i=0,k=0"和key="k=0,j=0"为例)

收到的值集合
"i=0,k=0" ["a:j=0,1.0", "a:j=1,2.0", "a:j=2,3.0"] (来自A的第0行所有元素)
"k=0,j=0" ["b:i=0,5.0", "b:i=1,5.0", "b:i=2,5.0", "b:i=3,5.0"] (来自B的第0行第0列)

阶段3:Reducer处理

处理key="i=0,k=0"

处理步骤 数据示例 说明
1. 分离A/B元素 A元素:j=0→1.0, j=1→2.0, j=2→3.0
B元素:i=0→5.0, i=1→5.0, i=2→5.0, i=3→5.0
建立j→A值和i→B值的映射
2. 计算笛卡尔积 j=0 * i=0 → 1.05.0=5.0
j=1 * i=0 → 2.0
5.0=10.0
...
生成部分乘积
3. 输出中间结果 ("i=0,j=0",5.0)
("i=0,j=0",10.0)
("i=0,j=0",15.0)
需要后续求和

完整流程示例(计算C[0][0])

步骤 Mapper输出示例 Shuffle后数据 Reducer处理 输出结果
1 A[0][0]=1 → ("i=0,k=0","a:0,1")
A[0][1]=2 → ("i=0,k=0","a:1,2")
A[0][2]=3 → ("i=0,k=0","a:2,3")
key="i=0,k=0" → [a:0,1; a:1,2; a:2,3] 解析A元素的j值:0→1,1→2,2→3
2 B[0][0]=5 → ("k=0,j=0","b:0,5")
B[1][0]=7 → ("k=0,j=0","b:0,7")
B[2][0]=9 → ("k=0,j=0","b:0,9")
key="k=0,j=0" → [b:0,5; b:0,7; b:0,9] 解析B元素的k值:0→5,1→7,2→9
3 - 系统自动合并相关键 计算乘积:
15=5
2
7=14
3*9=27
("0,0",5)
("0,0",14)
("0,0",27)
4 - 二次Shuffle聚合 求和:5+14+27=46 ("0,0",46)

全流程数据流总表

阶段 输入数据 处理逻辑 输出数据格式
Mapper A[i][j]或B[k][j] A:生成p个(i,k)键(p=B的列数)
B:生成m个(k,j)键(m=A的行数)
key="i,k"→"a:j,v"
key="k,j"→"b:i,v"
Shuffle 键值对 按key分组,相同key的数据发送到同一Reducer key→[values]列表
Reducer 同一key的A/B元素集合 1. 分离A的(j→v)和B的(i→v)
2. 计算所有j-i组合的乘积
key="i,j"→乘积结果
最终聚合 所有(i,j)的乘积结果 按(i,j)分组求和 key="i,j"→最终矩阵元素值

性能特征表

矩阵维度 阶段 数据量 计算复杂度
A:4x3 Mapper输出 4x3x2=24条 O(n³)
B:3x2 Mapper输出 3x2x4=24条 O(n³)
Shuffle 中间数据 24+24=48条 -
Reducer 乘积计算次数 4x2x3=24次 O(n³)

异常处理案例表

异常场景 检测方法 处理方案
矩阵维度不匹配 检查A的列数是否等于B的行数 抛出IllegalArgumentException
非数值型数据 try-catch解析数值 记录错误计数器,跳过该记录
数据格式错误 验证token数量是否=4 写入错误日志,忽略错误行
内存溢出 监控values集合大小 分批次处理或报错终止

通过这个完整的表格化流程展示,可以清晰看到:

  1. 数据分发策略:通过设计键结构确保必要元素的相遇
  2. 计算并行性:不同(i,k)对的Reducer可以完全并行执行
  3. 数据膨胀问题:Mapper输出量是原始数据的O(n³)倍
  4. 最终聚合需求:必须进行二次求和才能得到正确结果

这种可视化展示方式有助于理解MapReduce处理矩阵相乘的核心机制,同时也突显了该方法在大规模数据场景下的潜在性能瓶颈。

posted @ 2025-03-18 10:20  漫舞八月(Mount256)  阅读(188)  评论(0)    收藏  举报