深入浅出boot2.0 第17章 cloud 和 完结篇

  • 分布式开发
  • 服务发现
  • 配置中心
  • 消息总线
  • 负载均衡
    • 声明式 服务调用
  • 断路器
  • 数据监控
  • 分布式 事务

服务治理 和 服务发现 ——Eureka

注册中心

xml

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		
	<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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

配置

@SpringBootApplication
@EnableEurekaServer
public class Chapter17ServerApplication {}
# Spring项目名称
spring.application.name=server

# 服务器端口
server.port=7001

# Eureka注册服务器名称
eureka.instance.hostname=localhost

# 是否注册给服务中心:默认会自动查找 服务治理中心
eureka.client.register-with-eureka=false

# 是否检索服务:注册中心是 维护服务实例的,不需要这个功能
eureka.client.fetch-registry=false

# 治理客户端服务域:服务中心的域,提供给别的微服务注册
eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/

服务发现

xml

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

配置

//@EnableDiscoveryClient 新版本已经不需要这个注解了
#服务器端口
server.port=9001

#Spring服务名称
spring.application.name=product

#治理客户端服务域
eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/

management.endpoints.web.exposure.include=health,info,hystrix.stream
紧急!eureka可能是错误地声称实例已经启动,而实际上它们还没有启动。续费低于阈值,因此为了安全起见,实例不会过期。
15min中 低于85%

配置多个服务治理中心节点

## 微服务名称依旧保持不变
spring.application.name=server
#server.port=7002
eureka.instance.hostname=localhost
## 将7002端口服务治理中心,注册给7001端口服务治理中心
eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/

## 微服务名称依旧保持不变
spring.application.name=server
#server.port=7001
eureka.instance.hostname=localhost
## 将7002端口服务治理中心,注册给7001端口服务治理中心
eureka.client.serviceUrl.defaultZone=http://localhost:7002/eureka/
#治理客户端服务域
eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/,http://localhost:7002/eureka/

微服务之间的调用

Ribbon

  • 一个 RestTemplate
  • @LoadBalance 提供负载均衡算法

xml

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

服务提供者

@RestController
public class UserController {
	// 日志
	private Logger log = Logger.getLogger(this.getClass());

	// 服务发现客户端
	@Autowired
	private DiscoveryClient discoveryClient = null;

	// 获取用户信息
	@GetMapping("/user/{id}")
	public UserPo getUserPo(@PathVariable("id") Long id) {
        
		ServiceInstance service = discoveryClient.getInstances("USER").get(0);
		log.info("【" + service.getServiceId() + "】:" + service.getHost() + ":" + service.getPort());
        
        
		UserPo user = new UserPo();
		user.setId(id);
		int level = (int) (id % 3 + 1);
		user.setLevel(level);
		user.setUserName("user_name_" + id);
		user.setNote("note_" + id);
		return user;
	}
	
	// 新增用户,POST请求,且以请求体(body)形式传递
	@PostMapping("/insert")
	public Map<String, Object> insertUser(@RequestBody UserPo user) {
	    Map<String, Object> map = new HashMap<String, Object>();
	    map.put("success", true);
	    map.put("message", "插入用户信息【" +user.getUserName() + "】成功");
	    return map;
	}

	// 修改用户名,POST请求,其中用户编号使用请求头的形式传递
	@PostMapping("/update/{userName}") 
	public Map<String, Object> updateUsername(
	        @PathVariable("userName") String userName,
	        @RequestHeader("id") Long id) {
	    Map<String, Object> map = new HashMap<String, Object>();
	    map.put("success", true);
	    map.put("message", "更新用户【" +id +"】名称【" +userName + "】成功");
	    return map;
	}
	
	@GetMapping("/timeout")
	public String timeout() {
	    // 生成一个3000之内的随机数
	    long ms = (long)(3000L*Math.random());
	    try {
	        // 程序延迟,有一定的概率超过2000毫秒
	        Thread.sleep(ms);
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    }
	    return "熔断测试";
	}
}

服务调用者

//自定义扫描包
@ComponentScan(basePackages = "com.springboot.chapter17.product")
@SpringCloudApplication
public class Chapter17ProductApplication {

    // 初始化RestTemplate
    @LoadBalanced // 多节点负载均衡
    @Bean(name = "restTemplate")
    public RestTemplate initRestTemplate() {
        return new RestTemplate();
    }

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

@RestController
@RequestMapping("/product")
public class ProductController {

