一. 搭建简单的微服务工程
1. 从单体架构到微服务架构的演变
我们最先接触的单体架构,整个系统就只有一个工程,打包往往是打成了 war 包,然后部署 到单一 tomcat 上面,这种就是单体架构,如图:

单体架构优点
1、结构简单,部署简单
2、所需的硬件资源少
3、节省成本
缺点
1、版本迭代慢,往往改动一个代码会影响全局
2、不能满足一定并发的访问
3、代码维护困难,所有代码在一个工程里面,存在被其他人修改的风险
随着业务的拓展,公司的发展,单体架构慢慢的不能满足我们的需求,我们需要对架构进行 变动,我们能够想到的最简单的办法就是加机器,对应用横向扩展。 如图:

这种架构貌似暂时解决了我们的问题,但是用户量慢慢增加后,我们只能通过横向加机器来解决,还是会存在版本迭代慢,代码维护困难的问题。而且用户请求往往是读多写少的情况, 所以可能真正需要扩容的只是业务量大的模块而已,而现在是整个工程都扩容了,这无形中是一种资源的浪费,因为其他模块可能根本不需要扩容就可以满足需求。所以我们有必要对整个工 程按照模块进行拆分,拆分后的架构图如下:

模块拆分后,模块和模块之间是需要通过接口调用的方式进行通信,模块和模块之间通过分流软件进行负载均衡。这个架构解决前面的资源浪费问题和代码管理问题,因为我们是对系统拆分了,各个模块都有单独的工程,比如我修改商品模块,就不需要担心会不会影响购物车模块。但是这种架构扩展非常麻烦,一旦需要横向加机器,或者减机器都需要修改 nginx 配置,一旦机器变多了以后,nginx 的配置量就是一个不能完成的工作。OK,这时候 SOA 服 务治理框架就应运而生,架构图如下:

