cloud从看不懂到屏弃,希望能提供协理

创业半年多,中间做过一个app。各种各样的原因,最后虽然上线了,但是没有推广,每天百十来个用户,也不打算迭代了。我们后台使用的是基于spring-boot、spring-cloud的微服务框架。框架包含了spring-cloud全家桶的大部分组件,我自己花时间,重新把这些框架类的内容整理了一遍,构建了一个简单的场景,开源出来,希望对那些想学习
或者 快速搭建自己的微服务框架的同学能提供帮助。

引言

why

当我们使用一个新技术的时候,应该首先问的一个问题就是why:为什么要使用这个技术?或者问:这个技术是可以解决什么问题。
我也想写篇微服务的文章,以及微服务的优缺点
在微服务架构中,当一个大型系统被拆分成微服务系统以后,不仅包括功能拆分,还包括系统拆分、代码拆分、数据库拆分、缓存拆分等,多个系统的部署、维护、调用关系、调度、监控、fail
over就会成为一系列问题。同时微服务系统划分越多,调用链路可能会越长,调用链监控、全链路trace也会成为问题。
自然和自然的法则在黑夜中隐藏,上帝说让牛顿诞生吧,于是一切都被照亮。spring
cloud 就是这样诞生的。spring cloud为服务治理而生。

举个栗子,当一个大型系统被拆分成5个小业务系统以后,最容易想到的后端架构是:

必发365游戏官方网址 1

前端需要维持多个系统

这样问题很明显,client需要维持5个业务系统地址,可能经常出现某个动作需要调用超过1个业务系统才能完成,而且无法保证事务性。于是出现了下面一个架构:

必发365游戏官方网址 2

各个子系统之间不具有强关联关系

api
gateway和各个业务系统之间通过负载均衡发生调用关系,client只需要调用api
gateway。
看起来好像解决了拆分问题、调用问题和client端问题。但是因为负载均衡设备的存在,各个子系统之间不再有强关联关系,子系统看起来像是互不关联的系统一样,各提供各的服务,当某一个子系统响应变慢时,可能会造成api
gateway或者其他调用者系统也变慢,甚至会造成整个架构雪崩。自然,这种问题可以设置调用超时来一定程度上解决,但是spring
cloud可以提供更优雅的方案。这种结构,要想解决调度、监控、fail
over、全链路trace等问题,也需要接入其他第三方系统或工具,而spring
cloud针对这些问题提供了一套完整的解决方案。

代码地址:

微服务架构是一项在云中部署应用和服务的新技术。大部分围绕微服务的争论都集中在容器或其他技术是否能很好的实施微服务,而红帽说API应该是重点。

spring cloud简介

先来看一下spring cloud包含了什么组件:

必发365游戏官方网址 3

必发365游戏官方网址 4

必发365游戏官方网址 5

必发365游戏官方网址 6

必发365游戏官方网址 7

必发365游戏官方网址 8

