消息队列,一听很高大上,现在很多分布式系统都在用这个消息中间件 网上一搜, 说的都是些原理。
说下我的通俗理解, 你网上买了, 快递员给你投递, 会出现什么问题呢? 1 你不定时在家, 快递员 来了几次你都不在,不能到你手里。 2. 快递员很忙,手里一堆货物, 最后送到你手里就很慢了。
有问题就要解决,1: 你不定时在家? 1 送到你的房东家,然后你回去挨个找, 2 放到小区的 快递投递点,那里很多家 门牌号的 小柜子 ,你的东西就在那, 你自己去取 。
2 : 快递员很忙? 把快递放在仓库, 哪个快递员有空闲就去给你送。
这里对应的就是生产消费了。 我们知道, RPC 分布式服务, 把我们的业务系统都给划分了, 而不是之前一个复杂的系统了, 用户?拆分。 订单?拆分。 交易?拆分。基础服务?拆分。关系?拆分。各司其职。 分布式系统好处利大于弊。
各个业务 明确自己干什么, 另外可以针对性的提示性能 比如 用户业务访问的明显比订单多, 用户 业务 服务器加 三台, 如果你是一个复杂的大系统就不好调试了, 跑题了。 各业务必然有关联, 用户注册。必须要通过基础服务的 发短信, 推送。
这里就必须要中间件了,消息队列 有两种方式, 广播 , 排队,小区快递柜对应的各家的, 就是广播, 你自己拿你自己的。 不耽误快递员时间, 不耽误你时间, 也不耽误别人的时间, 排队,就是放在 房东家, 自己挨个去找。 不管如何, 这种异步方式都是
极大的提升了效率。
这里放个RabbitMQ 的简单示例, 现在各种中间件 都不错, 各有所长。
1 启动类
public class RechargeApplication implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(RechargeApplication.class); @Autowired private Environment env; public static void main(String[] args) { SpringApplication.run(RechargeApplication.class, args);
//启动的时候注册消费者, 一旦 注册, 消费者就会监听 队列,有数据就去拿,基于AMQP协议 , 举个例子, 你的快递被放到小区快递箱了, 手机收到短信, 你的快递到了。 快去A503 快递箱收取, 验证码 123456. SpringBeanHelper.getBean(Consumer.class).payConsumer(); } public void run(String... strings) throws Exception { logger.info("Coupon service started, run at {} environment", env.getActiveProfiles()); } }
SpringBeanHelper
public class SpringBeanHelper implements ApplicationContextAware { private static final Logger LOG = LoggerFactory.getLogger(SpringBeanHelper.class); protected static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringBeanHelper.applicationContext = applicationContext; } public static <T> T getBean(Class<T> requiredClass) { try { return applicationContext.getBean(requiredClass); } catch (NoSuchBeanDefinitionException e) { LOG.warn("no qualifying bean of type: {}", requiredClass); return null; } } public static <T> T getBean(String beanName, Class<T> requiredClass) { try { return applicationContext.getBean(beanName, requiredClass); } catch (NoSuchBeanDefinitionException e) { LOG.warn("no qualifying bean of name: {}, type: {}", beanName, requiredClass); return null; } } public static String projectDir(){ return System.getProperty("user.dir"); } public static String contextPath() { String basePath = applicationContext.getEnvironment().getProperty("server.context-path"); return StringUtils.isBlank(basePath) ? "" : basePath; } public static String applicationName() { return applicationContext.getEnvironment().getProperty("spring.application.name"); } public static Integer applicationPort(){ return Integer.parseInt(applicationContext.getEnvironment().getProperty("server.port")); } public static String applicationEnv() { String[] envList = applicationContext.getEnvironment().getActiveProfiles(); if (!CollectionUtils.isNullOrEmpty(envList)) { return envList[0]; } return ""; } /** * 判断给定的 taskNum 是不是本机要同步的房间 */ public static boolean isMineTask(long taskNum) { DiscoveryClient discoveryClient = applicationContext.getBean(DiscoveryClient.class); //通过服务发现取所有本服务的实例 List<ServiceInstance> serviceList = discoveryClient.getInstances(applicationName()); if (CollectionUtils.isNullOrEmpty(serviceList)) { return false; } SortedSet<EurekaDiscoveryClient.EurekaServiceInstance> esiSet = Sets.newTreeSet((o1, o2) -> { if (NetworkUtils.ip2long(o1.getHost()) < NetworkUtils.ip2long(o2.getHost())) { return 1; } else if(NetworkUtils.ip2long(o1.getHost()) == NetworkUtils.ip2long(o2.getHost())){ if(o1.getPort() < o2.getPort()){ return 1; }else { return -1; } } else { return -1; } }); //过滤状态为UP的实例并按IP排序 for (ServiceInstance si : serviceList) { if (si instanceof EurekaDiscoveryClient.EurekaServiceInstance) { EurekaDiscoveryClient.EurekaServiceInstance esi = (EurekaDiscoveryClient.EurekaServiceInstance) si; if (InstanceInfo.InstanceStatus.UP == esi.getInstanceInfo().getStatus()) { esiSet.add(esi); } } } if (CollectionUtils.isNullOrEmpty(esiSet)) { return false; } int index = 0, esiSize = esiSet.size(); long theIp = NetworkUtils.ip2long(NetworkUtils.ofInnerIp()); if (1 == esiSet.size()) { return theIp == NetworkUtils.ip2long(esiSet.first().getHost()); } //找到本机实例并且当 taskNum 对所有实例数取余等于本机实例 index 值时处理 for (EurekaDiscoveryClient.EurekaServiceInstance esi : esiSet) { if (NetworkUtils.ip2long(esi.getHost()) == theIp && index == taskNum % esiSize) { return true; } index++; } return false; } }
3 。生成者 , 快递员:
public void producer(Enum topic, Object message) { if (LOG.isDebugEnabled()) { LOG.debug("rabbit producer topic: {}, message: {}", topic, JsonUtils.toJSONString(message)); } try { AMQP.BasicProperties props = propertyOf(message); senderChannel.queueDeclare(topic.name(), true, false, false, null); this.senderChannel.basicPublish("", topic.name(), props, JsonUtils.toJSONBytes(message)); } catch (Exception e) { throw new RuntimeException("rabbitmq send message error.....", e); } }
4 消费者
public class Consumer { public static final Logger LOGGER = LoggerFactory.getLogger(Consumer.class); @Autowired private RabbitOperations rabbitOperations; @Autowired private RechargeService rechargeService; /** * 支付成功回调 */ public void payConsumer() { rabbitOperations.consumer(TradeCallBackTopic.CHARGE, 3, (contentType, body) -> { String content = StringUtils.toEncodedString(body, Charset.forName("UTF-8")); LOGGER.info("=======消费者:payConsumer start, content:{}============", content); CallBackDto dto = JSON.parseObject(content, CallBackDto.class); if (dto.getCallBackType() == PaymentConstant.CallBackNotifyType.PAY && dto.getTradeStatus() == PaymentConstant.TradeStatus.SUCCESS) { } }); } }