4月1日-10日面试题总结

1.冒泡排序

public int[] bubbleSort(int arr[]) {
    int len = arr.length;
    for (int i = 0; i < len - 1; i++) {
        for (int j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) { // 相邻元素两两对比
                int temp = arr[j + 1]; // 元素交换
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

2.选择排序

    public int[] selectionSort(int arr[]) {
        int len = arr.length;
        int minIndex, temp;
        for (int i = 0; i < len - 1; i++) {
            minIndex = i;
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) { // 寻找最小的数
                    minIndex = j; // 将最小数的索引保存
                }
            }
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        return arr;
    }

3.插入排序

 

    public int[] insertSort(int arr[]) {
        int len = arr.length;
        int preIndex, currentIndex;
        for (int i = 1; i < len; i++) {
            preIndex = i - 1;
            currentIndex = arr[i];
            while (preIndex >= 0 && currentIndex < arr[preIndex]) {
                arr[preIndex + 1] = arr[preIndex];
                preIndex--;
            }
            arr[preIndex + 1] = currentIndex;
        }
        return arr;
    }

4.常问数据结构

1. 线性表结构 ( 重点 )
线性表是由 N 个元素组成的有序序列,也是最常见的一种数据结构。重点有两个数组和链表。
数组 :数组是一种存储单元连续,用来存储固定大小元素的线性表。 java 中对应的集合实现,比如ArrayList。
链表 :链表又分单链表和双链表,是在物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中指针的链接次序实现的,可以在头尾添加元素.java 中对应的集合实现,比如 LinkedList
2. 栈与队列
后进先出, 表的末端叫栈顶,基本操作有push(进栈 ) pop( 出栈 ) java stack 就是简单的栈实现。
队列 先进先出, 表的前端只允许进行删除操作, 表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。java 中很多Queue的实现,消息中间件的队列本质也是基于此的。
3 ( 重点 )
在非线性结构里面,树是非常非常重要的一种数据结构。 基于其本身的结构优势,尤其在查找领域,应用广泛,其中又以二叉树最为重要。树的话我们这里只重点说一下二叉树。
二叉搜索树( 二叉查找树,二叉排序树)
左子树中每个节点的值都不大于该节点值;
右子树中每个节点的值都不小于该节点值。
没有键值相等的结点
平衡二叉树 :是进化版的二叉查找树, 这个方案很好的解决了二叉查找树 退化成链表的问题
它的左右子树的高度之差的绝对值不能超过 1 ,如果插入或者删除一个节点使得高度之差大于 1 就要进行节点之间的旋转 (左旋右旋)    将二叉树重新维持在一个平衡状态。
红黑树 :红黑树是一种特殊的平衡二叉树,它保证在最坏情况下基本动态集合操作的事件复杂度为O(log n)。
红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
树的根都是黑色的
不可能有连着的红色节点(红色节点不能有红色父节点或红色子节点,黑色节点可以连续)
所有叶子节点都是黑色(叶子是 NIL 节点)
从任一节点到其叶子的所有简单路径都包含相同数目的黑色节点

HashMap底层
Java7 : 数组 + 链表
Java8 : 数组 + 链表 或 红黑树 ( 链表超过 8 则转为红黑树,小于 6 则变会链表) >> 为啥呢? 为了加快查询

根据统计概率学的泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭, 等于7的时候不转换,大于等于8的时候才进行转换为红黑树,小于等于6的时候就化为链表

1 hashmap的数据结构:源码中每个节点用 Node<K V>表示,Node 是一个内部类,这里的 key 为键, value 为值, next 指向下一个元素,看出 HashMap 中元素不是一个单纯的键值对,还包含下一个元素的引用
    public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
        private static final long serialVersionUID = 362498820763181265L;

        static class Node<K, V> implements Map.Entry<K, V> {
            final int hash;
            final K key;
            V value;
            Node<K, V> next;
        }
    }

为什么采用这种结构来存储元素呢?

数组的特点:查询效率高,插入,删除效率低
链表的特点:查询效率低,插入删除效率高
HashMap 底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。
 
2 hashmap的存储元素的过程
1. 计算出key键“老孙 hashcode ,该值用来定位要将这个元素存放到数组中的什么位置
2. 假设老孙的 hashcode 12345678 ,数组长度为 8 ,则要存储在数组索引为 6 的位置( 12345678% 8=6)
可以分两种情况 :
1. 数组索引为 6 的地方是空的,这种情况很简单,直接将元素放进去就好了。
2. 已经有元素占据了索引为 6 的位置,这种情况下我们需要判断一下该位置的元素和当前元素是否相等,使用equals 来比较。
如果使用默认的规则是比较两个对象的地址。也就是两者需要是同一个对象才相等,当然我们也可以重写equals 方法来实现我们自己的比较规则,最常见的是通过比较属性值来判断是否相等。
如果两者相等则直接覆盖,如果不等则在原元素下面使用 链表 的结构存储该元素,如图所示,每个元素节点都有一个 next 属性指向下一个节点,这里由数组结构变成了 数组 + 链表结构

因为链表中元素太多的时候会影响查找效率,所以当链表的元素个数达到8的时候使用链表存储就转变成了使用红黑树存储,原因就是红黑树是平衡二叉树,在查找性能方面比链表要高.

5.volatile关键字

volatile 通常被比喻成 " 轻量级的 synchronized ",和 synchronized 不同, volatile 是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
volatile 修饰的共享变量,就具有了以下两点特性:
保证了不同线程对该变量操作的内存可见性( 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值 )
没有volatile修饰,main主线程和子线程1之间是彼此见不到i的,所以主线程修改i也没用。循环不会停止!
public class Test1 {
        private volatile static int i = 0;
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (i==0){
                //随便干点啥
            }
            System.out.println("结束了!");
            },"线程1").start();

            Thread.sleep(1000);
            i = 1;
        }
    }

