首页 > 技术文章 > 知识整理

KevinStark 2019-03-11 11:09 原文

Eureka服务注册和发现原理

eureka服务注册发现架构图

  • 有三个角色

    • 服务提供者(Eureka client)

    向服务注册中心注册自己的信息

    • 服务消费者 (Eureka client)

    向服务注册中心注册自己的信息,并获取自己要消费的服务提供者信息

    • 注册中心 (Eureka Server)

    接收服务消费者和提供者的注册的信息

dubbo + ZK如何实现CP、Eureka怎么实现AP
  • dubbo + zk

当向注册中心查询服务注册列表时,可以容忍注册中心返回的是几分钟以前的注册信息,但是不能接受服务直接down掉不可用。服务注册功能对可用性的要求高于一致性。在zk选举的时候,整个集群不可用,这样就导致注册服务瘫痪,漫长的选举期间导致整个注册服务长期不可用

  • Eureka 的AP

在设计的时候优先保证可用性。eureka的各个节点都是平等的,几个节点down掉不会影响其他节点的提供注册和查询服务的功能。而Eureka的客户端在向牧歌Eureka注册如果链接失败,则会自动切换到其他节点,只要有一台Eureka正常工作,就能保证注册服务可用,只不过查询服务的结果可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内,85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  1. Eureka不再从注册列表中移除因为长时间没有收到心跳的而过期的服务;
  2. Eureka仍然能够接收新的服务注册和查询请求,但不会同步到其他节点(保证当前节点依然可用);
  3. 当网络稳定时,当前实例新的注册信息会同步到其他节点;
    因此,Eureka可以很好的应对网络故障导致部分节点失去联系的情况,而不会像zk那样因为选举导致整个集群不可用

总之,dubbo+zk强调的是CP,强一致性,是zk的特性决定,而Eureka强调的是AP,高可用

Hystrix怎么实现熔断机制

Hystrix包括服务降级服务熔断, 线程合信号隔离请求缓存, 请求合并服务监控

  • hystrix对于依赖服务采用依赖隔离的方式主要有线程隔离合信号量隔离

    • 线程隔离:卫每个依赖服务创建一个独立的线程,性能上低于信号量隔离
    • 信号量隔离:用信号量控制单个依赖服务,开销小于线程隔离,但无法异步和设置超时。
  • 定义服务降级

    • fallback是实现服务降级的后备方法。只要在fallbackmethod属性指定的对应的方法就可。
  • 异常处理

    • 忽略异常:对某个异常,不调用fallback操作,而是抛出
    @HystrixCommand(ignoreExceptions = {需要忽略的异常类.class})
    
    • 异常分类降级:根据不同的异常,采用不同的降级处理
  • 请求缓存

    • @CacheResult:标记请求命令的结果应该被缓存,必须和@HystrixCommand注解结合使用,所用属性有:cacheKeyMethod
    /*
    代码中CacheResult代表开启缓存功能,当调用结果返回后Hystrix缓存,缓存的key值不指定就使用该方法中的所有参数,
    */
    @CacheResult(cacheKeyMethod = "getNameByidCacheKey")
    @HystrixCommand(fallbackMethod = "hiFallback")
    public String hiService(String name) {
        return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
    }
    
    private Long getNameByidCacheKey(Long id) {
        return id;
    }
    
    • 删除缓存:标记请求命令的缓存失效,失效的缓存根据定义的Key决定,常用属性 :command、cacheKeyMethod
    @CacheResult
    @HystrixCommand(fallbackMethod = "hiFallback")
    public String hiService(@CacheKey("name") String name) {
        return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
    }
    
    @CacheRemove(commandKey = "hiService")
    public void update(@CacheKey("name")User user){
    
        restTemplate.getForObject("http://USER-SERVICE/users",user,User.class);
    
    }
    
redis数据类型,底层是什么。

String

redis集群原理,怎么实现高可用,一致性hash算法。
redis怎么实现主从同步的。
dubbo工作原理,生产者、服务者之间是怎么通信的

