高并发环境下生成唯一流水号的主要思路有两种:
第一种是有一个控制全局的变量确保每个流水号的唯一性;
第二种是每台机器根据算法自己生成在系统中无冲突的流水号;
假设流水号的长度是128位(16字节);
第一种实现方法:(1)采用数据库的自增主键确保唯一性;
Database.java
package mine; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class Database { static String serialNumber; static String username="root"; static String pwd = "123"; static String url = "jdbc:mysql://192.168.1.6:3306/serialnumber"; static String driver = "org.gjt.mm.mysql.Driver"; private Connection con; private Statement statement; public static void main(String[] args){ serialNumber =new Database().getSerialNumber(); System.out.println(serialNumber); } private void start(){ try { Class.forName( driver ); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block System.out.println("error in loading driver."); } long time=10000; while((con)==null&&time>0){//设置超时时间10s try { Thread.sleep(100); time-=100; con = DriverManager.getConnection(url,username,pwd); }catch(Exception e){} } time=1000; while((statement)==null&&time>0){ try{ Thread.sleep(100); time-=100; statement = con.createStatement(); }catch(Exception e){} } } private void close(){ try { if(statement!=null) statement.close(); } catch (SQLException e) { // TODO Auto-generated catch block System.out.println("error in close statement."); } try { if(con!=null) con.close(); } catch (SQLException e) { // TODO Auto-generated catch block System.out.println("error in close connection."); } } public String getSerialNumber(){ start(); String str=""; long time =System.currentTimeMillis(); try{ statement.execute("insert serialnumber(time) values("+time+")"); ResultSet re = statement.executeQuery("select NO from serialnumber where time="+time+";"); if(re.next()){ str=re.getString(1); } re.close(); }catch(Exception e){} finally{ close(); } return ""+time+str; } }
ThreadTest.java,线程池容量为100(mysql的默认连接数是100);每个线程的连接超时时间为10秒,启动10000个线程;
运行时间:34s
package mine; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Client extends Thread{ private String serialNumber=""; private String ID=""; static int connect=0; Client(String id){ this.ID=id; } public void run(){ //connect++; new Database().getSerialNumber(); //System.out.println("thread "+ID+" run ……connect:"+connect); //connect--; } public String getSerialNumber(){ return serialNumber; } public String getID(){ return ID; } } public class ThreadTest { public static void main(String[] args) { // TODO Auto-generated method stub long time0=System.currentTimeMillis(); ExecutorService executor = Executors.newFixedThreadPool(100);//creating a pool of 5 threads for (int i = 0; i < 10000; i++) { Thread client = new Client("" + i); executor.execute(client);//calling execute method of ExecutorService } executor.shutdown(); while (!executor.isTerminated()) {} int time =((int)(System.currentTimeMillis()-time0))/1000; System.out.println("Finished all threads time:"+time+"s"); } }
……
Finished all threads time:34s
(2)采用全局变量加锁的方式确保唯一性;流水号为20位十进制数:13位表示时间的数+7位(全局变量);
package mine; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class GSerialNumber extends Thread{ private static Object lock=new Object(); private static long golbal=0; private String SerialNumber=""; public String getSerialNumber(){ long temp; synchronized(lock){ temp=golbal++; } SerialNumber=String.format("%013d%07d",System.currentTimeMillis(),temp); return SerialNumber; } public void run(){ String str=getSerialNumber(); } } public class GlobalLock { public static void main(String[] args) { // TODO Auto-generated method stub long time0=System.currentTimeMillis(); int n =10000; ExecutorService executor =Executors.newFixedThreadPool(n); for(int i=0;i<100*n;i++){ Thread thread = new GSerialNumber(); executor.execute(thread); } executor.shutdown(); while(!executor.isTerminated()){ try { Thread.sleep(100); } catch (InterruptedException e) {} } long time =(int)((System.currentTimeMillis()-time0)); System.out.println("Time:"+time+"ms"); } }
线程池容量10000;线程数:1000000;
运行时间:
Time:5107ms
采用字典树测试是否有冲突;
package mine; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import util.Trie; class GSerialNumber extends Thread{ private static Object lock=new Object(); private static long golbal=0; private String SerialNumber=""; public String getSerialNumber(){ long temp; synchronized(lock){ temp=golbal++; } SerialNumber=String.format("%013d%07d",System.currentTimeMillis(),temp); return SerialNumber; } static Trie trie=new Trie(); static Object tlock = new Object(); public void run(){ String str=getSerialNumber(); synchronized(tlock){ trie.insert(str); } } } public class GlobalLock { public static void main(String[] args) { // TODO Auto-generated method stub long time0=System.currentTimeMillis(); int n =10000; ExecutorService executor =Executors.newFixedThreadPool(n); for(int i=0;i<100*n;i++){ Thread thread = new GSerialNumber(); executor.execute(thread); } executor.shutdown(); while(!executor.isTerminated()){ try { Thread.sleep(100); } catch (InterruptedException e) {} } long time =(int)((System.currentTimeMillis()-time0)); System.out.println("Time:"+time+"ms"); System.out.println("冲突数:"+new GSerialNumber().trie.count); } }
create trie
Time:5755ms
冲突数:0
第二种方案(独自生成流水号):
(1)采用MAC地址+System.currentTimeMillis()+System.nanoTime();测试线程池容量为100,线程数为10000
public static String method2(){ NetworkInterface netInterface; long mac=0; try { netInterface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); mac =new BigInteger(netInterface.getHardwareAddress()).longValue();//.toString(10); }catch (SocketException | UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } String str = String.format("%039d%013d%013d",mac,System.currentTimeMillis(),System.nanoTime()); return encryp(str); }
create trie
冲突数:10
(2)采用MAC地址+System.currentTimeMillis()+Math.Random()*1000;测试线程池容量为100,线程数为10000
public static String method3(){ NetworkInterface netInterface; long mac=0; try { netInterface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); mac =new BigInteger(netInterface.getHardwareAddress()).longValue(); }catch (SocketException | UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } String str = String.format("%039d%013d%04d",mac,System.currentTimeMillis(),(int)(Math.random()*1000)); return str; }
create trie
冲突数:1
(3)采用MAC地址+PID+System.currentTimeMillis();Thread(10);
public static String method4(){ NetworkInterface netInterface; long mac=0; try { netInterface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); mac =new BigInteger(netInterface.getHardwareAddress()).longValue(); }catch (SocketException | UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } String name = ManagementFactory.getRuntimeMXBean().getName(); // get pid String pid = name.split("@")[0]; String str = String.format("%039d%s%013d",mac,pid,System.currentTimeMillis()); try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return encryp(str); }
这里采用多线程测试的时候有一个问题,不同的线程之间是公用一个pid,这样就会产生冲突;为此测试时我们应该把线程id也附加上;测试线程池容量为100,线程数为10000
测试代码:
package mine; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import util.Trie; class SerialCreate extends Thread{ static int ID=0; int id=ID++;//这里线程不安全,不能这么用! static Trie tries = new Trie(); static Object lock = new Object(); public void run(){ String str=new SerialNumber().method4()+String.format("%06d", id);//加入线程id synchronized (lock) { tries.insert(str); } } } public class SerialNumberTest { public static void main(String[] args){ int n =100; ExecutorService execupool = Executors.newFixedThreadPool(n); for(int i=0;i<100*n;i++){ Thread serial = new SerialCreate(); execupool.execute(serial); } execupool.shutdown(); while(!execupool.isTerminated()){} System.out.println("冲突数:"+new SerialCreate().tries.count); } }
测试结果:
create trie
冲突数:0
由于账单号是128位的,而上面的生成算法有的产生字符串表示的数字会超过128位,这样就需要用MD5算法加密散列成128位的数字;
转换函数:encrpy()
public static String encryp(String pwd){ byte[] message=null; message = pwd.getBytes(); MessageDigest md=null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } byte[] encrypwd =md.digest(message); BigInteger bigInteger = new BigInteger(1, encrypwd); return bigInteger.toString(10); }