Giter Club home page Giter Club logo

jseckill's Introduction

jseckill

license


📘 🛫 🐱 🏛 🛒 🚀 💡
介绍 演示 技术栈 架构图 秒杀过程 Quick Start 源码解析
📌 🐞 🔨 💌
Todo list Q&A 调试排错 做贡献 联系作者

介绍

jseckill:Java实现的秒杀网站,基于Spring Boot 2.X。

jseckill:Seckill website implemented with Java, based on Spring Boot 2.X.

谢谢您对本项目的支持
请点击此处进行Star

GitHub 地址为https://github.com/bootsrc/jseckill

建议访问GitHub以获取更多分布式项目源码https://github.com/bootsrc?tab=repositories

演示

点击进入演示http://jseckill.appjishu.com

注意:提升输入手机号时,随便输入一个11位的数字即可,不需要填自己的真实手机号

效果图

  

技术栈

  • Spring Boot 2.X
  • MyBatis
  • Redis, MySQL
  • Thymeleaf + Bootstrap
  • RabbitMQ
  • Zookeeper, Apache Curator

架构图

部署图 (zookeeper暂时没有用上, 忽略之)



秒杀过程

秒杀进行的过程包含两步骤: 步骤一(秒杀):在Redis里进行秒杀。 这个步骤用户并发量非常大,抢到后,给与30分钟的时间等待用户付款, 如果用户过期未付款,则Redis库存加1 ,算用户自动放弃付款。

步骤二(付款):用户付款成功后,后台把付款记录持久化到MySQL中,这个步骤并发量相对小一点,使用数据库的事务解决数据一致性问题

下面重点讲步骤一,秒杀过程

秒杀步骤流程图

流程图Step1:

1.先经过Nginx负载均衡;

2.Nginx里面通过配置文件配置限流功能,限流算法是漏统桶法;

3.Redis判断是否秒杀过。避免重复秒杀。如果没有秒杀过
把用户名(这里是手机号)和seckillId封装成一条消息发送到RabbitMQ,请求变成被顺序串行处理
立即返回状态“排队中”到客户端上,客户端上回显示“排队中...”

4.后台监听RabbitMQ里消息,每次取一条消息,并解析后,请求Redis做库存减1操作(decr命令)
并手动ACK队列 如果减库存成功,则在Redis里记录下库存成功的用户手机号userPhone.

5.流程图Step2:客户端排队成功后,定时请求后台查询是否秒杀成功,后面会去查询Redis是否秒杀成功
如果抢购成功,或者抢购失败则停止定时查询, 如果是排队中,则继续定时查询。

详情见源码文档

QuickStart

  • clone源码

git clone https://github.com/liushaoming/jseckill.git

  • 在Intelij IDEA/eclipse里导入根路径下的pom.xml,再导入文件夹jseckill-backend下面的pom.xml, 等待maven依赖下载完毕 详细操作:

如果是IDEA,先IDEA | File | Open...,选择jseckill根路径下的pom文件, Open as project以导入根项目jseckill。

操作菜单栏 View | Tool Windows | Maven Projects。 点击"+", 添加jseckill-backend下面的pom。

此时Maven Projects下面有根项目jseckill和jseckill-backend。如下图

如果是Eclipse, import导入maven项目,勾选jseckil和jseckill-backend下面共两个pom文件即可。

  • 修改application.properties里面的自己的Redis,MySQL,Zookeeper,RabbitMQ的连接配置

  • 右键JseckillBackendApp.java--run as--Java Application

开始Debug

源码解析

👉 进入源码解析

Java后端限流

使用Google guava的RateLimiter来进行限流
例如:每秒钟只允许10个人进入秒杀步骤. (可能是拦截掉90%的用户请求,拦截后直接返回"很遗憾,没抢到")
AccessLimitServiceImpl.java代码

package com.liushaoming.jseckill.backend.service.impl;

import com.google.common.util.concurrent.RateLimiter;
import com.liushaoming.jseckill.backend.service.AccessLimitService;
import org.springframework.stereotype.Service;

/**
 * 秒杀前的限流.
 * 使用了Google guava的RateLimiter
 */
@Service
public class AccessLimitServiceImpl implements AccessLimitService {
    /**
     * 每秒钟只发出10个令牌,拿到令牌的请求才可以进入秒杀过程
     */
    private RateLimiter seckillRateLimiter = RateLimiter.create(10);

    /**
     * 尝试获取令牌
     * @return
     */
    @Override
    public boolean tryAcquireSeckill() {
        return seckillRateLimiter.tryAcquire();
    }
}

👉 查看更多源码解析

Todo list

  • 秒杀成功30分钟订单过期的实现

方案: A:用redis对key设置过期时间,超时的监听 秒杀成功后订单保存在redis,对key设置过期时间为当时向后推半小时,当key过期后触发监听,对redis库存+1。

  • 怎么避免用来削峰的mq中的消息过长,导致mq崩溃

方案: 在进入削峰队列之前,需要判断mq中的消息数目是否过多,如果超过设定的数量限制,直接返回给客户端"已售罄" channel.messageCount("seckill") 可以获取到队列中当前到ready的消息的数目 见接口 http://localhost:27000/api/rabbitmq

Q and A

Q: 为什么有时候会发现消息发送到了队列中,但是不被消费?

A: 一种可能的原因是。 你的电脑上在Debug一个程序jseckill-backend, 另外在你自己的服务器上也运行了同样的程序。 两个程序如果连接的是同一个RabbitMQ,就会同时消费消息,就会发生这样的情况。因为我们在程序员里

com.liushaoming.jseckill.backend.mq.MQConsumer#receive里限制了消费者的个数。

channel.basicQos(0, 1, false);

调试排错

  • 1.java.net.SocketException: Socket Closed--nested exception is com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED
03/10-16:51:28 [main] WARN  org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext- Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initTask': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'MQConsumer': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'seckillServiceImpl': Unsatisfied dependency expressed through field 'mqProducer'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'MQProducer': Unsatisfied dependency expressed through field 'mqChannelManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'MQChannelManager': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mqConnectionSeckill' defined in class path resource [com/liushaoming/jseckill/backend/config/MQConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.rabbitmq.client.Connection]: Factory method 'mqConnectionSeckill' threw exception; nested exception is com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.
03/10-16:51:28 [AMQP Connection 47.99.196.243:5672] ERROR com.rabbitmq.client.impl.ForgivingExceptionHandler- An unexpected connection driver error occured
java.net.SocketException: Socket Closed
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:170)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
	at java.io.DataInputStream.readUnsignedByte(DataInputStream.java:288)
	at com.rabbitmq.client.impl.Frame.readFrom(Frame.java:91)
	at com.rabbitmq.client.impl.SocketFrameHandler.readFrame(SocketFrameHandler.java:164)
	at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:596)
	at java.lang.Thread.run(Thread.java:745)
03/10-16:51:28 [main] INFO  com.alibaba.druid.pool.DruidDataSource- {dataSource-1} closed
03/10-16:51:28 [main] INFO  org.apache.catalina.core.StandardService- Stopping service [Tomcat]
03/10-16:51:28 [main] INFO  org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener- 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.


分析: 这里关键点是nested exception is com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED
并且进一步说了- Login was refused using authentication mechanism PLAINM

说明这里是RabbitMQ的用户名和密码认证失败。你需要修改下application-dev.properties里RabbitMQ的用户名和密码相关配置。

  • 2.是否需要手动创建队列? 答:不需要手动创建。 程序会自动创建所需要的队列。默认是创建名为"seckill"的队列,待秒杀的请求会先放到这个队列里,后面出队,进入Redis进行秒杀操作。

  • 3.ERROR com.rabbitmq.client.impl.ForgivingExceptionHandler- An unexpected connection driver error occured. java.net.SocketException: socket closed


rabbitmq.virtual-host配置错误

分析过程:

日志的报错内容跟"用户名密码错误"是一样的。 如果密码已经配置正确了,就可以考虑下面的原因---virtual_host配置错误。

原因,application-dev.properties里面virtual_host默认配置是rabbitmq.virtual-host=/vh_test 如果你改成了"/", 或者其它的值。你需要登陆http://localhost:15672控制台去查看有效的virtual_host是多少。这里必须跟控制台的virtual_host

保持一致

如何在http控制台页面上现实默认的virutal_host=/vh_test, 你这里就配置成rabbitmq.virtual-host=/vh_test 如果是vh_test就配置成rabbitmq.virtual-host=vh_test (左边带不带"/", 是有差别的,这点必须注意)

  • 4.rabbitmq.address-list=192.168.20.3:5672,localhost:5672 请注意,这里需要配置的是mq的tcp端口,默认值为5672. 而不是mq的http端口15672

  • 5.Redis server服务没有设置密码,怎么配置此项目? 正确的配置是spring.redis.password= 而不是spring.redis.password='' 。切忌在配置里加多余的单引号或者双引号。

性能优化

做贡献

特別鸣谢一下对开源项目作出贡献的开发者

序号 开发者GitHub QQ 邮箱
1 liushaoming 944147540 [email protected]

联系作者

联系方式
Leader liushaoming
email [email protected]
QQ群2 1043200253
QQ群1(已满) 612871570

