深入淺出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 {
    
    }
    
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章