本项目所有代码可见:https://github.com/weiyu-zeng/SimpleRPC
前言
负载均衡(Load Balancing)是在多台服务器之间分配网络负载,以提高系统的整体性能、可用性和可靠性,避免任何单一资源过载,确保所有资源能够有效地处理请求,并提供一致的服务质量。
在simpleRPC-07中,我们为注册的服务接口提供一个负载均衡模块,
注:负载均衡的特点:
- 分散流量:负载均衡器接收来自客户端的请求,并根据预定义的策略将这些请求分散到后端的一组服务器上。这可以防止任何单个服务器因请求过多而变得过载。
- 高可用性:通过在多台服务器之间分配负载,负载均衡可以提高系统的可用性。即使某一台服务器出现故障或需要维护,其他服务器仍然可以继续处理请求,确保服务不会中断。
- 性能优化:负载均衡可以通过识别并路由请求到当前最可用或性能最佳的服务器来提高系统的整体性能。这可以减少响应时间并提高用户满意度。
- 灵活扩展:负载均衡使得系统能够轻松地添加或移除服务器,以适应不断变化的负载需求。这提供了水平扩展的能力,无需对现有架构进行大规模修改。
- 健康检查:负载均衡器通常会定期执行健康检查,以确定后端服务器是否能够正常处理请求。如果发现某个服务器出现问题,负载均衡器可以自动将其从服务中移除,直到问题得到解决。
- 多种算法:负载均衡器可以使用不同的算法来决定如何分配请求。常见的算法包括轮询、最少连接数、IP哈希、权重分配等。
- 安全性增强:负载均衡器还可以提供额外的安全层,例如SSL卸载、DDoS防护、访问控制等,帮助保护系统免受攻击和滥用。
负载均衡广泛应用于各种规模的网络环境,包括Web服务器集群、数据库集群、云计算平台、内容分发网络(CDN)等。通过有效的负载均衡策略,组织可以提高服务质量、降低运营成本,并为用户提供更稳定、更快速的在线体验。
实现
zookeeper
在simpleRPC-06时我们安装了zookeeper
这里我们还要把zookeeper打开:
启动成功之后,我们再去开客户端:
点回车,输入 ls /
:
我们之前的MyRPC还在,ok的,继续写代码
项目创建
创建名为simpleRPC-07的module,然后在java下创建com.rpc的package
然后我们回到simpleRPC-06,把com.rpc中的所有内容,复制一下:
然后回到simpleRPC-07中的com.rpc,粘贴过来:
然后记得把log4j的配置文件也复制过来:
依赖配置
依赖配置跟simpleRPC-06也是一样的:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SimpleRPC</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simpleRPC-07</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.51.Final</version>
</dependency>
<!-- 阿里的fastjson序列化框架 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
</dependency>
<!--这个jar包应该依赖log4j,不引入log4j会有控制台会有warn,但不影响正常使用-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
</project>
到此为止,simpleRPC-07的内容和simpleRPC-06是一样的了。
记得reload一下maven,我一般喜欢这样:
loadbalance
创建一个loadbalance 的package:
我们定义负载均衡接口:LoadBalance.java:
package com.rpc.loadbalance;
import java.util.List;
public interface LoadBalance {
String balance(List<String> addressList);
}
随机负载均衡(Random Load Balancing)是一种将请求随机分配给后端服务器的负载均衡策略,负载均衡器在接收到每个新的请求时,会从服务器列表中随机选择一台服务器来处理请求。
我们编写随机负载均衡:
RandomLoadBalance.java
package com.rpc.loadbalance;
import java.util.List;
import java.util.Random;
/**
* 随机负载均衡
*/
public class RandomLoadBalance implements LoadBalance {
@Override
public String balance(List<String> addressList) {
Random random = new Random();
int choose = random.nextInt(addressList.size());
System.out.println("负载均衡选择了" + choose + "服务器");
return addressList.get(choose);
}
}
轮询负载均衡(Round Robin Load Balancing)是一种简单且常用的负载均衡策略,负载均衡器会将接收到的请求按照顺序依次分配给后端服务器列表中的每一台服务器。
我们编写轮询负载均衡:
RoundLoadBalance.java
package com.rpc.loadbalance;
import java.util.List;
/**
* 轮询负载均衡
*/
public class RoundLoadBalance implements LoadBalance{
private int choose = -1;
@Override
public String balance(List<String> addressList) {
choose++;
choose = choose % addressList.size(); // 索引
System.out.println("负载均衡选择了" + choose + "服务器");
return addressList.get(choose); //
}
}
register
ZkServiceRegister.java
package com.rpc.register;
import com.rpc.loadbalance.LoadBalance;
import com.rpc.loadbalance.RoundLoadBalance;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import java.net.InetSocketAddress;
import java.util.List;
/**
* @author weiyu_zeng
*/
public class ZkServiceRegister implements ServiceRegister {
// curator 提供的zookeeper客户端
private CuratorFramework client;
// zookeeper根路径结点
private static final String ROOT_PATH = "MyRPC";
// 初始化负载均衡器, 这里用的是随机, 一般通过构造函数传入
private LoadBalance loadBalance = new RoundLoadBalance();
// 构造方法
// 这里负责zookeeper客户端的初始化,并与zookeeper服务端建立连接。
// 初始化包括指定重连策略,指定连接zookeeper的端口,指定超时时间,指定命名空间
// 初始化完成之后start()开启zookeeper客户端。
public ZkServiceRegister() {
// 重连策略:指数时间重试
RetryPolicy policy = new ExponentialBackoffRetry(1000, 3);
// zookeeper的地址固定,不管是服务提供者还是消费者,都要与之建立连接
// sessionTimeoutMs 与 zoo.cfg中的tickTime 有关系,
// zk还会根据minSessionTimeout与maxSessionTimeout两个参数重新调整最后的超时值。默认分别为tickTime 的2倍和20倍
// 使用心跳监听状态
this.client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
.sessionTimeoutMs(40000)
.retryPolicy(policy)
.namespace(ROOT_PATH)
.build();
this.client.start();
System.out.println("zookeeper 连接成功");
}
// 注册:传入服务方法名(String),传入主机名和端口号的套接字地址(InetSocketAddress)
@Override
public void register(String serviceName, InetSocketAddress serverAddress) {
try {
// serviceName创建成永久节点,服务提供者下线时,不删服务名,只删地址
Stat stat = client.checkExists().forPath("/" + serviceName);
if (stat == null) {
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath("/" + serviceName);
}
// 路径地址,一个/代表一个节点
String path = "/" + serviceName + "/" + getServiceAddress(serverAddress);
// 临时节点,服务器下线就删除节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (Exception e) {
System.out.println("此服务已存在");
}
}
// 根据服务名返回地址
@Override
public InetSocketAddress serviceDiscovery(String serviceName) {
try {
List<String> strings = client.getChildren().forPath("/" + serviceName);
// 负载均衡选择器,选择一个
String string = loadBalance.balance(strings);
// String string = strings.get(0);
return parseAddress(string);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 地址 -> XXX.XXX.XXX.XXX:port 字符串
private String getServiceAddress(InetSocketAddress serverAddress) {
return serverAddress.getHostName() + ":" + serverAddress.getPort();
}
// 字符串解析为地址:按照":"切分开,前半是host(String),后半是port(int)
private InetSocketAddress parseAddress(String address) {
String[] result = address.split(":");
return new InetSocketAddress(result[0], Integer.parseInt(result[1]));
}
}
client
和simpleRPC-06 一样
codec
和simpleRPC-06 一样
common
和simpleRPC-06 一样
service
和simpleRPC-06 一样
server
和simpleRPC-06 一样
文件结构
文件结构如下:
运行
先确认一下zookeeper server还在开启,否则可能卡在这个地方
zookeeper 连接成功
先运行TestServer.java:
我们在simpleRPC-06中已经注册了服务,所以显示User 和 Blog的两个服务已存在。
然后运行TestClient.java: