安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

分布式锁中基于Redis的实现需避坑Jedis篇

5月1日 枯心人投稿
  一、redis介绍
  Redis应该是目前最受欢迎的高性能的缓存数据库了,在五一期间看到一则Redis7。0发布的消息后,回想起多年前学习黄健宏老师《Redis从入门到精通》2。x的月伴时光,不由得感慨Reids发展之迅速。搜集了一下3。0及之后各版本的知名特性,整理出来方便读者朋友们有个简单了解(感兴趣的朋友还需自行深入研究),情况大致如下:3。0开始支持cluster集群模式4。0开发的lazyfree和PSYNC2解决了Redis长久的大key删除阻塞问题及同步中断无法续传的问题5。0新增了stream数据结构使Redis具备功能完整的轻量级消息队列能力6。0更是发布了诸多企业级特性如threadedio、TLS和ACL等,大幅提升了Redis的性能和安全性7。0Function彻底解决了过去Lua脚本同步丢失的问题;MultiPartAOF增强了Redis的数据持久化的可靠性1。1特性介绍
  为满足本篇目标所需,这里着重介绍以下几个关键特性:数据组织:Redis中支持多种数据结构,将他们灵活组合搭配即可满足分布式锁在不同场景下的功能需求:Jedis和Lettuce这类框架中常使用String来做简易的锁信息存储Redisson中使用Hash结构来存储更多维度的锁信息,如:业务名称作为key,uuid线程id作为field,加锁次数作为valueRedisson中在公平锁的场景下引入List和ZSet,List类型用于线程排队,Zset类型存放等待线程的顺序,分数score是等待线程的超时时间戳。
  Redis的数据结构(来自网络)集群模式:Redis采用集群模式分片存储数据,整个集群拥有固定的2的32次方个槽位,数据被分配到这些槽位中,每个实例只分管一部分槽位,而非如etcd、ZK这种每个实例中的数据都一致;集群模式提供的是数据规模扩大后的横向AP能力,应对单节点的风险需再加上主从模式,但当某个master节点挂之后,slave节点可能还未同步到全部数据,会导致数据丢失;一致性保障能力偏弱
  Redis的集群模式(来自网络)顺序变更:一种简单的抢锁逻辑是判断key是否已存在,Redis中没有给变更操作附加顺序信息(如etcd中的Revision),但服务端以串行方式处理数据的变更,那就可以结合其他数据结构来记录请求顺序信息,如公平锁的实现也会依赖其他数据结构存储信息,用于判断锁状态;但当用到的数据类型和指令变多后,由于是非原子性操作,自然就会遇到结果与预期不一致这类问题,Redis提供的lua脚本机制可用于解决此类问题,用户在客户端编排自定义脚本逻辑:可用多个指令操控多个数据,然后将脚本发送给服务端,服务端执行lua脚本,并保障一个lua脚本内的所有操作是原子性的
  Redislua脚本的工作机制(来自网络)TTL机制:TTL(TimeToLive)机制是给单个key设置存活时间,超过时间后Redis自动删除这个key1。2特性总结
  Redis的分布式锁正是基于以上特性来实现的,简单来说是:TTL机制:用于支撑异常情况下的锁自动释放的能力顺序变更:用于支撑获取锁和排队等待的能力集群主从模式:用于支撑锁服务的高可用
  Redis没有提供对分布式锁亲和的监听机制,需要客户端主动轮询感知数据变更。二。加锁解锁的流程描述
  使用Jedis指令实现分布式锁的核心流程如下图所示:
  准备客户端、key和value若key不存在,指定过期时间成功写入KeyValue则抢锁成功,并定时推后key的过期时间若key已存在,则采用重试策略间歇性抢锁。解锁时,删除key并撤销推后key过期时间的逻辑
  其中第2和第4是核心环节,有几个版本的演进很有趣味:插入key和设置过期时间并非原子操作:setnxexpire加锁和设置过期是两个分开的独立操作;若发生异常,导致设置过期操作未执行,则此锁就成了永恒锁,其他客户端就再也抢不到了以原子性操作完成插入key和设置过期时间:使用set的扩展指令,如下:SETkeyvalue〔EXseconds〕〔PXmilliseconds〕〔NXXX〕复制代码NX:当key不存在时,才插入KeyXX:当插入key时,指定值为固定的lockValueEXsecond:设置key的过期时间单位秒(PXEX二选一)PXmillisecond:设置键的过期时间单位毫秒(PXEX二选一)if(jedis。set(key,lockValue,NX,EX,100)1){加锁成功try{dowork执行业务这里缺点什么?}catch(Exceptione){。。。}finally{jedis。del(key);释放锁,这里可能误删其他client的锁key}}复制代码引入lockValue的随机值校验,避免误释放其它客户端的锁,场景如下:client1加锁成功,key10s后过期,完成逻辑后,删除key之前,因GC导致持锁超过10s,Redis自动删除了key,之后其他客户端可以抢锁假如是client2接下来成功抢锁,开始处理持锁后的逻辑。而此时client1GC结束了会继续执行删除key的操作,但此时释放的其实是client2的key
  解决办法是:加锁时指定的lockValue为随机值,每次加锁时的值都是唯一的,释放锁时若lockValue与加锁时的值一致才可释放,否则什么都不做,逻辑如下:if(jedis。set(key,randomLockValue,NX,EX,100)1){加锁try{dosomething业务处理}catch(){}finally{判断是不是当前线程加的锁,是才释放但判断和释放锁两个操作不是原子性的if(randomLockValue。equals(jedis。get(key))){jedis。del(key);释放锁}}}复制代码
  以上代码遗留的问题是判断randomlockValue和释放锁两个操作不是原子性的。引入lua脚本,保障判断randomlockValue和删除key这两个操作的原子性,逻辑如下:Stringscriptifredis。call(get,KEYS〔1〕)ARGV〔1〕thenreturnredis。call(del,KEYS〔1〕)elsereturn0Objectresultjedis。eval(script,Collections。singletonList(key),Collections。singletonList(randomLockValue));if(1。equals(result。toString())){}复制代码
  至此依然存在的一个问题是:若持锁后,业务逻辑执行耗时超过了key的过期时间,则锁Key会被Reids主动删除。引入watchDog定时推后key的过期时间,避免业务未执行完时,key过期被Redis删除。if(jedis。set(key,randomLockValue,NX,EX,100)1){加锁成功try{dowork执行业务watchDog定时延后Key的过期时间}catch(Exceptione){。。。}finally{Stringscriptifredis。call(get,KEYS〔1〕)ARGV〔1〕thenreturnredis。call(del,KEYS〔1〕)elsereturn0try{Objectresultjedis。eval(script,Collections。singletonList(key),Collections。singletonList(randomLockValue));if(1。equals(result。toString())){}}catch(Exceptione){。。。}}}复制代码三。Jedis分布式锁的能力
  可能读者是单篇阅读,这里引入第一篇《分布式锁上初探》中的一些内容,一个分布式锁应具备这样一些功能特点:互斥性:在同一时刻,只有一个客户端能持有锁安全性:避免死锁,如果某个客户端获得锁之后处理时间超过最大约定时间,或者持锁期间发生了故障导致无法主动释放锁,其持有的锁也能够被其他机制正确释放,并保证后续其它客户端也能加锁,整个处理流程继续正常执行可用性:也被称作容错性,分布式锁需要有高可用能力,避免单点故障,当提供锁的服务节点故障(宕机)时不影响服务运行,这里有两种模式:一种是分布式锁服务自身具备集群模式,遇到故障能自动切换恢复工作;另一种是客户端向多个独立的锁服务发起请求,当某个锁服务故障时仍然可以从其他锁服务读取到锁信息(Redlock)可重入性:对同一个锁,加锁和解锁必须是同一个线程程,即不能把其他线程持有的锁给释放了高效灵活:加锁、解锁的速度要快;支持阻塞和非阻塞;支持公平锁和非公平锁
  基于上文对Jedis分布式锁的介绍,这里简单总结一下Jedis的能力矩阵,ZK请看《分布式锁中基于Zookeeper的实现》,etcd请看《分布式锁中基于etcd的实现很优雅》,表格中标题使用Redis简单锁,主要是跟RedLock做区分,这种简单锁使用Jedis、Lettuce、Redisson都能实现,任何一把锁的信息只保存在一个Redismaster实例中,而RedLock是Redisson提供的高阶分布式锁,它需要客户端同时跟多个Redismaster实例协作才能完成,即一把锁的信息同时存在于多个master实例中。它的情况会在后续文章中补充(感兴趣的读者可以关注本号【架构染色】,文章完成时会主动推送给你)
  能力
  ZK
  etcd
  Redis简单锁
  Redlock
  MySql
  互斥
  是
  是
  是
  安全
  链接异常时,session丢失自动释放锁
  基于租约,超时自动释放锁
  基于TTL,超时自动释放锁
  可用性
  相对可用性还好
  好
  好
  可重入
  服务端非可重入,本地线程可重入
  服务端非可重入,Resission本地线程可重入
  服务端非可重入,本地线程可重入需自研
  加解锁速度
  速度不算快
  速度快,GRPC协议优势以及服务端能力的优势
  速度快
  阻塞非阻塞
  客户端两种能力都提供
  jetcdcore中,阻塞非阻塞由Futureget支撑
  Jedis非阻塞,Redission提供阻塞能力
  公平非公平
  公平锁
  公平锁
  非公平锁,Redission提供公平锁
  可续期
  天然支持
  天然支持
  Jedis需自研watchDog,Redission自带
  其他因素
  技术栈偏老,性能不佳
  多数公司不熟悉
  容易受业务缓存操作干扰
  四、Jedis库实现分布式锁
  Jedis是Redis官方推出的用于通过Java连接Redis客户端的一个工具包,提供了Redis的各种命令支持。4。1pom依赖dependencygroupIdredis。clientsgroupIdjedisartifactIdversion4。3。0versiondependency复制代码4。2相关的API介绍使用SET的扩展指令加锁(SETkeyvalue〔EXseconds〕〔pxmilliseconds〕〔NXXX〕)SetParamsparamsSetParams。setParams()。nx()。ex(lockState。getLeaseTTL());Stringresultclient。set(lockState。getLockKey(),lockState。getLockValue(),params);复制代码使用lua解锁Stringscriptifredis。call(get,KEYS〔1〕)ARGV〔1〕thenreturnredis。call(del,KEYS〔1〕)elsereturn0Objectresultclient。eval(script,1,lockState。getLockKey(),lockState。getLockValue());复制代码4。3分布式锁示例锁的封装packagecom。rock。dlock。importcom。rock。dlock。common。DtLockEimportcom。rock。dlock。common。KeepAliveAimportcom。rock。dlock。common。KeepAliveTimportorg。slf4j。Limportorg。slf4j。LoggerFimportredis。clients。jedis。JedisPimportredis。clients。jedis。params。SetPimportjava。net。SocketTimeoutEimportjava。util。concurrent。TimeUauthorzsdate202211134:44PMpublicclassDemoJedisLock{privatefinalstaticLoggerlogLoggerFactory。getLogger(DemoJedisLock。class);privateJedisPprivateLockStatelockSprivateKeepAliveTaskkeepAliveTprivateintsleepMprivatefinalstaticStringRESULTOKOK;privatestaticfinalLongUNLOCKSUCCESS1L;classLockState{privateStringlockKprivateStringlockVprivateStringerrorMprivateintleaseTTL;privatelongleaseId;privatebooleanlockSpublicLockState(StringlockKey,intleaseTTL){this。lockKeylockKthis。leaseTTLleaseTTL;}publicLockState(StringlockKey,Stringvalue,intleaseTTL){this。lockKeylockKthis。lockVthis。leaseTTLleaseTTL;}publicStringgetLockKey(){returnlockK}publicvoidsetLockKey(StringlockKey){this。lockKeylockK}publicStringgetLockValue(){returnlockV}publicvoidsetLockValue(StringlockValue){this。lockValuelockV}publicStringgetErrorMsg(){returnerrorM}publicvoidsetErrorMsg(StringerrorMsg){this。errorMsgerrorM}publiclonggetLeaseId(){returnleaseId;}publicvoidsetLeaseId(longleaseId){this。leaseIdleaseId;}publicbooleanisLockSuccess(){returnlockS}publicvoidsetLockSuccess(booleanlockSuccess){this。lockSuccesslockS}publicintgetLeaseTTL(){returnleaseTTL;}publicvoidsetLeaseTTL(intleaseTTL){this。leaseTTLleaseTTL;}}publicDemoJedisLock(JedisPooledclient,Stringkey,Stringvalue,intttlSeconds){1。准备客户端this。this。lockStatenewLockState(key,value,ttlSeconds);this。sleepMillisecond(ttlSeconds1000)3;抢锁的重试间隔可由用户指定}publicbooleantryLock(longwaitTime,TimeUnitwaitUnit)throwsDtLockException{longtotalMillisSecondswaitUnit。toMillis(waitTime);longstartSystem。currentTimeMillis();重试,直到成功或超过指定时间while(true){抢锁try{SetParamsparamsSetParams。setParams()。nx()。ex(lockState。getLeaseTTL());Stringresultclient。set(lockState。getLockKey(),lockState。getLockValue(),params);if(RESULTOK。equals(result)){manualKeepAlive();log。info(〔jedislock〕locksuccess线程:{}加锁成功,key:{},value:{},Thread。currentThread()。getName(),lockState。getLockKey(),lockState。getLockValue());lockState。setLockSuccess(true);}else{if(System。currentTimeMillis()starttotalMillisSeconds){}Thread。sleep(sleepMillisecond);}}catch(Exceptione){Throwablecausee。getCause();if(causeinstanceofSocketTimeoutException){忽略网络抖动等异常}log。error(〔jedislock〕lockfailed:e);thrownewDtLockException(〔jedislock〕lockfailed:e。getMessage(),e);}}}此实现中忽略,网络通信异常部分的处理,可参考tryLockpublicvoidunlock()throwsDtLockException{try{首先停止续约if(keepAliveTask!null){keepAliveTask。close();}Stringscriptifredis。call(get,KEYS〔1〕)ARGV〔1〕thenreturnredis。call(del,KEYS〔1〕)elsereturn0Objectresultclient。eval(script,1,lockState。getLockKey(),lockState。getLockValue());if(UNLOCKSUCCESS。equals(result)){log。info(〔jedislock〕unlocksuccess线程:{}解锁成功,锁key:{},路径:{},Thread。currentThread()。getName(),lockState。getLockKey(),lockState。getLockValue());}else{log。info(〔jedislock〕unlockdelkeyfailed,线程:{}解锁成功,锁key:{},路径:{},Thread。currentThread()。getName(),lockState。getLockKey(),lockState。getLockValue());}}catch(Exceptione){log。error(〔jedislock〕unlockfailed:e。getMessage(),e);thrownewDtLockException(〔jedislock〕unlockfailed:e。getMessage(),e);}}定时将Key的过期推迟privatevoidmanualKeepAlive(){finalStringtkeylockState。getLockKey();finalinttttllockState。getLeaseTTL();keepAliveTasknewKeepAliveTask(newKeepAliveAction(){Overridepublicvoidrun()throwsDtLockException{刷新值try{client。expire(tkey,tttl);}catch(Exceptione){e。printStackTrace();}}},tttl);keepAliveTask。start();}}复制代码异常类的简单实现packagecom。rock。dlock。publicclassDtLockExceptionextendsRuntimeException{publicDtLockException(Stringmessage){super(message);}publicDtLockException(Stringmessage,Throwablecause){super(message,cause);}publicstaticDtLockExceptionclientException(){returnnewDtLockException(clientisempty);}}复制代码watchDog的任务抽象packagecom。rock。dlock。publicinterfaceKeepAliveAction{voidrun()throwsDtLockE}复制代码watchDog的简单实现packagecom。rock。dlock。importorg。slf4j。Limportorg。slf4j。LoggerFimportjava。util。concurrent。TimeUauthorzsdate20221174:20PMpublicclassKeepAliveTaskextendsThread{privatestaticfinalLoggerLOGGERLoggerFactory。getLogger(KeepAliveTask。class);publicvolatilebooleanisR过期时间,单位sprivatelongttlSprivateKeepAliveApublicKeepAliveTask(KeepAliveActionaction,longttlSeconds){this。ttlSecondsttlSthis。this。setDaemon(true);}Overridepublicvoidrun(){finallongsleepthis。ttlSeconds10003;每隔三分之一过期时间,续租一次while(isRunning){try{1、续租,刷新值action。run();LOGGER。debug(续租成功!);TimeUnit。MILLISECONDS。sleep(sleep);}catch(InterruptedExceptione){close();}catch(DtLockExceptione){close();}}}publicvoidclose(){isRthis。interrupt();}}复制代码4。4测试锁importcom。rock。dlock。jedis。DemoJedisLimportredis。clients。jedis。JedisPimportjava。util。UUID;importjava。util。concurrent。TimeUauthorzsdate202211134:51PMpublicclassTestJedisLock{publicstaticvoidmain(String〔〕args){JedisPooledjedisnewJedisPooled(127。0。0。1,6379);DemoJedisLockdemoEtcdLock1newDemoJedisLock(jedis,rock,UUID。randomUUID()。toString(),10);DemoJedisLockdemoEtcdLock2newDemoJedisLock(jedis,rock,UUID。randomUUID()。toString(),10);booleanlock1demoEtcdLock1。tryLock(20,TimeUnit。SECONDS);if(lock1){try{System。out。printf(dosomething);}finally{demoEtcdLock1。unlock();}}demoEtcdLock1。tryLock(20,TimeUnit。SECONDS);demoEtcdLock2。tryLock(20,TimeUnit。SECONDS);等待锁,超时后放弃}}复制代码五、使用Jedis的一些注意事项
  通常分布式锁服务会和业务逻辑使用同一个Redis集群,自然也使用同一个Jedis客户端;当业务逻辑侧对Redis的读写并发提高时,会给Redis集群和Jedis客户度带来压力;为应对一些异常情况,我们除了解功能层面的API,还需要了解一下客户端的一些配置调优,主要是池化管理和网络通信两个方面5。1池化管理
  在使用Jedis时可以配置JedisPool连接池,池化处理有许多好处,如:提高响应的速度、降低资源的消耗、方便管理和维护;JedisPool配置参数大部分是由JedisPoolConfig的对应项来赋值的,在生产中我们需要关注它的配置并合理的赋值,如此能够提升Redis的服务性能,降低资源开销。下边是对一些重要参数的说明、默认及设置建议:
  参数
  说明
  默认值
  建议
  maxTotal
  资源池中的最大连接数
  8hrmaxIdle
  资源池允许的最大空闲连接数
  8hrminIdle
  资源池确保的最少空闲连接数
  0hrblockWhenExhausted
  当资源池用尽后,调用者是否要等待。只有当值为true时,下面的maxWaitMillis才会生效。
  true
  建议使用默认值。
  maxWaitMillis
  当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)。
  1(表示永不超时)
  不建议使用默认值。
  testOnBorrow
  向资源池借用连接时是否做连接有效性检测(ping)。检测到的无效连接将会被移除。
  false
  业务量很大时候建议设置为false,减少一次ping的开销。
  testOnReturn
  向资源池归还连接时是否做连接有效性检测(ping)。检测到无效连接将会被移除。
  false
  业务量很大时候建议设置为false,减少一次ping的开销。
  jmxEnabled
  是否开启JMX监控
  true
  建议开启,请注意应用本身也需要开启。
  空闲Jedis对象的回收检测由以下四个参数组合完成,testWhileIdle是该功能的开关。
  名称
  说明
  默认值
  建议
  testWhileIdle
  是否开启空闲资源检测。
  false
  true
  timeBetweenEvictionRunsMillis
  空闲资源的检测周期(单位为毫秒)
  1(不检测)
  建议设置,周期自行选择,也可以默认也可以使用下方JedisPoolConfig中的配置。
  minEvictableIdleTimeMillis
  资源池中资源的最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除。
  180000(即30分钟)
  可根据自身业务决定,一般默认值即可,也可以考虑使用下方JeidsPoolConfig中的配置。
  numTestsPerEvictionRun
  做空闲资源检测时,每次检测资源的个数。
  3hr可根据自身应用连接数进行微调,如果设置为1,就是对所有连接做空闲监测。
  通过源码可以发现这些配置是GenericObjectPoolConfig对象的属性,这个类实际上是rg。apache。commons。pool2。implapache提供的,也就是说jedis的连接池是依托于apache提供的对象池来,这个对象池的声明周期如下图,感兴趣的可以看下:
  5。2网络调优maxredirects:这个是集群模式下,重定向的最大数量;举例说明,比如第一台挂了,连第二台,第二台挂了连第三台,重新连接的次数不能超过这个值timeout:客户端超时时间,单位是毫秒
  Rsdis节点故障或者网络抖动时,这两个值如果不合理可能会导致很严重的问题,比如timeout设置为1000,maxRedirect为2,一旦出现redis连接问题,将会导致请求阻塞3s左右。而这个3秒的阻塞在可能导致常规业务流量下的线程池耗尽,需根据业务场景调整。
  原文链接:https:juejin。cnpost7166131629348356104
