微服务的服务拆分及远程调用

目录

前言

1. 服务拆分

1.1 服务的拆分原则

1.2 服务拆分时机

1.3 拆分案例

2.服务的远程调用

 2.1 服务的调用需求

2.2 总结


前言

微服务是一种良好的分布式架构方案。它将一个项目,拆分成不同的服务模块,使得服务更加独立,服务与服务之间的耦合度更低,减少了程序员重复代码的工作量。但也是因为这种拆分,使得项目的架构更加复杂,运维、监控、部署的难度都有所提高。正是因为它是一把双刃剑,它的拆分原则和拆分时机都很重要。

1. 服务拆分

1.1 服务的拆分原则

这里我总结了几个微服务的拆分原则

1.单一职责:每个微服务都有自己的职责范围,且各个服务之间的职责范围不可重复,这也包括了数据库、缓存和消息队列的独立。

2.高内聚、低耦合:相似逻辑的组件应该放在同一个微服务中,降低与其他服务的耦合度,便于独立的开发测试和维护。

3.可复用性:每个微服务都可以将自己的业务暴露为接口,供其他微服务的调用,避免重复开发的情况。

4.可扩展性:每个微服务都应该很容易水平或垂直扩展,以响应对网络、处理数据、数据库访问等请求,以应对大流量、高并发的业务场景。

5.轻量级通信:服务之间通过RESTful API或RPC框架,实现服务间互相协作的轻量级通信机制。

不管是哪种拆分原则,目标都是需要将相同或相似的服务聚合在一起,形成一个独立的自治服务。

1.2 服务拆分时机

微服务的拆分首先是由一个痛点来驱动的,是业务真正遇到了快速版本迭代和高并发问题,如果不拆分,整个项目的发展都会受到影响,只有在这个时候,微服务的拆分是确定有收益的,增加的运维成本才是值得的。这里我试着总结了几个拆分时机

1.2.1 提交代码频繁出现大量冲突

如果是一个大型项目,几百个人同时开发,同一个模块好几个人在改,如果是使用GIT来做代码管理,就经常会遇到代码提交冲突的事情。恰好你就是最后一个提交的人,你就有责任merge代码,好不容易merge成功了,再提交发现又出现了冲突,本来马上能够完成的工作,硬生生被代码冲突浪费了半天时间,你是不是很恼火。

所以这时候就应该拆分,不超过十个人来维护一个模块,首先代码冲突的概率小了,其次就算有冲突,他就在你旁边,吼一嗓子基本上问题就解决了。

每个模块对外提供接口,其他依赖模块可以不用关注具体的代码实现,只要保证接口正确就可以了。

1.2.2 小功能要累积到大版本才能上线

微服务对于快速迭代的效果,最明显的就是上线独立。当你修改了一个边角的小功能,但你不敢马上上线,因为你依赖的其他模块才开发了一半,你要等他,等他好了,也不敢马上上线,他依赖的模块还没有完全开发完成,如此往复,最后谁都没办法独立上线,大家一起开会约定一个时间,无论功能大小,死活都要这天上线。

这种模式上线,单次上线的需求列表非常长,风险也会比较大,可能小功能的错误会导致大功能的上线不正常,需要一点点check,这样上线时间长,影响范围还大。

服务拆分后,在团队职责明确、应用边界明确、接口稳定的情况下,不同的模块可以独立上线。这样上线的次数增多,单次上线的需求列表变小,可以随时回滚,风险变小,时间变短,影响面小,从而迭代速度加快。对于接口要升级部分,保证灰度,先做接口新增,而非原接口变更,当注册中心中监控到的调用情况,发现接口已经不用了,再删除。

1.2.3 横向扩展流程复杂

主要业务和次要业务耦合,有的业务是需要扩容的,有的业务时不需要扩容的,如果一起扩容,消耗的资源可能是拆分后的几倍,成本可能多出几个亿,而且由于配置复杂,在同一个工程里面,往往在配置文件中是这样组织的,这一块是这个模块的,那一块是那个模块的,这样扩容的时候,一些边角的业务也是需要对配置进行详细审查的,否则也不敢贸然扩容。

1.2.4 熔断降级全靠if-else

有时候为了保证核心业务流程,边角的业务流程人工设置为降级的状态,也就是默认不调用,将所有的资源用于核心业务流程,如果核心业务流程和变焦业务流程在同一个进程里的话,就需要使用大量的if-else语句,根据下发的配置来判断是否熔断或者降级,这会使得配置异常复杂,难以维护。

1.3 拆分案例

以我编写的cloud-demo为例,其结构如下:

 cloud-demo:父工程,管理依赖

  • order-service:订单模块,负责订单相关业务
  • user-service:用户模块,负责用户相关业务

demo要求:

  • 订单模块和用户模块都必须有各自的数据库,相互独立
  • 订单模块和用户模块都对外暴露Restful的接口
  • 订单模块如果需要查询用户信息,只能调用用户模块的Restful接口,不能直接查询用户的数据库

 1.3.1 初始化数据库

为了模拟微服务的应用场景,需要创建两个库,创建两张表,如下:

ps:我这里用到的工具是navicat premium 15.0.17mysql 8.0.34

创建tb_usertb_order两张表的sql语句如下:

用户模块的用户信息表单(tb_user):

----设置字符集
SET NAMES utf8mb4;

----关闭外键约束检查
SET FOREIGN_KEY_CHECKS = 0;

----创建用户信息表 tb_user
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人',
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

