实战:前端如何使用Redis实现一个双11的领券功能

1,907 阅读3分钟

业务场景

双11来了,漫天飞的优惠券怎么实现?

于是我们有了这样一个需求:某商家发优惠券,扣门,就发 10 张,参与的用户假设就 1000 人,假设有 50 人同时领取,那如何保证这个并发量下不会超领取呢?


实现方式

使用Redis计数器,这只是其应用场景之一,其中将用到三个命令:

  • exists:判断传入的key是否存在
  • setnx:设置值
  • incr:实现计数功能

手撸代码

代码不是很多,建议自己动手实践下,有注释

这里用到的是ioredis,地址github.com/luin/ioredi…,可以进去瞅一瞅

新建个文件夹就叫ioredis吧,然后里面新增两个JS文件,分别为luck.jsapp.js,上代码

// luck.js
// 引入Redis
// 这里是要先npm安装的 => npm i ioredis
const Redis = require('ioredis')
const redis = new Redis(6379, '127.0.0.1')

// 将日志写入指定文件,也就是抽中券的和没抽中券的请求一个记录
const fs = require('fs')
const { Console } = require('console')

const output = fs.createWriteStream('./stdout.log') // 抽中券的到这里来
const errorOutput = fs.createWriteStream('./stderr.log') // 没抽中券的到这里来
const logger = new Console(output, errorOutput)

async function luck() {
  const count = 10
  const key = 'counter:luck'
  const keyExists = await redis.exists(key) // 判断key是否存在

  // key不存在则初始化设置
  if(!keyExists) {
    await redis.setnx(key, 0)
  }

  // 每发送一次领取请求,采用 incr 命令进行自增,由于 Redis 单线程的原因,可以保证原子性,不会出现超领。
  const result = await redis.incr(key)

  console.log(`result: ${result}`)

  if(result > count) { //领取超限
    logger.error('luck failure', result)

    return
  }

  logger.info('luck success', result)
}

module.exports = luck

然后app.js

/* 起一个简单的服务,使得浏览器可以访问127.0.0.1:8000/luck接口,
代替一个领取优惠券的操作
*/

const luck = require('./luck')

const http = require('http')
const url = require('url')
const qs = require('querystring')

// 这个花里胡哨的DATA完全可以不要的,因为我们只需要起一个简单的服务
const DATA = userId => ({
  code: 0,
  success: true,
  data: {
    userId,
    name: '2oops',
    descripttion: 'go ahead',
    date: new Date()
  }
})
// 其实这里是一个比较标准的请求格式
http.createServer((req, res) => {
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
  const reqUrl = url.parse(req.url);
  if(reqUrl.pathname === '/luck') {
    const uid = qs.parse(reqUrl.query).userId;
    const RESULT = JSON.stringify(DATA(uid));
    luck() // 主要在这里调用就阔以,其他的都是耍流氓
    res.end(RESULT)
  } else {
    res.writeHead(404);
    res.end("Not Found")
  }
}).listen(8000, () => {
  console.log('listening at port: 8000')
})

接下来根目录下运行node app,这时候你应该会收到这样一个报错,是说没有起Redis server服务

这里给出问题的解决方法,点这里,再附上windows环境启动不了redis server服务的解决方法,亲测有效,windows下redis服务的安装

接下来,我们再运行app.js,可以在浏览器看到

而且当我们访问这个地址的时候,incr命令已经执行了一次计数操作


并发压测

最后一步,使用apchebench做一个并发压力测试,安装教程放这里,(其实这里遇见了一个注册服务出现(OS 5)拒绝访问的问题),按照教程配置好httpd.conf文件后,bin目录下运行

  • .\httpd.exe -k install
  • .\httpd.exe -k start
  • ab -c 50 -n 100 http://127.0.0.1:8000/luck (这里放只100个请求数)

最后我们会看到这个

最终可以看到stdout.logstderr.log文件下分别写入了10个抽中券的记录和40个未抽中券的记录。


总结

Redis的应用场景很多,这里的计数器只是一种,除了缓存用的比较多之外,还可以实现消息队列,Session存储,发布订阅等。

参考链接