这6张图来自https://springcloud.cc/
包括了spring
cloud现在有的所有组件,以及每个组件的作用。我这里粗浅介绍10个。

  1. spring cloud config

    • 远程配置服务。
    • 远程配置是每个都必不可少的中间件,远程配置的特点一般需要:多节点主备、配置化、动态修改、配置本地化缓存、动态修改的实时推送等。
    • config允许配置文件放在git上或者svn上,和spring
      boot的集成非常容易,但是缺点就是修改了git上的配置以后,只能一个一个的请求每个service的接口,让他们去更新配置,没有修改配置的推送消息。而且,如果要根据配置文件的修改,做一些重新初始化操作的话(如线程池的容量变化等),会需要一些work
      around的方法,所以建议如果有其他方案,不建议选择spring cloud
      config。
  2. spring cloud bus

    • 事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化。经常与Spring
      Cloud Config联合使用。
    • spring cloud
      config本身不能向注册过来的服务提供实时更新的推送。比如我们配置放在了git上,那么当修改github上配置内容的时候,最多可以配置webhook到一台config-server上,但是config-server自己不会将配置更新实时推送到各个服务上。
    • bus的作用就是将大家链接在一条总线上,这条线上的所有server共享状态,当webhook到bus上的某一台server的时候,其他server也会收到相同的hook状态。
    • 但是bus的使用需要依赖于MQ,bus直接继承了RabbitMq &
      kafka,只需要在spring中直接配置地址即可,但是对于其他类型的MQ,就需要一些手动配置。
    • 最大的问题还是,如果仅仅因为spring cloud
      bus而让自己的系统引入MQ,显然会有些得不偿失。我理解系统应该在满足现有业务需求的基础上,越简单越好,依赖越少链路越短,越能减少出问题的风险。
  3. eureka

    • spring
      cloud的服务发现组件。这个组件讲起来需要大篇幅,最好和consul一起讲。
    • eureka负责服务注册和服务发现,为了高可用,一般需要多个eureka
      server相互注册,组成集群。Eureka
      Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。
    • eureka内部对于注册的service主要通过心跳来监控service是否已经挂掉,默认心跳时间是15s。这就意味着,当一个服务提供方挂掉以后,服务订阅者最长可能30s以后才发现。
    • service启动连上eureka之后,会同步一份服务列表到本地缓存,服务注册有更新时,eureka会推送到每个service。
    • eureka也会有一些策略防止由于某个服务所在网络的不稳定导致的所有服务心跳停止的雪崩现象。
    • eureka自带web页面,在页面上能看到所有的服务注册情况 和
      eureka集群状态。
    • eureka支持服务自己主动下掉自己,请求service的下列地址,可以让服务从eureka上下掉自己,同时service进程也会自己停掉自己。
      curl -H 'Accept:application/json' -X POST localhost:${management.port}/shutdown
  4. consul

    • 也是一个服务发现工具,而且自带key-value存储服务、健康检查 和
      web页面。
    • 听起来好像比eureka高大上一些,里面使用了gossip协议和Raft协议,但是他的缺点就是比eureka难维护。
    • 服务注册是微服务架构的关键节点。所以我们现阶段选择的是eureka,然后远程配置使用的是spring
      cloud config。如果要上容器和编排的话,会再看具体情况做选择。
    • 但是,后来发现其实consul提供了官方的docker镜像,直接使用docker-consul集群用户服务发现的话,运维成本会直线下降,后面会考虑把eureka +
      spring cloud config 换成consul。
  5. ribbon:

    • 客户端负载均衡组件。
    • 服务发现以后,每个service在本地知道自己要调用的服务有多少台机器,机器的ip是什么,端口号是多少,那这个service在本地需要有一个负载均衡策略,为每一次请求选择一台目标机器进行调用,而ribbon做的就是负载均衡策略的选择。
    • ribbon提供了多种负载均衡策略,包括BestAvailableRule、AvailabilityFilteringRule、WeightedResponseTimeRule、RetryRule、RoundRobinRule、RandomRule、ZoneAvoidanceRule等,没记错的话,默认是ZoneAvoidanceRule。当然,也可以自定义自己的负载均衡策略,比如被调用服务需要灰度发布或者A/B测试的话,就可以在ribbon这一层做自定义。
  6. feign

    • 声明式、模板化的HTTP客户端。
    • 微服务之间的调用本质还是http请求,如果对于每个请求都需要写请求代码,增加请求参数,同时对请求结果做处理,就会存在大量重复工作,而feign非常优雅的帮助我们解决了这个问题,只需要定义一个interface,fegin就知道http请求的时候参数应该如何设置。
    • 同时,feign也集成了ribbon,只要在微服务中依赖了ribbon,feign默认会使用ribbon定义的负载均衡策略。
    • 最重要的是,feign并不是仅仅只能使用在有eureka或者ribbon的微服务系统中,任何系统中,只要涉及到http调用第三方服务,都可以使用feign,帮我们解决http请求的代码重复编写。
  7. hystrix

    • 断路器,类似于物理电路图中的断路器。
    • 正常情况下,当整个服务环境中,某一个服务提供方由于网络原因、数据库原因或者性能原因等,造成响应很慢的话,调用方就有可能短时间内累计大量的请求线程,最终造成调用方down,甚至整个系统崩溃。而加入hystrix之后,如果hystrix发现某个服务的某台机器调用非常缓慢或者多次调用失败,就会短时间内把这条路断掉,所有的请求都不会再发到这台机器上。
    • 如果某个服务所有的机器都挂了,hystrix会迅速失败,马上返回,保证被调用方不会有大量的线程堆积。
    • Feign默认集成了hystrix。
    • 上面有提到,使用eureka时,当一个服务提供方挂掉以后,服务订阅者最长可能30s以后才知道,那这30s就会出现大量的调用失败。如果在系统里面集成了hystrix,就会马上把挂掉的这台服务提供方断路掉,让请求不再转发到这台机器上,大量减少调用失败。
    • hystrix执行断路操作以后,并不表示这条路就永远断了,而是会一定时间间隔内缓慢尝试去请求这条路,如果能请求成功,断路就会恢复。
    • 有一点需要注意的是hystrix在做断路时,默认所有的调用请求都会放在一个的线程池中进行,线程池的作用很明显,有隔离性。比如gateway,集成了5个子业务系统,可能其中一个系统的调用量非常大,而另外四个系统的调用很小,如果没有线程池的话,显然第一个系统的大量调用会影响到后面四个系统的调用性能。hystrix的线程池和java标准线程池一样,可以配置一些参数:coreSize、maximumSize、maxQueueSize、queueSizeRejectionThreshold、allowMaximumSizeToDivergeFromCoreSize、keepAliveTimeMinutes等,如果某一个子系统的调用量突然激增,超过了线程池的容量,也会迅速失败,直接返回,起到降级和保护系统本身的作用。当然hystrix也支持非线程池的方式,在本地请求线程中做调用,即semaphore模式,官方不建议,除非系统qps真的很大。
  8. zuul

    • 是一个网关组件。提供动态路由,监控,弹性,安全等边缘服务的框架。
    • zuul主需要简单配置一下properties文件,不需要写具体的代码就可以实现将请求转发到相应的服务上去。
    • 还可以定制化一些filter做验证、隔离、限流、文件处理等切面,对于网关来说,使用zuul能减少大量的代码。
    • 不过我没有使用过,不太了解,现在我们的网关主要还是基于feignClient、ribbon、hystrix来实现的。zuul默认也集成了这些组件。有兴趣可以研究研究。
  9. turbine

    • 是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况.
    • 在复杂的分布式系统中,相同服务的节点经常需要部署上百甚至上千个,很多时候,运维人员希望能够把相同服务的节点状态以一个整体集群的形式展现出来,这样可以更好的把握整个系统的状态。
    • turbine提供把多个hystrix.stream的内容聚合为一个数据源供Dashboard展示.
  10. Spring Cloud Starters

    • spring boot热插拔、提供默认配置、开箱即用的依赖。
    • starter 是spring boot框架非常基础的部分。可以自定义starter。

