rabbitmq知识汇总

rabbitmq知识汇总

一、什么是消息中间件

1. 基于消息中间件的分布式系统的架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v81ZRat2-1624005504828)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210610153533157.png)]

2. 消息中间件应用的场景

  • 跨系统数据传递
  • 高并发的流量削峰
  • 数据的分发和异步处理
  • 大数据的分析与传递
  • 分布式事务

3. RabbitMQ 为什么需要信道?为什么不是TCP直接通信?

  • TCP的创建和销毁,开销大,创建需要三次握手,销毁需要四次分手
  • 如果不使用信道,那么引用程序就会使用TCP的方式连接到rabbitmq,高峰时每秒成千上万条连接会造成资源的巨大浪费(一条tcp消耗资源,成千上万的tcp会非常消耗资源),而且操作系统每秒处理TCP连接数量也是有限的,必定会造成性能瓶颈
  • 信道的原理是一条线程一条信道,多条线程多条信道共同使用一条TCP连接。一条TCP连接可以容纳无限的信道,及时每秒造成成千上万的请求也不会造成性能瓶颈

4. 消息持久化,常见的持久化方式

ActiveMqRabbitmqkafkaRockmq
文件存储支持支持支持支持
数据库支持///

5.消息分发策略的机制和对比

ActiveMqRabbitmqkafkaRockmq
发布订阅支持支持支持支持
轮询分发支持支持支持/
公平分发/支持支持/
重发支持支持/支持
消息拉取/支持支持支持

6.消息中间件的结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90wKXvzZ-1624005504831)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210617090341769.png)]

核心概念:

  • Server: 又称Broke,接受客户端的连接,实现AMQP实体服务,安装rabbitmq-server
  • Connection: 连接,应用程序与Broke的网络连接TCP/IP 三次握手和四次挥手
  • Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务
  • Message: 消息,服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级、延迟等高等特性,Body则就是消息体的内容
  • Virtual Host:虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟机可以有若干个Exchange和Queue,同一个虚拟机主机里面不能有相同名字和Exchange
  • Exchange:交换机,接收消息,根据路由键发送消息到绑定的队列(不具备消息存储的能力)
  • Binding:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key
  • routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息
  • Queue:队列,也成为Message Queue。消息队列,保存消息并将它们转发给消费者。

rabbitmq的运行流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLfQsC2m-1624005504832)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210617142927721.png)]

二、安装过程

1.安装erlang环境

  1. 查看erlang与rabbitmq的版本对应关系:

    https://www.rabbitmq.com/which-erlang.html

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-um99vqEL-1624005504834)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611091932922.png)]

    2.去erlang官网下载对应版本rpm文件,并上传至linux服务器在安装(推荐),在线安装比较慢,一般需要fanqiang下载:

    https://www.erlang-solutions.com/downloads/#

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HQ8IDsCo-1624005504835)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611092130951.png)]

    使用 rpm -Uvh esl-erlang_24.0-1_centos_7_amd64.rpm 会报如下错误:
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GEs1ziim-1624005504836)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611092300062.png)]

    需要先执行下列命令即可:

    sudo yum install epel-release
    sudo yum install unixODBC unixODBC-devel wxBase wxGTK SDL wxGTK-gl
    或者执行:
    rpm -Uvh esl-erlang_23.2-1_centos_7_amd64.rpm  --force --nodeps
    

    使用 erl -v 检查是否安装成功,如下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LcztHNQ2-1624005504837)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611092749327.png)]

  2. 安装socat,如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6zpmKIT-1624005504837)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611092932720.png)]

    至此,erlang环境安装完毕!

    2.安装rabbitmq

    1. 执行rpm -Uvh rabbitmq-server-3.8.16-1.el7.noarch.rpm

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h2JOSa9y-1624005504838)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611093602214.png)]

    2. 执行yum install rabbitmq-server -y

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hLwubiNA-1624005504839)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611093622407.png)]

    3. 执行systemctl start rabbitmq-server启动

    4. 执行systemctl status rabbitmq-server查看,如下

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gRBIS748-1624005504839)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210611094013631.png)]

    5. 执行systemctl enable rabbitmq-server开机自启动

    6. 执行systemctl stop rabbitmq-server开机自启动关闭

      3. 执行命令安装rabbitmqweb

      rabbitmq-plugins enable rabbitmq_management
      

      相关配置命令如下:

      1:查看防火状态 systemctl status firewalld service  iptables status
      2:暂时关闭防火墙 systemctl stop firewalld service  iptables stop
      3:永久关闭防火墙 systemctl disable firewalld chkconfig iptables off
      4:重启防火墙 systemctl enable firewalld service iptables restart  
      5:永久关闭后重启 chkconfig iptables on
      

      4.授权账号和密码

      新增用户:

      rabbitmqctl add_user admin admin
      

      设置用户分配操作权限:

      rabbitmqctl set_user_tags admin administrator
      

      用户级别如下:

      • administrator 可以登录控制台、查看所有信息、可以对rabbitmq进行管理
      • monitoring 监控者登录控制台、查看所有信息
      • policymaker 策略制定者 登录控制台指定策略
      • managment 普通管理员 登录控制台

      为用户赋权:

      sudo rabbitmqctl  set_permissions -p / admintest  '.*' '.*' '.*'
      

