最近买了一本springcloud微服务实战和开涛的亿级流量网站架构核心技术。之前完整的学习过springcloud,公司中也运用了部分技术,不做笔记过一段时间好多细节就会忘记,打算下半年写关于Springcloud一系列的博客。更加深入掌握springcloud。
程序猿DD,作者博客
单体架构
一个归档包包含了应用所有功能的应用程序, 我们通常称之为单体应用。
架构单体应用的架构风格, 我们称之为单体架构, 这是一种比较传统的架构风格。

什么是微服务架构
微服务架构源于Martin Fowler的一篇博文地址.
简单的说,微服务是系统架构上的一种设计风格,主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于http的RESTful API进行通信协作。被拆分成的每一个小型服务都围绕着系统中一些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储,业务开发,自动化测试案例以及独立部署机制。由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。
与以前的单体架构的对比
- 单体架构修改一个很小的功能,为了部署上线会影响其他功能的运行,可能会造成很大的问题。
- 单体应用中的功能的使用场景,并发量,消耗的资源类型都各有不同,对于资源的利用又互相影响,这样使得我们对各个业务模块系统容器很难给出较为准确的评估。
- 微服务架构每个服务都运行在自己的进程内,部署上有稳固的边界,这样每个服务的更新不会影响其他服务的运行。
- 独立部署的微服务,可以准确的给每个服务评估性能容量,通过配合服务间的协作流程也可以更容易地发现系统的瓶颈位置,以及给出较为准确的系统级性能容量评估。
如何实施微服务
- 运维的新挑战 部署更多的服务,对运维带来的挑战
- 接口的一致性 我们对接口进行修改,交互方也需要协调这样的改变来进行发布,以保证接口的正确调用。
- 分布式的复杂性 因为微服务都是独立部署在各自的进程内,它们只能通过通信进行协作,所以分布式环境的问题都将是微服务架构的系统设计时需要考虑的重要因素,比如说网络延迟,分布式事务,异步消息等。
分布式事务本身的实现难度就非常大,所以在微服务架构中,我们更加强调在各服务之间进行"无事务"的调用,而对数据的一致性,只需要数据在最后的处理状态是一致的即可.
spring cloud简介
spring cloud是一个基于spring boot实现的微服务架构开发工具。它为微服务架构中涉及的配置管理,服务治理,断路器,智能路由,微代理,控制总线,全局锁,决策竞选,分布式会话和集群状态管理等操作提供了一种简单的开发方式。
版本说明
由于Spring cloud不是像社区其他一些项目那样相对独立,它是一个拥有诸多子项目的大型综合项目,可以说是对微服务架构解决方案的综合套件组合,其包含的各个子项目也都独立进行着内容更新与迭代,各自都维护着自己的发布版本号。因此每一个spring cloud的版本都会包含多个不同版本的子项目,为了管理每个版本的子项目清单,避免Spring Cloud的版本号与其子项目的版本相混淆,没有采用版本号的方式,而是通过命名的方式。这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序,比如最早的Release版本的Angel,第二个Release版本的Brixton.....
spring cloud Eureka
spring cloud Eureka是spring cloud Netfix微服务套件中的一部分,它基于Netfix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。Spring cloud通过为Eureka增加了Spring boot风格的自动化配置,我们只需要通过简单引入依赖和注解配置就能让spring boot构建微服务应用轻松地与EUreka服务治理体系进行整合。
服务治理
服务治理可以说是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实列的自动化注册与发现。
在最初开始构建微服务系统的时候可能服务并不多,我们可以通过一些静态配置来完成服务的调用。比如,有二个服务A和B,其中服务A需要调用B来完成业务操作,为了实现B的高可用,不论采用服务端负载均衡还是客户端负载均衡,都需要手工维护B的具体实例清单。但是随着业务的发展,系统功能的复杂性越来越高,相应的微服务也不断增加,我们的静态配置就会变得越来越难以维护。并且面对不断发展的业务,我们的集群规模,服务的位置,服务的命名都有可能发生变化,还是通过手工维护的方式,极其容易出现问题。
为了解决微服务架构中的服务实例维护问题,产生了大量的服务治理框架和产品。这些框架和产品的实现都围绕服务注册与服务发现机制来完成对微服务应用实例的自动化管理。如果我们使用过阿里的dubbo就知道,zookeeper也是实现服务注册与发现的一种策略。当然springcloud 也支持使用zookeeper进行服务治理。
写一个demo:
定义一个父maven项目,指定spring cloud及其版本:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.5.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
我使用的是springcloud官网最新的release版(Camden.SR7),spring boot使用的是1.4.5.RELEASE版本。
Eureka注册中心
新建一个module项目,Eureka服务注册中心,
<parent>
<artifactId>springcloud-eureka</artifactId>
<groupId>com.zhihao.miao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>eurekaservice-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
通过一个@EnableEurekaServer
注解,启动服务注册中心:
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
定义application.yml文件:
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
register-with-eureka: false
:由于该应用为注册中心,所以设置false表明不向注册中心注册自己。
fetch-registry
:由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false
启动注册中心,访问http://localhost:8761/,页面如下:

