使用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. OpenOffice4.1.6 linux安装/卸载教程

    以下是centos的安装方法 1.先安装并配置好jdk 2.拷贝 把Apache_OpenOffice_4.1.6_Linux_x86-64_install-rpm_zh-CN.tar.gz拷贝到自己 ...

  2. [Kotlin Tutorials 22] 协程中的异常处理

    协程中的异常处理 Parent-Child关系 如果一个coroutine抛出了异常, 它将会把这个exception向上抛给它的parent, 它的parent会做以下三件事情: 取消其他所有的ch ...

  3. Oracle Users表空间重命名

    需求:默认无法直接删除Oracle的users表空间,直接尝试删除会有报错如下: SQL> drop tablespace users including contents and datafi ...

  4. 函数接口(Functional Interfaces)

    定义 首先,我们先看看函数接口在<Java语言规范>中是怎么定义的: 函数接口是一种只有一个抽象方法(除Object中的方法之外)的接口,因此代表一种单一函数契约.函数接口的抽象方法可以是 ...

  5. uni-app简单通用Request网络请求 支持请求成功 失败回调

    uni-app简单通用Request网络请求 支持请求成功 失败回调; 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=12794 ...

  6. 2023-06-23:redis中什么是缓存击穿?该如何解决?

    2023-06-23:redis中什么是缓存击穿?该如何解决? 答案2023-06-23: 缓存击穿是指一个缓存中的热点数据非常频繁地被大量并发请求访问,当该热点数据失效的瞬间,持续的大并发请求无法通 ...

  7. Taurus .Net Core 微服务开源框架:Admin 插件【4-1】 - 配置管理-Kestrel【含https启用】

    前言: 继上篇:Taurus .Net Core 微服务开源框架:Admin 插件[3] - 指标统计管理 本篇继续介绍下一个内容: 1.系统配置节点:App - Config 界面 界面图如下: 双 ...

  8. zynq7000 emc启动及其加速

    背景需求 ZYNQ 7000系统在出场时需要将固件从eMMC启动,原因有2: FLASH存储空间小: SD卡容易脱落,不适合产品存放系统文件: 需要注意,ZYNQ7000 系列不支持eMMC作为BOO ...

  9. 2020 OC项目集成flutter

    看了网上基本上都是相同套路,但是到我这就不行了,终于解决了, 主要就是profile文件编写, 1.在oc项目的同级目录项创建flutter模块 就是在oc项目的父级目录下,执行 flutter cr ...

  10. 详解在Linux中修改Tomcat使用的jdk版本

    问题分析 由于部署个人项目使用了openjdk11,但是我之前安装的是jdk1.8,jdk版本升级的后果就是,tomcat运行的时候报一点小bug(因为之前安装tomcat默认使用了系统的jdk版本) ...