认识Zookeeper是一套分布式协调服务。
优点:
简单:与文件系统类似,Znode的组织方式。
多副本:一般再线上都是三副本或者五副本的形式,最少会有三个节点。
有序:有序的操作,根据时间戳进行排序。
快:读多写少的情况下比较快。
在Spring cloud 中使用Zookeeper最为服务注册中心。
在pom引入spring-cloud-starter-zookeeper-discovery
org.springframework.cloud spring-cloud-starter-zookeeper-discovery
org.springframework.cloud
spring-cloud-dependencies
Greenwich.SR1
pom
import
在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应用
org.springframework.cloud spring-cloud-starter-zookeeper-discovery org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import
在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> ptr = new ParameterizedTypeReference
>() {}; ResponseEntity
> 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
request = RequestEntity .post(UriComponentsBuilder.fromUriString("http://waiter-service/order/").build().toUri()) .body(orderRequest); ResponseEntity 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); } }