十三、Spring cloud消息驱动整合(Kafka/rabbitmq)

  本章所涉及到的项目是复用十二、Spring cloud服务网关(Zuul)中的应用:
  服务依赖关系:

  • eureka-server
    • user-service-provider
    • config-server
      • user-service-client
      • zuul-proxy

备注:还有个 user-api 应用是无法启动的,只是被依赖!

一、整合 Kafka

这里我简单的谈一下 Kafka 在这里到底有什么作用?
  传统的 客户端 调用 服务端 资源的方式是:客户端 发送一个 HTTP 请求 给 服务端(通过 RestTemplate),然后 服务端 调用相应的资源并返回。这是最传统的方式,且不是唯一的方式。在Spring Cloud 中就引入了 Netflix Feign 来代替这种传统的 HTTP 方式,同样,除了 Feign 以外,还有其他方式,比如 消息队列 就是一种方式。而且 消息队列(诸如 Kafka )还有个独特特点,它是异步调用。
  消息队列(诸如 Kafka ) 实现 客户端 调用 服务端 资源的大概思路:客户端 将需要调用的资源 发送给 消息队列 特定的 topic,同时 服务端 一直在监听这个 topic,一旦这个 topic 中有新的 消息,立马调用特定的业务逻辑,并将处理结果再发送向另一个 topic1客户端 也会一直监听这个 Topic1,一旦有新的消息,说明 服务端 返回了相应的资源。

注意:经测试,现阶段只有使用 Kafka 发送消息,使用 Kafka-binder 接收消息是正常的;使用 Kafka-binder 发送消息,Kafka-binder 接收消息会报错!!!!!

(一 )改造 user-service-client 消息发送源(Source)

1、改造 user-api 应用中的的 User ,实现序列化接口

public class User implements Serializable {

    private static final long serialVersionUID = 3113741777436231359L;

	......
}

2、添加 kafka 依赖

		<!-- 整合 kafka -->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

3、实现 Kafka 序列化器:Java序列化协议

/**
 * Java 序列化协议
 * @author 咸鱼
 * @date 2018/11/24 14:59
 */
public class ObjectSerializer implements Serializer<Object> {
    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {

    }

    @Override
    public byte[] serialize(String topic, Object data) {
        System.out.println("topic : " + topic + " , object : " + data);
        byte[] dataArray = null;
        ByteArrayOutputStream outputStream = null;
        try {
            outputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(data);
            dataArray = outputStream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return dataArray;
    }

    @Override
    public void close() {

    }
}

4、配置 Kafka 生产者
application.properties

#配置 Kafka 生产者
spring.kafka.bootstrap-servers=192.168.10.130:9092,192.168.10.130:9093,192.168.10.130:9094
spring.kafka.consumer.group-id=my-group-1
#待定
#spring.kafka.consumer.client-id=my-client-1

#生产者 键、值的序列化
spring.kafka.producer.value-serializer=org.pc.serializer.ObjectSerializer
spring.kafka.producer.key-serializer=org.pc.serializer.ObjectSerializer

5、利用 KafkaTemplate 实现消息发送

@RestController
public class UserServiceClientController {
    private KafkaTemplate<String, Object> kafkaTemplate;

    @Autowired
    public UserServiceClientController(KafkaTemplate<String, Object> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    @PostMapping("/user/save/message")
    public boolean saveUserByMessage(@RequestBody User user){
        ListenableFuture<SendResult<String, Object>> future =
                kafkaTemplate.send("cluster-topic", "user", user);
        //是否执行完毕
        return future.isDone();
    }
  }

(二)改造 user-service-provider 消息接收器(Sink)

1、引入 spring-cloud-stream-binder-kafka

		<!-- 依赖 spring-cloud-stream-binder-kafka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-kafka</artifactId>
        </dependency>

2、用户消息 Stream 接口定义

/**
 * {@link User} 消息 Stream 接口定义(PS:用于管道通信)
 * @author 咸鱼
 * @date 2018/11/24 15:56
 */
public interface UserMessage {

    String INPUT = "input";

