• Spring Cloud微服务系列文,Hystrix与Eureka的整合


        和Ribbon等组件一样,在项目中,Hystrix一般不会单独出现,而是会和Eureka等组件配套出现。在Hystrix和Eureka整合后的框架里,一般会用到Hystrix的断路器以及合并请求等特性,而在Web框架里,大多会有专门的缓存组件,所以不怎么会用到Hystrix的缓存特性。

     1 准备Eureka服务器项目

        HystrixEurekaServer项目承担着Eureka服务器的作用,这部分的代码关键点如下。

        第一,在pom.xml里,通过如下关键代码引入Eureka服务器组件的依赖包。 

    1    <dependency>
    2        <groupId>org.springframework.cloud</groupId>
    3        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    4    </dependency>

        第二,在application.yml里,指定了本项目的主机名和端口号,并指定了对外提供eureka服务的Url路径,代码如下。    

    1    server:
    2      port: 8888
    3    eureka:
    4      instance:
    5        hostname: localhost
    6      client:
    7        register-with-eureka: false
    8        fetch-registry: false 
    9        serviceUrl:
    10          defaultZone: http://localhost:8888/eureka/  

        第三,在ServerStarter.java里,编写启动Eureka服务的代码,这里请注意,在第2和第3行里,通过注解声明了本类是Eureka服务器的启动类。    

    1    //省略必要的package和import的代码
    2    @EnableEurekaServer
    3    @SpringBootApplication
    4    public class ServerStarter 
    5    {
    6          public static void main( String[] args ){  
    7              SpringApplication.run(ServerStarter.class, args);  
    8        }
    9    }

        注:本部分给出的是Eureka服务器省略版的代码,完整代码大家可以参见另外一篇博文,架构师入门:搭建基本的Eureka架构(从项目里抽取)。    

    2 准备Eureka服务器项目

        在pom.xml里,我们除了指定Eureka的依赖包以外,还了指定了Hystrix的依赖包,关键代码如下。其中,前4行指定的是Eureka的依赖包,后4行指定的是Hystrix的依赖包。    

    1    <dependency>
    2            <groupId>org.springframework.cloud</groupId>
    3            <artifactId>spring-cloud-starter-eureka</artifactId>
    4        </dependency>
    5    <dependency>
    6            <groupId>org.springframework.cloud</groupId>
    7            <artifactId>spring-cloud-starter-hystrix</artifactId>
    8        </dependency>

        而在application.yml里,指定了本项目的服务端口是1111,对外提供的项目名是hystrixEureka,以及是向第一部分指定的Eureka服务器注册,代码如下。   

    1    server:
    2      port: 1111
    3    spring:
    4      application:
    5        name: hystrixEureka
    6    eureka:  
    7      client:
    8        serviceUrl:
    9          defaultZone: http://localhost:8888/eureka/

        

    3 在服务提供者项目里引入断路器机制

         在服务提供者的启动类ServiceProviderApp.java里,我们是通过加入@EnableCircuitBreaker注解来启动断路器,代码如下。     

    1    //省略必要的package和import代码
    2    @SpringBootApplication
    3    @EnableEurekaClient
    4    @EnableCircuitBreaker
    5    @ServletComponentScan
    6    public class ServiceProviderApp 
    7    {
    8        public static void main( String[] args ){ 
    9             SpringApplication.run(ServiceProviderApp.class, args);  
    10       }
    11    }

        在Controller.java这个控制器类里,我们是在第9行里,通过调用service类提供的方法来返回具体的OrderDetail信息,代码如下。    

    1    //省略必要的package和import代码
    2    @RestController
    3    public class Controller {
    4            @Autowired
    5        private OrderDetailService service;   
    6        //对外提供服务的getOrderDetailById方法 
    7            @RequestMapping(value = "/getOrderDetailById/{orderId}", method = RequestMethod.GET)
    8            public OrderDetail getOrderDetailById(@PathVariable("orderId")   String orderId) throws Exception {
    9            return service.getOrderDetailByID(orderId);
    10        }
    11    }

        在OrderDetailService.java里,我们用HashMap这个数据结构来模拟数据库,以此来模拟从数据库读OrderDetail的方式,提供了“根据ID找相应对象的服务”,代码如下。    

    1    //省略必要的package和import代码
    2    @Service
    3    public class OrderDetailService {
    4        static HashMap<String,String> orderDB = new HashMap<String,String> ();
    5        static //通过static代码,模拟数据库中存储的OrderDetail信息
    6        {
    7            orderDB.put("1","Peter");
    8            orderDB.put("2","Tom");
    9            orderDB.put("3","Mike");
    10        }
    11    //在方法之前,通过注解引入Hystrix,并指定回退方法
    12        @HystrixCommand(fallbackMethod = "getFallback")
    13        public OrderDetail getOrderDetailByID(String id) throws Exception
    14        {
    15            OrderDetail orderDetail = new OrderDetail();        
    16            if("error".equals(id) ) //如果输入是error,则故意抛出异常
    17            {throw new Exception(); }
    18            //模拟地从数据库里得到信息并返回
    19            orderDetail.setOrderId(id);
    20            orderDetail.setOrderOwner(orderDB.get(id));
    21            return orderDetail;
    22        }
    23        //定义Hystrix的回退方法
    24        public OrderDetail getFallback(String orderId) {
    25            OrderDetail orderDetail = new OrderDetail();
    26            orderDetail.setOrderId("error");
    27            orderDetail.setOrderOwner("error");        
    28            System.out.println("In fallbackForOrderDetail function");    
    29            return orderDetail;
    30        }
    31    }

         在第13行的getOrderDetailByID方法之前,我们在第12行通过fallbackMethod定义了回退方法,在这个方法的第16行里,我们定义了如果输入是error,那么则将抛出异常,以此触发回退方法getFallback。而在第24行定义的回退方法里,我们将返回一个ID和Owner都是error的OrderDetail对象。本类用到的OrderDetail模型类定义如下。    

    1    public class OrderDetail{
    2           private String orderId;//订单id
    3         private String orderOwner; //订单所有人 
    4        //省略必要的get和set方法
    5    }

        至此我们完成了开发工作,启动HystrixEurekaServer和HystrixEurekaserviceProvider后,如果在浏览器中输入http://localhost:1111/getOrderDetailById/1,能看到如下的输出,这说明走的是正常的流程。

    {"orderId":"1","orderOwner":"Peter"}

        但如果输入的是http://localhost:1111/getOrderDetailById/error,那么会在OrderDetailService类的getOrderDetailByID方法里抛出异常,从而走Hystrix的回退流程,由此会输入如下的语句。

    {"orderId":"error","orderOwner":"error"}

        在这个案例中,我们是在“提供者服务”的模块引入hytrix断路器,而不是在“服务调用”模块,这和项目中的常规做法相符,因为启动断路器的场景一般是“提供服务模块的流量超载”。    

    4 在服务调用者项目里引入合并请求机制

        这里我们将在HystrixEurekaserviceCaller项目里,调用HystrixEurekaserviceProvider里定义的服务,在调用时,我们将合并5秒内发送的请求。

        步骤一,在控制器类Controller.java里,初始化Hystrix请求上下文,并通过Future对象多次发送请求,代码如下。   

    1    //省略必要的package和import代码
    2    @Configuration
    3    @RestController
    4    public class Controller {         
    5          @Autowired
    6         private OrderDetailService service;//提供服务的service类
    7    //在这个方法里,将演示合并请求的效果
    8        @RequestMapping(value = "/mergeDemo", method = RequestMethod.GET)
    9        public List<OrderDetail> hystrixMergeDemo() throws Exception {
    10        //初始化Hystrix请求上下文         
    11            HystrixRequestContext context = HystrixRequestContext
    12                    .initializeContext();
    13            //通过定义三个Future对象,调用三次请求
    14            Future<OrderDetail> f1 = service.getOneOrderDetail("1");
    15            Future<OrderDetail> f2 = service.getOneOrderDetail("2");
    16            Future<OrderDetail> f3 = service.getOneOrderDetail("3");
    17            OrderDetail o1 = f1.get();
    18            OrderDetail o2 = f2.get();
    19            OrderDetail o3 = f3.get();
    20            //把三个返回对象组装到一个List中并返回
    21            List<OrderDetail> orderDetailList = new ArrayList<OrderDetail>();
    22            orderDetailList.add(o1);
    23            orderDetailList.add(o2);
    24            orderDetailList.add(o3);
    25            //释放上下文
    26            context.shutdown();        
    27            return orderDetailList;
    28         }
    29    }

         在上文的hystrixMergeDemo方法里,我们首先在第11行初始化Hystrix请求上下文,随后在第14到16行调用了三次getOneOrderDetail方法,并在第17到19行里,通过Furure类型对象的get方法,把三次调用的结果分别赋予三个OrderDetail类型的对象。

        之后,通过第21到24行的代码,把三个OrderDetail对象组装成一个List<OrderDetail>类型的orderDetailList对象,并在第27行返回了orderDetailList对象。

        这里虽然我们是发出了三次调用请求,但从后文的讲解里,我们能看到这三次请求其实是被合并处理的。由于在合并请求时,Hystrix处理类会把请求暂存在Hystrix请求上下文里,所以这里我们一定得通过类似于第11行的代码初始化上下文,否则将无法得到合并请求的结果。

        步骤二,之前我们看到,在Controller类里,是调用OrderDetailService类的方法来查询多个订单,所以合并请求的代码是写在这个类里的,我们来看下代码。    

    1    //省略必要的package和import代码
    2    @Component
    3    public class OrderDetailService {
    4        // 合并处理收集5秒内的请求
    5        @HystrixCollapser(batchMethod = "getMoreOrderDetails",collapserProperties = {@HystrixProperty(name ="timerDelayInMilliseconds",value = "5000")})
    6        public Future<OrderDetail> getOneOrderDetail(String id) {
    7            System.out.println("in getOneOrderDetail");
    8            return null;
    9        }
    10        @Bean
    11        @LoadBalanced
    12        public RestTemplate getRestTemplate()
    13        { return new RestTemplate();      }
    14    //这里是合并请求的代码    
    15        @HystrixCommand
    16        public List<OrderDetail> getMoreOrderDetails(List<String> orderIds) {
    17            System.out.println("in getMoreOrderDetails," + orderIds.size());
    18            List<OrderDetail> list = new ArrayList<OrderDetail>();
    19            RestTemplate template = getRestTemplate();
    20            //通过for循环,调用服务提供者的方法得到OrderDetail对象
    21            for (String orderId : orderIds) {
    22                OrderDetail orderDetail = new OrderDetail();  
    23                orderDetail = template.getForObject("http://localhost:1111/getOrderDetailById/{orderId}", OrderDetail.class,orderId);
    24                list.add(orderDetail);
    25            }
    26            return list;
    27        }
    28    }

        在第6行里,我们定义了只查询一个对象的getOneOrderDetail方法,在定义该方法的注解里,我们指定了会把在5秒内调用该方法的请求合并到getMoreOrderDetails方法里。

        在第16行的getMoreOrderDetails方法里,我们是通过第21行到第25行的for循环,依次遍历待查询的orderId,并通过第23行的getForObject方法,调用服务提供者的getOrderDetailById方法,得到对应的OrderDetail对象,并添加到List<OrderDetail>类型的list对象中。最后,是通过第26行的代码,返回包含多次请求结果的list对象。

        当我们启动Eureka服务器(HystrixEurekaServer)、服务提供者(HystrixEurekaserviceProvider)和服务调用者(HystrixEurekaserviceCaller)三个项目后,可以在浏览器里输入如下的请求,以此来查看合并请求的效果。

    http://localhost:8080/mergeDemo

        上述请求的输出如下,我们能看到3个OrderDetail对象。从上述的讲解能看出,这3个对象其实是通过一次合并后的请求得到的。

    [{"orderId":"1","orderOwner":"Peter"},{"orderId":"2","orderOwner":"Tom"},{"orderId":"3","orderOwner":"Mike"}]

        本文谢绝转载。其它和Spring Cloud相关的博文如下:

        

  • 相关阅读:
    Codeforces Round #326 (Div. 2)
    UVAlive 6611 Alice's Print Service 二分
    codeforces868D Huge Strings
    [HNOI2016]大数
    [NOI 2015]软件包管理器
    幻方
    poj3728 商务旅行
    [SCOI2016]背单词
    [USACO12FEB]牛的IDCow IDs
    [Cqoi2010]扑克牌
  • 原文地址:https://www.cnblogs.com/JavaArchitect/p/10129378.html
Copyright © 2020-2023  润新知