認識Zookeeper是一套分佈式協調服務。java
優勢: node
簡單:與文件系統相似,Znode的組織方式。web
多副本:通常再線上都是三副本或者五副本的形式,最少會有三個節點。spring
有序:有序的操做,根據時間戳進行排序。docker
快:讀多寫少的狀況下比較快。apache
在Spring cloud 中使用Zookeeper最爲服務註冊中心。bash
在pom引入spring-cloud-starter-zookeeper-discovery服務器
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency>
<!--dependencyManagement中加入spring cloud dependency-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
在application.properties中聲明app
#告訴服務器找到哪個zookeeper做爲服務註冊中心,做爲例子,在本地啓動了一個2181,在生產中最少使用三副本
spring.cloud.zookeeper.connect-string=localhost:2181
在bootstarp.properties 中定義分佈式
spring.application.name=waiter-service
開啓DiscoveryClient:@EnableDiscoveryClient
使用zookeeper做爲註冊中心的問題
《阿里巴巴爲何不用Zookeeper作服務發現》https://yq.aliyun.com/articles/599997
《Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery》.
核心思想
在實踐中,註冊中心不能由於自身的任何緣由破壞服務之間自己的可連通性
註冊中心須要AP,而Zookeeper是CP
CAP:一致性,可用性,分區容忍性
經過Docker啓動Zookeeper
官方指引
https://hub.docker.com/_/zookeeper
獲取鏡像
docker pull zookeeper:3.5
運行Zookeeper鏡像
docker run --name zookeeper -p 2181:2181 -d zookeeper:3.5
查看zookeeper日誌: docker logs zookeeper
進入zookeeper容器 : docker exec -it zookeeper bash
鏈接zookeeper:./zkCli.sh命令行鏈接
ls ls [-s] [-w] [-R] path [zk: localhost:2181(CONNECTED) 1] ls / [services, zookeeper] [zk: localhost:2181(CONNECTED) 2] ls /services [waiter-service] [zk: localhost:2181(CONNECTED) 3] ls /services/waiter-service [cb8e9631-f020-4cfa-9236-73025dc04a4d] [zk: localhost:2181(CONNECTED) 4] ls /services/waiter-service/cb8e9631-f020-4cfa-9236-73025dc04a4d [] [zk: localhost:2181(CONNECTED) 5] get /services/waiter-service/cb8e9631-f020-4cfa-9236-73025dc04a4d {"name":"waiter-service","id":"cb8e9631-f020-4cfa-9236-73025dc04a4d","address":"DESKTOP-JMA2LS2","port":55825,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"waiter-service-1","name":"waiter-service","metadata":{}},"registrationTimeUTC":1570436655426,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
使用customer來發現zookeeper中註冊的writer-service服務
pom應用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</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>
在application.properties中定義
server.port=0 #顯示全部信息 management.endpoint.health.show-details=always spring.cloud.zookeeper.connect-string=localhost:2181
bootstarp.properties
spring.application.name=customer-service
開啓DiscoveryClient: @EnableDiscoveryClient
注入:HttpComponentsClientHttpRequestFactory 以及RestTemplate
@Bean public HttpComponentsClientHttpRequestFactory requestFactory() { PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS); connectionManager.setMaxTotal(200); connectionManager.setDefaultMaxPerRoute(20); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(connectionManager) .evictIdleConnections(30, TimeUnit.SECONDS) .disableAutomaticRetries() // 有 Keep-Alive 認裏面的值,沒有的話永久有效 //.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE) // 換成自定義的 .setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy()) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); return requestFactory; }
@LoadBalanced @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .setConnectTimeout(Duration.ofMillis(100)) .setReadTimeout(Duration.ofMillis(500)) .requestFactory(this::requestFactory) .build(); }
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.http.HttpResponse; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import java.util.Arrays; public class CustomConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { private final long DEFAULT_SECONDS = 30; @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE)) .stream() .filter(h -> StringUtils.equalsIgnoreCase(h.getName(), "timeout") && StringUtils.isNumeric(h.getValue())) .findFirst() .map(h -> NumberUtils.toLong(h.getValue(), DEFAULT_SECONDS)) .orElse(DEFAULT_SECONDS) * 1000; } }
import geektime.spring.springbucks.customer.model.Coffee; import geektime.spring.springbucks.customer.model.CoffeeOrder; import geektime.spring.springbucks.customer.model.NewOrderRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.util.Arrays; import java.util.List; @Component @Slf4j public class CustomerRunner implements ApplicationRunner { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @Override public void run(ApplicationArguments args) throws Exception { showServiceInstances(); readMenu(); Long id = orderCoffee(); queryOrder(id); } /** * 打印discoveryClient信息 * */ private void showServiceInstances() { log.info("DiscoveryClient: {}", discoveryClient.getClass().getName()); discoveryClient.getInstances("waiter-service").forEach(s -> { log.info("Host: {}, Port: {}", s.getHost(), s.getPort()); }); } private void readMenu() { ParameterizedTypeReference<List<Coffee>> ptr = new ParameterizedTypeReference<List<Coffee>>() {}; ResponseEntity<List<Coffee>> list = restTemplate .exchange("http://waiter-service/coffee/", HttpMethod.GET, null, ptr); list.getBody().forEach(c -> log.info("Coffee: {}", c)); } private Long orderCoffee() { NewOrderRequest orderRequest = NewOrderRequest.builder() .customer("Li Lei") .items(Arrays.asList("capuccino")) .build(); RequestEntity<NewOrderRequest> request = RequestEntity .post(UriComponentsBuilder.fromUriString("http://waiter-service/order/").build().toUri()) .body(orderRequest); ResponseEntity<CoffeeOrder> response = restTemplate.exchange(request, CoffeeOrder.class); log.info("Order Request Status Code: {}", response.getStatusCode()); Long id = response.getBody().getId(); log.info("Order ID: {}", id); return id; } private void queryOrder(Long id) { CoffeeOrder order = restTemplate .getForObject("http://waiter-service/order/{id}", CoffeeOrder.class, id); log.info("Order: {}", order); } }