面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)

好文参考: SpringCloud 与 SpringBoot 微服务 架构 | 面试题及答案详解_Wbw Belief的博客-CSDN博客_微服务架构面试题

1、什么是微服务? 

将传统的单体应用,根据业务划分多个服务,每个服务互相独立,可以单独部署,而服务和服务之间又可以通过rpc、dubbo、http等互相调用

2、优缺点:

优点:

①针对频繁使用的服务可以水平扩展(添加机器)

②方便开发,避免代码版本冲突

③明确业务边界,解耦。

缺点:

①增加运维成本,单体的应用只需要维护一个机器,现在要维护很多机器。

组件nacos 注册中心

服务注册基本流程:服务方通过安装的nacos客服端发生http请求,注册到 nacos服务器上(它把收到的请求放到 BlockingQueue(阻塞)队列里,单线程异步地不断消费该队列,放到set集合里,从而实现注册),消费端从naocs服务器上拉取服务列表,消费端这时候就可以访问服务方了。如果是同一个服务有多个提供方(只有端口号不一样),这些服务方也都会注册到naocs服务器上,只是客服端拉取服务列表后,会通过ribbon进行轮询来选择哪一个服务方。如下图:

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第1张图片

 nacos如何支持消费者和服务端的高并发的读和写的“服务列表”?

答:copyOnWriter机制。某服务方拷贝一份“服务列表”并更新后,再替换服务注册中心的“服务列表”。客服端始终读取的是 “服务注册中心”的服务列表。

通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。

Nacos 客户端是怎么实时获取到 Nacos 服务端的最新数据的:

  • Nacos 并不是通过推的方式将服务端最新的配置信息发送给客户端的,而是客户端维护了一个长轮询的任务,定时去拉取发生变更的配置信息,然后将最新的数据推送给 Listener 的持有者。

那什么是长轮询,什么是短轮询呢?

短轮询:一直不停的发送请求问服务器,服务器立马响应客户端。

长轮询:一直不停的发送请求问服务器,服务器挂起这个请求,一直等到检测到数据变化才响应客户端。

HTTP的Connection Header

HTTP协议中的短轮询、长轮询、长连接和短连接 - 张龙豪 - 博客园

如果多个服务方进行服务 会出现并发覆盖“服务列表”吗?

答:不会,因为“注册中心”只有一个线程负责消费BlockingQueue队列,相当于会一个一个更新,所以不会导致并发覆盖服务列表。

nacos客户端发送心跳、服务端收到心跳会怎么做?

 客户端维持一个定时任务,向服务端发送请求,默认间隔5s。服务端接收本次心跳的时间-上次接收心跳的时间>超时时间(默认15s),则会把服务设置为不健康的状态,如果大于剔除时间(默认30s),就会从服务列表移除。

服务调用

Spring cloud 的服务的通讯是基于http 的。Spring cloud有两种服务调用方式:

一种是ribbon+restTemplate,另一种是feign。ribbon

ribbon是一个负载均衡客户端。Feign默认集成了ribbon。

Fegin的原理

通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类:

1、开始调用方法。

2、由动态代理Target接管方法运行。

3、Contract根据注解,取得MethodHandler列表。

4、执行Request相关的MethodHandler。

5、由Encoder包装Request,执行相应的装饰器,记录日志。

6、基于Client发起请求。取得请求Response,由Decoder解码。执行Response相关的MethodHandler。经由代理类返回最终结果。

Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端。



 

服务降级、熔断、限流原理

引入这些概念的原因是:因为服务雪崩。

使用组件:Sentinel、Hystrix 组件进行服务熔断和限流

服务雪崩:一个服务失败,导致整个链路服务都失败

解决手段:服务降级。(服务熔断是降级的一种)

服务降级:A服务调用B服务,因某种原因B服务无响应或者响应慢,A为了保证自己的服务整体可用,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。为了保护自己,避免宕机。

可细分为:

        ①服务熔断。调用失败次数达到一定阈值。触发熔断。

        ②开关降级。

        ③限流降级。

