消息队列(Message Queue),顾名思义,是队列这种数据结构的一种实现。今天我们对其进行详细的了解

一、什么是消息队列

消息队列是一种队列,是一种存储消息的中间件,我们可以把他看成是一种存储消息的容器。提到队列,就不得不说元素的进出顺序,先进先出。消息队列也遵循这个原则。生产者如果按照123的顺序进行消息的发送,那消费者必定要通过123的顺序进行接收。即时在多个消费者订阅同一主题的消息时,我们也应该从代码上实现他的顺序操作问题。

消息队列是实现分布式架构的重要的一环。消息队列可以通过多个服务器集群的方式,生产者,消费者,NameServer,broker均可以集群实现。

二、消息队列有哪些优点呢

解耦

使用消息队列可以降低系统的耦合性。消息队列相当于一个中间件。他把不同的系统分为两种角色,生产者和消费者。生产者和消费者之间没有耦合,通过消息队列这个中间件进行信息的交流。比如A系统有个orderId的字段,当前要求B系统需要,我们可以直接把两者进行耦合进行添加,通过A和B的关联实现消息的互通。但是如果我们需求变更,需要添加更多的系统,这样就需要修改相应的代码,修改过程复杂。但是如果我们通过A作为生产者把消息发给队列,这样这件事就与A没有关系。之后不管添加几个或者移除几个需要用到orderId的系统,都不回影响到A。这种生产者消费者模式大大的降低了系统的耦合性。这也是分布式的要求。

异步实现

消息队列可以将同步方法实现为异步方法。通过队列的存储功能,我们可以优先解决系统的重要流程,把一些诸如SMS,邮件通知等一些非必要信息实现异步调用。再例如,我们当前业务,下单之后会向用户发送邮件和短信,由于这部分功能没有那么高的即时性,即便是有延迟实现也能接收。下单处理占用30ms,发邮件占用20ms,发短信占用20ms。那么我们整个方法一共会占用70ms。但是如果我们把相关的信息放入消息队列的话,通过另外的服务实现之后进行异步操作,那么整个下单只会占用30ms。这种效率的提升在处理高并发系统时十分关键

 

 削峰

在面对高并发时间的时候,常常系统因为受不了压力而崩溃。比如双十一的下单狂潮,完全可能造成数据库崩溃。这时候我们可以把部分能接受的业务转化为通过消息队列实现。面对用户大量的请求,我们可以先把请求存入消息队列,在按照一定的顺序慢慢对队列里面的消息进行处理。这样可以应对高并发的压力

 三、消息队列的缺点

任何事物都不是完美无缺的,有优点就一定有缺点。消息队列的缺点有以下方面:

系统的复杂度提高

可能代码的复杂度提升了,因为要考虑到数据的一致性,缺失数据,数据重复性等多方面的问题

系统的可用性降低

如果消息队列服务器出问题的话,可能整个系统就会瘫痪

四、为什么要用消息队列呢 

  • 处理高并发
  • 降低耦合性
  • 实现分布式架构
  • 分布式架构事务处理(本次研究的重点)

五、代码和原理解析RocketMQ

接下来我们针对消息队列的原理进行解读

首先是RocketMQ的架构体系,体系图如下:

RocketMQ一共有四个角色:

生产者-Producer

生产者负责生产消息,并把它存入Broker中。此处的生产者是可以集群实现的。生产者发送消息的方式有同步、异步和单向三种方式

一、同步发送

同步消息发送适用于一些比较重要的情景业务之下,比如重要日志的保存,又比如重要邮件、短信的发送。这种方式要求收到接收方的正确反应之后才发送下一个数据包,否则不会发送数据

二、异步发送

异步发送算是最常用的一种发送方式。这种发送方式producer发出消息之后不必接受接收方的响应即可发送下一个数据包。一般情况下用于部分不重要日志的生成、非必要邮件的发送和视频上传之后通知转码服务等等。非重要业务逻辑的处理可以使用这种发送方式。这种发送方式的有点在于效率要比同步方式高得多

三、单向发送

单向发送是指异步发送中如果没有设置回调函数的场景,一般用于处理较大业务量的情况下,比如说日志系统的收集

生产者组-ProducerGroup

生产者组指的是具有同样行为的一组生产者。如果说一组生产者会发送同一类型的消息,则默认为这是一个生产者组。代码中我们可以通过如下方式设置生产者组:

//方式一,构造函数模式生成
DefaultMQProducer producer = new DefaultMQProducer("BatchProducerGroupName");//方式二
producer.setProducerGroup("testGroup");//默认构造函数下
public DefaultMQProducer() {this(null, MixAll.DEFAULT_PRODUCER_GROUP, null);}
public static final String DEFAULT_PRODUCER_GROUP = "DEFAULT_PRODUCER";

生产者组可以是不同的机器,也可以是同一机器的不同进程,也可以是一个进程的多个对象。同一个生产者组可以发送多个topic的消息。

主要工作内容

生产者主要是消息的发送方。发送消息时,先通过topic询问nameServer刺topic在哪个broker中,然后把消息发给broker。如果是同步的话还要判断该消息是否收到。

服务器-NameServer

NameServer是类似于Zookeeper一样的无状态的服务,也可以称为是注册中心。他的主要作用是处理生产者和消费者请求的topic名称并返回broker的相关信息给生产者和消费者。据说一开始这个项目阿里用的就是Zookeeper作为这个中间无状态的服务。

那么为什么要用这个注册中心呢,他的具体作用是什么呢?

  • 维护topic和broker的信息
  • 监控broker的状态
  • 为client提供路由能力

broker会注册服务到NameServer,并且和NameServer建立长期的联系。producer同样会和NameServer保持长期联系,通过在服务集群的一个节点的连接,并不时的获取topic和broker的相关信息。producer会和master保持长期联系,而consumer会和master,slave同时保持长期联系