三、spring boot集成

1.普通maven下的简单模式(simple),举例

<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.12.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
package com.study.dyq.rabbitmq_01.simple;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * maven 简单模式simple的demo
 * 注释:所有的中间件技术都是基于tcp/ip协议基础之上的,rabbitmq遵循的是amqp协议,便于在消息头传递一些附加的的信息
 */
public class Productor {

    public static void main(String[] args) {
        // 1.创建连接工程
        ConnectionFactory cf = new ConnectionFactory();
        cf.setHost("10.200.4.126");
        cf.setPort(5672);
        cf.setUsername("admintest");
        cf.setPassword("admintest");
        cf.setVirtualHost("/");
        Connection connection = null;// 声明连接
        Channel channel = null;// 声明通道
        try {
            // 2.创建连接connection
            connection = cf.newConnection("生产者");
            // 3.通过连接获取通道Channel
            channel = connection.createChannel();
            // 4.通过通过创建交换机,声明队列,绑定关系、路由key,发送消息和接收消息
            String queueName = "queue1";
            /**
             * @Param1 队列的名称
             * @Param2 是否持久化,true:持久化,false:不持久化
             * @Param3 排他性,是否是独占队列
             * @Param4 是否自动删除,随着最后一个消费者消费完消息后队列是否要自动删除
             * @Param5 携带附属参数
             */
            channel.queueDeclare(queueName, true, false, false, null);
            // 5.准备消息内容,可以为json格式
            JSONObject ob = new JSONObject();
            ob.put("消息内容", "我是第一个测试的消息队列呀!");
            String msg = JSON.toJSONString(ob);
            // 6.发送消息给队列queue
            channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());

        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 7.关闭连接
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            // 8.关闭通道
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

package com.study.dyq.rabbitmq_01.simple;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Cunsumer {

    public static void main(String[] args) {
        // 1.创建连接工程
        ConnectionFactory cf = new ConnectionFactory();
        cf.setHost("10.200.4.126");
        cf.setPort(5672);
        cf.setUsername("admintest");
        cf.setPassword("admintest");
        cf.setVirtualHost("/");
        Connection connection = null;// 声明连接
        Channel channel = null;// 声明通道
        try {
            // 2.创建连接connection
            connection = cf.newConnection("生产者");
            // 3.通过连接获取通道Channel
            channel = connection.createChannel();
            // 4.通过通过创建交换机,声明队列,绑定关系、路由key,发送消息和接收消息
            String queueName = "queue1";
            channel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println("收到的消息是:"+new String(delivery.getBody(), "utf-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("收到消息失败了!");
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 7.关闭连接
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
            // 8.关闭通道
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

1.简单模式(simple)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ome12qut-1624005504840)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210618095504495.png)]

1.生产者服务:

package com.study.dyq.rabbitmq_02.configration;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author dyq
 * @description 一对一简单模式
 * @data 2021/6/18
 */
@Configuration
public class SimpleRabbitConfigration {

    @Bean
    public Queue simpleQueue(){
        return new Queue("queue_simple");
    }
}

/**
     * 简单模式
     */
    public void addSimpleOrder(String userId, String productId, int num, String queueSimple){
        // 保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("创建生产者成功,订单号为:"+orderId);
        // 通知其他服务,通过mq来完成
        rabbitTemplate.convertAndSend(queueSimple,  userId+"买了"+num+"套"+productId+",订单号:"+orderId);
    }

2.消费者服务:

package com.study.dyq.rabbitmq_03.service.simple;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author dyq
 * @description simple模拟测试
 * @data 2021/6/17
 */
@Service
@RabbitListener(queues = {"queue_simple"})
public class smsSimpleService {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms simple-----接收到了订单的信息是:"+message);
    }
}

2.工作模式(work)

主要有两种模式:

  1. 轮询模式的分发:一个消费者消费一条,按均分配。
  2. 公平分发:根据消费者能力进行分发,按劳分配。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nM9QsMjI-1624005504841)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210618094917971.png)]

3.发布/订阅模式(fanout)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okhSYFmF-1624005504841)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210617151719526.png)]

sprintboot下的整合:

1.生产者服务:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
package com.study.dyq.rabbitmq_02.configration;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author dyq
 * @description rebbitmq消息中间件配置类
 * @data 2021/6/17
 */
@Configuration
public class RabbitConfigration {

    /**
     * 1.声明注册lanout模式的交换机
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanout_order_exchange", true, false);
    }

    /**
     * 2.声明队列
     */
    @Bean
    public Queue smsQueue(){
        // @param1 队列名称 @param2 是否持久化
        return new Queue("smsQueue", true);
    }

    @Bean
    public Queue duanxinQueue(){
        // @param1 队列名称 @param2 是否持久化
        return new Queue("duanxinQueue", true);
    }

    @Bean
    public Queue emailQueue(){
        // @param1 队列名称 @param2 是否持久化
        return new Queue("emailQueue", true);
    }

