Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 11850 articles
Browse latest View live

长连接网关技术专题(四):爱奇艺WebSocket实时推送网关技术实践

$
0
0

本文由爱奇艺技术团队原创分享,原题《构建通用WebSocket推送网关的设计与实践》,有优化和改动。

1、引言

丛所周之,HTTP协议是一种无状态、基于TCP的请求/响应模式的协议,即请求只能由客户端发起、由服务端进行响应。在大多数场景,这种请求/响应的Pull模式可以满足需求。但在某些情形:例如消息推送(IM中最为常见,比如IM的离线消息推送)、实时通知等应用场景,需要实时将数据同步到客户端,这就要求服务端支持主动Push数据的能力。

传统的Web服务端推送技术历史悠久,经历了短轮询、长轮询等阶段的发展(见《 新手入门贴:史上最全Web端即时通讯技术原理详解》),一定程度上能够解决问题,但也存在着不足,例如时效性、资源浪费等。HTML5标准带来的WebSocket规范基本结束了这一局面,成为目前服务端消息推送技术的主流方案。

在系统中集成WebSocket十分简单,相关讨论与资料很丰富。但如何实现一个通用的WebSocket推送网关尚未有成熟的方案。目前的云服务厂商主要关注iOS和安卓等移动端推送,也缺少对WebSocket的支持。本文分享了爱奇艺基于Netty实现WebSocket长连接实时推送网关时的实践经验总结。

学习交流:

- 即时通讯/推送技术开发交流5群: 215477170 [推荐]

- 移动端IM开发入门文章:《 新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码: https://github.com/JackJiang2011/MobileIMSDK

(本文同步发布于: http://www.52im.net/thread-3539-1-1.html

2、专题目录

本文是系列文章的第4篇,总目录如下:

长连接网关技术专题(一):京东京麦的生产级TCP网关技术实践总结

长连接网关技术专题(二):知乎千万级并发的高性能长连接网关技术实践

长连接网关技术专题(三):手淘亿级移动端接入层网关的技术演进之路

长连接网关技术专题(四):爱奇艺WebSocket实时推送网关技术实践》(* 本文)

其它相关技术文章:

绝对干货:基于Netty实现海量接入的推送服务技术要点

京东到家基于Netty的WebSocket应用实践分享

爱奇艺技术团队分享的其它文章:

爱奇艺技术分享:轻松诙谐,讲解视频编解码技术的过去、现在和将来

爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结

爱奇艺移动端网络优化实践分享:网络请求成功率优化篇

3、旧方案存在的技术痛点

爱奇艺号是我们内容生态的重要组成,作为前台系统,对用户体验有较高要求,直接影响着创作者的创作热情。

目前,爱奇艺号多个业务场景中用到了WebSocket实时推送技术,包括:

  • 1)用户评论:实时的将评论消息推送到浏览器;
  • 2)实名认证:合同签署前需要对用户进行实名认证,用户扫描二维码后进入第三方的认证页面,认证完成后异步通知浏览器认证的状态;
  • 3)活体识别:类似实名认证,当活体识别完成后,异步将结果通知浏览器。

在实际的业务开发中,我们发现,WebSocket实时推送技术在使用中存在一些问题。

这些问题是:

  • 1)首先:WebSocket技术栈不统一,既有基于Netty实现的,也有基于Web容器实现的,给开发和维护带来困难;
  • 2)其次:WebSocket实现分散在在各个工程中,与业务系统强耦合,如果有其他业务需要集成WebSocket,面临着重复开发的窘境,浪费成本、效率低下;
  • 3)第三:WebSocket是有状态协议的,客户端连接服务器时只和集群中一个节点连接,数据传输过程中也只与这一节点通信。WebSocket集群需要解决会话共享的问题。如果只采用单节点部署,虽然可以避免这一问题,但无法水平扩展支撑更高负载,有单点的风险;
  • 4)最后:缺乏监控与报警,虽然可以通过Linux的Socket连接数大致评估WebSocket长连接数,但数字并不准确,也无法得知用户数等具有业务含义的指标数据;无法与现有的微服务监控整合,实现统一监控和报警。

PS:限于篇幅本文不详细介绍WebSocket技术本身,有兴趣可以详读《 WebSocket从入门到精通,半小时就够!》。

4、新方案的技术目标

如上节所示,为了解决旧方案中存在的问题,我们需要实现统一的WebSocket长连接实时推送网关。

这套新的网关需要具备如下特点:

  • 1)集中实现长连接管理和推送能力:统一技术栈,将长连接作为基础能力沉淀,便于功能迭代和升级维护;
  • 2)与业务解耦:将业务逻辑与长连接通信分离,使业务系统不再关心通信细节,也避免了重复开发,浪费研发成本;
  • 3)使用简单:提供HTTP推送通道,方便各种开发语言的接入。业务系统只需要简单的调用,就可以实现数据推送,提升研发效率;
  • 4)分布式架构:实现多节点的集群,支持水平扩展应对业务增长带来的挑战;节点宕机不影响服务整体可用性,保证高可靠;
  • 5)多端消息同步:允许用户使用多个浏览器或标签页同时登陆在线,保证消息同步发送;
  • 6)多维度监控与报警:自定义监控指标与现有微服务监控系统打通,出现问题时可及时报警,保证服务的稳定性。

5、新方案的技术选型

在众多的WebSocket实现中,从性能、扩展性、社区支持等方面考虑,最终选择了Netty。Netty是一个高性能、事件驱动、异步非阻塞的网络通信框架,在许多知名的开源软件中被广泛使用。

PS:如果你对Netty知之甚少,可以详读以下两篇:

WebSocket是有状态的,无法像直接HTTP以集群方式实现负载均衡,长连接建立后即与服务端某个节点保持着会话,因此集群下想要得知会话属于哪个节点有点困难。

解决以上问题一般有两种技术方案:

  • 1)一种是使用类似微服务的注册中心来维护全局的会话映射关系;
  • 2)一种是使用事件广播由各节点自行判断是否持有会话,两种方案对比如下表所示。

WebSocket集群方案:

综合考虑实现成本与集群规模,选择了轻量级的事件广播方案。

实现广播可以选择基于RocketMQ的消息广播、基于Redis的Publish/Subscribe、基于ZooKeeper的通知等方案,其优缺点对比如下表所示。从吞吐量、实时性、持久化、实现难易等方面考虑,最终选择了RocketMQ。

广播的实现方案对比:

6、新方案的实现思路

6.1 系统架构

网关的整体架构如下图所示:

网关的整体流程如下:

1)客户端与网关任一节点握手建立起长连接,节点将其加入到内存维护的长连接队列。客户端定时向服务端发送心跳消息,如果超过设定的时间仍没有收到心跳,则认为客户端与服务端的长连接已断开,服务端会关闭连接,清理内存中的会话。

2)当业务系统需要向客户端推送数据时,通过网关提供的HTTP接口将数据发向网关。

3)网关在接收到推送请求后,将消息写入RocketMQ。

4)网关作为消费者,以广播模式消费消息,所有节点都会接收到消息。

5)节点接收到消息后判断推送的消息目标是否在自己内存中维护的长连接队列里,如果存在则通过长连接推送数据,否则直接忽略。

网关以多节点方式构成集群,每节点负责一部分长连接,可实现负载均衡,当面对海量连接时,也可以通过增加节点的方式分担压力,实现水平扩展。

同时,当节点出现宕机时,客户端会尝试重新与其他节点握手建立长连接,保证服务整体的可用性。

6.2 会话管理

WebSocket长连接建立起来后,会话维护在各节点的内存中。SessionManager组件负责管理会话,内部使用了哈希表维护了UID与UserSession的关系。

UserSession代表用户维度的会话,一个用户可能会同时建立多个长连接,因此UserSession内部同样使用了一个哈希表维护 Channel与ChannelSession的关系。

为了避免用户无限制的创建长连接,UserSession在内部的ChannelSession超过一定数量后,会将最早建立的ChannelSession关闭,减少服务器资源占用。SessionManager、UserSession、ChannelSession的关系如下图所示。

SessionManager组件:

6.3 监控与报警

为了了解集群建立了多少长连接、包含了多少用户,网关提供了基本的监控与报警能力。

网关接入了 Micrometer,将连接数与用户数作为自定义指标暴露,供 Prometheus进行采集,实现了与现有的微服务监控系统打通。

Grafana中方便地查看连接数、用户数、JVM、CPU、内存等指标数据,了解网关当前的服务能力与压力。报警规则也可以在Grafana中配置,当数据异常时触发奇信(内部报警平台)报警。

7、新方案的性能压测

压测准备:

  • 1)压测选择两台配置为4核16G的虚拟机,分别作为服务器和客户端;
  • 2)压测时选择为网关开放了20个端口,同时建立20个客户端;
  • 3)每个客户端使用一个服务端端口建立起5万连接,可以同时创建百万个连接。

连接数(百万级)与内存使用情况如下图所示:

给百万个长连接同时发送一条消息,采用单线程发送,服务器发送完成的平均耗时在10s左右,如下图所示。

服务器推送耗时: 

一般同一用户同时建立的长连接都在个位数。以10个长连接为例,在并发数600、持续时间120s条件下压测,推送接口的TPS大约在1600+,如下图所示。

长连接10、并发600、持续时间120s的压测数据:

当前的性能指标已满足我们的实际业务场景,可支持未来的业务增长。

8、新方案的实际应用案例

为了更生动的说明优化效果,文章最后,我们也以封面图添加滤镜效果为例,介绍一个爱奇艺号使用新WebSocket网关方案的案例。

爱奇艺号自媒体发表视频时,可选择为封面图添加滤镜效果,引导用户提供提供更优质的封面。

当用户选择一个封面图后,会提交异步的后台处理任务。当异步任务处理完成后,通过WebSocket将不同滤镜效果处理后的图片返回给浏览器,业务场景如下图所示。

从研发效率方面考虑,如果在业务系统中集成WebSocket,至少需要1-2天的开发时间。

如果直接使用新的WebSocket网关的推送能力,只需要简单的接口调用就实现了数据推送,开发时间降低到分钟级别,研发效率大大提高。

从运维成本方面考虑,业务系统不再含有与业务逻辑无关的通信细节,代码的可维护性更强,系统架构变得更加简单,运维成本大大降低。

9、写在最后

WebSocket是目前实现服务端推送的主流技术,恰当使用能够有效提供系统响应能力,提升用户体验。通过WebSocket长连接网关可以快速为系统增加数据推送能力,有效减少运维成本,提高开发效率。

长连接网关的价值在于:

  • 1)它封装了WebSocket通信细节,与业务系统解耦,使得长连接网关与业务系统可独立优化迭代,避免重复开发,便于开发与维护;
  • 2)网关提供了简单易用的HTTP推送通道,支持多种开发语言接入,便于系统集成和使用;
  • 3)网关采用了分布式架构,可以实现服务的水平扩容、负载均衡与高可用;
  • 4)网关集成了监控与报警,当系统异常时能及时预警,保证服务的健康和稳定。

目前,新的WebSocket长连接实时网关已在爱奇艺号图片滤镜结果通知、MCN电子签章等多个业务场景中得到应用。

未来还有许多方面需要探索,例如消息的重发与ACK、WebSocket二进制数据的支持、多租户的支持等。

附录:更多相关技术资料

[1] 有关WEB端即时通讯开发:

新手入门贴:史上最全Web端即时通讯技术原理详解

Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

SSE技术详解:一种全新的HTML5服务器推送事件技术

Comet技术详解:基于HTTP长连接的Web端实时通信技术

新手快速入门:WebSocket简明教程

WebSocket详解(一):初步认识WebSocket技术

WebSocket详解(二):技术原理、代码演示和应用案例

WebSocket详解(三):深入WebSocket通信协议细节

WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)

WebSocket详解(六):刨根问底WebSocket与Socket的关系

socket.io实现消息推送的一点实践及思路

LinkedIn的Web端即时通讯实践:实现单机几十万条长连接

Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践

Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)

开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器

使用WebSocket和SSE技术实现Web端消息推送

详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket

MobileIMSDK-Web的网络层框架为何使用的是Socket.io而不是Netty?

理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性

微信小程序中如何使用WebSocket实现长连接(含完整源码)

八问WebSocket协议:为你快速解答WebSocket热门疑问

Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?

WebSocket从入门到精通,半小时就够!

WebSocket硬核入门:200行代码,教你徒手撸一个WebSocket服务器

>>  更多同类文章 ……

[2] 有关推送技术的文章:

一个基于MQTT通信协议的完整Android推送Demo

求教android消息推送:GCM、XMPP、MQTT三种方案的优劣

移动端实时消息推送技术浅析

绝对干货:基于Netty实现海量接入的推送服务技术要点

极光推送系统大规模高并发架构的技术实践分享

魅族2500万长连接的实时消息推送架构的技术实践分享

专访魅族架构师:海量长连接的实时消息推送系统的心得体会

基于WebSocket实现Hybrid移动应用的消息推送实践(含代码示例)

一个基于长连接的安全可扩展的订阅/推送服务实现思路

实践分享:如何构建一套高可用的移动端消息推送系统?

Go语言构建千万级在线的高并发消息推送系统实践(来自360公司)

腾讯信鸽技术分享:百亿级实时消息推送的实战经验

百万在线的美拍直播弹幕系统的实时推送技术实践之路

京东京麦商家开放平台的消息推送架构演进之路

技术干货:从零开始,教你设计一个百万级的消息推送系统

长连接网关技术专题(四):爱奇艺WebSocket实时推送网关技术实践

>>  更多同类文章 ……

本文已同步发布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是: 点此进入。同步发布链接是: http://www.52im.net/thread-3539-1-1.html



Jack Jiang 2021-05-17 23:12 发表评论

这个金融级异常根因定位,智能运维界都直呼内行!

$
0
0
作者介绍

孟庆江、田忠毅,中金财富证券股份有限公司信息技术部,本文选自《交易技术前沿》总第四十期文章(2020年9月)。

 

证券行业是对连续性、稳定性要求最高的行业之一,客户交易体验永远是券商关注的话题。当交易系统出现异常时,快速定位异常根因并实施恢复可减少异常对客户体验造成的影响。但传统的根因定位方案目前存在一些困难:

 

  • 一是目前券商交易系统在各个环节的衔接广泛采取了多对多的机器架构,传统的链路定位在这种情况下效果并不理想;

 

  • 二是监控技术的发展使得告警数量呈爆发式的增长,导致告警与异常间的关联关系变得难以确定,增加了异常根因定位的难度。

     

智能化运维的出现,为我们解决这些问题提供了新的思路与方法。本文首先概述了中金财富证券近年来在智能化运维方面所作的工作,而后重点介绍了为解决以上两个运维痛点所作的探索与实践:通过聚类与贝叶斯方法实现智能化的根因定位,以期提高异常根因定位的效率。

 

一、引言

 

 1、智能化运维的产生与发展

 

在金融科技的不断发展、金融产品的不断增加及金融监管的愈发加强等因素的影响下,证券公司的IT架构也逐渐复杂化,其表现在机器设备不断增加、业务链条不断增长、系统间的相互关联性逐渐上升,这给券商运维团队提出了新的难题。

 

受限于人力、经验等客观条件的制约,使用传统的、基于规则的运维方式已无法满足券商业务的连续无中断性要求,因此,券商运行维护系统需要紧紧跟上证券公司业务创新发展的潮流,脱离依赖人工和经验为主的传统模式,运用新的技术解决新的问题。

 

随着大数据、人工智能等先进科技不断给金融行业赋能,智能化运维逐渐成为证券行业运维系统的发展方向。智能化运维顾名思义,是将运维数据和大数据、机器学习技术相结合,针对运维场景开发成一系列的智能策略,融入到运维系统中。智能化运维赋予了IT系统从历史中自动获取经验知识的能力,同时借助大数据平台的强大算力实现了横向的拓展,有效的解决了运维团队对于经验和人力的依赖。

 

自2015年开始中金财富逐步开始建立基于大数据、人工智能为技术基础,以事前预测、事中检测、事后分析为应用场景的智能化运维体系。该体系使用后,不仅可以有效的解决由于业务发展所带来的运维问题,提升了系统安全性,同时也帮助运维团队摆脱了以往简单重复性的工作,可以将更多精力投入到提升服务质量的需求开发中,增加了运维团队的工作价值。如图1.1所示,智能化运维促使券商IT系统形成了以业务需求推动监控发展、以监控发展促进智能化运维算法落地、智能化运维提升运维自动化水平、进而提升业务能力的闭环。

 

图1.1 中金财富智能运维价值闭环

 

 2、中金财富在智能化运维中的探索、实践

 

中金财富证券从自身业务需求出发,以事前预测、事中检测、事后分析为发展方向,自2015年以来不断探索、实践智能化运维算法,并推进落地,如图1.2所示,通过对历史数据的学习与挖掘、对实时数据的即时分析及对异常模式的识别,实现了基于动态数据拟合基线的指标异常检测、基于FT-Tree算法的日志概率异常检测、基于趋势预测算法的容量预测与评估、智能流量调度决策,基于XGBoost算法的机器健康度模型、基于聚类与贝叶斯算法的异常根因定位等等。在本文后续的章节,我们将主要介绍中金财富在智能化根因定位方面所作的一些探索与实践。

 

图1.2 中金财富智能运维实践场景

 

 3、智能化根因定位的发展

 

根因定位是智能化运维中一项重要且难于实现的领域。受限于当前的技术水平,即使再完善的系统也无法避免会出现异常,因此如何将快速定位异常的根因并实施恢复以降低异常造成的影响就显得至关重要。券商实行智能化异常定位的目标就是要尽量降低券商系统出现异常后给客户体验甚至资金安全造成的影响。但随着券商交易系统的规模不断扩大及各系统间的相互影响趋于复杂,让异常定位变得愈发困难。

 

目前行业内外有已有多种根因定位方案投入使用,如Twitter和大众点评等开源了他们基于调用链的根因定位方案,百度公司采用基于异常范围搜索的HotSpot算法进行根因分析,建设银行、光大证券等采用了基于时间序列相关性分析的算法等。但因为系统之间差异性及根因定位的复杂性,目前仍然没有一套能在所有系统行之有效的方案。因此,中金财富证券在学习业内外根因定位方案思路的基础上,为解决自身运维痛点,提出了两项智能化的根因定位方案,下文会对这两项方案做详细的介绍。

 

 4、中金财富智能化根因定位方案

 

在中金财富证券交易系统中,前端机器的响应时间为客户从下单到收到响应的时间,可以反映客户体验的好坏。一些前端机器的响应时间异常是由后台机器的响应时间异常引起,但前端机器与后台机器、后台机器之间的关联性会随时变动,为异常定位增加了难度,传统的基于链路的异常根因定位在这类情况下效果并不理想。

 

因此,我们设计了一种基于DBSCAN聚类算法,在识别到异常发生后,通过将异常的前端机器指标值序列与所有可能与其关联的后台机器指标值序列聚类,从而快速定位到异常根因。

 

同时交易系统有一部分引起异常的根因无法从数值中体现,而在异常发生前后以文字告警的形式出现。但随着监控技术的发展券商运维团队时刻会收到的大量的告警,这就需要我们去区分哪些告警是与交易系统异常相关,哪些无关。因此,我们基于贝叶斯方法,运用历史数据从概率的角度推断告警与异常的关联关系,对告警进行排序,从而定位异常根因。在下文中我们会详细介绍这两种方案在中金财富证券的落地实践,供大家参考。

 

二、基于聚类的异常根因定位方案

 

在这一方案中,我们主要实现了异常识别、告警压缩、特征处理、聚类实现四项功能。并以中金财富证券的一种移动端交易渠道A(以下称A交易系统)的响应时间为例,对方案进行了实践。本部分主要叙述了异常识别与聚类实现两项功能,以及在A交易系统的实践结果。

 

 1、异常识别

 

