在SpringCloud 中如何防止绕过网关请求后端服务
使用SpringCloud架构中在不作任何处理的情况下我们是可以绕过网关直接访问后端服务的。因此会带来很大的安全隐患.解决方案1.网络隔离将后端普通服务都部署在内网，通过防火墙策略限制只允许网关应用访问后端服务。2.应用层拦截请求后端服务时通过拦截器校验请求是否来自网关和服务间的调用，如果是非法调用则提示不允许访问。在请求经过网关的时候给请求头中增加一个额外的Header表示请求来源，在后端服务中写一个拦截器，判断请求头是否是来自网关或者是内部服务调用，如果不一致则不允许访问并给出提示。当然为了防止在每个后端服务都需要编写这个拦截器，我们可以将其写在一个公共的starter中，让后端服务引用即可。而且为了灵活，可以通过配置决定是否只允许后端服务访问。2.1 在网关gateway模块编写网关过滤器package cn.myxl.gateway.filter; import cn.myxl.gateway.constant.SecurityConstants; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest.Builder; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @author alan.yang.zhang * @date 2023-09-07 22:48 */ @Component @Slf4j public class RequestSourceFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest.Builder mutate = exchange.getRequest().mutate(); handleAddHeader(mutate, SecurityConstants.SOURCE, SecurityConstants.SOURCE_GATEWAY); ServerWebExchange webExchange = exchange.mutate().request(mutate.build()).build(); return chain.filter(webExchange); } private void handleAddHeader(Builder mutate, String headerName, String headerValue) { mutate.header(headerName, headerValue); } private void handleRemoveHeader(Builder mutate, String headerKey) { mutate.headers(httpHeaders -> httpHeaders.remove(headerKey)).build(); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 100; }2.1 创建Starter模块service-security-spring-boot-starter 创建annotation SecurityCheck 用来标记需要检查的方法 package cn.myxl.cloud.security.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author alan.yang.zhang * @date 2023-09-08 02:43 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SecurityCheck { } ServiceSecurityAutoConfiguration.java 是自动配置类，负责将自定义 Starter 中的服务注入到 Spring 容器中； ServiceSecurityProperties.java 是自定义 Starter 中的配置属性； spring.factories 是 Spring Boot 自动装配的核心配置文件，用于声明自动配置类。 创建starter配置类 package cn.myxl.cloud.security.starter; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author alan.yang.zhang * @date 2023-09-08 02:43 */ @Data @ConfigurationProperties(prefix = "service.security") public class ServiceSecurityProperties { private Boolean enabled; } 创建starter自动配置类 package cn.myxl.cloud.security.starter; import cn.myxl.cloud.security.aspect.SecurityCheckAspect; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author alan.yang.zhang * @date 2023-09-08 04:04 */ @Configuration @EnableConfigurationProperties({ServiceSecurityProperties.class}) @ConditionalOnProperty(prefix = "service.security", value = "enabled") public class ServiceSecurityAutoConfiguration { @Bean public SecurityCheckAspect securityCheckAspect() { return new SecurityCheckAspect(); } } 创建spring.factories 注意文件位置(src/main/resources/META-INF/spring.factories) org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.myxl.cloud.security.starter.ServiceSecurityAutoConfiguration 业务切面逻辑，用于校验请求是否经过网关 package cn.myxl.cloud.security.aspect; import cn.myxl.cloud.security.annotation.SecurityCheck; import cn.myxl.cloud.security.constant.SecurityConstants; import cn.myxl.cloud.security.exception.SecurityCheckException; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; /** * @author alan.yang.zhang * @date 2023-09-08 03:01 */ @Aspect @Component @Slf4j public class SecurityCheckAspect implements Ordered { @Before("@annotation(securityCheck)") public void doSecurityCheckBefore(JoinPoint point, SecurityCheck securityCheck) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String source = attributes.getRequest().getHeader(SecurityConstants.SOURCE); log.debug("[SecurityCheckAspect] [doSecurityCheckBefore] source 值为", source); if (!SecurityConstants.SOURCE_GATEWAY.equals(source) && !SecurityConstants.SOURCE_SERVICE_CALL.equals(source)) { log.debug("[SecurityCheckAspect] [doSecurityCheckBefore] 没有内部访问权限，不允许访问"); throw new SecurityCheckException("没有内部访问权限，不允许访问"); } } @Override public int getOrder() { return 0; } } 编写constant工具类 package cn.myxl.cloud.security.constant; /** * @author alan.yang.zhang * @date 2023-09-08 03:19 */ public class SecurityConstants { public static final String SOURCE = "X-Request-Source"; public static final String SOURCE_GATEWAY = "source-gateway"; public static final String SOURCE_SERVICE_CALL = "source-service-call"; } 编写SecurityCheckException工具类 package cn.myxl.cloud.security.exception; /** * @author alan.yang.zhang * @date 2023-09-08 03:53 */ public class SecurityCheckException extends RuntimeException { private static final long serialVersionUID = 2984861906864505926L; /** * Constructs a new runtime exception with the specified detail message. * The cause is not initialized, and may subsequently be initialized by a * call to {@link #initCause}. * * @param message the detail message. The detail message is saved for * later retrieval by the {@link #getMessage()} method. */ public SecurityCheckException(String message) { super(message); } }经过以上几步，一个公共的Starter模块就构建完成了。 后端服务引用此公共Starter模块即可<dependency> <groupId>cn.itcast.demo</groupId> <artifactId>service-security-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>添加注解SecurityCheck 到需要拦截的方法上@SecurityCheck @GetMapping("/{id}") public User queryById(@PathVariable("id") Long id) { return userService.queryById(id); }需要开启该功能service: security: enabled: true网关拦截调用完成了内部服务调用不需要拦截, 所以需要在方法调用处添加 header 标记来源为方法内部调用@FeignClient(contextId = "userClient", value = ServiceNameConstants.SERVICE_USER) public interface UserClient { @GetMapping(value = "/user/{id}", headers = {SecurityConstants.HEADER_SOURCE_SERVICE_CALL}) User findById(@PathVariable("id") Long id); }服务间内部调用完成了
项目结构: 前后分离部署方案很多，本次采用的是使用NGINX发布静态文件，并代理后端服务解决跨域问题。 前端：vue + element-ui 后端：SpringBoot 第一步: 后端部署 项目打包 mvn clean package 项目开启服务 java -jar digital-markting-customized-business-webapp-scrm-1.0.0.jar 第二步: 前端部署 vue项目打包 npm run build 拷贝dist下生成文件static和index.html到Nginx指定发布的目录（默认html目录下） 第三步: 配置Nginx server { listen 8080; server_name localhost; location / { root html; index index.html index.htm; } #代理后端API; #后端服务的地址为: http://localhost:8098/api/XXXX, 所以我们代理的地址为 /api location /api/ { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 后端服务地址 proxy_pass http://localhost:8098/api/; } #后面配置省略， 默认就好 } 第四步: 重启Nginx服务 重启nginx nginx -s reload 齐活。
1.准备 在spring Cloud Netflix栈中，各个微服务都是以HTTP接口的形式暴露自身服务的，因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是，用起来最方便、最优雅的还是要属Feign了。 Feign是一个声明式的Web Service客户端，它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。它具备可插拔的注解支持，包括Feign注解和JAX-RS注解。 Feign也支持可插拔的编码器和解码器。Spring Cloud为Feign增加了对Spring MVC注解的支持，还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。 2.使用Feign实现客户端负载均衡的消费者 1.通过 http://start.spring.io/ 使用SPRING INITIALIZR 创建一个基础的Spring Boot工程， Dependencies :Eureka Discovery + Feign Group : com.alanzh.platform Artifact : wish.feign.server Spring boot 版本: 1.5.4 <?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.alanzh.platform</groupId> <artifactId>wish.feign.server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>wish.feign.server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> @EnableFeignClients注解开启Feign功能 @EnableDiscoveryClient注解来添加发现服务能力。 @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 定义wish.user.service服务的接口，具体如下： @FeignClient(name="wish.user.service") public interface UserFeignService { @RequestMapping(value="/user/{userEntryID}", method=RequestMethod.GET) String getUserEntry(@PathVariable("userEntryID") Long userEntryID); } @FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现)，使用者可直接通过@Autowired注入。 value="/user/{userEntryID}"用来接收参数并传递参数到URI. @PathVariable("userEntryID") Long userEntryID 请不要忽略这里的@PathVariable("userEntryID") @FeignClient(name="wish.user.service") 注解来绑定该接口对应wish.user.service服务 创建Controller层调用wish.user.service服务的接口来消费wish.user.service服务. @RestController public class UserFeignAction { @Autowired UserFeignService userFeignService; @RequestMapping(value="/user/{userEntryID}", method=RequestMethod.GET) public String getUserEntry(@PathVariable Long userEntryID) { // 通过HTTP调用远程服务 return userFeignService.getUserEntry(userEntryID); } } application.properties中配置eureka服务注册中心 server.port=4000 spring.application.name=@project.name@ eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/ 启动应用，访问：http://localhost:4000/user/1 wish.user.service的两个服务提供方，分别输出了类似下面的日志内容： 2017-04-28 02:02:12.664 INFO 12912 --- [nio-2000-exec-4] c.a.p.controller.HelloWorldController : /hello, host:ALAN-HOME, service_id:ms-hello-world-service,Hello name 到这里，我们已经通过Feign在客户端已经实现了对服务调用的均衡负载。 Spring Cloud应用在启动时，Feign会扫描标有@FeignClient注解的接口，生成代理，并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象，该对象封装了HTTP请求需要的全部信息，请求参数名、请求方法等信息都是在这个过程中确定的，Feign的模板化就体现在这里。 齐活.
1. @SpringBootApplication注解的程序入口类必须放在所有的RestController的根路径的package下,否则无法被注册到Spring Boot的RestController上去 如果遇见这样得错误There was an unexpected error (type=Not Found, status=404).但是检查请求没有写错,那么请检查一下@SpringBootApplication是否在含有RestController类的根路径package下. package com.alanzh.platform.user.action; @RestController public class UserFeignAction { } package com.alanzh.platform; @SpringBootApplication public class Application { }
1.准备 Ribbon是一个基于HTTP和TCP客户端的负载均衡器,可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用. 当Ribbon与Eureka联合使用时，ribbonServerList会被DiscoveryEnabledNIWSServerList重写，扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing，它将职责委托给Eureka来确定服务端是否已经启动。 下面我们通过实例看看如何使用Ribbon来调用服务，并实现客户端的均衡负载。 启动服务注册中心：wish.eureka.server(端口设置为1000) 启动服务提供方：wish.user.service(端口设置为2000) 启动服务提供方：wish.user.service(端口设置为2001) 可以使用命令 java -jar wish.user.service-0.0.1-SNAPSHOT.jar --server.port=2001 启动服务并指定端口 此时访问：http://localhost:1000/ 2.使用Ribbon实现客户端负载均衡的消费者 1.通过 http://start.spring.io/ 使用SPRING INITIALIZR 创建一个基础的Spring Boot工程， Dependencies :Eureka Discovery + Ribbon Group : com.alanzh.platform Artifact : wish.ribbon.server Spring boot 版本: 1.5.4 <?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.alanzh.platform</groupId> <artifactId>wish.ribbon.server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>wish.ribbon.server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> @EnableDiscoveryClient注解来添加发现服务能力。 @Bean 创建RestTemplate实例,并注入到Spring容器里面. @LoadBalanced注解开启均衡负载能力。 @EnableDiscoveryClient @SpringBootApplication public class Application { @Bean @LoadBalanced RestTemplate getRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 3. 创建RibbonController通过RestTemplate来消费wish.user.service服务. WISH.USER.SERVICE 是服务名字 可在http://localhost:1000查看 @RequestMapping(value="/user") @RestController public class RibbonController { @RequestMapping(value="/{userEntryID}", method=RequestMethod.GET) public String getHelloWorld(@PathVariable String userEntryID) { return restTemplate.getForEntity("http://WISH.USER.SERVICE/user/" + userEntryID, String.class).getBody(); } @Autowired RestTemplate restTemplate; } application.yml中配置eureka服务注册中心 # Portal Info info: app: name: "@project.name@" description: "@project.description@" version: "@project.version@" # Spring Info spring: application: name: "@project.name@" server: port: 3000 eureka: instance: lease-renewal-interval-in-seconds: 5 # 心跳时间，即服务续约间隔时间（缺省为30s） lease-expiration-duration-in-seconds: 10 # 发呆时间，即服务续约到期时间（缺省为90s） client: serviceUrl: defaultZone: http://admin:123123@localhost:1000/eureka/ 启动应用，访问：http://localhost:3000/user/{ID} 3.在服务提供方打印日志 在UserEntryService.Java 中添加日志输出 private void sleep() { try { ServiceInstance instance = client.getLocalServiceInstance(); Thread.sleep(new Random().nextInt(2000)); logger.info("host:" + instance.getHost() + ", service_id:" + instance.getServiceId()); } catch (Exception e) { } } @Autowired private DiscoveryClient client; private final Logger logger = Logger.getLogger(getClass()); 重启之后调用服务就会发现,WISH.USER.SERVICE的两个服务提供方，分别输出了类似下面的日志内容： 2017-07-03 16:14:30.227 INFO 4188 --- [nio-2000-exec-4] c.a.platform.service.UserEntryService : host:ALAN-HOME, service_id:wish.user.service 4.齐活
之前我们使用的Eureka是标准模式只有单个节点现在我们使用peer模式构建Eureka集群 更新application 配置 更新第一章我们的Eureka-Server项目 application.properites 为 application.yml (语法更为简洁) # Portal Info info: app: name: "@project.name@" description: "@project.description@" version: "@project.version@" # Spring Info spring: application: name: "@project.name@" server: port: 1000 eureka: server: enable-self-preservation: false # 关闭自我保护模式（缺省为打开） eviction-interval-timer-in-ms: 5000 # 续期时间，即扫描失效服务的间隔时间（缺省为60*1000ms） client: fetch-registry: false register-with-eureka: false serviceUrl: defaultZone: http://admin:123123@localhost:1000/eureka/ 拷贝application.yml 为3份 分别命名为 application-eureka-server-0.yml, application-eureka-server-1.yml, application-eureka-server-2.yml 注: 我们要构筑3个peer eureka server,所以拷贝3份. application-eureka-server-0.yml # Spring Info server: port: 1000 eureka: instance: hostname: eureka-server-0 server: enable-self-preservation: false # 关闭自我保护模式（缺省为打开） eviction-interval-timer-in-ms: 5000 # 续期时间，即扫描失效服务的间隔时间（缺省为60*1000ms） client: register-with-eureka: true fetch-registry: true serviceUrl: defaultZone: http://admin:123123@eureka-server-1:1001/eureka/,http://admin:123123@eureka-server-2:1002/eureka/ 我们将app info等相同的信息保留在application.yml. 显示开启register-with-eureka: true 和 fetch-registry: true 否则会被application.yml 中信息覆盖. defaultZone: http://admin:123123@eureka-server-1:1001/eureka/,http://admin:123123@eureka-server-2:1002/eureka/, 在defaultZone 处分别填写集群中除了自己的其他节点. application-eureka-server-1.yml # Spring Info server: port: 1001 eureka: instance: hostname: eureka-server-1 server: enable-self-preservation: false # 关闭自我保护模式（缺省为打开） eviction-interval-timer-in-ms: 5000 # 续期时间，即扫描失效服务的间隔时间（缺省为60*1000ms） client: register-with-eureka: true fetch-registry: true serviceUrl: defaultZone: http://admin:123123@eureka-server-0:1000/eureka/,http://admin:123123@eureka-server-2:1002/eureka/ application-eureka-server-2.yml # Spring Info server: port: 1002 eureka: instance: hostname: eureka-server-2 server: enable-self-preservation: false # 关闭自我保护模式（缺省为打开） eviction-interval-timer-in-ms: 5000 # 续期时间，即扫描失效服务的间隔时间（缺省为60*1000ms） client: register-with-eureka: true fetch-registry: true serviceUrl: defaultZone: http://admin:123123@eureka-server-0:1000/eureka/,http://admin:123123@eureka-server-1:1001/eureka/ 更新DNS (Spring Cloud 本身不认为LocalHost为一个有效域) Windows 10 Host 文件 :C:\Windows\System32\drivers\etc\hosts 127.0.0.1 eureka-server-0 127.0.0.1 eureka-server-1 127.0.0.1 eureka-server-2 启动 使用maven 中 clean package 命令重新打包. 使用jar启动 添加参数 --spring.profiles.active=XXXX 指定启动使用哪个profile. 开启三个命令行执行下面的一条命令 java -jar wish.eureka.server-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-server-0 java -jar wish.eureka.server-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-server-1 java -jar wish.eureka.server-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka-server-2 第一个服务会抛出一些错误,请不用担心那是因为其余服务还没有启动.启动之后就会没有问题了. 二: 服务注册 在设置了多节点的服务注册中心之后，我们需要简单需求服务配置，就能将服务注册到Eureka Server集群中。我们以 Chapter1-3 中的user-service为基础，修改 application.yml 配置文件： defaultZone: http://admin:123123@eureka-server-0:1000/eureka/,http://admin:123123@eureka-server-2:1002/eureka/,http://admin:123123@eureka-server-1:1001/eureka/ *注:我们也可以不更新, eureka集群自动同步更新注册信息. 但当前指定的eureka server 在集群同步之前down掉,则信息无法更新. 上面的配置主要对 eureka.client.serviceUrl.defaultZone 属性做了改动，eureka-server-1,eureka-server-2,eureka-server-3。 我们启动该服务，我们访问eureka集群各个节点都会发现刚刚注册的服务,如果其中任意一台断开并不影响服务的使用.从而实现了高可用的服务注册中心。 深入理解 Eureka Server的同步遵循着一个非常简单的原则：只要有一条边将节点连接，就可以进行信息传播与同步. 场景一：假设我们有3个注册中心，我们将peer1、peer2、peer3各自都将serviceUrl指向另外两个节点。换言之，peer1、peer2、peer3是两两互相注册的。启动三个服务注册中心，并将compute-service的serviceUrl指向peer1并启动，可以获得如下图所示的集群效果。 访问http://localhost:1000/，可以看到3个注册中心组成了集群，服务通过peer1同步给了与之互相注册的peer2和peer3。 两两注册的方式可以实现集群中节点完全对等的效果，实现最高可用性集群，任何一台注册中心故障都不会影响服务的注册与发现 Eureka Server具备单方面有指向的服务传播与同步机制，在一些对服务发现有限制的情况下，可以利用这样的机制进行服务注册与发现的的单向控制
一. CAP理论概述 分布式领域中存在CAP理论，且该理论已被证明：任何分布式系统只可同时满足两点，无法三者兼顾。 C：Consistency，一致性，数据一致更新，所有数据变动都是同步的。 A：Availability，可用性，系统具有好的响应性能。 P：Partition tolerance，分区容错性。 因此，将精力浪费在思考如何设计能满足三者的完美系统上是愚钝的，应该根据应用场景进行适当取舍。 （1）一致性 一致性是指从系统外部读取系统内部的数据时，在一定约束条件下相同，即数据变动在系统内部各节点应该是同步的。根据一致性的强弱程度不同，可以将一致性级别分为如下几种： 强一致性（strong consistency）。任何时刻，任何用户都能读取到最近一次成功更新的数据。 单调一致性（monotonic consistency）。任何时刻，任何用户一旦读到某个数据在某次更新后的值，那么就不会再读到比这个值更旧的值。也就是说，可获取的数据顺序必是单调递增的。 会话一致性（session consistency）。任何用户在某次会话中，一旦读到某个数据在某次更新后的值，那么在本次会话中就不会再读到比这值更旧的值 会话一致性是在单调一致性的基础上进一步放松约束，只保证单个用户单个会话内的单调性，在不同用户或同一用户不同会话间则没有保障。示例case：php的session概念。 最终一致性（eventual consistency）。用户只能读到某次更新后的值，但系统保证数据将最终达到完全一致的状态，只是所需时间不能保障。 弱一致性（weak consistency）。用户无法在确定时间内读到最新更新的值。 二.ZooKeeper提供的一致性服务 很多文章和博客里提到，zookeeper是一种提供强一致性的服务，在分区容错性和可用性上做了一定折中，这和CAP理论是吻合的。但实际上zookeeper提供的只是单调一致性。 原因： 假设有2n+1个server，在同步流程中，leader向follower同步数据，当同步完成的follower数量大于 n+1时同步流程结束，系统可接受client的连接请求。如果client连接的并非同步完成的follower，那么得到的并非最新数据，但可以保证单调性。 follower接收写请求后，转发给leader处理；leader完成两阶段提交的机制。向所有server发起提案，当提案获得超过半数（n+1）的server认同后，将对整个集群进行同步，超过半数（n+1）的server同步完成后，该写请求完成。如果client连接的并非同步完成follower，那么得到的并非最新数据，但可以保证单调性。 用分布式系统的CAP原则来分析Zookeeper： C: Zookeeper保证了最终一致性,在十几秒可以Sync到各个节点. A: Zookeeper保证了可用性,数据总是可用的,没有锁.并且有一大半的节点所拥有的数据是最新的,实时的. 如果想保证取得是数据一定是最新的,需要手工调用Sync() P: 有2点需要分析的. 节点多了会导致写数据延时非常大,因为需要多个节点同步. 节点多了Leader选举非常耗时, 就会放大网络的问题. 可以通过引入 observer节点缓解这个问题. 问题: CAP满足哪几部分 | failover方式是怎样 | 语言机制 DNS <DNS 变更延迟问题> DNS最原始的配置文件和 DNS 来做服务发现，Host、端口都是写在配置文件里的，发生变更的时候只能修改配置文件并重启服务。所以当某台机器挂掉的时候，依赖它上面服务的其他系统也都全部会出问题。而应急的步骤都是先在别的机器上运行新的实例，修改配置文件并重启关联的其他系统。这样做费时、费力、且会有一个时间窗口内系统无法提供服务。 NGINX <中心化负载均衡，单点问题> 通过 Nginx 来做了负载均衡/主备的这样做还是有两个问题：（1）Nginx 本身成为一个故障点（2）连接数量翻倍，其中第二个问题曾导致我们的环境出现了 nf_conntrack table full 的问题。我们的关键服务都是多实例负载均衡的，当系统并发上升到一定程度的时候，某些服务器，尤其是跑着 Nginx 的机器很容易出现这个错误。 zookeeper <多语言问题> zk服务实例注册的 Node 类型是 ephemeral node，这种类型的节点只有在客户端保持着连接的时候才有效。所以当某个服务实例被停止或者出现网络异常的时候，对应的节点也会被删掉。因此，任何时候从 ZooKeeper 里查询到的都是当前活跃的实例。借助 ZooKeeper 的推送功能，服务的消费者可以得知实例的变化，从而可以从容应对服务实例的宕机和新实例的添加，无需重启。 SmartStack: Airbnb的自动服务发现和注册框架 SmartStack，在zookeeper和haproxy上封装一层 etcd(coreos开发，系统级别的) etcd是一个采用HTTP协议的健/值对存储系统，它是一个分布式和功能层次配置系统，可用于构建服务发现系统。其很容易部署、安装和使用，提供了可靠的数据持久化特性。它是安全的并且文档也十分齐全。etcd比Zookeeper是比更好的选择，因为它很简单，然而，它需要搭配一些第三方工具才可以提供服务发现功能。 consul(go语言写的) Consul是强一致性的数据存储，使用gossip形成动态集群。它提供分级键/值存储方式，不仅可以存储数据，而且可以用于注册器件事各种任务，从发送数据改变通知到运行健康检查和自定义命令，具体如何取决于它们的输出。与Zookeeper和etcd不一样，Consul内嵌实现了服务发现系统，所以这样就不需要构建自己的系统或使用第三方系统。这一发现系统除了上述提到的特性之外，还包括节点健康检查和运行在其上的服务。Zookeeper和etcd只提供原始的键/值队存储，要求应用程序开发人员构建他们自己的系统提供服务发现功能。而Consul提供了一个内置的服务发现的框架。客户只需要注册服务并通过DNS或HTTP接口执行服务发现。其他两个工具需要一个亲手制作的解决方案或借助于第三方工具。Consul为多种数据中心提供了开箱即用的原生支持，其中的gossip系统不仅可以工作在同一集群内部的各个节点，而且还可以跨数据中心工作。 Netflix的Eureka方案 Eureka 由两个组件组成： Eureka 服务器 和 Eureka 客户端 。Eureka 服务器用作服务注册服务器。Eureka 客户端是一个 java 客户端，用来简化与服务器的交互、作为轮询负载均衡器，并提供服务的故障切换支持。Netflix 在其生产环境中使用的是另外的客户端，它提供基于流量、资源利用率以及出错状态的加权负载均衡。当一个中间层服务首次启动时，他会将自己注册到 Eureka 中，以便让客户端找到它，同时每 30 秒发送一次心跳。如果一个服务在几分钟内没有发送心跳，它将从所有 Eureka 节点上注销。 一个 Amazon 域中可以有一个 Eureka 节点集群，每个可用区（Availability Zone）至少有一个 Eureka 节点。AWS 的域相互之间是隔离的。 为什么不应该使用ZooKeeper做服务发现 zk是满足CP牺牲A，这个不对，看ZooKeeper和CAP理论及一致性原则 ，其实zk只是满足最终一致性C，可用性A这个是保证的，并且保证一半的节点是最新的数据，分区性P这个得看节点多少及读写情况，节点多，则写耗时长，另外节点多了Leader选举非常耗时, 就会放大网络的问题，容易分区。 对于Service发现服务而言，宁可返回某服务5分钟之前在哪几个服务器上可用的信息，也不能因为暂时的网络故障而找不到可用的服务器，而不返回任何结果。所以说，用ZooKeeper来做Service发现服务是肯定错误的。总结一句就是，service不是强一致的，所以会有部分情况下没发现新服务导致请求出错。当部分或者所有节点跟ZooKeeper断开的情况下，每个节点还可以从本地缓存中获取到数据；但是，即便如此，ZooKeeper下所有节点不可能保证任何时候都能缓存所有的服务注册信息。如果ZooKeeper下所有节点都断开了，或者集群中出现了网络分割的故障（注：由于交换机故障导致交换机底下的子网间不能互访）；那么ZooKeeper会将它们都从自己管理范围中剔除出去，外界就不能访问到这些节点了，即便这些节点本身是“健康”的，可以正常提供服务的；所以导致到达这些节点的服务请求被丢失了。 Eureka处理网络问题导致分区。如果Eureka服务节点在短时间里丢失了大量的心跳连接（注：可能发生了网络故障），那么这个Eureka节点会进入”自我保护模式“，同时保留那些“心跳死亡“的服务注册信息不过期。此时，这个Eureka节点对于新的服务还能提供注册服务，对于”死亡“的仍然保留，以防还有客户端向其发起请求。当网络故障恢复后，这个Eureka节点会退出”自我保护模式“。所以Eureka的哲学是，同时保留”好数据“与”坏数据“总比丢掉任何”好数据“要更好，所以这种模式在实践中非常有效。 Eureka就是为发现服务所设计的，它有独立的客户端程序库，同时提供心跳服务、服务健康监测、自动发布服务与自动刷新缓存的功能。但是，如果使用ZooKeeper你必须自己来实现这些功能。 Eureka一致性分析 Eureka: How do I disable/configure peer replication? 通过配置eureka.serviceUrl.defaultZone来进行复制eureka.client.serviceUrl.defaultZone=http://:<peer1port&g... How to config multiple Eureka Servers from client in Spring Cloud 貌似是根据配置的url的前后顺序来复制的
Spring Cloud系列-微服务篇(二) 服务注册和服务发现
一：创建“服务提供方” 在这里使用Spring Boot快速的创建三个基础服务，并且其中三者之间存在逻辑依赖关系。 User Service Wish Service Tree Service 现在我们以User Service 为例通过 http://start.spring.io/ 使用SPRING INITIALIZR 创建一个基础的Spring Boot工程， Dependencies :Eureka Discovery Group : com.alanzh.portal Artifact : wish.user.server Spring boot 版本: 1.5.3 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.alanzh.platform</groupId> <artifactId>wish.user.service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>wish.user.service</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> User Entry 领域实体 package com.alanzh.platform.model; /** * @author Alan.Zhang */ public class UserEntry { // Constructor、Getter、Setter private Long userEntryID; private String userEntryName; } UserEntryAction 控制层 package com.alanzh.platform.action; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.alanzh.platform.model.UserEntry; import com.alanzh.platform.service.UserEntryService; /** * @author Alan.Zhang */ @RestController @RequestMapping(value="/user") public class UserEntryAction { @RequestMapping(value="/{userEntryID}", method=RequestMethod.GET) public UserEntry getUserEntry(@PathVariable("userEntryID") Long userEntryID) { return userEntryService.getUserEntry(userEntryID); } @RequestMapping(value="/{userEntryID}", method=RequestMethod.DELETE) public UserEntry deleteUserEntry(@PathVariable("userEntryID") Long userEntryID) { return userEntryService.deleteUserEntry(userEntryID); } @Autowired private UserEntryService userEntryService; } UserEntryService服务层 package com.alanzh.platform.service; import java.util.Random; import org.springframework.stereotype.Service; import com.alanzh.platform.model.UserEntry; /** * @author Alan.Zhang */ @Service public class UserEntryService { public UserEntry getUserEntry(Long userEntryID) { sleep(); return new UserEntry(userEntryID, "User-" + userEntryID); } public UserEntry deleteUserEntry(Long userEntryID) { sleep(); return new UserEntry(userEntryID, "User-" + userEntryID); } private void sleep() { try { Thread.sleep(new Random().nextInt(2000)); } catch (Exception e) { } } } Application 程序入口 package com.alanzh.platform; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ==@EnableDiscoveryClient注解==，该注解能激活Eureka中的DiscoveryClient实现. 配置客户端信息 application.yml 做一些配置工作.(在这里我们YMAL, application.propertie 同样可以) # Portal Info info: app: name: "@project.name@" description: "@project.description@" version: "@project.version@" # Spring Info spring: application: name: "@project.name@" server: port: 2000 eureka: client: serviceUrl: defaultZone: http://admin:123123@localhost:1000/eureka/ spring.application.name属性，我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。 @project.name@ 为Artifact 即wish.user.service eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容，指定服务注册中心的位置。 admin : 为上篇我们配置安全的用户 123123 为密码 启动该工程后，再次访问：http://localhost:1000/可以发现我们服务已经注册 现在我们构建Wish Service, 步骤和上面一致. 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.alanzh.platform</groupId> <artifactId>wish.wish.service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>wish.wish.service</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Wish Entry 领域实体 /** * */ package com.alanzh.platform.model; import java.util.Date; /** * @author Alan.Zhang */ public class WishEntry { // Constructor、Getter、Setter private Long wishEntryID; private Long userEntryID; private String userName; private String title; private String content; private int type; private Date createDate; } WishEntryAction 控制层 /** * */ package com.alanzh.platform.action; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.alanzh.platform.model.WishEntry; import com.alanzh.platform.service.WishEntryService; /** * @author Alan.Zhang * */ @RestController @RequestMapping(value="/wish") public class WishEntryAction { @RequestMapping(value="/{wishEntryID}", method=RequestMethod.DELETE) public WishEntry deleteWishEntry(@PathVariable("wishEntryID") Long wishEntryID) { return wishEntryService.deleteWishEntry(wishEntryID); } @RequestMapping(value="/{wishEntryID}", method=RequestMethod.GET) public WishEntry getWishEntry(@PathVariable("wishEntryID") Long wishEntryID) { return wishEntryService.getWishEntry(wishEntryID); } @Autowired private WishEntryService wishEntryService; } WishEntryService 服务层 /** * */ package com.alanzh.platform.service; import java.util.Random; import org.springframework.stereotype.Service; import com.alanzh.platform.model.WishEntry; /** * @author Alan.Zhang * */ @Service public class WishEntryService { public WishEntry getWishEntry(Long wishEntryID) { sleep(); return new WishEntry(wishEntryID, "WishEntry-" + wishEntryID); } public WishEntry deleteWishEntry(Long wishEntryID) { sleep(); return new WishEntry(wishEntryID, "WishEntry-" + wishEntryID); } private void sleep() { try { Thread.sleep(new Random().nextInt(2000)); } catch (Exception e) { } } } Application 程序入口 package com.alanzh.platform; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 配置文件 # Portal Info info: app: name: "@project.name@" description: "@project.description@" version: "@project.version@" # Spring Info spring: application: name: "@project.name@" server: port: 2200 eureka: instance: lease-renewal-interval-in-seconds: 5 # 心跳时间，即服务续约间隔时间（缺省为30s） lease-expiration-duration-in-seconds: 10 # 发呆时间，即服务续约到期时间（缺省为90s） client: serviceUrl: defaultZone: http://admin:123123@localhost:1000/eureka/ 注: 端口更改为2200 其余和 UserService 一致 WishService 完成, 启动服务. 依照上面步骤添加WishTree service.添加完成之后启动服务就会看到如图: 项目地址: https://code.aliyun.com/alanzh/ms-spring-cloud/tree/master/chapter-1/chapter-1-2
Spring Cloud系列-微服务篇(一) 服务注册和服务发现
在简单介绍了Spring Cloud和微服务架构之后，下面回归本文的主旨内容，如何使用Spring Cloud搭建服务注册与发现模块。 一：Eureka简介 Eureka是Spring Cloud Netflix的一个子模块，也是核心模块之一。用于云端服务发现，一个基于REST的服务，用于定位服务，以实现云端中间层服务发现和故障转移。 服务注册与发现对于微服务系统来说非常重要。有了服务发现与注册，你就不需要整天改服务调用的配置文件了，你只需要使用服务的标识符，就可以访问到服务。他的功能类似于dubbo的注册中心（register）。 服务发现： 服务发现是微服务基础架构的关键原则之一。试图着手配置每个客户端或某种格式的约定可以说是非常困难的和非常脆弱的。Eureka是Netflix服务发现的一种服务和客户端。这种服务是可以被高可用性配置的和部署,并且在注册的服务当中，每个服务的状态可以互相复制给彼此。 服务注册: 当一个客户端注册到Eureka,它提供关于自己的元数据（诸如主机和端口，健康指标URL，首页等）Eureka通过一个服务从各个实例接收心跳信息。如果心跳接收失败超过配置的时间，实例将会正常从注册里面移除 所以，我们这里的核心内容就是服务发现模块：Eureka。下面我们动手来做一些尝试。 二：创建“服务注册中心” 1.通过 http://start.spring.io/ 使用SPRING INITIALIZR 创建一个基础的Spring Boot工程， Dependencies :Eureka Server Group : com.alanzh.portal Artifact : ms-eureka-server Spring boot 版本: 1.5.3 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.alanzh.platform</groupId> <artifactId>wish.eureka.server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>wish.eureka.server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 配置Application.java 启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单，只需要在一个普通的Spring Boot应用中添加注解就能开启此功能. ==@EnableEurekaServer==注解启动一个服务注册中心提供给其他应用进行对话. ==@SpringBootApplication==注解表示程序入口. package com.alanzh.platform; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 在默认设置下，该服务注册中心也会将自己作为客户端来尝试注册它自己，所以我们需要禁用它的客户端注册行为，否则后台会报错, 只需要在application.properties中问增加如下配置： # eureka config server.port=1000 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/ 启动工程 (2种方式) 通过STS的Boot Dashboard 直接启动, 或者在程序入口Application.java类run as spring app boot. 通过命令行启动 java -jar wish.eureka.server-0.0.1-SNAPSHOT.jar 访问：http://localhost:1000/ 到此创建“服务注册中心”已经完成. 三：Eureka注册中心加入权限 (为安全考虑) 1：添加相应的jar，在pom.xml加入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 2: 配置文件新建一个bootstrap.yml文件在src/mian/resources下 security: basic: enabled: true user: name: admin password: 123123 注: bootstrap.yml 是使用YMAL语法, 其中 application.properties, application.yml, bootstrap.yml,bootstrap.properties 只是语法不同,作用一致 bootstrap.yml可以理解成系统级别的一些参数配置，这些参数一般是不会变动的 application.yml 可以用来定义应用级别的，如果搭配spring-cloud-config使用 application.yml里面定义的文件可以实现动态替换 项目下载 : https://code.aliyun.com/alanzh/ms-spring-cloud/tree/master/chapter-1/chapter-1-1/wish.eureka.server
Spring Cloud系列-微服务篇(开篇) SpringCloud组件和概念介绍
一: 什么是微服务（Microservice） “微服务架构”在这几年非常的火热，以至于关于微服务架构相关的产品社区也变得越来越活跃（比如：netflix、dubbo），Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。 那么什么是“微服务架构”呢？简单的说，微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务，每个服务都能独立部署、独立维护、独立扩展，服务与服务间通过诸如RESTful API的方式互相调用。 对于“微服务架构”，大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的《Microservices》，本文不做更多的介绍和描述。 我们把整个系统根据业务拆分成几个子系统。 1. 服务注册和发现 : 所有的服务都在注册中心注册，负载均衡也是通过在注册中心注册的服务来使用一定策略来实现。 2. 服务路由 : 所有的客户端都通过同一个网关地址访问后台的服务，通过路由配置，网关来判断一个URL请求由哪个服务处理。请求转发到服务上的时候也使用负载均衡。 3. 服务负载均衡 : 每个子系统可以部署多个应用，多个应用之间使用负载均衡。 4. 断路器:及时处理服务调用时的超时和错误，防止由于其中一个服务的问题而导致整体系统的瘫痪。 5. 服务部署 : 实现自动部署和CI 6. 服务配置管理 :实现统一的配置 7. 服务监控 :监控每个服务调用花费的时间等。 8. 日志处理: 实现统一的日志处理 目前主流的微服务框架：Dubbo、 SpringCloud、thrift、Hessian等。 二：SpringCloud项目简介 springCloud是基于SpringBoot的一整套实现微服务的框架。他提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。最重要的是， 跟spring boot框架一起使用的话，会让你开发微服务架构的云服务非常好的方便。 SpringBoot旨在简化创建产品级的 Spring 应用和服务，简化了配置文件，使用嵌入式web服务器，含有诸多开箱即用微服务功能 spring cloud子项目包括： Spring Cloud Config： 配置管理开发工具包，可以让你把配置放到远程服务器，目前支持本地存储、Git以及Subversion。 Spring Cloud Bus： 事件、消息总线，用于在集群（例如，配置变化事件）中传播状态变化，可与Spring Cloud Config联合实现热部署。 Spring Cloud Netflix： 针对多种Netflix组件提供的开发工具包，其中包括Eureka、Hystrix、Zuul、Archaius等。 Netflix Eureka： 云端负载均衡，一个基于 REST 的服务，用于定位服务，以实现云端的负载均衡和中间层服务器的故障转移。 Netflix Hystrix： 容错管理工具，旨在通过控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。 Netflix Zuul： 边缘服务工具，是提供动态路由，监控，弹性，安全等的边缘服务。 Netflix Archaius： 配置管理API，包含一系列配置管理API，提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。 Spring Cloud for Cloud Foundry： 通过Oauth2协议绑定服务到CloudFoundry，CloudFoundry是VMware推出的开源PaaS云平台。 Spring Cloud Sleuth： 日志收集工具包，封装了Dapper,Zipkin和HTrace操作。 Spring Cloud Data Flow： 大数据操作工具，通过命令行方式操作数据流。 Spring Cloud Security： 安全工具包，为你的应用程序添加安全控制，主要是指OAuth2。 Spring Cloud Consul： 封装了Consul操作，consul是一个服务发现与配置工具，与Docker容器可以无缝集成。 Spring Cloud Zookeeper： 操作Zookeeper的工具包，用于使用zookeeper方式的服务注册和发现。 Spring Cloud Stream： 数据流操作开发包，封装了与Redis,Rabbit、Kafka等发送接收消息。 Spring Cloud CLI： 基于 Spring Boot CLI，可以让你以命令行方式快速建立云组件。