正文
Zookeeper---作为服务注册中心
小程序:扫一扫查出行
【扫一扫了解最新限行尾号】
复制小程序
【扫一扫了解最新限行尾号】
复制小程序
认识Zookeeper是一套分布式协调服务。
优点:
简单:与文件系统类似,Znode的组织方式。
多副本:一般再线上都是三副本或者五副本的形式,最少会有三个节点。
有序:有序的操作,根据时间戳进行排序。
快:读多写少的情况下比较快。
在Spring cloud 中使用Zookeeper最为服务注册中心。
在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中声明
#告诉服务器找到哪一个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=alwaysspring.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);
}
}