答案: 微服務架構是單機架構的將來, 但不是銀彈多用於增加型業務!
若是你是一位軟件行業從業者,尤爲是從事服務器端或者後臺系統軟件開發,相信近年來必定被層出不窮的商業名詞所包圍:NoSQL、Big Data、Web-scale、Sharding、Eventual consistency、ACID、CAP理論、雲服務、MapReduce和Real-time等, 全部這些其實都圍繞着如何構建高效存儲與數據處理這一核心主題。過去十年,在數據庫領域與分佈式系統方面涌現了許多引人矚目的進展,由此深入地影響了如何構建上層應用系統。分析這些激動人心的變化背後,你會發現有如下幾個很是重要的驅動因素:
互聯網公司,包括Google、Yahoo! 、Amazon、Facebook、LinkedIn、Microsoft,以及Twitter等,它們天天都在面對海量數據和負載,迫使其不斷創新,並改進支撐系統以更有效地處理這種量級的數據。html商業方面因素,如敏捷開發、測試驅動和對市場機會作出快速反應等,都要求儘可能縮短產品開發週期,所以系統中的數據模型也要足夠靈活以方便調整。 java
硬件方面,CPU主頻增加日趨緩慢,而多核系統成爲新常態,網絡速度則依舊保持快速發展,這就意味着並行分佈式系統將會成爲業界主流。git
<數據密集型應用系統設計>github
答案: 先有微服務後有SpringCloud !2014年3月25日 敏捷開發教父 Martin Fowler 在 Microservices 一文中 對於 Microservice Architecture 進行了條理清晰的論述, 向世人展現了進可攻退可守的微服務架構思想, 奠基了後來者對 微服務的認知.web
早期 SpringCloud & Angel 系列基於Spring Boot 1.2.x, 而1.2版本最先誕生於 2014年12月11日,也就是說至少晚了8個月多! 其中不少設計思想也來源於前者!
(數據來源: github) 又一個活生生的學術界驅動工業界的例子. 因此你們有空仍是要關注一下 學術界大牛們的新做.才能保證走在技術最前沿.
筆者經過翻閱 Martin Fowler 發表的文章 Microservices , 將微服務理念梳理爲 如下 7 點
沒接觸過微服務的小夥伴看完暈倒了過去,? 過一會醒來瘋狂撓頭, 這知識不過腦子啊 !輕點,注意髮量哈?
其實每個點對應的都是一種微服務場景的解決方案。 而這些解決方案多是 Spring 官方提供,有多是 別的公司提供. 或者二者都有...面試
## 微服務之我見算法
筆者才畢業時那會業內流行 Dubbo , 只要涉及到有點難度的項目 別管併發,數據量怎麼樣, 都一概上 Dubbo 生產者消費者分離, 和今天小夥伴們使用 SpringCloud 的熱情一模一樣, 可是那時候 微服務這個概念並非很火熱 !spring
Dubbo 官方定義: Apache Dubbo |ˈdʌbəʊ| 是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動註冊和發現。
SpringCloud 官方定義: SpringCloud基於SpringBoot爲開發人員提供了組件,以快速構建分佈式系統中的一些常見模式(例如,配置中心,服務發現,斷路器,智能路由,微代理,控制總線,一次性令牌,全局鎖,領導選舉,分佈式會話,羣集狀態)。分佈式系統的協調致使樣板式樣,而且使用Spring Cloud開發人員能夠快速站起來實現這些樣板的服務和應用程序。它們能夠在任何分佈式環境中正常工做,包括開發人員本身的筆記本電腦,裸機數據中心以及Cloud Foundry等託管平臺。
### 那麼 Dubbo 和 SpringCloud 有什麼區別呢?數據庫
從技術棧上來看
dubbo:zookeeper+dubbo+springmvc/springboot 通訊方式:rpc 註冊中心:zookeeper,nacos 配置中心:diamond(淘寶開發) spring cloud:spring+Netflix 通訊方式:http restful 註冊中心:eureka,consul,nacos 配置中心:config 斷路器:hystrix 網關:zuul,gateway 分佈式追蹤系統:sleuth+zipkin
誠然 Dubbo 已經跟不上目前 微服務思想的發展了, 咱們在作微服務的時候 首選 SpringCloud.那 SpringCloud 有那麼多組合咱們選哪一個好呢? apache
### SpringCloud 組合大PK
SpringCloud 有哪些主流組合呢?
SpringCloud-Alibaba
SpringCloud-Netflix
二者的優缺點: SpringCloud 是一項標準而不是一門技術, 你能夠在它們互相兼容的前提下同時使用兩大陣營的組件, 二者最大的不一樣在於, Netflix 的服務通訊基於 Feign 組件傾向於 HTTP RestFul, 而 Alibaba 的服務通訊 基於 Dubbo 組件 的 RPC 調用, 從這裏不難看出來, 他們的基本盤分別是 SpringBoot 與 Dubbo, 若是你的項目基於 SpringBoot 就首選 Netflix , 若是你的項目基於 Dubbo 就首選 Alibaba 這樣對於重構系統來講會減小不少工做量, 從社區的角度看, 你們都知道 2018-12-12日,Netflix宣佈Spring Cloud Netflix 除了 Eureka 其餘組件都進入維護狀態(不會推出新功能), 但不等於 Netflix 就毫無但願, 最近 Netflix 推出了 PRE 3.0 M1 對 Eureka 進行迭代, 而 Alibaba 這個後起之秀的 GitHub Fork 數爲 3.8 K 而 Netflix 爲 2K 近乎兩倍, 對擁有國內 70% 市場的 Netflix 來講 進入維護狀態的 組件能夠, 用別的組件來替代就能夠, 而若是使用了 Alibaba 就被被捆綁銷售了 一堆本身的技術以及阿里雲的東西 ... 技術選型上面沒有銀彈, 選擇最適合項目的技術便可 !
### SpringCloud-Alibaba 太香了 ,? 我選 SpringCloud-Netflix !
推薦 SpringCloud-Netflix組合
微服務概念 | 技術棧 | 實現原理 | 相似方案 |
---|---|---|---|
經過服務進行組件化: 組件是獨立可替換和可升級的軟件單元。 | Eureka , OpenFeign | 服務註冊與發現組件後使用服務調用組件,組件之間能夠經過HTTP複用 | Dubbo |
分散治理: 集中治理的後果之一是傾向於在單一技術平臺上實現標準化。 | Zuul,Eureka | 經過網關路由給服務生產者, 網關,生產者能夠擁有多實例 | Nginx |
分散數據管理: 那些具備相同屬性的屬性可能具備不一樣的語義,而且(更差的)公共屬性具備不一樣的語義。 | SpringCloud Config ,Bus | 提供了配置集中管理,動態刷新配置的功能;每一個微服務均可以有本身的數據配置. | yml |
智能端點和啞管道: 一個很好的例子是企業服務總線(ESB),其中ESB產品一般包括用於消息路由,編排,轉換和應用業務規則的複雜工具。 | SpringCloudStream | 對分佈式消息進行抽象,包括髮布訂閱、分組消費等功能,實現了微服務之間的異步通訊. | ApplicationEvent |
基礎設施自動化: 升級工做軟件的渠道意味着咱們能夠自動部署 到每一個新環境。 | Jenkins, Docker, ,SpringCloudSleuth, Zipkin | 使用 Jenkins 進行 CI/CD到微服務到 Docker 後用 Sleuth,Zipkin 監控. | elk |
失敗設計: 使用服務做爲組件的結果是,須要對應用程序進行設計,以便它們能夠容忍服務故障。 | Hystrix | Hystrix 熔斷降級,在出現依賴服務失效的狀況下,經過隔離 系統依賴服務 的方式,防止服務級聯失敗,同時提供失敗回滾機制,使系統可以更快地從異常中恢復. | Nginx+Lua |
進化設計: 組件的關鍵屬性是獨立替換和可升級性的概念[13] -這意味着咱們尋找能夠想象重寫組件而不影響其協做者的觀點。 | k8s , Eureka, Ribbon,Sleuth,zipkin | 我認爲這裏表述的是可維護性, 也就是說 運維能力, 使用Sleuth,zipkin 監控服務流量, 在熱點服務流量幾何形增加時用 k8s 進行擴容, 基於 Eureka 與 Ribbon 進行流量分發 | OpenStack |
## SpringCloud-Netflix 組件源碼簡析
註冊中心 (場景: 服務註冊與服務發現, 能夠理解爲 IM服務器)
從eureka-core 包中的 AbstractInstanceRegistry 類看起
* Handles all registry requests from eureka clients. * * * Primary operations that are performed are the * Registers, Renewals, Cancels, Expirations, and Status Changes The * registry also stores only the delta operations * * @author Karthik Ranganathan * */ private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
- 第一層的 Map , K 是應用名稱. V 是Map 多實例元信息
- 第二層 Map 爲保持的多實例信息, K 爲 實例名稱, V 爲實例詳細信息(註冊信息,ip地址,實例id,端口, 狀態等)
- Eureka 經過維護 這個
ConcurrentHashMap
實現服務註冊以及發現.建議能夠從 193行 public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) 微服務註冊方法看起
API 網關 (場景: 動態路由, 監控, 能夠理解爲 快遞攬件配送)
從 org.springframework.cloud.netflix.zuul.filters.support FilterConstants 類看起
這個類中是有和 上面的Filters 相關的常量// Zuul Filter TYPE constants ----------------------------------- /** * {@link ZuulFilter#filterType()} error type. */ public static final String ERROR_TYPE = "error"; /** * {@link ZuulFilter#filterType()} post type. */ public static final String POST_TYPE = "post"; /** * {@link ZuulFilter#filterType()} pre type. */ public static final String PRE_TYPE = "pre"; /** * {@link ZuulFilter#filterType()} route type. */ public static final String ROUTE_TYPE = "route"; // OTHER constants -----------------------------------對這幾個 常量全局搜索 會找到與上述功能相同的過濾器類
PreDecorationFilter
SendForwardFilter
SendResponseFilter
SendErrorFilter
PreDecorationFilter 128行 初始化請求參數映射
RequestContext ctx = RequestContext.getCurrentContext(); ...以後 經過轉發過濾實現上述功能 !RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
RestTemplate (場景: 簡化 HTTP 通訊方式,統一了RESTful的標準併爲 執行復雜任務提供了一種具備默認行爲的簡化方法, 能夠理解爲 GOF中的 模板模式)
Spring 核心 HTTP 消息轉換器
HttpMessageConverter
Rest自描述信息: 媒體類型 (
MediaType
) : text/html; text/xml; application/jsonHTTP 協議特色: 純文本協議 ,須要自我描述
- REST 客戶端
- REST 服務端
反序列化 : 文本(通訊) ---> 對象(程序使用)
序列化: 對象(程序) ----> 文本(通訊)
分析 HttpMessageConverter
// 策略接口,它指定了一個轉換器,能夠將請求和響應轉換爲HTTP請求和響應。 public interface HttpMessageConverter<T> { // 判斷當前<T> 泛型是否能夠反序列化 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); // 判斷當前<T> 泛型是否能夠序列化 boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); // 當前支持的媒體類型 List<MediaType> getSupportedMediaTypes(); // 反序列化對象 T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; }特別提醒: SpringWebMVC 依賴於 Servlet, Spring 在設計早期時, 它就考慮到了去 Servlet 化.
HttpInputMessage 相似於 HttpServletRequest
RestTemplate
利用HttpMessageConverter
對一些媒體類型進行通用的序列化和反序列化
- JSON
- XML
- TEXT
- ByteArrays
它不依賴於
Servlet
它自定義實現, 對於 服務端而言. 將ServletAPI
適配或HttpInputMessage
以及HttpOutputMessage
.
RestTemplate 對應多個 HttpMessageConverter 那麼如何決策正確的媒體類型
將各類經常使用 HttpMessageConverter 支持的MediaType 和 JavaType 以及對應關係總結在此處:
類名 支持的JavaType 支持的MediaType ByteArrayHttpMessageConverter byte[] application/octet-stream, / StringHttpMessageConverter String text/plain, / MappingJackson2HttpMessageConverter Object application/json, application/*+json AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml RestTemplate 對應多個
HttpMessageConverter
, 那麼如何決策正確媒體類型 ?// 同志們 順着方法調用棧追蹤源碼. 別怕, 前方安全, 有註釋 ! // 建議 Debug 行點. // RestTemplate: 190,419,673,769,850 // HttpMessageConverterExtractor: 89 @Test public void getForObject() throws Exception { RestTemplate restTemplate = new RestTemplate( new HttpComponentsClientHttpRequestFactory()); // restTemplate = new RestTemplate(); // restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory()); // 設置攔截器記錄HTTP請求到響應時間 restTemplate.setInterceptors(Arrays.asList(new TimeInterceptor())); String result = restTemplate.getForObject("https://example.com",String.class); } // 記錄響應時間攔截器 class TimeInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 觸類旁通: 也能夠在請求前進行負載均衡到具體 IP long frontNow = System.currentTimeMillis(); ClientHttpResponse response = execution.execute(request, body); // 獲取請求消耗時間 System.out.println("消耗時間"+ (System.currentTimeMillis() - frontNow) / 1000 + "秒"); return response; } }從 SpringWeb包 org.springframework.web.client.RestTemplate 類看起
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations{ private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); static { // 初始化時 判斷 第三方 HttpMessageConverter 實現是否存在 ClassLoader classLoader = RestTemplate.class.getClassLoader(); jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); ... } public RestTemplate() { // 存在的默認內置 HttpMessageConverter以及第三方實現 按順序裝入 messageConverters this.messageConverters.add(new ByteArrayHttpMessageConverter()); if (jackson2XmlPresent) { this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); } if(..){...} .... } public RestTemplate(ClientHttpRequestFactory requestFactory) { this(); // 設置 requestFactory 適配器進行 http 請求 this.setRequestFactory(requestFactory); } @Override @Nullable public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { // 用指望 返回對象.class 初始 RequestCallback 對象, 響應後的用於反序列化 RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); // 根據 HttpMessageConverter 初始化 HttpMessageConverterExtractor 用來處理拿到響應後的反序列化策略 HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); // 調用抽象執行層 return execute(url, HttpMethod.GET, requestCallback, responseExtractor,uriVariables); } @Override @Nullable public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException { // 將 URL 與 動態參數 拼裝爲 真實URL URI expanded = getUriTemplateHandler().expand(url, uriVariables); return doExecute(expanded, method, requestCallback, responseExtractor); } @Nullable protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { // URL 與 method 不可爲空 Assert.notNull(url, "URI is required"); Assert.notNull(method, "HttpMethod is required"); ClientHttpResponse response = null; try { // 建立通用 ClientHttpRequestFactory 請求對象 ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { // 給請求頭 Accept 設置 可序列化的 HttpMessageConverter.MediaType 策略 requestCallback.doWithRequest(request); } // 執行請求攔截器鏈並使用 ClientHttpRequestFactory 適配實現類發送請求, 獲取 響應文本報文 response = request.execute(); // 處理給定的響應,執行適當的日誌記錄並調用 ResponseErrorHandler 處理異常 handleResponse(url, method, response); // 使用 extractData() 將文本數據按 messageConverters匹配順序反序列化爲 指望返回對象 return (responseExtractor != null ? responseExtractor.extractData(response) : null); } catch (IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex); } finally { if (response != null) { // 由於當前 response 對象是 接口因此沒法使用 jdk7自動關閉流, 需手動關閉 response.close(); } } } public void doWithRequest(ClientHttpRequest request) throws IOException { if (this.responseType != null) { List<MediaType> allSupportedMediaTypes = (List)RestTemplate.this.getMessageConverters().stream().filter((converter) -> { return this.canReadResponse(this.responseType, converter); }).flatMap(this::getSupportedMediaTypes).distinct().sorted(MediaType.SPECIFICITY_COMPARATOR).collect(Collectors.toList()); // debug 模式 打印日誌 if (RestTemplate.this.logger.isDebugEnabled()) { RestTemplate.this.logger.debug("Accept=" + allSupportedMediaTypes); } // 給請求頭 Accept 設置 可序列化的 HttpMessageConverter.MediaType 策略 request.getHeaders().setAccept(allSupportedMediaTypes); } } } // 其實能夠看出來 源碼閱讀不是很難, 讀者朋友們之後能夠和麪試官吹你看過 spring http 的核心源碼了
TODO 發表後一週內更新
深刻淺出分享 Java 乾貨 , 找回對代碼的 Passion , 助力月入 20K+