沧州三亚菏泽经济预测自然
投稿投诉
自然科学
知识物理
化学生物
地理解释
预测理解
本质社会
人类现象
行为研究
经济政治
心理结构
关系指导
人文遗产
菏泽德阳
山西湖州
宝鸡上海
茂名内江
三亚信阳
长春北海
西安安徽
黄石烟台
沧州湛江
肇庆鹤壁
六安韶关
成都钦州

实现高并发秒杀的七种方式

5月9日 温柔冢投稿
  引言商品秒杀超卖解决商品超卖方式一(改进版加锁)方式二(AOP版加锁)方式三(悲观锁一)方式四(悲观锁二)方式五(乐观锁)方式六(阻塞队列)方式七(Disruptor队列)小结1。引言
  高并发场景在现场的日常工作中很常见,特别是在互联网公司中,这篇文章就来通过秒杀商品来模拟高并发的场景。文章末尾会附上文章的所有代码、脚本和测试用例。本文环境:SpringBoot2。5。7MySQL8。0XMybatisPlusSwagger2。9。2模拟工具:Jmeter模拟场景:减库存创建订单模拟支付2。商品秒杀超卖
  在开发中,对于下面的代码,可能很熟悉:在Service里面加上Transactional事务注解和Lock锁
  控制层:ControllerApiOperation(value秒杀实现方式Lock加锁)PostMapping(startlock)publicResultstartLock(longskgId){try{log。info(开始秒杀方式一。。。);finallonguserId(int)(newRandom()。nextDouble()(99999100001))10000;ResultresultsecondKillService。startSecondKillByLock(skgId,userId);if(result!null){log。info(用户:{}{},userId,result。get(msg));}else{log。info(用户:{}{},userId,哎呦喂,人也太多了,请稍后!);}}catch(Exceptione){e。printStackTrace();}finally{}returnResult。ok();}
  业务层:ServiceOverrideTransactional(rollbackForException。class)publicResultstartSecondKillByLock(longskgId,longuserId){lock。lock();try{校验库存SecondKillsecondKillsecondKillMapper。selectById(skgId);IntegernumbersecondKill。getNumber();if(number0){扣库存secondKill。setNumber(number1);secondKillMapper。updateById(secondKill);创建订单SuccessKilledkillednewSuccessKilled();killed。setSeckillId(skgId);killed。setUserId(userId);killed。setState((short)0);killed。setCreateTime(newTimestamp(System。currentTimeMillis()));successKilledMapper。insert(killed);模拟支付PaymentpaymentnewPayment();payment。setSeckillId(skgId);payment。setSeckillId(skgId);payment。setUserId(userId);payment。setMoney(40);payment。setState((short)1);payment。setCreateTime(newTimestamp(System。currentTimeMillis()));paymentMapper。insert(payment);}else{returnResult。error(SecondKillStateEnum。END);}}catch(Exceptione){thrownewScorpiosException(异常了个乖乖);}finally{lock。unlock();}returnResult。ok(SecondKillStateEnum。SUCCESS);}
  对于上面的代码应该没啥问题吧,业务方法上加事务,在处理业务的时候加锁。
  但上面这样写法是有问题的,会出现超卖的情况,看下测试结果:模拟1000个并发,抢100商品
  Jmeter不了解的,可以参考这篇文章:
  https:blog。csdn。netzxd1435513775articledetails106372446
  这里在业务方法开始加了锁,在业务方法结束后释放了锁。但这里的事务提交却不是这样的,有可能在事务提交之前,就已经把锁释放了,这样会导致商品超卖现象。所以加锁的时机很重要!3。解决商品超卖
  对于上面超卖现象,主要问题出现在事务中锁释放的时机,事务未提交之前,锁已经释放。(事务提交是在整个方法执行完)。如何解决这个问题呢,就是把加锁步骤提前可以在controller层进行加锁可以使用Aop在业务方法执行之前进行加锁3。1方式一(改进版加锁)ApiOperation(value秒杀实现方式Lock加锁)PostMapping(startlock)publicResultstartLock(longskgId){在此处加锁lock。lock();try{log。info(开始秒杀方式一。。。);finallonguserId(int)(newRandom()。nextDouble()(99999100001))10000;ResultresultsecondKillService。startSecondKillByLock(skgId,userId);if(result!null){log。info(用户:{}{},userId,result。get(msg));}else{log。info(用户:{}{},userId,哎呦喂,人也太多了,请稍后!);}}catch(Exceptione){e。printStackTrace();}finally{在此处释放锁lock。unlock();}returnResult。ok();}
  上面这样的加锁就可以解决事务未提交之前,锁释放的问题,可以分三种情况进行压力测试:并发数1000,商品100并发数1000,商品1000并发数2000,商品1000
  对于并发量大于商品数的情况,商品秒杀一般不会出现少卖的请况,但对于并发数小于等于商品数的时候可能会出现商品少卖情况,这也很好理解。
  对于没有问题的情况就不贴图了,因为有很多种方式,贴图会太多
  3。2方式二(AOP版加锁)
  对于上面在控制层进行加锁的方式,可能显得不优雅,那就还有另一种方式进行在事务之前加锁,那就是AOP
  自定义AOP注解Target({ElementType。PARAMETER,ElementType。METHOD})Retention(RetentionPolicy。RUNTIME)DocumentedpublicinterfaceServiceLock{Stringdescription()}
  定义切面类Slf4jComponentScopeAspectOrder(1)order越小越是最先执行,但更重要的是最先执行的最后结束publicclassLockAspect{思考:为什么不用synchronizedservice默认是单例的,并发下lock只有一个实例privatestaticLocklocknewReentrantLock(true);互斥锁参数默认false,不公平锁Service层切点用于记录错误日志Pointcut(annotation(com。scorpios。secondkill。aop。ServiceLock))publicvoidlockAspect(){}Around(lockAspect())publicObjectaround(ProceedingJoinPointjoinPoint){lock。lock();Otry{objjoinPoint。proceed();}catch(Throwablee){e。printStackTrace();thrownewRuntimeException();}finally{lock。unlock();}}}
  在业务方法上添加AOP注解OverrideServiceLock使用Aop进行加锁Transactional(rollbackForException。class)publicResultstartSecondKillByAop(longskgId,longuserId){try{校验库存SecondKillsecondKillsecondKillMapper。selectById(skgId);IntegernumbersecondKill。getNumber();if(number0){扣库存secondKill。setNumber(number1);secondKillMapper。updateById(secondKill);创建订单SuccessKilledkillednewSuccessKilled();killed。setSeckillId(skgId);killed。setUserId(userId);killed。setState((short)0);killed。setCreateTime(newTimestamp(System。currentTimeMillis()));successKilledMapper。insert(killed);支付PaymentpaymentnewPayment();payment。setSeckillId(skgId);payment。setSeckillId(skgId);payment。setUserId(userId);payment。setMoney(40);payment。setState((short)1);payment。setCreateTime(newTimestamp(System。currentTimeMillis()));paymentMapper。insert(payment);}else{returnResult。error(SecondKillStateEnum。END);}}catch(Exceptione){thrownewScorpiosException(异常了个乖乖);}returnResult。ok(SecondKillStateEnum。SUCCESS);}
  控制层:ApiOperation(value秒杀实现方式二Aop加锁)PostMapping(startaop)publicResultstartAop(longskgId){try{log。info(开始秒杀方式二。。。);finallonguserId(int)(newRandom()。nextDouble()(99999100001))10000;ResultresultsecondKillService。startSecondKillByAop(skgId,userId);if(result!null){log。info(用户:{}{},userId,result。get(msg));}else{log。info(用户:{}{},userId,哎呦喂,人也太多了,请稍后!);}}catch(Exceptione){e。printStackTrace();}returnResult。ok();}
  这种方式在对锁的使用上,更高阶、更美观!3。3方式三(悲观锁一)
  除了上面在业务代码层面加锁外,还可以使用数据库自带的锁进行并发控制。
  悲观锁,什么是悲观锁呢?通俗的说,在做任何事情之前,都要进行加锁确认。这种数据库级加锁操作效率较低。
  使用forupdate一定要加上事务,当事务处理完后,forupdate才会将行级锁解除
  如果请求数和秒杀商品数量一致,会出现少卖ApiOperation(value秒杀实现方式三悲观锁)PostMapping(startpeslockone)publicResultstartPesLockOne(longskgId){try{log。info(开始秒杀方式三。。。);finallonguserId(int)(newRandom()。nextDouble()(99999100001))10000;ResultresultsecondKillService。startSecondKillByUpdate(skgId,userId);if(result!null){log。info(用户:{}{},userId,result。get(msg));}else{log。info(用户:{}{},userId,哎呦喂,人也太多了,请稍后!);}}catch(Exceptione){e。printStackTrace();}returnResult。ok();}
  业务逻辑OverrideTransactional(rollbackForException。class)publicResultstartSecondKillByUpdate(longskgId,longuserId){try{校验库存悲观锁SecondKillsecondKillsecondKillMapper。querySecondKillForUpdate(skgId);IntegernumbersecondKill。getNumber();if(number0){扣库存secondKill。setNumber(number1);secondKillMapper。updateById(secondKill);创建订单SuccessKilledkillednewSuccessKilled();killed。setSeckillId(skgId);killed。setUserId(userId);killed。setState((short)0);killed。setCreateTime(newTimestamp(System。currentTimeMillis()));successKilledMapper。insert(killed);支付PaymentpaymentnewPayment();payment。setSeckillId(skgId);payment。setSeckillId(skgId);payment。setUserId(userId);payment。setMoney(40);payment。setState((short)1);payment。setCreateTime(newTimestamp(System。currentTimeMillis()));paymentMapper。insert(payment);}else{returnResult。error(SecondKillStateEnum。END);}}catch(Exceptione){thrownewScorpiosException(异常了个乖乖);}finally{}returnResult。ok(SecondKillStateEnum。SUCCESS);}
  Dao层RepositorypublicinterfaceSecondKillMapperextendsBaseMapperSecondKill{将此行数据进行加锁,当整个方法将事务提交后,才会解锁paramskgIdreturnSelect(valueSELECTFROMseckillWHEREseckillid{skgId}FORUPDATE)SecondKillquerySecondKillForUpdate(Param(skgId)LongskgId);}
  上面是利用forupdate进行对查询数据加锁,加的是行锁3。4方式四(悲观锁二)
  悲观锁的第二种方式就是利用update更新命令来加表锁UPDATE锁表paramskgId商品idparamuserId用户idreturnOverrideTransactional(rollbackForException。class)publicResultstartSecondKillByUpdateTwo(longskgId,longuserId){try{不校验,直接扣库存更新intresultsecondKillMapper。updateSecondKillById(skgId);if(result0){创建订单SuccessKilledkillednewSuccessKilled();killed。setSeckillId(skgId);killed。setUserId(userId);killed。setState((short)0);killed。setCreateTime(newTimestamp(System。currentTimeMillis()));successKilledMapper。insert(killed);支付PaymentpaymentnewPayment();payment。setSeckillId(skgId);payment。setSeckillId(skgId);payment。setUserId(userId);payment。setMoney(40);payment。setState((short)1);payment。setCreateTime(newTimestamp(System。currentTimeMillis()));paymentMapper。insert(payment);}else{returnResult。error(SecondKillStateEnum。END);}}catch(Exceptione){thrownewScorpiosException(异常了个乖乖);}finally{}returnResult。ok(SecondKillStateEnum。SUCCESS);}
  Dao层RepositorypublicinterfaceSecondKillMapperextendsBaseMapperSecondKill{将此行数据进行加锁,当整个方法将事务提交后,才会解锁paramskgIdreturnSelect(valueSELECTFROMseckillWHEREseckillid{skgId}FORUPDATE)SecondKillquerySecondKillForUpdate(Param(skgId)LongskgId);Update(valueUPDATEseckillSETnumbernumber1WHEREseckillid{skgId}ANDnumber0)intupdateSecondKillById(Param(skgId)longskgId);}3。5方式五(乐观锁)
  乐观锁,顾名思义,就是对操作结果很乐观,通过利用version字段来判断数据是否被修改
  乐观锁,不进行库存数量的校验,直接做库存扣减
  这里使用的乐观锁会出现大量的数据更新异常(抛异常就会导致购买失败)、如果配置的抢购人数比较少、比如120:100(人数:商品)会出现少买的情况,不推荐使用乐观锁。ApiOperation(value秒杀实现方式五乐观锁)PostMapping(startoptlock)publicResultstartOptLock(longskgId){try{log。info(开始秒杀方式五。。。);finallonguserId(int)(newRandom()。nextDouble()(99999100001))10000;参数添加了购买数量ResultresultsecondKillService。startSecondKillByPesLock(skgId,userId,1);if(result!null){log。info(用户:{}{},userId,result。get(msg));}else{log。info(用户:{}{},userId,哎呦喂,人也太多了,请稍后!);}}catch(Exceptione){e。printStackTrace();}returnResult。ok();}OverrideTransactional(rollbackForException。class)publicResultstartSecondKillByPesLock(longskgId,longuserId,intnumber){乐观锁,不进行库存数量的校验,直接try{SecondKillkillsecondKillMapper。selectById(skgId);剩余的数量应该要大于等于秒杀的数量if(kill。getNumber()number){intresultsecondKillMapper。updateSecondKillByVersion(number,skgId,kill。getVersion());if(result0){创建订单SuccessKilledkillednewSuccessKilled();killed。setSeckillId(skgId);killed。setUserId(userId);killed。setState((short)0);killed。setCreateTime(newTimestamp(System。currentTimeMillis()));successKilledMapper。insert(killed);支付PaymentpaymentnewPayment();payment。setSeckillId(skgId);payment。setSeckillId(skgId);payment。setUserId(userId);payment。setMoney(40);payment。setState((short)1);payment。setCreateTime(newTimestamp(System。currentTimeMillis()));paymentMapper。insert(payment);}else{returnResult。error(SecondKillStateEnum。END);}}}catch(Exceptione){thrownewScorpiosException(异常了个乖乖);}finally{}returnResult。ok(SecondKillStateEnum。SUCCESS);}RepositorypublicinterfaceSecondKillMapperextendsBaseMapperSecondKill{将此行数据进行加锁,当整个方法将事务提交后,才会解锁paramskgIdreturnSelect(valueSELECTFROMseckillWHEREseckillid{skgId}FORUPDATE)SecondKillquerySecondKillForUpdate(Param(skgId)LongskgId);Update(valueUPDATEseckillSETnumbernumber1WHEREseckillid{skgId}ANDnumber0)intupdateSecondKillById(Param(skgId)longskgId);Update(valueUPDATEseckillSETnumbernumber{number},versionversion1WHEREseckillid{skgId}ANDversion{version})intupdateSecondKillByVersion(Param(number)intnumber,Param(skgId)longskgId,Param(version)intversion);}
  乐观锁会出现大量的数据更新异常(抛异常就会导致购买失败),会出现少买的情况,不推荐使用乐观锁3。6方式六(阻塞队列)
  利用阻塞队类,也可以解决高并发问题。其思想就是把接收到的请求按顺序存放到队列中,消费者线程逐一从队列里取数据进行处理,看下具体代码。
  阻塞队列:这里使用静态内部类的方式来实现单例模式,在并发条件下不会出现问题。秒杀队列(固定长度为100)publicclassSecondKillQueue{队列大小staticfinalintQUEUEMAXSIZE100;用于多线程间下单的队列staticBlockingQueueSuccessKilledblockingQueuenewLinkedBlockingQueueSuccessKilled(QUEUEMAXSIZE);使用静态内部类,实现单例模式privateSecondKillQueue(){};privatestaticclassSingletonHolder{静态初始化器,由JVM来保证线程安全privatestaticSecondKillQueuequeuenewSecondKillQueue();}单例队列returnpublicstaticSecondKillQueuegetSkillQueue(){returnSingletonHolder。}生产入队paramkillthrowsInterruptedExceptionadd(e)队列未满时,返回队列满则抛出IllegalStateException(Queuefull)异常AbstractQueueput(e)队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。offer(e)队列未满时,返回队列满时返回false。非阻塞立即返回。offer(e,time,unit)设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。publicBooleanproduce(SuccessKilledkill){returnblockingQueue。offer(kill);}消费出队poll()获取并移除队首元素,在指定的时间内去轮询队列看有没有首元素有则返回,否者超时后返回nulltake()与带超时时间的poll类似不同在于take时候如果当前队列空了它会一直等待其他线程调用notEmpty。signal()才会被唤醒publicSuccessKilledconsume()throwsInterruptedException{returnblockingQueue。take();}获取队列大小returnpublicintsize(){returnblockingQueue。size();}}
  消费秒杀队列:实现ApplicationRunner接口消费秒杀队列Slf4jComponentpublicclassTaskRunnerimplementsApplicationRunner{AutowiredprivateSecondKillServiceseckillSOverridepublicvoidrun(ApplicationArgumentsvar){newThread((){log。info(队列启动成功);while(true){try{进程内队列SuccessKilledkillSecondKillQueue。getSkillQueue()。consume();if(kill!null){ResultresultseckillService。startSecondKillByAop(kill。getSeckillId(),kill。getUserId());if(result!nullresult。equals(Result。ok(SecondKillStateEnum。SUCCESS))){log。info(TaskRunner,result:{},result);log。info(TaskRunner从消息队列取出用户,用户:{}{},kill。getUserId(),秒杀成功);}}}catch(InterruptedExceptione){e。printStackTrace();}}})。start();}}ApiOperation(value秒杀实现方式六消息队列)PostMapping(startqueue)publicResultstartQueue(longskgId){try{log。info(开始秒杀方式六。。。);finallonguserId(int)(newRandom()。nextDouble()(99999100001))10000;SuccessKilledkillnewSuccessKilled();kill。setSeckillId(skgId);kill。setUserId(userId);BooleanflagSecondKillQueue。getSkillQueue()。produce(kill);虽然进入了队列,但是不一定能秒杀成功进队出队有时间间隙if(flag){log。info(用户:{}{},kill。getUserId(),秒杀成功);}else{log。info(用户:{}{},userId,秒杀失败);}}catch(Exceptione){e。printStackTrace();}returnResult。ok();}
  注意:在业务层和AOP方法中,不能抛出任何异常,thrownewRuntimeException()这些抛异常代码要注释掉。因为一旦程序抛出异常就会停止,导致消费秒杀队列进程终止!
  使用阻塞队列来实现秒杀,有几点要注意:消费秒杀队列中调用业务方法加锁与不加锁情况一样,也就是seckillService。startSecondKillByAop()、seckillService。startSecondKillByLock()方法结果一样,这也很好理解当队列长度与商品数量一致时,会出现少卖的现象,可以调大数值下面是队列长度1000,商品数量1000,并发数2000情况下出现的少卖
  3。7。方式七(Disruptor队列)
  Disruptor是个高性能队列,研发的初衷是解决内存队列的延迟问题,在性能测试中发现竟然与IO操作处于同样的数量级,基于Disruptor开发的系统单线程能支撑每秒600万订单。事件生成工厂(用来初始化预分配事件对象)publicclassSecondKillEventFactoryimplementsEventFactorySecondKillEvent{OverridepublicSecondKillEventnewInstance(){returnnewSecondKillEvent();}}事件对象(秒杀事件)publicclassSecondKillEventimplementsSerializable{privatestaticfinallongserialVersionUID1L;privatelongseckillId;privatelonguserId;setget方法略}使用translator方式生产者publicclassSecondKillEventProducer{privatefinalstaticEventTranslatorVarargSecondKillEventtranslator(seckillEvent,seq,objs){seckillEvent。setSeckillId((Long)objs〔0〕);seckillEvent。setUserId((Long)objs〔1〕);};privatefinalRingBufferSecondKillEventringBpublicSecondKillEventProducer(RingBufferSecondKillEventringBuffer){this。ringBufferringB}publicvoidsecondKill(longseckillId,longuserId){this。ringBuffer。publishEvent(translator,seckillId,userId);}}消费者(秒杀处理器)Slf4jpublicclassSecondKillEventConsumerimplementsEventHandlerSecondKillEvent{privateSecondKillServicesecondKillService(SecondKillService)SpringUtil。getBean(secondKillService);OverridepublicvoidonEvent(SecondKillEventseckillEvent,longseq,booleanbool){ResultresultsecondKillService。startSecondKillByAop(seckillEvent。getSeckillId(),seckillEvent。getUserId());if(result。equals(Result。ok(SecondKillStateEnum。SUCCESS))){log。info(用户:{}{},seckillEvent。getUserId(),秒杀成功);}}}publicclassDisruptorUtil{staticDisruptorSecondKillEstatic{SecondKillEventFactoryfactorynewSecondKillEventFactory();intringBufferSize1024;ThreadFactorythreadFactoryrunnablenewThread(runnable);disruptornewDisruptor(factory,ringBufferSize,threadFactory);disruptor。handleEventsWith(newSecondKillEventConsumer());disruptor。start();}publicstaticvoidproducer(SecondKillEventkill){RingBufferSecondKillEventringBufferdisruptor。getRingBuffer();SecondKillEventProducerproducernewSecondKillEventProducer(ringBuffer);producer。secondKill(kill。getSeckillId(),kill。getUserId());}}ApiOperation(value秒杀实现方式七Disruptor队列)PostMapping(startdisruptor)publicResultstartDisruptor(longskgId){try{log。info(开始秒杀方式七。。。);finallonguserId(int)(newRandom()。nextDouble()(99999100001))10000;SecondKillEventkillnewSecondKillEvent();kill。setSeckillId(skgId);kill。setUserId(userId);DisruptorUtil。producer(kill);}catch(Exceptione){e。printStackTrace();}returnResult。ok();}
  经过测试,发现使用Disruptor队列队列,与自定义队列有着同样的问题,也会出现超卖的情况,但效率有所提高。4。小结
  对于上面七种实现并发的方式,做一下总结:一、二方式是在代码中利用锁和事务的方式解决了并发问题,主要解决的是锁要加载事务之前三、四、五方式主要是数据库的锁来解决并发问题,方式三是利用forupate对表加行锁,方式四是利用update来对表加锁,方式五是通过增加version字段来控制数据库的更新操作,方式五的效果最差六、七方式是通过队列来解决并发问题,这里需要特别注意的是,在代码中不能通过throw抛异常,否则消费线程会终止,而且由于进队和出队存在时间间隙,会导致商品少卖
  上面所有的情况都经过代码测试,测试分一下三种情况:并发数1000,商品数100并发数1000,商品数1000并发数2000,商品数1000
  思考:分布式情况下如何解决并发问题呢?下次继续试验。
  源码地址:
  https:github。comHofankingspringbootsecondskillexample