我们现阶段的后台系统中,上述除了spring cloud
bus、consul和zuul,其他都使用到。Talking is cheap, github地址:
https://github.com/chxfantasy/spring-cloud-demo

最后的重点是:求推荐靠谱运营,职位运营总监

必发365游戏官方网址 9代码目录

微服务可以在“自己的程序”中运行,并通过“轻量级设备与HTTP型API进行沟通”。关键在于该服务可以在自己的程序中运行。通过这一点我们就可以将服务公开与微服务架构(在现有系统中分布一个API)区分开来。在服务公开中,许多服务都可以被内部独立进程所限制。如果其中任何一个服务需要增加某种功能,那么就必须缩小进程范围。在微服务架构中,只需要在特定的某种服务中增加所需功能,而不影响整体进程。

如果对微服务本身不太太了解的话,或者不知道为什么需要微服务的话,请参考原来的一篇文章:我也想写篇微服务的文章,以及微服务的优缺点

实践微服务架构全方位的技术

如果对微服务里面的各个组件不太熟悉的话,可以参考这一篇文章:spring
cloud从看不懂到放弃

这仅仅是spring-cloud微服务框架的一个demo,可以完整运行。可能你初看到这里,会觉得这个项目目录太tm复杂了,确实,微服务要做的就是解耦、轻量化。这里面包含的组件和内容有:

  1. 这仅仅是spring-cloud微服务框架的一个demo,可以完整运行。可能你初看到这里,会觉得这个项目目录太tm复杂了,确实,微服务要做的就是解耦、轻量化。这里面包含的组件和内容有:
    • spring cloud eureka,服务注册和服务发现
    • spring cloud config,动态配置项
    • ribbon,客户端负载均衡
    • feign,
    • hystrix,熔断
    • turbine
    • Spring Cloud Starters
    • 同一个服务中的多数据库支持
    • 全链路traceId追踪
    • velocity 前端模板
    • mybatis, pageHelper , druid
    • redis(序列化采用的是jdk默认序列化方案)
    • slf4j & logback
    • 国际化配置
    • 全局错误信息catch
    • 线程池
    • 服务健康检查, 服务全链路健康检查
  2. 代码的业务是:首先有一个登录页面,用户登录以后,能看到一个微博列表,同时也可以添加微博。在每个微博的最右边,可以看到每个微博的评论列表,也能添加评论。前端代码写的非常粗糙。。。
    如果你真的运行了,求不吐槽。
  3. 这里不谈根据业务的需求去选择是否微服务化,也不争辩什么时候该用什么样的框架,我这里只是把我们app后端的代码,抽象成一个代码库,希望能为你提供帮助。我们产品上线2个多月,后端基本没有出过问题。

