目录
"微服务”一词源于 Martin Fowler的名为 Microservices的博文,可以在他的官方博客上找到 Microservices
1、微服务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间一般通过 HTTP 的 RESTfuLAPI 进行通信协作。
2、被拆分成的每一个小型服务都围绕着系统中的某一项或某些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发自动化测试案例以及独立部署机制。
3、由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。
4、微服务架构特点:
单一职责
服务粒度小
面向服务(对外暴露REST api)
服务之间相互独立
Spring Cloud 是一系列框架的有序集合。
Spring Cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来。
通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、 断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
Spring Cloud 版本命名方式采用了伦敦地铁站的名称,同时根据字母表的顺序来对应版本时间顺序,比如:最早的Release版本:Angel,第二个Release版本:Brixton,然后是Camden、Dalston、Edgware,Finchley,Greenwich,Hoxton。
Spring Cloud项目官方网址:Spring Cloud
后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了
群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring? Spring Cloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而Spring Cloud完全支持Spring Boot的开 发,用很少的配置就能完成微服务框架的搭建。
首先,我们需要模拟一个服务调用的场景。
微服务中需要同时创建多个项目,为了方便演示,先创建一个父工程,然后后续的工程都以这个工程为父,实现maven的聚合。这样可以在一个窗口看到所有工程,方便讲解。在实际开发中,每个微服务可独立一个工程。
因为这个是父工程,所有把src包给删除掉,也可以不删,不过src包存在没有意义。
- "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.0modelVersion>
-
- <groupId>cn.yhgroupId>
- <artifactId>yh-springcloudartifactId>
- <version>1.0-SNAPSHOTversion>
-
- <parent>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-parentartifactId>
- <version>2.1.5.RELEASEversion>
- <relativePath/>
- parent>
-
- <properties>
- <java.version>1.8java.version>
- <spring-cloud.version>Greenwich.SR1spring-cloud.version>
- <mapper.starter.version>2.1.5mapper.starter.version>
- <mysql.version>5.1.46mysql.version>
- properties>
-
- <dependencyManagement>
- <dependencies>
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-dependenciesartifactId>
- <version>${spring-cloud.version}version>
- <type>pomtype>
- <scope>importscope>
- dependency>
- <dependency>
- <groupId>org.mybatis.spring.bootgroupId>
- <artifactId>mybatis-spring-boot-starterartifactId>
- <version>1.3.2version>
- dependency>
-
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- <version>${mysql.version}version>
- dependency>
- dependencies>
- dependencyManagement>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-maven-pluginartifactId>
- plugin>
- plugins>
- build>
- project>
这里已经对大部分要用到的依赖的版本进行了 管理,方便后续使用。
新建一个项目user-service,对外提供查询用户的服务。