基于注册中心的 SOA 框架,扩展是非常方便的,因为不需要维护分流工具,但我们启动应 用的时候就会把服务通过 http 的方式注册到注册中心。
在 SOA 框架中一般会有三种角色:1、注册中心 2、服务提供方 3、服务消费方
1、注册中心
在注册中心维护了服务列表
2、服务提供方
服务提供方启动的时候会把自己注册到注册中心
3、服务消费方
服务消费方启动的时候,把获取注册中心的服务列表,然后调用的时候从这个服务列表中选择某一个去调用。
微服务工程的特点:
1、扩展灵活
2、每个应用都规模不大
3、服务边界清晰,各司其职
4、打包应用变多,往往需要借助 CI 持续集成工具
2. 简单的微服务工程搭建
1、注册中心搭建(eureka服务端: netflix-eureka-server)
Springcloud 中,我们选择 eureka 作为注册中心,springcloud 工程是基于 springboot 工程的。 pom.xml 中 jar 包依赖:
<!--parent-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--Springcloud 的版本-->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<!--Eureka 服务端启动器导入-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--Springcloud 的依赖仓库导入-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>application.properties 配置文件(不能叫bootstrap.properties)
server.port=8763
eureka.instance.hostname=localhost
#是否注册到eureka
eureka.client.registerWithEureka=false
#是否从eureka中拉取注册信息
eureka.client.fetchRegistry=false
##暴露eureka服务的地址
# http://localhost:8763/eureka/
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
#自我保护模式,当出现出现网络分区、eureka在短时间内丢失过多客户端时,会进入自我保护模式,即一个服务长时间没有发送心跳,eureka也不会将其删除,默认为true
eureka.server.enable-self-preservation=true
#eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
eureka.server.eviction-interval-timer-in-ms=60000
#服务手动下线 delete请求 http://localhost:8763/eureka/apps/MICRO-ORDER/localhost:xxx:8084启动类
package len.hgy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public interface EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}查看
http://localhost:8763/
2、服务提供方(eureka 客户端: netflix-eureka-client)
Pom 的 jar 包依赖,其他都跟 eureka 服务端是一样的,只是服务提供方要把服务注册到 eureka 服务端,所以服务提供方就是 eureka 的客户端,所以需要导入 eureka 客户端的启动器。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>bootstrap.properties
spring.application.name=micro-order
server.port=8084
eureka.client.serviceUrl.defaultZone=http://localhost:8763/eureka/
# 一下有默认配置
#服务续约,心跳的时间间隔
eureka.instance.lease-renewal-interval-in-seconds=30
#如果从前一次发送心跳时间起,90秒没接受到新的心跳,讲剔除服务
eureka.instance.lease-expiration-duration-in-seconds=90
#表示eureka client间隔多久去拉取服务注册信息,默认为30秒
eureka.client.registry-fetch-interval-seconds=30启动类
package len.hgy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication(scanBasePackages = {"len.hgy"})
// 注册到eureka
@EnableEurekaClient
@MapperScan("len.hgy.dao")
public class MicroOrderApplication {
public static void main(String[] args) {
SpringApplication.run(MicroOrderApplication.class, args);
}
}3、服务消费方(eureka 客户端: netflix-eureka-client)
pom 和属性配置文件基本上差不多,消费要负责调用服务提供方,所以需要调用客户端
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>bootstrap.properties
#是否注册到eureka
eureka.client.registerWithEureka=true
#是否从eureka中拉取注册信息
eureka.client.fetchRegistry=true
eureka.client.serviceUrl.defaultZone=http://localhost:8763/eureka/启动类
package len.hgy;
import len.hgy.service.feign.StudentService;
import len.hgy.service.feign.TeacherServiceFeign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication(scanBasePackages = {"len.hgy"})
//注册到eureka
@EnableEurekaClient
//开启断路器功能
//@EnableCircuitBreaker
//开启feign支持,clients指定哪个类开启feign
//@EnableFeignClients(clients = {StudentService.class,TeacherServiceFeign.class})
public class MicroWebApplication {
@Bean
// 负载均衡注解
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate(); // 使用这个bean实例才能负载均衡
}
public static void main(String[] args) {
SpringApplication.run(MicroWebApplication.class,args);
}
}服务调用的时候就根据服务提供方的服务名称来调用的
public static String SERVIER_NAME = "micro-order";
@Override
public List<ConsultContent> queryContents() {
s.incrementAndGet();
List<ConsultContent> results = restTemplate.getForObject("http://" + SERVIER_NAME + "/user/queryContent", List.class);
return results;
}服务提供方的名称,再加上服务提供方的接口名就可以完成调用了。 服务提供方和服务消费启动的时候都会往服务注册中心注册服务,eureka 服务端也可以通过 界面查看到服务注册情况:
http://localhost:8763/

调用web接口测试看看
// controller
package len.hgy.controller;
import len.hgy.bean.ConsultContent;
import len.hgy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/queryUser")
public List<ConsultContent> queryUser() {
return userService.queryContents();
}
@RequestMapping("/queryMonitor")
public String queryMonitor() {
return userService.queryMonitor();
}
}http://localhost:8083/user/queryUser
3. Eureka 用户认证
连接到 eureka 的时候需要带上连接的用户名和密码
Eureka 服务端改造
添加security启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> // spring-boot
</dependency>关闭 csrf 验证
package hgy.security;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.csrf().disable();
//开启认证:URL格式登录必须是httpBasic
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
}application.properties 配置
# 开启 basic 校验,设置登录用户名密码
security.basic.enabled=true
=admin
spring.security.user.password=adminEureka 客户端改造
跟 eureka 连接的时候要带上用户名密码
#eureka.client.serviceUrl.defaultZone=http://localhost:9876/eureka/
eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:9876/eureka/此时登录页需要输入用户密码
http://localhost:9876/

