preface
Cyclic dependencies fall into two categories:
- Circular dependencies between RPC services (Dubbo, HTTP)
- Cyclic dependencies between applications
-
Dubbo checks whether dependent services are available at startup and throws an exception if not, preventing Spring initialization from completing. This is called a cyclic dependency between RPC services. A cyclic dependency occurs, and one party must start first. So this kind of problem must be solved.
-
The cyclic dependency between applications is as follows: Application A invokes application B’s service, and application B invokes application A’s service, either indirectly or directly. This circular dependency is fine at first, but can evolve into a circular dependency between RPC services as the code changes.
You can avoid Dubbo’s cyclic dependency by turning off the check with check= “false”, but I think this is just a benefit.
Cyclic dependencies between applications
Currently, there is no RPC inter-service circular call in our application, but there is an inter-application circular call. Here’s the one 🌰 that’s the case, and I’ll just give you a quick overview of the situation.
- The SiteGroupService and SiteService interfaces of the site-base service are invoked to query information about groups, stores, and micro-stores.
- Site-base listens for the group initialization message and performs the group initialization. During the process, the marketing- Base service is called to initialize the article and poster data.
The example above is an inter-application loop dependency, which can cause code to explode if you’re not careful. This hole should be filled in as soon as possible.
Treatment scheme
General solution for cyclic dependencies between applications
Teamwork mode:
- Reassign responsibility in one direction
- Sharing the kernel (propose a common service)
Communication integration mode:
- MQ decouples dependencies between services
As the above two services have already been developed, and relatively mature applications, and direct dependence is relatively less. MQ looks like a better solution at this point.
Considering that site-base is a more basic service than marketing-base, the above invocation process in Figure 1 remains unchanged, and the way that site-base sends messages and marketing-base consumes messages is changed below. See Figure 2.
Show me Code
First of all, we need to create a new Topic to the operation and maintenance or ourselves in the standalone environment. I’ll create a new theme named newcar_siteinit_basicinfo_test.
Producer allocation
Add a Spring XML configuration file application-mq. XML to config/user/beans.
application-mq.xml
1 <! --ons config--> 2 <bean id="producerConfig" class="com.souche.optimus.mq.aliyunons.ONSProducerConnConfig">
3 <property name="accessKey" value="${ons.access.key}"/>
4 <property name="secretKey" value="${ons.secret.key}"/> 5 </bean> 6 7 <! -- group initializes sender --> 8 <bean id="groupInitProducerInvoker" class="com.souche.optimus.mq.aliyunons.ONSProducerInvoker">
9 <property name="producerId" value="${ons.groupinit.producer.id}"/>
10 <property name="config" ref="producerConfig"/>
11 </bean>
12
13 <bean id="groupInitProducer" class="com.souche.optimus.mq.aliyunons.ONSProducer">
14 <property name="topic" value="${ons.groupinit.topic}"/>
15 <property name="invoker" ref="groupInitProducerInvoker"/>
16 </bean>
Copy the code
ons.properties
Add ons. The properties in the config/optimus/properties folder
1ons.access.key = K8pfCPRU6gL2lldi
2ons.secret.key = U3lYVGl3L9nb23cEMogWcUVziLJ2T7
3
4Initialize the theme
5ons.groupinit.topic = newcar_siteinit_basicinfo_test
6ons.groupinit.producer.id = PID_newcar_siteinit_basicinfo_test
Copy the code
Producer logic code
1private void sendMsg(SiteGroupInfoDO siteGroupInfoDO) {
2 if (siteGroupInfoDO.getId() == null) {
3 return;
4 }
5 Map<String, Object> map = new HashMap<>();
6 map.put("id",siteGroupInfoDO.getId());
7 map.put("groupCode",siteGroupInfoDO.getGroupCode());
8 map.put("groupName",siteGroupInfoDO.getGroupName());
9 map.put("appName",siteGroupInfoDO.getAppName());
10 map.put("domainSuffix",siteGroupInfoDO.getDomainSuffix());
11 String uuid = UUIDUtil.getID();
12 mqProducer.send(map, uuid, CommonConstant.MQ_TAG);
13 LOGGER.info("Send message: body: {}, keys: {}, tag: {}", map, uuid, CommonConstant.MQ_TAG); 14}Copy the code
- Each message needs to have a unique business ID, the second parameter, which is also mandatory. You can use order ID, user ID, or uuidUtil.getid () if you’re not sure
- The third parameter, try to use a different tag in a queue to distinguish between different messages in a business/system, try not to use * or the default value
Consumer configuration
application-mq.xml
1 <bean id="contentPlatformMsgConsumer" class="com.souche.marketing.base.biz.mq.ContentPlatformMsgConsumer"/>
2 <bean id="tgcArticleInvoker" class="com.souche.optimus.mq.aliyunons.ONSConsumerInvoker">
3 <property name="config" ref="consumerConfig"/>
4 <property name="reciver" ref="contentPlatformMsgConsumer"/>
5 <property name="consumerId" value="${mq.ons.consumer.newcar.article.id}"/>
6 <property name="topic" value="${mq.ons.consumer.newcar.article.topic}"/ > 7 <! --<property name="tag" value="${mq.aliyun.car.center.tag}"/> < ! – Tag is optional. If no tag is specified, all messages under this topic will be monitored. > --> 8 <property name="enabled" value="true"/>
9 </bean>
Copy the code
An important point here is that our consumer ID must be created from ali Cloud console or contact operation and maintenance, as shown below:
ons.properties
1ons.access.key = K8pfCPRU6gL2lldi
2ons.secret.key = U3lYVGl3L9nb23cEMogWcUVziLJ2T7
3
4Initialize the theme
5mq.ons.consumer.newcar.siteinit.topic = newcar_siteinit_basicinfo_test
6mq.ons.consumer.newcar.siteinit.id = GID_newcar_siteinit_basicinfo_test
Copy the code
Consumer logic code
1/**
2 * @author james mu
3 * @date 2019/11/7 10:16
4 */
5public class GroupInitMsgConsumer implements MQConsumer {
6
7 public static final Logger LOGGER = LoggerFactory.getLogger(GroupInitMsgConsumer.class);
8
9 @Resource(name = "articleServiceForManageProvider")
10 private ArticleServiceForManage articleServiceForManage;
11
12 @Resource(name = "posterServiceForAppProvider")
13 private PosterServiceForApp posterServiceForApp;
14
15 @Override
16 public ConsumeResult onRecived(Map<String, Object> map) {
17 if (CollectionUtils.isEmpty(map)) {
18 LOGGER.warn("msg is empty");
19 return ConsumeResult.CommitMessage;
20 }
21 JSONObject msg = new JSONObject(map);
22 Integer groupId = msg.getInteger("id");
23 String groupCode = msg.getString("groupCode");
24 String groupName = msg.getString("groupName");
25 String appName = msg.getString("appName");
26 String domainSuffix = msg.getString("domainSuffix");
27 LOGGER.info("groupId: {}, groupCode: {}, groupName: {}, appName: {}, domainSuffix: {}", groupId, groupCode, groupName, appName, domainSuffix); 28, 29if (groupId == null) {
30 LOGGER.warn("param is miss");
31 returnConsumeResult.CommitMessage; 32}else{ 33 articleServiceForManage.importSubjectForGroup(groupId); 34 SiteGroupInfoDTO siteGroupInfoDTO = new SiteGroupInfoDTO(); 35 siteGroupInfoDTO.setId(groupId); 36 siteGroupInfoDTO.setGroupCode(groupCode); 37 siteGroupInfoDTO.setGroupName(groupName); 38 siteGroupInfoDTO.setAppName(appName); 39 siteGroupInfoDTO.setDomainSuffix(domainSuffix); 40 posterServiceForApp.initPosterByGroup(siteGroupInfoDTO); 41} 42returnConsumeResult.CommitMessage; 45}} 43 and 44Copy the code
In the consuming message business code, we must pay attention to deal with the idempotent problem, to prevent multiple consumption of messages, resulting in business errors. At this point, I believe you have understood how to clear the idea of loop dependency and processing.