diff --git a/core/core.services.yml b/core/core.services.yml
index a9af34679cca212ca6532d039b0f4ba75b102244..b3c4ffe49f66fd810a8c573989c41b5b28760fea 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1153,4 +1153,4 @@ services:
       - { name: mime_type_guesser }
   renderer:
     class: Drupal\Core\Render\Renderer
-    arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info']
+    arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@request_stack', '@cache_factory', '@cache_contexts']
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 409fa67d4c840b445eb24e606b05ca63bd2e53f7..1c915e9158f49cc62b3b5474bd918cee840a6580 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -2265,79 +2265,6 @@ function show(&$element) {
   return $element;
 }
 
-/**
- * Gets the cached, prerendered element of a renderable element from the cache.
- *
- * @param array $elements
- *   A renderable array.
- *
- * @return array
- *   A renderable array, with the original element and all its children pre-
- *   rendered, or FALSE if no cached copy of the element is available.
- *
- * @see drupal_render()
- * @see drupal_render_cache_set()
- */
-function drupal_render_cache_get(array $elements) {
-  if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) {
-    return FALSE;
-  }
-  $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
-
-  if (!empty($cid) && $cache = \Drupal::cache($bin)->get($cid)) {
-    $cached_element = $cache->data;
-    // Return the cached element.
-    return $cached_element;
-  }
-  return FALSE;
-}
-
-/**
- * Caches the rendered output of a renderable element.
- *
- * This is called by drupal_render() if the #cache property is set on an
- * element.
- *
- * @param $markup
- *   The rendered output string of $elements.
- * @param array $elements
- *   A renderable array.
- *
- * @see drupal_render_cache_get()
- */
-function drupal_render_cache_set(&$markup, array $elements) {
-  // Create the cache ID for the element.
-  if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) {
-    return FALSE;
-  }
-
-  // Cache implementations are allowed to modify the markup, to support
-  // replacing markup with edge-side include commands. The supporting cache
-  // backend will store the markup in some other key (like
-  // $data['#real-value']) and return an include command instead. When the
-  // ESI command is executed by the content accelerator, the real value can
-  // be retrieved and used.
-  $data['#markup'] = $markup;
-
-  // Persist attached data associated with this element.
-  $data['#attached'] = $elements['#attached'];
-
-  // Persist #post_render_cache callbacks associated with this element.
-  if (isset($elements['#post_render_cache'])) {
-    $data['#post_render_cache'] = $elements['#post_render_cache'];
-  }
-
-  // Persist cache tags associated with this element. Also associate the
-  // "rendered" cache tag. This allows us to invalidate the entire render cache,
-  // regardless of the cache bin.
-  $data['#cache']['tags'] = $elements['#cache']['tags'];
-  $data['#cache']['tags'][] = 'rendered';
-
-  $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
-  $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : Cache::PERMANENT;
-  \Drupal::cache($bin)->set($cid, $data, $expire, $data['#cache']['tags']);
-}
-
 /**
  * Generates a render cache placeholder.
  *
@@ -2358,7 +2285,7 @@ function drupal_render_cache_set(&$markup, array $elements) {
  *
  * @throws \Exception
  *
- * @see drupal_render_cache_get()
+ * @see \Drupal\Core\Render\Renderer::getFromCache()
  */
 function drupal_render_cache_generate_placeholder($callback, array &$context) {
   if (is_string($callback) && strpos($callback, '::') === FALSE) {
@@ -2382,33 +2309,6 @@ function drupal_render_cache_generate_placeholder($callback, array &$context) {
   return '<drupal-render-cache-placeholder callback="' . $callback . '" token="' . $context['token'] . '"></drupal-render-cache-placeholder>';
 }
 
-/**
- * Creates the cache ID for a renderable element.
- *
- * This creates the cache ID string, either by returning the #cache['cid']
- * property if present or by building the cache ID out of the #cache['keys'].
- *
- * @param $elements
- *   A renderable array.
- *
- * @return
- *   The cache ID string, or FALSE if the element may not be cached.
- */
-function drupal_render_cid_create($elements) {
-  if (isset($elements['#cache']['cid'])) {
-    return $elements['#cache']['cid'];
-  }
-  elseif (isset($elements['#cache']['keys'])) {
-    // Cache keys may either be static (just strings) or tokens (placeholders
-    // that are converted to static keys by the @cache_contexts service,
-    // depending on the request).
-    $cache_contexts = \Drupal::service("cache_contexts");
-    $keys = $cache_contexts->convertTokensToKeys($elements['#cache']['keys']);
-    return implode(':', $keys);
-  }
-  return FALSE;
-}
-
 /**
  * Retrieves the default properties for the defined element type.
  *
diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
index 323fd871c36f01819d303339e2389c97878a715d..081902ff93481f39772f1dffaeac196e4b72d66b 100644
--- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -185,16 +185,12 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
       // title. We set its $is_root_call parameter to FALSE, to ensure
       // #post_render_cache callbacks are not yet applied. This is essentially
       // "pre-rendering" the main content, the "full rendering" will happen in
-      // ::renderContentIntoResponse().
+      // ::renderResponse().
       // @todo Remove this once https://www.drupal.org/node/2359901 lands.
       if (!empty($main_content)) {
         $this->renderer->render($main_content, FALSE);
-        $main_content = [
-          '#markup' => $main_content['#markup'],
-          '#attached' => $main_content['#attached'],
-          '#cache' => ['tags' => $main_content['#cache']['tags']],
-          '#post_render_cache' => $main_content['#post_render_cache'],
-          '#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL,
+        $main_content = $this->renderer->getCacheableRenderArray($main_content) + [
+          '#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL
         ];
       }
 
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 91e4bd94ca8f750ce29c1321e9ddd14a60c4ed55..39d2090de591524f832433266c1de49fe984bd64 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -7,11 +7,14 @@
 
 namespace Drupal\Core\Render;
 
+use Drupal\Core\Cache\CacheContexts;
+use Drupal\Core\Cache\CacheFactoryInterface;
 use Drupal\Core\Controller\ControllerResolverInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Cache\Cache;
 use Drupal\Component\Utility\NestedArray;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Turns a render array into a HTML string.
@@ -39,6 +42,27 @@ class Renderer implements RendererInterface {
    */
   protected $elementInfo;
 
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The cache factory.
+   *
+   * @var \Drupal\Core\Cache\CacheFactoryInterface
+   */
+  protected $cacheFactory;
+
+  /**
+   * The cache contexts service.
+   *
+   * @var \Drupal\Core\Cache\CacheContexts
+   */
+  protected $cacheContexts;
+
   /**
    * The stack containing bubbleable rendering metadata.
    *
@@ -55,11 +79,20 @@ class Renderer implements RendererInterface {
    *   The theme manager.
    * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
    *   The element info.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   * @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
+   *   The cache factory.
+   * @param \Drupal\Core\Cache\CacheContexts $cache_contexts
+   *   The cache contexts service.
    */
-  public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info) {
+  public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContexts $cache_contexts) {
     $this->controllerResolver = $controller_resolver;
     $this->theme = $theme;
     $this->elementInfo = $element_info;
+    $this->requestStack = $request_stack;
+    $this->cacheFactory = $cache_factory;
+    $this->cacheContexts = $cache_contexts;
   }
 
   /**
@@ -133,7 +166,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // Try to fetch the prerendered element from cache, run any
     // #post_render_cache callbacks and return the final markup.
     if (isset($elements['#cache'])) {
-      $cached_element = drupal_render_cache_get($elements);
+      $cached_element = $this->cacheGet($elements);
       if ($cached_element !== FALSE) {
         $elements = $cached_element;
         // Only when we're not in a root (non-recursive) drupal_render() call,
@@ -312,7 +345,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
 
     // Cache the processed element if #cache is set.
     if (isset($elements['#cache'])) {
-      drupal_render_cache_set($elements['#markup'], $elements);
+      $this->cacheSet($elements);
     }
 
     // Only when we're in a root (non-recursive) drupal_render() call,
@@ -437,4 +470,109 @@ protected function processPostRenderCache(array &$elements) {
     }
   }
 
+  /**
+   * Gets the cached, prerendered element of a renderable element from the cache.
+   *
+   * @param array $elements
+   *   A renderable array.
+   *
+   * @return array
+   *   A renderable array, with the original element and all its children pre-
+   *   rendered, or FALSE if no cached copy of the element is available.
+   *
+   * @see ::render()
+   * @see ::saveToCache()
+   */
+  protected function cacheGet(array $elements) {
+    // Form submissions rely on the form being built during the POST request,
+    // and render caching of forms prevents this from happening.
+    // @todo remove the isMethodSafe() check when
+    //       https://www.drupal.org/node/2367555 lands.
+    if (!$this->requestStack->getCurrentRequest()->isMethodSafe() || !$cid = $this->createCacheID($elements)) {
+      return FALSE;
+    }
+    $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
+
+    if (!empty($cid) && $cache = $this->cacheFactory->get($bin)->get($cid)) {
+      $cached_element = $cache->data;
+      // Return the cached element.
+      return $cached_element;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Caches the rendered output of a renderable element.
+   *
+   * This is called by ::render() if the #cache property is set on an element.
+   *
+   * @param array $elements
+   *   A renderable array.
+   *
+   * @return bool|null
+   *  Returns FALSE if no cache item could be created, NULL otherwise.
+   *
+   * @see ::getFromCache()
+   */
+  protected function cacheSet(array &$elements) {
+    // Form submissions rely on the form being built during the POST request,
+    // and render caching of forms prevents this from happening.
+    // @todo remove the isMethodSafe() check when
+    //       https://www.drupal.org/node/2367555 lands.
+    if (!$this->requestStack->getCurrentRequest()->isMethodSafe() || !$cid = $this->createCacheID($elements)) {
+      return FALSE;
+    }
+
+    $data = $this->getCacheableRenderArray($elements);
+
+    // Cache tags are cached, but we also want to assocaite the "rendered" cache
+    // tag. This allows us to invalidate the entire render cache, regardless of
+    // the cache bin.
+    $data['#cache']['tags'][] = 'rendered';
+
+    $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
+    $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : Cache::PERMANENT;
+    $this->cacheFactory->get($bin)->set($cid, $data, $expire, $data['#cache']['tags']);
+  }
+
+  /**
+   * Creates the cache ID for a renderable element.
+   *
+   * This creates the cache ID string, either by returning the #cache['cid']
+   * property if present or by building the cache ID out of the #cache['keys'].
+   *
+   * @param array $elements
+   *   A renderable array.
+   *
+   * @return string
+   *   The cache ID string, or FALSE if the element may not be cached.
+   */
+  protected function createCacheID(array $elements) {
+    if (isset($elements['#cache']['cid'])) {
+      return $elements['#cache']['cid'];
+    }
+    elseif (isset($elements['#cache']['keys'])) {
+      // Cache keys may either be static (just strings) or tokens (placeholders
+      // that are converted to static keys by the @cache_contexts service,
+      // depending on the request).
+      $keys = $this->cacheContexts->convertTokensToKeys($elements['#cache']['keys']);
+      return implode(':', $keys);
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableRenderArray(array $elements) {
+    return [
+      '#markup' => $elements['#markup'],
+      '#attached' => $elements['#attached'],
+      '#post_render_cache' => $elements['#post_render_cache'],
+      '#cache' => [
+        'tags' => $elements['#cache']['tags'],
+      ],
+    ];
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php
index 97f09b6c7fb0cc10c04947e9b4156b8aa6550456..579ce5e973a82c0c8c6f98cb3f932a489fc5e161 100644
--- a/core/lib/Drupal/Core/Render/RendererInterface.php
+++ b/core/lib/Drupal/Core/Render/RendererInterface.php
@@ -97,8 +97,7 @@ public function renderPlain(&$elements);
    *       'keys' is set, the cache ID is created automatically from these keys.
    *       Cache keys may either be static (just strings) or tokens
    *       (placeholders that are converted to static keys by the
-   *       @cache_contexts service, depending on the request). See
-   *       drupal_render_cid_create().
+   *       'cache_contexts' service, depending on the request).
    *     - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is
    *       required. If 'cid' is set, 'keys' is ignored. Use only if you have
    *       special requirements.
@@ -269,4 +268,23 @@ public function renderPlain(&$elements);
    */
   public function render(&$elements, $is_root_call = FALSE);
 
+  /**
+   * Gets a cacheable render array for a render array and its rendered output.
+   *
+   * Given a render array and its rendered output (HTML string), return an array
+   * data structure that allows the render array and its associated metadata to
+   * be cached reliably (and is serialization-safe).
+   *
+   * If Drupal needs additional rendering metadata to be cached at some point,
+   * consumers of this method will continue to work. Those who only cache
+   * certain parts of a render array will cease to work.
+   *
+   * @param array $elements
+   *   A renderable array, on which ::render() has already been invoked.
+   *
+   * @return array
+   *   An array representing the cacheable data for this render array.
+   */
+  public function getCacheableRenderArray(array $elements);
+
 }
diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php
index bfd37d4a7858bbd4eb587fbf0b2c4ce7a13f83d0..bd2a986e2d36f2c6f3d1fd9655c805435d9f79ae 100644
--- a/core/modules/block/src/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php
@@ -160,7 +160,7 @@ protected function verifyRenderCacheHandling() {
 
     // Test that a cache entry is created.
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = 'entity_view:block:test_block:en:core';
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
 
@@ -223,10 +223,10 @@ public function testBlockViewBuilderAlter() {
     ));
     $alter_add_key = $this->randomMachineName();
     \Drupal::state()->set('block_test_view_alter_cache_key', $alter_add_key);
+    $cid = 'entity_view:block:test_block:en:core:' . $alter_add_key;
     $expected_keys = array_merge($default_keys, array($alter_add_key));
     $build = $this->getBlockRenderArray();
     $this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
-    $cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys)));
     $this->assertIdentical(drupal_render($build), '');
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
@@ -242,7 +242,6 @@ public function testBlockViewBuilderAlter() {
     $build = $this->getBlockRenderArray();
     sort($build['#cache']['tags']);
     $this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.');
-    $cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys)));
     $this->assertIdentical(drupal_render($build), '');
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
@@ -272,6 +271,8 @@ public function testBlockViewBuilderAlter() {
    * @see \Drupal\block\Tests\BlockCacheTest
    */
   public function testBlockViewBuilderCacheContexts() {
+    $cache_contexts = \Drupal::service("cache_contexts");
+
     // Force a request via GET so we can get drupal_render() cache working.
     $request = \Drupal::request();
     $request_method = $request->server->get('REQUEST_METHOD');
@@ -282,7 +283,7 @@ public function testBlockViewBuilderCacheContexts() {
       'max_age' => 600,
     ));
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = implode(':', $build['#cache']['keys']);
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.');
 
@@ -293,7 +294,7 @@ public function testBlockViewBuilderCacheContexts() {
     ));
     $old_cid = $cid;
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys']));
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.');
     $this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.');
