精华内容
下载资源
问答
  • 本文来自于weixin,本章从对遗留系统进行微服务改造的原则要求出发,探讨如何使用 Dubbo框架实现单体系统向微服务的迁移。Themicroservicesstyleofarchitecture highlightsrisingabstractionsinthedeveloperworld ...
  • 本文来自于技术琐话,本章介绍了遗留系统的特点、改造策略和场景,并结合一个实战案例进行了讲解,希望对您的学习有所帮助。在传统企业甚至互联网企业中往往存在大量的遗留系统,这些遗留系统大多都能够正常工作,有...
  • 一种电力调度自动化系统微服务改造技术.pdf
  • - 干货分享 -任何人类的设计都会腐化,很遗憾软件尤其会...其实,对于单体系统,也可以按照上下文拆分领域。如果这样做了,再把代码级的领域拆分为部署视角的微服务,也不是那...

    -     干货分享     - 

    任何人类的设计都会腐化,很遗憾

    软件尤其会...

    其实,对于单体系统,也可以按照上下文拆分领域。如果这样做了,再把代码级的领域拆分为部署视角的微服务,也不是那么难的事情了。

    -     微服务架构的九大特征     - 

    这里提一个思考题,这些特征哪些是必须的?没有就不能称之为微服务架构?


    -     可视化的好处     - 

    • 一图胜千言

    • 掌握系统业务

    • 明确系统边界

    • 小步改造系统

    用户画像和旅程

    事件风暴

    聚合示例

    服务画布

    干遗留系统的几种模式

    作者:毛超

    来源:DDD 中国峰会

    展开全文
  • 一种电力调度自动化系统微服务改造技术.rar
  • 系统微服务改造经验谈

    千次阅读 2018-11-06 11:52:40
    微服务是当下最流行的应用架构了,它跟容器云、DevOps 合称新时代三剑客,帮我们化解业务发展过快导致的产品迭代压力,让我们有自由选择最适合自己团队的技术栈,让系统能够承载互联网海量用户的访问。近些年在厂商...

    微服务是当下最流行的应用架构了,它跟容器云、DevOps 合称新时代三剑客,帮我们化解业务发展过快导致的产品迭代压力,让我们有自由选择最适合自己团队的技术栈,让系统能够承载互联网海量用户的访问。近些年在厂商、社区和用户等各方努力推动下,微服务相关的理论和产品都日趋成熟,从零开始搭建微服务变得非常简单快捷,那我们是否就此全面进入微服务时代呢?

    微服务的演进成熟需要时间,我们熟悉掌握这套新技术也需要时间,除此之外机房里面还跑着大量的单体式应用,它们需要继续维护和升级,任何时候我们都不可能抛开历史轻松上阵。这些单体式应用还担负着公司的核心业务,全部推倒重来、休克式重构是不可取的,投入大周期长,风险完全不可控。

    我们必须学会边行车边换胎的技能,在不影响现网业务的前提下推动微服务改造,让老系统焕发新的生命力,继续支持业务下一个十年的发展。本场 Chat 将跟你一起探讨微服务改造相关的经验方法,让你更加从容地拥抱微服务:

    1. 边行车边换胎三步走演进策略。
    2. 隔离网关接管新旧系统间交互。
    3. 单体式应用拆解微服务的方法。
    4. 旧模块微服务改造优先级原则。
    5. 微服务改造是否结束判断标准。
    6. 微服务架构新挑战与解决方案。

    阅读全文: http://gitbook.cn/gitchat/activity/5ba3bb8ee5a9df061c33d083

    您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

    FtooAtPSkEJwnW-9xkCLqSTRpBKX

    展开全文
  • 摘要: 在 2016 年 11 月份的《技术...只不过在拥有众多遗留系统的组织内,将曾经的单体系统拆分为微服务并不是一件容易的事情。Credit: Justin Kenneth Rowley. You can find the original photo at flickr.T...

    摘要: 在 2016 年 11 月份的《技术雷达》中,ThoughtWorks 给予了微服务很高的评价。同时,也有越来越多的组织将实施微服务作为架构演进的一个必选方向。只不过在拥有众多遗留系统的组织内,将曾经的单体系统拆分为微服务并不是一件容易的事情。

    Credit: Justin Kenneth Rowley. You can find the original photo at flickr.

    The microservices style of architecture highlights rising abstractions in the developer world because of containerization and the emphasis on low coupling, offering a high level of operational isolation. Developers can think of a container as a self-contained process and the PaaS as the common deployment target, using the microservices architecture as the common style. Decoupling the architecture allows the same for teams, cutting down on coordination cost among silos. Its attractiveness to both developers and DevOps has made this the de facto standard for new development in many organizations.

    在 2016 年 11 月份的《技术雷达》中,ThoughtWorks 给予了微服务很高的评价。同时,也有越来越多的组织将实施微服务作为架构演进的一个必选方向。只不过在拥有众多遗留系统的组织内,将曾经的单体系统拆分为微服务并不是一件容易的事情。本文将从对遗留系统进行微服务改造的原则要求出发,探讨如何使用 Dubbo 框架实现单体系统向微服务的迁移。

    一、原则要求

    想要对标准三层架构的单体系统进行微服务改造——简言之——就是将曾经单一进程内服务之间的本地调用改造为跨进程的分布式调用。这虽然不是微服务改造的全部内容,但却直接决定了改造前后的系统能否保持相同的业务能力,以及改造成本的多少。

    1.1 适合的框架

    在微服务领域,虽然技术栈众多,但无非 RPC 与 RESTful 两个流派,这其中最具影响力的代表当属 Dubbo 与 Spring Cloud 了 。他们拥有相似的能力,却有着截然不同的实现方式——本文并不是想要对微服务框架的选型过程进行深入剖析,也不想对这两种框架的孰优孰劣进行全面比较——本章所提到的全部这些原则要求都是超越具体实现的,其之于任何微服务框架都应该是适用的。读者朋友们大可以把本文中的 Dubbo 全部替换为 Spring Cloud,而并不会对最终结果造成任何影响,唯一需要改变的仅仅是实现的细节过程而已。因此,无论最后抉择如何,都是无所谓对错的,关键在于:要选择符合组织当下现状的最适合的那一个。

    1.2 方便的将服务暴露为远程接口

    单体系统,服务之间的调用是在同一个进程内完成的;而微服务,是将独立的业务模块拆分到不同的应用系统中,每个应用系统可以作为独立的进程来部署和运行。因此进行微服务改造,就需要将进程内方法调用改造为进程间通信。进程间通信的实现方式有很多种,但显然基于网络调用的方式是最通用且易于实现的。那么能否方便的将本地服务暴露为网络服务,就决定了暴露过程能否被快速实施,同时暴露的过程越简单则暴露后的接口与之前存在不一致性的风险也就越低。

    1.3 方便的生成远程服务调用代理

    当服务被暴露为远程接口以后,进程内的本地实现将不复存在。简化调用方的使用——为远程服务生成相应的本地代理,将底层网络交互细节进行深层次的封装——就显得十分必要。另外远程服务代理在使用与功能上不应该与原有本地实现有任何差别。

    1.4 保持原有接口不变或向后兼容

    在微服务改造过程中,要确保接口不变或向后兼容,这样才不至于对调用方产生巨大影响。在实际操作过程中,我们有可能仅仅可以掌控被改造的系统,而无法访问或修改调用方系统。倘若接口发生重大变化,调用方系统的维护人员会难以接受:这会对他们的工作产生不可预估的风险和冲击,还会因为适配新接口而产生额外的工作量。

    1.5 保持原有的依赖注入关系不变

    基于 Spring 开发的遗留系统,服务之间通常是以依赖注入的方式彼此关联的。进行微服务改造后,原本注入的服务实现变成了本地代理,为了尽量减少代码变更,最好能够自动将注入的实现类切换为本地代理。

    1.6 保持原有代码的作用或副作用效果不变

    这一点看上去有些复杂,但却是必不可少的。改造后的系统跟原有系统保持相同的业务能力,当且仅当改造后的代码与原有代码保持相同的作用甚至是副作用。这里要额外提及的是副作用。我们在改造过程中可以很好的关注一般作用效果,却往往会忽视副作用的影响。举个例子,Java 内部进行方法调用的时候参数是以引用的方式传递的,这意味着在方法体中可以修改参数里的值,并将修改后的结果“返回”给被调用方。看下面的例子会更容易理解:

    public void innerMethod(Map map) {
     map.put("key", "new");
    }

    public void outerMethod() {
    Map map = new HashMap<>();
    map.put("key", "old");
    System.out.println(map); // {key=old}
    this.innerMethod(map);
    System.out.println(map); // {key=new}
    }

    这段代码在同一个进程中运行是没有问题的,因为两个方法共享同一片内存空间,innerMethod 对 map 的修改可以直接反映到 outerMethod 方法中。但是在微服务场景下事实就并非如此了,此时 innerMethod 和 outerMethod 运行在两个独立的进程中,进程间的内存相互隔离,innerMethod修改的内容必须要主动回传才能被 outerMethod 接收到,仅仅修改参数里的值是无法达到回传数据的目的的。

    此处副作用的概念是指在方法体中对传入参数的内容进行了修改,并由此对外部上下文产生了可察觉的影响。显然副作用是不友好且应该被避免的,但由于是遗留系统,我们不能保证其中不会存在诸如此类写法的代码,所以我们还是需要在微服务改造过程中,对副作用的影响效果进行保持,以获得更好的兼容性。

    1.7 尽量少改动(最好不改动)遗留系统的内部代码

    多数情况下,并非所有遗留系统的代码都是可以被平滑改造的:比如,上面提到的方法具有副作用的情况,以及传入和传出参数为不可序列化对象(未实现 Serializable 接口)的情况等。我们虽然不能百分之百保证不对遗留系统的代码进行修改,但至少应该保证这些改动被控制在最小范围内,尽量采取变通的方式——例如添加而不是修改代码——这种仅添加的改造方式至少可以保证代码是向后兼容的。

    1.8 良好的容错能力

    不同于进程内调用,跨进程的网络通信可靠性不高,可能由于各种原因而失败。因此在进行微服务改造的时候,远程方法调用需要更多考虑容错能力。当远程方法调用失败的时候,可以进行重试、恢复或者降级,否则不加处理的失败会沿着调用链向上传播(冒泡),从而导致整个系统的级联失败。

    1.9 改造结果可插拔

    针对遗留系统的微服务改造不可能保证一次性成功,需要不断尝试和改进,这就要求在一段时间内原有代码与改造后的代码并存,且可以通过一些简单的配置让系统在原有模式和微服务模式之间进行无缝切换。优先尝试微服务模式,一旦出现问题可以快速切换回原有模式(手动或自动),循序渐进,直到微服务模式变得稳定。

    1.10 更多

    当然微服务改造的要求远不止上面提到的这些点,还应该包括诸如:配置管理、服务注册与发现、负载均衡、网关、限流降级、扩缩容、监控和分布式事务等,然而这些需求大部分是要在微服务系统已经升级改造完毕,复杂度不断增加,流量上升到一定程度之后才会遇到和需要的,因此并不是本文关注的重点。但这并不意味着这些内容就不重要,没有他们微服务系统同样也是无法正常、平稳、高速运行的。

    二、模拟一个单体系统

    2.1 系统概述

    我们需要构建一个具有三层架构的单体系统来模拟遗留系统,这是一个简单的 Spring Boot 应用,项目名叫做 hello-dubbo。本文涉及到的所有源代码均可以到 Github 上查看和下载。

    首先,系统存在一个模型 User 和对该模型进行管理的 DAO,并通过 UserService 向上层暴露访问 User 模型的接口;另外,还存在一个 HelloService,其调用 UserService 并返回一条问候信息;之后,由 Controller 对外暴露 RESTful 接口;最终再通过 Spring Boot 的 Application 整合成一个完整应用。

    2.2 模块化拆分

    通常来说,一个具有三层架构的单体系统,其 Controller、Service 和 DAO 是存在于一整个模块内的,如果要进行微服务改造,就要先对这个整体进行拆分。拆分的方法是以 Service 层为分界,将其分割为两个子模块:Service 层往上作为一个子模块(称为 hello-web),对外提供 RESTful 接口;Service 层往下作为另外一个子模块(称为 hello-core),包括 Service、DAO 以及模型。hello-core 被 hello-web 依赖。当然,为了更好的体现面向契约的编程精神,可以把 hello-core 再进一步拆分:所有的接口和模型都独立出来,形成 hello-api,而 hello-core 依赖 hello-api。最终,拆分后的模块关系如下:

    hello-dubbo

    |-- hello-web(包含 Application 和 Controller)

    |-- hello-core(包含 Service 和 DAO 的实现)

        |-- hello-api(包含 Service 和 DAO 的接口以及模型)

    2.3 核心代码分析

    2.3.1 User

    public class User implements Serializable {
     private String id;
     private String name;
     private Date createdTime;

    public String getId() {

    return this.id;

    }
    public void setId(String id) {

    this.id = id;

    }

    public String getName() {

    return this.name;

    }
    public void setName(String name) {

    this.name = name;

    }

    public Date getCreatedTime() {

    return this.createdTime;

    }
    public void setCreatedTime(Date createdTime) {

    this.createdTime = createdTime;

    }

    @Override
    public String toString() {

    }
    }

    User 模型是一个标准的 POJO,实现了 Serializable 接口(因为模型数据要在网络上传输,因此必须能够支持序列化和反序列化)。为了方便控制台输出,这里覆盖了默认的 toString 方法。

    2.3.2 UserRepository


    public interface UserRepository {
     User getById(String id);

    void create(User user);
    }

    UserRepository 接口是访问 User 模型的 DAO,为了简单起见,该接口只包含两个方法:getById 和 create

    2.3.3 InMemoryUserRepository

    @Repository
    public class InMemoryUserRepository implements UserRepository {
     private static final Map STORE = new HashMap<>();

    static {

    }

    @Override
    public User getById(String id) {

    return STORE.get(id);

    }

    @Override
    public void create(User user) {

    STORE.put(user.getId(), user);

    }
    }

    InMemoryUserRepository 是 UserRepository 接口的实现类。该类型使用一个 Map 对象 STORE 来存储数据,并通过静态代码块向该对象内添加了一个默认用户。getById 方法根据 id 参数从 STORE 中获取用户数据,而 create 方法就是简单将传入的 user 对象存储到 STORE 中。由于所有这些操作都只是在内存中完成的,因此该类型被叫做 InMemoryUserRepository

    2.3.4 UserService

    public interface UserService {
     User getById(String id);

    void create(User user);
    }

    与 UserRepository 的方法一一对应,向更上层暴露访问接口。

    2.3.5 DefaultUserService

    @Service("userService")
    public class DefaultUserService implements UserService {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUserService.class);

    @Autowired
    private UserRepository userRepository;

    @Override
    public User getById(String id) {

    }

    @Override
    public void create(User user) {

    }
    }

    DefaultUserService 是 UserService 接口的默认实现,并通过 @Service 注解声明为一个服务,服务 id 为 userService(该 id 在后面会需要用到)。该服务内部注入了一个 UserRepository 类型的对象 userRepositorygetUserById 方法根据 id 从 userRepository 中获取数据,而 createUser 方法则将传入的 user 参数通过 userRepository.create 方法存入,并在存入之前设置了该对象的创建时间。很显然,根据 1.6 节关于副作用的描述,为 user 对象设置创建时间的操作就属于具有副作用的操作,需要在微服务改造之后加以保留。为了方便看到系统工作效果,这两个方法里面都打印了日志。

    2.3.6 HelloService

    public interface HelloService {
     String sayHello(String userId);
    }

    HelloService 接口只提供一个方法sayHello,就是根据传入的userId 返回一条对该用户的问候信息。

    2.3.7 DefaultHelloService

    @Service("helloService")
    public class DefaultHelloService implements HelloService {
     @Autowired
     private UserService userService;

    @Override
    public String sayHello(String userId) {

    }
    }

    DefaultHelloService 是 HelloService 接口的默认实现,并通过 @Service 注解声明为一个服务,服务 id 为 helloService(同样,该名称在后面的改造过程中会被用到)。该类型内部注入了一个 UserService 类型的对象 userServicesayHello 方法根据 userId 参数通过 userService 获取用户信息,并返回一条经过格式化后的消息。

    2.3.8 Application

    @SpringBootApplication
    public class Application {

     public static void main(String[] args) throws Exception {

    SpringApplication.run(Application.class, args);

    }
    }

    Application 类型是 Spring Boot 应用的入口,详细描述请参考 Spring Boot 的官方文档,在此不详细展开。

    2.3.9 Controller

    @RestController
    public class Controller {
     @Autowired
     private HelloService helloService;

    @Autowired
    private UserService userService;

    @RequestMapping("/hello/{userId}")
    public String sayHello(@PathVariable("userId") String userId) {

    return this.helloService.sayHello(userId);

    }

    @RequestMapping(path = "/create", method = RequestMethod.POST)
    public String createUser(@RequestParam("userId") String userId, @RequestParam("name") String name) {

    }
    }

    Controller 类型是一个标准的 Spring MVC Controller,在此不详细展开讨论。仅仅需要说明的是这个类型注入了 HelloService 和 UserService 类型的对象,并在 sayHello 和 createUser 方法中调用了这两个对象中的有关方法。

    2.4 打包运行

    hello-dubbo 项目包含三个子模块:hello-apihello-core 和 hello-web,是用 Maven 来管理的。到目前为止所涉及到的 POM 文件都比较简单,为了节约篇幅,就不在此一一列出了,感兴趣的朋友可以到项目的 Github 仓库上自行研究。

    hello-dubbo 项目的打包和运行都非常直接:

    编译、打包和安装

    在项目根目录下执行命令

    $ mvn clean install

    运行

    在 hello-web 目录下执行命令

    $ mvn spring-boot:run

    测试结果如下,注意每次输出括号里面的日期时间,它们都应该是有值的。

    再返回 hello-web 系统的控制台,查看一下日志输出,时间应该与上面是一样的。

    三、动手改造

    3.1 改造目标

    上一章,我们已经成功构建了一个模拟系统,该系统是一个单体系统,对外提供了两个 RESTful 接口。本章要达到的目标是将该单体系统拆分为两个独立运行的微服务系统。如 2.2 节所述,进行模块化拆分是实施微服务改造的重要一步,因为在接下来的描述中会暗含一个约定:hello-webhello-core hello-api 这三个模块与上一章中所设定的能力是相同的。基于 1.7 节所提到的“尽量少改动(最好不改动)遗留系统的内部代码”的改造要求,这三个模块中的代码是不会被大面积修改的,只会有些许调整,以适应新的微服务环境。

    具体将要实现的目标效果如下:

    第一个微服务系统:

    hello-web(包含 Application 和 Controller)
    |-- hello-service-reference(包含 Dubbo 有关服务引用的配置)

    |-- hello-api(包含 Service 和 DAO 的接口以及模型)

    第二个微服务系统:

    hello-service-provider(包含 Dubbo 有关服务暴露的配置)
    |-- hello-core(包含 Service 和 DAO 的实现)

    |-- hello-api(包含 Service 和 DAO 的接口以及模型)

    hello-web 与原来一样,是一个面向最终用户提供 Web 服务的终端系统,其只包含 Application、Controller、Service 接口、 DAO 接口以及模型,因此它本身是不具备任何业务能力的,必须通过依赖 hello-service-reference 模块来远程调用 hello-service-provider 系统才能完成业务。而 hello-service-provider 系统则需要暴露可供 hello-service-reference 模块调用的远程接口,并实现 Service 及 DAO 接口定义的具体业务逻辑。

    本章节就是要重点介绍 hello-service-provider 和 hello-service-reference 模块是如何构建的,以及它们在微服务改造过程中所起到的作用。

    3.2 暴露远程服务

    Spring Boot 和 Dubbo 的结合使用可以引入诸如 spring-boot-starter-dubbo 这样的起始包,使用起来会更加方便。但是考虑到项目的单纯性和通用性,本文仍然延用 Spring 经典的方式进行配置。

    首先,我们需要创建一个新的模块,叫做 hello-service-provider,这个模块的作用是用来暴露远程服务接口的。依托于 Dubbo 强大的服务暴露及整合能力,该模块不用编写任何代码,仅需添加一些配置即可完成。

    注:有关 Dubbo 的具体使用和配置说明并不是本文讨论的重点,请参考官方文档。

    3.2.1 添加 dubbo-services.xml 文件

    dubbo-services.xml

     配置是该模块的关键,Dubbo 就是根据这个文件,自动暴露远程服务的。这是一个标准 Spring 风格的配置文件,引入了 Dubbo 命名空间,需要将其摆放在 

    src/main/resources/META-INF/spring

     目录下,这样 Maven 在打包的时候会自动将其添加到 classpath。

    3.2.2 添加 POM 文件

    有关 Maven 的使用与配置也不是本文关注的重点,但是这个模块用到了一些 Maven 插件,在此对这些插件的功能和作用进行一下描述。

    3.2.3添加 assembly.xml 文件

    Assembly 插件的主要功能是对项目重新打包,以便自定义打包方式和内容。对本项目而言,需要生成一个压缩包,里面包含所有运行该服务所需要的 jar 包、配置文件和启动脚本等。Assembly 插件需要 assembly.xml 

    文件来描述具体的打包过程,该文件需要摆放在 src/main/assembly 目录下。有关 assembly.xml 文件的具体配置方法,请参考官方文档。

    3.2.4 添加 logback.xml 文件

    由于在 POM 文件中指定了使用 logback 作为日志输出组件,因此还需要在 logback.xml 

    文件中对其进行配置。该文件需要摆放在 src/main/resources 目录下,有关该配置文件的具体内容请参见代码仓库,有关配置的详细解释,请参考官方文档。

    3.2.5 打包
    由于已经在 POM 文件中定义了打包的相关配置,因此直接在 hello-service-provider 目录下运行以下命令即可:

    $ mvn clean package

    成功执行以后,会在其 target 目录下生成一个名为

     hello-service-provider-0.1.0-SNAPSHOT-assembly.tar.gz

     的压缩包,里面的内容如图所示:

    3.2.6 运行

    如此配置完成以后,就可以使用如下命令来启动服务:

    $ MAVEN_OPTS="-Djava.net.preferIPv4Stack=true" mvn exec:java

    注:在 macOS 系统里,使用 multicast 机制进行服务注册与发现,需要添加-Djava.net.preferIPv4Stack=true 参数,否则会抛出异常。

    可以使用如下命令来判断服务是否正常运行:

    $ netstat -antl | grep 20880

    如果有类似如下的信息输出,则说明运行正常。

    如果是在正式环境运行,就需要将上一步生成的压缩包解压,然后运行 bin 目录下的相应脚本即可。

    3.2.7 总结

    使用这种方式来暴露远程服务具有如下一些优势:

    使用 Dubbo 进行远程服务暴露,无需关注底层实现细节

    对原系统没有任何入侵,已有系统可以继续按照原来的方式启动和运行

    暴露过程可插拔

    Dubbo 服务与原有服务在开发期和运行期均可以共存

    无需编写任何代码

    3.3 引用远程服务

    3.3.1 添加服务引用

    与 hello-service-provider 模块的处理方式相同,为了不侵入原有系统,我们创建另外一个模块,叫做 hello-service-reference。这个模块只有一个配置文件 dubbo-references.xml 放置在 src/main/resources/META-INF/spring/ 目录下。文件的内容非常简单明了:

    但不同于 hello-service-provider 模块的一点在于,该模块只需要打包成一个 jar 即可,POM 文件内容如下:

    总结一下,我们曾经的遗留系统分为三个模块 hello-web, hello-core 和 hello-api。经过微服务化处理以后,hello-core 和 hello-api 被剥离了出去,加上 hello-service-provider 模块,形成了一个可以独立运行的 hello-service-provider 系统,因此需要打包成一个完整的应用;而 hello-web 要想调用 hello-core 提供的服务,就不能再直接依赖 hello-core 模块了,而是需要依赖我们这里创建的 hello-service-reference 模块,因此 hello-service-reference 是作为一个依赖库出现的,其目的就是远程调用 hello-service-provider 暴露出来的服务,并提供本地代理。

    这时 hello-web模块的依赖关系就发生了变化:原来 hello-web 模块直接依赖 hello-core,再通过 hellocore 间接依赖 hello-api,而现在我们需要将其改变为直接依赖 hello-service-reference 模块,再通过 hello-service-reference 模块间接依赖 hello-api。改造前后的依赖关系分别为:

    3.3.2 启动服务

    因为是测试环境,只需要执行以下命令即可,但在进行本操作之前,需要先启动 hello-service-provider 服务。

    $ MAVEN_OPTS="-Djava.net.preferIPv4Stack=true" mvn spring-boot:run

    Oops!系统并不能像期望的那样正常运行,会抛出如下异常:

    意思是说 net.tangrui.demo.dubbo.hello.web.Controller 这个类的 helloService 字段需要一个类型为 net.tangrui.demo.dubbo.hello.service.HelloService 的 Bean,但是没有找到。相关代码片段如下:

    @RestController
    Public class Controller {
     @Autowired
     private HelloService helloService;

    @Autowired
    private UserService userService;

    ...
    }

    显然,helloService 和 userService 都是无法注入的,这是为什么呢?

    原因自然跟我们修改 hello-web 这个模块的依赖关系有关。原本 hello-web 是依赖于 hello-core的,hello-core 里面声明了 HelloService 和 UserService 这两个服务(通过 @Service 注解),然后 Controller 在 @Autowired 的时候就可以自动绑定了。但是,现在我们将 hello-core替换成了 hello-service-reference,在 hello-service-reference 的配置文件中声明了两个对远程服务的引用,按道理来说这个注入应该是可以生效的,但显然实际情况并非如此。

    仔细思考不难发现,我们在执行 mvn exec:java 命令启动 hello-service-provider 模块的时候指定了启动 com.alibaba.dubbo.container.Main 类型,然后才会开始启动并加载 Dubbo 的有关配置,这一点从日志中可以得到证实(日志里面会打印出来很多带有 [DUBBO] 标签的内容),显然在这次运行中,我们并没有看到类似这样的日志,说明 Dubbo 在这里没有被正确启动。归根结底还是 Spring Boot 的原因,即 Spring Boot 需要一些配置才能够正确加载和启动 Dubbo。

    让 Spring Boot 支持 Dubbo 有很多种方法,比如前面提到的 spring-boot-starter-dubbo 起始包,但这里同样为了简单和通用,我们依旧采用经典的方式来解决。

    继续思考,该模块没有成功启动 Dubbo,仅仅是因为添加了对 hello-service-reference 的引用,而 hello-service-reference 模块就只有一个文件 dubbo-references.xml,这就说明 Spring Boot 并没有加载到这个文件。顺着这个思路,只需要让 Spring Boot 能够成功加载这个文件,问题就可以了。Spring Boot 也确实提供了这样的能力,只可惜无法完全做到代码无侵入,只能说这些改动是可以被接受的。修改方式是替换 Application 中的注解(至于为什么要修改成这样的结果,超出了本文的讨论范围,请自行 Google)。

    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    @ImportResource("classpath:META-INF/spring/dubbo-references.xml")
    public class Application {
     public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args);

    }
    }

    这里的主要改动,是将一个 @SpringBootApplication 注解替换为 @Configuration@EnableAutoConfiguration@ComponentScan 和 @ImportResource 四个注解。不难看出,最后一个 @ImportResource 就是我们需要的。

    这时再重新尝试启动,就一切正常了。

    但是,我们如何验证结果确实是从 hello-service-provider 服务过来的呢?这时就需要用到 DefaultUserService里面的那几行日志输出了,回到 hello-service-provider 服务的控制台,能够看到类似这样的输出:

    如此便可以确信系统的拆分是被成功实现了。再试试创建用户的接口:

    $ curl -X POST 'http://127.0.0.1:8080/create?userId=huckleberry&name=Huckleberry%20Finn'

    等等,什么!括号里面的创建时间为什么是 N/A,这说明 createdTime 字段根本没有值!

    3.4 保持副作用效果

    让我们先来回顾一下 1.6 节所提到的副作用效果。在 DefaultUserService.create 方法中,我们为传入的 user 参数设置了创建时间,这一操作就是我们要关注的具有副作用效果的操作。

    先说单体系统的情况。单体系统是运行在一个 Java 虚拟机中的,所有对象共享一片内存空间,彼此可以互相访问。系统在运行的时候,先是由 Controller.create 方法获取用户输入,将输入的参数封装为一个 user 对象,再传递给 UserService.create 方法(具体是在调用 DefaultUserService.create 方法),这时 user 对象的 createdTime 字段就被设置了。由于 Java 是以引用的方式来传递参数,因此在 create 方法中对 user 对象所做的变更,是能够反映到调用方那里的——即 Controller.create 方法里面也是可以获取到变更的,所以返回给用户的时候,这个 createdTime 就是存在的。

    再说微服务系统的情况。此时系统是独立运行在两个虚拟机中的,彼此之间的内存是相互隔离的。起始点同样是 hello-web 系统的 Controller.create 方法:获取用户输入,封装 user 对象。可是在调用 UserService.create 方法的时候,并不是直接调用DefaultUserService中的方法,而是调用了一个具有相同接口的本地代理,这个代理将 user 对象序列化之后,通过网络传输给了 hello-service-provider 系统。该系统接收到数据以后,先进行反序列化,生成跟原来对象一模一样的副本,再由UserService.create 方法进行处理(这回调用的就是 DefaultUserService里面的实现了)。至此,这个被设置过 createdTime 的 user 对象副本是一直存在于 hello-service-provider 系统的内存里面的,从来没有被传递出去,自然是无法被 hello-web系统读取到的,所以最终打印出来的结果,括号里面的内容就是 N/A 了。记得我们有在 DefaultUserService.create 方法中输出过日志,所以回到 hello-service-provider系统的控制台,可以看到如下的日志信息,说明在这个系统里面 createdTime 字段确实是有值的。

    那么该如何让这个副作用效果也能够被处于另外一个虚拟机中的 hello-web 系统感知到呢,方法只有一种,就是将变更后的数据回传。

    3.4.1 为方法添加返回值

    这是最容易想到的一种实现方式,简单的说就是修改服务接口,将变更后的数据返回。

    首先,修改 UserService 接口的 create 方法,添加返回值:

    public interface UserService {
     ...

    // 为方法添加返回值
    User create(User user);
    }

    然后,修改实现类中相应的方法,将变更后的 user 对象返回:

    @Service("userService")
    public class DefaultUserService implements UserService {
     ...

    @Override
    public User create(User user) {

    }
    }

    最后,修改调用方实现,接收返回值:

    @RestController
    public class Controller {
     ...

    @RequestMapping(path = "/create", method = RequestMethod.POST)
    public String createUser(@RequestParam("userId") String userId, @RequestParam("name") String name) {

    }
    }

    编译、运行并测试(如下图),正如我们所期望的,括号中的创建时间又回来了。其工作原理与本节开始时所描述的是一样的,只是方向相反而已。在此不再详细展开,留给大家自行思考。

    这种修改方式有如下一些优缺点:

    方法简单,容易理解

    改变了系统接口,且改变后的接口与原有接口不兼容(违背了 1.4 节关于“保持原有接口不变或向后兼容”原则的要求)

    由此也不可避免的造成了对遗留系统内部代码的修改(违背了 1.7 节关于“尽量少改动(最好不改动)遗留系统的内部代码”原则的要求)

    修改方式不可插拔(违背了 1.9 节“改造结果可插拔”原则的要求)

    由此可见,这种改法虽然简单,却是利大于弊的,除非我们能够完全掌控整个系统,否则这种修改方式的风险会随着系统复杂性的增加而急剧上升。

    3.4.2 添加一个新方法

    如果不能做到不改变接口,那我们至少要做到改变后的接口与原有接口向后兼容。保证向后兼容性的一种解决办法,就是不改变原有方法,而是添加一个新的方法。过程如下:

    首先,为 UserService 接口添加一个新的方法 __rpc_create。这个方法名虽然看起来有些奇怪,但却有两点好处:第一、不会和已有方法重名,因为 Java 命名规范不建议使用这样的标识符来为方法命名;第二、在原有方法前加上 __rpc_ 前缀,能够做到与原有方法对应,便于阅读和理解。示例如下:

    public interface UserService {
     ...

    // 保持原有方法不变
    void create(User user);

    // 添加一个方法,新方法需要有返回值
    User __rpc_create(User user);
    }

    然后,在实现类中实现这个新方法:

    @Service("userService")
    public class DefaultUserService implements UserService {
     ...

    // 保持原有方法实现不变
    @Override
    public void create(User user) {

    }

    // 添加新方法的实现
    @Override
    public User __rpc_create(User user) {

    }
    }

    有一点需要展开解释:在 __rpc_create 方法中,因为 user 参数是以引用的方式传递给 create方法的,因此 create 方法对参数所做的修改是能够被 __rpc_create 方法获取到的。这以后就与前面回传的逻辑是相同的了。

    第三,在服务引用端添加本地存根(有关本地存根的概念及用法,请参考官方文档)。

    需要在 hello-service-reference 模块中添加一个类 UserServiceStub,内容如下:

    public class UserServiceStub implements UserService {
     private UserService userService;

    public UserServiceStub(UserService userService) {

    this.userService = userService;

    }

    @Override
    public User getById(String id) {

    return this.userService.getById(id);

    }

    @Override
    public void create(User user) {

    User newUser = this.__rpc_create(user);

     user.setCreatedTime(newUser.getCreatedTime());

    }

    @Override
    public User __rpc_create(User user) {

    return this.userService.__rpc_create(user);

    }
    }

    该类型即为本地存根。简单来说,就是在调用方调用本地代理的方法之前,会先去调用本地存根中相应的方法,因此本地存根与服务提供方和服务引用方需要实现同样的接口。本地存根中的构造函数是必须的,且方法签名也是被约定好的——需要传入本地代理作为参数。其中 getById 和 __rpc_create 方法都是直接调用了本地代理中的方法,不必过多关注,重点来说说 create 方法。首先,create 调用了本地存根中的 __rpc_create 方法,这个方法透过本地代理访问到了服务提供方的相应方法,并成功接收了返回值 newUser,这个返回值是包含修改后的 createdTime 字段的,于是我们要做的事情就是从 newUser 对象里面获取到 createdTime 字段的值,并设置给 user 参数,以达到产生副作用的效果。此时 user 参数会带着新设置的 createdTime 的值,将其“传递”给 create 方法的调用方。

    最后,在 dubbo-references.xml 文件中修改一处配置,以启用该本地存根:

     interface="net.tangrui.demo.dubbo.hello.service.UserService"
     version="1.0"
     stub="net.tangrui.demo.dubbo.hello.service.stub.UserServiceStub" />

    鉴于本地存根的工作机制,我们是不需要修改调用方 hello-web 模块中的任何代码及配置的。编译、运行并测试,同样可以达到我们想要的效果。

    这种实现方式会比第一种方式改进不少,但也有致命弱点:

    保持了接口的向后兼容性

    引入本地存根,无需修改调用方代码

    通过配置可以实现改造结果的可插拔

    实现复杂,尤其是本地存根的实现,如果遗留系统的代码对传入参数里的内容进行了无节制的修改的话,那么重现该副作用效果是非常耗时且容易出错的

    难以理解

    四、总结

    至此,将遗留系统改造为微服务系统的任务就大功告成了,而且基本上满足了文章最开始提出来的十点改造原则与要求(此处应给自己一些掌声),不知道是否对大家有所帮助?虽然示例项目是为了叙述要求而量身定制的,但文章中提到的种种理念与方法却实实在在是从实践中摸索和总结出来的——踩过的坑,遇到的问题,解决的思路以及改造的难点等都一一呈现给了大家。

    微服务在当下已经不是什么新鲜的技术了,但历史包袱依然是限制其发展的重要因素,希望这篇文章能带给大家一点启发,在接下来的工作中更好的拥抱微服务带来的变革。

    原文链接

    展开全文
  • 对于这样的应用系统和数据库,我们往往需要对它们进行拆分,通过微服务改造,保证系统能够不断地扩展和复用。   相比从头开始落地服务,对现有系统微服务改造,这会面临更多的挑战。   首先,应用和数据表...

      很多早期的互联网公司都有巨大的单体应用,底层的数据表集中放在一个数据库里,这些表加起来可能有几百张。对于这样的应用系统和数据库,我们往往需要对它们进行拆分,通过微服务化改造,保证系统能够不断地扩展和复用。
      相比从头开始落地服务,对现有系统做微服务化改造,这会面临更多的挑战。
      首先,应用和数据表紧密耦合在一起,代码模块和表是多对多的依赖关系。一个模块会访问多张表,多个模块也会对同一张表进行访问,而且由于表都在一个数据库里,开发人员往往会随意对表做关联,有时候甚至 Join 5~6 张表以上。这样,代码模块和表之间的关系是剪不断,理还乱,我们很难清晰地划分代码和数据表的边界,也就很难把它们封装成独立的微服务。
      还有,系统现在已经在运行了,我们的改造不能影响业务的稳定性。那微服务落地后,现有的系统要怎么对接微服务,数据要怎么迁移,才能保证系统的平滑过渡呢?
      所以,要想应对这些挑战,一方面,我们要保证比较合理的服务设计,才能达到优化系统架构的目的;另一方面,我们要做到整个过程对现有系统的影响比较小,才能达到系统改造顺利落地的目的。
      接下来,我就以 1 号店库存服务化改造为例,让你深入理解,我们是如何把库存相关的功能和数据表,从现有系统里剥离出来,最终构建独立的库存服务,并实现和业务系统平滑对接的。

    改造背景和目标

      我们先来看下这次架构改造的背景和目标。
      1 号店作为一个网上超市,售卖的商品种类有数十万个,包括 1 号店自营和第三方商家的商品。由于历史原因,所有商品相关的表都存在产品库里面,这里面有产品的表(产品、分类、品牌、组合关系、属性等)、商品 SKU 的表、商家和供应商的表、库存和价格的表等等,这些表加起来,数量超过了上百张。
      我们知道,商品是电商业务的核心,几乎所有的前后台系统都需要访问这个产品库,而这些系统的开发人员,早期的时候,只关心如何实现业务功能,对这些表的访问是怎么方便怎么来,有些 SQL 语句会对大量的表做 Join 关联。所以说,虽然系统是类似分布式的,但数据库是集中式的,如下图所示:
    在这里插入图片描述
      这样的方式,就给系统的维护带来了一系列的问题。
      从应用方面来说,各个系统功能重复建设,比如很多系统都会直接访问库存相关的表,类似的库存逻辑散布在很多地方;另外,如果修改了库存表的某个字段,这些系统同时会受影响,正所谓牵一发而动全身。
      从数据库方面来说,数据库的可用性是比较差的,如果某个系统有慢查询,它就很可能拖垮整个产品数据库,导致它不可用;还有,这么多系统同时访问产品库,数据库的连接数也经常不够用。
      所以,我们这次架构改造的目标,首先是对这个大数据库按照业务维度进行垂直拆分,比如分成产品数据库、库存数据库、价格数据库等等;然后基于这些拆分后的库,构建微服务,以接口的方式来支持数据库表的访问;最后将各个业务系统统一接入微服务,最终完成整个商品体系的微服务化改造。

    微服务改造过程

      你可以看到,这里涉及了多个微服务,如果同时进行服务化改造的话,牵扯太大,很难落地。于是,我们选择从库存微服务开始。一方面,库存的业务很重要,库存的规则也比较复杂,如果我们能够对库存逻辑进行优化,这会带来明显的业务价值;另一方面,电商的库存概念相对独立,涉及的表也比较少,我们可以相对容易地把它从现有体系中剥离出来。
      整个改造过程,从确定库存相关的表开始,到最后把库存表从产品库迁移出来,落到单独的库存数据库为止,一共分为两个阶段,每个阶段包含了 3 个步骤,具体如下图所示:
    在这里插入图片描述

    • 准备阶段:这个阶段为微服务改造做好前期的准备工作,具体步骤包括了圈表、收集SQL 和 SQL 拆分。
    • 实施阶段:这个阶段实际落地微服务,具体步骤包括微服务开发、服务接入和数据库独立
        通过这些良好定义的步骤,我们就很好地保证了整个库存微服务改造的有序和可控。接下来,我就具体说明下改造的各个步骤,包括哪些人负责哪些事情、具体的挑战在什么地方,这样,你可以深入地理解整个改造过程。

    准备阶段

      准备阶段的第一步,就是圈表。产品数据库有 100 多张表,圈表就是用来确定库存微服务
      具体包含哪些表,也就是确定服务的数据模型。在确定了表以后,库存微服务就负责这些表的访问,当然,库存微服务也不会访问其它的表,而业务系统后续将通过库存微服务的接口,实现对这些表的访问。
      圈表是微服务改造中比较有挑战性的地方,它实际上对应了服务的边界划分。只是针对老系统做服务化改造的时候,我们更多的是从数据库表的角度来考虑划分,这样更好落地。
      针对库存微服务来说,我们要求圈定的表,一方面要满足所有的库存访问需求,这些表之间关系紧密,和其它的表关联不大;另一方面,这些表的数量不能太多,一般不超过十几张。
      这样,我们既容易拆分数据库,又能控制服务的粒度,保证功能聚焦。
      在这个例子中,由于库存的概念比较独立,圈表相对比较容易,一共有 15 张表和库存直接相关,包括自营库存表 (这里有分表,实际是 12 张)、商家虚拟库存表、活动库存表和库存共享表,这些库存表之间是紧密相关的,它们一起决定了前台用户能看到的可用库存数量。
      这些库存相关的表都有商品 ID 字段,和商品基本信息表关联,我们知道,库存数量的计算不依赖于商品的具体信息。所以,这些库存表和其它表的关系比较弱,这样我们就可以比较清晰地实现库存表和其它表的切分,简化了库存服务的落地。
    在这里插入图片描述
      在微服务改造中,确定哪些表属于这个服务,会直接影响后续的所有改造工作,这需要有经验的业务架构师和数据架构师参与进来,通过深入地分析现有的业务场景和表的关系,才能对库表进行合理的划分。
      所以,你可以发现,对现有系统的改造,服务的边界划分主要是从圈表入手的,而不是从一个服务应该有哪些功能入手的,这一点和新服务设计是有所不同的。这有两方面原因:

    • 一方面,如果确定了服务包含哪些表,也就大致确定了服务有哪些功能,而表是现成的,它比业务功能要直观很多,所以从表入手比较高效;
    • 另一方面,如果从表入手,构造的服务和表是对应的,服务包含的是完整的表,不会产生一个表的一部分字段属于库存服务,而另一部分字段属于别的服务的情况,避免表字段的拆分带来额外的复杂性。
        值得注意的是,因为这是对现有系统的改造,为了避免一下子引入太多变化,我们先不对库存的表结构进行调整,表结构的优化可以放在服务的升级版里做,这样对业务系统的影响也最小。
    • 第二步是收集 SQL。在确定了哪些表属于库存服务后,我们会收集所有业务系统访问这些表的 SQL 语句,包括它的业务场景说明、访问频率等等。库存微服务后续就针对这些 SQL进行封装,提供相应的接口给业务系统使用。

      这里,服务开发团队负责提供 SQL 收集的 Excel 模板,各业务系统开发团队负责收集具体的 SQL。

    • 第三步是拆分 SQL。对于收集过来的 SQL 语句,有些 SQL 不仅仅访问圈定的这几张库存表,还会和产品库中的其他表进行关联。

      比如说,商品详情页需要展示商品详情,它会发起 SQL 查询商品基本信息表和库存表,一次性获取商品的基本信息和库存数量。针对这种情况,我们就需要把查询语句拆分为两条SQL,先查询商品表获取商品基本信息,再查询库存表获取库存数量。
      对于这样的 SQL 语句,我们就要求各个业务团队先进行拆分,保证最后提供给服务开发团队的 SQL,只包含访问库存的相关表。通过 SQL 拆分,我们切断了库存表和其他表的直接联系,等后面微服务落地后,业务系统就可以通过接入微服务,完成现有 SQL 的替换。
      SQL 拆分,会涉及一定的业务系统改造,这部分工作主要由各个研发团队负责,一般情况下,性能可能会受些影响,但问题不是很大。

    实施阶段

      完成了圈表、SQL 收集和拆分以后,接下来,我们就进入了服务实际落地的阶段。

    • 第四步是构建库存微服务。这里面包括了接口设计、代码开发、功能测试等步骤,服务开发团队会对业务方提供的 SQL 进行梳理,然后对接口做一定的通用化设计,避免为每个 SQL定制一个单独的接口,以此保证服务的复用能力。

      这部分工作由微服务开发团队负责,第一版的服务主要是做好接口设计,聚焦业务功能,以保证服务能够落地,业务系统能够顺利对接为目标。将来,服务可以持续迭代,内部做各种技术性优化,只要服务的接口保持不变,就不会影响业务系统。

    • 第五步是接入库存微服务。库存服务经过功能和性能验证以后,会由各个业务开发团队逐步接入,替换原来的 SQL语句。这部分工作主要由业务研发团队负责,难度不大,但需要耗费比较多的时间。
    • 最后一步是数据库独立。当服务接入完成,所有的 SQL语句都被替换后,业务系统已经不会直接访问这些库存的表。这时,我们就可以把库存相关的表,从原来的产品库中迁移出来,部署成为一个物理上独立的数据库。业务系统是通过服务来访问数据库的,因此,这个数据迁移对于业务系统来说是透明的,业务团队甚至都不用关心这些表的新位置。

      通过库存表独立成库,我们可以从物理层面,切断业务团队对这些表的依赖,同时,也可以大幅度降低产品库的压力,特别是大促的时候,库存读写压力是非常大的,数据库独立也为库存服务后续的技术优化打下了基础。
      这部分工作主要由微服务开发团队和 DBA 一起配合完成,主要是要避免业务系统还有遗漏的 SQL 语句,避免它们还在直接访问库存的表。我们可以在迁库前,通过代码扫描做好相应的检查工作。
      改造完成后的库存微服务架构如下图所示,库存微服务一共包含了 15 张表,对外有 30 多个接口,几十个业务系统接入库存服务。平时,库存服务会部署 50 个实例,大促时会部署更多,我们很容易通过加机器的方式,实现库存服务的水平扩展。
    在这里插入图片描述

    微服务改造小结

      到这里,我们的库存微服务就改造完成了,整个改造大概持续了 3 个月,主要是对接的工作比较耗时。
      从前面的步骤中,你可以看到,除了做好库存服务本身的设计开发工作,相关团队之间的配合也是非常重要的。
      在整个改造过程中,有很多团队之间沟通和确认的环节。比如说,服务开发团队圈定表以后,需要和业务开发团队一起确认,保证圈表的合理性;在业务团队拆分 SQL 的过程中,服务开发团队需要介入进去,帮助解决拆分时带来的性能和一致性问题;在服务接口设计和接入过程中,服务的接口可能需要重新调整,也可能有新的 SQL 进来,双方需要及时沟通,相互配合。
      这些都是纯技术层面的问题,值得一提的是,系统改造不会产生直接的业务价值,对于业务开发团队来说,他们往往还需要承担大量新需求的开发工作。所以,从项目推进的角度来看,这种核心服务的改造,很多时候都是技术一把手工程。在库存微服务改造过程中,我们也是老板高度重视,大家事先定好时间计划,每周 Review 进度,协调各个团队工作的优先级,确保改造的顺利落地。
      以上就是库存微服务改造的例子。1 号店的系统从 08 年就开始建设了,由于历史原因,形成了几个典型的大库,比如产品库、用户库等等,我们通过类似的微服务改造,逐步把这些大库拆分开,构建了一系列的基础服务,如订单服务、用户服务、产品服务、库存服务、价格服务等等。而且通过这些微服务化改造,我们同时提升了业务的复用性和系统的稳定性。
      最后,我在这里放了一张 1 号店的总体系统架构图,你可以深入看下,一个历史包袱很重的系统,它是如何经过服务化改造,最终变成一个能够高度复用和扩展的平台的。
    在这里插入图片描述

    总结

      基于现有系统进行改造和全新的服务设计是有所不同的,我们不能追求理想化和一步到位,而是要考虑到系统的平滑过渡,先实现微服务的顺利落地,后续再考虑各种优化
      今天,通过库存微服务改造的例子,提供了一种可行的微服务落地套路,可以顺利地完成老系统的架构升级。
      通过今天的分享,你对现有系统如何进行微服务化改造有了更深入的理解,希望你在实践中也能灵活运用。

    展开全文
  • 对于这样的应用系统和数据库,我们往往需要对它们进行拆分,通过微服务改造,保证系统能够不断地扩展和复用。 相比从头开始落地服务,对现有系统微服务改造,这会面临更多的挑战。 首先,应用和数据表紧密耦合...
  • 后端业务系统微服务改造.docx
  • 业务系统微服务改造方案.docx
  • 业务系统是任何一个用户产品的必须组成,充当着一个门面的角色,用户的输入就是这个系统需要维护的,数据存取是整个系统的核心。例如,广告业务系统的输入是广告主的投放约束、定向条件,微博业务系统的输入是短文字...
  • 4 月 29 日, BoCloud 博云微服务治理产品 BeyondMicroService 在 V2.2 的基础上完善功能、优化性能,最终推出最新版本 BeyondMicroService V2.3 。 新版本在原有的 SpringCloud 治理功能上,完善上一版本负载均衡...
  • 遗留系统改造微服务

    2020-02-21 19:51:06
    随着企业规模的扩大以及...这就造成遗留系统改造微服务比新系统直接使用微服务方式开发更复杂,在这个复杂的过程,寻求业务与技术上的平衡。 系统改造原由 1、新开发人员维护系统存在大量知识盲区,一些业务规则...
  • 随着RESTful、云计算、DevOps、持续交付等概念的深入人心,微服务(Microservices)逐渐成为...在该分享中,王磊将通过实际的案例,跟大家探讨使用微服务改造遗留系统的实践之路。 · 什么是微服务 · 微服务的诞生
  • 微服务改造设计参考

    2019-06-25 11:38:30
    【摘要】 本文总结微服务改造相关的可行性分析、工作量评估、设计思路等问题,供选型参考。典型的场景包括将Servlet应用改造为CSE、Dubbo改造为CSE、Spring Cloud改造为CSE等。同时给开发者提供了一些开发建议,使得...
  • 微服务改造—架构设计

    千次阅读 2017-12-19 22:59:43
    随着我厂业务需求的压力逐渐增长,同时基础设施的不断完善,系统架构的微服务改造被正式提上日程。从微服务改造的目标架构蓝图设计开始讨论,架构组进行了整整两天的激烈讨论,明确了很多的业务边界。在此过程中我...
  • 本文来自于csdn,本章介绍了通过使用微服务架构,在不影响现有业务运转的情况下,团队有效的将遗留的单块架构系统逐渐分解成不同功能的微服务应用。随着公司国际化战略的推行以及本土业务的高速发展、《网络借贷信息...
  • 微服务改造那些痛

    2020-04-03 09:00:00
    作者|拾贝来源丨emacoo.cn/arch/microservice-overview/0 背景技术圈流行一句话,凡脱离业务谈架构的,都是耍流氓。作为微服务改造系列的第一篇博客,...
  • 微服务架构改造企业核心系统.pdf
  • 针对当前电网信息通信运维系统存在硬件平台超期服役,信息处理任务无法快速完成等问题,本文提出一种基于Spring Could框架的微服务应用系统迁移上云改造模型,将电网信息通信运维系统迁移至云平台,模型针对集中式...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,239
精华内容 8,095
关键字:

哪些系统要微服务改造