基于Spring Cloud 的微服务架构脚手架,满满的干货来啦~
Posted 緈諨の約錠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Spring Cloud 的微服务架构脚手架,满满的干货来啦~相关的知识,希望对你有一定的参考价值。
文章目录
1 前言
-----------------------------------------------------------------
小伙伴们运气不错呀,刷到我这个博客了,这篇博客可是融合了我不少心血,两万多字的详细教程,满满的干货
,先来个三连击吧,点赞
、收藏
、加关注
-----------------------------------------------------------------
工作了很多年,都没有自己的一个项目脚手架,所以说,前阵子就准备搞一个自己的Spring Cloud微服务
的架构。Spring Cloud 官网,2021-07-06
发布了Hoxton.SR12 这个版本, 本来想使用 Hoxton.SR12
这个Spring Cloud版本,查了一些资料,发现基于这个版本,好用的微服务架构体系并且开源的项目不是很多,可能是这个版本刚出来两三个月,就自己折腾了一个基础架构。在进行依赖管理的过程中,走了不少坑,各种jar冲突或者版本不兼容等等,这里总结记录下,防止以后再次踩坑。
为了搭建自己的脚手架,方便以后项目的管理和维护,现在开发了这一套基础脚手架项目,该项目基于 Spring Boot
+ Spring Cloud
+ MyBatis-Plus
,为了提高项目的开发效率,降低项目的维护成本,避免重复造轮子,我基于我上一篇博客 Spring Cloud 微服务基础功能架构来啦 中的基础架构,搭建了一套脚手架,可以直接拿来使用。
2 脚手架主要提供哪些功能
该项目主要包括以下功能模块:
统一管理项目依赖,核心依赖的版本控制
缓存管理以及分布式锁的处理
预警通知功能
异常管理
限流Api管理
Mock Server管理
消息中间件MQ管理
操作日志管理
定时任务管理
Swagger-Ui管理
工具类管理
3 如何使用该脚手架
3.1 项目统一依赖管理
-
集成基础设施项目:
在自己的maven项目中,在最顶层项目的pom文件中,继承该基础设施项目:
<parent> <groupId>com.smilehappiness</groupId> <artifactId>smilehappiness-architecture</artifactId> <version>1.0.0</version> </parent>
3.2 集成基础模块功能到自己的项目中
下面的这些依赖的功能模块,可以根据实际情况,选择集成哪些功能模块,添加如下依赖,即可把基础设施项目的核心功能,集成到自己的项目中:
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-cache</artifactId>
<exclusions>
<exclusion>
<artifactId>HdrHistogram</artifactId>
<groupId>org.hdrhistogram</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-common</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-early-warning-notice</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-exception</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-limit-api</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-mq</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-operation-log</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-schedule</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-utils</artifactId>
</dependency>
4 基础核心功能模块的使用
4.1 集成缓存管理模块
分布式、微服务背景下,对于性能的要求也越来越高,所以缓存越来越受到了重视。现在使用比较流行的缓存是Redis,所以,笔者也基于Redis做缓存处理。
Redis常见的场景有: 普通缓存
、分布式锁
、分布式限流
、幂等性校验
、短信登录限定次数
等等
4.1.1 添加cache模块依赖
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-cache</artifactId>
</dependency>
4.1.2 cache模块的功能使用
-
Redis工具的基本使用示例
注入以下工具类:
@Autowired private RedissonClient redissonClient; @Autowired private RedisUtil redisUtil; @Autowired private RedissonLockRedisUtil redissonLockRedisUtil;
然后使用以下测试用例:
public void testRedisUtil() //赋值 redisUtil.set("test1", "你好"); //该工具类,默认过期单位为秒 redisUtil.set("test2", "测试一下过期时间", 30); //取值 System.out.println(redisUtil.get("test1")); System.out.println(redisUtil.get("test2")); //删除值 redisUtil.del("test1");
-
Redis分布式锁工具的基本使用示例
/** * <p> * 测试分布式锁的使用,基于Redisson客户端实现(该方法的实现推荐使用) * <p/> * * @param * @return void * @Date 2021/10/4 16:51 */ @Test public void testDistributeLock() String bizLockKey = "smilehappiness:trialOrder:orderNumberxxxxxxx"; //支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁,当然,为了不占用资源,使用锁处理完业务,一般还是建议手动释放锁 RLock lock = redissonLockRedisUtil.lock(bizLockKey, 60L); if (lock.tryLock()) try //处理业务方法。。。 catch (Exception e) log.error("获取分布式锁失败,失败原因:", e); throw new SystemInternalException("获取分布式锁失败,失败原因:" + e.getMessage()); finally lock.unlock(); else log.error("系统繁忙,请稍后再试!"); throw new BusinessException("系统繁忙,请稍后再试!"); /** * <p> * 测试分布式锁的使用,基于Redisson客户端实现(该方法的实现推荐使用) * <p/> * * @param * @return void * @Date 2021/10/4 16:51 */ @Test public void testDistributeLockTwo() String bizLockKey = "smilehappiness:trialOrder:orderNumberxxxxxxx"; //尝试加锁,最多等待30秒,上锁以后120秒自动解锁 boolean lockFlag = redissonLockRedisUtil.tryLock(bizLockKey, 30L, 2 * 60L); if (lockFlag) try //处理业务方法。。。 catch (Exception e) log.error("获取分布式锁失败,失败原因:", e); throw new SystemInternalException("获取分布式锁失败,失败原因:" + e.getMessage()); finally redissonLockRedisUtil.unlock(bizLockKey); else log.error("系统繁忙,请稍后再试!"); throw new BusinessException("系统繁忙,请稍后再试!"); /** * <p> * 测试分布式锁的使用,基于Redisson客户端实现方式 * <p/> * * @param * @return void * @Date 2021/10/4 16:51 */ @Test public void testDistributeLockOriginal() String bizLockKey = "smilehappiness:trialOrder:orderNumberxxxxxxx"; RLock lock = redissonClient.getLock(bizLockKey); if (lock.tryLock()) try //处理业务方法。。。 catch (Exception e) log.error("获取分布式锁失败,失败原因:", e); throw new SystemInternalException("获取分布式锁失败,失败原因:" + e.getMessage()); finally lock.unlock(); else log.error("系统繁忙,请稍后再试!"); throw new BusinessException("系统繁忙,请稍后再试!");
4.2 集成通知预警管理模块
目前,只设计了钉钉预警通知
,后续可以集成邮件通知等等
4.2.1 添加通知预警模块依赖
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-early-warning-notice</artifactId>
</dependency>
4.2.2 添加yml配置
在yml文件或者properties配置文件中添加如下内容:
# 钉钉预警通知
earlyWarning:
notice:
# 一般预警通知
generalDingNoticeUrl: xxx
# 高频异常通知预警
errorDingNoticeUrl: xxx
记录好机器人的Webhook 地址,可以在自己项目中调用此地址向群聊发送相关消息通知,做到项目异常的预警通知
或者一些其它业务通知
,至此已经设置完成,剩下的只需要自己在项目中需要预警的地方调用接口通知即可。
如果你设定的机器人类型是关键字,内容需要包含关键字,才可以发送通知成功
注:这里分为了两个地址,可以根据实际情况,来决定用一个还是用两个,使用的时候非常简单,在钉钉上创建一个机器人,把webhook地址复制过来即可,限于篇幅,就不再详细说明,玩不转的小伙伴,可以参考资料:https://blog.csdn.net/nbskycity/article/details/106068455
4.2.3 钉钉预警使用示例
-
首先注入工具类
@Autowired private DingTalkWarningNoticeServer dingTalkWarningNoticeServer;
-
参考代码示例
/** * 钉钉预警通知测试,注意,如果你设定的机器人类型是关键字,内容需要包含关键字,才可以发送通知成功 */ @Test public void testDingTalkNotice() dingTalkWarningNoticeServer.sendWarningMessage("smile:这是一个警告的通知"); //第一个参数title,可以理解为关键信息标识,后续跟踪日志可以使用该关键信息标识快速找到 dingTalkWarningNoticeServer.sendWarningMessage("hello,", "smile:这是一个警告的通知"); try log.info("结果:", 1 / 0); catch (Exception e) dingTalkWarningNoticeServer.sendErrorMessage(StringUtils.join("smile:异常通知,原因:", e.getMessage())); dingTalkWarningNoticeServer.sendErrorMessage("world", StringUtils.join("smile:异常通知,原因:", e.getMessage()));
4.3 集成异常管理模块
项目中,经常会遇到各种各样的异常,有时候异常信息可以给用户看,比如说:银行卡号填写错误、邮箱格式不合法等、而有的信息不能给用户看,比如说:系统走神了…
还有一种场景,针对异常信息,有时候需要进行文字翻译,比如:系统返回的失败信息是 AAA,可能需要在适配层翻译为BBB返回给用户,可以做对照表等等进行处理
4.3.1 添加异常模块依赖
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-exception</artifactId>
</dependency>
4.3.2 使用异常
4.3.2.1 两种异常说明
目前定义了两种异常: 一种是业务异常,另外一种是系统级异常
-
使用业务异常(
BusinessException
)时,异常信息会直接返回给用户端,业务异常支持添加了普通code,业务code,以及异常信息。在设计的时候,业务异常类这里额外添加一个业务bizCode
参数,为了后续扩展性更强(可以基于业务bizCode做不同的信息对照展示,对于前端而言,基于普通的code,200或者非200即可判断是否请求接口成功)
-
使用系统级异常(
SystemInternalException
)时,针对这种系统异常,会统一降级处理,比如可以友好的返回:系统升级中,请您稍后再试...
,而不是返回系统走神了或者一大串英文异常给客户
4.3.2.2 异常使用示例
具体使用可参考如下示例:
@GetMapping("/getApiLoggerInfoByRequestUrlAndMethodName")
public CommonResult<List<ApiLogger>> getApiLoggerInfoByRequestUrlAndMethodName(@RequestParam("requestUrl") String requestUrl, @RequestParam("methodName") String methodName)
LocalDateTime bizTimeStart = LocalDateTime.now();
CommonResult<List<ApiLogger>> commonResult = new CommonResult<>();
try
List<ApiLogger> apiLoggerList = apiLoggerService.getApiLoggerInfoByRequestUrlAndMethodName(requestUrl, methodName);
if (CollectionUtils.isEmpty(apiLoggerList))
throw new BusinessException(FrameworkBusinessExceptionEnum.API_LOGGER_INFO_NULL);
log.info("通过请求url和方法名称,获取日志信息接口返回结果:", JSON.toJSONString(commonResult));
log.info("通过请求url和方法名称,获取日志信息方法执行耗时(毫秒):", DateUtil.getTakeTime(bizTimeStart, LocalDateTime.now(), TimeUnit.MILLISECONDS));
return commonResult;
catch (BusinessException e)
log.error("【业务异常】通过请求url和方法名称,获取日志信息异常,异常原因:", e.getMessage());
log.info("通过请求url和方法名称,获取日志信息方法执行耗时(毫秒):", DateUtil.getTakeTime(bizTimeStart, LocalDateTime.now(), TimeUnit.MILLISECONDS));
throw new BusinessException(e.getCode(), e.getBizCode(), "通过请求url和方法名称,获取日志信息异常,异常原因:" + e.getMessage());
catch (Exception e)
log.error("【系统异常】通过请求url和方法名称,获取日志信息异常,异常原因:", e.getMessage());
log.info("通过请求url和方法名称,获取日志信息方法执行耗时(毫秒):", DateUtil.getTakeTime(bizTimeStart, LocalDateTime.now(), TimeUnit.MILLISECONDS));
throw new SystemInternalException("通过请求url和方法名称,获取日志信息异常,异常原因:" + e.getMessage());
4.4 集成限流管理模块
4.4.1 前言
为了满足各种应用场景,有时候不得不对接口Api进行限流。比如说:短信服务,供应商可能会要求每秒访问不超过400条,如果超过了这个访问量,请求就会被供应商拒绝,从而导致漏发短信。
还有的接口,第三方Api会做限制,他们为了限制访问,设定一分钟只能请求接口20次,超过了就会超时或者响应异常。
总而言之,限流,在好多场景用的还是挺多的。该模块功能,主要基于Redis提供了基础的api限流次数,具体的限流方案有很多,可以具体场景具体选择
4.4.2 添加限流管理模块依赖
<dependency>
<groupId>com.smilehappiness</groupId>
<artifactId>smilehappiness-limit-api</artifactId>
</dependency>
4.4.3 实现的思路
思路: 借助于Redis
的INCR
操作来实现Limit限流
-
将INCR key中储存的数字值增一,如果key不存在,那么key的值会先被初始化为 0 ,然后再执行INCR操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误,本操作的值限制在 64 位(bit)有符号数字表示之内。
-
当API被调用时,在调用API前进行INCR key,key可以是ip地址相关,用户相关,
业务参数相关
,或是全局的一个key
。如果返回值为1,则表示刚开始调用,赋予key过期时间,然后判断返回值是否大于设定的Limit限流数量,如果大于抛异常或者阻塞重试。
4.4.4 如何使用该模块功能进行限流
主要就是对需要限流的业务方法,添加了@ApiLimit(limitCounts = 10, timeSecond = 120, limitApiName = "sendSmsMessage")
注解参数说明: limitCounts标识多少次开始限流,timeSecond 标识多少时间,单位秒,limitApiName标识限流的接口api业务key
以上注解含义: 表示2分钟只允许Api方法被调用10次,否则就会限流。第一次限流进行重试,如果10秒后还不能调用,则抛出异常,待业务端处理。
代码示例中,使用了两种方式进行拦截,一种是定义切入点
的方式,一种是直接拦截使用ApiLimit
注解的方法,这两种方式都可以。
限于篇幅,这里就不详细讲了,有兴趣的小伙伴,可以参考我之前写的另一篇博文,写的非常详细:分布式环境下,基于Redis实现Restful API接口的限流
4.4.5 代码示例
参考代码如下:
/**
* <p>
* 根据消息模板以及内容,发送短信
* <p/>
*
* @param smsMessage
* @return void
* @Date 2020/7/6 21:35
*/
void sendSmsMessage(SmsMessage smsMessage);
/**
* <p>
* 根据消息模板以及内容,发送短信
* 默认一分钟,限流500次,可以根据实际情况进行限流
* <p/>
*
* @param smsMessage
* @return void
* @Date 2020/7/6 21:35
*/
@ApiLimit(limitCounts = 10, timeSecond = 120, limitApiName = "sendSmsMessage")
@Override
public 基于Spring Cloud 的微服务架构脚手架,满满的干货来啦~