摘抄自官网

dubbo架构原理

  • 调用关系说明
    • 0:服务容器负责启动,加载,运行服务提供者。
    • 1.服务提供者在启动时,向注册中心注册自己提供的服务。
    • 2.服务消费者在启动时,向注册中心订阅自己所需的服务。
    • 3.注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
    • 4.服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
    • 5.服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
dubbo负载均衡
  • 随机负载均衡(RandomLoadBalance):先统计所有服务器上该接口方法权重总和,然后再随机nextInt,看生成的随机数落在哪个段内,就调用哪个服务。默认使用的就是随机轮询算法
  • 轮询(RoundRobinLoadBalance):如果所有服务器接口方法的权重都一样,依次调用;
  • 最少活跃负载均衡(LeastActiveLoadBalance)
dubbo容错,服务治理
  • 在dubboAdmin中,可以对具体的服务设置响应的权重、负载均衡等设置
hashmap为什么线程不安全

在进行扩容的时候,由于要将原有链表中的数据重新进行rehash运算,原有的链表顺序会由于rehash原因被定位到其他的数组下标下面,在并发多线程的情况下,存在同时其他元素的put操作,如果hash值相同,可能造成环形链表

服务隔离
垃圾回收算法有哪些,优点和不足
  • 标记-清除(mark swap)
    • 将标记成垃圾的对象清除出内存,容易造成内存空间的不连续性,在分配打对象的时候,由于连续空间的不足而频繁触发内存回收;
    • 适用于老年代,对象存活时间长的内存区域
  • 复制
    • 将内存块等分成相同大小的内存块。一块用于对象存储。当垃圾回收的时候,将未被标记回收的内存复制到另一块 内存块,然后剩下的就全部是要回收的内存
    • 缺点:只有一半的内存被使用
    • 优点:未回收的内存块还是连续的
    • 适用于新生代:朝生暮死的区域。在新生代中,又被分为Eden,survivor区,Eden区分配的对象都是朝生暮死;eden与survivor的比例时8:1;在Eden区,使用的回收算法是复制算法
  • 标记-整理
    • 标记-清除的升级版本。将不用回收的内存区域移到一起,然后再集中处理需要回收的区域
在dubbo中,如果zookeeper不工作的话,消费者能否调用到服务者
  a. 能调用到,zookeeper不工作的时候,消费者能取得本地缓存的地址列表直接调用服务者
  b. 消费者能绕过zk来直接发起调用,在使用url标签,直接指定哪个提供者
为什么要用分布式锁分布式锁有哪些,怎么实现
jvm内存模型,各个模型存储的是什么。
redis与memcache的相互比较
  • redis和memcache都是将数据放置到内存中,memcache还能存放图片、视频等数据;
  • redis除了K/V类型数据,还支持List,Set,Zset,List,Hash这些数据类型
  • 在redis中,当内存存满数据后,它会将不常用的value写到磁盘中;
  • 过期策略-memcache在set时就指定。redis可以只用expire来设定
  • 集群-memcache利用magent做一主多从;redis可以做一主多从,一可以做一主多从;
  • 安全性-memcache挂掉后,所有的数据就没了;redis可以定期持久化到磁盘中
  • 容灾恢复-memcache挂掉后,数据不可恢复,redis可以通过aof恢复
  • redis支持数据的备份,即master-slave模式数据备份
  • redis与memcache的异同点
    • 存储方式
    • 数据类型
    • 底层模型
    • 运行环境
redis为什么这么快
  • 单线程
  • IO非阻塞多路复用

    I/O多路复用实际上是指多个连接的管理可以在同一进程。多路是指网络连接,复用只是同一个线程。在网络服务中,I/O多路复用起的作用是一次性把多个连接的事件通知业务代码处理,处理的方式由业务代码来决定。在I/O多路复用模型中,最重要的函数调用就是I/O 多路复用函数,该方法能同时监控多个文件描述符(fd)的读写情况,当其中的某些fd可读/写时,该方法就会返回可读/写的fd个数。

    Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll的read、write、close等都转换成事件,不在网络I/O上浪费过多的时间。