投诉 评论 转载

实现高并发秒杀的七种方式引言商品秒杀超卖解决商品超卖方式一(改进版加锁)方式二(AOP版加锁)方式三(悲观锁一)方式四(悲观锁二)方式五(乐观锁)方式六(阻塞队列)方式七(Disruptor队列)小结……新时代,我在中国德国设计师感受兔年新春来自德国的芭芭拉(BarbaraFreiman)今年在沈阳过春节。虽然已经多次在中国欢度春节,但她和家人还没有体验过兔年的春节。我们都很喜欢兔子,我们期待看到各种艺术品。……中美航母编队同时现身南海,解放军举行实战演练,拿美军练兵中国山东舰航母战斗群、与美国的尼米兹号航母编队,在同一个时间段现身南海地区,解放军要拿美军练兵了。根据美国官方信息,尼米兹号航母打击群在1月12日进入南海,在菲律宾西部海……立足长期价值广发逆向策略连续5年位列同类前12中证网讯(记者万宇)银河证券近日发布的统计数据显示,在20182022年的5年时间里,全市场能连续每年排名同类前50的主动权益型基金仅有34只,而由同一位基金经理连续管理的产品……东汉外戚宦官交替专权文a纵横历史观编辑a纵横历史观引言东汉时期,政治体制逐渐趋于崩溃,内忧外患,社会矛盾激化。在这样的背景下,外戚和宦官相继掌握了朝政大权,成为东汉政治的两个重要势力。……老人是不是戴了助听器就能正常交流了啊?这可不一定,耳聋分为:传导性聋、感音神经性聋、混合性聋。老年人大部分都是感音神经性聋,如果耳聋的时间不长(半年内),配了助听器很可能马上能听清楚,前提最好配数字机。如果耳聋的时……经常走路有什么好处?经常走路好处太多了,数不胜数。走路运动锻炼,简单易行很方便,不论男女老少皆可。从简的说,能防病强身健体,又怡情益智延寿,何乐而不为之!生命在于运动,走路运动好处实在多。锻……你怀疑过支付宝的花呗账单是否属实吗?没有怀疑过花呗,但每次还花呗的时候都怀疑自己有严重的人格分裂症,要不然为什么每次要还的金额都比我印象中的要多?不过当我打开账单一笔一笔核对消费记录的时候,发现事实就是如此……桥梁专业哪所高校最好?世界上最长的跨海大桥港珠澳跨海大桥堪称世界第七大奇迹,全长55公里,历时9年建造完成。参与建设珠澳大桥的工程师大部分都是我国大学毕业的,他们创造了奇迹,为国家赢得了荣誉。下面我……17道夏天必吃的减脂美味,家常减肥餐越吃越瘦!蛋滑虾原料虾仁16只(约86克)鸡蛋2个(约110克)姜丝10克葱花18克盐少许油、生粉各适量做法1。虾仁去虾线清洗干净。……80年代走红的张潮再婚后62岁再做父亲,大女儿现在也是知名演头条创作挑战赛点击关注,每天都有名人故事感动您!张潮上世纪80年代,张潮是家喻户晓的当红男星,主演了《啊,摇篮》《陌生的朋友》《秋瑾》《祁连山的回声》《被爱情……服装行业的秘密,海澜红豆劲霸七匹狼,为啥会出剪标衣服?看到这个问题,我觉得我应该来回答一下,毕竟多年前我专门卖过剪标服装。有些人说是残次品啊,有些人说是剁做的尾单其实这两样可以忽略不计。最主要的货源,都是库存。比……
一口气让你读懂800年辉煌灿烂的周朝史记之西周篇一座千年古墓,专家苦苦寻找10年,老汉随口一言,破解古墓锁钥2020年全国新高考1卷数学试卷及参考答案一家人就是要整整齐齐的皇家的名字臭名昭著的四个历史人物,如今被影视剧洗白,反而成了大好人?提升幸福感,近期入手的这些好物值得推荐中国人的故事全国政协委员王静科技成果落地需要复合型明白人知青往事当年她是班里的校花,下乡插队时却嫁给了一个农民后生新增外放电,换怀挡,欧拉好猫新款将上市港媒曝大S具俊晔结婚是各取所需,大S婚后坚持原则,不做恋爱脑两会即将结束,几家欢喜几家愁?免费医疗,8小时制度,怕是凉了春季如何穿好蓝色?搭配做到这3点,不管多大年纪都能穿出好气质如何更改手机上网流量套餐如何教育孩子不抓饭教育孩子不抓饭的妙招那些年老师的谎言优秀作文电影封神画圣归来,挂羊头卖狗肉,瑜不掩瑕让你效率翻倍的心理小技巧如约志喜其一勇气产后风湿有哪些类型再造卓越读书笔记警惕!不止倭文化,古装剧以韩代华也猖狂!于正剧多次中招晨光暖了次章初识若华牛柏紫苡川楝茶清热解毒祛湿功效

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找