(031)Spring Boot之服务的注册与发现,使用zookeeper演示负载均衡
1、相关知识
先说一下两种负载均衡的方式,一种是静态的,例如使用nginx,需要把服务端配置到nginx里,当增删节点时手动维护。另一种是动态的,当服务启动时动态的将服务注册到注册中心,一般注册中心保存的是服务的IP、端口,调用方只需知道注册中心的IP、端口、服务名,就能获取到服务的IP、端口信息。常用zookeeper、consul,etcd、redis等实现注册中心。下面使用zookeeper演示一下服务的注册与发现及一个简单的负载均衡。
2、准备工作
注册中心使用的是zookeeper-3.4.6,参考我的这篇博客:https://www.cnblogs.com/javasl/p/12044446.html
服务提供方和服务调用方工程,参考我的这篇博客:https://www.cnblogs.com/javasl/p/11966678.html
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,报错。