使用shuffle sharding增加容错性

最近在看kubernetes的API Priority and Fairness,它使用shuffle sharding来为请求选择处理队列,以此防止高吞吐量流挤占低吞吐量流,进而造成请求延迟的问题。

介绍

首先看下什么是shuffle sharding,下面内容来自aws的Workload isolation using shuffle-sharding

首先来看如何使用一般分片方式来让系统具备可扩展性和弹性。

假设有一个8 workers节点的水平可扩展的系统或服务,下图红线表示达到这些节点的请求,worker可以是服务,队列或数据库等。

如果没有任何分片,则要求每个worker能够处理所有请求。这种方式高效且具备一定的冗余性。如果一个worker出现故障,则可以将它的任务分配到剩余的7个worker上。此时可能需要增加一定的系统容量。但如果突然出现大量请求,如DDoS攻击,可能会导致级联故障。下面两张图展示了故障是如何升级的。

首先会影响第一台worker,随后会级联到其他workers上,最终导致整个服务不可用。

为了防止故障转移,通常可以使用分片方式,如将workers分为4个分片,以效率换取影响度。下面两张图展示了如何使用分片来限制DDoS攻击。

本例中,每个分片包含2个workers,并按照资源(如域名)进行切片。此时的系统仍然具有冗余性,但由于每个分片只有2个workers,因此可能需要增加容量来避免故障。

通过这种方式降低了故障影响范围。这里有4个分片,如果一个分片故障,则只会影响该分片上的服务,其他分片则不受影响。影响范围为25%。使用shuffle sharding可以达到更好的效果。

shuffle sharding用到了虚拟分片(shuffle shard)的概念,这里将不会直接对workers进行分片,而是按照"用户"进行分片,目的是尽量将用户打散分布到不同的worker上。

下图展示的shuffle sharding布局中包含8个workers和8个客户,并给每个客户分配了2个workers。以彩虹和玫瑰表示的客户为例。

这里,我们给彩虹客户分配了第1个和第4个worker,这两个workers构成了该客户的shuffle shard,其他客户将使用不同的虚拟分片(含2个workers),如玫瑰客户分配了第1个和最后一个worker。

如果彩虹用户分配的worker 1和worker 4出现了问题(如恶意请求或请求泛红等),则此问题只会影响本虚拟分片,但不会影响到其他shuffle shard。事实上,最多只会有另外一个shuffle shard会受到影响(即另外一个服务都部署到了worker 1和worker 4)。如果请求方具有容错性,则可以继续使用剩余分片继续提供服务。

换句话说,当彩虹客户所在的节点因为出现问题或受到攻击而无法提供服务时,不会影响到其他节点。对于客户而言,虽然玫瑰客户和向日葵客户都和彩虹客户共享了worker,但并没有导致其服务中断,玫瑰客户仍然可以继续使用workers 8提供服务,而向日葵客户可以继续使用worker 6提供服务。

当出现上述问题时,虽然失去了四分之一的worker节点,但使用shuffle sharding可以大大降低影响范围。上述场景下,一共有28种两两worker的组合方式,即28种shuffle shards。当有上百甚至更多的客户时,我们可以给每个客户分配一个shuffle shards,以此可以将影响范围缩小到1/28,效果是一般分片方式的7倍。

kubernetes中的shuffle sharding

使用shuffle sharding为流分片队列

kubernetes的流控功能中使用了shuffle sharding,其代码实现如下:

func NewDealer(deckSize, handSize int) (*Dealer, error) {
if deckSize <= 0 || handSize <= 0 {
return nil, fmt.Errorf("deckSize %d or handSize %d is not positive", deckSize, handSize)
}
if handSize > deckSize {
return nil, fmt.Errorf("handSize %d is greater than deckSize %d", handSize, deckSize)
}
if deckSize > 1<<26 {
return nil, fmt.Errorf("deckSize %d is impractically large", deckSize)
}
if RequiredEntropyBits(deckSize, handSize) > MaxHashBits {
return nil, fmt.Errorf("required entropy bits of deckSize %d and handSize %d is greater than %d", deckSize, handSize, MaxHashBits)
} return &Dealer{
deckSize: deckSize,
handSize: handSize,
}, nil
} func (d *Dealer) Deal(hashValue uint64, pick func(int)) {
// 15 is the largest possible value of handSize
var remainders [15]int //这个for循环用于生成[0,deckSize)范围内的随机数。
for i := 0; i < d.handSize; i++ {
hashValueNext := hashValue / uint64(d.deckSize-i)
remainders[i] = int(hashValue - uint64(d.deckSize-i)*hashValueNext)
hashValue = hashValueNext
} for i := 0; i < d.handSize; i++ {
card := remainders[i]
for j := i; j > 0; j-- {
if card >= remainders[j-1] {
card++
}
}
pick(card)
}
} func (d *Dealer) DealIntoHand(hashValue uint64, hand []int) []int {
h := hand[:0]
d.Deal(hashValue, func(card int) { h = append(h, card) })
return h
}
  1. 首先使用func NewDealer(deckSize, handSize int)初始化一个实例,以kubernetes的APF功能为例,deckSize为队列数,handSize表示为一条流分配的队列数量

  2. 使用func (d *Dealer) DealIntoHand(hashValue uint64, hand []int)可以返回为流选择的队列ID,hashValue可以看做是流的唯一标识,hand为存放结果的数组。

    hashValue的计算方式如下,fsName为flowschemas的名称,fDistinguisher可以是用户名或namespace名称:

    func hashFlowID(fsName, fDistinguisher string) uint64 {
    hash := sha256.New()
    var sep = [1]byte{0}
    hash.Write([]byte(fsName))
    hash.Write(sep[:])
    hash.Write([]byte(fDistinguisher))
    var sum [32]byte
    hash.Sum(sum[:0])
    return binary.LittleEndian.Uint64(sum[:8])
    }

用法如下:

	var backHand [8]int
deal, _ := NewDealer(128, 9)
fmt.Println(deal.DealIntoHand(8238791057607451177, backHand[:]))
//输出:[41 119 0 49 67]

为请求分片队列

上面为流分配了队列,实现了流之间的队列均衡。此时可能为单条流分配了多个队列,下一步就是将单条流的请求均衡到分配到的各个队列中。核心代码如下:

func (qs *queueSet) shuffleShardLocked(hashValue uint64, descr1, descr2 interface{}) int {
var backHand [8]int
// Deal into a data structure, so that the order of visit below is not necessarily the order of the deal.
// This removes bias in the case of flows with overlapping hands.
//获取本条流的队列列表
hand := qs.dealer.DealIntoHand(hashValue, backHand[:])
handSize := len(hand)
//qs.enqueues表示队列中的请求总数,这里第一次哈希取模算出队列的起始偏移量
offset := qs.enqueues % handSize
qs.enqueues++
bestQueueIdx := -1
minQueueSeatSeconds := fqrequest.MaxSeatSeconds
//这里用到了上面的偏移量,并考虑到了队列处理延迟,找到延迟最小的那个队列作为目标队列
for i := 0; i < handSize; i++ {
queueIdx := hand[(offset+i)%handSize]
queue := qs.queues[queueIdx]
queueSum := queue.requests.QueueSum() // this is the total amount of work in seat-seconds for requests
// waiting in this queue, we will select the queue with the minimum.
thisQueueSeatSeconds := queueSum.TotalWorkSum
klog.V(7).Infof("QS(%s): For request %#+v %#+v considering queue %d with sum: %#v and %d seats in use, nextDispatchR=%v", qs.qCfg.Name, descr1, descr2, queueIdx, queueSum, queue.seatsInUse, queue.nextDispatchR)
if thisQueueSeatSeconds < minQueueSeatSeconds {
minQueueSeatSeconds = thisQueueSeatSeconds
bestQueueIdx = queueIdx
}
}
...
return bestQueueIdx
}

