常见的分布式协议与算法 试一下这里的代码

* ConsistentHashingWithoutVirtualNode.php

<?php
/**
 * Class ConsistentHashingWithoutVirtualNode */
class ConsistentHashingWithoutVirtualNode
{
    /** @var array assoc */
    private $sortedMap;

    /** @var array index */
    private $servers;

    /**
     * ConsistentHashingWithoutVirtualNode constructor.
     * @param $srvList array{string}
     */
    public function __construct($srvList) {
        $this->sortedMap = array();
        $this->servers = array();
        foreach ($srvList as $srv) {
            array_push($this->servers, $srv);
            $hash = self::getHash($srv);
            $this->sortedMap[$hash] = $srv;
            printf("[%s]加入集合中, 其Hash值为%d\n", $srv, $hash);
        }
        ksort($this->sortedMap);
        printf("\n");
    }

    public function getServer($node) {
        $hash = self::getHash($node);
        // 得到>=该Hash值之后的所有Map
        $subMap = array();
        foreach ($this->sortedMap as $h => $s) {
            if ($h >= $hash) {
                $subMap[$h] = $s;
            }
        }
        // 第一个Key就是顺时针过去离node最近的那个结点
        $key = key($subMap);
        // 返回对应的服务器名称
        return $subMap[$key];
    }

    public static function getHash(/* string */$str, $M = 249997) {
        $hash = 0;
        for($i = 0; isset($str[$i]); $i++) {
            $hash = ($hash << 4) + ord($str[$i]);
            $x = $hash & 0xf0000000;
            if ($x) {
                $hash ^= ($x >> 24);
                $hash &= ~$x;
            }
        }
        return $hash % $M;
    }
}

PHP 使用数组指针遍历数组
① next:将数组指针,后移一位。并返回后一位的值;没有返回false
② prev:将数组指针,前移一位。并返回前一位的值;没有返回false
③ end: 将数组指针,移至最后一位,返回最后一位的值;空数组返回false
④ reset:将数组指针,恢复到第一位。并返回第一位的值;空数组返回false
⑤ key: 返回当前指针所在位的键;超出返回null。
⑥ current:返回当前指针所在位的值;超出返回false。

* index.php

<?php
/**
 * Created by PhpStorm.
 * User: EDZ
 * Date: 2021/5/6
 * Time: 14:37
 */
include "ConsistentHashingWithoutVirtualNode.php";

use \ConsistentHashingWithoutVirtualNode as Hashing;

$servers = ["192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
            "192.168.0.3:111", "192.168.0.4:111"];
$hashing = new Hashing($servers);

$nodes = [
    "127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"
];
foreach ($nodes as $node) {
    printf("[%s]的hash值为%s, 被路由到的节点[%s]\n",
        $node, Hashing::getHash($node), $hashing->getServer($node));
}

一致性Hash, consitency hashing, 分布式_结点

一致性Hash, consitency hashing, 分布式_服务器_02

 

    private static int getHash(String str) {
        int hash = 0, x = 0;
        for (int i = 0; i < str.length(); i++) {
            hash = (hash << 4) + str.charAt(i);
            x = hash & 0xf0000000;
            if (0 != x) {
                hash ^= (x >> 24);
                hash &= ~x;
            }
        }
        return hash % 249997;
    }

这里的hash code算出来不一样,不知道哪里错了。php大数转换为float类型,还是符号、越界的问题?

ConsistentHashingWithoutVirtualNode.java
package com.merrytech;

import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 不带虚拟节点的一致性Hash算法
 */
