餐掌柜之——1、项目概述及环境搭建、Dubbo入门

个人笔记

什么是SaaS平台?它的特点是什么?

SaaS:软件即服务,通过网络提供软件服务,供应商将软件统一部署在服务器上,客户可以根据自己的需求向供应商购买

SaaS典型代表:用友,他主要通过子域名提供OA服务(办公自动化)

特点:

  • 互联网特性:通过SaaS提供在线服务, 这都是基于网络的,客户就不用投入成本在硬件、维护等等之上。
  • 多重租赁特性:通过一套标准软件系统给成百上千客户提供不同服务,用户可以根据自己需求购买软件服务。这要求SaaS服务能够支持不同租户之间数据和配置的隔离(软件共享,数据隔离)
  • 服务特性:SaaS使软件以互联网为载体的服务形式给客户使用
  • 可扩展性:可以最大限度地提高系统的并发性,用户也可以定制服务

餐掌柜各个平台的作用是什么?

客人点餐平台: 负责实体店线下开桌、点餐、追加菜品等

商家业务管理平台:管理点餐平台,提供员工、店铺、桌台、菜品、订单、结算等功能

运营商平台:运营商管理基础数据模块, 管理商家、统一权限、日志、图片、数字字典等

餐掌柜的通用服务、核心业务、系统架构作用是社么?分别用到了什么技术?

通用服务的作用:

  1. 通用的模块抽离出来以后,开发其他项目以及新增的业务时可以直接复用,可以提高项目开发的效率
  2. 随着业务的增加,系统模块之间会越来越复杂,把通用模块抽离出来,可以减少各个模块之间的耦合度,使项目更好维护

核心业务的作用:

  • 客人点餐平台: 负责实体店线下开桌、点餐、追加菜品等
  • 商家业务管理平台:管理点餐平台,提供员工、店铺、桌台、菜品、订单、结算等功能
  • 运营商平台:运营商管理基础数据模块, 管理商家、统一权限、日志、图片、数字字典等

系统架构:

  • 代理 与 数据展现:根据域名的不同,通过Nginx集群反向代理到相应服务的Gateway网关,实现统一的业务数据访问
  • 权限控制:使用JWT + Spring Security实现权限控制
  • 服务治理与配置中心:使用Nacos实现注册中心与配置中心
  • 服务调用:使用Dubbo实现服务与服务之间的调用
  • 流量控制:使用Sentinel完成对各个服务的 流量控制、降级熔断、负载保护等
  • 缓冲层:使用Spring Cache + Redis实现无侵入的业务数据缓冲,提高系统性能
  • 基础业务支撑:基于Spring Boot脚手架,集成OSS图片存储、Sharding-JDBC分库分表、Mybatis-Plus、Swagger2接口文档、Seata分布式事务、Mysql数据库、RabbitMQ消息队列,当然也少不了Docker容器化的使用

项目模块 及 核心项目模块 职责是什么?

餐掌柜中采用了Maven的分层架构,来维护整体的项目结构,各个模块相互独立,这样可以更好的实现功能及组件的复用,从而减少开发成本。

核心项目模块的作用:主要对各种中间件进行集成,例如:MQ消息队列、Seata分布式事务、Redis缓存等

项目数据库结构设计、分库设计是怎么样的?

餐掌柜的数据库遵循领域模型设计(DDD),把数据库根据功能模块垂直划分为5个库,分别对应各个服务(一个服务对应一个数据库):

  • 基础模块库
  • 权限模块库
  • 商家模块库
  • 交易模块库
  • 报表模块库

优点:

  1. 解决业务层面的耦合,让业务看起来更清晰,在对不同业务的数据进行维护时不会影响到其他业务
  2. 一个库分成了多个库,这样对并发高的业务数据库进行集群时能够不浪费资源
  3. 它是搭建微服务的基石,让每个微服务都是独立的。

什么是微服务?微服务的特点是什么?

微服务是一种基础的架构设计风格,把一个大项目分成很多个小的微服务可以减少各个模块之间的耦合,使其更好开发与维护。再然后,“微” 字顾名思义,每个微服务只做单一的一件事,一个小团队就能完成,这样可以更加好的协同工作,并且每个微服务都能独立运行,这样可以根据需求更好的做集群以应对高并发场景,而不会浪费资源。

微服务的特点:

  • 单一职责:每个服务只做一件事。
  • 微:微服务的服务拆分的粒度很小,麻雀虽小但五脏俱全。
  • 面向服务:通过REST风格的接口调用服务,而不管它的实现。
  • 自治:服务间互相独立,互不干扰,当然开发服务的团队也是,可以更好的协同工作。

Dubbo服务执行流程是怎么样的?

  1. 服务者把自己注册到注册中心
  2. 消费者向注册中心订阅某个服务
  3. 注册中心通过异步的方式把服务对应的ip和端口同步到消费者
  4. 消费者把服务列表缓存本地,并且通过同步的方式访问服务者

Dubbo的调用方式是什么?基于什么协议实现的?

调用方式:RPC远程过程调用

协议:Dubbo协议

与Spring Cloud的Feign的区别:Feign基于Http协议,而Dubbo基于自己的RPC协议

Dubbo与Feign对比的优缺点:

  • 优点:性能更好
  • 缺点:只支持Java语言,不过后续支持的语言会越来越多;Feign因为基于Http协议,所以只要能使用Http协议那么都能使用它

老师讲义

项目概述及环境搭建

学习目标:

  • 能够说出什么是saas平台及其特点
  • 能够说出餐掌柜核心架构、项目模块功能、数据库结构
  • 了解微服务平台中工作协调相关事宜
  • 掌握基础组件安装及项目启动
  • 掌握餐掌柜项目的开发规范
  • 掌握Dubbo基础使用

第一章 餐掌柜项目概述

1、SaaS平台

SaaS平台:供应商将应用软件统一部署在自己的服务器上,客户可以根据工作实际需求,通过互联网向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通过互联网获得Saas平台供应商提供的服务

image-20210427133333877

互联网特性

SaaS软件行业知名产品NetSuite所提供的在线ERP、在线CRM等模块产品都是基于网络的,这样的优势在于不必投入任何硬件费用,也不用请专业的系统维护人员就能上网,有浏览器就可以进行ERP、CRM系统的使用。快速的实施、便捷的使用、低廉的价格都有赖于SaaS产品的互联网特性。

多重租赁(Multi-tenancy)特性

