List 数据类型


list类型是用来存储多个有序的字符串的,列表当中的每一个字符看做一个元素,一个列表当中可以存储有一个或者多个元素,redis的list支持存储2^32次方-1个元素。redis可以从列表的两端进行插入(pubsh)和弹出(pop)元素,支持读取指定范围的元素集,或者读取指定下标的元素等操作。redis列表是一种比较灵活的链表数据结构,它可以充当队列或者栈的角色。

Redis列表是链表型的数据结构,所以它的元素是有序的,而且列表内的元素是可以重复的。意味着它可以根据链表的下标获取指定的元素和某个范围内的元素集。


常用命令


lpush 命令

描述:将一个或多个值插入到列表头部。 如果 key 不存在,则创建list,然后再插入数据操作。 当 key存在但不是列表类型时,返回一个错误。

命令使用:[lpush 命令] [key名称] [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 命令

描述:与 lpush 同理,将一个或多个元素从 list 的尾部插入。

命令使用:[rpush 命令] [key名称] [value值]

				[命令][key名称][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 命令

描述:redis的list是链表结构所以BLPOP命令正是取出列表的第一个元素,如果list当中没有没有元素,会一直等待到超时,或者发现有数据为止。

命令使用:[blpop 命令] [key名称] [过期时间 / 秒]

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是指定10秒内返回,假如10秒没有可返回的数据,就返回nil。


brpop 命令

描述:与 blpop 同理,将 list 列表的最后一个元素移出。

命令使用:[brpop 命令] [key名称] [过期时间 / 秒]

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 命令

描述:指的是在 list 列表的某一个元素前或者后插入另外一个元素。当指的元素不存在时,不执行任何动作。如果列表不存在时,视为空列表,不执行任何动作。

命令使用:[linsert 命令] [key名称] [before 之前加入 / after 之后加入] [在哪个值的前后加入] [加入的 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> 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 命令

描述:用于通过链表的下标索引获取列表中的元素。这里的下标也可以是负数表示 list 的最后一个元素,-2表示倒数第二个元素。

命令使用:[lindex 命令] [key名称] [下标索引]

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 命令

描述:用于返回 list 列表的长度。 假如 list 不存在,则 list 被解释为一个空列表,返回 0 。

命令使用:[llen 命令] [key名称]

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

lrange 命令

描述:于返回指定 list 区间内的元素。区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2表示列表的倒数第二个元素,以此类推。

命令使用:[lrange 命令] [key名称] [开始的下标索引] [结束的下标索引]

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 数据类型应用场景


一、队列、秒杀抢购

list类型的lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能,故而可以用Redis的list类型实现简单的点对点的消息队列。不过我不推荐在实战中这么使用,因为现在已经有Kafka、NSQ、RabbitMQ等成熟的消息队列了,它们的功能已经很完善了,除非是为了更深入地理解消息队列,不然我觉得没必要去重复造轮子。


数据表

秒杀商品表(product_seckill):

CREATE TABLE `product_seckill` (
	`id` int NOT NULL, --主键id
	`product_id` int NOT NULL, --商品id
	`start_at` datetime DEFAULT NULL, --活动开始时间
	`stop_at` datetime DEFAULT NULL, --活动结束时间
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

商品表(product):

CREATE TABLE `products` (
	`id` int NOT NULL AUTO_INCREMENT, --主键id
	`title` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, --商品标题
	`category_id` int DEFAULT NULL, --商品分类id
	`status` tinyint DEFAULT NULL, --商品状态,0:未上架,1:上架
	`type` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,--商品类型
	`shop_id` int DEFAULT NULL, --店铺id
	`stock` int DEFAULT NULL, --库存
	`rating` int DEFAULT NULL, --浏览量
	`sold_count` int DEFAULT NULL, --销量
	`review_count` int DEFAULT NULL, --
	`price` decimal(10,2) DEFAULT NULL, --价格
	`image` varchar(100) DEFAULT NULL, --图片路径
	`create_at` datetime DEFAULT NULL, --新增时间
	`updated_at` datetime DEFAULT NULL, --修改时间
	PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

订单表(order):

CREATE TABLE `orders` (
	`id` bigint unsigned NOT NULL AUTO_INCREMENT, --主键id
	`no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, --订单编号
	`user_id` bigint unsigned NOT NULL, --用户id
	`address` text COLLATE utf8mb4_unicode_ci NOT NULL, --详细地址
	`total_amount` decimal(10,2) NOT NULL, --订单总金额
	`remark` text COLLATE utf8mb4_unicode_ci, --备注
	`paid_at` datetime DEFAULT NULL, --支付时间
	`payment_method` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --支付类型
	`payment_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --支付编号
	`refund_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending', --物流状态
	`refund_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --物流编号
	`closed` tinyint(1) NOT NULL DEFAULT '0', --订单关闭
	`reviewed` tinyint(1) NOT NULL DEFAULT '0', --订单评价
	`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_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

业务场景

对于请求的数据进行验证以及查看商品库存:

<?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('该商品不存在');
					}
					
					if ($product->type !== Product::TYPE_SECKILL) {
						return $fail('该商品不支持秒杀');
					}
					
					if ($product->seckill->is_before_start) {
						return $fail('秒杀尚未开始');
					}
					
					if ($product->seckill->is_after_end) {
						return $fail('秒杀已经结束');
					}
					
					if (!$product->status) {
						return $fail('该商品未上架');
					}
					
					if ($product->stock < 1) {
						return $fail('该商品已售完');
					}
					
					if ($order = Order::query()
						// 筛选出当前用户的订单
						->where('user_id', $this->post('userId'))
						->whereHas('items', function ($query) use ($value) {
						// 筛选出包含当前 SKU 的订单
						$query->where('product_id', $value);
						})
						->where(function ($query) {
						// 已支付的订单
						$query->whereNotNull('paid_at')
						// 或者未关闭的订单
						->orWhere('closed', false);
						})
						->first()) {
						if ($order->paid_at) {
							return $fail('你已经抢购了该商品');
						}
						return $fail('你已经下单了该商品,请到订单页面支付');
					}
				},
		],
	];
	}
}
?>

对于活动下单时的处理:

<?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) {

            // 更新此地址的最后使用时间
            $address->update(['last_used_at' => Carbon::now()]);

            // 扣减对应 SKU 库存
            if ($product->decreaseStock(1) <= 0) {
                throw new \Exception('该商品库存不足');
            }

            // 创建一个订单
            $order = new Order([
                'address' => [ // 将地址信息放入订单中
                    '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()
            ]);

            // 订单关联到当前用户
            $order->user()->associate($user);

            // 写入数据库
            $order->save();

            // 创建一个新的订单项并与 SKU 关联
            $item = $order->items()->make([
                'amount' => 1, // 秒杀商品只能一份
                'price' => $product->price,
            ]);

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

            $item->save();

            return $order;

        });

        // 秒杀订单的自动关闭时间与普通订单不同
        dispatch(new CloseOrder($order, config('app.seckill_order_ttl')));

        if ($order){

            return ["status" => true,"message" => "下单成功,请到订单页面支付"];

        }else{

            return ["status" => false,"message" => "秒杀异常"];

        }

    }
    
}
?>



二、排行榜


list 类型的 lrange 命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在 list 类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等。

但是,并不是所有的排行榜都能用 list 类型实现,只有定时计算的排行榜才适合使用 list 类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list 类型不能支持实时计算的排行榜,之后在介绍有序集合 sortedset 的应用场景时会详细介绍实时计算的排行榜的实现。




Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