降级和熔断的区别:

        降级是从整体负荷考虑,熔断是具体的某个服务触发。

        降级一般从外围开始,熔断是某个任意服务出现意外。

        降级是为了维护重要的业务,损失一些不重要的业务。

高并发下,保护系统的方法:缓存、降级、限流。

服务限流:解决方式:

① 滑动计数器,单位时间内接受过多(比如10个)请求直接拒绝访问。

②漏桶算法:固定速率从通中流出水滴。如果漏斗溢出,就拒绝访问或者服务降级。

                        缺点:如果瞬时流量很大,那么会溢出(丢到很多请求)。

③令牌桶算法:根据1/QPS生成恒定速率的 生成令牌速度,生成的令牌放入桶中,桶满则丢弃令牌。  与此同时,每次来个新请求,就消掉一个token,拿到token则处理请求,没拿到token则拒绝请求。

好处:根据 QPS(每秒请求数)来提高或者降低处理请求的速度。

可使用Guava的RateLimiter进行实现

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第2张图片

消息队列

我的理解:本质是缓冲

 优点:

解耦:将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

异步:一些非必要的业务逻辑以同步的方式运行,太耗费时间。

消峰:系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。

消息队列面试题要点 - Mr.peter - 博客园

使用了消息队列会有什么缺点?

系统可用性降低,系统复杂性增加。

消息队列如何选型?

中小型软件公司,建议选RabbitMQ,因为管理界面用起来十分方便。

大型软件公司,根据具体使用在rocketMq和kafka之间二选一。

kafka多用于日志采集。

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第3张图片

必读文章:advanced-java/why-mq.md at main · doocs/advanced-java (github.com) 

如何保证消息队列是高可用的

RabbitMQ有三种模式:单机、集群、镜像模式。

单机是入门练习

集群模式是队列只在某个节点上所以也不保证可用性。(看作一个超级计算机)

只有镜像模式有高可用。 (每个节点上都复制一份数据,缺点:网络占用高、不能水平扩展)

思想:多个集群同步信息。

1、RabbitMQ在多台服务器启动实例、每台服务器一个实例、当你创建queue时、queue(元数据+具体数据)只会落在一台RabbitMQ实例上、但是集群中每个实例都会同步queue的元数据(元数据:真实数据的描述如具体位置等)。
2、当用户消费时如果连接的是另外一个实例,当前实例会根据同步的元数据找到具体的数据所在的实例从其上把具体数据拉过来消费。

这种方式的缺点很明显,没有做到所谓的分布式、只是一个普通的集群。这种方式在消费数据时要么随机选择一个实例拉去数据、要么固定连接那个queue所在的实例来拉取数据,前者导致一次实例见拉取数据的开销、而后在会导致单实例性能的瓶颈。

镜像模式:

不管是元数据还是里面的具体消息都会存在于所有的实例上。每次写消息时都会把消息同步到每个节点的queue中去。

这种方式的优点在于,你任何一个节点宕机了、都没事儿,别的节点都还可以正常使用。

缺点:开销大。

文章参考:https://segmentfault.com/a/1190000023008259

如何保证消息不被重复消费?(如何保证消息队列的幂等性?)

 正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。

那造成重复消费的原因?,就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。

如何解决?这个问题针对业务场景来答,分以下三种情况:

(1)比如,你拿到这个消息做数据库的insert操作,那就容易了,给这个消息做一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

(2)再比如,消费者拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。

(3)如果上面两种情况还不行,上大招。搞个全局变量,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将以K-V形式写入redis.那消费者开始消费前,先去redis中查询有没有消费记录即可。

如何保证消费的可靠性传输?

其实这个可靠性传输,每种MQ都要从三个角度来分析:

  • 生产者弄丢数据。解决方法:transaction机制,开启事务,失败就回滚。
  • 消息队列弄丢数据。解决方法:一般是开启持久化磁盘的配置,先保存,再发送。
  • 消费者弄丢数据。解决方法:自动确认改手动确认消息

如何保证消息的顺序性 

针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中(kafka中就是partition,rabbitMq中就是queue)。然后只用一个消费者去消费该队列。

缺点:降低了吞吐量。

