1. 消息传递,EDA和微服务的案例
使用同步请求 - 响应方法来通信状态更改
这种方法存在三个问题:
- 服务之间的紧密联系
- 服务之间的脆弱性
- 不愿意增加新的消费者以改变Organization service
使用消息传递在服务之间传递状态更改
这种方法有四个好处:
- 松耦合
- 耐久力
- 可扩展性
- 灵活性
消息传递架构的缺点
基于消息的架构可能很复杂,需要开发团队密切关注几个关键事项,包括:
- 消息处理语义
- 消息可见性
- 消息编排
2. 介绍Spring Cloud Stream
Spring Cloud Stream架构
如何使用Spring Cloud Stream来促进此消息传递
通过在Spring Cloud中发布和使用消息,发布和使用消息涉及四个组件:Source、Channel、Binder和Sink。
3. 编写简单的消息生产者和消费者
在organization service中编写消息生成器
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
启动类添加注释,@EnableBinding注释告诉Spring Cloud Stream希望将服务绑定到消息代理
@SpringBootApplication
@EnableBinding(Source.class)
public class OrganizationserviceApplication {
@Primary
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
List interceptors = template.getInterceptors();
if (interceptors == null) {
template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
} else {
interceptors.add(new UserContextInterceptor());
template.setInterceptors(interceptors);
}
return template;
}
public static void main(String[] args) {
SpringApplication.run(OrganizationserviceApplication.class, args);
}
}
将消息发布到消息代理
@Component
@AllArgsConstructor
public class SimpleSourceBean {
private Source source;
private static final Logger logger = LoggerFactory.getLogger(SimpleSourceBean.class);
public void publishOrgChange(String action, Long orgId) {
logger.debug("Sending RabbitMQ message {} for Organization Id: {}", action, orgId);
OrganizationChangeModel changeModel = new OrganizationChangeModel(OrganizationChangeModel.class.getTypeName(),
action, orgId, UserContext.getCorrelationId());
source.output().send(MessageBuilder.withPayload(changeModel).build());
}
}
用于发布消息的Spring Cloud Stream配置
spring:
cloud:
stream:
bindings:
output:
destination: orgChangeTopic
content-type: application/json
在organization service中发布消息
@Service
@AllArgsConstructor
public class OrganizationServiceImpl implements OrganizationService{
private OrganizationRepository organizationRepository;
private SimpleSourceBean simpleSourceBean;
@Override
@HystrixCommand
public void saveOrganizations(Organization organization) {
organizationRepository.save(organization);
simpleSourceBean.publishOrgChange("SAVE", organization.getId());
}
}
在licensing service中编写消息使用者
和organization service一样添加相同的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
使用Spring Cloud Stream消费消息
@EnableBinding(Sink.class)
@AllArgsConstructor
public class OrganizationChangeHandler {
private static final Logger logger = LoggerFactory.getLogger(OrganizationChangeHandler.class);
@StreamListener(Sink.INPUT)
@CacheEvict(value = "organizations", key = "#orgChange.organizationId", condition = "#orgChange.action == 'UPDATE' or #orgChange.action == 'DELETE'")
public void loggerSink(OrganizationChangeModel orgChange) {
logger.debug("Received a message of type " + orgChange.getType());
switch(orgChange.getAction()){
case "GET":
logger.debug("Received a GET event from the organization service for organization id {}", orgChange.getOrganizationId());
break;
case "SAVE":
logger.debug("Received a SAVE event from the organization service for organization id {}", orgChange.getOrganizationId());
break;
case "UPDATE":
logger.debug("Received a UPDATE event from the organization service for organization id {}", orgChange.getOrganizationId());
break;
case "DELETE":
logger.debug("Received a DELETE event from the organization service for organization id {}", orgChange.getOrganizationId());
break;
default:
logger.error("Received an UNKNOWN event from the organization service of type {}", orgChange.getType());
break;
}
}
}
将licensing service映射到rabbitmq中的消息主题
spring:
cloud:
stream:
bindings:
input:
destination: orgChangeTopic
content-type: application/json
group: licensingGroup
使用者组保证消息仅由一组服务实例处理一次
4. Spring Cloud Stream用例:分布式缓存
使用Redis缓存查找
添加Spring Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
OrganizationServiceClient中定义使用Redis缓存的方法
@FeignClient("organizationservice")
public interface OrganizationServiceClient {
@Cacheable(value = "organizations", cacheManager = "orgProtoCacheManager")
@RequestMapping(method = RequestMethod.GET, value = "/v1/organizations/{organizationId}", consumes = "application/x-protobuf", produces = "application/x-protobuf")
OrganizationProto.Organization getOrganizationProtobuf(@PathVariable("organizationId") Long organizationId);
}