中金财富证券通过基线的方式发现交易系统指标的异常,基线是通过学习历史经验,为业务指标提供判断标准的基准线。在实际应用中,为避免偶发告警造成误告,通常配置将M分钟内N次超出基线的事件判断为一次异常。为保障异常发现的时效性,M不宜过大,同时为兼顾异常发现的灵敏性,N/M也不宜过大。在本方案中我们选择M=5,N=3。这一方案对基线的准确性提出了较高的要求,在中金财富运维实践中,A交易系统的基线存在两种风险:

 

  • 系统配置发生变更后,与之相关的前端机器响应时间会随之发生变化,但基线的变化存在滞后性,使得与当前响应时间不匹配,导致误告警发出。

 

  • 基线学习了错误的历史经验而偏离,造成误告警。对此,我们利用配置变更校正算法和滑动T检验法,降低这两类误告警的概率。

 

(1)配置变更校正算法

 

A交易系统存在经常性的配置变更,在变更后关联机器的响应时间也会发生整体平移,往往会超出基线范围。为应对这种情况,我们设计了配置变更校正算法,通过计算基线中轨序列与响应时间序列间的差值并使用差值校正响应时间,减少此类误告警。算法步骤如下:                      

  • 读取从判定时刻起前30分钟内指标的基线值,并求出这30分钟内指标的基线均值序列 BLVS :  

                                                   

 

  • 读取从判定时刻起前30分钟内指标的真实值,并与基线均值做出这30分钟的差值序列DSVS:    

 

 

  • 对DSVS中的值进行异常过滤,设过滤后的序列为FDSV,对过滤后的序列求均值,为变更校正值BIAS:

      

 

 

  • 指标校正值序列 ADVS = value–BIAS,value为A交易系统响应时间序列。

 

 

如图2.1所示,中金财富A交易系统在一次变更后,一台前端机器的业务响应时间偏离基线的情况,图2.2为经校正的指标值与基线的比较。利用校正后的指标序列再次与基线进行比较,可有效减少因配置变更造成的误告警,该算法在中金财富A交易系统的实践中可过滤超过98%的告警。

 

(2)滑动T检验算法

 

滑动T检验算法常用于气象研究中,用于检测时间序列是否发生突变。本方案引用滑动T检验法,对异常序列进行突变检测,若异常序列并未发生突变,则可认定为并未发生异常。

 

图2.3 基线偏离事件

 

图2.3为中金财富A交易系统的一台前端机器发生的一次基线偏离,经滑动T检验算法,该次异常被过滤。滑动T检验算法可有效降低由于基线偏离而产生误告警的概率。该算法在中金财富A交易系统的实践中可过滤超过10%的告警,进一步提高了告警的准确性。

 

 2、聚类实现

 

为实现指标聚类,首先需定义指标序列间的距离度量,本方案对比了三种距离度量:欧式距离、基于Pearson相关系数的度量和基于Spearman相关系数的度量,假设CDS1、CDS2为两个时间序列,CDij为CDSi的第j个分量,n为时间序列的长度,他们的距离定义如下:

 

(1)欧式距离:

 

 

(2)基于Pearson相关系数的度量:

 

 

(3)基于Spearman相关系数的度量 :

 

 

其中,rk1、rk2 为CDS1、CDS2中的分量转换为降序位置指标后的序列。由于方案主要考察A交易系统前端机器响应时间与后台机器响应时间之间有无同增同减的线性关系,因此基于Pearson相关系数的度量与基于Spearman相关系数的度量更适合本方案,在中金财富A交易系统的实践中,基于Pearson相关系数的度量对于时间序列的距离度量最为准确。

 

在异常传导的过程中可能会跨分钟,因此,对于后台机器相关指标,我们同时取((t1-1)to(tl-1)),(t1to tl), 这两个时间段的值序列,并取这两个序列中与A交易系统前端机器异常响应时间序列距离的最小值作为最终距离。

 

本方案采用DBSCAN 聚类方法,DBSCAN基于密度对指标进行聚类,无需事先确定类的个数,与本方案的场景相符。图2.4展示了基于聚类的根因挖掘方案流程。

 

图2.4 基于聚类的根因挖掘方案流程图

 

 3、实践结果

 

本次实践以A交易系统为例:A交易系统是中金财富某一移动端交易系统。用户在移动端下达交易请求后,首先将请求传送至A交易系统的前端机器进行处理,前端机器处理后再按照请求信息,调用A交易系统后台机器的各项服务以执行客户请求。因此,前端机器对请求g的响应时间包括了前端机器处理请求的时间与各类后台机器执行请求的时间。如以下表达式所示,

 

 

为一笔请求g的响应时间,

 

为请求g在前端的处理时间,

 

为后台机器在执行请求g时第i阶段的响应时间:
 


 

当客户请求在前端机器的响应时间发生异常后,我们使用聚类根因定位算法,快速分析用户的请求是在哪一环节出现了问题,以便实施恢复。我们使用A交易系统2020年5-6月份数据进行回测,共识别出51次响应时间异常,成功挖掘出26次异常的根因。

 

 

图2.5为A交易系统某前端机器在一时刻响应时间的异常情况,使用本方案进行聚类后,识别出三台后主机的响应时间异常(图2.6-图2.8)为本次异常的根因。

 

三、基于贝叶斯推断的异常根因定位方案

 

A交易系统中,存在一部分引起异常的根因无法从数值中体现,而在异常发生前后以文字告警的形式出现。随着监控技术的发展、监控手段的丰富,中金财富集中告警管理平台每分钟可能收到数以百计的告警,使得区分哪些与异常相关、哪些与异常无关存在一定的困难。因此,我们基于贝叶斯方法,通过挖掘告警与异常之间的关联概率从而推断他们的关联关系,并在中金财富A交易系统中进行了实践。下文对方案实现及实践结果进行了叙述。

 

 1、方案实现

 

由贝叶斯公式,告警事件X会导致A交易系统发生异常事件Y的概率P(Y|X)可经如下计算得到:

P(Y|X) * P(X) =  P(X|Y) * P(Y)

P(Y|X) =  P(X|Y) * P(Y) / P(X)

 

因此,条件概率P(Y|X) 正比于 P(X|Y) / P(X) ,基于此,我们设计以下步骤实现该推断:

 

(1) 统计在历史上,Y事件发生的前五分钟以内有哪些告警事件发生,并计算每个异常告警X出现的条件概率P(X|Y)。

 

(2) 统计在历史上任意五分钟内X事件发生的概率,得到 P(X)。

 

(3) 计算关联概率相对值P(X|Y) /P(X) , 数值越大,表明告警X事件越可能导致Y事件。

 

(4) 收集A交易系统发生异常事件Y之前五分钟内的告警,并根据计算所得的关联概率相对值,由高到低将告警排序输出。

 

图3.1展示了基于贝叶斯推断的异常根因挖掘方案模型训练及推断流程。

 

图 3.1 基于贝叶斯推断的异常根因挖掘方案流程图

 

 2、实践结果

 

中金财富在2015年建立了全局统一的告警事件平台,用于接受各类监控所产生的告警。由于监控系统不断完善,告警时间平台在单位时间内收到的告警数量不断增长,其中大部分为轻微、偶发的告警,因此当交易异常发生后,我们通过使用基于贝叶斯推断的根因分析算法确定哪些告警与本次异常相关,从而掌握更多异常细节信息便于实施恢复。

 

我们使用了2020年2-6月份A交易系统的数据进行回测,由于贝叶斯方法的推断需要较大的数据量才能准确,因此我们选取了一个发生次数较多的异常事件进行挖掘:A交易系统前端机器V交易响应时间异常。

 

经回测发现如下结果:最有可能导致A交易系统V机器交易响应时间异常的三个告警事件和最不可能导致它的一个告警事件及其推断相对值是:

 

表3.1 贝叶斯推断结果

 

贝叶斯推断的结果具有较高的分离度,各类告警与A交易系统异常间的关联关系相对值差距明显,作为排序依据有一定的参考价值。

 

四、总结与展望

 

为保障客户体验,中金财富证券在行业内较早的实现了基于大数据的自动化运维,并一直致力于智能化运维的推广与落地。

 

本文介绍了中金财富在智能化运维中异常根因定位方面所作的一些探索与实践,所涉及的方案仍存在一定的局限性,在异常根因定位的成功率方面还有一定的提升空间。

 

在近期,智能化根因定位出现了一批新的算法方案,如基于知识图谱与基于自然语言处理的根因定位方案,中金财富会结合新的算法,持续探索、不断改进。随着智能化技术的不断发展,智能化运维必将愈发成为证券企业降本增效的利器,中金财富也会不断地升级技术手段,为客户提供更高质量的服务。

 

作者丨孟庆江 田忠毅

Oracle数据库给字段设置默认时间及更新字段之后时间更新 - 牧雨 - 博客园

$
0
0

一、给字段设置默认时间

1、建表时运用 DEFAULT SYSDATE 给字段设置默认时间:

CREATETABLE"TEST"."TEST_DATE" (
idVARCHAR2(2BYTE)NOTNULL,valuesNUMBERNOTNULL,
create_time DATEDEFAULTSYSDATE,
update_timeTIMESTAMP(6)DEFAULTSYSDATE
)。

 

2、运用 alter table 来给字段添加默认值:

altertableTEST_DATEadd"creat_time" DATEDEFAULTSYSDATE;

 

其中:

TEST_DATE 为表名。

"creat_time"为具体字段名。

DATE :为字段类型。

注意所选字段为当前表的字段,且字段正确性要验证,否则会多添加出一个字段 。

二、字段更新后自动更新update_time.

  通过给表设置触发器,当触发器触发时则会自动调用触发条件:

  

createorreplacetriggerTEST_DATE_trigger
beforeupdateonTEST_DATEforeach rowbegin:new.UPDATE_TIME :=sysdate;end;

其中:

TEST_DATE_trigger 为触发器名称。

TEST_DATE :为表名

UPDATE_TIME:为字段名

 

数据仓库系列之ETL中常见的增量抽取方式 - 简书

$
0
0

        为了实现数据仓库中的更加高效的数据处理,今天和小黎子一起来探讨ETL系统中的增量抽取方式。增量抽取是数据仓库ETL(数据的抽取(extraction)、转换(transformation)和装载(loading))实施过程中需要重点考虑的问题。ETL抽取数据的过程中,增量抽取的效率和可行性是决定ETL实施成败的关键问题之一,做过数据建模的小伙伴都知道ETL中的增量更新机制比较复杂,采用何种机制往往取决于源数据系统的类型以及对增量更新性能的要求。今天我们只重点对各种方法进行对比分析,从而总结各种机制的使用条件和优劣性,为数据仓库项目的ETL工程的实施提供增量抽取技术方案参考。

高效智能数据仓库平台

       在数据库仓库开发过程中,无论是全量抽取方案还是增量抽取方案,抽取数据的工作一般由数据仓库工具来完成。目前数据仓库开发工具非常多,比如SE-DWA,DTS,Kettle等等。虽然增量抽取方案设置比较简单,但是我们还是需要具体来了解一下增量抽取机制以便后续更合理的利用增量抽取方案。下面讨论各种增量抽取的实现机制原理。

一、增量抽取的机制

实现增量抽取关键准确快速的捕获变化的数据。优秀的增量抽取机制要求ETL能够将业务系统中的变化数据按一定的频率准确地捕获,同时不能对业务系统造成太大的压力,影响现有业务。相对全量抽取而言,增量抽取的设计更复杂,有一种将全量抽取过程自动转换为增量抽取过程的ETL设计思路,前提是必须捕获变化的数据,增量数据抽取中常用的捕获变化数据的方法小黎子了解到的有以下四种方式:

1 、基于触发器方式生成增量数据

触发器方式

   使用触发器生成增量数据是普遍采取的一种增量抽取机制。该方式是根据抽取要求,在要被抽取的源表上建立3个触发器插入、修改、删除,每当源表中的数据发生变化,就被相应的触发器将变化的数据写入一个增量日志表,ETL的增量抽取则是从增量日志表中而不是直接在源表中抽取数据,同时增量日志表中抽取过的数据要及时被标记或删除。

   为了简单演示,增量日志表一般不存储增量数据的所有字段信息,而只是存储源表名称、更新的关键字值和更新操作类型(knsen、update或delete),ETL增量抽取进程首先根据源表名称和更新的关键字值,从源表中提取对应的完整记录,再根据更新操作类型,对目标表进行相应的处理。

优点:数据库本身的触发器机制,契合度高,可靠性高,不会存在有增量数据未被捕获到的现象    

缺点:对于源系统有较大的影响,需要建立触发器机制,增加运维人员,还要建立临时表,储存临时表,增加储存成本和运维成本

2 、基于时间戳方式生成增量数据

时间戳方式

   时间戳方式是指增量抽取时,抽取进程通过比较系统时间与抽取源表的时间戳字段的值来决定抽取哪些数据。这种方式需要在源表上增加一个时间戳字段,系统中更新修改表数据的时候,同时修改时间戳字段的值。

   有的数据库(例如Sql Server)的时间戳支持自动更新,即表的其它字段的数据发生改变时,时间戳字段的值会被自动更新为记录改变的时刻。在这种情况下,进行ETL实施时就只需要在源表加上时间戳字段就可以了。对于不支持时间戳自动更新的数据库,这就要求业务系统在更新业务数据时,通过编程的方式手工更新时间戳字段。使用时间戳方式可以正常捕获源表的插入和更新操作,但对于删除操作则无能为力,需要结合其它机制才能完成。

  优点:数据处理逻辑清楚,速度较快,成本低廉,流程简单

   缺点:要求源表的时间字段必须是随表变动而变动的不为空数据,由于是直接读取表数据,该方法无法获取删除类型的数据。

3、 基于全表比对方式生成增量数据

全表对比

   全表比对即在增量抽取时,ETL进程逐条比较源表和目标表的记录,将新增和修改的记录读取出来。

   优化之后的全部比对方式是采用MD5校验码,需要事先为要抽取的表建立一个结构类似的MD5临时表,该临时表记录源表的主键值以及根据源表所有字段的数据计算出来的MD5校验码,每次进行数据抽取时,对源表和MD5临时表进行MD5校验码的比对,如有不同,进行update操作:如目标表没有存在该主键值,表示该记录还没有,则进行insert操作。然后,还需要对在源表中已不存在而目标表仍保留的主键值,执行delete操作。

    优点:因为是基于目标对比抽取数据,所以对源系统无影响

  缺点:该方法仅仅适合表有主键,唯一键或者数据量较小的表,不然海量数据中每条数据的每一列都进行逐一比对,很显然这种频繁的I/O操作以及复杂的比对运算会造成很大的性能开销。这样操作需要足够的硬件做支撑

4 、基于日志表方式生成增量数据

日志方式

   对于建立了业务系统的生产数据库,可以在数据库中创建业务日志表,当特定需要监控的业务数据发生变化时,由相应的业务系统程序模块来更新维护日志表内容。增量抽取时,通过读日志表数据决定加载哪些数据及如何加载。日志表的维护需要由业务系统程序用代码来完成。

   优点:可以做到数据无误差传输,有回滚机制,有容灾备份的能力

   缺点:数据库开归档模式会对源系统数据库的磁盘造成压力,增加储存成本,此外大多数数据库的日志都是不对外开放的,只针对数据库本身的工具开放读取

二、比较和分析

   可见,ETL在进行增量抽取操作时,有以上各种机制可以选择。现从兼容性、完备性、性能和侵入性3个方面对这些机制的优劣进行比较分析。各种数据增量抽取机制的优劣性综合分析如下图所示。

增量方式对比

   通过对各种增量抽取机制的对比分析,我们发现,没有一种机制具有绝对的优势,不同机制在各种因素的表现大体上都是相对平衡的。所以,ETL实施过程中究竞选择哪种增量抽取机制,要根据实际的数据源系统环境进行决策,需要综合考虑源系统数据库的类型、抽取的数据量(决定对性能要求的苛刻程度)、对源业务系统和数据库的控制能力以及实现难度等各种因素,甚至结合各种不同的增量机制以针对环境不同的数据源系统进行ETL实施。

三、总结

总结

   为了实现数据仓库中数据的高效抽取,增量抽取是ETL数据抽取过程中非常重要的一步,实现增量抽取的机制直接决定了数据仓库项目整体开发的效果。我们通过对比几种常见的增量抽取机制并总结了各种机制的特性并分析了它们的优劣。各种增量抽取机制都有它有存在的价值和固有的限制条件,所以在ETL的设计和实施工作过程中,只能依据项目的实际环境进行综合考虑,甚至需要对可采用的多种机制进行实际的测试,才能确定一个最优的增量抽取方法。

每周工作超55小时或心脏病致死,来看看世界公认的最健康作息时间表参考

$
0
0

世界卫生组织和国际劳工组织17日发布报告说,工作时间过长造成全球数十万人死于中风或心脏病,新冠疫情居家办公恐怕会令这一趋势恶化。

报告显示,每周工作至少55个小时导致全球74.5万人在2016年死于中风和局部缺血性心脏病,较2000年增长29%。这些人去世时年龄在60岁至79岁不等,他们在45岁至74岁期间每周工作55个小时或更长时间。长时间工作致死者中72%是男性。

报告分析154个国家和地区数据发现,相比每周工作35个小时至40个小时,每周工作55个小时或更长时间者死于中风的风险高35%,死于局部缺血性心脏病的风险高17%。

报告说,就地区而言,东南亚和西太平洋地区因长时间工作致死的情况更多。研究报告刊载于12日出版的美国杂志《国际环境》。

相关研究在2000年至2016年展开,未涉及新冠疫情以来情况。然而,世卫组织担心,超长时间工作的趋势自疫情以来会恶化,因为居家办公可能延长工作时间。

来看看世界公认的最健康作息时间表参考

  • 7:00:起床

英国威斯敏斯特大学的研究人员发现,那些在早上5:22―7:00 分起床的人,其血液中有一种能引起心脏病的物质含量较高,因此,在7:00之后起床对身体健康更加有益。打开台灯。“一醒来,就将灯打开,这样将会重新调整体内的生物钟,调整睡眠和醒来模式。”拉夫堡大学睡眠研究中心教授吉姆•霍恩说。喝一杯水。水是身体内成千上万化学反应得以进行的必需物质。早上喝一杯清水,可以补充晚上的缺水状态。

  • 7:00―7:20:在早饭之前刷牙

“在早饭之前刷牙可以防止牙齿的腐蚀,因为刷牙之后,可以在牙齿外面涂上一层含氟的保护层。要么,就等早饭之后半小时再刷牙。”英国牙齿协会健康和安全研究人员戈登•沃特金斯说。

  • 7:20―8:00:吃早饭

“早饭必须吃,因为它可以帮助你维持血糖水平的稳定。”伦敦大学国王学院营养师凯文•威尔伦说。早饭可以吃燕麦粥等,这类食物具有较低的血糖指数。

  • 8:30―9:00:避免运动

来自布鲁奈尔大学的研究人员发现,在早晨进行锻炼的运动员更容易感染疾病,因为免疫系统在这个时间的功能最弱。步行上班。马萨诸塞州大学医学院的研究人员发现,每天走路的人,比那些久坐不运动的人患感冒病的几率低25%。

  • 9:30:开始一天中最困难的工作

纽约睡眠中心的研究人员发现,大部分人在每天醒来的一两个小时内头脑最清醒。

  • 10:30:让眼睛离开屏幕休息一下

如果你使用电脑工作,那么每工作一小时,就让眼睛休息3分钟。

  • 11:00:吃点水果

这是一种解决身体血糖下降的好方法。吃一个橙子或一些红色水果,这样做能同时补充体内的铁含量和维生素C含量。

  • 12:30:在面包上加一些豆类蔬菜

你需要一顿可口的午餐,并且能够缓慢地释放能量。“烘烤的豆类食品富含纤维素,番茄酱可以当作是蔬菜的一部分。”维伦博士说。

  • 13:00―14:00:午休一小会儿

雅典的一所大学研究发现,那些每天中午午休30分钟或更长时间,每周至少午休3次的人,因心脏病死亡的几率会下降37%。

  • 16:00:喝杯酸奶

这样做可以稳定血糖水平。在每天三餐之间喝些酸牛奶,有利于心脏健康。

  • 17:00―19:00:锻炼身体

根据体内的生物钟,这个时间是运动的最佳时间,舍菲尔德大学运动学医生瑞沃•尼克说。

  • 19:30:晚餐少吃点

晚饭吃太多,会引起血糖升高,并增加消化系统的负担,影响睡眠。晚饭应该多吃蔬菜,少吃富含卡路里和蛋白质的食物。吃饭时要细嚼慢咽。

  • 20:00:看会电视或学习

这个时间看会儿电视放松一下,有助于睡眠,但要注意,尽量不要躺在床上看电视,这会影响睡眠质量。

  • 22:00:洗个热水澡

“体温的适当降低有助于放松和睡眠。”拉夫堡大学睡眠研究中心吉姆•霍恩教授说。

  • 22:30:上床睡觉

如果你早上7点起床,现在入睡可以保证你享受充足的睡眠。

任何试图更改生物钟的行为,都将给身体留下莫名其妙的疾病,35年之后再后悔,已经来不及了。

(编辑:梁宇芳)

自己动手实现springboot运行时执行java源码(运行时编译、加载、注册bean、调用)

$
0
0

  看来断点、单步调试还不够硬核,根本没多少人看,这次再来个硬核的。依然是由于apaas平台越来越流行了,如果apaas平台选择了java语言作为平台内的业务代码,那么不仅仅面临着IDE外的断点、单步调试,还面临着为了实现预览效果,需要将写好的java源码动态的装载到spring容器中然后调用源码内的某个方法。这篇文章主要就是实现spring/springboot运行时将源码先编译成class字节码数组,然后字节码数组再经过自定义类加载器变成Class对象,接着Class对象注册到spring容器成为BeanDefinition,再接着直接获取到对象,最后调用对象中指定方法。相信在网上其他地方已经找不到类似的实现了,毕竟像我这样专门做这种别人没有的原创的很少很少,大多都是转载下别人的,或者写些网上一大堆的知识点,哈哈!

  个人认为分析复杂问题常见思维方式可以类比软件领域的分治思想,将复杂问题分解成一个个小问题去解决。或者是使用减治思想,将复杂问题每次解决一小部分,留下的问题继续解决一个小部分,这样循环直到问题全部解决。所以软件世界和现实世界确实是想通的,很多思想都可以启迪我们的生活,所以我一直认为一个很会生活的程序员,一个把生活中出现的问题都解决的很好的程序员一定是个好程序员,表示很羡慕这种程序员。

  那么我们先分解下这个复杂问题,我们要将一个java类的源码直接加载到spring容器中调用,大致要经历的过程如下:

  1、先将java类源码动态编译成字节数组。这一点在java的tools.jar已经有工具可以实现,其实tools.jar工具包真的是一个很好的东西,往往你走投无路不知道怎么实现的功能在tools.jar都有工具,比如断点调试,比如运行时编译,呵呵

  2、拿到动态编译的字节码数组后,就需要将字节码加载到虚拟机,生成Class对象。这里应该不难,直接通过自定义一个类加载器就可以搞定

  3、拿到Class对象后,再将Class转成Spring的Bean模板对象BeanDefinition。这里可能需要一点spring的知识随便看一点spring启动那里的源码就懂了。

  4、使用spring的应用上下文对象ApplicationContext的getBean拿到真正的对象。这个应该用过spring的都知道

  5、调用对象的指定方法。这里为了不需要用反射,一般生成的对象都继承一个明确的基类或者实现一个明确的接口,这样就可以由多肽机制,通过接口去接收实现类的引用,然后直接调用指定方法。

  下面先看看动态编译的实现,核心源码如下

/**
 * 动态编译java源码类
 * @author rongdi
 * @date 2021-01-06
 */
public class DynamicCompiler {

    /**
     * 编译指定java源代码
     * @param javaSrc java源代码
     * @return 返回类的全限定名和编译后的class字节码字节数组的映射
     */
    public static Map compile(String javaSrc) {
        Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");
        Matcher matcher = pattern.matcher(javaSrc);
        if (matcher.find()) {
            return compile(matcher.group(1) + ".java", javaSrc);
        }
        return null;
    }

    /**
     * 编译指定java源代码
     * @param javaName java文件名
     * @param javaSrc java源码内容
     * @return 返回类的全限定名和编译后的class字节码字节数组的映射
     */
    public static Map compile(String javaName, String javaSrc) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
        try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
            JavaFileObject javaFileObject = manager.makeStringSource(javaName, javaSrc);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
            if (task.call()) {
                return manager.getClassBytes();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


}
复制代码

然后就是自定义类加载器的实现了

/**
 * 自定义动态类加载器
 * @author rongdi
 * @date 2021-01-06
 */
public class DynamicClassLoader extends URLClassLoader {

    Map classBytes = new HashMap();

    public DynamicClassLoader(Map classBytes) {
        super(new URL[0], DynamicClassLoader.class.getClassLoader());
        this.classBytes.putAll(classBytes);
    }

    /**
     * 对外提供的工具方法,加载指定的java源码,得到Class对象
     * @param javaSrc java源码
     * @return
     */
    public static Class load(String javaSrc) throws ClassNotFoundException {
        /**
         * 先试用动态编译工具,编译java源码,得到类的全限定名和class字节码的字节数组信息
         */
        Map bytecode = DynamicCompiler.compile(javaSrc);
        if(bytecode != null) {
            /**
             * 传入动态类加载器
             */
            DynamicClassLoader classLoader = new DynamicClassLoader(bytecode);
            /**
             * 加载得到Class对象
             */
            return classLoader.loadClass(bytecode.keySet().iterator().next());
        } else {
            throw new ClassNotFoundException("can not found class");
        }
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        byte[] buf = classBytes.get(name);
        if (buf == null) {
            return super.findClass(name);
        }
        classBytes.remove(name);
        return defineClass(name, buf, 0, buf.length);
    }

}
复制代码

接下来就是将源码编译、加载、放入spring容器的工具了

package com.rdpaas.core.utils;

import com.rdpaas.core.compiler.DynamicClassLoader;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * 基于spring的应用上下文提供一些工具方法
 * @author rongdi
 * @date 2021-02-06
 */
public class ApplicationUtil {

    /**
     * 注册java源码代表的类到spring容器中
     * @param applicationContext
     * @param src
     */
    public static void register(ApplicationContext applicationContext, String src) throws ClassNotFoundException {
        register(applicationContext, null, src);
    }

    /**
     * 注册java源码代表的类到spring容器中
     * @param applicationContext
     * @param beanName
     * @param src
     */
    public static void register(ApplicationContext applicationContext, String beanName, String src) throws ClassNotFoundException {

        /**
         * 使用动态类加载器载入java源码得到Class对象
         */
        Class clazz = DynamicClassLoader.load(src);

        /**
         * 如果beanName传null,则赋值类的全限定名
         */
        if(beanName == null) {
            beanName = clazz.getName();
        }

        /**
         * 将applicationContext转换为ConfigurableApplicationContext
         */
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        /**
         * 获取bean工厂并转换为DefaultListableBeanFactory
         */
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        /**
         * 万一已经有了这个BeanDefinition了,先remove掉,不然一次容器启动没法多次调用,这里千万别用成
         * defaultListableBeanFactory.destroySingleton()了,BeanDefinition的注册只是放在了beanDefinitionMap中,还没有
         * 放入到singletonObjects这个map中,所以不能用destroySingleton(),这个是没效果的
         */
        if (defaultListableBeanFactory.containsBeanDefinition(beanName)) {
            defaultListableBeanFactory.removeBeanDefinition(beanName);
        }
        /**
         * 使用spring的BeanDefinitionBuilder将Class对象转成BeanDefinition
         */
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        /**
         * 以指定beanName注册上面生成的BeanDefinition
         */
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());

    }

    /**
     * 使用spring上下文拿到指定beanName的对象
     */
    public static  T getBean(ApplicationContext applicationContext, String beanName) {
        return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(beanName);
    }

    /**
     * 使用spring上下文拿到指定类型的对象
     */
    public static  T getBean(ApplicationContext applicationContext, Class clazz) {
        return (T) ((ConfigurableApplicationContext) applicationContext).getBeanFactory().getBean(clazz);
    }

}
复制代码

再给出一些必要的测试类

package com.rdpaas.core.dao;

import org.springframework.stereotype.Component;

/**
 * 模拟一个简单的dao实现
 * @author rongdi
 * @date 2021-01-06
 */
@Component
public class TestDao {

    public String query(String msg) {
        return "msg:"+msg;
    }

}
复制代码
package com.rdpaas.core.service;

import com.rdpaas.core.dao.TestDao;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 模拟一个简单的service抽象类,其实也可以是接口,主要是为了把dao带进去,
 * 所以就搞了个抽象类在这里
 * @author rongdi
 * @date 2021-01-06
 */
public abstract class TestService {

    @Autowired
    protected TestDao dao;

    public abstract String sayHello(String msg);

}
复制代码

最后就是测试的入口类了

package com.rdpaas.core.controller;

import com.rdpaas.core.service.TestService;
import com.rdpaas.core.utils.ApplicationUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 测试入口类
 * @author rongdi
 * @date 2021-01-06
 */
@Controller
public class DemoController implements ApplicationContextAware {

    private static String javaSrc = "package com;" +"public class TestClass extends com.rdpaas.core.service.TestService{" +" public String sayHello(String msg) {" +"   return \"我查到了数据,\"+dao.query(msg);" +" }" +"}";

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 测试接口,实际上就是完成动态编译java源码、加载字节码变成Class,装载Class到spring容器,
     * 获取对象,调用对象的测试
     * @return
     * @throws Exception
     */
    @RequestMapping("/test")
    @ResponseBody
    public String test() throws Exception {
        /**
         * 美滋滋的注册源码到spring容器得到一个对象
         * ApplicationUtil.register(applicationContext, javaSrc);
         */
        ApplicationUtil.register(applicationContext,"testClass", javaSrc);
        /**
         * 从spring上下文中拿到指定beanName的对象
         * 也可以 TestService testService = ApplicationUtil.getBean(applicationContext,TestService.class);
         */
       TestService testService = ApplicationUtil.getBean(applicationContext,"testClass");

        /**
         * 直接调用
         */
        return testService.sayHello("haha");
    }

}
复制代码

  想想应该有点激动了,使用这套代码至少可以实现如下风骚的效果

  1、开放一个动态执行代码的入口,将这个代码内容放在一个post接口里提交过去,然后直接执行返回结果

  2、现在你有一个apaas平台,里面的业务逻辑使用java代码实现,写好保存后,直接放入spring容器,至于执行不执行看你自己业务了

  3、结合上一篇文章的断点调试,你现在已经可以实现在自己平台使用java代码写逻辑,并且支持断点和单步调试你的java代码了

  好了,这次的主题又接近尾声了,如果对我的文章感兴趣或者需要详细源码,请支持一下我的 同名微信公众号,方便大家可以第一时间收到文章更新,同时也让我有更大的动力继续保持强劲的热情,替大家解决一些网上搜索不到的问题,当然如果有啥想让我研究的,也可以文章留言或者公众号发送信息。如果有必要,我会花时间替大家研究研究。

 
复制代码

人人都在谈的图数据库到底是个啥?

$
0
0

摘要: 图数据库,如果是刚接触的人,可能会被其字面意思所误导。其实,图数据库并不是指存储图片、图像的数据库,而是指存储图这种数据结构的数据库。那么图又是什么呢?

本文分享自华为云社区 《图数据库到底是个啥》,原文作者:你好_TT 。

近些年来,在大数据处理过程中有一种被广泛提及和使用的数据库,那就是图数据库。那么图数据库究竟是什么呢?

图数据库,如果是刚接触的人,可能会被其字面意思所误导。其实,图数据库并不是指存储图片、图像的数据库,而是指存储图这种数据结构的数据库。那么图又是什么呢?

什么是图

我们通过下面的例子来认识一下。

东汉末年,孙权、刘备联军曾在赤壁一带以火攻敌船之计大破曹军。

如果我们把各阵营之间的关系抽象一下,以阵营作为点,阵营之间的关系作为边,这样我们就可以用如下的图来形象地表示上述关系:

​以上就是这里所谓的图(的可视化展示)。

我们把这种存储实体和实体之间关系的数据结构,称为图,Graph,图由点和边组成,一个点就是一个实体,比如上述实例中的阵营,两个实体之间的关系则用有方向或无方向的边来表示,比如刘备和孙权之间的联盟关系等。这种通用的结构可以对现实中的各种场景进行建模,从交通运输系统到组织架构管理,从工艺流程设计到社交网络。

什么是图数据库

知道了图的概念,你就可以理解什么是图数据库了。简单来说,图数据库就是用来处理图这种数据结构的工具。

不同于传统的使用二维表格存储数据的关系型数据库,图数据库在传统意义上被归类为 NoSQL(Not Only SQL)数据库的一种,也就是说图数据库属于非关系型数据库。

一般的图数据库至少包含图存储、图查询、图分析这三种功能。

为什么要用图数据库

那我们为什么要用图数据库呢?我们还是用东汉末年的例子来讲解一下图数据库相对于关系型数据库的优势。

假设某关系型数据库中有三张表,分别是东汉末年人物表、东汉末年战役表和东汉末年人物参战表。

当我们想知道“樊城之战的守方是谁”,查询一般会比较快,从表 2 可以直接得到,但当我们想知道“刘备集团发动了哪些战争”的时候,尽管我们也可以从表 2 查到答案,但是我们可能需要遍历整个表 2,查询效率会瞬间降低。而当我们要查询诸如“关羽出战过刘备集团发动的哪些战争”的时候,我们来看一下执行这条查询时关系型数据库是怎么做的:

A. 首先通过东汉末年人物表找到关羽对应的人物 ID

B. 再使用东汉末年人物参战表找到其参战的战役

C. 最后通过东汉末年战役表找到其参战的哪些战役的攻方是刘备集团

我们会发现,这个查询实在是太繁琐了。

而如果我们将以上表格转化为如下的一张关系图谱,那么谁和谁是什么关系就一目了然了。

这么说也许你还没有真正领略到图数据库的巨大威力,我们再来看一个最经典的社交网络中查询性能对比的数据。

在《Neo4j in Action》这本书中,作者做了一个测试:在一个包含 100 万人,每个人约有 50 个朋友的社交网络中找最大深度为 5 的朋友的朋友,得到的实验结果如下:

测试结果表明,深度为 2 时两种数据库的性能差别不大,都很迅速;当深度为 3 时,关系型数据库需要半分钟完成查询,图数据库依旧在 1 秒内搞定;当深度为 4 时,关系型数据库耗费了接近半小时返回结果,图数据库不到 2 秒;而当深度达到 5 以后,关系型数据库就迟迟无法响应了,图数据库却依旧可以「秒杀」,表现出了非常良好的性能。

据此,我们可以从以下几个方面理解为什么要用图数据库:

  • 关系型数据库不擅长处理数据之间的关系,而图数据库在处理数据之间关系方面灵活且高性能

我们不可否认关系型数据库自上世纪 80 年代以来一直都是数据库领域发展的主力,当前,随着社交、物联网、金融、电商等领域的快速发展,由此产生的数据呈现指数级的增长,而传统的关系型数据库在处理复杂关系的数据上表现很差,这是因为关系型数据库是通过外键的约束来实现多表之间的关系引用的。查询实体之间的关系需要 JOIN 操作,而 JOIN 操作通常非常耗时。

而图数据库的原始设计动机就是更好地描述实体之间的关系。图数据库与关系型数据库最大的不同就是免索引邻接。图数据模型中的每个节点都会维护与它相邻的节点关系,这就意味着查询时间与图的整体规模无关,只与每个节点的邻点数量有关,这使得图数据库在处理大量复杂关系时也能保持良好的性能。

另外,图的结构决定了其易于扩展的特性。我们不必在模型设计之初就把所有的细节都考虑到,因为在后续增加新的节点、新的关系、新的属性甚至新的标签都很容易,也不会破坏已有的查询和应用功能。

  • 数据之间的关系越来越重要

当我们在问图数据库为什么如此重要时,其实就是在问,数据之间的关系为何如此重要?正如大家都知道人际关系的价值,其实数据的价值也在于它们之间的关联关系上。

举个例子。最近直播带货非常火,假如某个主播在微博上有几百万的粉丝,这个数据如果不利用起来,价值并不大,但如果他直播带货,把关注他的粉丝和可能来他直播间购物的顾客联系起来时,这些数据立马展现出巨大的商业价值。

  • 使用图的方式表达现实世界中的很多事物更直接,更直观,也更易于理解

自然界中有各种各样的关系,而关系型数据库只能把这些拍扁成表格形态的行列数据,而图数据基于图模型以一种直观的方式去模拟这些关系,因而更形象。

另外,现在大部分的图数据库都提供了可视化的图展示,使得查询和分析变得很直观。

  • 专业的图分析算法为实际场景提供解决方案

图数据库起源于图理论,借助于专业的图分析算法,能够为实际场景提供合适的解决方案。

图数据库如何存储、查询、分析

  • 图存储

图数据库如何存储图,对查询和分析效率至关重要。图数据库使用图模型来操作图数据。所谓的图模型是指图数据库描述和组织图数据的方式。

目前主流的图数据库选择的图模型是属性图。属性图由点、边、标签和属性组成,我们结合一个具体的属性图实例来看一下。

以上属性图可以帮助我们理解一些相关概念:

1) 可以为点设置标签,比如 person, war 等,拥有相同标签的点我们认为它们属于一个分组,是一个集合,这样刘备和曹操属于一个分组;

2) 同样可以为边设置标签,标签可以为 relation 等;

3) 节点可以拥有很多属性,比如 style name、year 等,这些属性值以键值对的形式表示,例如:刘备的 style name 是玄德;

4) 边也可以拥有属性,比如 army 等;

5) 边允许有方向,例如刘备和汉中之战之间的边的方向是由刘备指向汉中之战的;

6) 元数据是用来描述点和边的属性信息的,元数据由若干标签组成,每个标签由若干属性组成。

  • 图查询

如果我们想知道刘备的籍贯在哪,刘备和曹操是什么关系,汉中之战的发动方是谁等等,这些都属于图查询的范畴。

我们知道,SQL 是关系型数据库的查询语言,但是图数据库的查询语言并没有复用 SQL。这是因为本质上图数据库处理的是高维数据,而 SQL 所适用的是二维的数据结构,其并不擅长关系的查询和操作。使用专门的图查询语言比 SQL 更加高效。

目前主流的图查询语言包括 Gremlin 和 Cypher 等。

  • 图分析

图分析是指通过各种图算法来挖掘图信息的一门技术。

核心的图算法可以分成三类:路径搜索类、中心性分析类和社区发现类。

路径搜索是探索图中节点通过边建立的直接或间接的联系。例如在下图中,通过路径搜索,我们发现了这样一条路径:孙策-[夫妻]-大乔-[姐妹]-小乔-[夫妻]-周瑜,据此得知孙策和周瑜是连襟的关系。路径搜索类算法广泛用于物流配送、社交关系分析等场景。

中心性分析是指分析特定节点在图中的重要程度及其影响力。例如在上图中,直观来看,孙权是一个重要的人物,因为与他直接相连的边的数量最多。中心性分析类算法一般用于网页排序、意见领袖挖掘、流感传播等场景。

社区发现意在发现图中联系更紧密的群体结构。如果把更多的三国人物和关系加到上图中,利用 Louvain 等社团挖掘类算法,我们很容易发现这些人物分属三个阵营,如下图所示。

社区发现类算法可用于犯罪团伙挖掘等场景。

图数据库有什么用

介绍完图数据库的主要功能,我们再来看看图数据库都有哪些应用场景。图数据库擅长的应用领域包括:

  • 社交领域:Facebook, Twitter 用它来进行社交关系管理、好友推荐

我们熟悉的好友推荐。就可以采用推荐好友的好友的方法。

徐庶和司马徽向刘备推荐诸葛亮可以通过下图形象地展示

  • 电商领域:华为商城用它来实现商品实时推荐

通过分析目标用户和其他用户的喜好商品,找到相似的其他用户,把这些用户购买过的商品推荐给目标用户。

  • 金融领域:中国工商银行、摩根大通用它来做风控管理