    // 3.设置绑定关系
    @Bean
    public Binding smsBinding(){
        return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
    }

    @Bean
    public Binding duanxinBinding(){
        return BindingBuilder.bind(duanxinQueue()).to(fanoutExchange());
    }

    @Bean
    public Binding emailBinding(){
        return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
    }
}

package com.study.dyq.rabbitmq_02.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * @author dyq
 * @description 模拟订单类
 * @data 2021/6/17
 */
@Service
public class OrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     *
     */
    public void addOrder(String userId, String productId, int num){
        // 保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("创建生产者成功,订单号为:"+orderId);
        // 通知其他服务,通过mq来完成
        String exchangeName = "fanout_order_exchange";
        rabbitTemplate.convertAndSend(exchangeName, "", userId+"买了"+num+"套"+productId+",订单号:"+orderId);
    }
}
# 服务端口
server:
  port: 8091

# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admintest
    password: admintest
    virtual-host: /
    host: 10.200.4.126
    port: 5672

2.消费者服务

 <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
package com.study.dyq.rabbitmq_03.service;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author dyq
 * @description fanout模拟测试
 * @data 2021/6/17
 */
@Service
@RabbitListener(queues = {"duanxinQueue"})
public class duanxinFanoutService {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("duanxin fanout-----接收到了订单的信息是:"+message);
    }
}

package com.study.dyq.rabbitmq_03.service;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author dyq
 * @description fanout模拟测试
 * @data 2021/6/17
 */
@Service
@RabbitListener(queues = {"emailQueue"})
public class emailFanoutService {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("email fanout-----接收到了订单的信息是:"+message);
    }
}

package com.study.dyq.rabbitmq_03.service;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author dyq
 * @description fanout模拟测试
 * @data 2021/6/17
 */
@Service
@RabbitListener(queues = {"smsQueue"})
public class smsFanoutService {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms fanout-----接收到了订单的信息是:"+message);
    }
}

4.路由模式(routing)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUhVFilj-1624005504842)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210617172836192.png)]

1.生产者服务

package com.study.dyq.rabbitmq_02.configration;

import com.rabbitmq.client.AMQP;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author dyq
 * @description rabbit测试direct模式
 * @data 2021/6/17
 */
@Configuration
public class DirectRabbitConfigration {

    /**
     * 1.声明注册direct模式的交换机
     * @return
     */
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("direct_order_exchange", true, false);
    }

    /**
     * 2.声明队列
     */
    @Bean
    public Queue smsDirectQueue(){
        // @param1 队列名称 @param2 是否持久化
        return new Queue("smsDirectQueue", true);
    }

    @Bean
    public Queue duanxinDirectQueue(){
        // @param1 队列名称 @param2 是否持久化
        return new Queue("duanxinDirectQueue", true);
    }

    @Bean
    public Queue emailDirectQueue(){
        // @param1 队列名称 @param2 是否持久化
        return new Queue("emailDirectQueue", true);
    }

    // 3.设置绑定关系
    @Bean
    public Binding smsDirectBinding(){
        return BindingBuilder.bind(smsDirectQueue()).to(directExchange()).with("sms");
    }

    @Bean
    public Binding duanxinDirectBinding(){
        return BindingBuilder.bind(duanxinDirectQueue()).to(directExchange()).with("duanxin");
    }

    @Bean
    public Binding emailDirectBinding(){
        return BindingBuilder.bind(emailDirectQueue()).to(directExchange()).with("email");
    }
}

public void addDirectOrder(String userId, String productId, int num, String exchangeName){
        // 保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("创建生产者成功,订单号为:"+orderId);
        // 通知其他服务,通过mq来完成
        rabbitTemplate.convertAndSend(exchangeName, "sms", userId+"买了"+num+"套"+productId+",订单号:"+orderId);
        rabbitTemplate.convertAndSend(exchangeName, "email", userId+"买了"+num+"套"+productId+",订单号:"+orderId);
    }

2.消费者服务

package com.study.dyq.rabbitmq_03.service.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author dyq
 * @description fanout模拟测试
 * @data 2021/6/17
 */
@Service
@RabbitListener(queues = {"smsDirectQueue"})
public class SmsDirectService {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms direct-----接收到了订单的信息是:"+message);
    }
}

package com.study.dyq.rabbitmq_03.service.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author dyq
 * @description fanout模拟测试
 * @data 2021/6/17
 */
@Service
@RabbitListener(queues = {"emailDirectQueue"})
public class EmailDirectService {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("email Direct-----接收到了订单的信息是:"+message);
    }
}

package com.study.dyq.rabbitmq_03.service.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
 * @author dyq
 * @description fanout模拟测试
 * @data 2021/6/17
 */
@Service
@RabbitListener(queues = {"duanxinDirectQueue"})
public class DuanxinDirectService {

    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("duanxin Direct-----接收到了订单的信息是:"+message);
    }
}

5.主题模式(topic)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-elytWTEG-1624005504843)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210617172903060.png)]

" * " : 表示任何一个词

" # ": 表示0或1个词

四、集群搭建

五、分布式事务

六、常见问题汇总

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杜家小型男

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值