通过zookeeper实现一个简易版的dubbo(RPC框架)

1. 项目常见小提示


zookeeper部署集群一般是基数(3,5,7等),目的节约资源。

常用查询端口占用情况的dos命令:

  • netstat -ano | findstr 8080

执行zookeeper的get时,除了能看到值,还能看到很多对应的属性等等:
在这里插入图片描述


zookeeper对应的命令:
在这里插入图片描述

2. zookeeper 分布式锁的效果


多个客户端可以向zookeeper里面存有序信息,这样可以从里面取出最小的来作为leader执行代码,达到一个分布式锁的一个效果。

3. Java操作zookeeper


导入依赖:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.8.0</version>
</dependency>

客户端类的相关操作:

package client;

import java.util.List;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class Client1 {
    
    
	
	public static void main(String[] args) throws Exception {
    
    
//		CountDownLatch c = new CountDownLatch(1);
//		ZooKeeperWatcher zw= new ZooKeeperWatcher();
		//${__setProperty(Token,${token_1},)}
//		${__property(Token)}
//		wx/goods/detail
//		id	1181045	false	text/plain	true
		W1 w1 = new W1();
//		
		ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 10000, w1);
//		
		w1.zk = zk;
		
//		zk.exists("/aa1",	true);
//		zk.getData("/aa1", true, null);
		zk.getChildren("/aa1",	true);
//
		Thread.sleep(2000000);
		
		
		
//		String rs = zk.create("/aa1/a1", "127.0.0.2:8082".getBytes(),Ids.OPEN_ACL_UNSAFE ,CreateMode.EPHEMERAL_SEQUENTIAL );
//		System.out.println(rs+"-----------------");
//		byte [] b= zk.getData("/getOrder/a", true, null);
//		System.out.println(new String(b));
		
//		List<String> children = zk.getChildren("/getOrder",	true);
//		System.out.println(children);
//		for(String p:children) {
    
    
//			
//			byte [] b=zk.getData("/getOrder/"+p, true, null);
//			System.out.println(p+" : "+new String(b));
//		}
//		zw.zk = zk;
//		c.await();
	}

}

监听器watcher对应的代码:

package client;

import java.util.List;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;

public class W1 implements Watcher{
    
    
	
	public ZooKeeper zk = null;

	@Override
	public void process(WatchedEvent event) {
    
    
		

		System.out.println("进入 process 。。。。。event = " + event);

		try {
    
    
			Thread.sleep(200);
		} catch (InterruptedException e) {
    
    
			e.printStackTrace();
		}

		if (event == null) {
    
    
			return;
		}

		// 连接状态
		KeeperState keeperState = event.getState();
		// 事件类型
		EventType eventType = event.getType();
		// 受影响的path
		String path = event.getPath();


		if (KeeperState.SyncConnected == keeperState) {
    
    
			// 成功连接上ZK服务器
			if (EventType.None == eventType) {
    
    
				System.out.println( "成功连接上ZK服务器");
			}
			// 创建节点
			else if (EventType.NodeCreated == eventType) {
    
    
				System.out.println( "节点创建");
				try {
    
    
					zk.exists("/aa1",	true);
//					zk.getChildren("/aa1",	true);
					Thread.sleep(100);
				} catch (Exception e) {
    
    
					e.printStackTrace();
				}
			}
			// 更新节点
			else if (EventType.NodeDataChanged == eventType) {
    
    
				System.out.println( "节点数据更新");
				try {
    
    
//					zk.exists("/aa1",	true);
					zk.getChildren("/aa1",	true);
				} catch (Exception e) {
    
    
					e.printStackTrace();
				}
			}
			// 更新子节点
			else if (EventType.NodeChildrenChanged == eventType) {
    
    
				System.out.println("子节点变更");
				try {
    
    
//					zk.exists("/aa1",	true);
					List<String >children = zk.getChildren("/aa1",	true);
					
					System.out.println(children);
					for(String p:children) {
    
    
						byte [] b=zk.getData("/aa1/"+p, true, null);
						System.out.println(p+" : "+new String(b));
					}
				} catch (Exception e) {
    
    
					e.printStackTrace();
				}
			}
			// 删除节点
			else if (EventType.NodeDeleted == eventType) {
    
    
				try {
    
    
					zk.exists("/aa1",	true);
//					zk.getChildren("/aa1",	true);
				} catch (KeeperException e) {
    
    
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
    
    
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("节点 " + path + " 被删除");
			} else
				;
		} else if (KeeperState.Disconnected == keeperState) {
    
    
			System.out.println("与ZK服务器断开连接");
		} else if (KeeperState.AuthFailed == keeperState) {
    
    
			System.out.println("权限检查失败");
		} else if (KeeperState.Expired == keeperState) {
    
    
			System.out.println("会话失效");
		} else
			;

		System.out.println("--------------------------------------------");

		
	}

}

上面是经常用到的一些代码逻辑!!

4. 环境准备 + 思想流程


一个zookeeper服务器,三台SpringBoot系统(一台consumer服务消费者,两台服务提供者)。

思想流程:

