HBase:为客户行为生成搜索点击事件统计信息

在本文中,我们将探索HBase来存储客户搜索点击事件数据,并利用其基于搜索查询字符串和构面过滤器点击来获取客户行为信息。 我们将介绍如何使用MiniHBaseCluster,HBase Schema设计,使用HBaseSink与Flume集成以存储JSON数据。



在之前的文章的基础上,

  • 客户产品搜索使用大数据进行点击分析 ,
  • Flume:使用Apache Flume收集客户产品搜索点击数据 ,
  • Hive:使用Apache Hive查询客户最喜欢的搜索查询和产品视图计数 ,
  • ElasticSearch-Hadoop:将产品视图计数和从Hadoop到ElasticSearch的客户顶部搜索查询建立索引 ,
  • Oozie:为Hive分区和ElasticSearch索引安排协调器/捆绑作业 ,
  • Spark:大数据的实时分析,可用于热门搜索查询和热门产品视图

我们已经探索了将搜索点击事件数据存储在Hadoop中并使用不同的技术对其进行查询。 在这里,我们将使用HBase实现相同的目的:

  • HBase小型集群设置
  • 使用Spring Data的HBase模板
  • HBase模式设计
  • 使用HBaseSink进行Flume集成
  • HBaseJsonSerializer序列化json数据
  • 查询过去一个小时的前10个搜索查询字符串
  • 查询过去一个小时的前10个搜索方面过滤器
  • 获取最近30天内客户的最近搜索查询字符串

searchanalytics-hbase-flume

HBase的

HBase “是Hadoop数据库,一个分布式,可扩展的大数据存储。”

HBaseMiniCluster / MiniZookeperCluster

要设置和启动小型集群,请检查HBaseServiceImpl.java

...miniZooKeeperCluster = new MiniZooKeeperCluster();miniZooKeeperCluster.setDefaultClientPort(10235);miniZooKeeperCluster.startup(new File("taget/zookeper/dfscluster_" + UUID.randomUUID().toString()).getAbsoluteFile());...Configuration config = HBaseConfiguration.create();config.set("hbase.tmp.dir", new File("target/hbasetom").getAbsolutePath());config.set("hbase.master.port", "44335");config.set("hbase.master.info.port", "44345");config.set("hbase.regionserver.port", "44435");config.set("hbase.regionserver.info.port", "44445");config.set("hbase.master.distributed.log.replay", "false");config.set("hbase.cluster.distributed", "false");config.set("hbase.master.distributed.log.splitting", "false");config.set("hbase.zookeeper.property.clientPort", "10235");config.set("zookeeper.znode.parent", "/hbase");miniHBaseCluster = new MiniHBaseCluster(config, 1);miniHBaseCluster.startMaster();...

MiniZookeeprCluster在客户端端口10235上启动,所有客户端连接都将在此端口上。 确保将hbase服务器端口配置为不与其他本地hbase服务器冲突。 在这里,我们仅在测试案例中启动一台hbase区域服务器。

使用Spring数据的HBase模板

我们将使用Spring hbase模板连接到HBase集群:

<hdp:hbase-configuration id="hbaseConfiguration" configuration-ref="hadoopConfiguration" stop-proxy="false" delete-connection="false" zk-quorum="localhost" zk-port="10235"></hdp:hbase-configuration><bean id="hbaseTemplate" class="org.springframework.data.hadoop.hbase.HBaseTemplate" p:configuration-ref="hbaseConfiguration" />

HBase表架构设计

我们具有以下格式的搜索点击事件JSON数据,

