1 写在前面
之前在做的业务系统,大概有10多个大业务模块,每个大业务模块下面差多有接近20个小业务模块,代码量惊人。
这还不是重点,因为我司是做定制化软件的,根据客户具体的业务需求开发不同的系统,每当要开发一个新系统前,就从已经开发过的系统中选择一个业务接近的系统拷贝一份,然后进行修改。假如A客户需要的业务模块是a、b、c、d、e,新客户B需要的业务模块是a、c、e、f、g,我们只需要把A客户的系统删除b、d模块,新增f模块就可以了,但是这样就会出现一个问题,A、B两个客户的系统存在严重的代码耦合(耦合a、c、e模块)。
有同事提出了解决方案,“我们做一个标准版的系统吧,包括a、b、c、d、e、f、g、...所有的业务模块!”这就是我司目前的做法。看似是一个不错的想法,但是把所有模块都集中在一个系统中,导致war包过大,代码过多,运维困难,一个错误可能导致系统无法运行。
我一直在思考,能不能把业务模块单独拿出来做成系统,单独部署,假如又有新客户C需要的业务模块是c、d、f,那么我们就可以从中选择这几个系统进行组合,能够达到组合的效果。这就是我学习spring could的动机(虽然我给公司这样建议了没被采纳!)。
在我的方案中,主要解决两个问题:
1)模块之间的耦合
2)模块的复用
2 微服务架构原理
2.1 传统开发模式——单应用架构
先来看一下传统的开发模式,也就是现在我司的开发模式:所有的模块都打包在一个war包里,部署在tomcat里,基本没有外部依赖,这就导致了很多缺点,例如:
1)效率低:开发都在同一个项目改代码,相互等待,冲突不断
2)维护难:代码功功能耦合在一起,新人不知道何从下手
3)不灵活:构建时间长,任何小修改都要重构整个项目,耗时
4)稳定性差:一个微小的问题,都可能导致整个应用挂掉
5)扩展性不够:无法满足高并发下的业务需求
2.2 微服务架构——多应用架构
微服务架构是将一个大型复杂软件分成多个子软件,或者称为子服务。各个子软件可被独立部署,这样就解决了模块之间耦合的问题,每一个子软件仅仅关注于完成一件任务,并提供服务,不去关注其他业务模块。模块与模块之间只通过接口进行关联,只关注接口,开发人员只对接口负责。
优点可以总结为:
1)每个微服务都很小,这样能聚焦一个指定的业务功能或业务需求
2)一个团队的新成员能够更快投入生产
3)微服务只是业务逻辑的代码,不会和HTML,CSS 或其他界面组件混合
4)每个微服务都有自己的存储能力,可以有自己的数据库。也可以有统一数据库
3 spring-cloud-Netflix-Eureka实现多服务注册及服务之间的相互调用
spring cloud提供了一个服务注册管理中心Eureka,每一个服务(子软件)都可将自己提供的服务在注册中心登记,如果想要寻找某一个服务,在服务注册中心就能够轻松的找到。以点买卖为例:饿了吗客户端就相当于一个餐厅注册中心,客户通过饿了吗查找特定的餐厅(寻找服务),每个餐厅提供一种服务。
3.1 创建服务注册中心
spring cloud是基于spring boot的,所以我们创建一个spring boot项目,命名为elema-eureka,pom文件如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
在src/main/resources目录下新建配置文件application.yml,并在其中配置启动端口,有一点需要特别注意,其实eureka服务注册中心其实也是一个服务,默认情况下,eureka会把自己当成一个服务去注册,我们可以在配置文件中禁止这个行为,application.yml文件内容如下:
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
接下来创建spring boot项目启动类,ServerApp.java,使用@EnableEurekaServer注解把该项目标记为eureka注册中心,内容如下:
@SpringBootApplication
@EnableEurekaServer
public class ServerApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ServerApp.class).web(true).run(args);
}
}
启动项目后,我们就可以访问eureka服务中心可视化界面了。localhost:8761,能够在该页面中清楚的看到哪些服务已经注册了。
3.2 创建服务,注册服务
同样是先创建一个spring boot项目,命名为restaurantA,pom文件内容如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
在src/main/resources目录下新建配置文件application.yml,并在其中配置启动端口,服务名称,及服务注册地址。服务注册地址就写eureka的地址即可,内容如下:
spring:
application:
name: restaurantA
server:
port: 8081
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
然后编写一个服务提供类,命名为RestaurantController.java,并使用@RestController注解向外提供服务,内容如下:
@RestController
public class RestaurantController {
@RequestMapping(value = "/call", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public String call() {
return "restaurantA is open!";
}
}
编写启动类后,然后启动,我们发现在eureka服务中心多了一个名为restaurantA的服务。
3.3 创建用户,调用restaurantA服务
有很多博客区分了服务提供者和服务使用者,其实在某种意义上来说,服务提供者和使用者是没有明确界限的,一个项目既可以做服务提供者也可以做使用者。
同样创建一个spring boot项目,pom文件内容如下,增加了对JSP支持的依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- JSP support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
</dependencies>
配置文件application.yml,虽然userA不向外提供服务,但仍然可以向eureka注册,其实也为了能够调用在eureka上注册的其他服务。同时也增加了对JSP文件的支持,可参考spring mvc的配置,内容如下:
spring:
application:
name: userA
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
http:
encoding:
force: true
charset: UTF-8
enabled: true
server:
port: 8089
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
下面编写订餐OrderController.java,并提供JSP订餐页面,代码如下:
@Controller
@Configuration
public class OrderController {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
/**
* 跳转到订餐页面
* @param request
* @return
*/
@RequestMapping("/toPage")
public String toList(HttpServletRequest request){
return "page/orderDinner/list";
}
@GetMapping("/order")
@ResponseBody
public String router() {
RestTemplate tpl = getRestTemplate();
String result = tpl.getForObject("http://restaurantA/call", String.class);
return result;
}
}
编写启动类,然后启动,访问订餐页面:http://localhost:8089/toPage,然后点击“调用订餐按钮”即可调用restaurantA提供的服务!
3.4 小节
上面一共创建了3个项目, elema-eureka:服务注册中心, restaurantA、userA:服务提供者,三者之间的关系如下:
参考: