人妻系列无码专区av在线,国内精品久久久久久婷婷,久草视频在线播放,精品国产线拍大陆久久尤物

當(dāng)前位置:首頁(yè) > 前端設(shè)計(jì) > 正文

redis并發(fā)扣減庫(kù)存(redis并發(fā)鎖incr)

redis并發(fā)扣減庫(kù)存(redis并發(fā)鎖incr)

本篇文章給大家談?wù)剅edis并發(fā)扣減庫(kù)存,以及redis并發(fā)鎖incr對(duì)應(yīng)的知識(shí)點(diǎn),文章可能有點(diǎn)長(zhǎng),但是希望大家可以閱讀完,增長(zhǎng)自己的知識(shí),最重要的是希望對(duì)各位有所幫助...

本篇文章給大家談?wù)剅edis并發(fā)扣減庫(kù)存,以及redis并發(fā)鎖incr對(duì)應(yīng)的知識(shí)點(diǎn),文章可能有點(diǎn)長(zhǎng),但是希望大家可以閱讀完,增長(zhǎng)自己的知識(shí),最重要的是希望對(duì)各位有所幫助,可以解決了您的問題,不要忘了收藏本站喔。

接口并發(fā)量高的解決方案

這個(gè)問題的解決方案是需要是要根據(jù)具體的業(yè)務(wù)場(chǎng)景具體分析的

舉例:常見的秒殺系統(tǒng)

1.限流,通過(guò)設(shè)置服務(wù)器的連接等待數(shù)量及等待時(shí)間,以tomcat為例,通過(guò)設(shè)置maxthread的值,當(dāng)連接數(shù)超過(guò)則會(huì)放入等待隊(duì)列,同時(shí)也可設(shè)置acceptcount值,若等待數(shù)超過(guò),則會(huì)提示連接拒絕

2.引入redis,將秒殺商品數(shù)據(jù)放入redis,用戶點(diǎn)擊搶購(gòu),將商品ID去查redis,若商品存在則生成訂單,并保存到緩存,同時(shí)庫(kù)存-1,減完后判斷商品庫(kù)存是否大于0,大于0則更新緩存,否則刪除該商品緩存,并更新庫(kù)表(以上步驟僅為單線程操作,需加鎖實(shí)現(xiàn),或可考慮采用redis的list對(duì)象去實(shí)現(xiàn)單線程操作)

3.利用CDN抗壓靜態(tài)頁(yè)面流量

為了防止用戶秒殺前不斷刷新產(chǎn)生的流量,可考慮將秒殺商品詳情頁(yè)的內(nèi)容靜態(tài)化處理,除了提交訂單,其他數(shù)據(jù)都可緩存在CDN上

除此之外還可引入消息隊(duì)列,對(duì)非即時(shí)響應(yīng)的服務(wù)通過(guò)隊(duì)列進(jìn)行解耦

redis預(yù)扣庫(kù)存如何補(bǔ)償

需要用代碼補(bǔ)償,很簡(jiǎn)單,就是個(gè)逆向思維,在每次redis執(zhí)行進(jìn)行回滾之前,校驗(yàn)一下redis售出庫(kù)存==真實(shí)售出庫(kù)存,如果發(fā)現(xiàn)redis售出庫(kù)存!=真實(shí)售出庫(kù)存,這時(shí)候先重置預(yù)扣減庫(kù)存為真實(shí)售出庫(kù)存,在進(jìn)行回滾就實(shí)現(xiàn)了redis預(yù)扣減的最終一致性。

如何解決秒殺編程高并發(fā)問題

高并發(fā)問題

就是指在同一個(gè)時(shí)間點(diǎn),有大量用戶同時(shí)訪問URL地址,比如淘寶雙11都會(huì)產(chǎn)生高并發(fā)。

高并發(fā)帶來(lái)的后果

服務(wù)端??導(dǎo)致站點(diǎn)服務(wù)器、DB服務(wù)器資源被占滿崩潰。??數(shù)據(jù)的存儲(chǔ)和更新結(jié)果和理想的設(shè)計(jì)不一致。用戶角度??尼瑪,網(wǎng)站這么卡,刷新了還這樣,垃圾網(wǎng)站,不玩了

二:分析阻礙服務(wù)速度的原因1:事物行級(jí)鎖的等待

java的事務(wù)管理機(jī)制會(huì)限制在一次commit之前,下一個(gè)用戶線程是無(wú)法獲得鎖的,只能等待