SaaS服务通常基于一套标准软件系统为成百上千的不同客户(又称为租户)提供服务。这要求SaaS服务能够支持不同租户之间数据和配置的隔离,从而保证每个租户数据的安全与隐私,以及用户对诸如界面、业务逻辑、数据结构等的个性化需求。由于SaaS同时支持多个租户,每个租户又有很多用户,这对支撑软件的基础设施平台的性能、稳定性和扩展性提出很大挑战。SaaS作为一种基于互联网的软件交付模式,优化软件大规模应用后的性能和运营成本是架构师的核心任务。

服务(Service)特性

SaaS使软件以互联网为载体的服务形式被客户使用,所以很多服务合约的签订、服务使用的计量、在线服务质量的保证和服务费用的收取等问题都必须加以考虑。而这些问题通常是传统软件没有考虑到的。

可扩展(Scalable)特性

可扩展性意味着最大限度地提高系统的并发性,更有效地使用系统资源。比如应用:优化资源锁的持久性,使用无状态的进程,使用资源池来共享线和数据库连接等关键资源,缓存参考数据,为大型数据库分区。

2、业务概述

餐饮SaaS管理系统就是【运营商】将餐饮管理系统部署到云端,商家只需要进行付费申请使用,而无需对技术、硬件、运维等各个方面再次投入,平台一般具有下列模块:

  • 运营平台:运营商管理基础数据模块【统一权限、日志、图片、数字字典】以及商家管理的平台
  • 商家平台:点餐后台核心业务,提供员工、店铺、桌台、菜品、订单、结算等功能
  • 点餐平台:H5点餐平台,客户实现开桌、点餐、追加菜品等功能

相对传统餐饮行业来说:连锁店数据还不能共享,老板管理店铺不方便,而SaaS餐饮管理软件,具有餐饮O2O应用场景所有功能,如自助点餐、线上外卖、自动收银等,既节约了顾客的用餐时间又节省了成本,还方便了店铺之间的管理,更提高了店铺线上与线下的数据实时更新。

image-20210427140624577

3、核心架构

3.1、通用服务

对于一个公司架构设计来说,必须思考的问题是,这个功能在现在或者将来能满足多少业务场景?如果将来有新的业务出现,是不是能够复用?或者说,需要做多大的调整才可以复用?甚至于,这个功能有没有可能对外输出,提供SaaS化的服务建立通用服务业务为了业务的敏捷,创新!有下面三个特征:

  • 敏捷 业务需求变化快,变更以天甚至更短的频率计算,一个单体大型应用,庞大的开发团队对单一应用的变更变得越来越困难。将大应用变为多个小的应用组合,才能适应外部的快速变化,实现业务的敏捷。
  • 解耦 随着业务的发展,业务系统之间的交互通常会变得越来越复杂。一个功能的修改可能会影响很多方面。只有将需要大量交互的功能独立,从应用中拆解出来,这样可以使得应用之间耦合度大幅下降。
  • 复用 一些公共的能力通过复用,大大提高了开发效率,避免了重复建设。同时使得数据和流程可以集中得以管理和优化。

餐掌柜项目中提供的通用服务:

image-20210430223111296

3.2、核心业务

1628691435250

请求接入:

H5:客户点餐接入

运营商:运营管理接入

商家:商家主业务接入

阿里云:OSS、ECS、部署平台接入

网关

Nginx:反向代理,路由承压力

Gateway:第二代网关服务,路由分发、权限鉴定

核心业务:

点餐平台:负责客户点餐业务,为Android、IOS、H5提供统一服务接口

商家平台:负责基础数据配置,同时提供店员及交易结算服务

运营平台:负责商家管理及运营报表系统

通用业务

通用非业务系统的中台系统:权限、支付、图片、数字自动、日志中心、报表等

3.3、系统架构

餐掌柜项目是基于spring-cloud-alibaba的架构体系,关于spring-cloud-alibaba,其核心组件如下:

image-20210502085644762

  • Sentinel

    阿里巴巴开源产品,把流量作为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性.

  • Nacos

    阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台.

  • RocketMQ

    Apache RocketMQ基于Java的高性能,高吞吐量的分布式消息和流计算平台.

  • Dubbo

    Apache Dubbo是一款高性能的Java RPC框架.

  • Seata

    阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案.

  • Alibaba Cloud OSS

    阿里云对象存储服务器(Object Storage Service,简称OSS),是阿里云提供的海量,安全,低成本,高可靠的云存储服务.

  • Alibaba Cloud Schedulerx

    阿里中间件团队开发的一款分布式调度产品,支持周期性的任务与固定时间点触发任务.

image-20210427180757677

展现层:负载与用户的交互,分为Android、IOS、web应用,他们都是通过访问统一的gateway网关来实现业务数据的访问

代理层:选用高性能的nginx服务,通过域名与不同servrce的绑定,通过gateway路由到不同的服务器组

权限控制层:使用无状态的JWT认证,结合Spring Security实现统一的权限控制

服务治理:使用nacos注册中心,配置中心实现服务的治理

服务调用:使用Spring Cloud alibaba 的核心组件dubbo进行服务之间的调用

流量控制:使用 Sentinel把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性

缓冲层:spring cache 配合redis轻松无侵入的实现业务数据的缓冲

基础业务支撑:基于spring boot脚手架,轻松集成OSS图片存储、sharding-jdbc分库分表、mybatis-plus、docker、接口文档swagger2、分布式事务seata、MySQL、RocketMQ/RabbitMQ等组件

4、项目模块

在餐掌柜项目中采用maven的分层构架,来维护整体的项目架构,各个模块相对独立,达到功能及组件的复用,从而减少开发成本的投入,避免反复造轮子的现象,整体的分层构建如下图所示:

image-20210810205426063

主项目结构说明:

1
2
3
4
5
6
7
8
|——restkeeper-super	        负责整个项目的模块定义,依赖和插件管理
|———— restkeeper-framework 核心组件模块,主要是对各个框架集成:mp、seata、jwt、redis等等
|———— restkeeper-gateway 网关对外的统一接口,集成对日志client、鉴权client、knife4j组件
|———— restkeeper-model-basic 基础服务模块,与业务无关的组件都在这里集成
|———— restkeeper-model-report 报表模块,提供统一的日志报表,对各个子系统报表提供接口支撑
|———— restkeeper-model-security 统一鉴权模块,依赖spring-security
|———— restkeeper-model-shop 商家中心模块:各个主业务功能的实现,并且提供H5点餐端
|———— restkeeper-model-trading 交易平台,提供商家平台支付业务的结算功能

5、数据库结构

5.1、数据库概述