使用shuffle sharding增加容错性的更多相关文章

  1. Hadoop笔记HDFS(1)

    环境:Hadoop2.7.3 1.Benchmarking HDFS 1.1测试集群的写入 运行基准测试是检测HDFS集群是否正确安装以及表现是否符合预期的好方法.DFSIO是Hadoop自带的一个基 ...

  2. SharePoint咨询师之路:设计之前的那些事四:负载均衡 - web服务器

     提示:本系列只是一个学习笔记系列,大部分内容都可以从微软官方网站找到,本人只是按照自己的学习路径来学习和呈现这些知识.有些内容是自己的经验和积累,如果有不当之处,请指正. 容量管理 规模 体系结构 ...

  3. 【转载】Apache Spark Jobs 性能调优(一)

    当你开始编写 Apache Spark 代码或者浏览公开的 API 的时候,你会遇到各种各样术语,比如 transformation,action,RDD 等等. 了解到这些是编写 Spark 代码的 ...

  4. Apache Spark Jobs 性能调优

    当你开始编写 Apache Spark 代码或者浏览公开的 API 的时候,你会遇到各种各样术语,比如transformation,action,RDD(resilient distributed d ...

  5. 消息队列中间件(三)Kafka 入门指南

    Kafka 来源 Kafka的前身是由LinkedIn开源的一款产品,2011年初开始开源,加入了 Apache 基金会,2012年从 Apache Incubator 毕业变成了 Apache 顶级 ...

  6. Kafka 基本概念学习笔记

    一. 什么是Kafka 面向数据流的生产,转换,存储,消费的整体流处理平台 二.Kafka三大特性 1.发布和订阅数据的流,类似于消息队列,消息系统 2..数据流存储平台 3.当数据产生的时候,对数据 ...

  7. Kafka流处理平台

    1. Kafka简介 Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性 ...

  8. Android注入完全剖析

    0 前沿 本文主要分析了一份实现Android注入的代码的技术细节,但是并不涉及ptrace相关的知识,所以读者如果不了解ptrace的话,最好先学习下ptrace原理再来阅读本文.首先,感谢源代码的 ...

  9. USB 3.0规范中译本 第7章 链路层

    本文为CoryXie原创译文,转载及有任何问题请联系cory.xie#gmail.com. 链路层具有维持链路连接性的责任,从而确保在两个链路伙伴之间的成功数据传输.基于包(packets)和链路命令 ...

  10. ~~番外:说说Python 面向对象编程~~

    进击のpython Python 是支持面向对象的 很多情况下使用面向对象编程会使得代码更加容易扩展,并且可维护性更高 但是如果你写的多了或者某一对象非常复杂了,其中的一些写法会相当相当繁琐 而且我们 ...

随机推荐

  1. 【python基础】复杂数据类型-列表类型(排序/长度/遍历)

    1.列表数据元素排序 在创建的列表中,数据元素的排列顺序常常是无法预测的.这虽然在大多数情况下都是不可避免的,但经常需要以特定的顺序呈现信息.有时候希望保留列表数据元素最初的排列顺序,而有时候又需要调 ...

  2. ensp 链路聚合

    链路聚合(Link Aggregation)   指将多个物理端口汇聚在一起,形成一个逻辑端口,以实现出/入流量吞吐量在各成员端口的负荷分担,链路聚合在增加链路带宽.实现链路传输弹性和工程冗余等方面是 ...

  3. Dapr在Java中的实践 之 状态管理

    状态管理 状态管理(State Management)使用键值对作为存储机制,可以轻松的使长时运行.高可用的有状态服务和无状态服务共同运行在我们的服务中. 我们的服务可以利用Dapr的状态管理API在 ...

  4. 【python基础】复杂数据类型-字典(增删改查)

    1.初识字典 字典,是另外一种复杂的数据类型,相较于列表,字典可以将相关信息关联起来.比如说一个人的信息有名字.年龄.性别等,如果用列表存储的话,不能表示他们之间是相关联的,而字典可以,字典是一个或多 ...

  5. Unity的IPostGenerateGradleAndroidProject:深入解析与实用案例

    Unity IPostGenerateGradleAndroidProject Unity是一款流行的跨平台游戏引擎,它支持多种平台,包括Android.在Unity中,我们可以使用IPostGene ...

  6. python中的数据容器

    第六章:Python数据容器 数据容器入门 什么是数据容器 一种可以容纳多份数据的数据类型,容纳的每一份数据称之为1个元素,每一个元素,可以是任意类型的数据,如字符串.数字.布尔等. 根据特点的不同分 ...

  7. Day03_Java_作业

    A:选择题 1.给出以下代码,请问表达式grade==70在以下两条语句中是否获得执行? boolean flag = false && grade == 70;//第一条语句 boo ...

  8. PTA 21级数据结构与算法实验4—字符串和数组

    目录 7-1 字符串模式匹配(KMP) 7-2 [模板]KMP字符串匹配 7-3 统计子串 7-4 好中缀 7-5 病毒变种 7-6 判断对称矩阵 7-7 三元组顺序表表示的稀疏矩阵转置运算Ⅰ 7-8 ...

  9. 进程相关API

    ID与句柄 如果我们成功创建一个进程,CreateProcess函数会给我们返回一个结构体,包括四个数 据:进程编号(ID).进程句柄.线程编号(ID).线程句柄. 进程ID其实我们早见过了,通常我们 ...

  10. Vue通过v-modal实现子组件通讯

    1.在props设置属性value props: { value: { type: Object, default: {} } } 1.设置data数据,接收value,在mounted或create ...