Meituan-Dianping / Leaf

Distributed ID Generate Service
Apache License 2.0
6.42k stars 1.84k forks source link

这个并发取号逻辑是否 可以这样 进一步优化? #190

Open ZJRui opened 2 years ago

ZJRui commented 2 years ago

在getIdFromSegmentBuffer 方法中, 假设 当前Segment已经没有可用id了,当前线程A 启动线程B 准备NextSegment。 假设此时已经有100个线程阻塞在取号的方,具体也就是waitAndSleep 方法的地方。 当线程B准备就绪NextSegment后, 线程C及时发现SegmentBuffer准备就绪并先获取到了cpu,这个时候线程C会执行Segment的切换工作。切换之后将SegmentBuffer的nextReady设置为false。 对于线程A和其他被阻塞的线程而言,当这些线程 从waitAndSleep方法返回 后 需要 依次获取写锁,然后从当前Segment中取号。显然这些线程没必要依次获取写锁,而且获取SegmentBuffer写锁 会影响 另外的其他尚未被阻塞线程并发使用读锁从当前Segment中获取id。这是第一种情况。

第二种情况就是,假设线程B 准备nextSegment失败了,此时已经有了100个线程处于阻塞状态waitAndSleep,那么这100个线程会依次获取写锁并检查发现SegmentBuffer的nextReady为false,然后返回 取号失败的结果 这些线程就退出了,因此触发nextSegment的准备工作需要 等待下一次取号请求。

按照我的理解:对于唤醒后的线程,如果SegmentBuffer的nextReady为false 则当前线程 没必要通过获取读锁来取号。 只需要走正常的取号流程就可以了。这个时候对于第二种情况也会有改善,因为走了正常的取号流程 也就相当于会启动新的线程执行nextSegment的初始化工作,而不是直接返回取号失败结果。

 public Result getIdFromSegmentBuffer(final SegmentBuffer buffer) {
        while (true) {
            buffer.rLock().lock();
            try {
                final Segment segment = buffer.getCurrent();
                if (!buffer.isNextReady() && (segment.getIdle() < 0.9 * segment.getStep()) && buffer.getThreadRunning().compareAndSet(false, true)) {
                    service.execute(new Runnable() {
                        @Override
                        public void run() {
                            Segment next = buffer.getSegments()[buffer.nextPos()];
                            boolean updateOk = false;
                            try {
                                updateSegmentFromDb(buffer.getKey(), next);
                                updateOk = true;
                                logger.info("update segment {} from db {}", buffer.getKey(), next);
                            } catch (Exception e) {
                                logger.warn(buffer.getKey() + " updateSegmentFromDb exception", e);
                            } finally {
                                if (updateOk) {
                                    buffer.wLock().lock();
                                    buffer.setNextReady(true);
                                    buffer.getThreadRunning().set(false);
                                    buffer.wLock().unlock();
                                } else {
                                    buffer.getThreadRunning().set(false);
                                }
                            }
                        }
                    });
                }
                long value = segment.getValue().getAndIncrement();
                if (value < segment.getMax()) {
                    return new Result(value, Status.SUCCESS);
                }
            } finally {
                buffer.rLock().unlock();
            }
            waitAndSleep(buffer);

              //这个地方优化
            buffer.wLock().lock();
            try {
                final Segment segment = buffer.getCurrent();
                long value = segment.getValue().getAndIncrement();
                if (value < segment.getMax()) {
                    return new Result(value, Status.SUCCESS);
                }
                if (buffer.isNextReady()) {
                    buffer.switchPos();
                    buffer.setNextReady(false);
                } else {
                     //这个地方优化
                    logger.error("Both two segments in {} are not ready!", buffer);
                    return new Result(EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL, Status.EXCEPTION);
                }
            } finally {
                buffer.wLock().unlock();
            }
        }
    }

@thelight1 @Yaccc

https://github.com/Meituan-Dianping/Leaf/pull/191

Ccccccai777 commented 1 year ago

(只需要走正常的取号流程就可以了。这个时候对于第二种情况也会有改善)您指的是在调用一次getIdFromSegmentBuffer方法吗