	// 注入RestTemplate
	@Autowired
	private RestTemplate restTemplate = null;

	@GetMapping("/ribbon")
	public UserPo testRibbon() {
		UserPo user = null;
		// 循环10次,然后可以看到各个用户微服务后台的日志打印
		for (int i = 0; i < 10; i++) {
            
			// 注意这里直接使用了USER这个服务ID,代表用户微服务系统
			// 该ID通过属性spring.application.name来指定
			user = restTemplate.getForObject("http://USER/user/" + (i + 1), UserPo.class);
            
		}
		return user;
	}
    
}

Feign 声明式 调用

xml

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

配置feign

@EnableFeignClients(basePackages = "com.springboot.chapter17.product")

使用feign

	// 测试
	@GetMapping("/feign")
	public UserPo testFeign() {
		UserPo user = null;
		// 循环10次
		for (int i = 0; i < 10; i++) {
			Long id = (long) (i + 1);
			user = userService.getUser(id);
		}
		return user;
	}

	@GetMapping("/feign2")
	public Map<String, Object> testFeign2() {
		Map<String, Object> result = null;
		UserPo user = null;
		for (int i = 1; i <= 10; i++) {
			Long id = (long) i;
			user = new UserPo();
			user.setId(id);
			int level = i % 3 + 1;
			user.setUserName("user_name_" + id);
			//user.setLevel(level);
			user.setNote("note_" + i);
			result = userService.addUser(user);
		}
		return result;
	}

	@GetMapping("/feign3")
	public Map<String, Object> testFeign3() {
		Map<String, Object> result = null;
		for (int i = 0; i < 10; i++) {
			Long id = (long) (i + 1);
			String userName = "user_name_" + id;
			result = userService.updateName(userName, id);
		}
		return result;
	}
@FeignClient("user") //user为 service Id
public interface UserService {
    // 指定通过HTTP的GET方法请求路径
    @GetMapping("/user/{id}")
    // 这里会采用Spring MVC的注解配置
    public UserPo getUser(@PathVariable("id") Long id);

    // POST方法请求用户微服务
    @PostMapping("/insert")
    public Map<String, Object> addUser(
            // 请求体参数
            @RequestBody UserPo user);

    // POST方法请求用户微服务
    @PostMapping("/update/{userName}")
    public Map<String, Object> updateName(
            // URL参数
            @PathVariable("userName") String userName,
            // 请求头参数
            @RequestHeader("id") Long id);

    // 调用用户微服务的timeout请求
    @GetMapping("/timeout")
    public String testTimeout();
}
	
	// 获取用户信息
	@GetMapping("/user/{id}")
	public UserPo getUserPo(@PathVariable("id") Long id) {
		ServiceInstance service = discoveryClient.getInstances("USER").get(0);
		log.info("【" + service.getServiceId() + "】:" + service.getHost() + ":" + service.getPort());
		UserPo user = new UserPo();
		user.setId(id);
		int level = (int) (id % 3 + 1);
		user.setLevel(level);
		user.setUserName("user_name_" + id);
		user.setNote("note_" + id);
		return user;
	}
	
	// 新增用户,POST请求,且以请求体(body)形式传递
	@PostMapping("/insert")
	public Map<String, Object> insertUser(@RequestBody UserPo user) {
	    Map<String, Object> map = new HashMap<String, Object>();
	    map.put("success", true);
	    map.put("message", "插入用户信息【" +user.getUserName() + "】成功");
	    return map;
	}

	// 修改用户名,POST请求,其中用户编号使用请求头的形式传递
	@PostMapping("/update/{userName}") 
	public Map<String, Object> updateUsername(
	        @PathVariable("userName") String userName,
	        @RequestHeader("id") Long id) {
	    Map<String, Object> map = new HashMap<String, Object>();
	    map.put("success", true);
	    map.put("message", "更新用户【" +id +"】名称【" +userName + "】成功");
	    return map;
	}

Hystrix 断路器

  • 其自身 不可用之后 可能继续蔓延 到其他 与之相关的服务上,会使更多的微服务不可用
  • 如果 电器 耗电大,导致电流过大,那么保险丝 就会熔断。

服务降级

