使用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. DASCTF二进制专项部分Writeup

    easynote create:堆大小可以任意分配只要不超过0xFFF create()  unsigned __int64 create() { int i; // [rsp+0h] [rbp-20 ...

  2. Vue3基本功能实现

    vue3 介绍 # Vue3的变化 # 1.性能的提升 打包大小减少41% 初次渲染快55%, 更新渲染快133% 内存减少54% # 2.源码的升级 使用Proxy代替defineProperty实 ...

  3. IM1281B电能计量模块_C语言例程

    一.前言 毕设采用了艾锐达公司的IM1281B电量计能模块,找了一圈没发现具体的51单片机的例程,现在写个能使用的C语言例程,方便以后的开发者们. 二.事前准备 引脚定义: 引脚 功能说明 V+ 供电 ...

  4. 爆肝万字带你超级详细全面了解Linux命令大全

    前言 作者主页:CSDN丨博客园 学习交流:在下周周ovoの社区 对这篇万字博客目录总结如下: 关机命令.重启命令,创建用户.删除用户.修改密码.切换用户.切换到超级用户.禁用/解锁用户账户.修改信息 ...

  5. Java 设计模式实战系列—工厂模式

    在 Java 开发中,对象的创建是一个常见的场景,如果对象的创建和使用都写在一起,代码的耦合度高,也不利于后期的维护.我们可以使用工厂模式来解决这个问题,工厂模式是一个创建型模式,将对象的创建和使用分 ...

  6. 服务器端Ajax异步分页类,基本通用分页类

    public static class PagerHelper { #region 数字分页类 /// <summary> /// /// </summary> /// < ...

  7. 本地数据local storage和session storage

    随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据, HTML5规范提出了相关解决方案. 本地存储特性 1.数据存储在用户浏览器 ...

  8. 【MAUI Blazor踩坑日记】3.Windows标题栏自定义颜色,运行时改变颜色

    目录 修改默认颜色 修改运行时颜色 效果图 MAUI中Windows默认的标题栏颜色是灰色的,有一点丑. 如果去掉默认的标题栏,自己画一个,可能会出现问题,也比较麻烦. 想要自定义默认标题栏的颜色,官 ...

  9. js闭包的一些笔记

    闭包 闭包是一个可以访问外部作用域的内部函数,即使这个外部作用域已经执行结束 作用域 作用域决定这个变量的生命周期及其可见性.当我们创建一个函数,就会生成一个新的作用域. 通过var创建的变量只有函数 ...

  10. Truncate 和 Delete 的区别与选择

    1)事务和日志 delete   语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行回滚操作. truncate table  则 一次性地从表中删除所有 ...