php thinkphp5.1 简单模拟redis秒杀实现

    科技2022-07-10  158

    提前准备:

    准备一:redis在tp框架里是做一个cache用的,所以在tp框架里只用了简单的key-value,没有封装一些稍微高级的方法,所以先要自己封装一个redis方法,你也可以直接改cache目录下的Redis类,写个简单的方法,把redis实例暴露出去,用原生的redis方法来解决问题。本案例因为秒杀里只用到了hash类型方法和list类型方法,所以简单的封装下。

    ①,在config里添加一些配置

    进入/application/config/cache.php 添加简单的配置,因为之前做的一个测试是redis读写分离,所以配置上稍微有点奇怪,大家随意看看就好

    <?php return [ // 驱动方式 'type' => 'File', // 缓存保存目录 'path' => '', // 缓存前缀 'prefix' => '', // 缓存有效期 0表示永久缓存 'expire' => 0, 'REDIS_W_HOST' => '127.0.0.1', 'REDID_W_PORT' => '6379', 'REDIS_R_HOST' => '127.0.0.1', 'REDID_R_PORT' => '6379', ];

    ②,在extend/Driver/Cache 里面创建一个Redis类,封装下原装的redis

    <?php namespace Driver\Cache; use Config; class Redis { private static $write_handle = null; private static $read_handle = null; public static function getWrite() { $option = array( 'host' => Config::get('cache.REDIS_W_HOST') ?? '127.0.0.1', 'port' => Config::get('cache.REDIS_W_PORT') ?? 6379, ); if (!self::$write_handle) { self::$write_handle = new \Redis(); self::$write_handle->connect($option['host'], $option['port']); } return self::$write_handle; } public static function getRead() { $option = array( 'host' => Config::get('cache.REDIS_R_HOST') ?? '127.0.0.1', 'port' => Config::get('cache.REDIS_R_PORT') ?? 6379, ); if (!self::$read_handle) { self::$read_handle = new \Redis(); self::$read_handle->connect($option['host'], $option['port']); } return self::$read_handle; } /* * $key 键值 * $value 值 * $expire 过期时间 0是永久 * */ public static function set($key, $value, $expire = 3600) { if (!self::getWrite()) return false; if ($expire == 0) { return self::getWrite()->set($key, $value); } else { return self::getWrite()->setex($key, $expire, $value); } } public static function get($key) { $func = is_array($key) ? 'mget' : 'get'; return self::getRead()->{$func}($key); } //-----Hash类型方法------------------------------------------------------------------------------------------------------------- /* * 自动递增 * $number 步长 * */ public static function incr($key, $number = 1) { if (!self::getWrite()) return false; if ($number == 1) { return self::getWrite()->incr($key); } else { return self::getWrite()->incrBy($key, $number); } } public static function strlen($key) { return self::getRead()->strlen($key); } public static function hset($key, $field, $value) { return self::getWrite()->hSet($key, $field, $value); } public static function hget($key, $field = '') { if (empty($field)) { return self::getRead()->hGetAll($key); } else { return self::getRead()->hGet($key, $field); } } public static function hlen($key) { return self::getRead()->hLen($key); } /* * $number 是步长 * */ public static function hincrby($key, $field, $number = 1) { if (!self::getWrite()) return false; return self::getWrite()->hIncrBy($key, $field, $number); } //-----List类型方法------------------------------------------------------------------------------------------------------------- public static function lpush($key, $value) { if (!self::getWrite()) return false; return self::getWrite()->lPush($key, $value); } public static function lpop($key) { if (!self::getWrite()) return false; return self::getWrite()->lPop($key); } public static function rpop($key) { if (!self::getWrite()) return false; return self::getWrite()->rPop($key); } public static function llen($key) { if (!self::getWrite()) return false; return self::getRead()->lLen($key); } }

    ②安装ab

    测试环境是 Ubuntu

    sudo apt-get install apache2-utils

    方法一:

    先把要秒杀的商品做成一个list,每次秒杀都删掉一个(lpop),当没有可以删掉的商品的时候,算秒杀失败

    1,制作一个商品列表

    // 生成一个商品列表 public function creat_goods_seckill() { $goods_id = 1; // 假设商品id是1 $goods_count = 50; // 假设商品库存为50 $cache_key = 'goods_seckill_list_' . $goods_id; for ($i = 1; $i <= $goods_count; $i++) { Redis::lpush($cache_key, $i); } return json(['info' => 'success', 'info_cn' => '秒杀设置成功', 'goods_id' => $goods_id]); }

    查看redis,50个太长了,只看开头和结尾

    可以看出确实是50个,那么商品列表正常

    2,秒杀方法

    public function miaosha() { $goods_id = 1; // 其实这是从外部接收的,这里偷个懒,直接写死 $cache_key = 'goods_seckill_list_' . $goods_id; // 随机一个用户名,用password_hash生成,没什么意义,password_hash即使是相同的password 也会生成不同的hash值 // 详情可以看官方文档 https://www.php.net/manual/zh/function.password-hash.php $uname = password_hash(time(), PASSWORD_DEFAULT); // 其实这个也是从登陆信息拿的,这里也是模拟下 $user_goods_list_cache_key = 'user_goods_list' . $goods_id; // 秒杀成功用户列表 $goods_top_id = Redis::lpop($cache_key); if ($goods_top_id>0) { Redis::hset($user_goods_list_cache_key,$goods_top_id,$uname); } else { Redis::incr('buy_false'); } }

    3,进行ab测试

    ab -c 100 -n 10000 http://localhost/index/index/miaosha

     

    get buy_false  

     

    可以看到 失败的总数是 9950个

    那成功的呢?

     

    可以看到秒杀成功的是50个,那么9950 + 50 正好是10000个请求,在100并发量下(实际可能更高),redis秒杀是没有问题的 

    Processed: 0.043, SQL: 8