一,微服务、原生云应用
微服务(Microservice)是近两年来非常火的概念,它的含义是:使用定义好边界的小的独立组件来做好一件事情。微服务是相对于传统单块式架构而言的。
单块式架构是一份代码,部署和伸缩都是基于单个单元进行的。它的优点是易于部署,但是面临可用性,可伸缩性差,集中发布的生命周期以及违反单一功能原则。微服务的出现解决了这个问题,它以单个独立的服务来做一个功能,且要做好这个功能。但使用微服务不可避免地将功能按照边界拆分为单个服务,体现出分布式的特征,这时每个微服务直接的通信将是我们要解决的问题。
spring cloud的出现为我们解决分布式开发常用到的问题给出了完整的解决方案。spring cloud基于spring boot,为我们提供了配置管理,服务发现,断路器,代理服务等我们在做分布式开发时常用问题的解决方案。
基于spring cloud开发的程序特别适合在Docker或者其他专业Pass(平台即服务,如Cloud Foundry)部署,所以又称作原生云应用(Cloud Native Application)。
二,Spring Cloud快速入门
1,配置服务
Spring Cloud提供了Config Server它有在分布式系统开发中外部配置的功能。通过Config Server,我们可以集中存储所有应用的配置文件。
Config Server支持在git或者svn或者在文件系统中放置配置文件。可以使用以下格式来区分不同应用的不同配置文件:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
spring cloud提供了注解@EnableConfigServer来启用配置服务。
2,服务发现
Spring Cloud通过Netflix OSS的Eureka来实现服务发现,服务发现的主要目的是为了让每个服务之间可以互相通信。Eureka Server为微服务注册中心。
Spring Cloud使用注解的方式提供了Eureka服务端(@EnableEurekaServer)和客户端(@EnableEurekaClient)
3,路由网关
路由网关的主要目的是为了让所有的微服务对外只有一个接口,我们只需访问一个网关地址,即可由网关将我们的请求代理到不同的服务中。
spring cloud是通过Zuul来实现的,支持自动路由映射到在Eureka Server上注册的服务。Spring Cloud提供了注解@EnableZuulProxy来启用路由代理。
4,负载均衡
spring cloud提供了Ribbon和Feign作为客户端的负载均衡。在Spring Cloud下使用Ribbon直接注入一个RestTemplate对象即可,此RestTemlate已做好负载均衡的配置;而使用Feign只需定义个注解,有@FeignClient注解的接口,然后使用@RequestMapping注解在方法映射远程的REST服务,此方法也是做好负载均衡配置的。
5,断路器
断路器,主要是为了解决当某个方法调用失败的时候,调用后备方法来替代失败的方法,以达到容错,阻止级联错误等功能。
spring cloud使用@EnableCircuitBreaker来启用断路器支持,使用@HystrixCommand的fallbackMethod来指定后备方法。
spring cloud还给我们提供了一个控制台来监听断路器的运行情况。通过@EnableHystrixDashboard注解开启。
三,实战
下面是实战部分,主要由6个微服务组成:
config:配置服务,下面的是person-service和some-service提供外部配置。
discovery:Eureka Server为微服务提供注册。
person:为UI模块提供保存person的REST服务。
some:为UI模块返回一段字符串。
UI:作为应用网关,提供外部访问的唯一入口。使用Feign消费person服务,Ribbon消费some服务,且都提供断路器功能。
monitor:监控UI模块中的断路器
1,项目构建
创建一个mava项目,项目名为springcloud3,打包方式是pom发生,使用modules标签来实现模块化,pom.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jack</groupId>
<artifactId>springcloud3</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!--模块化-->
<modules>
<module>config</module>
<module>discovery</module>
<module>ui</module>
<module>person</module>
<module>some</module>
<module>monitor</module>
</modules>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.40</version>
</dependency>
</dependencies>
</project>
2,服务发现-discovery(Eureka Server)
1)创建spring boot项目,项目名为discovery,模块需要添加如下依赖:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jack</groupId> <artifactId>discovery</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>discovery</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.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.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> <version>1.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>1.5.8.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
2)主要代码
package com.jack.discovery;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication//开启注册中心@EnableEurekaServerpublic class DiscoveryApplication { public static void main(String[] args) { SpringApplication.run(DiscoveryApplication.class, args); }}
3)配置
application.yml配置如下:
server: port: 8761 #\u5F53\u524DEureka Server\u670D\u52A1\u7684\u7AEF\u53E3\u4E3A8761eureka: instance: hostname: localhost #\u5F53\u524DEureka Server\u7684hostname\u4E3Alocalhost client: register-with-eureka: false #\u5F53\u524D\u670D\u52A1\u4E0D\u9700\u8981\u5230Eureka Server\u4E0A\u6CE8\u518C fetch-registry: false #\u662F\u5426\u9700\u8981\u4ECEEureka Server\u83B7\u53D6\u6CE8\u518C\u6D88\u606F
3,配置-config(config server)
创建一个spring boot项目,项目名为config
1)依赖
依赖的jar包在pom.xml里面的配置如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jack</groupId> <artifactId>config</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>config</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.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.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> <version>1.2.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>1.5.8.RELEASE</version> </dependency> <!--eureka客户端的依赖--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.3.5.RELEASE</version> </dependency> <!--配置服务的依赖--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> <version>1.3.3.RELEASE</version> </dependency> <!--web依赖--> <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> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
2)主要代码如下:
package com.jack.config;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.config.server.EnableConfigServer;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication@EnableEurekaClient@EnableConfigServerpublic class ConfigApplication { public static void main(String[] args) { SpringApplication.run(ConfigApplication.class, args); }}
3)配置
bootstrap.yml配置如下:
spring: application: name: config #在Erueka Server注册的服务名为config profiles: active: native #配置服务器使用本地配置(默认为git配置)eureka: instance: non-secure-port: ${server.port:8888} #非SSL端口,若环境变量中server.port有值,则使用环境变量的值,没有则使用8080端口 metadata-map: instanceId: ${spring.application.name}:${random.value} #配置在Eureka Server的实例ID client: service-url: #Eureka客户端设置Eureka Server的地址 defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/
application.yml配置如下:
spring: cloud: config: server: native: #\u914D\u7F6E\u5176\u4ED6\u5E94\u7528\u6240\u9700\u7684\u914D\u7F6E\u6587\u4EF6\u7684\u4F4D\u7F6E\u4F4D\u4E8E\u7C7B\u8DEF\u5F84\u4E0B\u7684config\u76EE\u5F55\u4E0B search-locations: classpath:/configserver: port: 8888
4)创建一个目录存放配置文件
在src/main/resources目录下创建一个config目录,其他应用的配置文件就存放在这里,实现统一配置的功能,如下:
配置文件的规则为:应用名+profile.yml
4,服务模块-person服务
1)依赖
本模块需要做数据库操作,需要添加spring-boot-starter-data-jpa依赖(在开发环境下使用hsqldb,在docker生产环节下使用PostgreSQL);本模块还需要config server的配置,pom.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jack</groupId> <artifactId>person</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>person</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.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.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> <version>1.2.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-config --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>1.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.3.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>1.5.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>1.5.8.RELEASE</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/postgresql/postgresql --> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.4.1208-jdbc42-atlassian-hosted</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
2,关键代码
package com.jack.person;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication@EnableEurekaClientpublic class PersonApplication { public static void main(String[] args) { SpringApplication.run(PersonApplication.class, args); }}
package com.jack.person.entity;import javax.persistence.*;/** * create by jack 2017/10/3 *///@Entity注解指明这是一个和数据库表映射的实体类@Entitypublic class Person { /** * 主键id * @Id注解指明这个属性映射为数据库的主键 * @GeneratedValue定义主键生成的方式,下面采用的是mysql的自增属性 */ @Id//主键 //@GeneratedValue(strategy= GenerationType.AUTO)使用内存数据库需要注释掉 private Integer id; /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; /** * 地址 */ private String address; public Person() { super(); } public Person(String name) { this.name = name; } public Person(Integer id,String name, Integer age, String address) { super(); this.id = id; this.name = name; this.age = age; this.address = address; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; }}
package com.jack.person.dao;import com.jack.person.entity.Person;import org.springframework.data.jpa.repository.JpaRepository;/** * create by jack 2017/10/3 */public interface PersonRepository extends JpaRepository<Person,Integer> {}
package com.jack.person.controller;import com.jack.person.dao.PersonRepository;import com.jack.person.entity.Person;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.PageRequest;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import java.util.List;/** * create by jack 2017/11/11 */@RestControllerpublic class PersonController { @Autowired private PersonRepository personRepository; @RequestMapping(value = "/save",method = RequestMethod.POST) public List<Person> savePerson(@RequestBody String personName){ Person p = new Person(personName); //需要设置id p.setId(2); personRepository.save(p); List<Person> people = personRepository.findAll(new PageRequest(0,10)).getContent(); return people; }}
3)配置
bootstrap.yml
spring: application: name: person cloud: config: enabled: true discovery: enabled: true service-id: configeureka: instance: non-secure-port: ${server.port:8082} client: service-url: defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/
指定config server服务,将会通过eureka server 发现config server,在开封环境下使用hsqldb(config server下的person.yml):
spring: jpa: database: HSQL
在docker生产环境下使用PostgreSQL(config server下的person-docker.yml)
spring: jpa: database: POSTGRESQL datasource: platform: postgres url: jdbc:postgresql://postgres:5432/postgres username: postgres password: postgres driver-class-name: org.postgresql.Driver
application.yml配置如下:
server: port: 8082spring: jpa: hibernate: ddl-auto: update
5,服务模块-some服务
创建一个some名的spring boot项目
1)依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jack</groupId> <artifactId>some</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>some</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.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.8</java.version> </properties> <dependencies> <!--<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> <version>1.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>1.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
2)关键代码
package com.jack.some;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication@EnableDiscoveryClientpublic class SomeApplication { public static void main(String[] args) { SpringApplication.run(SomeApplication.class, args); }}
package com.jack.some.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * create by jack 2017/11/11 */@RestControllerpublic class SomeController { /** * 下面是通过@Value注入的值来自于Config Server */ @Value("${my.message}") private String message; @RequestMapping(value = "/getsome") public String getsome() { return message; }}
此处通过@Value注入的值来自于config server
在开发环境(config server下的some.yml)
my: message: Message from Development
在docker生产环境下(config server下的some-docker.yml)
my: message: Message from Production
3)配置
bootstrap.yml
spring: application: name: some cloud: config: enabled: true discovery: enabled: true service-id: configeureka: instance: non-secure-port: ${server.port:8083} client: service-url: defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/
application.yml配置如下:
server: port: 8083
代码地址: https://github.com/wj903829182/springcloud3
接下来的步骤,见下一篇博客