RPC框架流程图
Demo结构
连接Zookeeper
public class ZkConnect {
//地址 ip:port
private String zkServer;
//会话超时时间
private int sessionTimeout;
public ZkConnect() {
super();
//设置默认值
this.zkServer="localhost:2181";
this.sessionTimeout=10000;
}
public ZkConnect(String zkServer, int sessionTimeout) {
this.zkServer = zkServer;
this.sessionTimeout = sessionTimeout;
}
public ZooKeeper getZk() throws Exception{
return new ZooKeeper(zkServer, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("watch方法在执行中");
}
});
}
}
建立注册中心
功能:(1)将服务注册到注册中心,并且保存到Zookeeper上。(2)客户端调用时,可以从Zookeeper中获得服务的地址、名称,在注册中心找到该服务并调用服务。
/**
* 注册工具
* zk连接 remote接口实现 完成rmi拼接
*/
@Data
public class RpcRegistry {
private ZkConnect zkConnect;
private String ip;
private int port;
/**
* 注册服务
* 服务接口类【remote接口的子接口】 服务实现类【实现服务接口类】
* 访问地址存在zk中
*/
@SneakyThrows
public void registryService(Class<? extends Remote> serviceInterface, Remote serviceObject){
String rmi="rmi://"+ip+":"+port+"/"+serviceInterface.getName();
//zk节点名
String path="/rpc/"+serviceInterface.getName();
List<String> children = zkConnect.getZk().getChildren("/rpc", false);
if (children.contains(serviceInterface.getName())){
//节点存在需要删除
Stat stat = new Stat();
zkConnect.getZk().getData(path,false,stat);
zkConnect.getZk().delete(path,stat.getCversion());
}
zkConnect.getZk().create(path,rmi.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//注册服务
Naming.rebind(rmi,serviceObject);
System.out.println("注册成功:"+rmi);
}
/**
* 查询、发现服务 根据接口类型 访问zk 获得rmi远程代理对象
* 1.拼接zk节点名称
* 2.访问zk的数据
* 3.根据2的数据创建代理对象
* @return
*/
public <T extends Remote> T getService(Class<? extends Remote> serviceInterface) throws Exception {
String path="/rpc/"+serviceInterface.getName();
//节点获得的数据是二进制数组,需要转换为String
byte[] data = zkConnect.getZk().getData(path, false, null);
String rmi = new String(data);
System.out.println("rmi:"+rmi);
//在注册中心找该服务
Object obj = Naming.lookup(rmi);
return (T)obj;
}
}
框架入口
factory需要初始化Zookeeper,建立Zookeeper连接。初始化注册中心,创建RMI注册器
LocateRegistry.createRegistry(serverPort);
静态的registryService和getProxy方法,是用于快速创建注册服务的。
public class RpcFactory {
private static final Properties config=new Properties();
private static final ZkConnect zkConnect;
private static final RpcRegistry registry;
//用于读取初始化的配置对象
private static final Properties services=new Properties();
/**
* 初始化,提供配置文件rpc.properties
* 文件:registry.ip=
* registry.port=
* zk.server=zk访问地址
* zk.session=
*/
static {
try {
InputStream inputStream = RpcFactory.class.getClassLoader().getResourceAsStream("rpc.properties");
//读取配置文件
config.load(inputStream);
String serverIP=config.getProperty("registry.ip")==null?"localhost":config.getProperty("registry.ip");
int serverPort=config.getProperty("registry.port")==null?9090:Integer.parseInt(config.getProperty("registry.port"));
String serverZk=config.getProperty("zk.server")==null?"localhost:2181":config.getProperty("zk.server");
int serverZkSession=config.getProperty("zk.session")==null?10000:Integer.parseInt(config.getProperty("zk.session"));
//创建连接对象
zkConnect = new ZkConnect(serverZk, serverZkSession);
//创建注册器对象
registry=new RpcRegistry();
registry.setIp(serverIP);
registry.setPort(serverPort);
registry.setZkConnect(zkConnect);
//创建RMI注册器
LocateRegistry.createRegistry(serverPort);
//初始化zk中的父节点
List<String> children = zkConnect.getZk().getChildren("/", false);
if(!children.contains("rpc")){
zkConnect.getZk().create("/rpc",null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//判断在classpath下是否有 rpc-services.properties【如果有多个服务,可以遍历】
//接口=实现类
InputStream servicesInput=RpcFactory.class.getClassLoader().getResourceAsStream("rpc-services.properties");
if (servicesInput!=null){
services.load(servicesInput);
//遍历services
for (Object key:services.keySet()){
Object value=services.get(key);
//key是接口的名字 value是实现类的名字
Class<Remote> serviceInterface=(Class<Remote>)Class.forName(key.toString());
Remote serviceObject=(Remote)Class.forName(value.toString()) .newInstance();
//注册接口对象、服务对象
registry.registryService(serviceInterface,serviceObject);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new ExceptionInInitializerError(e);
}
}
//快速创建注册服务和创建客户端的静态工具方法
public static void registryService(Class<? extends Remote> serviceInterface, Remote serviceObject){
registry.registryService(serviceInterface,serviceObject);
}
public static <T extends Remote> T getProxy(Class<? extends Remote> serviceInterface) throws Exception{
return registry.getService(serviceInterface);
}
}
上面的注册多个接口的rpc-service.properties文件可以见文末补充
Demo 调用RPC框架
结构
Entity类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private int id;
private String last_name;
private String email;
private int gender;
private int age;
}
Api接口
需要被客户端调用的接口
public interface UserService extends Remote {
//模糊查询
List<User> getUserByName(String name) throws RemoteException;
}
服务端
1、Mapper
@Mapper
public interface UserMapper {
@Select("SELECT * FROM tbl_employee WHERE last_name LIKE concat('%',#{name},'%')")
public List<User> selectByName(String name);
}
2、接口的impl
/**
* 服务对象,只要spring容器管理即可
* 要用自定义框架,把当前对象和实现接口信息
* 通过FactoryRpc注册到zk中
*/
@Service
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
//注册该接口到注册中心
public UserServiceImpl() throws RemoteException{
System.out.println("UserServiceImpl对象创建");
RpcFactory.registryService(UserService.class,this);
System.out.println("注册服务:UserServiceImpl");
}
@Autowired
private UserMapper userMapper;
@Override
public List<User> getUserByName(String name) throws RemoteException {
return userMapper.selectByName(name);
}
}
3、rpc.properties
zk.server=你的IP:2181
#注册中心的端口
registry.port=9091
客户端
1、Controller层调用接口UserService
@Controller
public class UserController {
private UserService userService;
//在注册中心找userService
public UserController() throws Exception {
this.userService= RpcFactory.getProxy(UserService.class);
}
@RequestMapping("/getByName")
@ResponseBody
public List<User> getUserByName(String name) {
try {
List<User> users = userService.getUserByName(name);
return users;
} catch (RemoteException e) {
e.printStackTrace();
return new ArrayList<>();
}
}
}
RPC框架的service不是用到autowired注入,而是调用factory中的getproxy()方法,在注册中心找到。
2、rpc.properties
zk.server=你的IP:2181
补充:
如果要注册多个接口,可以添加rpc-service.properties
com.jane.service.UserService=com.jane.impl.UserServiceImpl
com.jane.service.CustomerService=com.jane.impl.CustomerServiceImpl
1、服务端注册时,执行
Class.forName("com.jane.RpcFactory");
2、客户端调用服务时,同上的Controller层调用方法一致
该框架要先启动服务端,后启动客户端