Istio Mixer Cache工作原理与源码分析(4)-签名

接前文,继续分析Mixer Check Cache的源码,这次的重点是签名算法,也就是Referenced::Signature()方法。

前情回顾:

  1. Referenced保存的是mixer adapter使用的引用属性的一个组合,也就是前面例子中的 “a,b,c”或者“a,b,c不存在”
  2. Referenced中有两个数据结构: std::vector<AttributeRef> absence_keys_std::vector<AttributeRef> exact_keys_,exact_keys_保存的是一定要出现的属性, absence_keys_中保存的是没有出现的属性

基本流程

我们来看详细源代码,具体在文件src/istio/mixerclient/referenced.cc中,代码的基本流程非常清晰:

bool Referenced::Signature(const Attributes &attributes,
                           const std::string &extra_key,
                           std::string *signature) const {
  // 第一步,先检查输入是否匹配保存的引用属性
  // 必须同时满足absent key和exact key的要求
  if (!CheckAbsentKeys(attributes) || !CheckExactKeys(attributes)) {
    return false;
  }

  // 发现匹配之后,才开始计算签名
  CalculateSignature(attributes, extra_key, signature);
  return true;
}

切记:请更新到 istio/proxy 仓库的最新代码,在master分支上才能看到这个版本。

这里的代码在此之前是存在性能问题的,我为此提交了一个改进方案,由于0.8版本发布前锁了master分支,因此这个fix的代码是在0.8版本发布之后才进的master分支。

详情请见:https://github.com/istio/proxy/issues/1531

引用属性匹配

先检查absent key,这里要求请求中的属性,不能出现 absence_keys_ 保存的属性,否则就是不匹配:

bool Referenced::CheckAbsentKeys(const Attributes &attributes) const {
  const auto &attributes_map = attributes.attributes();
  for (std::size_t i = 0; i < absence_keys_.size(); ++i) {
    // 检查每个absence_key
    const auto &key = absence_keys_[i];
    const auto it = attributes_map.find(key.name);
    if (it == attributes_map.end()) {
      // 如果在输入的属性中没有找到,就继续下一个
      continue;
    }

    // 如果找到了,则直接返回不匹配
    return false;
    // 实际代码中还有特别的 StringMap 类型的属性需要额外处理,简单起见我们忽略它
  }
  // 只有absence_key都没有在输入的属性中出现,才表示匹配
  return true;
}

再检查exact keys,这里要求exact keys中保存的每一个属性,必须在请求中出现,否则就是不匹配:

bool Referenced::CheckExactKeys(const Attributes &attributes) const {
  const auto &attributes_map = attributes.attributes();
  for (std::size_t i = 0; i < exact_keys_.size(); ++i) {
    // 检查每个exact_key
    const auto &key = exact_keys_[i];
    const auto it = attributes_map.find(key.name);
    // 如果没有在请求中出现就返回不匹配
    if (it == attributes_map.end()) {
      return false;
    }
	// 实际代码中还有特别的 StringMap 类型的属性需要额外处理,简单起见我们忽略它
    }
  }
  // 只有exact_key都在输入的属性中出现,才表示匹配
  return true;
}

简单说,引用属性匹配的要求就是:exact key都必须出现,absence key都不能出现。

输入 exact=“a,b,c”,absent="" exact=“a,b”,absent=“c”
“a=1,b=2,c=3,e=4,f=5” Yes No
“a=1,b=2,e=4,f=5” No Yes

计算签名

在exact key和absent key检查通过之后,就意味着请求中的属性满足当前Referenced的匹配要求。

下一步就可以进行签名计算了,CalculateSignature()方法的参数中attributes是输入的所有属性,extra_key这个参数目前没有使用,忽略即可:

void Referenced::CalculateSignature(const Attributes &attributes,
                                    const std::string &extra_key,
                                    std::string *signature) const {
  const auto &attributes_map = attributes.attributes();

  utils::MD5 hasher;
  // 游历exact_keys_ 中的每个属性
  for (std::size_t i = 0; i < exact_keys_.size(); ++i) {
    const auto &key = exact_keys_[i];
    // 在输入的属性中通过属性名找到包含值的属性
    const auto it = attributes_map.find(key.name);

    hasher.Update(it->first);
    hasher.Update(kDelimiter, kDelimiterLength);

    // 根据属性值的不同类型,调用hasher.Update()方法进行计算
    const Attributes_AttributeValue &value = it->second;
    switch (value.value_case()) {
      case Attributes_AttributeValue::kStringValue:
        hasher.Update(value.string_value());
        break;
      ......// 忽略其他类型的处理代码
      case Attributes_AttributeValue::VALUE_NOT_SET:
        break;
    }
    hasher.Update(kDelimiter, kDelimiterLength);
  }
  hasher.Update(extra_key);

  // 完成签名计算的最后一步,得到签名
  *signature = hasher.Digest();
}

即CalculateSignature()方法会将exact_keys_ 指定的请求属性进行签名,注意只对 exact_keys_ 的属性进行签名,absent key反正没有出现自然无需也无法对它们进行计算。

形象起见,以我们前面介绍基础概念和工作原理时的例子做讲解,假设 referenced_map 保存的引用属性组合为 {“k1”: “a,b,c”, “k2”: “a,b,c不存在” }

请求 和请求匹配的引用属性 进行签名计算的实际属性值
“a=1,b=2,c=3,e=4,f=5” exact=“a,b,c”, absent="" a=1,b=2,c=3
“a=1,b=2,e=4,f=5” exact=“a,b”, absent=“c” a=1,b=2

总结

签名算法的关键在于需要先匹配exact key和absent key,然后再计算。和主流程代码一样,只要理解了引用属性和absent key的概念,就容易理解了。

敖小剑
敖小剑
新时代农民工 * 中年码农

我目前研究的方向主要在Microservice、Servicemesh、Serverless等Cloud Native相关的领域,全职从事Dapr开发,欢迎交流和指导。