bootstracp实现树形列表_Java实现一致性哈希算法,并搭建环境测试其负载均衡特性...

实现负载均衡是后端领域一个重要的话题,一致性哈希算法是实现服务器负载均衡的方法之一,你很可能已在一些远程服务框架中使用过它。下面我们尝试一下自己实现一致性哈希算法。

一. 简述一致性哈希算法

这里不详细介绍一致性哈希算法的起源了,网上能方便地搜到许多介绍一致性哈希算法的好文章。本文主要想动手实现一致性哈希算法,并搭建一个环境进行实战测试。

在开始之前先整理一下算法的思路:

一致性哈希算法通过把每台服务器的哈希值打在哈希环上,把哈希环分成不同的段,然后对到来的请求计算哈希值从而得知该请求所归属的服务器。这个办法解决了传统服务器增减机器时需要重新计算哈希的麻烦。

但如果服务器的数量较少,可能导致计算出的哈希值相差较小,在哈希环上分布不均匀,导致某台服务器过载。为了解决负载均衡问题,我们引入虚拟节点技术,为每台服务器分配一定数量的节点,通过节点的哈希值在哈希环上进行划分。这样一来,我们就可以根据机器的性能为其分配节点,性能好就多分配一点,差就少一点,从而达到负载均衡。

二. 实现一致性哈希算法

奠定了整体思路后我们开始考虑实现的细节

哈希算法的选择

选择能散列出32位整数的 FNV 算法, 由于该哈希函数可能产生负数, 需要作取绝对值处理.

请求节点在哈希环上寻找对应服务器的策略

策略为:新节点寻找最近比且它大的节点, 比如说现在已经有环[0, 5, 7, 10], 来了个哈希值为6的节点, 那么它应该由哈希值为7对应的服务器处理. 如果请求节点所计算的哈希值大于环上的所有节点, 那么就取第一个节点. 比如来了个11, 将分配到0所对应的节点.

哈希环的组织结构

开始的时候想过用顺序存储的结构存放,但是在一致性哈希中,最频繁的操作是在集合中查找最近且比目标大的数. 如果用顺序存储结构的话,时间复杂度是收敛于O(N)的,而树形结构则为更优的O(logN)。

但凡事有两面,采用树形结构存储的代价是数据初始化的效率较低,而且运行期间如果有节点插入删除的话效率也比较低。但是在现实中,服务器在一开始注册后基本上就不怎么变了,期间增减机器,宕机,机器修复等事件的频率相比起节点的查询简直是微不足道。所以本案例决定使用使用树形结构存储。

贴合上述要求,并且提供有序存储的,首先想到的是红黑树,而且Java中提供了红黑树的实现TreeMap。

虚拟节点与真实节点的映射关系

如何确定一个虚拟节点对应的真实节点也是个问题。理论上应该维护一张表记录真实节点与虚拟节点的映射关系。本案例为了演示,采用简单的字符串处理。

比方说服务器192.168.0.1:8888分配了 1000 个虚拟节点, 那么它的虚拟节点名称从192.168.0.1:8888@1一直到192.168.0.1:8888@1000。通过这样的处理,我们在通过虚拟节点找真实节点时只需要裁剪字符串即可。

计划定制好后, 下面是具体代码:

