1.介绍Zuul

Zuul也是Netflix下的一个开源项目,主要为微服务集群提供过代理、过滤、路由等功能。前面的几节学习我们已经搭建起了spring cloud的集群服务,现在我们将使用Zuul来为我们的集群添加一层网关,这样苦户端将不必再纠结于整个集群的内部结构是怎样的,只需关心网关提供的访问借口就行,对于Spring Cloud集群来说,不必过多的暴露服务,提升了集群的安全性。

2.Spring Cloud集群的结构图

200120_8VIh_3665821.png

加入zuul后的结构:
200130_hLZc_3665821.png

相信看了这两张图,大家应该对整个集群的架构很清晰了。

3.添加新的服务在provideService

一直使用hello world,现在我们需要加点新的服务了,不能一直写hello,开心不,激动不。为了不再重新建一个项目,我们就只接在provideService里直接写了。增加一个用户服务UserApi,一个订单服务OrderApi。实际开发中这应该是两个服务,会分成两个项目,部署多个实列到多台服务器上,应为时本地玩单机就先这样写了。
添加User:

package com.demo;

public class User {
    private int id;
    private String name;
    private String address;
    private String phone;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

}

添加Order:

package com.demo;

public class Order {
    private int num;
    private String good;
    private User user;
    private double money;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public String getGood() {
        return good;
    }

    public void setGood(String good) {
        this.good = good;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

}

添加用户服务:

package com.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserApi {
    
    @GetMapping("/api/user/{id}")
    public User getUser(@PathVariable(name="id")int id){
        User u1 = new User();
        u1.setId(1);
        u1.setAddress("北京");
        u1.setName("张三");
        u1.setPhone("123456");
        
        User u2 = new User();
        u2.setId(2);
        u2.setAddress("河南");
        u2.setName("李四");
        u2.setPhone("456789");
        System.out.println("请求用户服务");
        if(1==id){
            return u1;
        }else if(2==id){
            return u2;
        }else{
            return new User();
        }
    }
}

添加订单服务:

package com.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderApi {
    
    @GetMapping("/api/order/{num}")
    public Order getOrder(@PathVariable(name="num")int num){
        User u1 = new User();
        u1.setId(1);
        u1.setAddress("北京");
        u1.setName("张三");
        u1.setPhone("123456");
        
        User u2 = new User();
        u2.setId(2);
        u2.setAddress("河南");
        u2.setName("李四");
        u2.setPhone("456789");
        Order o1 = new Order();
        o1.setNum(1);
        o1.setMoney(200);
        o1.setGood("spring cloud");
        o1.setUser(u1);
        Order o2 = new Order();
        o2.setNum(2);
        o2.setMoney(89);
        o2.setGood("vue");
        o2.setUser(u2);
        System.out.println("请求订单服务");
        if(1==num){
            return o1;
        }else if(2==num){
            return o2;
        }else{
            return new Order();
        }
    }
}

4.在服务消费者李调用新加的服务

package com.demo;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import com.demo.CallClient.MyFallBack;
@Component
@FeignClient(name="provider-server",fallback = MyFallBack.class)  //申明调用服务
public interface CallClient {
    @CacheResult
    @HystrixCommand
    @GetMapping("/api/hello")
    String hello();
    @CacheResult
    @HystrixCommand
    @GetMapping("/api/user/{id}")
    String user(@PathVariable(name="id") int id);
    @CacheResult
    @HystrixCommand
    @GetMapping("/api/order/{num}")
    String order(@PathVariable(name="num")int num);
    @Component
    static class MyFallBack implements CallClient{

        public String hello() {
            System.out.println("调用hello api出现错误,执行回退方法");
            return "error";
        }

        @Override
        public String user(int id) {
            System.out.println("调用user api出现错误,执行回退方法");
            return "error";
        }

        @Override
        public String order(int num) {
            System.out.println("调用order api出现错误,执行回退方法");
            return "error";
        }
        
    }
}
package com.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.netflix.hystrix.HystrixCircuitBreaker;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;

@RestController
@Configuration

public class CallService {
    @Autowired
    private CallClient ccl;
    
    
    @GetMapping("/call")
    public String call(){
        String s = ccl.hello();
         // 获取断路器
        HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory
                .getInstance(HystrixCommandKey.Factory
                        .asKey("CallClient#hello()"));        
        System.out.println("断路器状态:" + breaker.isOpen());
        return s;
    }
    
    @GetMapping("/user/{id}")
    public String user(@PathVariable(name="id")int id){
        String s = ccl.user(id);
         // 获取断路器
        /*HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory
                .getInstance(HystrixCommandKey.Factory
                        .asKey("CallClient#user()"));        
        System.out.println("断路器状态:" + breaker.isOpen());*/
        return s;
    }
    
    @GetMapping("/order/{num}")
    public String order(@PathVariable(name="num")int num){
        String s = ccl.order(num);
         // 获取断路器
       /* HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory
                .getInstance(HystrixCommandKey.Factory
                        .asKey("CallClient#order()"));
        System.out.println("断路器状态:" + breaker.isOpen());*/
        return s;
    }
}

不知道为什么新加的两个服务,无法获取断路器的状态,根据key获取的结果是null,但是根据command的默认规则就是类名#方法名,这个不晓得什么原因。修改invokerService的application.yml.command使用默认配置。

spring:
  application:
    name: invoker-server1
server:
  port: 8093
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:
      execution:
        isolation:
          thread: 
            timeoutInMilliseconds: 500
      circuitBreaker:
        requestVolumeThreshold: 3

5. 增加一个Zuul的路由工程

spring:
  application:
    name: zuul-gateway
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
zuul: 
  routes:
    service:
      path: /service/**
      serviceId: invoker-server1

里面我们配置了service/**的请求路径到我们的服务消费者,这里可以分开配置你的多个服务,这个service可以根据你的实际改成其他名字,比如本项目应该是user和order但是我把他们写道一个服务里去了 ,所以就用了service.

package com.demo;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@SpringBootApplication
public class App {
  
    public static void main(String[] args) {
         new SpringApplicationBuilder(App.class).run(args);
    }
}
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

这样访问:
http://localhost:8080/service/call,
http://localhost:8080/service/order/2,
http://localhost:8080/service/user/2。
就可以访问刚才我们新加的用户,订单服务。不过缓存好像实效了,这个需要研究下,毕竟也是学习别人的。


Mike晓
95 声望18 粉丝