遵循领域模型设计【DDD】,我们按照功能模块领域垂直把数据库分为5个库,具体库的职能以及内部所含有的表详细情况如下图所示:

image-20210430091937127

带来的提升:

  • 解决业务层面的耦合,业务清晰

  • 能对不同业务的数据进行分级管理、维护、监控、扩展等

  • 高并发场景下,垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈

    关于==【垂直分库、水平分库、垂直分表、水平分表】==在后续的项目中我们会使用sharding-jdbc来实现

  • 微服务处理业务搭建一个基石

5.2、表数据冗余

根据数据库设计的第三范式:在数据库设计过程中,应该尽量消除冗余。即设计数据库时,某一个字段属于一张表,但它同时出现在另一个或多个表,且完全等同于它在其本来所属表的意义表示,那么这个字段就是一个冗余字段。

问题:随着企业数据量与并发量不断的增加,冗余字段的存在到底是好还是坏呢?

创建一个关系型数据库设计,我们有两种选择:

  • 尽量遵循范式理论的规约,尽可能少的冗余字段,让数据库设计看起来精致、优雅、让人心醉。

  • 合理的加入冗余字段这个润滑剂,减少join,让数据库执行性能更高更快。

所有问题出现必然因为场景问题,针对冗余字段问题,分为两个场景:

快照场景(副本场景):交易场景大部分是数据快照,而不是冗余,用户下单时候的用户名、地址、商品名称、商品描述等,若采用关联,商品在下单后发生了更新的话再去关联查询就会导致和用户操作时的数据不一致,从而产生纠纷,例如我们在项目中的设计:

image-20210430221443943

冗余场景:一般数据改动的可能性少,而查询多的场景会使用冗余,例如淘宝的店铺名称,淘宝商家中心会有这个字段,可能里面的商家论坛也有,再假设聚划算这种独立的大业务自己也存一份,再来个垂直频道电器城的后台管理也独立存一份,这种场景是由于对查询性能要求高产生的,所以必须要冗余,在业务的取舍上,肯定是对让用户更快看到信息,那么不可避免的是带来维护成本的增加,对于数据一致性问题,只要做到最终一致就可以了,分布式的CAP原则的实际应用基本都是通过牺牲数据一致性(C)来保证高可用(A)和高可靠(P), 因为这种场景大部分都是可以接受短暂的数据不一致的,对业务的影响及其微小。

在餐掌柜的项目我们遵循的原则:

  • 项目全部采用逻辑关联,没有采用主外键约束
  • 尽可能少使用多表关联查询。冗余是为了效率,减少join
  • 尽可能服务独立化,查询单表化,例如:查询用户信息又需要用户的头像,处理的原则是调用【用户服务】和【附件服务】在dubbo层组装数据

5.3、数据库导入

我们使用docker-compose第一次安装mysql的时候,会自动执行初始化脚本,具体会在【第二章-基础组件安装】中演示

6、工作协调

6.1、团队组织及分工

image-20210320090817003

微服务,顾名思义,微服务得从两个方面去理解,什么是”微”、什么是”服务”, 狭义来讲就是体积小、著名的”2 pizza 团队”很好的诠释了这一解释【2 pizza 团队最早是亚马逊 CEO Bezos提出来的,意思是说单个服务的设计,所有参与人从设计、开发、测试、运维所有人加起来 只需要2个披萨就够了】,而做为微服务,具备有下列四个要素:

  • 小:微服务体积小,2 pizza 团队。
  • 独:能够独立的部署和运行。
  • 轻:使用轻量级的通信机制和架构。
  • 松:为服务之间是松耦合的。

微服务的特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
  • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
  • 面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  • 自治:自治是说服务间互相独立,互不干扰
    • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
    • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
    • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
    • 数据库分离:每个服务都使用自己的数据源
    • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护

6.2、gitee/gitlab代码管理

为了方便开发小组的成员们进行代码共享,我们使用给Git来管理小组内的代码,Git是目前世界上最先进的分布式文件版本控制系统(没有之一)。对于我们java程序员而言,管理的就是代码文件版本,其主体架构图下:

image-20210428212725905

这里我们使用gitee平台来管理代码,同学们可用访问【https://gitee.com】进行代码管理

6.2.1、创建组织

登录自己的gitee账号后,在左侧选择【组织】点击【+】号

image-20210428220911960

image-20210428221043307

image-20210428221101744

6.2.2、创建仓库

选择【仓库】—>【所有的】然后点击【+】号

image-20210428214111029

image-20210428214404878

image-20210428213915523

点击【创建】,完成创建

image-20210428214455027

6.2.3、升级账号

主页面选择【企业版】

image-20210428221234649

点击【开始免费使用】

image-20210428221312384

点击【马上升级】

image-20210428221442516

点击【组织升级企业】—>选择刚刚建立的组织点击【升级】

image-20210428221527122

填写验证码,点击【升级】

image-20210428221629830

完成升级,在【我参与的仓库】中含有刚刚升级的项目

image-20210428221717035

6.2.4、项目上传

在【资料\基础代码\restkeeper-super】中有为各位提供的基础代码,这里我们需要把项目代码上传到gitee上:

image-20210428222709590

拷贝【资料\基础代码\restkeeper-super】到你自己的硬盘上,这里我拷贝到【F:\restkeeper\restkeeper-super】盘下

image-20210428222855930

使用IDEA打开【F:\restkeeper\restkeeper-super】项目

image-20210428223523697

image-20210428223555659

下面,我们需要先创建本地仓库

image-20210428224140550

image-20210428224219482

初始化完成后,进行第一次本地提交,提交时间比较长,请耐心等待

image-20210428224502899

关联远程仓库

image-20210428232709379

image-20210428232919038

点击复制,填写入上一步中的URL

image-20210428232807006

先拉取远程仓库代码

image-20210428233138606

image-20210428233223884

==如果出现以下异常==

image-20210428233316646

点击右下角【Git:master】,做合并代码操作

image-20210428233502671

image-20210428233601557

合并完成

image-20210428233717169

提交代码

image-20210428233826400

image-20210428233916977

提交完成

image-20210428233943648

查看远程gitee,可用看见刚刚上传的代码

image-20210428234042974

第二章 项目快速启动

1、基础组件安装

首先想启动餐掌柜SaaS平台,则需要安装下列三方组件:

  • 缓存服务:redis组件
  • 注册配置中心:nacos平台
  • 数据库:Mysql
  • 消息中间件:RabbitMQ/RocketMQ
  • 分布式事务:seata-server
  • 分布式调度中心:xxl-job-admin
  • 反向服务器:nginx