    @Input(INPUT)
    SubscribableChannel input();
}

3、激活用户消息 Stream 接口
@EnableBinding

/**
 * 注解 @EnableHystrix:激活 Hystrix
 * 注解 @EnableDiscoveryClient:激活 Eureka Client
 * 注解 @EnableBinding(UserMessage.class):激活 Stream Binding 到 UserMessage
 * @author 咸鱼
 * @date 2018/11/12 18:44
 */
@EnableBinding(UserMessage.class)
@EnableDiscoveryClient
@EnableHystrix
@SpringBootApplication
public class UserServiceProviderApplication {
    .....
}

4、配置 Kafka 以及 Stream Destination

#Spring Cloud Stream Binding 配置
## input 是 org.pc.stream.UserMessage#input() 方法上@Input 注解的 value 属性(PS:默认 input() 方法名)
## destination 指定配置 Kafka Topic
spring.cloud.stream.bindings.input.destination=cluster-topic

#配置 Kafka 消费者
spring.kafka.bootstrap-servers=192.168.10.130:9092,192.168.10.130:9093,192.168.10.130:9094
spring.kafka.consumer.group-id=my-group-1
spring.kafka.consumer.client-id=user-service-provider

5、添加消息监听器

  • 方式一:SubscribableChannel 实现
  • 方式二:@ServiceActivator

(1)方式一:SubscribableChannel 实现

/**
 * 用户 消息服务
 * @author 咸鱼
 * @date 2018/11/24 16:27
 */
@Service
public class UserMessageService {

    @Autowired
    private UserMessage userMessage;
    @Autowired
    @Qualifier("inMemoryUserServiceImpl")
    private UserService userService;

    /**
     * 添加消息监听器 方式一:SubscribableChannel 实现
     */
    @PostConstruct
    public void init(){
        SubscribableChannel channel = userMessage.input();

        //在这里会接收到消息队列中的字节流,我们需要反序列化成对象
        channel.subscribe(message -> {
            //message body 是字节流 byte[]
            byte[] body = (byte[]) message.getPayload();
            saveUser(body);
        });
    }

