警惕看不见的重试机制:为什么使用RPC必须考虑幂等性_天天速读
在RPC场景中因为重试或者没有实现幂等机制而导致的重复数据问题,必须引起大家重视,有可能会造成例如一次购买创建多笔订单,一条通知信息被发送多次等问题,这是技术人员必须面对和解决的问题。
有人可能会说:当调用失败时程序并没有显示重试,为什么还会产生重复数据问题呢?这是因为即使没有显示重试,RPC框架在集群容错机制中自动进行了重试,这个问题必须引起关注。
本文我们以DUBBO框架为例分析为什么重试,怎么做重试,怎么做幂等三个问题。
(资料图)
如果简单对一个RPC交互过程进行分类,我们可以分为三类:响应成功、响应失败、没有响应。
对于响应成功和响应失败这两种情况,消费者很好处理。因为响应信息明确,所以只要根据响应信息,继续处理成功或者失败逻辑即可。但是没有响应这种场景比较难处理,这是因为没有响应可能包含以下情况:
(1) 生产者根本没有接收到请求(2) 生产者接收到请求并且已处理成功,但是消费者没有接收到响应(3) 生产者接收到请求并且已处理失败,但是消费者没有接收到响应假设你是一名RPC框架设计者,究竟是选择重试还是放弃调用呢?其实最终如何选择取决于业务特性,有的业务本身就具有幂等性,但是有的业务不能允许重试否则会造成重复数据。
那么谁对业务特性最熟悉呢?答案是消费者,因为消费者作为调用方肯定最熟悉自身业务,所以RPC框架只要提供一些策略供消费者选择即可。
2 怎么做重试2.1 集群容错策略DUBBO作为一款优秀RPC框架,提供了如下集群容错策略供消费者选择:
Failover: 故障转移Failfast: 快速失败Failsafe: 安全失败Failback: 异步重试Forking: 并行调用Broadcast:广播调用(1) Failover故障转移策略。作为默认策略当消费发生异常时通过负载均衡策略再选择一个生产者节点进行调用,直到达到重试次数
(2) Failfast快速失败策略。消费者只消费一次服务,当发生异常时则直接抛出
(3) Failsafe安全失败策略。消费者只消费一次服务,如果消费失败则包装一个空结果,不抛出异常
(4) Failback异步重试策略。当消费发生异常时返回一个空结果,失败请求将会进行异步重试。如果重试超过最大重试次数还不成功,放弃重试并不抛出异常
(5) Forking并行调用策略。消费者通过线程池并发调用多个生产者,只要有一个成功就算成功
(6) Broadcast广播调用策略。消费者遍历调用所有生产者节点,任何一个出现异常则抛出异常
2.2 源码分析2.2.1 FailoverFailover故障转移策略作为默认策略,当消费发生异常时通过负载均衡策略再选择一个生产者节点进行调用,直到达到重试次数。即使业务代码没有显示重试,也有可能多次执行消费逻辑从而造成重复数据:
public class FailoverClusterInvoker extends AbstractClusterInvoker { public FailoverClusterInvoker(Directory directory) { super(directory); } @Override public Result doInvoke(Invocation invocation, final List> invokers, LoadBalance loadbalance) throws RpcException { // 所有生产者Invokers List> copyInvokers = invokers; checkInvokers(copyInvokers, invocation); String methodName = RpcUtils.getMethodName(invocation); // 获取重试次数 int len = getUrl().getMethodParameter(methodName, Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1; if (len <= 0) { len = 1; } RpcException le = null; // 已经调用过的生产者 List> invoked = new ArrayList>(copyInvokers.size()); Set providers = new HashSet(len); // 重试直到达到最大次数 for (int i = 0; i < len; i++) { if (i > 0) { // 如果当前实例被销毁则抛出异常 checkWhetherDestroyed(); // 根据路由策略选出可用生产者Invokers copyInvokers = list(invocation); // 重新检查 checkInvokers(copyInvokers, invocation); } // 负载均衡选择一个生产者Invoker Invoker invoker = select(loadbalance, invocation, copyInvokers, invoked); invoked.add(invoker); RpcContext.getContext().setInvokers((List) invoked); try { // 服务消费发起远程调用 Result result = invoker.invoke(invocation); if (le != null && logger.isWarnEnabled()) { logger.warn("Although retry the method " + methodName + " in the service " + getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + ", but there have been failed providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le); } // 有结果则返回 return result; } catch (RpcException e) { // 业务异常直接抛出 if (e.isBiz()) { throw e; } le = e; } catch (Throwable e) { // RpcException不抛出继续重试 le = new RpcException(e.getMessage(), e); } finally { // 保存已经访问过的生产者 providers.add(invoker.getUrl().getAddress()); } } throw new RpcException(le.getCode(), "Failed to invoke the method " + methodName + " in the service " + getInterface().getName() + ". Tried " + len + " times of the providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le.getCause() != null ? le.getCause() : le); }} 消费者调用生产者节点A发生RpcException异常时(例如超时异常),在未达到最大重试次数之前,消费者会通过负载均衡策略再次选择其它生产者节点消费。试想如果生产者节点A其实已经处理成功了,但是没有及时将成功结果返回给消费者,那么再次重试可能就会造成重复数据问题。
2.2.2 Failfast快速失败策略。消费者只消费一次服务,当发生异常时则直接抛出,不会进行重试:
public class FailfastClusterInvoker extends AbstractClusterInvoker { public FailfastClusterInvoker(Directory directory) { super(directory); } @Override public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { // 检查生产者Invokers是否合法 checkInvokers(invokers, invocation); // 负载均衡选择一个生产者Invoker Invoker invoker = select(loadbalance, invocation, invokers, null); try { // 服务消费发起远程调用 return invoker.invoke(invocation); } catch (Throwable e) { // 服务消费失败不重试直接抛出异常 if (e instanceof RpcException && ((RpcException) e).isBiz()) { throw (RpcException) e; } throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName() + " select from all providers " + invokers + " for service " + getInterface().getName() + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); } }} 2.2.3 Failsafe安全失败策略。消费者只消费一次服务,如果消费失败则包装一个空结果,不抛出异常,不会进行重试:
public class FailsafeClusterInvoker extends AbstractClusterInvoker { private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class); public FailsafeClusterInvoker(Directory directory) { super(directory); } @Override public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { try { // 检查生产者Invokers是否合法 checkInvokers(invokers, invocation); // 负载均衡选择一个生产者Invoker Invoker invoker = select(loadbalance, invocation, invokers, null); // 服务消费发起远程调用 return invoker.invoke(invocation); } catch (Throwable e) { // 消费失败包装为一个空结果对象 logger.error("Failsafe ignore exception: " + e.getMessage(), e); return new RpcResult(); } }} 2.2.4 Failback异步重试策略。当消费发生异常时返回一个空结果,失败请求将会进行异步重试。如果重试超过最大重试次数还不成功,放弃重试并不抛出异常:
public class FailbackClusterInvoker extends AbstractClusterInvoker { private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class); private static final long RETRY_FAILED_PERIOD = 5; private final int retries; private final int failbackTasks; private volatile Timer failTimer; public FailbackClusterInvoker(Directory directory) { super(directory); int retriesConfig = getUrl().getParameter(Constants.RETRIES_KEY, Constants.DEFAULT_FAILBACK_TIMES); if (retriesConfig <= 0) { retriesConfig = Constants.DEFAULT_FAILBACK_TIMES; } int failbackTasksConfig = getUrl().getParameter(Constants.FAIL_BACK_TASKS_KEY, Constants.DEFAULT_FAILBACK_TASKS); if (failbackTasksConfig <= 0) { failbackTasksConfig = Constants.DEFAULT_FAILBACK_TASKS; } retries = retriesConfig; failbackTasks = failbackTasksConfig; } private void addFailed(LoadBalance loadbalance, Invocation invocation, List> invokers, Invoker lastInvoker) { if (failTimer == null) { synchronized (this) { if (failTimer == null) { // 创建定时器 failTimer = new HashedWheelTimer(new NamedThreadFactory("failback-cluster-timer", true), 1, TimeUnit.SECONDS, 32, failbackTasks); } } } // 构造定时任务 RetryTimerTask retryTimerTask = new RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, retries, RETRY_FAILED_PERIOD); try { // 定时任务放入定时器等待执行 failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS); } catch (Throwable e) { logger.error("Failback background works error,invocation->" + invocation + ", exception: " + e.getMessage()); } } @Override protected Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { Invoker invoker = null; try { // 检查生产者Invokers是否合法 checkInvokers(invokers, invocation); // 负责均衡选择一个生产者Invoker invoker = select(loadbalance, invocation, invokers, null); // 消费服务发起远程调用 return invoker.invoke(invocation); } catch (Throwable e) { logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: " + e.getMessage() + ", ", e); // 如果服务消费失败则记录失败请求 addFailed(loadbalance, invocation, invokers, invoker); // 返回空结果 return new RpcResult(); } } @Override public void destroy() { super.destroy(); if (failTimer != null) { failTimer.stop(); } } /** * RetryTimerTask */ private class RetryTimerTask implements TimerTask { private final Invocation invocation; private final LoadBalance loadbalance; private final List> invokers; private final int retries; private final long tick; private Invoker lastInvoker; private int retryTimes = 0; RetryTimerTask(LoadBalance loadbalance, Invocation invocation, List> invokers, Invoker lastInvoker, int retries, long tick) { this.loadbalance = loadbalance; this.invocation = invocation; this.invokers = invokers; this.retries = retries; this.tick = tick; this.lastInvoker = lastInvoker; } @Override public void run(Timeout timeout) { try { // 负载均衡选择一个生产者Invoker Invoker retryInvoker = select(loadbalance, invocation, invokers, Collections.singletonList(lastInvoker)); lastInvoker = retryInvoker; // 服务消费发起远程调用 retryInvoker.invoke(invocation); } catch (Throwable e) { logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e); // 超出最大重试次数记录日志不抛出异常 if ((++retryTimes) >= retries) { logger.error("Failed retry times exceed threshold (" + retries + "), We have to abandon, invocation->" + invocation); } else { // 未超出最大重试次数重新放入定时器 rePut(timeout); } } } private void rePut(Timeout timeout) { if (timeout == null) { return; } Timer timer = timeout.timer(); if (timer.isStop() || timeout.isCancelled()) { return; } timer.newTimeout(timeout.task(), tick, TimeUnit.SECONDS); } }} 2.2.5 Forking并行调用策略。消费者通过线程池并发调用多个生产者,只要有一个成功就算成功:
public class ForkingClusterInvoker extends AbstractClusterInvoker { private final ExecutorService executor = Executors.newCachedThreadPool(new NamedInternalThreadFactory("forking-cluster-timer", true)); public ForkingClusterInvoker(Directory directory) { super(directory); } @Override public Result doInvoke(final Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { try { checkInvokers(invokers, invocation); final List> selected; // 获取配置参数 final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS); final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 获取并行执行的Invoker列表 if (forks <= 0 || forks >= invokers.size()) { selected = invokers; } else { selected = new ArrayList<>(); for (int i = 0; i < forks; i++) { // 选择生产者 Invoker invoker = select(loadbalance, invocation, invokers, selected); // 防止重复增加Invoker if (!selected.contains(invoker)) { selected.add(invoker); } } } RpcContext.getContext().setInvokers((List) selected); final AtomicInteger count = new AtomicInteger(); final BlockingQueue 2.2.6 Broadcast广播调用策略。消费者遍历调用所有生产者节点,任何一个出现异常则抛出异常:
public class BroadcastClusterInvoker extends AbstractClusterInvoker { private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class); public BroadcastClusterInvoker(Directory directory) { super(directory); } @Override public Result doInvoke(final Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { checkInvokers(invokers, invocation); RpcContext.getContext().setInvokers((List) invokers); RpcException exception = null; Result result = null; // 遍历调用所有生产者节点 for (Invoker invoker : invokers) { try { // 执行消费逻辑 result = invoker.invoke(invocation); } catch (RpcException e) { exception = e; logger.warn(e.getMessage(), e); } catch (Throwable e) { exception = new RpcException(e.getMessage(), e); logger.warn(e.getMessage(), e); } } // 任何一个出现异常则抛出异常 if (exception != null) { throw exception; } return result; }} 3 怎么做幂等经过上述分析我们知道,RPC框架自带的重试机制可能会造成数据重复问题,那么在使用中必须考虑幂等性。幂等性是指一次操作与多次操作产生结果相同,并不会因为多次操作而产生不一致性。常见幂等方案有取消重试、幂等表、数据库锁、状态机。
3.1 取消重试取消重试有两种方法,第一是设置重试次数为零,第二是选择不重试的集群容错策略。
3.2 幂等表假设用户支付成功后,支付系统将支付成功消息,发送至消息队列。物流系统订阅到这个消息,准备为这笔订单创建物流单。
但是消息队列可能会重复推送,物流系统有可能接收到多次这条消息。我们希望达到效果是:无论接收到多少条重复消息,只能创建一笔物流单。
解决方案是幂等表方案。新建一张幂等表,该表就是用来做幂等,无其它业务意义,有一个字段名为key建有唯一索引,这个字段是幂等标准。
物流系统订阅到消息后,首先尝试插入幂等表,订单编号作为key字段。如果成功则继续创建物流单,如果订单编号已经存在则违反唯一性原则,无法插入成功,说明已经进行过业务处理,丢弃消息。
这张表数据量会比较大,我们可以通过定时任务对数据进行归档,例如只保留7天数据,其它数据存入归档表。
还有一种广义幂等表就是我们可以用Redis替代数据库,在创建物流单之前,我们可以检查Redis是否存在该订单编号数据,同时可以为这类数据设置7天过期时间。
3.3 状态机物流单创建成功后会发送消息,订单系统订阅到消息后更新状态为完成,假设变更是将订单状态0更新至状态1。订单系统也可能收到多条消息,可能在状态已经被更新至状态1之后,依然收到物流单创建成功消息。
解决方案是状态机方案。首先绘制状态机图,分析状态流转形态。例如经过分析状态1已经是最终态,那么即使接收到物流单创建成功消息也不再处理,丢弃消息。
3.4 数据库锁数据库锁又可以分为悲观锁和乐观锁两种类型,悲观锁是在获取数据时加锁:
select * from table where col="xxx" for update 乐观锁是在更新时加锁,第一步首先查出数据,数据包含version字段。第二步进行更新操作,如果此时记录已经被修改则version字段已经发生变化,无法更新成功:
update table set xxx,version = #{version} + 1 where id = #{id} and version = #{version}4 文章总结本文首先分析了为什么重试这个问题,因为对于RPC交互无响应场景,重试是一种重要选择。然后分析了DUBBO提供的六种集群容错策略,Failover作为默认策略提供了重试机制,在业务代码没有显示重试情况下,仍有可能发起多次调用,这必须引起重视。最后我们分析了几种常用幂等方案,希望本文对大家有所帮助。
标签:
推荐文章
- 警惕看不见的重试机制:为什么使用RPC必须考虑幂等性_天天速读
- 日本男子当街持枪与警察对峙,致包括警察在内3人死亡
- 参见两个白板5软件下载|环球热点评
- 来自台湾的科技公司Advanced Biomed向SEC提交上市申请,拟挂牌Nasdaq
- 曼朱基奇发文纪念拜仁欧冠夺冠10周年:最爱的足球记忆之一-环球今日报
- 今日热文:庚明顶 沈伽佳_庚明顶
- 枫桥夜泊的诗意是什么
- 英辰科技股东吴琼质押700万股 用于为公司向吉林银行股份有限公司长春新区支行贷款提供质押担保
- 5大发现!分贝通《2022-2023一体化企业支出管理报告》正式发布
- 崔建波
- 工业富联涨停 机构看好公司受益于全球AI算力需求增长实现收入高增
- 浙商证券发布深度报告 看好首药控股 ALK 商业化潜质|全球短讯
- 世界快报:黑土优品|龙江好蜜
- 教育部部署开展“2023高考护航行动”-环球时快讯
- 【天天新要闻】卖不动的北京二手房
- 安阳市2023年粤港澳大湾区招商引智推介大会在社会各界引发热烈讨论
- 时讯:北京大学滨海医院开展“全国护肤日”义诊活动
- 全球观点:新装修的房子孕妇可以住吗 新装修的房子孕妇可以住吗?对胎儿有影响吗
- 【世界速看料】硬核科技论 | 为什么说油电混动才是最优解?
- 每日速讯:海口发布雷电黄色预警信号
- 奋楫争先,奇安信再获CNNVD多项重磅大奖|天天头条
- 暗恋桃花源无锡站在哪里
- 五月票房破30亿
- 贵州都匀戒毒所禁毒宣传正当时 看点
- 观点:“两高”发布司法解释:从严惩处强奸、猥亵等性侵害未成年人犯罪
- 未来智能全新录音降噪会议耳机iFLYBUDS Nano系列正式发布,“耳机+AI”打破会议效率上限
- 每日汇市|俄罗斯外贸银行行长:未来十年人民币有望取代美元成为全球主要储备货币 焦点短讯
- 马龙0-4惨败被质疑打假球!
- 阉鸡什么人不可以吃(孕妇为什么不能吃阉鸡)
- 陆薄言-环球播报
- 全球快报:Xbox官宣:合金装备3RE、龙之信条2都将登陆Xbox
- 环球简讯:中方敦促以色列停止蚕食巴勒斯坦人民的土地和资源
- 环球头条:中国人民银行等部门:推动更多优质企业登陆北京证券交易所上市
- 世界资讯:甘李药业哪天开盘
- 世界快报:怎样提高学习记忆力 如何提高学习记忆力
- 按钮式广告是什么_按钮式广告 环球动态
- 深圳上民办小学需要什么条件允许(深圳申请民办小学需要什么条件) 每日资讯
- 今日视点:武汉法利普纳泽切割系统有限公司(关于武汉法利普纳泽切割系统有限公司介绍)
- 5月前三周汽车销量快报:乘用车销量大涨41%,新能源37.2万辆_环球消息
- 天天最资讯丨全系标配超光影长焦 OPPO Reno10 系列正式发布
- 汤姆·索亚历险记读书笔记_汤姆索亚历险记主要内容100字_全球讯息
- 即时:微信公众平台:6月30日后商业合作营销内容需通过腾讯官方广告平台发布
- 视讯!《流量飞轮》首发!短视频流量破局的系统方案 | 新书推荐
- 李鸿彬:美联储会议纪要来袭,黄金震荡何去何从?-全球快播报
- 环球信息:【能力作风建设】大庆交警深入农村地区开展“美丽乡村行”交通安全巡回宣讲活动
- 2023成都宠博会可以免费领东西吗 焦点速递
- 公认6种“最脏”水果,有的正大量上市!正确清洗法,你必须知道|焦点热议
- 世界焦点!湖北美术学院2023届本科生毕业作品展开幕
- 环球快看:4亿美元购入、5300万美元甩手,Meta 出售GIF动图搜索平台Giphy
- 研报掘金丨天风证券:泽布替尼海外放量符合预期,维持百济神州“买入”评级 新要闻
- 世界微资讯!由华云数据提供云服务的湖北聚游科技获得OpenInfra基金会“2023年超级用户大奖”提名
- 天天热点评!节气门清洗有必要吗 节气门坏了会导致车出现什么问题?
- 苏州吴中高新区:八证齐发,总投资16.8亿元,拿地即开工!_动态
- 教育部:2023年中西部农村订单定向免费本科医学生将招收6150人
最新资讯
- 新标准国际音标_关于新标准国际音标简介
- 华人健康:5月23日融资买入921.26万元,融资融券余额4372.44万元
- 离通车更近一步!重庆轨道18号线南段接触网送电成功
- 全球热议:浙江义乌:打造“国际文化创客村”
- 公告速递:嘉实恒生科技ETF(QDII)2023年5月26日暂停申购、赎回业务
- 环球热资讯!南宁市武鸣区常态化开展廉政谈话 系好党员干部“廉洁扣”
- 【世界速看料】受访职场新人认为高校要加强职业规划和社交技能培训
- 浙江首届“最美外卖骑手”正式亮相
- 学士山公园整体项目1600亩生态公园开展征收
- 商业银行担纲债务融资工具承销主角 专家建议提升承销专业性与合规性
- 72腰围是多少尺寸_72腰围是多大|天天速看
- 方格子老虎教案公开课_方格子老虎教案|当前头条
- 当前热议!英飞拓(002528.SZ):公司有关注信创和国资云的业务机会并积极研判
- 日本正式出台半导体制造设备出口管制措施,我商务部:滥用出口管制-全球视讯
- 热门看点:淘宝客贷-淘宝客贷申请链接
- 2023鼓励高考生冲刺的励志话语 祝金榜题名的金句
- 和AI网聊10分钟被骗430万,真实诈骗案震惊全网
- 【热闻】尤文图斯被扣分后惨败
- 辽宁省组织开展2023年海上大规模人命救助综合演习 聚焦
- 今日讯!2023杭州拱墅清正源府人才共有产权房二期报名登记
- 每日热讯!塞读音是什么并组词(塞读音)
- 欢瑞世纪股东户数增加33.35%,户均持股5.85万元
- 微动态丨原百度副总裁马杰加入创新工场,成立AIGC公司|36氪独家
- 千亿级母基金再现:重庆设立2000亿元产业母基金,将带动超万亿元先进制造业投资 天天看热讯
- 黑洞之谜:为什么不能直接看到它们,又能通过什么方式探测到它们-观点
- 多家知名企业获得制造业单项冠军等荣誉 上海将激励培育更多冠军企业 今日要闻
- 必应成中国第一大桌面搜索引擎,京东618开启_焦点短讯
- 巴塞罗那车展:新能源车型成亮点 中国品牌引关注_全球要闻
- 2023法检考试公共基础知识法律知识:取保候审
- 世界聚焦:赛睿APEX Pro TKL二代发布
- A股上市公司薪酬指标揭榜 三家公司员工人均百万年薪 播报
- 还不休息!名记:我认为詹姆斯今天计划打满48分钟 环球即时
- 飞凡战队是富有专业匠心精神的投资团队 焦点快报
- 断头!雨天急刹又急打方向,货车“身首分离”! 当前资讯
- 航天长峰:5月22日融资买入266.61万元,融资融券余额2.54亿元_每日看点
- 中原区市场监督管理局开展 2023年“计量惠民进社区”宣传活动
- WTI原油日内涨幅扩大至2%,报96.02美元/桶 环球今亮点
- 海军士官学校举办校园心理剧展演活动——“我”的心灵之旅 全球看点
- 目标:大满贯!连场4-0,陈梦状态爆棚,单局11-1,记者来不及采访
- 十四岁的第一次 孤独精灵_十四岁的第一次_每日热点
- 热门中概股盘前普涨 小鹏汽车涨超4%_焦点要闻
- 西华大学安德校区是本科吗_西华大学安德校区|当前独家
- 4006999555 4006
- 【视频】电瓶车电梯内爆燃,车主烧伤住院治疗-世界热讯
- 南漳:万亩小麦丰收在望
- 每日信息:成都充(换)电基础设施补贴范围
- 第十八届海峡旅游博览会 黑龙江斩获三大奖项
- 北交所龙虎榜|晟楠科技今日成交7675.03万元,换手率达22.27%
- 环球微速讯:推特开始为打造万能应用招募人才,马斯克曾表示要打造“万能应用”
- 天津子女投靠父母落户相关政策咨询电话是多少?