这里我们采用docker-compose来进行安装,需要特别注意的:

宿主机地址请设置为:192.168.200.129, 【宿主机地址设置见课程资料】

1.1、docker安装

  • 卸载老版本docker

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-selinux \
    docker-engine-selinux \
    docker-engine \
    docker-ce
  • 设置仓库

    1
    yum install -y yum-utils device-mapper-persistent-data lvm2
  • 更新本地镜像库

    1
    yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  • 更新镜像源缓存

    1
    2
    3
    sed -i 's/download.docker.com/mirrors.ustc.edu.cn\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo

    yum makecache fast
  • 安装docker

    1
    yum install -y docker-ce
  • 关闭防火墙

    1
    2
    systemctl stop firewalld
    systemctl disable firewalld
  • 启动docker

    1
    systemctl start docker
  • 开机自启动docker

    1
    systemctl enable docker

国内从 DockerHub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务,

当配置某一个加速器地址之后,若发现拉取不到镜像,请切换到另一个加速器地址。国内各大云服务商均提供了 Docker 镜像加速服务,建议根据运行 Docker 的云平台选择对应的镜像加速服务。

阿里云镜像获取地址:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors,登陆后,左侧菜单选中镜像加速器就可以看到你的专属地址了:

img

之前还有 Docker 官方加速器 https://registry.docker-cn.com ,现在好像已经不能使用了,我们可以多添加几个国内的镜像,如果有不能使用的,会切换到可以使用个的镜像来拉取。

修改配置:

1
2
3
4
5
6
7
8
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://3h1x6xgs.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker

1.2、docker-compose安装

  • 安装docker-compose

    注意: 安装时去官网下载最新版本

    1
    curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
  • 修改docker-compose权限

    1
    2
    chmod +x /usr/local/bin/docker-compose
    ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
  • 执行docker-compose version 查看安装情况image-20210502225526789

  • docker-compose –help 仓库其命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    [root@localhost docker-demo]# docker-compose --help
    利用Docker来定义和构建一个多容器的应用

    使用方式:
    docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
    docker-compose -h|--help

    Options:
    -f, --file FILE 指定一个 compose 文件,
    (默认: docker-compose.yml)
    -p, --project-name NAME 指定project名字
    (默认: 目录名称)
    --verbose 显示更多日志
    --log-level LEVEL 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
    -v, --version 打印版本并退出
    -H, --host HOST Daemon socket to connect to
    Commands:
    build 构建多个service
    config 校验 Compose 文件,格式是否正确,若正确则显示配置,
    若格式错误显示错误原因
    down 停止并删除 容器, 网络, 镜像, 和 数据卷
    exec 进入一个指定的容器
    help Get help on a command
    images 列出该Compose中包含的各个镜像
    kill 通过发送 SIGKILL 信号来强制停止服务容器
    格式为 docker-compose kill [options] [SERVICE...]
    logs 查看服务容器的输出日志
    格式为 docker-compose logs [options] [SERVICE...]。
    pause 暂停一个容器
    port 打印某个容器端口所映射的公共端口
    ps 列出项目中目前的所有容器
    pull 拉取服务依赖的镜像
    push 推送服务依赖的镜像到 Docker 镜像仓库
    restart 重启项目中的服务
    rm 删除停止的容器(要先停止容器)
    run 在某个服务上运行指令
    scale 设定某个容器的运行个数
    start 启动多个 services
    stop 停止多个 services
    top 查看各个服务容器内运行的进程。
    unpause 恢复处于暂停状态中的服务。
    up 创建并启动多个service的容器
    version Show the Docker-Compose version information

1.3、初始化组件

上面我们完成docker和docker-compose的安装,下面我们来安装基础组件,首先把【资料\环境搭建\docker】的文件上传到你centos中的/opt目录:

image-20210708230535241

并且为rocketmq文件目录授权:chmod -R 777 rocketmq/

opt目录下运行下列命令,整个创建过程需要一定的时间【5分钟左右】,启动完成执行命令:

1
docker-compose up -d

查看启动情况:

1
docker-compose ps -a

image-20210504205711658

1.3.1、MySQL服务

校验mysql数据库

同步mysql时间

1
docker cp /usr/share/zoneinfo/Asia/Shanghai mysql:/etc/localtime

==账号:root 密码:pass==

image-20210708231011314

1.3.2、Nacos注册中心服务

地址:http://192.168.200.129:8848/nacos

账号:nacos 密码:nacos

image-20210504211146506

1.3.3、消息服务RocketMQ

地址:http://192.168.200.129:8180/#/cluster

image-20210504211300496

1.3.4、xxl-job定时任务平台

地址:http://192.168.200.129:8280/xxl-job-admin/

账号:admin 密码:123456

image-20210504211448340

1.3.5、分布式事务Seata服务

seata-server我们使用的是nacos,只需要在服务列表中看见seata-server

image-20210504211552233

1.3.6、Redis服务

使用RedisDesktopManager连接192.168.200.129端口6379

image-20210708231758147

至此我们的基本组件安装完成

2、运营平台启动

2.1、调用链路

image-20210626164406901

上图为运营平台的调用链路,从中我们可用看到:

  • 用户发起请求访问restkeeper-vue-operator,在vue中有网关设置
  • vue中网关会调用restkeeper-gateway-operator网关,然后由restkeeper-gateway-operator网关路由对应业务系统web项目
  • model-basic-job-listen模块主要负责日志的收集【后面分析】
  • 运营平台中有2个核心web模块:model-security-web【统一权限】、model-basic-web【基础服务】的服务消费方模块
  • 运营平台中有2个核心producer模块:model-security-producer【统一权限】、model-basic-producer【基础服务】的服务提供方模块

我们需要启动上述的6个模块才可把运营平台启动

2.2、服务启动

2.2.1、后端服务启动

找到需要启动的模块下com.itheima.restkeeper包下以***Start结尾的启动类,直接启动即可,例如:

image-20210508174411297

启动后端服务,包含下列6个模块,按下列顺序启动:

  • restkeeper-gateway-operator
  • model-basic-job-listen
  • model-security-producer
  • model-basic-producer
  • model-security-web
  • model-basic-web

image-20210708231959334

为防止内存不够使用可以启动时设置JVM参数:

1
-Xms128M -Xmx128M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M

启动完成访问:http://192.168.200.129:8848/nacos,注意:model-basic-job-listen模块不需要注册到nacos中

image-20210708232103784

2.2.2、运营平台前端项目启动

2.2.2.1、依赖前端环境

