NicholasTao / NicholasTao.github.io

✨ Build a beautiful and simple website in literally minutes. Demo at https://beautifuljekyll.com
https://beautifuljekyll.com
MIT License
0 stars 0 forks source link

kk #9

Open NicholasTao opened 2 years ago

NicholasTao commented 2 years ago

背景

目标

新升级框架,满足未来5年内的

旧升级流程

现有问题

大规模性能

Client上报处理时间长

现状

以upg-server向Host1下发10个组件升级rpm命令为例,

  1. upg-server查询Host的组件部署情况,计算需要升级的rpm包,将相关包信息、rpm信息、升级参数、是否是重试场景等信息下发给upg-client。大规模测试1500节点前处理耗时12分钟。
  2. upg-client在执行过程中会每分钟定时上报任务执行情况,如组件5个undo、3个doing、2个done。
  3. upg-server接收到上报信息后会保存instance执行状态,并查询对应package的所有组件是否执行成功,如果成功则刷新package状态。1500节点后在数据面升级平均处理时间5秒, 共计2小时。

问题分析

  1. upg-server对于每个节点的请求,需要进行大量前处理组织。
  2. 每分钟定时上报任务,在大规模环境下会占用大量资源以1000个host同时操作为例,平均每秒处理16.7个上报请求,其中绝大多数是不变的
  3. 每次接收上报后都刷新宏观package状态,要进行O(n)的查询计算操作,乘以节点规模,升级过程中每分钟都要进行O(n^2)的百万级别计算操作。严重影响性能。
  4. 每次操作都会对数据库和zookeeper进行循环读写,造成IO压力

解决方案

  1. 升级相关数据提前(在升级前信息收集阶段)保存在upg-client端,upg-server仅需下发需要操作的template列表。节省了upg-server前处理计算量, 分布式到各节点计算;也方便通过命令行手动升级。

    up_rpm host1 [nova-api, swift-store, keystone...]
  2. 改为定期且仅上报变更参数:upg-client仅上报上一分钟有变更的task状态。对于已上报的成功\失败的task不再次上报

  3. upg-server仅保存task状态,不进行任何其它操作。任务整体是否成功的判断,由另一线程(或进程)定期检查。

  4. 考虑到现网环境下upg-server较为稳定,必要的数据库zookeeper读写改为定时批量执行,平时已本地数据为主。异常情况下可由upg-client重新上报数据。

  5. 采用数据库范式设计数据库,减少冗余数据读写

Server查询时间长

现状

升级前端(HCCI\UPDATETOOL)在升级中会频繁调用查询接口。但一直以来大规模查询接口都比较慢,单次查询最多需要十分钟。

问题分析

  1. 升级框架数据结构不合理,大规模2000节点下,每次查询需要进行千量级读取,十万量级数据处理。
  2. 冗余数据过多,大部分情况下前端只需要知道升级是否正常进行中,每次频繁返回详细数据是没有必要的

解决方案

  1. 仅记录最小升级工步单元task的状态,高级状态由task状态组合而来。
  2. 减少查询次数,仅节点、组件完成工步后才返回详细状态,过程中仅显示doing或简略估计的进度。

效果

  1. 提高前端相应速度至少5倍
  2. 减少升级时长,2000节点下节省2小时以上。
  3. 提高大规模性能,避免大规模下查询计算量过大导致升级框架崩溃问题。

升级框架前后需要兼容

问题现状

升级框架自升级时需要用到前一版本的升级框架,导致升级框架自升级困难。例如:

  1. 8.1.0版本升级框架需要引入gaussdb, 要求8.0.3-8.1.0的升级框架自升级中进行数据库切换准备,此时使用的框架是8.0.3版本发布的升级框架。这意味着在开发8.0.2-8.0.3这一跳升级框架时必须预埋8.1.0版本的功能。
  2. 8.1.0新安装环境,cps数据已经从zookeeper切换到radis中,升级框架自分发代码还在读取zookeeper。由于8.1.0发布前测试无法完全测试8.1.0-8.1.X的自分发功能,导致该Bug一直无法发现,差点发布到现网造成8.1.0版本无法往后升级。

问题分析

升级框架当前是作为常驻服务部署的。如8.0.3新安装版本,尽管在很长一段时间内都不会有升级任务,upg-server, upg-client仍长期部署在环境中。8.0.3-8.1.0升级时,需要用8.0.3的升级框架对自身进行升级。这导致了:

  1. 升级框架在不升级时仍占用资源,有潜在内存泄漏、安全等风险。
  2. 开发困难,自升级逻辑复杂,需要做特殊处理。一般来说当前版本的自升级能力无法在当前版本充分测试,这导致自升级Bug多次出现在现网发布版本中。
  3. 需要跨版本考虑预埋功能,使开发测试困难。对于开发,当前版本开发需要考虑与下个版本兼容。对于测试,无法在当前版本充分测试下个版本的预埋功能。
  4. 升级框架必须随Openstack版本发布,限制了发布的灵活性