目前来看,金融领域对图数据库的需求很迫切,以贷款为例,在整个贷款周期中,图数据库都能发挥巨大的作用。

  • 安平领域:公安用它来进行嫌疑关系审查、犯罪团伙挖掘

东汉末年,曹操刺杀董卓,貂蝉挑拨董卓父子关系,吕布斩杀董卓,但是董卓却不知道,这些事件幕后主凶之一都有王允,如下图所示。现实中也可能是这样,幕后真凶可能与目标案件没有直接关系,只有间接的关系。

什么样的场景适合用图数据库

你可以根据以下几点来判断你的问题是否需要图数据库:

如果你的问题中频繁出现多对多的关系,建议首选图数据库;如果你的问题中数据之间的关系非常重要,建议首选图数据库;如果你需要处理大规模数据集之间的关系,建议首选图数据库。

图数据库产品

现在图数据库产品已经出现百家争鸣的局面,Neo4j 作为老牌图数据的代表,尽管依然拥趸众多,但是由于其自身的缺陷,挑战者正在增多,而 华为云图引擎图数据库GES作为国产图数据库之光,正在成为其中的佼佼者。

GES 使用界面

点击关注,第一时间了解华为云新鲜技术~

再见 Jenkins !几行脚本搞定自动化部署,这款神器有点厉害!

$
0
0

SpringBoot实战电商项目mall(40k+star)地址: github.com/macrozheng/…

摘要

在开发或生产环境中,我们经常会搞一套自动化部署方案(俗称一键部署)。比较流行的一种就是Gitlab+Jenkins实现方案,不过这种方案占用内存比较大,没有个8G内存,很难流畅运行,而且部署起来也不快。最近发现一款神器Drone,轻量级CI/DI工具,结合Gogs使用内存占用不到1G,几行脚本就能实现自动化部署,推荐给大家!

Drone简介

Drone是一款基于容器技术的持续集成工具,使用简单的YAML配置文件即可完成复杂的自动化构建、测试、部署任务,在Github上已经有22K+Star。

Gogs安装

我们将使用轻量级的Gogs来搭建Git仓库,这里只是简单说下安装步骤,具体使用可以参考 《Github标星34K+Star,这款开源项目助你秒建Git服务!》

  • 首先需要下载Gogs的Docker镜像;
docker pull gogs/gogs
复制代码
  • 下载完成后在Docker容器中运行Gogs;
docker run -p 10022:22 -p 10080:3000 --name=gogs \
-e TZ="Asia/Shanghai" \
-v /mydata/gogs:/data  \
-d gogs/gogs
复制代码

Drone安装

接下来我们安装下Drone,不愧是基于容器的CI/DI工具,使用Docker安装很方便!

  • 首先下载Drone的Server和Runner的镜像;
# Drone的Server
docker pull drone/drone:1
# Drone的Runner
docker pull drone-runner-docker:1
复制代码
  • 这里有个Server和Runner的概念,我们先来理解下;
    • Server:为Drone的管理提供了Web页面,用于管理从Git上获取的仓库中的流水线任务。
    • Runner:一个单独的守护进程,会轮询Server,获取需要执行的流水线任务,之后执行。
  • 接下来我们来安装 drone-server,使用如下命令即可;
docker run \
  -v /mydata/drone:/data \
  -e DRONE_AGENTS_ENABLED=true \
  -e DRONE_GOGS_SERVER=http://192.168.5.78:10080 \
  -e DRONE_RPC_SECRET=dronerpc666 \
  -e DRONE_SERVER_HOST=192.168.5.78:3080 \
  -e DRONE_SERVER_PROTO=http \
  -e DRONE_USER_CREATE=username:macro,admin:true \
  -e TZ="Asia/Shanghai" \
  -p 3080:80 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone:1
复制代码
  • 这里的配置参数比较多,下面统一解释下;

    • DRONE_GOGS_SERVER:用于配置Gogs服务地址。
    • DRONE_RPC_SECRET:Drone的共享秘钥,用于验证连接到server的rpc连接,server和runner需要提供同样的秘钥。
    • DRONE_SERVER_HOST:用于配置Drone server外部可访问的地址。
    • DRONE_SERVER_PROTO:用于配置Drone server外部可访问的协议,必须是http或https。
    • DRONE_USER_CREATE:创建一个管理员账号,该账号需要在Gogs中注册好。
  • 接下来安装 drone-runner-docker,当有需要执行的任务时,会启动临时的容器来执行流水线任务;

docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e DRONE_RPC_PROTO=http \
  -e DRONE_RPC_HOST=192.168.5.78:3080 \
  -e DRONE_RPC_SECRET=dronerpc666 \
  -e DRONE_RUNNER_CAPACITY=2 \
  -e DRONE_RUNNER_NAME=runner-docker \
  -e TZ="Asia/Shanghai" \
  -p 3000:3000 \
  --restart always \
  --name runner-docker \
  drone/drone-runner-docker:1
复制代码
  • 这里的配置参数比较多,下面统一解释下。
    • DRONE_RPC_PROTO:用于配置连接到Drone server的协议,必须是http或https。
    • DRONE_RPC_HOST:用于配置Drone server的访问地址,runner会连接到server获取流水线任务并执行。
    • DRONE_RPC_SECRET:用于配置连接到Drone server的共享秘钥。
    • DRONE_RUNNER_CAPACITY:限制runner并发执行的流水线任务数量。
    • DRONE_RUNNER_NAME:自定义runner的名称。

Drone使用

  • 让我们来访问下Drone的控制台页面,第一次登录需要输入账号密码(在Gogs中注册的账号),访问地址: http://192.168.5.78:3080/

  • 此时我们在Gogs中的项目会现在在列表中,如果没有的话可以点下 SYNC按钮;

  • 接下来我们需要对仓库进行设置,将仓库设置为 Trusted(否则Drone创建的容器无法挂载目录到宿主机),最后点击 SAVE按钮保存;

  • 保存成功后会在Gogs中自动配置一个Web钩子,当我们推送代码到Gogs中去时,会触发这个钩子,然后执行在Drone中的流水线任务;

  • 拉到最下面,我们可以发送一个测试推送,推送成功会显示绿色的√;

  • 此时我们在Drone中发现其实流水线执行失败了,那是因为我们在脚本中引用了Secret中的 ssh_password

  • 在仓库的设置中添加一个Secret即可,Secret是专门用来存储密码的,此密码只能被使用或删除,无法被查看;

  • ACTIVITY FEED中使用 RESTART可以重新执行该流水线,发现已经成功执行。

编写脚本

当我们向Git仓库Push代码时,会自动触发Web钩子,然后Drone就会从Git仓库Clone代码,再通过项目目录下的 .drone.yml配置,执行相应的流水线,接下来我们来看看这个脚本是如何写的。

  • 首先我们来了解下在 .drone.yml中配置的工作流都有哪些操作,看下流程图就知道了;

  • 再来一个完整的 .drone.yml,配上详细的注解,看下就基本懂了!
kind: pipeline # 定义对象类型,还有secret和signature两种类型
type: docker # 定义流水线类型,还有kubernetes、exec、ssh等类型
name: mall-tiny-drone # 定义流水线名称

steps: # 定义流水线执行步骤,这些步骤将顺序执行
  - name: package # 流水线名称
    image: maven:3-jdk-8 # 定义创建容器的Docker镜像
    volumes: # 将容器内目录挂载到宿主机,仓库需要开启Trusted设置
      - name: maven-cache
        path: /root/.m2 # 将maven下载依赖的目录挂载出来,防止重复下载
      - name: maven-build
        path: /app/build # 将应用打包好的Jar和执行脚本挂载出来
    commands: # 定义在Docker容器中执行的shell命令
      - mvn clean package # 应用打包命令
      - cp target/mall-tiny-drone-1.0-SNAPSHOT.jar /app/build/mall-tiny-drone-1.0-SNAPSHOT.jar
      - cp Dockerfile /app/build/Dockerfile
      - cp run.sh /app/build/run.sh

  - name: build-start
    image: appleboy/drone-ssh # SSH工具镜像
    settings:
      host: 192.168.5.78 # 远程连接地址
      username: root # 远程连接账号
      password:
        from_secret: ssh_password # 从Secret中读取SSH密码
      port: 22 # 远程连接端口
      command_timeout: 5m # 远程执行命令超时时间
      script:
        - cd /mydata/maven/build # 进入宿主机构建目录
        - chmod +x run.sh # 更改为可执行脚本
        - ./run.sh # 运行脚本打包应用镜像并运行

volumes: # 定义流水线挂载目录,用于共享数据
  - name: maven-build
    host:
      path: /mydata/maven/build # 从宿主机中挂载的目录
  - name: maven-cache
    host:
      path: /mydata/maven/cache
复制代码

总结

对比Jenkins复杂的图形化界面操作,Drone使用脚本来定义流水线任务无疑更简单、更直观。Drone更加轻量级,内存占用少且响应速度快!自动化部署要啥Jenkins?直接给Git整个CI/DI功能难道不香么?

参考资料

项目源码地址

github.com/macrozheng/…

本文 GitHub github.com/macrozheng/…已经收录,欢迎大家Star!


Jessibuca 2.0 发布,H5 直播流播放器

$
0
0

2.0更新内容:

  • 同时支持H264和H265解码,无需重新加载解码器
  • 音频支持AAC、PCMA、PCMU格式(也可以通过编译FFmpeg来支持更多格式)
  • 代码大幅度精简,删去无用代码,C++代码减少了80%,网络通讯和协议解包部分移入js端实现,方便二次开发
  • 实现OffscreenCanvas性能优化,CPU和内存消耗显著减少
  • 音频解码实现了连续播放,解决了原来分段播放带来的瑕疵
  • 采用最新的emscripten(2.0.20)和ffmpeg(4.4)版本编译,实现极限wasm压缩体积(1.2m)
  • clone项目后运行vuepress dev . (提前安装好vuepress,注意命令后面有个点)即可看到效果

软件介绍:

Jessibuca是一款开源的纯H5直播流播放器,通过Emscripten将音视频解码库编译成Js(ams.js/wasm)运行于浏览器之中。兼容几乎所有浏览器,可以运行在PC、手机、微信中,无需额外安装插件。

  • 支持解码H.264视频(Baseline, Main, High Profile全支持,支持解码B帧视频)
  • 支持解码H.265视频(flv id == 12)
  • 支持解码AAC音频(LC,HE,HEv2 Profile全支持)
  • 支持解码PCMA音频以及PCMU音频格式
  • 可设置播放缓冲区时长,可设置0缓冲极限低延迟(网络抖动会造成卡顿现象)
  • 支持智能不花屏丢帧,长时间播放绝不累积延迟。
  • 可创建多个播放实例
  • 程序精简,经CDN加速,GZIP压缩(实际下载500k),加载速度更快
  • 同时支持http-flv和websocket-flv协议以及websocket-raw私有协议(裸数据,传输量更小,需要搭配Monibuca服务器) 注:以http-flv请求时,存在跨域请求的问题,需要设置access-control-allow-origin, websocket-flv默认不存在此问题
  • 支持HTTPS/WSS加密视频传输,保证视频内容传输安全
  • 手机浏览器内打开视频不会变成全屏播放

建设 Kubernetes 生产环境的 16条建议

$
0
0

Kubernetes是用于构建高度可扩展系统的强大工具。结果,许多公司已经开始或正在计划使用它来协调生产服务。不幸的是,像大多数强大的技术一样, Kubernetes也很复杂。我们整理了以下清单,以帮助你生产环境最佳实践Kubernetes( Kubernetes生产环境最佳实践)。

容器最佳实践

Kubernetes提供了一种编排容器化服务的方法,因此,如果您没有按顺序实践你的容器,那么集群一开始就不会处于良好状态。请按照以下提示开始。

1、使用精简基础镜像

what:容器是内置在系统镜像中的应用程序堆栈。从业务逻辑到内核的所有内容都打包在一起。最小的镜像会占用尽可能多的OS,并迫使您显式添加所需的任何组件。

why:仅在您的容器中包括要使用的软件,同时具有性能和安全性方面的好处。磁盘上的字节数更少,复制镜像的网络流量更少,并且潜在的攻击者无法访问的工具也更少。

how:Alpine Linux是一个流行的选择,并具有广泛的支持。

2、使用提供最佳正常运行时间的注册表

what:注册表是镜像的存储库,使这些镜像可供下载和启动。在指定部署配置时,您需要指定从何处获取路径为 <registry> / <remote name>:<tag>的镜像:

图片

why:您的集群需要镜像去运行。

how:大多数云提供商都提供私有镜像注册表服务:Google提供Google容器注册表,AWS提供Amazon ECR,Microsoft提供Azure容器注册表。

仔细调研,并选择提供最佳正常运行时间的私人注册表。由于您的群集将依靠您的注册表来启动软件的较新版本,因此任何停机时间都将阻止对正在运行的服务进行更新。

3、使用ImagePullSecrets对您的注册表进行身份验证

what:ImagePullSecrets是 Kubernetes对象,可让您的群集通过注册表进行身份验证,因此注册表可以选择谁可以下载镜像。

why:如果您的注册表足够公开,可以让集群从中提取镜像,则表明注册表足够公开,需要身份验证。

how: Kubernetes网站在配置ImagePullSecrets方面有很好的演练,该示例使用Docker作为示例注册表。

管理你的集群

微服务本质上是一团糟。使用微服务的许多好处来自在服务级别上强制职责分离,有效地为后端的各个组件创建了抽象。一些很好的例子是运行与业务逻辑分离的数据库,运行软件的单独开发和生产版本,或分离出水平可伸缩的流程。

具有不同服务执行不同职责的阴暗面是,它们不能被平等对待。值得庆幸的是, Kubernetes为您提供了许多解决此问题的工具。

4、使用命名空间隔离环境

what:命名空间是 Kubernetes中最基本,最强大的分组机制。它们几乎像虚拟集群一样工作。默认情况下,Kubernetes中的大多数对象仅限于一次影响单个名称空间。

why:大多数对象都是在命名空间范围内定义的,因此您必须使用命名空间。鉴于它们提供了强大的隔离性,因此它们非常适合隔离具有不同目的的环境,例如用户服务的生产环境和严格用于测试的环境,或者分离支持单个应用程序的不同服务堆栈,例如保持安全解决方案的工作负载与您自己的应用程序分开。一个好的经验法则是按资源分配划分名称空间:如果两组微服务将需要不同的资源池,请将它们放在单独的名称空间中。

how:它是大多数对象类型的元数据的一部分:

图片

请注意,您应该始终创建自己的名称空间,而不要依赖“默认”名称空间。Kubernetes的默认设置通常会为开发人员优化以最小的摩擦,这通常意味着甚至放弃最基本的安全措施。

5、通过Labels 管理您的集群

what:Labels是组织 集群的最基本且可扩展的方法。它们允许您创建用于分隔Kubernetes对象的任意key:value对。例如,您可以创建一个标签密钥,将处理敏感信息的服务与不处理敏感信息的服务区分开。

why:如前所述,Kubernetes使用标签进行组织,但更具体地说,它们用于选择。这意味着,当您想给Kubernetes对象引用某个命名空间中的一组对象时(例如告诉网络策略允许哪些服务相互通信),请使用它们的标签。由于它们代表了这种开放式组织类型,因此请尽最大努力使事情简单化,并且仅在需要选择权的地方创建标签。

how:标签是一个简单的规范字段,您可以将其添加到YAML文件中:

图片

6、使用注释来跟踪重要的系统更改等

what:注释是可以附加到pod的任意键值元数据,就像标签一样。但是,Kubernetes不会读取或处理批注,因此围绕您可以和不能使用批注进行注释的规则相当宽松,并且不能用于选择。

why:它们可帮助您跟踪容器化应用程序的某些重要功能,例如版本号或首次启动的日期和时间。仅在Kubernetes的上下文中,注释是一种无能为力的构造,但是当用于跟踪重要的系统更改时,注释可以成为开发人员和运营团队的资产。

how:注释是类似于标签的规格字段。

图片

让你的集群更加安全

好了,您已经建立了集群并按所需方式组织了-现在呢?好吧,接下来是要确保一些安全。您可能会花费一生的时间来学习,但仍未发现有人可以侵入您系统的所有方式。博客文章的内容空间要比一生少得多,因此您必须满足一些强烈的建议。

7、使用RBAC实施访问控制

what:RBAC(基于角色的访问控制)使您可以控制谁可以查看或修改群集的不同方面。

why:如果要遵循最小特权原则,则需要设置RBAC来限制群集用户和部署能够执行的操作。

how:如果要设置自己的集群(即不使用托管的Kube服务),请确保使用''--authorization-mode = Node,RBAC“启动您的kube apiserver。如果使用托管的Kubernetes例如,您可以通过查询用于启动kube apiserver的命令来检查它是否设置为使用RBAC。唯一通用的检查方法是在kubectl cluster-info dump的输出中查找“ --authorization-mode ...”。

RBAC打开后,您需要更改默认权限以适合您的需求。Kubernetes项目站点在此处提供了有关设置角色和RoleBindings的演练。托管的Kubernetes服务需要启用RBAC的自定义步骤-请参阅Google的GKE指南或Amazon的AKS指南。

8、使用Pod安全策略防止危险行为

what:Pod安全策略是一种资源,非常类似于Deployment或Role,可以通过kubectl以相同的方式创建和更新。每个都有一个标志集合,可用来防止集群中特定的不安全行为。

why:如果创建Kubernetes的人认为限制这些行为足够重要,可以创建一个特殊的对象来处理它,那么它们很重要。

how:让他们工作可能会令人沮丧。我建议启动并运行RBAC,然后在此处查看Kubernetes项目的指南。在我看来,最重要的使用是防止特权容器和对主机文件系统的写访问,因为它们代表了容器抽象中一些较泄漏的部分。

9、使用网络策略实施网络控制/防火墙

what:网络策略是允许您明确声明允许哪些流量的对象,而Kubernetes将阻止所有其他不符合标准的流量。

why:限制群集中的网络流量是一项基本且重要的安全措施。默认情况下,Kubernetes启用所有服务之间的开放式通信。保留此“默认开放”配置意味着与Internet连接的服务与存储敏感信息的数据库仅一步之遥。

how:有一篇文章写的很好,具体详情查看这里。

10、使用Secrets来存储和管理必要的敏感信息

what:Secrets是您如何在Kubernetes中存储敏感数据,包括密码,证书和令牌。

why:无论您是实施TLS还是限制访问,您的服务都可能需要相互认证,与其他第三方服务或您的用户进行认证。

how:Kubernetes项目在此处提供了指南。一个关键建议:避免将机密作为环境变量加载,因为在您的环境中拥有机密数据通常是不安全的。相反,将机密装入容器中的只读卷中-您可以在本 Use Secrets中找到一个示例。

11、使用镜像扫描识别和修复镜像漏洞

what:扫描仪检查镜像中安装的组件。从操作系统到应用程序堆栈的所有内容。扫描程序对于找出镜像所包含的软件版本中存在哪些漏洞非常有用。

why:漏洞一直在流行的开源软件包中发现。一些著名的例子是Heartbleed和Shellshock。您将想知道这些漏洞在系统中的什么位置,以便您知道哪些镜像可能需要更新。

how:扫描仪是基础设施中相当常见的部分-大多数云提供商都提供了产品。如果您想自己托管一些东西,那么开源Clair项目是一个受欢迎的选择。

保持集群稳定

Kubernetes代表很高的技术栈。您拥有在嵌入式内核上运行的应用程序,在VM中运行的应用程序(在某些情况下甚至在裸机上),以及Kubernetes自己的服务共享硬件。考虑到所有这些因素,在物理和虚拟领域中很多事情都会出错,因此尽可能降低开发周期的风险非常重要。Kubernetes周围的生态系统已经开发了一系列最佳实践,以使事情尽可能保持一致。

12、遵循CI / CD方法

what:持续集成/持续部署是一种过程哲学。相信对代码库进行的每次修改都应增加增量值,并准备投入生产。因此,如果代码库中的某些内容发生了更改,则可能要启动服务的新版本,以运行测试。

why:遵循CI / CD可以帮助您的工程团队在日常工作中牢记质量。如果出现问题,修复问题将成为整个团队的当务之急,因为此后依赖于已分解的提交的所有更改也将被分解。

how:由于云部署软件的兴起,CI / CD越来越流行。因此,您可以从托管或自托管的众多出色产品中进行选择。如果您的团队比较小,我建议您采用托管路线,因为节省的时间和精力绝对值得您付出额外的费用。

13、使用Canary方法进行更新

what:Canary是一种将服务更改从代码库中的提交带给用户的方法。您启动了一个运行最新版本的新实例,然后将用户缓慢迁移到新实例,从而逐渐获得了对更新的信心,而不是一次全部交换。

why:无论您的单元测试和集成测试有多广泛,它们都无法完全模拟生产中的运行-总是有可能某些功能无法按预期运行。使用金丝雀可以限制用户接触这些问题。

how:Kubernetes的可扩展性提供了许多途径来逐步推出服务更新。最直接的方法是创建一个单独的部署,与当前正在运行的实例共享一个负载平衡器。这个想法是您扩展新的部署,同时缩减旧的部署,直到所有正在运行的实例都是新版本。

14、实施监控并将其与SIEM集成

what:监视意味着跟踪和记录您的服务正在做什么。

why:让我们面对现实吧-不管您的开发人员多么出色,无论您的安全专家如何努力地发挥他们的聪明才智,事情都会出错。当他们这样做时,您将想知道发生了什么,以确保您不会两次犯相同的错误。

how:成功监视服务有两个步骤-需要对代码进行检测,并且需要将该检测的输出馈送到某个地方以进行存储,检索和分析。执行检测的方式在很大程度上取决于您的工具链,但是快速的网络搜索应该可以让您有所作为。就存储输出而言,除非您有专门知识或需求,否则我建议使用托管SIEM(例如Splunk或Sumo Logic)-根据我的经验,DIY始终是与任何存储相关的期望时间和精力的10倍。

深度建议

一旦集群达到一定规模后,您将发现手动执行所有最佳做法将变得不再可行,结果将给系统的安全性和稳定性带来挑战。超过此阈值后,请考虑以下主题:

15、使用服务网格管理服务间通信

what:服务网格是管理服务间通信的一种方法,可以有效地创建在实施服务时使用的虚拟网络。

why:使用服务网格可以减轻管理群集的一些较繁琐的方面,例如确保对通信进行正确的加密。

how:根据您对服务网格的选择,启动和运行的复杂性可能千差万别。作为最常用的服务网格,Istio似乎正在蓬勃发展,并且您的配置过程将在很大程度上取决于您的工作负载。

一个警告:如果您需要采用一个服务网格,请尽早采用它而不是稍后采用它-逐渐改变集群中的通信样式可能会非常痛苦。

16、使用准入控制器解锁Kubernetes中的高级功能

what:准入控制器是一种很好的万能工具,可用于管理集群中发生的一切。它们允许您设置Kubernetes在启动时将参考的Webhook。它们有两种形式:变异和验证。突变准入控制器会在部署启动之前更改其配置。验证准入控制器会与您的webhook一致,以允许启动给定的部署。

why:它们的用例广泛且数量众多–它们提供了一种通过自行开发的逻辑和限制来迭代地提高集群稳定性的好方法。

how:查看有关如何开始使用Admission Controllers的指南。

来源:开源Linux

电力市场常见术语解读 - 知乎

$
0
0

电力现货市场的一些常见术语应该如何理解?

接下来由本小仙带您一起学习吧!

『部分图片来源于网络,侵删,图片与文字内容无必然联系』

1. 市场成员

在我国电力市场成员主要包括市场主体、电网公司和市场运营机构。其中市场主体也就是电力交易的主体,主要包含各类发电企业、电力用户、售电公司、辅助服务供应商等。目前的售电公司有两类,一类是没有配电网经营权的独立售电公司,另一类是拥有配电网经营权的售电公司,电网公司已经全面退出售电公司业务。市场运营机构指的就是电力交易中心和电力调度中心,在美国成熟的电力市场中,二者是合二为一的,称为ISO(ISO,Independent System Operator)或者RTO(RTO,Regional Transmission Organization)。美国加州曾经有过把能量现货的交易中心和调度中心分设的失败的教训,而现阶段美国成熟电力市场中交易中心和调度中心一体化避免了机构的重叠,促进了市场交易和电网运行的信息沟通,实现了电网的可靠运行。 因此,交易和调度是否要合二为一也将是未来电力现货市场建设需要面临的一大考验。

2. 电力批发交易

电力批发交易是指发电企业、售电公司、电力大用户之间通过市场化方式进行的电力交易活动的总称。现阶段,是指发电企业、售电公司、电力大用户等市场主体通过市场化方式开展的中长期电能量交易、现货(日前、实时)电能量交易以及辅助服务交易。

3. 电力零售交易

电力零售交易是指售电公司与电力用户之间开展的电能量交易活动的总称。

4. 中长期电能量交易

中长期电能量交易是指符合准入条件的发电企业、售电公司、电力用户等市场主体,通过双边协商、集中竞价、挂牌等市场化方式,开展的多年、年、月、月内等执行日前(不含)以上的 多日电力(电量)交易。

5. 现货电能量交易

现货电能量交易是指符合准入条件的发电企业、售电公司、电力用户等市场主体,通过集中竞价方式、按节点边际价格出清的市场化交易方式,开展的日前和日内的现货电力(电量)交易。注意,现阶段现货市场开展较好的省份,也还是发电侧单边竞价的模式,暂时没有发电侧、用电侧双边竞价的条件。

6. 电力用户

目前我国现货市场中,电力用户分为市场用户和非市场用户,市场用户是指参与电力市场化交易的电力用户;根据用电规模,市场用户又可以分为电力大用户和一般用户,各省关于电力大用户的用电量标准各有定义;电力大用户既可以直接参与批发市场交易,也可以在零售市场向售电公司购电,一般用户只能选择向售电公司购电。

7. 交易限价

由于各种因素的影响,包括政策因素、市场力因素等,经上级部门批准,我国电力现货市场中,普遍都有申报价格上限和申报价格下限的设置,而欧美成熟电力现货市场往往是没有价格限制的(有也是那种很离谱的价格,几乎可以等同于没有限制)。

8. 非市场电量

非市场电量指政府定价的优先发电电量和基准价电量,由政府部门按年度下达,发电企业、电网企业签订厂网间年度购售电合同。我们也常把非市场电量称之为“计划电”,计划电与市场电夹杂的情况下,就会给现货市场带来不平衡资金的问题,这也是现阶段现货市场建设的重难点问题。以山东为例,山东于2020年5月16日~5月19日开展了为期四天的连续结算试运行,期间产生不平衡费用9508.19万元。

9. 不平衡资金

电力现货市场运行过程中,不平衡费用是无法明确具体承担主体,但在现阶段现货市场结算试运行时又不可避免的,且需要向全体市场主体或部分市场主体分摊或者返还的资金款项,其分摊返还的主要原则是“谁引起、谁承担;谁受益、谁分摊”。不平衡费用种类科目各省大致相同,主要有发电成本补偿类、阻塞费用类、偏差考核类、计划与市场双轨运行偏差类、辅助服务类、其他费用分摊类等。

10. 竞价空间

目前我国现货市场试点省份基本在日前市场都采用全电量优化、全时空配置的组织方式,并以次日全部省内用电需求预测和中长期外送交易结果作为竞价优化空间。

11. 出清方式

电力调度机构通过技术支持系统,基于市场主体申报信息及运行日的电网运行边界条件,以全网发电成本最小化为目标,采用安全约束机组组合(Security-Constrained Unit Commitment, SCUC)程序、安全约束经济调度(Security-Constrained Economic Dispatch, SCED)程序进行出清。现阶段,电力大用户(售电公司)参与现货交易时,采取“报量不报价”的方式,即所谓的发电侧报价、用户侧不报价的单边报价模式,用户申报的用电需求曲线作为自身参与日前电能量市场结算依据,不作为日前电能量市场出清的边界条件。随着现货市场不断发展和用户侧参与程度的提高,逐步实现用户侧以报量报价的方式参与现货交易。

12. 节点边际电价(Locational Marginal Price, LMP)

指在满足当前输电网络设备约束条件和各类其它资源的工作特点的情况下,在某一节点增加单位负荷需求时所需要增加的边际成本,简称节点电价。目前,节点电价由系统电能价格与阻塞价格两部分构成。

13. 交易时间

运行日(D)为执行日前电能量市场交易计划的自然日,每15分钟为一个交易出清时段,每个运行日含有96个交易出清时段。竞价日为运行日前一日(D-1),竞价日内由发电企业进行交易申报,并通过日前电能量市场出清形成运行日的交易结果。我国电力现货市场处于起步阶段,因此现货出清时段为15分钟,而欧美成熟现货市场的出清时段都是5分钟,这也是电力现货市场改革的目标和未来建设的方向。

14. 日前市场出清

电力调度机构将次日系统负荷预测曲线、联络线外送计划、各机组报价、机组运行参数、线路运行参数等作为输入信息,以全网发电成本最小化为目标,考虑备用需求、断面极限等电网运行约束,以及最大最小出力、爬坡限制等机组运行约束,经安全约束机组组合(SCUC)、安全约束经济调度(SCED)程序计算,形成日前开机组合、各机组日前96点(现阶段)发电计划曲线。

15. 辅助服务

辅助服务分为基本辅助服务和有偿辅助服务。基本辅助服务指为保证电力系统安全、稳定运行和电能质量需要,根据并网调度协议规定的技术性能要求必须无偿提供的辅助服务,包括发电机组一次调频、基本无功调节等。有偿辅助服务指基本辅助服务之外提供的其他辅助服务,主要包括二次调频(自动发电控制AGC)、有偿调峰、备用、有偿无功调节、黑启动、需求侧响应等。

值得注意的是,在欧美国家电力市场中,调峰都是不作为辅助服务的,因为系统的峰谷负荷是可以精确预测的,调峰问题可以由日前能量市场(如果有日前能量市场)或运行方式部门(如果没有日前能量市场)做出的日计划解决。而我国由于计划体制、政策、市场改革程度等问题,则需要调峰服务,或许随着电力现货市场的推进建设,以后我们也不再把调峰作为辅助服务。

关于辅助服务产品的更细节介绍,可参见另一篇文章。

https://zhuanlan.zhihu.com/p/340285225​ zhuanlan.zhihu.com 图标

有道精品课实时数据中台建设实践

$
0
0

撰文/ 李荣谦

编辑/ Ryan

来源:有道技术团队(ID: youdaotech)

0 序言

本期文章中,有道精品课技术团队将和大家分享有道精品课 数据中台的架构演进过程以及 Doris 作为一个 MPP 分析型数据库是如何为不断增长的业务体量提供有效支撑并进行数据赋能的。

本文以我们在实时数仓选型的经验为切入点,进一步着重分享使用 Doris 过程中遇到的问题,以及我们针对这些问题所做出的调整和优化。

1 背景概述

1.1 业务场景

根据业务需求,目前有道精品课的数据层架构上可分为 离线实时两部分。

离线系统主要处理埋点相关数据,采用批处理的方式定时计算。而实时流数据主要来源于各个业务系统实时产生的数据流以及数据库的变更日志,需要考虑数据的准确性、实时性和时序特征,处理过程非常复杂。

有道精品课数据中台团队依托于其实时计算能力在整个数据架构中主要承担了实时数据处理的角色,同时为下游离线数仓提供实时数据同步服务。

数据中台主要服务的 用户角色和对应的 数据需求如下:

  1. 运营/策略/负责人主要查看学生的整体情况,查询数据中台的一些课程维度实时聚合数据;
  2. 辅导/销售主要关注所服务学生的各种实时明细数据;
  3. 品控主要查看课程/老师/辅导各维度整体数据,通过T+1的离线报表进行查看;
  4. 数据分析师对数据中台 T+1 同步到离线数仓的数据进行交互式分析;

1.2 数据中台前期系统架构及业务痛点

如上图所示,在数据中台1.0架构中我们的实时数据存储主要依托于 Elasticsearch, 遇到了以下几个问题:

  1. 聚合查询效率不高
  2. 数据压缩空间低
  3. 不支持多索引的 join,在业务设计上我们只能设置很多大宽表来解决问题
  4. 不支持标准 SQL,查询成本较高

2、实时数仓选型

基于上面的业务痛点,我们开始对实时数仓进行调研,调研了 Doris、ClickHouse、TiDB+TiFlash、Druid、Kylin,考虑到查询性能、社区发展、运维成本等多种因素,我们最后 选择 Doris 作为我们的实时数仓。

3、基于Apache Doris的数据中台2.0

3.1 架构升级

在完成了实时数仓的选型后,我们针对 Doris 做了一些 架构上的改变,以发挥它最大的作用,主要分为以下几个方面:

>>>>Flink双写

将所有 Flink Job 改写,在写入Elasticsearch的时候旁路输出一份数据到 Kafka,并对复杂嵌套数据创建下游任务进行转化发送到 Kafka,Doris 使用 Routine Load 导入数据。

>>>>Doris On Es

由于之前我们的实时数仓只有 Es,所以在使用 Doris 的初期,我们选择了通过 Doris 创建 Es 外表的方式来完善我们的 Doris 数仓底表,同时也降低了查询成本,业务方可以无感知的使用数仓底表。

>>>>数据同步

原来我们使用 Es 的时候,由于很多表没有数据写入时间,数据分析师需要每天扫全表导出全量数据到 Hive,这对我们的集群有很大压力,并且也会导致数据延迟上升,我们在引入了 Doris 后,对所有数仓表都添加 eventStamp, updateStamp, deleted这三个字段。

  • eventStamp:事件发生时间
  • updateStamp:Doris数据更新时间,在Routine Load中生成
  • deleted:数据是否删除,由于我们很多实时数仓需要定时同步到离线数仓,所以数据需要采取软删除的模式。

数据同步我们采用了多种方式,通过 hive 表名后缀来决定不同同步场景:

  • _f:每天/每小时全量同步,基于 Doris Export 全量导出
  • _i:每天/每小时增量同步,基 于Doris Export 按分区导出/网易易数扫表导出
  • _d:每天镜像同步,基于 Doris Export 全量导出

>>>>指标域划分/数据分层

将 Elasticsearch 中的数据进行整理并结合后续的业务场景,我们划分出了如下 四个指标域:

根据上面的指标域,我们基于星型模型开始构建实时数仓,在 Doris 中构建了20余张数仓底表以及10余张维表, 通过网易易数构建了完整的指标系统。

>>>>定时生成 DWS/ADS 层

基于 Doris insert into select 的导入方式,我们实现了一套定时根据 DWD 层数据生成 DWS/ADS 层数据的逻辑,延迟最低可以支持到分钟级。

>>>数据血缘

我们基于 Routine Load 和 Flink 实现了数据中台完善的数据血缘,供数据开发/数据分析师进行查询。

3.2 数据中台2.0架构

基于围绕 Doris 的系统架构调整,我们完成了 数据中台2.0架构:

  • 使用网易易数数据运河替换 Canal,拥有了更完善的数据订阅监控
  • Flink计算层引入 Redis/Tidb 来做临时/持久化缓存
  • 复杂业务逻辑拆分至 Grpc 服务,减轻Flink中的业务逻辑
  • 数据适配层新增 Restful 服务,实现一些 case by case 的复杂指标获取需求
  • 通过网易易数离线调度跑通了实时到离线的数据同步
  • 新增了数据报表/自助分析系统两个数据出口

数据中台2.0架构的数据流转如下图所示:

我们对数据中台整体架构进行梳理, 整体结构如下图所示:

4、Doris带来的收益

1. 数据导入方式简单,我们针对不同业务场景使用了三种导入方式:

  • Routine Load:实时异步数据导入
  • Broker Load:定时同步离线数仓数据,用于查询加速
  • Insert into:定时通过 DWD 层数仓表生成 DWS/ADS 层数仓表

2. 数据占用空间降低,由原来Es中的1T左右降低到了200G左右。

3. 数仓使用成本降低:

Doris 支持 Mysql 协议,数据分析师可以直接进行自助取数,一些临时分析需求不需要再将 Elasticsearch 数据同步到 Hive 供分析师进行查询。

一些在 Es 中的明细表我们通过 Doris 外表的方式暴露查询,大大降低了业务方的查询成本。

同时,因为 Doris 支持 Join,原来一些需要查询多个 Index 再从内存中计算的逻辑可以直接下推到 Doris 中,提升了查询服务的稳定性,加快了响应时间。

聚合计算速度通过物化视图和列存优势获得了较大提升。

5、上线表现

目前已经上线了数十个实时数据报表,在线集群的 P99 稳定在 1s 左右。同时也上线了一些长耗时分析型查询,离线集群的 P99 稳定在 1min 左右。

同时,也形成了一套完善的开发体系使数据需求的日常迭代更加迅速。

6、总结规划

Doris 的引入推进了有道精品课数据分层的构建,加速了实时数仓的规范化进程,数据中台团队在此基础上一方面向全平台各业务线提供统一的数据接口,并依托于 Doris 生产实时数据看板,另一方面定时将实时数仓数据同步至下游离线数仓供分析师进行自助分析,为实时和离线场景提供数据支撑。

对于后续工作的开展, 我们做了如下规划:

  • 基于Doris明细表生成更多的上层聚合表,降低Doris计算压力,提高查询服务的整体响应时间。
  • 基于Flink实现Doris Connector,实现Flink对Doris的读写功能
  • 开发Doris On Es支持嵌套数据的查询。

最后,感谢各业务方对数据中台的支持,目前数据中台还在迅速发展中,欢迎志同道合的朋友加入我们。

美团二面:Redis与MySQL双写一致性如何保证?

$
0
0

前言

四月份的时候,有位朋友去美团面试,他说被问到Redis与MySQL双写一致性如何保证? 这道题其实就是在问缓存和数据库在双写场景下,一致性是如何保证的?本文将跟大家一起来探讨如何回答这个问题。

谈谈一致性

一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型

三个经典的缓存模式

缓存可以提升性能、缓解数据库压力,但是使用缓存也会导致数据 不一致性的问题。一般我们是如何使用缓存呢?有三种经典的缓存模式:

  • Cache-Aside Pattern
  • Read-Through/Write through
  • Write behind

Cache-Aside Pattern

Cache-Aside Pattern,即 旁路缓存模式,它的提出是为了尽可能地解决缓存与数据库的数据不一致问题。

Cache-Aside读流程

Cache-Aside Pattern的读请求流程如下:

Cache-Aside读请求

  1. 读的时候,先读缓存,缓存命中的话,直接返回数据
  2. 缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应。

Cache-Aside 写流程

Cache-Aside Pattern的写请求流程如下:

Cache-Aside写请求

更新的时候,先 更新数据库,然后再删除缓存

Read-Through/Write-Through(读写穿透)

Read/Write Through模式中,服务端把缓存作为主要数据存储。应用程序跟数据库缓存交互,都是通过 抽象缓存层完成的。

Read-Through

Read-Through的简要流程如下

Read Through简要流程

  1. 从缓存读取数据,读到直接返回
  2. 如果读取不到的话,从数据库加载,写入缓存后,再返回响应。

这个简要流程是不是跟 Cache-Aside很像呢?其实 Read-Through就是多了一层 Cache-Provider,流程如下:

Read-Through流程

Read-Through实际只是在 Cache-Aside之上进行了一层封装,它会让程序代码变得更简洁,同时也减少数据源上的负载。

Write-Through

Write-Through模式下,当发生写请求时,也是由 缓存抽象层完成数据源和缓存数据的更新,流程如下: Write-Through流程

Write behind (异步缓存写入)

Write behindRead-Through/Write-Through有相似的地方,都是由 Cache Provider来负责缓存和数据库的读写。它两又有个很大的不同: Read/Write Through是同步更新缓存和数据的, Write Behind则是只更新缓存,不直接更新数据库,通过 批量异步的方式来更新数据库。

Write behind流程

这种方式下,缓存和数据库的一致性不强, 对一致性要求高的系统要谨慎使用。但是它适合频繁写的场景,MySQL的 InnoDB Buffer Pool机制就使用到这种模式。