前端依赖的环境分别为:nodeJS、cnpm、webpack

安装node.js:https://nodejs.org/zh-cn/,这里我下载的版本为v12.21版本,课程资料中有对应的安装包

image-20210508175014835

安装直接全部点击下一步最后完成安装
安装结束后打开cmd命令窗口 输入以下命令验证是否安装成功,如出现版本号则安装成功

1
2
3
4
#查看node版本 
node -v
#查看npm版本
npm -v

image-20210508175826705

安装cnpm:在cmd命令窗口使用如下命令安装npm的国内镜像cnpm

1
npm install -g cnpm --registry=http://registry.npm.taobao.org

安装webpack:

1
2
cnpm webpack -g
cnpm webpack-cli -g

下载项目依赖js包:

第一次运行项目之前我们需要执行下列命令,安装项目

1
npm install

在cmd窗口中切换到restkeeper-vue-operator项目所在模块:

image-20210508180337540

image-20210508180540761

==【非必须执行】==如果安装中有失败,这边可用在host

image-20210508180639143

中做如下配置,这里是让github更快加载

1
2
3
4
5
140.82.114.4 github.com
199.232.69.194 github.global.ssl.fastly.net
185.199.108.153 assets-cdn.github.com
185.199.110.153 assets-cdn.github.com
185.199.111.153 assets-cdn.github.com
2.2.2.2、 启动项目

Vscode打开项目,使用下列命令启动项目:

1
npm run dev

image-20210508181148417

注意:这里默认打开的访问路径为:http://localhost/#/login,但是我们的系统是基于saas系统,这里需要绑定域名,但是我们没有域名,所以这里需要设置hosts,再次找到hosts

image-20210508180639143

添加如下配置:

1
2
127.0.0.1 www.eehp.cn
127.0.0.1 ppsk.shop.eehp.cn

地址:http://www.eehp.cn

账号:hm@itcast.cn 密码:pass

image-20210508184948933

第三章 项目开发规范

在项目开发过程中,如果开发人员过多又没有一个开发统一规范,在后期的维护、迭代升级的过程中会相当痛苦,也不利于新人接手项目,项目规范是为了统一一个开发标准,让代码易懂便于维护升级,下面我们为整个系统指定规范。

1、基础父类定义

1.1、BasicPojo

各个模块中POJO对象继承的基础父类

隶属模块:framework-mybatis-plus

包路径:com.itheima.restkeeper.basic

作用:所有实体类公共字段的定义

其中结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.itheima.restkeeper.basic;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.itheima.restkeeper.utils.ToString;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

/**
* @Description:实体基础类
*/
@Data
@NoArgsConstructor
public class BasicPojo implements Serializable {

//主键
@JsonFormat(shape = JsonFormat.Shape.STRING)
public Long id;

//分片键
@TableField(fill = FieldFill.INSERT)
@JsonFormat(shape = JsonFormat.Shape.STRING)
public Long shardingId;

//创建时间
@TableField(fill = FieldFill.INSERT)//INSERT代表只在插入时填充
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
public Date createdTime;

//修改时间
@TableField(fill = FieldFill.INSERT_UPDATE)// INSERT_UPDATE 首次插入、其次更新时填充(或修改)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
public Date updatedTime;

//是否有效
@TableField(fill = FieldFill.INSERT)
public String enableFlag;

//构造函数
public BasicPojo(Long id) {
this.id = id;
}
}

1.2、BasicVo

各个模块中VO对象继承的基础父类

隶属模块:framework-vo

包路径:com.itheima.restkeeper.basic

作用:所有VO对象公共字段的定义