    /**
     * 将接收到的 消息队列 中的字节流 反序列化成对象
     */
    private void saveUser(byte[] data) {
        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(data);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            //反序列化成 User 对象
            User user = (User) objectInputStream.readObject();
            userService.saveUser(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(2)方式二:@ServiceActivator

/**
 * 用户 消息服务
 * @author 咸鱼
 * @date 2018/11/24 16:27
 */
@Service
public class UserMessageService {
    @Autowired
    @Qualifier("inMemoryUserServiceImpl")
    private UserService userService;

    /**
     * 添加消息监听器 方式二:@ServiceActivator 实现
     */
    @ServiceActivator(inputChannel = UserMessage.INPUT)
    public void listen(byte[] data) {
        saveUser(data);
    }
    
    /**
     * 将接收到的 消息队列 中的字节流 反序列化成对象
     */
    private void saveUser(byte[] data) {
        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(data);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            //反序列化成 User 对象
            User user = (User) objectInputStream.readObject();
            userService.saveUser(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(3)方式三:@ServiceActivator

/**
 * 用户 消息服务
 * @author 咸鱼
 * @date 2018/11/24 16:27
 */
@Service
public class UserMessageService {
    @Autowired
    @Qualifier("inMemoryUserServiceImpl")
    private UserService userService;

    /**
     * 添加消息监听器 方式三:@StreamListener 实现
     */
    @StreamListener(UserMessage.INPUT)
    public void onMessage(byte[] data){
        saveUser(data);
    }
    
    /**
     * 将接收到的 消息队列 中的字节流 反序列化成对象
     */
    private void saveUser(byte[] data) {
        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(data);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            //反序列化成 User 对象
            User user = (User) objectInputStream.readObject();
            userService.saveUser(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

二、整合 RabbitMQ

改造 user-service-client 消息发送源( Stream Binder : Rabbit MQ)

(一)增加 spring-cloud-stream-binder-rabbitmq 依赖

		<!-- 整合 Spring Cloud Stream Binder Rabbit MQ -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>

(二)添加用户消息接口

/**
 * 用户消息(输出)
 *
 * @author <a href="mailto:[email protected]">Mercy</a>
 * @since 0.0.1
 */
public interface UserMessage {

    @Output("user-message-out")
    MessageChannel output();

}

(三)配置发送源管道

#Spring Cloud Stream Binding 配置
## input 是 org.pc.stream.UserMessage#output() 方法上@Output注解的 value 属性(PS:默认 Output() 方法名)
## destination 指定配置 RabbitMQ Topic
spring.cloud.stream.bindings.user-message-out.destination=cluster-topic

(四)激活用户消息接口

/**
 * 注解 @RibbonClient:激活 Ribbon
 * 注解 @EnableCircuitBreaker:激活 服务短路
 * 注解 @EnableFeignClients:激活 Feign 客户端
 * 注解 @EnableDiscoveryClient:激活 Eureka 客户端
 * 注解:@EnableBinding(UserMessage.class):激活激活用户消息接口 UserMessage
 */
@EnableBinding(UserMessage.class)
@EnableDiscoveryClient
@EnableFeignClients(clients = UserService.class)//clients 属性:申明 UserService 接口作为 Feign Client 调用
@EnableCircuitBreaker
@SpringBootApplication
@RibbonClient("user-service-provider")//指定目标应用名称
public class UserServiceClientApplication {
....
}

(五)发送消息

@RestController
public class UserServiceClientController {
    @Autowired
    private UserMessage userMessage;
    /**
     * Jackson 中的类,已自动装配
     */
    @Autowired
    private ObjectMapper objectMapper;

    @PostMapping("/user/save/message/kafka")
    public boolean saveUserByKafka(@RequestBody User user) throws JsonProcessingException {
        MessageChannel messageChannel = userMessage.output();

        // User 序列化成 JSON(使用 Json序列化 代替 Java序列化)
        String payload = objectMapper.writeValueAsString(user);
        GenericMessage<String> message = new GenericMessage<>(payload);

        //发送消息
        return messageChannel.send(message);
    }
}

注意:若采取上述 Json 方式序列化 对象 成String 类型,那么在 服务端,接收到的消息也应该是 String 类型。而我们前面接收消息的参数全部为 byte[] 数组,所以相应的,我们需要需要改造消息接收端,使其能够识别消息。核心思想:前后端序列化方式应该保持一致!!!

三、Spring Cloud Stream 整合

(一)改造 user-service-client 消息发送器(Source)

1、增加 spring-cloud-stream-binder-rabbitmq 依赖

		<!-- 依赖 spring-cloud-stream-binder-kafka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-kafka</artifactId>
        </dependency>

(二)添加用户消息接口

/**
 * 用户消息(输出)
 *
 * @author <a href="mailto:[email protected]">Mercy</a>
 * @since 0.0.1
 */
public interface UserMessage {

    @Output("user-message-out")
    MessageChannel output();

}

(三)配置发送源管道

#Spring Cloud Stream Binding 配置
## input 是 org.pc.stream.UserMessage#output() 方法上@Output注解的 value 属性(PS:默认 Output() 方法名)
## destination 指定配置 消息队列 Topic
spring.cloud.stream.bindings.user-message-out.destination=cluster-topic

(四)激活用户消息接口

/**
 * 注解 @RibbonClient:激活 Ribbon
 * 注解 @EnableCircuitBreaker:激活 服务短路
 * 注解 @EnableFeignClients:激活 Feign 客户端
 * 注解 @EnableDiscoveryClient:激活 Eureka 客户端
 * 注解:@EnableBinding(UserMessage.class):激活激活用户消息接口 UserMessage
 */
@EnableBinding(UserMessage.class)
@EnableDiscoveryClient
@EnableFeignClients(clients = UserService.class)//clients 属性:申明 UserService 接口作为 Feign Client 调用
@EnableCircuitBreaker
@SpringBootApplication
@RibbonClient("user-service-provider")//指定目标应用名称
public class UserServiceClientApplication {
....
}

(五)发送消息

@RestController
public class UserServiceClientController {
    @Autowired
    private UserMessage userMessage;
    /**
     * Jackson 中的类,已自动装配
     */
    @Autowired
    private ObjectMapper objectMapper;

    @PostMapping("/user/save/message/kafka")
    public boolean saveUserByKafka(@RequestBody User user) throws JsonProcessingException {
        MessageChannel messageChannel = userMessage.output();

        // User 序列化成 JSON(使用 Json序列化 代替 Java序列化)
        String payload = objectMapper.writeValueAsString(user);
        GenericMessage<String> message = new GenericMessage<>(payload);

        //发送消息
        return messageChannel.send(message);
    }
}

(二)改造 user-service-provider 消息接收器(Sink)

1、引入 spring-cloud-stream-binder-kafka

		<!-- 依赖 spring-cloud-stream-binder-kafka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-kafka</artifactId>
        </dependency>

2、用户消息 Stream 接口定义

/**
 * {@link User} 消息 Stream 接口定义(PS:用于管道通信)
 * @author 咸鱼
 * @date 2018/11/24 15:56
 */
public interface UserMessage {

    String INPUT = "input";

    @Input(INPUT)
    SubscribableChannel input();
}

3、激活用户消息 Stream 接口
@EnableBinding

/**
 * 注解 @EnableHystrix:激活 Hystrix
 * 注解 @EnableDiscoveryClient:激活 Eureka Client
 * 注解 @EnableBinding(UserMessage.class):激活 Stream Binding 到 UserMessage
 * @author 咸鱼
 * @date 2018/11/12 18:44
 */
@EnableBinding(UserMessage.class)
@EnableDiscoveryClient
@EnableHystrix
@SpringBootApplication
public class UserServiceProviderApplication {
    .....
}

4、配置 Kafka 以及 Stream Destination

#Spring Cloud Stream Binding 配置
## input 是 org.pc.stream.UserMessage#input() 方法上@Input 注解的 value 属性(PS:默认 input() 方法名)
## destination 指定配置 Kafka Topic
spring.cloud.stream.bindings.input.destination=cluster-topic

#配置 Kafka 消费者
spring.kafka.bootstrap-servers=192.168.10.130:9092,192.168.10.130:9093,192.168.10.130:9094
spring.kafka.consumer.group-id=my-group-1
spring.kafka.consumer.client-id=user-service-provider

5、添加消息监听器

  • 方式一:SubscribableChannel 实现
  • 方式二:@ServiceActivator

(1)方式一:SubscribableChannel 实现

/**
 * 用户 消息服务
 * @author 咸鱼
 * @date 2018/11/24 16:27
 */
@Service
public class UserMessageService {

    @Autowired
    private UserMessage userMessage;
    @Autowired
    @Qualifier("inMemoryUserServiceImpl")
    private UserService userService;

    /**
     * 添加消息监听器 方式一:SubscribableChannel 实现
     */
    @PostConstruct
    public void init(){
        SubscribableChannel channel = userMessage.input();

        //在这里会接收到消息队列中的字节流,我们需要反序列化成对象
        channel.subscribe(message -> {
            //message body 是字节流 byte[]
            byte[] body = (byte[]) message.getPayload();
            saveUser(body);
        });
    }

    /**
     * 将接收到的 消息队列 中的字节流 反序列化成对象
     */
    private void saveUser(byte[] data) {
        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(data);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            //反序列化成 User 对象
            User user = (User) objectInputStream.readObject();
            userService.saveUser(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(2)方式二:@ServiceActivator

/**
 * 用户 消息服务
 * @author 咸鱼
 * @date 2018/11/24 16:27
 */
@Service
public class UserMessageService {
    @Autowired
    @Qualifier("inMemoryUserServiceImpl")
    private UserService userService;

    /**
     * 添加消息监听器 方式二:@ServiceActivator 实现
     */
    @ServiceActivator(inputChannel = UserMessage.INPUT)
    public void listen(byte[] data) {
        saveUser(data);
    }
    
    /**
     * 将接收到的 消息队列 中的字节流 反序列化成对象
     */
    private void saveUser(byte[] data) {
        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(data);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            //反序列化成 User 对象
            User user = (User) objectInputStream.readObject();
            userService.saveUser(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(3)方式三:@ServiceActivator

/**
 * 用户 消息服务
 * @author 咸鱼
 * @date 2018/11/24 16:27
 */
@Service
public class UserMessageService {
    @Autowired
    @Qualifier("inMemoryUserServiceImpl")
    private UserService userService;

    /**
     * 添加消息监听器 方式三:@StreamListener 实现
     */
    @StreamListener(UserMessage.INPUT)
    public void onMessage(byte[] data){
        saveUser(data);
    }
    
    /**
     * 将接收到的 消息队列 中的字节流 反序列化成对象
     */
    private void saveUser(byte[] data) {
        ByteArrayInputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(data);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            //反序列化成 User 对象
            User user = (User) objectInputStream.readObject();
            userService.saveUser(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/panchang199266/article/details/84403831