操作缓存的时候,删除缓存呢,还是更新缓存?

一般业务场景,我们使用的就是 Cache-Aside模式。 有些小伙伴可能会问, Cache-Aside在写入请求的时候,为什么是 删除缓存而不是更新缓存呢?

Cache-Aside写入流程

我们在操作缓存的时候,到底应该删除缓存还是更新缓存呢?我们先来看个例子:

  1. 线程A先发起一个写操作,第一步先更新数据库
  2. 线程B再发起一个写操作,第二步更新了数据库
  3. 由于网络等原因,线程B先更新了缓存
  4. 线程A更新缓存。

这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据 不一致了,脏数据出现啦。如果是 删除缓存取代更新缓存则不会出现这个脏数据问题。

更新缓存相对于删除缓存,还有两点劣势:

  • 如果你写入的缓存值,是经过复杂计算才得到的话。更新缓存频率高的话,就浪费性能啦。
  • 在写数据库场景多,读数据场景少的情况下,数据很多时候还没被读取到,又被更新了,这也浪费了性能呢(实际上,写多的场景,用缓存也不是很划算了)

双写的情况下,先操作数据库还是先操作缓存?

Cache-Aside缓存模式中,有些小伙伴还是有疑问,在写入请求的时候,为什么是 先操作数据库呢?为什么 不先操作缓存呢?

假设有A、B两个请求,请求A做更新操作,请求B做查询读取操作。 image.png

  1. 线程A发起一个写操作,第一步del cache
  2. 此时线程B发起一个读操作,cache miss
  3. 线程B继续读DB,读出来一个老数据
  4. 然后线程B把老数据设置入cache
  5. 线程A写入DB最新的数据

酱紫就有问题啦, 缓存和数据库的数据不一致了。缓存保存的是老数据,数据库保存的是新数据。因此, Cache-Aside缓存模式,选择了先操作数据库而不是先操作缓存。

缓存延时双删

有些小伙伴可能会说,不一定要先操作数据库呀,采用 缓存延时双删策略就好啦?什么是延时双删呢?

image.png

  1. 先删除缓存
  2. 再更新数据库
  3. 休眠一会(比如1秒),再次删除缓存。

这个休眠一会,一般多久呢?都是1秒?

这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。

删除缓存重试机制

不管是 延时双删还是 Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢,删除失败会导致脏数据哦~

删除失败就多删除几次呀,保证删除缓存成功呀~ 所以可以引入 删除缓存重试机制

image.png

  1. 写请求更新数据库
  2. 缓存因为某些原因,删除失败
  3. 把删除失败的key放到消息队列
  4. 消费消息队列的消息,获取要删除的key
  5. 重试删除缓存操作

读取biglog异步删除缓存

重试删除缓存机制还可以,就是会造成好多业务代码入侵。其实,还可以通过 数据库的binlog来异步淘汰key

image.png

以mysql为例 可以使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性

参考与感谢

李学凌亲自接管 BIGO,欢聚集团组织、方向生变

$
0
0

《晚点 LatePost》获悉,自 2 月开始,欢聚集团(NASDAQ:YY)创始人、董事长兼 CEO 李学凌已亲自接管欢聚旗下公司 BIGO。


BIGO 各业务负责人已经不再对 BIGO 总裁胡建强(Jason)汇报,转为直接向李学凌汇报。胡建强目前处于休假状态。


BIGO 的直播应用 Bigo Live 是海外收入第二的中国非游戏应用,仅次于字节跳动的 TikTok。根据应用数据统计公司 Sensor Tower 数据,自 2020 年 3 月至今,BIGO 几乎每月都在全球应用收入榜前十。


李学凌是最早将直播做成一门生意的人。YY 从一款游戏聊天工具扩张为直播平台,2014 年收入就超过 21 亿元。但随着抖音、快手涉足直播,YY 和虎牙都在中国面临激烈竞争。


欢聚在 2020 年 4 月将虎牙直播卖给腾讯,又在 11 月将 YY 直播卖给百度。在出售 YY 直播前,BIGO 已经是欢聚第一大收入来源。2020 年前三季度 BIGO 收入 85.6 亿元人民币,比 YY 多出近 3 亿元。


BIGO 是李学凌的二次创业,其联合创始人胡建强早年是欢聚的工程师。BIGO 于2014 年由李学凌创立,2016 年转型做海外市场,发展出直播应用 Bigo Live、短视频应用 Likee、聊天应用 imo 这三个主要海外产品。


BIGO 进军海外后不久,胡建强就开始主管 BIGO 的业务。2018 年欢聚以 22 亿美元收购 BIGO。此后胡建强继续担任 BIGO 总裁,所有业务线负责人对他汇报。


在去年的采访中,胡建强曾告诉《晚点 LatePost》,他擅长执行,李学凌擅长思考,两人很互补。


现在李学凌回来管理一线业务,“天天在 BIGO 开会”。一位 BIGO 知情人士对《晚点 LatePost》表示,“李学凌现在亲自管理 BIGO,全力以赴 BIGO 的全球市场。”


《晚点 LatePost》了解到,李学凌接管后,员工可见的变化主要是优化成本,重点是调整烧钱的短视频 Likee 和业绩无明显起色的 imo(2020 财年报甚至没有提到这款产品)。imo 的负责人胡承彰(Jeff)于 3 月离职,imo暂由BIGO高级副总裁 James 接管。同时 imo 团队减员 1/3。


意料之中的主将调换

但没想到那么快


一位知情人士对《晚点 LatePost》表示,此次李学凌接管可以说是 “意料之外、情理之中”,只不过没想到会这么快发生。


另一位知情人士则表示,此次变动是“公司在不同阶段,因为市场变化而做出的不同反应。(事发)有偶然、也有必然。”


出售 YY 直播和虎牙之后,BIGO 是欢聚集团仅剩的核心业务。欢聚集团财报显示, BIGO 2020 年营收 119.5 亿元,占集团营收的 90%——剥离虎牙和 YY 后。


上述知情人士表示,YY 直播剥离后,BIGO 成为欢聚集团的主要业务。集团也需要面对“核心管理层的设定、长期利益的分配”等问题。尽管胡建强任 BIGO 总裁多年,但他从未在欢聚集团管理层名单之列。


胡建强曾是欢聚集团工程师,一位技术专家,曾被评价“技术很棒,但(向上)沟通不行”。


欢聚集团的基因,长于理解用户、迎合用户,而弱于技术和商业化。李学凌做 BIGO 时刻意补足短板,重用了胡建强。胡建强一手搭建了 BIGO 的技术系统,并在海外搭建基础设施、降低运作成本。这些为 BIGO 扩张、提升效率打下基础。


而李学凌在早期把控 BIGO 的战略,明确了全力做海外直播的方向。2017 年年初,尝试了三个方向后,李学凌设立了做短视频(Likee)的未来战略。之后,李学凌退居幕后。


一位欢聚集团人士认为复杂的国际形势加快了变化发生。2020 年 6 月,印度政府宣布封禁 59 款中国应用,字节的 TikTok 以及 BIGO 的 Bigo Live 和 Likee 均在列。印度是 BIGO 的运营四大区之一,BIGO 在其中投入了 2、3 亿美元。


印度用户在线消费很少,封禁对 BIGO 收入的直接影响微乎其微。但它的直播应用 Bigo Live 以及短视频应用 Likee 都在印度有大量用户,损失了潜在的增长可能。


BIGO 在海外曾一度对标 TikTok,一度单个季度就能花超过 10 亿元人民币打广告,为短视频和直播拉用户。但二者差距正在拉大。2019 年,BIGO 各业务的全球总月活(MAU) 近 4 亿,只比 TikTok 少一成。2020 年 10 月,BIGO 的月活降至 3.5 亿,不到 TikTok 的一半。


印度封禁后不久,BIGO 负责短视频的高管大批离职。2020 年 9 月,Likee 负责人危文离职。随后几个月内,Likee 中东大区负责人吴宇峰、北美大区负责人黄知进、东欧大区负责人王品也相继离职。


据《晚点 LatePost》了解,为弥补印度封禁带来的损失,BIGO 上线了新的短视频产品。同时 2020 年下半年到年底,BIGO 也在内部试水了跨境电商业务,但还没带来多少收入。


子公司和集团利益不会完全相同。去年接受《晚点 LatePost》采访时,胡建强曾说,BIGO 被欢聚并购是 “吃亏了”,一是作价较低,二是在上市公司体系内“拿钱、花钱”相对更难,不利于新产品扩张。


今年春节后,BIGO 内部系统显示,各业务负责人不再对胡建强汇报,改为直接向李学凌汇报。员工称,之后没有在公司办公室看到胡建强。


两大业务减员

BIGO 未来将加大海外电商投入


BIGO 在直播以外的主要业务过去一年都经历了人事震荡。


短视频应用 Likee 的高管大批离职后,聊天应用 imo 负责人胡承彰(Jeff) 已在今年三月离职。imo 团队减员 30%。一位接近欢聚的人士称,“imo亏钱、数据一直跌,吃力不讨好。”


imo 是一款 2007 年诞生的聊天工具,到 2019 年年初被 BIGO 收购时,在全球有 2 亿多月活。


“刚开始,李学凌很兴奋也有无数的点子在 imo 上,但逐个试过后发现效果都不好,就交给胡建强和团队去试错了。”上述知情人士表示,李学凌看到了把 imo 做成类似的产品的可能性。


财报显示, imo 2020 年第四季度平均月活跃用户(MAU)约为 1.86 亿。虽然已是欢聚所有产品中月活最高的应用,但它的用户规模已经比收购时少了超过 10%。


上述知情人士表示,BIGO不会放弃 imo,这款产品将聚焦在语音房产品打磨和变现。


而短视频业务 Likee 也将削减团队规模,争取盈利。


一位接近 BIGO 的人士称,Likee 这三年更看重纯用户量的增长,以追赶 TikTok,争取来的很多用户商业价值较低。现在 Likee 的广告投放预算也大幅减少。


李学凌在 2020 年四季度财报电话会议中称,Likee 在过去三年注重用户规模的增长,现在开始进行商业化,未来还会发生一些比较明显的改变。


一位 BIGO 知情人士告诉《晚点 LatePost》,Likee 减员比例近 20%。其中主要包括算法团队。而之所以选择裁减算法团队,是因为其工资成本最高,并且算法框架搭建好以后,后续迭代的人力要求较低。


一些业务缩减投入的同时,新产品还在上线。欢聚于今年 3 月在 Google Play 和苹果 App Store 上线了一款新的社交应用 CrushMe。据 App Growing Global 数据,该应用已从 3 月底开始通过 Facebook 在海外市场投放广告拉用户。


李学凌为 BIGO 商业化选定的长期目标是电商。他认为中国兴起的直播带货有机会被推广到海外。


李学凌曾在去年二季度的财报电话会议中称电商业务是欢聚的长期目标。未来三到五年将着重发展电商业务。最终欢聚的营收将转为以电商为主,直播为辅,再加上一部分广告收入。


去年 8 月,欢聚战略投资了跨境电商开店服务 Shopline,后者在中国和越南均有团队。


欢聚并不是唯一进入电商领域的短视频、直播公司。快手内部孵化了 Zynn,《晚点 LatePost》曾独家报道,字节跳动也在探索针对海外消费者的电商项目,代号 “麦哲伦 XYZ”。


经过一年多的业务出售和调整,欢聚已经大不相同。2019 年年底,它一半业务和收入在中国,另一半在海外。今天欢聚几乎全部产品和收入都来自海外。


它所面对的世界也是几年前所无法预料的:地缘政治冲突不断激化,新冠肺炎疫情爆发一年未见平息迹象。


创办了欢聚的李学凌重新掌舵 BIGO,带着一个不一样的公司,面对一个不一样的世界。


电动车真的更环保?Science:必须在2030以前解决锂电池污染问题

$
0
0
为了达到「碳中和」的目标,全球各个国家和地区都在积极推广新能源电动车。然而在「零排放」的同时,电池回收成为了一个亟待解决的难题。在最新一期《科学》杂志中,研究者们对于如何回收锂电池的问题进行了探讨。

十到十五年后,数百万辆电动车就将寿命到期,传统汽车的铅酸电池能被广泛回收利用,但新能源电动车的锂离子电池,看起来却不太好办。

Tesla Model S 的电池组是一项复杂的工程。成千上万个原料来自世界各地的圆柱形电池,将锂元素和电子转化为足够的能量,让汽车能够不排放尾气行驶。但是,当电池到达寿命时,新能源车便不再环保。在进入垃圾场后,电池会释放出各种有毒成分,包括重金属。莱斯特大学的材料科学家 Dana Thompson 警告道:「回收电池可能是一项危险的工作。」 分割特斯拉电池时,太深或错误的位置,都可能会导致短路、燃烧、有毒烟雾的释放。

而这仅仅是 Thompson 等研究人员面临的众多问题之一。他们正在面对一个更紧急的问题:如何回收制造商期望在未来几十年内生产的数百万辆电动汽车(EV)电池。英国研究电池问题的研究中心 Faraday Institution 的研究人员 Thompson 说:「当前的 EV 电池实际上并非旨在回收利用。」 

当电动汽车很少见时,这并不是什么大问题。但是现在技术正在起飞,几家汽车制造商表示,他们计划在几十年内逐步淘汰内燃机。而且行业分析师预测,相比去年的 1100 万辆,到 2030 年,至少有 1.45 亿辆电动汽车将上路行驶。Thompson 说:「人们开始意识到这是一个问题。」 

政府也在监督一定程度的回收利用。2018 年,中国实施了旨在促进电动汽车电池组件再利用的新法规。欧盟有望在今年最终确定其首个要求。在美国,联邦政府尚未提高回收要求,但包括美国最大的汽车市场加利福尼亚在内的几个州正在探索制定自己的法规。

只靠立法就解决问题是不现实的。各种电池的化学和构造差异很大,所以很难创建有效的回收系统。而且电池间经常靠黏力极强的胶粘在一起,这使它们非常难以分解。还有经济上的障碍,对于电池制造商来说,购买新鲜开采的金属通常要比使用再生材料便宜多了。
研究人员指出,更好的回收方法不仅可以防止污染,而且可以通过增加关键电池金属的供应来帮助政府提高经济和国家安全。「一方面,处置 EV 电池是废物管理问题;另一方面,这是可持续二次生产关键性材料的机会,」 伯明翰大学研究电动汽车政策问题的研究员 Gavin Harper 说。

为了快速启动回收,政府和行业正在将资金投入到一系列研究计划中。美国能源部(DOE)已向 ReCell 中心注资 1500 万美元,以协调学术界、工业界、政府实验室的科学家进行研究。英国支持了一个多机构参与的的 ReLiB 项目。DOE 的 Argonne 国家实验室负责电池回收的 Linda Gaines 说:「随着 EV 行业的蓬勃发展,我们要尽早使这一切运转起来。」

电动电池的构造有点像套娃。通常,一个主包包含几个模块,如下图所示,每个模块都由许多较小的单元构成。在每个单元内部,锂原子穿过电解质在石墨阳极和由金属氧化物组成的阴极片之间移动。电池通常由阴极中的金属定义,主要有三种类型:镍钴铝、磷酸铁、镍锰钴。
现在,回收商的主要目标是阴极上价格高昂的金属(例如钴和镍)。锂和石墨对于回收来说太便宜了,以至于利润不够高。但是由于数量少,找到并回收这些金属难如大海捞针。

废旧电池的新生命

科学家们正在努力确保现在出售的电动汽车(EV)电池能够在 2030 年及以后回收,毕竟届时每天都将用尽成千上万的电池。EV 电池有多种设计,但通常组件是共享的。在 EV 电池包内部,电气组件管理着数十个模块的充电和稳定性。

为了提取这些金属,回收商依赖于两种技术:「火法冶金」和「湿法冶金」。

其中「火法冶金」更为常见:回收者首先将电池进行机械粉碎,然后进行燃烧从而留下一堆烧焦的塑料、金属、胶水,最后使用包括进一步燃烧的几种方法来提取金属。Gaines 说:「从本质上说,Pyromet 就像对待矿石一样对待电池。」

而「湿法冶金」是将电池材料浸入酸池中从而产生金属负载的汤,有时会结合使用这两种方法。

每种方法都有其优点和缺点。比如,「火法冶金」不需要回收者知道电池的设计或成分,甚至不需要知道电池是否已完全放完电就可以安全地进行,但作为代价能源消耗很大。

而「湿法冶金」可以提取不易通过燃烧获得的材料,但其中可能涉及对健康有危害的化学物质,而且从研究人员的化学汤中回收想要的元素是很困难的。研究人员为此正在试图寻找能够溶解某些电池金属但以固体形式保留其他金属的化合物。Thompson 已经确定了一种候选物,即一种被称为深共熔溶剂的酸碱混合物它能溶解除镍以外的所有物质。

研究表明,这两种方法都会产生大量废料并排放温室气体,且其商业模式也不太靠谱。大多数回收商都是依靠出售回收的钴来获得收入,可是电池制造商正在逐步摆脱这种昂贵的金属。难道以后回收商要靠「出售废料」创收吗?
理想情况是直接回收利用,这样就能让阴极混合物保持完整。Gaines 指出,这对电池制造商来说极具吸引力,因为回收过程不需要大量处理工作。因此,从循环经济角度考虑,直接回收利用比起火法或湿法冶炼要轻松得多。

在直接回收过程中,工人首先要吸走电解液并切碎电池,然后通过加热或溶剂去除粘合剂,并使用浮选技术分离阳极和阴极材料。这时候正极材料看上去很像婴儿爽身粉。

到目前为止,直接回收实验的只面向单个电池,仅能产生数十克的阴极粉末。但是美国国家可再生能源实验室的研究人员已经搭建了经济模型,证明该技术在适当的条件下扩大规模的可行性。

但在直接回收过程中,电池制造商、回收商、研究人员都需要解决很多问题。首先是确保制造商给电池贴上标签,以便让回收商知道他们要处理的是哪种电池,以及阴极金属是否具有任何价值。鉴于电池市场瞬息万变,如今制造的阴极将来未必能找到未来的买家。
另一项挑战是如何有效打开 EV 电池。日产的矩形 Leaf 电池模块可能需要两个小时拆卸,特斯拉的电池尤其独特,除了是圆柱形的,还有聚氨酯水泥将其固定在一起,几乎「坚不可摧」。

研究人员提到,工程们可能制造出加快电池拆卸速度的机器人,但进入电池内部后仍存在粘性问题,因为此前用了大量胶水将阳极、阴极和其他组件固定在适当的位置。回收商用来溶解粘合剂的溶剂是有毒的,且受到欧盟的使用限制,去年,美国环境保护署也确定这一溶剂会对工人造成「不当风险」。

莱斯特大学化学家兼汤普森大学顾问 Andrew Abbott 表示:「从经济层面来说,必须要拆卸 EV 电池,一旦拆卸,就必然要用到胶水。」

为了简化这一过程,来自 Thompson 及其他机构的研究者敦促电动汽车和电池制造商在设计产品时就加入对回收利用的考量。Abbott 说,理想中的电池应该像圣诞爆竹,当收到的人将两端拉开,糖果等礼物就露了出来。他提到了去年比亚迪发布的磷酸铁锂刀片电池,其包装取消了模块组件,而是将单体电池直接存储在内部,用手即可取出,再不必为电线及胶水困扰。

2018 年,中国要求电动汽车制造商确保电池的可回收利用,这也是刀片电池诞生的大背景。目前,中国使用火法和湿法冶金方法回收的锂离子电池数量超过世界其他地区的总和。

同样采取类似政策的其他国家还面临着一些棘手问题,其中之一就是「谁承担回收的责任」。这一责任是属于购买了电动汽车的消费者,还是属于制造并出售电池的制造商?

同时,高效地电池回收不只依靠技术进步。长距离或跨境运输可燃物品的高成本都会阻碍回收。因此,在正确的位置建立回收中心会产生「巨大影响」,但真正的挑战在于系统集成,以及将所有这些不同的研究领域整合在一起。

