背景:

接着上篇文章来,上篇文章讲的是如何利用ApplicationContext的事件机制来达到业务解耦,而且这只能作用在单体应用中。在当下这么盛行的微服务架构中,想要再利用此方案做业务解耦是不可能的了,我们也提到,现在比较流行的解决方案是利用消息队列来完成,例如现在流行的RabbitMQ、RocketMQ、ActiveMQ,Kafka。
    当然了,我们还可以利用Redis的队列来完成,也是完全没问题的。刚好我自己的阿里云装好了一个redis,我们就直接用Redis来解决吧。Redis提供了生产/消费模式和发布/订阅模式。这里提一下,生产消费模式适合那些一对一的,因为只能一个消费者去消费:例如用户注册了只发短信提示。而我们之前的例子是一对多的,即用户注册了需要发送短信和发送邮件,所以我们会用到发布订阅模式,只要订阅了某个频道,所有订阅者都能收到这频道的消息,然后来对此进行消费。那么开始吧~
    说到微服务我们会想到Spring Cloud,可是我们现在是业务解耦,不需要服务之间直接的调用,所以我们直接只使用Spring Boot做微服务架构即可。简单分四大模块,用户模块(hyf-user)、短信模块(hyf-message)、邮件模块(hyf-mail)、公共类模块(hyf-encapsulation)。

开始:

1、项目结构如下图所示:

微服务架构-利用Redis特性进行业务解耦

2、因为使用Redis,所以我们首先得引入Redis相关依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
3、并且在application.properties写上配置:
# redis服务端口
spring.redis.port=6379
# redis的服务地址
spring.redis.host=127.0.0.1
# 如果redis设置了密码这里也要配上
spring.redis.password=xxx
# 连接超时时间(毫秒)
spring.redis.timeout=10000
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
spring.redis.database=0
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
4、发布信息:

用户Service直接调用默认的StringRedisTemplate去给指定的通道发布消息即可

/**
 * @author Howinfun
 * @desc 用户Service
 * @date 2019/5/13
 */
@Service
@Slf4j
@AllArgsConstructor
public class UserService {
 
    private StringRedisTemplate stringRedisTemplate;
 
    /**
     * 用户注册
     * @param user
     */
    public void registerUser(User user){
        log.info("用户:"+user.getName()+"注册成功");
        // 给redis的channel中发布消息
        String userInfo = JSON.toJSONString(user);
        stringRedisTemplate.convertAndSend(UserConstants.USER_REGISTER,userInfo);
    }
}
5、订阅频道:

1、订阅稍微麻烦一点,首先抽象一个消费信息的接口:

public interface AbstractReceiver {
    // 消费消息的方法
    void receiveMessage(Object message);
}

2、然后发送短信需要创建一个类去实现此接口,然后在重写方法里头实现自己的业务逻辑(发送邮件的同理):

/**
 * @author Howinfun
 * @desc
 * @date 2019/5/14
 */
@Component
public class MessageReceiver implements AbstractReceiver {
    @Autowired
    private MessageService messageService;
    @Override
    public void receiveMessage(Object message) {
        User user = JSON.parseObject((String) message, User.class);
        // 发送短信的业务逻辑
        messageService.sendMessage(user);
    }
}

3、然后需要给短信订阅弄一个配置类(发送邮件的同理):

/**
 * @author Howinfun
 * @desc
 * @date 2019/5/14
 */
@Configuration
public class RedisConfig {
 
    /**
     * redis消息监听器容器
     * 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
     * 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
     * @param messageListener
     * @return
     */
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter messageListener) {
 
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //mailListener订阅了一个叫user:register 的通道
        container.addMessageListener(messageListener, new PatternTopic(UserConstants.USER_REGISTER));
        return container;
    }
 
    /**
     * 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
     * @param receiver
     * @return
     */
    @Bean
    MessageListenerAdapter messageListener(MessageReceiver receiver) {
        //这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
        //MessageListenerAdapter提供的默认调用处理器的方法是handleMessage 可以自己到源码里面看
        // 所以如果我们定义的方法不是这个,需要在构造函数这添加上
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }
}
6、最后我们就可以启动项目来测试一下了,可以看到已经成功了:

hyf-user控制台:

2019-05-15 09:55:16.493  INFO 13672 --- [nio-8080-exec-2] com.hyf.user.service.UserService         : 用户:howinfun注册成功
2019-05-15 09:55:16.626  INFO 13672 --- [nio-8080-exec-2] io.lettuce.core.EpollProvider            : Starting without optional epoll library
2019-05-15 09:55:16.627  INFO 13672 --- [nio-8080-exec-2] io.lettuce.core.KqueueProvider           : Starting without optional kqueue library

hyf-message控制台:

2019-05-15 09:55:17.422  INFO 8088 --- [    container-2] com.hyf.message.service.MessageService   : 给用户howinfun发送短信,手机号码为:12345678900

hyf-mail控制台:

2019-05-15 09:55:17.422  INFO 15352 --- [    container-2] com.hyf.mail.service.MailService         : 给用户howinfun发送邮件,EMail为:baidu@qq.com

最后

因为不想篇幅太长,所以只放上了核心代码,需要详细了解的可到码云上看:Redis解决业务解耦源码
可能有些同学会问到,万一redis挂了呢,那就岂不是没得发动短信和发动邮件了?答案是对的~哈哈哈,挂了那当然就没得发送了。不过对于这种关联性不强的没啥所谓,注册完没法短信也不是特别大的事情,但是呢,如果是电商的项目,下单和减少库存可是要强一致性的,那么有啥方案,可参考https://mp.weixin.qq.com/s/FAlv-qE1jjiiF0JPoMVcWA?