基于etcd实现大规模服务治理应用实战
Posted 架构师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于etcd实现大规模服务治理应用实战相关的知识,希望对你有一定的参考价值。
err !=
b := tx.Bucket([]
v := b.Get([]
fmt.Println(
tx.Commit()
*Cursor) search(key []byte, pgid pgid)
p, n := p != && (p.flags&(branchPageFlag|leafPageFlag)) ==
panic(fmt.
e := elemRefpage: p, node: n
e.isLeaf()
n !=
c.searchPage(key, p)
三、百度基于etcd打造大规模服务治理建设思路
3.1 具体的挑战
天路是百度小程序团队开发打造的面向大型业务服务治理需求的一套解决方案,其目标之一就是打造成百度的服务治理规范样板。天路由注册中心、可视化管理平台、SDK框架、统一网关、tianlu-mesher五个部分组成,目前已经接入了150+产品线,实例数已达数十万级别。随着接入平台的团队数增多、以及服务实例的快速增长,大量团队间如何轻松的协作以及实现大规模服务治理平台的高可用、高性能一直是天路持续面临的挑战。3.2 整体架构建设思路与方案
天路作为一个服务治理平台,核心理念是为所有的服务提供便捷的调用,统一的服务监控管理,简化服务的开发和维护成本。我们从以下不同的方面思考基于etcd打造大规模服务治理平台:高可用、高性能、高扩展、易用性。考虑到etcd跨机房调用的高网络延时,我们采用单机房部署,同时我们也实现了主备集群切换的方案,解决在单机房实例全部宕机的情况下,etcd集群不可用的问题。
为了降低平台对etcd的强依赖,我们做了etcd降级使用缓存的方案。在监控到etcd集群的性能无法支持当前平台的时候,使用缓存存储实例数据,能够让运维人员在恢复etcd之前,系统不受影响正常运行;etcd恢复之后,切换回etcd集群继续工作。
etcd集群的kv查询性能很高,qps能达到10000以上。为了解决在极端并发下的性能问题,注册中心采用多级缓存提升查询效率,降低对etcd的访问压力。
服务间调用使用直连的方式,请求不需要经过注册中心进行转发,调用基本没有时间损耗。
考虑到将来服务实例数达到百万级别,我们需要考虑架构的高扩展性。
用户通过可视化的管理平台可以查看已注册的服务,也可通过管理平台实时更新服务治理策略的配置,实时调整服务治理策略。
将调用日志接入trace平台,用户可通过traceId在trace平台查到整个调用链的记录,便于出错时进行快速的问题定位。
多语言 SDK,支持多种rpc技术,包括百度自研的rpc技术brpc(https://github.com/baidu/Jprotobuf-rpc-socket【java/go sdk】)和http jsonrpc协议等
3.3 关键的指标与运维目标
此外针对更好的实施服务治理平台的运维,还需要以下的关键考核指标与运维要求。关键指标:运维目标:故障发现早· 配置监控告警,包括注册中心实例健康、etcd平响、内存和cpu监控。故障处理快
· 自动处理:通过noah的回调机制,自动处理一些故障,提高处理速度。
四、总结
服务治理目前越来越被企业建设所重视,特别现在云原生,微服务等各种技术被更多的企业所应用,但是要真正在应用好,融合好,还是有非常多的挑战,除了一套成熟的服务治理产品外,包括团队整体对服务治理的认知,技术经验的深淀,遵循服务化的设计能力水平的能力等,都会影响到最终的实施效果。本文也仅在服务治理产品选型上给大家一些启发,希望在服务治理的道路上帮大家走得更好更稳。如喜欢本文,请点击右上角,把文章分享到朋友圈
如有想了解学习的技术点,请留言给若飞安排分享
·END·
相关阅读:
微服务等于Spring Cloud?了解微服务架构和框架
小团队真的适合引入SpringCloud微服务吗?
DDD兴起的原因以及与微服务的关系
微服务之间的最佳调用方式
微服务架构设计总结实践
基于 Kubernetes 的微服务项目设计与实现
微服务架构-设计总结
为什么微服务一定要有网关?
主流微服务全链路监控系统之战
作者:百度小程序团队
来源:百度Geek说
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!
我们都是架构师!
关注架构师(JiaGouX),添加“星标”
获取每天技术干货,一起成为牛逼架构师
技术群请加若飞:1321113940 进架构师群
投稿、合作、版权等邮箱:admin@137x.com
基于ETCD实现分布式锁&实战:控制多个应用仅一台执行任务
我们知道,分布式锁有好几种方案:基于Redis、基于数据库如MySQL、基于注册中心如Zookeeper等;而 K8S 体系中基于 Go 语言编写的的 ETCD 则对于分布式锁有着更强大的支持。
ETCD 有一个租约机制,客户端跟 ETCD 服务端订立一个“租约”后,需要在租约到期之前进行续约,否则会在到期后被自动解除租约,而租约可以挂载多个 key-value,当租约过期时,挂载在上面的 key-value 也会跟着被删除。既有类似 Redis / Zookeeper 的 key-value 机制能够实现分布式锁,同时租约机制,又能实现某个客户端宕机后,服务端自动检测锁超时并自动释放锁。
储备阅读:
基于 Etcd 的分布式锁实现原理及方案
基于 etcd 实现分布式锁
还有比Redis更骚的分布式锁的实现方式吗?有,etcd!
这里,使用 ETCD 封装好的 lockClient 来实现分布式锁,例子如下:
maven依赖:
io.etcd:jetcd-core:jar:0.5.3:compile
io.grpc:grpc-core:jar:1.31.1:compile
# 有用到dubbo的话直接引入一个依赖即可:
org.apache.dubbo:dubbo-remoting-etcd3:jar:2.7.11:compile
DistributedLock.java
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.Lease;
import io.etcd.jetcd.Lock;
import io.etcd.jetcd.lease.LeaseKeepAliveResponse;
import io.grpc.stub.StreamObserver;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
public class DistributedLock
private static DistributedLock lockProvider = null;
private static final Object MUTEX = new Object();
private Client client;
private Lock lockClient;
private Lease leaseClient;
private DistributedLock()
super();
this.client = Client.builder().endpoints("http://localhost:2379").build();
this.lockClient = client.getLockClient();
this.leaseClient = client.getLeaseClient();
public static DistributedLock getInstance()
synchronized (MUTEX)
if (null == lockProvider)
lockProvider = new DistributedLock();
return lockProvider;
public LockResult getLock(String lockName, long ttl)
LockResult lockResult = new LockResult();
lockResult.setIsLockSuccess(false);
long leaseId = 0;
try
leaseId = leaseClient.grant(ttl).get().getID();
lockResult.setLeaseId(leaseId);
catch (InterruptedException | ExecutionException e)
return lockResult;
StreamObserver<LeaseKeepAliveResponse> observer = new StreamObserver<LeaseKeepAliveResponse>()
@Override
public void onNext(LeaseKeepAliveResponse arg0)
@Override
public void onError(Throwable arg0)
@Override
public void onCompleted()
;
leaseClient.keepAlive(leaseId, observer);
try
lockClient.lock(ByteSequence.from(lockName, StandardCharsets.UTF_8), leaseId).get();
catch (InterruptedException | ExecutionException e1)
leaseClient.revoke(leaseId);
return lockResult;
lockResult.setIsLockSuccess(true);
return lockResult;
public static class LockResult
租约这块,可以参考基于 Etcd 的分布式锁实现原理及方案 的路线一那样,自定义一个ScheduledExecutorService
定时任务服务,定期对租约进行续期。而 ETCD 的 API 封装也支持通过观察器实现自动续约:Lease.keepAlive(long leaseId, StreamObserver<LeaseKeepAliveResponse> observer);
创建好租约并设置了自动续约后,那后面就是把 key-value 绑定到租约上,上面例子中lockClient.lock()
做的事情,其实就是多个客户端竞争lockName
这个 key,谁竞争到了这个 key 则将该 key 绑定到它的租约上,也就实现了由该线程获得了锁。
具体竞争机制参见基于 Etcd 的分布式锁实现原理及方案,基本流程就是所有要竞争锁的客户端,都生成一个 /lockName/revisionId
的 key-value,然后把所有 /locakName
前缀的键值对都取回来,然后看自己拿到的这个revisionId
是不是里边最小的,最小的说明就是最早获得了这个 key prefix 的线程。而 前面提到,ETCD 服务端会自动解除租约并删除上面的 key-value,所以当持有锁的客户端宕机被解除租约后,它的/lockName/revisionId
就会从键值对列表中自动删除,从而使得新的最小的revisionId
的那个线程获得锁。
*补充上述代码的内部类:LockResult *
public static class LockResult
private boolean isLockSuccess;
private long leaseId;
LockResult()
super();
public void setIsLockSuccess(boolean isLockSuccess)
this.isLockSuccess = isLockSuccess;
public void setLeaseId(long leaseId)
this.leaseId = leaseId;
public boolean getIsLockSuccess()
return this.isLockSuccess;
public long getLeaseId()
return this.leaseId;
@Override
public String toString()
return new StringBuilder("leaseId=").append(leaseId)
.append(",isLockSuccess=").append(isLockSuccess).toString();
分布式锁具体应用示例,在我的场景中,有一个 Spring ScheduleTask,但这个 Task 在多台应用都会启用,所以有一个问题,就是多台应用同时会跑 Task,而我们希望只有一台应用在执行这批 Task,所以用到分布式锁来控制只有单台应用在执行。具体实现很简单,基于上面的分布式锁,那我们可以让这些应用各自竞争这把锁,然后竞争到锁的应用获得 Task 的执行权。具体实现如下:
MyTask.java
public class MyTask
private static final Executor singleThreadExecutor = Executors.newSingleThreadExecutor();
private static Boolean lockTrying = false;
private static final Object MUTEX = new Object();
private static DistributedLock.LockResult lockResultCache = null;
private static final String LOCK_PREFIX = "/lock/schedule";
private static final long LOCK_TTL_OF_SECONDS = 10L;
public Result runTask()
if (lockResultCache != null)
return doSomething();
synchronized (MUTEX)
if (lockTrying || lockResultCache != null)
return null;
lockTrying = true;
if (lockResultCache != null)
return pjp.proceed();
singleThreadExecutor.execute(() ->
DistributedLock.LockResult lockResult = DistributedLock.getInstance().getLock(LOCK_PREFIX, LOCK_TTL_OF_SECONDS);
if (lockResult.getIsLockSuccess())
lockResultCache = lockResult;
lockTrying = false;
);
return null;
在我的场景中,还带来另一个问题,就是不仅多台应用能执行Task,并且每台应用都会有多个线程去执行 Task,而 ETCD API 这个 Lock,它是阻塞式获取锁的,会把线程夯住,直至拿到锁,或者出现异常中断。而实际上,对于一台应用来说,只需要一个线程去创建租约并阻塞拿锁就够了,只要这个线程拿到了锁,那就可以通知这台应用(JVM)上的所有线程,可以执行 Task 了,这就涉及到线程通信的问题,而线程通信,最简单的办法之一,就是共享内存。而 JVM 里边,静态变量是线程共享的,所以本例,使用了静态变量来存储获得的这把锁,只要拿锁的那个线程获得了锁,就给这个静态变量lockResult
赋值,那么 JVM 里边的其他线程也就知道这台应用获得了锁,就都可以执行 Task 了。同时,针对只要一个线程去阻塞拿锁的问题,加一个 MUTEX 同步锁来简单控制。
另外,阻塞拿锁这事相对独立,而执行 Task 的线程可能有其他用途,我们不希望拿它来阻塞拿锁,所以上述例子开了个新线程专门负责阻塞拿锁。
以上是关于基于etcd实现大规模服务治理应用实战的主要内容,如果未能解决你的问题,请参考以下文章
基于ETCD实现分布式锁&实战:控制多个应用仅一台执行任务
基于ETCD实现分布式锁&实战:控制多个应用仅一台执行任务
基于ETCD实现分布式锁&实战:控制多个应用仅一台执行任务
基于Dubbo的分布式系统架构实战视频课程
基于 Dubbo3.0 的服务治理的实践
基于Dubbo的分布式系统架构实战