我们最先接触的单体架构,整个系统就只有一个工程,打包往往是打成了 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 包依赖:
application.properties 配置文件(不能叫bootstrap.properties)
server.port=8763eureka.instance.hostname=localhost#是否注册到eurekaeureka.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也不会将其删除,默认为trueeureka.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@EnableEurekaServerpublic 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 客户端的启动器。
bootstrap.properties
spring.application.name=micro-orderserver.port=8084eureka.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 和属性配置文件基本上差不多,消费要负责调用服务提供方,所以需要调用客户端
依赖
bootstrap.properties
#是否注册到eurekaeureka.client.registerWithEureka=true#是否从eureka中拉取注册信息eureka.client.fetchRegistry=trueeureka.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";@Overridepublic List
服务提供方的名称,再加上服务提供方的接口名就可以完成调用了。 服务提供方和服务消费启动的时候都会往服务注册中心注册服务,eureka 服务端也可以通过 界面查看到服务注册情况:
http://localhost:8763/
调用web接口测试看看
// controllerpackage 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
http://localhost:8083/user/queryUser
3、Eureka 用户认证连接到 eureka 的时候需要带上连接的用户名和密码
Eureka 服务端改造
添加security启动器
关闭 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;@EnableWebSecuritypublic 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=truespring.security.user.name=adminspring.security.user.password=admin
Eureka 客户端改造
跟 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 也不会将其删 除,默认为 trueeureka.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=60000
5、Eureka 健康检测Eureka 默认的健康检测只是你校验服务连接是否是 UP 还是 DOWN 的,然后客户端只会调用 状态为 UP 状态的服务,但是有的情况下,虽然服务连接是好的,但是有可能这个服务的某 些接口不是正常的,可能由于需要连接 Redis,mongodb 或者 DB 有问题导致接口调用失败, 所以理论上服务虽然能够正常调用,但是它不是一个健康的服务。所以我们就有必要对这种 情况做自定义健康检测。
application.properties 配置
开启健康检测
#健康检测eureka.client.healthcheck.enabled=true
自定义健康检测代码
@Configurationpublic 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
比如有些情况是服务主机意外宕机了,也就意味着服务没办法给 eureka 心跳信息了,但是 eureka 在没有接受到心跳的情况下依赖维护该服务 90s,在这 90s 之内可能会有客户端调用 到该服务,这就可能会导致调用失败。所以我们必须要有一个机制能手动的立马把宕机的服 务从 eureka 服务列表中清除掉,避免被服务调用方调用到。
调用服务下线的接口: 这个接口是调用 eureka 服务端的接口
http://localhost:9876/eureka/apps/micro-order/localhost:micro-order:8765
delete请求
注意:
测试时候如果服务没有关闭, 会一直有心跳, 导致手动下线失败
另外手动停止idea的服务会直接移除掉服务, 不会保活90s, 因为是正常停机, 客户端自动服务下线了
可以通过如下命令自己模拟异常宕机
windows
netstat -nao | findstr 8765taskkill /f /pid 19016 # 此时eureka的服务列表中还是有服务, 到保活时间过去, eureka也会将其删除掉
linux
ps -aux | grep 8765kill -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=9877eureka.client.serviceUrl.defaultZone=http://localhost:9878/eureka/
端口为 9877 的 eureka 服务端把自己注册到 9878 的 eureka 服务端
# 多个使用逗号分隔server.port=9878eureka.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/