javaはredisでどうやってエレクトビジネスのプラットフォームを処理しますか?
一、会社に来たばかりの時間は長くないです。会社の元の同僚がこのようなコードを書いているのを見ました。
1、これは一つの方法で以下のコードを呼び出す部分です。
1、これは一つの方法で以下のコードを呼び出す部分です。
if (!this.checkSoldCountByRedisDate(key, limitCount, buyCount, endDate)) {// 10:
throw new ServiceException(" 【" + commodityTitle + "】, ");
}
2、次はセールの判断方法です。/** */
// :1;synchronized
private synchronized boolean checkSoldCountByRedisDate(String key, int limitCount, int buyCount, Date endDate) {
boolean flag = false;
if (redisUtil.exists(key)) {// :2;redisUtil.exists(key)
Integer soldCount = (int) redisUtil.get(key);// :3;redisUtil.get(key)
Integer totalSoldCount = soldCount + buyCount;
if (limitCount > (totalSoldCount)) {
flag = false;// :4;flag = false
} else {
if (redisUtil.tryLock(key, 80)) {// :5;rdisUtil.tryLock(key, 80)
redisUtil.remove(key);// // :6;redisUtil.remove(key)
redisUtil.set(key, totalSoldCount);// :7;redisUtil.set(key, totalSoldCount)
flag = true;
} else {
throw new ServiceException(" , ");
}
}
} else {
// :8;redisUtil.set(key, new String("buyCount"), DateUtil.diffDateTime(endDate, new Date()))
redisUtil.set(key, new String("buyCount"), DateUtil.diffDateTime(endDate, new Date()));
flag = false;
}
return flag;
}
3、上面提到的redisUtil类中的方法,其中redisTemplate为org.springframework.data.redis.core.RedisTemplate;这个不了解的可以去网上找下,spring-data-redis.jar的相关文档,贴出来redisUtil用到的相关方法:4、上記のDateUtil類は、下にファイル形式で送ります。/** * value * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * timeout. * * @param key * @param timeout * , * @return true, false */ public boolean tryLock(String key, long timeout) { boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(key, ""); if (isSuccess) {// :9;redisTemplate.expire redisTemplate.expire(key, timeout, TimeUnit.MILLISECONDS); } return isSuccess; } /** * * * @param key * @return */ public Object get(final String key) { Object result = null; ValueOperations
operations = redisTemplate.opsForValue(); result = operations.get(key); return result; } /** * value * * @param key */ public void remove(final String key) { if (exists(key)) { redisTemplate.delete(key); } } /** * * * @param key * @param value * @return */ public boolean set(final String key, Object value) { return set(key, value, null); } /** * * @Title: set * @Description: * @param key * @param value * @param expireTime * @return boolean * @throws */ public boolean set(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations operations = redisTemplate.opsForValue(); operations.set(key, value); if (expireTime != null) { redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); } result = true; } catch (Exception e) { e.printStackTrace(); } return result; }
二、今私達はこのコードを解読して、作者の意図を見てみます。そして問題点はどこにありますか?もっと多くの人に理解してもらいたいです。電気商のプラットフォームでは、買い占め、秒殺時に出てくるスーパー販売の状況をどう処理しますか?
1、パラメータの説明、上のchockSold CountByRedisDate方法は、それぞれ4つのパラメータがあります。
key:購入数量のカウントは、redisキャッシュにあるkeyです。
limitCount:ソースを検索して発見しました。元の注釈は:総購入数量制限;
buyCount:現在一回の注文のために買う数量;
endDate:活動終了時間;
2、上記の表示によって、著者の意図を解析します。
表示1:synchronizedキーワードで同期を実現したいですが、大丈夫そうです。
表示2:redisUtil.exists方法でkeyが存在するかどうかを判断します。大丈夫そうです。
表示3:redisUtil.get(key)購入総数を取得しても大丈夫です。
表示4:ユーザー総購入数量
表示5:redisUtil.tryLockにロックをかけて、スーパーセールスの処理を実現したいです。後ろのコードはカウントを実現します。問題がないようです。6:5を表示してロックをかけたら、redisUtil.removeによって解除されます。
表示7:redisUtil.setを通じてユーザーの購買量を記録します。原作者はこの意味です。
表示8:表示2で判断したkeyが存在しない場合は、ここでkeyを作成します。コードもこのように書きます。
表示9:元の著者はデッドロックが発生したくないと思います。redis Template.expireでロックタイムアウトをする方式でデッドロックを解除してもいいです。
3、上の作者の意図に対して分析して、私達は見にきて、問題がないように見えて、本当に大丈夫ですか?へへへ安いですね
下を見てください。各表示を見て、問題が発生する可能性があります。
表示1:synchronizedキーワードは、分布式の高合併の場合、同期処理が実現できなく、信頼テストの下で分かります。
それでは問題が発生する可能性があります。
同じユーザからA、Bまたは異なるユーザにA、Bを開始してください。同時にchockSold CountByRedisDate方法に入り、実行します。
表示2:買い占め開始時、A、Bは同時に率先して買い占めを要求し、check Sold CountByRedisDate方法に入る。
A、B要求はredisUtil.exists方法によってkeyが存在しないと判断され、
したがって、表示8の部分を実行し、同時にkeyを作成する動作を実行します。
本当にいい穴ですね。最初に買い占めを開始しても、奪えません。
表示3:A、Bが同時に到着することを要求する場合、A、Bが現在購入しているbuyCountパラメータは40であり、3が取得したソロdCount=50であり、limitCount=100であると仮定する。
この時A、Bにお願いしたtotalSoldCountは全部90です。また問題が来ました。
表示4:limitCount>(totalSoldCount):total Sold Count=90、limitCount=100、いくつかの時flagsはfalseに等しく、
10を表示する場所に戻り、異常情報を投稿します。
AもBも商品が取れてないようにお願いします。何の鬼ですか?全部で90を買って、総購入量は100です。これで異常を出して活動の制限購入数に達します。分かりませんでした。
表示5:ここにロックをかける場合、9:isSuccess=trueまで実行すると、クライアントは中断し、9以降のコードを表示しないでください。
しまった、デッドロックが現れた!誰も奪おうとしないでください
以下、A要求がB要求より少し遅いと仮定した場合、A、B要求のbuyCountパラメータは40で、3で得られたsoldCount=50、limitCount=100で実行されるelseのコードを表示します。
checkSold CountByRedisDate方法の中の:表示6、7:A要求が先に到着し、ロックが成功したと仮定して、ロックを解除しました。設定したkeyの値は90後です。ここでB要求もロックが成功し、ロックが解除されました。設定keyの値は90です。else { if (redisUtil.tryLock(key, 80)) { redisUtil.remove(key);// redisUtil.set(key, totalSoldCount); flag = true; } else { throw new ServiceException(" , "); } }
それでは問題が来ました。
A、Bはそれぞれ40を買って、もとの購入数は50で、総数量は100で、40+40+50=130、最大数量より大きいですが、成功的に実行しました。会社はどうやって取引先に説明しますか?
夜明けになって、無駄話は多くなくなりました。肝心な点は問題の処理を見て、直接コードをつけましょう。コールしたところは見ませんでしたが、コードもあまりありません。コメントがあります。見れば分かります。上記の方法で使うDateUtil類に関する方法を以下に貼り付けます。/** * * ------2016 6 17 * * @Title: checkSoldCountByRedisDate * @Description: ( ) * @param @param key key * @param @param limitCount * @param @param buyCount * @param @param endDate * @param @param lock unDieLock lock * @param @param expire ( ) * @param @return * @return boolean * @throws */ private boolean checkSoldCountByRedisDate(String key, int limitCount, int buyCount, Date endDate, String lock, int expire) { boolean check = false; if (this.lock(lock, expire)) { Integer soldCount = (Integer) redisUtil.get(key); Integer totalSoldCount = (soldCount == null ? 0 : soldCount) + buyCount; if (totalSoldCount <= limitCount) { redisUtil.set(key, totalSoldCount, DateUtil.diffDateTime(endDate, new Date())); check = true; } redisUtil.remove(lock); } else { if (this.unDieLock(lock)) { logger.info(" "); } else { throw new ServiceException(" , "); } } return check; } /** * * ------2016 6 17 * * @Title: lock * @Description: * @param @param lock * @param @param expire ( ) * @param @return * @return Boolean * @throws */ @SuppressWarnings("unchecked") public Boolean lock(final String lock, final int expire) { return (Boolean) redisTemplate.execute(new RedisCallback
() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { boolean locked = false; byte[] lockValue = redisTemplate.getValueSerializer().serialize(DateUtil.getDateAddMillSecond(null, expire)); byte[] lockName = redisTemplate.getStringSerializer().serialize(lock); locked = connection.setNX(lockName, lockValue); if (locked) connection.expire(lockName, TimeoutUtils.toSeconds(expire, TimeUnit.MILLISECONDS)); return locked; } }); } /** * * ------2016 6 17 * * @Title: unDieLock * @Description: * @param @param lock * @param @return * @return Boolean * @throws */ @SuppressWarnings("unchecked") public Boolean unDieLock(final String lock) { boolean unLock = false; Date lockValue = (Date) redisTemplate.opsForValue().get(lock); if (lockValue != null && lockValue.getTime() <= (new Date().getTime())) { redisTemplate.delete(lock); unLock = true; } return unLock; } ここで終わります。/** * ( ) * @param date Date * @param date1 Date * @return int * @author */ public static Long diffDateTime(Date date, Date date1) { return (Long) ((getMillis(date) - getMillis(date1))/1000); } public static long getMillis(Date date) { Calendar c = Calendar.getInstance(); c.setTime(date); return c.getTimeInMillis(); } /** * Date * * @param date * @param millSecond * @return */ public static Date getDateAddMillSecond(Date date, int millSecond) { Calendar cal = Calendar.getInstance(); if (null != date) {// cal.setTime(date); } cal.add(Calendar.MILLISECOND, millSecond); return cal.getTime(); }
新規追加:import java.util.Calendar; import java.util.Date; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.TimeoutUtils; import org.springframework.stereotype.Component; import cn.mindmedia.jeemind.framework.utils.redis.RedisUtils; import cn.mindmedia.jeemind.utils.DateUtils; /** * @ClassName: LockRetry * @Description: * @author * @date 2017 7 29 11:54:54 * */ @SuppressWarnings("rawtypes") @Component("lockRetry") public class LockRetry { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private RedisTemplate redisTemplate; /** * * @Title: retry * @Description: * @author * @param @param lock * @param @param expire ( ), 10 * @param @param num , 3 * @param @param interval * @param @param forceLock , ; * @param @return * @param @throws Exception * @return Boolean * @throws */ @SuppressWarnings("unchecked") public Boolean retryLock(final String lock, final int expire, final int num, final long interval, final boolean forceLock) throws Exception { Date lockValue = (Date) redisTemplate.opsForValue().get(lock); if (forceLock) { RedisUtils.remove(lock); } if (num <= 0) { if (null != lockValue && lockValue.getTime() >= (new Date().getTime())) { logger.debug(String.valueOf((lockValue.getTime() - new Date().getTime()))); Thread.sleep(lockValue.getTime() - new Date().getTime()); RedisUtils.remove(lock); return retryLock(lock, expire, 1, interval, forceLock); } return false; } else { return (Boolean) redisTemplate.execute(new RedisCallback
() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { boolean locked = false; byte[] lockValue = redisTemplate.getValueSerializer().serialize(DateUtils.getDateAdd(null, expire, Calendar.SECOND)); byte[] lockName = redisTemplate.getStringSerializer().serialize(lock); logger.debug(lockValue.toString()); locked = connection.setNX(lockName, lockValue); if (locked) return connection.expire(lockName, TimeoutUtils.toSeconds(expire, TimeUnit.SECONDS)); else { try { Thread.sleep(interval); return retryLock(lock, expire, num - 1, interval, forceLock); } catch (Exception e) { e.printStackTrace(); return locked; } } } }); } } } /** * * @Title: getDateAddMillSecond * @Description: (TODO) * @author * @param @param date * @param @param millSecond * @param @return * @return Date * @throws */ public static Date getDateAdd(Date date, int expire, int idate) { Calendar calendar = Calendar.getInstance(); if (null != date) {// calendar.setTime(date); } calendar.add(idate, expire); return calendar.getTime(); }
/** * value * @param key */ public static void remove(final String key) { if (exists(key)) { redisTemplate.delete(key); } }
/** * value * @param key * @return */ public static boolean exists(final String key) { return stringRedisTemplate.hasKey(key); }
private static StringRedisTemplate stringRedisTemplate = ((StringRedisTemplate) SpringContextHolder.getBean("stringRedisTemplate"));