RabbitMQ实践

  rabbitMq 共有4种类型的交换机,每种交换机的匹配规则不一样
 1、direct 直连交换机:  
        顾名思义,直接指定 某routingKey 即可把消息发生到 某消息队列上,(其实它也经过交换机,只不过rabbitMq有默认的交换机 叫:AMQP default)
       适用场景: 1个生产者 1个消费者

 2、fanout广播类型的交换机 :
一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。
  fanout交换机转发消息是最快的。

 3、topic 主题类型的交换机。 
用类似与正则表达式的,指定交换机名称 且符合匹配规则路由键的队列 会接收到生产者发送来的消息
  场景: 一个交换机下绑定了很多很多队列,方便管理,但又想发生到其中的几个队列

 4、headers类型的交换机。
 headers类型的交换机是根据消息的headers来投递消息而不是根据 routing key 来投递消息,
    在绑定队列时需要指定参数Arguments,发送消息时只有指定的headers与队列绑定时对应的Arguments相匹配时,消息才会被正确投递
 文章参考:RabbitMQ四种交换机类型 - 掘金 (juejin.cn)

rabbitMq 共有6种工作模式 

1、安装:

 官方地址:Installing on Windows — RabbitMQ

2、代码

依赖: 

  
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.boot
            spring-boot-starter-amqp
        

application.properties文件配置:

spring.rabbitmq.addresses=127.0.0.1
spring.rabbitmq.port=5678
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#手动确认消息。不设置默认自动确认消息
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#虚拟主机设置 虚拟主机 类似于命名空间, 虚拟主机和虚拟主机之间是互相隔离的,队列和虚拟机 在多个部门直接使用时,便于管理和标识 才使用这个功能
#spring.rabbitmq.virtual-host=my

代码:

@Slf4j
@Configuration
public class RabbitMQConfig {

    private static final String topicExchangeName = "topic-exchange";
    private static final String fanoutExchange = "fanout-exchange";
    private static final String headersExchange = "headers-exchange";

    private static final String queueName = "cord";

    //声明队列
    @Bean
    public Queue queue() {
        //Queue(String name, boolean durable, boolean exclusive, boolean autoDelete)
        return new Queue("cord", false, false, false);
    }

    //声明Topic交换机
    @Bean
    TopicExchange topicExchange() {
        return new TopicExchange(topicExchangeName);
    }

    //将队列与Topic交换机进行绑定,并指定路由键
    @Bean
    Binding topicBinding(Queue queue, TopicExchange topicExchange) {
        /*
        模糊匹配符号及其规则
        # :代表匹配一个多或多个、或者一个也匹配不到,支持多级
        * :代表必须匹配一个,且只能是一级
        例如:org.cord.# 能够匹配 org.cord.test 和 org.cord.test.test1
        * */
        return BindingBuilder.bind(queue).to(topicExchange).with("org.cord.#");
    }

    //声明fanout交换机
    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange(fanoutExchange);
    }

    //将队列与fanout交换机进行绑定
    @Bean
    Binding fanoutBinding(Queue queue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queue).to(fanoutExchange);
    }

    //声明Headers交换机
    @Bean
    HeadersExchange headersExchange() {
        return new HeadersExchange(headersExchange);
    }

    //将队列与headers交换机进行绑定
    @Bean
    Binding headersBinding(Queue queue, HeadersExchange headersExchange) {
        Map map = new HashMap<>();
        map.put("First","A");
        map.put("Fourth","D");
        //whereAny表示部分匹配,whereAll表示全部匹配
//        return BindingBuilder.bind(queue).to(headersExchange).whereAll(map).match();
        return BindingBuilder.bind(queue).to(headersExchange).whereAny(map).match();
    }
}

消费者:

@Component
@RabbitListener(queues = "cord")
public class Consumer {

    @RabbitHandler
    public void processMessage(Channel channel,  String receiveMsg, Message message) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.format("Receiving Message: -----[%s]----- \n.", receiveMsg);

        //如果想要手动确认收到消息,需要配置: 去掉自动签收功能 spring.rabbitmq.listener.simple.acknowledge-mode:manual    (自动是 auto 手动是 manual),否则:报错:Shutdown Signal: channel error; protocol method: #method(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag
        channel.basicAck(deliveryTag, false);
    }
}