  • 请求其他 微服务出现超时,或者 发生故障时,就会使用自身服务其他的方法进行相应。

  • Hystrix 默认服务之间的调用 超过 2000ms (2s),会根据你配置的其他方法进行相应。

  • 写了一个3秒以上的方法

	@GetMapping("/timeout")
	public String timeout() {
	    // 生成一个3000之内的随机数
	    long ms = (long)(3000L*Math.random());
	    try {
	        // 程序延迟,有一定的概率超过2000毫秒
	        Thread.sleep(ms);
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    }
	    return "熔断测试";
	}

引入 pom

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

配置

// 启动断路器
@EnableCircuitBreaker

使用断路器

	// Ribbon断路
	@GetMapping("/circuitBreaker1")
	@HystrixCommand(fallbackMethod = "error", commandProperties = {
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") })
	public String circuitBreaker1() {
		return restTemplate.getForObject("http://USER/timeout", String.class);
	}

	// Feign断路测试
	@GetMapping("/circuitBreaker2")
	@HystrixCommand(fallbackMethod = "error")
	public String circuitBreaker2() {
		return userService.testTimeout();
	}

	// 降级服务方法
	public String error() {
		return "超时出错。";
	}

dashboard 启用hystrix仪表盘

引入xml

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

启动仪表盘


// 启用Hystrix仪表盘
@EnableHystrixDashboard

server.port=6001
spring.application.name=hystrix_dashboard

http://localhost:6001/hystrix
  • Delay: 2000 ms 轮询时间
  • title: 标题,如产品监控
  • https://hystrix-app:port/actuator/hystrix.stream

产品微服务引入监控的依赖

  • spring-boot-starter-actuator

  • 还要打开端点的暴露:

    • management.endpoints.web.exposure.include=health,info,hystrix.stream
      
  • 之后再访问一下 产品微服务 地址的断路器

路由网关

  • 请求路由到真实的服务器上,保护真实服务器的IP地址
  • 作为负载均衡的手段
  • 提供过滤器,判定请求是否有效

引入 pom

		<!--引入服务发现 -->
		<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>

配置

//启动Zuul代理功能。里面包含了 启用断路器的机制
@EnableZuulProxy
# 服务端口
server.port=80
# Spring应用名称
spring.application.name=zuul

# 用户微服务映射规则
# 指定ANT风格的URL匹配
zuul.routes.user-service.path=/u/**

#指定映射的服务用户地址,这样Zuul就会将请求转发到用户微服务上了
zuul.routes.user-service.url=http://localhost:8001/

#产品微服务映射规则
zuul.routes.product-service.path=/p/**
#映射产品服务中心服务ID,Zuul会自动使用服务端负载均衡,分摊请求
zuul.routes.product-service.serviceId=product

#注册给服务治理中心
eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/,http://localhost:7002/eureka/

  • 默认访问: http://localhost/user/timeout 即可映射到 user 项目,timeout方法
  • 配置了上面:localhost/p/product/ribbon 即可访问

使用redis 做过滤器

网关:

  • 检测用户登录

  • 黑名单用户

  • 购物验证码

  • 恶意刷请求

  • 模拟场景:

    • 提交表单,每个表单存在一个序列号:serialNumber
    • 对应 一个验证码 verificationCode
    • 这两个参数一起提交给表单。reids以 序列号为key,验证码为值
    • 判断用户提交的验证码 与 redis上是否一致。

引入redis和配置

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<exclusions>
				<!--不依赖Redis的异步客户端lettuce -->
				<exclusion>
					<groupId>io.lettuce</groupId>
					<artifactId>lettuce-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<!--引入Redis的客户端驱动jedis -->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
spring.redis.port=6379
spring.redis.host=192.168.10.128
spring.redis.password=123456
spring.redis.timeout=1000

ZuulFilter

  • 实现 4个抽象方法

    • shouldFilter 如果为true,则执行 这个过滤器的方法
    • run 核心方法
    • filterType 过滤器类型
      • pre 执行前
      • route 处理请求,进行路由
      • post 处理完成后
      • error 出现错误的时候
    • filterOrder 过滤器的顺序。值越小越优先。
  • Qualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,

  • @Qualifier(“myZuulFilter”)

@Component
@Qualifier
public class MyZuulFilter extends ZuulFilter {

	// 注入StringRedisTemplate
	@Autowired
	private StringRedisTemplate residTemplate = null;

	// 是否过滤
	@Override
	public boolean shouldFilter() {
		// 请求上下文
		RequestContext ctx = RequestContext.getCurrentContext();
        
		// 获取HttpServletRequest对象
		HttpServletRequest req = ctx.getRequest();
        
		// 取出表单序列号
		String serialNumber = req.getParameter("serialNumber");
		// 如果存在验证码返回为true,启用过滤器
		return !StringUtils.isEmpty(serialNumber);
        //这里有问题,如果没有这个表单序列号,那这个过滤器不启用,应该在下面,拦截。
	}

	// 过滤器逻辑方法
	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest req = ctx.getRequest();
        
		// 取出表单序列号和请求验证码
		String serialNumber = req.getParameter("serialNumber");
		String reqCode = req.getParameter("verificationCode");
        
		// 从Redis中取出验证码
		String verifCode = residTemplate.opsForValue().get(serialNumber);
        
		// Redis验证码为空或者与请求不一致,拦截请求报出错误
		if (verifCode == null || !verifCode.equals(reqCode)) {
            
			// 不再转发请求
			ctx.setSendZuulResponse(false);
			// 设置HTTP响应码为401(未授权)
			ctx.setResponseStatusCode(401);
			// 设置响应类型为JSON数据集
			ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8.getType());
			// 设置响应体
			ctx.setResponseBody("{'success': false, " + "'message':'Verification Code Error'}");
		}
		// 一致放过
		return null;
	}

	// 过滤器类型为请求前
	@Override
	public String filterType() {
		return "pre";
	}

	// 过滤器排序,数字越小优先级越高
	@Override
	public int filterOrder() {
		return 0;
	}
}

开发自己的注解

  • 研究 @SpringCloudApplication
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}

  • 目前没有配置 扫描包,所以 一般都加上

    • //自定义扫描包
      @ComponentScan(basePackages = "com.springboot.chapter17.product")
      @EnableFeignClients(basePackages = "com.springboot.chapter17.product")
      

boot 的知识补充点

可以选择 tomcat 服务器

  • 还可以选择 Jetty 或 Undertow

    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    			<exclusions>
    				<exclusion>
    					<groupId>org.springframework.boot</groupId>
    					<artifactId>spring-boot-starter-tomcat</artifactId>
    				</exclusion>
    			</exclusions>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<!--<artifactId>spring-boot-starter-jetty</artifactId>-->
    			<artifactId>spring-boot-starter-undertow</artifactId>
    		</dependency>
    

修改商标

  • http://patorjk.com/software/taag/
  • 录入文字,保存为 banner.txt
  • 或者把图标保存为 banner.jpg

boot自动装配

  • spring-boot-start-web 会引入 spriing-boot-starters ——引入了 spring-boot-autocofigure包

  • RedisAutoConfiguration

    //配置类
    @Configuration(
        proxyBeanMethods = false
    )
    //存在哪些类,ioc容器才会装配这个类
    @ConditionalOnClass({RedisOperations.class})
    //使用哪个类 可以通过配置文件装配
    @EnableConfigurationProperties({RedisProperties.class})//可以通过文件配置
    
    @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
    public class RedisAutoConfiguration {
        public RedisAutoConfiguration() {
        }
    
        @Bean
        @ConditionalOnMissingBean(
            name = {"redisTemplate"}
        )//在缺失redisTemplate,才将方法 返回的bean装配到 Ioc容器中
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        @Bean
        @ConditionalOnMissingBean
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    }
    
    
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(EnableRedisRepositories.class)
    @ConditionalOnBean(RedisConnectionFactory.class)
    
    //当存在这个 .repositories.* 属性后,才会启动这个类作为 配置文件
    @ConditionalOnProperty(prefix = "spring.data.redis.repositories", name = "enabled", havingValue = "true",
    		matchIfMissing = true)
    @ConditionalOnMissingBean(RedisRepositoryFactoryBean.class)
    
    //加在其他其他类 到当前环境中来
    @Import(RedisRepositoriesRegistrar.class)
    //完成RedisAutoConfiguration的装配后,才执行。还存在before 定制在哪些类之前初始化
    @AutoConfigureAfter(RedisAutoConfiguration.class)
    
    public class RedisRepositoriesAutoConfiguration {
    
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值