├── LICENSE ├── README.md ├── SUMMARY.md ├── cover.jpg ├── deploying ├── running-spark-on-yarn.md ├── spark-standalone-mode.md └── submitting-applications.md ├── graphx-programming-guide ├── README.md ├── examples.md ├── getting-started.md ├── graph-algorithms.md ├── graph-builders.md ├── graph-operators.md ├── pregel-api.md ├── property-graph.md └── vertex-and-edge-rdds.md ├── img ├── data_parallel_vs_graph_parallel.png ├── flume.png ├── graph_analytics_pipeline.png ├── property_graph.png ├── streaming-arch.png ├── streaming-dstream-ops.png ├── streaming-dstream-window.png ├── streaming-dstream.png ├── streaming-flow.png ├── streaming-kinesis-arch.png ├── tables_and_graphs.png └── triplet.png ├── more └── spark-configuration.md ├── programming-guide ├── README.md ├── from-here.md ├── initializing-spark.md ├── linking-with-spark.md ├── rdds │ ├── README.md │ ├── actions.md │ ├── external-datasets.md │ ├── parallelized-collections.md │ ├── passing-functions-to-spark.md │ ├── rdd-operations.md │ ├── rdd-persistences.md │ ├── rdd_persistence.md │ ├── transformations.md │ └── working-with-key-value-pairs.md └── shared-variables.md ├── quick-start ├── README.md ├── standalone-applications.md ├── using-spark-shell.md └── where-to-go-from-here.md ├── spark-sql ├── README.md ├── compatibility-with-other-systems │ ├── compatibility-with-apache-hive.md │ └── migration-guide-shark-user.md ├── data-sources │ ├── README.md │ ├── hive-tables.md │ ├── jSON-datasets.md │ ├── parquet-files.md │ └── rdds.md ├── getting-started.md ├── other-sql-interfaces.md ├── performance-tuning.md ├── spark-sql-dataType-reference.md └── writing-language-integrated-relational-queries.md └── spark-streaming ├── README.md ├── a-quick-example.md ├── basic-concepts ├── README.md ├── caching-persistence.md ├── checkpointing.md ├── custom-receiver.md ├── deploying-applications.md ├── discretized-streams.md ├── flume-integration-guide.md ├── initializing-StreamingContext.md ├── input-DStreams.md ├── kafka-integration-guide.md ├── kinesis-integration.md ├── linking.md ├── monitoring-applications.md ├── output-operations-on-DStreams.md └── transformations-on-DStreams.md ├── fault-tolerance-semantics └── README.md └── performance-tuning ├── README.md ├── memory-tuning.md ├── reducing-processing-time.md └── setting-right-batch-size.md /LICENSE: -------------------------------------------------------------------------------- 1 | License 2 | 3 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 4 | 5 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 6 | 7 | 1. Definitions 8 | 9 | "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 10 | "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. 11 | "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 12 | "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 13 | "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 14 | "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 15 | "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 16 | "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 17 | "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 18 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 19 | 20 | 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 21 | 22 | to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 23 | to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 24 | to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 25 | to Distribute and Publicly Perform Adaptations. 26 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights set forth in Section 4(d). 27 | 28 | 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 29 | 30 | You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. 31 | You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in connection with the exchange of copyrighted works. 32 | If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, (iv) consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 33 | For the avoidance of doubt: 34 | 35 | Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 36 | Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and, 37 | Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c). 38 | Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 39 | 5. Representations, Warranties and Disclaimer 40 | 41 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 42 | 43 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 44 | 45 | 7. Termination 46 | 47 | This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 48 | Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 49 | 8. Miscellaneous 50 | 51 | Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 52 | Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 53 | If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 54 | No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 55 | This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 56 | The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 57 | Creative Commons Notice 58 | 59 | Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. 60 | 61 | Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. 62 | 63 | Creative Commons may be contacted at http://creativecommons.org/. 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spark 编程指南简体中文版 2 | ============================= 3 | 4 | * [Introduction](README.md) 5 | * [快速上手](quick-start/README.md) 6 | * [Spark Shell](quick-start/using-spark-shell.md) 7 | * [独立应用程序](quick-start/standalone-applications.md) 8 | * [开始翻滚吧!](quick-start/where-to-go-from-here.md) 9 | * [编程指南](programming-guide/README.md) 10 | * [引入 Spark](programming-guide/linking-with-spark.md) 11 | * [初始化 Spark](programming-guide/initializing-spark.md) 12 | * [Spark RDDs](programming-guide/rdds/README.md) 13 | * [并行集合](programming-guide/rdds/parallelized-collections.md) 14 | * [外部数据集](programming-guide/rdds/external-datasets.md) 15 | * [RDD 操作](programming-guide/rdds/rdd-operations.md) 16 | * [传递函数到 Spark](programming-guide/rdds/passing-functions-to-spark.md) 17 | * [使用键值对](programming-guide/rdds/working-with-key-value-pairs.md) 18 | * [Transformations](programming-guide/rdds/transformations.md) 19 | * [Actions](programming-guide/rdds/actions.md) 20 | * [RDD持久化](programming-guide/rdds/rdd-persistences.md) 21 | * [共享变量](programming-guide/shared-variables.md) 22 | * [从这里开始](programming-guide/from-here.md) 23 | * [Spark Streaming](spark-streaming/README.md) 24 | * [一个快速的例子](spark-streaming/a-quick-example.md) 25 | * [基本概念](spark-streaming/basic-concepts/README.md) 26 | * [关联](spark-streaming/basic-concepts/linking.md) 27 | * [初始化StreamingContext](spark-streaming/basic-concepts/initializing-StreamingContext.md) 28 | * [离散流](spark-streaming/basic-concepts/discretized-streams.md) 29 | * [输入DStreams](spark-streaming/basic-concepts/input-DStreams.md) 30 | * [DStream中的转换](spark-streaming/basic-concepts/transformations-on-DStreams.md) 31 | * [DStream的输出操作](spark-streaming/basic-concepts/output-operations-on-DStreams.md) 32 | * [缓存或持久化](spark-streaming/basic-concepts/caching-persistence.md) 33 | * [Checkpointing](spark-streaming/basic-concepts/checkpointing.md) 34 | * [部署应用程序](spark-streaming/basic-concepts/deploying-applications.md) 35 | * [监控应用程序](spark-streaming/basic-concepts/monitoring-applications.md) 36 | * [性能调优](spark-streaming/performance-tuning/README.md) 37 | * [减少批数据的执行时间](spark-streaming/performance-tuning/reducing-processing-time.md) 38 | * [设置正确的批容量](spark-streaming/performance-tuning/setting-right-batch-size.md) 39 | * [内存调优](spark-streaming/performance-tuning/memory-tuning.md) 40 | * [容错语义](spark-streaming/fault-tolerance-semantics/README.md) 41 | * [Spark SQL](spark-sql/README.md) 42 | * [开始](spark-sql/getting-started.md) 43 | * [数据源](spark-sql/data-sources/README.md) 44 | * [RDDs](spark-sql/data-sources/rdds.md) 45 | * [parquet文件](spark-sql/data-sources/parquet-files.md) 46 | * [JSON数据集](spark-sql/data-sources/jSON-datasets.md) 47 | * [Hive表](spark-sql/data-sources/hive-tables.md) 48 | * [性能调优](spark-sql/performance-tuning.md) 49 | * [其它SQL接口](spark-sql/other-sql-interfaces.md) 50 | * [编写语言集成(Language-Integrated)的相关查询](spark-sql/writing-language-integrated-relational-queries.md) 51 | * [Spark SQL数据类型](spark-sql/spark-sql-dataType-reference.md) 52 | * [GraphX编程指南](graphx-programming-guide/README.md) 53 | * [开始](graphx-programming-guide/getting-started.md) 54 | * [属性图](graphx-programming-guide/property-graph.md) 55 | * [图操作符](graphx-programming-guide/graph-operators.md) 56 | * [Pregel API](graphx-programming-guide/pregel-api.md) 57 | * [图构造者](graphx-programming-guide/graph-builders.md) 58 | * [顶点和边RDDs](graphx-programming-guide/vertex-and-edge-rdds.md) 59 | * [图算法](graphx-programming-guide/graph-algorithms.md) 60 | * [例子](graphx-programming-guide/examples.md) 61 | * [部署](deploying/submitting-applications.md) 62 | * [提交应用程序](deploying/submitting-applications.md) 63 | * [独立运行Spark](deploying/spark-standalone-mode.md) 64 | * [在yarn上运行Spark](deploying/running-spark-on-yarn.md) 65 | * [更多文档](more/spark-configuration.md) 66 | * [Spark配置](more/spark-configuration.md) 67 | * [RDD 持久化](programming-guide/rdds/rdd_persistence.md) 68 | 69 | ## Copyright 70 | 71 | 本文翻译自[Spark 官方文档](https://spark.apache.org/docs/latest/) 1.2.1 72 | 73 | ## License 74 | 75 | 本文使用的许可请查看[这里](LICENSE) 76 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [快速上手](quick-start/README.md) 5 | * [Spark Shell](quick-start/using-spark-shell.md) 6 | * [独立应用程序](quick-start/standalone-applications.md) 7 | * [开始翻滚吧!](quick-start/where-to-go-from-here.md) 8 | * [编程指南](programming-guide/README.md) 9 | * [引入 Spark](programming-guide/linking-with-spark.md) 10 | * [初始化 Spark](programming-guide/initializing-spark.md) 11 | * [Spark RDDs](programming-guide/rdds/README.md) 12 | * [并行集合](programming-guide/rdds/parallelized-collections.md) 13 | * [外部数据集](programming-guide/rdds/external-datasets.md) 14 | * [RDD 操作](programming-guide/rdds/rdd-operations.md) 15 | * [传递函数到 Spark](programming-guide/rdds/passing-functions-to-spark.md) 16 | * [使用键值对](programming-guide/rdds/working-with-key-value-pairs.md) 17 | * [Transformations](programming-guide/rdds/transformations.md) 18 | * [Actions](programming-guide/rdds/actions.md) 19 | * [RDD持久化](programming-guide/rdds/rdd-persistences.md) 20 | * [共享变量](programming-guide/shared-variables.md) 21 | * [从这里开始](programming-guide/from-here.md) 22 | * [Spark Streaming](spark-streaming/README.md) 23 | * [一个快速的例子](spark-streaming/a-quick-example.md) 24 | * [基本概念](spark-streaming/basic-concepts/README.md) 25 | * [关联](spark-streaming/basic-concepts/linking.md) 26 | * [初始化StreamingContext](spark-streaming/basic-concepts/initializing-StreamingContext.md) 27 | * [离散流](spark-streaming/basic-concepts/discretized-streams.md) 28 | * [输入DStreams](spark-streaming/basic-concepts/input-DStreams.md) 29 | * [DStream中的转换](spark-streaming/basic-concepts/transformations-on-DStreams.md) 30 | * [DStream的输出操作](spark-streaming/basic-concepts/output-operations-on-DStreams.md) 31 | * [缓存或持久化](spark-streaming/basic-concepts/caching-persistence.md) 32 | * [Checkpointing](spark-streaming/basic-concepts/checkpointing.md) 33 | * [部署应用程序](spark-streaming/basic-concepts/deploying-applications.md) 34 | * [监控应用程序](spark-streaming/basic-concepts/monitoring-applications.md) 35 | * [性能调优](spark-streaming/performance-tuning/README.md) 36 | * [减少批数据的执行时间](spark-streaming/performance-tuning/reducing-processing-time.md) 37 | * [设置正确的批容量](spark-streaming/performance-tuning/setting-right-batch-size.md) 38 | * [内存调优](spark-streaming/performance-tuning/memory-tuning.md) 39 | * [容错语义](spark-streaming/fault-tolerance-semantics/README.md) 40 | * [Spark SQL](spark-sql/README.md) 41 | * [开始](spark-sql/getting-started.md) 42 | * [数据源](spark-sql/data-sources/README.md) 43 | * [RDDs](spark-sql/data-sources/rdds.md) 44 | * [parquet文件](spark-sql/data-sources/parquet-files.md) 45 | * [JSON数据集](spark-sql/data-sources/jSON-datasets.md) 46 | * [Hive表](spark-sql/data-sources/hive-tables.md) 47 | * [性能调优](spark-sql/performance-tuning.md) 48 | * [其它SQL接口](spark-sql/other-sql-interfaces.md) 49 | * [编写语言集成(Language-Integrated)的相关查询](spark-sql/writing-language-integrated-relational-queries.md) 50 | * [Spark SQL数据类型](spark-sql/spark-sql-dataType-reference.md) 51 | * [GraphX编程指南](graphx-programming-guide/README.md) 52 | * [开始](graphx-programming-guide/getting-started.md) 53 | * [属性图](graphx-programming-guide/property-graph.md) 54 | * [图操作符](graphx-programming-guide/graph-operators.md) 55 | * [Pregel API](graphx-programming-guide/pregel-api.md) 56 | * [图构造者](graphx-programming-guide/graph-builders.md) 57 | * [顶点和边RDDs](graphx-programming-guide/vertex-and-edge-rdds.md) 58 | * [图算法](graphx-programming-guide/graph-algorithms.md) 59 | * [例子](graphx-programming-guide/examples.md) 60 | * [部署](deploying/submitting-applications.md) 61 | * [提交应用程序](deploying/submitting-applications.md) 62 | * [独立运行Spark](deploying/spark-standalone-mode.md) 63 | * [在yarn上运行Spark](deploying/running-spark-on-yarn.md) 64 | * [更多文档](more/spark-configuration.md) 65 | * [Spark配置](more/spark-configuration.md) 66 | * [RDD 持久化](programming-guide/rdds/rdd_persistence.md) 67 | -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/cover.jpg -------------------------------------------------------------------------------- /deploying/running-spark-on-yarn.md: -------------------------------------------------------------------------------- 1 | # 在YARN上运行Spark 2 | 3 | ## 配置 4 | 5 | 大部分为`Spark on YARN`模式提供的配置与其它部署模式提供的配置相同。下面这些是为`Spark on YARN`模式提供的配置。 6 | 7 | ### Spark属性 8 | 9 | Property Name | Default | Meaning 10 | --- | --- | --- 11 | spark.yarn.applicationMaster.waitTries | 10 | ApplicationMaster等待Spark master的次数以及SparkContext初始化尝试的次数 12 | spark.yarn.submit.file.replication | HDFS默认的复制次数(3) | 上传到HDFS的文件的HDFS复制水平。这些文件包括Spark jar、app jar以及任何分布式缓存文件/档案 13 | spark.yarn.preserve.staging.files | false | 设置为true,则在作业结束时保留阶段性文件(Spark jar、app jar以及任何分布式缓存文件)而不是删除它们 14 | spark.yarn.scheduler.heartbeat.interval-ms | 5000 | Spark application master给YARN ResourceManager发送心跳的时间间隔(ms) 15 | spark.yarn.max.executor.failures | numExecutors * 2,最小为3 | 失败应用程序之前最大的执行失败数 16 | spark.yarn.historyServer.address | (none) | Spark历史服务器(如host.com:18080)的地址。这个地址不应该包含一个模式(http://)。默认情况下没有设置值,这是因为该选项是一个可选选项。当Spark应用程序完成从ResourceManager UI到Spark历史服务器UI的连接时,这个地址从YARN ResourceManager得到 17 | spark.yarn.dist.archives | (none) | 提取逗号分隔的档案列表到每个执行器的工作目录 18 | spark.yarn.dist.files | (none) | 放置逗号分隔的文件列表到每个执行器的工作目录 19 | spark.yarn.executor.memoryOverhead | executorMemory * 0.07,最小384 | 分配给每个执行器的堆内存大小(以MB为单位)。它是VM开销、interned字符串或者其它本地开销占用的内存。这往往随着执行器大小而增长。(典型情况下是6%-10%) 20 | spark.yarn.driver.memoryOverhead | driverMemory * 0.07,最小384 | 分配给每个driver的堆内存大小(以MB为单位)。它是VM开销、interned字符串或者其它本地开销占用的内存。这往往随着执行器大小而增长。(典型情况下是6%-10%) 21 | spark.yarn.queue | default | 应用程序被提交到的YARN队列的名称 22 | spark.yarn.jar | (none) | Spark jar文件的位置,覆盖默认的位置。默认情况下,Spark on YARN将会用到本地安装的Spark jar。但是Spark jar也可以HDFS中的一个公共位置。这允许YARN缓存它到节点上,而不用在每次运行应用程序时都需要分配。指向HDFS中的jar包,可以这个参数为"hdfs:///some/path" 23 | spark.yarn.access.namenodes | (none) | 你的Spark应用程序访问的HDFS namenode列表。例如,`spark.yarn.access.namenodes=hdfs://nn1.com:8032,hdfs://nn2.com:8032`,Spark应用程序必须访问namenode列表,Kerberos必须正确配置来访问它们。Spark获得namenode的安全令牌,这样Spark应用程序就能够访问这些远程的HDFS集群。 24 | spark.yarn.containerLauncherMaxThreads | 25 | 为了启动执行者容器,应用程序master用到的最大线程数 25 | spark.yarn.appMasterEnv.[EnvironmentVariableName] | (none) | 添加通过`EnvironmentVariableName`指定的环境变量到Application Master处理YARN上的启动。用户可以指定多个该设置,从而设置多个环境变量。在yarn-cluster模式下,这控制Spark driver的环境。在yarn-client模式下,这仅仅控制执行器启动者的环境。 26 | 27 | ## 在YARN上启动Spark 28 | 29 | 确保`HADOOP_CONF_DIR`或`YARN_CONF_DIR`指向的目录包含Hadoop集群的(客户端)配置文件。这些配置用于写数据到dfs和连接到YARN ResourceManager。 30 | 31 | 有两种部署模式可以用来在YARN上启动Spark应用程序。在yarn-cluster模式下,Spark driver运行在application master进程中,这个进程被集群中的YARN所管理,客户端会在初始化应用程序 32 | 之后关闭。在yarn-client模式下,driver运行在客户端进程中,application master仅仅用来向YARN请求资源。 33 | 34 | 和Spark单独模式以及Mesos模式不同,在这些模式中,master的地址由"master"参数指定,而在YARN模式下,ResourceManager的地址从Hadoop配置得到。因此master参数是简单的`yarn-client`和`yarn-cluster`。 35 | 36 | 在yarn-cluster模式下启动Spark应用程序。 37 | 38 | ```shell 39 | ./bin/spark-submit --class path.to.your.Class --master yarn-cluster [options] [app options] 40 | ``` 41 | 42 | 例子: 43 | ```shell 44 | $ ./bin/spark-submit --class org.apache.spark.examples.SparkPi \ 45 | --master yarn-cluster \ 46 | --num-executors 3 \ 47 | --driver-memory 4g \ 48 | --executor-memory 2g \ 49 | --executor-cores 1 \ 50 | --queue thequeue \ 51 | lib/spark-examples*.jar \ 52 | 10 53 | ``` 54 | 55 | 以上启动了一个YARN客户端程序用来启动默认的 Application Master,然后SparkPi会作为Application Master的子线程运行。客户端会定期的轮询Application Master用于状态更新并将 56 | 更新显示在控制台上。一旦你的应用程序运行完毕,客户端就会退出。 57 | 58 | 在yarn-client模式下启动Spark应用程序,运行下面的shell脚本 59 | 60 | ```shell 61 | $ ./bin/spark-shell --master yarn-client 62 | ``` 63 | 64 | ### 添加其它的jar 65 | 66 | 在yarn-cluster模式下,driver运行在不同的机器上,所以离开了保存在本地客户端的文件,`SparkContext.addJar`将不会工作。为了使`SparkContext.addJar`用到保存在客户端的文件, 67 | 在启动命令中加上`--jars`选项。 68 | ```shell 69 | $ ./bin/spark-submit --class my.main.Class \ 70 | --master yarn-cluster \ 71 | --jars my-other-jar.jar,my-other-other-jar.jar 72 | my-main-jar.jar 73 | app_arg1 app_arg2 74 | ``` 75 | 76 | ## 注意事项 77 | 78 | - 在Hadoop 2.2之前,YARN不支持容器核的资源请求。因此,当运行早期的版本时,通过命令行参数指定的核的数量无法传递给YARN。在调度决策中,核请求是否兑现取决于用哪个调度器以及 79 | 如何配置调度器。 80 | - Spark executors使用的本地目录将会是YARN配置(yarn.nodemanager.local-dirs)的本地目录。如果用户指定了`spark.local.dir`,它将被忽略。 81 | - `--files`和`--archives`选项支持指定带 * # * 号文件名。例如,你能够指定`--files localtest.txt#appSees.txt`,它上传你在本地命名为`localtest.txt`的文件到HDFS,但是将会链接为名称`appSees.txt`。当你的应用程序运行在YARN上时,你应该使用`appSees.txt`去引用该文件。 82 | - 如果你在yarn-cluster模式下运行`SparkContext.addJar`,并且用到了本地文件, `--jars`选项允许`SparkContext.addJar`函数能够工作。如果你正在使用 HDFS, HTTP, HTTPS或FTP,你不需要用到该选项 83 | 84 | -------------------------------------------------------------------------------- /deploying/spark-standalone-mode.md: -------------------------------------------------------------------------------- 1 | # Spark独立部署模式 2 | 3 | ## 安装Spark独立模式集群 4 | 5 | 安装Spark独立模式,你只需要将Spark的编译版本简单的放到集群的每个节点。你可以获得每个稳定版本的预编译版本,也可以自己编译。 6 | 7 | ## 手动启动集群 8 | 9 | 你能够通过下面的方式启动独立的master服务器。 10 | 11 | ```shell 12 | ./sbin/start-master.sh 13 | ``` 14 | 15 | 一旦启动,master将会为自己打印出`spark://HOST:PORT` URL,你能够用它连接到workers或者作为"master"参数传递给`SparkContext`。你也可以在master web UI上发现这个URL, 16 | master web UI默认的地址是`http://localhost:8080`。 17 | 18 | 相同的,你也可以启动一个或者多个workers或者将它们连接到master。 19 | 20 | ```shell 21 | ./bin/spark-class org.apache.spark.deploy.worker.Worker spark://IP:PORT 22 | ``` 23 | 24 | 一旦你启动了一个worker,查看master web UI。你可以看到新的节点列表以及节点的CPU数以及内存。 25 | 26 | 下面的配置参数可以传递给master和worker。 27 | 28 | Argument | Meaning 29 | --- | --- 30 | -h HOST, --host HOST | 监听的主机名 31 | -i HOST, --ip HOST | 同上,已经被淘汰 32 | -p PORT, --port PORT | 监听的服务的端口(master默认是7077,worker随机) 33 | --webui-port PORT | web UI的端口(master默认是8080,worker默认是8081) 34 | -c CORES, --cores CORES | Spark应用程序可以使用的CPU核数(默认是所有可用);这个选项仅在worker上可用 35 | -m MEM, --memory MEM | Spark应用程序可以使用的内存数(默认情况是你的机器内存数减去1g);这个选项仅在worker上可用 36 | -d DIR, --work-dir DIR | 用于暂存空间和工作输出日志的目录(默认是SPARK_HOME/work);这个选项仅在worker上可用 37 | --properties-file FILE | 自定义的Spark配置文件的加载目录(默认是conf/spark-defaults.conf) 38 | 39 | ## 集群启动脚本 40 | 41 | 为了用启动脚本启动Spark独立集群,你应该在你的Spark目录下建立一个名为`conf/slaves`的文件,这个文件必须包含所有你要启动的Spark worker所在机器的主机名,一行一个。如果 42 | `conf/slaves`不存在,启动脚本默认为单个机器(localhost),这台机器对于测试是有用的。注意,master机器通过ssh访问所有的worker。在默认情况下,SSH是并行运行,需要设置无密码(采用私有密钥)的访问。 43 | 如果你没有设置为无密码访问,你可以设置环境变量`SPARK_SSH_FOREGROUND`,为每个worker提供密码。 44 | 45 | 一旦你设置了这个文件,你就可以通过下面的shell脚本启动或者停止你的集群。 46 | 47 | - sbin/start-master.sh:在机器上启动一个master实例 48 | - sbin/start-slaves.sh:在每台机器上启动一个slave实例 49 | - sbin/start-all.sh:同时启动一个master实例和所有slave实例 50 | - sbin/stop-master.sh:停止master实例 51 | - sbin/stop-slaves.sh:停止所有slave实例 52 | - sbin/stop-all.sh:停止master实例和所有slave实例 53 | 54 | 注意,这些脚本必须在你的Spark master运行的机器上执行,而不是在你的本地机器上面。 55 | 56 | 你可以在`conf/spark-env.sh`中设置环境变量进一步配置集群。利用`conf/spark-env.sh.template`创建这个文件,然后将它复制到所有的worker机器上使设置有效。下面的设置可以起作用: 57 | 58 | Environment Variable | Meaning 59 | --- | --- 60 | SPARK_MASTER_IP | 绑定master到一个指定的ip地址 61 | SPARK_MASTER_PORT | 在不同的端口上启动master(默认是7077) 62 | SPARK_MASTER_WEBUI_PORT | master web UI的端口(默认是8080) 63 | SPARK_MASTER_OPTS | 应用到master的配置属性,格式是 "-Dx=y"(默认是none),查看下面的表格的选项以组成一个可能的列表 64 | SPARK_LOCAL_DIRS | Spark中暂存空间的目录。包括map的输出文件和存储在磁盘上的RDDs(including map output files and RDDs that get stored on disk)。这必须在一个快速的、你的系统的本地磁盘上。它可以是一个逗号分隔的列表,代表不同磁盘的多个目录 65 | SPARK_WORKER_CORES | Spark应用程序可以用到的核心数(默认是所有可用) 66 | SPARK_WORKER_MEMORY | Spark应用程序用到的内存总数(默认是内存总数减去1G)。注意,每个应用程序个体的内存通过`spark.executor.memory`设置 67 | SPARK_WORKER_PORT | 在指定的端口上启动Spark worker(默认是随机) 68 | SPARK_WORKER_WEBUI_PORT | worker UI的端口(默认是8081) 69 | SPARK_WORKER_INSTANCES | 每台机器运行的worker实例数,默认是1。如果你有一台非常大的机器并且希望运行多个worker,你可以设置这个数大于1。如果你设置了这个环境变量,确保你也设置了`SPARK_WORKER_CORES`环境变量用于限制每个worker的核数或者每个worker尝试使用所有的核。 70 | SPARK_WORKER_DIR | Spark worker运行目录,该目录包括日志和暂存空间(默认是SPARK_HOME/work) 71 | SPARK_WORKER_OPTS | 应用到worker的配置属性,格式是 "-Dx=y"(默认是none),查看下面表格的选项以组成一个可能的列表 72 | SPARK_DAEMON_MEMORY | 分配给Spark master和worker守护进程的内存(默认是512m) 73 | SPARK_DAEMON_JAVA_OPTS | Spark master和worker守护进程的JVM选项,格式是"-Dx=y"(默认为none) 74 | SPARK_PUBLIC_DNS | Spark master和worker公共的DNS名(默认是none) 75 | 76 | 注意,启动脚本还不支持windows。为了在windows上启动Spark集群,需要手动启动master和workers。 77 | 78 | `SPARK_MASTER_OPTS`支持一下的系统属性: 79 | 80 | Property Name | Default | Meaning 81 | --- | --- | --- 82 | spark.deploy.retainedApplications | 200 | 展示完成的应用程序的最大数目。老的应用程序会被删除以满足该限制 83 | spark.deploy.retainedDrivers | 200 | 展示完成的drivers的最大数目。老的应用程序会被删除以满足该限制 84 | spark.deploy.spreadOut | true | 这个选项控制独立的集群管理器是应该跨节点传递应用程序还是应努力将程序整合到尽可能少的节点上。在HDFS中,传递程序是数据本地化更好的选择,但是,对于计算密集型的负载,整合会更有效率。 85 | spark.deploy.defaultCores | (infinite) | 在Spark独立模式下,给应用程序的默认核数(如果没有设置`spark.cores.max`)。如果没有设置,应用程序总数获得所有可用的核,除非设置了`spark.cores.max`。在共享集群上设置较低的核数,可用防止用户默认抓住整个集群。 86 | spark.worker.timeout | 60 | 独立部署的master认为worker失败(没有收到心跳信息)的间隔时间。 87 | 88 | `SPARK_WORKER_OPTS`支持的系统属性: 89 | 90 | Property Name | Default | Meaning 91 | --- | --- | --- 92 | spark.worker.cleanup.enabled | false | 周期性的清空worker/应用程序目录。注意,这仅仅影响独立部署模式。不管应用程序是否还在执行,用于程序目录都会被清空 93 | spark.worker.cleanup.interval | 1800 (30分) | 在本地机器上,worker清空老的应用程序工作目录的时间间隔 94 | spark.worker.cleanup.appDataTtl | 7 * 24 * 3600 (7天) | 每个worker中应用程序工作目录的保留时间。这个时间依赖于你可用磁盘空间的大小。应用程序日志和jar包上传到每个应用程序的工作目录。随着时间的推移,工作目录会很快的填满磁盘空间,特别是如果你运行的作业很频繁。 95 | 96 | ## 连接一个应用程序到集群中 97 | 98 | 为了在Spark集群中运行一个应用程序,简单地传递`spark://IP:PORT` URL到[SparkContext](http://spark.apache.org/docs/latest/programming-guide.html#initializing-spark) 99 | 100 | 为了在集群上运行一个交互式的Spark shell,运行一下命令: 101 | 102 | ```shell 103 | ./bin/spark-shell --master spark://IP:PORT 104 | ``` 105 | 你也可以传递一个选项`--total-executor-cores `去控制spark-shell的核数。 106 | 107 | ## 启动Spark应用程序 108 | 109 | [spark-submit脚本](submitting-applications.md)支持最直接的提交一个Spark应用程序到集群。对于独立部署的集群,Spark目前支持两种部署模式。在`client`模式中,driver启动进程与 110 | 客户端提交应用程序所在的进程是同一个进程。然而,在`cluster`模式中,driver在集群的某个worker进程中启动,只有客户端进程完成了提交任务,它不会等到应用程序完成就会退出。 111 | 112 | 如果你的应用程序通过Spark submit启动,你的应用程序jar包将会自动分发到所有的worker节点。对于你的应用程序依赖的其它jar包,你应该用`--jars`符号指定(如` --jars jar1,jar2`)。 113 | 114 | 另外,`cluster`模式支持自动的重启你的应用程序(如果程序一非零的退出码退出)。为了用这个特征,当启动应用程序时,你可以传递`--supervise`符号到`spark-submit`。如果你想杀死反复失败的应用, 115 | 你可以通过如下的方式: 116 | 117 | ```shell 118 | ./bin/spark-class org.apache.spark.deploy.Client kill 119 | ``` 120 | 121 | 你可以在独立部署的Master web UI(http://:8080)中找到driver ID。 122 | 123 | ## 资源调度 124 | 125 | 独立部署的集群模式仅仅支持简单的FIFO调度器。然而,为了允许多个并行的用户,你能够控制每个应用程序能用的最大资源数。在默认情况下,它将获得集群的所有核,这只有在某一时刻只 126 | 允许一个应用程序才有意义。你可以通过`spark.cores.max`在[SparkConf](http://spark.apache.org/docs/latest/configuration.html#spark-properties)中设置核数。 127 | 128 | ```scala 129 | val conf = new SparkConf() 130 | .setMaster(...) 131 | .setAppName(...) 132 | .set("spark.cores.max", "10") 133 | val sc = new SparkContext(conf) 134 | ``` 135 | 另外,你可以在集群的master进程中配置`spark.deploy.defaultCores`来改变默认的值。在`conf/spark-env.sh`添加下面的行: 136 | 137 | ```properties 138 | export SPARK_MASTER_OPTS="-Dspark.deploy.defaultCores=" 139 | ``` 140 | 141 | 这在用户没有配置最大核数的共享集群中是有用的。 142 | 143 | ## 高可用 144 | 145 | 默认情况下,独立的调度集群对worker失败是有弹性的(在Spark本身的范围内是有弹性的,对丢失的工作通过转移它到另外的worker来解决)。然而,调度器通过master去执行调度决定, 146 | 这会造成单点故障:如果master死了,新的应用程序就无法创建。为了避免这个,我们有两个高可用的模式。 147 | 148 | ### 用ZooKeeper的备用master 149 | 150 | 利用ZooKeeper去支持领导选举以及一些状态存储,你能够在你的集群中启动多个master,这些master连接到同一个ZooKeeper实例上。一个被选为“领导”,其它的保持备用模式。如果当前 151 | 的领导死了,另一个master将会被选中,恢复老master的状态,然后恢复调度。整个的恢复过程大概需要1到2分钟。注意,这个恢复时间仅仅会影响调度新的应用程序-运行在失败master中的 152 | 应用程序不受影响。 153 | 154 | #### 配置 155 | 156 | 为了开启这个恢复模式,你可以用下面的属性在`spark-env`中设置`SPARK_DAEMON_JAVA_OPTS`。 157 | 158 | System property | Meaning 159 | --- | --- 160 | spark.deploy.recoveryMode | 设置ZOOKEEPER去启动备用master模式(默认为none) 161 | spark.deploy.zookeeper.url | zookeeper集群url(如192.168.1.100:2181,192.168.1.101:2181) 162 | spark.deploy.zookeeper.dir | zookeeper保存恢复状态的目录(默认是/spark) 163 | 164 | 可能的陷阱:如果你在集群中有多个masters,但是没有用zookeeper正确的配置这些masters,这些masters不会发现彼此,会认为它们都是leaders。这将会造成一个不健康的集群状态(因为所有的master都会独立的调度)。 165 | 166 | #### 细节 167 | 168 | zookeeper集群启动之后,开启高可用是简单的。在相同的zookeeper配置(zookeeper URL和目录)下,在不同的节点上简单地启动多个master进程。master可以随时添加和删除。 169 | 170 | 为了调度新的应用程序或者添加worker到集群,它需要知道当前leader的IP地址。这可以通过简单的传递一个master列表来完成。例如,你可能启动你的SparkContext指向`spark://host1:port1,host2:port2`。 171 | 这将造成你的SparkContext同时注册这两个master-如果`host1`死了,这个配置文件将一直是正确的,因为我们将找到新的leader-`host2`。 172 | 173 | "registering with a Master"和正常操作之间有重要的区别。当启动时,一个应用程序或者worker需要能够发现和注册当前的leader master。一旦它成功注册,它就在系统中了。如果 174 | 错误发生,新的leader将会接触所有之前注册的应用程序和worker,通知他们领导关系的变化,所以它们甚至不需要事先知道新启动的leader的存在。 175 | 176 | 由于这个属性的存在,新的master可以在任何时候创建。你唯一需要担心的问题是新的应用程序和workers能够发现它并将它注册进来以防它成为leader master。 177 | 178 | ### 用本地文件系统做单节点恢复 179 | 180 | zookeeper是生产环境下最好的选择,但是如果你想在master死掉后重启它,`FILESYSTEM`模式可以解决。当应用程序和worker注册,它们拥有足够的状态写入提供的目录,以至于在重启master 181 | 进程时它们能够恢复。 182 | 183 | #### 配置 184 | 185 | 为了开启这个恢复模式,你可以用下面的属性在`spark-env`中设置`SPARK_DAEMON_JAVA_OPTS`。 186 | 187 | System property | Meaning 188 | --- | --- 189 | spark.deploy.recoveryMode | 设置为FILESYSTEM开启单节点恢复模式(默认为none) 190 | spark.deploy.recoveryDirectory | 用来恢复状态的目录 191 | 192 | #### 细节 193 | 194 | - 这个解决方案可以和监控器/管理器(如[monit](http://mmonit.com/monit/))相配合,或者仅仅通过重启开启手动恢复。 195 | - 虽然文件系统的恢复似乎比没有做任何恢复要好,但对于特定的开发或实验目的,这种模式可能是次优的。特别是,通过`stop-master.sh`杀掉master不会清除它的恢复状态,所以,不管你何时启动一个新的master,它都将进入恢复模式。这可能使启动时间增加到1分钟。 196 | - 虽然它不是官方支持的方式,你也可以创建一个NFS目录作为恢复目录。如果原始的master节点完全死掉,你可以在不同的节点启动master,它可以正确的恢复之前注册的所有应用程序和workers。未来的应用程序会发现这个新的master。 -------------------------------------------------------------------------------- /deploying/submitting-applications.md: -------------------------------------------------------------------------------- 1 | # 提交应用程序 2 | 3 | 在Spark bin目录下的`spark-submit`可以用来在集群上启动应用程序。它可以通过统一的接口使用Spark支持的所有[集群管理器](https://spark.apache.org/docs/latest/cluster-overview.html#cluster-manager-types) 4 | ,所有你不必为每一个管理器做相应的配置。 5 | 6 | ## 用spark-submit启动应用程序 7 | 8 | `bin/spark-submit`脚本负责建立包含Spark以及其依赖的类路径(classpath),它支持不同的集群管理器以及Spark支持的加载模式。 9 | 10 | ```shell 11 | ./bin/spark-submit \ 12 | --class 13 | --master \ 14 | --deploy-mode \ 15 | --conf = \ 16 | ... # other options 17 | \ 18 | [application-arguments] 19 | ``` 20 | 21 | 一些常用的选项是: 22 | 23 | - `--class`:你的应用程序的入口点(如org.apache.spark.examples.SparkPi) 24 | - `--master`:集群的master URL(如spark://23.195.26.187:7077) 25 | - `--deploy-mode`:在worker节点部署你的driver(cluster)或者本地作为外部客户端(client)。默认是client。 26 | - `--conf`:任意的Spark配置属性,格式是key=value。 27 | - `application-jar`:包含应用程序以及其依赖的jar包的路径。这个URL必须在集群中全局可见,例如,存在于所有节点的`hdfs://`路径或`file://`路径 28 | - `application-arguments`:传递给主类的主方法的参数 29 | 30 | 一个通用的部署策略是从网关集群提交你的应用程序,这个网关机器和你的worker集群物理上协作。在这种设置下,`client`模式是适合的。在`client`模式下,driver直接在`spark-submit`进程 31 | 中启动,而这个进程直接作为集群的客户端。应用程序的输入和输出都和控制台相连接。因此,这种模式特别适合涉及REPL的应用程序。 32 | 33 | 另一种选择,如果你的应用程序从一个和worker机器相距很远的机器上提交,通常情况下用`cluster`模式减少drivers和executors的网络迟延。注意,`cluster`模式目前不支持独立集群、 34 | mesos集群以及python应用程序。 35 | 36 | 有几个我们使用的集群管理器特有的可用选项。例如,在Spark独立集群的`cluster`模式下,你也可以指定`--supervise`用来确保driver自动重启(如果它因为非零退出码失败)。 37 | 为了列举spark-submit所有的可用选项,用`--help`运行它。 38 | 39 | ```shell 40 | # Run application locally on 8 cores 41 | ./bin/spark-submit \ 42 | --class org.apache.spark.examples.SparkPi \ 43 | --master local[8] \ 44 | /path/to/examples.jar \ 45 | 100 46 | 47 | # Run on a Spark Standalone cluster in client deploy mode 48 | ./bin/spark-submit \ 49 | --class org.apache.spark.examples.SparkPi \ 50 | --master spark://207.184.161.138:7077 \ 51 | --executor-memory 20G \ 52 | --total-executor-cores 100 \ 53 | /path/to/examples.jar \ 54 | 1000 55 | 56 | # Run on a Spark Standalone cluster in cluster deploy mode with supervise 57 | ./bin/spark-submit \ 58 | --class org.apache.spark.examples.SparkPi \ 59 | --master spark://207.184.161.138:7077 \ 60 | --deploy-mode cluster 61 | --supervise 62 | --executor-memory 20G \ 63 | --total-executor-cores 100 \ 64 | /path/to/examples.jar \ 65 | 1000 66 | 67 | # Run on a YARN cluster 68 | export HADOOP_CONF_DIR=XXX 69 | ./bin/spark-submit \ 70 | --class org.apache.spark.examples.SparkPi \ 71 | --master yarn-cluster \ # can also be `yarn-client` for client mode 72 | --executor-memory 20G \ 73 | --num-executors 50 \ 74 | /path/to/examples.jar \ 75 | 1000 76 | 77 | # Run a Python application on a Spark Standalone cluster 78 | ./bin/spark-submit \ 79 | --master spark://207.184.161.138:7077 \ 80 | examples/src/main/python/pi.py \ 81 | 1000 82 | ``` 83 | 84 | ## Master URLs 85 | 86 | 传递给Spark的url可以用下面的模式 87 | 88 | Master URL | Meaning 89 | --- | --- 90 | local | 用一个worker线程本地运行Spark 91 | local[K] | 用k个worker线程本地运行Spark(理想情况下,设置这个值为你的机器的核数) 92 | local[*] | 用尽可能多的worker线程本地运行Spark 93 | spark://HOST:PORT | 连接到给定的Spark独立部署集群master。端口必须是master配置的端口,默认是7077 94 | mesos://HOST:PORT | 连接到给定的mesos集群 95 | yarn-client | 以`client`模式连接到Yarn集群。群集位置将基于通过HADOOP_CONF_DIR变量找到 96 | yarn-cluster | 以`cluster`模式连接到Yarn集群。群集位置将基于通过HADOOP_CONF_DIR变量找到 97 | -------------------------------------------------------------------------------- /graphx-programming-guide/README.md: -------------------------------------------------------------------------------- 1 | # GraphX编程指南 2 | 3 | GraphX是一个新的(alpha)Spark API,它用于图和并行图(graph-parallel)的计算。GraphX通过引入[Resilient Distributed Property Graph](property-graph.md):带有 4 | 顶点和边属性的有向多重图,来扩展Spark RDD。为了支持图计算,GraphX公开一组基本的功能操作以及Pregel API的一个优化。另外,GraphX包含了一个日益增长的图算法和图builders的 5 | 集合,用以简化图分析任务。 6 | 7 | 从社交网络到语言建模,不断增长的规模和图形数据的重要性已经推动了许多新的`graph-parallel`系统(如[Giraph](http://giraph.apache.org/)和[GraphLab](http://graphlab.org/))的发展。 8 | 通过限制可表达的计算类型和引入新的技术来划分和分配图,这些系统可以高效地执行复杂的图形算法,比一般的`data-parallel`系统快很多。 9 | 10 | ![data parallel vs graph parallel](../img/data_parallel_vs_graph_parallel.png) 11 | 12 | 然而,通过这种限制可以提高性能,但是很难表示典型的图分析途径(构造图、修改它的结构或者表示跨多个图的计算)中很多重要的stages。另外,我们如何看待数据取决于我们的目标,并且同一原始数据可能有许多不同表和图的视图。 13 | 14 | ![表和图](../img/tables_and_graphs.png) 15 | 16 | 结论是,图和表之间经常需要能够相互移动。然而,现有的图分析管道必须组成`graph-parallel`和`data- parallel`系统`,从而实现大数据的迁移和复制并生成一个复杂的编程模型。 17 | 18 | ![图分析路径](../img/graph_analytics_pipeline.png) 19 | 20 | GraphX项目的目的就是将`graph-parallel`和`data-parallel`统一到一个系统中,这个系统拥有一个唯一的组合API。GraphX允许用户将数据当做一个图和一个集合(RDD),而不需要 21 | 而不需要数据移动或者复杂。通过将最新的进展整合进`graph-parallel`系统,GraphX能够优化图操作的执行。 22 | 23 | * [开始](getting-started.md) 24 | * [属性图](property-graph.md) 25 | * [图操作符](graph-operators.md) 26 | * [Pregel API](pregel-api.md) 27 | * [图构造者](graph-builders.md) 28 | * [顶点和边RDDs](vertex-and-edge-rdds.md) 29 | * [图算法](graph-algorithms.md) 30 | * [例子](examples.md) -------------------------------------------------------------------------------- /graphx-programming-guide/examples.md: -------------------------------------------------------------------------------- 1 | # 例子 2 | 3 | 假定我们想从一些文本文件中构建一个图,限制这个图包含重要的关系和用户,并且在子图上运行page-rank,最后返回与top用户相关的属性。可以通过如下方式实现。 4 | 5 | ```scala 6 | // Connect to the Spark cluster 7 | val sc = new SparkContext("spark://master.amplab.org", "research") 8 | 9 | // Load my user data and parse into tuples of user id and attribute list 10 | val users = (sc.textFile("graphx/data/users.txt") 11 | .map(line => line.split(",")).map( parts => (parts.head.toLong, parts.tail) )) 12 | 13 | // Parse the edge data which is already in userId -> userId format 14 | val followerGraph = GraphLoader.edgeListFile(sc, "graphx/data/followers.txt") 15 | 16 | // Attach the user attributes 17 | val graph = followerGraph.outerJoinVertices(users) { 18 | case (uid, deg, Some(attrList)) => attrList 19 | // Some users may not have attributes so we set them as empty 20 | case (uid, deg, None) => Array.empty[String] 21 | } 22 | 23 | // Restrict the graph to users with usernames and names 24 | val subgraph = graph.subgraph(vpred = (vid, attr) => attr.size == 2) 25 | 26 | // Compute the PageRank 27 | val pagerankGraph = subgraph.pageRank(0.001) 28 | 29 | // Get the attributes of the top pagerank users 30 | val userInfoWithPageRank = subgraph.outerJoinVertices(pagerankGraph.vertices) { 31 | case (uid, attrList, Some(pr)) => (pr, attrList.toList) 32 | case (uid, attrList, None) => (0.0, attrList.toList) 33 | } 34 | 35 | println(userInfoWithPageRank.vertices.top(5)(Ordering.by(_._2._1)).mkString("\n")) 36 | ``` -------------------------------------------------------------------------------- /graphx-programming-guide/getting-started.md: -------------------------------------------------------------------------------- 1 | # 开始 2 | 3 | 开始的第一步是引入Spark和GraphX到你的项目中,如下面所示 4 | 5 | ```scala 6 | mport org.apache.spark._ 7 | import org.apache.spark.graphx._ 8 | // To make some of the examples work we will also need RDD 9 | import org.apache.spark.rdd.RDD 10 | ``` 11 | 如果你没有用到Spark shell,你还将需要SparkContext。 -------------------------------------------------------------------------------- /graphx-programming-guide/graph-algorithms.md: -------------------------------------------------------------------------------- 1 | # 图算法 2 | 3 | GraphX包括一组图算法来简化分析任务。这些算法包含在`org.apache.spark.graphx.lib`包中,可以被直接访问。 4 | 5 | ## PageRank算法 6 | 7 | PageRank度量一个图中每个顶点的重要程度,假定从u到v的一条边代表v的重要性标签。例如,一个Twitter用户被许多其它人粉,该用户排名很高。GraphX带有静态和动态PageRank的实现方法 8 | ,这些方法在[PageRank object](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.lib.PageRank$)中。静态的PageRank运行固定次数 9 | 的迭代,而动态的PageRank一直运行,直到收敛。[GraphOps]()允许直接调用这些算法作为图上的方法。 10 | 11 | GraphX包含一个我们可以运行PageRank的社交网络数据集的例子。用户集在`graphx/data/users.txt`中,用户之间的关系在`graphx/data/followers.txt`中。我们通过下面的方法计算 12 | 每个用户的PageRank。 13 | 14 | ```scala 15 | // Load the edges as a graph 16 | val graph = GraphLoader.edgeListFile(sc, "graphx/data/followers.txt") 17 | // Run PageRank 18 | val ranks = graph.pageRank(0.0001).vertices 19 | // Join the ranks with the usernames 20 | val users = sc.textFile("graphx/data/users.txt").map { line => 21 | val fields = line.split(",") 22 | (fields(0).toLong, fields(1)) 23 | } 24 | val ranksByUsername = users.join(ranks).map { 25 | case (id, (username, rank)) => (username, rank) 26 | } 27 | // Print the result 28 | println(ranksByUsername.collect().mkString("\n")) 29 | ``` 30 | 31 | ## 连通体算法 32 | 33 | 连通体算法用id标注图中每个连通体,将连通体中序号最小的顶点的id作为连通体的id。例如,在社交网络中,连通体可以近似为集群。GraphX在[ConnectedComponents object](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.lib.ConnectedComponents$) 34 | 中包含了一个算法的实现,我们通过下面的方法计算社交网络数据集中的连通体。 35 | 36 | ```scala 37 | / Load the graph as in the PageRank example 38 | val graph = GraphLoader.edgeListFile(sc, "graphx/data/followers.txt") 39 | // Find the connected components 40 | val cc = graph.connectedComponents().vertices 41 | // Join the connected components with the usernames 42 | val users = sc.textFile("graphx/data/users.txt").map { line => 43 | val fields = line.split(",") 44 | (fields(0).toLong, fields(1)) 45 | } 46 | val ccByUsername = users.join(cc).map { 47 | case (id, (username, cc)) => (username, cc) 48 | } 49 | // Print the result 50 | println(ccByUsername.collect().mkString("\n")) 51 | ``` 52 | 53 | ## 三角形计数算法 54 | 55 | 一个顶点有两个相邻的顶点以及相邻顶点之间的边时,这个顶点是一个三角形的一部分。GraphX在[TriangleCount object](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.lib.TriangleCount$) 56 | 中实现了一个三角形计数算法,它计算通过每个顶点的三角形的数量。需要注意的是,在计算社交网络数据集的三角形计数时,`TriangleCount`需要边的方向是规范的方向(srcId < dstId), 57 | 并且图通过`Graph.partitionBy`分片过。 58 | 59 | ```scala 60 | // Load the edges in canonical order and partition the graph for triangle count 61 | val graph = GraphLoader.edgeListFile(sc, "graphx/data/followers.txt", true).partitionBy(PartitionStrategy.RandomVertexCut) 62 | // Find the triangle count for each vertex 63 | val triCounts = graph.triangleCount().vertices 64 | // Join the triangle counts with the usernames 65 | val users = sc.textFile("graphx/data/users.txt").map { line => 66 | val fields = line.split(",") 67 | (fields(0).toLong, fields(1)) 68 | } 69 | val triCountByUsername = users.join(triCounts).map { case (id, (username, tc)) => 70 | (username, tc) 71 | } 72 | // Print the result 73 | println(triCountByUsername.collect().mkString("\n")) 74 | ``` -------------------------------------------------------------------------------- /graphx-programming-guide/graph-builders.md: -------------------------------------------------------------------------------- 1 | # 图构造者 2 | 3 | GraphX提供了几种方式从RDD或者磁盘上的顶点和边集合构造图。默认情况下,没有哪个图构造者为图的边重新分区,而是把边保留在默认的分区中(例如HDFS中它们的原始块)。[Graph.groupEdges](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@groupEdges((ED,ED)⇒ED):Graph[VD,ED]) 4 | 需要重新分区图,因为它假定相同的边将会被分配到同一个分区,所以你必须在调用groupEdges之前调用[Graph.partitionBy](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@partitionBy(PartitionStrategy):Graph[VD,ED]) 5 | 6 | ```scala 7 | object GraphLoader { 8 | def edgeListFile( 9 | sc: SparkContext, 10 | path: String, 11 | canonicalOrientation: Boolean = false, 12 | minEdgePartitions: Int = 1) 13 | : Graph[Int, Int] 14 | } 15 | ``` 16 | 17 | [GraphLoader.edgeListFile](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.GraphLoader$@edgeListFile(SparkContext,String,Boolean,Int):Graph[Int,Int]) 18 | 提供了一个方式从磁盘上的边列表中加载一个图。它解析如下形式(源顶点ID,目标顶点ID)的连接表,跳过以`#`开头的注释行。 19 | 20 | ```scala 21 | # This is a comment 22 | 2 1 23 | 4 1 24 | 1 2 25 | ``` 26 | 27 | 它从指定的边创建一个图,自动地创建边提及的所有顶点。所有的顶点和边的属性默认都是1。`canonicalOrientation`参数允许重定向正方向(srcId < dstId)的边。这在[connected components](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.lib.ConnectedComponents$) 28 | 算法中需要用到。`minEdgePartitions`参数指定生成的边分区的最少数量。边分区可能比指定的分区更多,例如,一个HDFS文件包含更多的块。 29 | 30 | ```scala 31 | object Graph { 32 | def apply[VD, ED]( 33 | vertices: RDD[(VertexId, VD)], 34 | edges: RDD[Edge[ED]], 35 | defaultVertexAttr: VD = null) 36 | : Graph[VD, ED] 37 | def fromEdges[VD, ED]( 38 | edges: RDD[Edge[ED]], 39 | defaultValue: VD): Graph[VD, ED] 40 | def fromEdgeTuples[VD]( 41 | rawEdges: RDD[(VertexId, VertexId)], 42 | defaultValue: VD, 43 | uniqueEdges: Option[PartitionStrategy] = None): Graph[VD, Int] 44 | } 45 | ``` 46 | [Graph.apply](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph$@apply[VD,ED](RDD[(VertexId,VD)],RDD[Edge[ED]],VD)(ClassTag[VD],ClassTag[ED]):Graph[VD,ED]) 47 | 允许从顶点和边的RDD上创建一个图。重复的顶点可以任意的选择其中一个,在边RDD中而不是在顶点RDD中发现的顶点分配默认的属性。 48 | 49 | [Graph.fromEdges](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph$@fromEdges[VD,ED](RDD[Edge[ED]],VD)(ClassTag[VD],ClassTag[ED]):Graph[VD,ED]) 50 | 允许仅仅从一个边RDD上创建一个图,它自动地创建边提及的顶点,并分配这些顶点默认的值。 51 | 52 | [Graph.fromEdgeTuples](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph$@fromEdgeTuples[VD](RDD[(VertexId,VertexId)],VD,Option[PartitionStrategy])(ClassTag[VD]):Graph[VD,Int]) 53 | 允许仅仅从一个边元组组成的RDD上创建一个图。分配给边的值为1。它自动地创建边提及的顶点,并分配这些顶点默认的值。它还支持删除边。为了删除边,需要传递一个[PartitionStrategy](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.PartitionStrategy) 54 | 为值的`Some`作为`uniqueEdges`参数(如uniqueEdges = Some(PartitionStrategy.RandomVertexCut))。分配相同的边到同一个分区从而使它们可以被删除,一个分区策略是必须的。 55 | -------------------------------------------------------------------------------- /graphx-programming-guide/graph-operators.md: -------------------------------------------------------------------------------- 1 | # 图操作符 2 | 3 | 正如RDDs有基本的操作map, filter和reduceByKey一样,属性图也有基本的集合操作,这些操作采用用户自定义的函数并产生包含转换特征和结构的新图。定义在[Graph](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph)中的 4 | 核心操作是经过优化的实现。表示为核心操作的组合的便捷操作定义在[GraphOps](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.GraphOps)中。然而, 5 | 因为有Scala的隐式转换,定义在`GraphOps`中的操作可以作为`Graph`的成员自动使用。例如,我们可以通过下面的方式计算每个顶点(定义在GraphOps中)的入度。 6 | 7 | ```scala 8 | val graph: Graph[(String, String), String] 9 | // Use the implicit GraphOps.inDegrees operator 10 | val inDegrees: VertexRDD[Int] = graph.inDegrees 11 | ``` 12 | 13 | 区分核心图操作和`GraphOps`的原因是为了在将来支持不同的图表示。每个图表示都必须提供核心操作的实现并重用很多定义在`GraphOps`中的有用操作。 14 | 15 | ## 操作一览 16 | 17 | 一下是定义在`Graph`和`GraphOps`中(为了简单起见,表现为图的成员)的功能的快速浏览。注意,某些函数签名已经简化(如默认参数和类型的限制已删除),一些更高级的功能已经被 18 | 删除,所以请参阅API文档了解官方的操作列表。 19 | 20 | ```scala 21 | /** Summary of the functionality in the property graph */ 22 | class Graph[VD, ED] { 23 | // Information about the Graph =================================================================== 24 | val numEdges: Long 25 | val numVertices: Long 26 | val inDegrees: VertexRDD[Int] 27 | val outDegrees: VertexRDD[Int] 28 | val degrees: VertexRDD[Int] 29 | // Views of the graph as collections ============================================================= 30 | val vertices: VertexRDD[VD] 31 | val edges: EdgeRDD[ED] 32 | val triplets: RDD[EdgeTriplet[VD, ED]] 33 | // Functions for caching graphs ================================================================== 34 | def persist(newLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED] 35 | def cache(): Graph[VD, ED] 36 | def unpersistVertices(blocking: Boolean = true): Graph[VD, ED] 37 | // Change the partitioning heuristic ============================================================ 38 | def partitionBy(partitionStrategy: PartitionStrategy): Graph[VD, ED] 39 | // Transform vertex and edge attributes ========================================================== 40 | def mapVertices[VD2](map: (VertexID, VD) => VD2): Graph[VD2, ED] 41 | def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2] 42 | def mapEdges[ED2](map: (PartitionID, Iterator[Edge[ED]]) => Iterator[ED2]): Graph[VD, ED2] 43 | def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2] 44 | def mapTriplets[ED2](map: (PartitionID, Iterator[EdgeTriplet[VD, ED]]) => Iterator[ED2]) 45 | : Graph[VD, ED2] 46 | // Modify the graph structure ==================================================================== 47 | def reverse: Graph[VD, ED] 48 | def subgraph( 49 | epred: EdgeTriplet[VD,ED] => Boolean = (x => true), 50 | vpred: (VertexID, VD) => Boolean = ((v, d) => true)) 51 | : Graph[VD, ED] 52 | def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED] 53 | def groupEdges(merge: (ED, ED) => ED): Graph[VD, ED] 54 | // Join RDDs with the graph ====================================================================== 55 | def joinVertices[U](table: RDD[(VertexID, U)])(mapFunc: (VertexID, VD, U) => VD): Graph[VD, ED] 56 | def outerJoinVertices[U, VD2](other: RDD[(VertexID, U)]) 57 | (mapFunc: (VertexID, VD, Option[U]) => VD2) 58 | : Graph[VD2, ED] 59 | // Aggregate information about adjacent triplets ================================================= 60 | def collectNeighborIds(edgeDirection: EdgeDirection): VertexRDD[Array[VertexID]] 61 | def collectNeighbors(edgeDirection: EdgeDirection): VertexRDD[Array[(VertexID, VD)]] 62 | def aggregateMessages[Msg: ClassTag]( 63 | sendMsg: EdgeContext[VD, ED, Msg] => Unit, 64 | mergeMsg: (Msg, Msg) => Msg, 65 | tripletFields: TripletFields = TripletFields.All) 66 | : VertexRDD[A] 67 | // Iterative graph-parallel computation ========================================================== 68 | def pregel[A](initialMsg: A, maxIterations: Int, activeDirection: EdgeDirection)( 69 | vprog: (VertexID, VD, A) => VD, 70 | sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexID,A)], 71 | mergeMsg: (A, A) => A) 72 | : Graph[VD, ED] 73 | // Basic graph algorithms ======================================================================== 74 | def pageRank(tol: Double, resetProb: Double = 0.15): Graph[Double, Double] 75 | def connectedComponents(): Graph[VertexID, ED] 76 | def triangleCount(): Graph[Int, ED] 77 | def stronglyConnectedComponents(numIter: Int): Graph[VertexID, ED] 78 | } 79 | ``` 80 | 81 | ## 属性操作 82 | 83 | 如RDD的`map`操作一样,属性图包含下面的操作: 84 | 85 | ```scala 86 | class Graph[VD, ED] { 87 | def mapVertices[VD2](map: (VertexId, VD) => VD2): Graph[VD2, ED] 88 | def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2] 89 | def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2] 90 | } 91 | ``` 92 | 每个操作都产生一个新的图,这个新的图包含通过用户自定义的map操作修改后的顶点或边的属性。 93 | 94 | 注意,每种情况下图结构都不受影响。这些操作的一个重要特征是它允许所得图形重用原有图形的结构索引(indices)。下面的两行代码在逻辑上是等价的,但是第一个不保存结构索引,所以 95 | 不会从GraphX系统优化中受益。 96 | 97 | ```scala 98 | val newVertices = graph.vertices.map { case (id, attr) => (id, mapUdf(id, attr)) } 99 | val newGraph = Graph(newVertices, graph.edges) 100 | ``` 101 | 另一种方法是用[mapVertices](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@mapVertices[VD2]((VertexId,VD)⇒VD2)(ClassTag[VD2]):Graph[VD2,ED])保存索引。 102 | 103 | ```scala 104 | val newGraph = graph.mapVertices((id, attr) => mapUdf(id, attr)) 105 | ``` 106 | 107 | 这些操作经常用来初始化的图形,用作特定计算或者用来处理项目不需要的属性。例如,给定一个图,这个图的顶点特征包含出度,我们为PageRank初始化它。 108 | 109 | ```scala 110 | // Given a graph where the vertex property is the out degree 111 | val inputGraph: Graph[Int, String] = 112 | graph.outerJoinVertices(graph.outDegrees)((vid, _, degOpt) => degOpt.getOrElse(0)) 113 | // Construct a graph where each edge contains the weight 114 | // and each vertex is the initial PageRank 115 | val outputGraph: Graph[Double, Double] = 116 | inputGraph.mapTriplets(triplet => 1.0 / triplet.srcAttr).mapVertices((id, _) => 1.0) 117 | ``` 118 | 119 | ## 结构性操作 120 | 121 | 当前的GraphX仅仅支持一组简单的常用结构性操作。下面是基本的结构性操作列表。 122 | 123 | ```scala 124 | class Graph[VD, ED] { 125 | def reverse: Graph[VD, ED] 126 | def subgraph(epred: EdgeTriplet[VD,ED] => Boolean, 127 | vpred: (VertexId, VD) => Boolean): Graph[VD, ED] 128 | def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED] 129 | def groupEdges(merge: (ED, ED) => ED): Graph[VD,ED] 130 | } 131 | ``` 132 | 133 | [reverse](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@reverse:Graph[VD,ED])操作返回一个新的图,这个图的边的方向都是反转的。例如,这个操作可以用来计算反转的PageRank。因为反转操作没有修改顶点或者边的属性或者改变边的数量,所以我们可以 134 | 在不移动或者复制数据的情况下有效地实现它。 135 | 136 | [subgraph](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@subgraph((EdgeTriplet[VD,ED])⇒Boolean,(VertexId,VD)⇒Boolean):Graph[VD,ED])操作 137 | 利用顶点和边的谓词(predicates),返回的图仅仅包含满足顶点谓词的顶点、满足边谓词的边以及满足顶点谓词的连接顶点(connect vertices)。`subgraph`操作可以用于很多场景,如获取 138 | 感兴趣的顶点和边组成的图或者获取清除断开链接后的图。下面的例子删除了断开的链接。 139 | 140 | ```scala 141 | // Create an RDD for the vertices 142 | val users: RDD[(VertexId, (String, String))] = 143 | sc.parallelize(Array((3L, ("rxin", "student")), (7L, ("jgonzal", "postdoc")), 144 | (5L, ("franklin", "prof")), (2L, ("istoica", "prof")), 145 | (4L, ("peter", "student")))) 146 | // Create an RDD for edges 147 | val relationships: RDD[Edge[String]] = 148 | sc.parallelize(Array(Edge(3L, 7L, "collab"), Edge(5L, 3L, "advisor"), 149 | Edge(2L, 5L, "colleague"), Edge(5L, 7L, "pi"), 150 | Edge(4L, 0L, "student"), Edge(5L, 0L, "colleague"))) 151 | // Define a default user in case there are relationship with missing user 152 | val defaultUser = ("John Doe", "Missing") 153 | // Build the initial Graph 154 | val graph = Graph(users, relationships, defaultUser) 155 | // Notice that there is a user 0 (for which we have no information) connected to users 156 | // 4 (peter) and 5 (franklin). 157 | graph.triplets.map( 158 | triplet => triplet.srcAttr._1 + " is the " + triplet.attr + " of " + triplet.dstAttr._1 159 | ).collect.foreach(println(_)) 160 | // Remove missing vertices as well as the edges to connected to them 161 | val validGraph = graph.subgraph(vpred = (id, attr) => attr._2 != "Missing") 162 | // The valid subgraph will disconnect users 4 and 5 by removing user 0 163 | validGraph.vertices.collect.foreach(println(_)) 164 | validGraph.triplets.map( 165 | triplet => triplet.srcAttr._1 + " is the " + triplet.attr + " of " + triplet.dstAttr._1 166 | ).collect.foreach(println(_)) 167 | ``` 168 | 169 | 注意,上面的例子中,仅仅提供了顶点谓词。如果没有提供顶点或者边的谓词,`subgraph`操作默认为true。 170 | 171 | [mask](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@mask[VD2,ED2](Graph[VD2,ED2])(ClassTag[VD2],ClassTag[ED2]):Graph[VD,ED])操作 172 | 构造一个子图,这个子图包含输入图中包含的顶点和边。这个操作可以和`subgraph`操作相结合,基于另外一个相关图的特征去约束一个图。例如,我们可能利用缺失顶点的图运行连通体(?连通组件connected components),然后返回有效的子图。 173 | 174 | ```scala 175 | / Run Connected Components 176 | val ccGraph = graph.connectedComponents() // No longer contains missing field 177 | // Remove missing vertices as well as the edges to connected to them 178 | val validGraph = graph.subgraph(vpred = (id, attr) => attr._2 != "Missing") 179 | // Restrict the answer to the valid subgraph 180 | val validCCGraph = ccGraph.mask(validGraph) 181 | ``` 182 | 183 | [groupEdges](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@groupEdges((ED,ED)⇒ED):Graph[VD,ED])操作合并多重图 184 | 中的并行边(如顶点对之间重复的边)。在大量的应用程序中,并行的边可以合并(它们的权重合并)为一条边从而降低图的大小。 185 | 186 | ## 连接操作 187 | 188 | 在许多情况下,有必要将外部数据加入到图中。例如,我们可能有额外的用户属性需要合并到已有的图中或者我们可能想从一个图中取出顶点特征加入到另外一个图中。这些任务可以用join操作完成。 189 | 下面列出的是主要的join操作。 190 | 191 | ```scala 192 | class Graph[VD, ED] { 193 | def joinVertices[U](table: RDD[(VertexId, U)])(map: (VertexId, VD, U) => VD) 194 | : Graph[VD, ED] 195 | def outerJoinVertices[U, VD2](table: RDD[(VertexId, U)])(map: (VertexId, VD, Option[U]) => VD2) 196 | : Graph[VD2, ED] 197 | } 198 | ``` 199 | 200 | [joinVertices](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.GraphOps@joinVertices[U](RDD[(VertexId,U)])((VertexId,VD,U)⇒VD)(ClassTag[U]):Graph[VD,ED]) 201 | 操作将输入RDD和顶点相结合,返回一个新的带有顶点特征的图。这些特征是通过在连接顶点的结果上使用用户定义的`map`函数获得的。在RDD中没有匹配值的顶点保留其原始值。 202 | 203 | 注意,对于给定的顶点,如果RDD中有超过1个的匹配值,则仅仅使用其中的一个。建议用下面的方法保证输入RDD的唯一性。下面的方法也会预索引返回的值用以加快后续的join操作。 204 | 205 | ```scala 206 | val nonUniqueCosts: RDD[(VertexID, Double)] 207 | val uniqueCosts: VertexRDD[Double] = 208 | graph.vertices.aggregateUsingIndex(nonUnique, (a,b) => a + b) 209 | val joinedGraph = graph.joinVertices(uniqueCosts)( 210 | (id, oldCost, extraCost) => oldCost + extraCost) 211 | ``` 212 | 213 | 除了将用户自定义的map函数用到所有顶点和改变顶点属性类型以外,更一般的[outerJoinVertices](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@outerJoinVertices[U,VD2](RDD[(VertexId,U)])((VertexId,VD,Option[U])⇒VD2)(ClassTag[U],ClassTag[VD2]):Graph[VD2,ED])与`joinVertices`类似。 214 | 因为并不是所有顶点在RDD中拥有匹配的值,map函数需要一个option类型。 215 | 216 | ```scala 217 | val outDegrees: VertexRDD[Int] = graph.outDegrees 218 | val degreeGraph = graph.outerJoinVertices(outDegrees) { (id, oldAttr, outDegOpt) => 219 | outDegOpt match { 220 | case Some(outDeg) => outDeg 221 | case None => 0 // No outDegree means zero outDegree 222 | } 223 | } 224 | ``` 225 | 226 | 你可能已经注意到了,在上面的例子中用到了curry函数的多参数列表。虽然我们可以将f(a)(b)写成f(a,b),但是f(a,b)意味着b的类型推断将不会依赖于a。因此,用户需要为定义 227 | 的函数提供类型标注。 228 | 229 | ```scala 230 | val joinedGraph = graph.joinVertices(uniqueCosts, 231 | (id: VertexID, oldCost: Double, extraCost: Double) => oldCost + extraCost) 232 | ``` 233 | 234 | ## 相邻聚合(Neighborhood Aggregation) 235 | 236 | 图分析任务的一个关键步骤是汇总每个顶点附近的信息。例如我们可能想知道每个用户的追随者的数量或者每个用户的追随者的平均年龄。许多迭代图算法(如PageRank,最短路径和连通体) 237 | 多次聚合相邻顶点的属性。 238 | 239 | 为了提高性能,主要的聚合操作从`graph.mapReduceTriplets`改为了新的`graph.AggregateMessages`。虽然API的改变相对较小,但是我们仍然提供了过渡的指南。 240 | 241 | ### 聚合消息(aggregateMessages) 242 | 243 | GraphX中的核心聚合操作是[aggregateMessages](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@aggregateMessages[A]((EdgeContext[VD,ED,A])⇒Unit,(A,A)⇒A,TripletFields)(ClassTag[A]):VertexRDD[A])。 244 | 这个操作将用户定义的`sendMsg`函数应用到图的每个边三元组(edge triplet),然后应用`mergeMsg`函数在其目的顶点聚合这些消息。 245 | 246 | ```scala 247 | class Graph[VD, ED] { 248 | def aggregateMessages[Msg: ClassTag]( 249 | sendMsg: EdgeContext[VD, ED, Msg] => Unit, 250 | mergeMsg: (Msg, Msg) => Msg, 251 | tripletFields: TripletFields = TripletFields.All) 252 | : VertexRDD[Msg] 253 | } 254 | ``` 255 | 256 | 用户自定义的`sendMsg`函数是一个[EdgeContext](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.EdgeContext)类型。它暴露源和目的属性以及边缘属性 257 | 以及发送消息给源和目的属性的函数([sendToSrc](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.EdgeContext@sendToSrc(msg:A):Unit)和[sendToDst](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.EdgeContext@sendToDst(msg:A):Unit))。 258 | 可将`sendMsg`函数看做map-reduce过程中的map函数。用户自定义的`mergeMsg`函数指定两个消息到相同的顶点并保存为一个消息。可以将`mergeMsg`函数看做map-reduce过程中的reduce函数。 259 | [aggregateMessages](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@aggregateMessages[A]((EdgeContext[VD,ED,A])⇒Unit,(A,A)⇒A,TripletFields)(ClassTag[A]):VertexRDD[A]) 260 | 操作返回一个包含聚合消息(目的地为每个顶点)的`VertexRDD[Msg]`。没有接收到消息的顶点不包含在返回的`VertexRDD`中。 261 | 262 | 另外,[aggregateMessages](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@aggregateMessages[A]((EdgeContext[VD,ED,A])⇒Unit,(A,A)⇒A,TripletFields)(ClassTag[A]):VertexRDD[A]) 263 | 有一个可选的`tripletFields`参数,它指出在`EdgeContext`中,哪些数据被访问(如源顶点特征而不是目的顶点特征)。`tripletsFields`可能的选项定义在[TripletFields](http://spark.apache.org/docs/latest/api/java/org/apache/spark/graphx/TripletFields.html)中。 264 | `tripletFields`参数用来通知GraphX仅仅只需要`EdgeContext`的一部分允许GraphX选择一个优化的连接策略。例如,如果我们想计算每个用户的追随者的平均年龄,我们仅仅只需要源字段。 265 | 所以我们用`TripletFields.Src`表示我们仅仅只需要源字段。 266 | 267 | 在下面的例子中,我们用`aggregateMessages`操作计算每个用户更年长的追随者的年龄。 268 | 269 | ```scala 270 | // Import random graph generation library 271 | import org.apache.spark.graphx.util.GraphGenerators 272 | // Create a graph with "age" as the vertex property. Here we use a random graph for simplicity. 273 | val graph: Graph[Double, Int] = 274 | GraphGenerators.logNormalGraph(sc, numVertices = 100).mapVertices( (id, _) => id.toDouble ) 275 | // Compute the number of older followers and their total age 276 | val olderFollowers: VertexRDD[(Int, Double)] = graph.aggregateMessages[(Int, Double)]( 277 | triplet => { // Map Function 278 | if (triplet.srcAttr > triplet.dstAttr) { 279 | // Send message to destination vertex containing counter and age 280 | triplet.sendToDst(1, triplet.srcAttr) 281 | } 282 | }, 283 | // Add counter and age 284 | (a, b) => (a._1 + b._1, a._2 + b._2) // Reduce Function 285 | ) 286 | // Divide total age by number of older followers to get average age of older followers 287 | val avgAgeOfOlderFollowers: VertexRDD[Double] = 288 | olderFollowers.mapValues( (id, value) => value match { case (count, totalAge) => totalAge / count } ) 289 | // Display the results 290 | avgAgeOfOlderFollowers.collect.foreach(println(_)) 291 | ``` 292 | 当消息(以及消息的总数)是常量大小(列表和连接替换为浮点数和添加)时,`aggregateMessages`操作的效果最好。 293 | 294 | ### Map Reduce三元组过渡指南 295 | 296 | 在之前版本的GraphX中,利用[mapReduceTriplets]操作完成相邻聚合。 297 | 298 | ```scala 299 | class Graph[VD, ED] { 300 | def mapReduceTriplets[Msg]( 301 | map: EdgeTriplet[VD, ED] => Iterator[(VertexId, Msg)], 302 | reduce: (Msg, Msg) => Msg) 303 | : VertexRDD[Msg] 304 | } 305 | ``` 306 | `mapReduceTriplets`操作在每个三元组上应用用户定义的map函数,然后保存用用户定义的reduce函数聚合的消息。然而,我们发现用户返回的迭代器是昂贵的,它抑制了我们添加额外优化(例如本地顶点的重新编号)的能力。 307 | [aggregateMessages](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph@aggregateMessages[A]((EdgeContext[VD,ED,A])⇒Unit,(A,A)⇒A,TripletFields)(ClassTag[A]):VertexRDD[A]) 308 | 暴露三元组字段和函数显示的发送消息到源和目的顶点。并且,我们删除了字节码检测转而需要用户指明三元组的哪些字段实际需要。 309 | 310 | 下面的代码用到了`mapReduceTriplets` 311 | 312 | ```scala 313 | val graph: Graph[Int, Float] = ... 314 | def msgFun(triplet: Triplet[Int, Float]): Iterator[(Int, String)] = { 315 | Iterator((triplet.dstId, "Hi")) 316 | } 317 | def reduceFun(a: Int, b: Int): Int = a + b 318 | val result = graph.mapReduceTriplets[String](msgFun, reduceFun) 319 | ``` 320 | 321 | 下面的代码用到了`aggregateMessages` 322 | 323 | ```scala 324 | val graph: Graph[Int, Float] = ... 325 | def msgFun(triplet: EdgeContext[Int, Float, String]) { 326 | triplet.sendToDst("Hi") 327 | } 328 | def reduceFun(a: Int, b: Int): Int = a + b 329 | val result = graph.aggregateMessages[String](msgFun, reduceFun) 330 | ``` 331 | 332 | ### 计算度信息 333 | 334 | 最一般的聚合任务就是计算顶点的度,即每个顶点相邻边的数量。在有向图中,经常需要知道顶点的入度、出度以及总共的度。[GraphOps](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.GraphOps) 335 | 类包含一个操作集合用来计算每个顶点的度。例如,下面的例子计算最大的入度、出度和总度。 336 | 337 | ```scala 338 | // Define a reduce operation to compute the highest degree vertex 339 | def max(a: (VertexId, Int), b: (VertexId, Int)): (VertexId, Int) = { 340 | if (a._2 > b._2) a else b 341 | } 342 | // Compute the max degrees 343 | val maxInDegree: (VertexId, Int) = graph.inDegrees.reduce(max) 344 | val maxOutDegree: (VertexId, Int) = graph.outDegrees.reduce(max) 345 | val maxDegrees: (VertexId, Int) = graph.degrees.reduce(max) 346 | ``` 347 | ### Collecting Neighbors 348 | 349 | 在某些情况下,通过收集每个顶点相邻的顶点及它们的属性来表达计算可能更容易。这可以通过[collectNeighborIds](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.GraphOps@collectNeighborIds(EdgeDirection):VertexRDD[Array[VertexId]]) 350 | 和[collectNeighbors](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.GraphOps@collectNeighbors(EdgeDirection):VertexRDD[Array[(VertexId,VD)]])操作来简单的完成 351 | 352 | ```scala 353 | class GraphOps[VD, ED] { 354 | def collectNeighborIds(edgeDirection: EdgeDirection): VertexRDD[Array[VertexId]] 355 | def collectNeighbors(edgeDirection: EdgeDirection): VertexRDD[ Array[(VertexId, VD)] ] 356 | } 357 | ``` 358 | 这些操作是非常昂贵的,因为它们需要重复的信息和大量的通信。如果可能,尽量用`aggregateMessages`操作直接表达相同的计算。 359 | 360 | ### 缓存和不缓存 361 | 362 | 在Spark中,RDDs默认是不缓存的。为了避免重复计算,当需要多次利用它们时,我们必须显示地缓存它们。GraphX中的图也有相同的方式。当利用到图多次时,确保首先访问`Graph.cache()`方法。 363 | 364 | 在迭代计算中,为了获得最佳的性能,不缓存可能是必须的。默认情况下,缓存的RDDs和图会一直保留在内存中直到因为内存压力迫使它们以LRU的顺序删除。对于迭代计算,先前的迭代的中间结果将填充到缓存 365 | 中。虽然它们最终会被删除,但是保存在内存中的不需要的数据将会减慢垃圾回收。只有中间结果不需要,不缓存它们是更高效的。这涉及到在每次迭代中物化一个图或者RDD而不缓存所有其它的数据集。 366 | 在将来的迭代中仅用物化的数据集。然而,因为图是由多个RDD组成的,正确的不持久化它们是困难的。对于迭代计算,我们建议使用Pregel API,它可以正确的不持久化中间结果。 367 | 368 | -------------------------------------------------------------------------------- /graphx-programming-guide/pregel-api.md: -------------------------------------------------------------------------------- 1 | # Pregel API 2 | 3 | 图本身是递归数据结构,顶点的属性依赖于它们邻居的属性,这些邻居的属性又依赖于自己邻居的属性。所以许多重要的图算法都是迭代的重新计算每个顶点的属性,直到满足某个确定的条件。 4 | 一系列的graph-parallel抽象已经被提出来用来表达这些迭代算法。GraphX公开了一个类似Pregel的操作,它是广泛使用的Pregel和GraphLab抽象的一个融合。 5 | 6 | 在GraphX中,更高级的Pregel操作是一个约束到图拓扑的批量同步(bulk-synchronous)并行消息抽象。Pregel操作者执行一系列的超级步骤(super steps),在这些步骤中,顶点从 7 | 之前的超级步骤中接收进入(inbound)消息的总和,为顶点属性计算一个新的值,然后在以后的超级步骤中发送消息到邻居顶点。不像Pregel而更像GraphLab,消息作为一个边三元组的函数被并行 8 | 计算,消息计算既访问了源顶点特征也访问了目的顶点特征。在超级步中,没有收到消息的顶点被跳过。当没有消息遗留时,Pregel操作停止迭代并返回最终的图。 9 | 10 | 注意,与更标准的Pregel实现不同的是,GraphX中的顶点仅仅能发送信息给邻居顶点,并利用用户自定义的消息函数构造消息。这些限制允许在GraphX进行额外的优化。 11 | 12 | 一下是[ Pregel操作](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.GraphOps@pregel[A](A,Int,EdgeDirection)((VertexId,VD,A)⇒VD,(EdgeTriplet[VD,ED])⇒Iterator[(VertexId,A)],(A,A)⇒A)(ClassTag[A]):Graph[VD,ED])的类型签名以及实现草图(注意,访问graph.cache已经被删除) 13 | 14 | ```scala 15 | class GraphOps[VD, ED] { 16 | def pregel[A] 17 | (initialMsg: A, 18 | maxIter: Int = Int.MaxValue, 19 | activeDir: EdgeDirection = EdgeDirection.Out) 20 | (vprog: (VertexId, VD, A) => VD, 21 | sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexId, A)], 22 | mergeMsg: (A, A) => A) 23 | : Graph[VD, ED] = { 24 | // Receive the initial message at each vertex 25 | var g = mapVertices( (vid, vdata) => vprog(vid, vdata, initialMsg) ).cache() 26 | // compute the messages 27 | var messages = g.mapReduceTriplets(sendMsg, mergeMsg) 28 | var activeMessages = messages.count() 29 | // Loop until no messages remain or maxIterations is achieved 30 | var i = 0 31 | while (activeMessages > 0 && i < maxIterations) { 32 | // Receive the messages: ----------------------------------------------------------------------- 33 | // Run the vertex program on all vertices that receive messages 34 | val newVerts = g.vertices.innerJoin(messages)(vprog).cache() 35 | // Merge the new vertex values back into the graph 36 | g = g.outerJoinVertices(newVerts) { (vid, old, newOpt) => newOpt.getOrElse(old) }.cache() 37 | // Send Messages: ------------------------------------------------------------------------------ 38 | // Vertices that didn't receive a message above don't appear in newVerts and therefore don't 39 | // get to send messages. More precisely the map phase of mapReduceTriplets is only invoked 40 | // on edges in the activeDir of vertices in newVerts 41 | messages = g.mapReduceTriplets(sendMsg, mergeMsg, Some((newVerts, activeDir))).cache() 42 | activeMessages = messages.count() 43 | i += 1 44 | } 45 | g 46 | } 47 | } 48 | ``` 49 | 50 | 注意,pregel有两个参数列表(graph.pregel(list1)(list2))。第一个参数列表包含配置参数初始消息、最大迭代数、发送消息的边的方向(默认是沿边方向出)。第二个参数列表包含用户 51 | 自定义的函数用来接收消息(vprog)、计算消息(sendMsg)、合并消息(mergeMsg)。 52 | 53 | 我们可以用Pregel操作表达计算单源最短路径( single source shortest path)。 54 | 55 | ```scala 56 | import org.apache.spark.graphx._ 57 | // Import random graph generation library 58 | import org.apache.spark.graphx.util.GraphGenerators 59 | // A graph with edge attributes containing distances 60 | val graph: Graph[Int, Double] = 61 | GraphGenerators.logNormalGraph(sc, numVertices = 100).mapEdges(e => e.attr.toDouble) 62 | val sourceId: VertexId = 42 // The ultimate source 63 | // Initialize the graph such that all vertices except the root have distance infinity. 64 | val initialGraph = graph.mapVertices((id, _) => if (id == sourceId) 0.0 else Double.PositiveInfinity) 65 | val sssp = initialGraph.pregel(Double.PositiveInfinity)( 66 | (id, dist, newDist) => math.min(dist, newDist), // Vertex Program 67 | triplet => { // Send Message 68 | if (triplet.srcAttr + triplet.attr < triplet.dstAttr) { 69 | Iterator((triplet.dstId, triplet.srcAttr + triplet.attr)) 70 | } else { 71 | Iterator.empty 72 | } 73 | }, 74 | (a,b) => math.min(a,b) // Merge Message 75 | ) 76 | println(sssp.vertices.collect.mkString("\n")) 77 | ``` -------------------------------------------------------------------------------- /graphx-programming-guide/property-graph.md: -------------------------------------------------------------------------------- 1 | # 属性图 2 | 3 | [属性图](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph)是一个有向多重图,它带有连接到每个顶点和边的用户定义的对象。 4 | 有向多重图中多个并行(parallel)的边共享相同的源和目的地顶点。支持并行边的能力简化了建模场景,这个场景中,相同的顶点存在多种关系(例如co-worker和friend)。每个顶点由一个 5 | 唯一的64位长的标识符(VertexID)作为key。GraphX并没有对顶点标识强加任何排序。同样,顶点拥有相应的源和目的顶点标识符。 6 | 7 | 属性图通过vertex(VD)和edge(ED)类型参数化,这些类型是分别与每个顶点和边相关联的对象的类型。 8 | 9 | 在某些情况下,在相同的图形中,可能希望顶点拥有不同的属性类型。这可以通过继承完成。例如,将用户和产品建模成一个二分图,我们可以用如下方式 10 | 11 | ```scala 12 | class VertexProperty() 13 | case class UserProperty(val name: String) extends VertexProperty 14 | case class ProductProperty(val name: String, val price: Double) extends VertexProperty 15 | // The graph might then have the type: 16 | var graph: Graph[VertexProperty, String] = null 17 | ``` 18 | 和RDD一样,属性图是不可变的、分布式的、容错的。图的值或者结构的改变需要按期望的生成一个新的图来实现。注意,原始图的大部分都可以在新图中重用,用来减少这种固有的功能数据结构的成本。 19 | 执行者使用一系列顶点分区试探法来对图进行分区。如RDD一样,图中的每个分区可以在发生故障的情况下被重新创建在不同的机器上。 20 | 21 | 逻辑上的属性图对应于一对类型化的集合(RDD),这个集合编码了每一个顶点和边的属性。因此,图类包含访问图中顶点和边的成员。 22 | 23 | ```scala 24 | class Graph[VD, ED] { 25 | val vertices: VertexRDD[VD] 26 | val edges: EdgeRDD[ED] 27 | } 28 | ``` 29 | 30 | `VertexRDD[VD]`和`EdgeRDD[ED]`类分别继承和优化自`RDD[(VertexID, VD)]`和`RDD[Edge[ED]]`。`VertexRDD[VD]`和`EdgeRDD[ED]`都支持额外的功能来建立在图计算和利用内部优化。 31 | 32 | ## 属性图的例子 33 | 34 | 在GraphX项目中,假设我们想构造一个包括不同合作者的属性图。顶点属性可能包含用户名和职业。我们可以用描述合作者之间关系的字符串标注边缘。 35 | 36 | ![属性图](../img/property_graph.png) 37 | 38 | 所得的图形将具有类型签名 39 | 40 | ```scala 41 | val userGraph: Graph[(String, String), String] 42 | ``` 43 | 有很多方式从一个原始文件、RDD构造一个属性图。最一般的方法是利用[Graph object](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Graph$)。 44 | 下面的代码从RDD集合生成属性图。 45 | 46 | ```scala 47 | // Assume the SparkContext has already been constructed 48 | val sc: SparkContext 49 | // Create an RDD for the vertices 50 | val users: RDD[(VertexId, (String, String))] = 51 | sc.parallelize(Array((3L, ("rxin", "student")), (7L, ("jgonzal", "postdoc")), 52 | (5L, ("franklin", "prof")), (2L, ("istoica", "prof")))) 53 | // Create an RDD for edges 54 | val relationships: RDD[Edge[String]] = 55 | sc.parallelize(Array(Edge(3L, 7L, "collab"), Edge(5L, 3L, "advisor"), 56 | Edge(2L, 5L, "colleague"), Edge(5L, 7L, "pi"))) 57 | // Define a default user in case there are relationship with missing user 58 | val defaultUser = ("John Doe", "Missing") 59 | // Build the initial Graph 60 | val graph = Graph(users, relationships, defaultUser) 61 | ``` 62 | 在上面的例子中,我们用到了[Edge](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.Edge)样本类。边有一个`srcId`和`dstId`分别对应 63 | 于源和目标顶点的标示符。另外,`Edge`类有一个`attr`成员用来存储边属性。 64 | 65 | 我们可以分别用`graph.vertices`和`graph.edges`成员将一个图解构为相应的顶点和边。 66 | 67 | ```scala 68 | val graph: Graph[(String, String), String] // Constructed from above 69 | // Count all users which are postdocs 70 | graph.vertices.filter { case (id, (name, pos)) => pos == "postdoc" }.count 71 | // Count all the edges where src > dst 72 | graph.edges.filter(e => e.srcId > e.dstId).count 73 | ``` 74 | 75 | ``` 76 | 注意,graph.vertices返回一个VertexRDD[(String, String)],它继承于 RDD[(VertexID, (String, String))]。所以我们可以用scala的case表达式解构这个元组。另一方面, 77 | graph.edges返回一个包含Edge[String]对象的EdgeRDD。我们也可以用到case类的类型构造器,如下例所示。 78 | 79 | graph.edges.filter { case Edge(src, dst, prop) => src > dst }.count 80 | ``` 81 | 82 | 除了属性图的顶点和边视图,GraphX也包含了一个三元组视图,三元视图逻辑上将顶点和边的属性保存为一个`RDD[EdgeTriplet[VD, ED]]`,它包含[EdgeTriplet](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.EdgeTriplet)类的实例。 83 | 可以通过下面的Sql表达式表示这个连接。 84 | ```sql 85 | SELECT src.id, dst.id, src.attr, e.attr, dst.attr 86 | FROM edges AS e LEFT JOIN vertices AS src, vertices AS dst 87 | ON e.srcId = src.Id AND e.dstId = dst.Id 88 | ``` 89 | 90 | 或者通过下面的图来表示。 91 | 92 | ![triplet](../img/triplet.png) 93 | 94 | `EdgeTriplet`类继承于`Edge`类,并且加入了`srcAttr`和`dstAttr`成员,这两个成员分别包含源和目的的属性。我们可以用一个三元组视图渲染字符串集合用来描述用户之间的关系。 95 | 96 | ```scala 97 | val graph: Graph[(String, String), String] // Constructed from above 98 | // Use the triplets view to create an RDD of facts. 99 | val facts: RDD[String] = 100 | graph.triplets.map(triplet => 101 | triplet.srcAttr._1 + " is the " + triplet.attr + " of " + triplet.dstAttr._1) 102 | facts.collect.foreach(println(_)) 103 | ``` 104 | 105 | 106 | -------------------------------------------------------------------------------- /graphx-programming-guide/vertex-and-edge-rdds.md: -------------------------------------------------------------------------------- 1 | # 顶点和边RDDs 2 | 3 | GraphX暴露保存在图中的顶点和边的RDD。然而,因为GraphX包含的顶点和边拥有优化的数据结构,这些数据结构提供了额外的功能。顶点和边分别返回`VertexRDD`和`EdgeRDD`。这一章 4 | 我们将学习它们的一些有用的功能。 5 | 6 | ## VertexRDDs 7 | 8 | `VertexRDD[A]`继承自`RDD[(VertexID, A)]`并且添加了额外的限制,那就是每个`VertexID`只能出现一次。此外,`VertexRDD[A]`代表了一组属性类型为A的顶点。在内部,这通过 9 | 保存顶点属性到一个可重复使用的hash-map数据结构来获得。所以,如果两个`VertexRDDs`从相同的基本`VertexRDD`获得(如通过filter或者mapValues),它们能够在固定的时间内连接 10 | 而不需要hash评价。为了利用这个索引数据结构,`VertexRDD`暴露了一下附加的功能: 11 | 12 | ```scala 13 | class VertexRDD[VD] extends RDD[(VertexID, VD)] { 14 | // Filter the vertex set but preserves the internal index 15 | def filter(pred: Tuple2[VertexId, VD] => Boolean): VertexRDD[VD] 16 | // Transform the values without changing the ids (preserves the internal index) 17 | def mapValues[VD2](map: VD => VD2): VertexRDD[VD2] 18 | def mapValues[VD2](map: (VertexId, VD) => VD2): VertexRDD[VD2] 19 | // Remove vertices from this set that appear in the other set 20 | def diff(other: VertexRDD[VD]): VertexRDD[VD] 21 | // Join operators that take advantage of the internal indexing to accelerate joins (substantially) 22 | def leftJoin[VD2, VD3](other: RDD[(VertexId, VD2)])(f: (VertexId, VD, Option[VD2]) => VD3): VertexRDD[VD3] 23 | def innerJoin[U, VD2](other: RDD[(VertexId, U)])(f: (VertexId, VD, U) => VD2): VertexRDD[VD2] 24 | // Use the index on this RDD to accelerate a `reduceByKey` operation on the input RDD. 25 | def aggregateUsingIndex[VD2](other: RDD[(VertexId, VD2)], reduceFunc: (VD2, VD2) => VD2): VertexRDD[VD2] 26 | } 27 | ``` 28 | 29 | 举个例子,`filter`操作如何返回一个VertexRDD。过滤器实际使用一个`BitSet`实现,因此它能够重用索引以及保留和其它`VertexRDDs`做连接时速度快的能力。同样的,`mapValues`操作 30 | 不允许`map`函数改变`VertexID`,因此可以保证相同的`HashMap`数据结构能够重用。当连接两个从相同的`hashmap`获取的VertexRDDs和使用线性扫描而不是昂贵的点查找实现连接操作时,`leftJoin` 31 | 和`innerJoin`都能够使用。 32 | 33 | 从一个`RDD[(VertexID, A)]`高效地构建一个新的`VertexRDD`,`aggregateUsingIndex`操作是有用的。概念上,如果我通过一组顶点构造了一个`VertexRDD[B]`,而`VertexRDD[B]`是 34 | 一些`RDD[(VertexID, A)]`中顶点的超集,那么我们就可以在聚合以及随后索引`RDD[(VertexID, A)]`中重用索引。例如: 35 | 36 | ```scala 37 | val setA: VertexRDD[Int] = VertexRDD(sc.parallelize(0L until 100L).map(id => (id, 1))) 38 | val rddB: RDD[(VertexId, Double)] = sc.parallelize(0L until 100L).flatMap(id => List((id, 1.0), (id, 2.0))) 39 | // There should be 200 entries in rddB 40 | rddB.count 41 | val setB: VertexRDD[Double] = setA.aggregateUsingIndex(rddB, _ + _) 42 | // There should be 100 entries in setB 43 | setB.count 44 | // Joining A and B should now be fast! 45 | val setC: VertexRDD[Double] = setA.innerJoin(setB)((id, a, b) => a + b) 46 | ``` 47 | 48 | ## EdgeRDDs 49 | 50 | `EdgeRDD[ED]`继承自`RDD[Edge[ED]]`,使用定义在[PartitionStrategy](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.graphx.PartitionStrategy)的 51 | 各种分区策略中的一个在块分区中组织边。在每个分区中,边属性和相邻结构被分别保存,当属性值改变时,它们可以最大化的重用。 52 | 53 | `EdgeRDD`暴露了三个额外的函数 54 | 55 | ```scala 56 | // Transform the edge attributes while preserving the structure 57 | def mapValues[ED2](f: Edge[ED] => ED2): EdgeRDD[ED2] 58 | // Revere the edges reusing both attributes and structure 59 | def reverse: EdgeRDD[ED] 60 | // Join two `EdgeRDD`s partitioned using the same partitioning strategy. 61 | def innerJoin[ED2, ED3](other: EdgeRDD[ED2])(f: (VertexId, VertexId, ED, ED2) => ED3): EdgeRDD[ED3] 62 | ``` 63 | 64 | 在大多数的应用中,我们发现,EdgeRDD操作可以通过图操作者(graph operators)或者定义在基本RDD中的操作来完成。 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /img/data_parallel_vs_graph_parallel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/data_parallel_vs_graph_parallel.png -------------------------------------------------------------------------------- /img/flume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/flume.png -------------------------------------------------------------------------------- /img/graph_analytics_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/graph_analytics_pipeline.png -------------------------------------------------------------------------------- /img/property_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/property_graph.png -------------------------------------------------------------------------------- /img/streaming-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/streaming-arch.png -------------------------------------------------------------------------------- /img/streaming-dstream-ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/streaming-dstream-ops.png -------------------------------------------------------------------------------- /img/streaming-dstream-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/streaming-dstream-window.png -------------------------------------------------------------------------------- /img/streaming-dstream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/streaming-dstream.png -------------------------------------------------------------------------------- /img/streaming-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/streaming-flow.png -------------------------------------------------------------------------------- /img/streaming-kinesis-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/streaming-kinesis-arch.png -------------------------------------------------------------------------------- /img/tables_and_graphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/tables_and_graphs.png -------------------------------------------------------------------------------- /img/triplet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyanbo/spark-programming-guide-zh-cn/487c68a9aba8529ee6b81ff4795ae22819210232/img/triplet.png -------------------------------------------------------------------------------- /more/spark-configuration.md: -------------------------------------------------------------------------------- 1 | # Spark配置 2 | 3 | Spark提供三个位置用来配置系统: 4 | 5 | - Spark properties控制大部分的应用程序参数,可以用SparkConf对象或者java系统属性设置 6 | - Environment variables可以通过每个节点的` conf/spark-env.sh`脚本设置每台机器的设置。例如IP地址 7 | - Logging可以通过log4j.properties配置 8 | 9 | ## Spark属性 10 | 11 | Spark属性控制大部分的应用程序设置,并且为每个应用程序分别配置它。这些属性可以直接在[SparkConf](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.SparkConf)上配置,然后传递给`SparkContext`。`SparkConf` 12 | 允许你配置一些通用的属性(如master URL、应用程序明)以及通过`set()`方法设置的任意键值对。例如,我们可以用如下方式创建一个拥有两个线程的应用程序。注意,我们用`local[2]`运行,这意味着两个线程-表示最小的 13 | 并行度,它可以帮助我们检测当在分布式环境下运行的时才出现的错误。 14 | 15 | ```scala 16 | val conf = new SparkConf() 17 | .setMaster("local[2]") 18 | .setAppName("CountingSheep") 19 | .set("spark.executor.memory", "1g") 20 | val sc = new SparkContext(conf) 21 | ``` 22 | 23 | 注意,我们在本地模式中拥有超过1个线程。和Spark Streaming的情况一样,我们可能需要一个线程防止任何形式的饥饿问题。 24 | 25 | ### 动态加载Spark属性 26 | 27 | 在一些情况下,你可能想在`SparkConf`中避免硬编码确定的配置。例如,你想用不同的master或者不同的内存数运行相同的应用程序。Spark允许你简单地创建一个空conf。 28 | 29 | ```scala 30 | val sc = new SparkContext(new SparkConf()) 31 | ``` 32 | 然后你在运行时提供值。 33 | 34 | ```shell 35 | ./bin/spark-submit --name "My app" --master local[4] --conf spark.shuffle.spill=false 36 | --conf "spark.executor.extraJavaOptions=-XX:+PrintGCDetails -XX:+PrintGCTimeStamps" myApp.jar 37 | ``` 38 | 39 | Spark shell和`spark-submit`工具支持两种方式动态加载配置。第一种方式是命令行选项,例如`--master`,如上面shell显示的那样。`spark-submit`可以接受任何Spark属性,用`--conf` 40 | 标记表示。但是那些参与Spark应用程序启动的属性要用特定的标记表示。运行`./bin/spark-submit --help`将会显示选项的整个列表。 41 | 42 | `bin/spark-submit`也会从`conf/spark-defaults.conf`中读取配置选项,这个配置文件中,每一行都包含一对以空格分开的键和值。例如: 43 | 44 | ``` 45 | spark.master spark://5.6.7.8:7077 46 | spark.executor.memory 512m 47 | spark.eventLog.enabled true 48 | spark.serializer org.apache.spark.serializer.KryoSerializer 49 | ``` 50 | 51 | 任何标签(flags)指定的值或者在配置文件中的值将会传递给应用程序,并且通过`SparkConf`合并这些值。在`SparkConf`上设置的属性具有最高的优先级,其次是传递给`spark-submit` 52 | 或者`spark-shell`的属性值,最后是`spark-defaults.conf`文件中的属性值。 53 | 54 | ### 查看Spark属性 55 | 56 | 在`http://:4040`上的应用程序web UI在“Environment”标签中列出了所有的Spark属性。这对你确保设置的属性的正确性是很有用的。注意,只有通过spark-defaults.conf, SparkConf以及 57 | 命令行直接指定的值才会显示。对于其它的配置属性,你可以认为程序用到了默认的值。 58 | 59 | ### 可用的属性 60 | 61 | 控制内部设置的大部分属性都有合理的默认值,一些最通用的选项设置如下: 62 | 63 | #### 应用程序属性 64 | 65 | Property Name | Default | Meaning 66 | --- | --- | --- 67 | spark.app.name | (none) | 你的应用程序的名字。这将在UI和日志数据中出现 68 | spark.master | (none) | 集群管理器连接的地方 69 | spark.executor.memory | 512m | 每个executor进程使用的内存数。和JVM内存串拥有相同的格式(如512m,2g) 70 | spark.driver.memory | 512m | driver进程使用的内存数 71 | spark.driver.maxResultSize | 1g | 每个Spark action(如collect)所有分区的序列化结果的总大小限制。设置的值应该不小于1m,0代表没有限制。如果总大小超过这个限制,工作将会终止。大的限制值可能导致driver出现内存溢出错误(依赖于spark.driver.memory和JVM中对象的内存消耗)。设置合理的限制,可以避免出现内存溢出错误。 72 | spark.serializer | org.apache.spark.serializer.JavaSerializer | 序列化对象使用的类。默认的java序列化类可以序列化任何可序列化的java对象但是它很慢。所有我们建议用[org.apache.spark.serializer.KryoSerializer](http://spark.apache.org/docs/latest/tuning.html) 73 | spark.kryo.classesToRegister | (none) | 如果你用Kryo序列化,给定的用逗号分隔的自定义类名列表表示要注册的类 74 | spark.kryo.registrator | (none) | 如果你用Kryo序列化,设置这个类去注册你的自定义类。如果你需要用自定义的方式注册你的类,那么这个属性是有用的。否则`spark.kryo.classesToRegister`会更简单。它应该设置一个继承自[KryoRegistrator](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.serializer.KryoRegistrator)的类 75 | spark.local.dir | /tmp | Spark中暂存空间的使用目录。在Spark1.0以及更高的版本中,这个属性被SPARK_LOCAL_DIRS(Standalone, Mesos)和LOCAL_DIRS(YARN)环境变量覆盖。 76 | spark.logConf | false | 当SparkContext启动时,将有效的SparkConf记录为INFO。 77 | 78 | #### 运行环境 79 | 80 | Property Name | Default | Meaning 81 | --- | --- | --- 82 | spark.executor.extraJavaOptions | (none) | 传递给executors的JVM选项字符串。例如GC设置或者其它日志设置。注意,在这个选项中设置Spark属性或者堆大小是不合法的。Spark属性需要用SparkConf对象或者`spark-submit`脚本用到的`spark-defaults.conf`文件设置。堆内存可以通过`spark.executor.memory`设置 83 | spark.executor.extraClassPath | (none) | 附加到executors的classpath的额外的classpath实体。这个设置存在的主要目的是Spark与旧版本的向后兼容问题。用户一般不用设置这个选项 84 | spark.executor.extraLibraryPath | (none) | 指定启动executor的JVM时用到的库路径 85 | spark.executor.logs.rolling.strategy | (none) | 设置executor日志的滚动(rolling)策略。默认情况下没有开启。可以配置为`time`(基于时间的滚动)和`size`(基于大小的滚动)。对于`time`,用`spark.executor.logs.rolling.time.interval`设置滚动间隔;对于`size`,用`spark.executor.logs.rolling.size.maxBytes`设置最大的滚动大小 86 | spark.executor.logs.rolling.time.interval | daily | executor日志滚动的时间间隔。默认情况下没有开启。合法的值是`daily`, `hourly`, `minutely`以及任意的秒。 87 | spark.executor.logs.rolling.size.maxBytes | (none) | executor日志的最大滚动大小。默认情况下没有开启。值设置为字节 88 | spark.executor.logs.rolling.maxRetainedFiles | (none) | 设置被系统保留的最近滚动日志文件的数量。更老的日志文件将被删除。默认没有开启。 89 | spark.files.userClassPathFirst | false | (实验性)当在Executors中加载类时,是否用户添加的jar比Spark自己的jar优先级高。这个属性可以降低Spark依赖和用户依赖的冲突。它现在还是一个实验性的特征。 90 | spark.python.worker.memory | 512m | 在聚合期间,每个python worker进程使用的内存数。在聚合期间,如果内存超过了这个限制,它将会将数据塞进磁盘中 91 | spark.python.profile | false | 在Python worker中开启profiling。通过`sc.show_profiles()`展示分析结果。或者在driver退出前展示分析结果。可以通过`sc.dump_profiles(path)`将结果dump到磁盘中。如果一些分析结果已经手动展示,那么在driver退出前,它们再不会自动展示 92 | spark.python.profile.dump | (none) | driver退出前保存分析结果的dump文件的目录。每个RDD都会分别dump一个文件。可以通过`ptats.Stats()`加载这些文件。如果指定了这个属性,分析结果不会自动展示 93 | spark.python.worker.reuse | true | 是否重用python worker。如果是,它将使用固定数量的Python workers,而不需要为每个任务fork()一个Python进程。如果有一个非常大的广播,这个设置将非常有用。因为,广播不需要为每个任务从JVM到Python worker传递一次 94 | spark.executorEnv.[EnvironmentVariableName] | (none) | 通过`EnvironmentVariableName`添加指定的环境变量到executor进程。用户可以指定多个`EnvironmentVariableName`,设置多个环境变量 95 | spark.mesos.executor.home | driver side SPARK_HOME | 设置安装在Mesos的executor上的Spark的目录。默认情况下,executors将使用driver的Spark本地(home)目录,这个目录对它们不可见。注意,如果没有通过` spark.executor.uri`指定Spark的二进制包,这个设置才起作用 96 | spark.mesos.executor.memoryOverhead | executor memory * 0.07, 最小384m | 这个值是`spark.executor.memory`的补充。它用来计算mesos任务的总内存。另外,有一个7%的硬编码设置。最后的值将选择`spark.mesos.executor.memoryOverhead`或者`spark.executor.memory`的7%二者之间的大者 97 | 98 | #### Shuffle行为(Behavior) 99 | 100 | Property Name | Default | Meaning 101 | --- | --- | --- 102 | spark.shuffle.consolidateFiles | false | 如果设置为"true",在shuffle期间,合并的中间文件将会被创建。创建更少的文件可以提供文件系统的shuffle的效率。这些shuffle都伴随着大量递归任务。当用ext4和dfs文件系统时,推荐设置为"true"。在ext3中,因为文件系统的限制,这个选项可能机器(大于8核)降低效率 103 | spark.shuffle.spill | true | 如果设置为"true",通过将多出的数据写入磁盘来限制内存数。通过`spark.shuffle.memoryFraction`来指定spilling的阈值 104 | spark.shuffle.spill.compress | true | 在shuffle时,是否将spilling的数据压缩。压缩算法通过`spark.io.compression.codec`指定。 105 | spark.shuffle.memoryFraction | 0.2 | 如果`spark.shuffle.spill`为“true”,shuffle中聚合和合并组操作使用的java堆内存占总内存的比重。在任何时候,shuffles使用的所有内存内maps的集合大小都受这个限制的约束。超过这个限制,spilling数据将会保存到磁盘上。如果spilling太过频繁,考虑增大这个值 106 | spark.shuffle.compress | true | 是否压缩map操作的输出文件。一般情况下,这是一个好的选择。 107 | spark.shuffle.file.buffer.kb | 32 | 每个shuffle文件输出流内存内缓存的大小,单位是kb。这个缓存减少了创建只中间shuffle文件中磁盘搜索和系统访问的数量 108 | spark.reducer.maxMbInFlight | 48 | 从递归任务中同时获取的map输出数据的最大大小(mb)。因为每一个输出都需要我们创建一个缓存用来接收,这个设置代表每个任务固定的内存上限,所以除非你有更大的内存,将其设置小一点 109 | spark.shuffle.manager | sort | 它的实现用于shuffle数据。有两种可用的实现:`sort`和`hash`。基于sort的shuffle有更高的内存使用率 110 | spark.shuffle.sort.bypassMergeThreshold | 200 | (Advanced) In the sort-based shuffle manager, avoid merge-sorting data if there is no map-side aggregation and there are at most this many reduce partitions 111 | spark.shuffle.blockTransferService | netty | 实现用来在executor直接传递shuffle和缓存块。有两种可用的实现:`netty`和`nio`。基于netty的块传递在具有相同的效率情况下更简单 112 | 113 | #### Spark UI 114 | 115 | Property Name | Default | Meaning 116 | --- | --- | --- 117 | spark.ui.port | 4040 | 你的应用程序dashboard的端口。显示内存和工作量数据 118 | spark.ui.retainedStages | 1000 | 在垃圾回收之前,Spark UI和状态API记住的stage数 119 | spark.ui.retainedJobs | 1000 | 在垃圾回收之前,Spark UI和状态API记住的job数 120 | spark.ui.killEnabled | true | 运行在web UI中杀死stage和相应的job 121 | spark.eventLog.enabled | false | 是否记录Spark的事件日志。这在应用程序完成后,重新构造web UI是有用的 122 | spark.eventLog.compress | false | 是否压缩事件日志。需要`spark.eventLog.enabled`为true 123 | spark.eventLog.dir | file:///tmp/spark-events | Spark事件日志记录的基本目录。在这个基本目录下,Spark为每个应用程序创建一个子目录。各个应用程序记录日志到直到的目录。用户可能想设置这为统一的地点,像HDFS一样,所以历史文件可以通过历史服务器读取 124 | 125 | #### 压缩和序列化 126 | 127 | Property Name | Default | Meaning 128 | --- | --- | --- 129 | spark.broadcast.compress | true | 在发送广播变量之前是否压缩它 130 | spark.rdd.compress | true | 是否压缩序列化的RDD分区。在花费一些额外的CPU时间的同时节省大量的空间 131 | spark.io.compression.codec | snappy | 压缩诸如RDD分区、广播变量、shuffle输出等内部数据的编码解码器。默认情况下,Spark提供了三种选择:lz4, lzf和snappy。你也可以用完整的类名来制定。`org.apache.spark.io.LZ4CompressionCodec`,`org.apache.spark.io.LZFCompressionCodec`,`org.apache.spark.io.SnappyCompressionCodec` 132 | spark.io.compression.snappy.block.size | 32768 | Snappy压缩中用到的块大小。降低这个块的大小也会降低shuffle内存使用率 133 | spark.io.compression.lz4.block.size | 32768 | LZ4压缩中用到的块大小。降低这个块的大小也会降低shuffle内存使用率 134 | spark.closure.serializer | org.apache.spark.serializer.JavaSerializer | 闭包用到的序列化类。目前只支持java序列化器 135 | spark.serializer.objectStreamReset | 100 | 当用`org.apache.spark.serializer.JavaSerializer`序列化时,序列化器通过缓存对象防止写多余的数据,然而这会造成这些对象的垃圾回收停止。通过请求'reset',你从序列化器中flush这些信息并允许收集老的数据。为了关闭这个周期性的reset,你可以将值设为-1。默认情况下,每一百个对象reset一次 136 | spark.kryo.referenceTracking | true | 当用Kryo序列化时,跟踪是否引用同一对象。如果你的对象图有环,这是必须的设置。如果他们包含相同对象的多个副本,这个设置对效率是有用的。如果你知道不在这两个场景,那么可以禁用它以提高效率 137 | spark.kryo.registrationRequired | false | 是否需要注册为Kyro可用。如果设置为true,然后如果一个没有注册的类序列化,Kyro会抛出异常。如果设置为false,Kryo将会同时写每个对象和其非注册类名。写类名可能造成显著地性能瓶颈。 138 | spark.kryoserializer.buffer.mb | 0.064 | Kyro序列化缓存的大小。这样worker上的每个核都有一个缓存。如果有需要,缓存会涨到`spark.kryoserializer.buffer.max.mb`设置的值那么大。 139 | spark.kryoserializer.buffer.max.mb | 64 | Kryo序列化缓存允许的最大值。这个值必须大于你尝试序列化的对象 140 | 141 | #### Networking 142 | 143 | Property Name | Default | Meaning 144 | --- | --- | --- 145 | spark.driver.host | (local hostname) | driver监听的主机名或者IP地址。这用于和executors以及独立的master通信 146 | spark.driver.port | (random) | driver监听的接口。这用于和executors以及独立的master通信 147 | spark.fileserver.port | (random) | driver的文件服务器监听的端口 148 | spark.broadcast.port | (random) | driver的HTTP广播服务器监听的端口 149 | spark.replClassServer.port | (random) | driver的HTTP类服务器监听的端口 150 | spark.blockManager.port | (random) | 块管理器监听的端口。这些同时存在于driver和executors 151 | spark.executor.port | (random) | executor监听的端口。用于与driver通信 152 | spark.port.maxRetries | 16 | 当绑定到一个端口,在放弃前重试的最大次数 153 | spark.akka.frameSize | 10 | 在"control plane"通信中允许的最大消息大小。如果你的任务需要发送大的结果到driver中,调大这个值 154 | spark.akka.threads | 4 | 通信的actor线程数。当driver有很多CPU核时,调大它是有用的 155 | spark.akka.timeout | 100 | Spark节点之间的通信超时。单位是s 156 | spark.akka.heartbeat.pauses | 6000 | This is set to a larger value to disable failure detector that comes inbuilt akka. It can be enabled again, if you plan to use this feature (Not recommended). Acceptable heart beat pause in seconds for akka. This can be used to control sensitivity to gc pauses. Tune this in combination of `spark.akka.heartbeat.interval` and `spark.akka.failure-detector.threshold` if you need to. 157 | spark.akka.failure-detector.threshold | 300.0 | This is set to a larger value to disable failure detector that comes inbuilt akka. It can be enabled again, if you plan to use this feature (Not recommended). This maps to akka's `akka.remote.transport-failure-detector.threshold`. Tune this in combination of `spark.akka.heartbeat.pauses` and `spark.akka.heartbeat.interval` if you need to. 158 | spark.akka.heartbeat.interval | 1000 | This is set to a larger value to disable failure detector that comes inbuilt akka. It can be enabled again, if you plan to use this feature (Not recommended). A larger interval value in seconds reduces network overhead and a smaller value ( ~ 1 s) might be more informative for akka's failure detector. Tune this in combination of `spark.akka.heartbeat.pauses` and `spark.akka.failure-detector.threshold` if you need to. Only positive use case for using failure detector can be, a sensistive failure detector can help evict rogue executors really quick. However this is usually not the case as gc pauses and network lags are expected in a real Spark cluster. Apart from that enabling this leads to a lot of exchanges of heart beats between nodes leading to flooding the network with those. 159 | 160 | #### Security 161 | 162 | Property Name | Default | Meaning 163 | --- | --- | --- 164 | spark.authenticate | false | 是否Spark验证其内部连接。如果不是运行在YARN上,请看`spark.authenticate.secret` 165 | spark.authenticate.secret | None | 设置Spark两个组件之间的密匙验证。如果不是运行在YARN上,但是需要验证,这个选项必须设置 166 | spark.core.connection.auth.wait.timeout | 30 | 连接时等待验证的实际。单位为秒 167 | spark.core.connection.ack.wait.timeout | 60 | 连接等待回答的时间。单位为秒。为了避免不希望的超时,你可以设置更大的值 168 | spark.ui.filters | None | 应用到Spark web UI的用于过滤类名的逗号分隔的列表。过滤器必须是标准的[javax servlet Filter](http://docs.oracle.com/javaee/6/api/javax/servlet/Filter.html)。通过设置java系统属性也可以指定每个过滤器的参数。`spark..params='param1=value1,param2=value2'`。例如`-Dspark.ui.filters=com.test.filter1`、`-Dspark.com.test.filter1.params='param1=foo,param2=testing'` 169 | spark.acls.enable | false | 是否开启Spark acls。如果开启了,它检查用户是否有权限去查看或修改job。 Note this requires the user to be known, so if the user comes across as null no checks are done。UI利用使用过滤器验证和设置用户 170 | spark.ui.view.acls | empty | 逗号分隔的用户列表,列表中的用户有查看(view)Spark web UI的权限。默认情况下,只有启动Spark job的用户有查看权限 171 | spark.modify.acls | empty | 逗号分隔的用户列表,列表中的用户有修改Spark job的权限。默认情况下,只有启动Spark job的用户有修改权限 172 | spark.admin.acls | empty | 逗号分隔的用户或者管理员列表,列表中的用户或管理员有查看和修改所有Spark job的权限。如果你运行在一个共享集群,有一组管理员或开发者帮助debug,这个选项有用 173 | 174 | #### Spark Streaming 175 | 176 | Property Name | Default | Meaning 177 | --- | --- | --- 178 | spark.streaming.blockInterval | 200 | 在这个时间间隔(ms)内,通过Spark Streaming receivers接收的数据在保存到Spark之前,chunk为数据块。推荐的最小值为50ms 179 | spark.streaming.receiver.maxRate | infinite | 每秒钟每个receiver将接收的数据的最大记录数。有效的情况下,每个流将消耗至少这个数目的记录。设置这个配置为0或者-1将会不作限制 180 | spark.streaming.receiver.writeAheadLogs.enable | false | Enable write ahead logs for receivers. All the input data received through receivers will be saved to write ahead logs that will allow it to be recovered after driver failures 181 | spark.streaming.unpersist | true | 强制通过Spark Streaming生成并持久化的RDD自动从Spark内存中非持久化。通过Spark Streaming接收的原始输入数据也将清除。设置这个属性为false允许流应用程序访问原始数据和持久化RDD,因为它们没有被自动清除。但是它会造成更高的内存花费 182 | 183 | ## 环境变量 184 | 185 | 通过环境变量配置确定的Spark设置。环境变量从Spark安装目录下的`conf/spark-env.sh`脚本读取(或者windows的`conf/spark-env.cmd`)。在独立的或者Mesos模式下,这个文件可以给机器 186 | 确定的信息,如主机名。当运行本地应用程序或者提交脚本时,它也起作用。 187 | 188 | 注意,当Spark安装时,`conf/spark-env.sh`默认是不存在的。你可以复制`conf/spark-env.sh.template`创建它。 189 | 190 | 可以在`spark-env.sh`中设置如下变量: 191 | 192 | Environment Variable | Meaning 193 | --- | --- 194 | JAVA_HOME | java安装的路径 195 | PYSPARK_PYTHON | PySpark用到的Python二进制执行文件路径 196 | SPARK_LOCAL_IP | 机器绑定的IP地址 197 | SPARK_PUBLIC_DNS | 你Spark应用程序通知给其他机器的主机名 198 | 199 | 除了以上这些,Spark [standalone cluster scripts](http://spark.apache.org/docs/latest/spark-standalone.html#cluster-launch-scripts)也可以设置一些选项。例如 200 | 每台机器使用的核数以及最大内存。 201 | 202 | 因为`spark-env.sh`是shell脚本,其中的一些可以以编程方式设置。例如,你可以通过特定的网络接口计算`SPARK_LOCAL_IP`。 203 | 204 | ## 配置Logging 205 | 206 | Spark用[log4j](http://logging.apache.org/log4j/) logging。你可以通过在conf目录下添加`log4j.properties`文件来配置。一种方法是复制`log4j.properties.template`文件。 207 | -------------------------------------------------------------------------------- /programming-guide/README.md: -------------------------------------------------------------------------------- 1 | # 概论 2 | 3 | 宏观上说,每个 Spark 应用程序都由一个*驱动程序(driver programe)*构成,驱动程序在集群上运行用户的 `main` 函数来执行各种各样的*并行操作(parallel operations)*。Spark 的主要抽象是提供一个*弹性分布式数据集(RDD resilient distributed dataset)*,RDD 是指能横跨集群所有节点进行并行计算的分区元素集合。RDD 可以从 Hadoop 文件系统中的一个文件中创建而来(或其他 Hadoop 支持的文件系统),或者从一个已有的 Scala 集合转换得到。用户可以要求 Spark 将 RDD *持久化(persist)*到内存中,来让它在并行计算中高效地重用。最后,RDD 能从节点失败中自动地恢复过来。 4 | 5 | Spark 的第二个抽象是*共享变量(shared variables)*,共享变量能被运行在并行计算中。默认情况下,当 Spark 运行一个并行函数时,这个并行函数会作为一个任务集在不同的节点上运行,它会把函数里使用的每个变量都复制搬运到每个任务中。有时,一个变量需要被共享到交叉任务中或驱动程序和任务之间。Spark 支持 2 种类型的共享变量:*广播变量(broadcast variables)*,用来在所有节点的内存中缓存一个值;累加器(accumulators),仅仅只能执行“添加(added)”操作,例如:记数器(counters)和求和(sums)。 6 | 7 | 这个指南会在 Spark 支持的所有语言中演示它的每一个特征。可以非常简单地从一个 Spark 交互式 shell 开始 -—— `bin/spark-shell` 开始一个 Scala shell,或 `bin/pyspark` 开始一个 Python shell。 8 | 9 | * [引入 Spark](linking-with-spark.md) 10 | * [初始化 Spark](initializing-spark.md) 11 | * [Spark RDDs](rdds/README.md) 12 | * [共享变量](shared-variables.md) 13 | * [从这里开始](from-here.md) -------------------------------------------------------------------------------- /programming-guide/from-here.md: -------------------------------------------------------------------------------- 1 | # 从这里开始 2 | 3 | 你能够从spark官方网站查看一些[spark运行例子](http://spark.apache.org/examples.html)。另外,Spark的example目录包含几个Spark例子,你能够通过如下方式运行Java或者scala例子: 4 | ```shell 5 | ./bin/run-example SparkPi 6 | ``` 7 | 为了优化你的项目, [configuration](https://spark.apache.org/docs/latest/configuration.html)和[tuning](https://spark.apache.org/docs/latest/tuning.html)指南提高了最佳 8 | 实践的信息。保证你保存在内存中的数据是有效的格式是非常重要的事情。为了给部署操作提高帮助,[集群模式概述](https://spark.apache.org/docs/latest/cluster-overview.html)介绍了 9 | 包含分布式操作和支持集群管理的组件。 10 | 11 | 最后,完整的API文档可以在后面链接[scala](https://spark.apache.org/docs/latest/api/scala/#org.apache.spark.package),[java](https://spark.apache.org/docs/latest/api/java/), 12 | [python](https://spark.apache.org/docs/latest/api/python/)中查看。 -------------------------------------------------------------------------------- /programming-guide/initializing-spark.md: -------------------------------------------------------------------------------- 1 | # 初始化 Spark 2 | 3 | Spark 编程的第一步是需要创建一个 [SparkContext](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.SparkContext) 对象,用来告诉 Spark 如何访问集群。在创建 `SparkContext` 之前,你需要构建一个 [SparkConf](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.SparkConf) 对象, SparkConf 对象包含了一些你应用程序的信息。 4 | 5 | ```scala 6 | val conf = new SparkConf().setAppName(appName).setMaster(master) 7 | new SparkContext(conf) 8 | ``` 9 | 10 | `appName` 参数是你程序的名字,它会显示在 cluster UI 上。`master` 是 [Spark, Mesos 或 YARN 集群的 URL](https://spark.apache.org/docs/latest/submitting-applications.html#master-urls),或运行在本地模式时,使用专用字符串 “local”。在实践中,当应用程序运行在一个集群上时,你并不想要把 `master` 硬编码到你的程序中,你可以[用 spark-submit 启动你的应用程序](https://spark.apache.org/docs/latest/submitting-applications.html)的时候传递它。然而,你可以在本地测试和单元测试中使用 “local” 运行 Spark 进程。 11 | 12 | ## 使用 Shell 13 | 14 | 在 Spark shell 中,有一个专有的 SparkContext 已经为你创建好。在变量中叫做 `sc`。你自己创建的 SparkContext 将无法工作。可以用 `--master` 参数来设置 SparkContext 要连接的集群,用 `--jars` 来设置需要添加到 classpath 中的 JAR 包,如果有多个 JAR 包使用**逗号**分割符连接它们。例如:在一个拥有 4 核的环境上运行 `bin/spark-shell`,使用: 15 | 16 | ``` 17 | $ ./bin/spark-shell --master local[4] 18 | ``` 19 | 20 | 或在 classpath 中添加 `code.jar`,使用: 21 | 22 | ``` 23 | $ ./bin/spark-shell --master local[4] --jars code.jar 24 | ``` 25 | 26 | 执行 `spark-shell --help` 获取完整的选项列表。在这之后,调用 `spark-shell` 会比 [spark-submit 脚本](https://spark.apache.org/docs/latest/submitting-applications.html)更为普遍。 -------------------------------------------------------------------------------- /programming-guide/linking-with-spark.md: -------------------------------------------------------------------------------- 1 | # 引入 Spark 2 | 3 | Spark 1.2.0 使用 Scala 2.10 写应用程序,你需要使用一个兼容的 Scala 版本(例如:2.10.X)。 4 | 5 | 写 Spark 应用程序时,你需要添加 Spark 的 Maven 依赖,Spark 可以通过 Maven 中心仓库来获得: 6 | 7 | ``` 8 | groupId = org.apache.spark 9 | artifactId = spark-core_2.10 10 | version = 1.2.0 11 | ``` 12 | 13 | 另外,如果你希望访问 HDFS 集群,你需要根据你的 HDFS 版本添加 `hadoop-client` 的依赖。一些公共的 HDFS 版本 tags 在[第三方发行页面](https://spark.apache.org/docs/latest/hadoop-third-party-distributions.html)中被列出。 14 | 15 | ``` 16 | groupId = org.apache.hadoop 17 | artifactId = hadoop-client 18 | version = 19 | ``` 20 | 21 | 最后,你需要导入一些 Spark 的类和隐式转换到你的程序,添加下面的行就可以了: 22 | 23 | ```scala 24 | import org.apache.spark.SparkContext 25 | import org.apache.spark.SparkContext._ 26 | import org.apache.spark.SparkConf 27 | ``` -------------------------------------------------------------------------------- /programming-guide/rdds/README.md: -------------------------------------------------------------------------------- 1 | # 弹性分布式数据集 (RDDs) 2 | 3 | Spark 核心的概念是 _Resilient Distributed Dataset (RDD)_:一个可并行操作的有容错机制的数据集合。有 2 种方式创建 RDDs:第一种是在你的驱动程序中并行化一个已经存在的集合;另外一种是引用一个外部存储系统的数据集,例如共享的文件系统,HDFS,HBase或其他 Hadoop 数据格式的数据源。 4 | 5 | * [并行集合](parallelized-collections.md) 6 | * [外部数据集](external-datasets.md) 7 | * [RDD 操作](rdd-operations.md) 8 | * [传递函数到 Spark](passing-functions-to-spark.md) 9 | * [使用键值对](working-with-key-value-pairs.md) 10 | * [Transformations](transformations.md) 11 | * [Actions](actions.md) 12 | * [RDD持久化](rdd-persistences.md) 13 | -------------------------------------------------------------------------------- /programming-guide/rdds/actions.md: -------------------------------------------------------------------------------- 1 | # Actions 2 | 3 | 下面的表格列了 Spark 支持的一些常用 actions。详细内容请参阅 RDD API 文档([Scala](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.RDD), [Java](https://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaRDD.html), [Python](https://spark.apache.org/docs/latest/api/python/pyspark.rdd.RDD-class.html)) 和 PairRDDFunctions 文档([Scala](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.PairRDDFunctions), [Java](https://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaPairRDD.html))。 4 | 5 | Action | Meaning 6 | --- | --- 7 | reduce(func) | Aggregate the elements of the dataset using a function func (which takes two arguments and returns one). The function should be commutative and associative so that it can be computed correctly in parallel. 8 | collect() | Return all the elements of the dataset as an array at the driver program. This is usually useful after a filter or other operation that returns a sufficiently small subset of the data. 9 | count() | Return the number of elements in the dataset. 10 | first() | Return the first element of the dataset (similar to take(1)). 11 | take(n) | Return an array with the first n elements of the dataset. Note that this is currently not executed in parallel. Instead, the driver program computes all the elements. 12 | takeSample(withReplacement, num, [seed]) | Return an array with a random sample of num elements of the dataset, with or without replacement, optionally pre-specifying a random number generator seed. 13 | takeOrdered(n, [ordering]) | Return the first n elements of the RDD using either their natural order or a custom comparator. 14 | saveAsTextFile(path) | Write the elements of the dataset as a text file (or set of text files) in a given directory in the local filesystem, HDFS or any other Hadoop-supported file system. Spark will call toString on each element to convert it to a line of text in the file. 15 | saveAsSequenceFile(path) (Java and Scala) | Write the elements of the dataset as a Hadoop SequenceFile in a given path in the local filesystem, HDFS or any other Hadoop-supported file system. This is available on RDDs of key-value pairs that either implement Hadoop's Writable interface. In Scala, it is also available on types that are implicitly convertible to Writable (Spark includes conversions for basic types like Int, Double, String, etc). 16 | saveAsObjectFile(path) (Java and Scala) | Write the elements of the dataset in a simple format using Java serialization, which can then be loaded using SparkContext.objectFile(). 17 | countByKey() | Only available on RDDs of type (K, V). Returns a hashmap of (K, Int) pairs with the count of each key. 18 | foreach(func) | Run a function func on each element of the dataset. This is usually done for side effects such as updating an accumulator variable (see below) or interacting with external storage systems. -------------------------------------------------------------------------------- /programming-guide/rdds/external-datasets.md: -------------------------------------------------------------------------------- 1 | ## 外部数据集 2 | 3 | Spark 可以从任何一个 Hadoop 支持的存储源创建分布式数据集,包括你的本地文件系统,HDFS,Cassandra,HBase,[Amazon S3](http://wiki.apache.org/hadoop/AmazonS3)等。 Spark 支持文本文件(text files),[SequenceFiles](http://hadoop.apache.org/docs/current/api/org/apache/hadoop/mapred/SequenceFileInputFormat.html) 和其他 Hadoop [InputFormat](http://hadoop.apache.org/docs/stable/api/org/apache/hadoop/mapred/InputFormat.html)。 4 | 5 | 文本文件 RDDs 可以使用 SparkContext 的 `textFile` 方法创建。 在这个方法里传入文件的 URI (机器上的本地路径或 `hdfs://`,`s3n://` 等),然后它会将文件读取成一个行集合。这里是一个调用例子: 6 | 7 | ```scala 8 | scala> val distFile = sc.textFile("data.txt") 9 | distFile: RDD[String] = MappedRDD@1d4cee08 10 | ``` 11 | 12 | 一旦创建完成,`distFiile` 就能做数据集操作。例如,我们可以用下面的方式使用 `map` 和 `reduce` 操作将所有行的长度相加:`distFile.map(s => s.length).reduce((a, b) => a + b)`。 13 | 14 | 注意,Spark 读文件时: 15 | 16 | - 如果使用本地文件系统路径,文件必须能在 work 节点上用相同的路径访问到。要么复制文件到所有的 workers,要么使用网络的方式共享文件系统。 17 | - 所有 Spark 的基于文件的方法,包括 `textFile`,能很好地支持文件目录,压缩过的文件和通配符。例如,你可以使用 `textFile("/my/文件目录")`,`textFile("/my/文件目录/*.txt")` 和 `textFile("/my/文件目录/*.gz")`。 18 | - `textFile` 方法也可以选择第二个可选参数来控制切片(_slices_)的数目。默认情况下,Spark 为每一个文件块(HDFS 默认文件块大小是 64M)创建一个切片(_slice_)。但是你也可以通过一个更大的值来设置一个更高的切片数目。注意,你不能设置一个小于文件块数目的切片值。 19 | 20 | 除了文本文件,Spark 的 Scala API 支持其他几种数据格式: 21 | 22 | - `SparkContext.wholeTextFiles` 让你读取一个包含多个小文本文件的文件目录并且返回每一个(filename, content)对。与 `textFile` 的差异是:它记录的是每个文件中的每一行。 23 | - 对于 [SequenceFiles](http://hadoop.apache.org/docs/current/api/org/apache/hadoop/mapred/SequenceFileInputFormat.html),可以使用 SparkContext 的 `sequenceFile[K, V]` 方法创建,K 和 V 分别对应的是 key 和 values 的类型。像 [IntWritable](http://hadoop.apache.org/docs/current/api/org/apache/hadoop/io/IntWritable.html) 与 [Text](http://hadoop.apache.org/docs/current/api/org/apache/hadoop/io/Text.html) 一样,它们必须是 Hadoop 的 [Writable](http://hadoop.apache.org/docs/current/api/org/apache/hadoop/io/Writable.html) 接口的子类。另外,对于几种通用的 Writables,Spark 允许你指定原生类型来替代。例如: `sequenceFile[Int, String]` 将会自动读取 IntWritables 和 Text。 24 | - 对于其他的 Hadoop InputFormats,你可以使用 `SparkContext.hadoopRDD` 方法,它可以指定任意的 `JobConf`,输入格式(InputFormat),key 类型,values 类型。你可以跟设置 Hadoop job 一样的方法设置输入源。你还可以在新的 MapReduce 接口(org.apache.hadoop.mapreduce)基础上使用 `SparkContext.newAPIHadoopRDD`(译者注:老的接口是 `SparkContext.newHadoopRDD`)。 25 | - `RDD.saveAsObjectFile` 和 `SparkContext.objectFile` 支持保存一个RDD,保存格式是一个简单的 Java 对象序列化格式。这是一种效率不高的专有格式,如 Avro,它提供了简单的方法来保存任何一个 RDD。 -------------------------------------------------------------------------------- /programming-guide/rdds/parallelized-collections.md: -------------------------------------------------------------------------------- 1 | ## 并行集合 2 | 3 | 并行集合 (_Parallelized collections_) 的创建是通过在一个已有的集合(Scala `Seq`)上调用 SparkContext 的 `parallelize` 方法实现的。集合中的元素被复制到一个可并行操作的分布式数据集中。例如,这里演示了如何在一个包含 1 到 5 的数组中创建并行集合: 4 | 5 | ```scala 6 | val data = Array(1, 2, 3, 4, 5) 7 | val distData = sc.parallelize(data) 8 | ``` 9 | 10 | 一旦创建完成,这个分布式数据集(`distData`)就可以被并行操作。例如,我们可以调用 `distData.reduce((a, b) => a + b)` 将这个数组中的元素相加。我们以后再描述在分布式上的一些操作。 11 | 12 | 并行集合一个很重要的参数是切片数(_slices_),表示一个数据集切分的份数。Spark 会在集群上为每一个切片运行一个任务。你可以在集群上为每个 CPU 设置 2-4 个切片(slices)。正常情况下,Spark 会试着基于你的集群状况自动地设置切片的数目。然而,你也可以通过 `parallelize` 的第二个参数手动地设置(例如:`sc.parallelize(data, 10)`)。 -------------------------------------------------------------------------------- /programming-guide/rdds/passing-functions-to-spark.md: -------------------------------------------------------------------------------- 1 | # 传递函数到 Spark 2 | 3 | Spark 的 API 很大程度上依靠在驱动程序里传递函数到集群上运行。这里有两种推荐的方式: 4 | 5 | - [匿名函数 (Anonymous function syntax)](http://docs.scala-lang.org/tutorials/tour/anonymous-function-syntax.html),可以在比较短的代码中使用。 6 | - 全局单例对象里的静态方法。例如,你可以定义 `object MyFunctions` 然后传递 `MyFounctions.func1`,像下面这样: 7 | 8 | ```scala 9 | object MyFunctions { 10 | def func1(s: String): String = { ... } 11 | } 12 | 13 | myRdd.map(MyFunctions.func1) 14 | ``` 15 | 16 | 注意,它可能传递的是一个类实例里的一个方法引用(而不是一个单例对象),这里必须传送包含方法的整个对象。例如: 17 | 18 | ```scala 19 | class MyClass { 20 | def func1(s: String): String = { ... } 21 | def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) } 22 | } 23 | ``` 24 | 25 | 这里,如果我们创建了一个 `new MyClass` 对象,并且调用它的 `doStuff`,`map` 里面引用了这个 `MyClass` 实例中的 `func1` 方法,所以这个对象必须传送到集群上。类似写成 `rdd.map(x => this.func1(x))`。 26 | 27 | 以类似的方式,访问外部对象的字段将会引用整个对象: 28 | 29 | ```scala 30 | class MyClass { 31 | val field = "Hello" 32 | def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(x => field + x) } 33 | } 34 | ``` 35 | 36 | 相当于写成 `rdd.map(x => this.field + x)`,引用了整个 `this` 对象。为了避免这个问题,最简单的方式是复制 `field` 到一个本地变量而不是从外部访问它: 37 | 38 | ```scala 39 | def doStuff(rdd: RDD[String]): RDD[String] = { 40 | val field_ = this.field 41 | rdd.map(x => field_ + x) 42 | } 43 | ``` -------------------------------------------------------------------------------- /programming-guide/rdds/rdd-operations.md: -------------------------------------------------------------------------------- 1 | # RDD 操作 2 | 3 | RDDs 支持 2 种类型的操作:_转换(transformations)_ 从已经存在的数据集中创建一个新的数据集;_动作(actions)_ 在数据集上进行计算之后返回一个值到驱动程序。例如,`map` 是一个转换操作,它将每一个数据集元素传递给一个函数并且返回一个新的 RDD。另一方面,`reduce` 是一个动作,它使用相同的函数来聚合 RDD 的所有元素,并且将最终的结果返回到驱动程序(不过也有一个并行 `reduceByKey` 能返回一个分布式数据集)。 4 | 5 | 在 Spark 中,所有的转换(transformations)都是惰性(lazy)的,它们不会马上计算它们的结果。相反的,它们仅仅记录转换操作是应用到哪些基础数据集(例如一个文件)上的。转换仅仅在这个时候计算:当动作(action) 需要一个结果返回给驱动程序的时候。这个设计能够让 Spark 运行得更加高效。例如,我们可以实现:通过 `map` 创建一个新数据集在 `reduce` 中使用,并且仅仅返回 `reduce` 的结果给 driver,而不是整个大的映射过的数据集。 6 | 7 | 默认情况下,每一个转换过的 RDD 会在每次执行动作(action)的时候重新计算一次。然而,你也可以使用 `persist` (或 `cache`)方法持久化(`persist`)一个 RDD 到内存中。在这个情况下,Spark 会在集群上保存相关的元素,在你下次查询的时候会变得更快。在这里也同样支持持久化 RDD 到磁盘,或在多个节点间复制。 8 | 9 | ## 基础 10 | 11 | 为了说明 RDD 基本知识,考虑下面的简单程序: 12 | 13 | ```scala 14 | val lines = sc.textFile("data.txt") 15 | val lineLengths = lines.map(s => s.length) 16 | val totalLength = lineLengths.reduce((a, b) => a + b) 17 | ``` 18 | 19 | 第一行是定义来自于外部文件的 RDD。这个数据集并没有加载到内存或做其他的操作:`lines` 仅仅是一个指向文件的指针。第二行是定义 `lineLengths`,它是 `map` 转换(transformation)的结果。同样,`lineLengths` 由于懒惰模式也_没有_立即计算。最后,我们执行 `reduce`,它是一个动作(action)。在这个地方,Spark 把计算分成多个任务(task),并且让它们运行在多个机器上。每台机器都运行自己的 map 部分和本地 reduce 部分。然后仅仅将结果返回给驱动程序。 20 | 21 | 如果我们想要再次使用 `lineLengths`,我们可以添加: 22 | 23 | ```scala 24 | lineLengths.persist() 25 | ``` 26 | 27 | 在 `reduce` 之前,它会导致 `lineLengths` 在第一次计算完成之后保存到内存中。 -------------------------------------------------------------------------------- /programming-guide/rdds/rdd-persistences.md: -------------------------------------------------------------------------------- 1 | # RDD 持久化 2 | 3 | Spark最重要的一个功能是它可以通过各种操作(operations)持久化(或者缓存)一个集合到内存中。当你持久化一个RDD的时候,每一个节点都将参与计算的所有分区数据存储到内存中,并且这些 4 | 数据可以被这个集合(以及这个集合衍生的其他集合)的动作(action)重复利用。这个能力使后续的动作速度更快(通常快10倍以上)。对应迭代算法和快速的交互使用来说,缓存是一个关键的工具。 5 | 6 | 你能通过`persist()`或者`cache()`方法持久化一个rdd。首先,在action中计算得到rdd;然后,将其保存在每个节点的内存中。Spark的缓存是一个容错的技术-如果RDD的任何一个分区丢失,它 7 | 可以通过原有的转换(transformations)操作自动的重复计算并且创建出这个分区。 8 | 9 | 此外,我们可以利用不同的存储级别存储每一个被持久化的RDD。例如,它允许我们持久化集合到磁盘上、将集合作为序列化的Java对象持久化到内存中、在节点间复制集合或者存储集合到 10 | [Tachyon](http://tachyon-project.org/)中。我们可以通过传递一个`StorageLevel`对象给`persist()`方法设置这些存储级别。`cache()`方法使用了默认的存储级别—`StorageLevel.MEMORY_ONLY`。完整的存储级别介绍如下所示: 11 | 12 | Storage Level | Meaning 13 | --- | --- 14 | MEMORY_ONLY | 将RDD作为非序列化的Java对象存储在jvm中。如果RDD不适合存在内存中,一些分区将不会被缓存,从而在每次需要这些分区时都需重新计算它们。这是系统默认的存储级别。 15 | MEMORY_AND_DISK | 将RDD作为非序列化的Java对象存储在jvm中。如果RDD不适合存在内存中,将这些不适合存在内存中的分区存储在磁盘中,每次需要时读出它们。 16 | MEMORY_ONLY_SER | 将RDD作为序列化的Java对象存储(每个分区一个byte数组)。这种方式比非序列化方式更节省空间,特别是用到快速的序列化工具时,但是会更耗费cpu资源—密集的读操作。 17 | MEMORY_AND_DISK_SER | 和MEMORY_ONLY_SER类似,但不是在每次需要时重复计算这些不适合存储到内存中的分区,而是将这些分区存储到磁盘中。 18 | DISK_ONLY | 仅仅将RDD分区存储到磁盘中 19 | MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc. | 和上面的存储级别类似,但是复制每个分区到集群的两个节点上面 20 | OFF_HEAP (experimental) | 以序列化的格式存储RDD到[Tachyon](http://tachyon-project.org/)中。相对于MEMORY_ONLY_SER,OFF_HEAP减少了垃圾回收的花费,允许更小的执行者共享内存池。这使其在拥有大量内存的环境下或者多并发应用程序的环境中具有更强的吸引力。 21 | 22 | NOTE:在python中,存储的对象都是通过Pickle库序列化了的,所以是否选择序列化等级并不重要。 23 | 24 | Spark也会自动持久化一些shuffle操作(如`reduceByKey`)中的中间数据,即使用户没有调用`persist`方法。这样的好处是避免了在shuffle出错情况下,需要重复计算整个输入。如果用户计划重用 25 | 计算过程中产生的RDD,我们仍然推荐用户调用`persist`方法。 26 | 27 | ## 如何选择存储级别 28 | 29 | Spark的多个存储级别意味着在内存利用率和cpu利用效率间的不同权衡。我们推荐通过下面的过程选择一个合适的存储级别: 30 | 31 | - 如果你的RDD适合默认的存储级别(MEMORY_ONLY),就选择默认的存储级别。因为这是cpu利用率最高的选项,会使RDD上的操作尽可能的快。 32 | 33 | - 如果不适合用默认的级别,选择MEMORY_ONLY_SER。选择一个更快的序列化库提高对象的空间使用率,但是仍能够相当快的访问。 34 | 35 | - 除非函数计算RDD的花费较大或者它们需要过滤大量的数据,不要将RDD存储到磁盘上,否则,重复计算一个分区就会和重磁盘上读取数据一样慢。 36 | 37 | - 如果你希望更快的错误恢复,可以利用重复(replicated)存储级别。所有的存储级别都可以通过重复计算丢失的数据来支持完整的容错,但是重复的数据能够使你在RDD上继续运行任务,而不需要重复计算丢失的数据。 38 | 39 | - 在拥有大量内存的环境中或者多应用程序的环境中,OFF_HEAP具有如下优势: 40 | - 它运行多个执行者共享Tachyon中相同的内存池 41 | - 它显著地减少垃圾回收的花费 42 | - 如果单个的执行者崩溃,缓存的数据不会丢失 43 | 44 | ## 删除数据 45 | 46 | Spark自动的监控每个节点缓存的使用情况,利用最近最少使用原则删除老旧的数据。如果你想手动的删除RDD,可以使用`RDD.unpersist()`方法 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /programming-guide/rdds/rdd_persistence.md: -------------------------------------------------------------------------------- 1 | # RDD 持久化 2 | 3 | Spark 有一个最重要的功能是在内存中_持久化_ (或 _缓存_)一个数据集。 4 | -------------------------------------------------------------------------------- /programming-guide/rdds/transformations.md: -------------------------------------------------------------------------------- 1 | # Transformations 2 | 3 | 下面的表格列了 Spark 支持的一些常用 transformations。详细内容请参阅 RDD API 文档([Scala](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.RDD), [Java](https://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaRDD.html), [Python](https://spark.apache.org/docs/latest/api/python/pyspark.rdd.RDD-class.html)) 和 PairRDDFunctions 文档([Scala](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.PairRDDFunctions), [Java](https://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/api/java/JavaPairRDD.html))。 4 | 5 | Transformation | Meaning 6 | --- | --- 7 | map(_func_) | 返回一个新的分布式数据集,将数据源的每一个元素传递给函数 _func_ 映射组成。 8 | filter(_func_) | 返回一个新的数据集,从数据源中选中一些元素通过函数 _func_ 返回 true。 9 | flatMap(_func_) | 类似于 map,但是每个输入项能被映射成多个输出项(所以 _func_ 必须返回一个 Seq,而不是单个 item)。 10 | mapPartitions(_func_) | 类似于 map,但是分别运行在 RDD 的每个分区上,所以 _func_ 的类型必须是 `Iterator => Iterator` 当运行在类型为 T 的 RDD 上。 11 | mapPartitionsWithIndex(_func_) | 类似于 mapPartitions,但是 _func_ 需要提供一个 integer 值描述索引(index),所以 _func_ 的类型必须是 (Int, Iterator) => Iterator 当运行在类型为 T 的 RDD 上。 12 | sample(withReplacement, fraction, seed) | 对数据进行采样。 13 | union(otherDataset) | Return a new dataset that contains the union of the elements in the source dataset and the argument. 14 | intersection(otherDataset) | Return a new RDD that contains the intersection of elements in the source dataset and the argument. 15 | distinct([numTasks])) | Return a new dataset that contains the distinct elements of the source dataset. 16 | groupByKey([numTasks]) | When called on a dataset of (K, V) pairs, returns a dataset of (K, Iterable) pairs. Note: If you are grouping in order to perform an aggregation (such as a sum or average) over each key, using reduceByKey or combineByKey will yield much better performance. Note: By default, the level of parallelism in the output depends on the number of partitions of the parent RDD. You can pass an optional numTasks argument to set a different number of tasks. 17 | reduceByKey(func, [numTasks]) | When called on a dataset of (K, V) pairs, returns a dataset of (K, V) pairs where the values for each key are aggregated using the given reduce function func, which must be of type (V,V) => V. Like in groupByKey, the number of reduce tasks is configurable through an optional second argument. 18 | aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | When called on a dataset of (K, V) pairs, returns a dataset of (K, U) pairs where the values for each key are aggregated using the given combine functions and a neutral "zero" value. Allows an aggregated value type that is different than the input value type, while avoiding unnecessary allocations. Like in groupByKey, the number of reduce tasks is configurable through an optional second argument. 19 | sortByKey([ascending], [numTasks]) | When called on a dataset of (K, V) pairs where K implements Ordered, returns a dataset of (K, V) pairs sorted by keys in ascending or descending order, as specified in the boolean ascending argument. 20 | join(otherDataset, [numTasks]) | When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (V, W)) pairs with all pairs of elements for each key. Outer joins are also supported through leftOuterJoin and rightOuterJoin. 21 | cogroup(otherDataset, [numTasks]) | When called on datasets of type (K, V) and (K, W), returns a dataset of (K, Iterable, Iterable) tuples. This operation is also called groupWith. 22 | cartesian(otherDataset) | When called on datasets of types T and U, returns a dataset of (T, U) pairs (all pairs of elements). 23 | pipe(command, [envVars]) | Pipe each partition of the RDD through a shell command, e.g. a Perl or bash script. RDD elements are written to the process's stdin and lines output to its stdout are returned as an RDD of strings. 24 | coalesce(numPartitions) | Decrease the number of partitions in the RDD to numPartitions. Useful for running operations more efficiently after filtering down a large dataset. 25 | repartition(numPartitions) | Reshuffle the data in the RDD randomly to create either more or fewer partitions and balance it across them. This always shuffles all data over the network. -------------------------------------------------------------------------------- /programming-guide/rdds/working-with-key-value-pairs.md: -------------------------------------------------------------------------------- 1 | # 使用键值对 2 | 3 | 虽然很多 Spark 操作工作在包含任意类型对象的 RDDs 上的,但是少数几个特殊操作仅仅在键值(key-value)对 RDDs 上可用。最常见的是分布式 "shuffle" 操作,例如根据一个 key 对一组数据进行分组和聚合。 4 | 5 | 在 Scala 中,这些操作在包含[二元组(Tuple2)](http://www.scala-lang.org/api/2.10.4/index.html#scala.Tuple2)(在语言的内建元组中,通过简单的写 (a, b) 创建) 的 RDD 上自动地变成可用的,只要在你的程序中导入 `org.apache.spark.SparkContext._` 来启用 Spark 的隐式转换。在 PairRDDFunctions 的类里键值对操作是可以使用的,如果你导入隐式转换它会自动地包装成元组 RDD。 6 | 7 | 例如,下面的代码在键值对上使用 `reduceByKey` 操作来统计在一个文件里每一行文本内容出现的次数: 8 | 9 | ```scala 10 | val lines = sc.textFile("data.txt") 11 | val pairs = lines.map(s => (s, 1)) 12 | val counts = pairs.reduceByKey((a, b) => a + b) 13 | ``` 14 | 15 | 我们也可以使用 `counts.sortByKey()`,例如,将键值对按照字母进行排序,最后 `counts.collect()` 把它们作为一个对象数组带回到驱动程序。 16 | 17 | 注意:当使用一个自定义对象作为 key 在使用键值对操作的时候,你需要确保自定义 `equals()` 方法和 `hashCode()` 方法是匹配的。更加详细的内容,查看 [Object.hashCode() 文档](http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode())中的契约概述。 -------------------------------------------------------------------------------- /programming-guide/shared-variables.md: -------------------------------------------------------------------------------- 1 | # 共享变量 2 | 3 | 一般情况下,当一个传递给Spark操作(例如map和reduce)的函数在远程节点上面运行时,Spark操作实际上操作的是这个函数所用变量的一个独立副本。这些变量被复制到每台机器上,并且这些变量在远程机器上 4 | 的所有更新都不会传递回驱动程序。通常跨任务的读写变量是低效的,但是,Spark还是为两种常见的使用模式提供了两种有限的共享变量:广播变量(broadcast variable)和累加器(accumulator) 5 | 6 | ## 广播变量 7 | 8 | 广播变量允许程序员缓存一个只读的变量在每台机器上面,而不是每个任务保存一份拷贝。例如,利用广播变量,我们能够以一种更有效率的方式将一个大数据量输入集合的副本分配给每个节点。(Broadcast variables allow the 9 | programmer to keep a read-only variable cached on each machine rather than shipping a copy of it with tasks.They can be used, for example, 10 | to give every node a copy of a large input dataset in an efficient manner.)Spark也尝试着利用有效的广播算法去分配广播变量,以减少通信的成本。 11 | 12 | 一个广播变量可以通过调用`SparkContext.broadcast(v)`方法从一个初始变量v中创建。广播变量是v的一个包装变量,它的值可以通过`value`方法访问,下面的代码说明了这个过程: 13 | 14 | ```scala 15 | scala> val broadcastVar = sc.broadcast(Array(1, 2, 3)) 16 | broadcastVar: spark.Broadcast[Array[Int]] = spark.Broadcast(b5c40191-a864-4c7d-b9bf-d87e1a4e787c) 17 | scala> broadcastVar.value 18 | res0: Array[Int] = Array(1, 2, 3) 19 | ``` 20 | 广播变量创建以后,我们就能够在集群的任何函数中使用它来代替变量v,这样我们就不需要再次传递变量v到每个节点上。另外,为了保证所有的节点得到广播变量具有相同的值,对象v不能在广播之后被修改。 21 | 22 | ## 累加器 23 | 24 | 顾名思义,累加器是一种只能通过关联操作进行“加”操作的变量,因此它能够高效的应用于并行操作中。它们能够用来实现`counters`和`sums`。Spark原生支持数值类型的累加器,开发者可以自己添加支持的类型。 25 | 如果创建了一个具名的累加器,它可以在spark的UI中显示。这对于理解运行阶段(running stages)的过程有很重要的作用。(注意:这在python中还不被支持) 26 | 27 | 一个累加器可以通过调用`SparkContext.accumulator(v)`方法从一个初始变量v中创建。运行在集群上的任务可以通过`add`方法或者使用`+=`操作来给它加值。然而,它们无法读取这个值。只有驱动程序可以使用`value`方法来读取累加器的值。 28 | 如下的代码,展示了如何利用累加器将一个数组里面的所有元素相加: 29 | 30 | ```scala 31 | scala> val accum = sc.accumulator(0, "My Accumulator") 32 | accum: spark.Accumulator[Int] = 0 33 | scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum += x) 34 | ... 35 | 10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s 36 | scala> accum.value 37 | res2: Int = 10 38 | ``` 39 | 这个例子利用了内置的整数类型累加器。开发者可以利用子类[AccumulatorParam](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.AccumulatorParam)创建自己的 40 | 累加器类型。AccumulatorParam接口有两个方法:`zero`方法为你的数据类型提供一个“0 值”(zero value);`addInPlace`方法计算两个值的和。例如,假设我们有一个`Vector`类代表数学上的向量,我们能够 41 | 如下定义累加器: 42 | 43 | ```scala 44 | object VectorAccumulatorParam extends AccumulatorParam[Vector] { 45 | def zero(initialValue: Vector): Vector = { 46 | Vector.zeros(initialValue.size) 47 | } 48 | def addInPlace(v1: Vector, v2: Vector): Vector = { 49 | v1 += v2 50 | } 51 | } 52 | // Then, create an Accumulator of this type: 53 | val vecAccum = sc.accumulator(new Vector(...))(VectorAccumulatorParam) 54 | ``` 55 | 在scala中,Spark支持用更一般的[Accumulable](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.Accumulable)接口来累积数据-结果类型和用于累加的元素类型 56 | 不一样(例如通过收集的元素建立一个列表)。Spark也支持用`SparkContext.accumulableCollection`方法累加一般的scala集合类型。 57 | -------------------------------------------------------------------------------- /quick-start/README.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | 本节课程提供一个使用 Spark 的快速介绍,首先我们使用 Spark 的交互式 shell(用 Python 或 Scala) 介绍它的 API。当演示如何在 Java, Scala 和 Python 写独立的程序时,看[编程指南](https://spark.apache.org/docs/latest/programming-guide.html)里完整的参考。 4 | 5 | 依照这个指南,首先从 [Spark 网站](https://spark.apache.org/downloads.html)下载一个 Spark 发行包。因为我们不会使用 HDFS,你可以下载任何 Hadoop 版本的包。 6 | 7 | 8 | * [Spark Shell](using-spark-shell.md) 9 | * [独立应用程序](standalone-applications.md) 10 | * [开始翻滚吧!](where-to-go-from-here.md) -------------------------------------------------------------------------------- /quick-start/standalone-applications.md: -------------------------------------------------------------------------------- 1 | # 独立应用程序 2 | 3 | 现在假设我们想要使用 Spark API 写一个独立的应用程序。我们将通过使用 Scala(用 SBT),Java(用 Maven) 和 Python 写一个简单的应用程序来学习。 4 | 5 | 我们用 Scala 创建一个非常简单的 Spark 应用程序。如此简单,事实上它的名字叫 `SimpleApp.scala`: 6 | 7 | ```scala 8 | /* SimpleApp.scala */ 9 | import org.apache.spark.SparkContext 10 | import org.apache.spark.SparkContext._ 11 | import org.apache.spark.SparkConf 12 | 13 | object SimpleApp { 14 | def main(args: Array[String]) { 15 | val logFile = "YOUR_SPARK_HOME/README.md" // 应该是你系统上的某些文件 16 | val conf = new SparkConf().setAppName("Simple Application") 17 | val sc = new SparkContext(conf) 18 | val logData = sc.textFile(logFile, 2).cache() 19 | val numAs = logData.filter(line => line.contains("a")).count() 20 | val numBs = logData.filter(line => line.contains("b")).count() 21 | println("Lines with a: %s, Lines with b: %s".format(numAs, numBs)) 22 | } 23 | } 24 | ``` 25 | 26 | 这个程序仅仅是在 Spark README 中计算行里面包含 'a' 和包含 'b' 的次数。你需要注意将 `YOUR_SPARK_HOME` 替换成你已经安装 Spark 的路径。不像之前的 Spark Shell 例子,这里初始化了自己的 SparkContext,我们把 SparkContext 初始化作为程序的一部分。 27 | 28 | 我们通过 SparkContext 的构造函数参入 [SparkConf](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.SparkConf) 对象,这个对象包含了一些关于我们程序的信息。 29 | 30 | 我们的程序依赖于 Spark API,所以我们需要包含一个 sbt 文件文件,`simple.sbt` 解释了 Spark 是一个依赖。这个文件还要补充 Spark 依赖于一个 repository: 31 | 32 | ```scala 33 | name := "Simple Project" 34 | 35 | version := "1.0" 36 | 37 | scalaVersion := "2.10.4" 38 | 39 | libraryDependencies += "org.apache.spark" %% "spark-core" % "1.2.0" 40 | ``` 41 | 42 | 要让 sbt 正确工作,我们需要把 `SimpleApp.scala` 和 `simple.sbt` 按照标准的文件目录结构布局。上面的做好之后,我们可以把程序的代码创建成一个 JAR 包。然后使用 `spark-submit` 来运行我们的程序。 43 | 44 | ``` 45 | # Your directory layout should look like this 46 | $ find . 47 | . 48 | ./simple.sbt 49 | ./src 50 | ./src/main 51 | ./src/main/scala 52 | ./src/main/scala/SimpleApp.scala 53 | 54 | # Package a jar containing your application 55 | $ sbt package 56 | ... 57 | [info] Packaging {..}/{..}/target/scala-2.10/simple-project_2.10-1.0.jar 58 | 59 | # Use spark-submit to run your application 60 | $ YOUR_SPARK_HOME/bin/spark-submit \ 61 | --class "SimpleApp" \ 62 | --master local[4] \ 63 | target/scala-2.10/simple-project_2.10-1.0.jar 64 | ... 65 | Lines with a: 46, Lines with b: 23 66 | ``` -------------------------------------------------------------------------------- /quick-start/using-spark-shell.md: -------------------------------------------------------------------------------- 1 | # 使用 Spark Shell 2 | 3 | ## 基础 4 | 5 | Spark 的 shell 作为一个强大的交互式数据分析工具,提供了一个简单的方式来学习 API。它可以使用 Scala(在 Java 虚拟机上运行现有的 Java 库的一个很好方式) 或 Python。在 Spark 目录里使用下面的方式开始运行: 6 | 7 | ```scala 8 | ./bin/spark-shell 9 | ``` 10 | 11 | Spark 最主要的抽象是叫Resilient Distributed Dataset(RDD) 的弹性分布式集合。RDDs 可以使用 Hadoop InputFormats(例如 HDFS 文件)创建,也可以从其他的 RDDs 转换。让我们在 Spark 源代码目录从 README 文本文件中创建一个新的 RDD。 12 | 13 | ```scala 14 | scala> val textFile = sc.textFile("README.md") 15 | textFile: spark.RDD[String] = spark.MappedRDD@2ee9b6e3 16 | ``` 17 | 18 | RDD 的 [actions](https://spark.apache.org/docs/latest/programming-guide.html#actions) 从 RDD 中返回值,[transformations](https://spark.apache.org/docs/latest/programming-guide.html#transformations) 可以转换成一个新 RDD 并返回它的引用。让我们开始使用几个操作: 19 | 20 | ```scala 21 | scala> textFile.count() // RDD 的数据条数 22 | res0: Long = 126 23 | 24 | scala> textFile.first() // RDD 的第一行数据 25 | res1: String = # Apache Spark 26 | ``` 27 | 28 | 现在让我们使用一个 transformation,我们将使用 [filter](https://spark.apache.org/docs/latest/programming-guide.html#transformations) 在这个文件里返回一个包含子数据集的新 RDD。 29 | 30 | ```scala 31 | scala> val linesWithSpark = textFile.filter(line => line.contains("Spark")) 32 | linesWithSpark: spark.RDD[String] = spark.FilteredRDD@7dd4af09 33 | ``` 34 | 35 | 我们可以把 actions 和 transformations 链接在一起: 36 | 37 | ```scala 38 | scala> textFile.filter(line => line.contains("Spark")).count() // 有多少行包括 "Spark"? 39 | res3: Long = 15 40 | ``` 41 | 42 | ## 更多 RDD 操作 43 | 44 | RDD actions 和 transformations 能被用在更多的复杂计算中。比方说,我们想要找到一行中最多的单词数量: 45 | 46 | ```scala 47 | scala> textFile.map(line => line.split(" ").size).reduce((a, b) => if (a > b) a else b) 48 | res4: Long = 15 49 | ``` 50 | 51 | 首先将行映射成一个整型数值产生一个新 RDD。 在这个新的 RDD 上调用 `reduce` 找到行中最大的个数。 `map` 和 `reduce` 的参数是 Scala 的函数串(闭包),并且可以使用任何语言特性或者 Scala/Java 类库。例如,我们可以很方便地调用其他的函数声明。 我们使用 `Math.max()` 函数让代码更容易理解: 52 | 53 | ```scala 54 | scala> import java.lang.Math 55 | import java.lang.Math 56 | 57 | scala> textFile.map(line => line.split(" ").size).reduce((a, b) => Math.max(a, b)) 58 | res5: Int = 15 59 | ``` 60 | 61 | Hadoop 流行的一个通用的数据流模式是 MapReduce。Spark 能很容易地实现 MapReduce: 62 | 63 | ```scala 64 | scala> val wordCounts = textFile.flatMap(line => line.split(" ")).map(word => (word, 1)).reduceByKey((a, b) => a + b) 65 | wordCounts: spark.RDD[(String, Int)] = spark.ShuffledAggregatedRDD@71f027b8 66 | ``` 67 | 68 | 这里,我们结合 [flatMap](), [map]() 和 [reduceByKey]() 来计算文件里每个单词出现的数量,它的结果是包含一组(String, Int) 键值对的 RDD。我们可以使用 [collect] 操作在我们的 shell 中收集单词的数量: 69 | 70 | ```scala 71 | scala> wordCounts.collect() 72 | res6: Array[(String, Int)] = Array((means,1), (under,2), (this,3), (Because,1), (Python,2), (agree,1), (cluster.,1), ...) 73 | ``` 74 | 75 | ## 缓存 76 | 77 | Spark 支持把数据集拉到集群内的内存缓存中。当要重复访问时这是非常有用的,例如当我们在一个小的热(hot)数据集中查询,或者运行一个像网页搜索排序这样的重复算法。作为一个简单的例子,让我们把 `linesWithSpark` 数据集标记在缓存中: 78 | 79 | ```scala 80 | scala> linesWithSpark.cache() 81 | res7: spark.RDD[String] = spark.FilteredRDD@17e51082 82 | 83 | scala> linesWithSpark.count() 84 | res8: Long = 15 85 | 86 | scala> linesWithSpark.count() 87 | res9: Long = 15 88 | ``` 89 | 90 | 缓存 100 行的文本文件来研究 Spark 这看起来很傻。真正让人感兴趣的部分是我们可以在非常大型的数据集中使用同样的函数,甚至在 10 个或者 100 个节点中交叉计算。你同样可以使用 `bin/spark-shell` 连接到一个 cluster 来替换掉[编程指南](https://spark.apache.org/docs/latest/programming-guide.html#initializing-spark)中的方法进行交互操作。 -------------------------------------------------------------------------------- /quick-start/where-to-go-from-here.md: -------------------------------------------------------------------------------- 1 | # 开始翻滚吧! 2 | 3 | 祝贺你成功运行你的第一个 Spark 应用程序! 4 | 5 | - 要深入了解 API,可以从[Spark编程指南](https://spark.apache.org/docs/latest/programming-guide.html)开始,或者从其他的组件开始,例如:Spark Streaming。 6 | - 要让程序运行在集群(cluster)上,前往[部署概论](https://spark.apache.org/docs/latest/cluster-overview.html)。 7 | - 最后,Spark 在 `examples` 文件目录里包含了 [Scala](https://github.com/apache/spark/tree/master/examples/src/main/scala/org/apache/spark/examples), [Java](https://github.com/apache/spark/tree/master/examples/src/main/java/org/apache/spark/examples) 和 [Python](https://github.com/apache/spark/tree/master/examples/src/main/python) 的几个简单的例子,你可以直接运行它们: 8 | 9 | ``` 10 | # For Scala and Java, use run-example: 11 | ./bin/run-example SparkPi 12 | 13 | # For Python examples, use spark-submit directly: 14 | ./bin/spark-submit examples/src/main/python/pi.py 15 | ``` -------------------------------------------------------------------------------- /spark-sql/README.md: -------------------------------------------------------------------------------- 1 | # Spark SQL 2 | 3 | Spark SQL允许Spark执行用SQL, HiveQL或者Scala表示的关系查询。这个模块的核心是一个新类型的RDD-[SchemaRDD](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.SchemaRDD)。 4 | SchemaRDDs由[行](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.package@Row:org.apache.spark.sql.catalyst.expressions.Row.type)对象组成,行对象拥有一个模式(scheme) 5 | 来描述行中每一列的数据类型。SchemaRDD与关系型数据库中的表很相似。可以通过存在的RDD、一个[Parquet](http://parquet.io/)文件、一个JSON数据库或者对存储在[Apache Hive](http://hive.apache.org/)中的数据执行HiveSQL查询中创建。 6 | 7 | 本章的所有例子都利用了Spark分布式系统中的样本数据,可以在`spark-shell`中运行它们。 8 | 9 | * [开始](getting-started.md) 10 | * [数据源](data-sources/README.md) 11 | * [RDDs](data-sources/rdds.md) 12 | * [parquet文件](data-sources/parquet-files.md) 13 | * [JSON数据集](data-sources/jSON-datasets.md) 14 | * [Hive表](data-sources/hive-tables.md) 15 | * [性能调优](performance-tuning.md) 16 | * [其它SQL接口](other-sql-interfaces.md) 17 | * [编写语言集成(Language-Integrated)的相关查询](writing-language-integrated-relational-queries.md) 18 | * [Spark SQL数据类型](spark-sql-dataType-reference.md) -------------------------------------------------------------------------------- /spark-sql/compatibility-with-other-systems/compatibility-with-apache-hive.md: -------------------------------------------------------------------------------- 1 | # 与Apache Hive的兼容性 2 | 3 | Spark SQL设计得和Hive元存储(Metastore)、序列化反序列化(SerDes)、用户自定义函数(UDFs)相兼容。当前的Spark SQL以Hive 0.12.0和0.13.1为基础。 4 | 5 | ## 部署存在的Hive仓库 6 | 7 | Spark SQL Thrift JDBC服务器按照“开箱即用(out of the box)”的方式设计得和存在的Hive相兼容。你不必修改存在的Hive仓库或者改变数据的位置或者分割表。 8 | 9 | ## 支持的Hive特征 10 | 11 | Spark SQL支持绝大部分的Hive特征。 12 | 13 | - Hive查询语句 14 | - SELECT 15 | - GROUP BY 16 | - ORDER BY 17 | - CLUSTER BY 18 | - SORT BY 19 | - 所有的Hive操作 20 | - 关系运算符(=, ⇔, ==, <>, <, >, >=, <=等) 21 | - 算术运算符(+, -, *, /, %等) 22 | - 逻辑运算符(AND, &&, OR, ||) 23 | - 复杂的类型构造函数 24 | - 数学函数(sign, ln, cos等) 25 | - 字符串函数(instr, length, printf等) 26 | - 用户自定义函数 27 | - 用户自定义聚合函数(UDAF) 28 | - 用户自定义序列化格式 29 | - Joins 30 | - JOIN 31 | - {LEFT|RIGHT|FULL} OUTER JOIN 32 | - LEFT SEMI JOIN 33 | - CROSS JOIN 34 | - Unions 35 | - 子查询 36 | - SELECT col FROM ( SELECT a + b AS col from t1) t2 37 | - 采样 38 | - Explain 39 | - Partitioned表 40 | - 视图 41 | - 所有的Hive DDL函数 42 | - CREATE TABLE 43 | - CREATE TABLE AS SELECT 44 | - ALTER TABLE 45 | - 大部分的Hive数据类型 46 | - TINYINT 47 | - SMALLINT 48 | - INT 49 | - BIGINT 50 | - BOOLEAN 51 | - FLOAT 52 | - DOUBLE 53 | - STRING 54 | - BINARY 55 | - TIMESTAMP 56 | - DATE 57 | - ARRAY<> 58 | - MAP<> 59 | - STRUCT<> 60 | 61 | -------------------------------------------------------------------------------- /spark-sql/compatibility-with-other-systems/migration-guide-shark-user.md: -------------------------------------------------------------------------------- 1 | # Shark用户迁移指南 2 | 3 | ## 调度(Scheduling) 4 | 5 | 为JDBC客户端会话设置[Fair Scheduler](https://spark.apache.org/docs/latest/job-scheduling.html#fair-scheduler-pools)池。用户可以设置`spark.sql.thriftserver.scheduler.pool` 6 | 变量。 7 | 8 | ```shell 9 | SET spark.sql.thriftserver.scheduler.pool=accounting; 10 | ``` 11 | ## Reducer数量 12 | 13 | 在Shark中,默认的reducer数目是1,可以通过`mapred.reduce.tasks`属性来控制其多少。Spark SQL反对使用这个属性,支持`spark.sql.shuffle.partitions`属性,它的默认值是200。 14 | 用户可以自定义这个属性。 15 | 16 | ```shell 17 | SET spark.sql.shuffle.partitions=10; 18 | SELECT page, count(*) c 19 | FROM logs_last_month_cached 20 | GROUP BY page ORDER BY c DESC LIMIT 10; 21 | ``` 22 | 23 | 你也可以在`hive-site.xml`中设置这个属性覆盖默认值。现在,`mapred.reduce.tasks`属性仍然可以被识别,它会自动转换为`spark.sql.shuffle.partition`。 24 | 25 | ## Caching 26 | 27 | 表属性`shark.cache`不再存在,名字以`_cached`结尾的表也不再自动缓存。作为替代的方法,我们提供`CACHE TABLE`和`UNCACHE TABLE`语句显示地控制表的缓存。 28 | 29 | ```shell 30 | CACHE TABLE logs_last_month; 31 | UNCACHE TABLE logs_last_month; 32 | ``` 33 | 注意:`CACHE TABLE tbl`是懒加载的,类似于在RDD上使用`.cache`。这个命令仅仅标记tbl来确保计算时分区被缓存,但实际并不缓存它,直到一个查询用到tbl才缓存。 34 | 35 | 要强制表缓存,你可以简单地执行`CACHE TABLE`后,立即count表。 36 | 37 | ```shell 38 | CACHE TABLE logs_last_month; 39 | SELECT COUNT(1) FROM logs_last_month; 40 | ``` 41 | 42 | 有几个缓存相关的特征现在还不支持: 43 | 44 | - 用户定义分区级别的缓存逐出策略 45 | - RDD重加载 46 | - 内存缓存的写入策略(In-memory cache write through policy) -------------------------------------------------------------------------------- /spark-sql/data-sources/README.md: -------------------------------------------------------------------------------- 1 | # 数据源 2 | 3 | Spark SQL支持通过SchemaRDD接口操作各种数据源。一个SchemaRDD能够作为一个一般的RDD被操作,也可以被注册为一个临时的表。注册一个SchemaRDD为一个表就 4 | 可以允许你在其数据上运行SQL查询。这节描述了加载数据为SchemaRDD的多种方法。 5 | 6 | * [RDDs](rdds.md) 7 | * [parquet文件](parquet-files.md) 8 | * [JSON数据集](jSON-datasets.md) 9 | * [Hive表](hive-tables.md) -------------------------------------------------------------------------------- /spark-sql/data-sources/hive-tables.md: -------------------------------------------------------------------------------- 1 | # Hive表 2 | 3 | Spark SQL也支持从Apache Hive中读出和写入数据。然而,Hive有大量的依赖,所以它不包含在Spark集合中。可以通过`-Phive`和`-Phive-thriftserver`参数构建Spark,使其 4 | 支持Hive。注意这个重新构建的jar包必须存在于所有的worker节点中,因为它们需要通过Hive的序列化和反序列化库访问存储在Hive中的数据。 5 | 6 | 当和Hive一起工作是,开发者需要提供HiveContext。HiveContext从SQLContext继承而来,它增加了在MetaStore中发现表以及利用HiveSql写查询的功能。没有Hive部署的用户也 7 | 可以创建HiveContext。当没有通过`hive-site.xml`配置,上下文将会在当前目录自动地创建`metastore_db`和`warehouse`。 8 | 9 | ```scala 10 | // sc is an existing SparkContext. 11 | val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc) 12 | 13 | sqlContext.sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)") 14 | sqlContext.sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src") 15 | 16 | // Queries are expressed in HiveQL 17 | sqlContext.sql("FROM src SELECT key, value").collect().foreach(println) 18 | ``` -------------------------------------------------------------------------------- /spark-sql/data-sources/jSON-datasets.md: -------------------------------------------------------------------------------- 1 | # JSON数据集 2 | 3 | Spark SQL能够自动推断JSON数据集的模式,加载它为一个SchemaRDD。这种转换可以通过下面两种方法来实现 4 | 5 | - jsonFile :从一个包含JSON文件的目录中加载。文件中的每一行是一个JSON对象 6 | - jsonRDD :从存在的RDD加载数据,这些RDD的每个元素是一个包含JSON对象的字符串 7 | 8 | 注意,作为jsonFile的文件不是一个典型的JSON文件,每行必须是独立的并且包含一个有效的JSON对象。结果是,一个多行的JSON文件经常会失败 9 | 10 | ```scala 11 | // sc is an existing SparkContext. 12 | val sqlContext = new org.apache.spark.sql.SQLContext(sc) 13 | 14 | // A JSON dataset is pointed to by path. 15 | // The path can be either a single text file or a directory storing text files. 16 | val path = "examples/src/main/resources/people.json" 17 | // Create a SchemaRDD from the file(s) pointed to by path 18 | val people = sqlContext.jsonFile(path) 19 | 20 | // The inferred schema can be visualized using the printSchema() method. 21 | people.printSchema() 22 | // root 23 | // |-- age: integer (nullable = true) 24 | // |-- name: string (nullable = true) 25 | 26 | // Register this SchemaRDD as a table. 27 | people.registerTempTable("people") 28 | 29 | // SQL statements can be run by using the sql methods provided by sqlContext. 30 | val teenagers = sqlContext.sql("SELECT name FROM people WHERE age >= 13 AND age <= 19") 31 | 32 | // Alternatively, a SchemaRDD can be created for a JSON dataset represented by 33 | // an RDD[String] storing one JSON object per string. 34 | val anotherPeopleRDD = sc.parallelize( 35 | """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil) 36 | val anotherPeople = sqlContext.jsonRDD(anotherPeopleRDD) 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /spark-sql/data-sources/parquet-files.md: -------------------------------------------------------------------------------- 1 | # Parquet文件 2 | 3 | Parquet是一种柱状(columnar)格式,可以被许多其它的数据处理系统支持。Spark SQL提供支持读和写Parquet文件的功能,这些文件可以自动地保留原始数据的模式。 4 | 5 | ## 加载数据 6 | 7 | ```scala 8 | // sqlContext from the previous example is used in this example. 9 | // createSchemaRDD is used to implicitly convert an RDD to a SchemaRDD. 10 | import sqlContext.createSchemaRDD 11 | 12 | val people: RDD[Person] = ... // An RDD of case class objects, from the previous example. 13 | 14 | // The RDD is implicitly converted to a SchemaRDD by createSchemaRDD, allowing it to be stored using Parquet. 15 | people.saveAsParquetFile("people.parquet") 16 | 17 | // Read in the parquet file created above. Parquet files are self-describing so the schema is preserved. 18 | // The result of loading a Parquet file is also a SchemaRDD. 19 | val parquetFile = sqlContext.parquetFile("people.parquet") 20 | 21 | //Parquet files can also be registered as tables and then used in SQL statements. 22 | parquetFile.registerTempTable("parquetFile") 23 | val teenagers = sqlContext.sql("SELECT name FROM parquetFile WHERE age >= 13 AND age <= 19") 24 | teenagers.map(t => "Name: " + t(0)).collect().foreach(println) 25 | ``` 26 | 27 | ## 配置 28 | 29 | 可以在SQLContext上使用setConf方法配置Parquet或者在用SQL时运行`SET key=value`命令来配置Parquet。 30 | 31 | Property Name | Default | Meaning 32 | --- | --- | --- 33 | spark.sql.parquet.binaryAsString | false | 一些其它的Parquet-producing系统,特别是Impala和其它版本的Spark SQL,当写出Parquet模式的时候,二进制数据和字符串之间无法区分。这个标记告诉Spark SQL将二进制数据解释为字符串来提供这些系统的兼容性。 34 | spark.sql.parquet.cacheMetadata | true | 打开parquet元数据的缓存,可以提高静态数据的查询速度 35 | spark.sql.parquet.compression.codec | gzip | 设置写parquet文件时的压缩算法,可以接受的值包括:uncompressed, snappy, gzip, lzo 36 | spark.sql.parquet.filterPushdown | false | 打开Parquet过滤器的pushdown优化。因为已知的Paruet错误,这个特征默认是关闭的。如果你的表不包含任何空的字符串或者二进制列,打开这个特征仍是安全的 37 | spark.sql.hive.convertMetastoreParquet | true | 当设置为false时,Spark SQL将使用Hive SerDe代替内置的支持 38 | -------------------------------------------------------------------------------- /spark-sql/data-sources/rdds.md: -------------------------------------------------------------------------------- 1 | # RDDs 2 | 3 | Spark支持两种方法将存在的RDDs转换为SchemaRDDs。第一种方法使用反射来推断包含特定对象类型的RDD的模式(schema)。在你写spark程序的同时,当你已经知道了模式,这种基于反射的 4 | 方法可以使代码更简洁并且程序工作得更好。 5 | 6 | 创建SchemaRDDs的第二种方法是通过一个编程接口来实现,这个接口允许你构造一个模式,然后在存在的RDDs上使用它。虽然这种方法更冗长,但是它允许你在运行期之前不知道列以及列 7 | 的类型的情况下构造SchemaRDDs。 8 | 9 | ## 利用反射推断模式 10 | 11 | Spark SQL的Scala接口支持将包含样本类的RDDs自动转换为SchemaRDD。这个样本类定义了表的模式。 12 | 13 | 给样本类的参数名字通过反射来读取,然后作为列的名字。样本类可以嵌套或者包含复杂的类型如序列或者数组。这个RDD可以隐式转化为一个SchemaRDD,然后注册为一个表。表可以在后续的 14 | sql语句中使用。 15 | 16 | ```scala 17 | // sc is an existing SparkContext. 18 | val sqlContext = new org.apache.spark.sql.SQLContext(sc) 19 | // createSchemaRDD is used to implicitly convert an RDD to a SchemaRDD. 20 | import sqlContext.createSchemaRDD 21 | 22 | // Define the schema using a case class. 23 | // Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit, 24 | // you can use custom classes that implement the Product interface. 25 | case class Person(name: String, age: Int) 26 | 27 | // Create an RDD of Person objects and register it as a table. 28 | val people = sc.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(p => Person(p(0), p(1).trim.toInt)) 29 | people.registerTempTable("people") 30 | 31 | // SQL statements can be run by using the sql methods provided by sqlContext. 32 | val teenagers = sqlContext.sql("SELECT name FROM people WHERE age >= 13 AND age <= 19") 33 | 34 | // The results of SQL queries are SchemaRDDs and support all the normal RDD operations. 35 | // The columns of a row in the result can be accessed by ordinal. 36 | teenagers.map(t => "Name: " + t(0)).collect().foreach(println) 37 | ``` 38 | 39 | ## 编程指定模式 40 | 41 | 当样本类不能提前确定(例如,记录的结构是经过编码的字符串,或者一个文本集合将会被解析,不同的字段投影给不同的用户),一个SchemaRDD可以通过三步来创建。 42 | 43 | - 从原来的RDD创建一个行的RDD 44 | - 创建由一个`StructType`表示的模式与第一步创建的RDD的行结构相匹配 45 | - 在行RDD上通过`applySchema`方法应用模式 46 | 47 | ```scala 48 | // sc is an existing SparkContext. 49 | val sqlContext = new org.apache.spark.sql.SQLContext(sc) 50 | 51 | // Create an RDD 52 | val people = sc.textFile("examples/src/main/resources/people.txt") 53 | 54 | // The schema is encoded in a string 55 | val schemaString = "name age" 56 | 57 | // Import Spark SQL data types and Row. 58 | import org.apache.spark.sql._ 59 | 60 | // Generate the schema based on the string of schema 61 | val schema = 62 | StructType( 63 | schemaString.split(" ").map(fieldName => StructField(fieldName, StringType, true))) 64 | 65 | // Convert records of the RDD (people) to Rows. 66 | val rowRDD = people.map(_.split(",")).map(p => Row(p(0), p(1).trim)) 67 | 68 | // Apply the schema to the RDD. 69 | val peopleSchemaRDD = sqlContext.applySchema(rowRDD, schema) 70 | 71 | // Register the SchemaRDD as a table. 72 | peopleSchemaRDD.registerTempTable("people") 73 | 74 | // SQL statements can be run by using the sql methods provided by sqlContext. 75 | val results = sqlContext.sql("SELECT name FROM people") 76 | 77 | // The results of SQL queries are SchemaRDDs and support all the normal RDD operations. 78 | // The columns of a row in the result can be accessed by ordinal. 79 | results.map(t => "Name: " + t(0)).collect().foreach(println) 80 | ``` -------------------------------------------------------------------------------- /spark-sql/getting-started.md: -------------------------------------------------------------------------------- 1 | # 开始 2 | 3 | Spark中所有相关功能的入口点是[SQLContext](http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.SQLContext)类或者它的子类, 4 | 创建一个SQLContext的所有需要仅仅是一个SparkContext。 5 | 6 | ```scala 7 | val sc: SparkContext // An existing SparkContext. 8 | val sqlContext = new org.apache.spark.sql.SQLContext(sc) 9 | 10 | // createSchemaRDD is used to implicitly convert an RDD to a SchemaRDD. 11 | import sqlContext.createSchemaRDD 12 | ``` 13 | 除了一个基本的SQLContext,你也能够创建一个HiveContext,它支持基本SQLContext所支持功能的一个超集。它的额外的功能包括用更完整的HiveQL分析器写查询去访问HiveUDFs的能力、 14 | 从Hive表读取数据的能力。用HiveContext你不需要一个已经存在的Hive开启,SQLContext可用的数据源对HiveContext也可用。HiveContext分开打包是为了避免在Spark构建时包含了所有 15 | 的Hive依赖。如果对你的应用程序来说,这些依赖不存在问题,Spark 1.2推荐使用HiveContext。以后的稳定版本将专注于为SQLContext提供与HiveContext等价的功能。 16 | 17 | 用来解析查询语句的特定SQL变种语言可以通过`spark.sql.dialect`选项来选择。这个参数可以通过两种方式改变,一种方式是通过`setConf`方法设定,另一种方式是在SQL命令中通过`SET key=value` 18 | 来设定。对于SQLContext,唯一可用的方言是“sql”,它是Spark SQL提供的一个简单的SQL解析器。在HiveContext中,虽然也支持"sql",但默认的方言是“hiveql”。这是因为HiveQL解析器更 19 | 完整。在很多用例中推荐使用“hiveql”。 -------------------------------------------------------------------------------- /spark-sql/other-sql-interfaces.md: -------------------------------------------------------------------------------- 1 | # 其它SQL接口 2 | 3 | Spark SQL也支持直接运行SQL查询的接口,不用写任何代码。 4 | 5 | ## 运行Thrift JDBC/ODBC服务器 6 | 7 | 这里实现的Thrift JDBC/ODBC服务器与Hive 0.12中的[HiveServer2](https://cwiki.apache.org/confluence/display/Hive/Setting+Up+HiveServer2)相一致。你可以用在Spark 8 | 或者Hive 0.12附带的beeline脚本测试JDBC服务器。 9 | 10 | 在Spark目录中,运行下面的命令启动JDBC/ODBC服务器。 11 | 12 | ```shell 13 | ./sbin/start-thriftserver.sh 14 | ``` 15 | 16 | 这个脚本接受任何的`bin/spark-submit`命令行参数,加上一个`--hiveconf`参数用来指明Hive属性。你可以运行`./sbin/start-thriftserver.sh --help`来获得所有可用选项的完整 17 | 列表。默认情况下,服务器监听`localhost:10000`。你可以用环境变量覆盖这些变量。 18 | 19 | ```shell 20 | export HIVE_SERVER2_THRIFT_PORT= 21 | export HIVE_SERVER2_THRIFT_BIND_HOST= 22 | ./sbin/start-thriftserver.sh \ 23 | --master \ 24 | ... 25 | ``` 26 | 或者通过系统变量覆盖。 27 | 28 | ```shell 29 | ./sbin/start-thriftserver.sh \ 30 | --hiveconf hive.server2.thrift.port= \ 31 | --hiveconf hive.server2.thrift.bind.host= \ 32 | --master 33 | ... 34 | ``` 35 | 现在你可以用beeline测试Thrift JDBC/ODBC服务器。 36 | 37 | ```shell 38 | ./bin/beeline 39 | ``` 40 | 连接到Thrift JDBC/ODBC服务器的方式如下: 41 | 42 | ```shell 43 | beeline> !connect jdbc:hive2://localhost:10000 44 | ``` 45 | 46 | Beeline将会询问你用户名和密码。在非安全的模式,简单地输入你机器的用户名和空密码就行了。对于安全模式,你可以按照[Beeline文档](https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients)的说明来执行。 47 | 48 | ## 运行Spark SQL CLI 49 | 50 | Spark SQL CLI是一个便利的工具,它可以在本地运行Hive元存储服务、执行命令行输入的查询。注意,Spark SQL CLI不能与Thrift JDBC服务器通信。 51 | 52 | 在Spark目录运行下面的命令可以启动Spark SQL CLI。 53 | 54 | ```shell 55 | ./bin/spark-sql 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /spark-sql/performance-tuning.md: -------------------------------------------------------------------------------- 1 | # 性能调优 2 | 3 | 对于某些工作负载,可以在通过在内存中缓存数据或者打开一些实验选项来提高性能。 4 | 5 | ## 在内存中缓存数据 6 | 7 | Spark SQL可以通过调用`sqlContext.cacheTable("tableName")`方法来缓存使用柱状格式的表。然后,Spark将会仅仅浏览需要的列并且自动地压缩数据以减少内存的使用以及垃圾回收的 8 | 压力。你可以通过调用`sqlContext.uncacheTable("tableName")`方法在内存中删除表。 9 | 10 | 注意,如果你调用`schemaRDD.cache()`而不是`sqlContext.cacheTable(...)`,表将不会用柱状格式来缓存。在这种情况下,`sqlContext.cacheTable(...)`是强烈推荐的用法。 11 | 12 | 可以在SQLContext上使用setConf方法或者在用SQL时运行`SET key=value`命令来配置内存缓存。 13 | 14 | Property Name | Default | Meaning 15 | --- | --- | --- 16 | spark.sql.inMemoryColumnarStorage.compressed | true | 当设置为true时,Spark SQL将为基于数据统计信息的每列自动选择一个压缩算法。 17 | spark.sql.inMemoryColumnarStorage.batchSize | 10000 | 柱状缓存的批数据大小。更大的批数据可以提高内存的利用率以及压缩效率,但有OOMs的风险 18 | 19 | ## 其它的配置选项 20 | 21 | 以下的选项也可以用来调整查询执行的性能。有可能这些选项会在以后的版本中弃用,这是因为更多的优化会自动执行。 22 | 23 | Property Name | Default | Meaning 24 | --- | --- | --- 25 | spark.sql.autoBroadcastJoinThreshold | 10485760(10m) | 配置一个表的最大大小(byte)。当执行join操作时,这个表将会广播到所有的worker节点。可以将值设置为-1来禁用广播。注意,目前的统计数据只支持Hive Metastore表,命令`ANALYZE TABLE COMPUTE STATISTICS noscan`已经在这个表中运行。 26 | spark.sql.codegen | false | 当为true时,特定查询中的表达式求值的代码将会在运行时动态生成。对于一些拥有复杂表达式的查询,此选项可导致显著速度提升。然而,对于简单的查询,这个选项会减慢查询的执行 27 | spark.sql.shuffle.partitions | 200 | 配置join或者聚合操作shuffle数据时分区的数量 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /spark-sql/spark-sql-dataType-reference.md: -------------------------------------------------------------------------------- 1 | # Spark SQL数据类型 2 | 3 | - 数字类型 4 | - ByteType:代表一个字节的整数。范围是-128到127 5 | - ShortType:代表两个字节的整数。范围是-32768到32767 6 | - IntegerType:代表4个字节的整数。范围是-2147483648到2147483647 7 | - LongType:代表8个字节的整数。范围是-9223372036854775808到9223372036854775807 8 | - FloatType:代表4字节的单精度浮点数 9 | - DoubleType:代表8字节的双精度浮点数 10 | - DecimalType:代表任意精度的10进制数据。通过内部的java.math.BigDecimal支持。BigDecimal由一个任意精度的整型非标度值和一个32位整数组成 11 | - StringType:代表一个字符串值 12 | - BinaryType:代表一个byte序列值 13 | - BooleanType:代表boolean值 14 | - Datetime类型 15 | - TimestampType:代表包含字段年,月,日,时,分,秒的值 16 | - DateType:代表包含字段年,月,日的值 17 | - 复杂类型 18 | - ArrayType(elementType, containsNull):代表由elementType类型元素组成的序列值。`containsNull`用来指明`ArrayType`中的值是否有null值 19 | - MapType(keyType, valueType, valueContainsNull):表示包括一组键 - 值对的值。通过keyType表示key数据的类型,通过valueType表示value数据的类型。`valueContainsNull`用来指明`MapType`中的值是否有null值 20 | - StructType(fields):表示一个拥有`StructFields (fields)`序列结构的值 21 | - StructField(name, dataType, nullable):代表`StructType`中的一个字段,字段的名字通过`name`指定,`dataType`指定field的数据类型,`nullable`表示字段的值是否有null值。 22 | 23 | Spark的所有数据类型都定义在包`org.apache.spark.sql`中,你可以通过`import org.apache.spark.sql._`访问它们。 24 | 25 | 数据类型 | Scala中的值类型 | 访问或者创建数据类型的API 26 | --- | --- | --- 27 | ByteType | Byte | ByteType 28 | ShortType | Short | ShortType 29 | IntegerType | Int | IntegerType 30 | LongType | Long | LongType 31 | FloatType | Float | FloatType 32 | DoubleType | Double | DoubleType 33 | DecimalType | scala.math.BigDecimal | DecimalType 34 | StringType | String | StringType 35 | BinaryType | Array[Byte] | BinaryType 36 | BooleanType | Boolean | BooleanType 37 | TimestampType | java.sql.Timestamp | TimestampType 38 | DateType | java.sql.Date | DateType 39 | ArrayType | scala.collection.Seq | ArrayType(elementType, [containsNull]) 注意containsNull默认为true 40 | MapType | scala.collection.Map | MapType(keyType, valueType, [valueContainsNull]) 注意valueContainsNull默认为true 41 | StructType | org.apache.spark.sql.Row | StructType(fields) ,注意fields是一个StructField序列,相同名字的两个StructField不被允许 42 | StructField | The value type in Scala of the data type of this field (For example, Int for a StructField with the data type IntegerType) | StructField(name, dataType, nullable) -------------------------------------------------------------------------------- /spark-sql/writing-language-integrated-relational-queries.md: -------------------------------------------------------------------------------- 1 | # 编写语言集成(Language-Integrated)的相关查询 2 | 3 | 语言集成的相关查询是实验性的,现在暂时只支持scala。 4 | 5 | Spark SQL也支持用领域特定语言编写查询。 6 | 7 | ```scala 8 | // sc is an existing SparkContext. 9 | val sqlContext = new org.apache.spark.sql.SQLContext(sc) 10 | // Importing the SQL context gives access to all the public SQL functions and implicit conversions. 11 | import sqlContext._ 12 | val people: RDD[Person] = ... // An RDD of case class objects, from the first example. 13 | 14 | // The following is the same as 'SELECT name FROM people WHERE age >= 10 AND age <= 19' 15 | val teenagers = people.where('age >= 10).where('age <= 19).select('name) 16 | teenagers.map(t => "Name: " + t(0)).collect().foreach(println) 17 | ``` 18 | 19 | DSL使用Scala的符号来表示在潜在表(underlying table)中的列,这些列以前缀(')标示。将这些符号隐式转换成由SQL执行引擎计算的表达式。你可以在[ScalaDoc](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.SchemaRDD) 20 | 中了解详情。 21 | 22 | -------------------------------------------------------------------------------- /spark-streaming/README.md: -------------------------------------------------------------------------------- 1 | # Spark Streaming 2 | 3 | Spark streaming是Spark核心API的一个扩展,它对实时流式数据的处理具有可扩展性、高吞吐量、可容错性等特点。我们可以从kafka、flume、Twitter、 ZeroMQ、Kinesis等源获取数据,也可以通过由 4 | 高阶函数map、reduce、join、window等组成的复杂算法计算出数据。最后,处理后的数据可以推送到文件系统、数据库、实时仪表盘中。事实上,你可以将处理后的数据应用到Spark的[机器学习算法](https://spark.apache.org/docs/latest/mllib-guide.html)、 5 | [图处理算法](https://spark.apache.org/docs/latest/graphx-programming-guide.html)中去。 6 | 7 | ![Spark Streaming处理流程](../img/streaming-arch.png) 8 | 9 | 在内部,它的工作原理如下图所示。Spark Streaming接收实时的输入数据流,然后将这些数据切分为批数据供Spark引擎处理,Spark引擎将数据生成最终的结果数据。 10 | 11 | ![Spark Streaming处理原理](../img/streaming-flow.png) 12 | 13 | Spark Streaming支持一个高层的抽象,叫做离散流(`discretized stream`)或者`DStream`,它代表连续的数据流。DStream既可以利用从Kafka, Flume和Kinesis等源获取的输入数据流创建,也可以 14 | 在其他DStream的基础上通过高阶函数获得。在内部,DStream是由一系列RDDs组成。 15 | 16 | 本指南指导用户开始利用DStream编写Spark Streaming程序。用户能够利用scala、java或者Python来编写Spark Streaming程序。 17 | 18 | 注意:Spark 1.2已经为Spark Streaming引入了Python API。它的所有DStream transformations和几乎所有的输出操作可以在scala和java接口中使用。然而,它只支持基本的源如文本文件或者套接字上 19 | 的文本数据。诸如flume、kafka等外部的源的API会在将来引入。 20 | 21 | * [一个快速的例子](a-quick-example.md) 22 | * [基本概念](basic-concepts/README.md) 23 | * [关联](basic-concepts/linking.md) 24 | * [初始化StreamingContext](basic-concepts/initializing-StreamingContext.md) 25 | * [离散流](basic-concepts/discretized-streams.md) 26 | * [输入DStreams](basic-concepts/input-DStreams.md) 27 | * [DStream中的转换](basic-concepts/transformations-on-DStreams.md) 28 | * [DStream的输出操作](basic-concepts/output-operations-on-DStreams.md) 29 | * [缓存或持久化](basic-concepts/caching-persistence.md) 30 | * [Checkpointing](basic-concepts/checkpointing.md) 31 | * [部署应用程序](basic-concepts/deploying-applications.md) 32 | * [监控应用程序](basic-concepts/monitoring-applications.md) 33 | * [性能调优](performance-tuning/README.md) 34 | * [减少批数据的执行时间](performance-tuning/reducing-processing-time.md) 35 | * [设置正确的批容量](performance-tuning/setting-right-batch-size.md) 36 | * [内存调优](performance-tuning/memory-tuning.md) 37 | * [容错语义](fault-tolerance-semantics/README.md) -------------------------------------------------------------------------------- /spark-streaming/a-quick-example.md: -------------------------------------------------------------------------------- 1 | # 一个快速的例子 2 | 3 | 在我们进入如何编写Spark Streaming程序的细节之前,让我们快速地浏览一个简单的例子。在这个例子中,程序从监听TCP套接字的数据服务器获取文本数据,然后计算文本中包含的单词数。做法如下: 4 | 5 | 首先,我们导入Spark Streaming的相关类以及一些从StreamingContext获得的隐式转换到我们的环境中,为我们所需的其他类(如DStream)提供有用的方法。[StreamingContext](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.StreamingContext) 6 | 是Spark所有流操作的主要入口。然后,我们创建了一个具有两个执行线程以及1秒批间隔时间(即以秒为单位分割数据流)的本地StreamingContext。 7 | 8 | ```scala 9 | import org.apache.spark._ 10 | import org.apache.spark.streaming._ 11 | import org.apache.spark.streaming.StreamingContext._ 12 | // Create a local StreamingContext with two working thread and batch interval of 1 second 13 | val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount") 14 | val ssc = new StreamingContext(conf, Seconds(1)) 15 | ``` 16 | 利用这个上下文,我们能够创建一个DStream,它表示从TCP源(主机位localhost,端口为9999)获取的流式数据。 17 | 18 | ```scala 19 | // Create a DStream that will connect to hostname:port, like localhost:9999 20 | val lines = ssc.socketTextStream("localhost", 9999) 21 | ``` 22 | 这个`lines`变量是一个DStream,表示即将从数据服务器获得的流数据。这个DStream的每条记录都代表一行文本。下一步,我们需要将DStream中的每行文本都切分为单词。 23 | 24 | ```scala 25 | // Split each line into words 26 | val words = lines.flatMap(_.split(" ")) 27 | ``` 28 | `flatMap`是一个一对多的DStream操作,它通过把源DStream的每条记录都生成多条新记录来创建一个新的DStream。在这个例子中,每行文本都被切分成了多个单词,我们把切分 29 | 的单词流用`words`这个DStream表示。下一步,我们需要计算单词的个数。 30 | 31 | ```scala 32 | import org.apache.spark.streaming.StreamingContext._ 33 | // Count each word in each batch 34 | val pairs = words.map(word => (word, 1)) 35 | val wordCounts = pairs.reduceByKey(_ + _) 36 | // Print the first ten elements of each RDD generated in this DStream to the console 37 | wordCounts.print() 38 | ``` 39 | `words`这个DStream被mapper(一对一转换操作)成了一个新的DStream,它由(word,1)对组成。然后,我们就可以用这个新的DStream计算每批数据的词频。最后,我们用`wordCounts.print()` 40 | 打印每秒计算的词频。 41 | 42 | 需要注意的是,当以上这些代码被执行时,Spark Streaming仅仅准备好了它要执行的计算,实际上并没有真正开始执行。在这些转换操作准备好之后,要真正执行计算,需要调用如下的方法 43 | 44 | ```scala 45 | ssc.start() // Start the computation 46 | ssc.awaitTermination() // Wait for the computation to terminate 47 | ``` 48 | 完整的例子可以在[NetworkWordCount](https://github.com/apache/spark/blob/master/examples/src/main/scala/org/apache/spark/examples/streaming/NetworkWordCount.scala)中找到。 49 | 50 | 如果你已经下载和构建了Spark环境,你就能够用如下的方法运行这个例子。首先,你需要运行Netcat作为数据服务器 51 | 52 | ```shell 53 | $ nc -lk 9999 54 | ``` 55 | 然后,在不同的终端,你能够用如下方式运行例子 56 | ```shell 57 | $ ./bin/run-example streaming.NetworkWordCount localhost 9999 58 | ``` 59 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/README.md: -------------------------------------------------------------------------------- 1 | # 基本概念 2 | 3 | 在了解简单的例子的基础上,下面将介绍编写Spark Streaming应用程序必需的一些基本概念。 4 | 5 | * [关联](linking.md) 6 | * [初始化StreamingContext](initializing-StreamingContext.md) 7 | * [离散流](discretized-streams.md) 8 | * [输入DStreams](input-DStreams.md) 9 | * [DStream中的转换](transformations-on-DStreams.md) 10 | * [DStream的输出操作](output-operations-on-DStreams.md) 11 | * [缓存或持久化](caching-persistence.md) 12 | * [Checkpointing](checkpointing.md) 13 | * [部署应用程序](deploying-applications.md) 14 | * [监控应用程序](monitoring-applications.md) -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/caching-persistence.md: -------------------------------------------------------------------------------- 1 | # 缓存或持久化 2 | 3 | 和RDD相似,DStreams也允许开发者持久化流数据到内存中。在DStream上使用`persist()`方法可以自动地持久化DStream中的RDD到内存中。如果DStream中的数据需要计算多次,这是非常有用的。像`reduceByWindow`和`reduceByKeyAndWindow`这种窗口操作、`updateStateByKey`这种基于状态的操作,持久化是默认的,不需要开发者调用`persist()`方法。 4 | 5 | 例如通过网络(如kafka,flume等)获取的输入数据流,默认的持久化策略是复制数据到两个不同的节点以容错。 6 | 7 | 注意,与RDD不同的是,DStreams默认持久化级别是存储序列化数据到内存中,这将在[性能调优](../performance-tuning/README.md)章节介绍。更多的信息请看[rdd持久化](../../programming-guide/rdds/rdd-persistences.md) -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/checkpointing.md: -------------------------------------------------------------------------------- 1 | # Checkpointing 2 | 3 | 一个流应用程序必须全天候运行,所有必须能够解决应用程序逻辑无关的故障(如系统错误,JVM崩溃等)。为了使这成为可能,Spark Streaming需要checkpoint足够的信息到容错存储系统中, 4 | 以使系统从故障中恢复。 5 | 6 | - Metadata checkpointing:保存流计算的定义信息到容错存储系统如HDFS中。这用来恢复应用程序中运行worker的节点的故障。元数据包括 7 | - Configuration :创建Spark Streaming应用程序的配置信息 8 | - DStream operations :定义Streaming应用程序的操作集合 9 | - Incomplete batches:操作存在队列中的未完成的批 10 | - Data checkpointing :保存生成的RDD到可靠的存储系统中,这在有状态transformation(如结合跨多个批次的数据)中是必须的。在这样一个transformation中,生成的RDD依赖于之前 11 | 批的RDD,随着时间的推移,这个依赖链的长度会持续增长。在恢复的过程中,为了避免这种无限增长。有状态的transformation的中间RDD将会定时地存储到可靠存储系统中,以截断这个依赖链。 12 | 13 | 元数据checkpoint主要是为了从driver故障中恢复数据。如果transformation操作被用到了,数据checkpoint即使在简单的操作中都是必须的。 14 | 15 | ## 何时checkpoint 16 | 17 | 应用程序在下面两种情况下必须开启checkpoint 18 | 19 | - 使用有状态的transformation。如果在应用程序中用到了`updateStateByKey`或者`reduceByKeyAndWindow`,checkpoint目录必需提供用以定期checkpoint RDD。 20 | - 从运行应用程序的driver的故障中恢复过来。使用元数据checkpoint恢复处理信息。 21 | 22 | 注意,没有前述的有状态的transformation的简单流应用程序在运行时可以不开启checkpoint。在这种情况下,从driver故障的恢复将是部分恢复(接收到了但是还没有处理的数据将会丢失)。 23 | 这通常是可以接受的,许多运行的Spark Streaming应用程序都是这种方式。 24 | 25 | ## 怎样配置Checkpointing 26 | 27 | 在容错、可靠的文件系统(HDFS、s3等)中设置一个目录用于保存checkpoint信息。着可以通过`streamingContext.checkpoint(checkpointDirectory)`方法来做。这运行你用之前介绍的 28 | 有状态transformation。另外,如果你想从driver故障中恢复,你应该以下面的方式重写你的Streaming应用程序。 29 | 30 | - 当应用程序是第一次启动,新建一个StreamingContext,启动所有Stream,然后调用`start()`方法 31 | - 当应用程序因为故障重新启动,它将会从checkpoint目录checkpoint数据重新创建StreamingContext 32 | 33 | ```scala 34 | // Function to create and setup a new StreamingContext 35 | def functionToCreateContext(): StreamingContext = { 36 | val ssc = new StreamingContext(...) // new context 37 | val lines = ssc.socketTextStream(...) // create DStreams 38 | ... 39 | ssc.checkpoint(checkpointDirectory) // set checkpoint directory 40 | ssc 41 | } 42 | 43 | // Get StreamingContext from checkpoint data or create a new one 44 | val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _) 45 | 46 | // Do additional setup on context that needs to be done, 47 | // irrespective of whether it is being started or restarted 48 | context. ... 49 | 50 | // Start the context 51 | context.start() 52 | context.awaitTermination() 53 | ``` 54 | 55 | 如果`checkpointDirectory`存在,上下文将会利用checkpoint数据重新创建。如果这个目录不存在,将会调用`functionToCreateContext`函数创建一个新的上下文,建立DStreams。 56 | 请看[RecoverableNetworkWordCount](https://github.com/apache/spark/tree/master/examples/src/main/scala/org/apache/spark/examples/streaming/RecoverableNetworkWordCount.scala)例子。 57 | 58 | 除了使用`getOrCreate`,开发者必须保证在故障发生时,driver处理自动重启。只能通过部署运行应用程序的基础设施来达到该目的。在部署章节将有更进一步的讨论。 59 | 60 | 注意,RDD的checkpointing有存储成本。这会导致批数据(包含的RDD被checkpoint)的处理时间增加。因此,需要小心的设置批处理的时间间隔。在最小的批容量(包含1秒的数据)情况下,checkpoint每批数据会显著的减少 61 | 操作的吞吐量。相反,checkpointing太少会导致谱系以及任务大小增大,这会产生有害的影响。因为有状态的transformation需要RDD checkpoint。默认的间隔时间是批间隔时间的倍数,最少10秒。它可以通过`dstream.checkpoint` 62 | 来设置。典型的情况下,设置checkpoint间隔是DStream的滑动间隔的5-10大小是一个好的尝试。 63 | 64 | 65 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/custom-receiver.md: -------------------------------------------------------------------------------- 1 | # 自定义receiver指南 2 | 3 | Spark Streaming可以从包括内置数据源在内的任意数据源获取数据(其他数据源包括flume,kafka,kinesis,文件,套接字等等)。这需要开发者去实现一个定制`receiver`从具体的数据源接收 4 | 数据。本指南介绍了实现自定义`receiver`的过程,以及怎样将`receiver`用到Spark Streaming应用程序中。 5 | 6 | ## 实现一个自定义的Receiver 7 | 8 | 这一节开始实现一个[Receiver](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.receiver.Receiver)。一个自定义的receiver必须继承 9 | 这个抽象类,实现它的两个方法`onStart()`(开始接收数据)以及`onStop()`(停止接收数据)。 10 | 11 | 需要注意的是`onStart()`和`onStop()`不能够无限期的阻塞。通常情况下,`onStart()`启动线程负责数据的接收,`onStop()`确保这个接收过程停止。接收线程也能够调用`receiver`的`isStopped` 12 | 方法去检查是否已经停止接收数据。 13 | 14 | 一旦接收了数据,这些数据就能够通过调用`store(data)`方法存到Spark中,`store(data)`是[Receiver]类中的方法。有几个重载的`store()`方法允许你存储接收到的数据(record-at-a-time or as whole collection of objects/serialized bytes) 15 | 16 | 在接收线程中出现的任何异常都应该被捕获或者妥善处理从而避免`receiver`在没有提示的情况下失败。`restart()`方法将会重新启动`receiver`,它通过异步的方式首先调用`onStop()`方法, 17 | 然后在一段延迟之后调用`onStart()`方法。`stop()`将会调用`onStop()`方法终止`receiver`。`reportError()`方法在不停止或者重启`receiver`的情况下打印错误消息到 18 | 驱动程序(driver)。 19 | 20 | 如下所示,是一个自定义的`receiver`,它通过套接字接收文本数据流。它用分界符'\n'把文本流分割为行记录,然后将它们存储到Spark中。如果接收线程碰到任何连接或者接收错误,`receiver`将会 21 | 重新启动以尝试再一次连接。 22 | 23 | ```scala 24 | 25 | class CustomReceiver(host: String, port: Int) 26 | extends Receiver[String](StorageLevel.MEMORY_AND_DISK_2) with Logging { 27 | 28 | def onStart() { 29 | // Start the thread that receives data over a connection 30 | new Thread("Socket Receiver") { 31 | override def run() { receive() } 32 | }.start() 33 | } 34 | 35 | def onStop() { 36 | // There is nothing much to do as the thread calling receive() 37 | // is designed to stop by itself isStopped() returns false 38 | } 39 | 40 | //Create a socket connection and receive data until receiver is stopped 41 | private def receive() { 42 | var socket: Socket = null 43 | var userInput: String = null 44 | try { 45 | // Connect to host:port 46 | socket = new Socket(host, port) 47 | // Until stopped or connection broken continue reading 48 | val reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")) 49 | userInput = reader.readLine() 50 | while(!isStopped && userInput != null) { 51 | store(userInput) 52 | userInput = reader.readLine() 53 | } 54 | reader.close() 55 | socket.close() 56 | // Restart in an attempt to connect again when server is active again 57 | restart("Trying to connect again") 58 | } catch { 59 | case e: java.net.ConnectException => 60 | // restart if could not connect to server 61 | restart("Error connecting to " + host + ":" + port, e) 62 | case t: Throwable => 63 | // restart if there is any other error 64 | restart("Error receiving data", t) 65 | } 66 | } 67 | } 68 | 69 | ``` 70 | 71 | ## 在Spark流应用程序中使用自定义的receiver 72 | 73 | 在Spark流应用程序中,用`streamingContext.receiverStream()`方法,可以使用自动用`receiver`。代码如下所示: 74 | 75 | ```scala 76 | // Assuming ssc is the StreamingContext 77 | val customReceiverStream = ssc.receiverStream(new CustomReceiver(host, port)) 78 | val words = lines.flatMap(_.split(" ")) 79 | ... 80 | ``` 81 | 82 | 完整的代码见例子[CustomReceiver.scala](https://github.com/apache/spark/blob/master/examples/src/main/scala/org/apache/spark/examples/streaming/CustomReceiver.scala) 83 | 84 | ## Receiver可靠性 85 | 86 | 基于Receiver的稳定性以及容错语义,Receiver分为两种类型 87 | 88 | - Reliable Receiver:可靠的源允许发送的数据被确认。一个可靠的receiver正确的应答一个可靠的源,数据已经收到并且被正确地复制到了Spark中(指正确完成复制)。实现这个receiver并 89 | 仔细考虑源确认的语义。 90 | - Unreliable Receiver :这些receivers不支持应答。即使对于一个可靠的源,开发者可能实现一个非可靠的receiver,这个receiver不会正确应答。 91 | 92 | 为了实现可靠receiver,你必须使用`store(multiple-records)`去保存数据。保存的类型是阻塞访问,即所有给定的记录全部保存到Spark中后才返回。如果receiver的配置存储级别利用复制 93 | (默认情况是复制),则会在复制结束之后返回。因此,它确保数据被可靠地存储,receiver恰当的应答给源。这保证在复制的过程中,没有数据造成的receiver失败。因为缓冲数据不会应答,从而 94 | 可以从源中重新获取数据。 95 | 96 | 一个不可控的receiver不必实现任何这种逻辑。它简单的从源中接收数据,然后用`store(single-record)`一次一个地保存它们。虽然它不能用`store(multiple-records)`获得可靠的保证, 97 | 它有下面一些优势: 98 | 99 | - 系统注重分块,将数据分为适当大小的块。 100 | - 如果指定了速率的限制,系统注重控制接收速率。 101 | - 因为以上两点,不可靠receiver比可靠receiver更容易实现。 102 | 103 | 下面是两类receiver的特征 104 | Receiver Type | Characteristics 105 | --- | --- 106 | Unreliable Receivers | 实现简单;系统更关心块的生成和速率的控制;没有容错的保证,在receiver失败时会丢失数据 107 | Reliable Receivers | 高容错保证,零数据丢失;块的生成和速率的控制需要手动实现;实现的复杂性依赖源的确认机制 108 | 109 | ## 实现和使用自定义的基于actor的receiver 110 | 111 | 自定义的Akka actor也能够拥有接收数据。[ActorHelper](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.receiver.ActorHelper)trait可以 112 | 应用于任何Akka actor中。它运行接收的数据通过调用`store()`方法存储于Spark中。可以配置这个actor的监控(supervisor)策略处理错误。 113 | 114 | ```scala 115 | class CustomActor extends Actor with ActorHelper { 116 | def receive = { 117 | case data: String => store(data) 118 | } 119 | } 120 | ``` 121 | 122 | 利用这个actor,一个新的输入数据流就能够被创建。 123 | 124 | ```scala 125 | // Assuming ssc is the StreamingContext 126 | val lines = ssc.actorStream[String](Props(new CustomActor()), "CustomReceiver") 127 | ``` 128 | 129 | 完整的代码间例子[ActorWordCount.scala](https://github.com/apache/spark/blob/master/examples/src/main/scala/org/apache/spark/examples/streaming/ActorWordCount.scala) 130 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/deploying-applications.md: -------------------------------------------------------------------------------- 1 | # 部署应用程序 2 | 3 | ## Requirements 4 | 5 | 运行一个Spark Streaming应用程序,有下面一些步骤 6 | 7 | - 有管理器的集群-这是任何Spark应用程序都需要的需求,详见[部署指南](../../deploying/README.md) 8 | - 将应用程序打为jar包-你必须编译你的应用程序为jar包。如果你用[spark-submit](../../deploying/submitting-applications.md)启动应用程序,你不需要将Spark和Spark Streaming打包进这个jar包。 9 | 如果你的应用程序用到了高级源(如kafka,flume),你需要将它们关联的外部artifact以及它们的依赖打包进需要部署的应用程序jar包中。例如,一个应用程序用到了`TwitterUtils`,那么就需要将`spark-streaming-twitter_2.10` 10 | 以及它的所有依赖打包到应用程序jar中。 11 | - 为executors配置足够的内存-因为接收的数据必须存储在内存中,executors必须配置足够的内存用来保存接收的数据。注意,如果你正在做10分钟的窗口操作,系统的内存要至少能保存10分钟的数据。所以,应用程序的内存需求依赖于使用 12 | 它的操作。 13 | - 配置checkpointing-如果stream应用程序需要checkpointing,然后一个与Hadoop API兼容的容错存储目录必须配置为检查点的目录,流应用程序将checkpoint信息写入该目录用于错误恢复。 14 | - 配置应用程序driver的自动重启-为了自动从driver故障中恢复,运行流应用程序的部署设施必须能监控driver进程,如果失败了能够重启它。不同的集群管理器,有不同的工具得到该功能 15 | - Spark Standalone:一个Spark应用程序driver可以提交到Spark独立集群运行,也就是说driver运行在一个worker节点上。进一步来看,独立的集群管理器能够被指示用来监控driver,并且在driver失败(或者是由于非零的退出代码如exit(1), 16 | 或者由于运行driver的节点的故障)的情况下重启driver。 17 | - YARN:YARN为自动重启应用程序提供了类似的机制。 18 | - Mesos: Mesos可以用[Marathon](https://github.com/mesosphere/marathon)提供该功能 19 | - 配置write ahead logs-在Spark 1.2中,为了获得极强的容错保证,我们引入了一个新的实验性的特性-预写日志(write ahead logs)。如果该特性开启,从receiver获取的所有数据会将预写日志写入配置的checkpoint目录。 20 | 这可以防止driver故障丢失数据,从而保证零数据丢失。这个功能可以通过设置配置参数`spark.streaming.receiver.writeAheadLogs.enable`为true来开启。然而,这些较强的语义可能以receiver的接收吞吐量为代价。这可以通过 21 | 并行运行多个receiver增加吞吐量来解决。另外,当预写日志开启时,Spark中的复制数据的功能推荐不用,因为该日志已经存储在了一个副本在存储系统中。可以通过设置输入DStream的存储级别为`StorageLevel.MEMORY_AND_DISK_SER`获得该功能。 22 | 23 | 24 | ## 升级应用程序代码 25 | 26 | 如果运行的Spark Streaming应用程序需要升级,有两种可能的方法 27 | 28 | - 启动升级的应用程序,使其与未升级的应用程序并行运行。一旦新的程序(与就程序接收相同的数据)已经准备就绪,旧的应用程序就可以关闭。这种方法支持将数据发送到两个不同的目的地(新程序一个,旧程序一个) 29 | - 首先,平滑的关闭(`StreamingContext.stop(...)`或`JavaStreamingContext.stop(...)`)现有的应用程序。在关闭之前,要保证已经接收的数据完全处理完。然后,就可以启动升级的应用程序,升级 30 | 的应用程序会接着旧应用程序的点开始处理。这种方法仅支持具有源端缓存功能的输入源(如flume,kafka),这是因为当旧的应用程序已经关闭,升级的应用程序还没有启动的时候,数据需要被缓存。 31 | 32 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/discretized-streams.md: -------------------------------------------------------------------------------- 1 | # 离散流(DStreams) 2 | 3 | 离散流或者DStreams是Spark Streaming提供的基本的抽象,它代表一个连续的数据流。它要么是从源中获取的输入流,要么是输入流通过转换算子生成的处理后的数据流。在内部,DStreams由一系列连续的 4 | RDD组成。DStreams中的每个RDD都包含确定时间间隔内的数据,如下图所示: 5 | 6 | ![DStreams](../../img/streaming-dstream.png) 7 | 8 | 任何对DStreams的操作都转换成了对DStreams隐含的RDD的操作。在前面的[例子](../a-quick-example.md)中,`flatMap`操作应用于`lines`这个DStreams的每个RDD,生成`words`这个DStreams的 9 | RDD。过程如下图所示: 10 | 11 | ![DStreams](../../img/streaming-dstream-ops.png) 12 | 13 | 通过Spark引擎计算这些隐含RDD的转换算子。DStreams操作隐藏了大部分的细节,并且为了更便捷,为开发者提供了更高层的API。下面几节将具体讨论这些操作的细节。 -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/flume-integration-guide.md: -------------------------------------------------------------------------------- 1 | # flume集成指南 2 | 3 | flume是一个分布式的、稳定的、有效的服务,它能够高效的收集、聚集以及移动大量的日志数据。flume的架构如下图所示。 4 | 5 | ![flume](../../img/flume.png) 6 | 7 | 本节将介绍怎样配置flume以及Spark Streaming如何从flume中接收数据。主要有两种方法可用。 8 | ## 方法一:基于推送的flume风格的方法 9 | 10 | flume被设计用来在flume agent间推送数据。在这个方法中,Spark Streaming本质上是建立一个`receiver`,这个`receiver`充当一个Avro代理,用于接收flume推送的数据。下面是配置的过程。 11 | 12 | ### 一般需求 13 | 14 | 在你的集群中,选择一台满足下面条件的机器: 15 | 16 | - 当你的flume和Spark Streaming应用程序启动以后,必须有一个Spark worker运行在这台机器上面 17 | - 可以配置flume推送数据到这台机器的某个端口 18 | 19 | 因为是推送模式,安排好`receiver`并且监听选中端口的流应用程序必须是开启的,以使flume能够发送数据。 20 | 21 | ### 配置flume 22 | 23 | 通过下面的配置文件配置flume agent,发送数据到一个Avro sink。 24 | 25 | ``` 26 | agent.sinks = avroSink 27 | agent.sinks.avroSink.type = avro 28 | agent.sinks.avroSink.channel = memoryChannel 29 | agent.sinks.avroSink.hostname = 30 | agent.sinks.avroSink.port = 31 | ``` 32 | 查看[flume文档](https://flume.apache.org/documentation.html)了解更多的信息。 33 | 34 | ### 配置Spark Streaming应用程序 35 | 36 | - 关联:在你的SBT或者Maven项目定义中,引用下面的组件到流应用程序中。 37 | ``` 38 | groupId = org.apache.spark 39 | artifactId = spark-streaming-flume_2.10 40 | version = 1.1.1 41 | ``` 42 | - 编程:在应用程序代码中,引入`FlumeUtils`创建输入DStream。 43 | ``` 44 | import org.apache.spark.streaming.flume._ 45 | val flumeStream = FlumeUtils.createStream(streamingContext, [chosen machine's hostname], [chosen port]) 46 | ``` 47 | 查看[API文档](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.flume.FlumeUtils$)和[例子](https://github.com/apache/spark/tree/master/examples/src/main/scala/org/apache/spark/examples/streaming/FlumeEventCount.scala) 48 | 49 | 注意,hostname必须和集群(Mesos,YARN或者Spark Standalone)的resource manager所使用的机器的hostname是同一个,这样就可以根据名称分配资源,在正确的机器上启动`receiver`。 50 | 51 | - 部署:将`spark-streaming-flume_2.10`和它的依赖(除了`spark-core_2.10`和`spark-streaming_2.10`)打包到应用程序jar包中。然后用`spark-submit`方法启动你的应用程序。 52 | 53 | 54 | ## 方法2:利用自定义sink的基于拉的方法 55 | 56 | 作为直接推送数据到Spark Streaming方法的替代方法,这个方法运行一个自定义的flume sink用于满足下面两点功能。 57 | 58 | - flume推送数据到sink,该数据被缓存在sink中。 59 | - Spark Streaming利用事务从sink中拉取数据。只有数据接收并且被Spark Streaming复制了之后,事务才算成功。这使这个方法比之前的方法具有更好的稳定性和容错性。然而,这个方法需要flume去 60 | 运行一个自定义sink。下面是配置的过程。 61 | 62 | ### 一般需求 63 | 64 | 选择一台机器在flume agent中运行自定义的sink,配置余下的flume管道(pipeline)发送数据到agent中。集群中的其它机器应该能够访问运行自定义sink的机器。 65 | 66 | ### 配置flume 67 | 68 | 在选定的机器上面配置flume需要以下两个步骤。 69 | 70 | - Sink Jars:添加下面的jar文件到flume的classpath目录下面 71 | - 自定义sink jar:通过下面的方式下载jar(或者[这里](http://search.maven.org/remotecontent?filepath=org/apache/spark/spark-streaming-flume-sink_2.10/1.1.1/spark-streaming-flume-sink_2.10-1.1.1.jar)) 72 | ``` 73 | groupId = org.apache.spark 74 | artifactId = spark-streaming-flume-sink_2.10 75 | version = 1.1.1 76 | ``` 77 | - scala library jar:下载scala 2.10.4库,你能够通过下面的方式下载(或者[这里](http://search.maven.org/remotecontent?filepath=org/scala-lang/scala-library/2.10.4/scala-library-2.10.4.jar)) 78 | ``` 79 | groupId = org.scala-lang 80 | artifactId = scala-library 81 | version = 2.10.4 82 | ``` 83 | - 配置文件:通过下面的配置文件配置flume agent用于发送数据到Avro sink。 84 | 85 | ``` 86 | agent.sinks = spark 87 | agent.sinks.spark.type = org.apache.spark.streaming.flume.sink.SparkSink 88 | agent.sinks.spark.hostname = 89 | agent.sinks.spark.port = 90 | agent.sinks.spark.channel = memoryChannel 91 | ``` 92 | 要确保配置的逆流flume管道(upstream Flume pipeline)运行这个sink发送数据到flume代理。 93 | 94 | ### 配置Spark Streaming应用程序 95 | 96 | - 关联:在你的SBT或者Maven项目定义中,引入`spark-streaming-flume_2.10`组件 97 | - 编程:在应用程序代码中,引入`FlumeUtils`创建输入DStream。 98 | 99 | ``` 100 | import org.apache.spark.streaming.flume._ 101 | val flumeStream = FlumeUtils.createPollingStream(streamingContext, [sink machine hostname], [sink port]) 102 | ``` 103 | 104 | 可以查看用例[FlumePollingEventCount](https://github.com/apache/spark/tree/master/examples/src/main/scala/org/apache/spark/examples/streaming/FlumePollingEventCount.scala) 105 | 106 | 注意,每个输入DStream都可以配置为从多个sink接收数据。 107 | 108 | - 部署:将`spark-streaming-flume_2.10`和它的依赖(除了`spark-core_2.10`和`spark-streaming_2.10`)打包到应用程序的jar包中。然后用`spark-submit`方法启动你的应用程序。 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/initializing-StreamingContext.md: -------------------------------------------------------------------------------- 1 | # 初始化StreamingContext 2 | 3 | 为了初始化Spark Streaming程序,一个StreamingContext对象必需被创建,它是Spark Streaming所有流操作的主要入口。一个[StreamingContext](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.StreamingContext) 4 | 对象可以用[SparkConf](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.SparkConf)对象创建。 5 | 6 | ```scala 7 | import org.apache.spark._ 8 | import org.apache.spark.streaming._ 9 | val conf = new SparkConf().setAppName(appName).setMaster(master) 10 | val ssc = new StreamingContext(conf, Seconds(1)) 11 | ``` 12 | 13 | `appName`表示你的应用程序显示在集群UI上的名字,`master`是一个[Spark、Mesos、YARN](https://spark.apache.org/docs/latest/submitting-applications.html#master-urls)集群URL 14 | 或者一个特殊字符串“local[*]”,它表示程序用本地模式运行。当程序运行在集群中时,你并不希望在程序中硬编码`master`,而是希望用`spark-submit`启动应用程序,并从`spark-submit`中得到 15 | `master`的值。对于本地测试或者单元测试,你可以传递“local”字符串在同一个进程内运行Spark Streaming。需要注意的是,它在内部创建了一个SparkContext对象,你可以通过` ssc.sparkContext` 16 | 访问这个SparkContext对象。 17 | 18 | 批时间片需要根据你的程序的潜在需求以及集群的可用资源来设定,你可以在[性能调优](../performance-tuning/README.md)那一节获取详细的信息。 19 | 20 | 可以利用已经存在的`SparkContext`对象创建`StreamingContext`对象。 21 | 22 | ```scala 23 | import org.apache.spark.streaming._ 24 | val sc = ... // existing SparkContext 25 | val ssc = new StreamingContext(sc, Seconds(1)) 26 | ``` 27 | 28 | 当一个上下文(context)定义之后,你必须按照以下几步进行操作 29 | 30 | - 定义输入源; 31 | - 准备好流计算指令; 32 | - 利用`streamingContext.start()`方法接收和处理数据; 33 | - 处理过程将一直持续,直到`streamingContext.stop()`方法被调用。 34 | 35 | 几点需要注意的地方: 36 | 37 | - 一旦一个context已经启动,就不能有新的流算子建立或者是添加到context中。 38 | - 一旦一个context已经停止,它就不能再重新启动 39 | - 在JVM中,同一时间只能有一个StreamingContext处于活跃状态 40 | - 在StreamingContext上调用`stop()`方法,也会关闭SparkContext对象。如果只想仅关闭StreamingContext对象,设置`stop()`的可选参数为false 41 | - 一个SparkContext对象可以重复利用去创建多个StreamingContext对象,前提条件是前面的StreamingContext在后面StreamingContext创建之前关闭(不关闭SparkContext)。 -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/input-DStreams.md: -------------------------------------------------------------------------------- 1 | # 输入DStreams和receivers 2 | 3 | 输入DStreams表示从数据源获取输入数据流的DStreams。在[快速例子](../a-quick-example.md)中,`lines`表示输入DStream,它代表从netcat服务器获取的数据流。每一个输入流DStream 4 | 和一个`Receiver`对象相关联,这个`Receiver`从源中获取数据,并将数据存入内存中用于处理。 5 | 6 | 输入DStreams表示从数据源获取的原始数据流。Spark Streaming拥有两类数据源 7 | - 基本源(Basic sources):这些源在StreamingContext API中直接可用。例如文件系统、套接字连接、Akka的actor等。 8 | - 高级源(Advanced sources):这些源包括Kafka,Flume,Kinesis,Twitter等等。它们需要通过额外的类来使用。我们在[关联](linking.md)那一节讨论了类依赖。 9 | 10 | 需要注意的是,如果你想在一个流应用中并行地创建多个输入DStream来接收多个数据流,你能够创建多个输入流(这将在[性能调优](../performance-tuning/README.md)那一节介绍) 11 | 。它将创建多个Receiver同时接收多个数据流。但是,`receiver`作为一个长期运行的任务运行在Spark worker或executor中。因此,它占有一个核,这个核是分配给Spark Streaming应用程序的所有 12 | 核中的一个(it occupies one of the cores allocated to the Spark Streaming application)。所以,为Spark Streaming应用程序分配足够的核(如果是本地运行,那么是线程) 13 | 用以处理接收的数据并且运行`receiver`是非常重要的。 14 | 15 | 几点需要注意的地方: 16 | - 如果分配给应用程序的核的数量少于或者等于输入DStreams或者receivers的数量,系统只能够接收数据而不能处理它们。 17 | - 当运行在本地,如果你的master URL被设置成了“local”,这样就只有一个核运行任务。这对程序来说是不足的,因为作为`receiver`的输入DStream将会占用这个核,这样就没有剩余的核来处理数据了。 18 | 19 | ## 基本源 20 | 21 | 我们已经在[快速例子](../a-quick-example.md)中看到,`ssc.socketTextStream(...)`方法用来把从TCP套接字获取的文本数据创建成DStream。除了套接字,StreamingContext API也支持把文件 22 | 以及Akka actors作为输入源创建DStream。 23 | 24 | - 文件流(File Streams):从任何与HDFS API兼容的文件系统中读取数据,一个DStream可以通过如下方式创建 25 | 26 | ```scala 27 | streamingContext.fileStream[keyClass, valueClass, inputFormatClass](dataDirectory) 28 | ``` 29 | Spark Streaming将会监控`dataDirectory`目录,并且处理目录下生成的任何文件(嵌套目录不被支持)。需要注意一下三点: 30 | 31 | 1 所有文件必须具有相同的数据格式 32 | 2 所有文件必须在`dataDirectory`目录下创建,文件是自动的移动和重命名到数据目录下 33 | 3 一旦移动,文件必须被修改。所以如果文件被持续的附加数据,新的数据不会被读取。 34 | 35 | 对于简单的文本文件,有一个更简单的方法`streamingContext.textFileStream(dataDirectory)`可以被调用。文件流不需要运行一个receiver,所以不需要分配核。 36 | 37 | 在Spark1.2中,`fileStream`在Python API中不可用,只有`textFileStream`可用。 38 | 39 | - 基于自定义actor的流:DStream可以调用`streamingContext.actorStream(actorProps, actor-name)`方法从Akka actors获取的数据流来创建。具体的信息见[自定义receiver指南](https://spark.apache.org/docs/latest/streaming-custom-receivers.html#implementing-and-using-a-custom-actor-based-receiver) 40 | `actorStream`在Python API中不可用。 41 | - RDD队列作为数据流:为了用测试数据测试Spark Streaming应用程序,人们也可以调用`streamingContext.queueStream(queueOfRDDs)`方法基于RDD队列创建DStreams。每个push到队列的RDD都被 42 | 当做DStream的批数据,像流一样处理。 43 | 44 | 关于从套接字、文件和actor中获取流的更多细节,请看[StreamingContext](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.StreamingContext)和 45 | [JavaStreamingContext](https://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/streaming/api/java/JavaStreamingContext.html) 46 | 47 | ## 高级源 48 | 49 | 这类源需要非Spark库接口,并且它们中的部分还需要复杂的依赖(例如kafka和flume)。为了减少依赖的版本冲突问题,从这些源创建DStream的功能已经被移到了独立的库中,你能在[关联](linking.md)查看 50 | 细节。例如,如果你想用来自推特的流数据创建DStream,你需要按照如下步骤操作: 51 | 52 | - 关联:添加`spark-streaming-twitter_2.10`到SBT或maven项目的依赖中 53 | - 编写:导入`TwitterUtils`类,用`TwitterUtils.createStream`方法创建DStream,如下所示 54 | ```scala 55 | import org.apache.spark.streaming.twitter._ 56 | TwitterUtils.createStream(ssc) 57 | ``` 58 | - 部署:将编写的程序以及其所有的依赖(包括spark-streaming-twitter_2.10的依赖以及它的传递依赖)打为jar包,然后部署。这在[部署章节](deploying-applications.md)将会作更进一步的介绍。 59 | 60 | 需要注意的是,这些高级的源在`spark-shell`中不能被使用,因此基于这些源的应用程序无法在shell中测试。 61 | 62 | 下面将介绍部分的高级源: 63 | 64 | - Twitter:Spark Streaming利用`Twitter4j 3.0.3`获取公共的推文流,这些推文通过[推特流API](https://dev.twitter.com/docs/streaming-apis)获得。认证信息可以通过Twitter4J库支持的 65 | 任何[方法](http://twitter4j.org/en/configuration.html)提供。你既能够得到公共流,也能够得到基于关键字过滤后的流。你可以查看API文档([scala](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.twitter.TwitterUtils$)和[java](https://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/streaming/twitter/TwitterUtils.html)) 66 | 和例子([TwitterPopularTags](https://github.com/apache/spark/blob/master/examples/src/main/scala/org/apache/spark/examples/streaming/TwitterPopularTags.scala)和[TwitterAlgebirdCMS](https://github.com/apache/spark/blob/master/examples/src/main/scala/org/apache/spark/examples/streaming/TwitterAlgebirdCMS.scala)) 67 | - Flume:Spark Streaming 1.2能够从flume 1.4.0中获取数据,可以查看[flume集成指南](flume-integration-guide.md)了解详细信息 68 | - Kafka:Spark Streaming 1.2能够从kafka 0.8.0中获取数据,可以查看[kafka集成指南](kafka-integration-guide.md)了解详细信息 69 | - Kinesis:查看[Kinesis集成指南](kinesis-integration.md)了解详细信息 70 | 71 | ## 自定义源 72 | 73 | 在Spark 1.2中,这些源不被Python API支持。 74 | 输入DStream也可以通过自定义源创建,你需要做的是实现用户自定义的`receiver`,这个`receiver`可以从自定义源接收数据以及将数据推到Spark中。通过[自定义receiver指南](custom-receiver.md)了解详细信息 75 | 76 | ## Receiver可靠性 77 | 78 | 基于可靠性有两类数据源。源(如kafka、flume)允许。如果从这些可靠的源获取数据的系统能够正确的应答所接收的数据,它就能够确保在任何情况下不丢失数据。这样,就有两种类型的receiver: 79 | 80 | - Reliable Receiver:一个可靠的receiver正确的应答一个可靠的源,数据已经收到并且被正确地复制到了Spark中。 81 | - Unreliable Receiver :这些receivers不支持应答。即使对于一个可靠的源,开发者可能实现一个非可靠的receiver,这个receiver不会正确应答。 82 | 83 | 怎样编写可靠的Receiver的细节在[自定义receiver](custom-receiver.md)中有详细介绍。 84 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/kafka-integration-guide.md: -------------------------------------------------------------------------------- 1 | # kafka集成指南 2 | 3 | [Apache kafka](http://kafka.apache.org/)是一个分布式的发布-订阅消息系统,它可以分布式的、可分区的、可重复提交的方式读写日志数据。下面我们将具体介绍Spark Streaming怎样从kafka中 4 | 接收数据。 5 | 6 | - 关联:在你的SBT或者Maven项目定义中,引用下面的组件到流应用程序中。 7 | 8 | ``` 9 | groupId = org.apache.spark 10 | artifactId = spark-streaming-kafka_2.10 11 | version = 1.1.1 12 | ``` 13 | 14 | - 编程:在应用程序代码中,引入`FlumeUtils`创建输入DStream。 15 | 16 | ```scala 17 | import org.apache.spark.streaming.kafka._ 18 | val kafkaStream = KafkaUtils.createStream( 19 | streamingContext, [zookeeperQuorum], [group id of the consumer], [per-topic number of Kafka partitions to consume]) 20 | ``` 21 | 22 | 有两点需要注意的地方: 23 | 24 | 1. kafka的topic分区(partition)和由Spark Streaming生成的RDD分区不相关。所以在`KafkaUtils.createStream()`方法中,增加特定topic的分区数只能够增加单个`receiver`消费这个 25 | topic的线程数,不能增加Spark处理数据的并发数。 26 | 27 | 2. 通过不同的group和topic,可以创建多个输入DStream,从而利用多个`receiver`并发的接收数据。 28 | 29 | - 部署:将`spark-streaming-kafka_2.10`和它的依赖(除了`spark-core_2.10`和`spark-streaming_2.10`)打包到应用程序的jar包中。然后用`spark-submit`方法启动你的应用程序。 -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/kinesis-integration.md: -------------------------------------------------------------------------------- 1 | # Kinesis集成指南 2 | 3 | 亚马逊Kinesis是一个实时处理大规模流式数据的全托管服务。Kinesis receiver应用Kinesis客户端库(KCL)创建一个输入DStream。KCL由亚马逊提供,它拥有亚马逊软件许可证(ASL)。KCL构建在 4 | apache 2.0许可的AWS java SDK之上,通过Workers、检查点(Checkpoints)和Shard Leases等概念提供了负载均衡、容错、检查点机制。下面将详细介绍怎样配置Spark Streaming从Kinesis获取 5 | 数据。 6 | 7 | ## 配置Kinesis 8 | 9 | 一个Kinesis流可以通用一个拥有一个或者多个shard的有效Kinesis端点(endpoint)来建立,详情请见[指南](http://docs.aws.amazon.com/kinesis/latest/dev/step-one-create-stream.html) 10 | 11 | ## 配置Spark Streaming应用程序 12 | 13 | 1、关联:在你的SBT或者Maven项目定义中,引用下面的组件到流应用程序中 14 | 15 | ``` 16 | groupId = org.apache.spark 17 | artifactId = spark-streaming-kinesis-asl_2.10 18 | version = 1.1.1 19 | ``` 20 | 需要注意的是,关联这个artifact,你必须将[ASL](https://aws.amazon.com/asl/)认证代码加入到你的应用程序中。 21 | 22 | 2、编程:在你的应用程序代码中,通过引入`KinesisUtils`创建DStream 23 | 24 | ```scala 25 | import org.apache.spark.streaming.Duration 26 | import org.apache.spark.streaming.kinesis._ 27 | import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream 28 | val kinesisStream = KinesisUtils.createStream( 29 | streamingContext, [Kinesis stream name], [endpoint URL], [checkpoint interval], [initial position]) 30 | ``` 31 | 32 | 可以查看[API文档](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.streaming.kinesis.KinesisUtils$)和[例子](https://github.com/apache/spark/tree/master/extras/kinesis-asl/src/main/scala/org/apache/spark/examples/streaming/KinesisWordCountASL.scala)。 33 | 34 | - streamingContext:streamingContext包含一个应用程序名称,这个名称关联Kinesis应用程序和Kinesis流。 35 | - Kinesis stream name:这个流应用程序的Kinesis流获取名称满足一下几点: 36 | - 在流上下文中使用的应用程序名称可以作为Kinesis应用程序名称 37 | - 对于某个地区的同一账户,应用程序名称必须唯一 38 | - Kinesis的后端通过一个DynamoDB表(一般情况下在us-east-1 region)自动的关联应用程序名称和Kinesis流,这个DynamoDB表由Kinesis流初始化 39 | - 在某些情况下,改变应用程序名称或者流名称可能导致Kinesis错误,如果你发现了错误,你可能需要手动删除DynamoDB表 40 | - endpoint URL:合法的Kinesis endpoint URL能够在[这里](http://docs.aws.amazon.com/general/latest/gr/rande.html#ak_region)找到。 41 | - checkpoint interval:KCL在流中保存检查点位置的时间间隔,对于初学者,可以将其和流应用程序的批时间间隔设置得一致。 42 | - initial position:可以是`InitialPositionInStream.TRIM_HORIZON`也可以是`InitialPositionInStream.LATEST`(可以查看Kinesis checkpoint和亚马逊Kinesis API文档了解详细信息) 43 | 44 | 3、部署:将`spark-streaming-kinesis-asl_2.10`和它的依赖(除了`spark-core_2.10`和`spark-streaming_2.10`)打包到应用程序的jar包中。然后用`spark-submit`方法启动你的应用程序。 45 | 46 | 在运行过程中需要注意一下几点: 47 | 48 | - Kinesis的每个分区的数据处理都是有序的,每一条消息至少出现一次 49 | - 多个应用程序可以从相同的Kinesis流读取数据,Kinesis将会保存特定程序的shard和checkpoint到DynamodDB中 50 | - 在某一时间单个的Kinesis流shard只能被一个输入DStream处理 51 | - 单个的Kinesis DStream通过创建多个`KinesisRecordProcessor`线程,可以从Kinesis流的多个shard中读取数据 52 | - 分开运行在不同的processes或者instances中的多个输入DStream能够从Kinesis流中读到 53 | - Kinesis输入DStream的数量不应比Kinesis shard的数量多,这是因为每个输入DStream都将创建至少一个`KinesisRecordProcessor`线程去处理单个的shard 54 | - 通过添加或者删除DStreams(在单个处理器或者多个processes/instance之间)可以获得水平扩展,直到扩展到Kinesis shard的数量。 55 | - Kinesis输入DStream将会平衡所有DStream的负载,甚至是跨processes/instance的DStream 56 | - Kinesis输入DStream将会平衡由于变化引起的re-shard事件(合并和切分)的负载 57 | - 作为一个最佳实践,建议避免使用过度的re-shard 58 | - 每一个Kinesis输入DStream都包含它们自己的checkpoint信息 59 | - Kinesis流shard的数量与RDD分区(在Spark输入DStream处理的过程中产生)的数量之间没有关系。它们是两种独立的分区模式 60 | 61 | ![Spark流Kinesis架构](../../img/streaming-kinesis-arch.png) 62 | 63 | ## 运行实例 64 | 65 | - 下载Spark 源代码,然后按照下面的方法build Spark。 66 | ``` 67 | mvn -Pkinesis-asl -DskipTests clean package 68 | ``` 69 | - 在AWS中设定Kinesis流。注意Kinesis流的名字以及endpoint URL与流创建的地区相关联 70 | - 在你的AWS证书中设定`AWS_ACCESS_KEY_ID`和`AWS_SECRET_KEY`环境变量 71 | - 在Spark根目录下面,运行例子 72 | 73 | ``` 74 | bin/run-example streaming.KinesisWordCountASL [Kinesis stream name] [endpoint URL] 75 | ``` 76 | 这个例子将会等待从Kinesis流中获取数据 77 | 78 | - 在另外一个终端,为了生成生成随机的字符串数据到Kinesis流中,运行相关的Kinesis数据生产者 79 | ``` 80 | bin/run-example streaming.KinesisWordCountProducerASL [Kinesis stream name] [endpoint URL] 1000 10 81 | ``` 82 | 这步将会每秒推送1000行,每行带有10个随机数字的数据到Kinesis流中,这些数据将会被运行的例子接收和处理 83 | 84 | ## Kinesis Checkpointing 85 | 86 | - 每一个Kinesis输入DStream定期的存储流的当前位置到后台的DynamoDB表中。这允许系统从错误中恢复,继续执行DStream留下的任务。 87 | - Checkpointing太频繁将会造成AWS检查点存储层过载,并且可能导致AWS节流(throttling)。提供的例子通过随机回退重试(random-backoff-retry)策略解决这个节流问题 88 | - 当输入DStream启动时,如果没有Kinesis checkpoint信息存在。它将会从最老的可用的记录(InitialPositionInStream.TRIM_HORIZON)或者最近的记录(InitialPostitionInStream.LATEST)启动。 89 | - 如果数据添加到流中的时候还没有输入DStream在运行,InitialPositionInStream.LATEST可能导致丢失记录。 90 | - InitialPositionInStream.TRIM_HORIZON可能导致记录的重复处理,这个错误的影响依赖于checkpoint的频率以及处理的幂等性。 91 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/linking.md: -------------------------------------------------------------------------------- 1 | # 关联 2 | 3 | 与Spark类似,Spark Streaming也可以利用maven仓库。编写你自己的Spark Streaming程序,你需要引入下面的依赖到你的SBT或者Maven项目中 4 | 5 | ```maven 6 | 7 | org.apache.spark 8 | spark-streaming_2.10 9 | 1.2 10 | 11 | ``` 12 | 为了从Kafka, Flume和Kinesis这些不在Spark核心API中提供的源获取数据,我们需要添加相关的模块`spark-streaming-xyz_2.10`到依赖中。例如,一些通用的组件如下表所示: 13 | 14 | Source | Artifact 15 | --- | --- 16 | Kafka | spark-streaming-kafka_2.10 17 | Flume | spark-streaming-flume_2.10 18 | Kinesis | spark-streaming-kinesis-asl_2.10 19 | Twitter | spark-streaming-twitter_2.10 20 | ZeroMQ | spark-streaming-zeromq_2.10 21 | MQTT | spark-streaming-mqtt_2.10 22 | 23 | 为了获取最新的列表,请访问[Apache repository](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.spark%22%20AND%20v%3A%221.2.0%22) 24 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/monitoring-applications.md: -------------------------------------------------------------------------------- 1 | # 监控应用程序 2 | 3 | 除了Spark的监控功能,Spark Streaming增加了一些专有的功能。应用StreamingContext的时候,[Spark web UI](https://spark.apache.org/docs/latest/monitoring.html#web-interfaces) 4 | 显示添加的`Streaming`菜单,用以显示运行的receivers(receivers是否是存活状态、接收的记录数、receiver错误等)和完成的批的统计信息(批处理时间、队列等待等待)。这可以用来监控 5 | 流应用程序的处理过程。 6 | 7 | 在WEB UI中的`Processing Time`和`Scheduling Delay`两个度量指标是非常重要的。第一个指标表示批数据处理的时间,第二个指标表示前面的批处理完毕之后,当前批在队列中的等待时间。如果 8 | 批处理时间比批间隔时间持续更长或者队列等待时间持续增加,这就预示系统无法以批数据产生的速度处理这些数据,整个处理过程滞后了。在这种情况下,考虑减少批处理时间。 9 | 10 | Spark Streaming程序的处理过程也可以通过[StreamingListener](https://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.scheduler.StreamingListener)接口来监控,这 11 | 个接口允许你获得receiver状态和处理时间。注意,这个接口是开发者API,它有可能在未来提供更多的信息。 -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/output-operations-on-DStreams.md: -------------------------------------------------------------------------------- 1 | # DStreams上的输出操作 2 | 3 | 输出操作允许DStream的操作推到如数据库、文件系统等外部系统中。因为输出操作实际上是允许外部系统消费转换后的数据,它们触发的实际操作是DStream转换。目前,定义了下面几种输出操作: 4 | 5 | Output Operation | Meaning 6 | --- | --- 7 | print() | 在DStream的每个批数据中打印前10条元素,这个操作在开发和调试中都非常有用。在Python API中调用`pprint()`。 8 | saveAsObjectFiles(prefix, [suffix]) | 保存DStream的内容为一个序列化的文件`SequenceFile`。每一个批间隔的文件的文件名基于`prefix`和`suffix`生成。"prefix-TIME_IN_MS[.suffix]",在Python API中不可用。 9 | saveAsTextFiles(prefix, [suffix]) | 保存DStream的内容为一个文本文件。每一个批间隔的文件的文件名基于`prefix`和`suffix`生成。"prefix-TIME_IN_MS[.suffix]" 10 | saveAsHadoopFiles(prefix, [suffix]) | 保存DStream的内容为一个hadoop文件。每一个批间隔的文件的文件名基于`prefix`和`suffix`生成。"prefix-TIME_IN_MS[.suffix]",在Python API中不可用。 11 | foreachRDD(func) | 在从流中生成的每个RDD上应用函数`func`的最通用的输出操作。这个函数应该推送每个RDD的数据到外部系统,例如保存RDD到文件或者通过网络写到数据库中。需要注意的是,`func`函数在驱动程序中执行,并且通常都有RDD action在里面推动RDD流的计算。 12 | 13 | ## 利用foreachRDD的设计模式 14 | 15 | dstream.foreachRDD是一个强大的原语,发送数据到外部系统中。然而,明白怎样正确地、有效地用这个原语是非常重要的。下面几点介绍了如何避免一般错误。 16 | - 经常写数据到外部系统需要建一个连接对象(例如到远程服务器的TCP连接),用它发送数据到远程系统。为了达到这个目的,开发人员可能不经意的在Spark驱动中创建一个连接对象,但是在Spark worker中 17 | 尝试调用这个连接对象保存记录到RDD中,如下: 18 | 19 | ```scala 20 | dstream.foreachRDD(rdd => { 21 | val connection = createNewConnection() // executed at the driver 22 | rdd.foreach(record => { 23 | connection.send(record) // executed at the worker 24 | }) 25 | }) 26 | ``` 27 | 28 | 这是不正确的,因为这需要先序列化连接对象,然后将它从driver发送到worker中。这样的连接对象在机器之间不能传送。它可能表现为序列化错误(连接对象不可序列化)或者初始化错误(连接对象应该 29 | 在worker中初始化)等等。正确的解决办法是在worker中创建连接对象。 30 | 31 | - 然而,这会造成另外一个常见的错误-为每一个记录创建了一个连接对象。例如: 32 | 33 | ``` 34 | dstream.foreachRDD(rdd => { 35 | rdd.foreach(record => { 36 | val connection = createNewConnection() 37 | connection.send(record) 38 | connection.close() 39 | }) 40 | }) 41 | ``` 42 | 43 | 通常,创建一个连接对象有资源和时间的开支。因此,为每个记录创建和销毁连接对象会导致非常高的开支,明显的减少系统的整体吞吐量。一个更好的解决办法是利用`rdd.foreachPartition`方法。 44 | 为RDD的partition创建一个连接对象,用这个两件对象发送partition中的所有记录。 45 | 46 | ``` 47 | dstream.foreachRDD(rdd => { 48 | rdd.foreachPartition(partitionOfRecords => { 49 | val connection = createNewConnection() 50 | partitionOfRecords.foreach(record => connection.send(record)) 51 | connection.close() 52 | }) 53 | }) 54 | ``` 55 | 这就将连接对象的创建开销分摊到了partition的所有记录上了。 56 | 57 | - 最后,可以通过在多个RDD或者批数据间重用连接对象做更进一步的优化。开发者可以保有一个静态的连接对象池,重复使用池中的对象将多批次的RDD推送到外部系统,以进一步节省开支。 58 | 59 | ``` 60 | dstream.foreachRDD(rdd => { 61 | rdd.foreachPartition(partitionOfRecords => { 62 | // ConnectionPool is a static, lazily initialized pool of connections 63 | val connection = ConnectionPool.getConnection() 64 | partitionOfRecords.foreach(record => connection.send(record)) 65 | ConnectionPool.returnConnection(connection) // return to the pool for future reuse 66 | }) 67 | }) 68 | ``` 69 | 70 | 需要注意的是,池中的连接对象应该根据需要延迟创建,并且在空闲一段时间后自动超时。这样就获取了最有效的方式发生数据到外部系统。 71 | 72 | 其它需要注意的地方: 73 | 74 | - 输出操作通过懒执行的方式操作DStreams,正如RDD action通过懒执行的方式操作RDD。具体地看,RDD actions和DStreams输出操作接收数据的处理。因此,如果你的应用程序没有任何输出操作或者 75 | 用于输出操作`dstream.foreachRDD()`,但是没有任何RDD action操作在`dstream.foreachRDD()`里面,那么什么也不会执行。系统仅仅会接收输入,然后丢弃它们。 76 | - 默认情况下,DStreams输出操作是分时执行的,它们按照应用程序的定义顺序按序执行。 77 | 78 | 79 | -------------------------------------------------------------------------------- /spark-streaming/basic-concepts/transformations-on-DStreams.md: -------------------------------------------------------------------------------- 1 | # DStream中的转换(transformation) 2 | 3 | 和RDD类似,transformation允许从输入DStream来的数据被修改。DStreams支持很多在RDD中可用的transformation算子。一些常用的算子如下所示: 4 | 5 | Transformation | Meaning 6 | --- | --- 7 | map(func) | 利用函数`func`处理原DStream的每个元素,返回一个新的DStream 8 | flatMap(func) | 与map相似,但是每个输入项可用被映射为0个或者多个输出项 9 | filter(func) | 返回一个新的DStream,它仅仅包含源DStream中满足函数func的项 10 | repartition(numPartitions) | 通过创建更多或者更少的partition改变这个DStream的并行级别(level of parallelism) 11 | union(otherStream) | 返回一个新的DStream,它包含源DStream和otherStream的联合元素 12 | count() | 通过计算源DStream中每个RDD的元素数量,返回一个包含单元素(single-element)RDDs的新DStream 13 | reduce(func) | 利用函数func聚集源DStream中每个RDD的元素,返回一个包含单元素(single-element)RDDs的新DStream。函数应该是相关联的,以使计算可以并行化 14 | countByValue() | 这个算子应用于元素类型为K的DStream上,返回一个(K,long)对的新DStream,每个键的值是在原DStream的每个RDD中的频率。 15 | reduceByKey(func, [numTasks]) | 当在一个由(K,V)对组成的DStream上调用这个算子,返回一个新的由(K,V)对组成的DStream,每一个key的值均由给定的reduce函数聚集起来。注意:在默认情况下,这个算子利用了Spark默认的并发任务数去分组。你可以用`numTasks`参数设置不同的任务数 16 | join(otherStream, [numTasks]) | 当应用于两个DStream(一个包含(K,V)对,一个包含(K,W)对),返回一个包含(K, (V, W))对的新DStream 17 | cogroup(otherStream, [numTasks]) | 当应用于两个DStream(一个包含(K,V)对,一个包含(K,W)对),返回一个包含(K, Seq[V], Seq[W])的元组 18 | transform(func) | 通过对源DStream的每个RDD应用RDD-to-RDD函数,创建一个新的DStream。这个可以在DStream中的任何RDD操作中使用 19 | updateStateByKey(func) | 利用给定的函数更新DStream的状态,返回一个新"state"的DStream。 20 | 21 | 最后两个transformation算子需要重点介绍一下: 22 | 23 | ## UpdateStateByKey操作 24 | 25 | 26 | updateStateByKey操作允许不断用新信息更新它的同时保持任意状态。你需要通过两步来使用它 27 | 28 | - 定义状态-状态可以是任何的数据类型 29 | - 定义状态更新函数-怎样利用更新前的状态和从输入流里面获取的新值更新状态 30 | 31 | 让我们举个例子说明。在例子中,你想保持一个文本数据流中每个单词的运行次数,运行次数用一个state表示,它的类型是整数 32 | 33 | ```scala 34 | def updateFunction(newValues: Seq[Int], runningCount: Option[Int]): Option[Int] = { 35 | val newCount = ... // add the new values with the previous running count to get the new count 36 | Some(newCount) 37 | } 38 | ``` 39 | 40 | 这个函数被用到了DStream包含的单词上 41 | 42 | ```scala 43 | import org.apache.spark._ 44 | import org.apache.spark.streaming._ 45 | import org.apache.spark.streaming.StreamingContext._ 46 | // Create a local StreamingContext with two working thread and batch interval of 1 second 47 | val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount") 48 | val ssc = new StreamingContext(conf, Seconds(1)) 49 | // Create a DStream that will connect to hostname:port, like localhost:9999 50 | val lines = ssc.socketTextStream("localhost", 9999) 51 | // Split each line into words 52 | val words = lines.flatMap(_.split(" ")) 53 | // Count each word in each batch 54 | val pairs = words.map(word => (word, 1)) 55 | val runningCounts = pairs.updateStateByKey[Int](updateFunction _) 56 | ``` 57 | 58 | 更新函数将会被每个单词调用,`newValues`拥有一系列的1(从 (词, 1)对而来),runningCount拥有之前的次数。要看完整的代码,见[例子](https://github.com/apache/spark/blob/master/examples/src/main/scala/org/apache/spark/examples/streaming/StatefulNetworkWordCount.scala) 59 | 60 | ## Transform操作 61 | 62 | `transform`操作(以及它的变化形式如`transformWith`)允许在DStream运行任何RDD-to-RDD函数。它能够被用来应用任何没在DStream API中提供的RDD操作(It can be used to apply any RDD operation that is not exposed in the DStream API)。 63 | 例如,连接数据流中的每个批(batch)和另外一个数据集的功能并没有在DStream API中提供,然而你可以简单的利用`transform`方法做到。如果你想通过连接带有预先计算的垃圾邮件信息的输入数据流 64 | 来清理实时数据,然后过了它们,你可以按如下方法来做: 65 | 66 | ```scala 67 | val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...) // RDD containing spam information 68 | 69 | val cleanedDStream = wordCounts.transform(rdd => { 70 | rdd.join(spamInfoRDD).filter(...) // join data stream with spam information to do data cleaning 71 | ... 72 | }) 73 | ``` 74 | 75 | 事实上,你也可以在`transform`方法中用[机器学习](https://spark.apache.org/docs/latest/mllib-guide.html)和[图计算](https://spark.apache.org/docs/latest/graphx-programming-guide.html)算法 76 | 77 | ## 窗口(window)操作 78 | 79 | Spark Streaming也支持窗口计算,它允许你在一个滑动窗口数据上应用transformation算子。下图阐明了这个滑动窗口。 80 | 81 | ![滑动窗口](../../img/streaming-dstream-window.png) 82 | 83 | 如上图显示,窗口在源DStream上滑动,合并和操作落入窗内的源RDDs,产生窗口化的DStream的RDDs。在这个具体的例子中,程序在三个时间单元的数据上进行窗口操作,并且每两个时间单元滑动一次。 84 | 这说明,任何一个窗口操作都需要指定两个参数: 85 | 86 | - 窗口长度:窗口的持续时间 87 | - 滑动的时间间隔:窗口操作执行的时间间隔 88 | 89 | 这两个参数必须是源DStream的批时间间隔的倍数。 90 | 91 | 下面举例说明窗口操作。例如,你想扩展前面的[例子](../a-quick-example.md)用来计算过去30秒的词频,间隔时间是10秒。为了达到这个目的,我们必须在过去30秒的`pairs` DStream上应用`reduceByKey` 92 | 操作。用方法`reduceByKeyAndWindow`实现。 93 | 94 | ```scala 95 | // Reduce last 30 seconds of data, every 10 seconds 96 | val windowedWordCounts = pairs.reduceByKeyAndWindow((a:Int,b:Int) => (a + b), Seconds(30), Seconds(10)) 97 | ``` 98 | 99 | 一些常用的窗口操作如下所示,这些操作都需要用到上文提到的两个参数:窗口长度和滑动的时间间隔 100 | 101 | Transformation | Meaning 102 | --- | --- 103 | window(windowLength, slideInterval) | 基于源DStream产生的窗口化的批数据计算一个新的DStream 104 | countByWindow(windowLength, slideInterval) | 返回流中元素的一个滑动窗口数 105 | reduceByWindow(func, windowLength, slideInterval) | 返回一个单元素流。利用函数func聚集滑动时间间隔的流的元素创建这个单元素流。函数必须是相关联的以使计算能够正确的并行计算。 106 | reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]) | 应用到一个(K,V)对组成的DStream上,返回一个由(K,V)对组成的新的DStream。每一个key的值均由给定的reduce函数聚集起来。注意:在默认情况下,这个算子利用了Spark默认的并发任务数去分组。你可以用`numTasks`参数设置不同的任务数 107 | reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]) | A more efficient version of the above reduceByKeyAndWindow() where the reduce value of each window is calculated incrementally using the reduce values of the previous window. This is done by reducing the new data that enter the sliding window, and "inverse reducing" the old data that leave the window. An example would be that of "adding" and "subtracting" counts of keys as the window slides. However, it is applicable to only "invertible reduce functions", that is, those reduce functions which have a corresponding "inverse reduce" function (taken as parameter invFunc. Like in reduceByKeyAndWindow, the number of reduce tasks is configurable through an optional argument. 108 | countByValueAndWindow(windowLength, slideInterval, [numTasks]) | 应用到一个(K,V)对组成的DStream上,返回一个由(K,V)对组成的新的DStream。每个key的值都是它们在滑动窗口中出现的频率。 -------------------------------------------------------------------------------- /spark-streaming/fault-tolerance-semantics/README.md: -------------------------------------------------------------------------------- 1 | # 容错语义 2 | 3 | 这一节,我们将讨论在节点错误事件时Spark Streaming的行为。为了理解这些,让我们先记住一些Spark RDD的基本容错语义。 4 | 5 | - 一个RDD是不可变的、确定可重复计算的、分布式数据集。每个RDD记住一个确定性操作的谱系(lineage),这个谱系用在容错的输入数据集上来创建该RDD。 6 | - 如果任何一个RDD的分区因为节点故障而丢失,这个分区可以通过操作谱系从源容错的数据集中重新计算得到。 7 | - 假定所有的RDD transformations是确定的,那么最终转换的数据是一样的,不论Spark机器中发生何种错误。 8 | 9 | Spark运行在像HDFS或S3等容错系统的数据上。因此,任何从容错数据而来的RDD都是容错的。然而,这不是在Spark Streaming的情况下,因为Spark Streaming的数据大部分情况下是从 10 | 网络中得到的。为了获得生成的RDD相同的容错属性,接收的数据需要重复保存在worker node的多个Spark executor上(默认的复制因子是2),这导致了当出现错误事件时,有两类数据需要被恢复 11 | 12 | - Data received and replicated :在单个worker节点的故障中,这个数据会幸存下来,因为有另外一个节点保存有这个数据的副本。 13 | - Data received but buffered for replication:因为没有重复保存,所以为了恢复数据,唯一的办法是从源中重新读取数据。 14 | 15 | 有两种错误我们需要关心 16 | 17 | - worker节点故障:任何运行executor的worker节点都有可能出故障,那样在这个节点中的所有内存数据都会丢失。如果有任何receiver运行在错误节点,它们的缓存数据将会丢失 18 | - Driver节点故障:如果运行Spark Streaming应用程序的Driver节点出现故障,很明显SparkContext将会丢失,所有执行在其上的executors也会丢失。 19 | 20 | ## 作为输入源的文件语义(Semantics with files as input source) 21 | 22 | 如果所有的输入数据都存在于一个容错的文件系统如HDFS,Spark Streaming总可以从任何错误中恢复并且执行所有数据。这给出了一个恰好一次(exactly-once)语义,即无论发生什么故障, 23 | 所有的数据都将会恰好处理一次。 24 | 25 | ## 基于receiver的输入源语义 26 | 27 | 对于基于receiver的输入源,容错的语义既依赖于故障的情形也依赖于receiver的类型。正如之前讨论的,有两种类型的receiver 28 | 29 | - Reliable Receiver:这些receivers只有在确保数据复制之后才会告知可靠源。如果这样一个receiver失败了,缓冲(非复制)数据不会被源所承认。如果receiver重启,源会重发数 30 | 据,因此不会丢失数据。 31 | - Unreliable Receiver:当worker或者driver节点故障,这种receiver会丢失数据 32 | 33 | 选择哪种类型的receiver依赖于这些语义。如果一个worker节点出现故障,Reliable Receiver不会丢失数据,Unreliable Receiver会丢失接收了但是没有复制的数据。如果driver节点 34 | 出现故障,除了以上情况下的数据丢失,所有过去接收并复制到内存中的数据都会丢失,这会影响有状态transformation的结果。 35 | 36 | 为了避免丢失过去接收的数据,Spark 1.2引入了一个实验性的特征`write ahead logs`,它保存接收的数据到容错存储系统中。有了`write ahead logs`和Reliable Receiver,我们可以 37 | 做到零数据丢失以及exactly-once语义。 38 | 39 | 下面的表格总结了错误语义: 40 | 41 | Deployment Scenario | Worker Failure | Driver Failure 42 | --- | --- | --- 43 | Spark 1.1 或者更早, 没有write ahead log的Spark 1.2 | 在Unreliable Receiver情况下缓冲数据丢失;在Reliable Receiver和文件的情况下,零数据丢失 | 在Unreliable Receiver情况下缓冲数据丢失;在所有receiver情况下,过去的数据丢失;在文件的情况下,零数据丢失 44 | 带有write ahead log的Spark 1.2 | 在Reliable Receiver和文件的情况下,零数据丢失 | 在Reliable Receiver和文件的情况下,零数据丢失 45 | 46 | ## 输出操作的语义 47 | 48 | 根据其确定操作的谱系,所有数据都被建模成了RDD,所有的重新计算都会产生同样的结果。所有的DStream transformation都有exactly-once语义。那就是说,即使某个worker节点出现 49 | 故障,最终的转换结果都是一样。然而,输出操作(如`foreachRDD`)具有`at-least once`语义,那就是说,在有worker事件故障的情况下,变换后的数据可能被写入到一个外部实体不止一次。 50 | 利用`saveAs***Files`将数据保存到HDFS中的情况下,以上写多次是能够被接受的(因为文件会被相同的数据覆盖)。 -------------------------------------------------------------------------------- /spark-streaming/performance-tuning/README.md: -------------------------------------------------------------------------------- 1 | # 性能调优 2 | 3 | 集群中的Spark Streaming应用程序获得最好的性能需要一些调整。这章将介绍几个参数和配置,提高Spark Streaming应用程序的性能。你需要考虑两件事情: 4 | 5 | - 高效地利用集群资源减少批数据的处理时间 6 | - 设置正确的批容量(size),使数据的处理速度能够赶上数据的接收速度 7 | 8 | * [减少批数据的执行时间](reducing-processing-time.md) 9 | * [设置正确的批容量](setting-right-batch-size.md) 10 | * [内存调优](memory-tuning.md) -------------------------------------------------------------------------------- /spark-streaming/performance-tuning/memory-tuning.md: -------------------------------------------------------------------------------- 1 | # 内存调优 2 | 3 | 调整内存的使用以及Spark应用程序的垃圾回收行为已经在[Spark优化指南](../../other/tuning-spark.md)中详细介绍。在这一节,我们重点介绍几个强烈推荐的自定义选项,它们可以 4 | 减少Spark Streaming应用程序垃圾回收的相关暂停,获得更稳定的批处理时间。 5 | 6 | - Default persistence level of DStreams:和RDDs不同的是,默认的持久化级别是序列化数据到内存中(DStream是`StorageLevel.MEMORY_ONLY_SER`,RDD是` StorageLevel.MEMORY_ONLY`)。 7 | 即使保存数据为序列化形态会增加序列化/反序列化的开销,但是可以明显的减少垃圾回收的暂停。 8 | - Clearing persistent RDDs:默认情况下,通过Spark内置策略(LUR),Spark Streaming生成的持久化RDD将会从内存中清理掉。如果spark.cleaner.ttl已经设置了,比这个时间存在更老的持久化 9 | RDD将会被定时的清理掉。正如前面提到的那样,这个值需要根据Spark Streaming应用程序的操作小心设置。然而,可以设置配置选项`spark.streaming.unpersist`为true来更智能的去持久化(unpersist)RDD。这个 10 | 配置使系统找出那些不需要经常保有的RDD,然后去持久化它们。这可以减少Spark RDD的内存使用,也可能改善垃圾回收的行为。 11 | - Concurrent garbage collector:使用并发的标记-清除垃圾回收可以进一步减少垃圾回收的暂停时间。尽管并发的垃圾回收会减少系统的整体吞吐量,但是仍然推荐使用它以获得更稳定的批处理时间。 -------------------------------------------------------------------------------- /spark-streaming/performance-tuning/reducing-processing-time.md: -------------------------------------------------------------------------------- 1 | # 减少批数据的执行时间 2 | 3 | 在Spark中有几个优化可以减少批处理的时间。这些可以在[优化指南](../../other/tuning-spark.md)中作了讨论。这节重点讨论几个重要的。 4 | 5 | ## 数据接收的并行水平 6 | 7 | 通过网络(如kafka,flume,socket等)接收数据需要这些数据反序列化并被保存到Spark中。如果数据接收成为系统的瓶颈,就要考虑并行地接收数据。注意,每个输入DStream创建一个`receiver`(运行在worker机器上) 8 | 接收单个数据流。创建多个输入DStream并配置它们可以从源中接收不同分区的数据流,从而实现多数据流接收。例如,接收两个topic数据的单个输入DStream可以被切分为两个kafka输入流,每个接收一个topic。这将 9 | 在两个worker上运行两个`receiver`,因此允许数据并行接收,提高整体的吞吐量。多个DStream可以被合并生成单个DStream,这样运用在单个输入DStream的transformation操作可以运用在合并的DStream上。 10 | 11 | ```scala 12 | val numStreams = 5 13 | val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) } 14 | val unifiedStream = streamingContext.union(kafkaStreams) 15 | unifiedStream.print() 16 | ``` 17 | 18 | 另外一个需要考虑的参数是`receiver`的阻塞时间。对于大部分的`receiver`,在存入Spark内存之前,接收的数据都被合并成了一个大数据块。每批数据中块的个数决定了任务的个数。这些任务是用类 19 | 似map的transformation操作接收的数据。阻塞间隔由配置参数`spark.streaming.blockInterval`决定,默认的值是200毫秒。 20 | 21 | 多输入流或者多`receiver`的可选的方法是明确地重新分配输入数据流(利用`inputStream.repartition()`),在进一步操作之前,通过集群的机器数分配接收的批数据。 22 | 23 | ## 数据处理的并行水平 24 | 25 | 如果运行在计算stage上的并发任务数不足够大,就不会充分利用集群的资源。例如,对于分布式reduce操作如`reduceByKey`和`reduceByKeyAndWindow`,默认的并发任务数通过配置属性来确定(configuration.html#spark-properties) 26 | `spark.default.parallelism`。你可以通过参数(`PairDStreamFunctions` (api/scala/index.html#org.apache.spark.streaming.dstream.PairDStreamFunctions))传递并行度,或者设置参数 27 | `spark.default.parallelism`修改默认值。 28 | 29 | ## 数据序列化 30 | 31 | 数据序列化的总开销是平常大的,特别是当sub-second级的批数据被接收时。下面有两个相关点: 32 | 33 | - Spark中RDD数据的序列化。关于数据序列化请参照[Spark优化指南](../../other/tuning-spark.md)。注意,与Spark不同的是,默认的RDD会被持久化为序列化的字节数组,以减少与垃圾回收相关的暂停。 34 | - 输入数据的序列化。从外部获取数据存到Spark中,获取的byte数据需要从byte反序列化,然后再按照Spark的序列化格式重新序列化到Spark中。因此,输入数据的反序列化花费可能是一个瓶颈。 35 | 36 | ## 任务的启动开支 37 | 38 | 每秒钟启动的任务数是非常大的(50或者更多)。发送任务到slave的花费明显,这使请求很难获得亚秒(sub-second)级别的反应。通过下面的改变可以减小开支 39 | 40 | - 任务序列化。运行kyro序列化任何可以减小任务的大小,从而减小任务发送到slave的时间。 41 | - 执行模式。在Standalone模式下或者粗粒度的Mesos模式下运行Spark可以在比细粒度Mesos模式下运行Spark获得更短的任务启动时间。可以在[在Mesos下运行Spark](../../deploying/running-spark-on-mesos.md)中获取更多信息。 42 | 43 | These changes may reduce batch processing time by 100s of milliseconds, thus allowing sub-second batch size to be viable. 44 | 45 | -------------------------------------------------------------------------------- /spark-streaming/performance-tuning/setting-right-batch-size.md: -------------------------------------------------------------------------------- 1 | # 设置正确的批容量 2 | 3 | 为了Spark Streaming应用程序能够在集群中稳定运行,系统应该能够以足够的速度处理接收的数据(即处理速度应该大于或等于接收数据的速度)。这可以通过流的网络UI观察得到。批处理时间应该小于批间隔时间。 4 | 5 | 根据流计算的性质,批间隔时间可能显著的影响数据处理速率,这个速率可以通过应用程序维持。可以考虑`WordCountNetwork`这个例子,对于一个特定的数据处理速率,系统可能可以每2秒打印一次单词计数 6 | (批间隔时间为2秒),但无法每500毫秒打印一次单词计数。所以,为了在生产环境中维持期望的数据处理速率,就应该设置合适的批间隔时间(即批数据的容量)。 7 | 8 | 找出正确的批容量的一个好的办法是用一个保守的批间隔时间(5-10,秒)和低数据速率来测试你的应用程序。为了验证你的系统是否能满足数据处理速率,你可以通过检查端到端的延迟值来判断(可以在 9 | Spark驱动程序的log4j日志中查看"Total delay"或者利用StreamingListener接口)。如果延迟维持稳定,那么系统是稳定的。如果延迟持续增长,那么系统无法跟上数据处理速率,是不稳定的。 10 | 你能够尝试着增加数据处理速率或者减少批容量来作进一步的测试。注意,因为瞬间的数据处理速度增加导致延迟瞬间的增长可能是正常的,只要延迟能重新回到了低值(小于批容量)。 11 | 12 | --------------------------------------------------------------------------------