redis主从备份原理过程

主从同步流程图

slave发送sync指定,此时master执行bgsave命令进行全量备份,并缓存所有的写命令;等bgsave执行完后,将rbd发送给slave,然后slave将数据写入;写入完所有快照后,master再发送缓存的写指定到slave,slave就写相应的数据,之后的增量的同步的方式同步master数据

  • 同步方式
    • bgsave - 全量备份
    • AOF(append only file)- 增量
redis cluster的高可用和主备切换原理
  • 判断节点宕机
    如果一个节点认为另外一个节点宕机,那么就是pfail 主观宕机。如果多个节点认为另外一个节点宕机,那么就是fail客观宕机,跟哨兵原理一致,sdown, odown
  • 从节点过滤

对宕机的master节点,从其所有的slave的节点中选择一个切换成 master节点。
检查每个slave节点与,master的断开实践,如果超过了 cluster-node-timeout * cluster-slave-validity-factor 该slave节点就没有资格晋升为master节点了。

  • 从节点选举
    每个从节点都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制的数据越多)的从节点,选举时间越靠前,优先进行选举。
    所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master 节点 (n/2 + 1) 都投票给了某个从节点,那么该从节点就晋升成master。
redis节点间的内部通信机制
  • 基本通信原理
    集群元数据的维护有两种方式:集中式Gossip协议。redis cluster节点间采用的是gossip协议。
    • 集中式是将集群元数据(节点信息,故障etc)集中存储在某个节点上。
    • redis集群维护元数据采用gossip协议,所有节点都持有一份元数据,不同节点如果出现了元数据的变更,就不断将元数据发送给其他的节点,让其他节点也进行元数据变更。
  • 集中式Gossip协议的优缺点
    • 集中式的好处在于元数据的读取更新时效性非常好,一单出现数据变更,就立刻更新到集中式的存储中,其他节点读取的时候可以随时感知;不足之处是所有的元数据的更新集中于一个地方,可能会导致元数据的存储有压力。
    • Gossip协议的好处在于元数据比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低压力;不足之处,元数据更新有延迟,可能导致集群中的一些操作会滞后;
      • 10000端口:每个节点都有一个鱼鱼节点间通信的端口。每个节点每隔一段时间会往其他节点发送ping消息,同时其他几个节点接收到ping后返回pong.
      • 交换信息:交换的信息包括故障信息、节点增加和删除,hash slot 等信息
  • Gossip协议
    gossip协议包括多种消息,包含ping,pong,fail
    • meet: 某个节点发送meet给新加入的节点,让新节点加入集群,然后新节点会开始与其他节点进行通信。如下指令就是发送meet消息给新加入的节点,通知先加入的节点加入集群
    redis-trib.rb add-node
    
    • ping:每个节点会频繁的给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据。
    • pong: 返回ping和meet,包含自己的状态和其他信息,也用于信息广播和更新。
    • fail:某个节点判断另外一个节点fail后,就发送fail给其他节点,通知其他节点说,某个节点宕机了。
redis cluster的hash slot算法

redis cluster有固定的16384个slot。对每个key进行CCR16值,再对16384取模。这样就能获取到可以对应的hash slot。

redis clsuter中队每个master都会持有部分的slot,如果有3个master,那么每个master就持有 16384 /3 个hashslot。hash slot 让节点的增加和移除很简单,增加一个master,就讲其他master的hash slot移出部分过去,减少一个master,就将它的hash slot移动到其他的master上。任何一台机器宕机,另外两个节点互不影响。因为key找的是hash slot不是机器

redis线程模型

redis内部使用文本事件处理器 file event handler。这个文本事件处理器是单线程的,所以说redis的工作模式是单线程的。它采用IO多路复用机制监听多个socket,将产生事件的socket压入内存队列中,时间分派器根据socket上的事件类型来选择对应的时间处理器进行处理;

