提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
随着项目的越来越大,每次启动编译时间将会越来越长,这对于开发、测试、部署带来极大的影响,大大降低了效率。这时,关于微服务的模式就兴起了。
Spring cloud是一个基于Spring Boot实现的服务治理工具包,用于微服务架构中管理和协调服务的。微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被技术选型,独立开发,独立部署,独立运维,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
服务注册发现(注册中心)——Netflix Eureka : 帮我们管理服务的通信地址(ip,端口) 客户端负载均衡——Netflix Ribbon\Feign : 解决服务的调用的 断路器——Netflix Hystrix :解决微服务故障的,保护微服务的 服务网关——Netflix Zuul :统一访问入口,微服务的大门(安保部门) 分布式配置——Spring Cloud Config :统一管理微服务的配置
搭建一个maven项目,配置pom文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </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>Finchley.SR1</spring-cloud.version> <springboot.version>2.0.5.RELEASE</springboot.version> </properties> <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>新建子模块,服务提供者,maven项目,编写接口。此处偷懒将业务代码直接写到controller,应该是写到service层的。
@RestController @RequestMapping("/provider") public class UserController { // @Autowired // private IUserService userService; @GetMapping("/user/{id}") //user/1 public User getUser(@PathVariable("id") Long id) { // 正常应该调用service获取用户,现在模拟一下 return new User(id, "zs"); } }同理建立子模块,服务消费者。这里用到了框架自带的RestTemplate发送请求。需要先在入口类注入。
@Configuration // <beans></beans> public class CfgBean { @Bean //<bean class="org.springframework.web.client.RestTemplate"></bean> public RestTemplate getRestTemplate(){ return new RestTemplate(); } } @RestController @RequestMapping("/consumer") public class UserController { //多个方法调用只需改一处就ok public static final String URL_PREFIX = "http://localhost:8001"; @Autowired private RestTemplate restTemplate; @RequestMapping("/user/{id}") public User getUser(@PathVariable("id")Long id){ //调用远程服务 http请求 String url = URL_PREFIX+"/provider/user/"+id; return restTemplate.getForObject(url,User.class ); } }分别启动,通过调用消费者得到提供者的返回值。
上面我们是在消费者中写死了提供者的URL,这肯定在实际中是不可取的,所以我们可以调用Eureka将服务提供者统一管理。
创建一个普通maven项目
pom
<!--springboot支持--> <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> </dependency> <!--Eureka服务端支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>yml配置
server: port: 7001 eureka: instance: hostname: localhost client: registerWithEureka: false #是否要注册到eureka fetchRegistry: false #表示是否从Eureka Server获取注册信息 serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机配置入口类
@SpringBootApplication @EnableEurekaServer //标识是eureka服务端 public class EnrekaServerApplication_7001 { public static void main(String[] args) { SpringApplication.run(EnrekaServerApplication_7001.class); } }添加依赖
<!--eureka客户端支持 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>yml
eureka: client: service-url: defaultZone: http://localhost:7001/eureka #告诉服务提供者要把服务注册到哪儿 instance: prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称 ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找入口类
@SpringBootApplication @EnableEurekaClient //表示是eureka的客户端 public class UserProviderApplication_8001 { public static void main(String[] args) { SpringApplication.run(UserProviderApplication_8001.class); } }添加依赖
<!--eureka客户端支持 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>yml
eureka: client: service-url: defaultZone: http://localhost:7001/eureka #告诉服务提供者要把服务注册到哪儿 instance: prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称 ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找入口类
@SpringBootApplication @EnableEurekaClient //表示是eureka的客户端 public class UserProviderApplication_8001 { public static void main(String[] args) { SpringApplication.run(UserProviderApplication_8001.class); } }服务调用
@Autowired private DiscoveryClient discoveryClient;// Eureka客户端,可以获取到服务实例信息 // String baseUrl = "http://localhost:8081/user/"; // 根据服务名称,获取服务实例 List<ServiceInstance> instances = discoveryClient.getInstances("user-service"); // 因为只有一个UserService,因此我们直接get(0)获取 ServiceInstance instance = instances.get(0); // 获取ip和端口信息 String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/"; this.restTemplate.getForObject(baseUrl + id, User.class)为了提供并发量,有时同一个服务提供者可以部署多个(商品服务)。这个客户端在调用时要根据一定的负责均衡策略完成负载调用。
简单的说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中列出load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。
依赖
<!--客户端负载均衡实现 ribbon--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>消费者入口类修改
@Bean @LoadBalanced //开启负载均衡 public RestTemplate getRestTemplate(){ return new RestTemplate(); }服务消费
@RestController @RequestMapping("/consumer") public class UserController { //多个方法调用只需改一处就ok //public static final String URL_PREFIX = "http://localhost:8001"; public static final String URL_PREFIX = "http://USER-PROVIDER"; //通过服务名从注册中心获取服务列表,通过负载均衡调用 @Autowired private RestTemplate restTemplate; @RequestMapping("/user/{id}") public User getUser(@PathVariable("id")Long id){ //调用远程服务 http请求 String url = URL_PREFIX+"/provider/user/"+id; return restTemplate.getForObject(url,User.class ); } }测试 会出现轮询的效果
部分
RoundRobinRule(默认):简单轮询服务列表来选择服务器。WeightedResponseTimeRule:为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。RandomRule:随机选择一个可用的服务器。前面的可以发现当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下,并且显得好傻。
Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix(关于Hystrix我们后面再讲),可以让我们不再需要显式地使用这两个组件。
总起来说,Feign具有如下特性: 可插拔的注解支持,包括Feign注解和JAX-RS注解; 支持可插拔的HTTP编码器和解码器; 支持Hystrix和它的Fallback; 支持Ribbon的负载均衡; 支持HTTP请求和响应的压缩。
依赖,将ribbon修改为feign
<!--feign的支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>yml
server: port: 9003 eureka: client: registerWithEureka: false #不注册到Eureka,不在注册中心显示 service-url: #defaultZone: http://localhost:7001/eureka defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka调用服务的名字,要和服务提供者里面访问地址和参数等保持一致。
//调用服务名字 @FeignClient(value = "USER-PROVIDER") @RequestMapping("/provider") public interface UserCilent { @RequestMapping("/user/{id}") //user/1 User getUser(@PathVariable("id") Long id); }入口类
@SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = "上面服务接口包") public class UserConsumerFeign9002Application { public static void main(String[] args) { SpringApplication.run(UserConsumerFeign9002Application.class, args); } }消费代码
@RestController @RequestMapping("/consumer") public class UserController { @Autowired private UserCilent userCilent; @RequestMapping("/user/{id}") public User getUser(@PathVariable("id")Long id){ System.out.println(userCilent.getClass()); return userCilent.getUser(id); } }Hystrix是保证微服务群健壮框架,做了隔离,熔断,降级等操作.最终达到不会由于某一个服务出问题而导致雪崩现象,让整体群死掉。
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。
Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。
资源隔离(限流):包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
熔断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
缓存:提供了请求缓存、请求合并实现。
服务提供依赖
<!--断路器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>yml
server: port: 4020 spring: application: name: user-provider eureka: instance: prefer-ip-address: true #显示eureka客户端真实ip client: service-url: #defaultZone: http://localhost:1010/eureka #告诉服务提供者要把服务注册到哪 #eureka集群环境 defaultZone: http://eureka1:1010/eureka,http://eureka2:1011/eureka,http://eureka3:1012/eureka入口类
Feign自己封装了Hytrix,不需要再导入jar yml
server: port: 9003 eureka: client: registerWithEureka: false #不注册到Eureka,不在注册中心显示 service-url: defaultZone: http://localhost:7001/eureka feign: hystrix: enabled: true #开启熔断支持 client: config: remote-service: #服务名,填写default为所有服务 connectTimeout: 3000 readTimeout: 3000 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000降级处理
package cn.itsource.springcloud.client; import cn.itsource.springcloud.domain.User; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component //UserClient 表示对那个接口进行托底处理 public class UserClientHystrixFallbackFactory implements FallbackFactory<UserClient> { @Override public UserClient create(Throwable throwable) { return new UserClient() { @Override public User getUser(Long id) { return new User(id,"出异常,请联系管理员!"); } }; } } //调用服务名字 @FeignClient(value = "USER-PROVIDER",fallbackFactory = UserClientHystrixFallbackFactory.class) @RequestMapping("/provider") public interface UserClient { @RequestMapping("/user/{id}") User getUser(@PathVariable("id") Long id); }依赖
<!--springboot支持--> <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> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>yml
server: port: 2010 spring: application: name: zuul-gateway eureka: client: service-url: defaultZone: http://eureka1:1010/eureka,http://eureka2:1011/eureka,http://eureka3:1012/eureka instance: instance-id: zuul:2010 prefer-ip-address: false入口类
@SpringBootApplication @EnableEurekaClient @EnableZuulProxy public class ZuulGatewayApp2010 { public static void main(String[] args) { SpringApplication.run(ZuulGatewayApp2010.class,args); } }路由配置
zuul: routes: myUser.serviceId: user-provider # 服务名 myUser.path: /myUser/** # 把myUser打头的所有请求都转发给user-provider ignored-services: "*" #所有服务都不允许以服务名来访问 prefix: "/services" #加一个统一前缀Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
自定义过滤器
@Component public class LoginFilter extends ZuulFilter{ @Override public String filterType() { // 登录校验,肯定是在前置拦截 return "pre"; } @Override public int filterOrder() { // 顺序设置为1 return 1; } @Override public boolean shouldFilter() { // 返回true,代表过滤器生效。 return true; } @Override public Object run() throws ZuulException { // 登录校验逻辑。 // 1)获取Zuul提供的请求上下文对象 RequestContext ctx = RequestContext.getCurrentContext(); // 2) 从上下文中获取request对象 HttpServletRequest req = ctx.getRequest(); // 3) 从请求中获取token String token = req.getParameter("access-token"); // 4) 判断 if(token == null || "".equals(token.trim())){ // 没有token,登录校验失败,拦截 ctx.setSendZuulResponse(false); // 返回401状态码。也可以考虑重定向到登录页。 ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } // 校验通过,可以考虑把用户信息放入上下文,继续向后执行 return null; } }Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:
zuul: retryable: true ribbon: ConnectTimeout: 250 # 连接超时时间(ms) ReadTimeout: 2000 # 通信超时时间(ms) OkToRetryOnAllOperations: true # 是否对所有操作重试 MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数 MaxAutoRetries: 1 # 同一实例的重试次数 hystrix: command: default: execution: isolation: thread: timeoutInMillisecond: 3000 # 熔断超时时长:3000ms在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
依赖
<dependencies> <!--springboot支持--> <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> </dependency> <!--eureka客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies>yml
server: port: 1299 eureka: client: service-url: defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka # 集群 instance: prefer-ip-address: true spring: application: name: spring-cloud-config-server cloud: config: server: git: uri: gitee的uri username: xxx password: xxx入口类
@SpringBootApplication @EnableEurekaClient //加入注册中心 @EnableConfigServer //启用配置服务端 public class ConfigServerApplication_1299 { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication_1299.class); } }测试 http://localhost:1299/application-user/test http://localhost:1299/application-user/dev
简单的springcloud就差不多入门了,还有更多更深入的等着我们。。。。。。