public class ConsistentHashTest {    /**     * 服务器列表,一共有3台服务器提供服务, 将根据性能分配虚拟节点     */    public static String[] servers = {            "192.168.0.1#100", //服务器1: 性能指数100, 将获得1000个虚拟节点            "192.168.0.2#100", //服务器2: 性能指数100, 将获得1000个虚拟节点            "192.168.0.3#30"   //服务器3: 性能指数30,  将获得300个虚拟节点    };    /**     * 真实服务器列表, 由于增加与删除的频率比遍历高, 用链表存储比较划算     */    private static List realNodes = new LinkedList<>();    /**     * 虚拟节点列表     */    private static TreeMap virtualNodes = new TreeMap<>();    static{        for(String s : servers){            //把服务器加入真实服务器列表中            realNodes.add(s);            String[] strs = s.split("#");            //服务器名称, 省略端口号            String name = strs[0];            //根据服务器性能给每台真实服务器分配虚拟节点, 并把虚拟节点放到虚拟节点列表中.            int virtualNodeNum = Integer.parseInt(strs[1]) * 10;            for(int i = 1; i <= virtualNodeNum; i++){                virtualNodes.put(FVNHash(name + "@" + i), name + "@" + i);            }        }    }    public static void main(String[] args) {        new Thread(new RequestProcess()).start();    }    static class RequestProcess implements Runnable{        @Override        public void run() {            String client = null;            while(true){                //模拟产生一个请求                client = getN() + "." + getN() + "." + getN() + "." + getN() + ":" + (1000 + (int)(Math.random() * 9000));                //计算请求的哈希值                int hash = FVNHash(client);                //判断请求将由哪台服务器处理                System.out.println(client + " 的请求将由 " + getServer(client) + " 处理");                try {                    Thread.sleep(500);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }    private static String getServer(String client) {        //计算客户端请求的哈希值        int hash = FVNHash(client);        //得到大于该哈希值的所有map集合        SortedMap subMap = virtualNodes.tailMap(hash);        //找到比该值大的第一个虚拟节点, 如果没有比它大的虚拟节点, 根据哈希环, 则返回第一个节点.        Integer targetKey = subMap.size() == 0 ? virtualNodes.firstKey() : subMap.firstKey();        //通过该虚拟节点获得真实节点的名称        String virtualNodeName = virtualNodes.get(targetKey);        String realNodeName = virtualNodeName.split("@")[0];        return realNodeName;    }    public static int getN(){        return (int)(Math.random() * 128);    }    public static int FVNHash(String data){        final int p = 16777619;        int hash = (int)2166136261L;        for(int i = 0; i < data.length(); i++)            hash = (hash ^ data.charAt(i)) * p;        hash += hash << 13;        hash ^= hash >> 7;        hash += hash << 3;        hash ^= hash >> 17;        hash += hash << 5;        return hash < 0 ? Math.abs(hash) : hash;    }}/* 运行结果片段55.1.13.47:6240     的请求将由 192.168.0.1 处理5.49.56.126:1105    的请求将由 192.168.0.1 处理90.41.8.88:6884     的请求将由 192.168.0.2 处理26.107.104.81:2989  的请求将由 192.168.0.2 处理114.66.6.56:8233    的请求将由 192.168.0.1 处理123.74.52.94:5523   的请求将由 192.168.0.1 处理104.59.60.2:7502    的请求将由 192.168.0.2 处理4.94.30.79:1299     的请求将由 192.168.0.1 处理10.44.37.73:9332    的请求将由 192.168.0.2 处理115.93.93.82:6333   的请求将由 192.168.0.2 处理15.24.97.66:9177    的请求将由 192.168.0.2 处理100.39.98.10:1023   的请求将由 192.168.0.2 处理61.118.87.26:5108   的请求将由 192.168.0.2 处理17.79.104.35:3901   的请求将由 192.168.0.1 处理95.36.5.25:8020     的请求将由 192.168.0.2 处理126.74.56.71:7792   的请求将由 192.168.0.2 处理14.63.56.45:8275    的请求将由 192.168.0.1 处理58.53.44.71:2089    的请求将由 192.168.0.3 处理80.64.57.43:6144    的请求将由 192.168.0.2 处理46.65.4.18:7649     的请求将由 192.168.0.2 处理57.35.27.62:9607    的请求将由 192.168.0.2 处理81.114.72.3:3444    的请求将由 192.168.0.1 处理38.18.61.26:6295    的请求将由 192.168.0.2 处理71.75.18.82:9686    的请求将由 192.168.0.2 处理26.11.98.111:3781   的请求将由 192.168.0.1 处理62.86.23.37:8570    的请求将由 192.168.0.3 处理*/

经过上面的测试我们可以看到性能较好的服务器1和服务器2分担了大部分的请求,只有少部分请求落到了性能较差的服务器3上,已经初步实现了负载均衡。

下面我们将结合zookeeper,搭建一个更加逼真的服务器集群,看看在部分服务器上线下线的过程中,一致性哈希算法是否仍能够实现负载均衡。

三. 结合zookeeper搭建环境

环境介绍

首先会通过启动多台虚拟机模拟服务器集群,各台服务器都提供一个相同的接口供消费者消费。

同时会有一个消费者线程不断地向服务器集群发起请求,这些请求会经过一致性哈希算法均衡负载到各个服务器。

为了能够模拟上述场景, 我们必须在客户端维护一个服务器列表, 使得客户端能够通过一致性哈希算法选择服务器发送。 (现实中可能会把一致性哈希算法实现在前端服务器, 客户先访问前端服务器, 再路由到后端服务器集群)。

但是我们的重点是模拟服务器的宕机和上线,看看一致性哈希算法是否仍能实现负载均衡。所以客户端必须能够感知服务器端的变化并动态地调整它的服务器列表。

为了完成这项工作, 我们引入zookeeper, zookeeper的数据一致性算法保证数据实时, 准确, 客户端能够通过zookeeper得知实时的服务器情况。

具体操作是这样的: 服务器集群先以临时节点的方式连接到zookeeper, 并在zookeeper上注册自己的接口服务(注册节点). 客户端连接上zookeeper后, 把已注册的节点(服务器)添加到自己的服务器列表中。

如果有服务器宕机的话, 由于当初注册的是瞬时节点的原因, 该台服务器节点会从zookeeper中注销。客户端监听到服务器节点有变时, 也会动态调整自己的服务器列表, 把当宕机的服务器从服务器列表中删除, 因此不会再向该服务器发送请求, 负载均衡的任务将交到剩余的机器身上。

当有服务器从新连接上集群后, 客户端的服务器列表也会更新, 哈希环也将做出相应的变化以提供负载均衡。

具体操作:

I. 搭建zookeeper集群环境:

