cc.siyecao.uid.impl.CachedUidGenerator Maven / Gradle / Ivy
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.siyecao.uid.impl;
import cc.siyecao.uid.BitsAllocator;
import cc.siyecao.uid.UidGenerator;
import cc.siyecao.uid.buffer.BufferPaddingExecutor;
import cc.siyecao.uid.buffer.RejectedPutBufferHandler;
import cc.siyecao.uid.buffer.RejectedTakeBufferHandler;
import cc.siyecao.uid.buffer.RingBuffer;
import cc.siyecao.uid.exception.UidGenerateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a cached implementation of {@link UidGenerator} extends
* from {@link DefaultUidGenerator}, based on a lock free {@link RingBuffer}
*
* The spring properties you can specified as below:
*
boostPower: RingBuffer size boost for a power of 2, Sample: boostPower is 3, it means the buffer size
* will be ({@link BitsAllocator#getMaxSequence()} + 1) <<
* {@link #boostPower}
, Default as {@value #DEFAULT_BOOST_POWER}
* paddingFactor: Represents a percent value of (0 - 100). When the count of rest available UIDs reach the
* threshold, it will trigger padding buffer. Default as{@link RingBuffer#DEFAULT_PADDING_PERCENT}
* Sample: paddingFactor=20, bufferSize=1000 -> threshold=1000 * 20 /100, padding buffer will be triggered when tail-cursorscheduleInterval: Padding buffer in a schedule, specify padding buffer interval, Unit as second
* rejectedPutBufferHandler: Policy for rejected put buffer. Default as discard put request, just do logging
* rejectedTakeBufferHandler: Policy for rejected take buffer. Default as throwing up an exception
*
* @author yutianbao
*/
public class CachedUidGenerator extends DefaultUidGenerator implements DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger( CachedUidGenerator.class );
private static final int DEFAULT_BOOST_POWER = 3;
/**
* Spring properties
*/
private int boostPower = DEFAULT_BOOST_POWER;
private int paddingFactor = RingBuffer.DEFAULT_PADDING_PERCENT;
private Long scheduleInterval;
private RejectedPutBufferHandler rejectedPutBufferHandler;
private RejectedTakeBufferHandler rejectedTakeBufferHandler;
/**
* RingBuffer
*/
private RingBuffer ringBuffer;
private BufferPaddingExecutor bufferPaddingExecutor;
@Override
public void afterPropertiesSet() throws Exception {
// initialize workerId & bitsAllocator
super.afterPropertiesSet();
// initialize RingBuffer & RingBufferPaddingExecutor
this.initRingBuffer();
LOGGER.info( "Initialized RingBuffer successfully." );
}
@Override
public long getUID() {
try {
return ringBuffer.take();
} catch (Exception e) {
LOGGER.error( "Generate unique id exception. ", e );
throw new UidGenerateException( e );
}
}
@Override
public String parseUID(long uid) {
return super.parseUID( uid );
}
@Override
public void destroy() throws Exception {
bufferPaddingExecutor.shutdown();
}
/**
* Get the UIDs in the same specified second under the max sequence
*
* @param currentSecond
* @return UID list, size of {@link BitsAllocator#getMaxSequence()} + 1
*/
protected List nextIdsForOneSecond(long currentSecond) {
// Initialize result list size of (max sequence + 1)
int listSize = (int) bitsAllocator.getMaxSequence() + 1;
List uidList = new ArrayList<>( listSize );
// Allocate the first sequence of the second, the others can be calculated with the offset
long firstSeqUid = bitsAllocator.allocate( currentSecond - epochSeconds, workerId, 0L );
for (int offset = 0; offset < listSize; offset++) {
uidList.add( firstSeqUid + offset );
}
return uidList;
}
/**
* Initialize RingBuffer & RingBufferPaddingExecutor
*/
private void initRingBuffer() {
// initialize RingBuffer
int bufferSize = ((int) bitsAllocator.getMaxSequence() + 1) << boostPower;
this.ringBuffer = new RingBuffer( bufferSize, paddingFactor );
LOGGER.info( "Initialized ring buffer size:{}, paddingFactor:{}", bufferSize, paddingFactor );
// initialize RingBufferPaddingExecutor
boolean usingSchedule = (scheduleInterval != null);
this.bufferPaddingExecutor = new BufferPaddingExecutor( ringBuffer, this::nextIdsForOneSecond, usingSchedule );
if (usingSchedule) {
bufferPaddingExecutor.setScheduleInterval( scheduleInterval );
}
LOGGER.info( "Initialized BufferPaddingExecutor. Using schdule:{}, interval:{}", usingSchedule, scheduleInterval );
// set rejected put/take handle policy
this.ringBuffer.setBufferPaddingExecutor( bufferPaddingExecutor );
if (rejectedPutBufferHandler != null) {
this.ringBuffer.setRejectedPutHandler( rejectedPutBufferHandler );
}
if (rejectedTakeBufferHandler != null) {
this.ringBuffer.setRejectedTakeHandler( rejectedTakeBufferHandler );
}
// fill in all slots of the RingBuffer
bufferPaddingExecutor.paddingBuffer();
// start buffer padding threads
bufferPaddingExecutor.start();
}
/**
* Setters for spring property
*/
public void setBoostPower(int boostPower) {
Assert.isTrue( boostPower > 0, "Boost power must be positive!" );
this.boostPower = boostPower;
}
public void setPaddingFactor(int paddingFactor) {
this.paddingFactor = paddingFactor;
}
public void setRejectedPutBufferHandler(RejectedPutBufferHandler rejectedPutBufferHandler) {
Assert.notNull( rejectedPutBufferHandler, "RejectedPutBufferHandler can't be null!" );
this.rejectedPutBufferHandler = rejectedPutBufferHandler;
}
public void setRejectedTakeBufferHandler(RejectedTakeBufferHandler rejectedTakeBufferHandler) {
Assert.notNull( rejectedTakeBufferHandler, "RejectedTakeBufferHandler can't be null!" );
this.rejectedTakeBufferHandler = rejectedTakeBufferHandler;
}
public void setScheduleInterval(long scheduleInterval) {
Assert.isTrue( scheduleInterval > 0, "Schedule interval must positive!" );
this.scheduleInterval = scheduleInterval;
}
}