已经接触过的 Spring Cloud 类
DiscoveryClient
- EurekaDiscoveryClient
- ZookeeperDiscoveryClient
- ConsulDiscoveryClient
- NacosDiscoveryClient
LoadBalancerClient
- RibbonLoadBalancerClient
实现自己的 DiscoveryClient
需要做的为以下4点:
- 返回该 DiscoveryClient 能提供的完整的服务名列表
- 返回指定服务对应的 ServiceInstance 列表
- 返回 DiscoveryClient 的顺序,一般不更改
- 返回 HealthIndicator 做健康检查里显示的描述
实现自己的 RibbonClient 支持
需要做的:
- 实现自己的 ServerList<T extends Server>
• Ribbon 提供了了 AbstractServerList<T extends Server>
直接继承 AbstractServerList - 提供一个配置类,声明 ServerList Bean 实例,Ribbon 就能去找到它,在后面去做服务的初始化和更新的时候 就会调用 ServerList Bean
例子
由两部分构成:fixed-discovery-client-demo与waiter-service
fixed-discovery-client-demo
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
bootstrap.properties
spring.application.name=customer-service
application.yml
server.port: 8090
waiter:
services:
- localhost:8080
# - localhost:8081
# 配置一个列表 还可以再加上 - localhost:8081 之类的地址 加上其他地址之后 需要 对CustomerRunner进行循环处理 否则报错
Coffee
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Coffee implements Serializable {
private Long id;
private String name;
private Money price;
private Date createTime;
private Date updateTime;
}
CoffeeOrder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CoffeeOrder {
private Long id;
private String customer;
private List<Coffee> items;
private OrderState state;
private Date createTime;
private Date updateTime;
}
NewOrderRequest
@Builder
@Getter
@Setter
public class NewOrderRequest {
private String customer;
private List<String> items;
}
OrderState
public enum OrderState {
INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
}
CustomConnectionKeepAliveStrategy
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;
}
}
FixedDiscoveryClient
@ConfigurationProperties("waiter")
//开启 application.yml 中 waiter 的设置项
@Setter
public class FixedDiscoveryClient implements DiscoveryClient {
public static final String SERVICE_ID = "waiter-service";
// 加载 列表 waiter.services 里面的每一行都代表是我的一个waiterservices
private List<String> services;
@Override
public String description() {
return "DiscoveryClient that uses service.list from application.yml.";
}
@Override
public List<ServiceInstance> getInstances(String serviceId) {
if (!SERVICE_ID.equalsIgnoreCase(serviceId)) { //判断是否是 waiter-service
return Collections.emptyList();//返回空list
}
// 这里忽略了很多边界条件判断,认为就是 HOST:PORT 形式
return services.stream()
.map(s -> new DefaultServiceInstance(s,
SERVICE_ID,
s.split(":")[0],
Integer.parseInt(s.split(":")[1]),
false)).collect(Collectors.toList());//如果是 根据services 做一个处理 将list中每一个条目都取出来 用它去构建DefaultServiceInstance这个实例 通过map 将字符串转成一个 DefaultServiceInstance 对象 将它处理为一个list 并返回
}
@Override
public List<String> getServices() {
return Collections.singletonList(SERVICE_ID);//因为 只有 SERVICE_ID 所以就不处理 直接返回
}
}
FixedServerList
public class FixedServerList implements ServerList<Server> {
@Autowired
private FixedDiscoveryClient discoveryClient;
@Override
public List<Server> getInitialListOfServers() {
return getServers();
}
@Override
public List<Server> getUpdatedListOfServers() {
return getServers();
}
private List<Server> getServers() {
return discoveryClient.getInstances(FixedDiscoveryClient.SERVICE_ID).stream()
.map(i -> new Server(i.getHost(), i.getPort()))
.collect(Collectors.toList());//取出 SERVICE_ID 的全部实例
}
}
MoneyDeserializer
@JsonComponent
public class MoneyDeserializer extends StdDeserializer<Money> {
protected MoneyDeserializer() {
super(Money.class);
}
@Override
public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Money.of(CurrencyUnit.of("CNY"), p.getDecimalValue());
}
}
MoneySerializer
@JsonComponent
public class MoneySerializer extends StdSerializer<Money> {
protected MoneySerializer() {
super(Money.class);
}
@Override
public void serialize(Money money, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(money.getAmount());
}
}
CustomerRunner
@Component
@Slf4j
public class CustomerRunner implements ApplicationRunner {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private DiscoveryClient fixedDiscoveryClient;
@Override
public void run(ApplicationArguments args) throws Exception {
showServiceInstances();
readMenu();
Long id = orderCoffee();
queryOrder(id);
}
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 123")
.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);
}
}
CustomerServiceApplication
@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
public class CustomerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerServiceApplication.class, args);
}
@Bean //注入bean
public DiscoveryClient fixedDiscoveryClient() {
return new FixedDiscoveryClient();
}
@Bean
public FixedServerList fixedServerList() {
return new FixedServerList();
}
@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();
}
}
waiter-service
引至hhttps://blog.csdn.net/weixin_43790623/article/details/103654374 中的springbucks
结果分析
customer成功访问并录入数据
在application.yml中 如果 我们加入多个services地址 需要在CustomerRunner对地址进行循环处理 否则报错
我们在getServers处打个断点,可以看到,在创建ZoneAwareLoadBalancer的时候 会去调用getUpdatedListOfServers,然后进入getServers