范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

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

  一、redis 介绍
  Redis 应该是目前最受欢迎的高性能的缓存数据库了,在五一期间看到一则 Redis 7.0 发布的消息后,回想起多年前学习黄健宏老师《Redis 从入门到精通》2.x 的月伴时光,不由得感慨 Reids 发展之迅速。搜集了一下 3.0 及之后各版本的知名特性,整理出来方便读者朋友们有个简单了解(感兴趣的朋友还需自行深入研究),情况大致如下:3.0 开始支持 cluster 集群模式4.0 开发的 lazyfree 和 PSYNC2 解决了 Redis 长久的大 key 删除阻塞问题及同步中断无法续传的问题5.0 新增了 stream 数据结构使 Redis 具备功能完整的轻量级消息队列能力6.0 更是发布了诸多企业级特性如 threaded-io、TLS 和 ACL 等,大幅提升了 Redis 的性能和安全性7.0 Function 彻底解决了过去 Lua 脚本同步丢失的问题;Multi Part AOF 增强了 Redis 的数据持久化的可靠性1.1 特性介绍
  为满足本篇目标所需,这里着重介绍以下几个关键特性:数据组织:Redis 中支持多种数据结构,将他们灵活组合搭配即可满足分布式锁在不同场景下的功能需求: Jedis 和 Lettuce 这类框架中常使用 String 来做简易的锁信息存储 Redisson 中使用 Hash 结构来存储更多维度的锁信息,如:业务名称作为 key,uuid + 线程 id 作为 field,加锁次数作为 value Redisson 中在公平锁的场景下引入 List 和 ZSet, List 类型用于线程排队,Zset 类型存放等待线程的顺序,分数 score 是等待线程的超时时间戳。
  Redis 的数据结构(来自网络)集群模式:Redis 采用集群模式分片存储数据,整个集群拥有固定的 2 的 32 次方个槽位,数据被分配到这些槽位中,每个实例只分管一部分槽位,而非如 etcd、ZK 这种每个实例中的数据都一致;集群模式提供的是数据规模扩大后的横向 AP 能力,应对单节点的风险需再加上主从模式,但当某个 master 节点挂之后,slave 节点可能还未同步到全部数据,会导致数据丢失;一致性保障能力偏弱
  Redis 的集群模式(来自网络)顺序变更:一种简单的抢锁逻辑是判断 key 是否已存在,Redis 中没有给变更操作附加顺序信息(如 etcd 中的 Revision),但服务端以串行方式处理数据的变更,那就可以结合其他数据结构来记录请求顺序信息,如公平锁的实现也会依赖其他数据结构存储信息,用于判断锁状态;但当用到的数据类型和指令变多后,由于是非原子性操作,自然就会遇到结果与预期不一致这类问题,Redis 提供的 lua 脚本机制可用于解决此类问题 ,用户在客户端编排自定义脚本逻辑:可用多个指令操控多个数据,然后将脚本发送给服务端,服务端执行 lua 脚本,并保障一个 lua 脚本内的所有操作是原子性的
  Redis lua 脚本的工作机制(来自网络)TTL 机制:TTL(Time To Live)机制是给单个 key 设置存活时间,超过时间后 Redis 自动删除这个 key1.2 特性总结
  Redis 的分布式锁正是基于以上特性来实现的,简单来说是:TTL 机制:用于支撑异常情况下的锁自动释放的能力顺序变更:用于支撑获取锁和排队等待的能力集群+主从模式:用于支撑锁服务的高可用
  Redis 没有提供对分布式锁亲和的监听机制,需要客户端主动轮询感知数据变更。二. 加锁解锁的流程描述
  使用 Jedis 指令实现分布式锁的核心流程如下图所示:
  准备客户端、key 和 value若 key 不存在,指定过期时间成功写入 Key-Value 则抢锁成功,并定时推后 key 的过期时间若 key 已存在,则采用重试策略间歇性抢锁。解锁时,删除 key 并撤销推后 key 过期时间的逻辑
  其中第 2 和第 4 是核心环节,有几个版本的演进很有趣味:插入 key 和设置过期时间并非原子操作:setnx + expire 加锁和设置过期是两个分开的独立操作;若发生异常,导致设置过期操作未执行,则此锁就成了永恒锁,其他客户端就再也抢不到了以原子性操作完成插入 key 和设置过期时间:使用 set 的扩展指令,如下:SET key value [EX seconds] [PX milliseconds] [NX|XX] 复制代码NX :当 key 不存在时,才插入 KeyXX :当插入 key 时,指定值为固定的 lockValueEX second :设置 key 的过期时间单位秒(PXEX 二选一)PX millisecond :设置键的过期时间单位毫秒(PXEX 二选一)if(jedis.set(key, lockValue, "NX", "EX", 100) == 1){ //加锁成功   try {       do work //执行业务       //这里缺点什么?   }catch(Exception e){       //...   }finally {      jedis.del(key); //释放锁,这里可能误删其他client的锁key   } } 复制代码引入 lockValue 的随机值校验,避免误释放其它客户端的锁,场景如下:client1 加锁成功,key 10s 后过期,完成逻辑后,删除 key 之前,因 GC 导致持锁超过 10s,Redis 自动删除了 key,之后其他客户端可以抢锁假如是 client2 接下来成功抢锁,开始处理持锁后的逻辑。而此时 client1 GC 结束了会继续执行删除 key 的操作,但此时释放的其实是 client2 的 key
  解决办法是:加锁时指定的 lockValue 为随机值,每次加锁时的值都是唯一的,释放锁时若 lockValue 与加锁时的值一致才可释放,否则什么都不做,逻辑如下:if(jedis.set(key, randomLockValue, "NX", "EX", 100) == 1){ //加锁    try {        do something  //业务处理    }catch(){  }  finally {       //判断是不是当前线程加的锁,是才释放       //但判断和释放锁两个操作不是原子性的       if (randomLockValue.equals(jedis.get(key))) {          jedis.del(key); //释放锁       }    } } 复制代码
  以上代码遗留的问题是判断 randomlockValue 和释放锁两个操作不是原子性的。引入 lua 脚本,保障判断 randomlockValue 和删除 key 这两个操作的原子性,逻辑如下:String script =         "if redis.call("get",KEYS[1]) == ARGV[1] then" +                 "   return redis.call("del",KEYS[1]) " +                 "else" +                 "   return 0 " +                 "end"; Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(randomLockValue)); if("1".equals(result.toString())){     return true; } 复制代码
  至此依然存在的一个问题是:若持锁后,业务逻辑执行耗时 超过了 key 的过期时间,则锁 Key 会被 Reids 主动删除。引入 watchDog 定时推后 key 的过期时间,避免业务未执行完时,key 过期被 Redis 删除。if(jedis.set(key, randomLockValue, "NX", "EX", 100) == 1){ //加锁成功   try {       do work //执行业务       //watchDog定时延后Key的过期时间   }catch(Exception e){       //...   }finally {      String script =               "if redis.call("get",KEYS[1]) == ARGV[1] then" +                       "   return redis.call("del",KEYS[1]) " +                       "else" +                       "   return 0 " +                       "end";       try {           Object result = jedis.eval(script, Collections.singletonList(key),                                   Collections.singletonList(randomLockValue));           if("1".equals(result.toString())){               return true;           }           return false;       }catch(Exception e){       //...     }   } } 复制代码三. Jedis 分布式锁的能力
  可能读者是单篇阅读,这里引入第一篇《分布式锁上-初探》中的一些内容,一个分布式锁应具备这样一些功能特点:互斥性:在同一时刻,只有一个客户端能持有锁安全性:避免死锁,如果某个客户端获得锁之后处理时间超过最大约定时间,或者持锁期间发生了故障导致无法主动释放锁,其持有的锁也能够被其他机制正确释放,并保证后续其它客户端也能加锁,整个处理流程继续正常执行可用性:也被称作容错性,分布式锁需要有高可用能力,避免单点故障,当提供锁的服务节点故障(宕机)时不影响服务运行,这里有两种模式:一种是分布式锁服务自身具备集群模式,遇到故障能自动切换恢复工作;另一种是客户端向多个独立的锁服务发起请求,当某个锁服务故障时仍然可以从其他锁服务读取到锁信息(Redlock)可重入性:对同一个锁,加锁和解锁必须是同一个线程程,即不能把其他线程持有的锁给释放了高效灵活:加锁、解锁的速度要快;支持阻塞和非阻塞;支持公平锁和非公平锁
  基于上文对 Jedis 分布式锁的介绍,这里简单总结一下 Jedis 的能力矩阵,ZK 请看《分布式锁中-基于 Zookeeper 的实现》,etcd 请看《分布式锁中-基于 etcd 的实现很优雅》 ,表格中标题使用 Redis-简单锁,主要是跟 RedLock 做区分,这种简单锁使用 Jedis 、Lettuce、Redisson 都能实现,任何一把锁的信息只保存在一个 Redis master 实例中,而 RedLock 是 Redisson 提供的高阶分布式锁,它需要客户端同时跟多个 Redis master 实例协作才能完成,即一把锁的信息同时存在于多个 master 实例中。它的情况会在后续文章中补充(感兴趣的读者可以关注本号 【架构染色】 ,文章完成时会主动推送给你)
  能力
  ZK
  etcd
  Redis-简单锁
  Redlock
  MySql
  互斥
  是
  是
  是
  安全
  链接异常时,session 丢失自动释放锁
  基于租约,超时自动释放锁
  基于 TTL,超时自动释放锁
  可用性
  相对可用性还好
  好
  好
  可重入
  服务端非可重入,本地线程可重入
  服务端非可重入,Resission本地线程可重入
  服务端非可重入,本地线程可重入需自研
  加解锁速度
  速度不算快
  速度快,GRPC 协议优势以及服务端能力的优势
  速度快
  阻塞非阻塞
  客户端两种能力都提供
  jetcd-core 中,阻塞非阻塞由 Future#get 支撑
  Jedis非阻塞,# Redission提供阻塞能力
  公平非公平
  公平锁
  公平锁
  非公平锁,# Redission提供公平锁
  可续期
  天然支持
  天然支持
  Jedis需自研 watchDog,Redission自带
  其他因素
  技术栈偏老,性能不佳
  多数公司不熟悉
  容易受业务缓存操作干扰
  四、Jedis 库实现分布式锁
  Jedis 是 Redis 官方推出的用于通过 Java 连接 Redis 客户端的一个工具包,提供了 Redis 的各种命令支持。4.1 pom 依赖     redis.clients     jedis     4.3.0  复制代码4.2 相关的 API 介绍使用 SET 的扩展指令加锁(SET key value [EX seconds][px milliseconds] [NX|XX]) SetParams params = SetParams.setParams().nx().ex(lockState.getLeaseTTL());  String result = client.set(lockState.getLockKey(), lockState.getLockValue(), params); 复制代码使用 lua 解锁String script = "if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end"; Object result = client.eval(script, 1, lockState.getLockKey(), lockState.getLockValue()); 复制代码4.3 分布式锁示例锁的封装package com.rock.dlock.jedis;  import com.rock.dlock.common.DtLockException; import com.rock.dlock.common.KeepAliveAction; import com.rock.dlock.common.KeepAliveTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.JedisPooled; import redis.clients.jedis.params.SetParams;  import java.net.SocketTimeoutException; import java.util.concurrent.TimeUnit;  /**  * @author zs  * @date 2022/11/13 4:44 PM  */ public class DemoJedisLock {     private final static Logger log = LoggerFactory.getLogger(DemoJedisLock.class);     private JedisPooled client;      private LockState lockState;     private KeepAliveTask keepAliveTask;      private int sleepMillisecond;      private final static String RESULT_OK = "OK";     private static final Long UNLOCK_SUCCESS = 1L;      class LockState {         private String lockKey;         private String lockValue;         private String errorMsg;         private int leaseTTL;         private long leaseId;         private boolean lockSuccess;          public LockState(String lockKey, int leaseTTL) {             this.lockKey = lockKey;             this.leaseTTL = leaseTTL;         }          public LockState(String lockKey, String value, int leaseTTL) {             this.lockKey = lockKey;             this.lockValue = value;             this.leaseTTL = leaseTTL;         }          public String getLockKey() {             return lockKey;         }          public void setLockKey(String lockKey) {             this.lockKey = lockKey;         }          public String getLockValue() {             return lockValue;         }          public void setLockValue(String lockValue) {             this.lockValue = lockValue;         }          public String getErrorMsg() {             return errorMsg;         }          public void setErrorMsg(String errorMsg) {             this.errorMsg = errorMsg;         }          public long getLeaseId() {             return leaseId;         }          public void setLeaseId(long leaseId) {             this.leaseId = leaseId;         }          public boolean isLockSuccess() {             return lockSuccess;         }          public void setLockSuccess(boolean lockSuccess) {             this.lockSuccess = lockSuccess;         }          public int getLeaseTTL() {             return leaseTTL;         }          public void setLeaseTTL(int leaseTTL) {             this.leaseTTL = leaseTTL;         }     }       public DemoJedisLock(JedisPooled client, String key, String value, int ttlSeconds) {         //1.准备客户端         this.client = client;         this.lockState = new LockState(key, value, ttlSeconds);         this.sleepMillisecond = (ttlSeconds * 1000) / 3; //抢锁的重试间隔可由用户指定     }       public boolean tryLock(long waitTime, TimeUnit waitUnit) throws DtLockException {         long totalMillisSeconds = waitUnit.toMillis(waitTime);         long start = System.currentTimeMillis();         //重试,直到成功或超过指定时间         while (true) {             // 抢锁             try {                 SetParams params = SetParams.setParams().nx().ex(lockState.getLeaseTTL());                 String result = client.set(lockState.getLockKey(), lockState.getLockValue(), params);                 if (RESULT_OK.equals(result)) {                     manualKeepAlive();                     log.info("[jedis-lock] lock success 线程:{} 加锁成功,key:{} , value:{}", Thread.currentThread().getName(), lockState.getLockKey(), lockState.getLockValue());                     lockState.setLockSuccess(true);                     return true;                 } else {                     if (System.currentTimeMillis() - start >= totalMillisSeconds) {                         return false;                     }                     Thread.sleep(sleepMillisecond);                 }             } catch (Exception e) {                 Throwable cause = e.getCause();                 if (cause instanceof SocketTimeoutException) {//忽略网络抖动等异常                 }                 log.error("[jedis-lock] lock failed:" + e);                 throw new DtLockException("[jedis-lock] lock failed:" + e.getMessage(), e);             }          }     }      //此实现中忽略,网络通信异常部分的处理,可参考tryLock     public void unlock() throws DtLockException {         try {             // 首先停止续约             if (keepAliveTask != null) {                 keepAliveTask.close();             }             String script = "if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end";             Object result = client.eval(script, 1, lockState.getLockKey(), lockState.getLockValue());              if (UNLOCK_SUCCESS.equals(result)) {                 log.info("[jedis-lock] unlock success 线程 : {} 解锁成功,锁key : {} ,路径:{}", Thread.currentThread().getName(), lockState.getLockKey(), lockState.getLockValue());             } else {                 log.info("[jedis-lock] unlock del key failed ,线程 : {} 解锁成功,锁key : {} ,路径:{}", Thread.currentThread().getName(), lockState.getLockKey(), lockState.getLockValue());             }         } catch (Exception e) {             log.error("[jedis-lock] unlock failed:" + e.getMessage(), e);             throw new DtLockException("[jedis-lock] unlock failed:" + e.getMessage(), e);         }     }      // 定时将Key的过期推迟     private void manualKeepAlive() {         final String t_key = lockState.getLockKey();         final int t_ttl = lockState.getLeaseTTL();          keepAliveTask = new KeepAliveTask(new KeepAliveAction() {             @Override             public void run() throws DtLockException {                 // 刷新值                 try {                     client.expire(t_key, t_ttl);                 } catch (Exception e) {                     e.printStackTrace();                 }             }         }, t_ttl);         keepAliveTask.start();     } } 复制代码异常类的简单实现package com.rock.dlock.common;  public class DtLockException extends RuntimeException{     public DtLockException(String message) {         super(message);     }      public DtLockException(String message, Throwable cause) {         super(message, cause);     }      public static DtLockException clientException(){         return new DtLockException("client is empty");     } } 复制代码watchDog 的任务抽象 package com.rock.dlock.common;  public interface KeepAliveAction {     void run() throws DtLockException; } 复制代码watchDog 的简单实现package com.rock.dlock.common;  import org.slf4j.Logger; import org.slf4j.LoggerFactory;  import java.util.concurrent.TimeUnit;  /**  * @author zs  * @date 2022/11/7 4:20 PM  */ public class KeepAliveTask extends Thread {     private static final Logger LOGGER = LoggerFactory.getLogger(KeepAliveTask.class);     public volatile boolean isRunning = true;     /**      * 过期时间,单位s      */     private long ttlSeconds;     private KeepAliveAction action;     public KeepAliveTask(KeepAliveAction action, long ttlSeconds) {         this.ttlSeconds = ttlSeconds;         this.action = action;         this.setDaemon(true);     }      @Override     public void run() {         final long sleep = this.ttlSeconds * 1000 / 3; // 每隔三分之一过期时间,续租一次         while (isRunning) {             try {                 // 1、续租,刷新值                 action.run();                 LOGGER.debug("续租成功!");                 TimeUnit.MILLISECONDS.sleep(sleep);             } catch (InterruptedException e) {                 close();             } catch (DtLockException e) {                 close();             }         }     }      public void close() {         isRunning = false;         this.interrupt();     } } 复制代码4.4 测试锁import com.rock.dlock.jedis.DemoJedisLock; import redis.clients.jedis.JedisPooled;  import java.util.UUID; import java.util.concurrent.TimeUnit;  /**  * @author zs  * @date 2022/11/13 4:51 PM  */ public class TestJedisLock {     public static void main(String[] args) {          JedisPooled jedis = new JedisPooled("127.0.0.1", 6379);         DemoJedisLock demoEtcdLock1 = new DemoJedisLock(jedis, "rock", UUID.randomUUID().toString(), 10);         DemoJedisLock demoEtcdLock2 = new DemoJedisLock(jedis, "rock", UUID.randomUUID().toString(), 10);          boolean lock1 = demoEtcdLock1.tryLock(20, TimeUnit.SECONDS);         if (lock1) {             try {                 System.out.printf("do something");             } 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
  资源池中的最大连接数
  8
  maxIdle   资源池允许的最大空闲连接数   8
  minIdle   资源池确保的最少空闲连接数   0
  blockWhenExhausted   当资源池用尽后,调用者是否要等待。只有当值为 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   做空闲资源检测时,每次检测资源的个数。   3
  可根据自身应用连接数进行微调,如果设置为 -1,就是对所有连接做空闲监测。   通过源码可以发现这些配置是 GenericObjectPoolConfig 对象的属性,这个类实际上是 rg.apache.commons.pool2.impl apache 提供的,也就是说 jedis 的连接池是依托于 apache 提供的对象池来,这个对象池的声明周期如下图,感兴趣的可以看下:   5.2 网络调优max-redirects:这个是集群模式下,重定向的最大数量;举例说明,比如第一台挂了,连第二台,第二台挂了连第三台,重新连接的次数不能超过这个值timeout:客户端超时时间,单位是毫秒   Rsdis 节点故障或者网络抖动时,这两个值如果不合理可能会导致很严重的问题,比如 timeout 设置为 1000,maxRedirect 为 2,一旦出现 redis 连接问题,将会导致请求阻塞 3s 左右。而这个 3 秒的阻塞在可能导致常规业务流量下的线程池耗尽,需根据业务场景调整。   原文链接:https://juejin.cn/post/7166131629348356104

凌晨0点,比利时女排四大主力无缘世联赛,蔡斌有望夺开门红北京时间6月13日,根据国内权威女排媒体人凌晨0点发布的最新消息显示,中国女排在第二周世界联赛中的第一个对手比利时女排出现了一些紧急状况,球队副攻让森斯莱门斯,主攻吉尔逊,二传范萨2022斯诺克冠军联赛正式打响!赵心童颜丙涛领衔,丁俊晖放弃参赛2022年的斯诺克冠军联赛将在6月28号至7月29号进行,为期一个月,作为这个赛季的揭幕战,由于奖金较少,含金量较低,不少运动员都放弃参加本次冠军联赛了,比如中国斯诺克名将丁俊晖就中国日本女排女篮女足男排男篮男足最新世界排名排球联赛中日竞艳图据排联媒体中国女排最新世界排名第3。日本女排排名第7。美国第1,巴西第2。土耳其第4。塞尔维亚第5。意大利第6。韩国第16。这是今天凌晨更新的最新排名。鉴于国际排联采即时更新制,恒大95悍将唐诗令人唏嘘!沦落到踢职工联赛!原本他可以踢中超的北京时间6月13日,中超豪门广州队球员唐诗有了最新的消息,这位上赛季帮助梅州客家冲超成功的95悍将,在赛季开启前回归了广州队,在广州队集训期间唐诗受到的关注度不比韦世豪严鼎皓少,可前2年他连前300名都没进,如今逆袭成全美第1!小乔治要打进NBA了近来众多NBA球队正忙着选秀,雷霆亦是如此。今年他们是选秀大户,未来几年也是。今年雷霆手握2号签12号签30号签。普雷斯蒂早已在抓紧时间考察新人,想再次施展选秀神操作,为雷霆选中顶35岁坐拥上亿身家,丁俊晖首次正面回应捞金质疑我就是为了挣钱2022年斯诺克揭幕战冠军联赛即将开始,世界台联也已经于日前公布了具体的参赛名单。从名单上来看,中国共有24名选手参加比赛,其中被誉为中国斯诺克未来领军人物的赵心童和颜丙涛两人将领伊藤美诚终于亮相,早田希娜位列女单头号种子,刘国梁派蒯曼迎敌北京时间6月13日,乒乓球WTT克罗地亚站比赛正式开战,该站赛事设立男女单男女双和混双5个项目,冠军积分为400分,由于赛事级别并不高,国乒主力队员并没有参加,依旧在威海南海为7月悉尼奥运会,国乒为何放弃世乒赛亚军马琳,选择没进16强的刘国正大家都知道,国乒在奥运会的参赛人选上一直都非常慎重,由于奥运会参赛名额的限制,在奥运会参赛人选上,一直都是强调大赛成绩和大赛经验。但是在2000年悉尼奥运会的时候,中国男乒上报的奥这个非洲人来中国跑马拉松赚钱,最后离开中国时,反倒欠了4500元这个非洲人来中国跑马拉松赚钱,辛辛苦苦跑了第四,最后离开中国时反倒欠了4500元,这是怎么回事呢?非洲人来中国跑马拉松挣钱2019年4月,一架飞机降落在北京首都国际机场,一群来自埃2比0后连丢三局!中国女排2比3遭逆转,日本夺U18亚锦赛八连冠北京时间6月13日消息,2022年U18女排亚锦赛在泰国佛统府落下帷幕,决赛中,叶文挂帅的中国女排局分2比0后连丢三局,以2比3被日本女排逆转,屈居亚军,日本女排夺得该项赛事的八连新K1王者朝久泰央挑战妖刀魏锐2021年7月17日,K1WGP比赛在日本福冈市举行,压轴大战是轻量级冠军金腰带争霸赛,本土选手朝久泰央激战四局分歧判定战胜大魔神龚纳帕,成为新任K1WGP轻量级王者。格斗家族近期
泰国后卫德鲁联赛狂轰63分完爆詹皇!美媒嘲讽勒布朗,中国队悬了北京时间8月7日,中国男篮狼来了,连常年大弱队泰国队现在都已经出现了一个顶尖归化强援,下届亚洲杯杜锋的男篮可能连他们都打不过了。美国德鲁联赛最近很让人关心,毕竟詹姆斯在之前的德鲁联非诚勿扰仨元老现状乐嘉卖课被骂骗子,孟非玩遍大半个中国非诚勿扰是江苏卫视2010年开播的节目,播出后即爆红,使得相亲类节目成为了香饽饽,别的卫视也跟风搞了类似的节目比如爱情连连看非常完美百里挑一等。非诚勿扰最火的那几年,除了依靠大批量泰国后卫德鲁联赛狂轰63分完爆詹皇!美媒嘲讽勒布朗,中国队悬了北京时间8月7日,中国男篮狼来了,连常年大弱队泰国队现在都已经出现了一个顶尖归化强援,下届亚洲杯杜锋的男篮可能连他们都打不过了。美国德鲁联赛最近很让人关心,毕竟詹姆斯在之前的德鲁联非诚勿扰仨元老现状乐嘉卖课被骂骗子,孟非玩遍大半个中国非诚勿扰是江苏卫视2010年开播的节目,播出后即爆红,使得相亲类节目成为了香饽饽,别的卫视也跟风搞了类似的节目比如爱情连连看非常完美百里挑一等。非诚勿扰最火的那几年,除了依靠大批量中国股市四大机构重仓的区块链龙头,谁会是下一个宁德时代还有谁?在A股,股王是贵州茅台,宁王是宁德时代,最近比亚迪市值破万亿后被称为比王,光伏硅料龙头通威股份股价大涨后又被称为硅王,掌握磷矿资源的云天化则在去年就被称为磷王。股王宁王比王中国股市四大机构重仓的区块链龙头,谁会是下一个宁德时代还有谁?在A股,股王是贵州茅台,宁王是宁德时代,最近比亚迪市值破万亿后被称为比王,光伏硅料龙头通威股份股价大涨后又被称为硅王,掌握磷矿资源的云天化则在去年就被称为磷王。股王宁王比王金砖国家再迎强援,非洲最大能源国有可能加入,还是中国的好朋友提到中国在非洲的好朋友,阿尔及利亚绝对是个中翘楚。曾经,中国宁愿不跟法国建交,也要支持阿尔及利亚人民的解放。当然,阿方也将中国的帮助铭记于心。随后,在联合国大会上,阿尔及利亚这个提金砖国家再迎强援,非洲最大能源国有可能加入,还是中国的好朋友提到中国在非洲的好朋友,阿尔及利亚绝对是个中翘楚。曾经,中国宁愿不跟法国建交,也要支持阿尔及利亚人民的解放。当然,阿方也将中国的帮助铭记于心。随后,在联合国大会上,阿尔及利亚这个提菲律宾发现中国火箭残骸?上面印着五星红旗,哪里来的?飞船火箭残骸再入大气层的事情是很常见的,因为这些残骸在完成使命以后,最终的归宿都是再入大气层,只是时间早晚的问题。有一些卫星火箭飞船残骸停留在太空的时间比较长,可能长达几个月甚至几菲律宾发现中国火箭残骸?上面印着五星红旗,哪里来的?飞船火箭残骸再入大气层的事情是很常见的,因为这些残骸在完成使命以后,最终的归宿都是再入大气层,只是时间早晚的问题。有一些卫星火箭飞船残骸停留在太空的时间比较长,可能长达几个月甚至几黄牛哭了SteamDeck销售地区新增中国港台地区起售价2830元近日,Steam官方宣布,在Komodo的助力下,SteamDeck将在今年晚些时候于日本韩国中国台湾地区及中国香港地区发售。预约售价分别为64GB款3288港币(约2830元人民