{"eventid":"24-1399386809805-629e9b5f-ff4a-4168-8664-6c8df8214aa7","hostedmachinename":"192.168.182.1330","pageurl":"http://blahblah:/5&quot ;,"customerid":24,"sessionid":"648a011d-570e-48ef-bccc-84129c9fa400","querystring":null,"sortorder":"desc","pagenumber":3,"totalhits":28,"hitsshown":7,"createdtimestampinmillis":1399386809805,"clickeddocid":"41","favourite":null,"eventidsuffix":"629e9b5f-ff4a-4168-8664-6c8df8214aa7","filters":[{"code":"searchfacettype_color_level_2","value":"Blue"},{"code":"searchfacettype_age_level_2","value":"12-18 years"}]}

处理数据的一种方法是将其直接存储在一个列族和json列下。 这样扫描json数据将变得不那么容易和灵活。 另一种选择是将其存储在一个列族下,但具有不同的列。 但是将过滤器数据存储在单列中将很难进行扫描。 下面的混合方法是将其划分为多个列族,并动态生成用于过滤器数据的列。

转换后的架构为:

{
"client:eventid" => "24-1399386809805-629e9b5f-ff4a-4168-8664-6c8df8214aa7",
"client:eventidsuffix" => "629e9b5f-ff4a-4168-8664-6c8df8214aa7",
"client:hostedmachinename" => "192.168.182.1330",
"client:pageurl" => "http://blahblah:/5",
"client:createdtimestampinmillis" => 1399386809805,
"client:cutomerid" => 24,
"client:sessionid" => "648a011d-570e-48ef-bccc-84129c9fa400",
"search:querystring" => null,
"search:sortorder" => desc,
"search:pagenumber" => 3,
"search:totalhits" => 28,
"search:hitsshown" => 7,
"search:clickeddocid" => "41",
"search:favourite" => null,
"filters:searchfacettype_color_level_2" => "Blue",
"filters:searchfacettype_age_level_2" => "12-18 years"
}

将创建以下三列系列:

  • client :存储事件的客户和客户数据特定信息。
  • search :与查询字符串和分页信息有关的搜索信息存储在此处。
  • 过滤器:为了将来支持更多构面等,并更灵活地扫描数据,将基于构面名称/代码动态创建列名称,并将列值存储为构面过滤器值。

要创建hbase表,

...TableName name = TableName.valueOf("searchclicks");HTableDescriptor desc = new HTableDescriptor(name);desc.addFamily(new HColumnDescriptor(HBaseJsonEventSerializer.COLUMFAMILY_CLIENT_BYTES));desc.addFamily(new HColumnDescriptor(HBaseJsonEventSerializer.COLUMFAMILY_SEARCH_BYTES));desc.addFamily(new HColumnDescriptor(HBaseJsonEventSerializer.COLUMFAMILY_FILTERS_BYTES));try {HBaseAdmin hBaseAdmin = new HBaseAdmin(miniHBaseCluster.getConf());hBaseAdmin.createTable(desc);hBaseAdmin.close();} catch (IOException e) {throw new RuntimeException(e);}...

在创建表时已添加了相关列族,以支持新的数据结构。 通常,建议尽量减少列族的数量,并牢记如何根据使用情况来构造数据。 根据以上示例,我们将扫描场景保持为:

  • 如果您想根据网站上的总访问量信息来检索客户或客户信息,请扫描客户家庭。
  • 扫描搜索信息以查看最终客户正在寻找哪些免费文本搜索,而导航搜索却无法满足这些需求。 请参阅在哪个页面上单击了相关产品,您是否需要加强应用才能将产品推高。
  • 扫描过滤器系列,以了解导航搜索如何为您工作。 是否为最终客户提供他们想要的产品。 查看更多点击哪些构面过滤器,您是否需要在订购中提高一点以便于客户轻松使用。
  • 应避免在家庭之间进行扫描,而应使用行键设计来获得特定的客户信息。

行键设计信息

在我们的例子中,行键设计基于customerId-timestamp -randomuuid 。 由于所有列族的行键都相同,因此我们可以使用“前缀过滤器”对仅与特定客户相关的行进行过滤。

final String eventId = customerId + "-" +  searchQueryInstruction.getCreatedTimeStampInMillis() + "-" + searchQueryInstruction.getEventIdSuffix();
...
byte[] rowKey = searchQueryInstruction.getEventId().getBytes(CHARSET_DEFAULT);
...
# 24-1399386809805-629e9b5f-ff4a-4168-8664-6c8df8214aa7

这里的每个列族都有相同的行键,并且您可以使用前缀过滤器仅扫描特定客户的行。

水槽整合

HBaseSink用于将搜索事件数据直接存储到HBase。 检查详细信息FlumeHBaseSinkServiceImpl.java

...channel = new MemoryChannel();Map<String, String> channelParamters = new HashMap<>();channelParamters.put("capacity", "100000");channelParamters.put("transactionCapacity", "1000");Context channelContext = new Context(channelParamters);Configurables.configure(channel, channelContext);channel.setName("HBaseSinkChannel-" + UUID.randomUUID());sink = new HBaseSink();sink.setName("HBaseSink-" + UUID.randomUUID());Map<String, String> paramters = new HashMap<>();paramters.put(HBaseSinkConfigurationConstants.CONFIG_TABLE, "searchclicks");paramters.put(HBaseSinkConfigurationConstants.CONFIG_COLUMN_FAMILY, new String(HBaseJsonEventSerializer.COLUMFAMILY_CLIENT_BYTES));paramters.put(HBaseSinkConfigurationConstants.CONFIG_BATCHSIZE, "1000");paramters.put(HBaseSinkConfigurationConstants.CONFIG_SERIALIZER, HBaseJsonEventSerializer.class.getName());Context sinkContext = new Context(paramters);sink.configure(sinkContext);sink.setChannel(channel);sink.start();channel.start();...

客户端列系列仅用于HBaseSink的验证。

HBaseJsonEventSerializer

创建自定义序列化器以存储JSON数据:

public class HBaseJsonEventSerializer implements HBaseEventSerializer {public static final byte[] COLUMFAMILY_CLIENT_BYTES = "client".getBytes();public static final byte[] COLUMFAMILY_SEARCH_BYTES = "search".getBytes();public static final byte[] COLUMFAMILY_FILTERS_BYTES = "filters".getBytes();...byte[] rowKey = searchQueryInstruction.getEventId().getBytes(CHARSET_DEFAULT);Put put = new Put(rowKey);// Client Inforput.add(COLUMFAMILY_CLIENT_BYTES, "eventid".getBytes(), searchQueryInstruction.getEventId().getBytes());...if (searchQueryInstruction.getFacetFilters() != null) {for (SearchQueryInstruction.FacetFilter filter : searchQueryInstruction.getFacetFilters()) {put.add(COLUMFAMILY_FILTERS_BYTES, filter.getCode().getBytes(),filter.getValue().getBytes());}}...

检查更多详细信息, HBaseJsonEventSerializer.java

事件主体从Json转换为Java bean,并进一步处理数据以在相关的列系列中进行序列化。

查询原始单元格数据

要查询原始单元格数据:

...Scan scan = new Scan();scan.addFamily(HBaseJsonEventSerializer.COLUMFAMILY_CLIENT_BYTES);scan.addFamily(HBaseJsonEventSerializer.COLUMFAMILY_SEARCH_BYTES);scan.addFamily(HBaseJsonEventSerializer.COLUMFAMILY_FILTERS_BYTES);List<String> rows = hbaseTemplate.find("searchclicks", scan,new RowMapper<String>() {@Overridepublic String mapRow(Result result, int rowNum) throws Exception {return Arrays.toString(result.rawCells());}});for (String row : rows) {LOG.debug("searchclicks table content, Table returned row: {}", row);}

检查HBaseServiceImpl.java以获得详细信息。

数据以以下格式存储在hbase中:

searchclicks table content, Table returned row: [84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/client:createdtimestampinmillis/1404832918166/Put/vlen=13/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/client:customerid/1404832918166/Put/vlen=2/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/client:eventid/1404832918166/Put/vlen=53/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/client:hostedmachinename/1404832918166/Put/vlen=16/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/client:pageurl/1404832918166/Put/vlen=19/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/client:sessionid/1404832918166/Put/vlen=36/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/filters:searchfacettype_product_type_level_2/1404832918166/Put/vlen=7/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/search:hitsshown/1404832918166/Put/vlen=2/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/search:pagenumber/1404832918166/Put/vlen=1/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/search:querystring/1404832918166/Put/vlen=13/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/search:sortorder/1404832918166/Put/vlen=3/mvcc=0, 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923/search:totalhits/1404832918166/Put/vlen=2/mvcc=0]

查询过去一个小时的前10个搜索查询字符串

要仅查询搜索字符串,我们只需要搜索列族。 要在时间范围内进行扫描,我们可以使用client列系列创建的timestampinmillis列,但这将是扩展扫描。

...Scan scan = new Scan();scan.addColumn(HBaseJsonEventSerializer.COLUMFAMILY_CLIENT_BYTES, Bytes.toBytes("createdtimestampinmillis"));scan.addColumn(HBaseJsonEventSerializer.COLUMFAMILY_SEARCH_BYTES, Bytes.toBytes("querystring"));List<String> rows = hbaseTemplate.find("searchclicks", scan,new RowMapper<String>() {@Overridepublic String mapRow(Result result, int rowNum) throws Exception {String createdtimestampinmillis = new String(result.getValue(HBaseJsonEventSerializer.COLUMFAMILY_CLIENT_BYTES, Bytes.toBytes("createdtimestampinmillis")));byte[] value = result.getValue(HBaseJsonEventSerializer.COLUMFAMILY_SEARCH_BYTES, Bytes.toBytes("querystring"));String querystring = null;if (value != null) {querystring = new String(value);}if (new DateTime(Long.valueOf(createdtimestampinmillis)).plusHours(1).compareTo(new DateTime()) == 1 && querystring != null) {return querystring;}return null;}});...//sort the keys, based on counts collection of the query strings.List<String> sortedKeys = Ordering.natural().onResultOf(Functions.forMap(counts)).immutableSortedCopy(counts.keySet());...

查询过去一个小时的前10个搜索方面过滤器

基于动态列创建,您可以扫描数据以返回点击次数最高的构面过滤器。

动态列将基于您的方面代码,这些代码可以是以下任意一种:

#searchfacettype_age_level_1#searchfacettype_color_level_2#searchfacettype_brand_level_2#searchfacettype_age_level_2for (String facetField : SearchFacetName.categoryFacetFields) {scan.addColumn(HBaseJsonEventSerializer.COLUMFAMILY_FILTERS_BYTES, Bytes.toBytes(facetField));}

检索到:

...hbaseTemplate.find("searchclicks", scan, new RowMapper<String>() {@Overridepublic String mapRow(Result result, int rowNum) throws Exception {for (String facetField : SearchFacetName.categoryFacetFields) {byte[] value = result.getValue(HBaseJsonEventSerializer.COLUMFAMILY_FILTERS_BYTES, Bytes.toBytes(facetField));if (value != null) {String facetValue = new String(value);List<String> list = columnData.get(facetField);if (list == null) {list = new ArrayList<>();list.add(facetValue);columnData.put(facetField, list);} else {list.add(facetValue);}}}return null;}});...

您将获得所有构面的完整列表,可以进一步处理数据以计算顶面并对其进行排序。 有关完整的详细信息,请检查HBaseServiceImpl.findTopTenSearchFiltersForLastAnHour

获取客户的最近搜索查询字符串

如果需要检查客户当前正在寻找什么,我们可以在“客户”和“搜索”之间的两个列族之间创建扫描。 或者,另一种方式是设计行键,以便为您提供相关信息。 在我们的例子中,行键设计基于CustomerId_timestamp _randomuuid。 由于所有列族的行键都相同,因此我们可以使用“前缀过滤器”对仅与特定客户相关的行进行过滤。

final String eventId = customerId + "-" +  searchQueryInstruction.getCreatedTimeStampInMillis() + "-" + searchQueryInstruction.getEventIdSuffix();
...
byte[] rowKey = searchQueryInstruction.getEventId().getBytes(CHARSET_DEFAULT);
...
# 84-1404832902498-7965306a-d256-4ddb-b7a8-fd19cdb99923

要扫描特定客户的数据,

...Scan scan = new Scan();scan.addColumn(HBaseJsonEventSerializer.COLUMFAMILY_SEARCH_BYTES, Bytes.toBytes("customerid"));Filter filter = new PrefixFilter(Bytes.toBytes(customerId + "-"));scan.setFilter(filter);...

有关详细信息,请检查HBaseServiceImpl.getAllSearchQueryStringsByCustomerInLastOneMonth

希望这可以帮助您入门HBase模式设计和处理数据。

翻译自: https://www.javacodegeeks.com/2014/07/hbase-generating-search-click-events-statistics-for-customer-behavior.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362753.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WildFly 8.1.0.Final上的SwitchYard 2.0.0.Alpha1入门

最近&#xff0c;我一直在关注一些热门的RedHat技术&#xff0c;在其中很多有趣的部分中&#xff0c;我找到了SwitchYard 。 过去&#xff0c;对于所有人都围绕SOA和面向服务的体系结构不屑一顾&#xff0c;这对我来说一直很奇怪&#xff0c;作为Java EE开发人员。 过去&#…

zTree 优秀的jquery树插件

zTree 优秀的jquery树插件,文档详细&#xff0c;渲染快 使用方法&#xff1a; 1、引用zTree的js和css文件   <link href"~/Content/zTree_v3/css/zTreeStyle/zTreeStyle.css" rel"stylesheet" /><script src"~/Content/zTree_v3/js/jque…

Android 小項目之---Iphone拖动图片特效 (附源码)

曾经被Iphone用手指在屏幕上滑来滑去拖动图片的操作方式吸引吗&#xff1f;在Android里头&#xff0c;这并不是什么难事。 所需要的技术点如下&#xff1a;Android.content.Context 、Android.widget.BaseAdapter、Android.widget.ImageView等通常会用在设计相册、 图片类型的选…

前端打印功能

方法一&#xff1a; html代码&#xff1a; 在要打印的内容上加入&#xff1a;<!--startprint-->和<!--endprint--> js代码&#xff1a; $(.printData).click(function(){bdhtmlwindow.document.body.innerHTML; sprnstr"<!--startprint-->"; …

同步多线程

同步多线程&#xff08;SMT&#xff09;是一种在一个CPU 的时钟周期内能够执行来自多个线程的指令的硬件多线程技术。本质上&#xff0c;同步多线程是一种将线程级并行处理&#xff08;多CPU&#xff09;转化为指令级并行处理&#xff08;同一CPU&#xff09;的方法。 同步多线…

Centos7安装Python3的方法

由于centos7原本就安装了Python2&#xff0c;而且这个Python2不能被删除&#xff0c;因为有很多系统命令&#xff0c;比如yum都要用到。 [rootVM_105_217_centos Python-3.6.2]# python Python 2.7.5 (default, Aug 4 2017, 00:39:18) [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)…

ajax请求导致status为canceled的原因

在使用layui的form表单提交以后&#xff0c;请求状态总是canceled。后来在form表单的后面添加了一行代码&#xff1a; return false; 就可以了。 文档&#xff1a;https://www.layui.com/doc/modules/form.html#onsubmit 错误&#xff1a; 解决方法&#xff1a; 总结一下&…

点击返回上一页失效

点击取消的时候&#xff0c;返回上一页&#xff0c;没有效果&#xff0c;仍然停留在当前页&#xff0c;还报500&#xff1b;改成window.history.go(-1)也不生效 代码&#xff1a; <button class"st-btn-normal st-btn-cancel cancle" onclick"javascript:wi…

如何通过7个Logback调整立即改善Java日志记录

基准测试可帮助您发现Logback在压力下的性能 日志记录对于服务器端应用程序是必不可少的&#xff0c;但这是有代价的。 令人惊讶的是&#xff0c;微小的更改和配置调整对应用程序的日志记录吞吐量有多大影响。 在这篇文章中&#xff0c;我们将根据每分钟的日志条目对Logback的性…

JS中的编码

今天发现我输入的中文变成了另外一种格式&#xff0c;查了一下&#xff0c;原来是转换成了数字编码。在这里介绍一下数字编码和base64&#xff0c;做个记录 1.出现原因&#xff1a;在开发中经常需要对用户输入的数据进行编码然后才能通过HTTP请求发送给后台&#xff0c;或者对…

【Flutter】解决依赖版本冲突

为什么会存在版本依赖问题 我们在开发flutter项目中&#xff0c;常常会依赖第三方库。flutter依赖项越多&#xff0c;就越有可能出现依赖版本冲突。 Because new_trend depends on build_runner >0.9.0 which requires SDK version >2.0.0-dev.61 <3.0.0, version sol…

安装vue-cli时报错

今天用npm安装vue-cli时一直报错&#xff0c;后面百度说用cnpm重装&#xff0c;结果还是报错&#xff0c;后面找到一个博主的&#xff0c;说是vue升级的原因&#xff0c;vue init webpack hello-world 已经替换为 vue create hello-world npm install -g vue/cli-init # vue…

CenterNet算法笔记(目标检测论文)

论文名称&#xff1a;CenterNet: Keypoint Triplets for Object Detectiontection 论文链接&#xff1a;https://arxiv.org/abs/1904.08189 代码链接&#xff1a;https://github.com/Duankaiwen/CenterNet 简介 该论文是由中科院&#xff0c;牛津大学以及华为诺亚方舟实验室联合…

elementUI的container布局设置全屏宽度

进用element UI的 container 布局&#xff0c;发现不能全屏铺满 添加了一行代码&#xff1a; .el-container{height:100%;padding:0;margin:0;width:100%;}发现高度占满全屏&#xff0c;但是宽度还没有 继续给 html,body加了 width&#xff1a;100%&#xff1b;还是没有任何…

CSS border-radius边框圆角

在CSS3中提供了对边框进行圆角设定的支持&#xff0c;可对边框1~4个角进行圆角样式设置。 目录 1. 介绍 2. value值的格式和类型 3. border-radius 1~4个参数说明 4. 在线示例 1. 介绍 1.1 圆角属性 CSS3提供了5种圆角属性 border-radius &#xff1a;同时设置4个边框的…

element实现动态路由+面包屑

要掌握&#xff1a;localStorage&#xff0c;组件封装 ​​​​ emm&#xff0c;第一次上传视频转gif的图片&#xff0c;效果不咋好。。。 视频转gif 的软件连接 http://www.zxt2007.com/downloads.html 找到一个可以截取部分视频制作gif图的软件https://soft.onlinedo.cn/gif…

Oracle最新的Java 8更新破坏了您的工具-它是如何发生的?

如果您最近一直在关注Java世界的消息&#xff0c;那么您可能听说过Oracle发布的最新Java 8构建&#xff0c;Java 8u11&#xff08;和Java 7u65&#xff09;引入了错误并破坏了一些流行的第三方工具&#xff0c;例如ZeroTurnaround的JRebel&#xff0c;Javassist&#xff0c;Goo…

为什么选择Dojo - 记Dojo中文博客正式开张

公告栏里写到&#xff1a;Dojo的高门槛一旦跨过&#xff0c;必将别无所求。含义有二&#xff1a;第一&#xff0c;Dojo难学&#xff1b;第二&#xff0c;Dojo很强大。 这也揭示了本博客的目标&#xff1a;帮助大家用好Dojo这个优秀的Ajax框架。 在回答为什么选择Dojo之前&#…

ExtJS 4.2 第一个程序

本篇介绍如何创建一个ExtJS应用程序。并通过创建目录、导入文件、编写代码及分析代码等步骤来解释第一个ExtJS程序。 目录 1. 创建程序 1.1 创建目录建议 1.2 实际目录 1.3 index.html 1.4 运行图 2. 代码分析 2.1 Ext.onReady() 2.2 Ext.create() 1. 创建程序 1.1 创建目录…

ZooKeeper,策展人以及微服务负载平衡的工作方式

Zookeeper如何确保每位工人都能愉快地从工作委托经理那里得到一些工作。 Apache ZooKeeper是注册&#xff0c;管理和发现在不同计算机上运行的服务的工具。 当我们必须处理具有许多节点的分布式系统时&#xff0c;它是技术堆栈中必不可少的成员&#xff0c;这些节点需要知道其…