All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.javaclub.cdl.client.sequence.SequenceDO Maven / Gradle / Ivy

The newest version!
package com.github.javaclub.cdl.client.sequence;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.lang3.StringUtils;

import com.github.javaclub.cdl.client.config.DbConfigManager;
import com.github.javaclub.cdl.client.group.SGroupDataSource;

/**
 * Sequence ID
 */
public class SequenceDO {
	
	private String table; // sequence表名称
	private static String nameColumn = "name"; // seqid名称列名,必须是unique key
	private String name; // seqid名称
	private static String valueColumn = "value"; // seqid值列名
	private long interval = 200l; // 每次递增的id段

	private DbConfigManager dbConfigManager;
	private List groupList;

	private Map MAX_ID_MAP = new ConcurrentHashMap();
	private Map SEQ_ID_MAP = new ConcurrentHashMap();
	private Map dsMap = new ConcurrentHashMap();
	private Map inActiveGroupName = new ConcurrentHashMap();

	private ReadWriteLock lock = new ReentrantReadWriteLock();
	private AtomicBoolean inited = new AtomicBoolean();
	private AtomicInteger rid = new AtomicInteger();

	public void init() throws SQLException {
		if (inited.compareAndSet(false, true)) {
			if (groupList == null || groupList.isEmpty()) {
				throw new RuntimeException("not found db group for sequence ID");
			}

			for (String group : groupList) {
				SGroupDataSource sGroupDataSource = dsMap.get(group);
				if (sGroupDataSource == null) {
					sGroupDataSource = new SGroupDataSource();
					sGroupDataSource.setDbConfigManager(dbConfigManager);
					sGroupDataSource.setGroupName(group);
					sGroupDataSource.init();
					dsMap.put(group, sGroupDataSource);
				}

				if (null == getIdFromDB(group)) {
					initId2DB(group);
				}
			}
		}
	}

	public long nextSeqId() throws SQLException {
		if(!inited.get()){
			throw new SQLException("init method not be invoked at first, check your spring bean");
		}
		String groupName = getGroupName();
		if (SEQ_ID_MAP.get(groupName) != null && MAX_ID_MAP.get(groupName) != null) {
			try {
				lock.readLock().lock();
				long id = SEQ_ID_MAP.get(groupName).addAndGet(getStep());
				if (id < MAX_ID_MAP.get(groupName)) {
					return id;
				}
			} finally {
				lock.readLock().unlock();
			}

		}
		try {
			long nextid = updateSeqId(groupName);
			inActiveGroupName.remove(groupName);
			return nextid;
		} catch (SQLException e) {
			inActiveGroupName.put(groupName, System.currentTimeMillis());
			String retryGroupName = getGroupName();
			if (!groupName.equals(retryGroupName)) {
				long nextid = updateSeqId(retryGroupName);
				inActiveGroupName.remove(retryGroupName);
				return nextid;
			} else {
				throw new SQLException("failed to updateSeqId");
			}
		}
	}

	private String getGroupName() {
		if (groupList.size() == 1) {
			return groupList.get(0);
		}

		if (rid.get() > Integer.MAX_VALUE - 10000) {
			rid.set(0);
		}

		int index = rid.incrementAndGet() % groupList.size();
		String groupName = groupList.get(index);
		int retry = 3;
		while (retry-- > 0) {
			// 3s内不会选择非正常节点
			Long lastInActiveTime = inActiveGroupName.get(groupName);
			if (lastInActiveTime != null && (System.currentTimeMillis() - lastInActiveTime < 3 * 1000l)) {
				index = rid.incrementAndGet() % groupList.size();
				groupName = groupList.get(index);
			} else {
				break;
			}
		}
		return groupName;
	}

	private int getInitId(String groupName) {
		int index = 1;
		for (String group : groupList) {
			if (group.equals(groupName)) {
				return index;
			}
			index++;
		}
		return index;
	}

	private int getStep() {
		return groupList.size();
	}

