首页 文章 精选 留言 我的

精选列表

搜索[图形后端],共10000篇文章
优秀的个人博客,低调大师

打通前后端逻辑,客户端Flutter代码一天上线

一、前沿 ​ 随着闲鱼的业务快速增长,运营类的需求也越来越多,其中不乏有很多界面修改或运营坑位的需求。闲鱼的版本现在是每2周一个版本,如何快速迭代产品,跳过窗口期来满足这些需求?另外,闲鱼客户端的包体也变的很大,企业包的大小,iOS已经到了94.3M,Android也到了53.5M。Android的包体大小,相比2016年,已经增长了近1倍,怎么能将包体大小降下来?首先想到的是如何动态化的解决此类问题。 ​ 对于原生的能力的动态化,Android平台各公司都有很完善的动态化方案,甚至Google还提供了Android App Bundles让开发者们更好地支持动态化。由于Apple官方担忧动态化的风险,因此并不太支持动态化。因此动态化能力就会考虑跟Web结合,从一开始基于 WebView 的 Hybrid 方案 PhoneGap、Titanium,到现在与原生相结合的 React Native 、Weex。 ​ 但Native和JavaScript Context之间的通讯,频繁的交互就成了程序的性能瓶颈。于此同时随着闲鱼Flutter技术的推广,已经有10多个页面用Flutter实现,上面提到的几种方式都不适合Flutter场景,如何解决这个问题Flutter的动态化的问题? 二、动态方案 我们最初调研了Google的动态化方案CodePush。 2.1 CodePush ​ CodePush是谷歌官方推出的动态化方案,目前只有在Android上面实现了。Dart VM在执行的时候,加载isolate_snapshot_data和isolate_snapshot_instr2个文件,通过动态更改这些文件,就达到动态更新的目的。官方的Flutter源码当中,已经有相关的提交来做动态更新的内容,具体内容可以参考ResourceExtractor.java。 ​ 根据官方给出的Guide,我们这边也做了相关的测试,patch的包体大小会很大(939kb)。为了降低包体大小,还可以通过增量的修改snapshot文件的方式来更新。通过bsdiff生成的snapshot的差异文件,2个文件分别可以缩小到48kb和870kb。 ​ 目前看来,CodePush还不能做到很好的工程化。而且如何管理patch文件,需要制定baseline和patch文件的规则。 2.2 动态模板 ​ 动态模板,就是通过定义一套DSL,在端侧解析动态的创建View来实现动态化,比如LuaViewSDK、Tangram-iOS和Tangram-Android。这些方案都是创建的Native的View,如果想在Flutter里面实现,需要创建Texture来桥接;Native端渲染完成之后,再将纹理贴在Flutter的容器里面,实现成本很高,性能也有待商榷,不适合闲鱼的场景。 ​ 所以我们提出了闲鱼自己的Flutter动态化方案,前面已经有同事介绍过方案的原理:《做了2个多月的设计和编码,我梳理了Flutter动态化的方案对比及最佳实现》,下面看下具体的实现细节。 三、模板编译 自定义一套DSL,维护成本较高,怎么能不自定义DSL来实现模板下发?闲鱼的方案就是直接将Dart文件转化成模板,这样模板文件也可以快速沉淀到端侧。 3.1 模板规范 ​ 先来看下一个完整的模板文件,以新版我的页面为例,这个是一个列表结构,每个区块都是一个独立的Widget,现在我们期望将“卖在闲鱼”这个区块动态渲染,对这个区块拆分之后,需要3个子控件:头部、菜单栏、提示栏;因为这3部分界面有些逻辑处理,所以先把他们的逻辑内置。 内置的子控件分别是MenuTitleWidget、MenuItemWidget和HintItemWidget,编写的模板如下: @override Widget build(BuildContext context) { return new Container( child: new Column( children: <Widget>[ new MenuTitleWidget(data), // 头部 new Column( // 菜单栏 children: <Widget>[ new Row( children: <Widget>[ new MenuItemWidget(data.menus[0]), new MenuItemWidget(data.menus[1]), new MenuItemWidget(data.menus[2]), ], ) ], ), new Container( // 提示栏 child: new HintItemWidget(data.hints[0])), ], ), ); } 中间省略了样式描述,可以看到写模板文件就跟普通的widget写法一样,但是有几点要注意: 每个Widget都需要用new或const来修饰 数据访问以data开头,数组形式以[]访问,字典形式以.访问 ​ 模板写好之后,就要考虑怎么在端上渲染,早期版本是直接在端侧解析文件,但是考虑到性能和稳定性,还是放在前期先编译好,然后下发到端侧。 3.2 编译流程 ​ 编译模板就要用到Dart的Analyzer库,通过parseCompilationUnit函数直接将Dart源码解析成为以CompilationUnit为Root节点的AST树中,它包含了Dart源文件的语法和语义信息。接下来的目标就是将CompilationUnit转换成为一个JSON格式。 ​ 上面的模板解析出来build函数孩子节点是ReturnStatementImpl,它又包含了一个子节点InstanceCreationExpressionImpl,对应模板里面的new Container(…),它的孩子节点中,我们最关心的就是ConstructorNameImpl和ArgumentListImpl节点。ConstructorNameImpl标识创建节点的名称,ArgumentListImpl标识创建参数,参数包含了参数列表和变量参数。 定义如下结构体,来存储这些信息: class ConstructorNode { // 创建节点的名称 String constructorName; // 参数列表 List<dynamic> argumentsList = <dynamic>[]; // 变量参数 Map<String, dynamic> arguments = <String, dynamic>{}; } 递归遍历整棵树,就可以得到一个ConstructorNode树,以下代码是解析单个Node的参数: ArgumentList argumentList = astNode; for (Expression exp in argumentList.arguments) { if (exp is NamedExpression) { NamedExpression namedExp = exp; final String name = ASTUtils.getNodeString(namedExp.name); if (name == 'children') { continue; } /// 是函数 if (namedExp.expression is FunctionExpression) { currentNode.arguments[name] = FunctionExpressionParser.parse(namedExp.expression); } else { /// 不是函数 currentNode.arguments[name] = ASTUtils.getNodeString(namedExp.expression); } } else if (exp is PropertyAccess) { PropertyAccess propertyAccess = exp; final String name = ASTUtils.getNodeString(propertyAccess); currentNode.argumentsList.add(name); } else if (exp is StringInterpolation) { StringInterpolation stringInterpolation = exp; final String name = ASTUtils.getNodeString(stringInterpolation); currentNode.argumentsList.add(name); } else if (exp is IntegerLiteral) { final IntegerLiteral integerLiteral = exp; currentNode.argumentsList.add(integerLiteral.value); } else { final String name = ASTUtils.getNodeString(exp); currentNode.argumentsList.add(name); } } 端侧拿到这个ConstructorNode节点树之后,就可以根据Widget的名称和参数,来生成一棵Widget树。 四、渲染引擎 端侧拿到编译好的模板JSON后,就是解析模板并创建Widget。先看下,整个工程的框架和工作流: 工作流程: 开发人员编写dart文件,编译上传到CDN 端侧拿到模板列表,并在端侧存库 业务方直接下发对应的模板id和模板数据 Flutter侧再通过桥接获取到模板,并创建Widget树 对于Native测,主要负责模板的管理,通过桥接输出到Flutter侧。 4.1 模板获取 模板获取分为2部分,Native部分和Flutter部分;Native主要负责模板的管理,包括下载、降级、缓存等。 程序启动的时候,会先获取模板列表,业务方需要自己实现,Native层获取到模板列表会先存储在本地数据库中。Flutter侧业务代码用到模板的时候,再通过桥接获取模板信息,就是我们前面提到的JSON格式的信息,Flutter也会有缓存,已减少Flutter和Native的交互。 4.2 Widget创建 Flutter侧当拿到JSON格式的,先解析出ConstructorNode树,然后递归创建Widget。 创建每个Widget的过程,就是解析节点中的argumentsList和arguments并做数据绑定。例如,创建HintItemWidget需要传入提示的数据内容,new HintItemWidget(data.hints[0]),在解析argumentsList时,会通过key-path的方式从原始数据中解析出特定的值。 解析出来的值都会存储在WidgetCreateParam里面,当递归遍历每个创建节点,每个widget都可以从WidgetCreateParam里面解析出需要的参数。 /// 构建widget用的参数 class WidgetCreateParam { String constructorName; /// 构建的名称 dynamic context; /// 构建的上下文 Map<String, dynamic> arguments = <String, dynamic>{}; /// 字典参数 List<dynamic> argumentsList = <dynamic>[]; /// 列表参数 dynamic data; /// 原始数据 } ​ 通过以上的逻辑,就可以将ConstructorNode树转换为一棵Widget树,再交给Flutter Framework去渲染。 至此,我们已经能将模板解析出来,并渲染到界面上,交互事件应该怎么处理? 4.3 事件处理 在写交互的时候,一般都会通过GestureDector、InkWell等来处理点击事件。交互事件怎么做动态化? ​ 以InkWell组件为例,定义它的onTap函数为openURL(data.hints[0].href, data.hints[0].params)。在创建InkWell时,会以OpenURL作为事件ID,查找对应的处理函数,当用户点击的时候,会解析出对应的参数列表并传递过去,代码如下: ... final List<dynamic> tList = <dynamic>[]; // 解析出参数列表 exp.argumentsList.forEach((dynamic arg) { if (arg is String) { final dynamic value = valueFromPath(arg, param.data); if (value != null) { tList.add(value); } else { tList.add(arg); } } else { tList.add(arg); } }); // 找到对应的处理函数 final dynamic handler = TeslaEventManager.sharedInstance().eventHandler(exp.actionName); if (handler != null) { handler(tList); } ... 五、 效果 新版我的页面添加了动态化渲染能力之后,如果有需求新添加一种组件类型,就可以直接编译发布模板,服务端下发新的数据内容,就可以渲染出来了;动态化能力有了,大家会关心渲染性能怎么样。 5.1 帧率 在加了动态加载逻辑之后,已经开放了2个动态卡片,下图是新版本我的页面近半个月的的帧率数据: 从上图可以看到,帧率并没有降低,基本保持在55-60帧左右,后续可以多添加动态的卡片,观察下效果。 注:因为我的页面会有本地的一些业务判断,从其他页面回到我的tab,都会刷新界面,所以帧率会有损耗。 ​ 从实现上分析,因为每个卡片,都需要遍历ConstructorNode树来创建,而且每个构建都需要解析出里面的参数,这块可以做一些优化,比如缓存相同的Widget,只需要映射出数据内容并做数据绑定。 5.2 失败率 现在监控了渲染的逻辑,如果本地没有对应的Widget创建函数,会主动抛Error。监控数据显示,渲染的流程中,还没有异常的情况,后续还需要对桥接层和native层加错误埋点。 六、展望 ​ 基于Flutter动态模板,之前需要走发版的Flutter需求,都可以来动态化更改。而且以上逻辑都是基于Flutter原生的体系,学习和维护成本都很低,动态的代码也可以快速的沉淀到端侧。 ​ 另外,闲鱼正在研究UI2Code的黑科技,不了解的老铁,可以参考闲鱼大神的这篇文章《重磅系列文章!UI2CODE智能生成Flutter代码——整体设计篇》。可以设想下,如果有个需求,需要动态的显示一个组件,UED出了视觉稿,通过UI2Code转换成Dart文件,再通过这个系统转换成动态模板,下发到端侧就可以直接渲染出来,程序员都不需要写代码了,做到自动化运营,看来以后程序员失业也不是没有可能了。 ​ 基于Flutter的Widget,还可以拓展更多个性化的组件,比如内置动画组件,就可以动态化下发动画了,更多好玩的东西等待大家来一起探索。 参考文献 https://github.com/flutter/flutter/issues/14330 https://www.dartlang.org/ https://mp.weixin.qq.com/s/4s6MaiuW4VoHr_7f0S_vuQ https://github.com/flutter/engine 作者:闲鱼技术-景松 原文链接 ​本文为云栖社区原创内容,未经允许不得转载。

优秀的个人博客,低调大师

前后端分离架构使用shiro框架进行登录的两种实现

方法一:重写FormAuthenticationFilter 原理: 假设在shiro.xml中配置了 /** = authc 而默认authc对应org.apache.shiro.web.filter.authc.FormAuthenticationFilter过滤器 则表示所有路径都被此过滤器拦截 当未登录请求被拦截,会调用FormAuthenticationFilter.onAccessDeny(): 如果请求的是loginUrl,则调用AuthenticatingFilter.executeLogin() 如果不是,则使request重定向到loginUrl,并return false; AuthenticatingFilter.executeLogin(): 调用subject.login(token) 如果登录成功,则调用onLoginSuccess() 失败则调用onLoginFailure() AuthenticatingFilter.onLoginSuccess(): return true; AuthenticatingFilter.onLoginFailure(): return false; subject.login(token): 最终会调用realm的doGetAuthenticationInfo 思路 重写默认的FormAuthenticationFilter, 在onAccessDeny()方法中: 如果请求的是loginUrl,则调用AuthenticatingFilter.executeLogin() 如果不是,则返回json,提示“未登录,无法访问该地址” @Override protectedbooleanonAccessDenied(ServletRequestrequest,ServletResponseresponse)throwsException{ if(this.isLoginRequest(request,response)){ if(this.isLoginSubmission(request,response)){ if(log.isTraceEnabled()){ log.trace("Loginsubmissiondetected.Attemptingtoexecutelogin."); } returnthis.executeLogin(request,response); }else{ if(log.isTraceEnabled()){ log.trace("Loginpageview."); } returntrue; } }else{ if(log.isTraceEnabled()){ log.trace("Attemptingtoaccessapathwhichrequiresauthentication.ForwardingtotheAuthenticationurl["+this.getLoginUrl()+"]"); } response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); PrintWriterout=response.getWriter(); ServerResponseserverResponse=ServerResponse.createByErrorMessage("未登录,无法访问该地址"); Gsongson=GsonFactory.getGson(); Strings=gson.toJson(serverResponse); out.println(s); out.flush(); out.close(); returnfalse; } } 由于AuthenticatingFilter.executeLogin()会调用onLoginSuccess()和onLoginFailure()方法 所以我们重写两个方法,前者返回登录成功的json,并把当前用户放入session;后者返回登录失败的json @Override protectedbooleanonLoginSuccess(AuthenticationTokentoken,Subjectsubject,ServletRequestrequest,ServletResponseresponse)throwsException{ response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); PrintWriterout=null; try{ out=response.getWriter(); }catch(IOExceptione1){ e1.printStackTrace(); } HttpSessionsession=((HttpServletRequest)request).getSession(); Useruser=userMapper.selectByUserName(token.getPrincipal().toString()); session.setAttribute(Const.CURRENT_USER,user); ServerResponseserverResponse=ServerResponse.createBySuccessMsg("登录成功"); Gsongson=GsonFactory.getGson(); Strings=gson.toJson(serverResponse); out.println(s); out.flush(); out.close(); returntrue; } @Override protectedbooleanonLoginFailure(AuthenticationTokentoken,AuthenticationExceptione,ServletRequestrequest,ServletResponseresponse){ response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); PrintWriterout=null; try{ out=response.getWriter(); }catch(IOExceptione1){ e1.printStackTrace(); } ServerResponseserverResponse=ServerResponse.createByErrorMessage("登录失败"); Gsongson=GsonFactory.getGson(); Strings=gson.toJson(serverResponse); out.println(s); out.flush(); out.close(); returnfalse; } } 最后配置shiro.xml将authc对应的默认的FormAuthenticationFilter,替换成我们的MyAuthenticationFilter <beanid="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <propertyname="securityManager"ref="securityManager"/> <propertyname="loginUrl"value="/user/login.do"/> <propertyname="unauthorizedUrl"value="/user/unauthorized_err"/> <propertyname="filters"> <map> <entrykey="authc"value-ref="myAuthenticationFilter"/> </map> </property> <propertyname="filterChainDefinitions"> <value> /user/login_err.do=anon /user/unauthorized_err.do=anon /**=authc </value> </property> </bean> <beanid="myAuthenticationFilter"class="com.mmall.shiro.filter.MyAuthenticationFilter"/> 方法二:使用PassThruAuthenticationFilter代替FormAuthenticationFilter 原理: org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter源码: publicclassPassThruAuthenticationFilterextendsAuthenticationFilter{ publicPassThruAuthenticationFilter(){ } protectedbooleanonAccessDenied(ServletRequestrequest,ServletResponseresponse)throwsException{ if(this.isLoginRequest(request,response)){ returntrue; }else{ this.saveRequestAndRedirectToLogin(request,response); returnfalse; } } } 官方文档: An authentication filter that redirects the user to the login page when they are trying to access a protected resource. However, if the user is trying to access the login page, the filter lets the request pass through to the application code. The difference between this filter and the FormAuthenticationFilter is that on a login submission (by default an HTTP POST to the login URL), the FormAuthenticationFilter filter attempts to automatically authenticate the user by passing the username and password request parameter values to Subject.login(usernamePasswordToken) directly. Conversely, this controller always passes all requests to the loginUrl through, both GETs and POSTs. This is useful in cases where the developer wants to write their own login behavior, which should include a call to Subject.login(AuthenticationToken) at some point. For example, if the developer has their own custom MVC login controller or validator, this PassThruAuthenticationFilter may be appropriate. 我们继承PassThruAuthenticationFilter并重写onAccessDenied方法,和redirectToLogin方法 publicclassMyPassThruAuthenticationFilterextendsPassThruAuthenticationFilter{ privateStringloginErrUrl="/"; publicvoidsetLoginErrUrl(StringloginErrUrl){ this.loginErrUrl=loginErrUrl; } @Override protectedbooleanonAccessDenied(ServletRequestrequest,ServletResponseresponse)throwsException{ if(this.isLoginRequest(request,response)){ returntrue; }else{ this.saveRequestAndRedirectToLogin(request,response); returnfalse; } } //重写redirectToLogin方法是因为saveRequestAndRedirectToLogin方法会调用它,而原始的redirectToLogin方法会使得请求重定向到loginUrl @Override protectedvoidredirectToLogin(ServletRequestrequest,ServletResponseresponse)throwsIOException{ WebUtils.issueRedirect(request,response,loginErrUrl); } } 首先修改shiro.xml , 将authc默认对应的FormAuthenticationFilter修改为MyPassThruAuthenticationFilter <beanid="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <propertyname="securityManager"ref="securityManager"/> <propertyname="loginUrl"value="/user/login.do"/> <propertyname="unauthorizedUrl"value="/user/unauthorized_err"/> <propertyname="filters"> <map> <entrykey="authc"> <beanclass="com.mmall.shiro.filter.MyPassThruAuthenticationFilter"> <propertyname="loginErrUrl"value="/user/login_err.do"/><!--注意这里的loginErrUrl与上面的loginUrl的区别--> </bean> </entry> </map> </property> <propertyname="filterChainDefinitions"> <value> /=anon /user/login_err.do=anon /user/unauthorized_err.do=anon /user/logout.do=logout /**=authc </value> </property> </bean> 然后修改loginUrl对应的Contoller的方法,在其中要调用subject.login()完成shiro的认证 //UserController中:结合shiro,使用PassThruAuthenticationFilter的登录,需要调用subject.login()完成shiro的认证 @RequestMapping(value="login.do",method=RequestMethod.POST) @ResponseBody publicServerResponselogin(@RequestBodyUseruser,HttpSessionsession){ Subjectsubject=SecurityUtils.getSubject(); UsernamePasswordTokentoken=newUsernamePasswordToken(user.getUsername(),user.getPassword()); //ServerResponseserverResponse=iUserService.login(user.getUsername(),user.getPassword()); try{ /**subject.login(token)提交申请,验证能不能通过,也就是交给shiro。这里会回调reaml(或自定义的realm)里的一个方法 protectedAuthenticationInfodoGetAuthenticationInfo()*/ subject.login(token); }catch(AuthenticationExceptione){//验证身份失败 returnServerResponse.createByErrorMessage("登陆客户身份失败!"); } /**Shiro验证后,跳转到此处,这里判断验证是否通过*/ if(subject.isAuthenticated()){//验证身份通过 session.setAttribute(Const.CURRENT_USER,subject.getPrincipal()); returnServerResponse.createBySuccessMsg("登录成功"); }else{ returnServerResponse.createByErrorMessage("登陆客户身份失败!"); } } 而loginErrUrl对应的方法则返回Json提示未登录: @RequestMapping(value="login_err.do",method=RequestMethod.GET) @ResponseBody publicServerResponselogin_error(){ returnServerResponse.createByErrorMessage("用户未登录"); }

优秀的个人博客,低调大师

后端技术杂谈11:十分钟理解Kubernetes核心概念

十分钟带你理解Kubernetes核心概念 本文将会简单介绍 Kubernetes的核心概念。因为这些定义可以在Kubernetes的文档中找到,所以文章也会避免用大段的枯燥的文字介绍。相反,我们会使用一些图表(其中一些是动画)和示例来解释这些概念。我们发现一些概念(比如Service)如果没有图表的辅助就很难全面地理解。在合适的地方我们也会提供Kubernetes文档的链接以便读者深入学习。 【Kubernetes培训通知】DockOne将会于2018年8月17日在深圳举办Kubernetes技术培训,培训内容包括:Docker介绍、Docker镜像、网络、存储、容器安全;Kubernetes架构、设计理念、常用对象、网络、存储、网络隔离、服务发现与负载均衡;Kubernetes核心组件、Pod、插件、微服务、云原生、Kubernetes Operator、集群灾备、Helm等。 这就开始吧。 什么是Kubernetes? Kubernetes(k8s)是自动化容器操作的开源平台,这些操作包括部署,调度和节点集群间扩展。如果你曾经用过Docker容器技术部署容器,那么可以将Docker看成Kubernetes内部使用的低级别组件。Kubernetes不仅仅支持Docker,还支持Rocket,这是另一种容器技术。 使用Kubernetes可以: 自动化容器的部署和复制 随时扩展或收缩容器规模 将容器组织成组,并且提供容器间的负载均衡 很容易地升级应用程序容器的新版本 提供容器弹性,如果容器失效就替换它,等等... 实际上,使用Kubernetes只需一个 部署文件,使用一条命令就可以部署多层容器(前端,后台等)的完整集群: $kubectlcreate-fsingle-config-file.yaml kubectl是和Kubernetes API交互的命令行程序。现在介绍一些核心概念。 集群 集群是一组节点,这些节点可以是物理服务器或者虚拟机,之上安装了Kubernetes平台。下图展示这样的集群。注意该图为了强调核心概念有所简化。 这里可以看到一个典型的Kubernetes架构图。 上图可以看到如下组件,使用特别的图标表示Service和Label: Pod Container(容器) Label()(标签) Replication Controller(复制控制器) Service()(服务) Node(节点) Kubernetes Master(Kubernetes主节点) Pod Pod(上图绿色方框)安排在节点上,包含一组容器和卷。同一个Pod里的容器共享同一个网络命名空间,可以使用localhost互相通信。Pod是短暂的,不是持续性实体。你可能会有这些问题: 如果Pod是短暂的,那么我怎么才能持久化容器数据使其能够跨重启而存在呢? 是的,Kubernetes支持卷的概念,因此可以使用持久化的卷类型。 是否手动创建Pod,如果想要创建同一个容器的多份拷贝,需要一个个分别创建出来么?可以手动创建单个Pod,但是也可以使用Replication Controller使用Pod模板创建出多份拷贝,下文会详细介绍。 如果Pod是短暂的,那么重启时IP地址可能会改变,那么怎么才能从前端容器正确可靠地指向后台容器呢?这时可以使用Service,下文会详细介绍。 Lable 正如图所示,一些Pod有Label( )。一个Label是attach到Pod的一对键/值对,用来传递用户定义的属性。比如,你可能创建了一个"tier"和“app”标签,通过Label( tier=frontend, app=myapp)来标记前端Pod容器,使用Label( tier=backend, app=myapp)标记后台Pod。然后可以使用 Selectors选择带有特定Label的Pod,并且将Service或者Replication Controller应用到上面。 Replication Controller 是否手动创建Pod,如果想要创建同一个容器的多份拷贝,需要一个个分别创建出来么,能否将Pods划到逻辑组里? Replication Controller确保任意时间都有指定数量的Pod“副本”在运行。如果为某个Pod创建了Replication Controller并且指定3个副本,它会创建3个Pod,并且持续监控它们。如果某个Pod不响应,那么Replication Controller会替换它,保持总数为3.如下面的动画所示: 如果之前不响应的Pod恢复了,现在就有4个Pod了,那么Replication Controller会将其中一个终止保持总数为3。如果在运行中将副本总数改为5,Replication Controller会立刻启动2个新Pod,保证总数为5。还可以按照这样的方式缩小Pod,这个特性在执行滚动 升级时很有用。 当创建Replication Controller时,需要指定两个东西: Pod模板:用来创建Pod副本的模板 Label:Replication Controller需要监控的Pod的标签。 现在已经创建了Pod的一些副本,那么在这些副本上如何均衡负载呢?我们需要的是Service。 Service 如果Pods是短暂的,那么重启时IP地址可能会改变,怎么才能从前端容器正确可靠地指向后台容器呢? Service是定义一系列Pod以及访问这些Pod的策略的一层 抽象。Service通过Label找到Pod组。因为Service是抽象的,所以在图表里通常看不到它们的存在,这也就让这一概念更难以理解。 现在,假定有2个后台Pod,并且定义后台Service的名称为‘backend-service’,lable选择器为( tier=backend, app=myapp)。 backend-service的Service会完成如下两件重要的事情: 会为Service创建一个本地集群的DNS入口,因此前端Pod只需要DNS查找主机名为 ‘backend-service’,就能够解析出前端应用程序可用的IP地址。 现在前端已经得到了后台服务的IP地址,但是它应该访问2个后台Pod的哪一个呢?Service在这2个后台Pod之间提供透明的负载均衡,会将请求分发给其中的任意一个(如下面的动画所示)。通过每个Node上运行的代理(kube-proxy)完成。这里有更多技术细节。 下述动画展示了Service的功能。注意该图作了很多简化。如果不进入网络配置,那么达到透明的负载均衡目标所涉及的底层网络和路由相对先进。如果有兴趣, 这里有更深入的介绍。 有一个特别类型的Kubernetes Service,称为' LoadBalancer',作为外部负载均衡器使用,在一定数量的Pod之间均衡流量。比如,对于负载均衡Web流量很有用。 Node 节点(上图橘色方框)是物理或者虚拟机器,作为Kubernetes worker,通常称为Minion。每个节点都运行如下Kubernetes关键组件: Kubelet:是主节点代理。 Kube-proxy:Service使用其将链接路由到Pod,如上文所述。 Docker或Rocket:Kubernetes使用的容器技术来创建容器。 Kubernetes Master 集群拥有一个Kubernetes Master(紫色方框)。Kubernetes Master提供集群的独特视角,并且拥有一系列组件,比如Kubernetes API Server。API Server提供可以用来和集群交互的REST端点。master节点包括用来创建和复制Pod的Replication Controller。 下一步 现在我们已经了解了Kubernetes核心概念的基本知识,你可以进一步阅读Kubernetes 用户手册。用户手册提供了快速并且完备的学习文档。 如果迫不及待想要试试Kubernetes,可以使用 Google Container Engine。Google Container Engine是托管的Kubernetes容器环境。简单注册/登录之后就可以在上面尝试示例了。 原文链接:Learn the Kubernetes Key Concepts in 10 Minutes(翻译:崔婧雯) =========================== 译者介绍 崔婧雯,现就职于IBM,高级软件工程师,负责IBM WebSphere业务流程管理软件的系统测试工作。曾就职于VMware从事桌面虚拟化产品的质量保证工作。对虚拟化,中间件技术,业务流程管理有浓厚的兴趣。 微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。(关注公众号后回复”Java“即可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源) ​

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册