簡介
Spring Cloud是一套完整的微服務(wù)解決方案承疲,是一系列不同功能的微服務(wù)框架的集合府瞄。
Spring Cloud基于Spring Boot敷矫,簡化了分布式系統(tǒng)的開發(fā)础芍,集成了服務(wù)發(fā)現(xiàn)灸叼、配置管理神汹、消息總線、負(fù)載均衡怜姿、斷路器慎冤、數(shù)據(jù)監(jiān)控等各種服務(wù)治理能力。比如sleuth提供了全鏈路追蹤能力沧卢,Netflix套件提供了hystrix熔斷器蚁堤、zuul網(wǎng)關(guān)等眾多的治理組件。config組件提供了動(dòng)態(tài)配置能力,bus組件支持使用RabbitMQ披诗、kafka撬即、Activemq等消息隊(duì)列,實(shí)現(xiàn)分布式服務(wù)之間的事件通信呈队。
基礎(chǔ)組件1-Eureka server
這個(gè)是微服務(wù)的通訊錄(注冊(cè)中心)剥槐,既然是微服務(wù),那么在調(diào)用別的微服務(wù)的時(shí)候肯定需要其他微服務(wù)的地址宪摧、端口等信息粒竖,而這些信息都有Eureka Server來管理。 而Eureka Server的啟動(dòng)比較簡單几于,作為一個(gè)Spring boot類型的項(xiàng)目來啟動(dòng)蕊苗。 對(duì)于習(xí)慣了Dubbo開發(fā)的同學(xué)來說,在使用Spring Cloud時(shí)遇到的第一個(gè)不習(xí)慣的地方就是沿彭,注冊(cè)中心Eureka不是一個(gè)像Zookeeper那樣獨(dú)立運(yùn)行的中間件朽砰,而是可以用Springboot來啟動(dòng)運(yùn)行,有點(diǎn)類似于嵌入式的tomcat喉刘,這是Spring Cloud體系的一大特點(diǎn)瞧柔,除了注冊(cè)中心還有網(wǎng)關(guān)Zuul也是類似的啟動(dòng)方式。
基本的server
- 引入pom文件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 增加啟動(dòng)類注解
@EnableEurekaServer
@SpringBootApplication
public class PlatformEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(PlatformEurekaApplication.class, args);
}
}
- 配置yml文件
- 默認(rèn)yml
server:
port: 7001 #(eureka 默認(rèn)端口為:8761)
spring:
application:
name: platform-eureka #服務(wù)名
eureka:
instance:
#服務(wù)失效時(shí)間睦裳,Eureka多長時(shí)間沒收到服務(wù)的renew操作造锅,就剔除該服務(wù),默認(rèn)90秒
leaseExpirationDurationInSeconds: 15
ip-address: ${spring.cloud.client.ip-address}
hostname: ${eureka.instance.ip-address}
instanceId: ${eureka.instance.ip-address}:${server.port}
preferIpAddress: true #將IP注冊(cè)到Eureka Server上
client:
#是否注冊(cè)自身到eureka服務(wù)器廉邑,因?yàn)楫?dāng)前這個(gè)應(yīng)用就是eureka服務(wù)器备绽,沒必要注冊(cè)自身,所以這里是false
registerWithEureka: false
fetchRegistry: false #表示是否從eureka服務(wù)器獲取注冊(cè)信息
serviceUrl:
#是設(shè)置eureka服務(wù)器所在的地址鬓催,查詢服務(wù)和注冊(cè)服務(wù)都需要依賴這個(gè)地址(注意:地址最后面的 /eureka/ 這個(gè)是固定值)
defaultZone: http://${eureka.instance.ip-address}:${server.port}/eureka/
server:
#設(shè)為false,關(guān)閉自我保護(hù)恨锚,開發(fā)測試環(huán)境需要頻繁啟動(dòng)注冊(cè)實(shí)例宇驾,需要關(guān)閉自我保護(hù)功能,以免請(qǐng)求跑到舊實(shí)例中猴伶,生成環(huán)境需要開啟自我保護(hù)功能
enableSelfPreservation: false
#eureka server清理無效節(jié)點(diǎn)的時(shí)間間隔课舍,默認(rèn)60000毫秒,即60秒
eviction-interval-timer-in-ms: 5000
# 續(xù)期時(shí)間他挎,即掃描失效服務(wù)的間隔時(shí)間(缺省為60*1000ms)
eureka.server.evictionIntervalTimerInMs: 20000
- dev.yml
eureka:
instance:
ip-address: 127.0.0.1
增加監(jiān)控
可以對(duì)每個(gè)服務(wù)增加安全監(jiān)控筝尾,利用形如http://localhost:7001/Actuator 的訪問方式進(jìn)行訪問。
- 修改pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 修改yml
# Spring Cloud應(yīng)用監(jiān)控與管理Actuator
management:
endpoints:
enabled-by-default: true
web:
exposure:
include: "*"
增加安全認(rèn)證
- 引入安全模塊的pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 修改yml文件
spring:
security:
basic:
enabled: true # 開啟基于HTTP basic的認(rèn)證
user:
name: admin # 配置登錄的賬號(hào)是admin
password: 123456 #配置登錄的密碼是 123456
- 增加一個(gè)java類配置登錄模式
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // 關(guān)閉csrf
http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); // 開啟認(rèn)證
}
}
基礎(chǔ)組件2-服務(wù)提供者
服務(wù)提供者要作為Eureka的客戶端在注冊(cè)中心注冊(cè)為服務(wù)提供者办桨,這里重點(diǎn)是注冊(cè)自己的名字和服務(wù)地址筹淫。
- 引入pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 默認(rèn)yml
server:
port: ${server.port}
spring:
application:
name: platform-user
eureka:
instance:
hostname: ${eureka.instance.ip-address}
instanceId: ${eureka.instance.ip-address}:${server.port}
prefer-ip-address: true
registry.default-open-for-traffic-count: ${eureka.instance.registry.default-open-for-traffic-count}
registry.expected-number-of-renews-per-min: ${eureka.instance.registry.expected-number-of-renews-per-min}
client:
enabled: true
serviceUrl:
defaultZone: ${eureka.client.serviceUrl.defaultZone}
- dev.yml
server:
port: 7081
spring:
profiles: dev
eureka:
instance:
ip-address: 127.0.0.1
registry.default-open-for-traffic-count: 1
registry.expected-number-of-renews-per-min: 1
client:
serviceUrl:
defaultZone: http://admin:123456@127.0.0.1:7001/eureka/
management:
endpoints:
enabled-by-default: true
web:
exposure:
include: "*"
- 增加注解
@EnableDiscoveryClient
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.itcast.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
注意:
從Spring Cloud Edgware開始,@EnableDiscoveryClient 或@EnableEurekaClient 可省略呢撞。只需加上相關(guān)依賴损姜,并進(jìn)行相應(yīng)配置饰剥,即可將微服務(wù)注冊(cè)到服務(wù)發(fā)現(xiàn)組件上。
基礎(chǔ)組件3-服務(wù)消費(fèi)者
通過注冊(cè)中心查找自己需要的服務(wù)地址摧阅,就想提供名字查電話一樣汰蓉。 與服務(wù)提供者的編寫方式基本一致。為了后面增加網(wǎng)關(guān)支持的方便棒卷,這里面的服務(wù)消費(fèi)者本身也是服務(wù)提供者顾孽。
基礎(chǔ)組件4-Feign
在消費(fèi)者中使用,簡化Http API的調(diào)用比规,使消費(fèi)者調(diào)用服務(wù)提供者就想調(diào)用本地接口一樣方便若厚。 Spring Cloud對(duì)原生Feign進(jìn)行了整合。
- 引入pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 增加注解
@EnableFeignClients
- 按照服務(wù)提供者對(duì)應(yīng)服務(wù)協(xié)議編寫對(duì)應(yīng)接口(不用實(shí)現(xiàn))
@FeignClient(name = "PLATFORM-USER")
public interface UserService {
/***
* 用戶信息查詢
*/
@GetMapping(value = "/user/search")
public String searchUser(@RequestParam("userName") String userName);
}
- 像本地方法一樣調(diào)用
@Autowired
private UserService userService;
// 業(yè)務(wù)代碼
strJson = userService.searchUser(orderInfo.getUserName());
基礎(chǔ)組件5-Hystrix
Feign中已經(jīng)支持Hystrix苞俘,當(dāng)服務(wù)提供者出現(xiàn)故障時(shí)盹沈,Hystrix會(huì)自動(dòng)切換到備胎方案。
- 增加fallback參數(shù)
@FeignClient(name = "PLATFORM-USER", fallback = UserServiceFallbackImpl.class)
- 實(shí)現(xiàn)備胎代碼
@Component
//@Service
public class UserServiceFallbackImpl implements UserService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceFallbackImpl.class);
@Override
public String searchUser(String userName){
LOGGER.error("用戶信息查詢接口調(diào)用異常:searchUser");
return JsonUtils.toText(ResponseUtils.failure("調(diào)用用戶信息查詢接口服務(wù)異常吃谣!"));
}
}
基礎(chǔ)組件6-Zuul
內(nèi)容簡介
Zuul網(wǎng)關(guān)的功能和工作機(jī)制乞封、結(jié)合代碼介紹如何使用Zuul構(gòu)建一個(gè)簡單的網(wǎng)關(guān)、介紹Zuul的路由配置方式岗憋、了解Filter工作原理并實(shí)現(xiàn)一些擴(kuò)展功能肃晚。
Zuul網(wǎng)關(guān)簡介
Zuul是Spring Cloud全家桶中的微服務(wù)API網(wǎng)關(guān)。 所有從設(shè)備或網(wǎng)站來的請(qǐng)求都會(huì)經(jīng)過Zuul到達(dá)后端的Netflix應(yīng)用程序仔戈。作為一個(gè)邊界性質(zhì)的應(yīng)用程序关串,Zuul提供了動(dòng)態(tài)路由、監(jiān)控监徘、彈性負(fù)載和安全功能晋修。Zuul底層利用各種filter實(shí)現(xiàn)如下功能:
認(rèn)證和安全 識(shí)別每個(gè)需要認(rèn)證的資源,拒絕不符合要求的請(qǐng)求凰盔。
性能監(jiān)測 在服務(wù)邊界追蹤并統(tǒng)計(jì)數(shù)據(jù)墓卦,提供精確的生產(chǎn)視圖。
動(dòng)態(tài)路由 根據(jù)需要將請(qǐng)求動(dòng)態(tài)路由到后端集群户敬。
壓力測試 逐漸增加對(duì)集群的流量以了解其性能落剪。
負(fù)載卸載 預(yù)先為每種類型的請(qǐng)求分配容量,當(dāng)請(qǐng)求超過容量時(shí)自動(dòng)丟棄尿庐。
靜態(tài)資源處理 直接在邊界返回某些響應(yīng)忠怖。
編寫一個(gè)Zuul網(wǎng)關(guān)
1、新建一個(gè)zuul-demo模塊抄瑟,在依賴項(xiàng)處添加【Cloud Discovery->Eureka Discovery和Cloud Rouing->Zuul】凡泣。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2、修改入口類,增加EnableZuulProxy注解
@SpringBootApplication
@EnableZuulProxy
public class ZuulDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication .class, args);
}
}
- 修改appliation.yml
server:
port: 7000
spring:
application:
name: zuul-demo
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
prefer-ip-address: true
#將IP注冊(cè)到Eureka Server上问麸,而如果不配置就是機(jī)器的主機(jī)名往衷。
4、啟動(dòng)Eureka Server严卖、orderservice和 Zuul席舍,在瀏覽器中輸入http://localhost:7000/orderservice/order/pay 獲取返回結(jié)果。
從上面的例子中的地址可以看出來默認(rèn)Zuul的路由方式是:http://ZUULHOST:ZUULPORT/serviceId/**哮笆。
如果啟動(dòng)多個(gè)orderservice可以發(fā)現(xiàn)Zuul里面還內(nèi)置了Ribbon的負(fù)載均衡功能来颤。
Filter工作原理
Zuul中的Filter
Zuul是圍繞一系列Filter展開的,這些Filter在整個(gè)HTTP請(qǐng)求過程中執(zhí)行一連串的操作稠肘。 Zuul Filter有以下幾個(gè)特征: Type:用以表示路由過程中的階段(內(nèi)置包含PRE福铅、ROUTING、POST和ERROR) Execution Order:表示相同Type的Filter的執(zhí)行順序 Criteria:執(zhí)行條件 Action:執(zhí)行體
Zuul請(qǐng)求生命周期
一圖勝千言项阴,下面通過官方的一張圖來了解Zuul請(qǐng)求的生命周期滑黔。自定義一個(gè)Filter實(shí)現(xiàn)token驗(yàn)證
- 添加一個(gè)AuthZuulFilter
public class AuthZuulFilter extends ZuulFilter {
// 日志輸出器
private final static Logger LOGGER = LoggerFactory.getLogger(AuthZuulFilter.class);
@Value("${server.port}")
private String serverPort;
//四種類型:pre,routing,error,post
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
//自定義過濾器執(zhí)行的順序,數(shù)值越大越靠后執(zhí)行环揽,越小就越先執(zhí)行
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER;
}
//控制過濾器生效不生效略荡,可以在里面寫一串邏輯來控制
@Override
public boolean shouldFilter() {
return true;
}
//執(zhí)行過濾邏輯
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//從請(qǐng)求頭中獲取token信息
String userToken = request.getHeader("userToken");
LOGGER.debug("網(wǎng)關(guān)端口:" + serverPort);
if (StringUtils.isEmpty(userToken)) {
//設(shè)置為false就不會(huì)繼續(xù)執(zhí)行服務(wù)代碼
ctx.setSendZuulResponse(false);
//設(shè)置狀態(tài)碼
ctx.setResponseStatusCode(401);
//設(shè)置相應(yīng)信息
ctx.setResponseBody("userToken is null");
return null;
}
return null;
}
}
- 修改啟動(dòng)程序,添加Filter注入:
@Bean
public AuthZuulFilter authZuulFilter() {
AuthZuulFilter filter = new AuthZuulFilter();
return filter;
}
路由配置
Zuul提供了一套簡單且強(qiáng)大路由配置策略歉胶,利用路由配置我們可以完成對(duì)微服務(wù)和URL更精確的控制
- 重寫指定微服務(wù)的訪問路徑:
zuul:
routes:
platform-order: /orderservice/**
這表示將rest-demo微服務(wù)的地址映射到/rest/**路徑汛兜。
- 忽略指定微服務(wù):
zuul:
ignored-services: xxx-service
使用“*”可忽略所有微服務(wù),多個(gè)指定微服務(wù)以半角逗號(hào)分隔通今。
3粥谬、忽略所有微服務(wù),只路由指定微服務(wù):
zuul:
ignored-services: *
routes:
platform-order: /orderservice/**
4辫塌、路由別名:
zuul:
routes:
platform-order: #路由別名漏策,無其他意義,與例1效果一致
service-id: platform-order
path: /orderservice/**
5臼氨、指定path和URL
zuul:
routes:
platform-order:
url: http://localhost:8000/
path: /orderservice/**
此例將http://ZUULHOST:ZUULPORT/rest/映射到http://localhost:8000/哟玷,同時(shí)由于并非用service-id定位服務(wù),所以也無法使用負(fù)載均衡功能一也。
限流
#服務(wù)限流
zuul:
ratelimit:
enabled: true
repository: REDIS #使用redis存儲(chǔ),一定要大寫
policies:
platform-order: #針對(duì)platform-order服務(wù)限流
limit: 10 #多少個(gè)請(qǐng)求(次數(shù))
refreshInterval: 60 #測試客戶端如果60秒內(nèi)請(qǐng)求超過10次喉脖,服務(wù)端就拋出429異常(秒)
type:
- ORIGIN #可選 限流方式: url通過請(qǐng)求路徑區(qū)分 origin通過客戶端IP地址區(qū)分 user是通過登錄用戶名進(jìn)行區(qū)分椰苟,也包括匿名用戶