-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 85.5 KB
/
Copy pathcontent.json
File metadata and controls
1 lines (1 loc) · 85.5 KB
1
{"meta":{"title":"wr4javaee","subtitle":null,"description":null,"author":"wr4javaee","url":"https://wangran.me"},"pages":[{"title":"404","date":"2016-07-22T09:56:56.000Z","updated":"2019-06-05T13:59:40.812Z","comments":true,"path":"404/index.html","permalink":"https://wangran.me/404/index.html","excerpt":"","text":""},{"title":"about","date":"2016-07-22T02:18:45.000Z","updated":"2019-06-05T13:59:40.816Z","comments":true,"path":"about/index.html","permalink":"https://wangran.me/about/index.html","excerpt":"","text":"关于我 初级java码农一枚 联系我 email:[email protected] QQ:332863826 模板来源 模板作者:MOxFIVE"},{"title":"tags","date":"2016-07-22T02:17:44.000Z","updated":"2019-06-05T13:59:40.844Z","comments":true,"path":"tags/index.html","permalink":"https://wangran.me/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"Spring Cloud Finchley 在业务系统中的实践总结 - Zuul的实践与增强","slug":"spring-cloud_zuul_01","date":"2018-10-19T07:38:00.000Z","updated":"2019-06-06T05:02:02.561Z","comments":true,"path":"2018/10/19/spring-cloud_zuul_01/","link":"","permalink":"https://wangran.me/2018/10/19/spring-cloud_zuul_01/","excerpt":"前言 本文主要针对Zuul在实际业务场景中的一些实践心得进行总结,重点围绕Zuul本身关于动态配置缺陷问题、老项目接入及老环境上线问题等方面进行讨论。关于Zuul的一些基本用法,可直接参考Spring Cloud官方文档 与Zuul Github。 我们使用的Zuul基于Spring官方目前最新版本Spring Cloud Finchley.SR1。","text":"前言 本文主要针对Zuul在实际业务场景中的一些实践心得进行总结,重点围绕Zuul本身关于动态配置缺陷问题、老项目接入及老环境上线问题等方面进行讨论。关于Zuul的一些基本用法,可直接参考Spring Cloud官方文档 与Zuul Github。 我们使用的Zuul基于Spring官方目前最新版本Spring Cloud Finchley.SR1。 Zuul是什么 Zuul可以用两个单词总结,即路由和过滤器(Router and Filter),它是Netflix开源,并被Spring所集成,成为当前Spring Cloud最核心的组件之一。它的基本功能可概括如下: 认证鉴权(Authentication) 审查(Insights) 压力测试(Stress Testing) 金丝雀测试(Canary Testing) 动态路由(Dynamic Routing) 服务迁移(Service Migration) 负载剪裁(Load Shedding) 安全(Security) 静态应答处理(Static Response handling) Active/Active traffic management 为什么要使用它 路由是微服务的架构体系当中必不可少的一部分,那么缺少它会带来什么问题呢?这里以公司某业务线实际场景为例: 上图是是一个简单的前后端交互架构,来自PC/微信/H5/IOS/Android等客户端将请求发送至Nginx,后由Nginx反向代理到指定后端服务集群中。我们能够很方便的为后端服务集群提供负载均衡,同时通过Nginx的反向代理,可以隐藏后端真实IP,即提供了一定程度的安全保障,也使得前后端服务调用隔离,降低开发成本。 随着业务规模的不断扩大,后端微服务已从数个服务集群扩展到了数十个,此时Nginx为每一个服务集群都配置了唯一的域名用于反向代理,每当这些服务集群需要扩容或机器迁移时,需要运维投入大量的精力去手动修改配置。此外,这些数十个域名也造成了资源的极度浪费。 在近些年火热的微服务浪潮下,我们都热衷于将每一个原子业务单元都拆分成微服务,这对运维来说造成了很多压力,就好像上图中,当你面相这种规模的服务集群时,传统的维护手段已接近于灾难,必须要想办法借助更多的工具来帮助我们。 相比Nginx,我们可以将Zuul理解为微服务集群的一个可靠的大管家,它是上述问题的解决方案之一。使用它,可以方便的用一个域名来替换掉之前的数十个域名,此外,当服务集群扩容或迁移时,Zull可以做到自动识别,即动态路由,它还具有服务鉴权、服务降级、熔断、分流控制、接口控制等Nginx所不具备的重要功能。 问题与挑战 Spring Cloud Finchley版本基于Spring Boot2.0构建,按照约定大于配置的思想,我们能够很轻易的上手使用这些开源框架。但是任何开源框架放到实际应用场景中,都不可能百分百的切合我们的实际需求,例如 当前Spring Cloud Zuul最新版本不支持路由的动态配置 虽然Zuul能够做到动态路由,但是反向代理的配置信息是需要在application.yml配置文件中维护的,这意味着每一次有新的服务集群需要进行反向代理时,我们需要像重启Nginx一样来重启Zuul,重启意味着增加了上线的风险与成本。 非Spring Boot项目接入问题 对于任何一个Spring Boot项目来说,可以非常简单方便的与Spring Cloud Zuul整合,但是官方并未提供一些工具来支持一些老项目的接入,目前实际业务线大部分服务均是非Spring Boot的老服务,我们需要自己来实现这些通用的工具组件。 老环境上线迁移问题 长期以来前端业务线一直采用的是Request-Nginx-Server架构。基于前文介绍,我们的Request端分散在各大手机应用市场、微信等合作渠道,对于这些已经集成了数十个不同服务域名的前端来说,接入Zuul会有一系列的问题需要解决: App端每次更新都要重新发版,时效性差 App端接入Zuul后若出现问题,无法及时回滚 旧版本App无法修改接入Zuul 上线验证问题 测试与线上环境不一致,上线存在未知的风险 我们的业务模式,无法在线上环境进行全流程测试 基于业务模式,系统中有大量节点需要对接第三方机构,无法进行全流程测试,同时,当前我们的测试环境无法做到与线上环境完全一致,对于Zuul的接入来说是一个非常大的挑战。 Zuul的核心架构 Zuul的核心其实很简单,就是Filter,如下图所示: 可以看到,Zuul将过滤器大致分为4类,Pre、Routing、Post/Error pre: 这种过滤器在请求被路由之前调用,安全与鉴权功能可在此实现。 routing:通过HttpClient/Ribbon将请求路由到后端服务。 post:路由后执行,可以接收到后端服务的响应信息,并转发给请求端。 error:在其他阶段发生错误时执行该过滤器。 Spring Zuul提供的默认过滤器 Zuul已经为我们封装好了基本的过滤器,表格中Order是这些过滤器的执行顺序,可见Zuul为我们充分预留了很多空间来对默认过滤器进行增强,例如 安全/鉴权 流量控制 接口控制 访问统计 … 动态路由增强,实现路由的动态配置 Zuul相比较于Nginx的一个很大优势在于其提供的动态路由功能,前文已经介绍过,当服务过多的时候,运维需要大量的时间去手动维护路由的映射关系,极易造成严重的线上问题。 好在Zuul与Eureka(注册中心)在Spring Cloud中实现了完美的整合,Eureka能够实时管理不同集群的每一个节点信息,Zuul便能够通过注册中心获取每一个服务的详细清单,并通过Ribbon实现负载均衡,从而达到请求到转发的自动路由机制。 前文提过Zuul并没有实现路由的动态配置,每次新增反向代理规则都需要重启Zuul未免有点尴尬,是否可以修改Zuul来避免重启呢?我们需要看一下Zuul的动态路由的实现原理。 源码分析 找到spring-cloud-netflix-zuul-2.0.1.RELEASE.jar,看一下路由定位器相关实现: RouteLocator 路由定位基础接口 RefreshableRouteLocator 提供刷新接口 SimpleRouteLocator 基础实现定位器,主要实现了路由定位与路由加载逻辑 CompositeRouteLocator 复合定位器,提供路由定位、路由刷新功能 DiscoveryClientRouteLocator 组合静态以及配置好的路由 通过查看Zuul的源码发现,Zuul已经为我们定义好路由定位于刷新的接口标准,RouteLocator接口主要定义了路由定位器,如下 1234567891011121314/** * Ignored route paths (or patterns), if any. */Collection<String> getIgnoredPaths();/** * A map of route path (pattern) to location (e.g. service id or URL). */List<Route> getRoutes();/** * Maps a path to an actual route with full metadata. */Route getMatchingRoute(String path); RefreshableRouteLocator接口,只定义了一个路由信息刷新接口 1void refresh(); SimpleRouteLocator,主要实现了RouteLocator接口,在内部引入了ZuulProperties。ZuulProperties的配置是我们在application.yml中配置好的Zuul相关信息,其中包含了反向代理的配置,SimpleRouteLocator是基于静态配置文件的路由定位器的重要实现。 123456789101112131415161718192021private ZuulProperties properties;... @Override public List<Route> getRoutes() { List<Route> values = new ArrayList<>(); for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) { ZuulRoute route = entry.getValue(); String path = route.getPath(); values.add(getRoute(route, path)); } return values; }... @Override public Route getMatchingRoute(final String path) { return getSimpleMatchingRoute(path); } 接下来再来看看实现了刷新接口的CompositeRouteLocator与DiscoveryClientRouteLocator,DiscoveryClientRouteLocator继承了SimpleRouteLocator,同时实现了RefreshableRouteLocator。 12public class DiscoveryClientRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator { 它主要增加了DiscoveryClientRouteLocator方法,用于通过DiscoveryClient(例如Eureka)发现路由信息,以及实现了动态的路由刷新接口,这里的doRefresh仅仅是调用了SimpleRouteLocator的方法。 1234567/** * Calculate all the routes and set up a cache for the values. Subclasses can call * this method if they need to implement {@link RefreshableRouteLocator}. */protected void doRefresh() { this.routes.set(locateRoutes());} CompositeRouteLocator,其内部维护了routeLocators的集合,并继承了RefreshableRouteLocator方法。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455/** * RouteLocator that composes multiple RouteLocators. * * @author Johannes Edmeier * */public class CompositeRouteLocator implements RefreshableRouteLocator { private final Collection<? extends RouteLocator> routeLocators; private ArrayList<RouteLocator> rl; public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) { Assert.notNull(routeLocators, "'routeLocators' must not be null"); rl = new ArrayList<>(routeLocators); AnnotationAwareOrderComparator.sort(rl); this.routeLocators = rl; } @Override public Collection<String> getIgnoredPaths() { List<String> ignoredPaths = new ArrayList<>(); for (RouteLocator locator : routeLocators) { ignoredPaths.addAll(locator.getIgnoredPaths()); } return ignoredPaths; } @Override public List<Route> getRoutes() { List<Route> route = new ArrayList<>(); for (RouteLocator locator : routeLocators) { route.addAll(locator.getRoutes()); } return route; } @Override public Route getMatchingRoute(String path) { for (RouteLocator locator : routeLocators) { Route route = locator.getMatchingRoute(path); if (route != null) { return route; } } return null; } @Override public void refresh() { for (RouteLocator locator : routeLocators) { if (locator instanceof RefreshableRouteLocator) { ((RefreshableRouteLocator) locator).refresh(); } } }} routeLocators就是我们需要的入口了,如果我们可以自定义一个通过数据库来加载的路由Locator,并自己实现refresh方法,那么就初步实现了路由动态配置最重要的一步。 如何将自定义Locator添加至routeLocators集合中呢?我们来梳理一下Zuul初始化的过程,在jar中我们能够找到有两个分账关键的配置类,分别是ZuulServerAutoConfiguration与ZuulProxyAutoConfiguration。 ZuulServerAutoConfiguration,里面初始化了CompositeRouteLocator、SimpleRouteLocator、ZuulController、各种关键默认的Filter、ZuulRefreshListener等,是我们要实现目的的关键入口。 123456 @Bean@Primarypublic CompositeRouteLocator primaryRouteLocator( Collection<RouteLocator> routeLocators) { return new CompositeRouteLocator(routeLocators);} 这里是CompositeRouteLocator初始化入口,由此可见我们完全可以将自定义的Locator加载进去。 自定义路由定位器与路由刷新策略 根据上面的方法,我们便可以将自定义的locator添加进CompositeRouteLocator中的locators集合。 定义一个MyDynamicRouteLocator,实现RefreshableRouteLocator,Ordered接口,其核心功能有两点,其一是将数据库中配置的路由信息加载,作为路由定位方法判定的基础,其二是实现刷新方法,使之能够识别数据库中变化的配置信息。 1public class MyDynamicRouteLocator implements RefreshableRouteLocator, Ordered { 然后,在项目启动时初始化MyDynamicRouteLocator 12345678910@Configurationpublic class DynamicRouteConfiguration { ... @Bean public MyDynamicRouteLocator dynamicRouteLocator() { ... }} 路由动态配置的优化 Zuul默认会维持心跳每隔30秒调用一次refresh方法来刷新路由信息,如果路由配置发生变更时,我们希望它可以实时生效,如何做呢? 前面提到ZuulServerAutoConfiguration内部初始化了一个ZuulRefreshListener,它实现了Spring的ApplicationListener接口,当它监听到一些指定的Event时,便最终会调用refresh方法来刷新路由配置信息。 123456789101112131415161718192021222324252627282930313233343536private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> { @Autowired private ZuulHandlerMapping zuulHandlerMapping; private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor(); @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent || event instanceof InstanceRegisteredEvent) { reset(); } else if (event instanceof ParentHeartbeatEvent) { ParentHeartbeatEvent e = (ParentHeartbeatEvent) event; resetIfNeeded(e.getValue()); } else if (event instanceof HeartbeatEvent) { HeartbeatEvent e = (HeartbeatEvent) event; resetIfNeeded(e.getValue()); } } private void resetIfNeeded(Object value) { if (this.heartbeatMonitor.update(value)) { reset(); } } private void reset() { this.zuulHandlerMapping.setDirty(true); }} 因此,我们可以通过广播-订阅的方式,建立一个Zuul的管理后台,当我们认为将路由配置信息变更时,发送一个广播,在Zuul集群的服务中实时监听该消息,当监听到刷新请求后,通过ApplicationEventPublisher发布RoutesRefreshedEvent,Zuul的事件监听器会自动为我们处理后续流程,从而实现了配置修改的实时刷新。 核心方法 123456789101112/** * 取得订阅的消息后的处理 * * 说明:当接收到路由规则刷新通知时, 发布路由刷新事件 * @param channel * @param message */@Overridepublic void onMessage(String channel, String message) { // step. 发布路由刷新事件 this.publisher.publishEvent(new RoutesRefreshedEvent(compositeRouteLocator));} Zuul与非Spring Boot项目集成 前面介绍过,Zuul的动态路由与负载均衡离不开Spring Cloud的Discovery Client(例如Eureka)与Ribbon,对于Spring Boot项目来说,其接入成本即引入一个jar包,添加几行配置与注解即可与Zuul集成。 对于非Spring Boot项目,我们需要自己封装一个类似的spring-cloud-starter-netflix-eureka-client组件。 自定义非Spring Boot项目spring-cloud-starter-netflix-eureka-client组件 参考官方starter,该组件内部的核心是eureka-client.jar,核心逻辑很简单,即项目启动时将项目的地址、Host等信息注册至Eureka中,并开启Eureka的心跳机制,以便监听服务状态实时更新注册中心。 首先引入eureka-client引入版本需与spring-cloud-starter-netflix-eureka-client所用一致 123456789101112<!-- Eureka --><dependency> <groupId>com.netflix.eureka</groupId> <artifactId>eureka-client</artifactId> <version>${eureka.version}</version> <exclusions> <exclusion> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> </exclusion> </exclusions></dependency> 这里需要注意一点,由于旧项目所用servlet-api版本不同,因此需要注意组件的依赖关系。 定义ListenerListener必须具有以下功能: 项目启动时服务信息注册到Eureka,并提供心跳监测 项目销毁时注销Eureka 维持心跳监测 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495/** * 用途:Eureka服务注册 * 说明: * 1、项目启动时服务信息注册到Eureka, 并提供心跳监测 * 2、项目销毁时注销Eureka * 3、Eureka客户端默认配置的覆盖选项从Classpath下寻找eureka-client.properties中加载 * * @author Wang Ran <br/> */public class EurekaDiscoveryListener implements ServletContextListener { private static ApplicationInfoManager applicationInfoManager; private static EurekaClient eurekaClient; private static EurekaInstanceConfig instanceConfig; private static EurekaClientConfig clientConfig = new DefaultEurekaClientConfig(); /** * 服务启动时调用, 注册Eureka * @param sce */ @Override public void contextInitialized(ServletContextEvent servletContextEvent) { // step. 初始化Eureka Client try { final String ipAddress = EurekaClientUtils.getLocalIpAddress(); instanceConfig = new MyDataCenterInstanceConfig() { /** * 强制将注册到Eureka的hostName从主机名换成IP地址加端口号的形式 * @param refresh * @return */ @Override public String getHostName(boolean refresh) { return ipAddress; } /** * 强制将Eureka上显示的实例名称初始化为ip:appname:port的形式 * @return */ @Override public String getInstanceId() { return ipAddress.concat(":").concat(this.getAppname()) .concat(":").concat(String.valueOf(this.getNonSecurePort())); } }; } catch (SocketException e) { throw new RuntimeException("Eureka Register init process occurred SocketException", e); } // 初始化Eureka ApplicationInfoManager initializeApplicationInfoManager(instanceConfig); // 初始化Eureka客户端并向Eureka注册 initializeEurekaClient(applicationInfoManager, clientConfig); // 注册成功后向Eureka通知注册状态 applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.UP); } /** * 服务销毁时调用, 注销Eureka * @param servletContextEvent */ @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { eurekaClient.shutdown(); } /** * 初始化Eureka ApplicationInfoManager * @param instanceConfig * @return */ private static synchronized ApplicationInfoManager initializeApplicationInfoManager( EurekaInstanceConfig instanceConfig) { if (applicationInfoManager == null) { InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get(); applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo); } return applicationInfoManager; } /** * 初始化Eureka客户端 * @param applicationInfoManager * @param clientConfig * @return */ private static synchronized EurekaClient initializeEurekaClient( ApplicationInfoManager applicationInfoManager, EurekaClientConfig clientConfig) { if (eurekaClient == null) { eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig); } return eurekaClient; }} 非Spring Boot项目客户端接入Eureka引入自定义Client12345<dependency> <groupId>com.xxx</groupId> <artifactId>my-spring-cloud-starter-netflix-eureka-client</artifactId> <version>${xxx.version}</version></dependency> 在Classpath添加文件:eureka-client.properties12345678910111213141516171819202122232425### Eureka Client configuration# configuration related to reaching the eureka servers# 例如:http://eureka-node1:port/eureka/,http://eureka-node2:port/eureka/eureka.serviceUrl.default=${EUREKA_URI}# 输入客户端服务集群的id, 名称需为英文, 例如: xxx-servereureka.name=${EUREKA-CLIENT-ID}# 输入客户端服务端口, 例如8080eureka.port=${EUREKA-CLIENT-PORT}# 向服务发现注册真实ipeureka.instance.prefer-ip-address=true# 心跳时间,即服务续约间隔时间(缺省为30s)eureka.instance.lease-renewal-interval-in-seconds=5# 发呆时间,即服务续约到期时间(缺省为90s)eureka.instance.lease-expiration-duration-in-seconds=20# 客户识别此服务的虚拟主机名eureka.vipAddress=${eureka.name}eureka.vitualVipAddress=${eureka.name}## configuration related to reaching the eureka serverseureka.preferSameZone=true# 是否要使用基于DNS的查找来确定其他eureka服务器eureka.shouldUseDns=false# 是否注册自身到eurekaeureka.registration.enabled=trueeureka.decoderName=JacksonJson web.xml文件引入Eureka监听器1234<listener> <description>Eureka Listener</description> <listener-class>com.xxx.listener.EurekaDiscoveryListener</listener-class></listener> 分流上线 & 验证 前文所述,我们现有集群架构接入Zuul面临了一些上线问题: App端每次更新都要重新发版,时效性差 App端接入Zuul后若出现问题,无法及时回滚 旧版本App无法修改接入Zuul 测试与线上环境不一致,上线存在未知的风险 我们的业务模式,无法在线上环境进行全流程测试 为了解决上述问题,我们必须做到在前端不进行任何修改的情况下,以一种分流且可验证的形式接入Zuul中。 分流方案 若想做到前端在前期不做任何修改,意味着Zuul当前必须与前端已有的Nginx域名对接。 我们假设Zuul的域名为:my-zuul.com,旧服务1的域名为old-service-01.com,映射的机器为10.10.10.1,10.10.10.2。 对于分流方案,我们可以直接以Nginx的轮询策略为基准,将my-zuul.com作为类似10.10.10.1的一个节点,通过权重的形式将一部分访问old-service-01.com的流量分发到10.10.10.1,一部分流量分发到my-zuul.com。 但是这里存在一个问题,前端需要将old-service-01.com域名替换为my-zuul.com/service-01,而Nginx的upstream在映射到zuul.com时无法为域名添加/service-01,因此需要引入old-service-01-neiwang.com域名来解决这个问题。 12345 upstream old-service-01.com { server 10.10.10.1:8080 fail_timeout=10s max_fails=3 weight 1; ... server old-service-01-neiwang.com weight 1; } 然后,在Nginx中针对old-service-01-neiwang.com域名进行redirect,使之转到my-zuul.com/service-01,最终的架构详见下图。 验证方案 通过Nginx我们实现了分流的上线策略,那么便可通过新增Post类型的自定义过滤器,拦截服务之间的请求与响应关键信息,进而通过streaming实时分析日志记录,监测Zuul的访问情况与业务数据的指标范围,来确定上线是否存在异常情形,进而可以逐步提升Zuul在Nginx的分流权重,直至最终将老架构过渡到以Zuul为核心的最终方案。 总结 本文主要与大家分享一下我们在使用Spring Cloud中关于Zuul的一些实践心得,鉴于作者水平有限,文章中不免会有理解不到位的地方。关于Zuul本身与其他Spring Cloud组件,以及其他微服务架构有非常多的地方值得深入探讨,若您对文章中某些问题存在疑问,或是发现某些内容存在错误、有更好的解决方案、意见以及建议等,欢迎留言,非常希望能与各路大神广泛交流。 参考资料Spring Cloud官方文档 Zuul Github","categories":[{"name":"Spring Cloud","slug":"Spring-Cloud","permalink":"https://wangran.me/categories/Spring-Cloud/"}],"tags":[{"name":"Zuul","slug":"Zuul","permalink":"https://wangran.me/tags/Zuul/"}]},{"title":"基于Github搭建个人博客","slug":"github-pages_init","date":"2016-10-19T16:00:00.000Z","updated":"2019-06-05T13:59:40.813Z","comments":true,"path":"2016/10/20/github-pages_init/","link":"","permalink":"https://wangran.me/2016/10/20/github-pages_init/","excerpt":"基于Github搭建个人博客Github Pages什么是github pages?GitHub Pages is a static site hosting service. GitHub Pages is designed to host your personal, organization, or project pages directly from a GitHub repository. To learn more about the different types of GitHub Pages sites, see “User, organization, and project pages.” You can create and publish GitHub Pages online using the Automatic Page Generator. If you prefer to work locally, you can use GitHub Desktop or the command line. 由用户编写的、免费的、托管在github上的静态网页","text":"基于Github搭建个人博客Github Pages什么是github pages?GitHub Pages is a static site hosting service. GitHub Pages is designed to host your personal, organization, or project pages directly from a GitHub repository. To learn more about the different types of GitHub Pages sites, see “User, organization, and project pages.” You can create and publish GitHub Pages online using the Automatic Page Generator. If you prefer to work locally, you can use GitHub Desktop or the command line. 由用户编写的、免费的、托管在github上的静态网页 如何搭建Github Pages官方教程 https://pages.github.com/ https://help.github.com/articles/user-organization-and-project-pages/ 搭建github博客基本分为如下几步: 第一步,create a repository 第二步, clone the repository 第三步,create your website pages 第四步,commit and push your files github pages为我们搭建的个人博客地址是有规则的:例如,我的github地址为https://github.com/wr4javaee那么,我的博客主页地址为https://wr4javaee.github.io 若我的项目名称叫blog,那么对应的pages地址就是https://wr4javaee.github.io/blog 注册github账号 https://github.com/join [email protected]&3377128016,[email protected]&2027194199 github为我们准备了两种搭建博客方式,一种是github提供模板,我们只需写md文档即可。另一种是从头搭建。 我们先介绍第一种方式。 方法一,利用github pages模板搭建博客进入项目->settings->github pages->launch 点击launch后,进入 填写好项目名称、项目描述后,进入下一步,选择项目模板 选择好模板后,点击 publish page即可提交。 稍等片刻后,github即可为我们搭建好项目对应的网站。 通过访问https://onepieceworld.github.io/test/ 方法二,从头搭建博客若我们查看github模板为我们创建的博客,会发现github为我们自动创建了一个分支gh-pages作为默认分支,博客的文件都提交在这个特定的分支中。 那么,若我们从头搭建博客,就得模仿这一步骤。github规定,只有该分支中的页面,才会生成静态网页文件。 我们新创建一个reponsitory,名为test1 https://github.com/onepieceworld/test1.git 接下来,我们创建一个全新的gh-pages分支,并将其作为默认分支 1yumao@ubuntu:~/github/test1$ git checkout --orphan gh-pages 为新的空白分支创建默认页面并提交 12345678910yumao@ubuntu:~/github/test1$ git checkout --orphan gh-pagesSwitched to a new branch 'gh-pages'yumao@ubuntu:~/github/test1$ git branch yumao@ubuntu:~/github/test1$ echo "Hello World!" > index.htmlyumao@ubuntu:~/github/test1$ git add index.html yumao@ubuntu:~/github/test1$ git commit -a -m "commit index pages"[gh-pages (root-commit) 0fab7c5] commit index pages 1 file changed, 1 insertion(+) create mode 100644 index.htmlyumao@ubuntu:~/github/test1$ git push origin gh-pages 提交成功后,访问https://onepieceworld.github.io/test1/, 可以看到,github已经自动为我们将静态文件部署。 利用Jekyll搭建个人博客Jekyll官方网站Jekyll github 什么是Jekyll?Jekyll is a simple, blog-aware, static site generator perfect forpersonal, project, or organization sites. Think of it like a file-basedCMS, without all the complexity. Jekyll takes your content, rendersMarkdown and Liquid templates, and spits out a complete, static websiteready to be served by Apache, Nginx or another web server. Jekyll is the engine behind GitHub Pages, which you can use to host sites right from your GitHub repositories. 一个可以将纯文本转化为静态网站文件的生成器。 基于Jekyll,我们可以只关注编写纯文本,如markdown,而无需将精力放在HTML本身。 Jekyll的简易用法 Jekyll官方使用简介,内容很详细作者阮一峰关于Jekyll与Github Blog的入门教程,推荐大家阅读 首先,Jekyll依赖Ruby 安装Ruby环境过程中会遇到GWF,需要用到淘宝提供的RubyGems镜像服务,它与官方服务同步频率为15分钟。 Ruby环境搭建好后,我们通过以下命令安装Jekyll 1~ $ gem install jekyll 接下来,利用jekyll命令jekyll new my-awesome-site自动创建jekyll项目 123456789101112131415161718192021222324252627282930yumao@ubuntu:~/github$ jekyll new jekyll_testNew jekyll site installed in /home/yumao/github/jekyll_test. Running bundle install in /home/yumao/github/jekyll_test... Fetching gem metadata from https://rubygems.org/................Fetching version metadata from https://rubygems.org/...Fetching dependency metadata from https://rubygems.org/..Resolving dependencies...Using addressable 2.4.0Using colorator 1.1.0Using ffi 1.9.14Using forwardable-extended 2.6.0Using sass 3.4.22Using rb-fsevent 0.9.7Using kramdown 1.12.0Using liquid 3.0.6Using mercenary 0.3.6Using rouge 1.11.1Using safe_yaml 1.0.4Using minima 2.0.0Using bundler 1.13.5Using rb-inotify 0.9.7Using pathutil 0.14.0Using jekyll-sass-converter 1.4.0Using listen 3.0.8Using jekyll-watch 1.5.0Using jekyll 3.3.0Using jekyll-feed 0.8.0Bundle complete! 3 Gemfile dependencies, 20 gems now installed.Use `bundle show [gemname]` to see where a bundled gem is installed.yumao@ubuntu:~/github$ 进入项目目录下,执行Jekyll命令 jekyll serve,启动项目 123456789101112131415161718yumao@ubuntu:~/github$ cd jekyll_test/yumao@ubuntu:~/github/jekyll_test$ jekyll serveWARN: Unresolved specs during Gem::Specification.reset: rouge (~> 1.7) jekyll-watch (~> 1.1)WARN: Clearing out unresolved specs.Please report a bug if this causes problems.Configuration file: /home/yumao/github/jekyll_test/_config.ymlConfiguration file: /home/yumao/github/jekyll_test/_config.yml Source: /home/yumao/github/jekyll_test Destination: /home/yumao/github/jekyll_test/_site Incremental build: disabled. Enable with --incremental Generating... done in 0.286 seconds. Auto-regeneration: enabled for '/home/yumao/github/jekyll_test'Configuration file: /home/yumao/github/jekyll_test/_config.yml Server address: http://127.0.0.1:4000/ Server running... press ctrl-c to stop. 至此,一个简易的jekyll项目就搭建好了,我们将它部署到github中,每次只要新增md文档,提交到github后,其会自动编译jekyll项目,自动解析成静态网页文件。 Jekyll的目录结构 123456789101112131415161718.├── _config.yml├── _drafts| ├── begin-with-the-crazy-ideas.textile| └── on-simplicity-in-technology.markdown├── _includes| ├── footer.html| └── header.html├── _layouts| ├── default.html| └── post.html├── _posts| ├── 2007-10-29-why-every-programmer-should-play-nethack.textile| └── 2009-04-26-barcamp-boston-4-roundup.textile├── _data| └── members.yml├── _site└── index.html 文件 / 目录 描述 _config.yml 保存配置数据。很多配置选项都会直接从命令行中进行设置,但是如果你把那些配置写在这儿,你就不用非要去记住那些命令了。 _drafts drafts 是未发布的文章。这些文件的格式中都没有 title.MARKUP 数据。学习如何使用 drafts. _includes 你可以加载这些包含部分到你的布局或者文章中以方便重用。可以用这个标签 {nclude file.ext 来把文件 _includes/file.ext 包含进来。 _layouts layouts 是包裹在文章外部的模板。布局可以在 YAML 头信息中根据不同文章进行选择。 这将在下一个部分进行介绍。标签 content 可以将content插入页面中。 _posts 这里放的就是你的文章了。文件格式很重要,必须要符合: YEAR-MONTH-DAY-title.MARKUP。 The permalinks 可以在文章中自己定制,但是数据和标记语言都是根据文件名来确定的。 _data Well-formatted site data should be placed here. The jekyll engine will autoload all yaml files (ends with .yml or .yaml) in this directory. If there’s a file members.yml under the directory, then you can access contents of the file through site.data.members. _site 一旦 Jekyll 完成转换,就会将生成的页面放在这里(默认)。最好将这个目录放进你的 .gitignore 文件中。 index.html and other HTML, Markdown, Textile files 如果这些文件中包含 YAML 头信息 部分,Jekyll 就会自动将它们进行转换。当然,其他的如 .html, .markdown, .md,或者 .textile 等在你的站点根目录下或者不是以上提到的目录中的文件也会被转换。 Other Files/Folders 其他一些未被提及的目录和文件如 css 还有 images 文件夹, favicon.ico 等文件都将被完全拷贝到生成的 site 中。 这里有一些使用 Jekyll 的站点,如果你感兴趣就来看看吧。 _config.yml配置文件官方配置文件说明 使用Jekyll的开源模板网络上免费的Jekyll主题非常多,推荐2个主题网站 Jekyll主题网站一 Jekyll主题网站二 这里为大家推荐一个我喜欢的主题来源 Gaohaoyang github地址 https://github.com/Gaohaoyang/gaohaoyang.github.io.git Jekyll的优缺点Jekyll是比较流行的博客系统,其主题、插件数量上比较丰富,且上手简单。缺点为文章量多的时候,生成速度慢。 利用Hexo搭建个人博客Hexo与Jekyll的区别Hexo是基于Node.js编写的静态博客系统,使用Jekyl我们可以将原生的markdown文档上传到github,由github自动解析成静态html文件。但hexo为本地将markdown文档解析后,将生成的静态html文件直接上传到github中。 由于hexo基于nodejs,因此其生成html速度较Jekyll快,相对更灵活。主题数量然有Jade/Stylus/Less等各种方言支持。 语言 活跃度 开箱即用 主题数量 主题系统 生成速度 博客适应性 非博客适应性 Jekyll Ruby S B A A C A C Hexo NodeJS B A A B A A B Hexo官方文档Hexo官方文档 安装HexoHexo基于Node.js,因此首先应安装Node.js环境。 cURL: 1$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh Wget 1$ wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh 安装完成后,重启终端执行下列命令完成安装Node.js 1$ nvm install stable 若环境准备就绪,使用以下命令安装Hexo 1$ npm install -g hexo-cli 使用Hexo建立网站使用以下命令初始化网站 123$ hexo init <folder>$ cd <folder>$ npm install 建站后的文件目录如下: 12345678.├── _config.yml├── package.json├── scaffolds├── source| ├── _drafts| └── _posts└── themes scaffolds模板文件夹 建站的模板,hexo依赖于此模板来生成静态文件。 _config.yml配置文件 hexo官方配置文件说明 以下为hexo的默认配置 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596# Hexo Configuration## Docs: https://hexo.io/docs/configuration.html## Source: https://github.com/hexojs/hexo/# Site# 网站标题title: Hexo # 网站副标题subtitle: # 网站描述description: author: John Doe# 网站使用的语言language: # 网站时区。Hexo 默认使用您电脑的时区。时区列表。比如说:America/New_York, Japan, 和 UTC 。timezone: # URL## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'# 如果您的网站存放在子目录中,例如 http://yoursite.com/blog,则请将您的 url 设为 #http://yoursite.com/blog 并把 root 设为 /blog/。url: http://yoursite.com# 网站根目录root: /# 文章的 永久链接 格式 :year/:month/:day/:title/permalink: :year/:month/:day/:title/# 永久链接中各部分的默认值 permalink_defaults:# Directory# 资源文件夹,这个文件夹用来存放内容。source_dir: source# 公共文件夹,这个文件夹用于存放生成的站点文件public_dir: public# 标签文件夹tag_dir: tags# 归档文件夹archive_dir: archives# 分类文件夹category_dir: categories# Include code 文件夹code_dir: downloads/code# 国际化(i18n)文件夹i18n_dir: :lang# 跳过指定文件的渲染,您可使用 glob 表达式来匹配路径。 skip_render:# Writingnew_post_name: :title.md # File name of new postsdefault_layout: posttitlecase: false # Transform title into titlecaseexternal_link: true # Open external links in new tabfilename_case: 0render_drafts: falsepost_asset_folder: falserelative_link: falsefuture: truehighlight: enable: true line_number: true auto_detect: false tab_replace:# Category & Tag# 默认分类 uncategorizeddefault_category: uncategorized# 分类别名 category_map:# 标签别名 tag_map:# Date / Time format## Hexo uses Moment.js to parse and display date## You can customize the date format as defined in## http://momentjs.com/docs/#/displaying/format/# 日期格式 YYYY-MM-DDdate_format: YYYY-MM-DD# 时间格式 H:mm:sstime_format: HH:mm:ss# Pagination## Set per_page to 0 to disable pagination# 每页显示的文章量 (0 = 关闭分页功能) 10per_page: 10# 分页目录 pagepagination_dir: page# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/# 当前主题名称。值为false时禁用主题theme: landscape# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: type: source资源文件夹 用来存放用户资源,其中_post文件夹下的markdown文档,会被hexo自动解析成html,并放置到public文件夹中 其他以_开头的文件夹会被自动忽略 themes主题文件夹 hexo在解析时,会根据主题来生成静态页面。 新建一篇文章1$ hexo new [layout] <title> layout若不指定,默认从_config.yml文件中的default_layout代替 生成静态文件1$ hexo generate 选项 描述 -d, --deploy 文件生成后立即部署网站 -w, --watch 监视文件变动 使用generate命令后,hexo的文件夹会自动生成静态文件,放置到配置文件中规定的public目录中 public文件夹中的内容如下 启动服务器1$ hexo server 部署网站1$ hexo deploy 参数 描述 -g, --generate 部署之前预先生成静态文件 部署后的界面如下 清除缓存文件1$ hexo clean 清除缓存文件 (db.json) 和已生成的静态文件 (public)。 其他hexo命令其他hexo命令 hexo部署到github中通过_config.yml文件建立关联, 1234deploy: type: git repository: https://github.com/onepieceworld/hexo_test.git branch: gh-pages 执行命令 12~ $ npm install hexo-deployer-git --save~ $ hexo deploy 至此,hexo已成功将解析后的html文件上传到github中","categories":[{"name":"Github","slug":"Github","permalink":"https://wangran.me/categories/Github/"}],"tags":[{"name":"Github","slug":"Github","permalink":"https://wangran.me/tags/Github/"}]},{"title":"Zookeeper Curator的使用","slug":"zookeeper_curator","date":"2016-07-26T16:00:00.000Z","updated":"2019-06-05T13:59:40.814Z","comments":true,"path":"2016/07/27/zookeeper_curator/","link":"","permalink":"https://wangran.me/2016/07/27/zookeeper_curator/","excerpt":"前言Curator是ZooKeeper的开源客户端。官网:http://curator.apache.org/API文档:http://curator.apache.org/apidocs/index.html","text":"前言Curator是ZooKeeper的开源客户端。官网:http://curator.apache.org/API文档:http://curator.apache.org/apidocs/index.html 在工程中需要引入curator依赖 12345<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>${curator.version}</version></dependency> 截止目前文章中使用的版本为3.2.0创建Session使用CuratorFrameworkFactory创建Session可以通过newClient方法建立ZooKeeper会话,但这里推荐使用其自带的Builder模式,下面是newClient的官方API,Builder模式的case见下文。 newClient12public static CuratorFramework newClient(String connectString, RetryPolicy retryPolicy) Create a new client with default session timeout and default connection timeout Parameters: connectString - list of servers to connect to retryPolicy - retry policy to use Returns: client newClient1234public static CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy) Create a new client Parameters: connectString - list of servers to connect to sessionTimeoutMs - session timeout connectionTimeoutMs - connection timeout retryPolicy - retry policy to use Returns: client RetryPolicy是Curator提供的创建会话的策略接口。 1234567891011121314151617/** * Abstracts the policy to use when retrying connections */public interface RetryPolicy{ /** * Called when an operation has failed for some reason. This method should return * true to make another attempt. * * * @param retryCount the number of times retried so far (0 the first time) * @param elapsedTimeMs the elapsed time in ms since the operation was attempted * @param sleeper use this to sleep - DO NOT call Thread.sleep * @return true/false */ public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper);} 官方提供的RetryPolicy的继承关系如下,我们也可通过继承RetryPolicy来实现自己的策略类。 这里,我们使用ExponentialBackoffRetry这个重试策略来建立ZooKeeperSession。 case123456789101112131415161718192021222324252627282930313233343536373839404142434445package me.wangran.zookeeper.demo.curator;import org.apache.curator.RetryPolicy;import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.CuratorFrameworkFactory;import org.apache.curator.retry.ExponentialBackoffRetry;/** * Curator创建SessionDemo * @author Wang Ran */public class CuratorCreateSessionDemo { public static void main(String[] args) { createSessionDemo1(); } /** * 使用CuratorFrameworkFactory创建Session */ public static void createSessionDemo1() { String connectString = \"192.168.128.75:2181\"; int baseSleepTimeMs = 1000; // 初始sleep时间 int maxRetries = 100; // 最大重试次数 int maxSleepMs = 25000; // 最大sleep时间 // ExponentialBackoffRetry : Retry policy that retries a set number of // times with increasing sleep time between retries RetryPolicy retryPolicy = new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries, maxSleepMs); int sessionTimeoutMs = 10000; int connectionTimeoutMs = 10000; CuratorFramework cff = CuratorFrameworkFactory.builder() .connectString(connectString) .retryPolicy(retryPolicy) .sessionTimeoutMs(sessionTimeoutMs) .connectionTimeoutMs(connectionTimeoutMs) .build(); cff.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\"ZooKeeper current state is \" + cff.getState()); }} 当我运行该代码后,控制台报错如下: 123456Exception in thread "main" java.lang.NoSuchMethodError: org.apache.zookeeper.server.quorum.flexible.QuorumMaj.<init>(Ljava/util/Map;)V at org.apache.curator.framework.imps.EnsembleTracker.<init>(EnsembleTracker.java:57) at org.apache.curator.framework.imps.CuratorFrameworkImpl.<init>(CuratorFrameworkImpl.java:158) at org.apache.curator.framework.CuratorFrameworkFactory$Builder.build(CuratorFrameworkFactory.java:156) at me.wangran.zookeeper.demo.curator.CuratorCreateSessionDemo.createSessionDemo1(CuratorCreateSessionDemo.java:36) at me.wangran.zookeeper.demo.curator.CuratorCreateSessionDemo.main(CuratorCreateSessionDemo.java:15) 报错的原因如下: Curator 2.x.x - compatible with both ZooKeeper 3.4.x and ZooKeeper 3.5.xCurator 3.x.x - compatible only with ZooKeeper 3.5.x and includes support for new features such as dynamic reconfiguration, etc. 在依赖中,我引入的curator与zookeeper版本不兼容导致 3.4.6 3.2.0 将zookeeper版本改为3.5.2-alpha后ZooKeeper会话创建成功,控制台输出如下: 1ZooKeeper current state is STARTED 这里特别说明一下,这个zookeeper版本指的是maven依赖的客户端版本,并不是zookeeper服务本身的版本。","categories":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/categories/Zookeeper/"}],"tags":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/tags/Zookeeper/"}]},{"title":"Zookeeper Java API的使用","slug":"zookeeper_java_api","date":"2016-07-25T16:00:00.000Z","updated":"2019-06-05T13:59:40.815Z","comments":true,"path":"2016/07/26/zookeeper_java_api/","link":"","permalink":"https://wangran.me/2016/07/26/zookeeper_java_api/","excerpt":"前言Zookeeper官方API文档 http://zookeeper.apache.org/doc/r3.4.6/api/index.html","text":"前言Zookeeper官方API文档 http://zookeeper.apache.org/doc/r3.4.6/api/index.html 通过API建立ZooKeeper SessionZooKeeper构造方法官方文档摘录如下 Constructor and Description ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)To create a ZooKeeper client object, the application needs to pass a connection string containing a comma separated list of host:port pairs, each corresponding to a ZooKeeper server. ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)To create a ZooKeeper client object, the application needs to pass a connection string containing a comma separated list of host:port pairs, each corresponding to a ZooKeeper server. ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd)To create a ZooKeeper client object, the application needs to pass a connection string containing a comma separated list of host:port pairs, each corresponding to a ZooKeeper server. ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)To create a ZooKeeper client object, the application needs to pass a connection string containing a comma separated list of host:port pairs, each corresponding to a ZooKeeper server. Demo github地址: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475package me.wangran.zookeeper.demo.api;import java.util.concurrent.CountDownLatch;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.Watcher.Event.KeeperState;import org.apache.zookeeper.ZooKeeper;/** * Zookeeper 创建Session * @author Wang Ran */public class ZooKeeperSessionDemo implements Watcher { private static CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) throws Exception { demo3(); } /** * 创建最基本的会话 */ public static void demo1() throws Exception { String zookeeperAddress = \"192.168.128.75:2181\"; // Zookeeper地址,多个Zookeeper地址可以英文逗号分隔,地址可指定节点 int sessionTimeout = 10000; // session超时时间,单位毫秒 Watcher watcher = new ZooKeeperSessionDemo(); // 事件通知处理器, 创建会话时可为null ZooKeeper zookeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, watcher); System.out.println(\"zookeeper connecting, state is \" + zookeeper.getState()); countDownLatch.await(); System.out.println(\"zookeeper connected, state is \" + zookeeper.getState()); } /** * 创建指定read only模式的会话 */ public static void demo2() throws Exception { String zookeeperAddress = \"192.168.128.75:2181/test_root\"; // Zookeeper地址,多个Zookeeper地址可以英文逗号分隔,地址可指定节点 int sessionTimeout = 10000; // session超时时间,单位毫秒 Watcher watcher = new ZooKeeperSessionDemo(); // 事件通知处理器, 创建会话时可为null boolean canBeReadOnly = true; // 是否支持read only模式,默认为false,若一个机器和Zookeeper Cluster过半机器失去网络连接,则不处理所有读写请求。若未true,则该情况可提供读请求。 ZooKeeper zookeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, watcher, canBeReadOnly); System.out.println(\"zookeeper connecting, state is \" + zookeeper.getState()); countDownLatch.await(); System.out.println(\"zookeeper connected, state is \" + zookeeper.getState()); } /** * 恢复会话 */ public static void demo3() throws Exception { String zookeeperAddress = \"192.168.128.75:2181/test_root\"; // Zookeeper地址,多个Zookeeper地址可以英文逗号分隔,地址可指定节点 int sessionTimeout = 10000; // session超时时间,单位毫秒 Watcher watcher = new ZooKeeperSessionDemo(); // 事件通知处理器, 创建会话时可为null ZooKeeper zookeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, watcher); System.out.println(\"zookeeper connecting, state is \" + zookeeper.getState()); countDownLatch.await(); System.out.println(\"zookeeper connected, state is \" + zookeeper.getState()); long sessionId = zookeeper.getSessionId(); // 用于恢复会话 byte[] sessionPwd = zookeeper.getSessionPasswd(); // 用于恢复会话 countDownLatch = new CountDownLatch(1); zookeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, watcher, sessionId, sessionPwd); System.out.println(\"zookeeper reconnecting, state is \" + zookeeper.getState()); countDownLatch.await(); System.out.println(\"zookeeper reconnected, state is \" + zookeeper.getState()); } @Override public void process(WatchedEvent watchedEvent) { System.out.println(\"ZooKeeperWatcherDemo process, watchedEvent is \" + watchedEvent); if(KeeperState.SyncConnected == watchedEvent.getState()) { countDownLatch.countDown(); } }} 通过API创建节点说明创建节点API地址:http://zookeeper.apache.org/doc/r3.4.6/api/index.html 这里,我们主要用到两个方法,分别是 返回值 方法 String create(String path, byte[] data, List acl, CreateMode createMode)Create a node with the given path. void create(String path, byte[] data, List acl, CreateMode createMode, AsyncCallback.StringCallback cb, Object ctx)The asynchronous version of create. path 指定创建的节点路径 data 节点的内容 acl 创建节点策略 1234567891011121314151617181920212223242526272829/** * This Id represents anyone. */public final Id ANYONE_ID_UNSAFE = new Id(\"world\", \"anyone\");/** * This Id is only usable to set ACLs. It will get substituted with the * Id's the client authenticated with. */public final Id AUTH_IDS = new Id(\"auth\", \"\");/** * This is a completely open ACL . */public final ArrayList<ACL> OPEN_ACL_UNSAFE = new ArrayList<ACL>( Collections.singletonList(new ACL(Perms.ALL, ANYONE_ID_UNSAFE)));/** * This ACL gives the creators authentication id's all permissions. */public final ArrayList<ACL> CREATOR_ALL_ACL = new ArrayList<ACL>( Collections.singletonList(new ACL(Perms.ALL, AUTH_IDS)));/** * This ACL gives the world the ability to read. */public final ArrayList<ACL> READ_ACL_UNSAFE = new ArrayList<ACL>( Collections .singletonList(new ACL(Perms.READ, ANYONE_ID_UNSAFE))); cb 异步回调函数,当节点创建后ZooKeeper客户端自动调用本方法。需要实现StringCallback接口,重写processResult方法 ctx 回调方法执行时使用的对象,通常放置上线文Context信息 CreateMode节点类型枚举如下 1234567891011121314151617181920212223242526272829303132/** * The znode will not be automatically deleted upon client's disconnect * 持久节点 */PERSISTENT (0, false, false, false),/*** The znode will not be automatically deleted upon client's disconnect,* and its name will be appended with a monotonically increasing number.* 持久顺序节点*/PERSISTENT_SEQUENTIAL (2, false, true, false),/** * The znode will be deleted upon the client's disconnect. * 临时节点 */EPHEMERAL (1, true, false, false),/** * The znode will be deleted upon the client's disconnect, and its name * will be appended with a monotonically increasing number. * 临时顺序节点 */EPHEMERAL_SEQUENTIAL (3, true, true, false),/** * The znode will be a container node. Container * nodes are special purpose nodes useful for recipes such as leader, lock, * etc. When the last child of a container is deleted, the container becomes * a candidate to be deleted by the server at some point in the future. * Given this property, you should be prepared to get * {@link org.apache.zookeeper.KeeperException.NoNodeException} * when creating children inside of this container node. */CONTAINER (4, false, false, true); Case case 1 根节点下创建持久节点/node_pokemon 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950package me.wangran.zookeeper.demo.api;import java.util.concurrent.CountDownLatch;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooKeeper;import org.apache.zookeeper.Watcher.Event.KeeperState;import org.apache.zookeeper.ZooDefs.Ids;/** * ZooKeeper创建节点Demo * @author Wang Ran */public class ZooKeeperCreateNodeDemo implements Watcher { private static CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) throws Exception { new ZooKeeperCreateNodeDemo().demo1(); } /** * 创建最基本的会话 */ public void demo1() throws Exception { String zookeeperAddress = \"192.168.128.75:2181\"; // Zookeeper地址,多个Zookeeper地址可以英文逗号分隔,地址可指定节点 int sessionTimeout = 10000; // session超时时间,单位毫秒 Watcher watcher = new ZooKeeperCreateNodeDemo(); // 事件通知处理器, 创建会话时可为null ZooKeeper zookeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, watcher); System.out.println(\"zookeeper connecting, state is \" + zookeeper.getState()); countDownLatch.await(); System.out.println(\"zookeeper connected, state is \" + zookeeper.getState()); String newNodePath = \"/node_pokemon\"; byte[] newNodeData = \"go\".getBytes(); CreateMode newNodeMode = CreateMode.PERSISTENT; zookeeper.create(newNodePath, newNodeData, Ids.OPEN_ACL_UNSAFE, newNodeMode); System.out.println(\"zookeeper node created\"); } @Override public void process(WatchedEvent watchedEvent) { System.out.println(\"ZooKeeperWatcherDemo process, watchedEvent is \" + watchedEvent); if(KeeperState.SyncConnected == watchedEvent.getState()) { countDownLatch.countDown(); } } } 控制台输出: zookeeper connecting, state is CONNECTINGZooKeeperWatcherDemo process, watchedEvent is WatchedEvent state:SyncConnected type:None path:nullzookeeper connected, state is CONNECTEDzookeeper node created case2 创建节点后异步触发回调函数 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586package me.wangran.zookeeper.demo.api;import java.util.concurrent.CountDownLatch;import org.apache.zookeeper.AsyncCallback;import org.apache.zookeeper.AsyncCallback.StringCallback;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooKeeper;import org.apache.zookeeper.Watcher.Event.KeeperState;import org.apache.zookeeper.ZooDefs.Ids;/** * ZooKeeper创建节点Demo * @author Wang Ran */public class ZooKeeperCreateNodeDemo implements Watcher { private static CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) throws Exception { new ZooKeeperCreateNodeDemo().demo2(); } /** * 创建持久节点 */ public void demo1() throws Exception { String zookeeperAddress = \"192.168.128.75:2181\"; // Zookeeper地址,多个Zookeeper地址可以英文逗号分隔,地址可指定节点 int sessionTimeout = 10000; // session超时时间,单位毫秒 Watcher watcher = new ZooKeeperCreateNodeDemo(); // 事件通知处理器, 创建会话时可为null ZooKeeper zookeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, watcher); System.out.println(\"zookeeper connecting, state is \" + zookeeper.getState()); countDownLatch.await(); System.out.println(\"zookeeper connected, state is \" + zookeeper.getState()); String newNodePath = \"/node_pokemon\"; byte[] newNodeData = \"go\".getBytes(); CreateMode newNodeMode = CreateMode.PERSISTENT; zookeeper.create(newNodePath, newNodeData, Ids.OPEN_ACL_UNSAFE, newNodeMode); System.out.println(\"zookeeper node created\"); } /** * 创建节点后异步回调业务函数 */ public void demo2() throws Exception { String zookeeperAddress = \"192.168.128.75:2181/node_pokemon\"; // Zookeeper地址,多个Zookeeper地址可以英文逗号分隔,地址可指定节点 int sessionTimeout = 10000; // session超时时间,单位毫秒 Watcher watcher = new ZooKeeperCreateNodeDemo(); // 事件通知处理器, 创建会话时可为null ZooKeeper zookeeper = new ZooKeeper(zookeeperAddress, sessionTimeout, watcher); System.out.println(\"zookeeper connecting, state is \" + zookeeper.getState()); countDownLatch.await(); System.out.println(\"zookeeper connected, state is \" + zookeeper.getState()); String newNodePath = \"/node_kinosaki\"; byte[] newNodeData = \"osen\".getBytes(); CreateMode newNodeMode = CreateMode.PERSISTENT_SEQUENTIAL; Object ctx = \"ContextDemo\"; zookeeper.create(newNodePath, newNodeData, Ids.OPEN_ACL_UNSAFE, newNodeMode, new StringCallbackDemo(), ctx); System.out.println(\"zookeeper node created\"); Thread.sleep(10); } /** * 异步回调函数 * @author Wang Ran */ class StringCallbackDemo implements AsyncCallback.StringCallback { @Override public void processResult(int rc, String path, Object ctx, String name) { System.out.println(\"StringCallbackDemo.processResult executed!, resultCode is \" + rc + \", path is\" + path // 调用接口时传入的Path + \", context is \" + ctx.toString() // 调用接口时传入的Context + \", node name is \" + name); // 实际在服务器中创建的节点名称 } } @Override public void process(WatchedEvent watchedEvent) { System.out.println(\"ZooKeeperWatcherDemo process, watchedEvent is \" + watchedEvent); if(KeeperState.SyncConnected == watchedEvent.getState()) { countDownLatch.countDown(); } }} 控制台输出: zookeeper connecting, state is CONNECTINGZooKeeperWatcherDemo process, watchedEvent is WatchedEvent state:SyncConnected type:None path:nullzookeeper connected, state is CONNECTEDzookeeper node createdStringCallbackDemo.processResult executed!, resultCode is 0, path is/node_kinosaki, context is ContextDemo, node name is /node_kinosaki0000000005 通过API读取节点API说明: getChildren查询指定节点的所有子节点 getData查询指定节点的数据内容 返回值 方法 List getChildren (String path, boolean watch)Return the list of the children of the node of the given path. void getChildren (String path, boolean watch, AsyncCallback.Children2Callback cb, Object ctx)The asynchronous version of getChildren. void getChildren (String path, boolean watch, AsyncCallback.ChildrenCallback cb, Object ctx)The asynchronous version of getChildren. List getChildren (String path, boolean watch, Stat stat)For the given znode path return the stat and children list. List getChildren (String path, Watcher watcher)Return the list of the children of the node of the given path. void getChildren (String path, Watcher watcher, AsyncCallback.Children2Callback cb, Object ctx)The asynchronous version of getChildren. void getChildren (String path, Watcher watcher, AsyncCallback.ChildrenCallback cb, Object ctx)The asynchronous version of getChildren. List getChildren (String path, Watcher watcher, Stat stat)For the given znode path return the stat and children list. 返回值 方法 void getData(String path, boolean watch, AsyncCallback.DataCallback cb, Object ctx)The asynchronous version of getData. byte[] getData(String path, boolean watch, Stat stat)Return the data and the stat of the node of the given path. void getData(String path, Watcher watcher, AsyncCallback.DataCallback cb, Object ctx)The asynchronous version of getData. byte[] getData(String path, Watcher watcher, Stat stat)Return the data and the stat of the node of the given path. 参数说明 参数 说明 path 想要查询的节点路径。 watch false为不需要注册watcher,true为使用默认watcher。 watcher 可为null。指定watcher用于订阅子节点列表变化通知。当子节点被添加或删除时会向客户端发送通知(通知内容不包含节点列表,客户端需重新查询以获得节点列表信息)。 cb 异步回调函数 ctx 上下文对象 stat 指定数据节点的状态信息,包含cZxid(节点创建的事务id)、mZxid(节点最后一次更新的事务id)、dataLength(节点数据内容长度),传入变量的值会在方法执行过程中被服务器响应的值替换。 通过API更新节点API说明 返回值 方法 Stat setData (String path, byte[] data, int version)Set the data for the node of the given path if such a node exists and the given version matches the version of the node (if the given version is -1, it matches any node’s versions).说明:该方法为同步更新方法 void setData (String path, byte[] data, int version, AsyncCallback.StatCallback cb, Object ctx)The asynchronous version of setData.说明:该方法为异步更新方法 参数说明 参数 说明 path 想要更新的节点路径。 data[ ] 更新的内容 version 指定更新的数据版本 cb 异步回调函数 ctx 上下文对象 通过API删除节点API说明 返回值 方法 void delete (String path, int version)Delete the node with the given path. void delete (String path, int version, AsyncCallback.VoidCallback cb, Object ctx)The asynchronous version of delete. 通过API判断节点是否存在API说明 返回值 方法 Stat exists (String path, boolean watch)Return the stat of the node of the given path. void exists (String path, boolean watch, AsyncCallback.StatCallback cb, Object ctx)The asynchronous version of exists. Stat exists (String path, Watcher watcher)Return the stat of the node of the given path. void exists (String path, Watcher watcher, AsyncCallback.StatCallback cb, Object ctx)The asynchronous version of exists. 权限控制API说明 返回值 方法 List getACL (String path, Stat stat)Return the ACL and stat of the node of the given path. void getACL (String path, Stat stat, AsyncCallback.ACLCallback cb, Object ctx)The asynchronous version of getACL. Stat setACL (String path, List acl, int version)Set the ACL for the node of the given path if such a node exists and the given version matches the version of the node. void setACL (String path, List acl, int version, AsyncCallback.StatCallback cb, Object ctx)The asynchronous version of setACL.","categories":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/categories/Zookeeper/"}],"tags":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/tags/Zookeeper/"}]},{"title":"Zookeeper的安装部署与客户端使用","slug":"zookeeper_deploy_and_client_using","date":"2016-07-22T16:00:00.000Z","updated":"2019-06-05T13:59:40.815Z","comments":true,"path":"2016/07/23/zookeeper_deploy_and_client_using/","link":"","permalink":"https://wangran.me/2016/07/23/zookeeper_deploy_and_client_using/","excerpt":"zookeeper官方网站: http://zookeeper.apache.org/ Zookeeper的安装与部署Zookeeper通过Java编写,因此运行前需要配置好JRE 下载zookeeper下载地址为http://mirrors.cnnic.cn/apache/zookeeper/","text":"zookeeper官方网站: http://zookeeper.apache.org/ Zookeeper的安装与部署Zookeeper通过Java编写,因此运行前需要配置好JRE 下载zookeeper下载地址为http://mirrors.cnnic.cn/apache/zookeeper/ 安装Zookeeper官方下载的文件为.tar.gz格式,我们只需解压即可完成安装 1tar -xzvf zookeeper-3.4.6.tar.gz 配置创建zoo.cfg文件我们进入到解压后的Zookeeper目录下,找到/conf/zoo_sample.cfg文件,重命名为zoo.cfg 1234567891011121314151617181920212223242526272829303132333435# The number of milliseconds of each tick# 基本事件单元,以毫秒为单位。它用来指示心跳,最小的 session 过期时间为两倍的 tickTime.tickTime=2000# The number of ticks that the initial # synchronization phase can takeinitLimit=10# The number of ticks that can pass between # sending a request and getting an acknowledgementsyncLimit=5# the directory where the snapshot is stored.# do not use /tmp for storage, /tmp here is just # example sakes.# 存储内存中数据库快照的位置,如果不设置参数,更新事务日志将被存储到默认位置dataDir=/home/yumao/zookeeper/datadataLogDir=/home/yumao/zookeeper/log# the port at which the clients will connect# 监听客户端连接的端口clientPort=2181# the maximum number of client connections.# increase this if you need to handle more clients#maxClientCnxns=60## Be sure to read the maintenance section of the # administrator guide before turning on autopurge.## http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance## The number of snapshots to retain in dataDir#autopurge.snapRetainCount=3# 格式为server.id=host:port:port, 其中id为在dataDir目录下创建的myid文件中的数字, 每个机器的数字必须保证唯一, id的范围是1~255# 以下配置为单机配置server.1=127.0.0.1:2888:3888# Set to "0" to disable auto purge feature#autopurge.purgeInterval=1 创建myid文件由于在zoo.cfg配置中指定了dataDir位置,进入dataDir=/home/yumao/zookeeper/data目录下,创建文件myid,内容为1 启动服务1234yumao@ubuntu:/opt/zookeeper-3.4.6/bin$ sudo ./zkServer.sh startJMX enabled by defaultUsing config: /opt/zookeeper-3.4.6/bin/../conf/zoo.cfgStarting zookeeper ... STARTED 停止服务12345yumao@ubuntu:/opt/zookeeper-3.4.6/bin$ sudo ./zkServer.sh stopJMX enabled by defaultUsing config: /opt/zookeeper-3.4.6/bin/../conf/zoo.cfgStopping zookeeper ... STOPPEDyumao@ubuntu:/opt/zookeeper-3.4.6/bin$ 设置环境变量在/etc/profile文件中添加 123#Set ZooKeeper Enviromentexport ZOOKEEPER_HOME=/opt/zookeeper-3.4.6export PATH=$PATH:$ZOOKEEPER_HOME/bin:$ZOOKEEPER_HOME/conf Zookeeper客户端的使用客户端启动1sh zkCli.sh 注:在执行该脚本的时候,系统报了个错 zkCli.sh: 81: /opt/zookeeper-3.4.6/bin/zkEnv.sh: Syntax error: “(“ unexpected (expecting “fi”) 一般出现该问题是因为没有配置Zookeeper的环境变量,但还有一个问题是ubuntu本身的配置问题导致,执行以下命令后解决。 12345yumao@ubuntu:/bin$ ls -l /bin/shlrwxrwxrwx 1 root root 4 May 17 10:34 /bin/sh -> dashyumao@ubuntu:/bin$ sudo ln -sf bash /bin/shyumao@ubuntu:/bin$ ls -l /bin/shlrwxrwxrwx 1 root root 4 Jul 26 03:23 /bin/sh -> bash 若客户端脚本成功启动,则系统控制台输出如下 12345678910111213141516171819202122232425262728293031yumao@ubuntu:/opt/zookeeper-3.4.6/bin$ sudo sh ./zkServer.sh startJMX enabled by defaultUsing config: /opt/zookeeper-3.4.6/bin/../conf/zoo.cfgStarting zookeeper ... STARTEDyumao@ubuntu:/opt/zookeeper-3.4.6/bin$ sudo sh zkCli.sh Connecting to localhost:21812016-07-26 03:29:22,781 [myid:] - INFO [main:Environment@100] - Client environment:zookeeper.version=3.4.6-1569965, built on 02/20/2014 09:09 GMT2016-07-26 03:29:22,789 [myid:] - INFO [main:Environment@100] - Client environment:host.name=ubuntu2016-07-26 03:29:22,790 [myid:] - INFO [main:Environment@100] - Client environment:java.version=1.7.0_512016-07-26 03:29:22,791 [myid:] - INFO [main:Environment@100] - Client environment:java.vendor=Oracle Corporation2016-07-26 03:29:22,791 [myid:] - INFO [main:Environment@100] - Client environment:java.home=/usr/jdk1.7.0_51/jre2016-07-26 03:29:22,791 [myid:] - INFO [main:Environment@100] - Client environment:java.class.path=/opt/zookeeper-3.4.6/bin/../build/classes:/opt/zookeeper-3.4.6/bin/../build/lib/*.jar:/opt/zookeeper-3.4.6/bin/../lib/slf4j-log4j12-1.6.1.jar:/opt/zookeeper-3.4.6/bin/../lib/slf4j-api-1.6.1.jar:/opt/zookeeper-3.4.6/bin/../lib/netty-3.7.0.Final.jar:/opt/zookeeper-3.4.6/bin/../lib/log4j-1.2.16.jar:/opt/zookeeper-3.4.6/bin/../lib/jline-0.9.94.jar:/opt/zookeeper-3.4.6/bin/../zookeeper-3.4.6.jar:/opt/zookeeper-3.4.6/bin/../src/java/lib/*.jar:/opt/zookeeper-3.4.6/bin/../conf:$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib2016-07-26 03:29:22,791 [myid:] - INFO [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib2016-07-26 03:29:22,792 [myid:] - INFO [main:Environment@100] - Client environment:java.io.tmpdir=/tmp2016-07-26 03:29:22,792 [myid:] - INFO [main:Environment@100] - Client environment:java.compiler=<NA>2016-07-26 03:29:22,792 [myid:] - INFO [main:Environment@100] - Client environment:os.name=Linux2016-07-26 03:29:22,792 [myid:] - INFO [main:Environment@100] - Client environment:os.arch=amd642016-07-26 03:29:22,793 [myid:] - INFO [main:Environment@100] - Client environment:os.version=4.4.0-31-generic2016-07-26 03:29:22,793 [myid:] - INFO [main:Environment@100] - Client environment:user.name=root2016-07-26 03:29:22,793 [myid:] - INFO [main:Environment@100] - Client environment:user.home=/root2016-07-26 03:29:22,793 [myid:] - INFO [main:Environment@100] - Client environment:user.dir=/opt/zookeeper-3.4.6/bin2016-07-26 03:29:22,794 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=localhost:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@4b4cab63Welcome to ZooKeeper!JLine support is enabled2016-07-26 03:29:22,865 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@975] - Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)2016-07-26 03:29:22,887 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@852] - Socket connection established to localhost/127.0.0.1:2181, initiating session[zk: localhost:2181(CONNECTING) 0] 2016-07-26 03:29:22,936 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1235] - Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x15626c0466b0000, negotiated timeout = 30000WATCHER::WatchedEvent state:SyncConnected type:None path:null 若想以指定Zookeeper地址启动,则命令应输入 sh zkCli.sh -server ip:port 通过客户端创建一个Zookeeper节点 create [-s] [-e] path data acl 其中 -s或-e分别指节点的特性,-s代表顺序节点,-e则代表临时节点。若不添加-e或-s参数,默认创建持久节点。 acl表示用来进行权限控制。 1234WatchedEvent state:SyncConnected type:None path:nullcreate /test_root test_dataCreated /test_root[zk: localhost:2181(CONNECTED) 1] 读取Zookeeper节点ls命令列出指定节点下的所有子节点。只能看到指定节点下的第一级的所有子节点。 ls path [watch] case:在根节点下创建了/test_root持久节点后,使用ls命令查看根节点。然后再/test_root节点下创建了新的持久子节点1,再次使用ls命令查看根节点,结果无变化。 123456789101112WatchedEvent state:SyncConnected type:None path:nullcreate /test_root test_dataCreated /test_root[zk: localhost:2181(CONNECTED) 1] ls /[test_root, dubbo, zookeeper][zk: localhost:2181(CONNECTED) 2] create /test_root/1 test_data_1Created /test_root/1[zk: localhost:2181(CONNECTED) 3] ls /[test_root, dubbo, zookeeper][zk: localhost:2181(CONNECTED) 4] ls /test_root[1][zk: localhost:2181(CONNECTED) 5] get命令获取指定节点数据内容和属性信息 get path [watch] case:使用get命令查看根节点test_root 1234567891011121314[zk: localhost:2181(CONNECTED) 5] get /test_roottest_datacZxid = 0xbfctime = Tue Jul 26 03:35:51 PDT 2016mZxid = 0xbfmtime = Tue Jul 26 03:35:51 PDT 2016pZxid = 0xc0cversion = 1dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 9numChildren = 1[zk: localhost:2181(CONNECTED) 6] 更新Zookeeper节点set命令 set path data [version] case: 将/test_root节点的值更新为luffy,注意更新后节点version的变化 1234567891011121314151617181920212223242526[zk: localhost:2181(CONNECTED) 3] get /test_roottest_datacZxid = 0xbfctime = Tue Jul 26 03:35:51 PDT 2016mZxid = 0xbfmtime = Tue Jul 26 03:35:51 PDT 2016pZxid = 0xc0cversion = 1dataVersion = 0aclVersion = 0ephemeralOwner = 0x0dataLength = 9numChildren = 1[zk: localhost:2181(CONNECTED) 4] set /test_root luffycZxid = 0xbfctime = Tue Jul 26 03:35:51 PDT 2016mZxid = 0xc3mtime = Tue Jul 26 19:24:32 PDT 2016pZxid = 0xc0cversion = 1dataVersion = 1aclVersion = 0ephemeralOwner = 0x0dataLength = 5numChildren = 1[zk: localhost:2181(CONNECTED) 5] 删除Zookeeper节点delete 命令 delete path [version]注意,只能删除叶子节点。 case: 将/test_root下的1节点删除。 123456[zk: localhost:2181(CONNECTED) 5] ls /test_root[1][zk: localhost:2181(CONNECTED) 6] delete /test_root/1[zk: localhost:2181(CONNECTED) 7] ls /test_root [][zk: localhost:2181(CONNECTED) 8]","categories":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/categories/Zookeeper/"}],"tags":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/tags/Zookeeper/"}]},{"title":"Zookeeper学习笔记二","slug":"zookeeper_summary","date":"2016-07-17T16:00:00.000Z","updated":"2019-06-05T13:59:40.816Z","comments":true,"path":"2016/07/18/zookeeper_summary/","link":"","permalink":"https://wangran.me/2016/07/18/zookeeper_summary/","excerpt":"基本概念集群角色ZooKeeper没有沿用传统集群模式(主备模式,Master/Slave),而是采用了Leader、Follower、Observer三种角色。 Leader 选举而来,为client提供读/写服务。 Follower 为client提供读服务,可参与选举。 Observer 为client提供读服务,不参与Leader选举,不参与写操作的“过半写成功”策略,通常用于提升集群读性能。","text":"基本概念集群角色ZooKeeper没有沿用传统集群模式(主备模式,Master/Slave),而是采用了Leader、Follower、Observer三种角色。 Leader 选举而来,为client提供读/写服务。 Follower 为client提供读服务,可参与选举。 Observer 为client提供读服务,不参与Leader选举,不参与写操作的“过半写成功”策略,通常用于提升集群读性能。 会话(Session) 数据节点(Znode)指数据模型(Znode Tree)中的数据单元 版本 事件监听器(Watcher) ACL(Access Control Lists)ZooKeeper定义了5种权限: CREATE 创建子节点的权限 READ 获取节点数据和节点列表的权限 WRITE 更新节点数据的权限 DELETE 删除子节点的权限 ADMIN 设置节点ACL的权限 ZAB协议ZooKeeper原子消息广播协议(Zookeeper Atomic Broadcast)。ZooKeeper并未采用Paxos,而是采用ZAB作为数据一致性的核心算法。 ZAB协议两种基本模式崩溃恢复 服务器启动、Leader服务器挂掉时进入该模式,选举产生新Leader服务器。 当集群中有过半服务器(包含Leader)与Leader服务器完成状态同步(即数据同步)后,退出该模式。 基本特性 ZAB协议需要确保已在Leader提交的事务最终被所有服务器提交。 ZAB协议需要确保丢弃那些只在Leader服务器上被提出的事务。 Leader选举算法思路:确保已被Leader提交的事务Proposal,同时丢弃已被掉过的事务Proposal。即保证选举出的Leader服务器拥有集群中ZXID最大的事务Proposal。 数据同步Leader服务器通过确认事务日志中所有Proposal是否已被集群中过半的服务器提交,来判断是否完成数据同步。 消息广播ZAB协议核心定义了可能改变ZooKeeper服务器数据状态的数据请求处理方式: Leader服务器收到事务请求后,生成事务Proposal,并为其分配事务ID(全局单调递增的ID,ZXID),放入队列中。 Follower服务器接收到Proposal后,以事务日志形式写入到本地磁盘,写入成功后Respone Leader服务器“Ack”。 当Leader服务器接收超过半数Follower服务器Ack响应,广播Commit消息发送给所有Follower服务器,Leader自身与收到Commit消息的Follower服务器会完成事务提交。 基于TCP协议(FIFO)保证消息接收与发送的顺序性。","categories":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/categories/Zookeeper/"}],"tags":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/tags/Zookeeper/"}]},{"title":"Zookeeper学习笔记一之Paxos算法","slug":"zookeeper_paxos","date":"2016-07-09T16:00:00.000Z","updated":"2019-06-05T13:59:40.816Z","comments":true,"path":"2016/07/10/zookeeper_paxos/","link":"","permalink":"https://wangran.me/2016/07/10/zookeeper_paxos/","excerpt":"背景1990年提出的一种基于消息传递且具有高度容错性的一致性算法。","text":"背景1990年提出的一种基于消息传递且具有高度容错性的一致性算法。 算法描述假设有一组可以提出提案的进程集合,对于一个一致性算法来说需保证: 提案只有一个会被选定 若没有提案提出,则没有被选定的提案 提案若被选定,进程可以获取提案信息 分布式系统的【三态】:成功、失败、超时。","categories":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/categories/Zookeeper/"}],"tags":[{"name":"Zookeeper","slug":"Zookeeper","permalink":"https://wangran.me/tags/Zookeeper/"}]},{"title":"TCP长连接与短连接的区别","slug":"tcp_connection","date":"2016-07-02T16:00:00.000Z","updated":"2019-06-05T13:59:40.814Z","comments":true,"path":"2016/07/03/tcp_connection/","link":"","permalink":"https://wangran.me/2016/07/03/tcp_connection/","excerpt":"TCP连接当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的。","text":"TCP连接当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的。 经典的三次握手示意图: 经典的四次握手关闭图: TCP短连接我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在client/server间传递一次读写操作 短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段 TCP长连接接下来我们再模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。 首先说一下TCP/IP详解上讲到的TCP保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。 如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一: 客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。 客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。 客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应。 从上面可以看出,TCP保活功能主要为探测长连接的存活状况,不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。 在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。 长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。 本文转自http://www.cnblogs.com/beifei/archive/2011/06/26/2090611.html","categories":[{"name":"TCP","slug":"TCP","permalink":"https://wangran.me/categories/TCP/"}],"tags":[{"name":"TCP","slug":"TCP","permalink":"https://wangran.me/tags/TCP/"}]},{"title":"数据库学习笔记 - 数据库保护","slug":"db_trans","date":"2016-05-21T16:00:00.000Z","updated":"2019-06-05T13:59:40.813Z","comments":true,"path":"2016/05/22/db_trans/","link":"","permalink":"https://wangran.me/2016/05/22/db_trans/","excerpt":"什么是事务(Transaction)事务是用户定义的数据操作系列,这些操作可作为一个完整的工作单元(数据库的逻辑工作单位)。","text":"什么是事务(Transaction)事务是用户定义的数据操作系列,这些操作可作为一个完整的工作单元(数据库的逻辑工作单位)。 事务的特性(ACID) 原子性(Atomicity)事务中的操作要么都做,要么都不做。 一致性(Consistency)只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。 隔离性(Isolation)数据库中一个事务的执行不能被其他事务干扰。 持久性(Durability)事务一旦提交,其对数据库中数据的改变就是永久的。 并发控制并发问题导致的异常情况归类如下: 丢失更新(Lost Update)两个事务都同时更新一行数据,第一个事务的提交结果被第二个事务的提交结果破坏。 脏读(Dirty Reads)一个事务读了某个失败事务运行过程中的数据。 不可重复读(Non-repeatable Reads)一个事务对同一行数据重复读取两次,但是却得到了不同的结果。 幻读(Phantom Reads)幻读实际属于不可重复读的范畴。它指当事务T1按照一定条件读取某些数据,事务T2对其中部分记录做了删除或更新操作,当事务T1再次以相同条件读取数据时,发现少或多了某些记录。 事务的隔离级别未授权读取(Read Uncommitted)也称读未提交,隔离级别最低,允许脏读。 授权读取(Read Committed)也称读已提交,允许不可重复读。 可重复读(Repeatable Read)允许出现幻读。 串行化(Serializable)最严格的事务隔离级别,要求所有事务都被串行执行。 隔离级别 脏读 可重复读 幻读 未授权读取 存在 不可以 存在 授权读取 不存在 不可以 存在 可重复读取 不存在 可以 存在 串行化 不存在 可以 不存在 分布式事务CAP定理一个分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),最多只能同时满足其中两项。 BASE理论一致性协议协调者:统一调度所有分布式节点执行逻辑组件。参与者:被协调者调度的分布式节点。 2PC二阶段提交协议(Two-Phase Commit)3PC锁悲观锁(Pessimistic Concurrency Control, PCC)又称排他锁,若事务T1使用悲观锁对数据进行处理,则处理完成前,其他事务均不能对数据进行更新操作。 乐观锁 (Optimistic Concurrency Control, OCC)乐观锁事务控制分为三阶段,分别是:数据读取、写入校验、数据写入。","categories":[{"name":"Database","slug":"Database","permalink":"https://wangran.me/categories/Database/"}],"tags":[{"name":"数据库","slug":"数据库","permalink":"https://wangran.me/tags/数据库/"}]}]}