生产者:

import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class Producer {

    @Autowired
    private AmqpTemplate template;

    @Autowired
    private AmqpAdmin admin;

    /**
     * @param routingKey 路由关键字
     * @param msg 消息体
     */
    public void sendDirectMsg(String routingKey, String msg) {
        template.convertAndSend(routingKey, msg);
    }

    /**work模式
     * @param routingKey 路由关键字
     * @param msg 消息体
     * @param exchange 交换机
     */
    public void sendExchangeMsg(String exchange, String routingKey, String msg) {
        template.convertAndSend(exchange, routingKey, msg);
    }

    /**
     * @param map 消息headers属性
     * @param exchange 交换机
     * @param msg 消息体
     */
    public void sendHeadersMsg(String exchange, String msg, Map map) {
        template.convertAndSend(exchange, null, msg, message -> {
            message.getMessageProperties().getHeaders().putAll(map);
            return message;
        });
    }
}

 使用生产者:

@SpringBootTest
public class RabbitmqTest {

    @Autowired
    private Producer producer;

//    @Autowired
//    private  RabbitTemplate myRabbitTemplate;

    /*
        Direct 直接根据routingKey发送数据,
    *  简单模式 无需指定交换机, rabbitMq会通过默认的  “AMQP default”交换机,将我们的消息投递到指定的队列,它是一种Direct类型的交换机
    *  适用场景: 一个生产者  写入一个队列,然后一个消费者消费
    * */
    @Test
    public void sendDirectMsg() {
        producer.sendDirectMsg("cord", "hello word");
    }


    /*
    * topic(模糊匹配的routing key)
    * topic交换机可以实现更加复杂的消息发送规则,即发送消息时,指定更为复杂的routing key,类似于模糊匹配,routing key可以多变。
    * 只要发送消息时指定的routing key符合交换机与队列绑定的binding key的匹配规则,则消息可以被正确投递到指定队列。
    * */
    @Test
    public void sendtopicMsg() {
        producer.sendExchangeMsg("topic-exchange","org.cord.test", "hello world topic");
    }

    //Fanout
    @Test
    public void sendFanoutMsg() {
        producer.sendExchangeMsg("fanout-exchange", "abcdefg", String.valueOf(System.currentTimeMillis()));
    }

    //Headers
    @Test
    public void sendHeadersMsg() {
        Map map = new HashMap<>();
        map.put("First","A");
        producer.sendHeadersMsg("headers-exchange", "hello word", map);
    }

扩展:上面代码是 rabbitMq中使用的是 默认的虚拟主机(或者项目中只用一个特定的虚拟主机,可以指定:spring.rabbitmq.virtual-host=my)。 当实际开发中使用到了 多个指定的虚拟主机(命名空间)后,就需要自定义。

 配置文件:

my.spring.rabbitmq.host=192.168.11.111
my.spring.rabbitmq.port=5678
my.spring.rabbitmq.username=guest
my.spring.rabbitmq.password=guest
my.spring.rabbitmq.virtual-host=jarvis

配置类为:

@Slf4j
@Configuration
public class RabbitMQConfig {


    @Bean("myConnectionFactory")
    @Primary
    public CachingConnectionFactory defaultConnectionFactory(@Value("${my.spring.rabbitmq.host}") String host,
                                                             @Value("${my.spring.rabbitmq.port}") int port,
                                                             @Value("${my.spring.rabbitmq.username}") String username,
                                                             @Value("${my.spring.rabbitmq.password}") String password,
                                                             @Value("${my.spring.rabbitmq.virtual-host}") String virtualHost) {
        // 连接工厂
        CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
        cachingConnectionFactory.setHost(host);
        cachingConnectionFactory.setPort(port);
        cachingConnectionFactory.setUsername(username);
        cachingConnectionFactory.setPassword(password);
        cachingConnectionFactory.setVirtualHost(virtualHost);
        cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        cachingConnectionFactory.setPublisherReturns(true);
        return cachingConnectionFactory;
    }