加QQ群讨论
qq群号 1043200253

微信公众号

GitHub

GitHub为本人的开源项目主战场,Gitee为从GitHub同步过来的代码。欢迎移步GitHub点击Star并查看本人的更多项目源码

https://github.com/bootsrc/jseckill

jseckill's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jseckill's Issues

客户端轮询秒杀是否成功,换成websocket是否更好

一些 web 应用不采用 webocket有一个原因是多页面重开。你想想,同时打开几个秒杀页面就是几个 websocket 连接,这个比较难处理。不像客户端,登录一个可以踢掉另一个,在 web 上这种情况很普遍,轮询处理简单很多。
而且一旦查询到秒杀成功或者失败,就马上停止轮询,不会太消耗服务器的带宽.

秒杀输入手机号问题

大佬你好,我在github上看到了你的秒杀项目,想问一下:
1.第一次启动项目时可以输入手机号,后来启动就再也没有输入手机号这个功能了是什么原因?
2.这个项目可以用测试工具测试吗?

demo

1.大佬问一下支付功能怎么体现,我在输入手机号后秒杀成功,然后也没有支付界面,mysql中库存也没变。。。
2.大佬,Jemeter怎么传不同的手机号啊。。。

rabbitmq的生产和消费是只用了一个队列吗?

不是很懂rabbitmq,所以希望有大佬能解释一下,因为看那个流程图上画的是,在redis进行秒杀的时候就是发送到名字为seckill的队列中,在支付的时候发送到另外一个队列中去。所以就有疑问,秒杀的时候是放到一个队列中去吗?

关于mysql读写分离

大佬你好,想问一下你文档中说的MySQL读写分离这里你是在哪里配置的呢,谢谢

是否存在情况当未在指定时间段内收到ACK返回,但Redis却被处理的情况(handleInRedis方法正常执行),导致redis库存数-1的情况

是否存在情况当未在指定时间段内收到ACK返回,

    boolean sendAcked = false;
        try {
            sendAcked = channel.waitForConfirms(100);  100ms时间内
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }

但Redis却被处理的情况(handleInRedis方法正常执行),

 @Override
    public void handleInRedis(long seckillId, long userPhone) throws SeckillException {
        Jedis jedis = jedisPool.getResource();

        String inventoryKey = RedisKeyPrefix.SECKILL_INVENTORY + seckillId;
        String boughtKey = RedisKeyPrefix.BOUGHT_USERS + seckillId;

        String inventoryStr = jedis.get(inventoryKey);
        int inventory = Integer.valueOf(inventoryStr);
        if (inventory <= 0) {
            logger.info("handleInRedis SECKILLSOLD_OUT. seckillId={},userPhone={}", seckillId, userPhone);
            throw new SeckillException(SeckillStateEnum.SOLD_OUT);
        }
        if (jedis.sismember(boughtKey, String.valueOf(userPhone))) {
            logger.info("handleInRedis SECKILL_REPEATED. seckillId={},userPhone={}", seckillId, userPhone);
            throw new SeckillException(SeckillStateEnum.REPEAT_KILL);
        }
        jedis.decr(inventoryKey);
        jedis.sadd(boughtKey, String.valueOf(userPhone));
        logger.info("handleInRedis_done");
    }

导致redis库存数-1,且用户无法继续购买的问题

12/17-17:18:54 [http-nio-27000-exec-2] INFO  com.liushaoming.jseckill.backend.mq.MQProducer-  [mqSend] '{"seckillId":1000,"userPhone":18668042850}'
12/17-17:18:54 [pool-3-thread-6] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- ---receive_threadId_1=42
12/17-17:18:54 [pool-3-thread-6] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- [mqReceive]  '{"seckillId":1000,"userPhone":18668042850}'
12/17-17:18:54 [pool-3-thread-6] INFO  com.liushaoming.jseckill.backend.service.impl.SeckillServiceImpl- handleInRedis_done
12/17-17:18:54 [pool-3-thread-6] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- ------processIt----
12/17-17:18:54 [pool-3-thread-6] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- ---->ACK
java.util.concurrent.TimeoutException
	at com.rabbitmq.client.impl.ChannelN.waitForConfirms(ChannelN.java:229)
	at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.waitForConfirms(AutorecoveringChannel.java:697)
	at com.liushaoming.jseckill.backend.mq.MQProducer.send(MQProducer.java:53)
	at com.liushaoming.jseckill.backend.service.impl.SeckillServiceImpl.handleSeckillAsync(SeckillServiceImpl.java:172)
	at com.liushaoming.jseckill.backend.service.impl.SeckillServiceImpl.executeSeckill(SeckillServiceImpl.java:124)
	at com.liushaoming.jseckill.backend.service.impl.SeckillServiceImpl$$FastClassBySpringCGLIB$$85f44643.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684)
	at com.liushaoming.jseckill.backend.service.impl.SeckillServiceImpl$$EnhancerBySpringCGLIB$$4cd4b877.executeSeckill(<generated>)
	at com.liushaoming.jseckill.backend.controller.SeckillController.execute(SeckillController.java:90)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)
