使⽤Spring Cloud Netflix 中的 Eureka 搭建服务注册与发现中⼼
1、spring web2、eureka server
## 设置服务注册与发现中⼼的端⼝ server: port: 8761 ## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的 ## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名 spring: application: null name: service-eureka eureka: client: ## ip 就是服务注册中⼼服务器的ip,port 就是服务注册与发现中⼼设置的port service-url: defaultZone: 'http://192.168.54.59:8761/eureka' ## 设置服务注册与发现中⼼是否为为集群搭建(如果为集群模式,多个eureka节点之间需要相互注册) register-with-eureka: false ## 设置服务注册与发现中是否作为服务进⾏注册 fetch-registry: false
@SpringBootApplication @EnableEurekaServer public class ServiceEurekaApplication { public static void main(String[] args) { SpringApplication.run(ServiceEurekaApplication.class, args); } }
创建保存订单的服务(order-add)注册到服务注册与发现中⼼
创建spring boot应⽤,完成功能开发
将能够完成特定业务的SpringBoot应⽤作为服务提供者,注册到服务注册与发现中⼼
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
server: port: 9001 ## 当前应⽤名会作为服务唯⼀标识注册到eureka spring: application: name: order-add datasource: driver-class-name: com.mysql.jdbc.Driver url: 'jdbc:mysql://localhost:3306/db_2010_sc?characterEncoding=utf-8' username: root password: admin123 mybatis: mapper-locations: 'classpath:mappers/*' type-aliases-package: com.qfedu.order.beans ## 配置Eureka服务注册与发现中⼼的地址 eureka: client: service-url: defaultZone: 'http://localhost:8761/eureka'
@SpringBootApplication @MapperScan("com.qfedu.order.dao") @EnableEurekaClient public class OrderAddApplication { public static void main(String[] args) { SpringApplication.run(OrderAddApplication.class, args); } }
服务消费者(api-order-add)通过eureka查找服务提供者(order-add),通过服务调⽤组件调⽤提供者
- eureka server
- ribbon
Ribbon客户端已经停更进维啦
- eureka server
- ribbon
server: port: 8001 spring: application: name: api-order-add eureka: client: service-url: defaultZone: 'http://localhost:8761/eureka'
@SpringBootApplication @EnableDiscoveryClient public class ApiOrderAddApplication { public static void main(String[] args) { SpringApplication.run(ApiOrderAddApplication.class, args); } }
@Configuration public class AppConfig { @LoadBalanced //启⽤Ribbon(负载均衡) @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
@Service public class OrderAddServiceImpl implements OrderAddService { @Autowired private RestTemplate restTemplate; @Override public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 ResultVO vo = restTemplate.postForObject("http://order-add/order/add", order, ResultVO.class); //2. 调⽤ orderitem-add 保存订单快照 //3. 调⽤ stock-update 修改商品库存 //4. 调⽤ shopcart-del 删除购物⻋记录 return null; } }
@LoadBalanced注解是Ribbon的⼊⼝,在RestTemplate对象上添加此注解之后,再使⽤RestTemplate发送REST请求的时候,就可以通过Ribbon根据服务名称从Eureka中查找服务对应的访问地址列表,再根据负载均衡策略(默认轮询)选择其中的⼀个,然后完成服务的调⽤
- 获取服务列表
- 根据负载均衡策略选择服务
- 完成服务调⽤
SpringCloud的服务调⽤是基于REST的,因此当服务提供者规定了请求的⽅式,服务消费者必须发送对应⽅式的请求才能完成服务的调⽤,RestTemplate提供了多个⽅法⽤于发送不同形式的请求。
//post⽅式请求 restTemplate.postForObject(); //get⽅式请求 restTemplate.getForObject(); //delete⽅式请求 restTemplate.delete(); //put⽅式请求 restTemplate.put();
//参数1:访问服务的url //参数2:传递的对象参数 //参数3:指定服务提供者返回的数据类型 ResultVO vo = restTemplate.postForObject("http://order-add/order/add",order, ResultVO.class);
2、服务提供者接收参数
@PostMapping("/add") public ResultVO addOrder(@RequestBody Order order){ return orderService.saveOrder(order); }
1、服务消费者请求传参
String userId = order.getUserId();
ResultVO vo = restTemplate.getForObject("http://order-add/order/add?userId="+userId, ResultVO.class);
2、服务提供者接收参数
@GetMapping("/add") public ResultVO addOrder(Order order){ return orderService.saveOrder(order); } @GetMapping("/add") public ResultVO addOrder(String userId){ //return orderService.saveOrder(order); }
spring webeureka serverOpenFeign
server: port: 8002 spring: application: name: api-order-add-feign eureka: client: service-url: defaultZone: 'http://localhost:8761/eureka'
@SpringBootApplication @EnableDiscoveryClient //声明为服务消费者 @EnableFeignClients //声明启⽤feign客户端 public class ApiOrderAddFeignApplication { public static void main(String[] args) { SpringApplication.run(ApiOrderAddFeignApplication.class, args); } }
使⽤Feign进⾏服务调⽤的时候,需要⼿动创建⼀个服务访问客户端(接⼝)
@FeignClient("order-add") public interface OrderAddClient { @PostMapping("order/add") public ResultVO addOrder(Order order); }
@Service public class OrderAddServiceImpl implements OrderAddService { @Autowired private OrderAddClient orderAddClient; @Override public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 ResultVO vo = orderAddClient.addOrder(order); //2. 调⽤ orderitem-add 保存订单快照 //3. 调⽤ stock-update 修改商品库存 //4. 调⽤ shopcart-del 删除购物⻋记录 return vo; } }
1、通过请求体传递对象
服务提供者@PostMapping("/add") public ResultVO addOrder(@RequestBody Order order){ System.out.println("-------------------order-add"); System.out.println(order); return orderService.saveOrder(order); }服务消费者(Feign客户端)@FeignClient("order-add") public interface OrderAddClient { @PostMapping("order/add") public ResultVO addOrder(Order order); }
2、通过请求⾏传参
服务提供者@PostMapping("/add") public ResultVO addOrder(@RequestBody Order order,String str){ System.out.println("-------------------order-add"); System.out.println(order); System.out.println(str); return orderService.saveOrder(order); }服务消费者(Feign客户端)//1.对⽤POST请求调⽤服务,Feign客户端的⽅法参数默认为body传值(body只能有⼀个值) //2.如果有多个参数,则需要通过@RequestParam声明参数为请求⾏传值 @PostMapping("order/add") public ResultVO addOrder(Order order,@RequestParam("str") String str);
3、Get请求
Get请求调⽤服务,只能通过url传参在Feign客户端的⽅法中,如果不指定参数的传值⽅式,则默认为body传参,Get请求也不例外;因此对于get请求传递参数,必须通过@RequestParam注解声明
服务提供者@GetMapping("/get") public Order addOrder(String orderId){ return new Order(); }服务消费者(Feign客户端)@GetMapping("order/get") public Order getOrder(@RequestParam("orderId") String orderId);
在微服务架构系统中,服务消费者是通过服务注册与发现中⼼发现服务、调⽤服务的,服务注册与发现中⼼服务器⼀旦挂掉,将会导致整个微服务架构系统的崩溃,如何保证Eureka的可靠性呢?
- 使⽤eureka集群
相互注册、相互发现
## 设置服务注册与发现中⼼的端⼝ server: port: 8761 ## 在微服务架构中,服务注册中⼼是通过服务应⽤的名称来区分每个服务的 ## 我们在创建每个服务之后,指定当前服务的 应⽤名/项⽬名 spring: application: name: service-eureka eureka: client: ## 设置服务注册与发现中⼼是否为集群搭建 register-with-eureka: true ## 设置服务注册与发现中是否作为服务进⾏注册 fetch-registry: true ## ip 就是服务注册中⼼服务器的ip ## port 就是服务注册与发现中⼼设置的port service-url: defaultZone: 'http://192.168.54.10:8761/eureka'
当完成Eureka的搭建之后,只要知道ip和port就可以随意的注册服务、调⽤服务,这是不安全的,我们可以通过设置帐号和密码来限制服务的注册及发现。
- 在eureka中整合Spring Security安全框架实现帐号和密码验证
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
spring:
security:
user:
name: zhangsan
password: 123456
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); // 设置当前服务器的所有请求都要使⽤spring security的认证 http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); } }
eureka: client: service-url: defaultZone: 'http://zhangsan:123456@localhost:8761/eureka'
服务故障的雪崩效应:当A服务调⽤B服务时,由于B服务的故障导致A服务处于阻塞状态,当量的请求可能会导致A服务因资源耗尽⽽出现故障。
1、服务降级 :⽤户请求A服务,A服务调⽤B服务,当B服务出现故障或者在特定的时间段内不能给A服务响应,为了避免A服务因等待B服务⽽产⽣阻塞,A服务就不等B服务的结果了,直接给⽤户⼀个降级响应2、服务熔断 :⽤户请求A服务,A服务调⽤B服务,当B服务出现故障的频率过⾼达到特定阈值(5s 20次)时,当⽤户再请求A服务时,A服务将不再调⽤B服务,直接给⽤户⼀个降级响应
1、添加熔断器依赖 hystrix
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>2、在启动类添加 @EnableHystrix 注解
@SpringBootApplication @EnableDiscoveryClient @EnableHystrix public class ApiOrderAddApplication { public static void main(String[] args) { SpringApplication.run(ApiOrderAddApplication.class, args); } }3、在调⽤服务提供者的业务处理⽅法中,进⾏降级配置
@Service public class OrderAddServiceImpl implements OrderAddService { @Autowired private RestTemplate restTemplate; @HystrixCommand(fallbackMethod ="fallbackSaveOrder",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000") } ) public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 //参数1:访问服务的url //参数2:传递的对象参数 //参数3:指定服务提供者返回的数据类型 ResultVO vo = restTemplate.postForObject("http://order-add/order/add",order, ResultVO.class); System.out.println(vo); return vo; } /** * 降级⽅法:与业务⽅法拥有相同的参数和返回值 * @return */ public ResultVO fallbackSaveOrder(Order order){ return ResultVO.fail("⽹络异常,请重试!",null); } }
1、配置步骤⼀致2、服务提供者接⼝降级
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @HystrixCommand(fallbackMethod ="fallbackAddOrder",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000") } ) @PostMapping("/add") public ResultVO addOrder(@RequestBody Order order){ System.out.println("-------------------order-add"); System.out.println(order); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return orderService.saveOrder(order); } public ResultVO fallbackAddOrder(@RequestBody Order order){ System.out.println("-------------------order-add--fallback"); return ResultVO.fail("订单保存失败!",null); } }
1、服务熔断配置
熔断器状态:闭合、打开、半开服务熔断配置@HystrixCommand(fallbackMethod ="fallbackSaveOrder",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000"), @HystrixProperty(name="circuitBreaker.enabled",value="true"),//启⽤服务熔断 @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="10000"),//时间 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value="10"),//请求次数 @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50"),//服务错误率 } ) public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 ResultVO vo = restTemplate.postForObject("http://orderadd/order/add", order, ResultVO.class); System.out.println(vo); return vo; } /** * 降级⽅法:与业务⽅法拥有相同的参数和返回值 * @return */ public ResultVO fallbackSaveOrder(Order order){ return ResultVO.fail("⽹络异常,请重试!",null); }服务熔断:当⽤户请求服务A,服务A调⽤服务B时,如果服务B的故障率达到特定的阈值时,熔断器就会被打开⼀个时间周期(默认5s,可⾃定义),在这个时间周期内如果⽤户请求服务A,服务A将不再调⽤服务B,⽽是直接响应降级服务。
Feign是基于Ribbon和Hystrix的封装
1、添加依赖(SpringBoot 2.3.11 、Spring Cloud H)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.11.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR11</spring-cloud.version> </properties><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>2、在application.yml启⽤熔断器机制
feign: hystrix: enabled: true3、在启动类添加 @EnableHystrix
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @EnableHystrix public class ApiOrderAddFeignApplication { public static void main(String[] args) { SpringApplication.run(ApiOrderAddFeignApplication.class, args); } }4、创建服务降级处理类
5、FeignClient的服务降级类:
5.1.必须实现Feign客户端接⼝
5.2.必须交给Spring容器管理
@Component public class OrderAddClientFallback implements OrderAddClient { public ResultVO addOrder(Order order, String str) { System.out.println("-------addOrder的降级服务"); return ResultVO.fail("fail",null); } public Order getOrder(String orderId) { System.out.println("-------getOrder的降级服务"); return new Order(); } }6、在Feign客户端指定降级处理类
@FeignClient(value = "order-add", fallback =OrderAddClientFallback.class) public interface OrderAddClient { //1.对⽤POST请求调⽤服务,Feign客户端的⽅法参数默认为body传值(body只能有⼀个值) //2.如果有多个参数,则需要通过@RequestParam声明参数为请求⾏传值 @PostMapping("order/add") public ResultVO addOrder(Order order,@RequestParam("str") String str);
@GetMapping("order/get") public Order getOrder(@RequestParam("orderId") String orderId); }7、Service类通过Feign客户端调⽤服务
@Service public class OrderAddServiceImpl implements OrderAddService { @Autowired private OrderAddClient orderAddClient; //当我们创建Feign客户端的降级类并交给Spring管理后 在Spring容器中就会出现两个OrderAddClient对象 @Override public ResultVO saveOrder(Order order) { //1. 调⽤ order-add服务进⾏保存 ResultVO vo = orderAddClient.addOrder(order,"测试字符串"); Order order1 = orderAddClient.getOrder("订单编号"); System.out.println(order1); return vo; } }
ribbon: ## Ribbon建⽴连接最⼤等待时间 ConnectTimeout: 1000 ## 在当前服务提供者尝试连接次数 MaxAutoRetries: 2 ## 与服务提供者通信时间 ReadTimeout: 5000 ## 设置熔断器服务降级时间 (默认 1000) hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 8000
查看各个服务的熔断器状态面板:
- 熔断器仪表盘
1、创建SpringBoot项⽬,添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrixdashboard</artifactId> </dependency>2、配置仪表盘的port和appName
server: port: 9999 spring: application: name: hystrix-dashboard hystrix: dashboard: proxy-stream-allow-list: localhost3、启动类添加 @EanbleHystrixDashboard 注解
@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardApplication { public static void main(String[] args) { SpringApplication.run(HystrixDashboardApplication.class,args); } }4、访问 http://localhost:9999/hystrix
1、添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>2、配置(给每个需要监控熔断器的项目配置)
@Configuration public class DashBoardConfig { @Bean public ServletRegistrationBean getServletRegistrationBean(){ HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setName("HystrixMetricsStreamServlet"); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); return registrationBean; } }3、查看指定服务的熔断器⼯作参数