用戶金額的終極解決方案--Redis Lua

咱們在開發各類訂單系統的時候都會碰到一個必須面對的問題,用戶金額的最終結算。通常咱們會把金額存在數據庫中,採用行級鎖的方式來對金額的變更來進行處理,可是在高併發下,對於結算時間有要求的狀況下,這種方式是極爲低效的。redis

因此咱們打算將用戶的金額放在Redis中進行處理,不管用戶的查詢,充值,付帳,開獎增長都會有一個高效的返回。可是咱們遇到的問題依然是是否要用鎖的問題。由於Redis是單線程的,在多個動做同時執行的時候,要保證咱們代碼執行到每一步,他的金額都不會錯亂,好比不會小於0的狀況下依然能夠付帳成功。若是使用分佈式鎖來鎖定用戶每次操做,那麼大量的用戶進行操做時對性能的影響是很是大的,這個時候咱們的主角登場了——Redis Lua,它能夠將多條Redis命令合成一條原子操做命令,避免在判斷金額大小的時候,其餘線程修改了金額,在非加鎖的狀況下後續的操做殊不知道的尷尬。固然最後咱們能夠經過異步的方式將數據更新到數據庫中。再存儲每一條金額的變更流水日誌,以方便對帳和校準。數據庫

具體實現爲併發

在接口中添加兩個方法異步

/**
 * 支付金額
 * @param key
 * @param amount
 * @return
 */
Object payMonery(String key,String amount);

/**
 * 增長金額
 * @param key
 * @param amount
 * @return
 */
Object addMonery(String key,String amount);

集羣實現類方法分佈式

@Override
public Object payMonery(String key, String amount) {
    String script = "if redis.call('exists',KEYS[1]) == 1 then if (redis.call('get',KEYS[1]) + ARGV[1]) >= 0 then" +
            " redis.call('incrbyfloat',KEYS[1],ARGV[1]) return redis.call('set',KEYS[1],string.format('%0.2f',redis.call('get',KEYS[1])))" +
            "  else return 0 end else return 0  end";
    return jedisCluster.eval(script,1,key,"-" + amount);
}

@Override
public Object addMonery(String key, String amount) {
    String script = "if redis.call('exists',KEYS[1]) == 1 then  redis.call('incrbyfloat',KEYS[1],ARGV[1])" +
            " return redis.call('set',KEYS[1],string.format('%0.2f',redis.call('get',KEYS[1]))) " +
            " else return 0  end";
    return jedisCluster.eval(script,1,key, amount);
}

單機實現類方法ide

@Override
public Object payMonery(String key, String amount) {
    String script = "if redis.call('exists',KEYS[1]) == 1 then if (redis.call('get',KEYS[1]) + ARGV[1]) >= 0 then" +
            " redis.call('incrbyfloat',KEYS[1],ARGV[1]) return redis.call('set',KEYS[1],string.format('%0.2f',redis.call('get',KEYS[1])))" +
            "  else return 0 end else return 0  end";
    return execute((jedis -> jedis.eval(script,1,key,"-" + amount)));
}

@Override
public Object addMonery(String key, String amount) {
    String script = "if redis.call('exists',KEYS[1]) == 1 then  redis.call('incrbyfloat',KEYS[1],ARGV[1])" +
            " return redis.call('set',KEYS[1],string.format('%0.2f',redis.call('get',KEYS[1]))) " +
            " else return 0  end";
    return execute((jedis -> jedis.eval(script,1,key, amount)));
}
相關文章
相關標籤/搜索