其中结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.itheima.restkeeper.basic;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
* @ClassName BasicVo.java
* @Description 基础请求
*/
@Data
@NoArgsConstructor
public class BasicVo implements Serializable {

@ApiModelProperty(value = "主键")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id;

@ApiModelProperty(value = "数据源分片Id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long shardingId;

@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
protected Date createdTime;

@ApiModelProperty(value = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get
protected Date updatedTime;

@ApiModelProperty(value = "是否有效")
protected String enableFlag;

public BasicVo(Long id) {
this.id = id;
}
}

1.3、IBasicEnum

各个模块中枚举对象实现的基础父接口

隶属模块:framework-vo

包路径:com.itheima.restkeeper.basic

作用:枚举对象实现的基础方法定义

其中结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.itheima.restkeeper.basic;

/**
* @Description:枚举接口
*/
public interface IBasicEnum {

//编码
public String getCode();

//信息
public String getMsg();

}

2、统一返回数据

2.1、ResponseWrap

所有对前端接口的返回对象

隶属模块:framework-vo

包路径:com.itheima.restkeeper.basic

作用:定义返回对象的包装

其中结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.itheima.restkeeper.basic;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

/**
* @Description 返回结果
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResponseWrap<T> implements Serializable {

//响应返回编码
@ApiModelProperty(value = "状态码")
private String code;

//响应返回信息
@ApiModelProperty(value = "状态信息")
private String msg;

//返回结果
@ApiModelProperty(value = "返回结果")
private T datas;

//操作用户
@ApiModelProperty(value = "操作人ID")
private Long userId;

//操作用户名称
@ApiModelProperty(value = "操作人姓名")
private String userName;

//创建时间,处理json的时间参数解析
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss",timezone = "GMT+8")
@ApiModelProperty(value = "操作时间")
private Date operationTime;

}

2.2、ResponseWrapBuild

构造ResponseWrap工具

隶属模块:framework-commons

包路径:com.itheima.restkeeper.utils

作用:构造ResponseWrap工具,并且从UserVoContext上下文中获取当前登录对象作为操作人信息填入

其中结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.itheima.restkeeper.utils;

import com.alibaba.fastjson.JSONObject;
import com.itheima.restkeeper.basic.IBasicEnum;
import com.itheima.restkeeper.basic.ResponseWrap;
import com.itheima.restkeeper.req.UserVo;

import java.util.Date;

/**
* @Description 构造ResponseWrap工具
*/
public class ResponseWrapBuild {

public static <T>ResponseWrap<T> build(IBasicEnum basicEnumIntface, T t){

//从UserVoContext中拿到userVoString
String userVoString = UserVoContext.getUserVoString();
UserVo userVo = null;
if (!EmptyUtil.isNullOrEmpty(userVoString)){
userVo = JSONObject.parseObject(userVoString, UserVo.class);
}else {
userVo = new UserVo();
}
//构建对象
return ResponseWrap.<T>builder()
.code(basicEnumIntface.getCode())
.msg(basicEnumIntface.getMsg())
.operationTime(new Date())
.userId(userVo.getId())
.userName(userVo.getUsername())
.datas(t)
.build();
}

}

3、vo与pojo

VO :value object 值对象 / view object 表现层对象,

主要职能作用:

  • 对应页面显示的数据对象
  • 页面请求参数封装
  • dubbo层调用多个服务,服务返回结果封装

例如:

  • 用户信息 —>【用户服务】返回VO +【附件服务】返回图片Vo

POJO :plain ordinary java object 无规则简单java对象,在餐掌柜中严格遵循POJO的实体类结构使用【驼峰规则】映射数据库字段

3.1、作用范围

以restkeeper-model-security模块为例,一个标准的模块其模块结构如下

1
2
3
4
5
|——restkeeper-model-security    统一权限模块
|———— model-security-interface dubbo接口定义层【被服务提供方、服务消费方依赖】
|———— model-security-producer dubbo接口实现【服务提供方】
|———— model-security-service 核心业务开发【最小开发单元】
|———— model-security-web 对外接口服务层【服务消费方】

VO和POJO的负责区域入下图所示:

image-20210501000248157

其中model-security-producer是VO与POJO对象转换层

3.2、对象转换工具

VO对象与POJO对象属成员变量拷贝工具

隶属模块:framework-mybatis-plus

包路径:com.itheima.restkeeper.basic

作用:VO对象与POJO对象属成员变量拷贝工具

注意:VO对象与POJO对象属成员变量【类型】和【成员变量名称】必须保持一致,支持父类属性拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.itheima.restkeeper.utils;

import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.converter.BidirectionalConverter;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.Type;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

/**
* @Description 对象转换工具
*/
@Slf4j
public class BeanConv {

private static MapperFacade mapper;

private static MapperFacade notNullMapper;

static {
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter(new LocalDateTimeConverter());
converterFactory.registerConverter(new LocalDateConverter());
converterFactory.registerConverter(new LocalTimeConverter());
mapper = mapperFactory.getMapperFacade();
MapperFactory notNullMapperFactory =
new DefaultMapperFactory.Builder().mapNulls(false).build();
notNullMapper = notNullMapperFactory.getMapperFacade();
}

private static class LocalDateTimeConverter
extends BidirectionalConverter<LocalDateTime, LocalDateTime> {

@Override
public LocalDateTime convertTo(
LocalDateTime localDateTime,
Type<LocalDateTime> type,
MappingContext mappingContext) {
return LocalDateTime.from(localDateTime);
}

@Override
public LocalDateTime convertFrom(
LocalDateTime localDateTime,
Type<LocalDateTime> type,
MappingContext mappingContext) {
return LocalDateTime.from(localDateTime);
}
}
private static class LocalDateConverter
extends BidirectionalConverter<LocalDate, LocalDate> {

@Override
public LocalDate convertTo(
LocalDate localDate,
Type<LocalDate> type,
MappingContext mappingContext) {
return LocalDate.from(localDate);
}

@Override
public LocalDate convertFrom(
LocalDate localDate,
Type<LocalDate> type,
MappingContext mappingContext) {

return LocalDate.from(localDate);
}
}
private static class LocalTimeConverter
extends BidirectionalConverter<LocalTime, LocalTime> {

@Override
public LocalTime convertTo(
LocalTime localTime,
Type<LocalTime> type,
MappingContext mappingContext) {
return LocalTime.from(localTime);
}

@Override
public LocalTime convertFrom(
LocalTime localTime,
Type<LocalTime> type,
MappingContext mappingContext) {
return LocalTime.from(localTime);
}
}

/**
* 复制对象所有属性
* @param source 源对象
* @param destination 目标对象
*/
public static void toBean(Object source, Object destination) {
mapper.map(source, destination);
}

/**
* 复制对象非null属性
*
* @param source 源对象
* @param destination 目标对象
*/
public static void toBeanNotNull(Object source, Object destination) {
notNullMapper.map(source, destination);
}

/**
* 深度复制对象
* @param source 源对象
* @param destinationClass 目标类型
* @return 复制出的目标对象
*/
public static <T> T toBean(Object source, Class<T> destinationClass) {
if (EmptyUtil.isNullOrEmpty(source)){
return null;
}
return mapper.map(source, destinationClass);
}

/**
* 复制List
* @param sourceList 源List
* @param destinationClass 目标List的元素类型
* @return 复制出的目标List
*/
public static <T> List<T> toBeanList(List<?> sourceList, Class<T> destinationClass) {
if (EmptyUtil.isNullOrEmpty(sourceList)){
return null;
}
return mapper.mapAsList(sourceList,destinationClass);
}

}

单个对象拷贝:

image-20210501002410579

List对象拷贝:

image-20210501002526150

4、异常处理

4.1、统一异常处理

在项目开发过程中,异常是需要统一,如图所示,service—>producer—>interface—>web逐层向上抛出:

image-20210501135422934

首先,自定义异常类ProjectException

隶属模块:framework-web

包路径:com.itheima.restkeeper.exception

作用:统一异常处理自定义异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.itheima.restkeeper.exception;

import com.itheima.restkeeper.basic.IBasicEnum;

/**
* @Description:自定义异常
*/
public class ProjectException extends RuntimeException {

//错误编码
private String code;

//提示信息
private String message;

//异常接口
private IBasicEnum basicEnumIntface;

public ProjectException() {
}

public ProjectException(IBasicEnum basicEnumIntface) {
this.code = basicEnumIntface.getCode();
this.message = basicEnumIntface.getMsg();
this.basicEnumIntface = basicEnumIntface;
}

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

@Override
public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public IBasicEnum getBasicEnumIntface() {
return basicEnumIntface;
}

public void setBasicEnumIntface(IBasicEnum basicEnumIntface) {
this.basicEnumIntface = basicEnumIntface;
}

@Override
public String toString() {
return "ProjectException{" +
"code='" + code + '\'' +
", message='" + message + '\'' +
'}';
}
}

再定义BaseController类采用SpringMVC的统一异常处理,使用@ControllerAdvice、@ExceptionHandler注解,优雅的处理异常

隶属模块:framework-web

包路径:com.itheima.restkeeper.web

作用:统一异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.itheima.restkeeper.web;

import com.alibaba.fastjson.JSON;
import com.itheima.restkeeper.basic.ResponseWrap;
import com.itheima.restkeeper.enums.BasicEnum;
import com.itheima.restkeeper.exception.ProjectException;
import com.itheima.restkeeper.utils.ResponseWrapBuild;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @ClassName BaseController.java
* @Description 基础的controller
* ControllerAdvice:对controller层的增强,其他的controller则不需要继承,也会被拦截处理
*/
@ControllerAdvice
public class BaseController {

//表示当请求发生异常时,被ExceptionHandler注释的方法会去处理
@ExceptionHandler
public void ExceptionHandler(Exception ex, HttpServletResponse response) throws IOException {
ResponseWrap<Object> responseWrap = null;
//自定义异常
if (ex instanceof ProjectException){
ProjectException projectException = (ProjectException) ex;
responseWrap = ResponseWrapBuild.build(projectException.getBasicEnumIntface(), null);
}else {
//系统异常
responseWrap = ResponseWrapBuild.build(BasicEnum.SYSYTEM_FAIL, null);
}
//编码防止中文问题
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(responseWrap));
}
}

4.2、异常枚举

为了规范异常处理后返回的信息,我们为每一个模块定义了异常处理枚举:

隶属模块:framework-vo

包路径:com.itheima.restkeeper.enums

作用:各模块的异常枚举

image-20210501142443969

需要注意,每个enum都需要实现IBasicEnum接口,以UserEnum为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.itheima.restkeeper.enums;

import com.itheima.restkeeper.basic.IBasicEnum;

/**
* @ClassName UserEnum.java
* @Description TODO
*/
public enum UserEnum implements IBasicEnum {
SUCCEED("200","操作成功"),
LOGOUT_SUCCEED("1004","退出成功"),
FAIL("1000","操作失败"),
PAGE_FAIL("70005", "查询用户列表失败"),
CREATE_FAIL("70007", "保存用户失败"),
UPDATE_FAIL("70008", "修改用户失败"),
DELETE_FAIL("70009", "修改用户失败"),
SELECT_USER_FAIL("70010", "查询用户失败"),
SELECT_ROLE_FAIL("70011", "查询用户对应角色失败"),
SELECT_RESOURCE_FAIL("70012", "查询用户对应资源失败"),
SELECT_CURRENT_USER("70013", "查询当前用户失败"),
SELECT_USER_LIST_FAIL("70014", "查询用户list失败"),
;

private String code;
private String msg;

UserEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}

public String getCode() {
return code;
}

public String getMsg() {
return msg;
}

}