  1. 创建3个zookeeper服务, 构成集群. 在各自的data文件夹中添加一个myid文件, 各个id分别为1, 2, 3.
0522d12f4ae5b0bfef84fe4461d4c121.png
  1. 重新复制一份配置文件, 在配置文件中配置各个zookeeper的端口号. 本案例中三台zookeeper分别在2181, 2182, 2183端口
73c52687e96d11a24d2db479b68367b8.png
  1. 启动zookeeper集群

由于zookeeper不是本案例的重点, 细节暂不展开讲了.

II. 创建服务器集群, 提供RPC远程调用服务

  1. 首先创建一个服务器项目(使用Maven), 添加zookeeper依赖
  2. 创建常量接口, 用于存储连接zookeeper 的信息
public interface Constant {    //zookeeper集群的地址    String ZK_HOST = "192.168.117.129:2181,192.168.117.129:2182,192.168.117.129:2183";    //连接zookeeper的超时时间    int ZK_TIME_OUT = 5000;    //服务器所发布的远程服务在zookeeper中的注册地址, 也就是说这个节点中保存了各个服务器提供的接口    String ZK_REGISTRY = "/provider";    //zookeeper集群中注册服务的url地址的瞬时节点    String ZK_RMI = ZK_REGISTRY + "/rmi";}

3.封装操作zookeeper和发布远程服务的接口供自己调用, 本案例中发布远程服务使用Java自身提供的rmi包完成, 如果没有了解过可以参考这篇

public class ServiceProvider {    private CountDownLatch latch = new CountDownLatch(1);    /**     * 连接zookeeper集群     */    public ZooKeeper connectToZK(){        ZooKeeper zk = null;        try {            zk = new ZooKeeper(Constant.ZK_HOST, Constant.ZK_TIME_OUT, new Watcher() {                @Override                public void process(WatchedEvent watchedEvent) {                    //如果连接上了就唤醒当前线程.                    latch.countDown();                }            });            latch.await();//还没连接上时当前线程等待        } catch (Exception e) {            e.printStackTrace();        }        return zk;    }    /**     * 创建znode节点     * @param zk     * @param url 节点中写入的数据     */    public void createNode(ZooKeeper zk, String url){        try{            //要把写入的数据转化为字节数组            byte[] data = url.getBytes();            zk.create(Constant.ZK_RMI, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 发布rmi服务     */    public String publishService(Remote remote, String host, int port){        String url = null;        try{            LocateRegistry.createRegistry(port);            url = "rmi://" + host + ":" + port + "/rmiService";            Naming.bind(url, remote);        } catch (Exception e) {            e.printStackTrace();        }        return url;    }    /**     * 发布rmi服务, 并且将服务的url注册到zookeeper集群中     */    public void publish(Remote remote, String host, int port){        //调用publishService, 得到服务的url地址        String url = publishService(remote, host, port);        if(null != url){            ZooKeeper zk = connectToZK();//连接到zookeeper            if(null != zk){                createNode(zk, url);            }        }    }}
  1. 自定义远程服务. 服务提供一个简单的方法: 客户端发来一个字符串, 服务器在字符串前面添加上Hello, 并返回字符串。
//UserServicepublic interface UserService extends Remote {    public String helloRmi(String name) throws RemoteException;}//UserServiceImplpublic class UserServiceImpl implements UserService {    public UserServiceImpl() throws RemoteException{        super();    }    @Override    public String helloRmi(String name) throws RemoteException {        return "Hello " + name + "!";    }}
  1. 修改端口号, 启动多个java虚拟机, 模拟服务器集群. 为了方便演示, 自定义7777, 8888, 9999端口开启3个服务器进程, 到时会模拟7777端口的服务器宕机和修复重连。
public static void main(String[] args) throws RemoteException {    //创建工具类对象    ServiceProvider sp = new ServiceProvider();    //创建远程服务对象    UserService userService = new UserServiceImpl();    //完成发布    sp.publish(userService, "localhost", 9999);}
5ee38bb6e543ae21ff8eaf8f3b22b38f.png

III. 编写客户端程序(运用一致性哈希算法实现负载均衡

  1. 封装客户端接口: ```java public class ServiceConsumer { /**提供远程服务的服务器列表, 只记录远程服务的url */ private volatile List urls = new LinkedList<>(); /**远程服务对应的虚拟节点集合 */ private static TreeMap virtualNodes = new TreeMap<>();public ServiceConsumer(){ ZooKeeper zk = connectToZK();//客户端连接到zookeeper if(null != zk){ //连接上后关注zookeeper中的节点变化(服务器变化) watchNode(zk); } }private void watchNode(final ZooKeeper zk) { try{ //观察/provider节点下的子节点是否有变化(是否有服务器登入或登出) List nodeList = zk.getChildren(Constants.ZK_REGISTRY, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { //如果服务器节点有变化就重新获取 if(watchedEvent.getType() == Event.EventType.NodeChildrenChanged){ System.out.println("服务器端有变化, 可能有旧服务器宕机或者新服务器加入集群..."); watchNode(zk); } } }); //将获取到的服务器节点数据保存到集合中, 也就是获得了远程服务的访问url地址 List dataList = new LinkedList<>(); TreeMap newVirtualNodesList = new TreeMap<>(); for(String nodeStr : nodeList){ byte[] data = zk.getData(Constants.ZK_REGISTRY + "/" + nodeStr, false, null); //放入服务器列表的url String url = new String(data); //为每个服务器分配虚拟节点, 为了方便模拟, 默认开启在9999端口的服务器性能较差, 只分配300个虚拟节点, 其他分配1000个. if(url.contains("9999")){ for(int i = 1; i <= 300; i++){ newVirtualNodesList.put(FVNHash(url + "@" + i), url + "@" + i); } }else{ for(int i = 1; i <= 1000; i++){ newVirtualNodesList.put(FVNHash(url + "@" + i), url + "@" + i); } } dataList.add(url); } urls = dataList; virtualNodes = newVirtualNodesList; dataList = null;//好让垃圾回收器尽快收集 newVirtualNodesList = null; } catch (Exception e) { e.printStackTrace(); } }/**根据url获得远程服务对象 */ public T lookUpService(String url){ T remote = null; try{ remote = (T)Naming.lookup(url); } catch (Exception e) { //如果该url连接不上, 很有可能是该服务器挂了, 这时使用服务器列表中的第一个服务器url重新获取远程对象. if(e instanceof ConnectException){ if (urls.size() != 0){ url = urls.get(0); return lookUpService(url); } } } return remote; }/**通过一致性哈希算法, 选取一个url, 最后返回一个远程服务对象 */ public T lookUp(){ T service = null; //随机计算一个哈希值 int hash = FVNHash(Math.random() * 10000 + ""); //得到大于该哈希值的所有map集合 SortedMap subMap = virtualNodes.tailMap(hash); //找到比该值大的第一个虚拟节点, 如果没有比它大的虚拟节点, 根据哈希环, 则返回第一个节点. Integer targetKey = subMap.size() == 0 ? virtualNodes.firstKey() : subMap.firstKey(); //通过该虚拟节点获得服务器url String virtualNodeName = virtualNodes.get(targetKey); String url = virtualNodeName.split("@")[0]; //根据服务器url获取远程服务对象 service = lookUpService(url); System.out.print("提供本次服务的地址为: " + url + ", 返回结果: "); return service; }private CountDownLatch latch = new CountDownLatch(1);public ZooKeeper connectToZK(){ ZooKeeper zk = null; try { zk = new ZooKeeper(Constants.ZK_HOST, Constants.ZK_TIME_OUT, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { //判断是否连接zk集群 latch.countDown();//唤醒处于等待状态的当前线程 } }); latch.await();//没有连接上的时候当前线程处于等待状态. } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return zk; }public static int FVNHash(String data){ final int p = 16777619; int hash = (int)2166136261L; for(int i = 0; i < data.length(); i++) hash = (hash ^ data.charAt(i)) * p; hash += hash « 13; hash ^= hash » 7; hash += hash « 3; hash ^= hash » 17; hash += hash « 5; return hash < 0 ? Math.abs(hash) : hash; } } ```
  2. 启动客户端进行测试:
public static void main(String[] args){    ServiceConsumer sc = new ServiceConsumer();//创建工具类对象    while(true){        //获得rmi远程服务对象        UserService userService = sc.lookUp();        try{            //调用远程方法            String result = userService.helloRmi("炭烧生蚝");            System.out.println(result);            Thread.sleep(100);        }catch(Exception e){            e.printStackTrace();        }    }}
  1. 客户端跑起来后, 在显示台不断进行打印…下面将对数据进行统计。
56c0d45455df3007e3845afec1fb4030.png
2d2ec50318950febcd36400090a1e965.png

IV. 对服务器调用数据进行统计分析

重温一遍模拟的过程: 首先分别在7777, 8888, 9999端口启动了3台服务器. 然后启动客户端进行访问. 7777, 8888端口的两台服务器设置性能指数为1000, 而9999端口的服务器性能指数设置为300。

在客户端运行期间, 我手动关闭了8888端口的服务器, 客户端正常打印出服务器变化信息。此时理论上不会有访问被路由到8888端口的服务器。当我重新启动8888端口服务器时, 客户端打印出服务器变化信息, 访问能正常到达8888端口服务器。

下面对各服务器的访问量进行统计, 看是否实现了负载均衡。

测试程序如下:

public class DataStatistics {    private static float ReqToPort7777 = 0;    private static float ReqToPort8888 = 0;    private static float ReqToPort9999 = 0;    public static void main(String[] args) {        BufferedReader br = null;        try {            br = new BufferedReader(new FileReader("C://test.txt"));            String line = null;            while(null != (line = br.readLine())){                if(line.contains("7777")){                    ReqToPort7777++;                }else if(line.contains("8888")){                    ReqToPort8888++;                }else if(line.contains("9999")){                    ReqToPort9999++;                }else{                    print(false);                }            }            print(true);        } catch (Exception e) {            e.printStackTrace();        }finally {            if(null != br){                try {                    br.close();                } catch (IOException e) {                    e.printStackTrace();                }                br = null;            }        }    }    private static void print(boolean isEnd){        if(!isEnd){            System.out.println("------------- 服务器集群发生变化 -------------");        }else{            System.out.println("------------- 最后一次统计 -------------");        }        System.out.println("截取自上次服务器变化到现在: ");        float total = ReqToPort7777 + ReqToPort8888 + ReqToPort9999;        System.out.println("7777端口服务器访问量为: " + ReqToPort7777 + ", 占比" + (ReqToPort7777 / total));        System.out.println("8888端口服务器访问量为: " + ReqToPort8888 + ", 占比" + (ReqToPort8888 / total));        System.out.println("9999端口服务器访问量为: " + ReqToPort9999 + ", 占比" + (ReqToPort9999 / total));        ReqToPort7777 = 0;        ReqToPort8888 = 0;        ReqToPort9999 = 0;    }}/* 以下是输出结果------------- 服务器集群发生变化 -------------截取自上次服务器变化到现在: 7777端口服务器访问量为: 198.0, 占比0.44196438888端口服务器访问量为: 184.0, 占比0.41071439999端口服务器访问量为: 66.0, 占比0.14732143------------- 服务器集群发生变化 -------------截取自上次服务器变化到现在: 7777端口服务器访问量为: 510.0, 占比0.75892868888端口服务器访问量为: 1.0, 占比0.00148809539999端口服务器访问量为: 161.0, 占比0.23958333------------- 最后一次统计 -------------截取自上次服务器变化到现在: 7777端口服务器访问量为: 410.0, 占比0.432489458888端口服务器访问量为: 398.0, 占比0.419831229999端口服务器访问量为: 140.0, 占比0.14767933*/

V. 结果

从测试数据可以看出, 不管是8888端口服务器宕机之前, 还是宕机之后, 三台服务器接收的访问量和性能指数成正比,成功地验证了一致性哈希算法的负载均衡作用。

四. 扩展思考

初识一致性哈希算法的时候, 对这种奇特的思路佩服得五体投地。但是一致性哈希算法除了能够让后端服务器实现负载均衡, 还有一个特点可能是其他负载均衡算法所不具备的。

这个特点是基于哈希函数的, 我们知道通过哈希函数, 固定的输入能够产生固定的输出. 换句话说, 同样的请求会路由到相同的服务器. 这点就很牛逼了, 我们可以结合一致性哈希算法和缓存机制提供后端服务器的性能。

比如说在一个分布式系统中, 有一个服务器集群提供查询用户信息的方法, 每个请求将会带着用户的uid到达, 我们可以通过哈希函数进行处理(从上面的演示代码可以看到, 这点是可以轻松实现的), 使同样的uid路由到某个独定的服务器. 这样我们就可以在服务器上对该的uid背后的用户信息进行缓存, 从而减少对数据库或其他中间件的操作, 从而提高系统效率。

当然如果使用该策略的话, 你可能还要考虑缓存更新等操作, 但作为一种优良的策略, 我们可以考虑在适当的场合灵活运用。

以上思考受启发于Dubbo框架中对其实现的四种负载均衡策略的描述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/533075.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统分析师和系统架构设计师难度比较_系统架构设计师,马上开课了!

一年只考一次的系统架构设计师7月7日通关指南开课系统架构设计师考试&#xff0c;是2009年11月计算机资格考试新增专业&#xff0c;这个级别属于高级资格考试。与该考试同级别的还有系统分析师、信息系统项目管理师、系统规划与管理师以及网络规划设计师。系统架构设计师每年考…

pythonhelloworld实例_Python基于Tkinter的HelloWorld入门实例

本文实例讲述了Python基于Tkinter的HelloWorld入门实例。分享给大家供大家参考。具体分析如下&#xff1a;初学Python&#xff0c;打算做几个Tkinter的应用来提高。刚学的HelloWorld&#xff0c;秀一下。我用Python3.2的&#xff0c;Windows版本的。源代码如下&#xff1a; #导…

tensorflow提取mel谱特征_【脑电信号分类】脑电信号提取PSD功率谱密度特征

点击上面"脑机接口社区"关注我们更多技术干货第一时间送达本文是由CSDN用户[frostime]授权分享。主要介绍了脑电信号提取PSD功率谱密度特征&#xff0c;包括&#xff1a;功率谱密度理论基础、matlab中PSD函数的使用介绍以及实验示例。感谢 frostime&#xff01;1. 序…

从mysql中取出代理ip_GitHub - lican09/IPProxyTool: 抓取大量免费代理 ip,提取有效 ip 使用...

IPProxyTool使用 scrapy 爬虫抓取代理网站&#xff0c;获取大量的免费代理 ip。过滤出所有可用的 ip&#xff0c;存入数据库以备使用。可以访问我的个人站点&#xff0c;查看我的更多有趣项目 awolfly9个人项目欢迎加微信吐槽如果在使用中有任何疑问&#xff0c;或者项目中有任…

docker卸载 windows版本_DevOps系列 006 - Docker安装

这是DevOps系列的第六节&#xff0c;我们开始安装DockerDebian 上安装可以基于最新debian10的发行版&#xff0c;我现在还用着debian9&#xff0c;不过随后&#xff0c;我会发出Windows / macOs / Ubuntu的参考。安装如果您已经是root用户&#xff0c;则无需使用sudo1、卸载任何…

tab vue 竖排_vue 实现tab切换保持数据状态

页面做tab切换&#xff0c;由于组件每一次切换都会重新实例化组件&#xff0c;我们想要页面不论怎么切换都仍然保持tab里面的内容不会刷新&#xff0c;减少页面重新渲染以及减少请求实现方法&#xff1a;使用包裹组件 列表页面跳转详情 &#xff0c;列表页面保持上一次操作状态…

multisim连接MySQL_首次使用Multisim软件进行电路仿真设计

第一次接触使用Multisim进行电路仿真设计&#xff0c;通过使用这款软件&#xff0c;从中也学习到了很多东西&#xff0c;在这里想简单介绍一下这款软件的最主要也是最重要的功能和特点。创建电路&#xff0c;必定要放置元器件&#xff0c;这就需要用到元器件工具栏&#xff0c;…

mysql到pg怎么高效_干货 | Debezium实现Mysql到Elasticsearch高效实时同步(示例代码)

题记来自Elasticsearch中文社区的问题——MySQL中表无唯一递增字段&#xff0c;也无唯一递增时间字段&#xff0c;该怎么使用logstash实现MySQL实时增量导数据到es中&#xff1f;logstash和kafka_connector都仅支持基于自增id或者时间戳更新的方式增量同步数据。回到问题本身&a…

java thread safe_Java 线程安全 Thread-Safety

在 Java 的线程安全是老生常谈的问题。经常是各种写法说法一大堆&#xff0c;感觉很多的来源都是在面试的时候&#xff0c;很多考官都喜欢问线程安全的问题。起源这个问题的起源就是 Java 是支持多线程的。如果对进程和线程是什么不太清楚的话&#xff0c;可以恶补下大学课程《…

java socket调用接口_Java中socket接口调用

最近一个项目中接口通讯这一块主要是调用银联系统的socket接口&#xff0c;我方是客户端&#xff0c;即发送请求接收返回报文的一方。在贴代码之前&#xff0c;还是要了解一下关于socket的基础知识。Socket的基本概念1&#xff0e;建立连接当需要建立网络连接时&#xff0c;必须…

protobuf java 编译_Maven项目中,编译proto文件成Java类

新建Maven项目新建一个 Maven 项目&#xff1a;pom定义了最小的maven2元素&#xff0c;即&#xff1a;groupId,artifactId,version。 groupId:项目或者组织的唯一标志&#xff0c;并且配置时生成的路径也是由此生成&#xff0c;如org.codehaus.mojo生成的相对路径为&#xff1a…

python灰色关联度分析代码_灰色关联分析法步骤 - osc_uwnmtz9n的个人空间 - OSCHINA - 中文开源技术交流社区...

https://wenku.baidu.com/view/dc356290af1ffc4fff47ac0d.html?rec_flagdefault&sxts1538121950212利用灰色关联分析的步骤是&#xff1a;1&#xff0e;根据分析目的确定分析指标体系&#xff0c;收集分析数据。设n个数据序列形成如下矩阵&#xff1a;其中m为指标的个数&a…

aio 系统原理 Java_Java新一代网络编程模型AIO原理及Linux系统AIO介绍

从JDK 7版本开始&#xff0c;Java新加入的文件和网络io特性称为nio2(new io 2, 因为jdk1.4中已经有过一个nio了)&#xff0c;包含了众多性能和功能上的改进&#xff0c;其中最重要的部分&#xff0c;就是对异步io的支持&#xff0c;称为Java AIO(asynchronous IO)。因为AIO的实…

java请假审批怎么实现_java实现请假时间判断

笔记:需求分析:每周上班6天夏季早上8:30-12:00下午14:00-17:30冬季早上8:30-12:00下午14:30-18:00请假最低为半天按照上午8:00-12:00,下午14:00-18:00计算,包括了夏季和冬季时间,规律分布如下public String getDouble(HttpServletRequest request) throws ParseException {//参…

java原子整数_多线程(四、原子类-AtomicInteger)

案例10个线程并发累加一个整数&#xff0c;每个线程累加1000&#xff0c;保证线程安全Unsafe类&#xff0c;来源于sun.misc包。该类封装了许多类似指针操作&#xff0c;可以直接进行内存管理、操纵对象、阻塞/唤醒线程等操作。package com.jane;import java.util.ArrayList;imp…

java 极客_Java极客思维

​开篇介绍大家好&#xff0c;公众号【Java极客思维】近期会整理一些Java高频面试题分享给小伙伴&#xff0c;也希望看到的小伙伴在找工作过程中能够用得到&#xff01;本章节主要针对Java一些消息中间件高频面试题进行分享。通知&#xff1a;公众号【Java极客思维】正在送书福…

java拼三级魔方_魔方秘籍(详细解法)《三阶》

魔方根据视频理解&#xff1a;上 下 左 右先将白面变好&#xff1a;(1).变一个白十字(如图所示)(2).转好以后检查十字的四个角的颜色(蓝绿红橙)与旁边面上的中心块的颜色是否相同。(有两个相同的时&#xff0c;如果它们相邻&#xff0c;就一个放在后面&#xff0c;一个放在左面…

pHp30充电宝能用快充吗,65W快充 30分钟充满电 是时候淘汰充电宝了吗?

在过去的一年里&#xff0c;手机快充技术有了新的突破&#xff0c;OPPO推出了65W快充。无独有偶&#xff0c;联想拯救者电竞手机的预热宣传中&#xff0c;号称搭载90W快充。有评测称&#xff0c;使用65W快充&#xff0c;30分钟可以充满一块4000mAh容量的电池&#xff0c;使用90…

matlab画圆柱,Matlab 画三维圆柱体

主要学习了画空间圆柱体和空间长方形的绘制方法。有两个surface property&#xff1a;FaceColor和EdgeColor’;先讲FaceColor’&#xff0c;它指定了surface画出曲面的颜色&#xff0c;可以是[r,g,b]的一个向量&#xff0c;分别表示了红绿蓝的颜色配比&#xff1b;也可以是inte…

matlab类间散度矩阵,协方差矩阵和散布矩阵(散度矩阵)的意义

在机器学习模式识别相关算法中&#xff0c;经常需要求样本的协方差矩阵C和散布矩阵S。如在PCA主成分分析中&#xff0c;就需要计算样本的散度矩阵&#xff0c;而有的教材资料是计算协方差矩阵。实质上协方差矩阵和散度矩阵的意义就是一样的&#xff0c;散布矩阵(散度矩阵)前乘以…