----插入用户数据
INSERT INTO `tb_user` VALUES (1, '柳岩', '湖南省衡阳市');
INSERT INTO `tb_user` VALUES (2, '文二狗', '陕西省西安市');
INSERT INTO `tb_user` VALUES (3, '华沉鱼', '湖北省十堰市');
INSERT INTO `tb_user` VALUES (4, '张必沉', '天津市');
INSERT INTO `tb_user` VALUES (5, '郑爽爽', '辽宁省沈阳市大东区');
INSERT INTO `tb_user` VALUES (6, '范兵兵', '山东省青岛市');

----打开外键约束检查
SET FOREIGN_KEY_CHECKS = 1;

订单模块的订单信息表单(tb_order):

----设置字符集
SET NAMES utf8mb4;

----关闭外键约束检查
SET FOREIGN_KEY_CHECKS = 0;

----创建订单信息表tb_order
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `user_id` bigint(20) NOT NULL COMMENT '用户id',
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称',
  `price` bigint(20) NOT NULL COMMENT '商品价格',
  `num` int(10) NULL DEFAULT 0 COMMENT '商品数量',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

----插入订单数据
INSERT INTO `tb_order` VALUES (101, 1, 'Apple 苹果 iPhone 12 ', 699900, 1);
INSERT INTO `tb_order` VALUES (102, 2, '雅迪 yadea 新国标电动车', 209900, 1);
INSERT INTO `tb_order` VALUES (103, 3, '骆驼(CAMEL)休闲运动鞋女', 43900, 1);
INSERT INTO `tb_order` VALUES (104, 4, '小米10 双模5G 骁龙865', 359900, 1);
INSERT INTO `tb_order` VALUES (105, 5, 'OPPO Reno3 Pro 双模5G 视频双防抖', 299900, 1);
INSERT INTO `tb_order` VALUES (106, 6, '美的(Midea) 新能效 冷静星II ', 544900, 1);
INSERT INTO `tb_order` VALUES (107, 2, '西昊/SIHOO 人体工学电脑椅子', 79900, 1);
INSERT INTO `tb_order` VALUES (108, 3, '梵班(FAMDBANN)休闲男鞋', 31900, 1);

----打开外键约束检查
SET FOREIGN_KEY_CHECKS = 1;

 cloud-user库下的tb_user表数据如下:

 cloud-order库下的tb_order表数据如下:

1.3.2 项目具体情况

下面的是我的百度网盘,里面有这次案例的demo和用的软件,如果有需要都可以免费下载

链接:https://pan.baidu.com/s/1SYwisZZ37msXztZUvtQFdQ?pwd=1234

提取码:1234

1.3.2.1 导入demo工程

项目结构如下:

导入后,你可以点击左下角的服务(service),就会出现下面的情况:

 配置下项目使用的JDK,我这里使用的是JDK19,你也可以使用JDK1.8的版本,上面的百度网盘有我现在使用的JDK版本,有需要可以自取,当然我的idea是中文版,如果你的不是,你可以在插件里下载一个Chinese:

2.服务的远程调用

在一些项目当中,我们常需要服务与服务之间的互相调用,这种远程调用具体是怎么实现的呢?我会通过刚刚导入的demo讲一讲。在order-service服务中,有一个根据id查询订单的接口:

根据id查询订单,返回值是Order对象,如图:

返回出来的user为null

PS:如果你是第一次发起启动报错了,demo里面的两个service都有一个application.yml同名文件,里面配置了数据库的端口,密码之类的信息,你可以检查一下,是否跟你本地的一致。

 2.1 服务的调用需求

在上述接口里,我们只返回了订单数据库里的信息,如果我们需要订单接口把用户数据库里,用户的信息也返回出来,就需要在order-service服务中,调用user-service查询用户信息的接口,如下图:

总体来说是分为两步,第一步,在user-service中写一个根据userId查询用户信息的接口,能够返回用户的数据,第二步,修改order-service中的根据id查询订单业务接口,要求在查询订单的同时调用用户模块的接口,根据订单中包含的userId查询出用户信息,一起返回。

2.1.1 实现user-service根据id查询用户信息

在user-service中写一个根据userId查询用户信息的接口,这个demo中已经有了,如下图:

查询的结果如图所示:

2.1.2 修改order-service中的根据id查询订单业务接口

现在我们来思考一个问题,在浏览器中,我们是怎么来实现远程调用的?只要在浏览器中输入一个远程调用的地址,按下一个回车键,就是发起了一个http请求,这时候用户模块在接收到请求就会去数据库中查询数据,然后把对应的用户信息返回给浏览器。如果浏览器可以发起http请求,那么只要我们的订单模块发起一个http请求给用户模块,那么服务和服务之间的远程调用是不是就可以实现了,如下图:

那么要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一起返回。我们只需要在order-service中向user-service发起一个http的请求,调用http://localhost:8081/user/{userId}这个接口。

大概的步骤是这样的:

  • 注册一个RestTemplate的实例到Spring容器
  • 修改order-service服务中的OrderService类中queryOrderById方法,根据Order对象中的userId查询User
  • 将查询的User填充到Order对象,一起返回

 2.1.2.1 注册RestTemplate

RestTemplate是spring提供给我们发起http请求用的,那么只要我们在order-service中注入RestTemplate对象,就可以在任何地方使用这个对象了,因为bean的注入是发生在模块启动时的动作,那么我们只要在order-service模块入口的地方注入就可以了,如下图:

 2.1.2.1 实现远程调用

修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法:

PS:restTemplate.getForObject(url, User.class),因为用户模块用的是get请求,所以用的getForObject(url, User.class)方法,如果你用的是post请求,用的就是postFoeObject(),后面的User.class是返回的数据类型。

 这时候返回出来的数据如下图:

2.2 总结

微服务之间的远程调用方式,是基于RestTemplate发起的http请求实现远程调用,RestTemplate他能够发起各种类型的请求调用,比如get和post,同时能够帮我们将返回的json数据直接转换成我们需要的对象类型。http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值