第四章 springcloud-alibaba-dubbo

1、架构演进概述

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

image

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

2、Dubbo架构

下面我们看下dubbo架构

dubbo-architucture

名词解释

节点 角色说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

调用关系说明

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向添加中心添加自己提供的服务。
  • 服务服务消费方在启动时,向添加中心订阅自己所需的服务。
  • 添加中心返回服务提供者地址列表给服务消费方,如果有变更,添加中心将基于长连接推送变更数据给服务消费方。
  • 服务服务消费方,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务服务消费方和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

3、dubbo快速入门

上面我们介绍了dubbo的架构,下面我们来使用dubbo来进行开发,在开发之前,我们先看下面的图解:

image-20210510090307452

从上图我们可用看出在一个标准的dubbo服务调用中,他分为3个部分

dubbo-interface

负责接口的定义,这里我们通常定义***Face结构的接口类,例如:UserFace

dubbo-producer

【服务提供方】负责接口的实现,这里我们通常用==@DubboService==定义***FaceImpl结构的接口类,例如:UserFaceImpl

dubbo-web

【服务消费方】负责调用接口,通常我们在web层使用==@DubboReference==调用接口

下面我们来构建第一个dubbo服务,我们需要在dubbo-parent中pom.xml引入下列依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependencies>
<!--接口定义层-->
<dependency>
<groupId>com.itheima.dubbo</groupId>
<artifactId>dubbo-interface</artifactId>
<version>${interFace.version}</version>
</dependency>
<!---spring-cloud-alibaba主配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!---springboot主配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>

在dubbo-producer和dubbo-web中pom.xml导入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<!--接口定义层-->
<dependency>
<groupId>com.itheima.dubbo</groupId>
<artifactId>dubbo-interface</artifactId>
</dependency>
<!--web支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos支持-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--dubbo支持-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
</dependencies>

3.1、定义接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima.dubbo;

/**
* @Description: 定义接口
* @Version: V1.0
*/
public interface UserFace {

/**
* 定义远程调用方法
* @return
*/
public String hello(String username);

}

3.2、服务提供方实现

服务提供方:dubbo-producer负责服务的提供,我们需要把他添加到nacos添加中心中

(1)application.yml添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#服务配置
server:
#端口
port: 8080
#服务编码
tomcat:
uri-encoding: UTF-8
#spring相关配置
spring:
#应用配置
application:
#应用名称
name: dubbo-producer
main:
allow-bean-definition-overriding: true
cloud:
#nacos添加中心
nacos:
discovery:
server-addr: 192.168.200.129:8848
dubbo:
#dubbo服务版本
application:
version: 1.0.0
logger: slf4j
cloud:
#表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割,如果不消费服务,那么""。
subscribed-services: model-shop-producer,model-basic-producer
#dubbo接口扫描路径
scan:
base-packages: com.itheima.dubbo
#dubbo服务添加
registry:
address: spring-cloud://192.168.200.129
#dubbo服务协议类型及端口,线程数【这里是默认配置】
protocol:
name: dubbo
port: 28080
threads: 200
accesslog: logs/dubbo-producer-01.log

(2)实现dubbo-interface的Userface接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itheima.dubbo.service;

import com.itheima.dubbo.UserFace;
import org.apache.dubbo.config.annotation.DubboService;

/**
* @Description:
* @Version: V1.0
*/
@DubboService(version = "${dubbo.application.version}", timeout = 5000)
public class UserFaceImpl implements UserFace {
/**
* 定义远程调用方法
* @param username
* @return
*/
@Override
public String hello(String username) {
return "Hello SpringCloud Alibaba Dubbo " + username;
}
}

3.3、服务消费方实现

服务消费方:dubbo-web负责服务的接口消费,我们需要把他添加到nacos添加中心中

(1)application.yml配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#服务配置
server:
#端口
port: 8081
#服务编码
tomcat:
uri-encoding: UTF-8
#spring相关配置
spring:
#应用配置
application:
#应用名称
name: dubbo-web
main:
allow-bean-definition-overriding: true
#nacos添加中心
cloud:
nacos:
discovery:
server-addr: 192.168.200.129:8848