修改子工程的pom.xml文件
- "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">
- <parent>
- <artifactId>yh-springcloudartifactId>
- <groupId>cn.yhgroupId>
- <version>1.0-SNAPSHOTversion>
- parent>
- <modelVersion>4.0.0modelVersion>
-
- <artifactId>user-serviceartifactId>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- dependency>
- <dependency>
- <groupId>org.mybatis.spring.bootgroupId>
- <artifactId>mybatis-spring-boot-starterartifactId>
- dependency>
- dependencies>
- project>
修改application.yml配置文件
- server:
- port: 8081
- spring:
- application:
- name: user-service
- datasource:
- driver-class-name: com.mysql.jdbc.Driver
- url: jdbc:mysql://localhost:3306/ssm_crud?useSSL=false&useUnicode=true&characterEncoding=utf-8
- username: root
- password: xxx
- mybatis:
- type-aliases-package: cn.itssl.pojo
- mapper-locations: classpath:mappers/*.xml
- @SpringBootApplication
- @MapperScan("cn.itssl.mapper")
- public class UserServiceApplication {
- public static void main(String[] args) {
- SpringApplication.run(UserServiceApplication.class, args);
- }
- }
- @Data
- public class User {
- private String id;
- private String email;
- private String username;
- private String password;
- private String phoneNum;
- private Integer status;
- }
这里使用的是注解形式,如果是多表查询还是推荐使用xml形式
- @Repository
- public interface UserMapper {
- @Select("select * from users where id=#{id}")
- User getUserById(String id);
- }
- @Service
- public class UserServiceImpl implements UserService {
- @Autowired
- private UserMapper userMapper;
-
- @Override
- public User getUserById(String id) {
- return userMapper.getUserById(id);
- }
- }
- @RestController
- @RequestMapping("/user")
- public class UserController {
- @Autowired
- private UserService userService;
-
- @RequestMapping("/{id}")
- public User queryById(@PathVariable String id) {
- return userService.getUserById(id);
- }
- }
为了能够测试另一服务去远程调用user-service的查询用户功能,我们需要创建一个服务调用者consumer-service。

修改pom.xml配置文件
因为consumer-service去远程调用user-service的查询用户功能,所以在依赖中就不需要再添加mysql连接依赖以及mybatis依赖了。
- "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">
- <parent>
- <artifactId>springcloud-day01artifactId>
- <groupId>com.itsslgroupId>
- <version>1.0-SNAPSHOTversion>
- parent>
- <modelVersion>4.0.0modelVersion>
-
- <artifactId>consumer-serviceartifactId>
- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- dependency>
- dependencies>
-
- project>
- server:
- port: 8082
- spring:
- application:
- name: consumer-service
与user-service中的User一模一样,直接copy过来就可。
在启动类下添加一个远程调用工具的bean对象
- @SpringBootApplication
- @EnableEurekaClient //开启eureka客户端
- public class ConsumerApplication {
- public static void main(String[] args) {
- SpringApplication.run(ConsumerApplication.class, args);
- }
-
- //远程调用工具
- @Bean
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- }
- @RestController
- @RequestMapping("/consumer")
- public class ConsumerController {
- @Autowired
- private RestTemplate restTemplate;
- @Autowired
- private DiscoveryClient discoveryClient;
-
- @GetMapping("/{id}")
- public User queryById(@PathVariable("id") String id){
- String url = "http://localhost:8081/user/" + id;
- return restTemplate.getForObject(url, User.class);
- }
-
- }
可以发现,我们访问的是8082端口下的consumer请求路径,查到了用户信息,那么就证明consumer-service远程调用user-service服务成功!

存在什么问题?
在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效
consumer不清楚user-service的状态,服务宕机也不知道
user-service只有1台服务,不具备高可用性
即便user-service形成集群,consumer还需自己实现负载均衡
其实上面说的问题,概括一下就是分布式服务必然要面临的问题:
服务管理
如何自动注册和发现
如何实现状态监管
如何实现动态路由
服务如何实现负载均衡
服务如何解决容灾问题
服务如何实现统一配置
在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而consumer-service(调用者)需要记录。
服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复。
杂的互联网环境,一个项目可能会拆分出十几,甚至几十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
DevOps的思想是系统可以通过一组过程、方法或系统;提高应用发布和运维的效率,降低管理成本。
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。

Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
首先搭建一个eureka-server工程,创建一个项目 eureka-server ,启动一个Eureka Server Application服务注册中心。

修改pom.xml文件
- "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">
- <parent>
- <artifactId>springcloud-day01artifactId>
- <groupId>com.itsslgroupId>
- <version>1.0-SNAPSHOTversion>
- parent>
- <modelVersion>4.0.0modelVersion>
-
- <artifactId>eureka-serverartifactId>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
- dependency>
- dependencies>
- project>
- @SpringBootApplication
- @EnableEurekaServer //声明当前应用为eureka服务
- public class EurekaApplication {
- public static void main(String[] args) {
- SpringApplication.run(EurekaApplication.class, args);
- }
- }
- server:
- port: 10086
- spring:
- application:
- name: eureka-server
- eureka:
- client:
- service-url:
- # eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
- defaultZone: http://127.0.0.1:10086/eureka
- # 不注册自己
- register-with-eureka: false
- # 不拉取服务
- fetch-registry: false
启动 eureka-server 访问:http://127.0.0.1:10086

注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。
添加依赖
我们在user-service中添加Eureka客户端依赖:
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
- dependency>
修改启动类,在启动类上开启Eureka客户端功能,通过添加 @EnableDiscoveryClient或者@EnableEurkaClient来开启Eureka客户端功能
- @SpringBootApplication
- @MapperScan("cn.itssl.mapper")
- // @EnableDiscoveryClient 各个厂商的注册中心都可使用
- @EnableEurekaClient //只针对eureka,开启Eureka客户端发现功能
- public class UserServiceApplication {
- public static void main(String[] args) {
- SpringApplication.run(UserServiceApplication.class, args);
- }
- }
修改配置文件
- server:
- port: 8081
- spring:
- application:
- name: user-service
- datasource:
- driver-class-name: com.mysql.jdbc.Driver
- url: jdbc:mysql://localhost:3306/ssm_crud?useSSL=false&useUnicode=true&characterEncoding=utf-8
- username: root
- password: xxx
- mybatis:
- type-aliases-package: cn.itssl.pojo
- mapper-locations: classpath:mappers/*.xml
-
- eureka:
- client:
- service-url:
- defaultZone: http://127.0.0.1:10086/eureka
- instance:
- # 更倾向使用ip地址,而不是host名
- prefer-ip-address: true
- # ip地址
- ip-address: 127.0.0.1
- # 续约间隔,默认30秒
- lease-renewal-interval-in-seconds: 5
- # 服务失效时间,默认90秒
- lease-expiration-duration-in-seconds: 5
这里我们添加了spring.application.name属性来指定应用名称,将来会作为服务的id使用
测试
重启 user-service 项目,访问Eureka监控页面,我们发现user-service服务已经注册成功了

与上面user-service操作一致,只不过就是yml配置文件和controller类不同
- server:
- port: 8082
- spring:
- application:
- name: consumer-service
- eureka:
- client:
- service-url:
- defaultZone: http://127.0.0.1:10086/eureka
- # 获取服务地址列表间隔时间,默认30秒
- registry-fetch-interval-seconds: 10
修改处理器,修改代码,用DiscoveryClient类的方法,根据服务名称,获取服务实例。
- @RestController
- @RequestMapping("/consumer")
- public class ConsumerController {
- @Autowired
- private RestTemplate restTemplate;
- @Autowired
- private DiscoveryClient discoveryClient;
-
- @GetMapping("/{id}")
- public User queryById(@PathVariable("id") String id){
- // String url="http://localhost:8081/user/"+id;
- //获取eureka中注册的user-service的实例
- List
serviceInstances = discoveryClient.getInstances("user-service"); - ServiceInstance serviceInstance = serviceInstances.get(0);
- String url=serviceInstance.getUri().toString()+"/user/"+id;
- return restTemplate.getForObject(url,User.class);
- }
-
- }
如果有三个Eureka,则每一个EurekaServer都需要注册到其它几个Eureka服务中,例如:有三个分别为10086、10087、10088,则:
10086要注册到10087和10088上
10087要注册到10086和10088上
10088要注册到10086和10087上
我们再创建两个Eureka服务,并修改yml文件
- server:
- port: 10086
- spring:
- application:
- name: eureka-server
- eureka:
- client:
- service-url:
- # eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
- defaultZone: http://127.0.0.1:10087/eureka,http://127.0.0.1:10088/eureka
- server:
- port: 10087
- spring:
- application:
- name: eureka-server
- eureka:
- client:
- service-url:
- # eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
- defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10088/eureka
- server:
- port: 10088
- spring:
- application:
- name: eureka-server
- eureka:
- client:
- service-url:
- # eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
- defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

consumer-service
- server:
- port: 8082
- spring:
- application:
- name: consumer-service
- eureka:
- client:
- service-url:
- defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka,http://127.0.0.1:10088/eureka
user-service
- server:
- port: 8081
- spring:
- application:
- name: user-service
- datasource:
- driver-class-name: com.mysql.jdbc.Driver
- url: jdbc:mysql://localhost:3306/ssm_crud?useSSL=false&useUnicode=true&characterEncoding=utf-8
- username: root
- password: xxx
- mybatis:
- type-aliases-package: cn.itssl.pojo
- mapper-locations: classpath:mappers/*.xml
-
- eureka:
- client:
- service-url:
- defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka,http://127.0.0.1:10088/eureka

访问任意一个Eureka页面,可以看到现在Eureka-server以及变为了三个

我们模拟一下其中一台Eureka服务宕机了,会不会影响到consumer-service正常访问数据查询

可以看到,还是能够查询到数据,因为只是其中一台Eureka服务宕机,还有两台服务启动着,不会丝毫影响正常业务。这就体现出了Eureka集群的高可用性能。

如下的配置都是在Eureka Server服务端进行
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。
我们关停一个服务,很可能会在Eureka面板看到一条警告:

这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。
可以通过下面的配置来关停自我保护:
- eureka:
- server:
- enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
- eureka:
- server:
- eviction-interval-timer-in-ms: 60000 #剔除失效服务的时间间隔,默认是60秒
- eureka:
- instance:
- lease-renewal-interval-in-seconds: 30 #服务续约时间,默认是30秒
- lease-expiration-duration-in-seconds: 90 #服务失效时间,默认是90秒
- client:
- registry-fetch-interval-seconds: 30 #默认每隔30秒会从注册中心服务列表中重新拉取一次