解决方案

升级框架不再常驻在环境中,每次升级前,通过cps导入所需升级框架安装包,安装升级框架再开始升级。

  1. 升级框架不升级时不占用资源,减少内存泄漏、安全等风险。
  2. 无需考虑自升级逻辑。
  3. 无需跨版本预埋功能。
  4. 升级框架可以独立发布,只要对对应Openstack发布版本挑选一个升级框架版本配合测试使用即可

效果

  1. 减少升级框架前后兼容工作量。约占总开发量的10%。
  2. 避免了升级框架自升级Bug出现在现网环境,影响之后版本升级

开发困难

升级粒度过大(重要)

底层数据结构不灵活,修改牵一发动全身。

问题现状

精细化升级需求,开发周期长,引入问题多

升级框架底层采用Service和Package作为基本单元处理

升级框架

依赖错误

牵一发而动全身,动静不分离

测试困难

无法单独调试错误点

现网定位时间长

代码可读性差

回退复杂

升级

背景介绍

旧升级流程

设计方案

总体设计

简略全局图和主要区别点

4+1视图

逻辑视图

简略模块划分

物理视图

新部署、未来分布式部署

运行视图

某个task流程的时序图

开发视图

重要,模块类关系

特性视图

主要特性

痛点解决

痛点解决图

逐点分析

模块详细设计

Task系统

模块图,以管理面升级为例

管理面升级Task流程

  1. 管理面升级前: 生成升级计划书

    # 升级计划书
    ...
    stop # 停止第一批组件
    nova-api host1-3, swift-store host1-3, ...
    stop # 停止第二批组件
    rabbitmq host1-2, keystone host1-3
    up_rpm # 升级所有组件rpm
    nova-api host1-3, swift-store host1-3, rabbitmq host1-2, keystone host1-3, ...
    start # 启动第二批组件
    rabbitmq host1-2, keystone host1-3
    start # 启动第一批组件
    nova-api host1-3, swift-store host1-3, ...
    ...
    # 数据面升级
    # 升级提交

    数据库中保存了所有task状态

    task_ID Action instance STATUS
    10001 stop nova-api host1 done
    10002 stop nova-api host2 undo
    ...
  2. upg-server API接收HCCI指令:

    upgrade "nova, rabbitmq, keystone" host1-2
  3. upg-server解析命令, 从升级计划书截取出当前:

    # 当前计划书
    stop # 停止第一批组件
    nova-api host1-2
    stop # 停止第二批组件
    rabbitmq host1-2, keystone host1-2
    up_rpm # 升级所有组件rpm
    nova-api host1-2, rabbitmq host1-2, keystone host1-2
    start # 启动第二批组件
    rabbitmq host1-2, keystone host1-2
    start # 启动第一批组件
    nova-api host1-2
  4. upg-server task执行器下发升级命令给upg-client(host1-2), 以下发升级所有组件rpm命令为例(可以看见命令非常简单, 因为其余所需数据, 以保存在了client端)

    # 下发给host1的命令
    up_rpm nova-api, rabbitmq, keystone
  5. upg-client执行接收到的命令, 安装相关rpm包

  6. upg-client三个instance执行完成后,一次性上报结果

    nova-api host1 done
    rabbitmq host1 done
    keystone host1 fail
  7. upg-server上报接收器收到upg-client的task执行结果后,定期将结果批量保存到数据库中 Plan B: upg-client直接写入数据库

  8. upg-server task执行器定时监控数据库,当所有task执行完成后。继续下一步(下发启动第二批组件命令), 如果有失败(如上文keystone host1 fail)则中止执行。

如此循环直到所有task执行完成

缩小升级粒度至instance

数据整理

次要改动:升级框架重新部署,脚本易用性,统一数据接口,简化回退,简化upg-server数据处理,模块解耦

Server模块

Client模块

NicholasTao commented 2 years ago

Server模块

(升级前)信息收集模块

升级编排模块

任务执行下发模块

Server侧任务模块

数据库模块

其它数据接口模块

其它模块

Client模块

信息收集模块

模块设计

(升级前)信息收集模块

Client收集节点组件部署、节点组件升级信息。上

Todo img sc交互、sc数据库

升级编排模块

任务执行模块

数据接口模块

