SEQUENCE Principle of TDDL in Distributed

Create a sequence table in the database to record the maximum id currently occupied.
Each client host takes an id interval (for example, 1000-2000) to cache locally and updates the id maximum record in the sequence table.
Client hosts take different id intervals, run out and retrieve, and use the optimistic locking mechanism to control concurrency.
TDDL should be familiar to you, Taobao Distributed Data Layer. It is very good for us to implement functions such as repository and tabulation, Master/Salve, dynamic data source configuration and so on.

After the distribution, the database self-increasing sequence is no longer needed. How to solve this problem quickly and easily? TDDL also provides a solution for SEQUENCE.

Here is a brief analysis of the implementation principle.

surface

Create a table corresponding to the sequence.
 

CREATE TABLE `imp_sequence` (
  `BIZ_NAME` varchar(45) NOT NULL COMMENT 'Business Name',
  `CURRENT_VALUE` int(11) NOT NULL COMMENT 'Current Maximum',
  `GMT_CREATE` datetime DEFAULT NULL COMMENT 'Creation Time',
  `GMT_MODIFIED` datetime DEFAULT NULL COMMENT 'Modification Time',
  PRIMARY KEY (`BIZ_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Data Sequence Table';

Table names and fields can be defined according to their respective rules, which then need to correspond to the definition in the second step of the DAO!

Several logical tables need to declare several sequence s

bean Configuration

Configure sequenceDao

<bean id="sequenceDao" class="com.taobao.tddl.client.sequence.impl.DefaultSequenceDao">
        <!-- data source -->
        <property name="dataSource"  ref="dataSource" />
        <!-- step-->
        <property name="step" value="1000" />
        <!-- retry count-->
        <property name="retryTimes" value="1" />
        <!-- sequence Table Name-->
        <property name="tableName" value="imp_sequence" />
        <!-- sequence Name-->
        <property name="nameColumnName" value="BIZ_NAME" />
        <!-- sequence Current Value-->
        <property name="valueColumnName" value="CURRENT_VALUE" />
        <!-- sequence Update Time-->
        <property name="gmtModifiedColumnName" value="gmt_modified" />
    </bean>

Step 3: Configure the sequence generator

<bean id="businessSequence"  class="com.taobao.tddl.client.sequence.impl.DefaultSequence">
        <property name="sequenceDao" ref="sequenceDao"/>
        <property name="name" value="business_sequence" />
    </bean>

Technological process

Default Configuration

private static final int MIN_STEP = 1;
private static final int MAX_STEP = 100000;
private static final int DEFAULT_STEP = 1000;
private static final int DEFAULT_RETRY_TIMES = 150;

private static final String DEFAULT_TABLE_NAME = "sequence";
private static final String DEFAULT_NAME_COLUMN_NAME = "name";
private static final String DEFAULT_VALUE_COLUMN_NAME = "value";
private static final String DEFAULT_GMT_MODIFIED_COLUMN_NAME = "gmt_modified";

private static final long DELTA = 100000000L;

private DataSource dataSource;

/**
 * retry count
 */
private int retryTimes = DEFAULT_RETRY_TIMES;

/**
 * step
 */
private int step = DEFAULT_STEP;

/**
 * Table name where the sequence is located
 */
private String tableName = DEFAULT_TABLE_NAME;

/**
 * Column name to store sequence name
 */
private String nameColumnName = DEFAULT_NAME_COLUMN_NAME;

/**
 * Column name storing sequence values
 */
private String valueColumnName = DEFAULT_VALUE_COLUMN_NAME;

/**
 * Column name for last update time of storage sequence
 */
private String gmtModifiedColumnName = DEFAULT_GMT_MODIFIED_COLUMN_NAME;

Code

IbatisSmDAO 

public class IbatisSmDAO extends SqlMapClientDaoSupport implements SmDAO {

  /**smSequence*/
  private DefaultSequence   businessSequence;

    public int insert(SmDO sm) throws DataAccessException {
        if (sm == null) {
            throw new IllegalArgumentException("Can't insert a null data object into db.");
        }

        try {
            sm.setId((int)businessSequence.nextValue());
        } catch (SequenceException e) {
            throw new RuntimeException("Can't get primary key.");
        }

        getSqlMapClientTemplate().insert("MS-SM-INSERT", sm);

        return sm.getId();
    }
}

From the code, calling businessSequence.nextValue(), we can see that it involves an important class, DefaultSequence, and continue to look at DefaultSequence   

public class DefaultSequence implements Sequence {
    private final Lock lock = new ReentrantLock();

    private SequenceDao sequenceDao;

    /**
     * Sequence Name
     */
    private String name;

    private volatile SequenceRange currentRange;

    public long nextValue() throws SequenceException {
        if (currentRange == null) {
            lock.lock();
            try {
                if (currentRange == null) {
                    currentRange = sequenceDao.nextRange(name);
                }
            } finally {
                lock.unlock();
            }
        }

        long value = currentRange.getAndIncrement();
        if (value == -1) {
            lock.lock();
            try {
                for (;;) {
                    if (currentRange.isOver()) {
                        currentRange = sequenceDao.nextRange(name);
                    }

                    value = currentRange.getAndIncrement();
                    if (value == -1) {
                        continue;
                    }

                    break;
                }
            } finally {
                lock.unlock();
            }
        }

        if (value < 0) {
            throw new SequenceException("Sequence value overflow, value = " + value);
        }

        return value;
    }

    public SequenceDao getSequenceDao() {
        return sequenceDao;
    }

    public void setSequenceDao(SequenceDao sequenceDao) {
        this.sequenceDao = sequenceDao;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Called   sequenceDao.nextRange(name); An important class   SequenceDao, keep watching   SequenceDao's nextRange(name) method

public SequenceRange nextRange(String name) throws SequenceException {  
        if (name == null) {  
            throw new IllegalArgumentException("Sequence name cannot be empty");  
        }  

        long oldValue;  
        long newValue;  

        Connection conn = null;  
        PreparedStatement stmt = null;  
        ResultSet rs = null;  

        for (int i = 0; i < retryTimes + 1; ++i) {  
            try {  
                conn = dataSource.getConnection();  
                stmt = conn.prepareStatement(getSelectSql());  
                stmt.setString(1, name);  
                rs = stmt.executeQuery();  
                rs.next();  
                oldValue = rs.getLong(1);  

                if (oldValue < 0) {  
                    StringBuilder message = new StringBuilder();  
                    message.append("Sequence value cannot be less than zero, value = ").append(oldValue);  
                    message.append(", please check table ").append(getTableName());  

                    throw new SequenceException(message.toString());  
                }  

                if (oldValue > Long.MAX_VALUE - DELTA) {  
                    StringBuilder message = new StringBuilder();  
                    message.append("Sequence value overflow, value = ").append(oldValue);  
                    message.append(", please check table ").append(getTableName());  

                    throw new SequenceException(message.toString());  
                }  

                newValue = oldValue + getStep();  
            } catch (SQLException e) {  
                throw new SequenceException(e);  
            } finally {  
                closeResultSet(rs);  
                rs = null;  
                closeStatement(stmt);  
                stmt = null;  
                closeConnection(conn);  
                conn = null;  
            }  

            try {  
                conn = dataSource.getConnection();  
                stmt = conn.prepareStatement(getUpdateSql());  
                stmt.setLong(1, newValue);  
                stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));  
                stmt.setString(3, name);  
                stmt.setLong(4, oldValue);  
                int affectedRows = stmt.executeUpdate();  
                if (affectedRows == 0) {  
                    // retry  
                    continue;  
                }  

                return new SequenceRange(oldValue + 1, newValue);  
            } catch (SQLException e) {  
                throw new SequenceException(e);  
            } finally {  
                closeStatement(stmt);  
                stmt = null;  
                closeConnection(conn);  
                conn = null;  
            }  
        }  

        throw new SequenceException("Retried too many times, retryTimes = " + retryTimes);  
    }

  The getSelectSql(), getUpdateSql() methods were called.

View getSelectSql() method

  private String getSelectSql() {
        if (selectSql == null) {
            synchronized (this) {
                if (selectSql == null) {
                    StringBuilder buffer = new StringBuilder();
                    buffer.append("select ").append(getValueColumnName());
                    buffer.append(" from ").append(getTableName());
                    buffer.append(" where ").append(getNameColumnName()).append(" = ?");

                    selectSql = buffer.toString();
                }
            }
        }

        return selectSql;
    }

   

View getUpdateSql() method  

private String getUpdateSql() {
        if (updateSql == null) {
            synchronized (this) {
                if (updateSql == null) {
                    StringBuilder buffer = new StringBuilder();
                    buffer.append("update ").append(getTableName());
                    buffer.append(" set ").append(getValueColumnName()).append(" = ?, ");
                    buffer.append(getGmtModifiedColumnName()).append(" = ? where ");
                    buffer.append(getNameColumnName()).append(" = ? and ");
                    buffer.append(getValueColumnName()).append(" = ?");

                    updateSql = buffer.toString();
                }
            }
        }

        return updateSql;
    }

Next, let's look at the nextRange method: Get the next available sequence interval

Query the latest value through getSelectSql, then add a step point to update to the database through getUpdateSql

  private String getSelectSql() {
        if (selectSql == null) {
            synchronized (this) {
                if (selectSql == null) {
                    StringBuilder buffer = new StringBuilder();
                    buffer.append("select ").append(getValueColumnName());
                    buffer.append(" from ").append(getTableName());
                    buffer.append(" where ").append(getNameColumnName()).append(" = ?");

                    selectSql = buffer.toString();
                }
            }
        }

        return selectSql;
    }

    private String getUpdateSql() {
        if (updateSql == null) {
            synchronized (this) {
                if (updateSql == null) {
                    StringBuilder buffer = new StringBuilder();
                    buffer.append("update ").append(getTableName());
                    buffer.append(" set ").append(getValueColumnName()).append(" = ?, ");
                    buffer.append(getGmtModifiedColumnName()).append(" = ? where ");
                    buffer.append(getNameColumnName()).append(" = ? and ");
                    buffer.append(getValueColumnName()).append(" = ?");

                    updateSql = buffer.toString();
                }
            }
        }

        return updateSql;
    }

There is a special need to note that in an update statement, where needs to pass in the previous value as a condition. Optimistic lock operation for type version is implemented. If two AB machines simultaneously request the same value at the same time, only one update operation can succeed. Failure will be retried by retryTimes.
Next, I'll look at DefaultSequence, which is fairly simple and doesn't illustrate
 

Opportunities are not waiting. Opportunities are created through hard work.

Keywords: Database SQL

Added by slushpuppie on Sun, 21 Nov 2021 19:35:41 +0200