#dubbo消费端配置
dubbo:
application:
version: 1.0.0
logger: slf4j
cloud:
#表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。
subscribed-services: dubbo-producer
scan:
#扫描路径
base-packages: com.itheima.dubbo.web
registry:
address: spring-cloud://192.168.200.129
#dubbo服务协议类型及端口,线程数【这里是默认配置】
protocol:
name: dubbo
port: 28081
threads: 200
accesslog: logs/dubbo-web-01.log

(2)调用dubbo-interface的Userface接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.itheima.dubbo.web;

import com.itheima.dubbo.UserFace;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Description:
* @Version: V1.0
*/
@RestController
@RequestMapping("user")
public class UserController {

@DubboReference(version = "${dubbo.application.version}", check = false)
private UserFace userFace;

@GetMapping("{userName}")
public String helloUser(@PathVariable("userName") String userName){
return userFace.hello(userName);
}
}

3.4、测试

访问Nacos地址:http://192.168.200.129:8848/nacos/

image-20210706175940629

可以点击 详情 查看服务注册的详细内容:

image-20210706180249400

启动dubbo-producer和dubbo-web模块,访问:http://127.0.0.1:8081/user/itheima

image-20210706180350207

4、业务模块开发流程初识

4.1、业务调用链路

在开始业务开发之前,我们首先看一下系统的调用链路,以 restkeeper-model-shop 模块为例,其调用的时序图如下所示:

image-20210501145955490

以restkeeper-model-shop模块为例,一个标准的模块其模块结构如下

1
2
3
4
5
6
7
8
|——restkeeper-model-shop	  商家服务平台
|———— model-shop-applet H5点餐小程序dubbo接口实现【服务提供方】
|———— model-shop-interface 商家平台所有dubbo接口定义
|———— model-shop-job-listen 商家服务平台定时任务及监听模块【监听消费、定义任务】
|———— model-shop-producer 后端业务dubbo接口实现【服务提供方】
|———— model-shop-service 核心业务层【被所有服务提供方、服务消费方、监听、定时任务依赖】
|———— model-shop-user 用户业务依赖于model-security-service的业务实现【服务提供方】
|———— model-shop-web 对外商家服务平台web层,被restkeeper-gateway-shop系统调用【服务消费方】

4.2、服务提供方分析

在restkeeper-model-shop模块中有3个【提供者】模块:

  • model-shop-applet
  • model-shop-producer
  • model-shop-user

这里以model-shop-producer为例,首先查看模块依赖关系:

image-20210514150936530

其中 model-shop-producer 模块他有以下职能:

1
2
3
- dubbo微服务【提供方】
- 【本地】调用service【核心业务层】实现dubbo服务接口的业务逻辑
- 对象转换:从POJO对象转换为VO对象

==注意:餐掌柜中为避免模块职能混乱,禁止服务提供方模块调用服务提供方模块,当产生跨服务接口调用,例如一个接口需要多个接口来支持,我们会放到web层进行服务调用然后业务组装,如果牵涉分布式事务问题,我们会采用seata方式来解决==

下面我们对model-shop-producer进行dubbo的集成,首先在model-shop-producer的pom.xml导入下列依赖:

1
2
3
4
5
<!-- Dubbo Spring Cloud Starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

再在model-shop-producer的application.yml添加定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dubbo:
#dubbo应用服务定义
application:
#版本
version: 1.0.0
#日志
logger: slf4j
scan:
#扫描路径
base-packages: com.itheima.restkeeper
registry:
#添加中心:这里采用nacos添加中心
address: spring-cloud://192.168.200.129
#服务协议定义
protocol:
#服务协议名称
name: dubbo
#协议端口
port: 27077
#线程数
threads: 200
#dubbo调用日志
accesslog: logs/model-shop-producer-01.log

4.3、服务消费方分析

在restkeeper-model-shop模块中有1个【服务消费方】模块:model-shop-web,这里以model-shop-web为例,首先查看模块依赖关系:

image-20210514154417356

其中model-shop-web模块他有以下职能:

1
2
3
4
- 传入参数的接收及校验工作
- 调用对应业务的dubbo服务,本身不负责业务逻辑的处理【服务消费方】
- 返回参数的封装,以及当下层发生异常,则抛出指定的自定义异常
- 定义swagger2的接口暴露,方便测试

下面我们对model-shop-web进行dubbo的集成,首先在model-shop-producer的pom.xml导入下列依赖:

1
2
3
4
5
<!-- Dubbo Spring Cloud Starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

再在model-shop-web的application.yml添加定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
dubbo:
#dubbo应用服务定义
application:
#版本
version: 1.0.0
#日志
logger: slf4j
cloud:
#表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。
subscribed-services: model-shop-producer,model-basic-producer,model-shop-applet,model-shop-user,model-trading-producer
scan:
#扫描路径
base-packages: com.itheima.restkeeper.web
registry:
#添加中心
address: spring-cloud://192.168.200.129
#服务协议定义
protocol:
#服务协议名称
name: dubbo
#协议端口
port: 27078
#线程数
threads: 200
#dubbo调用日志
accesslog: logs/model-shop-web-01.log

相比【服务提供方】来说【服务消费方】就多subscribed-services属性配置,此属性为订阅服务的服务名

4.4、服务接口定义

可能在以往的开发中我们只是知道三层架构【mapper、service、web】,那这里的face层是什么意思呢?大家都知道dubbo服务的调用逻辑,【服务消费方】调用【提供方】,那他们直接能调用的集成也就是声明统一的接口定义,在餐掌柜系统中dubbo层接口就起到此作用:

1
2
3
- 定义dubbo服务接口
- 被服务提供方依赖,按照face层的dubbo接口定义实现业务
- 被服务消费方依赖,从face层的dubbo中选择自己的业务接口

首先我们需要定义一个dubbo接口,那我们在哪里写能?从餐掌柜maven分层构建中我们可用发现,每个以restkeeper-model-*** 开头的项目都是一个二级模块,并且模块中都有一个model-***-interface的模块,例如:

image-20210501155101989

没有错,这里就是我们定义face接口的三级模块,在定义dubbo接口的时,都需要找到类似:model-***-interface的模块去书写

4.5、service核心业务

上面我们再介绍【dubbo层接口实现】提到dubbo层接口实现会调用核心业务,这个核心也就是这里的service层,还是以restkeeper-model-shop为例:

image-20210501172119155

如果模块以model-***-service的格式出现,则表示此模块为核心模块,职能:

1
2
3
4
- pojo、mapper、service层定义
- 使用mybatis-push持久化框架完成CRUD操作
- 作为【核心业务层】被dubbo服务接口实现所调用
- 提供代码生成器的支持

商家平台启动依赖:

image-20210627160348232