首页 > Microservice

Zookeeper 是一种分布式协调服务,在分布式环境中协调和管理服务是一个复杂的过程。ZooKeeper 通过其简单的架构和 API 解决了这个问题。ZooKeeper 允许开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布式特性。

 

分布式协调服务主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成”脏数据”的后果。

为了防止分布式系统中的多个进程之间相互干扰,需要一种分布式协调技术来对这些进程进行调度。

而这个分布式协调技术的核心就是来实现这个分布式锁。

 

分布式锁应该具备哪些条件?

1、在分布式系统环境下,一段代码在同一时间只能被一个机器的一个线程执行

2、高可用的获取锁与释放锁

3、高性能的获取锁与释放锁

4、具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)

5、具备锁失效机制,防止死锁

6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

 

关于业界比较流行的分布式锁实现方案:

一般来说Redis会经常被提及到,但是Redis并不是天生为了实现分布式锁而设计出来的, 他是个NoSql内存数据库;不过我们可以利用一些Redis的特性来实现 分布式锁 这个需求

这个链接文章比较清晰的说了redis如何实现分布式锁和redis分布式锁面临的问题,这个说的很清楚了我就不详细说了。

http://zhangtielei.com/posts/blog-redlock-reasoning.html

 

那Zookeeper呢 ,雅虎工程师设计Zookeeper的初衷,就是为了实现分布式锁服务的,利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。当然Zookeeper不止这个功能,比如它可以用来实现服务发现与注册的功能,单以这个需求来使用Zookeeper的也是很多的。

 

在说Zookeeper实现分布式锁之前,首先来讲下他为啥可以实现分布式锁,以及Zookeeper的数据模型:

Zookeeper 的数据模型很像数据结构当中的树,也很像文件系统的目录。

树是由节点所组成,Zookeeper 的数据存储也同样是基于节点,这种节点叫做 Znode,但是,不同于树的节点,Znode 的引用方式是路径引用,类似于文件路径:/car/bmw   、/animal/cat

这样的层级结构,让每一个 Znode 节点拥有唯一的路径。

 

那么Znode 包含哪些元素呢?有这么些:

  • data:Znode 存储的数据信息。
  • ACL:记录 Znode 的访问权限,即哪些人或哪些 IP 可以访问本节点。
  • stat:包含 Znode 的各种元数据,比如事务 ID、版本号、时间戳、大小等等。
  • child:当前节点的子节点引用

Zookeeper 是为读多写少的场景所设计。Znode 并不是用来存储大规模业务数据,而是用于存储少量的状态和配置信息,每个节点数据最大只有1M。

关于Zookeeper的基本增删改查的操作就不详细说明了,主要有这么几种:create、delete、exists、getData、setData、getChildren。

而跟增删改查息息相关的,Zookeeper 客户端在请求读操作的时候,可以选择是否设置 Watch,也就是Zookeeper的事件通知机制

 

可以把 Watch 理解成是注册在特定 Znode 上的触发器。调用了 create、delete、setData 方法的时候 (做出修改操作时),将会触发 Znode 上注册的事件,请求 Watch 的客户端会接收到异步通知。

大概的事件通知机制如下:

  • 客户端调用 getData 方法,watch 参数是 true。服务端接到请求,返回节点数据,并且在对应的哈希表里插入被 Watch 的 Znode 路径,以及 Watcher 列表。
  • 当被 Watch 的 Znode 已删除,服务端会查找哈希表,找到该 Znode 对应的所有 Watcher,异步通知客户端,并且删除哈希表中对应的 Key-Value。

 

好,说了这么多,差不过该进入主题如何实现分布式锁了,上边说了利用 Zookeeper 的顺序临时节点可以实现分布式锁,那么这顺序临时节点又是个什么玩意?

默认创建的Znode  是”持久节点” 创建节点的客户端与 Zookeeper 断开连接后,该节点依旧存在。

除此之外,还有其他几种,其实看名字也能猜出是啥用了:

持久顺序节点:  所谓持久顺序节点,就是在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号。其他和持久节点一样。

临时节点:  和持久节点相反,当创建节点的客户端与 Zookeeper 断开连接后,临时节点会被删除。

临时顺序节点: 顾名思义,临时节点和顺序节点的合成体;在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与 Zookeeper 断开连接后,节点会被删除。

 

Zookeeper 分布式锁的原理机制:

说完节点,也就差不多可以实现分布式锁了,其实知道这几个节点是个什么玩意后,在结合一下Watch机制,基本可以在脑内猜想个实现大概出来。

加锁释放锁流程:

1、首先,在 Zookeeper 当中创建一个持久节点,给它取个名字名字叫 ParentLock。当第一个客户端想要获得锁时,在 ParentLock 这个节点下面创建一个临时顺序节点 Lock1。之后,Client1 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock1 是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。

2、这时候,如果再有一个客户端 Client2 前来获取锁,则在 ParentLock 下再创建一个临时顺序节点 Lock2。

3、Client2 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock2 是不是顺序最靠前的一个,结果发现节点 Lock2 并不是最小的。于是,Client2 向排序仅比它靠前的节点 Lock1 注册 Watcher,用于监听 Lock1 节点是否存在。这意味着 Client2 抢锁失败,进入了等待状态,这就形成了一个等待队列。

4、  当任务完成时,Client1 会显示调用删除节点 Lock1 的指令。此时由于 Client2 一直监听着 Lock1 的存在状态,当 Lock1 节点被删除,Client2 会立刻收到通知。这时候 Client2 会再次查询 ParentLock 下面的所有节点,确认自己创建的节点 Lock2 是不是目前最小的节点。如果是最小,则 Client2 顺理成章获得了锁。

5、如果说获得锁的 Client1 在任务执行过程中如果崩溃了,则会断开与 Zookeeper 服务端的链接。根据临时节点的特性,相关联的节点 Lock1 会随之自动删除;所以Client2 又收到通知获得了锁。

 

说到这,是不是觉得Zookeeper的机制来实现分布式锁较为不错,人家都给你封装好了,不用向Redis那样实现分布式锁还得考虑超时、原子性、误删除之类的。

还有等待队列可以提升抢锁效率,比起Redis实现的效果确实是舒服了许多。

 

阅读全文

说是简单,tm那是找到解决方案之后才简单。

可能是我用的SpringCloud版本太新了,自己配zipkin server把我给配吐了。

又是版本冲突、又是注册不进去Eureka、又是访问ui报错、又是找不到ObjectProvider.orderedStream()方法的,我从百度搜到必应搜到google搜到Stack Overflow,整了一个上午。

我就想着,这玩意怎么能这么恶心呢?感觉像写maven的时候狂报xml错误那种感觉。

 

后来怼到zipkin官网 zipkin.io 上去,卧槽,快速入门里有这么句话:

If you have Java 8 or higher installed, the quickest way to get started is to fetch the latest release as a self-contained executable jar:

 

 

我佛了。然后又查了些关于高版本资料,关于 Zipkin 的服务端,在使用 Spring Boot 2.x 版本后,官方就不推荐自行定制编译了,反而是直接提供了编译好的 jar 包来给我们使用,这是官方提供的演示: https://github.com/openzipkin/docker-zipkin

白搞了半天,原来只要自己去下他的jar包然后启动这个服务就行了,至于如何将微服务跟踪的数据存到mysql/kafka/es 里边去,也可以直接用启动参数指定。

那我还自己配个蛇皮的zipkin server。

 

可以去这个网址: https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/ 选择下载zipkin的各个不同版本的启动jar,我随便找了一个,启动很顺畅,进入 localhost:9411 可以访问ui。

那服务端就算这样配完了,全部流程: 下载 –> 使用 java -jar 命令运行下载的jar

 

服务端搞完后,就到客户端了。

微服务链路跟踪嘛,需要得到请求经过了哪些微服务,了解请求流转的具体参数,那么客户端自然就是一个个不同的微服务了。

 

以我之前的user微服务为例,配置都一样。

build.gradle中加上这一行:

本来应该是spring-cloud-starter-sleuth,spring-cloud-sleuth-zipkin这两个依赖,但是spring-cloud-starter-zipkin这个集成了前面那俩,和前面那俩依赖效果是一样的。

加完依赖后只需要指定zipkin服务地址就好了

我的applicayion.yml:

 

spring.zipkin.base-url 指定地址。zipkin端口号默认9411。

spring.sleuth.sampler.probability 设置采样百分比,采样百分比就是会按比例来跟踪/保存多少条请求数据,因为在线上运行的话那数据是相当庞大的。默认设置的是0.1,也就是百分之十,我这设置百分之百是为了本地调试。

 

然后就完事了

对,没问题。所有微服务开起来,Eureka、Zuul等等,当然还有Zipkin官方服务。网页请求几个API,再去 localhost:9411 刷新一下,就可以看到每一条请求的详细信息了。

 

