diff -Nuar a/vendor/magento/module-catalog/view/frontend/templates/product/list/items.phtml b/vendor/magento/module-catalog/view/frontend/templates/product/list/items.phtml
index 55c12c82362..6c360dcc63f 100644
--- a/vendor/magento/module-catalog/view/frontend/templates/product/list/items.phtml
+++ b/vendor/magento/module-catalog/view/frontend/templates/product/list/items.phtml
@@ -6,6 +6,8 @@
 
 // phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis
 // phpcs:disable Generic.WhiteSpace.ScopeIndent.Incorrect
+// phpcs:disable Generic.Files.LineLength
+// phpcs:disable Magento2.Templates.ThisInTemplate.FoundHelper
 
 /* @var $block \Magento\Catalog\Block\Product\AbstractProduct */
 ?>
@@ -23,6 +25,7 @@ switch ($type = $block->getType()) {
             $items = $block->getAllItems();
             $limit = $block->getPositionLimit();
             $shuffle = (int) $block->isShuffled();
+            $isWeightedRandom = (int) $block->getRotation()->isWeightedRandom($block->getProductListType());
             $canItemsAddToCart = $block->canItemsAddToCart();
 
             $showAddTo = true;
@@ -43,6 +46,7 @@ switch ($type = $block->getType()) {
             $items = $block->getItems();
             $limit = 0;
             $shuffle = 0;
+            $isWeightedRandom = 0;
             $canItemsAddToCart = $block->canItemsAddToCart();
 
             $showAddTo = true;
@@ -62,6 +66,7 @@ switch ($type = $block->getType()) {
             $items = $block->getAllItems();
             $limit = $block->getPositionLimit();
             $shuffle = (int) $block->isShuffled();
+            $isWeightedRandom = (int) $block->getRotation()->isWeightedRandom($block->getProductListType());
 
             $showAddTo = false;
             $showCart = false;
@@ -82,6 +87,7 @@ switch ($type = $block->getType()) {
             $items = $block->getItemCollection()->getItems();
             $limit = $block->getItemLimit('upsell');
             $shuffle = 0;
+            $isWeightedRandom = 0;
 
             $showAddTo = false;
             $showCart = false;
@@ -152,22 +158,22 @@ switch ($type = $block->getType()) {
 }
 ?>
 
-<?php if ($exist) :?>
+<?php if ($exist):?>
 
-<?php if ($type == 'related' || $type == 'upsell') :?>
-<?php if ($type == 'related') :?>
-<div class="block <?= $block->escapeHtmlAttr($class) ?>" data-mage-init='{"relatedProducts":{"relatedCheckbox":".related.checkbox"}}' data-limit="<?= $block->escapeHtmlAttr($limit) ?>" data-shuffle="<?= /* @noEscape */ $shuffle ?>">
-    <?php else :?>
-    <div class="block <?= $block->escapeHtmlAttr($class) ?>" data-mage-init='{"upsellProducts":{}}' data-limit="<?= $block->escapeHtmlAttr($limit) ?>" data-shuffle="<?= /* @noEscape */ $shuffle ?>">
+<?php if ($type == 'related' || $type == 'upsell'):?>
+<?php if ($type == 'related'):?>
+<div class="block <?= $block->escapeHtmlAttr($class) ?>" data-mage-init='{"relatedProducts":{"relatedCheckbox":".related.checkbox"}}' data-limit="<?= $block->escapeHtmlAttr($limit) ?>" data-shuffle="<?= /* @noEscape */ $shuffle ?>" data-shuffle-weighted="<?= /* @noEscape */ $isWeightedRandom ?>">
+    <?php else:?>
+    <div class="block <?= $block->escapeHtmlAttr($class) ?>" data-mage-init='{"upsellProducts":{}}' data-limit="<?= $block->escapeHtmlAttr($limit) ?>" data-shuffle="<?= /* @noEscape */ $shuffle ?>" data-shuffle-weighted="<?= /* @noEscape */ $isWeightedRandom ?>">
         <?php endif; ?>
-        <?php else :?>
+        <?php else:?>
         <div class="block <?= $block->escapeHtmlAttr($class) ?>">
             <?php endif; ?>
             <div class="block-title title">
                 <strong id="block-<?= $block->escapeHtmlAttr($class) ?>-heading" role="heading" aria-level="2"><?= $block->escapeHtml($title) ?></strong>
             </div>
             <div class="block-content content" aria-labelledby="block-<?= $block->escapeHtmlAttr($class) ?>-heading">
-                <?php if ($type == 'related' && $canItemsAddToCart) :?>
+                <?php if ($type == 'related' && $canItemsAddToCart):?>
                     <div class="block-actions">
                         <?= $block->escapeHtml(__('Check items to add to the cart or')) ?>
                         <button type="button" class="action select" data-role="select-all"><span><?= $block->escapeHtml(__('select all')) ?></span></button>
@@ -175,16 +181,16 @@ switch ($type = $block->getType()) {
                 <?php endif; ?>
                 <div class="products wrapper grid products-grid products-<?= $block->escapeHtmlAttr($type) ?>">
                     <ol class="products list items product-items">
-                        <?php foreach ($items as $_item) :?>
+                        <?php foreach ($items as $_item):?>
                             <?php $available = ''; ?>
-                            <?php if (!$_item->isComposite() && $_item->isSaleable() && $type == 'related') :?>
-                                <?php if (!$_item->getRequiredOptions()) :?>
+                            <?php if (!$_item->isComposite() && $_item->isSaleable() && $type == 'related'):?>
+                                <?php if (!$_item->getRequiredOptions()):?>
                                     <?php $available = 'related-available'; ?>
                                 <?php endif; ?>
                             <?php endif; ?>
-                            <?php if ($type == 'related' || $type == 'upsell') :?>
-                                <li class="item product product-item" style="display: none;">
-                            <?php else :?>
+                            <?php if ($type == 'related' || $type == 'upsell'):?>
+                                <li class="item product product-item" style="display: none;"  data-shuffle-group="<?= $block->escapeHtmlAttr($_item->getPriority()) ?>" >
+                            <?php else:?>
                                 <li class="item product product-item">
                             <?php endif; ?>
                             <div class="product-item-info <?= /* @noEscape */ $available ?>">
@@ -199,12 +205,12 @@ switch ($type = $block->getType()) {
 
                                     <?= /* @noEscape */ $block->getProductPrice($_item) ?>
 
-                                    <?php if ($templateType) :?>
+                                    <?php if ($templateType):?>
                                         <?= $block->getReviewsSummaryHtml($_item, $templateType) ?>
                                     <?php endif; ?>
 
-                                    <?php if ($canItemsAddToCart && !$_item->isComposite() && $_item->isSaleable() && $type == 'related') :?>
-                                        <?php if (!$_item->getRequiredOptions()) :?>
+                                    <?php if ($canItemsAddToCart && !$_item->isComposite() && $_item->isSaleable() && $type == 'related'):?>
+                                        <?php if (!$_item->getRequiredOptions()):?>
                                             <div class="field choice related">
                                                 <input type="checkbox" class="checkbox related" id="related-checkbox<?= $block->escapeHtmlAttr($_item->getId()) ?>" name="related_products[]" value="<?= $block->escapeHtmlAttr($_item->getId()) ?>" />
                                                 <label class="label" for="related-checkbox<?= $block->escapeHtmlAttr($_item->getId()) ?>"><span><?= $block->escapeHtml(__('Add to Cart')) ?></span></label>
@@ -212,16 +218,16 @@ switch ($type = $block->getType()) {
                                         <?php endif; ?>
                                     <?php endif; ?>
 
-                                    <?php if ($showAddTo || $showCart) :?>
+                                    <?php if ($showAddTo || $showCart):?>
                                         <div class="product actions product-item-actions">
-                                            <?php if ($showCart) :?>
+                                            <?php if ($showCart):?>
                                                 <div class="actions-primary">
-                                                    <?php if ($_item->isSaleable()) :?>
-                                                        <?php if ($_item->getTypeInstance()->hasRequiredOptions($_item)) :?>
+                                                    <?php if ($_item->isSaleable()):?>
+                                                        <?php if ($_item->getTypeInstance()->hasRequiredOptions($_item)):?>
                                                             <button class="action tocart primary" data-mage-init='{"redirectUrl": {"url": "<?= $block->escapeUrl($block->getAddToCartUrl($_item)) ?>"}}' type="button" title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>">
                                                                 <span><?= $block->escapeHtml(__('Add to Cart')) ?></span>
                                                             </button>
-                                                        <?php else :?>
+                                                        <?php else:?>
                                                             <?php $postDataHelper = $this->helper(Magento\Framework\Data\Helper\PostHelper::class);
                                                             $postData = $postDataHelper->getPostData($block->escapeUrl($block->getAddToCartUrl($_item)), ['product' => $_item->getEntityId()])
                                                             ?>
@@ -231,19 +237,19 @@ switch ($type = $block->getType()) {
                                                                 <span><?= $block->escapeHtml(__('Add to Cart')) ?></span>
                                                             </button>
                                                         <?php endif; ?>
-                                                    <?php else :?>
-                                                        <?php if ($_item->getIsSalable()) :?>
+                                                    <?php else:?>
+                                                        <?php if ($_item->getIsSalable()):?>
                                                             <div class="stock available"><span><?= $block->escapeHtml(__('In stock')) ?></span></div>
-                                                        <?php else :?>
+                                                        <?php else:?>
                                                             <div class="stock unavailable"><span><?= $block->escapeHtml(__('Out of stock')) ?></span></div>
                                                         <?php endif; ?>
                                                     <?php endif; ?>
                                                 </div>
                                             <?php endif; ?>
 
-                                            <?php if ($showAddTo) :?>
+                                            <?php if ($showAddTo):?>
                                                 <div class="secondary-addto-links actions-secondary" data-role="add-to-links">
-                                                    <?php if ($addToBlock = $block->getChildBlock('addto')) :?>
+                                                    <?php if ($addToBlock = $block->getChildBlock('addto')):?>
                                                         <?= $addToBlock->setProduct($_item)->getChildHtml() ?>
                                                     <?php endif; ?>
                                                 </div>
diff -Nuar a/vendor/magento/module-catalog/view/frontend/web/js/related-products.js b/vendor/magento/module-catalog/view/frontend/web/js/related-products.js
index c875dd8f5d2..bc54fdeac5c 100644
--- a/vendor/magento/module-catalog/view/frontend/web/js/related-products.js
+++ b/vendor/magento/module-catalog/view/frontend/web/js/related-products.js
@@ -28,10 +28,14 @@ define([
         _create: function () {
             $(this.options.selectAllLink, this.element).on('click', $.proxy(this._selectAllRelated, this));
             $(this.options.relatedCheckbox, this.element).on('click', $.proxy(this._addRelatedToProduct, this));
+
+            if (this.element.data('shuffle')) {
+                this._shuffle(this.element.find(this.options.elementsSelector));
+            }
             this._showRelatedProducts(
                 this.element.find(this.options.elementsSelector),
                 this.element.data('limit'),
-                this.element.data('shuffle')
+                this.element.data('shuffle-weighted')
             );
         },
 
@@ -69,24 +73,66 @@ define([
             );
         },
 
+        /* jscs:disable */
+        /* eslint-disable */
         /**
          * Show related products according to limit. Shuffle if needed.
          * @param {*} elements
          * @param {*} limit
-         * @param {*} shuffle
+         * @param weightedRandom
          * @private
          */
-        _showRelatedProducts: function (elements, limit, shuffle) {
-            var index;
-
-            if (shuffle) {
-                this._shuffle(elements);
-            }
+        _showRelatedProducts: function (elements, limit, weightedRandom) {
+            var index, weights = [], random = [], weight = 2, shown = 0, $element, currentGroup, prevGroup;
 
             if (limit === 0) {
                 limit = elements.length;
             }
 
+            if (weightedRandom && limit > 0 && limit < elements.length) {
+                for (index = 0; index < limit; index++) {
+                    $element = $(elements[index]);
+                    if ($element.data('shuffle-group') !== '') {
+                        break;
+                    }
+                    $element.show();
+                    shown++;
+                }
+                limit -= shown;
+                for (index = elements.length - 1; index >= 0; index--) {
+                    $element = $(elements[index]);
+                    currentGroup = $element.data('shuffle-group');
+                    if (currentGroup !== '') {
+                        weights.push([index, Math.log(weight)]);
+                        if (typeof prevGroup !== 'undefined' && prevGroup !== currentGroup) {
+                            weight += 2;
+                        }
+                        prevGroup = currentGroup;
+                    }
+                }
+
+                if (weights.length === 0) {
+                    return;
+                }
+
+                for (index = 0; index < weights.length; index++) {
+                    random.push([weights[index][0], Math.pow(Math.random(), 1 / weights[index][1])]);
+                }
+
+                random.sort(function(a, b) {
+                    a = a[1];
+                    b = b[1];
+                    return a < b ? 1 : (a > b ? -1 : 0);
+                });
+                index = 0;
+                while (limit) {
+                    $(elements[random[index][0]]).show();
+                    limit--;
+                    index++
+                }
+                return;
+            }
+
             for (index = 0; index < limit; index++) {
                 $(elements[index]).show();
             }
@@ -96,12 +142,19 @@ define([
         /* eslint-disable */
         /**
          * Shuffle an array
-         * @param {Array} o
+         * @param {Array} elements
          * @returns {*}
          */
-        _shuffle: function shuffle(o) { //v1.0
-            for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
-            return o;
+        _shuffle: function shuffle(elements) {
+            var parent, child, lastSibling;
+            if (elements.length) {
+                parent = $(elements[0]).parent();
+            }
+            while (elements.length) {
+                child = elements.splice(Math.floor(Math.random() *  elements.length), 1)[0];
+                lastSibling = parent.find('[data-shuffle-group="' + $(child).data('shuffle-group') + '"]').last();
+                lastSibling.after(child);
+            }
         }
 
         /* jscs:disable */
diff -Nuar a/vendor/magento/module-catalog/view/frontend/web/js/upsell-products.js b/vendor/magento/module-catalog/view/frontend/web/js/upsell-products.js
index da2526d5679..867011d3d16 100644
--- a/vendor/magento/module-catalog/view/frontend/web/js/upsell-products.js
+++ b/vendor/magento/module-catalog/view/frontend/web/js/upsell-products.js
@@ -19,33 +19,78 @@ define([
          * @private
          */
         _create: function () {
+            if (this.element.data('shuffle')) {
+                this._shuffle(this.element.find(this.options.elementsSelector));
+            }
             this._showUpsellProducts(
                 this.element.find(this.options.elementsSelector),
                 this.element.data('limit'),
-                this.element.data('shuffle')
+                this.element.data('shuffle-weighted')
             );
         },
 
+        /* jscs:disable */
+        /* eslint-disable */
         /**
          * Show upsell products according to limit. Shuffle if needed.
          * @param {*} elements
          * @param {Number} limit
-         * @param {Boolean} shuffle
+         * @param {Boolean} weightedRandom
          * @private
          */
-        _showUpsellProducts: function (elements, limit, shuffle) {
-            var index;
-
-            if (shuffle) {
-                this._shuffle(elements);
-            }
+        _showUpsellProducts: function (elements, limit, weightedRandom) {
+            var index, weights = [], random = [], weight = 2, shown = 0, $element, currentGroup, prevGroup;
 
             if (limit === 0) {
                 limit = elements.length;
             }
 
+            if (weightedRandom && limit > 0 && limit < elements.length) {
+                for (index = 0; index < limit; index++) {
+                    $element = $(elements[index]);
+                    if ($element.data('shuffle-group') !== '') {
+                        break;
+                    }
+                    $element.show();
+                    shown++;
+                }
+                limit -= shown;
+                for (index = elements.length - 1; index >= 0; index--) {
+                    $element = $(elements[index]);
+                    currentGroup = $element.data('shuffle-group');
+                    if (currentGroup !== '') {
+                        weights.push([index, Math.log(weight)]);
+                        if (typeof prevGroup !== 'undefined' && prevGroup !== currentGroup) {
+                            weight += 2;
+                        }
+                        prevGroup = currentGroup;
+                    }
+                }
+
+                if (weights.length === 0) {
+                    return;
+                }
+
+                for (index = 0; index < weights.length; index++) {
+                    random.push([weights[index][0], Math.pow(Math.random(), 1 / weights[index][1])]);
+                }
+
+                random.sort(function(a, b) {
+                    a = a[1];
+                    b = b[1];
+                    return a < b ? 1 : (a > b ? -1 : 0);
+                });
+                index = 0;
+                while (limit) {
+                    $(elements[random[index][0]]).show();
+                    limit--;
+                    index++
+                }
+                return;
+            }
+
             for (index = 0; index < limit; index++) {
-                $(this.element).find(elements[index]).show();
+                $(elements[index]).show();
             }
         },
 
@@ -53,12 +98,19 @@ define([
         /* eslint-disable */
         /**
          * Shuffle an array
-         * @param o
+         * @param elements
          * @returns {*}
          */
-        _shuffle: function shuffle(o){ //v1.0
-            for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
-            return o;
+        _shuffle: function shuffle(elements){ //v1.0
+            var parent, child, lastSibling;
+            if (elements.length) {
+                parent = $(elements[0]).parent();
+            }
+            while (elements.length) {
+                child = elements.splice(Math.floor(Math.random() *  elements.length), 1)[0];
+                lastSibling = parent.find('[data-shuffle-group="' + $(child).data('shuffle-group') + '"]').last();
+                lastSibling.after(child);
+            }
         }
 
         /* jscs:disable */