2:網(wǎng)絡(luò)延遲

3:JAVA的自動(dòng)回收機(jī)制(GC)

三:處理高并發(fā)的常見方法

1:首先可以將靜態(tài)資源放入CDN中,減少后端服務(wù)器的訪問

2:訪問數(shù)據(jù)使用Redis進(jìn)行緩存

3:使用Negix實(shí)現(xiàn)負(fù)載均衡

4:數(shù)據(jù)庫(kù)集群與庫(kù)表散列

四:實(shí)戰(zhàn)優(yōu)化秒殺系統(tǒng)

1:分析原因

當(dāng)用戶在想秒殺時(shí),秒殺時(shí)間未到,用戶可能會(huì)一直刷新頁(yè)面,獲取系統(tǒng)時(shí)間和資源(A:此時(shí)會(huì)一直訪問服務(wù)器),當(dāng)時(shí)間到了,大量用戶同時(shí)獲取秒殺接口API(B),獲取API之后執(zhí)行秒殺(C),指令傳輸?shù)礁鞯胤?wù)器,服務(wù)器執(zhí)行再將傳遞到中央數(shù)據(jù)庫(kù)執(zhí)行(D),服務(wù)器啟用事務(wù)執(zhí)行減庫(kù)存操作,在服務(wù)器端JAVA執(zhí)行過(guò)程中,可能因?yàn)镴AVA的自動(dòng)回收機(jī)制,還需要一部分時(shí)間回收內(nèi)存(E)。

2:優(yōu)化思路:

面對(duì)上面分析可能會(huì)影響的過(guò)程,我們可以進(jìn)行如下優(yōu)化

A:我們可以將一些靜態(tài)的資源放到CDN上,這樣可以減少對(duì)系統(tǒng)服務(wù)器的請(qǐng)求

B:對(duì)于暴露秒殺接口,這種動(dòng)態(tài)的無(wú)法放到CDN上,我們可以采用Redis進(jìn)行緩存

request——>Redis——>MySQL

C:數(shù)據(jù)庫(kù)操作,對(duì)于MYSQL的執(zhí)行速度大約可以達(dá)到1秒鐘40000次,影響速度的還是因?yàn)樾屑?jí)鎖,我們應(yīng)盡可能減少行級(jí)鎖持有時(shí)間。

DE:對(duì)于數(shù)據(jù)庫(kù)來(lái)說(shuō)操作可以說(shuō)是相當(dāng)快了,我們可以將指令放到MYSQL數(shù)據(jù)庫(kù)上去執(zhí)行,減少網(wǎng)絡(luò)延遲以及服務(wù)器GC的時(shí)間。

3:具體實(shí)現(xiàn)

3.1:使用Redis進(jìn)行緩存

引入redis訪問客戶端Jedis

1<!--redis客戶端:Jedis-->2<dependency>3<groupId>redis.clients</groupId>4<artifactId>jedis</artifactId>5<version>2.7.3</version>6</dependency>

優(yōu)化暴露秒殺接口:對(duì)于SecviceImpl中exportSeckillUrl方法的優(yōu)化,偽代碼如下

getfromcache//首先我們要從Redis中獲取需要暴露的URL

ifnull//如果從Redis中獲取的為空

getdb//那么我們就訪問MYSQL數(shù)據(jù)庫(kù)進(jìn)行獲取

putcache//獲取到后放入Redis中

elselocgoin//否則,則直接執(zhí)行

我們一般不能直接訪問Redis數(shù)據(jù)庫(kù),首先先建立數(shù)據(jù)訪問層RedisDao,RedisDao中需要提供兩個(gè)方法,一個(gè)是getSeckill和putSeckill

在編寫這兩個(gè)方法時(shí)還需要注意一個(gè)問題,那就是序列化的問題,Redis并沒有提供序列化和反序列化,我們需要自定義序列化,我們使用protostuff進(jìn)行序列化與反序列化操作

引入protostuff依賴包

1<!--protostuff序列化依賴-->2<dependency>3<groupId>com.dyuproject.protostuff</groupId>4<artifactId>protostuff-core</artifactId>5<version>1.0.8</version>6</dependency>7<dependency>8<groupId>com.dyuproject.protostuff</groupId>9<artifactId>protostuff-runtime</artifactId>10<version>1.0.8</version>11</dependency>