此时还没有实例注册到Eureka上。
服务生产端
定义一个用户服务user-service
,依赖如下:
<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.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
定义一个接口:
package com.zhihao.miao.user.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value="/user",method = RequestMethod.GET)
public String index(){
ServiceInstance instance = client.getLocalServiceInstance();
logger.info("/user,host:"+instance.getHost()+",service id:"+instance.getServiceId()+",port:"+instance.getPort());
return "hello user, local time="+ LocalDateTime.now();
}
}
启动类,通过注解@EnableDiscoveryClient
向Eureka注册服务:
package com.zhihao.miao.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
配置文件,application.yml:
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
server:
port: 8080
通过spring.application.name来定义服务名称。
启动服务
2017-07-08 18:27:55.790 INFO 46044 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER-SERVICE/192.168.1.57:user-service: registering service...
2017-07-08 18:27:56.054 INFO 46044 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-07-08 18:27:56.055 INFO 46044 --- [ main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 8080
2017-07-08 18:27:56.059 INFO 46044 --- [ main] com.zhihao.miao.user.UserApplication : Started UserApplication in 5.54 seconds (JVM running for 6.153)
2017-07-08 18:27:56.066 INFO 46044 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER-SERVICE/192.168.1.57:user-service - registration status: 204

发现在启动服务的时候像注册中心注册服务,再看刚才的Eureka面板:

发现user服务已经注册成功。
消费端服务
再定义一个服务order-service
,这里我们让order-service
作为消费端,调用user-service
服务,服务发现的任务由Eureka的客户端完成,而服务消费的任务由Ribbon
完成。
Ribbon
是一个基于http和tcp的客户端负载均衡器,它可以在通过客户端中配置的ribbonServerList
服务端列表去轮询访问以达到均衡负载的作用。当Ribbon
与Eureka
联合使用时,Ribbon
的服务实例清单ribbonServerList
会被DiscoveryEnabledNIWSServerList
重写,扩展成从Eureka注册中心获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。
一般Ribbon的依赖包含在eureka的依赖中,所以如果项目中已经加入了eureka的依赖就不必再加入Ribbon的依赖。当然也可以同时加入Eureka和Ribbon的依赖。
order-service
依赖:
<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.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
启动类:
package com.zhihao.miao.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
controller层:
package com.zhihao.miao.order.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
@Value("${user.service.url}")
private String userService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order")
public String index(){
String response = restTemplate.getForEntity(userService+"/user",String.class).getBody();
System.out.println(response);
return response;
}
}
配置文件:
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
server:
port: 9090
user:
service:
url: http://user-service
启动之后,Eureka的面板显示:


@LoadBalanced
深坑,自己在order-service
中使用的是http://user-service/user 来访问user-service
服务中的接口,由于没有在RestTemplate
中加@LoadBalanced
注解导致一直找不到user-service
服务,这个是Ribbon中的轮询策略,@LoadBalanced
表示负载均衡。下面会有博客具体去讲解Ribbon
。
为了便于测试Ribbon的负载均衡,我又重新启动user-service
,
java -jar user-service-1.0-SNAPSHOT.jar --server.port=8081 &
查看Eureka面板,

访问
order-service
服务,


在相当多的访问的时候会发现访问80,81的次数差不多。
我们停掉81的服务,
➜ lsof -i:8081
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 49024 naeshihiroshi 27u IPv6 0x7a58ea9c72f2fec1 0t0 TCP *:sunproxyadmin (LISTEN)
➜ kill 49024
➜ [1] + 49024 killed java -jar user-service-1.0-SNAPSHOT.jar --server.port=8081
我在工作中就是这样停掉springboot服务的,查看Eureka面板,

发现user-service还是多实例,因为Eureka是有缓存的。


发现访问order服务也是,因为有一个
user-service
节点挂掉,而由于Eureka并没有及时得去掉没有用的实例,造成有一半的时候会访问不了user-service
服务。不过这个时间很短暂,过一会儿就好了。
问题:
我发现使用kill -9 命令关闭服务并没有在Eureka Server中去掉该实例?这个问题找机会去解决。
发现还是跟kill命令有关当我使用kill命令而不是kill -9的时候就没问题了,kill -9 没有执行shutdown hook。

于是自己使用kill 进程号就可以了。
网友评论
发现访问order服务也是,因为有一个user-service节点挂掉,而由于Eureka并没有及时得去掉没有用的实例,造成有一半的时候会访问不了user-service服务。不过这个时间很短暂,过一会儿就好了。
想知道这个“”短暂的一会儿“”可以设置时间么? 怎么设置?