微信点餐之分布式锁(14)

创建模拟秒杀下单活动的service层接口

com\imooc\service\SecKillService.java

package com.imooc.service;

public interface SecKillService {

    /**
     * 查询秒杀活动特价商品的信息
     * @param productId
     * @return
     */
    String querySecKillProductInfo(String productId);

    /**
     * 模拟不同用户秒杀同一商品的请求
     * @param productId
     * @return
     */
    void orderProductMockDiffUser(String productId);

}

创建模拟秒杀下单活动的service接口的实现类

com\imooc\service\imp\SecKillServiceImp.java

package com.imooc.service.imp;

import com.imooc.exception.SellException;
import com.imooc.service.RedisLock;
import com.imooc.service.SecKillService;
import com.imooc.utils.KeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

Service
@Slf4j
public class SecKillServiceImp implements SecKillService {

    private static final int TIMEOUT = 10 * 1000; //超时时间 10s

    @Autowired
    private RedisLock redisLock;

    /**
     * 国庆活动,皮蛋粥特价,限量100000份
     */
    static Map<String,Integer> products;
    static Map<String,Integer> stock;
    static Map<String,String> orders;
    static
    {
        /**
         * 模拟多个表,商品信息表,库存表,秒杀成功订单表
         */
        products = new HashMap<>();
        stock = new HashMap<>();
        orders = new HashMap<>();
        products.put("123456", 100000);
        stock.put("123456", 100000);
    }

    private String queryMap(String productId)
    {
        return "国庆活动,皮蛋粥特价,限量份"
                + products.get(productId)
                +" 还剩:" + stock.get(productId)+" 份"
                +" 该商品成功下单用户数目:"
                +  orders.size() +" 人" ;
    }

    /*查询秒杀活动特价商品的信息*/
    @Override
    public String querySecKillProductInfo(String productId)
    {
        return this.queryMap(productId);
    }

    /*模拟不同用户秒杀同一商品的请求*/
    @Override
    public void orderProductMockDiffUser(String productId)
    {
        //加锁
        long time = System.currentTimeMillis() + TIMEOUT;
        log.info(String.valueOf(time));
        Boolean str = redisLock.lock(productId,String.valueOf(time));
        if(!str){
            throw new SellException(101,"哎呦喂 人太多了,换个姿势再试试!");
        }

        //1.查询该商品库存,为0则活动结束。
        int stockNum = stock.get(productId);
        if(stockNum == 0) {
            throw new SellException(100,"活动结束");
        }else {
            //2.下单(模拟不同用户openid不同)
            orders.put(KeyUtil.genUniqueKey(),productId);
            //3.减库存
            stockNum =stockNum-1;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stock.put(productId,stockNum);
        }

        //解锁
        redisLock.unlock(productId,String.valueOf(time));
    }
}

编写加锁和解锁的实现类

在秒杀或者下单的业务方法中调用
com\imooc\service\RedisLock.java

package com.imooc.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;


@Component
@Slf4j
public class RedisLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 加锁
     * @param key 例如:productId:123456
     * @param value 当前时间+超时时间 例如:1582891777814
     * @return
     */
    public boolean lock(String key, String value) {
        log.info("value={}",value);
        /*如果key不存在 就赋值 返回true  加锁成功  相当于reids的setnx*/
        if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        //如果key存在 获取当前key已经存在的value
        String currentValue = redisTemplate.opsForValue().get(key);
        log.info("currentValue={}",currentValue);
        //如果锁过期
        if (!StringUtils.isEmpty(currentValue)
                && Long.parseLong(currentValue) < System.currentTimeMillis()) {/*如果当前时间大于超时时间 说明已经抢单加锁超过10秒了*/
            //获取上一个锁的时间
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);/*获取当前key的旧value 同时把新的value set进去*/
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {/*代码严谨性判断*/
                return true;
            }
        }

        return false;
    }

    /**
     * 解锁
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e) {
            log.error("【redis分布式锁】解锁异常, {}", e);
        }
    }

}

编写websocket的实现类

com\imooc\service\WebSocket.java

package com.imooc.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author: menghaibin
 * @create: 2020-02-27 20:26
 * @description: websocket业务实现
 **/
Component
@ServerEndpoint("/webSocket")
@Slf4j
public class WebSocket {

    private Session session;

    /*定义websocket容器*/
    private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();

    /*得到连接时*/
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        webSocketSet.add(this);
        log.info("websocket 有新的链接 总数为:{}",webSocketSet.size());
    }

    /*关闭连接时*/
    @OnClose
    public void onClose(){
        webSocketSet.remove(this);
        log.info("websocket 链接断开 总数为:{}",webSocketSet.size());
    }

    /*收到消息*/
    @OnMessage
    public void onMessage(String message){
        log.info("websocket 收到客户端发来的消息:{}",message);
    }

    /*发送消息*/
    public void sendMessage(String message){
        for(WebSocket webSocket : webSocketSet){
            log.info("websocket 广播消息 message={}",message);
            try{
                webSocket.session.getBasicRemote().sendText(message);
            }catch (Exception e){
                log.error(e.getMessage());
            }
        }
    }

}

编写controller

com\imooc\controller\SecKillController.java

package com.imooc.controller;

import com.imooc.service.SecKillService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/skill")
@Slf4j
public class SecKillController {

    @Autowired
    private SecKillService secKillService;

    /**
     * 查询秒杀活动特价商品的信息
     * @param productId
     * @return
     */
    @GetMapping("/query/{productId}")
    public String query(@PathVariable String productId)throws Exception
    {
        return secKillService.querySecKillProductInfo(productId);
    }


    /**
     * 秒杀,没有抢到获得"哎呦喂,xxxxx",抢到了会返回剩余的库存量
     * @param productId
     * @return
     * @throws Exception
     */
    @GetMapping("/order/{productId}")
    public String skill(@PathVariable String productId)throws Exception
    {
        log.info("@skill request, productId:" + productId);
        secKillService.orderProductMockDiffUser(productId);
        return secKillService.querySecKillProductInfo(productId);
    }
}

可以用压测工具压测:http://127.0.0.1/sell/skill/query/123456

posted @ 2020-04-01 15:38  努力的校长  阅读(162)  评论(0编辑  收藏  举报