采用数据接口解耦数据(DataBase, Cps, Zookeeper...), 改善现有数据获取代码混乱的情况

其它模块

设计原理

通过升级前静态信息收集, 避免了升级中负责的状态变化、获取

NicholasTao commented 2 years ago

1 概述 【关键内容】 新升级框架是全面替换旧升级框架,在保留对外提供升级服务(保留所有对外接口)的同时,新增了灵活可控操作,提升性能,简化SDK等优化。也为后续升级需求(N-X升级,多版本升级等)提供了扩展性,降低开发成本。 1.1 目的 本文档对升级框架的,数据模型(数据存储、数据接口),任务下发模型(instance级别细粒度),模块解耦等进行重新设计。明确主要数据结构和主要处理过程,作为今后的编码阶段的输入和编码人员、测试人员的指导。 1.2 范围 暂空缺 2 特性需求概述 基本特性: 与旧升级框架保持一致,云底座升级 新增特性: a) 灵活升级 a) 小工步独立回退升级:任意小工步升级失败后可以独立回退,并重试或添加规避代码后重试 b) 单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换 b) 并行升级 a) 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件 c) 多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova d) N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式 e) 并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程 f) 大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。 g) 可视化升级 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒 3 需求场景分析 3.1 特性需求来源与价值概述 【关键内容】 客户需求 不同的客户需要不同的灵活控制升级方案。 客户机群规模不断增大,需要支持更大规模更快速的升级框架 减少升级时的错误,出现错误要能自动规避或低成本回退再升级 内部优化 需要可读的升级策略 其余组件需要更简单的白盒的升级SDK 升级框架代码需要增加可读性和可维护性 升级框架需要低开发成本的功能可扩展性 描述该特性需求来源或背景,比如:XXX竞争对手分析、XXX客户需求、XXX内部优化等。描述该特性对用户带来什么具体价值,如果没有该特性,对客户或竞争力带来什么损失。 3.2 特性场景分析 【关键内容】 描述该特性的业务使用场景 内容包括: 1)场景触发条件及对象:什么角色/工具/接口等在什么具体情况下使用该特性,使用对象技能如何? 2)使用时间及频度 3)描述该特性主要有哪些场景、子场景及关键任务操作。 3.3 特性影响分析 当前研发版本保持对外接口不变。 未来可选的对外需求 cps\nova提供更高性能接口 各组件可选择使用更简单升级脚本 升级工具交互接口简化 描述该特性在整个系统中的位置及周边接口。其涉及的网元、部件或服务,及其依赖关系。描述该特性有哪些关键约束或特性冲突。 与其他需求及特性的交互分析: 平台差异性分析: 兼容性分析:

约束及限制:

3.4 业界实现方案分析 【关键内容】 该特性的在业界的实现机制,对比分析,优劣性对比 4 特性/功能实现原理 【关键内容】 设计需求来源于《系统需求分析说明书》,《架构设计说明书》,《系统设计说明书》 【参考信息】 可以参考《系统设计说明书》 4.1 总体方案

升级前通过编排器生成升级计划书(用结构语言详细描述了所有升级任务的顺序)。 升级时计划下发器会读取升级计划书,再按顺序分发给相应的计划执行器;计划执行器会按顺序调用对应的升级脚本。循环直到所有升级计划书内的任务完成。 a) 编排逻辑统一集中到升级前编排器中(使编排和执行不再耦合) b) 提取任务执行过程,开发与升级实际业务解耦的计划执行器(取代复杂不灵活的状态机) c) 框架采用统一数据平台,避免分散式的底层数据操作,提高数据使用效率 4.1.1 新旧框架比较

相同点: 新旧升级框架对外接口(自定义升级脚本,api)保持一致 升级流程保持一致(分发、检查、执行、生效、提交) 差异: a) 升级流程灵活性 旧框架: 升级流程是代码中固定的,包括大工步(分发、执行…)小工步(修改配置、开关…)。 新框架: 大小工步被包装成原子化脚本,可以单独执行,也可以调整顺序(通过调整升级任务书)。方便二次开发和测试。 b) 编排与执行 旧框架: 编排逻辑散步在各个业务逻辑中。编排逻辑黑盒且互相影响,不利于开发验证。 编排与执行逻辑写在一起造成耦合,无法单独测试开发 新框架: 编排逻辑统一提取到升级前。 编排与执行逻辑解耦。通过任务计划书解耦。 c) 任务执行处理 旧框架: 每个升级任务(分发、执行…)有单独的初始化、重试、回退、忽略、校验等逻辑。造成代码冗余,开发困难,容易出错。 新框架: 采用业务无关的任务执行器来统一处理初始化、重试、回退、忽略、校验等逻辑。增加开发效率,模块解耦。 d) 小工步 指操作开关、数据清理、数据迁移、配置转换等主流程以外的工步。 旧框架: 没有统一模块处理,小工步逻辑散布在主流程逻辑中。版本变更时容易引起错误。无法单独回退测试 新框架: 同样采用升级脚本来规范开发。使小工步可检视、可编排、可回退。 e) 数据接口 旧框架: 没有统一数据接口,升级和部署数据通过deepcopy互相传递。强耦合,可维护性差,易出错。 新框架: 采用统一数据接口,根据数据库范式精简数据,尽量使用静态数据。提升代码可靠性。

NicholasTao commented 1 year ago

` HT = 8 WD = 8

class Game3(object): def init(self, px, py): self.px = px self.py = py

def main(self):
    pass

class Board3(object): def init(self, b=None, p=None): if b is None: self.b = [2] * 9 self.p = 0 else: self.b = b self.p = p

# def nmove(self, m):
#     if self.b[m] != 2:
#         raise ValueError("already occupy: %s" % str(m))
#     else:
#         nb = [_ for _ in self.b]
#         nb[m] = self.p
#         return self.__class__(nb, 1 - self.p)

def move(self, m):
    if self.b[m] != 2:
        raise ValueError("already occupy: %s" % str(m))
    else:
        self.b[m] = self.p
        self.p = 1 - self.p

def reverse(self, m):
    if self.b[m] == 2:
        raise ValueError("not occupy: %s" % str(m))
    else:
        self.b[m] = 2
        self.p = 1 - self.p

def winner(self):
    for x in [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]]:
        if self.b[x[0]] != 2 and self.b[x[0]] == self.b[x[1]] and self.b[x[0]] == self.b[x[2]]:
            return self.b[x[0]]
    if 2 not in self.b:
        return 2

def hash(self):
    h = self.b[0]
    for x in self.b[1:]:
        h = h * 3 + x
    return h

def __repr__(self):
    s = ""
    for i in range(3):
        for j in range(3):
            if self.b[i * 3 + j] == 0:
                s = s + "O "
            elif self.b[i * 3 + j] == 1:
                s = s + "X "
            else:
                # print("||", i, j, self.b[i*3+j], "||")
                s = s + "_ "
        s = s + "\n"
    return s
    # return "%s\n%s\n%s\n" % (self.b[:3], self.b[3:6], self.b[6:9])

def evaluation(b): if b.winner() is not None: if b.winner() == b.p: return 10 elif b.winner() == 1 - b.p: return -10 return 0

class GenMove(object): @staticmethod def gen_move(b): return [m for m in range(9) if b.b[m] == 2]

INF = 999999

class ABTree(object): board = Board3() count = 0 h2v = {}

def alphabeta(self, depth, beta):
    self.count = self.count + 1
    # print(33, self.board, self.board.winner())
    if depth <= 0 or self.board.winner() is not None:
        return evaluation(self.board)
    alpha = -INF
    for m in GenMove.gen_move(self.board):
        self.board.move(m)

        h = self.board.hash()
        if h in self.h2v:
            val = self.h2v[h]
        else:
            val = -self.alphabeta(depth - 1, -alpha)
            if depth == 99:
                print(333, self.board, val, 444)
            self.h2v[h] = val
        # val = -self.alphabeta(depth - 1, -alpha)
        # print(self.board, val, alpha, beta)
        self.board.reverse(m)
        if val >= beta:
            return val
        if val > alpha:
            alpha = val

    return alpha

b1 = Board3()

b1.b = [1,1,1,2,2,2,2,2,2]

print(b1.winner())

x = evaluation(b1)

print(x)

abt = ABTree()

abt.board.b = [0, 1, 0, 1, 0, 0, 1, 0, 1]

abt.board.b = [1,1,1,2,2,2,2,2,2]

abt.board.p = 1

abt.board.b = [2,2,2,2,2,2,2,2,2]

abt.board.move(4) abt.board.move(0)

abt.board.move(8)

abt.board.move()

print(abt.board) ret = abt.alphabeta(99, INF) print(ret) print(abt.count)

010

100

101

`

NicholasTao commented 1 year ago
  1. 去高电区 a. 循环所有电池 b. (电池范围R1内电量 + 中心电量) / 距离

  2. 开局直奔高分区, 点亮 根据平均每回合生成电池数

  3. 巡视高分区, 确定中点(地图)和半径()

  4. 顺路高分区

?. 攻防高分区 ?. 先攒电量, 最后发力 ?. 区域有优势, 后手 ?. 附近有人则先到一步等到期