jseckill icon indicating copy to clipboard operation
jseckill copied to clipboard

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

Open WXzhongwang opened this issue 5 years ago • 3 comments

是否存在情况当未在指定时间段内收到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

WXzhongwang avatar Dec 17 '20 09:12 WXzhongwang

这种情况是考虑是回滚redis数据还是增加ack时间。感觉都不是太好。盼回复

WXzhongwang avatar Dec 17 '20 09:12 WXzhongwang

我觉得应该回滚redis数据。 毕竟增加ack时间,这个会导致mq消息大量积压,拖垮系统。

bootsrc avatar Jan 01 '21 11:01 bootsrc

这里我也发现是有bug的,因为浏览器是通过接口去轮询,而作者写的轮询接口逻辑是这样的:1、先查看秒杀成功的商品set集合中是否存在用户手机号,存在,返回秒杀成功。不存在,则判断redis中推送成功的消息set集合,这里是重点!当你ack超时,但是却成功推送到了MQ中,将不会记录到Redis中!于是在redis中找不到推送成功的记录(实际上已经推送成功了),于是给浏览器返回秒杀失败,但是实际上已经成功了!这是问题所在,解决这个问题有我觉得有这些方法,1、完善这个请求秒杀状态的接口,及时将推送成功信息写入redis中。2、适当增加等待ack的时间,如指数退避算法。3、没有接收到ack请求的,一律忽略,不进行后面的逻辑处理,这样返回秒杀失败也合情合理

myhearis avatar Jan 11 '23 15:01 myhearis