「留给我们的时间不多了。人们绝对不想要一块能用 10 年但拆不开的电池。这情形尚未发生,但人们都在担心真正发生后的样子。」Abbott 表示。

抖音 360 百度等应用被指过多收集用户信息

$
0
0
网信办公布了《 关于抖音等105款App违法违规收集使用个人信息情况的通报》,称在调查之后发现这些应用收集了与其服务无关的个人信息或未经用户同意收集使用个人信息。被列入名单中的应用包括抖音、快手、360 浏览器、搜狗浏览器、百度浏览器、360 搜索、欧朋浏览器(Opera)、UC、傲游、领英、猎聘、前程无忧、智联、以及百度等等。网信办要求相关运营者在 15 个工作日内完成整改,逾期未完成整改将面临惩罚。

记一次 Kubernetes 网络故障深度追踪

$
0
0

某天晚上,客户碰到了 Kubernetes 集群一直扩容失败,所有的节点都无法正常加入集群。在经过多番折腾无解后,反馈到我们这里进行技术支持。这个问题的整个排查过程比较有意思,所以对其中的排查思路和用到的方法进行整理分享。

问题现象

运维同学在对客户的 Kubernetes 集群进行节点扩容时,发现新增的节点一直添加失败。该同学进行了初步的排查如下:
  • 在新增节点上,访问 Kubernetes master service vip 网络不通
  • 在新增节点上,直接访问 Kubernetes master hostIP + 6443 网络正常
  • 在新增节点上,访问其他节点的容器 IP 可以正常 ping 通
  • 在新增节点上,访问 coredns service vip 网络正常


该客户使用的 Kubernetes 版本是 1.13.10,宿主机的内核版本是 4.18(CentOS 8.2)。

问题排查过程

收到该一线同事的反馈,我们已经初步怀疑是 IPVS 的问题。根据以往网络问题排查的经验,先对现场做了些常规排查:
  • 确认内核模块 ip_tables 是否加载(正常)
  • 确认 iptable forward 是否默认 accpet (正常)
  • 确认宿主机网络是否正常(正常)
  • 确认容器网络是否正常(正常)
  • ……


排除了常规的问题之后,基本可以缩小范围,再继续基于 IPVS 相关层面进行排查。

通过 ipvsadm 命令排查

10.96.0.1 是客户集群 Kubernetes master service vip。
1.jpg

可以发现有异常连接,处于 SYN_RECV 的状态,并且可以观察到,启动时 kubelet + kube-proxy 是有正常建连的,说明是在启动之后,Kubernetes service 网络出现异常。

tcpdump 抓包分析

两端进行抓包,并通过 telnet 10.96.0.1 443命令进行确认。

结论:发现 SYN 包在本机没有发送出去。

初步总结

通过上面的排查,可以再次缩小范围,问题基本就在 kube-proxy 身上。我们采用了 IPVS 模式,也会依赖了 iptables 配置实现一些网络的转发、SNAT、drop 等等。

根据上面的排查过程,我们又缩小了范围,开始分析怀疑对象 kube-proxy。

查看 kube-proxy 日志

2.jpeg

发现异常日志,iptables-restore 命令执行异常。通过 Google、社区查看,确认问题。

相关 issue 链接可以参考下:


继续深入

通过代码查看(1.13.10 版本 pkg/proxy/ipvs/proxier.go:1427),可以发现该版本确实没有判断 KUBE-MARK-DROP 是否存在并创建的逻辑。当出现该链不存在时,会出现逻辑缺陷,导致 iptable 命令执行失败。

Kubernetes master service vip 不通,但是实际容器相关的 IP 是通的原因,与下面这条 iptable 规则有关:
iptable -t nat -A KUBE-SERVICES ! -s 9.0.0.0/8 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ  

根因探究

前面我们已经知道了 kube-proxy 1.13.10 版本存在缺陷,在没有创建 KUBE-MARK-DROP 链的情况下,执行 iptables-restore 命令配置规则。但是为何 Kubernetes 1.13.10 版本跑在 CentOS 8.2 4.18 内核的操作系统上会报错,跑在 CentOS 7.6 3.10 内核的操作系统上却正常呢?

查看下 kube-proxy 的源码,可以发现 kube-proxy 其实也就是执行 iptables 命令进行规则配置。那既然 kube-proxy 报错 iptables-restore 命令失败,我们就找一台 4.18 内核的机器,进入 kube-proxy 容器看下情况。

到容器内执行下 iptables-save 命令,可以发现 kube-proxy 容器内确实没有创建 KUBE-MARK-DROP 链(符合代码预期)。继续在宿主机上执行下 iptables-save 命令,却发现是有 KUBE-MARK-DROP 链。

这里有两个疑问:
  • 为何 4.18 内核宿主机的 iptables 有 KUBE-MARK-DROP 链?
  • 为何 4.18 内核宿主机的 iptables 规则和 kube-proxy 容器内的规则不一致?


第一个疑惑,凭感觉怀疑除了 kube-proxy,还会有别的程序在操作 iptables,继续撸下 Kubernetes 代码。

结论:发现确实除了 kube-proxy,还有 kubelet 也会修改 iptables 规则。具体代码可以查看 pkg/kubelet/kubelet_network_linux.go

第二个疑惑,继续凭感觉吧。Google 一发捞一下为何 kube-proxy 容器挂载了宿主机 /run/xtables.lock 文件的情况下,宿主机和容器 iptables 查看的规则不一致。

结论:CentOS 8 在网络方面摒弃 iptables 采用 nftables 框架作为默认的网络包过滤工具。

至此,所有的谜团都解开了。

团队完成过大量的客户项目交付,还是有些问题可以再解答下:

问题一:为何这么多客户环境第一次碰到该情况?因为需要 Kubernetes 1.13.10 + centos 8.2 的操作系统,这个组合罕见,且问题是必现。升级 Kubernetes 1.16.0+ 就不会有该问题。

问题二:为何使用 Kubernetes 1.13.10 + 5.5 内核却没有该问题?

因为那是与 CentOS 8 操作系统有关,我们手动升级 5.5 版本后,默认还是使用的 iptables 框架。

可以通过 iptables -v命令来确认,是否使用了 nftables。
3.png

nftables 是何方神圣?比 iptables 好么?这是另一个值得进一步学习的点,这里就不再深入了。

解决方法

针对以上的排查问题,我们总结下解决方法:
  • 调整内核版本到 3.10(CentOS 7.6+),或者手动升级内核版本到 5.0 +;
  • 升级 Kubernetes 版本,当前确认 1.16.10+ 版本没有该问题。


