首页 文章 精选 留言 我的

精选列表

搜索[团队协作工具],共10000篇文章
优秀的个人博客,低调大师

Elasticsearch与Clickhouse数据存储对比 | 京东云技术团队

1 背景 京喜达技术部在社区团购场景下采用JDQ+Flink+Elasticsearch架构来打造实时数据报表。随着业务的发展Elasticsearch开始暴露出一些弊端,不适合大批量的数据查询,高频次分页导出导致宕机、存储成本较高。 Elasticsearch的查询语句维护成本较高、在聚合计算场景下出现数据不精确等问题。Clickhouse是列式数据库,列式型数据库天然适合OLAP场景,类似SQL语法降低开发和学习成本,采用快速压缩算法节省存储成本,采用向量执行引擎技术大幅缩减计算耗时。所以做此对比,进行Elasticsearch切换至Clickhouse工作。 2 OLAP OLAP意思是On-Line Analytical Processing 联机分析处理,Clickhouse就是典型的OLAP联机分析型数据库管理系统(DBMS)。OLAP主要针对数据进行复杂分析汇总操作,比如我们业务系统每天都对当天所有运输团单做汇总统计,计算出每个省区的妥投率,这个操作就属于OLAP类数据处理。与OLAP类似的还有一个OLTP类数据处理,意思是On-Line Transaction Processing 联机事务处理,在OLTP场景中用户并发操作量会很大,要求系统实时进行数据操作的响应,需要支持事务,Mysql、Oracle、SQLServer等都是OLTP型数据库。 2.1 OLTP场景的特征 宽表,即每个表包含着大量的列 对于读取,从数据库中提取相当多的行,但只提取列的一小部分。 查询相对较少(通常每台服务器每秒查询数百次或更少) 查询结果明显小于源数据。换句话说,数据经过过滤或聚合,因此结果适合于单个服务器的RAM中 绝大多数是读请求 数据以相当大的批次(> 1000行)更新,而不是单行更新;或者根本没有更新。 对于简单查询,允许延迟大约50毫秒 列中的数据相对较小:数字和短字符串(例如,每个URL 60个字节) 处理单个查询时需要高吞吐量(每台服务器每秒可达数十亿行) 事务不是必须的 对数据一致性要求低 3 特性 3.1 Elasticsearch 搜索: 适用倒排索引,每个字段都可被索引且可用于搜索,海量数据下近实时实现秒级的响应,基于Lucene的开源搜索引擎,为全文检索、高亮、搜索推荐等提供了检索能力。百度搜索、淘宝商品搜索、日志搜索等 数据分析: Elasticsearch提供了大量数据分析的API和丰富的聚合能力,支持在海量数据的基础上进行数据分析处理。统计订单量、爬虫爬取不同电商的某个商品数据,通过Elasticsearch进行数据分析(各个平台的历史价格、购买力等等) 3.2 Clickhouse 列式存储 压缩算法:采用lz4和zstd算法数据压缩,高压缩比降低数据大小,降低磁盘IO,降低CPU使用率。 索引:按照主键对数据进行排序,clickhouse能在几十毫秒内完成在大量数据对特定数据或范围数据进行查找。 多核心并行处理:ClickHouse会使用服务器上一切可用的资源,来全力完成一次查询。 支持SQL:一种基于SQL的声明式查询语言,在许多情况下与ANSI SQL标准相同。支持group by,order by,from, join,in以及非相关子查询等。 向量引擎:为了高效的使用CPU,数据不仅仅按列存储,同时还按向量(列的一部分)进行处理,这样可以更加高效地使用CPU。 实时的数据更新:数据总是已增量的方式有序的存储在MergeTree中。数据可以持续不断高效的写入到表中,不会进行任何加锁等操作。写入流量在50M-200M/s 适合在线查询:响应速度快延迟极低 丰富的聚合计算函数 4 我们的业务场景 大宽表,读大量行少量列进行指标聚合计算查询,结果集比较小。数据表都是通过Flink加工出来的宽表,列比较多。在对数据进行查询或者分析时,经常选择其中的少数几列作为维度列、对其他少数几列作为指标列,对全表或者一定范围内的数据做聚合计算。这个过程会扫描大量的行数据,但是只用了少数几列。 大量的列表分页查询与导出 Flink中数据大批量追加写入,不做更新操作 有时某个指标计算需要全表扫描做聚合计算 很少进行全文搜索 结论:数据报表、数据分析场景是典型的OLAP场景,在业务场景上列式存储数据库Clickhouse比Elasticsearch更有优势,Elasticsearch在全文搜索上更占优势,但是我们这种全文搜索场景较少。 5 成本 学习成本:Clickhouse是SQL语法比Elasticsearch的DSL更简单,几乎所有后端研发都有Mysql开发经验,比较相通学习成本更低。 开发、测试、维护成本:Clickhouse是SQL语法,与Mysql开发模式相似,更好写单元测试。Elasticsearch是使用Java API拼接查询语句,复杂度较高,不易读不易维护。 运维成本:未知,互联网上在日志场景下Clickhouse比Elasticsearch成本更低。 服务器成本: Clickhouse的数据压缩比要高于Elasticsearch,相同业务数据占用的磁盘空间ES占用磁盘空间是Clickhouse的3-10倍,平均在6倍。 见图1 Clickhouse比ES占用更少的CPU和内存 结论:同等数据量情况下,Elasticsearch使用的存储空间是Clickhouse的3-10倍,平均在6倍。综合学习、开发、测试、维护方面,Clickhouse比Elasticsearch更友好 6 测试 6.1服务器配置 以下均基于下图配置进行测试 6.2 写入压测 以下基于wms_order_sku表,通过Flink在业务平稳情况下双写Elasticsearch和Clickhouse1000W+数据进行测试得到的结果 占用CPU情况:Elasticsearch CPU一直占用很高,Clickhouse占用很少CPU。见 图2 占用内存情况:Elasticsearch 内存升高频繁GC,Clickhouse占用内存较低,比较平稳。见 图3 写入吞吐量:CH单机写入速度大约为50~200MB/s,如果写入的数据每行为1kb,写入速度为5-20W/s,图 4(写入吞吐量)为互联网上Elasticsearch与Clickhouse写入数据的对比图,同等数据样本的情况下CH写入性能是Elasticsearch的5倍。由于我们目前Flink任务为双写,考虑到会互相影响,后续补充压测结果。 结论:批量写入数据时Elasticsearch比Clickhouse更吃内存和CPU,Elasticsearch消耗的内存是Clickhouse的5.3倍,消耗的CPU是Clickhouse的27.5倍。吞吐量是Elasticsearch的5倍 6.3 查询性能(单并发测试) 以下场景是我们数据报表以及数据分析中出现的高频场景,所以基于此进行查询性能测试 数据对比情况 Clickhouse自身在集群配置差一倍的情况下查询性能差异不是很大,CH2(48C 182GB)比CH1(80C 320GB)平均慢14%。见图 5 Elasticsearch在集群配置差一倍的情况下查询性能受影响较大,ES2(46C 320GB)比ES1(78C 576GB)平均慢40%。见图 6 ES2(46C 320GB)与CH2(48C 182GB)相比,ES2与CH2 CPU核数相近,ES2内存是CH2 1.75倍的情况下,CH2的响应速度是ES2的响应速度的的12.7倍。见图 7 结论:查询数据时Elasticsearch比Clickhouse慢,在配置相近的情况下Clickhouse的响应速度是Elasticsearch的12.7倍,特别是基于时间的多字段进行聚合查询是Clickhouse比Elasticsearch快32倍。Clickhouse的查询响应素速度受集群配置大小的影响较小。 6.4 查询压测(高并发测试,数据来源于互联网) 由于准备高并发测试比较复杂耗时多,后续会基于我们的业务数据以及业务场景进行查询压力测试。以下数据来源于互联网在用户画像场景(数据量262933269)下进行的测试,与我们的场景非常类似。 结论:Clickhouse对于高并发支持的不够,官方建议最大QPS为100。高并发情况下吞吐量不如Elasticsearch更友好 7 总结 Clickhouse与Elasticsearch对比Clickhouse的优缺点。 优点: 硬件资源成本更低,同等场景下,Clickhouse占用的资源更小。 人力成本更低,新人学习、开发单测以及测试方面都更加友好,更容易介入。 OLAP场景下Clickhouse比Elasticsearch更适合,聚合计算比Elasticsearch更精缺、更快,更节省服务器计算资源。 写入性能更高,同等情况下是Elasticsearch的5倍,写入时消耗的服务器资源更小。 Elasticsearch在大量导出情况下频繁GC,严重可能导致宕机,不如Clickhouse稳定。 查询性能平均是Elasticsearch的12.7倍,Clickhouse的查询性能受服务器配置影响较小 月服务器消费相同情况Clickhouse能够得到更好的性能。 缺点: 在全文搜索上不如Elasticsearch支持的更好,在高并发查询上支持的不如Elasticsearch支持的更好 作者:京东物流 马红岩 内容来源:京东云开发者社区

