1.【opensips2.4源码分析】模块的码解加载
2.Scroll源码解析
3.Flink Collector Output 接口源码解析
4.[UVM源代码研究] 谈谈uvm中的浅拷贝(shallow copy)与深拷贝(deep copy)
5.UE4 Multi-Process Cooking(多进程烘焙)
6.Java函数式编程:Collector接口详解
【opensips2.4源码分析】模块的加载
揭秘opensips 2.4源码中的模块加载奥秘 在opensips 2.4的底层架构中,模块的码解加载过程由loadmodule指令主导,核心实现主要集中在sr_module.c的码解load_module函数上。这个函数是码解模块集成的关键,通过统一的码解接口<strong>struct module_exports</strong>对外展示,无论是码解RedTorch源码静态模块如<strong>proto_udp.so</strong>和<strong>proto_tcp.so</strong>,还是码解动态模块,都遵循这一标准。码解 动态模块加载的码解路径是由<strong>mpath_buf变量控制,作为sr_load_module参数的码解一部分,它默认设置在opensips安装路径下的码解<strong>opensips/lib/opensips/modules/</strong>。 模块加载流程如下:解析配置:loadmodule指令被整合到全局配置中,码解引导模块的码解初始化流程。
初始化模块:调用<strong>struct module_exports的码解函数指针,确保模块能够正确启动。码解
理解模块的运作,关键在于它继承自<strong>struct module_exports,特别是其中的初始化函数<strong>preinit_f和<strong>init_f,它们是模块启动的核心步骤。 在main.c中的<strong>init_modules函数中,这个流程被细致地执行:遍历所有模块,尝试执行<strong>preinit_f,可能出现失败但不影响后续步骤。
调用<strong>init_f,设置init_done标志,标志着模块初始化完成。
释放依赖信息,确保内存管理的完整性。
在<strong>init_mod阶段,进一步执行以下操作:循环调用<strong>init_f
统计模块数据,与全局的stats_collector紧密相连。
注册管理接口到mi_cmds,以便于系统管理。
模块函数的注册过程十分关键,通过<strong>struct module_exports中的cmds字段,与全局的modules结构体关联起来,通过find_export函数找到并调用相应的函数。 值得注意的是,为了避免命名冲突,模块函数的名称通常会加上前缀,以此来标识其特定的命名空间。Scroll源码解析
1. Scroll查询在指定_doc排序时相较于不指定排序或指定某个字段排序能明显更快,这是由于Scroll查询的机制及底层实现所致。
首先查看Elasticsearch的Collector,其主要功能是收集文档并按照特定规则排序。其中,TopDocsCollector类在收集文档后会返回一个有序的TopDocs对象,该对象是搜索结果的返回值。TopDocsCollector有三个子类:SimpleFieldCollector、PagingFieldCollector、SimpleTopScoreDocCollector 和PagingTopScoreDocCollector。美好世界源码这些子类根据排序规则(如字段排序、简单排序等)进行文档排序。
2. 对于TopScoreDocCollector,其排序规则是先执行打分,分数相同的文档按文档号排序。TopFieldCollector则是先按照指定字段排序,值相同的文档再按文档号排序。
3. TopScoreDocsCollector的两个子类(SimpleTopScoreDocCollector和PagingTopScoreDocCollector)在功能上区别在于PagingTopScoreDocCollector针对翻页请求,代码上增加了对after的判断。对于使用TopScoreDocsCollector无论是否为翻页请求,每次请求都会扫描全部命中文档并计算分值。使用SimpleTopScoreDocCollector还是PagingTopScoreDocCollector取决于after是否为null。
4. 对于scroll请求,after参数等于scrollContext.lastEmittedDoc,即上次翻页最大的ScoreDoc。TopFieldCollector同样有两个子类(SimpleFieldCollector和PagingFieldCollector),其判断逻辑与TopScoreDocsCollector类似,也是根据searchContext.sort()是否为null来决定使用哪类Collector。
5. 在lucene6.4.1版本中,无论是SimpleFieldCollector和PagingFieldCollector都无法提前终止收集过程。然而,从更高版本的lucene开始,具备了提前结束收集的功能,判断依据是search sort=index sort一致时,通过抛出CollectionTerminatedException异常提前结束收集。Elasticsearch从6.x版本开始也支持了自定义写入顺序,可以不是_doc而是某个字段值。
6. 通过Elasticsearch的代码分析,我们确认scroll请求在指定_doc排序并从第二页开始时,只会收集指定数量的doc,性能表现更优。对于scroll请求,包装了一层MinDocQuery,用于过滤掉已经翻页过的数据,大大减少文档命中数,避免收集无用的doc,这对于深度翻页性能提升明显。
7. 对于scroll请求,由于不支持向前翻页,每次查询对于已查过的数据无需收集。Elasticsearch通过MinDocQuery实现跳跃功能,将doc跳到segmentMinDoc(lastEmittedDoc+1),在合并倒排表之后,实际上就不会再命中上一页的内容。触发提前终止后,后续倒排表合并也不再必要,性能提升显著。
8. Scroll与search_after查询实际上走的是相同的逻辑,都是通过一个after变量进行翻页。scroll的热门板块源码after参数为scrollContext.lastEmittedDoc(ScoreDoc),search_after的after参数为包含sort字段信息的FieldDoc,都是ScoreDoc。最终都会收集全部命中文档才能得到排序结果,但scroll对于_doc排序做了优化,性能表现更佳。
9. 对于search_after查询,即使指定_doc排序,仍然需要收集全部命中文档,因为search_after是动态的,MinDocQuery跳跃功能不适用。然而,search_after在lucene后续版本中支持了提前终止功能,当查询时指定sort为index sort,可以触发提前终止,不再收集全部命中文档。
. Scroll请求保存的上下文信息主要是maxScore和lastEmittedDoc用于翻页,但实际保存的不仅仅是ScrollContext,而是SearchContext,其中包含了更多关键信息,如searcher和IndexReader,后者对于后续索引更新是感知不到的,除非重新打开reader或使用DirectoryReader.openIfChanged(oldreader)。这是Scroll查询无法感知索引更新的原因。
. 经过测试,即使在scroll过程中触发了merge,被merge的segment文件也不会立即被删除,新的segment文件也不会被发现。这表明Scroll查询无法感知数据更新,其本质是快照了LeafReaderContext,并非检索命中的结果。
总结而言,Scroll查询在指定_doc排序时,通过优化收集过程和使用MinDocQuery实现跳跃功能,能显著提升性能,尤其是在翻页操作中。同时,Scroll请求的机制及底层实现使得其在查询处理上与search_after查询存在显著差异,但在Elasticsearch6.x版本中引入了索引预排序和提前终止功能,进一步优化了查询性能。
Flink Collector Output 接口源码解析
Flink Collector Output 接口源码解析
Flink中的Collector接口和其扩展Output接口在数据传递中起关键作用。Output接口增加了Watermark功能,是数据传输的基石。本文将深入解析collect方法及相关重要实现类,帮助理解数据传递的逻辑和场景划分。Collector和Output接口
Collector接口有2个核心方法,Output接口则增加了4个功能,WatermarkGaugeExposingOutput接口则专注于显示Watermark值。主要关注collect方法,它是数据发送的核心操作,Flink中有多个Output实现类,react渲染组件源码针对不同场景如数据传递、Metrics统计、广播和时间戳处理。Output实现类分类
Output类可以归类为:同一operatorChain内的数据传递(如ChainingOutput和CopyingChainingOutput)、跨operatorChain间(RecordWriterOutput)、统计Metrics(CountingOutput)、广播(BroadcastingOutputCollector)和时间戳处理(TimestampedCollector)。示例应用与调用链路
通过一个示例,我们了解了Kafka Source与Map算子之间的数据传递使用ChainingOutput,而Map到Process之间的传递则用RecordWriterOutput。在不同Output的选择中,objectReuse配置起着决定性作用,影响性能和安全性。 总结来说,ChainingOutput用于operatorChain内部,RecordWriterOutput处理跨chain,CountingOutput负责Metrics,BroadcastingOutputCollector用于广播,TimestampedCollector则用于设置时间戳。开启objectReuse会影响选择的Output类型。阅读推荐
Flink任务实时监控
Flink on yarn日志收集
Kafka Connector更新
自定义Kafka反序列化
SQL JSON Format源码解析
Yarn远程调试源码
State Processor API状态操作
侧流输出源码
Broadcast流状态源码解析
Flink启动流程分析
Print SQL Connector取样功能
[UVM源代码研究] 谈谈uvm中的浅拷贝(shallow copy)与深拷贝(deep copy)
在探讨UVM(Universal Verification Methodology)中的浅拷贝(shallow copy)与深拷贝(deep copy)之前,我们先对相关概念进行简要介绍,以便于理解以下讨论。浅拷贝和深拷贝是对象编程领域中基本概念,不仅限于系统Verilog(SV)和UVM(Universal Verification Methodology)。
浅拷贝:这一概念涉及的是拷贝对象的指针,即浅拷贝只复制指向对象内存空间的指针,使得目标对象与源对象共享同一内存空间。浅拷贝的局限性在于当内存空间被销毁时,所有指向该空间的指针必须重新定义,否则会导致野指针错误。
深拷贝:与此相反,深拷贝确保源对象和拷贝对象完全独立,两者之间互不影响,包括内存空间内容也被复制一份。例如,基本类型如Int、Double,以及结构体(struct)、枚举(Enum)会自动执行深拷贝,而类类型的对象则需区分浅拷贝与深拷贝。
在UVM中,`uvm_object`类提供了`copy`与`clone`函数来实现对象的拷贝。
`copy`函数为非虚拟、无返回值的函数,不能被重写,但`do_copy`函数为虚拟函数,可以通过重写`do_copy`函数实现对`copy`函数的间接重写。调用`copy`函数前,目标对象需先创建,三体游戏源码以实现源对象内部对象的深拷贝赋值,而不会对目标对象本身分配空间。
`clone`函数为虚拟函数,返回`uvm_object`类型,可以被重写。由于返回值类型限制,`clone`只能通过`$cast`来实现目标对象类型的转换,而不能直接赋值。`clone`函数返回一个指向源对象类型的`uvm_object`句柄,因此目标对象类型必须与源对象一致(通过`$cast`检查),以确保成功执行`clone`操作,且目标对象不需要事先分配空间,因为`clone`会自动分配新空间。
`copy`函数的实现中,除了`do_copy`之外的第行的`__m_uvm_field_automation(rhs, UVM_COPY, "")`完成了在`field_automation`中的配置实现。如果未重写`do_copy`函数,则所有拷贝行为依赖于`__m_uvm_field_automation`函数。
`uvm_object_defines.svh`文件在第行实现了将`copy`传入参数转换为局部变量`local_data__`,该变量类型为通过`uvm_object_untils_begin`传入的参数类型。`local_data__`在后续的`uvm_field_automation`宏中根据传入的标志位进行相应操作,以`uvm_field_object`为例。
在`uvm_field_object`中,关于`UVM_COPY`的具体操作表明,调用`copy`的源对象不能为空。如果`FLAG&UVM_NOCOPY`位为1,则直接结束代码执行。如果`FLAG&UVM_REFERENCE`位为1,或者`local_data__.ARG == null`,则将目标对象的`ARG`对象句柄指向源对象的`ARG`句柄。这种做法对于未分配空间的对象赋值,以避免错误。`UVM_REFERENCE`的应用场景主要针对`uvm_component`类型的对象注册,确保在进行`copy`和`clone`时执行浅拷贝,避免深拷贝导致的问题。
`uvm_component`类型在`copy`时默认执行深拷贝,而`UVM_REFERENCE`标志位则实现浅拷贝。例如,在`apb_env`中,`bus_monitor`和`bus_collector`被例化为`master`中的`monitor`和`collector`,同时`cfg`对象也传递给`master`。通过`field_automation`的修改,可以观察到`uvm_top`在打印树型结构时,`apb_monitor`和`cfg`对象的打印信息。
总结而言,UVM中的默认拷贝/克隆操作为深拷贝,`UVM_REFERENCE`标志位用于实现浅拷贝。理解这些概念对于在UVM中进行对象拷贝时避免错误至关重要。
UE4 Multi-Process Cooking(多进程烘焙)
MPCOOK(多进程Cook)是UE5.3的特色功能,它通过将Cook任务分发给多个进程,显著缩短Cook时间。由于我们项目在UE4中遇到Cook时间过长的问题,决定将MPCOOK功能移植到UE4。整个移植过程持续了约一个月,包括阅读源码、编写代码和调试。下面,我将分享这一过程中的主要思路和实现方案。
MPCOOK的核心流程涉及Host与Worker之间的通信。这两个角色通过Socket进行交互,实现任务的分发与结果的收集。在UE4中,我们面临的主要挑战是构建一个与MPCOOK结构兼容的代码框架,而不是逐行复制原代码。
网络逻辑处理中,我们选择了UE已提供的Socket功能,配合阻塞模式实现同步通信。考虑到消息处理的复杂性,我们决定用FMemoryArchive和自定义DataStream替代CompactBinary,以提升代码的可读性和可维护性。
在任务分配机制上,我们采用两种策略:LoadBalanceStriped平均分配任务,而LoadBalanceCookBurden则考虑了Cook任务和保存操作的开销,以达到更均衡的任务负载分配。在CookDirector模块中,我们实现了任务的管理,包括建立连接和分配任务等关键功能。
对于IMPCollector模块,我们简化了实现,去除了不必要的Collector,仅保留核心逻辑。IWorkerRequests接口的调整,让我们能够更清晰地区分Host与Worker的角色,优化了代码的组织结构。
MPCOOK的网络相关逻辑结构复杂,为了提高代码的可读性和可扩展性,我们重新设计了Socket的实现方式,并使用FMemoryArchive和DataStream来替代CompactBinary。CookDirector的设计允许我们为每个Worker分配少量任务,每完成一批任务后再请求下一批,确保每个任务的处理时间不超过3~5分钟。
对于CookWorkerClient和CookWorkerServer模块,我们保持了与原代码相似的结构,同时考虑了拓展性需求。IMPCollector模块的调整,简化了逻辑实现,提高了移植的效率。在CookOnTheFlyServer文件的移植过程中,我们采用了继承与虚函数实现的策略,简化了代码修改流程。
在ShaderLibrary部分,由于代码的增加,移植过程非常顺利。需要注意的是,使用FMemoryArchive替代CompactBinary是关键步骤之一。
数据传输环节,我们特别关注了ShaderCode、AssetPackageData、AssetData等数据的收集与发送,确保Cook流程的完整性。在启动Worker时,我们实现了一系列步骤,包括建立连接、接收设置信息、初始化资源和开始Tick等关键操作。
对于启动Host,我们特别处理了项目中对umap资源的Cook问题,确保依赖的umap和builtdata在同一个Worker上同时加载。在Worker发现新的依赖资源时,会在Host上记录资源来源,以便在需要时重新分发给指定的Worker。
通过这一系列的移植与优化,我们成功将MPCOOK功能引入到UE4项目中。使用MPCOOK后,项目全量Cook时间从约2个半小时缩短至分钟,预计进一步优化逻辑依赖问题后,时间可以缩短至分钟以内。这一移植与优化过程不仅提升了Cook效率,也为后续的项目开发提供了更强大的技术支持。
Java函数式编程:Collector接口详解
在Java8中引入了函数式编程范式,使得开发人员能更直观地利用Stream进行操作,例如对列表或数组中的元素进行分组等。这一过程中,Collector接口发挥了关键作用,帮助实现复杂操作的简洁化。
Collector接口的使用涵盖了三个泛型,具体功能则通过不同的方法实现。其核心在于提供从中间操作到最终结果的完整流程,包括生成容器、元素聚合以及结果合并等步骤。
具体来看,如Collectors.toList()方法就是将元素收集到一个列表中。这个方法内部首先生成一个ArrayList作为容器,接着使用accumulator方法将元素添加至容器内,最后通过指定的执行方式完成操作。此过程中,方法的调用路径涉及多个类,最终实现将多个元素整合为一个有序列表。
通过观察源码和运行过程,我们可以清晰地理解Collector接口的运作原理。例如,在实现自己的Collector接口时,需根据具体需求定义元素收集的逻辑,如去重操作。以Person对象的idCard字段去重为例,开发者可以设计特定的Collector实现类,来满足特定的业务需求。
实现步骤主要包括描述需求、定义方法逻辑、验证结果等环节。这不仅有助于优化代码结构,还能提高开发效率和代码可读性。通过实现自定义的Collector接口,可以灵活地应对各种复杂场景,实现更加高效、简洁的代码编写。
分布式链路追踪 SkyWalking 源码分析 —— DataCarrier 异步处理库
本文基于 SkyWalking 3.2.6 正式版,主要分享 SkyWalking Collector Remote 远程通信服务,用于 Collector 集群内部通信。Remote Module 应用于 SkyWalking 架构中,实现跨节点的流式处理。
本文从接口到实现顺序解析 SkyWalking Collector Remote 的项目结构和组件,包括 RemoteModule、RemoteSenderService、RemoteClientService、RemoteClient、CommonRemoteDataRegisterService、RemoteDataRegisterService、RemoteDataIDGetter、RemoteDataInstanceCreatorGetter、RemoteSerializeService、RemoteDeserializeService。RemoteModule 实现 Module 抽象类,定义服务如 RemoteSenderService、RemoteDataRegisterService,创建 RemoteClient 实现远程通信。CommonRemoteDataRegisterService 用于注册数据类型对应的远程数据创建器和获取数据协议编号。
接着,本文深入探讨基于 Google gRPC 的远程通信实现,包括 RemoteModuleGRPCProvider、GRPCRemoteSenderService、GRPCRemoteClientService、GRPCRemoteClient、RemoteCommonServiceHandler、GRPCRemoteSerializeService、GRPCRemoteDeserializeService。RemoteModuleGRPCProvider 提供基于 gRPC 的组件服务实现类,实现远程发送服务、客户端选择器和远程客户端服务。GRPCRemoteClient 实现基于 gRPC 的远程客户端,支持异步发送消息。
最后,本文提及 SkyWalking Collector Remote 也支持基于 Kafka 的远程通信实现,但目前暂未完成。为了进一步学习 SkyWalking 的分布式链路追踪和远程通信机制,读者可以关注公众号芋道源码,获取 Java 源码解析、原理讲解、面试题、学习指南,回复「书籍」领取 Java 从入门到架构的 本书籍,加入技术群讨论 Java、后端、架构相关技术。
Opentelemetry和Prometheus的remote-write-receiver的实验
实验目标:探索并实践Opentelemetry和Prometheus的集成,利用Prometheus的远程写功能与Opentelemetry的collector相结合,实现指标的主动推送,并通过Prometheus进行可视化管理。
实验环境:需要准备一个运行的Prometheus实例,以及一个Opentelemetry的collector。具体配置和部署步骤需参照实验环境部分。
实验过程:首先,配置Prometheus以抓取本地指标,通过修改Prometheus配置文件并启动windows_exporter实现本地指标的生成与输出。接着,配置和启动Opentelemetry的collector,确保其支持与Prometheus的远程写功能。在这一阶段,需要根据源代码(例如:wuqingtao/opentelemetry_demo/otel-collector-config.yaml)进行相应的调整。最后,通过执行指标生成命令(源代码来自:wuqingtao/opentelemetry_demo/app),确保指标能够被正确生成并主动推送至Prometheus。
可视化面板:在Prometheus中设置抓取目标,通常为运行的Prometheus实例。配置完成后,访问Prometheus控制面板,通过采集器面板查看并管理指标。同时,利用Prometheus的可视化功能,对主动写入的指标进行分析与监控。
实验结果:借助Prometheus的远程写功能和Opentelemetry的collector,实现了指标的主动推送至Prometheus。这一集成使得实时监控和分析数据成为可能,进一步强化了监控系统的能力,提升了数据处理效率。
深度解析Flink flatMap算子的自定义方法(附代码例子)
本文深入解读了Flink中flatMap算子的自定义方法,并提供了代码实例。在使用Flink的算子时,通常需要自定义,自定义时可以采用Lambda表达式或继承并重写函数类。
对于map、flatMap、reduce等操作,开发者可以实现MapFunction、FlatMapFunction、ReduceFunction等接口类。这些函数类拥有泛型参数,定义了输入或输出数据类型。要自定义函数,需要继承这些类并重写内部函数,例如FlatMapFunction接口由Flink的Function接口继承,且具备Serializable接口,用于确保在任务管理器之间进行序列化和反序列化。
在使用FlatMapFunction时,接口定义了两个泛型参数:T和O,分别对应输入和输出数据类型。自定义函数主要关注重写flatMap方法,该方法接受输入值value和Collector类out作为参数,负责处理输入数据并输出相应的结果。
本文提供了一个继承FlatMapFunction并实现flatMap的示例,用于对长度超过特定限制的字符串进行切词处理。
当处理逻辑简单时,使用Lambda表达式可能是更优的选择。Flink的Scala源码中提供三种定义flatMap的实现方式,每种方式在Lambda表达式的输入、输出类型和使用场景上有所不同。Lambda表达式可以简化代码编写,但需要注意类型匹配,以避免Intellij IDEA的类型检查提示。
本文还介绍了另一种实现方法——使用Intellij IDEA的类型检查和匹配功能,帮助开发者在代码编写过程中快速识别并修正类型不匹配的问题。
在某些情况下,Flink提供了更高级的Rich函数类,增加了Rich前缀的函数类在普通的函数类基础上增加了额外的功能,如RuntimeContext的访问,用于在分布式环境下进行更复杂的操作,如累加器的使用。
综上所述,Flink的自定义方法提供了丰富的功能,包括Lambda表达式、普通函数类和Rich函数类等。开发者可以根据实际需求选择合适的方法进行自定义,以实现高效的数据处理任务。