	private synchronized long updateSeqId(String groupName) throws SQLException {
		if (SEQ_ID_MAP.get(groupName) != null) {
			long id = SEQ_ID_MAP.get(groupName).addAndGet(getStep());
			if (id < MAX_ID_MAP.get(groupName)) {
				return id;
			}
		}

		Long seqId = getIdFromDB(groupName);
		if (seqId == null) {
			throw new RuntimeException("1 not found seq column or table");
		}
		long addId = interval * getStep();
		long curId = seqId;
		long maxId = seqId + addId;
		if (!updateId2DB(groupName, seqId + addId, seqId)) { // 更新冲突时
			seqId = getIdFromDB(groupName);
			if (seqId == null) {
				throw new RuntimeException("2 init seq id failed");
			}
			curId = seqId;
			maxId = seqId + addId;
			if (!updateId2DB(groupName, seqId + addId, seqId)) { // 更新冲突时
				throw new RuntimeException("3 update seq id failed");
			}
		}

		try {
			lock.writeLock().lock();
			MAX_ID_MAP.put(groupName, maxId);
			SEQ_ID_MAP.put(groupName, new AtomicLong());
			SEQ_ID_MAP.get(groupName).set(curId);
		} finally {
			lock.writeLock().unlock();
		}

		return curId;
	}

	private Long getIdFromDB(String groupName) throws SQLException {
		Connection connection = null;
		try {
			SGroupDataSource sGroupDataSource = dsMap.get(groupName);
			connection = sGroupDataSource.getConnection();
			StringBuilder sql = new StringBuilder();
			sql.append("select  ");
			sql.append(valueColumn);
			sql.append("  from  ");
			sql.append(table);
			sql.append(" where ");
			sql.append(nameColumn);
			sql.append(" = ?");
			PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
			preparedStatement.setString(1, name);
			ResultSet rs = preparedStatement.executeQuery();
			if (rs.next()) {
				return rs.getLong(1);
			}
		} finally {
			if (connection != null) {
				connection.close();
			}
		}

		return null;
	}

	private void initId2DB(String groupName) throws SQLException {
		Connection connection = null;
		try {
			SGroupDataSource sGroupDataSource = dsMap.get(groupName);
			connection = sGroupDataSource.getConnection();
			StringBuilder sql = new StringBuilder();
			sql.append("insert into ");
			sql.append(table);
			sql.append("(");
			sql.append(nameColumn);
			sql.append(" , ");
			sql.append(valueColumn);
			sql.append(") values( ?,? )");
			PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
			preparedStatement.setString(1, name);
			preparedStatement.setLong(2, getInitId(groupName));
			preparedStatement.execute();
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
	}

	private boolean updateId2DB(String groupName, long maxId, long lastMaxId) throws SQLException {
		Connection connection = null;
		try {
			SGroupDataSource sGroupDataSource = dsMap.get(groupName);
			connection = sGroupDataSource.getConnection();
			StringBuilder sql = new StringBuilder();
			sql.append("update ");
			sql.append(table);
			sql.append(" set ");
			sql.append(valueColumn);
			sql.append(" = ? ");
			sql.append(" where ");
			sql.append(nameColumn);
			sql.append(" = ? and ");
			sql.append(valueColumn);
			sql.append(" = ?");
			PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
			preparedStatement.setLong(1, maxId);
			preparedStatement.setString(2, name);
			preparedStatement.setLong(3, lastMaxId);
			preparedStatement.execute();
			return 1 == preparedStatement.getUpdateCount();
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
	}

	public void setTable(String table) {
		this.table = table;
	}

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

	public void setInterval(long interval) {
		this.interval = interval;
	}

	public void setDbConfigManager(DbConfigManager dbConfigManager) {
		this.dbConfigManager = dbConfigManager;
	}

	public void setGroups(String groups) {
		if (StringUtils.isBlank(groups)) {
			throw new RuntimeException("groupName is null");
		}
		String[] ss = groups.split(",");
		if (ss != null && ss.length > 0) {
			groupList = new ArrayList<>();
			for (String s : ss) {
				groupList.add(s.trim());
			}
			Collections.sort(groupList);
		} else {
			throw new RuntimeException("groupName is invalid");
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy