SpringCloud个人学习笔记(非教程)

本篇博客仅用于对个人学习的记录,并非教程。

首先,我们要实现两个独立项目之间的相互调用,调用方的项目名称为http-demo,被调用方所使用的项目为之前写的小练习,具体功能是根据id从数据库中查询相应的人员信息然后返回。

首先来看调用方的项目,同样为一个spirngboot工程,首先使用maven管理jar包,此处以springboot为父工程(可以避免手动管理版本的麻烦),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.leyou.demo</groupId>
	<artifactId>http-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>http-demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.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>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.1</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

在这里我们使用Spring提供的RestTemplate模板工具类,因为是springboot工程,所以添加启动类。

package com.leyou.httpdemo;

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class HttpDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(HttpDemoApplication.class, args);
	}

	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
}

写一个测试类去调用我们的另一个项目,注意另一个项目配置的端口号为8088。

package com.leyou.httpdemo;

import com.leyou.httpdemo.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {

	@Autowired
	private RestTemplate restTemplate;

	@Test
	public void httpGet() {
		User user = this.restTemplate.getForObject("http://localhost:8088/user/1", User.class);
		System.out.println(user);
	}

}

这样,一次简单的项目之间的调用就完成了。

接下来,新建一个工程并以此为父工程,父工程的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>cn.itcast.demo</groupId>
    <artifactId>cloud-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>userservice</module>
        <module>eureka-server</module>
        <module>consume-demo</module>
        <module>gateway</module>
    </modules>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath></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>Finchley.SR1</spring-cloud.version>
        <mapper.starter.version>2.0.3</mapper.starter.version>
        <mysql.version>5.1.32</mysql.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>

            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${mapper.starter.version}</version>
            </dependency>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    </project>

在工程中添加Module并以此作为Eureka的服务端,引入相关启动器,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">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>

配置yaml文件:多个enrekafu服务端之间可以相互注册形成集群。port为自己的duan端口号,service-url为其他eureka的地址,注意地址最后必须加上/eureka,name填写的为服务id。

server:
  port: 10086
eureka:
  client:
    service-url:
        defaultZone: http://192.168.1.13:10087/eureka
spring:
  application:
    name: eureka-server

添加启动类并写入相关注解,@SpringBootApplication注解为所有启动类必须添加的注解,@EnableEurekaServer注解为eureka服务端应添加的注解。

package cn.itcast;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Created by WIN 10 on 2019/1/22.
 */
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer.class);
    }
}

这样,我们的一个eureka服务端就完成了。

接下来将service和consumer都注册到注册中心。

首先在service工程中添加如下依赖:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

在yaml文件中设置服务id为user-service,并将自己注册到注册中心,最后在启动类上添加@EnableDiscoveryClient注解。

consumer的注册方法和service大致相同,添加依赖,设置服务id为consumer,在启动类上添加注解。需要改动的还有web层,之前我们通过硬编码写入service工程的地址来调用查询服务,现在我们通过从注册中心拉去服务列表来实现调用,代码如下:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("{id}")

    public String queryById(@PathVariable("id") Long id) {
        //根据服务id获取实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        ServiceInstance instance = instances.get(0);
        String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/User/" + id;
        String user = restTemplate.getForObject(url,String.class);
        return user;
    }

这样我们就可以实现通过注册中心来调用服务。

但是在上面的代码中,我们可以发现,我们每次都会从服务集合中取出第一个实例来进行调用,很显然这是不合理的,我们需要进行负载均衡,这里我们使用Ribbon,Ribbon中已经帮我们实现了多种负载均衡算法(随机,轮询等)。首先引入依赖(实际上eureka中已经集成了Ribbon)

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

使用负载均衡有两种方式,在此我们只使用较为简便的一种。在启动类中获取RestTemplate方法上方添加注解@LoadBalanced,web层不再需要DiscoveryClient,方法如下所示:

@GetMapping("{id}")
public String queryById(@PathVariable("id") Long id) {
        String url = "http//服务id/user" + id;
        String user = restTemplate.getForObject(url,String.class);
        return user;
}

原理是在我们使用restTemplate时,因添加了@LoadBalanced注解,所以内置的拦截器会自动拦截,根据服务id拿到服务,jinx进行负载均衡后取到实例,得到端口号和ip地址。进而完成最终的url地址。

 

接下来我们配置熔断器Hystix,改造consumer工程,首先引入Hystix依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

在启动类上添加注解@EnableCircuitBreaker,那么现在的启动类如下所示:

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

然而我们查看@SpringCloudApplication注解后可以发现,其内部已经包含了以上三个注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}

所以我们可以将原先在启动类上添加的三个注解换为@SpringCloudApplication注解使我们的代码更加简洁。接着在yaml文件中配置超时时间为3秒

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

改造comsumer的web层如下:

@GetMapping("{id}")
@HystrixCommand(fallbackMethod ="queryByIdFallBack" )
public String queryById(@PathVariable("id") Long id) {
        String url = "http//服务id/user" + id;
        String user = restTemplate.getForObject(url,String.class);
        return user;
}

public String queryByIdFallBack(Long id) {
        return "服务器出错了!";
}

fallbackMethod中填写的为此方法的回滚方法,queryBuIdFallBack为回滚方法,每个方法特有的回滚方法的返回值和参数必须和原方法的完全一致。当然,也可以为所有方法统一配置回滚方法,需要做出如下改动:

@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("{id}")
    @HystrixCommand()
    public String queryById(@PathVariable("id") Long id) {        
        String url = "http//user-service/user" + id;
        String user = restTemplate.getForObject(url,String.class);
        return user;
    }

    public String defaultFallback() {
        return "服务器出问题啦!";
    }
    /*
    * 通用方法的参数必须为空
    * 非通用方法的返回值和参数必须与原方法相同
    * */
}

可见,首先需要在类中给出通用的回滚方法,通用方法的返回值必须为String类型且必须无参;其次将原先写在方法上@HystrixCommand注解中的fallbackMethod属性去掉;最后在类上添加@DefaultProperties(defaultFallback = "defaultFallback")注解,其中的属性为通用方法的名称。但是,就算我们设置超时时间为3秒,也会极大的影响并发性能,因为正常处理所需时间仅为几十ms。所以,我们可以自由设置,使熔断器在一定条件下开启,比如最近十次调用此服务有百分之六十以上超时,那么我们将暂时关闭此服务,关闭服务期间如果被调用,仅需几十ms就可返回超时报错。当经过一段时间后(时间可自己设置),熔断器将进入半开启状态,这是将放入少量服务请求,如果不再超时,则关闭熔断器,服务恢复。(条件默认为20次,50%,5秒)

在上面的学习中,我们通过使用Ribbon,已经使服务调用得到了极大的简化,但是仍然需要书写大量重复代码(手写url并通过RestTemplate调用服务)。我们可以使用Feign对其进行进一步的简化。首先在consumer工程中引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

然后在启动类上添加注解@EnableFeignClients,因为Feign底层集成了Ribbon,所以我们不再需要自己注册注入RestTemplate,启动类代码如下:

@EnableFeignClients
@SpringCloudApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }
}

编写一个接口并添加相应注解如下:

package cn.itcast.consumer.client;

import cn.itcast.consumer.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("user-service")
public interface UserClient {

    @GetMapping("User/{id}")
    User queryById(@PathVariable("id") Long id);
}

接口上需添加@FeignClient注解,括号中填写要远程调用的服务id。Feign底层会使用动态代理自动帮我们完成其他操作。从注册中心拉去服务,调用方法传递参数返回结果等...原先的web层需jinx进行如下改动,首先删除RestTemplate的注入,添加自己编写的接口的注入。代码如下:

@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
    @Autowired
    private UserClient userClient;

    @GetMapping("{id}")
    @HystrixCommand()
    public String queryById(@PathVariable("id") Long id) {
         return userClient.queryById(id).toString();
    }

    public String defaultFallback() {
        return "服务器出错啦!";
    }

我们也可以通过yaml文件去配置Ribbon的相应参数如下:

user-service:
  ribbon:
    ConnectTimeout: 250 # 连接超时时间(ms)
    ReadTimeout: 1000 # 通信超时时间(ms)
    OkToRetryOnAllOperations: true # 是否对所有操作重试
    MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数
    MaxAutoRetries: 1 # 同一实例的重试次数

 

最后,配置Zuul网关。添加新的Moudle,引入依赖如下:

<?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">
    <parent>
        <artifactId>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>
</project>

在yaml中进行相应配置:

server:
  port: 10010

eureka:
  client:
    service-url:
      defaultZone: http://192.168.1.13:10086/eureka

#不进行以下配置也可以,因为zuul会从注册中心拉去服务列表,默认的映射路径为服务/id/**
zuul:
  routes:
    # 简化配法,key是服务的服务id,value是映射路径
    user-service: /user-service/**
    #serviceId: user-service
    #hehe(随意起名字):
      #path: /user-service/**
      #url: http://192.168.1.13:8081
    #ignored-services属性用于告知zuul哪些服务不被拉取,形式为集合,格式如下
    #ignored-services:
      #-consumer-service
      #-consumer-service1
spring:
  application:
    name: gateway

配置启动类:

package cn.itcast;

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

@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class);
    }
}

配置简易版的拦截器:

package cn.itcast.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class LoginFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //获取请求上下文
        RequestContext requestContext = RequestContext.getCurrentContext();
        //获取request对象
        HttpServletRequest request = requestContext.getRequest();
        String token = request.getParameter("access-token");
        if(StringUtils.isBlank(token)) {
            //true为放行,false为拦截
            requestContext.setSendZuulResponse(false);
            //返回403
            requestContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
        }
        //不拦截就返回null,表示放行
        return null;
    }
}

StringUtils类中的isBlank是高效判空方法,如要使用需导入相关依赖。filterType为拦截器类型,filterOrder为拦截顺序,shouldFilter为是否启用拦截器,返回true为启用。run方法中填写具体逻辑。通过RequestContext获取request对象。

因为Zuul集成了Ribbon和Hystix,所以我们可以在yaml文件中直接进行相关配置。

server:
  port: 10010

eureka:
  client:
    service-url:
      defaultZone: http://192.168.1.13:10086/eureka

#不进行以下配置也可以,因为zuul会从注册中心拉去服务列表,默认的映射路径为服务/id/**
zuul:
  routes:
    # 简化配法,key是服务的服务id,value是映射路径
    user-service: /user-service/**
    #serviceId: user-service
    #hehe(随意起名字):
      #path: /user-service/**
      #url: http://192.168.1.13:8081
    #ignored-services属性用于告知zuul哪些服务不被拉取,形式为集合,格式如下
    #ignored-services:
      #-consumer-service
      #-consumer-service1
spring:
  application:
    name: gateway
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
ribbon:
  ConnectionTimeOut: 500
  ReadTimeOut: 4000
  MaxAutoRetriesNextServer: 0 #不重试

完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值