{"id":1687,"date":"2019-05-09T02:52:09","date_gmt":"2019-05-09T02:52:09","guid":{"rendered":"http:\/\/kusuaks7\/?p=1292"},"modified":"2023-07-11T11:02:22","modified_gmt":"2023-07-11T11:02:22","slug":"high-level-overview-of-apache-spark","status":"publish","type":"post","link":"https:\/\/www.experfy.com\/blog\/bigdata-cloud\/high-level-overview-of-apache-spark\/","title":{"rendered":"High Level Overview of Apache Spark"},"content":{"rendered":"<h3 id=\"17b8\" name=\"17b8\" style=\"color:#aaa;font-style:italic;\">What is Spark? Let&rsquo;s take a look under the&nbsp;hood<\/h3>\n<section name=\"c44f\">\n<p id=\"97b9\" name=\"97b9\">In my <a href=\"https:\/\/www.experfy.com\/blog\/why-we-need-apache-spark\">previous post<\/a>, we introduced a problem: copious, never ending streams of data, and it&rsquo;s solution: Apache Spark. Here in Part II, we&rsquo;ll focus on Spark&rsquo;s internal architecture and data structures.<\/p>\n<\/section>\n<section name=\"27e8\">\n<hr \/>\n<blockquote id=\"2a8e\" name=\"2a8e\"><p><em>In pioneer days they used oxen for heavy pulling, and when one ox couldn&rsquo;t budge a log, they didn&rsquo;t try to grow a larger ox. We shouldn&rsquo;t be trying for bigger computers, but for more systems of computers\u200a&mdash;\u200aGrace&nbsp;Hopper<\/em><\/p><\/blockquote>\n<p id=\"9ecf\" name=\"9ecf\">With the scale of data growing at a rapid and ominous pace, we needed a way to process potential petabytes of data quickly, and we simply couldn&rsquo;t make a single computer process that amount of data at a reasonable pace. This problem is solved by creating a cluster of machines to perform the work for you, but how do those machines work together to solve the common problem?<\/p>\n<h3 id=\"43df\" name=\"43df\"><strong>Meet Spark<\/strong><\/h3>\n<figure id=\"c780\" name=\"c780\">\n<p><canvas height=\"50\" width=\"75\"><\/canvas><img decoding=\"async\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/0*HwnSXuF99iFBtNYT\" src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/0*HwnSXuF99iFBtNYT\" \/><\/p>\n<\/figure>\n<p name=\"8b64\" style=\"text-align: center;\">Photo by&nbsp;<a data-href=\"https:\/\/unsplash.com\/@jeztimms?utm_source=medium&amp;utm_medium=referral\" href=\"https:\/\/unsplash.com\/@jeztimms?utm_source=medium&amp;utm_medium=referral\" rel=\"photo-creator noopener noreferrer\" target=\"_blank\">Jez Timms<\/a>&nbsp;on&nbsp;<a data-href=\"https:\/\/unsplash.com?utm_source=medium&amp;utm_medium=referral\" href=\"https:\/\/unsplash.com\/?utm_source=medium&amp;utm_medium=referral\" rel=\"photo-source noopener noreferrer\" target=\"_blank\">Unsplash<\/a><\/p>\n<p id=\"8b64\" name=\"8b64\">Spark is the cluster computing framework for large-scale data processing. Spark offers a set of libraries in 3 languages (Java, Scala, Python) for its unified computing engine. What does this definition actually mean?<\/p>\n<p id=\"6df3\" name=\"6df3\"><code>Unified<\/code>: With Spark, there is no need to piece together an application out of multiple APIs or systems. Spark provides you with enough built-in APIs to get the job done<\/p>\n<p id=\"dba6\" name=\"dba6\"><code>Computing Engine:<\/code>&nbsp;Spark handles loading data from various file systems and runs computations on it, but does not store any data itself permanently. Spark operates entirely in memory\u200a&mdash;\u200aallowing unparalleled performance and speed<\/p>\n<p id=\"ffcf\" name=\"ffcf\"><code>Libraries:<\/code>&nbsp;Spark is comprised of a series of libraries built for data science tasks. Spark includes libraries for SQL (SparkSQL), Machine Learning (MLlib), Stream Processing (Spark Streaming and Structured Streaming), and Graph Analytics (GraphX)<\/p>\n<\/section>\n<section name=\"7d62\">\n<hr \/>\n<h3 id=\"292a\" name=\"292a\"><strong>The Spark Application<\/strong><\/h3>\n<p id=\"d8c7\" name=\"d8c7\">Every Spark Application consists of a&nbsp;<strong>Driver<\/strong>&nbsp;and a set of distributed worker processes (<strong>Executors<\/strong>).<\/p>\n<h3 id=\"620d\" name=\"620d\"><strong>Spark Driver<\/strong><\/h3>\n<p id=\"092f\" name=\"092f\">The Driver runs the main() method of our application and is where the SparkContext is created. The Spark Driver has the following duties:<\/p>\n<ul>\n<li id=\"4417\" name=\"4417\">Runs on a node in our cluster, or on a client, and schedules the job execution with a cluster manager<\/li>\n<li id=\"9b9a\" name=\"9b9a\">Responds to user&rsquo;s program or input<\/li>\n<li id=\"fe18\" name=\"fe18\">Analyzes, schedules, and distributes work across the exectors<\/li>\n<li id=\"5011\" name=\"5011\">Stores metadata about the running application and conveniently exposes it in a webUI<\/li>\n<\/ul>\n<h3 id=\"ba5b\" name=\"ba5b\"><strong>Spark Executors<\/strong><\/h3>\n<p id=\"ace0\" name=\"ace0\">An executor is a distributed process responsible for the execution of tasks. Each Spark Application has its own set of executors, which stay alive for the life cycle of a single Spark application.<\/p>\n<ul>\n<li id=\"3b20\" name=\"3b20\">Executors perform all data processing of a Spark job<\/li>\n<li id=\"724a\" name=\"724a\">Stores results in memory, only persisting to disk when specifically instructed by the driver program<\/li>\n<li id=\"5d34\" name=\"5d34\">Returns results to the driver once they have been completed<\/li>\n<li id=\"588a\" name=\"588a\">Each node can have anywhere from 1 executor per node to 1 executor per core<\/li>\n<\/ul>\n<figure id=\"c704\" name=\"c704\">\n<p><canvas height=\"30\" width=\"75\"><\/canvas><img decoding=\"async\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/1*GZG2aogNS8Jg14jOM2rjmQ.png\" src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/1*GZG2aogNS8Jg14jOM2rjmQ.png\" \/><\/p>\n<\/figure>\n<h3 id=\"79b8\" name=\"79b8\"><strong>Spark&rsquo;s Application Workflow<\/strong><\/h3>\n<p id=\"0286\" name=\"0286\">When you submit a job to Spark for processing, there is a lot that goes on behind the scenes.<\/p>\n<ol>\n<li id=\"000a\" name=\"000a\">Our Standalone Application is kicked off, and initializes its SparkContext. Only after having a SparkContext can an app be referred to as a Driver<\/li>\n<li id=\"e39d\" name=\"e39d\">Our Driver program asks the Cluster Manager for resources to launch its executors<\/li>\n<li id=\"4a98\" name=\"4a98\">The Cluster Manager launches the executors<\/li>\n<li id=\"5809\" name=\"5809\">Our Driver runs our actual Spark code<\/li>\n<li id=\"0a3d\" name=\"0a3d\">Executors run tasks and send their results back to the driver<\/li>\n<li id=\"8435\" name=\"8435\">SparkContext is stopped and all executors are shut down, returning resources back to the cluster<\/li>\n<\/ol>\n<\/section>\n<section name=\"d172\">\n<hr \/>\n<h3 id=\"62af\" name=\"62af\"><strong>MaxTemperature, Revisited<\/strong><\/h3>\n<p id=\"710f\" name=\"710f\">Lets take a deeper look at the Spark Job we wrote in&nbsp;<a data-href=\"https:\/\/hackernoon.com\/why-we-need-apache-spark-51c8a57aa57a\" href=\"https:\/\/hackernoon.com\/why-we-need-apache-spark-51c8a57aa57a\" target=\"_blank\" rel=\"noopener noreferrer\">Part I<\/a>&nbsp;to find max temperature by country. This abstraction hid a lot of set-up code, including the initialization of our SparkContext, lets fill in the gaps:<\/p>\n<figure id=\"7c1c\" name=\"7c1c\">\n<p><canvas height=\"38\" width=\"75\"><\/canvas><img decoding=\"async\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/1*8COf1mt_AxIt66ft83z-yg.png\" src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/1*8COf1mt_AxIt66ft83z-yg.png\" \/><\/p>\n<\/figure>\n<p name=\"51ff\" style=\"text-align: center;\">MaxTemperature Spark&nbsp;Setup<\/p>\n<p id=\"51ff\" name=\"51ff\">Remember that Spark is a framework, in this case, implemented in Java. It isn&rsquo;t until line 16 that Spark needs to do any work at all. Sure, we initialized our SparkContext, however, loading data into an RDD is the first bit of code that requires work be sent to our executors.<\/p>\n<p id=\"4a69\" name=\"4a69\">By now you may have seen the term &ldquo;RDD&rdquo; appear multiple times, it&rsquo;s about time we define it.<\/p>\n<\/section>\n<section name=\"70fb\">\n<hr \/>\n<h3 id=\"f0ea\" name=\"f0ea\"><strong>Spark Architecture Overview<\/strong><\/h3>\n<p id=\"4d7f\" name=\"4d7f\">Spark has a well-defined layered architecture with loosely coupled components based on two primary abstractions:<\/p>\n<ul>\n<li id=\"be6e\" name=\"be6e\">Resilient Distributed Datasets (RDDs)<\/li>\n<li id=\"31ee\" name=\"31ee\">Directed Acyclic Graph (DAG)<\/li>\n<\/ul>\n<h3 id=\"2b2b\" name=\"2b2b\"><strong>Resilient Distributed Datasets<\/strong><\/h3>\n<p id=\"d23d\" name=\"d23d\">RDDs are essentially the building blocks of Spark: everything is comprised of them. Even Sparks higher-level APIs (DataFrames, Datasets) are composed of RDDs under the hood. What does it mean to be a Resilient Distributed Dataset?<\/p>\n<ul>\n<li id=\"fecb\" name=\"fecb\"><code>Resilient:<\/code>&nbsp;Since Spark runs on a cluster of machines, data-loss from hardware failure is a very real concern, so RDDs are fault tolerant and can rebuild themselves in the event of failure<\/li>\n<li id=\"1b81\" name=\"1b81\"><code>Distributed:<\/code>&nbsp;A single RDD is stored on a series of different nodes in the cluster, belonging to no single source (and no single point of failure). This way our cluster can operate on our RDD in parallel<\/li>\n<li id=\"a80c\" name=\"a80c\"><code>Dataset:<\/code>&nbsp;A collection of values\u200a<em>&mdash;\u200athis one you should probably have known already<\/em><\/li>\n<\/ul>\n<p id=\"0189\" name=\"0189\">All data we work within Spark will be stored inside some form of RDD\u200a&mdash;\u200ait is, therefore, imperative to fully understand them.<\/p>\n<p id=\"c6d3\" name=\"c6d3\">Spark offers a slew of &ldquo;Higher Level&rdquo; APIs built on top of RDDs designed to abstract away complexity, namely the DataFrame and Dataset. With a strong focus on Read-Evaluate-Print-Loops (REPLs), Spark-Submit and the Spark-Shell in Scala and Python are targeted toward Data Scientists, who often desire repeat analysis on a dataset. The RDD is still imperative to understand, as it&rsquo;s the underlying structure of all data in Spark.<\/p>\n<p id=\"210f\" name=\"210f\">An RDD is colloquially equivalent to: &ldquo;Distributed Data Structure&rdquo;. A&nbsp;<code>JavaRDD&lt;String&gt;<\/code>&nbsp;is essentially just a&nbsp;<code>List&lt;String&gt;<\/code>&nbsp;dispersed amongst each node in our cluster, with each node getting several different chunks of our List. With Spark, we need to think in a distributed context, always.<\/p>\n<p id=\"5607\" name=\"5607\">RDDs work by splitting up their data into a series of partitions to be stored on each executor node. Each node will then perform its work only on its own partitions. This is what makes Spark so powerful: If an executor dies or a task fails Spark can rebuild just the partitions it needs from the original source and re-submit the task for completion.<\/p>\n<figure id=\"5c0d\" name=\"5c0d\">\n<p><canvas height=\"27\" width=\"75\"><\/canvas><img decoding=\"async\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/1*7aXGUcCy3qHD3U66COrTTA.png\" src=\"https:\/\/cdn-images-1.medium.com\/max\/720\/1*7aXGUcCy3qHD3U66COrTTA.png\" \/><\/p>\n<\/figure>\n<p name=\"8bba\" style=\"text-align: center;\">Spark RDD partitioned amongst executors<\/p>\n<h3 id=\"8bba\" name=\"8bba\"><strong>RDD Operations<\/strong><\/h3>\n<p id=\"49d4\" name=\"49d4\">RDDs are&nbsp;<code>Immutable<\/code>, meaning that once they are created, they cannot be altered in any way, they can only be&nbsp;<code>transformed<\/code>. The notion of transforming RDDs is at the core of Spark, and Spark Jobs can be thought of as nothing more than any combination of these steps:<\/p>\n<ul>\n<li id=\"ef53\" name=\"ef53\">Loading data into an RDD<\/li>\n<li id=\"b42a\" name=\"b42a\"><code>Transforming<\/code>&nbsp;an RDD<\/li>\n<li id=\"5b9d\" name=\"5b9d\">Performing an&nbsp;<code>Action<\/code>&nbsp;on an RDD<\/li>\n<\/ul>\n<p id=\"dda6\" name=\"dda6\">In fact, every Spark job I&rsquo;ve written is comprised of exclusively those types of tasks, with vanilla Java for flavour.<\/p>\n<p id=\"9e3f\" name=\"9e3f\">Spark defines a set of APIs for working with RDDs that can be broken down into two large groups:&nbsp;<strong>Transformations<\/strong>&nbsp;and&nbsp;<strong>Actions<\/strong>.<\/p>\n<p id=\"eedf\" name=\"eedf\">Transformations create a new RDD from an existing one.<\/p>\n<p id=\"53fa\" name=\"53fa\">Actions return a value, or values, to the Driver program after running a computation on its RDD.<\/p>\n<p id=\"9d12\" name=\"9d12\">For example, the&nbsp;<em>map<\/em>&nbsp;function weatherData.map() is a transformation that passes each element of an RDD through a function.<\/p>\n<p id=\"c47b\" name=\"c47b\"><em>Reduce<\/em>&nbsp;is an RDD action that aggregates all the elements of an RDD using some function and returns the final result to the driver program.<\/p>\n<\/section>\n<section name=\"0196\">\n<hr \/>\n<h3 id=\"5630\" name=\"5630\"><strong>Lazy Evaluation<\/strong><\/h3>\n<blockquote id=\"ad74\" name=\"ad74\"><p>&ldquo;I choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it.\u200a&mdash;\u200aBill Gates&rdquo;<\/p><\/blockquote>\n<p id=\"dda9\" name=\"dda9\">All transformations in Spark are&nbsp;<code>lazy<\/code>. This means that when we tell Spark to create an RDD via transformations of an existing RDD, it won&rsquo;t generate that dataset until a specific action is performed on it or one of its children. Spark will then perform the transformation and the action that triggered it. This allows Spark to run much more efficiently.<\/p>\n<p id=\"4bf8\" name=\"4bf8\">Let&rsquo;s re-examine the function declarations from our earlier Spark example to identify which functions are actions and which are transformations:<\/p>\n<pre id=\"6f97\" name=\"6f97\">\r\n16: JavaRDD&lt;String&gt; weatherData = sc.textFile(inputPath);<\/pre>\n<p id=\"6e31\" name=\"6e31\">Line 16 is neither an action or a transformation; it&rsquo;s a function of&nbsp;<strong>sc<\/strong>, our&nbsp;<strong>JavaSparkContext<\/strong>.<\/p>\n<pre id=\"63cb\" name=\"63cb\">\r\n17: JavaPairRDD&lt;String, Integer&gt; tempsByCountry = weatherData.mapToPair(new Func.....<\/pre>\n<p id=\"fbc5\" name=\"fbc5\">Line 17 is a&nbsp;<strong>transformation<\/strong>&nbsp;of the&nbsp;<em>weatherData<\/em>&nbsp;RDD, in it we map each line of&nbsp;<em>weatherData<\/em>&nbsp;to a pair comprised of (City, Temperature)<\/p>\n<pre id=\"942a\" name=\"942a\">\r\n26: JavaPairRDD&lt;String, Integer&gt; maxTempByCountry = tempsByCountry.reduce(new Func....<\/pre>\n<p id=\"88a4\" name=\"88a4\">Line 26 is also a&nbsp;<strong>transformation<\/strong>&nbsp;because we are iterating over key-value pairs. its a transformation of&nbsp;<em>tempsByCountry<\/em>&nbsp;in which we reduce each city to its highest recorded temperature.<\/p>\n<div id=\"0dee\" name=\"0dee\" style=\"background:#eee;border:1px solid #ccc;padding:5px 10px;\"><span style=\"font-family:courier new,courier,monospace;\">31: maxTempByCountry.saveAsHadoopFile(destPath, String.class, Integer.class, TextOutputFormat.class)<\/span>;<\/div>\n<p name=\"cbf6\">&nbsp;<\/p>\n<p id=\"cbf6\" name=\"cbf6\">Finally, on line 31 we trigger a Spark&nbsp;<strong>action<\/strong>: saving our RDD to our file system. Since Spark subscribes to the lazy execution model, it isn&rsquo;t until this line that Spark generates <i>weather data<\/i>,&nbsp;<em>tempsByCountry<\/em>, and&nbsp;<em>maxTempsByCountry<\/em>&nbsp;before finally saving our result.<\/p>\n<h3 id=\"5cec\" name=\"5cec\"><strong>Directed Acyclic&nbsp;Graph<\/strong><\/h3>\n<p id=\"29c6\" name=\"29c6\">Whenever an action is performed on an RDD, Spark creates a DAG, a finite direct graph with no directed cycles (otherwise our job would run forever). Remember that a graph is nothing more than a series of connected vertices and edges, and this graph is no different. Each vertex in the DAG is a Spark function, some operation performed on an RDD (map, mapToPair, reduceByKey, etc).<\/p>\n<p id=\"d4bb\" name=\"d4bb\">In MapReduce, the DAG consists of two vertices:&nbsp;<code>Map &rarr; Reduce<\/code>.<\/p>\n<p id=\"3f51\" name=\"3f51\">In our above example of MaxTemperatureByCountry, the DAG is a little more involved:<\/p>\n<p id=\"18fa\" name=\"18fa\"><code>parallelize &rarr; map &rarr; mapToPair &rarr; reduce &rarr; saveAsHadoopFile<\/code><\/p>\n<p id=\"3f57\" name=\"3f57\">The DAG allows Spark to optimize its execution plan and minimize shuffling. We&rsquo;ll discuss the DAG in greater depth in later posts, as it&rsquo;s outside the scope of this Spark overview.<\/p>\n<\/section>\n<section name=\"1cf9\">\n<hr \/>\n<h3 id=\"6d7c\" name=\"6d7c\"><strong>Evaluation Loops<\/strong><\/h3>\n<p id=\"721a\" name=\"721a\">With our new vocabulary, let us re-examine the problem with MapReduce as I defined in&nbsp;<a data-href=\"https:\/\/hackernoon.com\/why-we-need-apache-spark-51c8a57aa57a\" href=\"https:\/\/hackernoon.com\/why-we-need-apache-spark-51c8a57aa57a\" target=\"_blank\" rel=\"noopener noreferrer\">Part I<\/a>, quoted below:<\/p>\n<blockquote id=\"381b\" name=\"381b\"><p>MapReduce excels at batch data processing, however it lags behind when it comes to repeat analysis and small feedback loops. The only way to reuse data between computations is to write it to an external storage system (a la HDFS)&rdquo;<\/p><\/blockquote>\n<p id=\"7d93\" name=\"7d93\">&lsquo;Re-use data between computations&rsquo;? Sounds like an RDD that can have multiple actions performed on it! Let&#39;s suppose we have a file &ldquo;data.txt&rdquo; and want to accomplish two computations:<\/p>\n<ul>\n<li id=\"6e78\" name=\"6e78\">Total length of all lines in the file<\/li>\n<li id=\"ca92\" name=\"ca92\">Length of the longest line in the file<\/li>\n<\/ul>\n<p id=\"fad8\" name=\"fad8\">In MapReduce, each task would require a separate job or a fancy MulitpleOutputFormat implementation. Spark makes this a breeze in just four simple steps:<\/p>\n<ol>\n<li id=\"be04\" name=\"be04\">Load contents of&nbsp;<em>data.txt&nbsp;<\/em>into an RDD<\/li>\n<\/ol>\n<pre id=\"c4ae\" name=\"c4ae\">\r\nJavaRDD&lt;String&gt; lines = sc.textFile(&quot;data.txt&quot;);<\/pre>\n<p id=\"a667\" name=\"a667\">2. Map each line of &lsquo;<em>lines<\/em>&rsquo; to its length&nbsp;<em>(Lambda functions used for brevity)<\/em><\/p>\n<pre id=\"0894\" name=\"0894\">\r\nJavaRDD&lt;Integer&gt; lineLengths = lines.map(s -&gt; s.length());<\/pre>\n<p id=\"4909\" name=\"4909\">3. To solve for total length: reduce&nbsp;<em>lineLengths&nbsp;<\/em>to find the total line length sum, in this case, the sum of every element in the RDD<\/p>\n<pre id=\"e1cd\" name=\"e1cd\">\r\nint totalLength = lineLengths.reduce((a, b) -&gt; a + b);<\/pre>\n<p id=\"b9f2\" name=\"b9f2\">4. To solve for longest length: reduce&nbsp;<em>lineLengths&nbsp;<\/em>to find the maximum line length<\/p>\n<pre id=\"61b7\" name=\"61b7\">\r\nint maxLength = lineLengths.reduce((a, b) -&gt; Math.max(a,b));<\/pre>\n<p id=\"2ab6\" name=\"2ab6\"><em>Note that steps 3 and 4 are RDD actions, so they return a result to our Driver program, in this case, a Java int. Also recall that Spark is lazy and refuses to do any work until it sees an action, in this case, it will not begin any real work until step 3.<\/em><\/p>\n<\/section>\n<section name=\"418b\">\n<hr \/>\n<h3 id=\"649f\" name=\"649f\"><strong>Next Steps<\/strong><\/h3>\n<p id=\"54e6\" name=\"54e6\">So far we&rsquo;ve introduced our data problem and its solution: Apache Spark. We reviewed Spark&rsquo;s architecture and workflow, it&rsquo;s flagship internal abstraction (RDD), and its execution model. Next, we&rsquo;ll look into Functions and Syntax in Java, getting progressively more technical as we dive deeper into the framework.<\/p>\n<\/section>\n","protected":false},"excerpt":{"rendered":"<p>With the scale of data growing at a rapid and ominous pace, we needed a way to process potential petabytes of data quickly, and we simply couldn&rsquo;t make a single computer process that amount of data at a reasonable pace. This problem is solved by creating a cluster of machines to perform the work for you, but how do those machines work together to solve the common problem? Spark is the cluster computing framework for large-scale data processing.<\/p>\n","protected":false},"author":547,"featured_media":2684,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"content-type":"","footnotes":""},"categories":[187],"tags":[94],"ppma_author":[3216],"class_list":["post-1687","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-bigdata-cloud","tag-data-science"],"authors":[{"term_id":3216,"user_id":547,"is_guest":0,"slug":"eric-girouard","display_name":"Eric Girouard","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g","user_url":"","last_name":"Girouard","first_name":"Eric","job_title":"","description":"Eric Girouard&nbsp;is Data Engineer at BHE, an independent health analytics company."}],"_links":{"self":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/1687","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/users\/547"}],"replies":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/comments?post=1687"}],"version-history":[{"count":1,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/1687\/revisions"}],"predecessor-version":[{"id":5881,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/1687\/revisions\/5881"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/media\/2684"}],"wp:attachment":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/media?parent=1687"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/categories?post=1687"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/tags?post=1687"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=1687"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}