2014年5月9日 星期五

HiLo implemetation with Web API

HiLo implemetation with Web API
Author : JB


1.  Create table schema (with Sql Server)

CREATE TABLE HiLo
(
KEY_NAME varchar(100) NOT NULL,
NEXT_HI bigint NOT NULL,
MAX_VAL bigint NOT NULL default 0
);
ALTER TABLE HiLo ADD CONSTRAINT PK_HiLo PRIMARY KEY (KEY_NAME);



2.  SQL
Insert (Create HiLo) :
//Get
internal
void BindGetNextHiSql(ref Adaman.Infra.Lib.ISql sqlObj,String keyName)
{
sqlObj.SqlCommand = String.Format(@"
SELECT NEXT_HI,MAX_VAL FROM {0}
WHERE KEY_NAME=@KEY_NAME ", base.HILO_TABLE);

sqlObj.SqlParams = new SqlParameter[]{
new SqlParameter("@KEY_NAME", keyName)
};
}
//Get
internal void BindUptNextHiSql(
ref Adaman.Infra.Lib.ISql sqlObj, String keyName, long interval)
{
sqlObj.SqlCommand = String.Format(@"
UPDATE {0}
SET
NEXT_HI = CASE WHEN (NEXT_HI+1)*{1} > MAX_VAL THEN 0 ELSE (NEXT_HI+1) END
WHERE KEY_NAME=@KEY_NAME ", base.HILO_TABLE, interval);

sqlObj.SqlParams = new SqlParameter[]{
new SqlParameter("@KEY_NAME", keyName)
};
}



3.  Domain

Create HiLo :
Just call the sql to create a HiLo record into the database. No other business logic.

/// HiLoCreateManager
public class HiLoCreateManager : IDisposable
{
private IDbModule _dbModule = null;
public bool CreateSequence(Adaman.Infra.HiLo.HiLo hl)
{
bool isCreatedOk = false;
//Check if the Key name exists ?
if (this.ChkIfKeyNameExist(hl.KeyName))
isCreatedOk = false;
else {
this.CreateNewHiLoInstance(hl); //Use Get HiLo sql
isCreatedOk = true;
}
return isCreatedOk;
}
}

/// Create new HiLo instance in database
private void createNewHiLoInstance(Adaman.Infra.HiLo.HiLo hl)
{
ISql sqlObj = new Sql();               
using (SqlHiLoDomainManager sqlMnger = new SqlHiLoDomainManager())
{
sqlMnger.BindCreateNewHiLoInstanceSql(ref sqlObj, hl);
}
//insert
this._dbModule.ParaExecuteNonQuery(sqlObj.SqlCommand, sqlObj.SqlParams);
}


Get Next Hi :
In HiLo algorithm, we have a Singleton instance with minHi and maxHi. Every time one try to get the nextHi value, it comes from minHi to maxHi.
After the nextHi between the interval of these two values runs out, it will get another minHi and maxHi.
For example, when minHi = 0 and maxHi = 99, we will get nextHi like …
0, 1, …  99. After 99 is used, the minHi and maxHi will set to 100 and 199 as the interval is set to 100.
Besides, when we get the minHi/maxHi value, we must update the NEXT_HI for future use.

public sealed class HiLoGetValManager:IDisposable
{
        private String _keyName = String.Empty; //紀錄Key Name
        private long _minHiVal = 0;
        private long _maxHiVal = 0;

        private static long INTERVAL = 1000; //minHi~maxHi
        private static bool isCreated = false; //是否已intial min/max value
        private static object block = new object();
       
       
        /// Get Next HiLo value
        public long GetNextVal(String keyName)
        {
          
//First time loaded or change the key name
            if (!isCreated || !keyName.Equals(this._keyName))
            {
                this.syncSetInstance(keyName); //Get minHi/maxHi
                isCreated = true;
                this._keyName = keyName;
            }

            try
            {
                if (_minHiVal < _maxHiVal)
                {
                    _minHiVal++;
                }
                else //If minHi exceeds maxHi, get minHi/maxHi again!
                {
                    this.syncSetInstance(keyName);
                    _minHiVal++;
                }

                return this._minHiVal;
            }
            catch (Exception)
            {
                throw;
            }
        }


        /// <summary>
        /// 取得NEXT HI
        /// </summary>
        private void setMinMaxHi(String keyName)
        {
            long nextHi = 0;
            long maxVal = 0;

            try
            {
                TransactionOptions _transOptions = new TransactionOptions();
                _transOptions.IsolationLevel = System.Transactions.IsolationLevel.Serializable;
                _transOptions.Timeout = new TimeSpan(0, 1, 0); //timeout : 1 hr

                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, _transOptions))
                using (SqlHiLoGetValueSingleton sqlHiLoGetValueSingleton = new SqlHiLoGetValueSingleton())
                {
                    //Get current Hi value
                    ISql sqlObj = new Adaman.Infra.Lib.Sql();
                    sqlHiLoGetValueSingleton.BindGetNextHiSql(ref sqlObj, keyName);
                    DataTable dtNextHi = this._dbModule.ParaGetDataTable(sqlObj.SqlCommand, sqlObj.SqlParams);
                    if (dtNextHi != null && dtNextHi.Rows.Count > 0)
                    {
                        long.TryParse(dtNextHi.Rows[0].ItemArray[0].ToString(), out nextHi); //current Hi value
                        long.TryParse(dtNextHi.Rows[0].ItemArray[1].ToString(), out maxVal); //Max Value
                    }
                    sqlObj.Dispose();

                    //Set minHi/maxHi value with HiLo algorithm
                    this.setMinMaxHiValStrategy(ref nextHi, maxVal);

                    //Update NEXT HI in database
                    ISql sqlUpt = new Adaman.Infra.Lib.Sql();
                    sqlHiLoGetValueSingleton.BindUptNextHiSql(ref sqlUpt, keyName, INTERVAL);
                    this._dbModule.ParaGetDataTable(sqlUpt.SqlCommand, sqlUpt.SqlParams);
                    sqlUpt.Dispose();

                    scope.Complete();
                }
            }
            catch (Exception)
            {
                throw;
            }
           
        }

        /// lock the resource and avoid the instance be changed at the same time
        private void syncSetInstance(String keyName)
        {       
lock (block){
this.setMinMaxHi(keyName);
}
        }
        /// Set minHi/maxHi value
        private void setMinMaxHiValStrategy(ref long nextHi, long maxVal)
        {
            try
            {
                //Normal case
                this._minHiVal = nextHi * INTERVAL;
                this._maxHiVal = nextHi * INTERVAL + (INTERVAL - 1);
               
                //Check the MAX VALUE constraint
                if (this._minHiVal > maxVal)
                {
                    nextHi = 0;
                    this._minHiVal = nextHi * INTERVAL;
                    this._maxHiVal = nextHi * INTERVAL + (INTERVAL - 1);
                }
                else if (this._minHiVal <= maxVal && this._maxHiVal > maxVal)
                {
                    this._maxHiVal = maxVal;
                }
            }
            catch (Exception)
            {
                throw;
            }
           
        }
}


4.  Web API
I skipped the codes in Web API.
The important thing is that we must initial a singleton instance (
HiLoGetValManager) in Web API, in order to make sure everyone get the unique HiLo value with it.

So in WebApiConfig.cs

//Singleton instance
public static Icash.HiLo.Domain.HiLoGetValManager HiLoGetValMnger = null;

public
static void Register(HttpConfiguration config)
{
//
HiLoGetValMnger=new Domain.HiLoGetValManager();
}




5.  Test Result

沒有留言:

張貼留言