网站首页高可用nginx+lua 基于redis实现分布式锁

    科技2026-04-04  7

    网站首页高可用nginx+lua 基于redis实现分布式锁

    一、网站首页高可用nginx+lua1 lua介绍1.1 lua是什么1.2 lua的安装1.3 快速入门1.4 LUA的基本语法1.4.1 注释1.4.2 关键字1.4.3 定义变量1.4.4 Lua中的数据类型1.4.5 流程控制1.4.6 函数1.4.7 菜鸟学习传送门 二、nginx+lua+redis实现广告缓存2.1 需求分析2.2 OpenResty2.2.1OpenResty介绍2.2.2 OpenResty安装2.2.3 安装nginx 2.3 实现思路2.3.1 表结构分析2.3.2 缓存预热与二级缓存查询 2.4 代码实现2.4.1 缓存预热2.4.2 广告缓存读取2.4.3 二级缓存-加入openresty本地缓存2.4.4 前端页面实现(了解) 三、nginx限流3.1 控制速率3.1.1 漏桶算法实现控制速率限流3.1.2 处理突发流量 四、redis分布式锁4.1环境介绍4.2并发工具4.3事务和同步代码块同一事务中进入两个线程,导致多个线程读取数据不正确集群下产生的问题加了分布式锁之后引发问题---部分商品没成功卖出业务没执行完提前过期释放别人的锁出现问题最终代码

    一、网站首页高可用nginx+lua

    1 lua介绍

    1.1 lua是什么

    University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

    简单来说:

    Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

    lua 语言具有以下特性

    支持面向过程(procedure-oriented)编程和函数式编程(functional programming); 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象; 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持; 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。 应用场景

    游戏开发 独立应用脚本 Web 应用脚本 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench 安全系统,如入侵检测系统 redis中嵌套调用实现类似事务的功能 web容器中应用处理一些过滤 缓存等等的逻辑,例如nginx。

    1.2 lua的安装

    有linux版本的安装也有mac版本的安装。。我们采用linux版本的安装,首先我们准备一个linux虚拟机。

    安装步骤,在linux系统中执行下面的命令。

    yum install -y gcc ​ yum install libtermcap-devel ncurses-devel libevent-devel readline-devel ​ curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz ​ tar -zxf lua-5.3.5.tar.gz ​ cd lua-5.3.5 ​ make linux test ​ make install

    1.3 快速入门

    创建hello.lua文件,内容为

    print(“hello”); 保存。执行命令

    lua helloworld.lua 输出为:

    Hello

    1.4 LUA的基本语法

    lua有交互式编程和脚本式编程。 交互式编程就是直接输入语法,就能执行。 脚本式编程需要编写脚本文件,然后再执行。 一般采用脚本式编程。(例如:编写一个hello.lua的文件,输入文件内容,并执行lua hell.lua即可)

    1.4.1 注释

    单行注释:两个减号是单行注释:

    – 多行注释:

    –[[ 多行注释 多行注释 –]]

    1.4.2 关键字

    关键字就好比java中的 break if else等等一样的效果。lua的关键字如下:

    and break do else elseif end false for function if in local nil not or repeat return then true until while

    1.4.3 定义变量

    全局变量,默认的情况下,定义一个变量都是全局变量,

    如果要用局部变量 需要声明为local.例如:

    – 全局变量赋值 a=1 – 局部变量赋值 local b=2 如果变量没有初始化:则 它的值为nil 这和java中的null不同。

    1.4.4 Lua中的数据类型

    Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。

    Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

    数据类型 描述 nil 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 boolean 包含两个值:false和true。 number 表示双精度类型的实浮点数 string 字符串由一对双引号或单引号来表示 function 由 C 或 Lua 编写的函数 userdata 表示任意存储在变量中的C数据结构 thread 表示执行的独立线路,用于执行协同程序 table Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

    1.4.5 流程控制

    如下:类似于if else

    –[ 0 为 true ] if(0) then print(“0 为 true”) else print(“0 不为true”) end

    1.4.6 函数

    lua中也可以定义函数,类似于java中的方法。例如:

    –[[ 函数返回两个值的最大值 --]] function max(num1, num2) ​ if (num1 > num2) then result = num1; else result = num2; end ​ return result; end – 调用函数 print("两值比较最大值为 ",max(10,4)) print("两值比较最大值为 ",max(5,6)) 执行之后的结果:

    两值比较最大值为 10 两值比较最大值为 6 1.4.7 require 函数 require 用于 引入其他的模块,类似于java中的类要引用别的类的效果。

    用法:

    require “<模块名>”

    1.4.7 菜鸟学习传送门

    二、nginx+lua+redis实现广告缓存

    2.1 需求分析

    需要在页面上显示广告的信息。

    2.2 OpenResty

    2.2.1OpenResty介绍

    OpenResty(又称:ngx_openresty) 是一个基于 NGINX 的可伸缩的 Web 平台,由中国人章亦春发起,提供了很多高质量的第三方模块。

    OpenResty 是一个强大的 Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty可以 快速构造出足以胜任 10K 乃至1000K以上并发连接响应的超高性能 Web 应用系统。

    360,UPYUN,阿里云,新浪,腾讯网,去哪儿网,酷狗音乐等都是 OpenResty 的深度用户。

    OpenResty 简单理解,就相当于封装了nginx,并且集成了LUA脚本,开发人员只需要简单的其提供了模块就可以实现相关的逻辑,而不再像之前,还需要在nginx中自己编写lua的脚本,再进行调用了。

    2.2.2 OpenResty安装

    linux安装openresty:

    1.添加仓库执行命令

    yum install yum-utils ​ yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo 2.执行安装

    yum install openresty 3.安装成功后 会在默认的目录如下:

    /usr/local/openresty

    2.2.3 安装nginx

    默认已经安装好了nginx,在目录:/usr/local/openresty/nginx 下。

    修改/usr/local/openresty/nginx/conf/nginx.conf ,将配置文件使用的根设置为root,目的就是将来要使用lua脚本的时候 ,直接可以加载在root下的lua脚本。

    #user nobody; 配置文件第一行原来为这样, 现改为下面的配置 user root root; 测试访问 http://192.168.200.128

    2.3 实现思路

    2.3.1 表结构分析

    tb_ad (广告表)

    字段名称 字段含义 字段类型 字段长度 备注 id ID INT name 广告名称 VARCHAR position 广告位置 VARCHAR 系统定义 start_time 开始时间 DATETIME end_time 到期时间 DATETIME status 状态 CHAR 0:无效 1:有效 image 图片地址 VARCHAR url URL VARCHAR remarks 备注 VARCHAR web_index_lb 首页轮播图 web_index_amusing 有趣区 web_index_ea_lb 家用电器楼层轮播图 web_index_ea 家用电器楼层广告 web_index_mobile_lb 手机通讯楼层轮播图 web_index_mobile 手机通讯楼层广告

    2.3.2 缓存预热与二级缓存查询

    步骤一:编写lua脚本实现缓存预热(将mysql里的数据查询出来存入redis) 步骤二:编写lua脚本实现二级缓存读取

    2.4 代码实现

    2.4.1 缓存预热

    实现思路:

    定义请求:用于查询数据库中的数据更新到redis中。

    (1)连接mysql ,按照广告分类ID读取广告列表,转换为json字符串。

    (2)连接redis,将广告列表json字符串存入redis 。

    定义请求:

    请求: /ad_update 参数: position --指定广告位置 返回值: json

    在/root/lua目录下创建ad_load.lua ,实现连接mysql 查询数据 并存储到redis中。

    ngx.header.content_type="application/json;charset=utf8" local cjson = require("cjson") local mysql = require("resty.mysql") local uri_args = ngx.req.get_uri_args() local position = uri_args["position"]local db = mysql:new() db:set_timeout(1000) local props = { host = "192.168.200.128", port = 3306, database = "changgou_business", user = "root", password = "root" }local res = db:connect(props) local select_sql = "select url,image from tb_ad where status ='1' and position='"..position.."' and start_time<= NOW() AND end_time>= NOW()" res = db:query(select_sql) db:close()local redis = require("resty.redis") local red = redis:new() red:set_timeout(2000)local ip ="192.168.200.128" local port = 6379 red:connect(ip,port) ​ red:set("ad_"..position,cjson.encode(res)) red:close() ​ ngx.say("{flag:true}")

    修改/usr/local/openresty/nginx/conf/nginx.conf文件:

    代码如下:

    #user nobody; user root root; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid; ​ events { worker_connections 1024; } ​ http { include mime.types; default_type application/octet-stream; sendfile on; #tcp_nopush on;#keepalive_timeout 0; keepalive_timeout 65;#gzip on; ​ server { listen 80; server_name localhost; charset utf-8; #access_log logs/host.access.log main; # 添加 location /ad_update { content_by_lua_file /root/lua/ad_load.lua; } # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }

    重新启动nginx

    测试:http://192.168.200.128/ad_update?position=web_index_lb

    2.4.2 广告缓存读取

    实现思路:

    通过lua脚本直接从redis中获取数据即可。

    定义请求:

    请求:/ad_read 参数:position 返回值:json 在/root/lua目录下创建ad_read.lua

    ​ ngx.header.content_type="application/json;charset=utf8"local uri_args = ngx.req.get_uri_args(); local position = uri_args["position"];local redis = require("resty.redis");local red = redis:new() ​ red:set_timeout(2000)local ok, err = red:connect("192.168.200.128", 6379)local rescontent=red:get("ad_"..position) ​ ngx.say(rescontent) ​ red:close()

    在/usr/local/openresty/nginx/conf/nginx.conf中server下添加配置

    location /ad_read { content_by_lua_file /root/lua/ad_read.lua; }

    测试 http://192.168.200.128/ad_read?position=web_index_lb 输出

    [{"url":"img\/banner1.jpg","image":"img\/banner1.jpg"}, {"url":"img\/banner2.jpg","image":"img\/banner2.jpg"}]

    2.4.3 二级缓存-加入openresty本地缓存

    如上的方式没有问题,但是如果请求都到redis,redis压力也很大,所以我们一般采用多级缓存的方式来减少下游系统的服务压力。

    先查询openresty本地缓存 如果没有再查询redis中的数据

    修改/root/lua目录下ad_read文件, 内容如下:

    ngx.header.content_type="application/json;charset=utf8" local uri_args = ngx.req.get_uri_args(); local position = uri_args["position"]; local cache_ngx = ngx.shared.dis_cache; local adCache = cache_ngx:get('ad_cache_'..position); if adCache == "" or adCache == nil then local redis = require("resty.redis"); local red = redis:new() red:set_timeout(2000) local ok, err = red:connect("192.168.200.128", 6379) local rescontent=red:get("ad_"..position) ngx.say(rescontent) red:close() cache_ngx:set('ad_cache_'..position, rescontent, 10*60); else ngx.say(adCache) end

    修改nginx配置文件vi /usr/local/openresty/nginx/conf/nginx.conf ,http节点下添加配置: #包含redis初始化模块

    lua_shared_dict dis_cache 5m; #共享内存开启

    2.4.4 前端页面实现(了解)

    (1)修改index.html,编写脚本

    <script> new Vue({ el: '#app', data: { ad: { web_index_lb:[] } }, methods: { adRead: function(position) { axios.get('ad_read?position='+position).then(response =>{ this.ad[position]=response.data }) } }, created(){ this.adRead('web_index_lb') } }) </script>

    在页面上添加<div id='app'> ... </div>

    (2)修改index.html,渲染广告轮播图

    <div id="myCarousel" data-ride="carousel" data-interval="4000" class="sui-carousel slide"> <ol class="carousel-indicators"> <li data-target="#myCarousel" data-slide-to="0" class="active" v-for="item in ad.web_index_lb"></li> </ol> <div class="carousel-inner" id="lbt"> <div class="item" v-for="item in contentList"> <a :href="item.url"> <img :src="item.pic" /> </a> </div> </div> <a href="#myCarousel" data-slide="prev" class="carousel-control left"></a> <a href="#myCarousel" data-slide="next" class="carousel-control right"></a> </div>

    (3)上传至服务器并测试

    更改

    加载首页 location / { root html; index index.html index.htm; }

    三、nginx限流

    一般情况下,首页的并发量是比较大的,即使有了多级缓存,如果有大量恶意的请求,也会对系统造成影响。而限流就是保护措施之一。

    nginx提供两种限流的方式:

    一是控制速率 二是控制并发连接数

    3.1 控制速率

    控制速率的方式之一就是采用漏桶算法。

    3.1.1 漏桶算法实现控制速率限流

    漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下: 漏桶算法实现 nginx的配置

    配置示意图如下: 修改/usr/local/openresty/nginx/conf/nginx.conf:

    #user nobody; user root root; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid; ​ ​ events { worker_connections 1024; } ​ ​ http { include mime.types; default_type application/octet-stream;#log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"';#access_log logs/access.log main; ​ sendfile on; #tcp_nopush on;#keepalive_timeout 0; keepalive_timeout 65;#gzip on; ​ limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=2r/s; ​ server { listen 8081; server_name localhost; charset utf-8; location / { limit_req zone=myRateLimit; root html; index index.html index.htm; } } }

    解释:

    binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。 zone:定义共享内存区来存储访问信息, myRateLimit:10m 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。 rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求.我们这里设置成2 方便测试。 测试:重新加载配置文件

    cd /usr/local/openresty/nginx/sbin ./nginx -s reload

    访问页面: 直接报错。拒绝访问。

    3.1.2 处理突发流量

    上面例子限制 2r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。

    例如,如下配置表示:

    server { location / { limit_req zone=myRateLimit burst=5; root html; index index.html index.htm; } }

    burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数,当 rate=2r/s 时,将1s拆成2份,即每500ms可处理1个请求。

    此处,burst=5 ,若同时有6个请求到达,Nginx 会处理第一个请求,剩余5个请求将放入队列,然后每隔500ms从队列中获取一个请求进行处理。若请求数大于6,将拒绝处理多余的请求,直接返回503.

    不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。

    因此,burst 往往结合 nodelay 一起使用。

    例如:如下配置:

    server { location / { limit_req zone=myRateLimit burst=5 nodelay; root html; index index.html index.htm; } }

    如上表示:

    处理突发5个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。

    如上两种配置结合就达到了速率稳定,但突然流量也能正常处理的效果。配置代码如下:

    #user nobody; user root root; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid; ​ ​ events { worker_connections 1024; } ​ ​ http { include mime.types; default_type application/octet-stream;#log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"';#access_log logs/access.log main; ​ sendfile on; #tcp_nopush on;#keepalive_timeout 0; keepalive_timeout 65;#gzip on;# 设置限流配置 limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=2r/s; ​ server { listen 8081; server_name localhost; charset utf-8; location / { limit_req zone=myRateLimit burst = 5 nodelay; root html; index index.html index.htm; } } }

    测试:如下图 在1秒钟之内可以刷新5次,正常处理。

    但是超过之后,连续刷新5次,抛出异常。

    四、redis分布式锁

    4.1环境介绍

    package cn.icode.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; /** * @description: * @author: Baiyu * @modified By: * @version: 1.0 */ @TableName("tb_goods") public class Goods { @TableId(type = IdType.AUTO) private Integer id;//商品id private String name;//商品名称 private Integer num;//商品库存 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getNum() { return num; } public void setNum(Integer num) { this.num = num; } }

    4.2并发工具

    使用apache-jmeter-5.3来实现并发

    线程发起请求

    结果

    4.3事务和同步代码块

    同一事务中进入两个线程,导致多个线程读取数据不正确

    同步方法 在方法上加入sychronized即可 或者在调用层加入同步,都一样

    集群下产生的问题

    启动两台或者多台服务器,部署相同的项目,然后用多线程去测试,又引发超卖问题

    解决 需要找一个第三方来存储锁 使用分锁有很多 1)mysql数据库 2)redis 3)zookeeper 4)memcache(String)

    分布式锁的特点: 1)互斥性:当前只能有一个用户持久有锁 2)避免死锁:当一个用户持有锁后,服务器挂了,导致锁不能正常被释放,最终就引死锁 3)可用性:集群,高可用,读写分离 4)谁加锁谁解锁:解铃还需系铃人

    加了分布式锁之后引发问题—部分商品没成功卖出

    800个用户,抢购500个商品,最后商品剩余 解决 加入一个自旋机制,访问不成功多尝试几次

    /** * 尝试加锁(不断尝试) * @param key key:商品标识 * @param value 用户标识 大于等于50 * @param expTime 动态值 ,大于等于50 * @return */ private boolean tryLock(String key,String value,int expTime){ for (int i = 0; i < expTime; i++) { Boolean tryLock = stringRedisTemplate.boundValueOps(key).setIfAbsent(value, expTime, TimeUnit.SECONDS); if(tryLock){ return tryLock;//加锁成功 }else{ try { TimeUnit.MILLISECONDS.sleep(expTime);//隔一会去尝试获取锁,减轻内存压力 } catch (InterruptedException e) { e.printStackTrace(); } } } return false; }

    业务没执行完提前过期释放别人的锁出现问题

    谁加锁谁解锁问题及解决

    String luaScript="if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; stringRedisTemplate.execute(new DefaultRedisScript(luaScript,Long.class),Collections.singletonList(key),userFlag+"");

    最终代码

    controller

    package cn.icode.controller; import cn.icode.service.GoodsService; import cn.icode.util.RedisLockUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.Collections; import java.util.Random; import java.util.concurrent.TimeUnit; /** * @description: * @author: Baiyu * @modified By: * @version: 1.0 */ @RestController @ResponseBody public class GoodsController { @Autowired private GoodsService goodsService; @Autowired private StringRedisTemplate stringRedisTemplate; @GetMapping("cutNum") public String cutNum(Integer id,Integer num){ boolean b=true; //加锁 //setIfAbsent方法的参数:1)值(谁加锁设置用户标识)2) Random random = new Random(); int userFlag = random.nextInt(100)+50; //redis的value String key= "goods_" + id;//redis key boolean lock=tryLock(key,userFlag+"",userFlag); if(lock){ try { b= goodsService.cutNum(id,num);//业务逻辑 a 锁过期 } catch (Exception e) { e.printStackTrace(); } finally { //stringRedisTemplate.delete(key);//a String luaScript="if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; stringRedisTemplate.execute(new DefaultRedisScript(luaScript,Long.class),Collections.singletonList(key),userFlag+""); } }else{ System.out.println("没有抢到锁"); } //解锁 return b?"success":"fail"; } /** * 尝试加锁(不断尝试) * @param key key:商品标识 * @param value 用户标识 大于等于50 * @param expTime 动态值 ,大于等于50 * @return */ private boolean tryLock(String key,String value,int expTime){ for (int i = 0; i < expTime; i++) { Boolean tryLock = stringRedisTemplate.boundValueOps(key).setIfAbsent(value, expTime, TimeUnit.SECONDS); if(tryLock){ return tryLock;//加锁成功 }else{ try { TimeUnit.MILLISECONDS.sleep(expTime);//隔一会去尝试获取锁,减轻内存压力 } catch (InterruptedException e) { e.printStackTrace(); } } } return false; } }

    service

    package cn.icode.service.impl; import cn.icode.entity.Goods; import cn.icode.mapper.GoodsMapper; import cn.icode.service.GoodsService; import cn.icode.util.RedisUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.concurrent.TimeUnit; /** * @description: * @author: Baiyu * @modified By: * @version: 1.0 */ @Service public class GoodsServiceImpl implements GoodsService { @Autowired private GoodsMapper goodsMapper; @Autowired private StringRedisTemplate redisTemplate; /** * 减库存 * @param id 商品id * @param num 减的数据 */ @Transactional public boolean cutNum(Integer id,Integer num) { boolean flag=true; //synchronized (this){ Goods goods = goodsMapper.selectById(id); if (goods != null && goods.getNum() >= num) { int i = goodsMapper.updateNum(id, num); System.out.println("商品库存:" + goods.getNum()); Long count = redisTemplate.boundValueOps("count").increment(num);//销量, System.out.println("销售商品数:" + count); } else { System.out.println("没有捡到商品"); flag= false; } //} /* try { TimeUnit.MILLISECONDS.sleep(200);//模拟真实环境,还有其它业务操作 } catch (InterruptedException e) { e.printStackTrace(); }*/ return flag; } }
    Processed: 0.014, SQL: 9