文本事件处理器包含四个部分:

  1. 多个socket
  2. 多路IO复用
  3. 文本事件分派器
  4. 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
redis 单线程模型也能效率这么高?
  1. 纯内存操作
  2. 核心是基于非阻塞的 IO 多路复用机制
  3. 单线程反而避免了多线程的频繁上下文切换问题
dubbo与springcloud的比较
  • dubbo是通过二进制传输,占用宽带资源较少;springcloud是通过rest+json传输的,所以在性能上面,dubbo要优于springcloud;
  • 在多数情况下,dubbo使用的是长连接小数据量的模式工作的,少部分情况下使用的短连接大数据量的工作模式
redis持久化

参考链接

  • RDB(bgsave)

    RDB 是 Redis默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis重启会通过加载dump.rdb文件恢复数据。

  • AOF

分布式事务
  • 理论

    • 2pc

    在2PC的理论中,需要引入一个人协调者来统一掌控所有参与者的操作结果;整个过程有两个阶段

    1. 投票阶段;在投票的阶段中,参与者必须回复协调者,如果其中有一个参与者没有回复,整个阶段将处于阻塞的状态;
    2. 提交阶段;将所有参与者的回复提交给其他的参与者;在这个阶段,不仅要锁住所有所有参与者的资源还得锁住协调者资源

    2pc知识一个理论,效率很低

    • tcc
  • 消息最大努力交互

RPC
  • 定义
    • RPC主要功能目标是让构建分布式计算更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性
  • 调用方式
    • 同步调用:客户端等待调用执行完后并返回结果。
    • 异步调用:客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果
  • 结构拆解
    结构拆解示意图
    RPC服务器通过RPC server去导出远程接口方法,客户方import的方式去引入远程接口方法。客户方像调用本地方法一样调用远程接口方法,RPC框架提供接口的代理实现,实际的调用委托给代理RPCProxy。代理封装调用信息并将调用交给RpcInvoker去实际执行。再客户端的RpcInvoker通过连接器RpcConncecttor去维持与客户端的通道RpcChannel,并使用RpcProtocol执行协议编码并将编码后的请求消息通过通道发给服务方。Rpc服务端接收器RpcAcceptor接收客户端的调用请求,同样使用Rpcprotocol执行协议解码,并将解码后的信息传递给rpcprocessor去控制处理调用过程,最后委托调用Rpcinvoker去执行并返回调用结果。
  • 主要组件
    • RpcServer: 负责导出(export)远程接口
    • RpcClient:负责导入(import)远程接口的代理实现
    • RpcInvoker:
      • 客户方实现:负责编码调用信息和发送调用请求到服务方并等待结果返回(同步)
      • 服务方实现:负责调用服务端接口的具体实现并返回调用结果
    • RpcProtocol:负责协议编码/解码
    • RpcConnector:负责维护客户方与服务方的连接通道和发送数据到服务方。
    • RpcAcceptor:负责接收客户方请求并返回请求结果
    • RpcProcessor:负责在服务方控制调用过程,包括姑是哪里调用线程池,超时时间等。
    • RpcChannel:数据传输通道。
equals() 与 hashcode()
  • 为什么重写了equals方法必须重写hashcode方法

    相等的对象必须具有相等的散列码。
    • 覆盖equals方法时,需要遵循以下通用准则
      1. 自反性

        对于任意非null的引用值x,x.equals(x)必须返回true。
      2. 对称性

        对于任意非null的引用值x、y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
      3. 一致性性

        对于任意非null的引用值x、y,只要equals方法的比较操作在对象中所用的信息没有发生改变,那么多次调用x.equals(y)应该一致的返回true或false。
      4. 传递性
        对于任意非null的引用值x、y、z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)必须返回true。
    • hashcode在java中的通用准则
      1. 在应用运行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么多次调用该对象的equals方法应该始终如一的返回同一个整数在同一个应用程序的多次执行过程中,每次执行equals方法所返回的整数可以不一致。
      2. 如果两个对象使用equals(Object)方法比较是相等的,那么调用这两个对象中的任意一个对象的hashCode方法都必须产生相同的一个整数结果。 重写equals方法就必须重写hashcode方法的原因
      3. 如果两个对象使用equals(Object)方法比较是不相等的,那么调用这两个对象中的任意一个对象的hashCode方法,则不一定要产生不同的整数结果。如果给不同的对象产生不同的hash码,有可能提高散列表性能(比如往HashMap中添加数据时,具体添加到哪个桶中,就是根据(table.length - 1) & hash来计算的)
