高并发缓存队列防止溢出解决方案

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,倚天版 1GB 1个月
简介: 高并发缓存队列防止溢出解决方案

1 背景介绍

并发量非常大的系统,例如秒杀、抢红包、抢票等操作,都是存在溢出现象,比如秒杀超卖、抢红包超额、一票多单等溢出现象,如果采用数据库锁来控制溢出问题,效率非常低,在高并发场景下,很有可能直接导致数据库崩溃,因此针对高并发场景下数据溢出解决方案我们可以采用Redis缓存提升效率。


1.1 设计分析微信抢红包

0a7e5c5d64d6421d8b358d635544bc36.png

用户抢红包的时候,我们会分批次发放红包,每次发放红包会先根据发放红包的金额和红包的个数把每个红包计算好,然后存入到Redis队列中,存入到Redis队列后,用户每次抢红包都直接从Redis队列中获取一个红包即可,由于Redis是单线程,在这里可以有效的避免多个人同时抢到了一个红包,类似一种超卖现象。


表结构设计:

CREATE TABLE `money_package` (
`id` int(11) NOT NULL COMMENT '主键ID',
`money` int(11) NOT NULL COMMENT '红包总金额',
`count` int(11) NOT NULL COMMENT '红包数量',
`sort` int(2) DEFAULT NULL COMMENT '发红包顺序',
`type` int(11) DEFAULT '1' COMMENT '红包发放类型 1:延时发放 2:立即发放',
`hasload` int(1) DEFAULT '1' COMMENT '加载位置 1:未加载 2:加载到程序 3:加载到Redis缓存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.2 红包定时导入缓存队列

抢红包缓存队列溢出控制

上面已经实现将红包存入到Redis缓存队列中,用户每次抢红包的时候,只需要从Redis缓存队列中获取即可。

2 队列术限流

2.1 高并发场景分析

用户抢红包的高并发场景下,如果让后端服务器直接处理所有抢红包操作,服务器很有可能会崩溃,就像高铁站如果很多人蜂拥挤进站而不排队,很有可能导致整个高铁站秩序混乱,高铁站服务崩溃,程序也是如此。

普通模式:

队列术:

解决大量并发用户蜂拥而上的方法可以采用队列术将用户的请求用队列缓存起来,后端服务从队列缓存中有序消费,可以防止后端服务同时面临处理大量请求。缓存用户请求可以用RabbitMQ、Kafka、RocketMQ、ActiveMQ,我们这里采用RabbitMQ即可。


预备知识:MQ引入项目后的优缺点

2.2 队列削峰实战


用户抢红包的时候,我们用Lua脚本实现将用户抢红包的信息以生产者角色将消息发给RabbitMQ,后端应用服务以消费者身份从RabbitMQ获取消息并抢红包,再将抢红包信息以WebSocket方式通知给用户。如果想使用Lua识别用户令牌,我们需要引入 lua-resty-jwt 模块,是用于 ngx_lua 和 LuaJIT 的 Lua 实现库,在该模块能实现Jwt令牌生成、Jwt令牌校验,依赖库的地址: https://github.com/SkyLothar/lua-resty-jwt

3 设计原则

3.1 动静分离

1·后台springboot启动微服务模块

2.静态文件分离,nginx直接响应,不要再绕后台应用机器

3.2 微服务化

1·将模块细粒度拆分,微服务化

2·借助docker swarm的容器管理功能,实现不同服务的副本部署,滚动更新

3·在本项目中,api模块就部署了3份,以适应前端的高并发

3.3 负载均衡

1·多个实例之间通过nginx做负载均衡,提升并发性能

2·本项目为大家展示的模块均部署在1台节点。生产环境涉及多台机器,用upstream实现。

3.4 异步消息

1·中奖后,中奖人及奖品信息要持久化到数据库。引入rabbitmq,将抽奖操作与数据库操作异步隔离。

2·抽奖中奖后,只需要将中奖信息放入rabbitmq,并立即返回中奖信息给前端用户。

3·后端msg模块消费rabbitmq消息,缓慢处理。

3.5 缓存预热

1·每隔1分钟扫描一次活动表,查询未来1分钟内将要开始的活动。

2·将扫到的活动加载进redis,包括活动详细信息,中奖策略信息,奖品信息,抽奖令牌。

3·活动正式开始后,基于redis数据做查询,不必再与数据库打交道。

4 Nginx通过LUA脚本访问RabbitMQ消息队列

配置设置:OpenRestry安装

local strlen =  string.len
local json = require "json"
local rabbitmq = require "rabbitmqstomp"
local mq, err = rabbitmq:new()
if not mq then
      return
end
mq:set_timeout(60000)
local ok, err = mq:connect {
                    host = "127.0.0.1",
                    port = 61613,
                    username = "guest",
                    password = "guest",
                    vhost = "/"
                }
if not ok then
    return
end
ngx.log(ngx.INFO, "Connect: " .. "OK")
local msg = {key="value1", key2="value2"}
local headers = {}
headers["destination"] = "/queue/my_queue"
headers["receipt"] = "msg#1"
headers["app-id"] = "luaresty"
headers["persistent"] = "true"
headers["content-type"] = "application/json"
local ok, err = mq:send(json.encode(msg), headers)
if not ok then
    return
end
ngx.log(ngx.INFO, "Published: " .. json.encode(msg))
local headers = {}
headers["destination"] = "/queue/my_queue"
headers["persistent"] = "true"
headers["id"] = "123"
local ok, err = mq:subscribe(headers)
if not ok then
    return
end
local data, err = mq:receive()
if not data then
    return
end
ngx.log(ngx.INFO, "Consumed: " .. data)
ngx.header.content_type = "text/plain";
ngx.say(data);
local headers = {}
headers["persistent"] = "true"
headers["id"] = "123"
local ok, err = mq:unsubscribe(headers)
local ok, err = mq:set_keepalive(10000, 10000)
if not ok then
    return
end
location /rabbitmq {
            lua_code_cache off;
            content_by_lua_file conf/rabbitmq.lua;
        }
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
27天前
|
缓存 NoSQL 数据库
Redis问题之在高并发场景下,保证Redis缓存和数据库的一致性如何解决
Redis问题之在高并发场景下,保证Redis缓存和数据库的一致性如何解决
|
30天前
|
开发者 Sentinel 微服务
高并发架构设计三大利器:缓存、限流和降级问题之降级策略中的有限状态机的三种状态切换的问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之降级策略中的有限状态机的三种状态切换的问题如何解决
|
30天前
|
监控 应用服务中间件 nginx
高并发架构设计三大利器:缓存、限流和降级问题之Nginx的并发连接数计数的问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Nginx的并发连接数计数的问题如何解决
|
30天前
|
应用服务中间件 nginx 缓存
高并发架构设计三大利器:缓存、限流和降级问题之Nginx作为前置网关进行限流问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Nginx作为前置网关进行限流问题如何解决
|
30天前
|
监控 算法 Java
高并发架构设计三大利器:缓存、限流和降级问题之配置Sentinel的流量控制规则问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之配置Sentinel的流量控制规则问题如何解决
|
30天前
|
监控 Sentinel 缓存
高并发架构设计三大利器:缓存、限流和降级问题之RateLimiter的acquire()方法有什么作用
高并发架构设计三大利器:缓存、限流和降级问题之RateLimiter的acquire()方法有什么作用
|
30天前
|
存储 算法 缓存
高并发架构设计三大利器:缓存、限流和降级问题之使用RateLimiter来限制操作的频率问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之使用RateLimiter来限制操作的频率问题如何解决
|
30天前
|
存储 算法 Java
高并发架构设计三大利器:缓存、限流和降级问题之滑动日志算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之滑动日志算法问题如何解决
|
30天前
|
算法 Java 调度
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
|
30天前
|
缓存 算法 Java
高并发架构设计三大利器:缓存、限流和降级问题之使用代码实现漏桶算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之使用代码实现漏桶算法问题如何解决