首页 文章 精选 留言 我的

精选列表

搜索[设计协作平台],共10000篇文章
优秀的个人博客,低调大师

java版spring cloud+spring boot 社交电子商务平台-Ribbon设计原理

一. Spring集成下的Ribbon工作结构 先贴一张总览图,说明一下Spring如何集成Ribbon的,如下所示: Spring Cloud集成模式下的Ribbon有以下几个特征: 1.Ribbon 服务配置方式 每一个服务配置都有一个Spring ApplicationContext上下文,用于加载各自服务的实例。 和Feign的集成模式 在使用Feign作为客户端时,最终请求会转发成 http://<服务名称>/的格式,通过LoadBalancerFeignClient, 提取出服务标识<服务名称>,然后根据服务名称在上下文中查找对应服务的负载均衡器FeignLoadBalancer,负载均衡器负责根据既有的服务实例的统计信息,挑选出最合适的服务实例。 二、Spring Cloud模式下和Feign的集成实现方式 和Feign结合的场景下,Feign的调用会被包装成调用请求LoadBalancerCommand,然后底层通过Rxjava基于事件的编码风格,发送请求;Spring Cloud框架通过 Feigin 请求的URL,提取出服务名称,然后在上下文中找到对应服务的的负载均衡器实现FeignLoadBalancer,然后通过负载均衡器中挑选一个合适的Server实例,然后将调用请求转发到该Server实例上,完成调用,在此过程中,记录对应Server实例的调用统计信息。 /** * Create an {@link Observable} that once subscribed execute network call asynchronously with a server chosen by load balancer. * If there are any errors that are indicated as retriable by the {@link RetryHandler}, they will be consumed internally by the * function and will not be observed by the {@link Observer} subscribed to the returned {@link Observable}. If number of retries has * exceeds the maximal allowed, a final error will be emitted by the returned {@link Observable}. Otherwise, the first successful * result during execution and retries will be emitted. */ public Observable<T> submit(final ServerOperation<T> operation) { final ExecutionInfoContext context = new ExecutionInfoContext(); if (listenerInvoker != null) { try { listenerInvoker.onExecutionStart(); } catch (AbortExecutionException e) { return Observable.error(e); } } // 同一Server最大尝试次数 final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer(); //下一Server最大尝试次数 final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer(); // Use the load balancer // 使用负载均衡器,挑选出合适的Server,然后执行Server请求,将请求的数据和行为整合到ServerStats中 Observable<T> o = (server == null ? selectServer() : Observable.just(server)) .concatMap(new Func1<Server, Observable<T>>() { @Override // Called for each server being selected public Observable<T> call(Server server) { // 获取Server的统计值 context.setServer(server); final ServerStats stats = loadBalancerContext.getServerStats(server); // Called for each attempt and retry 服务调用 Observable<T> o = Observable .just(server) .concatMap(new Func1<Server, Observable<T>>() { @Override public Observable<T> call(final Server server) { context.incAttemptCount();//重试计数 loadBalancerContext.noteOpenConnection(stats);//链接统计 if (listenerInvoker != null) { try { listenerInvoker.onStartWithServer(context.toExecutionInfo()); } catch (AbortExecutionException e) { return Observable.error(e); } } //执行监控器,记录执行时间 final Stopwatch tracer = loadBalancerContext.getExecuteTracer().start(); //找到合适的server后,开始执行请求 //底层调用有结果后,做消息处理 return operation.call(server).doOnEach(new Observer<T>() { private T entity; @Override public void onCompleted() { recordStats(tracer, stats, entity, null); // 记录统计信息 } @Override public void onError(Throwable e) { recordStats(tracer, stats, null, e);//记录异常信息 logger.debug("Got error {} when executed on server {}", e, server); if (listenerInvoker != null) { listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo()); } } @Override public void onNext(T entity) { this.entity = entity;//返回结果值 if (listenerInvoker != null) { listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo()); } } private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) { tracer.stop();//结束计时 //标记请求结束,更新统计信息 loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler); } }); } }); //如果失败,根据重试策略触发重试逻辑 // 使用observable 做重试逻辑,根据predicate 做逻辑判断,这里做 if (maxRetrysSame > 0) o = o.retry(retryPolicy(maxRetrysSame, true)); return o; } }); // next请求处理,基于重试器操作 if (maxRetrysNext > 0 && server == null) o = o.retry(retryPolicy(maxRetrysNext, false)); return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() { @Override public Observable<T> call(Throwable e) { if (context.getAttemptCount() > 0) { if (maxRetrysNext > 0 && context.getServerAttemptCount() == (maxRetrysNext + 1)) { e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED, "Number of retries on next server exceeded max " + maxRetrysNext + " retries, while making a call for: " + context.getServer(), e); } else if (maxRetrysSame > 0 && context.getAttemptCount() == (maxRetrysSame + 1)) { e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_EXEEDED, "Number of retries exceeded max " + maxRetrysSame + " retries, while making a call for: " + context.getServer(), e); } } if (listenerInvoker != null) { listenerInvoker.onExecutionFailed(e, context.toFinalExecutionInfo()); } return Observable.error(e); } }); } 从一组ServerList 列表中挑选合适的Server /** * Compute the final URI from a partial URI in the request. The following steps are performed: * <ul> * <li> 如果host尚未指定,则从负载均衡器中选定 host/port * <li> 如果host 尚未指定并且尚未找到负载均衡器,则尝试从 虚拟地址中确定host/port * <li> 如果指定了HOST,并且URI的授权部分通过虚拟地址设置,并且存在负载均衡器,则通过负载就均衡器中确定host/port(指定的HOST将会被忽略) * <li> 如果host已指定,但是尚未指定负载均衡器和虚拟地址配置,则使用真实地址作为host * <li> if host is missing but none of the above applies, throws ClientException * </ul> * * @param original Original URI passed from caller */ public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException { String host = null; int port = -1; if (original != null) { host = original.getHost(); } if (original != null) { Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original); port = schemeAndPort.second(); } // Various Supported Cases // The loadbalancer to use and the instances it has is based on how it was registered // In each of these cases, the client might come in using Full Url or Partial URL ILoadBalancer lb = getLoadBalancer(); if (host == null) { // 提供部分URI,缺少HOST情况下 // well we have to just get the right instances from lb - or we fall back if (lb != null){ Server svc = lb.chooseServer(loadBalancerKey);// 使用负载均衡器选择Server if (svc == null){ throw new ClientException(ClientException.ErrorType.GENERAL, "Load balancer does not have available server for client: " + clientName); } //通过负载均衡器选择的结果中选择host host = svc.getHost(); if (host == null){ throw new ClientException(ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc); } logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original}); return svc; } else { // No Full URL - and we dont have a LoadBalancer registered to // obtain a server // if we have a vipAddress that came with the registration, we // can use that else we // bail out // 通过虚拟地址配置解析出host配置返回 if (vipAddresses != null && vipAddresses.contains(",")) { throw new ClientException( ClientException.ErrorType.GENERAL, "Method is invoked for client " + clientName + " with partial URI of (" + original + ") with no load balancer configured." + " Also, there are multiple vipAddresses and hence no vip address can be chosen" + " to complete this partial uri"); } else if (vipAddresses != null) { try { Pair<String,Integer> hostAndPort = deriveHostAndPortFromVipAddress(vipAddresses); host = hostAndPort.first(); port = hostAndPort.second(); } catch (URISyntaxException e) { throw new ClientException( ClientException.ErrorType.GENERAL, "Method is invoked for client " + clientName + " with partial URI of (" + original + ") with no load balancer configured. " + " Also, the configured/registered vipAddress is unparseable (to determine host and port)"); } } else { throw new ClientException( ClientException.ErrorType.GENERAL, this.clientName + " has no LoadBalancer registered and passed in a partial URL request (with no host:port)." + " Also has no vipAddress registered"); } } } else { // Full URL Case URL中指定了全地址,可能是虚拟地址或者是hostAndPort // This could either be a vipAddress or a hostAndPort or a real DNS // if vipAddress or hostAndPort, we just have to consult the loadbalancer // but if it does not return a server, we should just proceed anyways // and assume its a DNS // For restClients registered using a vipAddress AND executing a request // by passing in the full URL (including host and port), we should only // consult lb IFF the URL passed is registered as vipAddress in Discovery boolean shouldInterpretAsVip = false; if (lb != null) { shouldInterpretAsVip = isVipRecognized(original.getAuthority()); } if (shouldInterpretAsVip) { Server svc = lb.chooseServer(loadBalancerKey); if (svc != null){ host = svc.getHost(); if (host == null){ throw new ClientException(ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc); } logger.debug("using LB returned Server: {} for request: {}", svc, original); return svc; } else { // just fall back as real DNS logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port); } } else { // consult LB to obtain vipAddress backed instance given full URL //Full URL execute request - where url!=vipAddress logger.debug("Using full URL passed in by caller (not using load balancer): {}", original); } } // end of creating final URL if (host == null){ throw new ClientException(ClientException.ErrorType.GENERAL,"Request contains no HOST to talk to"); } // just verify that at this point we have a full URL return new Server(host, port); }

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