JWT安全性问题

对比于 session和cookie安全性,在获取到cookie或者session后,做不了数据被篡改和循环调用的防范措施

JWT如何防止篡改数据
  • JWT的组成部分
    • header(头部),头部信息主要包括(参数的类型--JWT,签名的算法--HS256)
        {
          "typ": "JWT",
          "alg": "HS256"
        }
    
    • poyload(负荷),负荷基本就是自己想要存放的信息(因为信息会暴露,不应该在载荷里面加入任何敏感的数据),有两个形式,
        {
         "iss": "John Wu JWT",
         "iat": 1441593502,
         "exp": 1441594722,
         "aud": "www.example.com",
         "sub": "jrocket@example.com",
         "from_user": "B",
         "target_user": "A"
        }
    
    • sign(签名): 将前面两个编码后的字符串用 . 拼装在一起,然后将拼接完后的字符串用header标识的 HS256进行加密,在加密的时候,我们还需要提供一个密钥(secret) 签名的作用就是为了防止恶意篡改数据。只要加密密钥不丢失,就不能踹改请求数据
JWT如何防止在token丢失的情况下重复请求

可以在playload中加入时间戳并且前后端都来参与解决。

1. 前端生成token时,在payload里增加当前时间戳
2. 后端接收后,对解析出来的时间戳和当前时间进行判断
3. 如果相差特定时间内(比如2秒),允许请求否则判定为重复攻击
基础
面向对象

面向对象是模型化的,只要抽象出一个类,里面就有数据和解决问题的方法。需要什么功能直接使用就可以,用户不需要怎么去实现的

面向对象的三大特性:

  1. 封装

    隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
  2. 继承

    提高代码复用性;继承是多态的前提。
  3. 多态

    父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。
线程相关
相关概念
  • 并发

    多个任务在同一时间段内执行。这些任务不是顺序执行的,而是以交替的方式被执行
  • 并行

    多个任务在同一时刻被执行
synchronized 作用域

synchronized的作用域分为 类锁对象锁

  • 类锁(针对这个类)
  1. 类锁需要 synchronized 来修饰静态 static 方法,写法如下
        public static synchronized void test(){
         // TODO
        }
    
  2. 使用代码块,需引用当前的类
        public static void test(){
            synchronized (TestSynchronized.class) {
            // TODO
        }
    }
    

类锁和对象锁其实是一样的,由于静态方法是类所有对象共用的,所以进行同步后,该静态方法的锁也是所有对象唯一的。每次只能有一个线程来访问对象的该非静态同步方法。 类锁和对象锁是不一样的锁,是互相独立的

  • 对象锁 (针对new出来的对象)
  1. synchronized修饰的非静态方法,修饰实例方法时,锁定的是当前对象
    public synchronized void test(){
            // TODO
    }
    
  2. 代码块使用 synchronized 修饰的写法,使用代码块,如果传入的参数是 this,那么锁定的也是当前的对象
        public void test(){
            synchronized (this) {
                // TODO
            }
        }
    

同步能够使得一个线程执行完毕后,另一个线程才开始执行

线程同步和线程安全的区别
  • 线程同步

    线程同步更强调的是多个线程间的执行顺序
  • 线程安全

    线程安全更加强调的是对资源的访问控制
实现线程同步有哪些方式
ConcurrentHashMap线程安全

推荐阅读