投诉 评论

分布式锁中基于Redis的实现需避坑Jedis篇一、redis介绍Redis应该是目前最受欢迎的高性能的缓存数据库了,在五一期间看到一则Redis7。0发布的消息后,回想起多年前学习黄健宏老师《Redis从入门到精通》……美团刘炽平已辞任非执行董事【美团:刘炽平已辞任非执行董事】财联社11月16日电,美团在港交所公告,由于腾讯控股于本公司的持股拟变化,刘炽平已辞任非执行董事,并立即生效。此外,本公司知悉,腾讯目前通过多家……解放J6F纯电长续航版来了,续航260KM,每公里电费低至0【卡车之家原创】在城市里运输快递、固定散货运输又或者接平台货源的卡友们,最近几年在选车时,会越来越关注纯电动轻卡,毕竟纯电动轻卡相比传统柴油或者汽油轻微卡,有更强的路权优势,不……老鼠也有音乐细胞据法新社11月15日消息,日本科学家发现,随着悦耳易记的音乐摇头摆尾不是人类独有的习惯,老鼠也能跟着LadyGaga等明星的音乐节奏舞动。报道称,东京大学的研究人员向佩戴……小视科技喜获国家级专精特新小巨人,开启人工智能领域新征程近日,国家工业和信息化部完成对第四批专精特新小巨人企业培育的审核与名单公示,南京甄视智能科技有限公司(即小视科技)成功入选。专精特新即专业化、精细化、特色化与新颖化。专精……重庆长江边上的江心岛,车可以直达,被称为重庆版呼伦贝尔大草原大家都知道重庆是一座山城,同时也是一座拥有几十公里江岸线的桥都,黄金水道长江从重庆流淌而过,日月星辰让重庆很多区域形成了江心岛,今天要给大家介绍的这个长江边上的江心岛被称为重庆……西甲三强分析皇马很强,马竞很弱,巴萨最复杂西甲三强分析:皇马很强,马竞很弱,巴萨最复杂!马竞:马竞已经不行了。马竞这赛季为了省钱格子还藏着掖着,看比赛就知道格子对马竞体系多重要,打弱队藏着就算了打强队还不首……有种整容叫蔡卓妍离婚,前夫郑中基变化不大,她却像换个人文sisi编辑嵋彼铭39岁的蔡卓妍,终于活成女王了!在舞台上唱着甜甜的《小酒窝》的她,早就不是当初懵懂无知的蔡卓妍。现在的她,气场强大,无论走到哪里都是焦点。……魔兽前夕升级实用WA,抢怪助手和任务物品自动使用(搬运)9月1日升级大军浩浩荡荡,每一次升级总会遇到一些很繁琐的操作,然而很多朋友,比如我的键位都是一只手俺不过来的,都是通过改建,设置宏尽量让手掌能够到该按键,别提任务道具还要单独拉……致态xJDG蓄势待发!新兴崛起与赛场黑马,等待迎战恭喜JDG在四分之一决赛阶段的角逐中,在BO5的比赛中以30的战绩轻松击败REG杀入4强,能拿到今天这个成绩真的太不容易了!在决胜局中,比赛前期佛耶戈试图闪现绕视野抓下路……远离!每周只要多吃114克油炸食品,您的心血管就危险了油炸食品的美味陷阱撰文范志红编辑保健君最近我国科研人员发表文章,汇总分析了19项相关研究,分析了吃油炸食品和心血管疾病风险之间的关系。结果发现,每周只要多吃1……风很大的以油养肤,是智商税吗?这不又到了一言不合就干燥、起皮的冬天了绵酱的肌肤已经自动从混油切换到了混干,最近整个状态就是干!干!干!(抓狂中。。。)身边的干皮姐妹,已经开始各种精华油往脸上糊了。以油养肤油……
世界杯决赛主裁透过社交平台,承认在决赛中犯了一个错误!真正的体面48岁的维多利亚(VictoriaSilvstedt)身着性杜淳携亲妈首次出镜,1岁女儿不让奶奶抱,王灿对婆婆的称呼疏离煤矿救援黑科技!侦察灭火机器人有效解决井下各种复杂环境华为FreeBudsPro2仅售1299,手里的Airpod推荐11月份值得一玩的10款游戏(第一篇)透过手机行业发展看新能源汽车行业的隐忧LOL当前版本强势英雄有哪些?686亿元!免了欧洲九月飘雪,中国电热毯订单骤增,但欧洲人似乎忘了个关键问题Google排名优化方法四年级体育教学工作计划我心目中的偶像作文1000字坚守心灵的执著茶泡饭对胃好吗为什么吃泡饭对胃不好贞观遗风是指哪位皇帝?历史评价包括两位皇帝的统治有贞观遗风2022年能恢复国际旅游吗?2022年可以去国外吗显示隐藏文件(怎么显示隐藏文件win7)一个基督徒的情感日记PS5到底能不能竖着放法国网友吵翻了为什么录音里听自己的声音和平时不一样?护舒宝的由来这件事让我感动

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利