#菜鸟学习过程
##高并发单服务的情况下作为初步优化目标。
###首先设定了一个主键的规则:
①时间戳=yyyyMMddHHmmss
②特定的头部=XX
③流水号=6位数字(最大支持999999个序号了,基本够用)
###拼接序列号的工具
####接口准备
/**
* ID生成器接口, 用于生成全局唯一的ID流水号
* @author
*
*/
public interface IdGenerator
{
/**
* 生成下一个流水号
* @return
*/
public String next();
}
/**
* Id生成器配置接口
* @author
*
*/
public interface IdGeneratorConfig {
/**
* 获取分隔符
* @return
*/
String getSplitString();
/**
* 获取初始值
* @return
*/
int getInitial();
/**
* 获取ID前缀
* @return
*/
String getPrefix();
/**
* 获取滚动间隔, 单位: 秒
* @return
*/
int getRollingInterval();
}
####接口的实现
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
/**
* 默认的ID生成器, 采用前缀+时间+原子数的形式实现
* 建议相同的配置采用同一个实例
* @see IdGeneratorConfig
* @author
*/
@Component("idGenerator")
public class DefaultIdGenerator implements IdGenerator, Runnable
{
private String time;
//AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
private AtomicInteger value;
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyyMMddHHmm");
@Resource
private IdGeneratorConfig config;
private Thread thread;
//读写锁
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public DefaultIdGenerator(){
config = new DefaultIdGeneratorConfig();
time = FORMATTER.format(new Date());
value = new AtomicInteger(config.getInitial());
thread = new Thread(this);
thread.setDaemon(true);
thread.start();
}
public DefaultIdGenerator(IdGeneratorConfig config){
this.config = config;
time = FORMATTER.format(new Date());
value = new AtomicInteger(config.getInitial());
thread = new Thread(this);
thread.setDaemon(true);
thread.start();
}
@Override
public void run()
{
while (true)
{
try
{
Thread.sleep(1000 * config.getRollingInterval());
} catch (InterruptedException e) {
e.printStackTrace();
}
String now = FORMATTER.format(new Date());
if (!now.equals(time)){
lock.writeLock().lock();
time = now;
value.set(config.getInitial());
lock.writeLock().unlock();
}
}
}
@Override
public String next()
{
lock.readLock().lock();
int index = value.getAndIncrement();
String num = String.valueOf(index);
// System.out.println("@"+num);
StringBuilder number = new StringBuilder();
if(num.length()<6)
{
for(int i=0;i<(6-num.length());i++)
{
number.append("0");
}
}
number.append(num);
// System.out.println("#"+number.toString());
StringBuffer sb = new StringBuffer(config.getPrefix()).append(config.getSplitString()).append(time).append(config.getSplitString()).append(number.toString());
lock.readLock().unlock();
return sb.toString();
}
}
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import LocalCacheUtil;//缓存工具类
@Component("idGeneratorConfig")
public class DefaultIdGeneratorConfig implements IdGeneratorConfig
{
@Resource
private PropertiesFileIOUtil propertiesFileIOUtil;
@Resource
private LocalCacheUtil localCacheUtil;
@Override
public String getSplitString()
{
return "";
}
@Override
public int getInitial()
{
return 1;
}
@Override
public String getPrefix()
{
String result = null;
try
{
//查询服务器的配置文件
result = (String)this.localCacheUtil.query(PropertiesFileIOUtil.MECHINE_CODE_KEY);
if(result==null)
{
Map<String, String> map = this.propertiesFileIOUtil.loadProps();
if(map==null || !map.containsKey(PropertiesFileIOUtil.MECHINE_CODE_KEY))
{
return "";
}
//服务器号
result = map.get(PropertiesFileIOUtil.MECHINE_CODE_KEY);
//加入本地缓存
this.localCacheUtil.put(PropertiesFileIOUtil.MECHINE_CODE_KEY, result);
}
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException("获取主键前缀失败");
}
return result;
}
@Override
public int getRollingInterval()
{
return 1;
}
}
到这里这个单服务器的主键就完成了
###服务器集群负载条件下的优化
在我重写的这个方法里,我加入了一个配置文件,里面配置了每台机器的机器号,然后用这个机器号做流水号的前缀
以区分不同服务器生成的流水号。再加上一个缓存,这样就避免不断反复读取配置文件了。
@Override
public String getPrefix()
{
String result = null;
try
{
//查询服务器的配置文件
result = (String)this.localCacheUtil.query(PropertiesFileIOUtil.MECHINE_CODE_KEY);
if(result==null)
{
Map<String, String> map = this.propertiesFileIOUtil.loadProps();
if(map==null || !map.containsKey(PropertiesFileIOUtil.MECHINE_CODE_KEY))
{
return "";
}
//服务器号
result = map.get(PropertiesFileIOUtil.MECHINE_CODE_KEY);
//加入本地缓存
this.localCacheUtil.put(PropertiesFileIOUtil.MECHINE_CODE_KEY, result);
}
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException("获取主键前缀失败");
}
####读取配置文件的工具类
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.stereotype.Component;
/**
* 读取配置文件工具类
* @author
*
*/
@Component("propertiesFileIOUtil")
public class PropertiesFileIOUtil
{
public static final String MECHINE_CODE_KEY = "MechineCode"; //机器号
private static Properties props;
public Map<String, String> loadProps()
{
Map<String, String> result = new HashMap<String, String>();
props = new Properties();
InputStream in = null;
try
{
//第一种,通过类加载器进行获取properties文件流
//in = PropertyUtil.class.getClassLoader().getResourceAsStream("sysConfig.properties");
//第二种,通过类进行获取properties文件流
in = PropertiesFileIOUtil.class.getClassLoader().getResourceAsStream("config/mechineCode.properties");
props.load(in);
String mechineCode = props.getProperty(MECHINE_CODE_KEY);
// System.out.println("mechineCode="+mechineCode);
result.put(MECHINE_CODE_KEY, mechineCode);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
//logger.error("sysConfig.properties文件未找到");
}
catch (IOException e)
{
e.printStackTrace();
//logger.error("出现IOException");
}
finally
{
try
{
if(null != in)
{
in.close();
}
}
catch (IOException e)
{
//logger.error("sysConfig.properties文件流关闭出现异常");
}
}
//logger.info("加载properties文件内容完成...........");
//logger.info("properties文件内容:" + props);
return result;
}
public String getProperty(String key)
{
if(null == props)
{
loadProps();
}
return props.getProperty(key);
}
public String getProperty(String key, String defaultValue)
{
if(null == props)
{
loadProps();
}
return props.getProperty(key, defaultValue);
}
}
注:该代码参考来源互联网。经过实际项目实践并测试后发表。供各位参考。
经过一段时间的测试以及压力测试,证明该代码可行,但是有一个地方需要进行修改:
@Override
public String next()
{
lock.readLock().lock();
int index = value.getAndIncrement();
//这里加这一句,来进行初始化,同时根据业务方主键长度的不同限制,也可以使用system.currenMi…这个东东可以精确到毫秒。
if(index==999999){
value = 1;
}
String num = String.valueOf(index);
// System.out.println("@"+num);
StringBuilder number = new StringBuilder();
if(num.length()<6)
{
for(int i=0;i<(6-num.length());i++)
{
number.append("0");
}
}
number.append(num);
// System.out.println("#"+number.toString());
StringBuffer sb = new StringBuffer(config.getPrefix()).append(config.getSplitString()).append(time).append(config.getSplitString()).append(number.toString());
lock.readLock().unlock();
return sb.toString();
}
除此之外,对于一个项目来说,不可能每一个类都搞这样的一个工具吧,所以应该把这个封装到一个dao的基类接口中,这样可以避免反复去写代码,由于每个dao接口的实现都继承了基类接口实现的代码,所以可对这个生成主键的方法进行重写,满足特定需求下主键的业务规则。
详情见下一篇文章。