优秀的个人博客,低调大师

文盘Rust —— rust连接oss | 京东云技术团队

作者:京东科技 贾世闻 对象存储是云的基础组件之一,各大云厂商都有相关产品。这里跟大家介绍一下rust与对象存储交到的基本套路和其中的一些技巧。 基本连接 我们以 [S3 sdk](https://github.com/awslabs/aws-sdk-rust)为例来说说基本的连接与操作,作者验证过aws、京东云、阿里云。主要的增删改查功能没有什么差别。 建立客户端 let shared_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::new( "LTAI5t7NPuPKsXm6UeSa1", "DGHuK03ESXQYqQ83buKMHs9NAwz", None, None, "Static", ))) .endpoint_url("http://oss-cn-beijing.aliyuncs.com") .region(Region::new("oss-cn-beijing")) .build(); let s3_config_builder = aws_sdk_s3::config::Builder::from(&shared_config); let client = aws_sdk_s3::Client::from_conf(s3_config_builder.build()); 建立Client所需要的参数主要有你需要访问的oss的AK、SK,endpoint url 以及服务所在的区域。以上信息都可以在服务商的帮助文档查询到。 对象列表 let mut obj_list = client .list_objects_v2() .bucket(bucket) .max_keys(max_keys) .prefix(prefix_str) .continuation_token(token_str); let list = obj_list.send().await.unwrap(); println!("{:?}",list.contents()); println!("{:?}",list.next_continuation_token()); 使用list_objects_v2函数返回对象列表,相比list_objects函数,list_objects_v2可以通过continuation_token和max_keys控制返回列表的长度。list.contents()返回对象列表数组, list.next_continuation_token()返回继续查询的token。 上传文件 let content = ByteStream::from("content in file".as_bytes()); let exp = aws_smithy_types::DateTime::from_secs(100); let upload = client .put_object() .bucket("bucket") .key("/test/key") .expires(exp) .body(content); upload.send().await.unwrap(); 指定bucket及对象路径,body接受ByteStream类型作为文件内容,最后设置过期时间expires,无过期时间时不指定该配置即可。 下载文件 let key = "/tmp/test/key".to_string(); let resp = client .get_object() .bucket("bucket") .key(&key) .send() .await.unwrap(); let data = resp.body.collect().await.unwrap(); let bytes = data.into_bytes(); let path = std::path::Path::new("/tmp/key") if let Some(p) = path.parent() { std::fs::create_dir_all(p).unwrap(); } let mut file = OpenOptions::new() .write(true) .truncate(true) .create(true) .open(path).unwrap(); let _ = file.write(&*bytes); file.flush().unwrap(); 通过get_object()函数获取GetObjectOutput。返回值的body 就是文件内容,将 body 转换为 bytes,最后打开文件写入即可。 删除文件 let mut keys = vec![]; let key1 = ObjectIdentifier::builder() .set_key(Some("/tmp/key1".to_string())) .build(); let key2 = ObjectIdentifier::builder() .set_key(Some("/tmp/key2".to_string())) .build() keys.push(key1); keys.push(key2) client .delete_objects() .bucket(bucket) .delete(Delete::builder().set_objects(Some(keys)).build()) .send() .await .unwrap(); delete_objects 批量删除对象。首先构建keys vector,定义要删除的对象,然后通过Delete::builder(),构建 Delete model。 大文件上传 let mut file = fs::File::open("/tmp/file_name").unwrap(); let chunk_size = 1024*1024; let mut part_number = 0; let mut upload_parts: Vec = Vec::new(); //获取上传id let multipart_upload_res: CreateMultipartUploadOutput = self .client .create_multipart_upload() .bucket("bucket") .key("/tmp/key") .send() .await.unwrap(); let upload_id = match multipart_upload_res.upload_id() { Some(id) => id, None => { return Err(anyhow!("upload id is None")); } }; //分段上传文件并记录completer_part loop { let mut buf = vec![0; chuck_size]; let read_count = file.read(&mut buf)?; part_number += 1; if read_count == 0 { break; } let body = &buf[..read_count]; let stream = ByteStream::from(body.to_vec()); let upload_part_res = self .client .upload_part() .key(key) .bucket(bucket) .upload_id(upload_id) .body(stream) .part_number(part_number) .send() .await.unwrap(); let completer_part = CompletedPart::builder() .e_tag(upload_part_res.e_tag.unwrap_or_default()) .part_number(part_number) .build(); upload_parts.push(completer_part); if read_count != chuck_size { break; } } // 完成上传文件合并 let completed_multipart_upload: CompletedMultipartUpload = CompletedMultipartUpload::builder() .set_parts(Some(upload_parts)) .build(); let _complete_multipart_upload_res = self .client .complete_multipart_upload() .bucket("bucket") .key(key) .multipart_upload(completed_multipart_upload) .upload_id(upload_id) .send() .await.unwrap(); 有时候面对大文件,比如几百兆甚至几个G的文件,为了节约带宽和内存,我才采取分段上传的方案,然后在对象存储的服务端做合并。基本流程是:指定bucket和key,获取一个上传id;按流读取文件,分段上传字节流,并记录CompletedPart;通知服务器按照CompletedPart 集合来合并文件。具体过程代码已加注释,这里不再累述。 大文件下载 let mut file = match OpenOptions::new() .truncate(true) .create(true) .write(true) .open("/tmp/target_file"); let key = "/tmp/test/key".to_string(); let resp = client .get_object() .bucket("bucket") .key(&key) .send() .await.unwrap(); let content_len = resp.content_length(); let mut byte_stream_async_reader = resp.body.into_async_read(); let mut content_len_usize: usize = content_len.try_into().unwrap(); loop { if content_len_usize > chunk_size { let mut buffer = vec![0; chunk_size]; let _ = byte_stream_async_reader.read_exact(&mut buffer).await.unwrap(); file.write_all(&buffer).unwrap(); content_len_usize -= chunk_size; continue; } else { let mut buffer = vec![0; content_len_usize]; let _ = byte_stream_async_reader.read_exact(&mut buffer).await.unwrap(); file.write_all(&buffer).unwrap(); break; } } file.flush().unwrap(); 在从对象存储服务端下载文件的过程中也会遇到大文件问题。为了节约带宽和内存,我们采取读取字节流的方式分段写入文件。首先get_object()函数获取ByteStream,通过async_reader流式读取对象字节,分段写入文件。 对象存储的相关话题今天先聊到这儿,下期见。

优秀的个人博客,低调大师

【提升团队运营效率】交易履约之订单中心实践

本文作者:京东科技-市场与平台运营中心-平台研发部,晏银喜、张学君、袁宝龙、高传江、杨迎心、游斌平、付达。 特别感谢:杨广兴、张然、姬英泽、赵宁、张彤,在系统建设过程中的贡献。 1、概述 1.1 交易履约是什么? 首先定义下什么是交易履约,交易履约是在甲乙双方达成交易产生订单后,乙方按照订单条款为甲方提供服务或交付约定物的行为。 1.2 交易履约订单中心是个什么系统? 交易履约订单中心为履约行为提供必要的系统能力支撑,交易履约订单中心记录了交易流通的过程和状态,包括交易主体、产品信息、成交金额、计费、支付、业务信息等全流程信息,为上下游提供数据标准化、全集数据查询和串联流程的功能。目前已接入的场景有:京音业绩匹配、交易数据看板、京音线上化结算、 交易流程串联等。目前交易履约订单中心年订单量 1.5 亿+,在电销、企微、金店、开放平台、用户增长等场景下,有效支持了消金、财富、保险、支付、分期商城等各大业务线的线上、线下的业务发展。 2、名词解释 数据来源:交易数据的来源,包含业务信息、联系人、数据接入协议等。 订单模版:交易履约订单中心采用泛化的格式存储交易数据,针对每个交易场景配置一个订单模版,模版上配置映射规则来解析数据。 跟单:履约订单中心接收满足某些条件的交易数据。 补单:当数据源的数据不完整或不满足业务场景需求,履约订单中心请求外部接口来补充交易数据。 推送模版:履约订单中心将交易数据推送到下游系统。 3、设计实现 3.1 整体架构 整体架构主要分成四个部分(如下图的蓝色部分),依据高内聚、低耦合的设计原则,每个分层只专注处理自己的业务逻辑,分层之间通过 MQ 消息驱动数据流转。 接收层:负责接收上游产品层的交易数据,目前支持 MQ 消息和杰夫接口两种协议。 数据处理层:负责对数据进行解析、幂等判断、交易时序判断、补充数据完整性、映射订单模型等。 数据推送层:负责对数据按照指定的规则格式化、推送到下游系统,目前支持 MQ 和杰夫两种协议。 查询服务:负责数据的查询和导出。 3.2 业务接入配置化 经过对整体架构的设计和抽象以后,我们发现各个业务线的数据处理流程具有高度的一致性:数据接收、数据处理、数据推送,而在不同的业务线产品的交易场景下会存在一些特定的差异,比如,只接收满足某些条件的交易数据、金条借款的订单与基金购买的订单模型不同、只有满足某些条件的数据才推送给结算系统等。为了提高业务的接入效率、降低接入成本,我们抽象了一套通用的数据处理流程,流程中的分支通过条件表达式来识别,同时提供一套完整的配置化页面供产品和运营同学使用,最终实现了业务接入配置化、自助化,如下图: 3.2.1 配置数据来源和订单模版 数据接收层通过配置的数据来源协议编码路由到订单模版,不同的业务产品交易场景会配置不同的订单模版。 3.2.2 配置模版内容 在数据的处理环节,我们要解决不同数据来源的数据解析、模型映射、幂等判断、时序判断等问题,不同来源的差异化我们通过配置化来支持,如下图所示的配置内容,将要解析的数据配置成 JsonPath,数据处理程序通过读取字段类型是“交易单号”的配置,来解析交易单号并完成幂等判断;通过读取“交易时间”的配置,来解析并完成数据时序的判断。 Fastjson 1.2.0 之后的版本支持 JSONPath,可以在 java 框架中当作对象查询语言(OQL)来使用。 // 解析贷款单号 Object loanId = JSONPath.extract(jsonStr, "$.jt_df_success.loanId"); // 解析还款单号 Object loanNo = JSONPath.extract(jsonStr, "$.jt_repayment.taskData.loanNo"); 3.2.3 配置表达式 前面提到过,在通用的数据流程中存在这样的分支流程:当满足一定条件时做某些事情,具体的条件根据业务场景的诉求确定,要做的事情是可以枚举和抽象的,比如过滤订单消息或者调用某个服务等。这种场景类似于一个轻量级的规则引擎,我们通过开源的 MVEL 类库来实现这个表达式引擎(特点:灵活、性能高、无类型限制)。下图所示为一个过滤消息的配置示例: MVEL 类库在订单中心主要的应用场景是对预配置的表达式进行逻辑运算。 Object result = MVEL.executeExpression("$actExt3$=='SECOBT_JD'&&$accountType$==21", context); 3.3 业务交易明细看板配置化 我们提供了通用的数据查询接口和通用的查询页面,来满足数据检索的诉求。针对不同业务产品的交易场景,下游系统都有个性化的查询诉求,比如那些字段需要作为查询条件、哪些字段要在列表页展示、哪些字段需要导出等,类似这样的个性化诉求我们一样是通过配置化来支持的,如下图的配置示例所示: 通用的查询页面通过切换业务线来联动更新查询条件和列表字段: 3.4 业务数据推送配置化 我们也具备将上游产品层的数据转发给下游系统的能力,目前支持杰夫接口和 MQ 消息两种协议,针对下游接口标准不统一的情况,我们同样通过配置化的方式来支持: 下游接口的字段可以灵活配置,推送程序运行时解析推送配置,以交易数据为上下文组装推送参数,泛化调用下游接口。 4、规划 交易履约订单中心经过 2 年的建设与推广使用,已经完成了系统的基本能力建设,通过配置化能满足多数交易场景的数据接入需求。但是对于运营效率提升、数据核对与告警等需求支持的还不完善,为了更好的发挥系统价值,进一步提升运营效率,交易履约订单中心有以下几个方面的规划: 完善配置化功能:优化配置页面交互方式,降低使用门槛、提高运营效率。 提升稳定性:建立熔断机制、应急响应机制等。 提升数字化能力:建设支持更多维度的数据看板、建立数据核对与告警机制。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册