实现微服务跟踪需要的步骤:

1、 下载官方推荐的jar包

2、运行起来

3、改下微服务,加个依赖加个配置

完事了,如果想要保存数据到es/mysql/kafka里分析,可以设置启动参数,具体可以去看官方、github

 

 

我的使用Gradle搭的SpringCloud Finchley.SR3 项目整体地址:

https://github.com/skypyb/codeDemo/tree/master/sc-demo-microservice

阅读全文

上篇文章主要是搭建了一下Zuul的服务,并且实现了Zuul过滤器的自定义需求。

里边讲到了,Zuul 已经集成了 Ribbon、Hystrix ; 而 Ribbon无需配置,会在请求路由时自动给你进行负载均衡。

但是在Zuul服务路由不到对应微服务时,是没有对应的回退机制的,还是得自己手动写一下。

 

实现路由失败回退机制,首先需要继承 FallbackProvider 类,实现其getRoute() 和 fallbackResponse() 方法。

getRoute方法是指定你需要回退的线路,返回一个服务名就行。它会在路由该服务发生故障时调用 fallbackResponse 方法从而返回你一个 ClientHttpResponse 对象。

 

我写的回退代码:

 

 

这个代码会在路由到sc-demo-microservice-user这个服务出错时触发,然后根据不同的异常给出不同的响应(我这就是变了个Status哈)

配上一个回退机制后,就让Zuul服务更加完善健壮了,这样子,就算路由的服务挂掉了,我也能返回个东西让前端显示,也能做一些其余的抢救操作。

 

Zuul这个服务到这,其实也就差不多了,该有的都有,虽说也有Zuul这个服务挂掉的可能,但是Zuul也是可以集群的呀,前端可以访问一个负载均衡的服务器(比如Nginx),让他来转发请求到集群Zuul服务上边,这样子,整个微服务架构就非常健壮了(像这种Nginx服务一般不会挂哈,就一个纯走流量的服务器都能挂了,那你的流量是有多猛   除非硬件问题,那没辙)

 

咳咳, 说起来Zuul服务搭是搭好了,但是你就做一个服务路由+负载均衡,那也不是个事啊,我客户端那边请求服务,我要是需要多个数据,我是不是要发多个请求?

每个请求都要走–>zuul–>service 这一套流程,是不是不大好,毕竟我发多个请求来请求多个服务,总会有性能损耗。

那要是我就发一个请求到Zuul Server ,然后由Zuul 来帮我聚合请求多个微服务,包装成一个数据返回响应给我好不好鸭,那当然是极好的。这些微服务所在的服务器八成是在一个局域网里(除非你买的实例所在地区不同甚至供应商都不同哈) 那个网络传输速度,快的飞起来。

 

这个是我写请求聚合的代码:

 

我这就请求了zuul一个服务,然后就全权交给zuul给我把我要的数据都拿到了,组合成一个数据响应给我,是不是非常nice。我客户端的请求代码编写,也变得简单了。

 

到现在为止,整个Zuul架构就差不多了。自定义过滤器、回退、请求聚合、我还提供了一个zuul的集群高可用方案。基本上可以满足大多生产需求。

我的项目地址:Github

阅读全文

 

微服务架构有一个问题,那就是客户端如何访问各个微服务。

总不能在客户端APP/HTML写很多个不同的地址来请求吧?这样维护及其困难、开发不易。

这时候就需要一个网关,客户端的请求都发给这个网关, 然后由他来给你路由到别的微服务里边。

netflix 就开源了一个微服务网关:Zuul ,可以和 SpringCloudNetflix 全家桶(Eureka、Ribbon、Hystrix等)完美集成。

 

废话不多说.代码撸起来。

首先,项目创建,当然,创建的还是我那个Gradle搭的SpringCloudFinchley项目的子模块。

这回不用加什么依赖,只需要一个Zuul的依赖和Eureka Client的依赖就够了。

而且Zuul自己本身就集成了Ribbon和Hystrix,非常强大。

build.gradle:

 

然后在Application启动类上边加上@EnableZuulProxy 用以声明zuul代理

最主要的: 配置类yml文件

既然这个 zuul 是提供一个服务路由的功能,又已知 zuul 完美的集成了 Eureka。可以从Eureka Server中得到所有注册进去的服务。

那么,最需要进行配置的。自然是访问路径所对应的服务啦, 在zuul这个服务的配置文件里,专门有个地方给你写这个路由规则( zuul.routes )

我的配置文件:

 