4. 服务续约保活
当客户端启动想 eureka 注册了本身服务列表后,需要隔段时间发送一次心跳给 eureka 服务 端来证明自己还活着,当 eureka 收到这个心跳请求后才会知道客户端还活着,才会维护该 客户端的服务列表信息。一旦因为某些原因导致客户端没有按时发送心跳给 eureka 服务端, 这时候 eureka 可能会认为你这个客户端已经挂了,它就有可能把该服务从服务列表中删除 掉。
有关续约保活的配置
客户端配置
# 客户端配置
#服务续约,心跳的时间间隔
eureka.instance.lease-renewal-interval-in-seconds=30
#如果从前一次发送心跳时间起,90 秒没接受到新的心跳,将剔除服务
eureka.instance.lease-expiration-duration-in-seconds=90
#表示 eureka client 间隔多久去拉取服务注册信息,默认为 30 秒
eureka.client.registry-fetch-interval-seconds=30服务端配置
# 服务端配置
#自我保护模式,当出现出现网络分区、eureka 在短时间内丢失过多客户端时, 会进入自我保护模式,即一个服务长时间没有发送心跳,eureka 也不会将其删 除,默认为 true
eureka.server.enable-self-preservation=true
#Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低 于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来
eureka.server.renewal-percent-threshold=0.85
#eureka server 清理无效节点的时间间隔,默认 60000 毫秒,即 60 秒
eureka.server.eviction-interval-timer-in-ms=600005. Eureka 健康检测
Eureka 默认的健康检测只是你校验服务连接是否是 UP 还是 DOWN 的,然后客户端只会调用 状态为 UP 状态的服务,但是有的情况下,虽然服务连接是好的,但是有可能这个服务的某 些接口不是正常的,可能由于需要连接 Redis,mongodb 或者 DB 有问题导致接口调用失败, 所以理论上服务虽然能够正常调用,但是它不是一个健康的服务。所以我们就有必要对这种 情况做自定义健康检测。
application.properties 配置
开启健康检测
#健康检测
eureka.client.healthcheck.enabled=true自定义健康检测代码
@Configuration
public class MicroWebHealthIndicator implements HealthIndicator {
@Override
public Health health() {
//这个状态就是数据库是否连接OK
if(UserController.canVisitDb) { // 业务标识, 更具db,redis, mysql等的连接情况得出一个标识
return new Health.Builder(Status.UP).build();
} else {
return new Health.Builder(Status.DOWN).build();
}
}
}jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>6. 服务下线
比如有些情况是服务主机意外宕机了,也就意味着服务没办法给 eureka 心跳信息了,但是 eureka 在没有接受到心跳的情况下依赖维护该服务 90s,在这 90s 之内可能会有客户端调用 到该服务,这就可能会导致调用失败。所以我们必须要有一个机制能手动的立马把宕机的服 务从 eureka 服务列表中清除掉,避免被服务调用方调用到。
调用服务下线的接口: 这个接口是调用 eureka 服务端的接口
http://localhost:9876/eureka/apps/micro-order/localhost:micro-order:8765
delete请求

注意:
测试时候如果服务没有关闭, 会一直有心跳, 导致手动下线失败
另外手动停止idea的服务会直接移除掉服务, 不会保活90s, 因为是正常停机, 客户端自动服务下线了
可以通过如下命令自己模拟异常宕机
windows
netstat -nao | findstr 8765
taskkill /f /pid 19016 # 此时eureka的服务列表中还是有服务, 到保活时间过去, eureka也会将其删除掉linux
ps -aux | grep 8765
kill -9 19016暂停服务接口
PUT http://localhost:9876/eureka/apps/micro-order/localhost:micro-order:8765?value=OUT_OF_SERVICE
上线服务
PUT http://localhost:9876/eureka/apps/micro-order/localhost:micro-order:8765?value=UP
7. Eureka 高可用
Eureka 热备份的架构图如下:

整个微服务中存在多个 eureka 服务,每个 eureka 服务都是相互复制的,会把客户端注册进 来的服务复制到 eureka 集群中的其他节点里面来。其实简单来说就是 eureka 每个节点相互 复制。
具体配置如下:
端口为 9878 的 eureka 服务端把自己注册到 9877 的 eureka 服务端
# 多个使用逗号分隔
server.port=9877
eureka.client.serviceUrl.defaultZone=http://localhost:9878/eureka/端口为 9877 的 eureka 服务端把自己注册到 9878 的 eureka 服务端
# 多个使用逗号分隔
server.port=9878
eureka.client.serviceUrl.defaultZone=http://localhost:9877/eureka/配置文件
application-9877.properties
application-9878.properties
启动的时候按照指定配置文件启动
java -jar netflix-eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=9877 --server.port=9877
java -jar netflix-eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=9878 --server.port=9878
配置就是这样的了
eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:9877/eureka/
















