本章所涉及到的项目是复用十二、Spring cloud服务网关(Zuul)中的应用:
服务依赖关系:
备注:还有个 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 依赖
<dependency>
<groupId>org.springframework.kafkagroupId>
<artifactId>spring-kafkaartifactId>
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
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-stream-binder-kafkaartifactId>
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、添加消息监听器
(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 依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-stream-binder-rabbitartifactId>
dependency>
(二)添加用户消息接口
/**
* 用户消息(输出)
*
* @author Mercy
* @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 依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-stream-binder-kafkaartifactId>
dependency>
(二)添加用户消息接口
/**
* 用户消息(输出)
*
* @author Mercy
* @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
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-stream-binder-kafkaartifactId>
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、添加消息监听器
(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);
}
}
}