项目需求
最近在做一个和支付相关的项目,由于上游通道对每个商户交易额度有上限风控。因此我们需要实现一个商户轮询的机制,通过使用多个商户号,来提高交易上限,满足交易需求。
需求分析
通过需求分析,我们知道商户的交易额度是共享资源,因此涉及到了共享资源同步的问题,需要我们控制商户的交易额度,及时切换上送的交易商户号,保证在多线程的情况下运行。
功能实现
不说那么多了,直接贴代码。
根据业务需求,设计接口:
public interface IncOrDecInterface {
/**
* 恢复额度
* 当交易失败时,恢复商户可使用的交易额度。
* @param amount
*/
boolean inc(Long amount);
/**
* 减少额度
* 交易时,根据交易金额减少商户可使用额度。
* @param amount
*/
boolean dec(Long amount);
/**
* 更新商户可使用额度
* 由于商户额度每天更新一次,因此设计此接口,用于重置或更新商户可使用额度。
* @param amount
*/
boolean update(Long amount);
/**
* 判断对象是否有锁
*/
boolean isLook();
}
商户对象
public class Merchant implements IncOrDecInterface{
private static final Logger log = LoggerFactory.getLogger(UnsPayMerchant.class);
private String merchantNo;
private String signKey;
private volatile long maxTransactionAmount;
private Lock lock = new ReentrantLock();
private boolean isLook = false;
public String getMerchantNo() {
return merchantNo;
}
public void setMerchantNo(String merchantNo) {
this.merchantNo = merchantNo;
}
public String getSignKey() {
return signKey;
}
public void setSignKey(String signKey) {
this.signKey = signKey;
}
public long getMaxTransactionAmount() {
try {
lock.lock();
return maxTransactionAmount;
}finally {
lock.unlock();
}
}
public void setMaxTransactionAmount(long maxTransactionAmount) {
this.maxTransactionAmount = maxTransactionAmount;
}
@Override
public boolean inc(Long amount) {
try{
lock.lock();
isLook = true;
log.info("商户号[{}] 当前额度[{}] 充值金额[{}]",this.merchantNo,this.maxTransactionAmount,amount);
this.maxTransactionAmount =this.maxTransactionAmount + amount;
Thread.sleep(1000);
return true;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
isLook = false;
}
return false;
}
@Override
public boolean dec(Long amount) {
try{
lock.lock();
isLook = true;
log.info("当前线程[{}] 商户号[{}] 当前额度[{}] 消费金额[{}]",Thread.currentThread().getName(),this.merchantNo ,this.maxTransactionAmount, amount);
if(this.maxTransactionAmount >= amount){
this.maxTransactionAmount = this.maxTransactionAmount - amount;
return true;
}
} finally {
lock.unlock();
isLook = false;
}
return false;
}
@Override
public boolean update(Long amount) {
try{
lock.lock();
isLook = true;
log.info("商户号[{}] 当前额度[{}] 恢复额度[{}]", this.merchantNo, this.maxTransactionAmount, amount);
this.maxTransactionAmount = amount;
return true;
}finally {
lock.unlock();
isLook = false;
}
}
@Override
public boolean isLook() {
return isLook;
}
}
商户容器
public class MerchantContainer {
private static final Logger log = LoggerFactory.getLogger(MerchantContainer.class);
private static ConcurrentLinkedQueue<UnsPayMerchant> unsPayMerchants = new ConcurrentLinkedQueue<>();
/**
* 获取商户信息
* @param amount
* @return
*/
public static UnsPayMerchant getMerchant(Long amount) {
//判断是否有可用额度的商户
while(unsPayMerchants.size() > 0) {
for (UnsPayMerchant unsPayMerchant : unsPayMerchants) {
if(unsPayMerchant.isLook()){
continue;
}else{
if(unsPayMerchant.getMaxTransactionAmount() >= amount){
unsPayMerchant.dec(amount);
}else{
unsPayMerchants.remove(unsPayMerchant);
continue;
}
}
return unsPayMerchant;
}
}
return null;
}
/**
* 交易失败恢复商户可用额度
*
* @param unsPayMerchantFailure
* @param amount
* @return
*/
public static boolean payFailure(UnsPayMerchant unsPayMerchantFailure, Long amount) {
for (UnsPayMerchant unsPayMerchant : unsPayMerchants) {
if (unsPayMerchantFailure.getMerchantNo().equals(unsPayMerchant.getMerchantNo())) {
if(unsPayMerchant.isLook()){
continue;
}else {
unsPayMerchant.inc(amount);
}
return true;
} else {
unsPayMerchants.add(unsPayMerchantFailure);
}
}
return false;
}
/**
* 更新商户额度
*
* @param merchant
*/
public static void updateMaxAmount(UnsPayMerchant merchant) {
for (UnsPayMerchant unsPayMerchant : unsPayMerchants) {
if (unsPayMerchant.getMerchantNo().equals(merchant.getMerchantNo())) {
unsPayMerchant.update(merchant.getMaxTransactionAmount());
return;
}
}
log.info("向商户池添加商户[{}] 额度为[{}] 当前商户池有[{}]个商户。", merchant.getMerchantNo(), merchant.getMaxTransactionAmount(), unsPayMerchants.size());
unsPayMerchants.add(merchant);
}
测试类
public class UnspayMerchantLoopTest {
private static Thread[] threads = new Thread[8];
/**
* 前置执行任务,准备可以使用的商户数据。
*/
@Before
public void init(){
final UnsPayMerchant unsPayMerchant = new UnsPayMerchant();
unsPayMerchant.setMerchantNo("101");
unsPayMerchant.setMaxTransactionAmount(10000);
UnsPayMerchant unsPayMerchant1 = new UnsPayMerchant();
unsPayMerchant1.setMerchantNo("102");
unsPayMerchant1.setMaxTransactionAmount(10000);
UnsPayMerchant unsPayMerchant2 = new UnsPayMerchant();
unsPayMerchant2.setMerchantNo("103");
unsPayMerchant2.setMaxTransactionAmount(10000);
MerchantContainer.updateMaxAmount(unsPayMerchant);
MerchantContainer.updateMaxAmount(unsPayMerchant1);
MerchantContainer.updateMaxAmount(unsPayMerchant2);
}
@Test
public void merLoop() {
doMerchantPay();
doPayFailuer();
doReloadMerchant();
for (Thread thread : threads) {
try {
thread.join();//等待线程执行结束
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 模拟商户交易
* 启动5个线程,每执行一次休眠6秒。
*/
private void doMerchantPay(){
for (int i = 0; i < 5; i++) {
Thread upLoadThread = new Thread(new Runnable() {
@Override
public void run() {
Random random = new Random();
int maxAmount = 1000;
int minAmount = 0;
while (true) {
UnsPayMerchant merchant = MerchantContainer.getMerchant((long) random.nextInt(maxAmount) % (maxAmount - minAmount + 1) + minAmount);
if (merchant == null) {
System.out.println(Thread.currentThread().getName() + "额度用完了!");
break;
}
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
upLoadThread.start();
threads[i] = upLoadThread;
}
}
/**
* 模拟交易失败时,返回商户额度。
* 启动2个线程,每执行一次休眠5秒。
*/
private void doPayFailuer(){
for (int i = 5; i < 7; i++) {
Thread upLoadThread = new Thread(new Runnable() {
@Override
public void run() {
String[] strings = new String[]{"101", "102", "103"};
Random random = new Random();
int maxAmount = 2000;
int minAmount = 0;
while (true) {
for (int i = 0; i < 3; i++) {
UnsPayMerchant unsPayMerchant3 = new UnsPayMerchant();
unsPayMerchant3.setMerchantNo(strings[i]);
MerchantContainer.payFailure(unsPayMerchant3, (long) random.nextInt(maxAmount) % (maxAmount - minAmount + 1) + minAmount);
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
upLoadThread.start();
threads[i] = upLoadThread;
}
}
/**
* 恢复商户可用额度
* 可用于商户每天恢复额度功能。
* 单线程,每20秒执行一次。
*/
private void doReloadMerchant(){
Thread upLoadThread = new Thread(new Runnable() {
@Override
public void run() {
String[] strings = new String[]{"101", "102", "103"};
Random random = new Random();
int maxAmount = 2000;
int minAmount = 0;
while (true) {
for (int i = 0; i < 3; i++) {
UnsPayMerchant unsPayMerchant3 = new UnsPayMerchant();
unsPayMerchant3.setMerchantNo(strings[i]);
unsPayMerchant3.setMaxTransactionAmount(1000);
MerchantContainer.updateMaxAmount(unsPayMerchant3);
}
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
upLoadThread.start();
threads[7] = upLoadThread;
}
}