public class ConsistentHashingWithoutVirtualNode
{
    /**
     * 待添加入Hash环的服务器列表
     */
    private static final String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
            "192.168.0.3:111", "192.168.0.4:111"};

    /**
     * key表示服务器的hash值,value表示服务器的名称
     */
    private static final SortedMap<Integer, String> sortedMap = new TreeMap<>();

    static
    {
        for (String server : servers) {
            int hash = getHash(server);
            System.out.println("[" + server + "]加入集合中, 其Hash值为" + hash);
            sortedMap.put(hash, server);
        }
        System.out.println();
    }

    /**
     * 得到应当路由到的结点
     */
    private static String getServer(String node)
    {
        // 得到带路由的结点的Hash值
        int hash = getHash(node);
        // 得到大于该Hash值的所有Map
        SortedMap<Integer, String> subMap = sortedMap.tailMap(hash);
        // 第一个Key就是顺时针过去离node最近的那个结点
        Integer i = subMap.firstKey();
        // 返回对应的服务器名称
        return subMap.get(i);
    }

    /**
     * 常见的hash 函数, RSHash     * @param str string
     * @return int
     */
//    private static int getHash(String str) {
//        int b    = 378551;
//        int a    = 63689;
//        int hash = 0;
//
//        for(int i = 0; i < str.length(); i++) {
//            hash = hash * a + str.charAt(i);
//            a = a * b;
//        }
//
//        return (hash & 0x7FFFFFFF);
//    }
    private static int getHash(String str) {
        int hash = 0, x = 0;
        for (int i = 0; i < str.length(); i++) {
            hash = (hash << 4) + str.charAt(i);
            x = hash & 0xf0000000;
            if (0 != x) {
                hash ^= (x >> 24);
                hash &= ~x;
            }
        }
        return hash % 249997;
    }

    public static void main(String[] args)
    {
        String[] nodes = {"127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"};
        for (String node : nodes) {
            System.out.println("[" + node + "]的hash值为" +
                    getHash(node) + ", 被路由到结点[" + getServer(node) + "]");
        }
    }
}

一致性Hash, consitency hashing, 分布式_i++_03

再试一下带虚拟节点的

package com.merrytech;

import java.util.*;

/**
 * 带虚拟节点的一致性Hash算法
 */
public class ConsistentHashingWithVirtualNode
{
    /**
     * 待添加入Hash环的服务器列表
     */
    private static final String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
            "192.168.0.3:111", "192.168.0.4:111"};

    /**
     * 真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
     */
    private static final List<String> realNodes = new LinkedList<String>();

    /**
     * 虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
     */
    private static final SortedMap<Integer, String> virtualNodes =
            new TreeMap<Integer, String>();

    /**
     * 虚拟节点的数目,这里写死,为了演示需要,一个真实结点对应5个虚拟节点
     */
    private static final int VIRTUAL_NODES = 5;

    static
    {
        // 先把原始的服务器添加到真实结点列表中
        Collections.addAll(realNodes, servers);

        // 再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高
        for (String str : realNodes) {
            for (int i = 0; i < VIRTUAL_NODES; i++) {
                String virtualNodeName = str + "&&VN" + String.valueOf(i);
                int hash = getHash(virtualNodeName);
                System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);
                virtualNodes.put(hash, virtualNodeName);
            }
        }
        System.out.println();
    }

    /**
     * 得到应当路由到的结点
     */
    private static String getServer(String node)
    {
        // 得到带路由的结点的Hash值
        int hash = getHash(node);
        // 得到大于该Hash值的所有Map
        SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
        // 第一个Key就是顺时针过去离node最近的那个结点
        Integer i = subMap.firstKey();
        // 返回对应的虚拟节点名称,这里字符串稍微截取一下
        String virtualNode = subMap.get(i);
        return virtualNode.substring(0, virtualNode.indexOf("&&"));
    }

    /**
     * 常见的hash 函数, RSHash
     * @return int
     */
    private static int getHash(String str) {
        int a = 63689, b = 378551, hash = 0;
        for(int i = 0; i < str.length(); i++) {
            hash = hash * a + str.charAt(i);
            a *= b;
        }
        return (hash & 0x7FFFFFFF);
    }

    public static void main(String[] args)
    {
        String[] nodes = {"127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"};
        for (String node : nodes) {
            System.out.println("[" + node + "]的hash值为" + getHash(node) + ", 被路由到结点["
                    + getServer(node) + "]");
        }

    }
}

一致性Hash, consitency hashing, 分布式_i++_04

 

1. fpm跟fcgi关系

FastCGI接口方式采用C/S结构,可以将HTTP服务器和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。

