1、相关知识
先说一下两种负载均衡的方式,一种是静态的,例如使用nginx,需要把服务端配置到nginx里,当增删节点时手动维护。另一种是动态的,当服务启动时动态的将服务注册到注册中心,一般注册中心保存的是服务的IP、端口,调用方只需知道注册中心的IP、端口、服务名,就能获取到服务的IP、端口信息。常用zookeeper、consul,etcd、redis等实现注册中心。下面使用zookeeper演示一下服务的注册与发现及一个简单的负载均衡。
2、准备工作
注册中心使用的是zookeeper-3.4.6,参考我的这篇博客:
服务提供方和服务调用方工程,参考我的这篇博客:
3、服务注册
修改mall-product的pom.xml文件,添加服务注册依赖(2.11.0对应zookeeper-3.4.6):
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery-server</artifactId>
<version>2.11.0</version>
</dependency>
修改配置文件application.properties,添加zookeeper的地址、端口
zookeeper.address=192.168.7.151:2181
新建注册类com.edu.spring.mall.product/ServiceRegister.java
package com.edu.spring.mall.product;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class ServiceRegister implements ApplicationRunner{
@Value("${zookeeper.address}")
private String zkAddress;
public void run(ApplicationArguments args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient(zkAddress, new RetryOneTime(1000));
client.start();
client.blockUntilConnected();
ServiceInstance<Object> instance = ServiceInstance.builder().name("product").address("192.168.7.103").port(8080).build();
ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client)
.basePath("/soa").build();
serviceDiscovery.registerService(instance);
serviceDiscovery.start();
System.out.println("service register ok");
}
}
服务名是product;本服务的地址端口分别是192.168.7.103、8080;zookeeper中保存文件路径是/soa。
启动zookeeper集群,查看目录
启动mall-product服务,再次查看目录,自动创建了/soa/product目录,注册了服务的IP、端口信息
服务注册成功,保存文件路径为:java中定义的基础路径+服务名
4、服务发现
修改mall-web的pom.xml文件,添加服务发现依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>2.11.0</version>
</dependency>
新建测试类com.edu.spring.web/Client.java
package com.edu.spring.web;
import java.util.Collection;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.web.client.RestTemplate;
public class Client {
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.7.151:2181", new RetryOneTime(1000));
client.start();
client.blockUntilConnected();
ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client)
.basePath("/soa").build();
Collection<ServiceInstance<Object>> list = serviceDiscovery.queryForInstances("product");
list.forEach((instance)->{
String servicePath = instance.getAddress()+":"+instance.getPort();
RestTemplate res=new RestTemplate();
String body= res.getForObject("http://"+servicePath+"/soa/product/20",String.class);
System.out.println("调用服务:"+servicePath);
System.out.println(body);
});
}
}
运行输出结果如下,服务发现成功:
5、负载均衡
复制一份mall-product命名为mall-product2,代表另一个服务。
修改配置文件application.properties,添加项目端口号
server.port=9090
修改注册类com.edu.spring.mall.product/ServiceRegister.java,端口号改为9090
ServiceInstance<Object> instance = ServiceInstance.builder().name("product").address("192.168.7.103").port(9090).build();
启动mall-product、mall-product2,并且查看zookeeper,发现多了一个服务。
运行客户端程序,发现两个服务都被调用了一次。
下面实现一个简单的负载均衡
在客户端新建com.edu.spring.web/LoadBalance.java
package com.edu.spring.web;
import java.util.List;
public class LoadBalance {
private int index = 0;
private List<String> services;
public LoadBalance(List<String> services) {
this.services = services;
}
public String choose() {
String service = services.get(index);
index++;
if(index >= services.size()) {
index = 0;
}
return service;
}
}
修改测试类com.edu.spring.web/Client.java,调用10次服务方。
package com.edu.spring.web;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.springframework.web.client.RestTemplate;
public class Client {
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.7.151:2181", new RetryOneTime(1000));
client.start();
client.blockUntilConnected();
ServiceDiscovery<Object> serviceDiscovery = ServiceDiscoveryBuilder.builder(Object.class).client(client)
.basePath("/soa").build();
Collection<ServiceInstance<Object>> list = serviceDiscovery.queryForInstances("product");
List<String> serviceList = new ArrayList<String>();
list.forEach((instance)->{
serviceList.add(instance.getAddress()+":"+instance.getPort());
});
LoadBalance lb = new LoadBalance(serviceList);
for(int i=0;i<10;i++) {
RestTemplate res=new RestTemplate();
String servicePath=lb.choose();
String body= res.getForObject("http://"+servicePath+"/soa/product/20",String.class);
System.out.println("调用服务:"+servicePath);
System.out.println(body);
}
}
}
输出结果如下,轮询负载成功:
假如停掉9090服务,zookeeper中会自动清除掉这个服务节点,此时在运行客户端,只能调用8080服务了
注:停止掉9090服务后,zookeeper不会马上清除掉该服务节点,有延迟(5s左右),这段时间运行客户端会无法调用9090,报错。