- 分佈式開發
- 服務發現
- 配置中心
- 消息總線
- 負載均衡
- 聲明式 服務調用
- 斷路器
- 數據監控
- 分佈式 事務
服務治理 和 服務發現 ——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 { }