`

【原创】分布式自增id的一次实践测试

 
阅读更多

自增id一般情况下有两种方案,一个是使用数据库自增功能,另外一种就是oracle这样的sequance机制。

个人觉得,无论你的系统是否考虑日后分布式扩展,建议统一采用sequance方式获取。这样对系统日后可能产生的数据库移植也是很好的支持。

 

比如说系统需要获得一个表的 新id,可以这样统一封装一个方法:seuHelper.getTableSeq(tableName);

 

 

public long getTableSeq(String tableName)
{
      synchronized(tableName)//重要,确保每个表一个锁
     {
          //自己实现sequance机制
     }
}

 

对于oracle有自己的sequance机制,实现起来就很容易了。但是对于mysql来说,它只有自增方式。如果是做大型集群、分库分表又如何实现一个统一的sequance机制呢?

经过网上查找,发现flicker采用了一种非常巧妙的既简单又可行的实现方案:

 

1、建一个存放对应表的sequance表

 

CREATE TABLE `order_seq` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `stub` char(1) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB AUTO_INCREMENT=102951 DEFAULT CHARSET=utf8;

 

这里有几个关键点:

每个表,一个sequance表,确保性能

UNIQUE KEY,确保每个表,只有一条记录,避免数据无限增大,提高性能

 

2、自增和获取最新id

 

REPLACE INTO pro_seq (stub) VALUES ('a')

SELECT LAST_INSERT_ID()

 

这里需要注意的是,这两个操作,需要放在一个连接里面执行,否则SELECT LAST_INSERT_ID()可能查询不到正确结果。另外还有一个重要原因,SELECT LAST_INSERT_ID()只会查询到当前连接最新的自增id,

也就是说,其他连接更新其他seq表,互不干扰。正是这种基础,保证了我们能建立多个seq表实现seq服务。

 

测试:

 

为了测试多个表,多并发下,是否正常互不干扰产生自增id,并且不重复,我们再建立几个表

 

DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
  `oid` bigint(20) NOT NULL,
  PRIMARY KEY (`oid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入新id,检测是否有主键重复

 

 

CREATE TABLE `pro_seq` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `stub` char(1) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB AUTO_INCREMENT=22207 DEFAULT CHARSET=utf8;

商品表seq

 

CREATE TABLE `pros` (
  `pid` bigint(20) NOT NULL,
  PRIMARY KEY (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

插入商品id,检测是否重复主键

 

 

数据库操作代码

 

	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public void addOrder(long id)
			throws DataAccessException {
		
		String sql = "insert into orders(oid)  values(?)";

		jdbcTemplate.update(
		        sql,
		        new Object[] {
		        		id
		        });
		
	}
	
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public synchronized long getOrdersSeq()
			throws DataAccessException {
		
		jdbcTemplate.update("REPLACE INTO order_seq (stub) VALUES ('a')");
		
		Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
		
		return id.longValue();
		
	}
	
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public synchronized long getProSeq()
			throws DataAccessException {
		
		jdbcTemplate.update("REPLACE INTO pro_seq (stub) VALUES ('a')");
		
		Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
		
		return id.longValue();
		
	}
	
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public void addPro(long id)
			throws DataAccessException {
		
		String sql = "insert into pros(pid)  values(?)";

		jdbcTemplate.update(
		        sql,
		        new Object[] {
		        		id
		        });
		
	}

 

 

并发测试代码

 

 

final SeqHelper seqHelper = new FileSystemXmlApplicationContext(Utils.getRootPath()+"/conf/app-context.xml").getBean(SeqHelper.class);
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i=0; i<10; i++)
				{
					for (int j=0; j<2000; j++)
					{
						new Thread(new Runnable() {
							
							@Override
							public void run() {
								long id = seqHelper.getOrdersSeq();
								System.out.println("order------>"+id);
								seqHelper.addOrder(id);
							}
						}).start();
					}
				}				
			}
		}).start();
		
		for (int i=0; i<10; i++)
		{
			for (int j=0; j<2000; j++)
			{
				new Thread(new Runnable() {
					
					@Override
					public void run() {
						long id = seqHelper.getProSeq();
						System.out.println("pro ------>"+id);
						seqHelper.addPro(id);
						
					}
				}).start();
			}
		}

 

观察测试过程,并没有出现主键冲突异常。

 

order------>122943
order------>122944
order------>122945
order------>122946
order------>122947
order------>122948
pro ------>42202
pro ------>42203
order------>122949
pro ------>42204
order------>122950
pro ------>42205
pro ------>42206

 

从测试结果看到,order表和pro表,产生的id是互相对立的,并不是顺序一起的。

结论是这个方案确实可行,在大型分片集群中,我们可以使用一台独立的数据库,作为专门的seq服务器。对于单点问题,flicker也给出一个很巧妙的方法,就是两台服务器做一个轮训负载,分别设置两台数据库产生的id方式为奇、偶,这样即使一台出现当机,也不会出现id混乱问题。对于小型无分布式应用,我们可以把seq表直接建立在同一个库中,这样以后扩展也是非常方便的。

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics