微服務架構案例(04):中間件集成,公共服務封裝

  • 2019 年 11 月 4 日
  • 筆記

本文源碼:GitHub·點這裡 || GitEE·點這裡

更新進度(共6節):

01:項目技術選型簡介,架構圖解說明

02:業務架構設計,系統分層管理

03:資料庫選型,業務數據設計規劃

04:中間件集成,公共服務管理

一、中間件簡介

中間件是基礎軟體的一類, 屬於復用性極高的軟體。處於作業系統軟體與應用程式的之間。是一種獨立的系統軟體,也可以是公共的服務程式,分散式架構系統藉助中間件,可以在不同的技術之間共享資源,或者不同的服務直接傳遞資訊。中間件位作業系統之上,管理電腦資源和網路通訊。是連接兩個獨立應用程式或獨立系統的軟體,例如:

  1. 消息隊列中間件,在兩個服務之間進行非同步的消息傳遞;
  2. 數據快取中間件,快取整合系統的熱點數據,提高程式的響應速度;
  3. Nginx中間件,提供負載均衡,服務代理,等功能;

二、公共服務簡介

公共服務,顧名思義就是系統內通用的服務,例如用戶身份驗證,消息發送,監控預警,網關服務等。

該案例的中間件和公共服務,都是基於Feign介面統一的方式提供服務。

三、中間件集成

1、消息中間件

  • RocketMq簡介

RocketMq 是一款分散式、隊列模型的消息中間件,有兩個核心角色:消息生產者和消息消費者。作為高並發系統的核心組件之一,能夠幫助業務系統解構提高系統穩定性。

  • 應用流程
  1. 消息生產者
@Component  public class MsgSendService {      @Resource      private ProducerConfig producerConfig ;      public void sendMsg (MsgWrap msgWrap) {          producerConfig.sendMsg(msgWrap.getGroup(),msgWrap.getTopic(),                                 msgWrap.getTag(),msgWrap.getContent());      }  }
  1. 消息消費者
@Component  @Consumer(group = MsgRoute.husky_group_1,            topic = MsgRoute.husky_topic_1 ,            tag = MsgRoute.husky_tag_1)  public class UserSearchListener implements MsgReadService {      @Resource      private BookEsAnalyFeign bookEsAnalyFeign ;      @Override      public void readMsg(String msg) throws Exception {          LOGGER.info("【用戶搜索消息監聽 Msg】:{}",msg) ;          // 轉發請求數據分析服務          bookEsAnalyFeign.sendBookEsMsg(msg);      }  }
  1. 提供Feign介面
@RestController  public class UserSearchController implements UserSearchFeign {      @Resource      private SendMsgService sendMsgService ;      @Override      public void sendBookSearch(String msgContent) {          MsgWrap msgWrap = new MsgWrap() ;          msgWrap.setContent(msgContent);          msgWrap.setGroup(MsgRoute.husky_group_1);          msgWrap.setTopic(MsgRoute.husky_topic_1);          msgWrap.setTag(MsgRoute.husky_tag_1);          sendMsgService.sendMsg(msgWrap);      }  }

2、快取中間件

  • Redis簡介

Redis 是一個基於記憶體的高性能key-value資料庫。對高並發系統提供各種場景的支撐:熱點數據快取,計數器,流量削峰等。

  • 應用流程
  1. 封裝操作方法
@Service  public class RedisServiceImpl implements RedisService {      @Resource      private RedisTemplate<Object,Object> redisTemplate ;      @Override      public boolean set(Object key, Object value) {          boolean redisFlag = true ;          try {              redisTemplate.opsForValue().set(key,value);          } catch (Exception e){              redisFlag = false ;              e.printStackTrace();          }          return redisFlag ;      }      @Override      public boolean set(Object key,Object value, long expire) {          boolean redisFlag = true ;          try {              redisTemplate.opsForValue().set(key,value,expire,TimeUnit.SECONDS);          } catch (Exception e){              redisFlag = false ;              e.printStackTrace();          }          return redisFlag ;      }      @Override      public String get(Object key) {          String value = null ;          try {              value = String.valueOf(redisTemplate.opsForValue().get(key)) ;          } catch (Exception e){              e.printStackTrace();          }          return value ;      }  }
  1. 提供Feign服務
@RestController  public class RedisController implements RedisFeign {      @Resource      private RedisService redisService ;      @Override      public boolean set (String key, String value) {          return redisService.set(key,value) ;      }      @Override      public boolean setTimeOut (String key, String value,long expire){          return redisService.set(key,value,expire) ;      }      @Override      public String get (String key) {          return redisService.get(key) ;      }  }

3、搜素中間件

  • ES搜索簡介

ElasticSearch是一個基於Lucene的搜索伺服器。它提供了一個分散式多用戶能力的全文搜索引擎,基於RESTful的 web介面。是當前流行的企業級搜索引擎。

  • 應用流程
  1. 封裝操作方法
@Service  public class BookInfoEsServiceImpl implements BookInfoEsService {      @Resource      private BookInfoRepository bookInfoRepository ;      @Override      public void batchSave(List<EsBookInfo> bookInfoList) {          bookInfoRepository.saveAll(bookInfoList) ;      }      @Override      public List<EsBookInfo> queryList() {          Iterable<EsBookInfo> bookInfoIterable = bookInfoRepository.findAll() ;          List<EsBookInfo> esBookInfoList = Lists.newArrayList(bookInfoIterable) ;          if (esBookInfoList == null){              esBookInfoList = new ArrayList<>() ;          }          return esBookInfoList;      }      @Override      public List<EsBookInfo> getByKeyWord(String keyWord) {          QueryStringQueryBuilder builder = new QueryStringQueryBuilder(keyWord);          Iterable<EsBookInfo> bookInfoIterable = bookInfoRepository.search(builder) ;          List<EsBookInfo> esBookInfoList = Lists.newArrayList(bookInfoIterable) ;          if (esBookInfoList == null){              esBookInfoList = new ArrayList<>() ;          }          return esBookInfoList ;      }  }
  1. 提供Feign服務
@RestController  public class BookInfoEsController implements BookInfoEsFeign {      @Resource      private BookInfoEsService bookInfoEsService ;      @Override      public void batchSave(List<EsBookInfo> bookInfoList) {          bookInfoEsService.batchSave(bookInfoList);      }      @Override      public List<EsBookInfo> queryList() {          return bookInfoEsService.queryList();      }      @Override      public List<EsBookInfo> getByKeyWord(String keyWord) {          return bookInfoEsService.getByKeyWord(keyWord);      }  }

4、定時器中間件

  • Quartz 簡介

Quartz是由Java編寫的開源任務調度的框架,通過觸發器設置作業定時運行規則,控制任務的執行時間。其中quartz集群通過故障切換和負載平衡的功能,能給調度器帶來高可用性和伸縮性。

  • 應用流程
@Component("SendMsgJob")  public class SendMsgJob implements TaskJobService {      @Resource      private SendEmailFeign sendEmailFeign ;      @Override      public void run(String param) {          String nowDate = TimeUtil.formatDate(new Date(),TimeUtil.FORMAT_01) ;          LOGGER.info("SendMsgJob Execute Time:{}",nowDate);          sendEmailFeign.sendEmail("","定時郵件通知",""+nowDate);      }  }

四、公共服務管理

1、Token服務

  • Token服務簡介

通過一個公共的Token管理服務,對訪問系統的用戶身份做管理:身份令牌創建,校驗,刷新等。

  • 應用流程
  1. 封裝操作方法
@Service  public class UserTokenServiceImpl implements UserTokenService {      @Resource      private UserBaseMapper userBaseMapper ;      @Resource      private RedisFeign redisFeign ;      @Override      public String getToken(String userName, String passWord) throws Exception {          UserBaseExample example = new UserBaseExample() ;          example.createCriteria().andUserNameEqualTo(userName) ;          UserBase userBase = selectByExample(example) ;          if (userBase != null){              String secrete = userBase.getPassWord() ;              if (secrete.equals(passWord)) {                  // 返回 Token                  String value = userBase.getId().toString() ;                  String publicKeyStr = RsaCryptUtil.getKey(RsaCryptUtil.PUB_KEY) ;                  String token = RsaCryptUtil.encrypt(RsaCryptUtil.createPublicKey(publicKeyStr),value.getBytes()) ;                  String key = RedisUtil.formatUserTokenKey(userBase.getId()) ;                  redisFeign.setTimeOut(key,token, Constant.USER_TOKEN_EXPIRE) ;                  return token ;              }          }          return null;      }      @Override      public Integer verifyToken(String token) throws Exception {          String privateKeyStr = RsaCryptUtil.getKey(RsaCryptUtil.PRI_KEY) ;          String userId = RsaCryptUtil.decrypt(RsaCryptUtil.createPrivateKey(privateKeyStr),                               RsaCryptUtil.parseBase64Binary(token));          return Integer.parseInt(userId) ;      }      @Override      public boolean refreshToken(String token) throws Exception {          Integer userId = verifyToken(token) ;          if (userId > 0 ){              String key = RedisUtil.formatUserTokenKey(userId) ;              // 判斷Token 是否過期              String cacheToken = redisFeign.get(key) ;              if (StringUtils.isEmpty(cacheToken)){                  return false ;              }              redisFeign.setTimeOut(key,token, Constant.USER_TOKEN_EXPIRE) ;              return true ;          }          return false ;      }  }
  1. 提供Feign服務
@FeignClient("MOPSZ-BASIS-TOKEN")  public interface UserTokenFeign {      /**       * 獲取 TOKEN       */      @PostMapping("/token/getToken")      RespObject getToken (@RequestParam("userName") String userName,                           @RequestParam("passWord") String passWord) ;      /**       * 驗證 TOKEN       */      @PostMapping("/token/verifyToken")      RespObject verifyToken (@RequestParam("token") String token) ;      /**       * 刷新 TOKEN       */      @PostMapping("/token/refreshToken")      boolean refreshToken (@RequestParam("token") String token) ;  }

2、消息服務

  • Msg服務簡介

在一個複雜的系統中,消息通知是一個必備模組,一般封裝方式主要從下面兩個方式入手,消息類型:用戶消息,系統消息等,消息接收方式:郵件,簡訊,應用端等。

  • 應用流程
  1. 封裝郵件發送
@Service  public class SendEmailServiceImpl implements SendEmailService {      @Override      public void sendEmail(String receive, String title, String msg) {          try {              EmailUtil.sendEmail01(receive,title,msg);          } catch (Exception e){              e.printStackTrace() ;              LOGGER.info("郵件發送失敗:{}",e.getMessage());          }      }  }
  1. 提供Feign服務
@FeignClient("MOPSZ-BASIS-MSGBOX")  public interface SendEmailFeign {      /**       * 發送Email       */      @PostMapping("/msgBox/sendEmail")      void sendEmail (@RequestParam("receive") String receive,                        @RequestParam("title") String title,                        @RequestParam("msg") String msg) ;  }

五、源程式碼地址

GitHub·地址  https://github.com/cicadasmile/husky-spring-cloud  GitEE·地址  https://gitee.com/cicadasmile/husky-spring-cloud