当HTTP服务器遇到动态PHP程序时,交付给FastCGI进程,PHP返回的结果传给浏览器。

FastCGI像是一个常驻(long-live)型的CGI,避免频繁fork。

PHP-FPM 是对于 FastCGI协议的具体实现,他负责管理一个进程池,来处理来自Web服务器的请求。


2. nginx为什么比apache快?

Nginx采用epoll模型,异步非阻塞。一个http请求划分为多个步骤,accept(), receive(),磁盘I/O,send(), 分别注册event handler

apache 对于请求,fork进程,CPU上下文切换


3. 讲一下https

传输加密、身份认证、SSL

传输数据之前,会通过基于X.509证书对双方进行身份认证 

客户端发起 SSL 握手消息给服务端要求连接。

服务端将证书发送给客户端。

客户端检查服务端证书,确认是否由自己信任的证书签发机构签发。

服务端要求客户端发送证书,并检查是否通过验证。


4. MySQL有哪几种索引,使用索引优缺点

FULLTEXT,HASH,BTREE,RTREE

优点: B+ tree的索引结构, 平衡多路查找树方式去查询,速度快; 减少访问数据库IO次数
缺点: 索引块占磁盘空间,update操作更新索引、delete删除索引中的值 产生额外开销


5. redis数据类型,哪些用途?

string: incr, decr, incrby, decrby
list: lpush, lpop, rpush, rpop, llen
set: sadd, scard, sismember, srem
zset: zadd, zcard, zrange
hash: hset, hget, hmget

redis的5种数据类型


6. MySQL分库分表

水平: 按照一定策略(hash、range, 时间等),将一个中的数据拆分到多个中。

垂直: 拆字段

Mysql数据库分库分表


7. PHP安全问题,有哪些措施?

防xss: strip_tags去掉传入的js代码, htmlspecialchars
防CSRF(跨站请求伪造cross site request forgery): 校验Referrer, 源IP
防盗链: apache .htaccess HTTP_REFERER, 使用云存储添加Expires, access key, 例如
   http://ks3-cn-beijing.ksyun.com/resource-test/crm/black_swan_20210510200138.png?Expires=1620649902&KSSAccessKeyId=AKLTzoWE5LxLRgSZ4oFYniPPug&Signature=XGEANBpAC3EGE8EJdcvACjyTFmQ%3D
防CC: redis计数, 限制同一域名单位时间的访问次数
防SQL注入: addslashes, stripslashes, mysql_ real _escape_string
    去掉引号,单引号, 空格 and, or, # - **, % _, 
    去掉 select,insert,update,delete, union, join, into, load_file,outfile


8. jwt讲一下

Json web token, 结构: header.payload.signature
header = base64({"type":"JWT","alg":"HS256"})
payload = base64(自定义要传输的json)
secret = HMACSHA256(header + payload + secret)
secret是保存在服务器端的私钥
使用方式: 
fetch('api/user/1', {
    headers:{
        'Authorization': 'Bearer ' + token
    }
});


9. websocket

  单个 TCP 连接上进行全双工通讯的协议。允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。比传统的ajax轮询节省服务器资源,能够更实时地进行通讯。 HTTP请求包含较长的头部, 所以websocket节省带宽。

websocket示例代码


10. 常见分布式算法有哪些?

一致性hash, gossip协议, Quorum NWR算法, PBFT算法, Pow算法

 

一致哈希算法是对节点ip地址 + 端口号字符串 调用hash函数,&0x7fffffff, 2^32 进行取模运算。将节点映射到哈希环上,从而每个节点就能确定其在哈希环上的位置了。

然后当要读取指定key(ip地址 + 端口号字符串)的值的时候,通过对key做一个hash,并确定此 key 在环上的位置,从这个位置沿着哈希环顺时针“行走”,

遇到的第一节点就是 key 对应的节点。(有序map key大于当前这个hash值, 首个key对应的value)

 

gossip协议

当一个节点有了新数据后,这个节点变成活跃状态,并周期性地联系其他节点向其发送新数据,直到所有的节点都存储了该新数据。(这里没有代码,看不懂)

PBFT

拜占庭容错