編寫數(shù)據(jù)訪問層RedisDao

1packagecom.xqc.seckill.dao.cache;23importorg.slf4j.Logger;4importorg.slf4j.LoggerFactory;56importcom.dyuproject.protostuff.LinkedBuffer;7importcom.dyuproject.protostuff.ProtostuffIOUtil;8importcom.dyuproject.protostuff.runtime.RuntimeSchema;9importcom.xqc.seckill.entity.Seckill;1011importredis.clients.jedis.Jedis;12importredis.clients.jedis.JedisPool;1314/**15*Redis緩存優(yōu)化16*17*@authorACang(xqc)18*19*/20publicclassRedisDao{21privatefinalLoggerlogger=LoggerFactory.getLogger(this.getClass());2223privatefinalJedisPooljedisPool;2425publicRedisDao(Stringip,intport){26jedisPool=newJedisPool(ip,port);27}2829privateRuntimeSchema<Seckill>schema=RuntimeSchema.createFrom(Seckill.class);3031publicSeckillgetSeckill(longseckillId){32//redis操作邏輯33try{34Jedisjedis=jedisPool.getResource();35try{36Stringkey="seckill:"+seckillId;37//并沒有實(shí)現(xiàn)內(nèi)部序列化操作38//get->byte[]->反序列化->Object(Seckill)39//采用自定義序列化40//protostuff:pojo.41byte[]bytes=jedis.get(key.getBytes());42//緩存中獲取到bytes43if(bytes!=null){44//空對(duì)象45Seckillseckill=schema.newMessage();46ProtostuffIOUtil.mergeFrom(bytes,seckill,schema);47//seckill被反序列化48returnseckill;49}50}finally{51jedis.close();52}53}catch(Exceptione){54logger.error(e.getMessage(),e);55}56returnnull;57}5859publicStringputSeckill(Seckillseckill){60//setObject(Seckill)->序列化->byte[]61try{62Jedisjedis=jedisPool.getResource();63try{64Stringkey="seckill:"+seckill.getSeckillId();65byte[]bytes=ProtostuffIOUtil.toByteArray(seckill,schema,66LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));67//超時(shí)緩存68inttimeout=60*60;//1小時(shí)69Stringresult=jedis.setex(key.getBytes(),timeout,bytes);70returnresult;71}finally{72jedis.close();73}74}catch(Exceptione){75logger.error(e.getMessage(),e);76}7778returnnull;79}808182}

優(yōu)化ServiceImpl的exportSeckillUrl的方法

1publicExposerexportSeckillUrl(longseckillId){2//優(yōu)化點(diǎn):緩存優(yōu)化:超時(shí)的基礎(chǔ)上維護(hù)一致性3//1:訪問redis4Seckillseckill=redisDao.getSeckill(seckillId);5if(seckill==null){6//2:訪問數(shù)據(jù)庫(kù)7seckill=seckillDao.queryById(seckillId);8if(seckill==null){9returnnewExposer(false,seckillId);10}else{11//3:放入redis12redisDao.putSeckill(seckill);13}14}1516DatestartTime=seckill.getStartTime();17DateendTime=seckill.getEndTime();18//系統(tǒng)當(dāng)前時(shí)間19DatenowTime=newDate();20if(nowTime.getTime()<startTime.getTime()21||nowTime.getTime()>endTime.getTime()){22returnnewExposer(false,seckillId,nowTime.getTime(),startTime.getTime(),23endTime.getTime());24}25//轉(zhuǎn)化特定字符串的過(guò)程,不可逆26Stringmd5=getMD5(seckillId);27returnnewExposer(true,md5,seckillId);28}2930privateStringgetMD5(longseckillId){31Stringbase=seckillId+"/"+salt;32Stringmd5=DigestUtils.md5DigestAsHex(base.getBytes());33returnmd5;34}

3.2并發(fā)優(yōu)化:

在執(zhí)行秒殺操作死,正常的執(zhí)行應(yīng)該如下:先減庫(kù)存,并且得到行級(jí)鎖,再執(zhí)行插入購(gòu)買明細(xì),然后再提交釋放行級(jí)鎖,這個(gè)時(shí)候行級(jí)鎖鎖住了其他一些操作,我們可以進(jìn)行如下優(yōu)化,這時(shí)只需要延遲一倍。

修改executeSeckill方法如下:

1@Transactional2/**3*使用注解控制事務(wù)方法的優(yōu)點(diǎn):4*1:開發(fā)團(tuán)隊(duì)達(dá)成一致約定,明確標(biāo)注事務(wù)方法的編程風(fēng)格。5*2:保證事務(wù)方法的執(zhí)行時(shí)間盡可能短,不要穿插其他網(wǎng)絡(luò)操作RPC/HTTP請(qǐng)求或者剝離到事務(wù)方法外部.6*3:不是所有的方法都需要事務(wù),如只有一條修改操作,只讀操作不需要事務(wù)控制.7*/8publicSeckillExecutionexecuteSeckill(longseckillId,longuserPhone,Stringmd5)9throwsSeckillException,RepeatKillException,SeckillCloseException{10if(md5==null||!md5.equals(getMD5(seckillId))){11thrownewSeckillException("seckilldatarewrite");12}13//執(zhí)行秒殺邏輯:減庫(kù)存+記錄購(gòu)買行為14DatenowTime=newDate();1516try{17//記錄購(gòu)買行為18intinsertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);19//唯一:seckillId,userPhone20if(insertCount<=0){21//重復(fù)秒殺22thrownewRepeatKillException("seckillrepeated");23}else{24//減庫(kù)存,熱點(diǎn)商品競(jìng)爭(zhēng)25intupdateCount=seckillDao.reduceNumber(seckillId,nowTime);26if(updateCount<=0){27//沒有更新到記錄,秒殺結(jié)束,rollback28thrownewSeckillCloseException("seckillisclosed");29}else{30//秒殺成功commit31SuccessKilledsuccessKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);32returnnewSeckillExecution(seckillId,SeckillStatEnum.SUCCESS,successKilled);33}34}35}catch(SeckillCloseExceptione1){36throwe1;37}catch(RepeatKillExceptione2){38throwe2;39}catch(Exceptione){40logger.error(e.getMessage(),e);41//所有編譯期異常轉(zhuǎn)化為運(yùn)行期異常42thrownewSeckillException("seckillinnererror:"+e.getMessage());43}44}

3.3深度優(yōu)化:(存儲(chǔ)過(guò)程)

定義一個(gè)新的接口,使用存儲(chǔ)過(guò)程執(zhí)行秒殺操作

1/**2*執(zhí)行秒殺操作by存儲(chǔ)過(guò)程3*@paramseckillId4*@paramuserPhone5*@parammd56*/7SeckillExecutionexecuteSeckillProcedure(longseckillId,longuserPhone,Stringmd5);

實(shí)現(xiàn)executeSeckillProcedure方法

1publicSeckillExecutionexecuteSeckillProcedure(longseckillId,longuserPhone,Stringmd5){2if(md5==null||!md5.equals(getMD5(seckillId))){3returnnewSeckillExecution(seckillId,SeckillStatEnum.DATA_REWRITE);4}5DatekillTime=newDate();6Map<String,Object>map=newHashMap<String,Object>();7map.put("seckillId",seckillId);8map.put("phone",userPhone);9map.put("killTime",killTime);10map.put("result",null);11//執(zhí)行存儲(chǔ)過(guò)程,result被復(fù)制12try{13seckillDao.killByProcedure(map);14//獲取result15intresult=MapUtils.getInteger(map,"result",-2);16if(result==1){17SuccessKilledsk=successKilledDao.18queryByIdWithSeckill(seckillId,userPhone);19returnnewSeckillExecution(seckillId,SeckillStatEnum.SUCCESS,sk);20}else{21returnnewSeckillExecution(seckillId,SeckillStatEnum.stateOf(result));22}23}catch(Exceptione){24logger.error(e.getMessage(),e);25returnnewSeckillExecution(seckillId,SeckillStatEnum.INNER_ERROR);2627}2829}

編寫SeckillDao實(shí)現(xiàn)有存儲(chǔ)過(guò)程執(zhí)行秒殺的邏輯

1/**2*使用存儲(chǔ)過(guò)程執(zhí)行秒殺3*@paramparamMap4*/5voidkillByProcedure(Map<String,Object>paramMap);

在Mybatis中使用

1<!--mybatis調(diào)用存儲(chǔ)過(guò)程-->2<selectid="killByProcedure"statementType="CALLABLE">3callexecute_seckill(4#{seckillId,jdbcType=BIGINT,mode=IN},5#{phone,jdbcType=BIGINT,mode=IN},6#{killTime,jdbcType=TIMESTAMP,mode=IN},7#{result,jdbcType=INTEGER,mode=OUT}8)9</select>

在Controller層使用

1@ResponseBody2publicSeckillResult<SeckillExecution>execute(@PathVariable("seckillId")LongseckillId,3@PathVariable("md5")Stringmd5,4@CookieValue(value="killPhone",required=false)Longphone){5//springmvcvalid6if(phone==null){7returnnewSeckillResult<SeckillExecution>(false,"未注冊(cè)");8}9SeckillResult<SeckillExecution>result;10try{11//存儲(chǔ)過(guò)程調(diào)用.12SeckillExecutionexecution=seckillService.executeSeckillProcedure(seckillId,phone,md5);13returnnewSeckillResult<SeckillExecution>(true,execution);14}catch(RepeatKillExceptione){15SeckillExecutionexecution=newSeckillExecution(seckillId,SeckillStatEnum.REPEAT_KILL);16returnnewSeckillResult<SeckillExecution>(true,execution);17}catch(SeckillCloseExceptione){18SeckillExecutionexecution=newSeckillExecution(seckillId,SeckillStatEnum.END);19returnnewSeckillResult<SeckillExecution>(true,execution);20}catch(Exceptione){21logger.error(e.getMessage(),e);22SeckillExecutionexecution=newSeckillExecution(seckillId,SeckillStatEnum.INNER_ERROR);23returnnewSeckillResult<SeckillExecution>(true,execution);24}25}

至此,此系統(tǒng)的代碼優(yōu)化工作基本完成。但是在部署時(shí)可以將其更加優(yōu)化,我們一般會(huì)使用如下架構(gòu)

有些程序員一直堅(jiān)持反對(duì)使用redis怎么辦

分享大佬的回答,似乎很有道理。

不要告訴我們用不用redis,你得告訴我們你為什么想要用redis,不用redis業(yè)務(wù)會(huì)有什么問題?天下沒有免費(fèi)的午餐,不動(dòng)腦子直接上緩存/NOSQL可能會(huì)帶來(lái)更多更嚴(yán)重的問題。

單一數(shù)據(jù)庫(kù)最大的好處在于事務(wù)性實(shí)現(xiàn)簡(jiǎn)單,由數(shù)據(jù)庫(kù)自己保證。舉個(gè)簡(jiǎn)單的例子,下訂單需要扣除一個(gè)庫(kù)存,然后插入一條訂單條目,如果庫(kù)存和訂單都是數(shù)據(jù)庫(kù)表項(xiàng)的話這個(gè)事務(wù)是無(wú)懈可擊的,如果庫(kù)存在redis里,訂單條目是MySQL,通常就需要先寫redis,成功之后再寫數(shù)據(jù)庫(kù),如果寫數(shù)據(jù)庫(kù)失敗了還需要回滾redis,如果最后這個(gè)回滾因?yàn)榫W(wǎng)絡(luò)之類的原因失敗了,就會(huì)多扣一個(gè)庫(kù)存。不要以為這些事情很好解決,事務(wù)性處理的復(fù)雜性遠(yuǎn)遠(yuǎn)超過(guò)你的想象,比如說(shuō)還有寫MySQL在提交的一瞬間連接斷了這種情況,你都沒法判斷提交到底成功了還是失敗了,那你的redis是回滾還是不回滾?

所以引入新的層一定要說(shuō)清楚,你為了什么目的一定要用緩存/NOSQL,能接受什么樣的一致性模型,否則就是在胡鬧。

javaWeb 在系統(tǒng)高并發(fā)的情況下生成有序流水號(hào)

1,建一個(gè)第三方id生成服務(wù)器,用rpc調(diào)用得到id

2,數(shù)據(jù)庫(kù)存一個(gè)id種子,每次取出種子,然后把種子及其之后1000條id放入內(nèi)存,種子加1000后馬上回寫數(shù)據(jù)庫(kù),

這樣做的話,如果各服務(wù)器在同一時(shí)刻用完1000條Id,同時(shí)又來(lái)取1000條id的時(shí)候可能會(huì)有并發(fā)問題。但是可能性不大。

3,捕獲id重復(fù)異常,碰到異常后再去取一次id

關(guān)于redis并發(fā)扣減庫(kù)存到此分享完畢,希望能幫助到您。