Dubbo学习系列之六(微服务架构实战)
看了最近文章的反馈,似乎波澜不惊的样子,应该是看官觉得都是小菜,那我就直上硬菜,人狠话不多,开始!
准备:Idea201902/JDK11/ZK3.5.5/Gradle5.4.1/RabbitMQ3.7.13/Mysql8.0.11/Lombok0.26/Erlang21.2/postman7.5.0
难度:新手--战士--老兵--大师
目标:1,模拟商城系统,订单服务RPC调用库存服务,同时物流服务 RPC调用订单服务(订单服务双重身份) 2.订单服务通过MQ消息机制与物流服务通信
步骤:
1.整体架构分析:
三服务:订单business,库存stock,物流logistics,business服务两个核心方法,一是未付订单处理,二是支付订单处理。stock服务一个核心方法,更新库存。logistics服务一个存快递单核心方法。四模块:common是公共模块,business
2.数据库设计,具体见sql脚本,5个table,其中delivery是快递,item是商品,order_detail是订单商品明细,
来个代表,order表字段设计,id做PK,BIGINT自增,order_id做订单号,为啥这么设计?id仅是PK,自增,有利于mysql的索引查询效率,mysql是按PK有序存储,pk简单有序,非常利于索引树查找,order_id具有业务含义,变化,值复杂,兼职做PK效率不高,反例就是直接使用自增id做订单号,等需要DB迁移时,就呵呵了,试想迁入的DB也有自增id字段。。。
3.mbg生成, RabbitMQ,Cors,统一异常,统一响应,均见往期文章,
4.改造下之前的MQ组件,com.biao.mall.common.service.MsgConsumer,将其中的URL变量去掉,放到sendPost参数中,注意这里是个json字符串,这样的妙处就是MsgConsumer完全作为一个独立的收消息组件了,只要有MQ消息,就处理,并根据json字符串的URL,发往目标URL,“高内聚,低耦合”,完美!反例就是将MQ消息接收放到各服务端。
5.写了两个BO(BusinessObject),业务对象,用于抽象对应真实的业务场景,只有与具体实际业务相关的属性。好处就是减少信息暴露风险,瘦身传输对象,还有如VO,DTO等,如下为OrderBO:
6. 写com.biao.mall.common.service.DubboOrderService,
两个方法,因是update,都加了事务注解,自动使用spring的事务管理,
7.写DubboOrderService实现,
com.biao.mall.business.impl.DubboOrderServiceImpl
只看重点:保存未付款订单方法,
前端传来一个orderBO,生成订单流程:使用雪花算法取得order_id,(见下节文章),然后就是写入数据库一个订单。常见的就是一个订单里多个商品,所以for循环处理,生成多个orderDetail,保存进DB。mbp的CRUD很方便,拿来就用,这里的 stockService即是RPC调用对象,不是spring的bean注入模式。
库存处理要注意,先看看剩余量,搞出库存负数就要和boss说拜拜了!
减库存模式:下单即减,下单加锁。下单即减,就直接减掉库存了,订单取消再返回来,简单粗暴。下单加锁,下单即锁住部分库存,不能用,但库存的数量包括锁住的,订单取消,只是取消锁住的部分。适用场景不同,下单即减在库存充足使用,下单加锁在秒杀,库存有限使用。我采用使用锁模式。12306就是典型的锁模式。
8.那既然调用到了stock服务,就来写service:
com.biao.mall.common.service.DubboStockService
updateStockRPC是DubboOrderServiceImpl中调用的,还有个updateStock,开发过程中废弃了,因不能RPC使用,因为参数QueryWrapper不支持RPC传递,MBP的缺陷?!
9.写stock服务的实现
com.biao.mall.stock.impl.DubboStockServiceImpl
注意点:updateById和update都是dao方法,但前者每次只更新一个,后者,呵呵,qw为null的话,全表update,务必保证qw非空!
10.再回来写com.biao.mall.business.impl.DubboOrderServiceImpl
订单付款处理:增加了快递服务的MQ逻辑,直接写入URL,并封装进消息。
for循环展开,里面一个细节:不需要if判断了?留给看官吧,
11. 写快递服务:com.biao.mall.common.service.DubboDeliveryService
12,写快递logistics服务com.biao.mall.logistic.impl.DubboDeliveryServiceImpl
的实现:RPC调用方式,
13.最后各模块的展开的全家福:
13.主体流程应该是:
14. 让马儿欢快地跑起来!启动:zk-->business-->stock-->logistics,可以看到服务已经成功注册到zk:
15.postman给个测试,模拟前端:第一条流程
16.订单数据库:
其他数据库就不一一细看了,
17.第二条流程测试:可以看到响应的是一个我们封装的对象。
18.项目源码地址:其中的day08
https://github.com/xiexiaobiao/dubbo-project.git
项目复盘记:
1.这个demo做出来,花了5个晚上,完全是从New-->Project开始的,一句话:纸上得来终觉浅,绝知此事要躬行!
2.dubbo的微服务架构,其实最核心就是这些,实际生产中就是服务更多,通信方式更多,并常有集群和负载均衡,其他一些中间件等。
3.Lombok不兼容典型表现,虽然实体类加了@Data,
4.既然是老兵级别,我想说些细节:a.字符串判空用StringUtil.isEmpty(); b.对象比较用Object.equals(), c.时间属性全部更新为localDateTime,代替Date,为了线程安全,d.对MQ做了抽象集中, e.实体属性用Integer f.表字段设计细节,
5.一个偶然发现,druid和mbg的版本要搭配好,否则出现,druid无法解析Date-->LocalDateTime
>>>>attempting to get column 'gmt_create' from result set. Cause: java.sql.SQLFeatureNotSupportedException
亲测:mbg3.1.2+druid1.1.18报错。mbg3.1.2+druid1.1.19正常!
5.如果前端传入就是非ISO格式,不带T的日期,我就是硬杠这个问题!经诊断,原因就是fastjson不能解析这个格式。解决:添加能解析的日期格式:com.biao.mall.common.util.MyDateFormat,并注入:com.biao.mall.common.conf.WebConfig
6.对DB中自增长的字段,务必要在entity上加上@TableId(type = IdType.AUTO),这个mbp也不会给自动加上,要手动设置。
测试:去掉后,insert前打印实体:DubboOrderEntity{ id=null, gmtCreate=2019-08-16T00:00,gmtModified=null,orderId='360059542158184448', >>>>>>>>> java.lang.IllegalArgumentException: argument type mismatch>>>>可见 id =null,导致无法insert,
添加后: ==> Preparing: INSERT INTO dubbo_order ( gmt_create, order_id, detail_id, order_desc, user_id ) VALUES ( ?, ?, ?, ?, ? ),可见,insert语句忽略了id字段,这正是想要的效果!
6.双重@service注解:有点意思! 测试完全ok。听说dubbo的@service不能和@Transactional 同时存在,有待验证!
7.模块间不能循环依赖,如 A-->B,B-->A,会提示出错:Circular dependency between the following tasks:
解决方法:将B中的部分转移到A,或到公共模块,避免循环。
8.遇到错误 >>> qos-server can not bind localhost:22222,
知识点:Qos=Quality of Service,qos是Dubbo的在线运维命令,可以对服务进行动态的配置、控制及查询,原因:provider已经使用了默认的qos端口导致consumer启动时使用默认的出错,配置为不同的端口即可。
9.遇到错误>>>com.alibaba.dubbo.rpc.RpcException: Unsupported client type: curator, supported client type is grizzly mina netty netty3 netty4
可能的问题:org.apache.curator 依赖未加入模块依赖
10.遇到错误提示:
问题分析:这里提示键值重复,我在DB中将该字段设置为UQ,目的是防止重复,这里却产生了问题,因为mybatis的单表默认方法:dubboStockDao.update(stockEntity,qw)生成的SQL是除了PK外,直接set其他所有字段,导致无法更新。解决:1.dao中自己写update方法,除去UQ字段 2,更好的:DB中的字段,都不要使用UQ,应该在应用层处理,这是个原则,DB只存数据,尽量少逻辑
11.其实上面这个项目有个重大的bug,各位看官可以思考下,且看我下回分解。
12.码字N小时,上头了.
往期文章推荐: