springboot 秒杀系统(三)百万级高并发服务间同步通讯优化

分布式系统的思想就是:

如果一个系统的压力过大,可以把一个服务拆分成多个服务,这个叫垂直拆分。

也可以考虑做镜像集群,负载平衡,这个叫水平拆分。

这个系统我们可以考虑垂直拆分,将订单相关的功能拆分出来。

我们将订单的逻辑拿出来,放到order-service中,通过backend来调用order-service

来创建订单。

服务:backend   接受客户端请求,判断userid是否订购过(redis判断userid存在即订购过),判断是否有库存。

          order-service  提供接口,供backend调用,创建订单,减库存。

有个问题:当我有1000个并发,同时提交到backend,并且通过了判断用户,判断库存,

这个时候就会有1000个网络请求到order-service,网络消耗很大,很多并发量大的项目都有这类问题

当然,我们可以走异步通讯,也就是消息队列,但是,有些通讯必须要走同步,必须要马上知道结果。

这个时候,我们可以考虑,是否可以在order-service弄个批量创建订单的接口。

在消费端backend,可以做个队列,存储需要调用的数据,

每10MS去把队列的数据读出来组成一个批量订单数据,调用order-service的批量接口,

将返回的数据,对应的之前提交的每个request的task-future,这样就可以减少服务与服务之间的交互。\

其实很多架构和中间件都有这种方案,有个设置 batchsize或者是limittime等,都是设置当你队列大小为多少个为一批

或者是多少时间段内一批,都是为了减少建立连接消耗和网络消耗

通俗点的意思就是 比如我一个客站送人,按照一般的做法就是,来一个人,送一个人,来一个,送一个

我下面的代码的意思是,我现在客站,按班次发车,一段时间发一车,一段时间发一车。

当然这种方法只适合那种大并发的情况,如果请求并不是很频繁,这样会适得其反了

思路大概是这样,代码传至git上。快速地址,核心代码如下:

@Component
@Slf4j
public class OrderService {
    /**
     * 请求包类
     */
    class Request{
        String seckillId;
        String userid;
        //每个请求一个线程观察者
        CompletableFuture<String> future;
    }
    //存请求的队列
    LinkedBlockingDeque<Request> queue = new LinkedBlockingDeque<>();

    public String createOrder(String seckillId,String userid) throws ExecutionException, InterruptedException {
        Request request = new Request();
        request.seckillId=seckillId;
        request.userid=userid;

        CompletableFuture<String> future = new CompletableFuture<>();
        request.future=future;
        //加入到请求队列
        queue.add(request);
        //返回等待,
        return future.get();
    }

    @Autowired
    private RemoteOrderService remoteOrderService;
    /**
     * bean初始化的时候,启动一个线程
     * 每10MS,把队列里的请求批量请求
     */
    @PostConstruct
    public void init(){
        ScheduledExecutorService scheduledExecutorService=
                Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                int size = queue.size();
                if(size ==0){
                    return;
                }
                //队列里面存在数据
                JSONArray jsonArray = new JSONArray();
                ArrayList<Request> requests = new ArrayList<>();
                JSONObject jsonObject;
                Request request;
                //取出队列,组成批量数据
                for(int i=0;i<size;i++){
                    jsonObject=new JSONObject();
                    request = queue.poll();
                    requests.add(request);
                    jsonObject.put("seckillId",request.seckillId);
                    jsonObject.put("userid",request.userid);
                    jsonArray.add(jsonObject);
                }
                log.info("批量提交的大小:"+jsonArray.size());
                //调用远程创建订单的批量接口
                JSONObject result = remoteOrderService.createOrder(jsonArray);

                //正确返回
                if(result.getString("returnCode").equals("100")){
                    for (Request request1 : requests) {
                        JSONObject jsonObject1 = result.getJSONObject("returnData");
                        //通过userid唯一取出结果并将请求的future完成触发之前的请求等待
                        String result1 = jsonObject1.getString(request1.userid);
                        request1.future.complete(result1);
                    }
                }
            }
        },0,10,TimeUnit.MICROSECONDS);//定时任务每10毫秒调用一次
    }
}

结果:我们可以看到,本来需要服务间调用500次,现在通过队列批量只调用了6次,大大减少了网络消耗。

我感觉这波操作很6,在大并发的情况下,大大的减少了通信消耗。是不是?

其他的判断用户在redis是否购买过,减库存等代码

//判断之前userid是否购买
        if(redisTemplate.opsForValue().getAndSet(userid,1)!=null){
            return "0";
        }
        redisTemplate.opsForValue().set(userid,"1",1000L*60,TimeUnit.MICROSECONDS);
        /**
         * 从redis获取库存数,因为redis的单线程,所以也不会出现超卖现象
         */
        long num = redisTemplate.opsForValue().decrement(seckillId);
        if(num<0){
            //库存已售完
            return "0";
        }

猜你喜欢

转载自blog.csdn.net/shrek11/article/details/103530742
今日推荐