• 微服务架构中,二次浅封装实践


    一、背景简介

    分布式系统中存在很多拆分的服务,在不断迭代升级的过程中,会出现如下常见的棘手情况:

    某个技术组件版本升级,依赖包升级导致部分语法或者API过期,或者组件修复紧急的漏洞,从而会导致分布式系统下各个服务被动的升级迭代,很容易引发意外的问题;不同的服务中对组件的依赖和版本各不相同,从而导致不兼容问题的出现,很难对版本做统一的管理和维护,一旦出现问题很容易手忙脚乱,引发蝴蝶效应;

    所以在复杂的系统中,对于依赖的框架和组件进行统一管理和二次浅封装,可以较大程度降低上述问题的处理成本与风险,同时可以更好的管理和控制技术栈。

    二、框架浅封装

    1、浅封装作用

    为什么浅封装,核心目的在于统一管理和协调组件的依赖与升级,并对常用方法做一层包装,实际上很多组件使用到的功能点并不多,只是在业务中的使用点很多,这样给组件本身的迭代升级带来了一定的难度:

    例如某个组件常用的API中存在巨大风险漏洞,或者替换掉过期的用法,需要对整个系统中涉及的地方做升级,这种操作的成本是非常高的;

    如果是对这种常用的组件方法进行二次包装,作为处理业务的工具方法,那么解决上面的问题就相对轻松许多,只要对封装的工具方法升级,服务的依赖升级即可,降低时间成本和风险。

    通过浅封装的手段,可以实现两个方面的解耦:

    业务与技术

    技术栈中常用的方法进行二次浅封装,这样可以较大程度的降低业务与技术的耦合,如此可以独立的升级技术栈,扩展功能而不影响业务服务的迭代。

    框架与组件

    不同的框架与组件都需要一定程度的自定义配置,同时分模块管理,在不同的服务中引入特定的依赖,也可以在基础包中做统一依赖,以此实现技术栈的快速组合搭配。

    这里说的浅封装,是指包装常规常用的语法,组件本身就是技术层面的深度封装,所以也不可能完全隔开技术栈原生用法。

    2、统一版本控制

    例如微服务架构下,不同的研发组负责不同的业务模块,然而受到开发人员的经验和能力影响,很容易出现不同的服务组件选型不一致,或者相同的组件依赖版本不同,这样很难对系统架构做标准的统一管理。

    对于二次封装的方式,可以严格的控制技术栈的迭代扩展,以及版本冲突的问题,通过对二次封装层的统一升级,可以快速实现业务服务的升级,解决不同服务的依赖差异问题。

    三、实践案例

    1、案例简介

    Java分布式系统中,微服务基础组件(Nacos、Feign、Gateway、Seata)等,系统中间件(Quartz、Redis、Kafka、ElasticSearch,Logstash)等,对常用功能、配置、API等,进行二次浅封装并统一集成管理,以满足日常开发中基础环境搭建与临时工具的快速实现。

    • butte-flyer 组件封装的应用案例;

    • butte-frame 常用技术组件二次封装;

    2、分层架构

    整体划分五层:网关层、应用层、业务层、中间件层、基础层,组合成一套分布式系统。

     

    服务总览

     

     

    3、目录结构

    butte-frame中对各个技术栈进行二次封装管理,在butte-flyer中进行依赖引用。

    1. butte-frame
    2. ├── frame-base 基础代码块
    3. ├── frame-jdbc 数据库组件
    4. ├── frame-core 服务基础依赖
    5. ├── frame-gateway 路由网关
    6. ├── frame-nacos 注册与配置中心
    7. ├── frame-seata 分布式事务
    8. ├── frame-feign 服务间调用
    9. ├── frame-security 安全管理
    10. ├── frame-search 搜索引擎
    11. ├── frame-redis 缓存管理
    12. ├── frame-kafka 消息中间件
    13. ├── frame-quartz 定时任务
    14. ├── frame-swagger 接口文档
    15. └── frame-sleuth 链路日志
    16. butte-flyer
    17. ├── flyer-gateway 网关服务:路由控制
    18. ├── flyer-facade 门面服务:功能协作接口
    19. ├── flyer-account 账户服务:用户账户
    20. ├── flyer-quartz 任务服务:定时任务
    21. └── flyer-admin 管理服务:后端管理

    4、技术栈组件

    系统常用的技术栈:基础框架、微服务组件、缓存、安全管理、数据库、定时任务、工具依赖等。

     

    四、微服务组件

    1、Nacos

    Nacos在整个组件体系中,提供两个核心能力,注册发现:适配微服务注册与发现标准,快速实现动态服务注册发现、元数据管理等,提供微服务组件中最基础的能力;配置中心:统一管理各个服务配置,集中在Nacos中存储管理,隔离多环境的不同配置,并且可以规避线上配置放开的风险;

    连接管理

    1. spring:
    2.   cloud:
    3.     nacos:
    4.       # 配置读取
    5.       config:
    6.         prefix: application
    7.         server-addr: 127.0.0.1:8848
    8.         file-extension: yml
    9.         refresh-enabled: true
    10.       # 注册中心
    11.       discovery:
    12.         server-addr: 127.0.0.1:8848

    配置管理

    • bootstrap.yml :服务中文件,连接和读取Nacos中配置信息;

    • application.yml :公共基础配置,这里配置mybatis组件;

    • application-dev.yml :中间件连接配置,用作环境标识隔离;

    • application-def.yml :各个服务的自定义配置,参数加载;

    2、Gateway

    Gateway网关核心能力,提供统一的API路由管理,作为微服务架构体系下请求唯一入口,还可以在网关层处理所有的非业务功能,例如:安全控制,流量监控限流,等等。

    路由控制:各个服务的发现和路由;

    1. @Component
    2. public class RouteFactory implements RouteDefinitionRepository {
    3.     @Resource
    4.     private RouteService routeService ;
    5.     /**
    6.      * 加载全部路由
    7.      * @since 2021-11-14 18:08
    8.      */
    9.     @Override
    10.     public Flux<RouteDefinitiongetRouteDefinitions() {
    11.         return Flux.fromIterable(routeService.getRouteDefinitions());
    12.     }
    13.     /**
    14.      * 添加路由
    15.      * @since 2021-11-14 18:08
    16.      */
    17.     @Override
    18.     public Mono<Voidsave(Mono routeMono) {
    19.         return routeMono.flatMap(routeDefinition -> {
    20.             routeService.saveRouter(routeDefinition);
    21.             return Mono.empty();
    22.         });
    23.     }
    24. }

    全局过滤:作为网关的基础能力;

    1. @Component
    2. public class GatewayFilter implements GlobalFilter {
    3.     private static final Logger logger = LoggerFactory.getLogger(GatewayFilter.class);
    4.     @Override
    5.     public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    6.         ServerHttpRequest request = exchange.getRequest();
    7.         String uri = request.getURI().getPath() ;
    8.         String host = String.valueOf(request.getHeaders().getHost()) ;
    9.         logger.info("request host : {} , uri : {}",host,uri);
    10.         return chain.filter(exchange);
    11.     }
    12. }

    3、Feign

    Feign组件是声明式的WebService客户端,使微服务之间的调用变得更简单,Feign通过注解手段,将请求进行模板化和接口化管理,可以更加标准的管理各个服务间的通信交互。

    响应解码:定义Feign接口响应时解码逻辑,校验和控制统一的接口风格;

    1. public class FeignDecode extends ResponseEntityDecoder {
    2.     public FeignDecode(Decoder decoder) {
    3.         super(decoder);
    4.     }
    5.     @Override
    6.     public Object decode(Response response, Type type) {
    7.         if (!type.getTypeName().startsWith(Rep.class.getName())) {
    8.             throw new RuntimeException("响应格式异常");
    9.         }
    10.         try {
    11.             return super.decode(response, type);
    12.         } catch (IOException e) {
    13.             e.printStackTrace();
    14.             throw new RuntimeException(e.getMessage());
    15.         }
    16.     }
    17. }

    4、Seata

    Seata组件是开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,实现AT、TCC、SAGA、XA事务模式,支持一站式的分布式解决方案。

    事务配置:基于nacos管理Seata组件的参数定义;

     

    服务注册:在需要管理分布式事务的服务中连接和使用Seata服务;

    1. seata:
    2.   enabled: true
    3.   application-id: ${spring.application.name}
    4.   tx-service-group: butte-seata-group
    5.   config:
    6.     type: nacos
    7.     nacos:
    8.       server-addr: ${spring.cloud.nacos.config.server-addr}
    9.       group: DEFAULT_GROUP
    10.   registry:
    11.     type: nacos
    12.     nacos:
    13.       server-addr: ${spring.cloud.nacos.config.server-addr}
    14.       application: seata-server
    15.       group: DEFAULT_GROUP

    五、中间件集成

    1、Kafka

    Kafka是由Apache开源,具有分布式、分区的、多副本的、多订阅者,基于Zookeeper协调的分布式消息处理平台,由Scala和Java语言编写。还常用于搜集用户在应用服务中产生的日志数据。

    消息发送:封装消息发送的基础能力;

    1. @Component
    2. public class KafkaSendOperate {
    3.     @Resource
    4.     private KafkaTemplate<StringString> kafkaTemplate ;
    5.     public void send (SendMsgVO entry) {
    6.         kafkaTemplate.send(entry.getTopic(),entry.getKey(),entry.getMsgBody()) ;
    7.     }
    8. }

    消息消费:消费监听时有两种策略;

    • 消息生产方自己消费,通过Feign接口去执行具体消费服务的逻辑,这样有利于流程跟踪排查;

    • 消息消费方直接监听,减少消息处理的流程节点,当然也可以打造统一的MQ总线服务(文尾);

    1. public class KafkaListen {
    2.     private static final Logger logger = LoggerFactory.getLogger(KafkaListen.class);
    3.     /**
    4.      * Kafka消息监听
    5.      * @since 2021-11-06 16:47
    6.      */
    7.     @KafkaListener(topics = KafkaTopic.USER_TOPIC)
    8.     public void listenUser (ConsumerRecord,String> record, Acknowledgment acknowledgment) {
    9.         try {
    10.             String key =  String.valueOf(record.key());
    11.             String body = record.value();
    12.             switch (key){ }
    13.         } catch (Exception e){
    14.             e.printStackTrace();
    15.         } finally {
    16.             acknowledgment.acknowledge();
    17.         }
    18.     }
    19. }

    2、Redis

    Redis是一款开源组件,基于内存的高性能的key-value数据结构存储系统,它可以用作数据库、缓存和消息中间件,支持多种类型的数据结构,如字符串、集合等。在实际应用中,通常用来做变动频率低的热点数据缓存和加锁机制。

    KV数据缓存:作为Redis最常用的功能,即缓存一个指定有效期的键和值,在使用时直接获取;

    1. @Component
    2. public class RedisKvOperate {
    3.     @Resource
    4.     private StringRedisTemplate stringRedisTemplate ;
    5.     /**
    6.      * 创建缓存,必须带缓存时长
    7.      * @param key 缓存Key
    8.      * @param value 缓存Value
    9.      * @param expire 单位秒
    10.      * @return boolean
    11.      * @since 2021-08-07 21:12
    12.      */
    13.     public boolean set (String key, String value, long expire) {
    14.         try {
    15.             stringRedisTemplate.opsForValue().set(key,value,expire, TimeUnit.SECONDS);
    16.         } catch (Exception e){
    17.             e.printStackTrace();
    18.             return Boolean.FALSE ;
    19.         }
    20.         return Boolean.TRUE ;
    21.     }
    22. }

    Lock加锁机制:基于spring-integration-redisRedisLockRegistry,实现分布式锁;

    1. @Component
    2. public class RedisLockOperate {
    3.     @Resource
    4.     protected RedisLockRegistry redisLockRegistry;
    5.     /**
    6.      * 尝试一次加锁,采用默认时间
    7.      * @param lockKey 加锁Key
    8.      * @return java.lang.Boolean
    9.      * @since 2021-09-12 13:14
    10.      */
    11.     @SneakyThrows
    12.     public  Boolean tryLock(T lockKey) {
    13.         return redisLockRegistry.obtain(lockKey).tryLock(time, TimeUnit.MILLISECONDS);
    14.     }
    15.     /**
    16.      * 释放锁
    17.      * @param lockKey 解锁Key
    18.      * @since 2021-09-12 13:32
    19.      */
    20.     public  void unlock(T lockKey) {
    21.         redisLockRegistry.obtain(lockKey).unlock();
    22.     }
    23. }

    3、ElasticSearch

    ElasticSearch是一个基于Lucene的搜索服务器,它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口,Elasticsearch是用Java开发的,是当前流行的企业级搜索引擎。

    索引管理:索引的创建和删除,结构添加和查询;

    基于ElasticsearchRestTemplate的模板方法操作;

    1. @Component
    2. public class TemplateOperate {
    3.     @Resource
    4.     private ElasticsearchRestTemplate template ;
    5.     /**
    6.      * 创建索引和结构
    7.      * @param clazz 基于注解类实体
    8.      * @return java.lang.Boolean
    9.      * @since 2021-08-15 19:25
    10.      */
    11.     public  Boolean createPut (Class clazz){
    12.         boolean createIf = template.createIndex(clazz) ;
    13.         if (createIf){
    14.             return template.putMapping(clazz) ;
    15.         }
    16.         return Boolean.FALSE ;
    17.     }
    18. }

    基于RestHighLevelClient原生API操作;

    1. @Component
    2. public class IndexOperate {
    3.     @Resource
    4.     private RestHighLevelClient client ;
    5.     /**
    6.      * 判断索引是否存在
    7.      * @return boolean
    8.      * @since 2021-08-07 18:57
    9.      */
    10.     public boolean exists (IndexVO entry) {
    11.         GetIndexRequest getReq = new GetIndexRequest (entry.getIndexName()) ;
    12.         try {
    13.             return client.indices().exists(getReq, entry.getOptions());
    14.         } catch (Exception e) {
    15.             e.printStackTrace();
    16.         }
    17.         return Boolean.FALSE ;
    18.     }
    19. }

    数据管理:数据新增、主键查询、修改、批量操作,业务性质的搜索封装复杂度很高;

    数据的增删改方法;

    1. @Component
    2. public class DataOperate {
    3.     @Resource
    4.     private RestHighLevelClient client ;
    5.     /**
    6.      * 批量更新数据
    7.      * @param entry 对象主体
    8.      * @since 2021-08-07 18:16
    9.      */
    10.     public void bulkUpdate (DataVO entry){
    11.         if (CollUtil.isEmpty(entry.getDataList())){
    12.             return ;
    13.         }
    14.         // 请求条件
    15.         BulkRequest bulkUpdate = new BulkRequest(entry.getIndexName(),entry.getType()) ;
    16.         bulkUpdate.setRefreshPolicy(entry.getRefresh()) ;
    17.         entry.getDataList().forEach(dataMap -> {
    18.             UpdateRequest updateReq = new UpdateRequest() ;
    19.             updateReq.id(String.valueOf(dataMap.get("id"))) ;
    20.             updateReq.doc(dataMap) ;
    21.             bulkUpdate.add(updateReq) ;
    22.         });
    23.         try {
    24.             // 执行请求
    25.             client.bulk(bulkUpdate, entry.getOptions());
    26.         } catch (IOException e) {
    27.             e.printStackTrace();
    28.         }
    29.     }
    30. }

    索引主键查询,分组查询方法;

    1. @Component
    2. public class QueryOperate {
    3.     @Resource
    4.     private RestHighLevelClient client ;
    5.     /**
    6.      * 指定字段分组查询
    7.      * @since 2021-10-07 19:00
    8.      */
    9.     public Map<String,Object> groupByField (QueryVO entry){
    10.         Map<String,Object> groupMap = new HashMap<>() ;
    11.         // 分组API
    12.         String groupName = entry.getGroupField()+"_group" ;
    13.         SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    14.         sourceBuilder.size(0) ;
    15.         TermsAggregationBuilder termAgg = AggregationBuilders.terms(groupName)
    16.                                                              .field(entry.getGroupField()) ;
    17.         sourceBuilder.aggregation(termAgg);
    18.         // 查询API
    19.         SearchRequest searchRequest = new SearchRequest(entry.getIndexName());
    20.         searchRequest.source(sourceBuilder) ;
    21.         try {
    22.             // 执行API
    23.             SearchResponse response = client.search(searchRequest, entry.getOptions());
    24.             // 响应结果
    25.             Terms groupTerm = response.getAggregations().get(groupName) ;
    26.             if (CollUtil.isNotEmpty(groupTerm.getBuckets())){
    27.                 for (Terms.Bucket bucket:groupTerm.getBuckets()){
    28.                     groupMap.put(bucket.getKeyAsString(),bucket.getDocCount()) ;
    29.                 }
    30.             }
    31.         } catch (IOException e) {
    32.             e.printStackTrace();
    33.         }
    34.         return groupMap ;
    35.     }
    36. }

    4、Logstash

    Logstash是一款开源的数据采集组件,具有实时管道功能。Logstash能够动态的从多个来源采集数据,进行标准化转换数据,并将数据传输到所选择的存储容器。

     

    • Sleuth:管理服务链路,提供核心TraceId和SpanId生成;

    • ElasticSearch:基于ES引擎做日志聚合存储和查询;

    • Logstash:提供日志采集服务,和数据发送ES的能力;

    logback.xml:服务连接Logstash地址,并加载核心配置;

    1. <configuration>
    2.     <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    3.     <springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="butte_app" />
    4.     <springProperty scope="context" name="DES_URI" source="logstash.destination.uri" />
    5.     <springProperty scope="context" name="DES_PORT" source="logstash.destination.port" />
    6.     
    7.     <appender name="LogStash"
    8.               class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    9.         <destination>${DES_URI:- }:${DES_PORT:- }destination>
    10.         <encoder
    11.                 class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
    12.             <providers>
    13.                 <timestamp>
    14.                     <timeZone>UTCtimeZone>
    15.                 timestamp>
    16.                 <pattern>
    17.                     <pattern>
    18.                         {
    19.                         "severity": "%level",
    20.                         "service": "${APP_NAME:-}",
    21.                         "trace": "%X{X-B3-TraceId:-}",
    22.                         "span": "%X{X-B3-SpanId:-}",
    23.                         "exportable": "%X{X-Span-Export:-}",
    24.                         "pid": "${PID:-}",
    25.                         "thread": "%thread",
    26.                         "class": "%logger{40}",
    27.                         "rest": "%message"
    28.                         }
    29.                     pattern>
    30.                 pattern>
    31.             providers>
    32.         encoder>
    33.     appender>
    34. configuration>

     

    5、Quartz

    Quartz是一个完全由java编写的开源作业调度框架,用来执行各个服务中的定时调度任务,在微服务体系架构下,通常开发一个独立的Quartz服务,通过Feign接口去触发各个服务的任务执行。

    配置参数:定时任务基础信息,数据库表,线程池;

    1. spring:
    2.   quartz:
    3.     job-store-type: jdbc
    4.     properties:
    5.       org:
    6.         quartz:
    7.           scheduler:
    8.             instanceName: ButteScheduler
    9.             instanceId: AUTO
    10.           jobStore:
    11.             classorg.quartz.impl.jdbcjobstore.JobStoreTX
    12.             driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    13.             tablePrefix: qrtz_
    14.             isClustered: true
    15.             clusterCheckinInterval: 15000
    16.             useProperties: false
    17.           threadPool:
    18.             classorg.quartz.simpl.SimpleThreadPool
    19.             threadPriority: 5
    20.             threadCount: 10
    21.             threadsInheritContextClassLoaderOfInitializingThread: true

    6、Swagger

    Swagger是常用的接口文档管理组件,通过对API接口和对象的简单注释,快速生成接口描述信息,并且提供可视化界面可以快速对接口发送请求和调试,该组件在前后端联调中,极大的提高效率。

    配置基本的包扫描能力即可;

    1. @Configuration
    2. public class SwaggerConfig {
    3.     @Bean
    4.     public Docket createRestApi() {
    5.         return new Docket(DocumentationType.SWAGGER_2)
    6.                 .apiInfo(apiInfo())
    7.                 .select()
    8.                 .apis(RequestHandlerSelectors.basePackage("com.butte"))
    9.                 .paths(PathSelectors.any())
    10.                 .build();
    11.     }
    12. }

    访问:服务:端口/swagger-ui.html即可打开接口文档;

     

    六、数据库配置

    1、MySQL

    微服务架构下,不同的服务对应不同的MySQL库,基于业务模块做库的划分是当前常用的方式,可以对各自业务下的服务做迭代升级,同时可以避免单点故障导致雪崩效应。

     

    2、HikariCP

    HikariCP作为SpringBoot2版本推荐和默认采用的数据库连接池,具有速度极快、轻量简单的特点。

    1. spring:
    2.   datasource:
    3.     type: com.zaxxer.hikari.HikariDataSource
    4.     driver-class-namecom.mysql.cj.jdbc.Driver
    5.     url: jdbc:mysql://127.0.0.1:3306/${data.name.mysql}?${spring.datasource.db-param}
    6.     username: root
    7.     password: 123456
    8.     db-param: useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false
    9.     hikari:
    10.       minimumIdle: 5
    11.       maximumPoolSize: 10
    12.       idleTimeout: 300000
    13.       maxLifetime: 500000
    14.       connectionTimeout: 30000

    连接池的配置根据业务的并发需求量,做适当的调优即可。

    3、Mybatis

    Mybatis持久层的框架组件,支持定制化SQL、存储过程以及高级映射,MyBatis-Plus是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,可以简化开发、提高效率。

    1. mybatis-plus:
    2.   mapper-locations: classpath*:/mapper/**/*.xml
    3.   configuration:
    4.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

     最近面试的小伙伴很多,对此我整理了一份Java面试题手册:基础知识、JavaOOP、Java集合/泛型面试题、
    Java异常面试题、Java中的IO与NIO面试题、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、
    Memcached、MongoDB、Spring、SpringBoot、SpringCloud、RabbitMQ、Dubbo、MyBatis、ZooKeeper、数据结构、算法、
    Elasticsearch、Kafka、微服务、Linux等等。可以分享给大家学习。【持续更新中】领取方式【999】就可以领取资料了

     

  • 相关阅读:
    一个方法用js生成随机双色球、大乐透
    设计模式之适配器模式
    NLP 模型中的偏差和公平性检测
    [附源码]java毕业设计家政管理系统
    全志H616语言控制刷抖音小项目(守护进程、udev机制)
    11.5MyBatis(进阶)
    优化理论笔记
    服务器硬件基础知识:新手完全指南
    怎样在nature上查文献?
    超实用的图片处理技巧,一分钟轻松完成图片编辑
  • 原文地址:https://blog.csdn.net/m0_67322837/article/details/126347162