LOGO
首页 网站广场 站长动态 活跃度榜 审核查询 逛逛好站 留言交流 提交申请 关于本站

站长动态

站长动态所展示的是已加入好站网成员站长文章
共同步 2383 篇博文
(每2小时更新一次)
陈仓颉
入驻第1年
《富兰克林自传》书摘
我最早在和菜头的《把自己安放妥当》这篇文章中得知《富兰克林自传》后开始阅读,开了个头不久便搁置,直到近期才一口 […]
Debug
入驻第1年
Phoenix框架 从0到1设计业务并发框架 自动构建有向无循环图设计
从 0 到 1 设计业务并发框架系列: Phoenix 框架 小米商城产品站革新之路 Phoenix 框架 怎么组织设计一个框架 Phoenix 框架 并发线程池的核心设计 Phoenix 自动构建有向无环图的业务并发框架,核心就在于不需要开发人员关心调用分层和依赖互斥的排序问题,通过算法进行自动构建、收集 Task 任务、检测环或者依赖,最后打印并发组分层信息。 本篇文章就讲解下如何构建有向无环图的设计实现方案及遇到的问题。 实现方案 有向无环图的构建采用的是设计模式中的策略模式,首先定义好 Builder 的实现方式,如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * @author debuginn */ public interface PhoenixBuilder { // 过滤 Phoenix API 使用到的 Task 任务 Map<String, ArrayList<TaskObj>> filterApiUsedTask(ArrayList<TransObj> transObjArrayList); // 根据 api 获取需要执行的 trans Map<String, ArrayList<TransObj>> apiTransMap(ArrayList<TransObj> transObjArrayList); // 是否存在依赖关系 void isHaveLoop(Map<String, ArrayList<TaskObj>> apiUsedTaskMap); // 处理并发组分层划分 Map<String, ArrayList<ArrayList<TaskObj>>> processConcurrentGroup(Map<String, ArrayList<TaskObj>> apiUsedTaskMap); // 打印并发组分层信息 void printConcurrentGroup(Map<String, ArrayList<ArrayList<TaskObj>>> phoenixApiArrayListMap); } PhoenixBuilder 需要实现 6 种方法: 首先,将收集上来的 Task,按照 API 进行分组,Task 存在依赖调用的都进行收集; 按照 API 进行收集 Trans,后续 Trans 使用请求线程进行串行执行; 判定每个 API 收集上来的 Task 是否存在相互依赖或循环依赖; 将每个 API 收集上来的 Task 按照先后依赖关系进行分组划分; 打印并发分组信息,用来给开发者调试及校验使用; 由于存在依赖关系,需要进行分层设计,这里可以结合 Phoenix 框架 怎么组织设计一个框架 来看,然而每一层并不需要关系执行的顺序问题,这里采用了最简单的数据结构存储分层信息,Map<String, ArrayList<ArrayList<TaskObj>>> Key 用来标识属于哪个 API 请求的并发分组,Value 则采用最简单的二维数组进行存储,每一维分别存储需要进行执行的 Task 任务。 遇到的问题 怎么判定存在环 由于我们要进行构建的是有向无环图,那么存在相互依赖的 Task,在框架设计逻辑中是行不通的,若存在相互依赖,那么究竟该先执行哪个 Task 呢? 可以看到上图,只要有两个场景: 相互依赖关系:TaskB 与 TaskD 存在相互依赖,那么就不能确定执行顺序; 环状依赖关系:TaskD、TaskF、TaskG 和 TaskE 存在依赖环,也无法确定执行顺序; 相互依赖关系判定比较简单,就是检索一个 TaskA 依赖的 TaskB 是不是也依赖这个 TaskA, 循环依赖判定相对来说比较复杂,需要遍历图的所有路径,若路径存在闭环,则代表着存在环,反之,就是不存在环路,代表就是单向依赖的分支路径。 怎么划分并发分组 划分并发分组,就是将彼此没有依赖关系的 Task 按照依赖的先后顺序进行分组,其实就是按照图的深度遍历。 这里的遍历,由于有依赖关系,可以采用向上遍历或者向下遍历的方式,我们采用了压栈的方式处理: 向上遍历 首先找到没有被依赖的 Task,这是一组,之后存入数组压入栈底; 之后栈底的 Task 收集出来需要依赖的 Task,这些收集上来的 Task 需要再判定是不是被其他 Task 依赖,若是依赖的话,则保存在临时的 Task 数组中,最后将剩下 Task 就是只被栈底 Task 数组依赖的 Task,那么将这个分组继续压入栈内; 重复第 2 步,把栈底的 Task 换成栈内最上层的数组,之后再把临时 Task 追加到收集出来需要依赖的 Task 上,去重,之后重复执行; 最后执行到剩下的 Task 没有依赖的 Task,这就是最后一个并发组,之后压入栈内; 最后程序执行的时候,就是先执行栈顶部的并发组,之后一次出栈执行。 向下遍历 首先找到不依赖其他 Task 的 Task,这是一组,之后存入数组压入栈底; 之后栈底的 Task 收集出来依赖这个分组的 Task,这些收集上来的 Task 判定是不是被其他 Task 依赖,若是依赖,也是保存在临时的 Task 数组中,最后就只剩下只依赖栈底的 Task 数组的 Task,之后将这个数组压入栈内; 重复第 2 步,把栈底的 Task 换成栈内最上层的数组,之后再把临时 Task 追加到收集出来需要依赖的 Task 上,去重,之后重复执行; 最后执行到剩下的 Task 没有任何 Task 依赖,这就是最后一个并发组,之后压入栈内; 此时,这个堆栈存储的是最先执行的 Task 并发分组在栈底,最后执行的在栈顶,需要进行反转操作,之后再依次进行执行。 为何要使用"策略模式" 在开发程序的时候,大家都不约而同地讲究程序的横向扩展能力,将核心的关键的任务拆分成具体执行的子任务,这样不仅可以提高程序的可阅读性,而且还可以扩展不同的遍历算法,用来后续框架的持续优化。 不仅这里,Phoenix 框架尽可能的采用策略模式实现,将核心功能点都进行拆分,做到模块化设计,这样的设计正是 Phoenix 框架的设计初衷,生生不息,持续迭代。 写在最后 本篇文章主要讲了如何进行自动构建有向无循环图的思路及遇到的问题,其实在开发中,这种解决依赖关系的场景还有很多,其实抛开上层的业务实现或者框架需求来看,底层就是最基本的数据结构,算法,图的遍历场景在当今比较火的 AI 场景下也是如此。 感谢你的阅读,你要是有好的方案或者好的 idea 可以与我一起交流,最后,如果你感兴趣,推荐关注公众号或订阅本站,欢迎互动与交流,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
小生
入驻第1年
我这31年的漫漫人生路
  我叫李会生,1993年生人,妈妈跟我说在当时为了要我,差了一点要了我母亲的命!当时医疗条件并不发达,在我们当地的卫生院都不敢留,到县里医院更是拒绝,万般无奈我的姥爷赶着牛车将母亲送回到了姥姥...
乌托邦
入驻第1年
Golang笔记-概况
//Hello World package main import "fmt" func main() {    /* 这是我的第一个简单的程序 */    fmt.Println("Hello, World!") } 关于包: 文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。 文件夹名与包名没有直接关系,并非需要一致。 同一个文件夹下的文件只能有一个包名,否则编译报错。 main函数只能出现在main包里 行的分割并不需要分号; 但若两个语句需要在同一行(并不推荐这样做),可以用分号隔开。 注释 //单行注释 /**/多行注释 无效标识符: 以数字开头的 Go 语言的关键字 有运算符 字符串连接用加号+ //字符串连接 package main import "fmt" func main() {     fmt.Println("Google" + "Runoob") } GoogleRunoob 格式化字符串(输出) Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。(作为返回值输出不打印) Printf 根据格式化参数生成格式化的字符串并写入标准输出。(打印输出) // 当前程序的包名 package main // 导入其他包 import . "fmt" // 常量定义 const PI = 3.14 // 全局变量的声明和赋值 var name = "gopher" // 一般类型声明 type newType int // 结构的声明 type gopher struct{} // 接口的声明 type golang interface{} // 函数声明 由main函数作为程序入口点启动 func main() { Println("Hello World!") } Go 程序是通过 package 来组织的。 只有 package 名称为 main 的源码文件可以包含 main 函数。 一个可执行程序有且仅有一个 main 包。 通过 import 关键字来导入其他非 main 包。 //import单个导入 import "fmt" import "io" //import多个导入 import( "fmt" "io" ) //给import导入包起别名 import fmt2 "fmt" 别名 原名 //省略调用(不建议使用) import . "fmt" func main (){ Println("hello,world") } 函数的private和public //函数名首字母小写为private func getid() {} //函数名首字母大写为public func Printf() 同理 包内调用方法名可小写首字母,包外调用方法名必须大写
乌托邦
入驻第1年
Welcome Back!
欢迎回来!现在是2024年4月 或许你会怀疑自己的眼睛,会疑惑为什么这个网页跟过去不一样了,会质疑浏览器打开了错误的网页,甚至会猜测是不是上任站长跑路了。 没错,正如你所见,在我们因为种种原因而无法相见的日子里,我们的网站完成了大改版重新上线了。 在过去的几个月里,我常常因为各种原因拖更文章,到最后更是彻底摆烂。各种bug和小毛病越攒越多,甚至网站的一些授权已经过期很久才被发现。 但现在,我回来了。人是喜新厌旧的,因此我决定重新改造这个网站,用一种新的面貌重新面对新的伙伴。或许全新的页面也能督促我多多更新吧。 最后,如果你想知道我们更新了些什么,欢迎访问全新改版的动态(现在叫瞬间),我会在那里发布更新日志。你也可以在这里催更(或许有用)闲聊,或者是提出你的意见。 总之,祝你我在见不到彼此的日子里,早安午安晚安。
九仞
入驻第1年
浙江财经大学校庆50周年之“三行代码作情诗”
代码 抽象版本如下 我为其添加了可运行的版本 运行说明,由于使用了JS的ES Module写法, 所以要手动将 […]
陈仓颉
入驻第1年
绿箭侠
You Have Failed This City! Arrow 时隔多年终于把大学时期未看完的 CW 台柱子 […]
Debug
入驻第1年
Phoenix框架 从0到1设计业务并发框架 并发线程池的核心设计
背景 从 0 到 1 设计业务并发框架系列: Phoenix 框架 小米商城产品站革新之路 Phoenix 框架 怎么组织设计一个框架 前两篇文章已经讲述了我设计框架的背景以及抽象设计的细节,今天讲一下并发框架最为关键的并发线程池的核心设计,主要讲一下在设计线程池划分遇到的问题以及最终我采用了哪种方式实现的。 将存在依赖关系的 Task 进行划分分组后,依次执行分组就可以拿到所有想要的结果,但是怎么划分线程池、设置线程池是面临的问题。 接下来,我将实际业务中的复杂度简化设计,将问题具象化呈现给大家。 方案:公用线程池 方案 最开始,我计划将分配的 Task 公用一个线程池,让 Task 去线程池竞争资源,如下图: 但是很快发现,单个线程池一旦请求数量上来,某个 Task 接口变慢就会导致整个接口成功率急速下降,直至不可用的状态。 为什么会出现这种情况呢? 效果 T1 时刻,第 1 波流量进来,之后率先执行 TaskA 或者 TaskB; TaskA 请求的快速递增,接口变得越来越慢; T2 时刻,还有两个 TaskA 并没有执行完毕,之后第二波流量进来: 第 1 波流量开始执行 TaskC 和 TaskD; 第 2 波流量进来,也有 TaskA 和 TaskB 获取到线程执行; T3 时刻,此时已经有 4 个 TaskA 还没有执行完,并且最开始的两个 TaskA 要面临着超时情况: 第 1 波流量执行的 TaskA 面临超时中断的情况; 第 2 波流量执行的 TaskA 也在运行状态中; 第 3 波流量进来,情况变得复杂,新的流量,有 TaskA 和 TaskB 进行执行; 此时第 1 波流量前两层运行完毕,开始执行 TaskE; 此时第 2 波流量的前一层运行完毕,开始执行 TaskC 和 TaskD; 之后按照 TaskA 始终慢的情况继续发展……. Tn 时刻,此时线程池大部分已经被前 n 波流量的 TaskA 占据着,并且大量被中断超时,其他 Task 无法竞争到线程进行执行。 这样的话,接口的可用性完全取决于 TaskA 的可用性,但是还有一个致命的问题就是其他 Task 无法执行或者由于依赖问题,前置该获取用作请求参数大部分为空,也无法正常请求,这样就算是接口返回了数据,也是不全的数据。 这种方案存在共用线程池大量线程等待超时的情况,是不可取的。 方案:分层线程池 方案 公用线程池的情况肯定是有问题的,在此基础上,尝试将分层并发划分不同的并发池,每一层公用线程池,如下图: 上了分层公用线程池之后,压力测试发现效果只有小幅的提升,没有达到预期的目标,甚至来说相差很远,为啥会出现这个问题? 效果 我们还是假设 TaskA 会随着请求量上来会大面积超时来举例。 T1 时刻,第 1 波流量进来,之后率先执行 TaskA 或者 TaskB,此次线程池 2、3 没有执行到; TaskA 请求的快速递增,接口变得越来越慢; T2 时刻,还有两个 TaskA 并没有执行完毕,之后第二波流量进来: 第 1 波流量开始执行线程池 2 的线程 TaskC 和 TaskD; 第 1 波流量存在 TaskC 执行完,陆续开始执行线程池 3 的线程 TaskE; 第 2 波流量进来,也有 TaskA 和 TaskB 获取到线程执行; T3 时刻,此时已经有 4 个 TaskA 还没有执行完,并且最开始的两个 TaskA 要面临着超时情况: 第 1 波流量执行的线程池 1 TaskA 面临超时中断的情况; 第 2 波流量执行的线程池 1 TaskA 也在运行状态中; 第 3 波流量进来,情况变得相对来说比较复杂,新的流量; 此时第 1 波流量前两层运行完毕,开始执行线程池 3 TaskE; 此时第 2 波流量的前一层运行完毕,开始执行线程池 2 TaskC 和 TaskD; 之后按照 TaskA 始终慢的情况继续发展……. Tn 时刻,此时线程池 1 大部分已经被前 n 波流量的 TaskA 占据着,并且大量被中断超时,由于依赖于 TaskA 和 TaskB 的结果作为下层的入参数: TaskA 过慢占据着接近 100% 的线程池 1 的资源; TaskB 竞争不到资源,被超时中断; 最后接口还是发展到不可用的状态,其实和公用线程池的问题一样,也还是存在大量线程等待超时 的情况。 这种公用线程池的现状是不可取的,那么该如何划分线程池来执行呢?其实分而治之的思想就可以解决这个问题,也就带来了 3.0 版本,独立 Task 线程池的方案。 方案:独立线程池 无论怎么公用线程池,都会出现被挤占的情况,只有将每个 Task 划分单独的线程池,才不会出现抢占等待的问题,那么如何设计的呢? 方案 每个 Task 单独创建线程池来承接流量,各个线程池互相不干扰,同时承接流量交给 CPU 抢占资源进行调度运行。 效果 由于是单独承接流量,这种设计满足了高可用的目标,还是依照 TaskA 接口随着并发请求的提升,接口越来越慢直至不可用,之后再加入一个条件,就是 TaskC 的执行条件是 TaskA 执行完毕的结果。 T1 时刻,第一波流量进来,所有线程池的线程都占满,开始进入核心调度执行; T2 时刻,第二波请求进来,第一波请求的 2 个 TaskA 还没有执行完毕,其他线程池的线程逐渐承接第二波请求等待调度; T3 时刻,第三波请求进来,这时候情况比较复杂: 第一波流量的 2 个 TaskA 已经超时被中断了,对应的 TaskC 的线程池的两个 TaskC 线程等待 Task 的执行结果失败,结束任务; 第二波流量的 2 个 TaskA 还没有执行完毕,也濒临超时; 其他线程池执行均正常运行; 就这样过了一段时间 … Tn 时刻: TaskA 已经达到了不可用的状态; 对此有依赖关系的 TaskC 也逐渐达到不可用状态; 其他线程执行正常; 这么看,针对于一个接口调用几十个上百个接口的场景,不会因为一个接口或者有依赖关系的接口可用性降低而影响整个接口的可用性,同时只要对单个线程池做好监控,加上报警即可动态感知哪些上游接口失败而及时通知到对应的系统维护同学,这样就大大的降低了维护成本。 这个版本作为线上生产环境的第一个版本推了上去,单台 8C 8G (k8s) 的配置空跑框架达到了 QPS 在 1.4w,接口可用性在 99.96%(结果仅供参考,根据公司集群部署策略、机器性能等问题会有浮动)。 但是,这种目前还是存在着显而易见的问题,就是每个 Task 执行的接口的接口响应都不是一致的,有的在 50ms 内、有的在 100ms 内、有的比较慢 500ms 内,分配相同的线程池数量是不合理的,因为这样就会造成 CPU 调度不公平,那么怎么让调度运行的比较公平呢? 优化 针对于这个问题,将线程池大小按照权重创建,像是比较慢的接口但是多等待一定时间可以返回的,我们就多分配线程池大小,接口响应很快的,我们就相对减少线程池大小,这样的设计可以在保证接口的可用性兼顾接口返回字段的完整性。 写在最后 本篇文章主要讲框架设计中怎么将划分好的分层并发执行,最终我们采用了独立线程池的方案,并且按照耗时、CPU 核数等权重评估分配每个 Task 任务线程池的大小,让 CPU 线程调度来确保线程都尽可能的公平执行到,最终保证接口的并发需求及高可用的场景。 如果你感兴趣,推荐关注公众号或订阅本站,欢迎互动与交流,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
陈仓颉
入驻第1年
三体问题
一个周末的时间品鉴完网飞三体,有三体腾讯剧版珠玉在前,网飞三体就像一坨。原著1.5本的剧情压缩到8集(一集60 […]
Debug
入驻第1年
Phoenix框架 从0到1设计业务并发框架 怎么组织设计一个框架
上篇文章主要讲了设计 Phoenix 框架前的遇到的问题和设计框架的思路 《 Phoenix 框架 从0到1设计业务并发框架 小米商城产品站革新之路》,本篇文章主要讲一下如何设计框架的。 不死鸟并发框架,是自动构建有向图按照深度进行构建并发组并进行并发调用结果的框架。 产品站业务静态接口与动态接口都需要调用大量的后台服务进行获取数据进行业务编排,而各个并发调用之间又相互存在依赖,采用并发组设计拆解依赖,同时并发控制调用,BO to DTO 采用统一的 Transfer 层进行设计,开发人员只需要关系定义每次调用事件的 Task 和 Transfer 代码逻辑的书写,直接返回业务数据。 名词解释 PhoenixFramework 不死鸟(凤凰)框架,此业务并发框架的名称; Task 在业务并发中定义一次调用,可以是 HTTP、DUBBO 或者是 Redis 获取、MySQL 读库操作; Transfer 在业务定义中是一个子业务模块的转换逻辑将 BO 数据转换为 DTO 数据; Task 与 Trans 注解 怎么定义 Task 在框架设计之初,我们内部有两种方案,一种是继承抽象类实现的方式,Task 通过继承实现 PhoenixTask 类实现定义 Task,另外一种是采用注解的方式,将每个 Task 都定义成具有强约束的 Task ,并且把详细的描述信息在注解中定义,给开发人员一目了然的设计思路。 经过内部讨论,我们选择了 Java 优秀的语言特性,注解的方式声明定义 Task ,这样的定义使得代码简洁明了,也有利于通过 Spring Bean 收集工具来收集我们的定义。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * PhoenixTask * 任务注解 * * @author debuginn */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PhoenixTask { String taskName(); // 任务名称 String[] beforeTaskName() default {}; // 前置任务 String[] filterPlatform() default {}; // 过滤渠道,黑名单 String[] taskBoName(); // 任务生成的 BO 数据 用来校验冲突 } PhoenixTask 注解定义非常简单: taskName用来标识任务名称,采用枚举,强制约束命名的唯一; beforeTaskName前置任务,前面讲到每个任务都是一次事件,区分前置任务是需要在并发调用的时候等待结果的返回,之后用来作为此 Task 调用的前置参数; filterPlatform 过滤渠道,也就是黑名单的功能,但请求渠道在 Task 中声明了黑名单,在并发执行的时候就自动屏蔽掉执行; taskBoName任务转化为 BO 的数据,通过接口调用或者中间件获取数据,转化为 Transfer 层使用的数据,在框架层做数据参数校验; 怎么定义 Trans Trans 是 Transfer 的简称,同样和 Task 设计一样,也是采用注解的方式定义: 1 2 3 4 5 6 7 8 9 10 11 12 13 /** * PhoenixTrans * 业务编排注解 * * @author debuginn */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PhoenixTrans { String transName(); // 业务编排块名称 String apiName(); // 执行 使用并发 api String[] tasks() default {}; // 依赖的 task 任务 } PhoenixTrans 注解定义同样非常简单: transName用来标识业务编排块名称; apiName 用来区分这个 Transfer 业务编排是隶属于哪个并发 API 所属的; tasks就是定义依赖的 Task 任务有哪些,一个业务编排可能会利用 n 个 Task 返回的 BO 数据进行编排; 大家在这里可能会比较疑惑,为啥 Task 中没有定义 apiName 而是 Transfer 中定义的,是因为在设计中,为了便于后续 Task 可以被 n 个并发 API 共·用,这样在 Transfer 定义了 apiName,之后通过 tasks 定义依赖的 Task 就可以推断出这个 Task 目前是被哪一个并发 API 使用的。 怎么收集 Task 和 Trans 自定义了 PhoenixTask 和 PhoenixTrans 注解,通过声明一个 AnnotationProcessor 继承 BeanPostProcessor 来进行收集定义的注解。 首先是根据注解类收集上来对应的 Task 和 Trans; 根据不同的 Trans 划分不同的 API,收集不同 API 依赖的 Task; 按照 Trans 是否进行依赖过滤使用到的 Task; 根据 Task 之间的相互依赖关系,将 Task 进行分组; 这样就完成了对框架的分层与自动构建的设计,框架的设计主要的是要思考如何将实际业务中使用的模块抽象化设计,同时要思考框架的扩展性与强约束性。 结尾 本篇文章主要讲解我如何将业务与调用关系进行抽象成 Trans 与 Task 的,接下来我将讲述并发框架并发线程池的核心设计、配置化思考、监控设计以及自动构建算法等系列文章。 如果你感兴趣,推荐关注公众号或订阅本站,欢迎互动与交流,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
阿川
入驻第1年
搞个公众号
阿川
入驻第1年
腾讯云COS文件跨域
小十
入驻第1年
画廊
Debug
入驻第1年
Phoenix框架 从0到1设计业务并发框架 小米商城产品站革新之路
前言 小米商城产品站之前由于历史原因,存在着诸多问题与不便,随着技术的快速变革,技术部中台化的建设,越来越不适用于现在快速迭代的业务需求,接下来我将以技术的视角讲解我们遇到的痛点,以及解决这些痛点的思路,也就是 Phoenix 框架诞生的故事。 为啥要进行设计一个框架,其实是业务发展导向的结果,若是我们不进行设计,那么我们会遇到如下一些问题: 在新的产品需求规划下,无法承接大型项目,只能进行小修小改; 小米网产品站最初,每个端一套代码逻辑,风格各异; 历史沉淀,一个接口函数 2000 多行,熟悉代码逻辑的成本越来越大; 隔离性差,服务可用性严重依赖下游,下游一个接口的抖动都会给我们接口带来恐慌; 技术上整体中台化建设,随着调用接口越来越多,接口越来越慢; 代码没有解耦,特别对新同事而言,新项目上线风险高; 缺少 Go 基础组件的维护,无法对下游接口实时监控; 思考 我们从技术上计划进行重构,那么我们如何将现有的调度逻辑抽象出一套兼顾稳定性、便捷开发、可维护性且可监控的框架模型是我们首先带来的问题。 我去调研了开源的一些并发框架,发现传统的并发调度模型基本上都有依赖关系、超时控制、线程池分配调度、熔断限流、接口监控等功能。 为啥我们没有直接使用开源并发框架进行开发呢? 我调研发现业界 LiteFlow 框架是最受欢迎与好评的框架,于是在 Github 上面去了解框架底层实现的细节,随着深入阅读源码,发现这款框架设计的是真的很优秀,但是也过于庞大、复杂,特别是 EL 规则的写法,相对来说还是有一定的上手成本。 那么我就在思考,我作为业务开发人员的话,我不想关心这么复杂的依赖关系,只需要关心自己产品站业务调用到的中台的接口及其依赖接口即可。特别是大型接口捆绑了几十个下游接口的逻辑,要是理解每个接口的设计细节更是不太可能的,要是依赖关系特别复杂,那么 EL 规则会写的非常复杂且维护成本极高。 那么该如何设计一款轻量、快速、高效、从根本上解决开发人员手动维护依赖关系的并发框架呢? 既然存在依赖关系,那是不是可以通过算法进行自动构建依赖关系呢? 设计 根据产品站的实际场景,我们发现,调用下游接口若干个,且请求接口存在不同的请求协议与不同的中间件。 更重要的是,接口存在着依赖关系,我们梳理接口调用发现,接口依赖正好是有向依赖图的结构, 那么我们就可以进行遍历依赖关系进行编排并发分组。 这样就解决了依赖的问题,我们可以依次并行执行每个并发组的任务,这样就可以得到所有接口或依赖的结果。 那么获取到结果之后,怎么进行业务逻辑的编排,怎么隔离下游接口,其实原理很简单,既然任务可以进行分层,那么我们业务调用、业务编排、防腐蚀层也可以进行分层设计。 Transfer 层的作用是业务逻辑层,用来进行业务编排,将 BO 数据提供给客户端使用; Task 任务层是并发执行的核心设计层,在这里通过并发分组的每个子 Task 在这里进行编排后执行调用,用来进行超时控制、耗时统计等操作; Infrastructure 层作为防腐层设计,用来隔离下游接口的调用,这样的设计提高了接口的稳定性; 写在最后 好了,上文就是给大家讲解的自动构建并发调用图的业务框架,也就是 Phoenix Framework。 Phoenix,最初在周志明老师的网站"凤凰架构"提及,一方面是对周老师的架构设计理解与 Java 相关知识学习的致敬,另一方面,Phoenix 不死鸟,软件的生命周期也是如此,随着业务的快速发展诞生、并随着业务的的收缩而凋亡,生生不息。 最后,我会以系列的方式进行讲解这个框架遇到的问题以及解决思路,感谢大家的阅读,大家要是感兴趣的话推荐大家关注公众号,让我们一起变得更强~ 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
陈仓颉
入驻第1年
2024春节观影
今年的春节档是中国影史最高票房(80.2亿,截止2月17日),我观看了其中三部电影。《熊出没·逆转时空》不是我 […]
阿川
入驻第1年
湖畔幽悠
乌托邦
入驻第1年
Linux安装wine版本微信
2024年11月更新:微信已推出正式版本的微信Linux版,官网https://linux.weixin.qq.com/,有需要的读者可以前往下载啦。这篇文章写在2022年末,当时的我恐怕也不会想到,两年后万恶的微信会真的适配Linux吧。2024年3月更新:微信已推出原生linux版本wechat(Universe),固本教程已存档。虽然万恶的腾讯对其进行了系统白名单限制,但使用一些办法依旧可以将原生版本微信安装至任何发行版上。在此不过多赘述,点此可直接https://pan.baidu.com/s/1WK1gxC5x7LWMYzBoJ6gPNQ?pwd=qx7z 破解限制后的deb安装包。前提本教程系统环境为Ubuntu23.10,大概大部分的GNU/linux也大同小异略有出入。wine版本8.0如果你不想使用deepin版本的微信,觉得有一种NTR的感觉;如果你不想用crossover安装微信,还得花一份钱;如果你不想在Steam Proton里安装微信,忍受每次启动微信都得先启动Steam的反人类操作。那么,以下教程适合你,只要多亿些步骤,就可以使用原生版本的wine安装一个纯净的微信。配置wine环境安装wine和winetricks你可以选择从存储库来安装wine和winetricks,这样做的好处是你可以获取到最新版本的wine和winetricks。(写此文章时最新版本为wine9.0)但是从Ubuntu存储库里安装,更加方便快捷,大大简化了安装流程和时间,还规避了墙内网络偶尔连接不上wine存储库的问题。虽然不是最新版本,但对于安装微信来说足够了。sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine sudo apt install winetricks使用winetricks创建一个新的32位容器打开winetricks图形化页面,创建一个新的容器,容器选择32位的,容器名称自己填一个,我建议填写wechat方便识别。创建好之后选择管理wechat容器,记住窗口上方的当前容器路径备用。配置wechat容器常规情况下,是需要选择第一项,安装Windows DDL或组件,然后安装riched20和msls31。但大部分情况下,一番尝试后你只会获得报错。这个时候如果你打开 容器路径/drive_c/windows/,你会发现里面组件齐全只是没启用。选择“运行wine配置程序”,切换到“函数库”选项卡,添加函数“riched20(原装先于内建)”和“msls31(原装先于内建)”。再切换到“显示”选项卡,将屏幕分辨率调整到196dpi。确认。安装字体此时容器内还缺少字体。你可以尝试在winetricks里选择“安装字体”,安装Arial字体。但大概率会遇到和上面一样的报错。打开 容器路径/drive_c/windows/Fonts$文件夹,向里面导入中文字体。神奇的是,经过我的尝试似乎只要是常用的中文字体就可以,宋体微软雅黑华文字体似乎都可以。附上微软雅黑字体的下载链接 安装微信在微信官网下载32位版本的安装包。打开安装包下载的位置,在此创建一个终端。WINEPREFIX=容器路径 wine WeChatSetup_x86.exe等待安装包加载完成后,安装界面即可正常打开。此时会发现经过配置后,安装界面的大小是正常的,否则没有配置过的界面是过小的。正常安装即可,安装完成后在桌面和应用抽屉里就会出现“微信”和“卸载微信”两个图标。此时微信可以正常打开登录,能够完成聊天传文件等大部分工作。关闭微信时直接杀掉进程即可。附上几张配置图
乌托邦
入驻第1年
联想yoga系列安装Ubuntu的注意事项
笔者机型联想yoga14s2021 同样适配于同型号不同年份设备 也可能适配联想小新系列部分机型 我该安装哪个版本 (省流:Ubuntu的最新版本即可) Ubuntu 20 及以前版本太老了,没测试且不推荐(更新:20.04可用) Ubuntu 22 全版本不推荐但能用。能够正常显示,但独显识别异常需要手动配置驱动。另外笔记本键盘在配置grub后有概率依旧无法使用,记得备好外置键盘。触控板可以正常使用。 Ubuntu 23.04 不可用不可用不可用!!! 安装完黑屏,疑似nvidia独立显卡驱动自动适配异常导致。除非你想自己区配置独显驱动,否则不要安装这个版本。NVIDIA FUCK YOU! Ubuntu 23.10 自20以来适配相当好的一个版本 nvidia独立显卡识别正常 显示正常不闪屏 驱动能够自动适配(前提是安装时勾选了安装第三方驱动)笔记本键盘安装后一就无法使用 但配置grub后可正常使用 甚至有概率键盘背光能够调节 (首选)Ubuntu 24 系列目前适配最好的一个版本 解决了一直以来的键盘问题 现在笔记本键盘无需配置就可以直接使用 只是没有背光调节 安装过程 省略 安装后的配置(24版本无需配置) 打开终端 sudo vim /etc/default/grub (没有vim的 sudo apt install vim) 修改GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" 为GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i8042.dumbkbd" (如果你有闪屏问题 尤其是Ubuntu22 也可以在后面继续加上 i915.enable_psr=0) ESC :wq 保存退出 刷新grub并重启系统 sudo update-grub sudo reboot 重启完成后你的笔记本键盘就可以用了
阿川
入驻第1年
年味越来越淡,没有感觉
阿川
入驻第1年
解决Jekyll时区数据源问题

© 2026 好站网HaoZhan.wang 1.5 版权所有

苏ICP备19065220号-4 萌ICP备20269980号 茶ICP备2026050346号 本站数据 版本历史 关于本站