禁止cpu指令重排序:是为了提高程序的性能(我们爬楼梯,你走的慢,我走的快,你让开点,我先走上去)

Thread t1 = new Thread ( new Runnable () {
public void run () {
a = 1 ; // 1
x = b ; // 2
}
});
Thread t2 = new Thread ( new Runnable () {
public void run () {
b = 1 ; // 3
y = a ; // 4
}
t1 . start ();
t2 . start ();
t1 . join ();
t2 . join ();

前提是12没有依赖关系34没有依赖关系,才会发生指令重排

而我们的 volatile 可以防止这种情况发生,为什么 volatile可以防止重排呢?volatile支持内存屏障, 指令和指令之间加一堵墙(屏障),不能随意挪动窜位
LoadLoad 屏障: 读与读指令之间加屏障
StoreStore 屏障:写与写指令之间加屏障
LoadStore 屏障:读与写指令之间加屏障
StoreLoad 屏障:写与读指令之间加屏障

6.为什么要用Dubbo

因为Dubbo是阿里开源的RPC框架,国内很多互联网公司都在用,现在进入了 Apache 项目 ,内部使用了 Netty Zookeeper ,保证了高性能高可用性
Dubbo 可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可用于提高业务复用灵活扩展,使前端应用能更快速的响应多变的市场需求
最重要的一点是,分布式架构可以承受更大规模的并发流量

7.dubbo的工作流程

首先在客户端发出一个请求,也就是客户端调用,然后将本地的客户端的参数数据进行一个序列化(把对象转换成一个字节码文件),把序列化好的文件通过NIO发送信息给服务层,(dubbo底层用的是netty通过NIO发送信息),服务层接收到信息会进行反序列化,调用本地服务执行业务,然后把操作完的信息返回,还是要进行序列化,发给客户端,客户端再进行反序列化处理对应的业务

8.Dubbo支持哪些协议,每种协议的应用场景,优缺点?

dubbo 单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议TCP ,异步, Hessian 序列化;
默认使用dubbo协议,dubbo协议底层采用的是netty4,性能高效!
redis 基于 redis 实现的 RPC 协议
webservice 基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,基于HTTP 传输,同步传输,适用系统集成和跨语言调用;
http 基于 Http 表单提交的远程调用协议,使用 Spring HttpInvoke 实现。多个短连接,传输协议HTTP ,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器 JS 调用;

rmi采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口,使用java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP。多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作。在依赖低版本的Common-Collections包,java序列化存在安全漏洞;

9.RPC使用了哪些关键技术?

1 、动态代理
生成 Client Stub (客户端存根)和 Server Stub (服务端存根)的时候需要用到 Java 动态代理技术,可以使用JDK 提供的原生的动态代理机制,也可以使用开源的: CGLib 代理, Javassist 字节码生成技术。
2 、序列化和反序列化(netty)
在网络中,所有的数据都将会被转化为字节进行传送,所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。
序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。
目前比较高效的开源序列化框架:如 Kryo FastJson Protobuf 等。
3 NIO 通信
出于并发性能的考虑,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO ,即 NIO
Java 提供了 NIO 的解决方案, Java 7 也提供了更优秀的 NIO.2 支持。可以选择 Netty 来解决NIO数据传输的问题。
4 、服务注册中心
可选: Redis Zookeeper
一般使用 ZooKeeper 提供服务注册与发现功能,解决单点故障以及分布式部署的问题 ( 注册中心 )

10.算法思维

贪心算法:是一种在每一步选中都采取在当前状态下最好或最优的选择,从而希望导致结果是全局最好或最优的算法。

贪心算法的应用
部分背包:某件物品是一堆,可以带走其一部分
0-1 背包:对于某件物品,要么被带走(选择了它),要么不被带走(没有选择它),不存在只带走一部分的情况。
public class BagDemo1 {
        最大承重
        double bag;
        public void take(Goods[] goodslist) {
            // 对物品按照价值排序从高到低
            Goods[] goodslist2 = sort(goodslist);
            当前总重
            double sum_w = 0;
            //取出价值最高的
            for (int i = 0; i < goodslist2.length; i++) {
                sum_w += goodslist2[i].weight;
                if (sum_w <= bag) {
                    System.out.println(goodslist2[i].name + "取" +goodslist2[i].weight +"kg");
                }else{
                    System.out.println(goodslist2[i].name + "取" +(bag-(sum_w-goodslist2[i].weight)) +"kg");
                }
            }
        }
        // 按物品的每kg 价值排序 由高到低 price/weight
        private Goods[] sort(Goods[] goodslist) {
            return goodslist;
        }

public static void main(String[] args) {
BagDemo1 bd = new BagDemo1();
Goods goods1 = new Goods("A", 10, 60);
Goods goods2 = new Goods("B", 20, 100);
Goods goods3 = new Goods("C", 30, 120);
Goods[] goodslist = {goods1, goods2, goods3};
bd.bag = 50;
bd.take(goodslist);
}

回溯算法

public class NQueens {
    皇后数
    static int QUEENS = 8;
    下标是行,result表示queen存储在哪一列  
    int[] result = new int[QUEENS];
在每行放置Queen
    public void setQueens(int row) {
        //递归中断
        if (row == QUEENS) {
            printQueens();
            return ;
        }
        //在每行依次放置列 没有合适的则回到上一层
        for(int col=0;col<QUEENS;col++){
            if(isOk(row,col)){
                //设置列
                result[row]=col;
                //开始下一行
                setQueens(row+1);
打印输出
    private void printQueens() {
        for (int i = 0; i < QUEENS; i++) {
            for (int j = 0; j < QUEENS; j++) {
                if (result[i] == j) {
                    System.out.print("Q| ");
                }
                else {
                    System.out.print("*| ");
                }
            }
        }
判断是否可以放置
    private boolean isOk(int row, int col) {
        int leftup = col - 1;
        int rightup = col + 1;
        // 逐行往上考察每一行
        for (int i = row - 1; i >= 0; i--) {
            //列上存在queen
            if (result[i] == col) return false;
            //左上对角线存在queen
            if (leftup >= 0) {
                if (result[i] == leftup) return false;
            }
            //右下对角线存在queen
            if (rightup < QUEENS) {
                if (result[i] == rightup) return false;
            }
            leftup--;
            rightup++;
        }
        return true;
    }

 

11.redis是单线程的吗?那他是怎么支持高并发请求的?

redis 内部使用了一个叫 文件事件处理器, 最重要的是单线程   所以才有了 redis 是单线程的这一说法。
它采用 IO多路复用机制 来同时监听多个Socket ,根据 Socket 上的事件类型来选择对应的事件处理器来处理这个事件。

redis为什么快?

1. Redis 采用了单线程的模型,保证了每个操作的原子性,也避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU ,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
2. 灵活多样的数据结构。 redis 内部使用一个 redisObject 对象来表示所有的 key value redisObject主要的信息包括数据类型、编码方式、数据指针、虚拟内存等。它包含String Hash List ,Set, Sorted Set 五种数据类型,针对不同的场景使用对应的数据类型,减少内存使用的同时,节
省网络流量传输。
3. Redis是纯内存数据库,一般都是简单的存取操作,所以读取速度快。没有多线程的切换
4. 再说一下 IO Redis 使用的是非阻塞 IO 多路复用,使用了单线程来轮询描述,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。

超市老板:今天情人节,水果特价,30个顾客消费,排队结账!

顾客1 想买苹果,咱有苹果,拿给你,结账完成!
阻塞 IO:     顾客2 想买香蕉,咱没货了,去新发地进货,此时顾客 2 等着,后面的 28 个顾客都在等待!
非阻塞 IO: 请您旁边等待,我是店里的销货员,我将你的需求记录下来了,你在这喝点茶,货马上就来,不会耽误顾客3,顾客2也不会生气!
 
起初当看到别人说 redis 是单线程时,很容易想成 redis 服务端只开启了一个线程用于做所有事情,然而实际上我们所说的redis 单线程 只是针对 redis 网络请求模块,即上文提到的 件事件处理器

12.缓存穿透、缓存雪崩、缓存击穿

缓存雪崩
在高并发下,大量的缓存 key 同一时间 失效,导致大量的请求落到数据库上,如活动系统里面同时进行着非常多的活动,但是在某个时间点所有的活动缓存全部过期。
解决方案:
1. 设置热点数据永远不过期。
2. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
3. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
缓存穿透
访问一个 不存在的 key (查询 userid = -10 ),缓存不起作用,请求会穿透到 DB ,流量大时 DB 会挂掉
解决方案:
1. 接口层增加校验,如用户鉴权校验, id 做基础校验, id<=0 的直接拦截;
2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将redis中把这个 key对应的value值设为空值直接返回,拦截了再次访问数据库了
3. 设置 缓存有效时间短点,如30 秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id 暴力攻击
缓存击穿
访问一个 存在的 key 缓存过期的那一刻同时有大量的请求 ,这些请求都会击穿到 DB ,造成瞬时 DB 请求量大、压力骤增
解决方案:
1. 设置热点数据永远不过期。
2. 加互斥锁:业界比较常用的做法。简单地来说,就是在缓存失效的时候(判断拿出来的值是否为空),不是立即去加载数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX )去 set 一个 mutex key ,当操作返回成功时,再进行加载数据库的操作并回设缓存;否则,就重试整个get 缓存的方法。
    public String get(key) {
        String value = redis.get(key);
        if (value == null) {代表缓存值过期
            if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {代表设置成功
                value = db.get(key);去加载数据库
                redis.set(key, value, expire_secs);将数据库的数据放在redis中,并设置过期时间
                redis.del(key_mutex);
            } else {这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                sleep(50);
                get(key);重试
            }
        } else {
            return value;
        }
    }

13.项目解决方案:MQ消息积压以及处理方案

只能操作临时扩容,以更快的速度去消费数据:
1增加多个消费者,加速消费:新建topic引流,将消息引导别的程序中,洪水一个道理

14.如何保证MQ中的消息不丢失

生产者 丢失消息
1. 可以选择使用 rabbitmq 提供的事务功能,就是生产者在发送数据之前开启事务,然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会受到异常报错,这时就可以回滚事务,然后尝试重新发送;如果收到了消息,那么就可以提交事务. 缺点: rabbitmq 事务已开启,就会变为同步阻塞操作,生产者会阻塞等待是否发送成功,太 耗性能会造成吞吐量的下降。
2. 可以开启消息确认confirm模式 。在生产者设置开启了 confirm 模式之后,每次写的消息都会分配一个唯一的id ,如果写入了 rabbitmq 中, rabbitmq 会给你回传一个 ack 消息,告诉你这个消息发送OK
如果 rabbitmq 没能处理这个消息,会回调你一个 nack 接口,告诉你这个消息失败了,你可以进行重试。而且你可以结合这个机制知道自己在内存里维护每个消息的id,如果超过一定时间还没接收到这个消息的回调,那么你可以进行重发二者不同在于 事务机制是同步的,你提交了一个事务之后会阻塞住,但是confirm机制是异步的,发送消息之后可以接着发送下一个消息,然后rabbitmq 回调告知成功与否。一般在生产者这块避免丢失,都是用confirm 机制。
rabbitmq 自己 弄丢了数据
设置消息持久化到磁盘。设置持久化有两个步骤:
①创建 queue 的时候将其设置为持久化的,这样就可以保证 rabbitmq 持久化 queue 的元数据(数据的描述而不是数据本身),但是不会持久化queue 里面的数据。
②发送消息的时候将消息的 deliveryMode 设置为 2 ,这样消息就会被设为持久化方式,此时rabbitmq就会将消息持久化到磁盘上。
必须要同时开启这两个才可以。
而且持久化可以跟生产的 confirm 机制配合起来,只有消息持久化到了磁盘之后,才会通知生产者ack ,这样就算是在持久化之前 rabbitmq 挂了,数据丢了,生产者收不到 ack 回调也会进行消息重发。
消费者 弄丢了数据
使用 rabbitmq 提供的 ack 机制,首先关闭 rabbitmq 的自动 ack ,然后每次在确保处理完这个消息之后,在代码里手动调用ack 。这样就可以避免消息还没有处理完就 ack

15.如何保证MQ中消息的顺序性

为什么要保证顺序
消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序执行,否则就会造成数据异常。
比如数据库对一条数据依次进行了 插入-> 更新 -> 删除操作,这个顺序必须是这样,如果在同步过程中,消息的顺序变成了 删除-> 插入 -> 更新,那么原本应该被删除的数据,就没有被删除,造成数据不一致问题

保证消息的消费顺序
1. 拆分多个 queue ,每个 queue 一个 consumer ,就是多一些 queue 而已,确实是麻烦点;这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式去消费。

2. 一个 queue 对应一个 consumer ,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的worker 来处理  在java程序中自己写一个队列

16.什么是服务熔断?什么是服务降级?怎么操作?

在复杂的分布式系统中,微服务之间的相互调用,有可能出现各种各样的原因导致服务的阻塞,在高并发场景下,服务的阻塞意味着线程的阻塞,导致当前线程不可用,服务器的线程全部阻塞,导致服务器崩溃,由于服务之间的调用关系是同步的,会对整个微服务系统造成服务雪崩

为了解决某个微服务的调用响应时间过长或者不可用进而占用越来越多的系统资源引起雪崩效应就需要进行服务熔断和服务降级处理。
所谓的服务熔断指的是某个服务故障或异常一起类似现实世界中的“ 保险丝 " 当某个异常条件被触发就直接熔断整个服务,而不是一直等到此服务超时。
服务熔断就是相当于我们电闸的保险丝,一旦发生服务雪崩的,就会熔断整个服务,通过维护一个自己的线程池,当线程达到阈值的时候就启动服务降级,如果其他请求继续访问就直接返回fallback的默认值

17.EurekaZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别

  • ZooKeeper保证的是CPEureka保证的是AP
  • (C所有节点在同一时间的数据是一致的:A:高可用:这个服务一直能用 P是分区容错性:分布式系统里面的某个节点有故障仍然可以对外满足一致性或者可用性的服务,允许犯一点错)
  • ZooKeeperLeaderFollower角色,Eureka各个节点平等
  • ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
  • Eureka本质上是一个工程,而ZooKeeper只是一个进程
  • 出现故障时,Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)

18.负载均衡策略(雨露均沾)

使用负载均衡带来的好处很明显: 当集群里的1 台或者多台服务器 down 的时候,剩余的没有 down 的服务器可以保证服务的继续使用,使用了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu 急剧上升
负载均衡有好几种实现策略,常见的有: 随机  轮询    哈希(ip 信息%机器数量)   加权( 开挂 )    一致性哈希(服务器ip 信息 % 2 32 次方 -1 的结果落在哈希环上,顺时针取最近的)

19.Ribbon内置的负载均衡策略

20.Ribbon可以使用一致性哈希算法的策略吗 ?

可以,我之前使用过guava的一致性哈希算法,然后自己写一个类,继承 AbstractLoadBalancerRule
    @Component
    public class ConsistentHash extends AbstractLoadBalancerRule {
        private Logger log = LoggerFactory.getLogger(ConsistentHash.class);
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {log.warn("没有负载均衡策略!");
                return null;
            }
            Server server = null;
            int count = 0;
            while (server == null && count++ < 10) {
                List<Server> reachableServers = lb.getReachableServers();
                List<Server> allServers = lb.getAllServers();
                int upCount = reachableServers.size();
                int serverCount = allServers.size();
                if ((upCount == 0) || (serverCount == 0)) {
                    log.warn("没有获取到可用的服务器进行负载均衡: " + lb);
                    return null;
                }
                //获取请求URI
                RequestContext ctx = RequestContext.getCurrentContext();
                HttpServletRequest request = ctx.getRequest();
                String URI = request.getServletPath()+"?"+request.getQueryString();
                int hashcode = URI.hashCode();
                int model = Hashing.consistentHash(hashcode, serverCount); //一致性哈希,直接返回第几个数
                        server = allServers.get(model);
                if (server == null) {
                    /* Transient. */
                    Thread.yield();
                    continue;
                }
                if (server.isAlive() && (server.isReadyToServe())) {
                    return (server);
                }
                // Next.
                server = null;
            }
            if (count >= 10) {
                log.warn("重试了10次,仍然没有从负载均衡中获取活动的服务器: " + lb);
            }
            return server;
        }
        @Override
        public Server choose(Object key) {
            return choose(getLoadBalancer(), key);
        }
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub
        }
    }
 

猜你喜欢

转载自blog.csdn.net/m0_52238299/article/details/115380736
今日推荐