Redis List data type, structure and application scenario

Keywords: Redis

List data type


List type is used to store multiple ordered strings. Each character in the list is regarded as an element. One or more elements can be stored in a list. redis's list supports storing 2 ^ 32 power - 1 element. redis can insert (pubsh) and pop-up (POP) elements from both ends of the list. It supports reading the element set of the specified range or reading the elements of the specified subscript. redis list is a flexible linked list data structure, which can act as a queue or stack.

Redis list is a linked list data structure, so its elements are orderly, and the elements in the list can be repeated. It means that it can get the specified element and the element set in a range according to the subscript of the linked list.


Common commands


lpush command

Description: inserts one or more values into the list header. If the key does not exist, create a list, and then insert the data operation. When the key exists but is not a list type, an error is returned.

Command use: [lpush command] [key name] [value value]

127.0.0.1:6379> lpush ranking mysql
(integer) 1
127.0.0.1:6379> lpush ranking redis
(integer) 2
127.0.0.1:6379>

rpush command

Description: in the same way as lpush, insert one or more elements from the tail of the list.

Command use: [rpush command] [key name] [value value]

				[command][key name][value value]
127.0.0.1:6379> rpush ranking html
(integer) 3
127.0.0.1:6379> rpush ranking php
(integer) 4
127.0.0.1:6379> 

blpop command

Description: redis's list is a linked list structure, so the BLPOP command is the first element of the list. If there is no element in the list, it will wait until timeout or data is found.

Command usage: [blpop command] [key name] [expiration time / sec]

127.0.0.1:6379> blpop ranking 10
1) "zz"
2) "redis"
127.0.0.1:6379> blpop ranking 10
1) "zz"
2) "mysql"
127.0.0.1:6379> blpop ranking 10
1) "zz"
2) "html"
127.0.0.1:6379> blpop ranking 10
1) "zz"
2) "php"
127.0.0.1:6379> blpop ranking 10
(nil)
(10.06s)
127.0.0.1:6379> 

10 is to return within 10 seconds. If there is no data to return within 10 seconds, nil will be returned.


brpop command

Description: in the same way as blpop, move the last element of the list out of the list.

Command usage: [brpop command] [key name] [expiration time / sec]

127.0.0.1:6379> lpush zz redis
(integer) 1
127.0.0.1:6379> lpush zz mysql
(integer) 2
127.0.0.1:6379> lpush zz php
(integer) 3
127.0.0.1:6379> brpop zz 10
1) "zz"
2) "redis"
127.0.0.1:6379> brpop zz 10
1) "zz"
2) "mysql"
127.0.0.1:6379> brpop zz 10
1) "zz"
2) "php"
127.0.0.1:6379> brpop zz 10
(nil)
(10.10s)
127.0.0.1:6379> 

linsert command

Description: refers to inserting another element before or after an element in the list. When the element referred to does not exist, no action is performed. If the list does not exist, it is regarded as an empty list and no action is performed.

Command use: [linsert command] [key name] [add before / after] [add before and after] [add before and after] [add value added]

127.0.0.1:6379> lpush ranking mysql
(integer) 1
127.0.0.1:6379> lpush ranking redis
(integer) 2
127.0.0.1:6379> lpush ranking php
(integer) 3
127.0.0.1:6379> lrange ranking 0 10
1) "php"
2) "redis"
3) "mysql"
127.0.0.1:6379> linsert ranking before redis java
(integer) 4
127.0.0.1:6379> linsert ranking after redis html
(integer) 5
127.0.0.1:6379> lrange ranking 0 10
1) "php"
2) "java"
3) "redis"
4) "html"
5) "mysql"
127.0.0.1:6379>

lindex command

Description: used to obtain the elements in the list through the subscript index of the linked list. The subscript here can also be a negative number, indicating the last element of the list, and - 2 indicating the penultimate element.

Command use: [lindex command] [key name] [subscript index]

127.0.0.1:6379> lrange ranking 0 10
1) "php"
2) "java"
3) "redis"
4) "html"
5) "mysql"
127.0.0.1:6379> lindex ranking 1
"java"
127.0.0.1:6379> lindex ranking 2
"redis"
127.0.0.1:6379> lindex ranking 0
"php"
127.0.0.1:6379> 

