記因循環依賴的解決方案

confusion.jpg

前言

循環依賴分爲2類:spring

  • RPC服務間(dubbo、http)循環依賴
  • 應用間循環依賴
  1. Dubbo缺省會在啓動時檢查依賴的服務是否可用,不可用時會拋出異常,防止Spring初始化完成。這種狀況咱們就叫作RPC服務間循環依賴。出現了循環依賴,必須有一方先啓動。因此這種問題是必定須要解決的。app

  2. 應用間循環依賴大體狀況以下: A應用調用B應用的服務,B應用也會調用A應用的服務,不管是間接調用仍是直接調用。 這種循環依賴剛開始不會出現問題 ,但隨着代碼變動,有可能會發展爲RPC服務間循環依賴。運維

能夠經過check=」false」關閉檢查來避免 Dubbo的循環依賴的報錯,可是我認爲這個只是權益之計。dom

應用間循環依賴

當前咱們應用中並無出現RPC服務間循環調用,可是出現了應用間循環調用。下面就是這個🌰就是這種狀況,我簡單描述下大體的狀況。ide

應用間循環依賴.png

  • marketing-base會調用site-base服務中的SiteGroupService、SiteService接口用來查詢集團、店鋪、微店的信息。
  • site-base裏有監聽集團初始化的消息,而後執行集團初始化,過程當中會調用marketing-base的服務以初始化文章、海報數據。

上面這個例子是應用間循環依賴,一不當心可能寫出暴雷的代碼。這裏應該儘快把這個坑填掉。post

處理方案

針對應用間循環依賴,大體的解決辦法ui

團隊協做模式:阿里雲

  1. 從新劃分歸屬,把職責給一個方向
  2. 共享內核(提出一個公共服務)

通訊集成模式:code

  1. MQ解耦服務間的依賴

因爲上述的2個服務早已開發完畢,且比較成熟的應用,並且直接的依賴仍是比較少的。此時MQ看起來是一個比較好的方案了。orm

這裏考慮到site-basemarketing-base是更加基礎的服務,因此圖一中上邊的調用流程不變,下方更改成site-base發送消息,而後marketing-base來消費消息的方式。如圖二所示。

應用循環調用解決方案.png

Show me Code

首先,咱們須要向運維或者本身在單機環境下,新建立一個Topic。我這裏新建立一個名叫: newcar_siteinit_basicinfo_test 的主題。

生產者配置

在config/user/beans中添加一個spring xml 配置文件 application-mq.xml。

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    <!-- 集團初始化 發送者 -->
 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>

ons.properties

增長ons.properties 在config/optimus/properties文件夾下

1ons.access.key = K8pfCPRU6gL2lldi
2ons.secret.key = U3lYVGl3L9nb23cEMogWcUVziLJ2T7
3
4#初始化主題
5ons.groupinit.topic = newcar_siteinit_basicinfo_test
6ons.groupinit.producer.id = PID_newcar_siteinit_basicinfo_test

生產者邏輯代碼

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("發送消息:body: {}, keys: {}, tag: {}", map, uuid, CommonConstant.MQ_TAG);
14    }
  1. 每一個消息都須要有一個惟一的業務ID, 即第二個參數, 也是強制要求傳入的. 能夠用訂單ID, 用戶ID, 若是很差肯定就直接用UUIDUtil.getId()
  2. 第三個參數, 儘可能用一個隊列的不一樣的tag去區分一個業務/系統中的不一樣消息, 儘可能不要用*或者默認的值

消費者配置

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}"/> &lt;!&ndash;tag選填, 若是不填將監聽該topic下的全部消息&ndash;&gt;-->
8        <property name="enabled" value="true"/>
9    </bean>

這裏有一個很重要的點是,咱們的消費者ID必須從阿里雲控制檯上創建或者聯繫運維建立,圖示以下:

截屏2019-11-1109.21.52.png

ons.properties

1ons.access.key = K8pfCPRU6gL2lldi
2ons.secret.key = U3lYVGl3L9nb23cEMogWcUVziLJ2T7
3
4#初始化主題
5mq.ons.consumer.newcar.siteinit.topic = newcar_siteinit_basicinfo_test
6mq.ons.consumer.newcar.siteinit.id = GID_newcar_siteinit_basicinfo_test

消費者邏輯代碼

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
29        if (groupId == null) {
30            LOGGER.warn("param is miss");
31            return ConsumeResult.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        }
42        return ConsumeResult.CommitMessage;
43    }
44
45}

在消費消息的業務代碼中,你們必定要注意處理冪等性的問題,防止屢次消費消息,致使業務的出錯。在此,相信你們已經瞭解怎麼清除循環依賴的思路和處理了。

相關文章
相關標籤/搜索