  • 两台提供者启动时,将其的ip:端口,注册到zookeeper目录中,这里注册的时候要用临时目录来注册!
  • 一台消费者,消费者要将watcher对象和zookeeper对象注册到ioc容器当中,通过watcher实时监测zookeeper里面对应提供者目录的数据变化,通过修改servletContext来获取对应的ip等等。
  • 负载均衡的效果,为了方便,使用的是random随机数获取。

5. 服务消费者 构建


zk-consumer01 服务消费者代码如下:


Watcher01类对象:

  • watcher对象。
package com.itholmes.config;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.List;

public class Watcher01 implements Watcher {
    
    

    @Autowired
    ServletContext servletContext;

    public ZooKeeper zk = null;

    @Override
    public void process(WatchedEvent event) {
    
    

        System.out.println("进入process---event = " + event);

        //这里对别后台使用
        List<String> children = new ArrayList<>();

        try {
    
    
            Thread.sleep(200);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        if (event == null){
    
    
            return;
        }
        //连接状态
        Event.KeeperState keeperState = event.getState();
        //事件类型
        Event.EventType eventType = event.getType();
        //受影响的path
        String path = event.getPath();

        if (Event.KeeperState.SyncConnected== keeperState){
    
    
            //成功连接上ZK服务器
            if (Event.EventType.None == eventType){
    
    
                System.out.println("成功连接上ZK服务器");
                //拿到最新的zk数据
                try {
    
    
                    children = zk.getChildren("/provider", true);
                } catch (KeeperException e) {
    
    
                    e.printStackTrace();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            //创建节点
            else if (Event.EventType.NodeCreated == eventType){
    
    
                System.out.println("节点创建");
                try {
    
    
                    //zk.exists(path,true);
                    //zk.getChildren("/aa1",	true);

                    //拿到最新的zk数据
                    children = zk.getChildren("/provider", true);
                    System.out.println("节点创建");
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
            //更新子节点
            else if (Event.EventType.NodeChildrenChanged == eventType){
    
    
                System.out.println("子节点变更");
                try {
    
    
                    //zk.exists(path,true);

                    //拿到最新的zk数据
                    children = zk.getChildren("/provider", true);
                    System.out.println("子节点变更");
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
            //删除节点
            else if (Event.EventType.NodeDeleted == eventType){
    
    
                try {
    
    
                    //zk.exists(path,true);

                    //拿到最新的zk数据
                    children = zk.getChildren("/provider", true);
                    System.out.println("删除节点");
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("节点 " + path + " 被删除");
            }else
                ;

            ArrayList<Object> list_IP = new ArrayList<>();
            for (String child : children) {
    
    
                byte[] data = new byte[0];
                try {
    
    
                    data = zk.getData("/provider/" + child, true, null);
                } catch (KeeperException e) {
    
    
                    e.printStackTrace();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                list_IP.add(new String(data));
            }

            //查看获取到的IP
            for (Object o : list_IP) {
    
    
                System.out.println(o.toString());
            }


            //通过随机数来获取
            //int i = (int)(Math.random() * (size - 1 - 0 + 1) + 0);
            servletContext.setAttribute("zk",list_IP);

        } else if (Event.KeeperState.Disconnected == keeperState) {
    
    
            System.out.println("与ZK服务器断开连接");
        } else if (Event.KeeperState.AuthFailed == keeperState) {
    
    
            System.out.println("权限检查失败");
        } else if (Event.KeeperState.Expired == keeperState) {
    
    
            System.out.println("会话失效");
        } else
            ;

        System.out.println("--------------------------------------------");

    }

}

ZkConfig类:

  • springboot配置类。
package com.itholmes.config;

import org.apache.zookeeper.ZooKeeper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;

@Configuration
public class Zkconfig {
    
    

    @Bean
    public RestTemplate getRestTemplate(){
    
    
        return new RestTemplate();
    }


    @Bean
    public Watcher01 myZkWatcher() {
    
    
        //创建一个watcher
        return new Watcher01();
    }

    @Bean
    public ZooKeeper create(Watcher01 myZkWatcher) throws IOException {
    
    
        System.out.println("准备...");
        ZooKeeper zk = new ZooKeeper("150.158.199.52:2181", 3000,myZkWatcher );
        myZkWatcher.zk = zk;
        System.out.println("连接..");
        return zk;
    }

}

MathRandom类:

  • 随机数工具类。
package com.itholmes.utils;

import java.util.List;

public class MathRandom {
    
    
    public static String getRandomString(List<String> list){
    
    
        int size = list.size();
        int i = (int)(Math.random() * (size - 1 - 0 + 1) + 0);
        return list.get(i);
    }
}

Consumer01类:

  • controller层接口类。
package com.itholmes.controller;

import com.itholmes.utils.MathRandom;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.List;

@RestController
public class Consumer01 {
    
    

    @Resource
    RestTemplate restTemplate;

    @GetMapping("/consumer/pay")
    public String testZk(HttpServletRequest request){
    
    
        ServletContext servletContext = request.getServletContext();
        List<String> zk = (List<String>)servletContext.getAttribute("zk");
        String randomString = MathRandom.getRandomString(zk);
        //我要拿到zookeeper那边的信息,放到这里
        String forObject = restTemplate.getForObject("http://" + randomString + "/provider", String.class);
        return forObject;
    }

}

SpringBoot主启动类:

package com.itholmes;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class ZkComsumer01Main {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ZkComsumer01Main.class,args);
    }
}

6. 服务提供者 构建


zk-consumer8001(项目里面打错了应该是zk-provider8001) 服务消费者代码如下:


MyListener类:

  • listener类,用配置类导入也可以!
package com.itholmes.listener;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyListener implements ServletContextListener {
    
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    
    
        System.out.println("项目启动~~~~");
        try {
    
    
            ZooKeeper zk = new ZooKeeper("150.158.199.52:2181", 10000, null);
            zk.create("/provider/consumer","127.0.0.1:8001".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    
    
        System.out.println("项目销毁~~~");
    }
}

MyServletConfig类

  • springboot配置类。
package com.itholmes.config;

import com.itholmes.listener.MyListener;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyServletConfig {
    
    

    //将自己的servlet添加进容器
    @Bean
    public ServletListenerRegistrationBean getMyListener(){
    
    
        //传入自己的监听器
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(new MyListener());
        return servletListenerRegistrationBean;
    }

}

MyController类:

  • controller层接口类,方便测试。
package com.itholmes.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {
    
    

    @RequestMapping("/provider")
    public String provider(){
    
    
        return "你好,我是8001";
    }

}

ZkConsumer8001Main类:

  • 主启动类。
package com.itholmes;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class ZkConsumer8001Main {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ZkConsumer8001Main.class,args);
    }
}

按照上面方式,在创建一个provider服务提供者,记得修改端口号就可以。

7. 测试


之后就可以启动三台项目和zoo keeper服务器,来进行测试运行,对zookeeper节点的增加删除等修改操作,尝试消费者端这边能否实时更新!
在这里插入图片描述

项目地址:https://gitee.com/it-sherlock/java-design-pattern-summary/tree/master/zookeeper%E5%AE%9E%E7%8E%B0%E7%AE%80%E6%98%93%E7%89%88dubbo%E6%A1%86%E6%9E%B6/zookeeper-project01

8. 存取zookeeper对象的两种方式


因为,zookeeper对象的watcher , 每个客户端都需要进行心跳检测,所以zookeeper对象必须一直存在。

  • 第一种:将其放到ioc容器里面
  • 第二种:放到servletContext.setAttribute(key,value)里面,这里的value是object类型,因此也会一直存在!

9. 可以使用Spring Boot Runner启动器 来项目启动就执行一些特定代码


如果你想在Spring Boot启动的时候运行一些特定的代码,你可以实现接口 ApplicationRunner或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个run方法。

在这里插入图片描述
在这里插入图片描述

如果有多个Runner启动器,可以通过order接口或者注解来排序:
在这里插入图片描述

10. 通过使用static 静态变量也可以实时传值


创建一个新类,在这个类中,创建一个static变量,随时赋值也是可以的!

11. 三种方式项目启动后,就执行代码


第一种:listener:项目启动后,生成对象,并且对象一直存在。

第二种:bean放到ioc容器:项目启动后,bean就执行,并且对象一直存在。

第三种:放到servletContext中:可以通过启动器。

猜你喜欢

转载自blog.csdn.net/IT_Holmes/article/details/125391618