@@ -306,7 +307,7 @@ public function testBlockViewBuilderCacheContexts() {
     $this->container->set('cache_context.url', $temp_context);
     $old_cid = $cid;
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys']));
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.');
     $this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.');
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index 6848dc9b974bf2f206bfe69c48d8a8be039f1dc5..b137d19f277621d1a7abeadb63f77d75021fe3db 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -525,7 +525,7 @@ function testDrupalRenderPostRenderCache() {
 
     // GET request: validate cached data.
     $element = array('#cache' => array('cid' => 'post_render_cache_test_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get('post_render_cache_test_GET')->data;
     $expected_element = array(
       '#markup' => '<p>#cache enabled, GET</p>',
       '#attached' => $test_element['#attached'],
@@ -566,8 +566,7 @@ function testDrupalRenderPostRenderCache() {
     $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.');
 
     // POST request: Ensure no data was cached.
-    $element = array('#cache' => array('cid' => 'post_render_cache_test_POST'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element));
+    $cached_element = \Drupal::cache('render')->get('post_render_cache_test_POST');
     $this->assertFalse($cached_element, 'No data is cached because this is a POST request.');
 
     // Restore the previous request method.
@@ -630,8 +629,7 @@ function testDrupalRenderChildrenPostRenderCache() {
     $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
 
     // GET request: validate cached data.
-    $element = array('#cache' => $element['#cache']);
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get('simpletest:drupal_render:children_post_render_cache')->data;
     $expected_element = array(
       '#attached' => array(
         'drupalSettings' => [
@@ -697,11 +695,8 @@ function testDrupalRenderChildrenPostRenderCache() {
     $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
 
     // GET request: validate cached data for both the parent and child.
-    $element = $test_element;
-    $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent');
-    $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child');
-    $cached_parent_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
-    $cached_child_element = \Drupal::cache('render')->get(drupal_render_cid_create($element['child']))->data;
+    $cached_parent_element = \Drupal::cache('render')->get('simpletest:drupal_render:children_post_render_cache:nested_cache_parent')->data;
+    $cached_child_element = \Drupal::cache('render')->get('simpletest:drupal_render:children_post_render_cache:nested_cache_child')->data;
     $expected_parent_element = array(
       '#attached' => array(
         'drupalSettings' => [
@@ -834,8 +829,7 @@ function testDrupalRenderRenderCachePlaceholder() {
 
     // GET request: validate cached data.
     $expected_token = $context['token'];
-    $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_GET')->data;
     // Parse unique token out of the cached markup.
     $dom = Html::load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
@@ -927,8 +921,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
 
     // GET request: validate cached data for child element.
     $expected_token = $context['token'];
-    $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_child_GET')->data;
     // Parse unique token out of the cached markup.
     $dom = Html::load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
@@ -953,8 +946,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
 
     // GET request: validate cached data (for the parent/entire render array).
-    $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_GET')->data;
     // Parse unique token out of the cached markup.
     $dom = Html::load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
@@ -981,8 +973,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     // GET request: validate cached data.
     // Check the cache of the child element again after the parent has been
     // rendered.
-    $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_child_GET')->data;
     // Verify that the child element contains the correct
     // render_cache_placeholder markup.
     $dom = Html::load($cached_element['#markup']);
diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
index d57b79c663cf0fdb73bdec92e70e5371e343e20b..2535bc7ebb852e8d170f890df8e3da0dbda99531 100644
--- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
@@ -33,6 +33,8 @@ protected function setUp() {
    * Tests entity render cache handling.
    */
   public function testEntityViewBuilderCache() {
+    $cache_contexts = \Drupal::service("cache_contexts");
+
     // Force a request via GET so we can get drupal_render() cache working.
     $request = \Drupal::request();
     $request_method = $request->server->get('REQUEST_METHOD');
@@ -48,7 +50,7 @@ public function testEntityViewBuilderCache() {
     // Get a fully built entity view render array.
     $entity_test->save();
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid = drupal_render_cid_create($build);
+    $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys']));
     $bin = $build['#cache']['bin'];
 
     // Mock the build array to not require the theme registry.
@@ -79,6 +81,8 @@ public function testEntityViewBuilderCache() {
    * Tests entity render cache with references.
    */
   public function testEntityViewBuilderCacheWithReferences() {
+    $cache_contexts = \Drupal::service("cache_contexts");
+
     // Force a request via GET so we can get drupal_render() cache working.
     $request = \Drupal::request();
     $request_method = $request->server->get('REQUEST_METHOD');
@@ -95,7 +99,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array for the referenced entity.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full');
-    $cid_reference = drupal_render_cid_create($build);
+    $cid_reference = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys']));
     $bin_reference = $build['#cache']['bin'];
 
     // Mock the build array to not require the theme registry.
@@ -114,7 +118,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid = drupal_render_cid_create($build);
+    $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys']));
     $bin = $build['#cache']['bin'];
 
     // Mock the build array to not require the theme registry.
diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
index bce200d8d51c558f6d5b158abbfca524239108ce..56c0fbb0f71ebbe0960218c3e6c9af24c504bec0 100644
--- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
+++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
@@ -7,10 +7,11 @@
 
 namespace Drupal\views\Plugin\views\cache;
 
-use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Render\RendererInterface;
 use Drupal\views\Plugin\views\PluginBase;
 use Drupal\Core\Database\Query\Select;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * @defgroup views_cache_plugins Views cache plugins
@@ -72,6 +73,43 @@ abstract class CachePluginBase extends PluginBase {
    */
   protected $outputKey;
 
+  /**
+   * The renderer service.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a CachePluginBase object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, RendererInterface $renderer) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('renderer')
+    );
+  }
+
   /**
    * Returns the outputKey property.
    *
@@ -143,8 +181,8 @@ public function cacheSet($type) {
         \Drupal::cache($this->resultsBin)->set($this->generateResultsKey(), $data, $this->cacheSetExpire($type), $this->getCacheTags());
         break;
       case 'output':
-        $this->storage['output'] = drupal_render($this->view->display_handler->output);
-        $this->gatherRenderMetadata($this->view->display_handler->output);
+        $this->renderer->render($this->view->display_handler->output);
+        $this->storage = $this->renderer->getCacheableRenderArray($this->view->display_handler->output);
         \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), $this->getCacheTags());
         break;
     }
@@ -180,17 +218,10 @@ public function cacheGet($type) {
         if ($cache = \Drupal::cache($this->outputBin)->get($this->generateOutputKey())) {
           if (!$cutoff || $cache->created > $cutoff) {
             $this->storage = $cache->data;
-
-            $this->restoreRenderMetadata();
-            $this->view->display_handler->output = array(
-              '#attached' => &$this->view->element['#attached'],
-              '#cache' => [
-                'tags' => &$this->view->element['#cache']['tags'],
-              ],
-              '#post_render_cache' => &$this->view->element['#post_render_cache'],
-              '#markup' => $cache->data['output'],
-            );
-
+            $this->view->display_handler->output = $this->storage;
+            $this->view->element['#attached'] = &$this->view->display_handler->output['#attached'];
+            $this->view->element['#cache']['tags'] = &$this->view->display_handler->output['#cache']['tags'];
+            $this->view->element['#post_render_cache'] = &$this->view->display_handler->output['#post_render_cache'];
             return TRUE;
           }
         }
@@ -235,27 +266,6 @@ public function postRender(&$output) { }
    */
   public function cacheStart() { }
 
-  /**
-   * Gather bubbleable render metadata from the render array.
-   *
-   * @param array $render_array
-   *   The view render array to collect data from.
-   */
-  protected function gatherRenderMetadata(array $render_array = []) {
-    $this->storage['attachments'] = $render_array['#attached'];
-    $this->storage['postRenderCache'] = $render_array['#post_render_cache'];
-    $this->storage['cacheTags'] = $render_array['#cache']['tags'];
-  }
-
-  /**
-   * Restore bubbleable render metadata.
-   */
-  public function restoreRenderMetadata() {
-    $this->view->element['#attached'] = drupal_merge_attached($this->view->element['#attached'], $this->storage['attachments']);
-    $this->view->element['#cache']['tags'] = Cache::mergeTags(isset($this->view->element['#cache']['tags']) ? $this->view->element['#cache']['tags'] : [], $this->storage['cacheTags']);
-    $this->view->element['#post_render_cache'] = NestedArray::mergeDeep(isset($this->view->element['#post_render_cache']) ? $this->view->element['#post_render_cache'] : [], $this->storage['postRenderCache']);
-  }
-
   /**
    * Calculates and sets a cache ID used for the result cache.
    *
diff --git a/core/modules/views/src/Plugin/views/cache/Time.php b/core/modules/views/src/Plugin/views/cache/Time.php
index b318cfa2e94d505e3fd48440cdae93fa39d03011..3c43755a6dbb187c6bd430f80d002dca537f5267 100644
--- a/core/modules/views/src/Plugin/views/cache/Time.php
+++ b/core/modules/views/src/Plugin/views/cache/Time.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Datetime\DateFormatter;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Render\RendererInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Form\FormStateInterface;
 
@@ -46,12 +47,14 @@ class Time extends CachePluginBase {
    *   The plugin_id for the plugin instance.
    * @param mixed $plugin_definition
    *   The plugin implementation definition.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
    * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
    *   The date formatter service.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, DateFormatter $date_formatter) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, RendererInterface $renderer, DateFormatter $date_formatter) {
     $this->dateFormatter = $date_formatter;
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $renderer);
   }
 
   /**
@@ -62,6 +65,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration,
       $plugin_id,
       $plugin_definition,
+      $container->get('renderer'),
       $container->get('date.formatter')
     );
   }