    @Bean("myContainerFactory")
    @Primary
    public SimpleRabbitListenerContainerFactory defaultContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                                        @Qualifier("myConnectionFactory") ConnectionFactory connectionFactory) {
        // 监听容器
        SimpleRabbitListenerContainerFactory listenerContainerFactory  = new SimpleRabbitListenerContainerFactory();
        listenerContainerFactory .setAcknowledgeMode(AcknowledgeMode.MANUAL);

        // 将连接工厂放入兼容容器中,监听对应连接工厂的队列
        configurer.configure(listenerContainerFactory , connectionFactory);
        return listenerContainerFactory ;
    }

    @Bean(name = "myRabbitTemplate")
    @Primary
    public RabbitTemplate defaultRabbitTemplate(@Qualifier("myConnectionFactory") ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        // Mandatory为true时,消息通过交换器无法匹配到队列会返回给生产者,为false时匹配不到会直接被丢弃
        rabbitTemplate.setMandatory(true);
        // 消费者确认收到消息后,手动ack回执
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *  ConfirmCallback机制只确认消息是否到达exchange(交换器),不保证消息可以路由到正确的queue;
             *  需要设置:publisher-confirm-type: CORRELATED;
             *  springboot版本较低 参数设置改成:publisher-confirms: true
             *
             *  以实现方法confirm中ack属性为标准,true到达
             *  config : 需要开启rabbitmq得ack publisher-confirm-type
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("ConfirmCallback  确认结果 (true代表发送成功) : {}  消息唯一标识 : {} 失败原因 :{}",ack,correlationData,cause);
            }
        });


        return rabbitTemplate;
    }

//多个 就多次复制上面的 除了 @Value("${my.spring.rabbitmq.virtual-host}") String virtualHost加载的参数不一样
}

生产者使用指定的 AmqpTemplate 进行注入:用

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第4张图片代替原来的:

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第5张图片

 消费者使用 指定的 containerFactory。用:

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第6张图片代替

 面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第7张图片

 中心思想:有几个虚拟主机,就自己在配置文件中写几个参数,并在配置类中 一一构建其对应

CachingConnectionFactory、
SimpleRabbitListenerContainerFactory、
RabbitTemplate 

学习文章:

超详细的RabbitMQ入门,看这篇就够了!-阿里云开发者社区 (aliyun.com)

为什么rabbitMq有消费者在监听队列,队列也有消息,但不消费?

场景:队列阻塞很多消息,但队列也显示有消费者存在,但消费者就是不消费,部分消息处于:

unacked.如:

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第8张图片 

 原因:消息的内容格式不正确:

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第9张图片面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第10张图片

 

接收的代码能够默认解析:应该是 content_type:application/json,假如第一个消息是 texp/plain 即使后面的消息格式都是 application/json,那也都被堵着了。

解决办法:①修改消息的 content_type为application/json

                ②修改接收消息的监听:

 

RabbitMQ的使用: 

涉及到的概念:

交换机:数据的入口选择,交换机绑定路由键

路由键:正则表达式匹配发送消息方的名字,一个路由键绑定一个特定的消息队列。

如:

消息队列:我们的数据存储的地方。

通过以上:总结:exchange 是数据大入口的选择,通过routingKey找到 绑定的具体的消息队列。

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第11张图片

此刻执行该函数,则可以在  rabbitMq中心查看发送的消息.下图说错了:是消息队列绑定的路由key作为正则表达式去匹配 生产者的路由key,  目的 :让 生产者找到要推送到的消息队列

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第12张图片

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第13张图片

查看发送上来的数据!

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第14张图片

注:如果想要查看推送到rabbitMq的数据, 最好找 先发到没有消费者的队列,因为如果有消费者,你刚发上来就被消费掉了,找不到!

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第15张图片

任务调度

芋道 Spring Boot 定时任务入门 | 芋道源码 —— 纯源码解析博客 (iocoder.cn)