spring cloud eureka,服务注册和服务发现

spring cloud config,动态配置项

  1. 首先,你在本地需要有一个redis和mysql,redis默认启动就可以。数据库表的创建语句见下:

ribbon,客户端负载均衡

feign,

create database test;CREATE TABLE `account` ( `user_id` varchar NOT NULL DEFAULT '', `user_name` varchar NOT NULL DEFAULT '', `password` varchar NOT NULL DEFAULT '', `gmt_created` datetime NOT NULL, `gmt_modified` datetime DEFAULT NULL, `is_deleted` tinyint NOT NULL DEFAULT '0', PRIMARY KEY (`user_id`), KEY `index_user_id` (`user_id`) KEY_BLOCK_SIZE=10) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `moment` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `user_id` varchar COLLATE utf8mb4_unicode_ci NOT NULL, `content` text COLLATE utf8mb4_unicode_ci, `gmt_created` datetime NOT NULL, `gmt_modified` datetime DEFAULT NULL, `is_deleted` tinyint DEFAULT '0', PRIMARY KEY , KEY `index_user_id` (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;create database test2;CREATE TABLE `comment` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `moment_id` bigint unsigned NOT NULL, `content` text COLLATE utf8mb4_unicode_ci, `gmt_created` datetime NOT NULL, `gmt_modified` datetime DEFAULT NULL, `is_deleted` tinyint DEFAULT '0', PRIMARY KEY , KEY `index_moment_id` (`moment_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

hystrix,熔断

  1. 系统环境需要Java 8 和 maven 3及以上
  2. 运行命令如下 (需要按顺序运行每个模块):

turbine

Spring Cloud Starters

 cd spring-cloud-parent mvn clean install -DskipTests cd ../spring-cloud-client mvn clean install -DskipTests cd ../spring-cloud-starter mvn clean install -DskipTests cd spring-cloud-eureka mvn clean spring-boot:run cd spring-cloud-account mvn clean spring-boot:run cd spring-cloud-biz mvn clean spring-boot:run cd spring-cloud-gateway mvn clean spring-boot:run

同一个服务中的多数据库支持(AOP)

  1. 浏览器中打开 http://127.0.0.1:7001/index

全链路traceId追踪

velocity 前端模板

  1. 代码中的依赖关系见下图:

    必发365游戏官方网址 10代码依赖关系

  2. spring-cloud-parent

    • 是一个空的mvn project,
      包含了一些被其他项目所需要的公共的依赖。我这里创建parent,仅仅是因为我不想把很多相同的依赖在spring-cloud-eureka、spring-cloud-biz、spring-cloud-account和spring-cloud-gateway这几个项目中重复写一遍,所以,一般微服务project都集成spring-cloud-parent。嗯,懒惰是人类的第一生产力。
  3. spring-cloud-starter
    • 是一个我创建的spring starter,
      里面包含的是一些公共的bean,和一些公共的bean配置,比如国际化locle配置、缓存CacheService配置,messageConvertor配置等,spring-cloud-biz、spring-cloud-account和spring-cloud-gateway等微服务都需要依赖和共用这些基础配置。
  4. spring-cloud-client
    • 是一个公共依赖,这里包含的是一个util类,以及多个模块需要共同使用的、和数据库表对应的Java
      model.
  5. spring-cloud-eureka
    • 是一个服务注册和服务发现中心,需要集群化。代码里,我把spring-cloud-config动态配置项也集成在这里面了。服务发现的心跳检测时间,也从15s改到了5s,这对于快速发现节点故障很有作用。
  6. spring-cloud-account
    • 是整个工程的账号服务模块。对于一个大型的系统来讲,单独的账号服务系统是很有必要解耦出来的。
  7. spring-cloud-biz
    • 是实际的业务模块,包括发微博模块和评论模块。整个代码中动态使用了两个数据库,我这里对数据库的切分只是简单从业务上切分,既:微博在一个库中,评论在另外一个库中,同时写了一个@TargetDataSource注解,方便动态切换数据库。其实对于分库、分表、读写分离,代码都是类似的。
  8. spring-cloud-gateway
    • 是整个系统的网关服务,所有来自端上的请求,包括app或者web页面,都需要先到网关,由网关做过处理之后再转发给其他服务。比如,网关需要处理登录状态问题,需要处理上传文件问题,需要做一些过滤,以及其他多种切面上的事情。
  9. 如果需要停止某个微服务,如spring-cloud-account、spring-cloud-biz或者spring-cloud-gateway,
    运行命令:
    curl -H 'Accept:application/json' -X POST 'http://127.0.0.1:${management.port}/shutdown',
    这条命令会先让这个微服务在注册中心下掉自己,然后再停掉自己。

mybatis, pageHelper (分页), druid (连接池)

redis(序列化采用的是jdk默认序列化方案)

  1. 服务的部署如下图:

    必发365游戏官方网址 11部署

  2. 需要注意的就是,整个系统只有gateway是可以被端访问到的,其他服务的所有接口,都应该只能在内网访问,这也可以让其他服务不做很强的权限验证。

  3. 代码里面细节很多,比如cache如何使用,
    GlobalCacheHelper,hystrix的自定义设置,全链路traceId的设置,信息的国际化、健康检查(去检查了cache、db等其他项,还检查了每一项的返回时间),这里没办法展开一一讲解。有问题请直接github上开issue,或者公众号联系我:不如假如。

slf4j & logback(及其配置)

国际化配置

全局错误信息catch

线程池

服务健康检查, 服务全链路健康检查

代码的业务是:首先有一个登录页面,用户登录以后,能看到一个微博(moment)列表,同时也可以添加微博。在每个微博的最右边,可以看到每个微博的评论列表,也能添加评论。前端代码写的非常粗糙。。。
如果你真的运行了,求不吐槽。

这里不谈根据业务的需求去选择是否微服务化,也不争辩什么时候该用什么样的框架,我这里只是把我们app后端的代码,抽象成一个代码库,希望能为你提供帮助。我们产品上线2个多月,后端基本没有出过问题(也跟量小有关系)。

运行

首先,你在本地需要有一个redis和mysql,redis默认启动就可以。数据库表的创建语句见下:

create database test;

CREATE TABLE `account` (

`user_id` varchar(127) NOT NULL DEFAULT ”,

`user_name` varchar(127) NOT NULL DEFAULT ”,

`password` varchar(127) NOT NULL DEFAULT ”,

`gmt_created` datetime NOT NULL,

`gmt_modified` datetime DEFAULT NULL,

`is_deleted` tinyint(1) NOT NULL DEFAULT ‘0’,

PRIMARY KEY (`user_id`),

KEY `index_user_id` (`user_id`) KEY_BLOCK_SIZE=10

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `moment` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`user_id` varchar(127) COLLATE utf8mb4_unicode_ci NOT NULL,

`content` text COLLATE utf8mb4_unicode_ci,

`gmt_created` datetime NOT NULL,

`gmt_modified` datetime DEFAULT NULL,

`is_deleted` tinyint(1) DEFAULT ‘0’,

PRIMARY KEY (`id`),

KEY `index_user_id` (`user_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

create database test2;

CREATE TABLE `comment` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`moment_id` bigint(20) unsigned NOT NULL,

`content` text COLLATE utf8mb4_unicode_ci,

`gmt_created` datetime NOT NULL,

`gmt_modified` datetime DEFAULT NULL,

`is_deleted` tinyint(1) DEFAULT ‘0’,

PRIMARY KEY (`id`),

KEY `index_moment_id` (`moment_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

系统环境需要Java 8 和 maven 3及以上

运行命令如下 (需要按顺序运行每个模块):

cd spring-cloud-parent

mvn clean install -DskipTests

cd ../spring-cloud-client

mvn clean install -DskipTests

cd ../spring-cloud-starter

mvn clean install -DskipTests

cd spring-cloud-eureka

mvn clean spring-boot:run

cd spring-cloud-account

mvn clean spring-boot:run

cd spring-cloud-biz

mvn clean spring-boot:run

cd spring-cloud-gateway

mvn clean spring-boot:run

浏览器中打开

代码解释

代码中的依赖关系见下图:

必发365游戏官方网址 12

spring-cloud-parent

是一个空的mvn project,
包含了一些被其他项目所需要的公共的依赖。我这里创建parent,仅仅是因为我不想把很多相同的依赖在spring-cloud-eureka、spring-cloud-biz、spring-cloud-account和spring-cloud-gateway这几个项目中重复写一遍,所以,一般微服务project都集成spring-cloud-parent。嗯,懒惰是人类的第一生产力。

spring-cloud-starter

是一个我创建的spring starter,
里面包含的是一些公共的bean,和一些公共的bean配置,比如国际化locle配置、缓存CacheService配置,messageConvertor配置等,spring-cloud-biz、spring-cloud-account和spring-cloud-gateway等微服务都需要依赖和共用这些基础配置。

spring-cloud-client

是一个公共依赖,这里包含的是一个util类,以及多个模块需要共同使用的、和数据库表对应的Java
model.

spring-cloud-eureka

是一个服务注册和服务发现中心,需要集群化。代码里,我把spring-cloud-config动态配置项也集成在这里面了。服务发现的心跳检测时间,也从15s改到了5s,这对于快速发现节点故障很有作用。

spring-cloud-account

是整个工程的账号服务模块。对于一个大型的系统来讲,单独的账号服务系统是很有必要解耦出来的。

spring-cloud-biz

是实际的业务模块,包括发微博模块和评论模块。整个代码中动态使用了两个数据库,我这里对数据库的切分只是简单从业务上切分,既:微博在一个库中,评论在另外一个库中,同时写了一个@TargetDataSource注解,方便动态切换数据库。其实对于分库、分表、读写分离,代码都是类似的。

spring-cloud-gateway

必发365游戏官方网址,是整个系统的网关服务,所有来自端上的请求,包括app或者web页面,都需要先到网关,由网关做过处理之后再转发给其他服务。比如,网关需要处理登录状态问题,需要处理上传文件问题,需要做一些过滤,以及其他多种切面上的事情。

总结


上就是我对Java大型互联网-高级架构师教你如何实践微服务架构全方位的技术问题
及其优化总结,分享给大家,希望大家知道什么是Java大型互联网-高级架构师教你如何实践微服务架构全方位的技术问题及其优化。觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!

1、多写多敲代码,好的代码与扎实的基础知识一定是实践出来的

2、可以去百度搜索腾讯课堂图灵学院的视频来学习一下java架构实战案例,还挺不错的。

最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步。

3丶想了解学习以上课程内容可加群:569068099验证码:简书(06 必过)

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website