当创建statefulset资源后,k8s组件如何协作

本文分享自华为云社区《当创建StatefulSet后,k8s会发生什么?》,作者:可以交个朋友 。 一、StatefulSet介绍 StatefulSet 是用来管理有状态应用的工作负载对象,StatefulSet 管理基于相同容器规约的一组 Pod,使用持久标识符为工作负载Pod提供持久存储。和Deployment 类似,也属于副本控制器,但和Deployment不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。无论该类Pod的生命周期如何变化,每个 Pod 的标识符ID不会变化。另外还支持服务实例有序部署和扩展,并提供有状态应用程序所需的有序启动和终止策略。 StatefulSet工作负载之间使用Headless Service来定义Pod网路标识,生成可解析的DNS域名名称记录,用于同一StatefulSet工作负载彼此Pod之间的通信。 二、StatefulSet工作负载访问方式 与其他的工作负载(如Deployment)的对外访问方式相似,但有所区别。Deployent工作负载使用service(带有IP地址的服务)提供对外服务访问,但在一些特殊场景中,客户端访问不需要kubernetes中service实现的负载均衡功能,而是由客户端直接去发现/选择服务端的后端实例访问,就需要一种特殊的服务“Headless service”。这是一种没有访问入口(即service没有IP地址)的service。kube-proxy不会为这种类型的service(Headless service)创建iptables/ipvs转发规则。 apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx # 必须匹配 .spec.template.metadata.labels serviceName: "nginx" replicas: 3 # 默认值是 1 minReadySeconds: 10 # 默认值是 0 template: metadata: labels: app: nginx # 必须匹配 .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: registry.k8s.io/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "my-storage-class" resources: requests: storage: 1Gi 其中StatefulSet关联的pod与pvc、pv的关系如下图: 三、StatefulSet工作负载创建流程 用户使用通过kubectl客户端发起创建StatefulSet资源对象请求至api-server。 api-server对请求用户鉴权、准入控制操作,然后将该请求事件写入到etcd存储中。 考虑到StatefulSet-controller采用非阻塞式长连接watch机制实时获取StatefulSet资源对象信息,一旦集群中有StatefulSet变化(包括创建、更新、删除),则通过api-server获取etcd中相关StatefulSet资源对象。 api-server将StatefulSet资源返回给StatefulSet-controller的watch接口长连接。 StatefulSet-controller维护StatefulSet的生命周期状态,并通过StatefulSet的模板确定副本数量,根据副本数量依序(0、1....N-1)创建Pod副本。 api-server接收到StatefulSet-controller创建结果后,更新etcd存储中的StatefulSet状态信息和Pod创建事件。 四、StatefulSet-Controller工作原理 在StatefulSet中,Informer和Event Handler是两个重要的组件 informer:是一个Kubernetes API客户端,用于监视Kubernetes API中的资源对象的变化并更新到本地缓存中。当资源对象的状态发生变化时,Informer会触发Event Handler。 Event Handler:是一个回调函数,用于处理Informer发出的事件,Event Handler会根据事件的类型,执行相应的操作。 Manage Revision:用于标识StatefulSet的每个更新版本,通过Manage Revision,可以查看StatefulSet的更新历史,以便在需要时回滚到先前的版本。 Manger pods in order:管理StatefulSet中pod的启动顺序, Update in order:有序的更新Pod。查看 Pod 期望的状态是否符合要求,不满足则删除重建, Update Status:管理StatefulSet对象的更新状态。 replicas数组:存放可用Pod的ord值(Pod名称中ID标识符),(0 <= ord < Spec.replicas) condemned数组:存放需要删除pod的ord值(Pod名称中ID标识符),(ord >= Spec.replicas) 创建StatefulSet工作负载Pod实例 判断StatefulSet的管理策略: 若实例管理策略为并行策略,则遍历replicas数组,所有Pod同步创建,不区分启动先后顺序。 若实例管理策略为有序策略,遍历replicas数组,依序(0、1....N-1)创建Pod。需确保replicas数组中的前一个Pod处于runing或ready状态。方可创建下一个Pod。 最后检查Pod的信息是否与StatefulSet匹配,若不匹配则更新Pod的状态。如当前StatefulSet已关联Pod,但是Pod标签不匹配则释放Pod,重新新建Pod副本关联。 4.1 更新StatefulSet工作负载Pod实例 判断statefulset的更新策略: 如果更新策略为OnDelete,则不会自动触发更新行为,需要手动删除Pod,系统对其进行重建更新。 如果更新策略为RollingUpdate,则不再关注实例管理策略。都是按顺序进行处理且等待当前Pod删除成功后才继续小于上一个Pod顺序号的Pod。 最后检查Pod的信息是否与StatefulSet匹配,若不匹配则更新Pod的状态。如当前StatefulSet已关联Pod,但是Pod标签不匹配则释放Pod,重新新建Pod副本关联。 4.2 扩缩容StatefulSet工作负载Pod实例 扩容操作: 更新statefulset的状态信息,将新建Pod的信息写入到replicas队列和condemned队列中。 遍历replicas数组,确保replicas数组中的Pod处于runing或ready状态。发现faild状态的Pod删除重建,对未创建的Pod则直接创建。 最后检查Pod的信息是否与StatefulSet匹配,若不匹配则更新Pod的状态。如当前StatefulSet已关联Pod,但是Pod标签不匹配则释放Pod,重新新建Pod副本关联。 缩容操作: 更新statefulset的状态信息。 遍历condemned数组,确保replicas数组中的Pod处于runing或ready状态。发现faild状态的Pod删除重建,对未创建的Pod则直接创建。然后按Pod名称标识符由大至小逆序删除condemned数组中若干个Pod 最后检查Pod的信息是否与StatefulSet匹配,若不匹配则更新Pod的状态。如当前StatefulSet已关联Pod,但是Pod标签不匹配则释放Pod,重新新建Pod副本关联。 4.3 删除StatefulSet工作负载Pod实例 如下 processCondemned函数主要介绍删除StatefulSet工作负载Pod func (ssc *defaultStatefulSetControl) processCondemned(ctx context.Context, set *apps.StatefulSet, firstUnhealthyPod *v1.Pod, monotonic bool, condemned []*v1.Pod, i int) (bool, error) { logger := klog.FromContext(ctx) if isTerminating(condemned[i]) { if monotonic { logger.V(4).Info("StatefulSet is waiting for Pod to Terminate prior to scale down", "statefulSet", klog.KObj(set), "pod", klog.KObj(condemned[i])) return true, nil } return false, nil } if !isRunningAndReady(condemned[i]) && monotonic && condemned[i] != firstUnhealthyPod { logger.V(4).Info("StatefulSet is waiting for Pod to be Running and Ready prior to scale down", "statefulSet", klog.KObj(set), "pod", klog.KObj(firstUnhealthyPod)) return true, nil } if !isRunningAndAvailable(condemned[i], set.Spec.MinReadySeconds) && monotonic && condemned[i] != firstUnhealthyPod { logger.V(4).Info("StatefulSet is waiting for Pod to be Available prior to scale down", "statefulSet", klog.KObj(set), "pod", klog.KObj(firstUnhealthyPod)) return true, nil } logger.V(2).Info("Pod of StatefulSet is terminating for scale down", "statefulSet", klog.KObj(set), "pod", klog.KObj(condemned[i])) return true, ssc.podControl.DeleteStatefulPod(set, condemned[i]) } 该函数首先判断需要删除的 Pod 是否正在终止中,如果是,则根据是否为Pod管理策略进行不同的处理。如果是有序策略,则会阻塞等待终止的 Pod 完全消失。如果不是有序策略,则直接返回,不做任何操作。 如果需要删除的 Pod 不是第一个不健康的 Pod,且当前Pod管理策略是有序策略,则会阻塞等待其他不健康 Pod 变为 Running 和 Ready 状态或者变为 Available 状态。如果不是有序策略,则直接返回,不做任何操作。 最后,如果需要删除的 Pod 不处于终止中,且满足删除条件,则会执行删除操作,并返回 true。否则,直接返回 false。 点击关注,第一时间了解华为云新鲜技术~

资源下载

更多资源
Mario

Mario

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

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

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

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册