虽然我这个配置文件里注释已经写得挺详细了,但还是简单的解释一下:

zuul.routes 这个属性树下面第一级节点,即我写的local-router、user-router 等,这一级只是让你写个路由规则的名字。 瞎写就行。

再下一级,大致可以分为两种:

1、路由到其余微服务的 指定好 service-id(即服务注册进Eureka的服务名) 和对应的访问路径就行了 ( 例如: 我用户微服务有个API请求路径是 user/{id},我现在就可以通过 ZUUL_ADDRESS:ZUUL_PORT/zuul_user/user/{id} 来访问我sc-demo-microservice-user中的API)。

 2、路由到本地由Zuul自己进行处理的 指定好客户端访问路径,和对应的本地API地址( forward: +本地API的地址) 就行

 

到这步,其实Zuul这个服务就搭建完毕了, 已经可以使用了。而且,不但可以帮你进行服务路由,还会用自带的Ribbon给你的请求进行负载均衡。

 

搭建完了,当然还不够,还能让这个 zuul 服务更加强大

zuul的路由功能是基于他自己编写的一大堆过滤器实现的,这里先简单说下他的过滤器类型

他主要有四大标准过滤器类型,这四种是最主要的

1、PRE  在请求被路由之前调用这个过滤器

2、ROUTING 这个过滤器将请求路由到微服务

3、POST 这个过滤器在路由到对应微服务之后再执行

4、ERROR 出错时进的

 

zuul 他自己有这几个过滤器的实现,当然,他写的我不动他,我可以自己额外写过滤器来实现自己的功能嘛。

zuul过滤器的实现非常简单,只要继承 ZuulFilter 这个类就完事了。

我写了个日志记录的过滤器:

这几个方法不多说,看下ZuulFilter源码的注释就知道具体是什么意思了。反正就是关于你写的过滤器的设置(类型、执行顺序、是否执行、具体逻辑)

我这过滤器很简单,就记录一下请求方法、地址,是个PRE类型的过滤器,会在路由之前调用。过滤器能干的事情很多,认证啊、加密啊啥的都行,看业务需求了。

 

 

阅读全文

上个文章使用了Eureka搭建了集群服务注册中心,但是最后实现 RPC 的方式还是  从代码中获得服务对应地址->字符串拼接->请求获得响应  的这样一种方式。

这种方式弊端还是有不少的。如果可以像调用自己的服务一样调用别人提供的服务那该多舒适啊。

 

而 Feign 就可以实现这种需求,Feign也是网飞开发的,SpringCloud 使用 Feign 非常简单,我下边演示一下:

首先 服务消费者这边肯定需要一个对应的依赖:

需要启用Feign的话,也得在启动类上面加个注解 @EnableFeignClients

然后,创建一个 Feign 的接口,像这样子

@FeignClient 注解里边的默认属性,也就是name属性是一个客户端的名字,如果使用了Eureka的话,会给他自动解析为 Eureka Server 服务注册表中的服务。

要是配了Ribbon,也会使用默认的负载均衡策略来执行请求。

Feign默认使用SpringMVC的注解声明请求,当然也可以用Feign自带的注解。不过没啥必要,还需要配置东西。

 

我上边这个例子写完了,实际使用的话只需要注入该类然后调用对应的方法就完事了。非常简便。

不过有几个点需要注意

在使用@FeignClient 声明的Feign伪装类中:

使用 @PathVariable 注解,必须加上参数!

GET请求无法使用对象作为入参! 要不有多少参数写多少参数(每个都要加@RequestParam(“参数名”) 注解),要不就用接受一个Map对象,也得加@RequestParam

POST请求可以接收对象,需要加上@RequestBody注解

 

 

Feign论使用的话,其实已经差不多了。

但是还有一些相关的操作也比较重要。

 

Feign 的请求都是使用的默认配置,我们其实可以实现自己的配置供 Feign 使用以实现编码、解码、日志记录、验证 等等等等。

比如我可以这么定义一个配置类:

该类配置了对应的日志记录器,和一个简单的效验,以应对请求的验证。

在对应的Feign伪装类中,上边的@FeignClient注解改一下,就可以使用自己写的配置:

之所以在 @FeignClient 注解中指定配置,是因为我的配置类是没有加 @Configuration 注解的,我想要实现细粒度的控制。 推荐这样做。

如果加了@Configuration 注解,则 Spring 会将其自动解析自动应用到全局,这样子就不方便为每个请求进行细粒度调整。

 

 

阅读全文
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00