特性 quartz elastic-job-lite xxl-job LTS
依赖 MySQL、jdk jdk、zookeeper mysql、jdk jdk、zookeeper、maven
高可用 多节点部署,通过竞争数据库锁来保证只有一个节点执行任务 通过zookeeper的注册与发现,可以动态的添加服务器 基于竞争数据库锁保证只有一个节点执行任务,支持水平扩容。可以手动增加定时任务,启动和暂停任务,有监控 集群部署,可以动态的添加服务器。可以手动增加定时任务,启动和暂停任务。有监控
任务分片 ×
管理界面 ×
难易程度 简单 简单 简单 略复杂
高级功能 - 弹性扩容,多种作业模式,失效转移,运行状态收集,多线程处理数据,幂等性,容错处理,spring命名空间支持 弹性扩容,分片广播,故障转移,Rolling实时日志,GLUE(支持在线编辑代码,免发布),任务进度监控,任务依赖,数据加密,邮件报警,运行报表,国际化 支持spring,spring boot,业务日志记录器,SPI扩展支持,故障转移,节点监控,多样化任务执行结果支持,FailStore容错,动态扩容。
版本更新 半年没更新 2年没更新 最近有更新 1年没更新

Spring Boot 定时任务的技术选型对比 - SegmentFault 思否

对比结果:XXL-job框架不错:学习文章:

实践:

步骤1:

package com.XXX.product.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * xxl-job config
 *
 * @author xuxueli 2017-04-28
 */
@Configuration
public class XxlJobConfig {
    private final   Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;
    @Value("${xxl.job.accessToken:}")
    private String accessToken;
    @Value("${xxl.job.executor.appname}")
    private String appname;
    @Value("${xxl.job.executor.address:}")
    private String address;
    @Value("${xxl.job.executor.ip:}")
    private String ip;
    @Value("${xxl.job.executor.port}")
    private int port;
    @Value("${xxl.job.executor.logpath}")
    private String logPath;
    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    /**
     *  执行器
     *
     * @return XxlJobSpringExecutor
     */
    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        logger.info(">>>>>>>>>>> xxl-job adminAddresses=" + adminAddresses + " appname=" + appname + " port=" + port);
        return xxlJobSpringExecutor;
    }

}

步骤2:配置(其中有几个字段配置是没有的,但有不影响。。)

xxl.job.admin.addresses=https://xxl-job.dev.interfocus11.tech
xxl.job.executor.appname=XXX-product-service
xxl.job.executor.port=10009
xxl.job.executor.logpath=/var/log/java/basic-product-service/xxljob
xxl.job.executor.logretentiondays=30

步骤3:使用:在调用的函数头上加 @XxlJob()注解

面试之 微服务架构 springCloud、消息队列、任务调度(xxl-job)_第16张图片

 步骤4、在调度中心配置

XXL-JOB分布式调度框架全面详解,一篇就够啦! - 掘金 (juejin.cn)

XXL-JOB的使用(详细教程)_fueen的博客-CSDN博客_xxl-job

kafka和RocketMq的区别 

1、数据可靠性 kafka使用异步刷盘方式,异步Replication RocketMQ支持异步刷盘,同步刷盘,同步Replication,异步Replication

2、严格的消息顺序 Kafka支持消息顺序,但是一台Broker宕机后,就会产生消息乱序 RocketMQ支持严格的消息顺序,在顺序消息场景下,一台Broker宕机后,发送消息会失败,但是不会乱序

3、消费失败重试机制 Kafka消费失败不支持重试 RocketMQ消费失败支持定时重试,每次重试间隔时间顺延

4、定时消息 Kafka不支持定时消息 RocketMQ支持定时消息

5、分布式事务消息 Kafka不支持分布式事务消息 阿里云ONS支持分布式定时消息,未来开源版本的RocketMQ也有计划支持分布式事务消息

6、消息查询机制 Kafka不支持消息查询 RocketMQ支持根据Message Id查询消息,也支持根据消息内容查询消息(发送消息时指定一个Message Key,任意字符串,例如指定为订单Id)

7、消息回溯 Kafka理论上可以按照Offset来回溯消息 RocketMQ支持按照时间来回溯消息,精度毫秒,例如从一天之前的某时某分某秒开始重新消费消息 

你可能感兴趣的:(Naocs,运维,面试,java,架构)