12/17-17:18:54 [http-nio-27000-exec-2] INFO  com.liushaoming.jseckill.backend.mq.MQProducer- sendAcked=false
12/17-17:18:54 [http-nio-27000-exec-2] INFO  com.liushaoming.jseckill.backend.mq.MQProducer- !!!mqSend_NACKED,NOW_RETRY>>>
12/17-17:18:54 [http-nio-27000-exec-2] INFO  com.liushaoming.jseckill.backend.service.impl.SeckillServiceImpl- ENQUEUE_PRE_SECKILL>>>seckillId=1000,userPhone=18668042850
12/17-17:18:54 [pool-3-thread-3] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- ---receive_threadId_1=38
12/17-17:18:54 [pool-3-thread-3] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- [mqReceive]  '{"seckillId":1000,"userPhone":18668042850}'
12/17-17:18:54 [pool-3-thread-3] INFO  com.liushaoming.jseckill.backend.service.impl.SeckillServiceImpl- handleInRedis SECKILL_REPEATED. seckillId=1000,userPhone=18668042850
12/17-17:18:54 [pool-3-thread-3] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- ------processIt----
12/17-17:18:54 [pool-3-thread-3] INFO  com.liushaoming.jseckill.backend.mq.MQConsumer- --LET_MQ_ACK REASON:SeckillStateEnum.SOLD_OUT,SeckillStateEnum.REPEAT_KILL

如果希望采用多个服务器进行消息的消费,是不是在操作redis减库存的时候加分布式锁呢?

` @OverRide
public void handleInRedis(long seckillId, long userPhone) throws SeckillException {
Jedis jedis = jedisPool.getResource();
//获取前缀,+商品的秒杀id=当前秒杀商品的key
String inventoryKey = RedisKeyPrefix.SECKILL_INVENTORY + seckillId;
//key前缀+商品的秒杀id
String boughtKey = RedisKeyPrefix.BOUGHT_USERS + seckillId;
//获取商品库存
String inventoryStr = jedis.get(inventoryKey);
//转换成int
int inventory = Integer.valueOf(inventoryStr);
if (inventory <= 0) {
logger.info("handleInRedis SECKILLSOLD_OUT. seckillId={},userPhone={}", seckillId, userPhone);
throw new SeckillException(SeckillStateEnum.SOLD_OUT);
}
//这里使用了redis的set集合,使用秒杀商品的id作为key,value是一个set集合,
//这里是先判断当前秒杀商品的秒杀集合中是否存在了用户的电话,如果存在,说明该用户已经秒杀过一次了,抛出重复秒杀异常
if (jedis.sismember(boughtKey, String.valueOf(userPhone))) {
logger.info("handleInRedis SECKILL_REPEATED. seckillId={},userPhone={}", seckillId, userPhone);
throw new SeckillException(SeckillStateEnum.REPEAT_KILL);
}
//商品redis中库存-1
jedis.decr(inventoryKey);
//往商品的set集合中添加手机号,作为成员,代表该手机号用户已经秒杀了该商品
jedis.sadd(boughtKey, String.valueOf(userPhone));
logger.info("handleInRedis_done");

}`

我给您的这个方法添加了一些注释,是不是需要在 jedis.decr(inventoryKey);这里使用分布式锁,防止多余的减库存请求进入redis,或者是在消费者调用这个方法的时候就使用分布式锁,我感觉后面这个比较好一些呢

RabbitMQ问题

  1. 是否没有配置消息队列最大长度限制以及溢出行为,队列满了之后默认会丢弃队头的元素,先进入队列的反而被丢弃了?
  2. 入队前redis判断库存并没有减库存,则可能导致远超库存限制的SeckillMsgBody进入队列,同个id的请求也可能重复进入队列,这样增加了MQ和consumer的压力,需要处理很多无效的请求?是否可以添加一个 set 记录历史入队的id,入队前判断已入队数量和是否重复入队再考虑是否需要入队?

请删除无效字符

/jseckill-backend/src/main/java/com/liushaoming/jseckill/backend/boot/InitTask.java:24

需要删除无效字符。

@Component

public class InitTask implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(InitTask.class);
å
    @Resource(name = "initJedisPool")
    private JedisPool jedisPool;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.