说到底,NameServer和Zookeeper的性质一样,都是一个无状态注册中心。通过这个注册中心,不仅可以实现配置管理,还能实现集群管理等

下面是NameServer源码结构

我们聚焦于NamesrvStartup来解析下整个服务的启动过程

首先是初始化NamesrvController ,然后开始启动函数

NamesrvController controller = createNamesrvController(args);
start(controller);

初始化方法如下

public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {//初始化相关的版本信息System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));//PackageConflictDetect.detectFastjson();//构建命令行操作的指令Options options = ServerUtil.buildCommandlineOptions(new Options());//转化为对应的命令行--需要继续添加c参数的命令行,config配置文件的处理commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());if (null == commandLine) {System.exit(-1);return null;}//初始化NamesrvConfig和NettyServerConfig---可见服务器的启动是通过netty启动的final NamesrvConfig namesrvConfig = new NamesrvConfig();final NettyServerConfig nettyServerConfig = new NettyServerConfig();//默认监听端口号为9876nettyServerConfig.setListenPort(9876);//加载config配置信息if (commandLine.hasOption('c')) {String file = commandLine.getOptionValue('c');if (file != null) {InputStream in = new BufferedInputStream(new FileInputStream(file));properties = new Properties();properties.load(in);MixAll.properties2Object(properties, namesrvConfig);MixAll.properties2Object(properties, nettyServerConfig);namesrvConfig.setConfigStorePath(file);System.out.printf("load config properties file OK, %s%n", file);in.close();}}//加载printConfigItem相关信息if (commandLine.hasOption('p')) {InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);MixAll.printObjectProperties(console, namesrvConfig);MixAll.printObjectProperties(console, nettyServerConfig);System.exit(0);}//把命令行的命令加载到namesrvConfigMixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);//环境变量的配置if (null == namesrvConfig.getRocketmqHome()) {System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation%n", MixAll.ROCKETMQ_HOME_ENV);System.exit(-2);}//日志相关LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();JoranConfigurator configurator = new JoranConfigurator();configurator.setContext(lc);lc.reset();configurator.doConfigure(namesrvConfig.getRocketmqHome() + "/conf/logback_namesrv.xml");log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);//把日志相关信息加载到namesrvConfig和nettyServerConfigMixAll.printObjectProperties(log, namesrvConfig);MixAll.printObjectProperties(log, nettyServerConfig);//新建控制器,该控制器内容不可改变final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);// remember all configs to prevent discard//注册配置信息controller.getConfiguration().registerConfig(properties);return controller;}

整个NamesrvController 的初始化方法基本是对一些配置信息和日志信息的加载,并且把相关信息注入到Namesrv的配置类和netty的配置类。通过Option对象生成命令行信息。加载配置的方法有以下两个

public static Options buildCommandlineOptions(final Options options) {Option opt = new Option("h", "help", false, "Print help");opt.setRequired(false);options.addOption(opt);//参数n指定namesrv的地址,可以是单机或者集群,如果是集群服务器,需要用";"隔开opt =new Option("n", "namesrvAddr", true,"Name server address list, eg: 192.168.0.1:9876;192.168.0.2:9876");opt.setRequired(false);options.addOption(opt);return options;}
public static Options buildCommandlineOptions(final Options options) {//加载namesrv配置信息Option opt = new Option("c", "configFile", true, "Name server config properties file");opt.setRequired(false);options.addOption(opt);opt = new Option("p", "printConfigItem", false, "Print all config item");opt.setRequired(false);options.addOption(opt);return options;}

最后的注册方法是如下实现的

public Configuration registerConfig(Properties extProperties) {if (extProperties == null) {return this;}try {readWriteLock.writeLock().lockInterruptibly();try {merge(extProperties, this.allConfigs);} finally {readWriteLock.writeLock().unlock();}} catch (InterruptedException e) {log.error("register lock error. {}" + extProperties);}return this;}

整个方法是通过锁实现了线程安全、merge方法把配置信息复制到当前类的配置信息文件中,实现了配置的保存

接下来我们看下start方法,即初始化之后整个系统是如何进行启动的。在启动所有服务之前,首先要对remotingServer进行初始化,并且要建立线程池和相关的线程等信息用于执行相关操作

public boolean initialize() {//加载kv配置管理类this.kvConfigManager.load();//初始化remotingServer--netty的服务类,囊括通讯在内的多种功能this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);//根据配置文件构建线程池--构建固定大小的线程池--后面是定义的线程池工厂--这个线程池是用于执行remotingServer的一些操作的this.remotingExecutor =Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));//注册这个过程this.registerProcessor();//主线程启动 为单个线程模式this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {NamesrvController.this.routeInfoManager.scanNotActiveBroker();}}, 5, 10, TimeUnit.SECONDS);//会周期性的执行这条命令,首先是在第一个延迟之后(1),然后每隔一个周期执行一次(10)this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {NamesrvController.this.kvConfigManager.printAllPeriodically();}}, 1, 10, TimeUnit.MINUTES);if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {//监听器--用于通信安全的监听// Register a listener to reload SslContexttry {fileWatchService = new FileWatchService(new String[] {TlsSystemConfig.tlsServerCertPath,TlsSystemConfig.tlsServerKeyPath,TlsSystemConfig.tlsServerTrustCertPath},new FileWatchService.Listener() {boolean certChanged, keyChanged = false;@Overridepublic void onChanged(String path) {if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) {log.info("The trust certificate changed, reload the ssl context");reloadServerSslContext();}if (path.equals(TlsSystemConfig.tlsServerCertPath)) {certChanged = true;}if (path.equals(TlsSystemConfig.tlsServerKeyPath)) {keyChanged = true;}if (certChanged && keyChanged) {log.info("The certificate and private key changed, reload the ssl context");certChanged = keyChanged = false;reloadServerSslContext();}}private void reloadServerSslContext() {((NettyRemotingServer) remotingServer).loadSslContext();}});} catch (Exception e) {log.warn("FileWatchService created error, can't load the certificate dynamically");}}return true;}

本次研究限于个人水平,只能有限的了解到,rocketmq的服务是基于Netty实现的。而Netty的所有服务的交互都是基于此模块的,大概的通信结构图如下

 第二,关于线程池方面。首先会生成一个固定大小的线程池,线程池的大小可以在配置文件中设置;之后生成主线程。并周期性的打印日志相关信息。初次之外,监听器也会在这个时候生成,用于监控模块间通讯的安全.

 

 

三、broker

broker是RocketMQ的核心内容,队列的大部分内容都是通过broker实现的,包括消息的存储和读取,消费持久化,消息的HA和服务端的过滤等。broker有两个角色,包括master和slave。master可以处理生产者发送的消息,也可处理consumer的订阅,把消息发送给消费者。同时能和namesrv进行通信,负责topic的等相关信息的确认。slave不能直接接收生产者传送的消息,slave其实算是一种备份,master写入的数据会备份到slave,当master不可用或者繁忙的时候的时候,slave也可以负责消息的订阅,读取等。通过这种master-slave的方式实现了高可用。消费者端不需要配置相关信息就可以通过切换读slave的方式实现高可用,但是生产者端需要通过配置相同name不同id的broker组实现多可用,创建topic的时候把多个消息放在多个broker组上,这样当一个broker不可用的时候,别的broker组的master仍然可用

broker的master和slave的联系:

一个master可以对应多个slave。对于master,其brokerId=0.对于slave涞水,其brokerId不为零。一个master可以对应多个slave,他们通过拥有同一个brokerName而被分为通过一个Broker set。日常使用至少需要两个brokerset

以下是消息领域模型的一些概念:

  • topic

topic相当于一级分类。我们把具有一致性或者相似型的一类消息定义为同一个topic。topic也是broker分类的最基本的字段。一个消息必须有topic

  • tag

同一个topic下的消息可能有所区别,tag可以代表这种区别。tag相当于二级分类,方便消息的灵活控制

  • group

组,标识具有相同角色的生产者和消费者的集合。在集群之下,我们把多个生产者/消费者加入到同一个组,当一个生产者down之后,我们可以利用同一个组的其他生产者/消费者替代,而不至于影响业务。同时如果加入一个新机器,我们只需要定义他的组名,就可以加入相关组

  • message queue

消息的物理管理单位。一个topic之下可以实现多个queue。在rocketmq中,所有的队列都是持久化并且无限长的数据结构。无限长指的是队列中的每个存储单元都是固定长度,访问其中的存储单元使用offset来访问(offset是long型的64位单位,被认为100年不会溢出)所以被认为是无线长度。我们同时也可以理解为queue是个无限长度的数组,而offset就是他的下标

broker源码解读:

首先是初始化BrokerController

public static BrokerController createBrokerController(String[] args) {//根据netty的命令类获取版本号等相关信息System.setProperty(RemotingCommand.REMOTING_VERSION_KEY, Integer.toString(MQVersion.CURRENT_VERSION));//socket,网络传输包的容量设置if (null == System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_SNDBUF_SIZE)) {NettySystemConfig.socketSndbufSize = 131072;}//socket,接收方包的容量设置if (null == System.getProperty(NettySystemConfig.COM_ROCKETMQ_REMOTING_SOCKET_RCVBUF_SIZE)) {NettySystemConfig.socketRcvbufSize = 131072;}try {
//            PackageConflictDetect.detectFastjson();//创建命令对象Options options = ServerUtil.buildCommandlineOptions(new Options());//主要加载三个重要的配置文件 configFile  printConfigItem printImportantConfigcommandLine = ServerUtil.parseCmdLine("mqbroker", args, buildCommandlineOptions(options),new PosixParser());if (null == commandLine) {System.exit(-1);}//初始化BrokerConfig,NettyServerConfig,NettyClientConfig--服务器对象和客户端对象final BrokerConfig brokerConfig = new BrokerConfig();final NettyServerConfig nettyServerConfig = new NettyServerConfig();final NettyClientConfig nettyClientConfig = new NettyClientConfig();nettyClientConfig.setUseTLS(Boolean.parseBoolean(System.getProperty(TLS_ENABLE,String.valueOf(TlsSystemConfig.tlsMode == TlsMode.ENFORCING))));//监听端口号nettyServerConfig.setListenPort(10911);//常量对象MessageStore的初始化final MessageStoreConfig messageStoreConfig = new MessageStoreConfig();//Broker有三个对象,master分两种//首先是SYNC_MASTER,是同步的master,指的是写入master的消息必须同步写入他的对应的slave才能返回成功信息//ASYNC_MASTER,指的是异步的master。写入此类master的信息会立即返回成功信息,在异步写入对应的slaveif (BrokerRole.SLAVE == messageStoreConfig.getBrokerRole()) {int ratio = messageStoreConfig.getAccessMessageInMemoryMaxRatio() - 10;//slave的主从读写分离机制,如果超过了内存的此限制,将使用从服务器messageStoreConfig.setAccessMessageInMemoryMaxRatio(ratio);}if (commandLine.hasOption('c')) {String file = commandLine.getOptionValue('c');if (file != null) {configFile = file;InputStream in = new BufferedInputStream(new FileInputStream(file));properties = new Properties();properties.load(in);properties2SystemEnv(properties);MixAll.properties2Object(properties, brokerConfig);MixAll.properties2Object(properties, nettyServerConfig);MixAll.properties2Object(properties, nettyClientConfig);MixAll.properties2Object(properties, messageStoreConfig);BrokerPathConfigHelper.setBrokerConfigPath(file);in.close();}}MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), brokerConfig);//环境变量检测if (null == brokerConfig.getRocketmqHome()) {System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation", MixAll.ROCKETMQ_HOME_ENV);System.exit(-2);}String namesrvAddr = brokerConfig.getNamesrvAddr();if (null != namesrvAddr) {try {String[] addrArray = namesrvAddr.split(";");for (String addr : addrArray) {RemotingUtil.string2SocketAddress(addr);}} catch (Exception e) {System.out.printf("The Name Server Address[%s] illegal, please set it as follows, \"127.0.0.1:9876;192.168.0.1:9876\"%n",namesrvAddr);System.exit(-3);}}switch (messageStoreConfig.getBrokerRole()) {case ASYNC_MASTER:case SYNC_MASTER://master的brokerId=0brokerConfig.setBrokerId(MixAll.MASTER_ID);break;case SLAVE://Slave的brokerId必须大于0if (brokerConfig.getBrokerId() <= 0) {System.out.printf("Slave's brokerId must be > 0");System.exit(-3);}break;default:break;}//日志相关if (messageStoreConfig.isEnableDLegerCommitLog()) {brokerConfig.setBrokerId(-1);}messageStoreConfig.setHaListenPort(nettyServerConfig.getListenPort() + 1);LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();JoranConfigurator configurator = new JoranConfigurator();configurator.setContext(lc);lc.reset();configurator.doConfigure(brokerConfig.getRocketmqHome() + "/conf/logback_broker.xml");if (commandLine.hasOption('p')) {InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME);MixAll.printObjectProperties(console, brokerConfig);MixAll.printObjectProperties(console, nettyServerConfig);MixAll.printObjectProperties(console, nettyClientConfig);MixAll.printObjectProperties(console, messageStoreConfig);System.exit(0);} else if (commandLine.hasOption('m')) {InternalLogger console = InternalLoggerFactory.getLogger(LoggerName.BROKER_CONSOLE_NAME);MixAll.printObjectProperties(console, brokerConfig, true);MixAll.printObjectProperties(console, nettyServerConfig, true);MixAll.printObjectProperties(console, nettyClientConfig, true);MixAll.printObjectProperties(console, messageStoreConfig, true);System.exit(0);}log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);MixAll.printObjectProperties(log, brokerConfig);MixAll.printObjectProperties(log, nettyServerConfig);MixAll.printObjectProperties(log, nettyClientConfig);MixAll.printObjectProperties(log, messageStoreConfig);//初始化对象并注册避免信息丢失final BrokerController controller = new BrokerController(brokerConfig,nettyServerConfig,nettyClientConfig,messageStoreConfig);// remember all configs to prevent discardcontroller.getConfiguration().registerConfig(properties);//初始化boolean initResult = controller.initialize();if (!initResult) {controller.shutdown();System.exit(-3);}//钩子函数--程序停止或者以外宕机的情况下执行清理现场的代码Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {private volatile boolean hasShutdown = false;private AtomicInteger shutdownTimes = new AtomicInteger(0);@Overridepublic void run() {synchronized (this) {log.info("Shutdown hook was invoked, {}", this.shutdownTimes.incrementAndGet());if (!this.hasShutdown) {this.hasShutdown = true;long beginTime = System.currentTimeMillis();//关闭controllercontroller.shutdown();long consumingTimeTotal = System.currentTimeMillis() - beginTime;//打印日志,包括相关消息log.info("Shutdown hook over, consuming total time(ms): {}", consumingTimeTotal);}}}}, "ShutdownHook"));return controller;} catch (Throwable e) {e.printStackTrace();System.exit(-1);}return null;}

接下来启动相关的所有服务即可。

 

 

四、consumer

consumer消费者,顾名思义所做的是从队列中获取信息并且对信息进行一定的处理。这时候涉及到许多问题,比如数据重复时的幂等问题,数据的执行顺序问题,事务失败的回滚问题,之后我们会一一分析

五、队列事务性消息的实现

1.什么是事务

事务指的是一组原子操作单元,从数据库的角度来看,是一组sql指令,如果某个原因使得其中的一条执行指令有错误,则撤销之前执行过的命令。或者通俗的说,一组原子操作要么全部执行,要么全不执行

2.事务的四个性质(ACID)

  • 原子性(Atomicity):事务的原子性是指一组事务可以看作是一个原型的操作,任何执行失败的操作都会影响到整个整体的成功或者失败。一个事务,要么全部成功,要么全不成功
  • 一致性(consistent):在事务执行的前后,事务的状态要保持一致。在事务管理的整个过程,事务的一致性是核心。AID都是为了C而服务的。所有的操作其实都是为了保持事务的一致性。事务在执行前后都有状态,我们需要确保提交前和提交之后的状态都是正确的。
  • 隔离性(Isolated):隔离性是指事务执行的过程中,不能被其他的事务所影响。这样主要是为了处理鬓发失误引起的问题:
  1. 脏读(dirty read):一个事务读取了另一个事务未提交的数据
  2. 不可重复读(non-repeatable read):一个事务的操作导致了另一个事务前后两次读取到不同的事务。例如事务1第二次的的时候事务2对事务1第一次读的数据做了修改,导致事务1前后两次独到的数据不一致
  3. 幻读(phantom read):一个事务的操作导致另一个数据索要读取的数据消逝不见或者多了之前不存在的数据(多是insert或者delte的时候)
  • 持久性(durable):事务一旦提交,对于对于数据库的影响是持久的,不可回滚。

3.如何保持分布式事务的一致性

事务的一致性在数据库方面有两个含义

  • ACID的数据一致性
  • CAP的数据一致性(CAP指的是分布式系统中一致性,可用性和分区容错性原则)
  1. 一致性:分布式系统中所有数据备份,在同一刻是否拥有相同的值
  2. 可用性:集群中某一个节点或者某一部分节点故障之后,集群还能否反应客户的读写请求
  3. 分区容错性:对通信的时限要求,如果所需要的时间限制不足的话,那么必须在C和A之间选择一个
  4. CAP的取舍策略:我们模拟一下场景,两台服务器S1和S2,他们分别有对应的数据库D1和V1。那么满足一致性的话,D1==V1;满足可用性的话,不论用户请求S1还是S2,服务区都会立即做出反应;满足容错性的话,S1或者S2任意一方宕机或者网络链接中断,都不会影响S1和S2彼此之间的正常运作。但是现在有如下状况:用户向S1请求修改数据库。D1变成了D2,但是S1和S2之间的通信中断,导致S2暂时不能同步更新为V2==D2.但是有用户向S2请求数据,由于数据还没有同步,不能返回给用户数据。这时候有如下两种解决方法:1.放弃一致性,响应数据给用户。2.放弃可用性,阻塞进程直到S1的数据同步到S2.所以说满足分区容错性的系统,并不能同时满足CA。我们需要对其进行取舍。共有如下三种策略:

 

1.CA舍弃P
舍弃分区策略,如果只有一个分区,那么可同时满足CA2.CP舍弃A---Zookeeper
不考虑可用性,多个分区采取强一致性,那么可以保持数据的高一致性。但是同时一个节点的故障将导致所有节点的阻塞3.AP舍弃C-----eureka
不考虑一致性,多个分区可以充分提高可用性。分区越多,用户越能够就近访问。提高了高可用性

接下来我们考虑到分布式数据库的一些问题。比如我们的下单操作,下单生成订单和减少余额可以看作是一个事务,但是订单和余额对应的数据库却放在了两台服务器之上,如果执行某个操作中途出现了bug,整个事件的回滚将变得很困难。

所以分布式的情况下,事务设计的将变得更加负责,大概有以下集中处理方式:

  1. 两阶段提交(2PC):两阶段提交是一种协议,这种协议基于一种名为协调器的实现。我们把整个事务的实现拆分为一下及部分:协调器和若干事务执行者,以之前下单为例子。下单的时候要生成日志,要进行商品库存和销量的修改,要进行订单的生成。我们把这些操作视为一个个模块。把这个消息通知给协调器。协调器把名为prepare的信息写到本地日志(回滚日志)。然后你协调器给各个模块发送prepare信息,对于不同的执行者这个prepare是不同的。事务执行者收到消息之后执行相应的操作,比如生成订单,生成日志等。但是执行后不提交,如果是成功的话则返回yes给协调器,否则返回no。协调器处理信息,如果收到的全是yes,则发送commit消息给各个事务执行者。否则返回rollback。
    但是实际的应用中,这种协议方法却很少被应用,因为其所需要的条件很苛刻。如果网络稍微有点卡顿延迟,则时间代价会相当的高。并且事务执行的过程中会上锁,这种方式会导致数据库长时间的不可用。所以很少有人采用这种方式
  2. 最终一致性(柔性事务):介于上述两阶段提交在分布式高并发情况下的表现,我们采取比较中和的手段,即不追求事务在执行的每一分每一秒中都是一致性的。而是追求最终结果的一致性。也就是说,我们可以接收事务在执行过程中短暂的不一致。从ACID的刚性一致性转化为BASE原则的柔性一致性

    一、重试和幂等
    首先我们介绍下分布式事务执行过程中不可以避免的一个问题,事务执行失败后该如何处理。
    一般来说,对于分布式,条件不如单机情况下的稳定。因此,难免会遇到某个主机宕机,接口返回失败等情况。所以需要反复重试来解决这种错误,而不是以失败就回滚。在队列中,业务异常导致一条消息重复的发到了消息队列中,但是消息处理失败之后放到retry队列,从而进行反复的尝试。这是候我们急需方式来解决这种情况
    重试就不得不提到幂等这个概念。在数学中f(x)=f(f(x)),我们称之为幂等函数。在编程中,一个系统,使用同样 的条件,请求一次和请求多次对这个系统的影响是一致的,我们称之为幂等。
    幂等我们有以下两点需要注意:
    MVCC指的是版本号,即数据更新的时候我们需要有对应的版本号,只有版本号一致的情况下才能操作成功,每个版本号只有一次执行成功的机会,失败后就需要重获版本号。这样减少了事务重复的可能。但是这可能不起太大的作用,因为job是反复执行的,会不断生成新的版本号
    去重:通过业务逻辑来去重,比如点赞这个业务逻辑,我们可以针对user_id和comments_id做一个表,如果点赞成功就添加一条新的记录。由于标的索引是唯一的,所谓不会重复插入记录,自然也就不会出现错误的情况

    二、异步确保
    本文探讨的重点--如何通过消息队列实现事务的一致性----思路实现一

    我们以转账为例,A转账给B100元,但是A和B放在不同的服务器和数据库上。
    首先可以确保A和B是必须要实现的原子性事务,少了任何一个操作剩下的那个也将变得没有意义。但是如果直接用ACID的刚性事务性来实现的话,我们需要考虑到网络延迟的存在。可能发送消息失败的话,那么B将不会增加100元。如果返回通知失败的话,但是B的实际操作已经成功。并且在DB事务中添加网络操作的话,会使得事务的执行时间变得很长,对DB的影响极大
    所以我们提出以下解决方式:(消息表)
    首先对于生产者(producer),我们先要添加一张表,消息表,用于记录发送的消息以及消息的回执等内容;生产者再想消费者发送业务数据的时候,也要在消息表增加一个消息记录。由于这两个操作都是对生产者的DB进行的操作,所以我们把他们放在同一个事务里用来保持一致性。同时对于这张表,我们需要一个维护者把表中未发送的消息放入到消息队列之中,另外检测消息的执行是否超时或者失败,遇到这种异常的话就进行重试。我们允许消息重复,但是不允许消逝丢失,并且顺序不能被打乱
    在接收方(consumer)我们必须实现幂等。出了上述的方法之外,我们还可以通过增加判重表来实现幂等。
    缺点:上述方法的缺点是增加了不必要的开销和耦合性

    三、事务消息(RocketMQ支持)
    消息队列的事务消息概念相当于异步确保思路的优化。
    首先消息的放松变成了两个阶段,准备和确认,生产方的步骤如下
    1.发送prepared给MQ
    2.执行本地的事务
    3.根据本地事务的执行结果发送确认消息给MQ,决定确认prepared或者取消
    这时如果第三步失败的话,怎么解决呢?RocketMQ会定期的扫描prepared消息,并询问发送方,是要确认还是取消。要求生产方需要实现Check接口

    消费方只需要接收相关消息处理即可。由于消息队列的事务消息支持重试,如果消费方失败,消息队列会自动发起重试,不需要消费方自己实现消息的重试。
    但是rocketMQ在新的版本中取消了回查的操作,所以回查部分现在需要自己解决,暂时先给出解决方法如下:
    我们暂时以银行转账系统为例:

    需要设计额外的表--转账消息确认表
    第一步:A银行系统生成一条转账消息,以事务消息的方式写入RocketMQ,此时B银行系统不可见这条消息(Prepare阶段)
    第二步:写入MQ成功后,回调A银行系统,对T1,T2表进行操作(很显然需要是一个事务)。我们重点关注下T2表,这个表是用来干嘛的呢?每条转账消息都会在T2表中,该表有2个特殊的字段:status,updatetime。
    第三步:完成第二步,接下来发送确认消息给MQ,如果这个确认消息发送成功,那么这条转账消息,将对B银行系统可见。然后B银行系统,会在一个事务中完成对t3,t5的操作。

    如果发送确认消息给MQ失败的处理思路:
    首先,B银行系统,有一个定时任务(比如说每隔1MIN执行一次),扫描表t5,取得一段时间内的数据,发送给A银行系统。要知道t5中的数据,必然是A银行系统成功处理并发送确认消息成功的转账数据。为什么要发送给A银行系统呢,其实就是为了找到那些发送确认消息失败的转账数据。那么怎么发给A银行系统呢,这个方式比较多,可以考虑在来一个Topic,也可以考虑Netty等。发送给A银行系统,其实就是为了更新t2表的status,updatetime。

    这里有一个关键,如何“扫描表t5,取得一段时间内的数据”?这就是t4的作用,在t4中记录一个time字段,每次定时任务启动,先更新time(比如设定为当前系统时间,设置前的的时间为old),然后扫描出t5中大于这个old时间的转账数据,如此循环往复。

    其次,A银行系统,也有一个定时任务(可以根据业务消费能力定,可以大一些),扫描t2表(指定status及updatetime条件),将那些确认消息发送失败的转账消息找出来,更新updatetime并发送给MQ。

    其实到这里,你可以发现RocketMQ的一个特点,就是将生产者和MQ绑定,而不需要特别处理消费者,这是为什么呢?因为消息只要发往RocketMQ成功,那么就意味着成功,为什么这么说?

    前面,我们说过,消费者端消费消息只会产生2种错误,第一:timeout,第二:exception。要知道RocketMQ对于超时,会不断重试;对于消费异常,会根据消费端的返回码,会有重试机制保证。也就是,RocketMQ一定会让消息得到消费,如果消费有问题,只能是消费者的问题,而不会是RocketMQ的问题!
    如果遇见消息失败但是重试无用的情况,只能选择人工介入了

六、消息队列的顺序问题

在一些严格的逻辑中,消息的执行是有先后顺序的,比如传统的下单,都是生成订单之后才会生成订单日志,生成送积分操作等等。那么消息队列应该如何实现消息的顺序执行呢?
首先我们应该清楚,所谓的顺序执行对于消息M1 M2来说,我们需要保证M1先于M2进行消费。如果发送到两台服务器上面的话,我们不能保证M1优先于M2到达服务器集群,也不能保证M1被优先消费。最简单的方法就是把这一类型的消息发送到同一台服务器之上,这样就能能保证M1优先于M2到达服务器。但是,这种保证也仅仅是理论之上的,现实中由于网络延迟的存在,M2仍然有可能先被消费。即便是二者按照先后顺序到达服务端,二者发往不同的消费端,仍有可能导致M2先到服务端。这种解决方法是将M1 M2发送到同一个消费端,并且需要消费端进行响应成功M1之后才能发送M2。

  • 保证生产者--MQServer--消费者是一对一的关系
    这是最简单的一种方法,根据队列的性质,只要先发送,就一定会先执行。但是却有如下的缺点:
    并行度不够造成吞吐量的限制
    异常处理难以解决,一旦消费方出现问题,整个流程将被阻塞

我们需要掌握以下原则:

  • 不关注消息的乱序的应用大量存在
  • 消息队列无序不代表消息无序

所以最好的解决方式是依赖业务层面对消息的顺序执行进行设计

RocketMQ的消息顺序发送是如下方法实现的:

1.发送消息时,会通过轮询所有队列的方式确认发送到哪一个队列(负载均衡策略),比如如下方法会根据订单号将消息先后发送到同一个队列中


// RocketMQ通过MessageQueueSelector中实现的算法来确定消息发送到哪一个队列上
// RocketMQ默认提供了两种MessageQueueSelector实现:随机/Hash
// 当然你可以根据业务实现自己的MessageQueueSelector来决定消息按照何种策略发送到消息队列中
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {@Overridepublic MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {Integer id = (Integer) arg;int index = id % mqs.size();return mqs.get(index);}
}, orderId);

获取到路由信息之后,会根据MessageQueueSelector的算法来选择一个队列,同一个orderId获取到的肯定的是同一个队列


private SendResult send()  {// 获取topic路由信息TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());if (topicPublishInfo != null && topicPublishInfo.ok()) {MessageQueue mq = null;// 根据我们的算法,选择一个发送队列// 这里的arg = orderIdmq = selector.select(topicPublishInfo.getMessageQueueList(), msg, arg);if (mq != null) {return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, timeout);}}
}

关于MessageQueueSelector的实现 官方提供了三种demo

rocketMQ支持自定义的筛选策略,但是必须要实现MessageQueueSelector接口:

public interface MessageQueueSelector {MessageQueue select(final List<MessageQueue> mqs, final Message msg, final Object arg);
}

这个接口只有一个select方法,首先是根据hash值分配策略如下

public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {int value = arg.hashCode();if (value < 0) {value = Math.abs(value);}value = value % mqs.size();return mqs.get(value);}

然后是根据随机数来分配的策略如下

public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {int value = random.nextInt(mqs.size());return mqs.get(value);}

我们可以根据自己的业务来确认根据何种方式来实现负载均衡

七、重复性的解决

RocketMQ不解决重复问题,需要在自己的业务逻辑中实现去冲

去冲的思路之前我们曾经有过讲解
1.保证业务逻辑的幂等性
2.保证每条消息都有唯一性编号并且消息处理成功生成对应日志

 

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 数学--数论--素数

    定义判断: bool isPrime (int n) {for(int i=2;i*i<=n;i++){if(n%i==0) return false;}else return false; }埃氏筛法 int primes[N],cnt; bool bprime[N]; void getPrime(int n){memset(bprime,false,sizeof(bprime));bprime[0]=true;bprime[1]=true; for(int i=2;i<…...

    2024/4/24 19:31:04
  2. 关于解决Request method 'HEAD' not supported

    该异常一般是在线上生产环境中产生的请求异常。产生该异常的缘由:实际生产中的head请求一般由搜索引擎发出 。 搜索引擎先发送HEAD 请求是为了获得页面的更新时间(即response header 中的Last-Modified 域), 用于判断自从上一次该页面被收入索引库以后有没有被更新过,如果…...

    2024/4/27 6:59:50
  3. TensorFlow源码分析(4):OpKernelContruction类的构造函数

    在文章(2)中,介绍了ConstantOp类的构造函数。构造函数中唯一的一个参数是OpKernelConstruction类型的对象。这个对象包含了操作类需要的所有输入,参数以及运行环境。它定义在tensorflow/core/framework/op_kernel.h文件中。OpKernelConstruction类的成员函数比较多,无法在…...

    2024/4/26 17:07:39
  4. 错题本

    let a = {};let b = { value: b }let c = { value: c }a[b] = 123;console.log(a); // {[object Object]: 123}a[c] = 456;console.log(a); // {[object Object]: 456}console.log(a[b]); // 456...

    2024/4/19 20:41:31
  5. 用Python实现中文编程

    用Python实现中文编程文章来源:企鹅号 - 一再学习既然是扯淡,就不要当真。所谓“中文编程”就是用中文写写代码,不管你看着累不累,我写着就得累死~“中文编程”由来已久,不知道你听说过“易语言”没有,我很早就听说过了,但是一直没有去尝试,因为觉得完全没必要,甚至是…...

    2024/4/17 1:44:02
  6. pytest知识点归纳-标记函数

    1、执行具体py文件 pytest filename2、执行py文件中的部分函数 pytest fileneme::函数名称 或者 pytest -k 匹配字段 filename3、如果要部分执行,且函数名称无规则的时候,就要使用pytest.mark在函数上面标记了。pytest -m finished fileanme ,这样就会执行test_raises…...

    2024/4/27 0:35:59
  7. Lambda 函数与表达式

    Lambda 函数与表达式 C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。 Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。 Lambda 表达式本质上与函数声明非常类似。La…...

    2024/4/20 10:00:29
  8. [论文解读]arXiv 219|MobileNetV3: Searching for MobileNetV3

    题目:Searching for MobileNetV3 作者:Andrew Howard 、 Mark Sandler 、 Grace Chu 、 Liang-Chieh Chen 、 Bo Chen 、Mingxing Tan… Motivation: 作者还是想在移动端使网络的性能发挥到极致,从而提出了MobileNetV3-Large 、MobileNetV3-Small。 Related Works: Network …...

    2024/4/23 3:32:38
  9. git下载新建仓库添加团队等操作

    Git基础一条龙操作 https://www.cnblogs.com/sakurayeah/p/5800424.html...

    2024/4/25 16:56:25
  10. 二叉树的遍历(递归,非递归,Morris)

    二叉树的遍历目录递归遍历 非递归遍历 Morris遍历1. 递归遍历 递归版遍历只要当前节点不为null,就可以三次回到当前节点。 public static void preOrderRecur(Node head) {if (head == null) {return;}System.out.print(head.value + " ");preOrderRecur(head.left)…...

    2024/4/26 21:53:59
  11. Django——加密、验密、解密

    简单介绍一下今天使用到的django内置的加解密包: from django.contrib.auth.hashers import make_password如上图所示,django.contrib.auth.hashers即为django内置的加解密包。 1.加密语句:make_password(原始密码)return 加密后的密码from django.contrib.auth.hashers imp…...

    2024/4/27 8:47:36
  12. Python爬虫系列——(二)爬取有道翻译

    Python爬虫系列——(二)爬取有道翻译 2.1功能说明打开有道翻译页面,输入要翻译的内容,页面并没有通过刷新来获取数据,所有是使用的前端的Ajax技术进行的交互,也就是说这里使用的是Ajax技术与有道的后台服务器进行的请求,从而得到返回结果。下面我们打开“Chrome"浏…...

    2024/4/26 9:30:51
  13. Leetcode 1291:顺次数(超详细的解法!!!)

    我们定义「顺次数」为:每一位上的数字都比前一位上的数字大 1 的整数。 请你返回由 [low, high] 范围内所有顺次数组成的 有序 列表(从小到大排序)。 示例 1: 输出:low = 100, high = 300 输出:[123,234]示例 2: 输出:low = 1000, high = 13000 输出:[1234,2345,3456,…...

    2024/4/26 19:53:50
  14. python3.7+flask+web学习笔记2

    Flask的学习1.Flask的基本结构首先,web的端口一般都是8080端口,可以考虑用修改端口的方式;apptest.run(debug=True, host=0.0.0.0, port=8080) 端口冲突才会用到。2.URL传参数Flask参数传递1.方式一/<参数名>/例如:如果没有编译成功,请按照一下 Flask3.URL反转代码…...

    2024/4/25 13:26:49
  15. JVM性能调优-FullGC与Minor的区别频繁FullGC问题分析

    简介:FullGC与MinorGC讲解 Minor GC触发条件 当Eden区满时,触发Minor GC FullGC触发条件调⽤ System.gc() 此⽅法的调⽤是建议 JVM 进⾏ Full GC,虽然只是建议⽽⾮⼀定,但很多情况下它会触发 Full GC。因此强烈建议能不使⽤此⽅法就不要使⽤,让虚拟机⾃⼰去管理它的内存。…...

    2024/4/27 7:05:39
  16. 七夕节H1215(数论素数、质因数、正约数)

    Problem Description七夕节那天,月老来到数字王国,他在城门上贴了一张告示,并且和数字王国的人们说:"你们想知道你们的另一半是谁吗?那就按照告示上的方法去找吧!"人们纷纷来到告示前,都想知道谁才是自己的另一半.另一半就是其所有正约数的和。数字N的因子就是所有比…...

    2024/4/23 14:48:30
  17. mybatis+oracle批量操作

    1.批量新增主键自增<insert id="timeTaskBatch" parameterType="java.util.List">insert into t_policy_schedule(SCHEDULE_ID,policy_id, start_date, end_date,turn_on_time, turn_off_time)SELECT SEQ_T_POLICY_SCHEDULE.Nextval,a.* FROM(<fo…...

    2024/4/15 22:19:15
  18. UE4自定义AI任务节点更改黑板中值会引起崩溃的Instance机制

    在UE4做AI的时候,做了一些继承自UBTTaskNode的任务节点,来完成我们的自定义AI行为节点需求,过程中遇到了一些坑,记录一下。自定义了UBTT_KTPSChangeMonsterState的任务节点,来更改怪物的状态,怪物的状态信息保存的在行为对应的黑板中。怪物状态在黑板中存储的是枚举类型S…...

    2024/4/26 13:31:03
  19. 常用LaTex语法积累,并示例代码和呈现效果

    积累常用的LaTex语法,并附上示例代码和呈现效果。持续更新。 上标和下标 上标用^表示,下标用_表示。 示例: E=mc^2, 效果: E=mc2E=mc^2E=mc2 示例: x_1=x_2, 效果: x1=x2x_1=x_2x1​=x2​ 行内公式和行间公式 行内公式以$作为开始和结束,行间公式以$$作为开始和结束。…...

    2024/4/25 13:11:24
  20. 洛谷1304 哥德巴赫猜想

    思路:判断两个数是否为素数j #include <stdio.h> #include <math.h> int sushu(int x); int main() {int N;int i,num1,num2;int x1,x2;printf("请输入一个数:");scanf("%d",&N);for(i = 4;i <= N;i = i + 2){for(num1 = 2;num1 <= …...

    2024/4/26 5:28:00

最新文章

  1. mac资源库的东西可以删除吗?提升Mac运行速度秘籍 Mac实用软件

    很多小伙伴在使用mac电脑处理工作的时候&#xff0c;就会很疑惑&#xff0c;电脑的运行速度怎么越来越慢&#xff0c;就想着通过删除mac资源库的东西&#xff0c;那么mac资源库的东西可以删除吗&#xff1f;删除了会不会造成电脑故障呢&#xff1f; 首先&#xff0c;mac资源库…...

    2024/4/27 13:17:53
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 基于ArrayList实现简单洗牌

    前言 在之前的那篇文章中&#xff0c;我们已经认识了顺序表—>http://t.csdnimg.cn/2I3fE 基于此&#xff0c;便好理解ArrayList和后面的洗牌游戏了。 什么是ArrayList? ArrayList底层是一段连续的空间&#xff0c;并且可以动态扩容&#xff0c;是一个动态类型的顺序表&…...

    2024/4/23 6:10:49
  4. 并发编程相关面试题详细总结

    1. 什么是线程安全&#xff1f; 答&#xff1a;线程安全是指在多线程环境下&#xff0c;一个代码块或对象能够在同时被多个线程访问时正确地工作&#xff0c;而不会出现不一致或错误的结果。线程安全的代码应该能够处理并发访问&#xff0c;避免竞态条件和数据不一致。 2. 什么…...

    2024/4/25 2:06:59
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/26 16:00:35
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57