原文链接: https://zhuanlan.zhihu.com/p/364382294

    为什么列存储能够大幅度提高数据的查询性能? - 悦光阴 - 博客园

    $
    0
    0

    传统的存储数据的方式是逐行存储(Row Store),每一个Page存储多行数据,而列存储(Column Store)把数据表中的每一列单独存储在Page集合中,这意味着,Page集合中存储的是某一列的数据,而不是一行的所有列的数据。

    列存储索引适合于数据仓库中,主要执行大容量数据加载和只读查询,与传统面向行的存储方式相比,使用列存储索引存储可最多提高 10 倍查询性能 ,与使用非压缩数据大小相比,可提供多达 7 倍数据压缩率 。列存储索引使用用“批处理执行模式”的模式,这与行存储使用的逐行数据读取模式对比,性能大幅提升。

    列存储索引主要在下面三个特性上提升查询的性能:

    • 行存储使用逐行处理模式,每次只处理一行数据;而列存储索引使用批处理模式,每次处理一批数据行。
    • 行存储是逐行存储(Row Store),每一个Page存储多行数据,而列存储(Column Store)把数据表中的每一列单独存储在Page集合中,这意味着,Page集合中存储的是某一列的数据,而不是一行中所有列的数据。在读取数据时,行存储把一行的所有列都加载到内存,即使有些列根本不会用到;而列存储只把需要的列加载到内存中,不需要的列不会被加载到内存中。
    • 列存储索引自动对数据进行压缩处理,由于同一行的数据具有很高的相似性,压缩率很高,数据读取更快速。

    一般情况下,数据仓库的查询语句只会查询少数几个列的数据,其他列的数据不需要加载到内存中,这就使得列存储特别适合用于数据仓库中。

    一,列存储的特点

    为什么列存储能够大幅度提高数据的查询性能呢?要回答这个问题,首先必须明白SQL Server引擎是怎样读取数据的。在读取数据时,SQL Server每次都把所需数据所在的整个Page读取到内存中,Page是数据读取的最小单位。如果采用行存储,每一个Page都存储所有列的数据,每行的Size决定了单个Page能够存储的数据行数量。

    我们可以粗略计算一下,如果一个数据行有10列,每列的平均Size是10B,一行的Size是100B,那么单个Page最多存储80行(8060B/100B);如果采用列存储模式,那么单个Page可以存储806行(8060B/10B)。就单个Page存储的数据行数量而言,列存储是行存储的10倍,SQL Server引擎把一个Page读取到内存中,能够获取的数据行数量成10倍增加。

    因此,采用列存储模式时,每一个Page能够存储更多的数据行。在加载列存储数据时,SQL Server只需要消耗少量的IO,就能把某一列的全部数据加载到缓存中。当从列很多的大表中读取几个列时,相比传统的行存储(Row Store)模式,列存储(Column Store)能够成千上万倍地提高数据的读取速度和查询性能。

    二,列存储的物理实现

    数据表(堆,B-Tree)以行存储模式存储数据,而列存储索引以列存储模式存储数据,行存储和列存储的示例图:

    1,列存储的优点

    对于列存储,列C1…C6 存储在不同的Page组中,列存储的有点是:

    • 列存储是把每一列都单独存储在Pages集合中,对于行存储,哪怕只从数据表中选择(select)一列,SQL Server引擎都把整个数据行所在的Page读取到内存中,而使用列存储索引,仅仅需要把select子句指定的列读取到内存,不需要的列不会被读取;因此,如果一个查询请求只需要从少量的几个列中获得数据,列存储能够大幅度提高查询性能;
    • 由于单个数据列的数据冗余度更高,因此同一列的数据更容易被压缩存储,单个Page存储更多的数据;
    • 缓存命中率提高,这是因为同一列的数据被高度压缩,常用的Page被频繁访问而变得异常活跃,Buffer Manager把活跃的数据页缓存到内存中,不常用的Page被换出(Page Out)。
    • 更高级的查询执行技术,列存储模式读取数据使用的是批处理模式(Batch Processing Mode),相对于传统的行处理技术,查询性能更高。

    2,列存储模式的物理实现

    SQL Server引擎分三步实现列存储:

    • step1,列存储索引先把数据表的所有数据行分组,每个分组也称作行组(Row Groups)。
    • step2,在每个行组中,每列的所有数据行构成一个列段(Column Segment),简称段。
    • step3,对每个段进行压缩处理和编码,每个段都单独存储在列存储索引中。

    3,编码和压缩

    列存储使用两种编码类型:基于字典(dictionary based)和基于值(value based),使用Vertipaq压缩数据。

    字典编码是把唯一值编入字典,每一个唯一值都匹配一个序号,而序号用于索引字典,通过存储序号来压缩数据。如果数据表中存在大量的重复值,那么使用字典编码压缩率高。

    值编码用于整数类型,或小数类型,编码的原理是把Value的范围按照比例缩小或增大,并使用一个指数(exponent)来表示比例。如果整数(integer) 或小数(decimal)的值分布集中,那么使用基于值(value-based)编码方法进行压缩非常高效。

    列存储索引的物理存储如下图所示:

     

    三,列存储索引

    SQL Server 2012开始引入列存储模式,用户通过创建列存储索引(Column Store Index)来体验列存储模式带来的性能提升。而列存储模式非常适用于星型连接(Star- Join)类型的聚合查询,所谓星型连接(Star-Join)的聚合查询是指对一个大表(Large Table)和多个小表(Little Table)进行连接,并对Large Table 进行聚合查询。在数据库仓库中,是指事实表和维度表的连接。

    在大表上创建列存储索引,SQL Server 引擎将充分使用批处理模式(Batch processing mode)来执行星型查询,获取更高的查询性能。

    典型的Star- Join的聚合查询类似于下面的示例脚本:

    selectlt.Grouping_Columns,
            AggregationFunction(bt.Columns)fromdbo.LittleTable ltwith(nolock)innerjoindbo.BitTable btwith(nolock)onlt.Int_Col1=bt.Int_col1where....groupbylt.Grouping_Columns

    在SQL Server 2012中,只能创建非聚集的列存储索引,由于列存储索引的每一列都有独立的存储空间(Page Set),因此,列存储索引会包含数据表的所有列,这样,每一个数据列都会被索引到。但是,并不是每一列都能获得的相同的性能提升,这是因为,列存储使用的压缩算法对于具有大量重复值的字符或数值的数据,压缩效率更高。对于列存储索引而言,查询性能的提升很大程度上依赖列数据的高度压缩,这会大幅减少存储该列数据所占用的数据页(Data Page),进而大幅减少把数据加载到内存所耗费的内存和时间。

    CREATE[NONCLUSTERED]COLUMNSTOREINDEXindex_nameONschema_name . table_name (column[,...n])[WITH ( DROP_EXISTING = { ON | OFF } | MAXDOP = max_degree_of_parallelism )][ON  partition_scheme_name ( column_name )  | filegroup_name]

    一旦表上创建了非聚集的列存储索引,基础表就变成只读的(read-only),不能对基础表做任何更新(insert,update,delete 或merge)操作,如果需要修改数据,那么,首先要禁用列存储索引,然后更新数据,最后重建列存储索引:

    ALTERINDEXmycolumnstoreindexONmytable DISABLE;--update mytable --ALTERINDEXmycolumnstoreindexonmytable REBUILD

    由于创建或重建列存储索引是IO密集型资源,十分耗费内存资源,因此必须在系统空闲的情况下,更新数据。 

    四,列存储索引的空间使用

    列存储索引首先把数据分组,然后每个行组中的每个列构成一个段(Segment),每段都是单独存储的,列存储索引占用的存储空间的大小是由所有段占用的硬盘空间的加和。

    系统视图:sys.column_store_segments 提供每个段的数据信息,每个段都是每个行组中的一列的数据的集合,例如,如果一个列存储索引分为10个行组,每个行组有15个数据列,那么,该视图将返回150个段。

    selecti.object_id,object_name(i.object_id)asobject_name,i.nameasindex_name
        ,i.type_descasindex_type
        ,col_name(i.object_id,ic.column_id)asindex_column_name
        ,sum(s.row_count)asrow_count
        ,sum(s.on_disk_size)/1024/1024ason_disk_size_mbfromsys.column_store_segments sinnerjoinsys.partitions pons.partition_id=p.partition_idinnerjoinsys.indexes ionp.object_id=i.object_idandp.index_id=i.index_idinnerjoinsys.index_columns iconi.object_id=ic.object_idandi.index_id=ic.index_idands.column_id=ic.index_column_idgroupbyi.object_id,i.index_id
        ,i.name
        ,i.type_desc
        ,ic.column_idorderbyi.object_id,i.name
        ,index_column_name
    View Code

    可以看出,列存储索引中每个段占用的硬盘空间是很少的,加载到内存所需要耗费的时间,IO次数和内存资源也是很少的,再配上性能更高的批处理模式,所以,列存储能够大幅度提高数据的查询性能,特别是对星型聚合的查询。

     

     

    推荐阅读:

    Columnstore Indexes

    SQL Server 2012新特性_列存储索引(1)

    SQL Server 2012新特性_列存储索引(2)

    SQL Server 2012新特性_列存储索引(3)

    处理海量数据:列式存储综述(存储篇) - 知乎

    $
    0
    0

    列式存储(Column-oriented Storage)并不是一项新技术,最早可以追溯到 1983 年的论文 Cantor。然而,受限于早期的硬件条件和使用场景,主流的事务型数据库(OLTP)大多采用行式存储,直到近几年分析型数据库(OLAP)的兴起,列式存储这一概念又变得流行。

    总的来说,列式存储的优势一方面体现在存储上能节约空间、减少 IO,另一方面依靠列式数据结构做了计算上的优化。本文中着重介绍列式存储的数据组织方式,包括数据的布局、编码、压缩等。在下一篇文章中将介绍计算层以及 DBMS 整体架构设计。


    什么是列式存储

    传统 OLTP 数据库通常采用行式存储。以下图为例,所有的列依次排列构成一行,以行为单位存储,再配合以 B+ 树或 SS-Table 作为索引,就能快速通过主键找到相应的行数据。

    行式存储对于 OLTP 场景是很自然的:大多数操作都以实体(entity)为单位,即大多为 增删改查一整行记录,显然把一行数据存在物理上相邻的位置是个很好的选择。

    然而,对于 OLAP 场景,一个典型的查询需要遍历整个表,进行分组、排序、聚合等操作,这样一来按行存储的优势就不复存在了。更糟糕的是,分析型 SQL 常常不会用到所有的列,而仅仅对其中某些感兴趣的列做运算,那一行中那些无关的列也不得不参与扫描。

    列式存储就是为这样的需求设计的。如下图所示,同一列的数据被一个接一个紧挨着存放在一起,表的每列构成一个长数组。

    显然,列式存储对于 OLTP 不友好,一行数据的写入需要同时修改多个列。但对 OLAP 场景有着很大的优势:

    • 当查询语句只涉及部分列时,只需要扫描相关的列
    • 每一列的数据都是相同类型的,彼此间相关性更大,对列数据压缩的效率较高
    BigTable(HBase)是列式存储吗?

    很多文章将 BigTable 归为列式存储。但严格地说,BigTable 并非列式存储,虽然论文中提到借鉴了 C-Store 等列式存储的某些设计,但 BigTable 本身按 Key-Value Pair 存储数据,和列式存储并无关系。

    有一点迷惑的是 BigTable 的列簇(column family)概念,列簇可以被指定给某个 locality group,决定了该列簇数据的物理位置,从而可以让同一主键的各个列簇分别存放在最优的物理节点上。由于 column family 内的数据通常具有相似性,对它做压缩要比对整个表压缩效果更好。

    另外,值得强调的一点是:列式数据库可以是关系型、也可以是 NoSQL,这和是否是列式并无关系。本文中讨论的 C-Store 就采用了关系模型。
    Column Families in BigTable

    起源:DSM 分页模式

    我们知道,由于机械磁盘受限于磁头寻址过程,读写通常都以一块(block)为单位, 故在操作系统中被抽象为块设备,与流设备相对。这能帮助上层应用是更好地管理储存空间、增加读写效率等。这一特性直接影响了数据库储存格式的设计:数据库的 Page 对应一个或几个物理扇区,让数据库的 Page 和扇区对齐,提升读写效率。

    那如何将数据存放到页上呢?

    大多数服务于在线查询的 DBMS 采用 NSM (N-ary Storage Model) 即按行存储的方式,将完整的行(即关系 relation)从 Header 开始依次存放。页的最后有一个索引,存放了页内各行的起始偏移量。由于每行长度不一定是固定的,索引可以帮助我们快速找到需要的行,而无需逐个扫描。

    NSM 的缺点在于,如果每次查询只涉及很小的一部分列,那多余的列依然要占用掉宝贵的内存以及 CPU Cache,从而导致更多的 IO;为了避免这一问题,很多分析型数据库采用 DSM (Decomposition Storage Model) 即按列分页:将 relation 按列拆分成多个 sub-relation。类似的,页的尾部存放了一个索引。

    顺便一提,2001 年 Ailamaki 等人提出 PAX (Partition Attributes Cross) 格式,尝试将 DSM 的一些优点引入 NSM,将两者的优点相结合。具体来说,NSM 能更快速的取出一行记录,这是因为一行的数据相邻保存在同一页;DSM 能更好的利用 CPU Cache 以及使用更紧凑的压缩。PAX 的做法是将一个页划分成多个 minipage,minipage 内按列存储,而一页中的各个 minipage 能组合成完整的若干 relation。

    如今,随着分布式文件系统的普及和磁盘性能的提高, 很多先进的 DBMS 已经抛弃了按页存储的模式,但是其中的某些思想,例如 数据分区、分区内索引、行列混合等,仍然处处可见于这些现代的系统中。

    分布式储存系统虽然不再有页的概念,但是仍然会将文件切割成分块进行储存,但分块的粒度要远远大于一般扇区的大小(如 HDFS 的 Block Size 一般是 128MB)。更大的读写粒度是为了适应网络 IO 更低的带宽以获得更大的吞吐量,但另一方面也牺牲了细粒度随机读写。

    列数据的编码与压缩

    无论对于磁盘还是内存数据库,IO 相对于 CPU 通常都是系统的性能瓶颈, 合理的压缩手段不仅能节省空间,也能减少 IO 提高读取性能。列式存储在数据编码和压缩上具有天然的优势。

    以下介绍的是 C-Store 中的数据编码方式,具有一定的代表性。根据 1) 数据本身是否按顺序排列(self-order) 2) 数据有多少不同的取值(distinct values),分成以下 4 种情况讨论:

    • 有序且 distinct 值不多。使用一系列的三元组 (v,f,n)对列数据编码,表示数值 v 从第 f 行出现,一共有 n 个(即 f 到 f+n−1 行)。例如:数值 4 出现在 12-18 行,则编码为 (4,12,7)
    • 无序且 distinct 值不多。对于每个取值 v 构造一个二进制串 b,表示 v 所在位置的 bitmap。例如:如果一列的数据是 0,0,1,1,2,1,0,2,1,则编码为 (0, 110000100)(1, 001101001)(2,000010010)。由于 bitmap 是稀疏的,可以对其再进行行程编码。
    • 有序且 distinct 值多。对于这种情况,把每个数值表示为前一个数值加上一个变化量(delta),当然第一个数值除外。例如,对于一列数据 1,4,7,7,8,12,可以表示为序列 1,3,3,0,1,4。显然编码后的数据更容易被 dense pack,且压缩比更高。
    • 无序且 distinct 值多。对于这种情况没有很好的编码方式。

    编码之后,还可以对数据进行压缩。由于一列的数据本身具有相似性,即使不做特殊编码,也能取得相对较好的压缩效果。通常采用 Snappy 等支持流式处理、吞吐量高的压缩算法。

    最后,编码和压缩不仅是节约空间的手段,更多时候也是组织数据的手段。在 PowerDrill、Dremel 等系统中,我们会看到 很多编码本身也兼具了索引的功能,例如在扫描中跳过不需要的分区,甚至完全改表查询执行的方式。

    列式存储与分布式文件系统

    在现代的大数据架构中,GFS、HDFS 等分布式文件系统已经成为存放大规模数据集的主流方式。分布式文件系统相比单机上的磁盘,具备多副本高可用、容量大、成本低等诸多优势,但也带来了一些单机架构所没有的问题:

    1. 读写均要经过网络,吞吐量可以追平甚至超过硬盘,但是 延迟要比硬盘大得多,且受网络环境影响很大。
    2. 可以进行大吞吐量的顺序读写,但随机访问性能很差,大多 不支持随机写入。为了抵消网络的 overhead,通常写入都以几十 MB 为单位。

    上述缺点对于重度依赖随机读写的 OLTP 场景来说是致命的。所以我们看到,很多定位于 OLAP 的列式存储选择放弃 OLTP 能力,从而能构建在分布式文件系统之上。

    要想将分布式文件系统的性能发挥到极致,无非有几种方法: 按块(分片)读取数据、流式读取、追加写入等。我们在后面会看到一些开源界流行的列式存储模型,将这些优化方法体现在存储格式的设计中。


    列式存储系统案例

    C-Store (2005) / Vertica

    大多数 DBMS 都是为写优化,而 C-Store 是第一个为读优化的 OLTP 数据库系统,虽然从今天的视角看它应当算作 HTAP 。在 ad-hoc 的分析型查询、ORM 的在线查询等场景中,大多数操作都是查询而非写入,在这些场景中列式存储能取得更好的性能。像主流的 DBMS 一样,C-Store 支持标准的关系型模型。

    就像本文开头即提到——列式存储不是新鲜事。C-Store 的主要贡献有以下几点: 通过精心设计的 projection 同时实现列数据的多副本和多种索引方式;用读写分层的方式兼顾了(少量)写入的性能。此外,C-Store 可能是第一个现代的列式存储数据库实现,其的设计启发了无数后来的商业或开源数据库,就比如 Vertica

    数据模型

    C-Store 是关系型数据库,它的逻辑表和其他数据库中的并没有什么不同。但是在 C-Store 内部,逻辑表被纵向拆分成 projections,每个 projection 可以包含一个或多个列,甚至可以包含来自其他逻辑表的列(构成索引)。当然,每个列至少会存在于一个 projections 上。

    下图的例子中,EMP 表被存储为 3 个 projections,DEPT 被存储为 1 个 projection。每个 projection 按照各自的 sort key 排序,在图中用下划线表示 sort key。

    Projection 内是以列式存储的:里面的每个列分别用一个数据结构存放。为了避免列太长引起问题,也支持每个 projection 以 sort key 的值做横向切分。

    查询时 C-Store 会先选择一组能覆盖结果中所有列的 projections 集合作为 covering set,然后进行 join 计算重构出原来的行。为了能高效地进行 projections 的 join(即按照另一个 key 重新排序),引入 join index 作为辅助,其中存储了 proj1 到 proj2 的下标映射关系。

    Projection 是有冗余性的,常常 1 个列会出现在多个 projection 中,但是它们的顺序也就是 sort key 并不相同,因此 C-Store 在查询时可以选用最优的一组 projections,使得查询执行的代价最小。

    巧妙的是, C-Store 的 projection 冗余性还用来实现 K-safe 高可用(容忍最多 K 台机器故障),当部分节点当机时,只要 C-Store 还能找到某个 covering set 就能执行查询,虽然不一定是最优的 covering set 组合。

    从另一个角度看,C-Store 的 Projection 可以看作是一种物化(materialized)的查询结果,即查询结果在查询执行前已经被预先计算好;并且由于每个列至少出现在一个 Projection 当中,没有必要再保存原来的逻辑表。

    为任意查询预先计算好结果显然不现实,但是如果物化某些经常用到的中间视图,就能在预计算代价和查询代价之间获得一个平衡。C-Store 物化的正是以某个 sort key 排好序(甚至 JOIN 了其他表)的一组列数据,同时预计算的还有 join index。

    C-Store 对写入的处理将在下一篇文章中呈现。

    Apache ORC

    Apache ORC 最初是为支持 Hive 上的 OLAP 查询开发的一种文件格式,如今在 Hadoop 生态系统中有广泛的应用。ORC 支持各种格式的字段,包括常见的 int、string 等,也包括 struct、list、map 等组合字段;字段的 meta 信息就放在 ORC 文件的尾部(这被称为自描述的)。

    数据结构及索引

    为分区构造索引是一种常见的优化方案,ORC 的数据结构分成以下 3 个层级,在每个层级上都有索引信息来加速查询。

    • File Level:即一个 ORC 文件,Footer 中保存了数据的 meta 信息,还有文件数据的索引信息,例如各列数据的最大最小值(范围)、NULL 值分布、布隆过滤器等,这些信息可用来 快速确定该文件是否包含要查询的数据。每个 ORC 文件中包含多个 Stripe。
    • Stripe Level对应原表的一个范围分区,里面包含该分区内各列的值。每个 Stripe 也有自己的一个索引放在 footer 里,和 file-level 索引类似。
    • Row-Group Level:一列中的每 10000 行数据构成一个 row-group,每个 row-group 拥有自己的 row-level 索引,信息同上。

    ORC 里的 Stripe 就像传统数据库的页,它是 ORC 文件批量读写的基本单位。这是由于分布式储存系统的读写延迟较大,一次 IO 操作只有批量读取一定量的数据才划算。这和按页读写磁盘的思路也有共通之处。

    像其他很多储存格式一样,ORC 和都选择将统计数据和 Metadata 放在 File 和 Stripe 的尾部而不是头部。

    但 ORC 在 Stripe 的读写上还有一点优化,那就是把分区粒度小于 Stripe 的结构(如 Column 和 Row-Group)的索引统一抽取出来放到 Stripe 的头部。这是因为在批处理计算中一般是把整个 Stripe 读入批量处理的,将这些索引抽取出来可以减少在批处理场景下需要的 IO(批处理读取可以跳过这一部分)。

    ACID 支持

    Apache ORC 提供有限的 ACID 事务支持。受限于分布式文件系统的特点,文件不能随机写,那如何把修改保存下来呢?

    类似于 LSM-Tree 中的 MVCC 那样,writer 并不是直接修改数据,而是为每个事务生成一个 delta 文件,文件中的修改被叠加在原始数据之上。当 delta 文件越来越多时,通过 minor compaction 把连续多个 delta 文件合成一个;当 delta 变得很大时,再执行 major compaction 将 delta 和原始数据合并。

    这种保持基线数据不变、分层叠加 delta 数据的优化方式在列式存储系统中十分常见,是一种通用的解决思路

    别忘了 ORC 的 delta 文件也是写入到分布式储存中的,因此每个 Delta 文件的内容不宜过短。这也解释了 ORC 文件虽然支持事务,但是主要是对批量写入的事务比较友好,不适合频繁且细小的写入事务的原因。

    Dremel (2010) / Apache Parquet

    Dremel 是 Google 研发的用于大规模只读数据的查询系统,用于进行快速的 ad-hoc 查询,弥补 MapReduce 交互式查询能力的不足。为了避免对数据的二次拷贝,Dremel 的数据就放在原处,通常是 GFS 这样的分布式文件系统,为此需要设计一种通用的文件格式。

    Dremel 的系统设计和大多 OLAP 的列式数据库并无太多创新点,但是其精巧的存储格式却变得流行起来,Apache Parquet 就是它的开源复刻版。注意 Parquet 和 ORC 一样都是一种存储格式,而非完整的系统。

    嵌套数据模型

    Google 内部大量使用 Protobuf 作为跨平台、跨语言的数据序列化格式,相比 JSON 要更紧凑并具有更强的表达能力。Protobuf 不仅允许用户定义必须(required)和可选(optinal)字段, 还允许用户定义 repeated 字段,意味着该字段可以出现 0~N 次,类似变长数组

    Dremel 格式的设计目的就是按列来存储 Protobuf 的数据。由于 repeated 字段的存在,这要比按列存储关系型的数据困难一些。一般的思路可能是用终止符表示每个 repeat 结束, 但是考虑到数据可能很稀疏,Dremel 引入了一种更为紧凑的格式。

    作为例子,下图左半边展示了数据的 schema 和 2 个 Document 的实例,右半边是序列化之后的各个列。序列化之后的列多出了 R、D 两列,分别代表 Repetition Level 和 Definition Level, 通过这两个值就能确保唯一地反序列化出原本的数据

    Repetition Level表示当前值在哪一个级别上重复。对于非 repeated 字段只要填上 trivial 值 0 即可;否则,只要这个字段可能出现重复(无论本身是 repeated 还是外层结构是 repeated),应当为 R 填上当前值在哪一层上 repeat。

    举个例子说明:对于 Name.Language.Code 我们一共有三条非 NULL 的记录。

    1. 第一个是 en-us,出现在第一个 Name 的第一个 Lanuage 的第一个 Code 里面。在此之前,这三个元素是没有重复过的,都是第一次出现。所以其 R=0
    2. 第二个是 en,出现在下一个 Language 里面。也就是说 Language 是重复的元素。Name.Language.Code 中Language 排第二个,所以其 R=2
    3. 第三个是 en-gb,出现在下一个 Name 中,Name 是重复元素,排第一个,所以其 R=1

    注意到 en-gb是属于第3个 Name 的而非第2个Name,为了表达这个事实,我们在 enen-gb中间放了一个 R=1 的 NULL。

    Definition Level是为了说明 NULL 被定义在哪一层,也就宣告那一层的 repeat 到此为止。对于非 NULL 字段只要填上 trivial 值,即数据本身所在的 level 即可。

    同样举个例子,对于 Name.Language.Country 列

    1. us非 NULL 值填上 Country 字段的 level 即 D=3
    2. NULL在 R1 内部,表示当前 Name 之内、后续所有 Language 都不含有 Country 字段。所以D为2。
    3. NULL在 R1 内部,表示当前 Document 之内、后续所有 Name 都不含有 Country 字段。所以D为1。
    4. gb非 NULL 值填上 Country 字段的 level 即 D=3
    5. NULL在 R2 内部,表示后续所有 Document 都不含有 Country 字段。所以D为0。

    可以证明,结合 R、D 两个数值一定能唯一构建出原始数据。 为了高效编解码,Dremel 在执行时首先构建出状态机,之后利用状态机处理列数据。不仅如此,状态机还会结合查询需求和数据的 structure 直接跳过无关的数据。

    状态机实现可以说是 Dremel 论文的最大贡献。但是受限于篇幅,有兴趣的同学请参考原论文。

    总结

    本文介绍了列式存储的存储结构设计。抛开种种繁复的细节,我们看到,以下这些思想或设计是具有共性的。

    1. 跳过无关的数据。从行存到列存,就是消除了无关列的扫描;ORC 中通过三层索引信息,能快速跳过无关的数据分片。
    2. 编码既是压缩,也是索引。Dremel 中用精巧的嵌套编码避免了大量 NULL 的出现;C-Store 对 distinct 值的编码同时也是对 distinct 值的索引;PowerDrill 则将字典编码用到了极致(见下一篇文章)。
    3. 假设数据不可变。无论 C-Store、Dremel 还是 ORC,它们的编码和压缩方式都完全不考虑数据更新。如果一定要有更新,暂时写到别处、读时合并即可。
    4. 数据分片。处理大规模数据,既要纵向切分也要横向切分,不必多说。

    下一篇文章中,将会结合 C-Store、MonetDB、Apache Kudu、PowerDrill 等现代列式数据库系统,侧重描述列式 DBMS 的整体架构设计以及独特的查询执行过程。 敬请期待!

    References

    1. Distinguishing Two Major Types of Column-Stores - Daniel Abadi
    2. Columnar Storage - Amazon Redshift
    3. Weaving Relations for Cache Performance - A Ailamaki, DJ DeWitt, MD Hill, M Skounakis
    4. C-Store and Google BigTable - Greg Linden
    5. The Design and Implementation of Modern Column-Oriented Database Systems - D Abadi, P Boncz, S Harizopoulos…
    6. C-store: a column-oriented DBMS - M Stonebraker, DJ Abadi, A Batkin, X Chen…
    7. Apache ORC Docs
    8. Dremel: Interactive Analysis of Web-Scale Datasets - S Melnik, A Gubarev, JJ Long, G Romer…


    最后,特别感谢 @张茄子同学为本文提出的各种建议和见解!


    本文章采用 CC BY-NC-SA 3.0许可协议。转载请注明出处!

    原文链接:https://ericfu.me/columnar-storage-overview-storage/

    有没有什么推荐的电子相册制作软件或网站? - 知乎

    $
    0
    0
    在线电子相册制作网站(1-2)
    1、bilibili云剪辑
    2、右糖在线视频制作
    Windows客户端软件(3-4)
    3、蜜蜂剪辑
    4、Adobe Premiere
    Mac OS客户端软件(5-6)
    5、iMovie
    6、Final Cut Pro X(FCPX)
    制作电子相册教程:




    在线电子相册制作网站(1-2)

    1、bilibili云剪辑

    b站2020年初推出的在线云端剪辑服务,无需导出、一键投稿到b站 ,不支持导出下载呢

    需要用74以上版本Chrome内核浏览器如:Chrome,Opera等才可以使用,无法在手机等移动设备上运行哦,滤镜、转场、变速、变声、字幕、贴纸、粒子、特效等功能:

    首先点击【资源库】-【上传】,上传你想要做成相册的文件(图片、视频、音乐),将素材拖曳到下方的时间轴,就可以按需调整持续时长、添加转场滤镜等。

    以上的操作完成后,可以点击右上角【立即投稿】,选即可一键投稿到b站喽~

    2、右糖在线视频制作

    右糖制作电子相册教程:

    【右糖教程】3分钟带你快速上手右糖视频制作_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com 图标

    右糖是一款拥有丰富模板的在线视频制作工具,模板场景涵盖:【婚礼爱情】、【生日祝福】、【成长记录】、【团建活动】、【商业宣传】、【教育党政】,外加如:哈利波特预言家日报动态报纸等【创意模板】效果;

    使用右糖,你可以轻松把照片制作为充满设计感的影集视频,支持调整视频比例(1:1、16:9、9:16)、添加字幕(更换字体、颜色等)、替换背景音乐、添加视频LOGO等功能。

    Windows客户端软件(3-4)

    3、蜜蜂剪辑

    蜜蜂剪辑制作电子相册教程:

    【蜜蜂剪辑教程】如何制作相册MV_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com 图标

    蜜蜂剪辑是一款由傲软软件公司开发的专业视频剪辑软件,Windows、Mac都可使用。软件体积小,性能稳定,对电脑配置没有要求,素材多、易操作等主要特性深受视频up主们的喜爱,只需简单的几步操作就可以剪出创意满分的短片。

    4、Adobe Premiere

    Pr 是一款常用的视频编辑软件,兼容性与功能丰富度兼顾,对电脑配置有一定要求,下载使用前需要了解自己的电脑配置是否达标。


    配置要求:
    Windows

    • Intel® Core™2 双核以上或 AMD羿龙®II以上处理器
    • Microsoft ® Windows®7 ServicePack 1或 Windows 10
    • 4GB的 RAM(建议使用8GB)
    • 4GB的可用硬盘空间用于安装;(无法安装在可移动闪存存储设备在安装过程中需要额外的可用空间)
    • 需要额外的磁盘空间预览文件、其他工作档案(建议使用10GB)
    • 1280x800+像素屏幕
    • 7200 RPM或更快的硬盘驱动器(多个快速的磁盘驱动器,最好配置RAID 0或SSD固态硬盘,推荐)
    • 声卡兼容ASIO协议或Microsoft Windows驱动程序模型
    • QuickTime的功能所需的QuickTime 7.6.6软件
    • 可选:Adobe认证的GPU卡的GPU加速性能
    • 互联网连接,并登记所必需的激活所需的软件,会员验证和访问在线服务。
    【教程】制作简单的电子相册-第②弹-_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com 图标


    MacOS

    • 多核Intel处理器
    • macOS10.10及更高版本
    • 4GB RAM内存(建议使用8GB)
    • 4GB的可用硬盘空间用于安装;(无法安装在使用区分大小写的文件系统上,或可移动闪存存储设备在安装过程中需要额外的可用空间)
    • 需要额外的磁盘空间预览文件和其他工作档案(建议使用10GB)
    • 1280x800+像素屏幕
    • 7200转硬盘驱动器(多个快速的磁盘驱动器,优选RAID 0配置,推荐)
    • QuickTime的功能所需的QuickTime 7.6.6支持组件
    • 可选: Adobe认证的GPU卡的GPU加速性能
    • 互联网连接,并登记所必需的激活所需的软件,会员验证,访问在线服务。

    Mac OS客户端软件(5-6)

    5、iMovie

    iMovie是一款由Mac编写的视频编辑软件,iMovie因为其界面功能的简洁而受到欢迎,大多数的视频编辑工作,只需要简单的点击和拖拽就能完成,同样物色了一个b站视频教程~

    超轻松学剪辑!苹果认证专家带你探索 iMovie 视频剪辑的奥秘!_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com 图标

    6、Final Cut Pro X(FCPX)

    FCPX是一款Mac专用的图像处理软件,利用它,你也可以很轻松地制作出一部精美电子相册。功能强大,最高支持8K清晰度的视频导出。

    制作电子相册教程:

    让你的照片动起来!教你如何用FCPX制作简单的视频相册_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com 图标

    和2个基本操作上手教程:

    10分钟上手Final Cut Pro X,零基础新手快速入门,视频剪辑基本操作教学_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com 图标【Final Cut Pro 教程】全系列视频拍摄、剪辑与后期制作教程丨放牛班乌托邦 出品【Final Cut Pro X 教程】【fcpx教程】​www.bilibili.com

    Viewing all 11850 articles
    Browse latest View live


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>