llen command

Description: used to return the length of the list. If the list does not exist, the list is interpreted as an empty list and returns 0.

Command use: [llen command] [key name]

127.0.0.1:6379> llen ranking
(integer) 5
127.0.0.1:6379> 

lrange command

Description: returns the elements in the specified list interval. The interval is specified by offset START and END. Where 0 represents the first element of the list, 1 represents the second element of the list, and so on. You can also use negative subscripts, with - 1 for the last element of the list, - 2 for the penultimate element of the list, and so on.

Command use: [lrange command] [key name] [starting index] [ending index]

127.0.0.1:6379> lrange ranking 0 10
1) "php"
2) "java"
3) "redis"
4) "html"
5) "mysql"
127.0.0.1:6379>


List data type application scenario


1, Queue, second kill and rush purchase

List type lpop and rpush (or conversely, lpush and rpop) can realize the function of queue, so Redis's list type can be used to realize simple point-to-point message queue. However, I don't recommend using it in actual combat, because there are mature message queues such as Kafka, NSQ and RabbitMQ, and their functions have been perfect. Unless it is for a deeper understanding of message queues, I don't think it's necessary to build wheels again.


data sheet

Stopwatch (product)_ seckill):

CREATE TABLE `product_seckill` (
	`id` int NOT NULL, --Primary key id
	`product_id` int NOT NULL, --commodity id
	`start_at` datetime DEFAULT NULL, --Activity start time
	`stop_at` datetime DEFAULT NULL, --Activity end time
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

product table:

CREATE TABLE `products` (
	`id` int NOT NULL AUTO_INCREMENT, --Primary key id
	`title` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, --Product title
	`category_id` int DEFAULT NULL, --Commodity classification id
	`status` tinyint DEFAULT NULL, --Commodity status, 0: not on the shelf, 1: on the shelf
	`type` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,--Commodity type
	`shop_id` int DEFAULT NULL, --shop id
	`stock` int DEFAULT NULL, --stock
	`rating` int DEFAULT NULL, --Views
	`sold_count` int DEFAULT NULL, --sales volume
	`review_count` int DEFAULT NULL, --
	`price` decimal(10,2) DEFAULT NULL, --Price
	`image` varchar(100) DEFAULT NULL, --Picture path
	`create_at` datetime DEFAULT NULL, --New time
	`updated_at` datetime DEFAULT NULL, --Modification time
	PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

order table:

CREATE TABLE `orders` (
	`id` bigint unsigned NOT NULL AUTO_INCREMENT, --Primary key id
	`no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, --Order No
	`user_id` bigint unsigned NOT NULL, --user id
	`address` text COLLATE utf8mb4_unicode_ci NOT NULL, --Detailed address
	`total_amount` decimal(10,2) NOT NULL, --Total order amount
	`remark` text COLLATE utf8mb4_unicode_ci, --remarks
	`paid_at` datetime DEFAULT NULL, --Payment time
	`payment_method` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --Payment type
	`payment_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --Payment No
	`refund_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending', --Logistics status
	`refund_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --Logistics number
	`closed` tinyint(1) NOT NULL DEFAULT '0', --Order closing
	`reviewed` tinyint(1) NOT NULL DEFAULT '0', --Order evaluation
	`ship_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending', --
	`ship_data` text COLLATE utf8mb4_unicode_ci,
	`extra` text COLLATE utf8mb4_unicode_ci,
	`created_at` timestamp NULL DEFAULT NULL,
	`updated_at` timestamp NULL DEFAULT NULL,
	PRIMARY KEY (`id`),
	UNIQUE KEY `orders_no_unique` (`no`),
	UNIQUE KEY `orders_refund_no_unique` (`refund_no`),
	KEY `orders_user_id_foreign` (`user_id`),
	CONSTRAINT `orders_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=420 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

Order details_ items):

CREATE TABLE `order_items` (
	`id` bigint unsigned NOT NULL AUTO_INCREMENT,
	`order_id` bigint unsigned NOT NULL,
	`product_id` bigint unsigned NOT NULL,
	`amount` int unsigned NOT NULL,
	`price` decimal(10,2) NOT NULL,
	`rating` int unsigned DEFAULT NULL,
	`review` text COLLATE utf8mb4_unicode_ci,
	`reviewed_at` timestamp NULL DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `order_items_order_id_foreign` (`order_id`),
	CONSTRAINT `order_items_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=419 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

Business scenario

Verify the requested data and view the inventory of goods:

<?php

namespace App\Http\Requests;
use App\Models\Order;
use App\Models\Product;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class SeckillOrderRequest extends FormRequest
{
	public function rules()
	{
		return [
			'product_id' => [
				'required',
				function ($attribute, $value, $fail) {
				
					if (!$product = Product::find($value)) {
						return $fail('The item does not exist');
					}
					
					if ($product->type !== Product::TYPE_SECKILL) {
						return $fail('This product does not support second kill');
					}
					
					if ($product->seckill->is_before_start) {
						return $fail('The second kill has not yet started');
					}
					
					if ($product->seckill->is_after_end) {
						return $fail('The second kill is over');
					}
					
					if (!$product->status) {
						return $fail('The product is not on the shelf');
					}
					
					if ($product->stock < 1) {
						return $fail('The product has been sold out');
					}
					
					if ($order = Order::query()
						// Filter out the orders of the current user
						->where('user_id', $this->post('userId'))
						->whereHas('items', function ($query) use ($value) {
						// Filter out orders containing the current SKU
						$query->where('product_id', $value);
						})
						->where(function ($query) {
						// Paid orders
						$query->whereNotNull('paid_at')
						// Or unclosed orders
						->orWhere('closed', false);
						})
						->first()) {
						if ($order->paid_at) {
							return $fail('You have snapped up the product');
						}
						return $fail('You have placed an order for this product. Please pay on the order page');
					}
				},
		],
	];
	}
}
?>

For the processing of event orders:

<?php

namespace App\Services;

use App\Models\Order;
use App\Models\User;
use App\Models\UserAddress;
use App\Models\Product;
use Carbon\Carbon;
use App\Jobs\CloseOrder;

class OrderService
{
    public function seckill(User $user,UserAddress $address, Product $product)
    {

        $order = \DB::transaction(function () use ($user,$address, $product) {

            // Update the last use time of this address
            $address->update(['last_used_at' => Carbon::now()]);

            // Deduct corresponding SKU inventory
            if ($product->decreaseStock(1) <= 0) {
                throw new \Exception('The item is out of stock');
            }

            // Create an order
            $order = new Order([
                'address' => [ // Put the address information into the order
                    'address' => $address->full_address,
                    'zip' => $address->zip,
                    'contact_name' => $address->contact_name,
                    'contact_phone' => $address->contact_phone,
                ],
                'remark' => '',
                'total_amount' => $product->price,
                'type' => Order::TYPE_SECKILL,
                'paid_at' => Carbon::now()
            ]);

            // The order is associated with the current user
            $order->user()->associate($user);

            // Write to database
            $order->save();

            // Create a new order item and associate it with the SKU
            $item = $order->items()->make([
                'amount' => 1, // Only one second kill commodity can be used
                'price' => $product->price,
            ]);

            $item->product()->associate($product->id);

            $item->save();

            return $order;

        });

        // The automatic closing time of second kill order is different from that of ordinary order
        dispatch(new CloseOrder($order, config('app.seckill_order_ttl')));

        if ($order){

            return ["status" => true,"message" => "Order successfully, please pay on the order page"];

        }else{

            return ["status" => false,"message" => "Spike exception"];

        }

    }
    
}
?>



2, Ranking list


The lrange command of list type can view the data in the queue in pages. The ranking list calculated every once in a while can be stored in the list type, such as Jingdong's daily mobile phone sales ranking, the score ranking of students in each monthly test of the school, the anchor ranking of the year-end festival of fighting fish, etc.

However, not all leaderboards can be implemented with list type. Only regularly calculated leaderboards can be stored with list type. The leaderboards corresponding to regularly calculated leaderboards are real-time calculated leaderboards. List type cannot support real-time calculated leaderboards, After that, when introducing the application scenario of ordered set sortedset, the implementation of leaderboard for real-time calculation will be introduced in detail.




Posted by krishnam1981 on Wed, 08 Sep 2021 00:19:14 -0700