今天已经进入第七讲了,整个微服务架构的搭建工作也基本完成。那到目前为止究竟使用了那些技术及实现了什么功能呢?我们先回顾一下。
使用的技术:SpringBoot、Dubbo、Zookeeper、Redis、Kafka
实现的功能:
1,Maven父子级项目,实现了分环境部署配置及服务端口号统一配置
2,Dubbo的集成接入、服务层分模块实现,提供者(四个)和消费者(一个)的配置及服务调用,微服务落地实现。
3,Maven子模块项目(接口及服务层)的版本号统一配置管理实现。
4,Redis的接入、单点登录及分布式缓存实现。
5,接口安全防范,令牌、签名及撒盐算法实现,过滤器对签名、token校验及拦截处理。
6,全局异常处理,Aop日志打印,防SQL注入拦截处理实现。
看到这些满满的干货,不知大家对最近我的系列文章还满意吗?接下来我会继续深入,慢慢探索和讲解实现电商微服务背后的技术应用及真相。
目前项目模块如下:
到目前为止,仅仅实现了四个微服务(提供者)用来项目实战的演示,随着后面的慢慢深入,微服务也会持续增多。今天,我要说说分布式项目中自己踩过的那些坑。
1,实体序列化问题
如果没做过分布式开发的小伙伴,这里一定得注意,如果你项目中的实体不序列化,就会造成无法实现远程过程调用,消费者在接收提供者服务返回的实体时,就会抛异常。
为什么一定要序列化呢?有人可能会说我之前做的项目,实体都没有序列化不是运行的好好的嘛。那是因为你之前的项目所有代码都在一个Web容器里运行,也就是说:你之前的整个项目就在一个JVM里。
但分布式项目就不一样了,提供者和消费者是在不同的web容器里运行(不同的JVM)。消费者在进行远程方法调用时,实际就是消费者的jvm在调用提供者jvm里的对象,但这个对象在消费者的jvm里并不存在,那要获取就得用Java对象序列化来解决。简而言之:序列化的作用就是为了不同jvm之间共享实例对象的一种解决方案。
2,分布式环境生成编号问题
这是我在项目中真真实实跳过的坑,根据之前多年项目的开发经验,一般编号(客户,商品、订单等编号)的生成规则基本都是借助于数据库的自增id实现,看似本来通用的解决方案,在分布式项目中,竟然是给自己埋坑。
之前的编号实现方式:在添加数据方法的Service实现里,先查询获得数据库最大ID对应的编号,然后给这个编号+1生成新编号作为当前新增数据的编号插入数据库。开始看着很完美,但后来突然有人反映编号重复,这就奇怪了?加班趴着debug代码,但就是找不出来问题的原因。面对这种突如其来的问题完全不知所措,不得不求助网络,各种搜索后才明白,是提供者集群惹的祸。
原因分析:当两个及以上并发请求同时进入集群中的不同提供者时,一个提供者的Service实现在生成编号并插入数据之前,另一个提供者的Service也查询了数据库并获取了跟前一个提供者获取相同的最大编号。导致两台服务最终生成的编号相同。那可能也有人说了,你先插入数据,然后根据插入数据生成的自增id再去生成编号更新数据库不就解决了。但你有没想过,更新数据库操作需要锁表,在高并发请求的情况下,这会造成很大的性能瓶颈。
目前流行的解决方案:雪花算法,是完全基于代码实现,不依赖数据库。
上图只是部分代码,我们看到这个工具类实现了单例模式,生成的编号是:时间(到毫秒)+ ip(后面取了4位) + 自增序列。
我来演示下效果吧,先写个main方法实例化工具类,并写for循环生成编号
我红框圈出来的,是在同一时间,虽然时间和四位ip相同,但后三位的自增序列值一直在递增。那如果服务端做集群呢,是不是编号又会重复?就算在同一时间的高并发请求,几个服务终端可能会生成时间相同、后三位序列号相同的编号,但是,不同终端通过ip最后获取的四位值肯定不同。所以不可能有重复出现。
3,日志统一打印问题
分布式环境中,如果每个服务的日志分散到各自服务所在机器上,那么以后如果线上出现异常或日志收集及分析检查时,会让你痛苦不已,集群和服务规模小还好,特别是在负载均衡后的多个服务实例,你无法确定某个请求被谁接收了,所以只能翻看每个实例的日志。
处理方案:Service业务实现层不要捕获异常,直接通过throws全部往外抛。
然后在接口层在做相应的统一处理,比如Aop里打印,或使用日志框架(如:ELK)统一收集等。这样如果你的服务层做了集群,线上报错你也不用纠结去哪个服务器看服务提供者的日志,你只要到对应接口层服务查看输出的日志或统一收集的地方去查看。
获取项目源代码,请扫码关注公众号,并发送Springboot获取。