高并发环境下生成唯一流水号

高并发环境下生成唯一流水号的主要思路有两种:

  第一种是有一个控制全局的变量确保每个流水号的唯一性;

  第二种是每台机器根据算法自己生成在系统中无冲突的流水号;

假设流水号的长度是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;
    } 
}
View Code

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");
    }

}
View Code
……
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");
    }

}
View Code
线程池容量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);
    }

}
View Code
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);
    }
View Code
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;
    }
View Code
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);
    }
View Code

这里采用多线程测试的时候有一个问题,不同的线程之间是公用一个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);
    }
}
View Code

测试结果:

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);
    }
View Code
原文地址:https://www.cnblogs.com/yuanzhenliu/p/5720588.html