Apollo 源码解析 —— Portal 创建 App

微服务配置中心 Apollo解析——Portal 创建 Cluster 1. 概述 2. Cluster 3. Portal 侧 4. Admin Service 侧 1. 概述 老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 。 本文分享Portal 创建 Cluster的流程,整个过程涉及 Portal、Admin Service ,如下图所示: 流程 下面,我们先来看看 Cluster 的实体结构 老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。 2. C.. 阅读详情

点击上方“芋道源码”,选择“设为星标”

做积极的人,而不是积极废人!

源码精品专栏

 

摘要: 原创出处 http://www.iocoder.cn/Apollo/portal-create-app/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 1. 概述

  • 2. App

  • 3. Portal 侧

  • 4. Admin Service 侧

  • 666. 彩蛋


阅读源码最好的方式,是使用 IDEA 进行调试 Apollo 源码,不然会一脸懵逼。

胖友可以点击「芋道源码」扫码关注,回复 git018 关键字
获得艿艿添加了中文注释的 Apollo 源码地址。

阅读源码很孤单,加入源码交流群,一起坚持!

1. 概述

老艿艿:本系列假定胖友已经阅读过 《Apollo 官方 wiki 文档》 。

本文分享 Portal 创建 App 的流程,整个过程涉及 Portal、Admin Service ,如下图所示:

流程

下面,我们先来看看 App 的实体结构

老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。

2. App

在 apollo-common 项目中, com.ctrip.framework.apollo.common.entity.App ,继承 BaseEntity 抽象类,应用信息实体。代码如下:

@Entity
@Table(name = "App")
@SQLDelete(sql = "Update App set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class App extends BaseEntity {

    /**
     * App 名
     */
    @Column(name = "Name", nullable = false)
    private String name;
    /**
     * App 编号
     */
    @Column(name = "AppId", nullable = false)
    private String appId;
    /**
     * 部门编号
     */
    @Column(name = "OrgId", nullable = false)
    private String orgId;
    /**
     * 部门名
     *
     * 冗余字段
     */
    @Column(name = "OrgName", nullable = false)
    private String orgName;
    /**
     * 拥有人名
     *
     * 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段
     */
    @Column(name = "OwnerName", nullable = false)
    private String ownerName;
    /**
     * 拥有人邮箱
     *
     * 冗余字段
     */
    @Column(name = "OwnerEmail", nullable = false)
    private String ownerEmail;
}
  • ORM 选用 Hibernate 框架。

  • @SQLDelete(...) + @Where(...) 注解,配合 BaseEntity.extends 字段,实现 App 的逻辑删除。

  • 字段比较简单,胖友看下注释。

2.1 BaseEntity

com.ctrip.framework.apollo.common.entity.BaseEntity ,基础实体抽象类。代码如下:

@MappedSuperclass
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {

    /**
     * 编号
     */
    @Id
    @GeneratedValue
    @Column(name = "Id")
    private long id;
    /**
     * 是否删除
     */
    @Column(name = "IsDeleted", columnDefinition = "Bit default '0'")
    protected boolean isDeleted = false;
    /**
     * 数据创建人
     *
     * 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段
     */
    @Column(name = "DataChange_CreatedBy", nullable = false)
    private String dataChangeCreatedBy;
    /**
     * 数据创建时间
     */
    @Column(name = "DataChange_CreatedTime", nullable = false)
    private Date dataChangeCreatedTime;
    /**
     * 数据最后更新人
     *
     * 例如在 Portal 系统中,使用系统的管理员账号,即 UserPO.username 字段
     */
    @Column(name = "DataChange_LastModifiedBy")
    private String dataChangeLastModifiedBy;
    /**
     * 数据最后更新时间
     */
    @Column(name = "DataChange_LastTime")
    private Date dataChangeLastModifiedTime;

    /**
     * 保存前置方法
     */
    @PrePersist
    protected void prePersist() {
        if (this.dataChangeCreatedTime == null) dataChangeCreatedTime = new Date();
        if (this.dataChangeLastModifiedTime == null) dataChangeLastModifiedTime = new Date();
    }

    /**
     * 更新前置方法
     */
    @PreUpdate
    protected void preUpdate() {
        this.dataChangeLastModifiedTime = new Date();
    }

    /**
     * 删除前置方法
     */
    @PreRemove
    protected void preRemove() {
        this.dataChangeLastModifiedTime = new Date();
    }
    
    // ... 省略 setting / getting 方法
}  
  • @MappedSuperclass 注解,见 《Hibernate 中 @MappedSuperclass 注解的使用说明》 文章。

  • @Inheritance(...) 注解,见 《Hibernate(11)映射继承关系二之每个类对应一张表(@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)》 文章。

  • id 字段,编号,Long 型,全局自增。

  • isDeleted 字段,是否删除,用于逻辑删除的功能。

  • dataChangeCreatedBy 和 dataChangeCreatedTime 字段,实现数据的创建人和时间的记录,方便追踪。

  • dataChangeLastModifiedBy 和 dataChangeLastModifiedTime 字段,实现数据的更新人和时间的记录,方便追踪。

  • @PrePersist、@PreUpdate、@PreRemove 注解,CRD 操作前,设置对应的时间字段。

  • 在 Apollo 中,所有实体都会继承 BaseEntity ,实现公用字段的统一定义。这种设计值得借鉴,特别是创建时间和更新时间这两个字段,特别适合线上追踪问题和数据同步。

2.2 为什么需要同步

在文初的流程图中,我们看到 App 创建时,在 Portal Service 存储完成后,会异步同步到 Admin Service 中,这是为什么呢?

在 Apollo 的架构中,一个环境( Env ) 对应一套 Admin Service 和 Config Service 。
而 Portal Service 会管理所有环境( Env ) 。因此,每次创建 App 后,需要进行同步。

或者说,App 在 Portal Service 中,表示需要管理的 App 。而在 Admin Service 和 Config Service 中,表示存在的 App 。

3. Portal 侧

3.1 AppController

在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.controller.AppController ,提供 App 的 API 。

在创建项目的界面中,点击【提交】按钮,调用创建 App 的 API 。

创建项目

代码如下:

  1: @RestController
  2: @RequestMapping("/apps")
  3: public class AppController {
  4: 
  5:     @Autowired
  6:     private UserInfoHolder userInfoHolder;
  7:     @Autowired
  8:     private AppService appService;
  9:     /**
 10:      * Spring 事件发布者
 11:      */
 12:     @Autowired
 13:     private ApplicationEventPublisher publisher;
 14:     @Autowired
 15:     private RolePermissionService rolePermissionService;
 16: 
 17:     /**
 18:      * 创建 App
 19:      *
 20:      * @param appModel AppModel 对象
 21:      * @return App 对象
 22:      */
 23:     @RequestMapping(value = "", method = RequestMethod.POST)
 24:     public App create(@RequestBody AppModel appModel) {
 25:         // 将 AppModel 转换成 App 对象
 26:         App app = transformToApp(appModel);
 27:         // 保存 App 对象到数据库
 28:         App createdApp = appService.createAppInLocal(app);
 29:         // 发布 AppCreationEvent 创建事件
 30:         publisher.publishEvent(new AppCreationEvent(createdApp));
 31:         // 授予 App 管理员的角色
 32:         Set<String> admins = appModel.getAdmins();
 33:         if (!CollectionUtils.isEmpty(admins)) {
 34:             rolePermissionService.assignRoleToUsers(RoleUtils.buildAppMasterRoleName(createdApp.getAppId()),
 35:                     admins, userInfoHolder.getUser().getUserId());
 36:         }
 37:         // 返回 App 对象
 38:         return createdApp;
 39:     }
 40:     
 41:     // ... 省略其他接口和属性
 42: }    
  • POST apps 接口,Request Body 传递 JSON 对象。

  • com.ctrip.framework.apollo.portal.entity.model.AppModel ,App Model 。在 com.ctrip.framework.apollo.portal.entity.model 包下,负责接收来自 Portal 界面的复杂请求对象。例如,AppModel 一方面带有创建 App 对象需要的属性,另外也带有需要授权管理员的编号集合 admins ,即存在跨模块的情况。

  • 第 26 行:调用 #transformToApp(AppModel) 方法,将 AppModel 转换成 App 对象。???? 转换方法很简单,点击方法,直接查看。

  • 第 28 行:调用 AppService#createAppInLocal(App) 方法,保存 App 对象到 Portal DB 数据库。在 「3.2 AppService」 中,详细解析。

  • 第 30 行:调用 ApplicationEventPublisher#publishEvent(AppCreationEvent) 方法,发布 com.ctrip.framework.apollo.portal.listener.AppCreationEvent 事件。

  • 第 31 至 36 行:授予 App 管理员的角色。详细解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。

  • 第 38 行:返回创建的 App 对象。

3.2 AppService

在 apollo-portal 项目中,com.ctrip.framework.apollo.portal.service.AppService ,提供 App 的 Service 逻辑。

#createAppInLocal(App) 方法,保存 App 对象到 Portal DB 数据库。代码如下:

  1: @Autowired
  2: private UserInfoHolder userInfoHolder;
  3: @Autowired
  4: private AppRepository appRepository;
  5: @Autowired
  6: private AppNamespaceService appNamespaceService;
  7: @Autowired
  8: private RoleInitializationService roleInitializationService;
  9: @Autowired
 10: private UserService userService;
 11:     
 12: @Transactional
 13: public App createAppInLocal(App app) {
 14:     String appId = app.getAppId();
 15:     // 判断 `appId` 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
 16:     App managedApp = appRepository.findByAppId(appId);
 17:     if (managedApp != null) {
 18:         throw new BadRequestException(String.format("App already exists. AppId = %s", appId));
 19:     }
 20:     // 获得 UserInfo 对象。若不存在,抛出 BadRequestException 异常
 21:     UserInfo owner = userService.findByUserId(app.getOwnerName());
 22:     if (owner == null) {
 23:         throw new BadRequestException("Application's owner not exist.");
 24:     }
 25:     app.setOwnerEmail(owner.getEmail()); // Email
 26:     // 设置 App 的创建和修改人
 27:     String operator = userInfoHolder.getUser().getUserId();
 28:     app.setDataChangeCreatedBy(operator);
 29:     app.setDataChangeLastModifiedBy(operator);
 30:     // 保存 App 对象到数据库
 31:     App createdApp = appRepository.save(app);
 32:     // 创建 App 的默认命名空间 "application"
 33:     appNamespaceService.createDefaultAppNamespace(appId);
 34:     // 初始化 App 角色
 35:     roleInitializationService.initAppRoles(createdApp);
 36:     // 【TODO 6001】Tracer 日志
 37:     Tracer.logEvent(TracerEventType.CREATE_APP, appId);
 38:     return createdApp;
 39: }
  • 第 15 至 19 行:调用 AppRepository#findByAppId(appId) 方法,判断 appId 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。

  • 第 20 至 25 行:调用 UserService#findByUserId(userId) 方法,获得 com.ctrip.framework.apollo.portal.entity.bo.UserInfo 对象。com.ctrip.framework.apollo.portal.entity.bo 包下,负责返回 Service 的业务对象。例如,UserInfo 只包含 com.ctrip.framework.apollo.portal.entity.po.UserPO 的部分属性:userId、username、email 。

  • 第 27 至 29 行:调用 UserInfoHolder#getUser()#getUserId() 方法,获得当前登录用户,并设置为 App 的创建和修改人。关于 UserInfoHolder ,后续文章,详细分享。

  • 第 31 行:调用 AppRepository#save(App) 方法,保存 App 对象到数据库中。

  • 第 33 行:调用 AppNameSpaceService#createDefaultAppNamespace(appId) 方法,创建 App 的默认 Namespace (命名空间) "application" 。对于每个 App ,都会有一个默认 Namespace 。具体的代码实现,我们在 《Apollo 源码解析 —— Portal 创建 Namespace》

  • 第 35 行:初始化 App 角色。详解解析,见 《Apollo 源码解析 —— Portal 认证与授权(二)之授权》 。

  • 第 37 行:【TODO 6001】Tracer 日志

3.3 AppRepository

在 apollo-portal 项目中,com.ctrip.framework.apollo.common.entity.App.AppRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 App 的数据访问,即 DAO 。

代码如下:

public interface AppRepository extends PagingAndSortingRepository<App, Long> {

  App findByAppId(String appId);

  List<App> findByOwnerName(String ownerName, Pageable page);

  List<App> findByAppIdIn(Set<String> appIds);

}

基于 Spring Data JPA 框架,使用 Hibernate 实现。详细参见 《Spring Data JPA、Hibernate、JPA 三者之间的关系》 文章。

???? 不熟悉 Spring Data JPA 的胖友,可以看下 《Spring Data JPA 介绍和使用》 文章。

3.4 AppCreationEvent

com.ctrip.framework.apollo.portal.listener.AppCreationEvent ,实现 org.springframework.context.ApplicationEvent 抽象类,App 创建事件。

代码如下:

public class AppCreationEvent extends ApplicationEvent {

  public AppCreationEvent(Object source) {
    super(source);
  }

  public App getApp() {
    Preconditions.checkState(source != null);
    return (App) this.source;
  }

}
  • 构造方法,将 App 对象作为方法参数传入。

  • #getApp() 方法,获得事件对应的 App 对象。

3.4.1 CreationListener

com.ctrip.framework.apollo.portal.listener.CreationListener ,对象创建监听器,目前监听 AppCreationEvent 和 AppNamespaceCreationEvent 事件。

我们以  AppCreationEvent 举例子,代码如下:

  1: @Autowired
  2: private PortalSettings portalSettings;
  3: @Autowired
  4: private AdminServiceAPI.AppAPI appAPI;
  5: 
  6: @EventListener
  7: public void onAppCreationEvent(AppCreationEvent event) {
  8:     // 将 App 转成 AppDTO 对象
  9:     AppDTO appDTO = BeanUtils.transfrom(AppDTO.class, event.getApp());
 10:     // 获得有效的 Env 数组
 11:     List<Env> envs = portalSettings.getActiveEnvs();
 12:     // 循环 Env 数组,调用对应的 Admin Service 的 API ,创建 App 对象。
 13:     for (Env env : envs) {
 14:         try {
 15:             appAPI.createApp(env, appDTO);
 16:         } catch (Throwable e) {
 17:             logger.error("Create app failed. appId = {}, env = {})", appDTO.getAppId(), env, e);
 18:             Tracer.logError(String.format("Create app failed. appId = %s, env = %s", appDTO.getAppId(), env), e);
 19:         }
 20:     }
 21: }
  • @EventListener 注解 + 方法参数,表示 #onAppCreationEvent(...) 方法,监听 AppCreationEvent 事件。不了解的胖友,可以看下 《Spring 4.2框架中注释驱动的事件监听器详解》 文章。

  • 第 9 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 App 转换成 com.ctrip.framework.apollo.common.dto.AppDTO 对象。com.ctrip.framework.apollo.common.dto 包下,提供 Controller 和 Service 层的数据传输。???? 笔者思考了下,Apollo 中,Model 和 DTO 对象很类似,差异点在 Model 更侧重 UI 界面提交“复杂”业务请求。另外 Apollo 中,还有 VO 对象,侧重 UI 界面返回复杂业务响应。整理如下图:

    • 老艿艿认为,PO 对象,可以考虑不暴露给 Controller 层,只在 Service 和 Repository 之间传递和返回。

    • 和彩笔老徐交流了下,实际项目可以简化,使用 VO + DTO + PO 。

  • 第 11 行:调用 PortalSettings#getActiveEnvs()  方法,获得有效的 Env 数组,例如 PROD UAT 等。后续文章,详细分享该方法。

  • 第 12 至 20 行:循环 Env 数组,调用 AppAPI#createApp(Env, AppDTO) 方法,调用对应的 Admin Service 的 API ,创建 App 对象,从而同步 App 到 Config DB。

3.5 AdminServiceAPI

com.ctrip.framework.apollo.portal.api.AdminServiceAPI ,Admin Service API 集合,包含 Admin Service 所有模块 API 的调用封装。简化代码如下:

代码

3.5.1 API

com.ctrip.framework.apollo.portal.api.API ,API 抽象类。代码如下:

public abstract class API {

  @Autowired
  protected RetryableRestTemplate restTemplate;

}
  • 提供统一的 restTemplate 的属性注入。对于 RetryableRestTemplate 的源码实现,我们放到后续文章分享。

3.5.2 AppAPI

com.ctrip.framework.apollo.portal.api.AdminServiceAPI.AppAPI ,实现 API 抽象类,封装对 Admin Service 的 App 模块的 API 调用。代码如下:

@Service
public static class AppAPI extends API {

    public AppDTO loadApp(Env env, String appId) {
        return restTemplate.get(env, "apps/{appId}", AppDTO.class, appId);
    }

    public AppDTO createApp(Env env, AppDTO app) {
        return restTemplate.post(env, "apps", app, AppDTO.class);
    }

    public void updateApp(Env env, AppDTO app) {
        restTemplate.put(env, "apps/{appId}", app, app.getAppId());
    }

}
  • 使用 restTemplate ,调用对应的 API 接口。

4. Admin Service 侧

4.1 AppController

在 apollo-adminservice 项目中, com.ctrip.framework.apollo.adminservice.controller.AppController ,提供 App 的 API 。

#create(AppDTO) 方法,创建 App 。代码如下:

  1: @RestController
  2: public class AppController {
  3: 
  4:     @Autowired
  5:     private AppService appService;
  6:     @Autowired
  7:     private AdminService adminService;
  8: 
  9:     /**
 10:      * 创建 App
 11:      *
 12:      * @param dto AppDTO 对象
 13:      * @return App 对象
 14:      */
 15:     @RequestMapping(path = "/apps", method = RequestMethod.POST)
 16:     public AppDTO create(@RequestBody AppDTO dto) {
 17:         // 校验 appId 格式。若不合法,抛出 BadRequestException 异常
 18:         if (!InputValidator.isValidClusterNamespace(dto.getAppId())) {
 19:             throw new BadRequestException(String.format("AppId格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
 20:         }
 21:         // 将 AppDTO 转换成 App 对象
 22:         App entity = BeanUtils.transfrom(App.class, dto);
 23:         // 判断 `appId` 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。
 24:         App managedEntity = appService.findOne(entity.getAppId());
 25:         if (managedEntity != null) {
 26:             throw new BadRequestException("app already exist.");
 27:         }
 28:         // 保存 App 对象到数据库
 29:         entity = adminService.createNewApp(entity);
 30:         // 将保存的 App 对象,转换成 AppDTO 返回
 31:         dto = BeanUtils.transfrom(AppDTO.class, entity);
 32:         return dto;
 33:     }
 34:     
 35:     // ... 省略其他接口和属性
 36: }   
  • POST apps 接口,Request Body 传递 JSON 对象。

  • 第 17 至 20 行:调用 InputValidator#isValidClusterNamespace(appId) 方法,校验 appId 是否满足 "[0-9a-zA-Z_.-]+" 格式。若不合法,抛出 BadRequestException 异常。

  • 第 22 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将 AppDTO 转换成 App对象。

  • 第 24 至 27 行:调用 AppService#findOne(appId) 方法,判断 appId 是否已经存在对应的 App 对象。若已经存在,抛出 BadRequestException 异常。

  • 第 29 行:调用 AdminService#createNewApp(App) 方法,保存 App 对象到数据库。

  • 第 30 至 32 行:调用 BeanUtils#transfrom(Class<T> clazz, Object src) 方法,将保存的 App 对象,转换成 AppDTO 返回。

4.2 AdminService

com.ctrip.framework.apollo.biz.service.AdminService ,???? 无法定义是什么模块的 Service ,目前仅有 #createNewApp(App) 方法,代码如下:

  1: @Service
  2: public class AdminService {
  3: 
  4:     @Autowired
  5:     private AppService appService;
  6:     @Autowired
  7:     private AppNamespaceService appNamespaceService;
  8:     @Autowired
  9:     private ClusterService clusterService;
 10:     @Autowired
 11:     private NamespaceService namespaceService;
 12: 
 13:     @Transactional
 14:     public App createNewApp(App app) {
 15:         // 保存 App 对象到数据库
 16:         String createBy = app.getDataChangeCreatedBy();
 17:         App createdApp = appService.save(app);
 18:         String appId = createdApp.getAppId();
 19:         // 创建 App 的默认命名空间 "application"
 20:         appNamespaceService.createDefaultAppNamespace(appId, createBy);
 21:         // 创建 App 的默认集群 "default"
 22:         clusterService.createDefaultCluster(appId, createBy);
 23:         // 创建 Cluster 的默认命名空间
 24:         namespaceService.instanceOfAppNamespaces(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, createBy);
 25:         return app;
 26:     }
 27: 
 28: }
  • 第 15 至 18 行:调用 AppService#save(App) 方法,保存 App 对象到数据库中。

  • 第 20 行:调用 AppNamespaceService#createDefaultAppNamespace(appId, createBy) 方法,创建 App 的默认 Namespace (命名空间) "application" 。具体的代码实现,我们在 《Apollo 源码解析 —— Portal 创建 Namespace》 详细解析。

  • ========== 如下部分,是 Admin Service 独有 ==========

  • App 下有哪些 Cluster ,在 Portal 中是不进行保存,通过 Admin Service API 读取获得。

  • 【AppNamespace】第 22 行:调用 ClusterService#createDefaultCluster(appId, createBy) 方法,创建 App 的默认 Cluster "default" 。后续文章,详细分享。

  • 【Namespace】第 24 行:调用 NamespaceService#instanceOfAppNamespaces(appId, createBy) 方法,创建 Cluster 的默认命名空间。

4.3 AppService

在 apollo-biz 项目中,com.ctrip.framework.apollo.biz.service.AppService ,提供 App 的 Service 逻辑给 Admin Service 和 Config Service 。

#save(App) 方法,保存 App 对象到数据库中。代码如下:

  1: @Autowired
  2: private AppRepository appRepository;
  3: @Autowired
  4: private AuditService auditService;
  5: 
  6: @Transactional
  7: public App save(App entity) {
  8:     // 判断是否已经存在。若是,抛出 ServiceException 异常。
  9:     if (!isAppIdUnique(entity.getAppId())) {
 10:         throw new ServiceException("appId not unique");
 11:     }
 12:     // 保护代码,避免 App 对象中,已经有 id 属性。
 13:     entity.setId(0); // protection
 14:     App app = appRepository.save(entity);
 15:     // 记录 Audit 到数据库中
 16:     auditService.audit(App.class.getSimpleName(), app.getId(), Audit.OP.INSERT, app.getDataChangeCreatedBy());
 17:     return app;
 18: }
  • 第 8 至 11 行:调用 #isAppIdUnique(appId) 方法,判断是否已经存在。若是,抛出 ServiceException 异常。代码如下:

    public boolean isAppIdUnique(String appId) {
        Objects.requireNonNull(appId, "AppId must not be null");
        return Objects.isNull(appRepository.findByAppId(appId));
    }
    
  • 第 13 行:置“空” App 对象,防御性编程,避免 App 对象中,已经有 id 属性。

  • 第 14 行:调用 AppRepository#save(App) 方法,保存 App 对象到数据库中。

  • 第 16 行:记录 Audit 到数据库中。

4.4 AppRepository

com.ctrip.framework.apollo.biz.repository.AppRepository ,继承 org.springframework.data.repository.PagingAndSortingRepository 接口,提供 App 的数据访问 给 Admin Service 和 Config Service 。代码如下:

public interface AppRepository extends PagingAndSortingRepository<App, Long> {

  @Query("SELECT a from App a WHERE a.name LIKE %:name%")
  List<App> findByName(@Param("name") String name);

  App findByAppId(String appId);

}

666. 彩蛋

我们知道,但凡涉及跨系统的同步,无可避免会有事务的问题,对于 App 创建也会碰到这样的问题,例如:

  1. Portal 在同步 App 到 Admin Service 时,发生网络异常,同步失败。那么此时会出现该 App 存在于 Portal ,却不存在于 Admin Service 中。

  2. 新增了一套环境( Env ) ,也会导致 Portal 和 Admin Service 不一致的情况。

那么 Apollo 是怎么解决这个问题的呢????? 感兴趣的胖友,可以先自己翻翻源码。嘿嘿。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

Understanding Lockup Cells 工具会分析扫描链和EDT逻辑之间的控制时序元素的时钟的时序关系,当必须要同步时钟并保持数据完整性时插入边沿触发寄存器(lockup cells)。可以使用report_edt_lockup_cells命令来展示工具已经插入的lockup cells的详细报告。 阅读详情

相关推荐

从报错到定位:Memtest86 DQ位算法解析与实践指南

本文深入解析了Memtest86内存测试工具报错信息的解读方法,重点介绍了通过地址解析、数据转换、位比对和DQ定位四步算法,精准锁定故障DQ位的实践流程。文章以DDR4和DDR5为例,详细拆解了从十六进制报错到二进制比对的完整步骤,并提供了从单个报错分析到故障模式诊断的进阶指南,帮助用户将抽象的报错信息转化为具体的维修或排查依据。

sre5engineer的博客 486

Apollo源码解析——创建App

文章目录前言一、概述二、创建流程1.页面操作2.后台代码:总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 一、概述 创建流程 二、创建流程 1.页面操作 示例如下: 2.后台代码: AppController#apps方法 在接到请求后,首先将app对象转成model对象,然后写库,发布app创建的事件出去。 public App create(@V.

ZXMSH的博客 411

在overleaf中使用bibtex引用参考文献

最近在挑选写tex的平台,挑来挑去,个人感觉overleaf给我带来的体验感是最舒适的。不过由于对内部的文件组织和make进行了一层封装,所以与一般的本地平台编译tex会有一点不同。此处写一下在overleaf中使用bibtex的方法。 在tex中使用bibtex引用文献是一件很舒适的事,毕竟这是tex的优点之一(要是tex比word难用,我干什么不去用word,hhh) 第一步,我们创建一个.bib文件,名字随便起,我这边起为reference.bib。 然后打开谷歌学术,搜索你想要引用的文章,并找到

锦恢的博客 4万+

Apollo-阿波罗配置中心超详细教程

Apollo基本概念一、简介 Apollo - A reliable configuration management system Apollo的Github地址Apollo包括服务端和客户端两部分: 1、服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。 2、Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。二、特性 基于配置的特殊性,所以Apollo从设计

hfwangyl的博客 2万+

python批量生成CAD图纸

python批量生成CAD图纸

轩逸云的博客 1万+

实测有效!YOLOv8融合EMA注意力机制后,在无人机航拍目标检测中的性能提升对比

本文详细介绍了YOLOv8融合EMA注意力机制在无人机航拍目标检测中的性能提升效果。通过多尺度特征融合和通道-空间双重注意力机制,EMA模块显著提升了小目标检测的准确率,mAP@0.5平均提升4.2%,同时保持推理速度基本不变。文章还提供了模型定制、训练技巧和实际部署优化的全流程指南。

weixin_33704234的博客 391

微服务架构~携程Apollo配置中心架构剖析

本文转自微信公众号 波波微课 的文章 微服务架构~携程Apollo配置中心架构剖析 一、介绍 Apollo(阿波罗)[参考附录1]是携程框架部研发并开源的一款生产级的配置中心产品,它能够集中管理应用在不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。 Apollo目前在国内开发者社区比较热,在Github上有超过5...

weixin_34362991的博客 1202

模型算力需求估算

(Billion Parameters,简称B)和训练/推理的计算任务复杂度,结合硬件计算能力(例如每秒浮点运算次数,FLOPS)来估算。以下是详细的方法和公式说明,以及实际的计算示例。算力需求的估算依赖于参数量、模型架构及硬件性能,结合训练/推理场景的特定要求来计算。通过上述公式和示例,我们可以定量分析不同任务的算力需求,并合理配置硬件资源以优化运行效率。结论:推理任务相对较轻,单台H100可以在30秒内完成此任务。推理算力需求一般远低于训练,因为只需要执行前向传播。,可以将训练时间缩短到约。

形上得其象,形下合于数;阴阳自济,玄理自明。 8955

[特殊字符] Apollo Portal应用创建全流程解析

Apollo应用创建流程解析:文章详细阐述了在Apollo配置管理系统中创建应用的完整流程。系统采用分层架构设计,包含前端交互、后端控制、服务逻辑、数据持久等层次。核心流程包括参数校验、应用唯一性验证、权限初始化、默认命名空间创建等关键环节,并实现了事务管理、权限控制和扩展事件机制。创建过程严格校验AppId格式、部门归属和管理员权限,同时自动初始化相关权限结构和默认配置空间。整个流程展现了企业级系统在数据一致性、安全控制和扩展性方面的设计考量,为配置管理系统开发提供了参考范式。

油墨香^-^的博客 635

从零开始安装 Windows 11 和 Ubuntu 22.04 双系统

本文详解Windows与Ubuntu 22.04双系统安装流程:需提前确认BIOS模式(UEFI/GPT或Legacy/MBR),关闭BitLocker加密、设备加密及快速启动;推荐Rufus/Ventoy制作启动盘。安装时需规划分区方案(建议单独划分EFI系统分区),注意引导器安装位置选择。双系统启动通过BIOS引导顺序管理,安装后需同步Windows与Linux系统时间。教程适配主流UEFI+GPT设备,强调操作前数据备份及Windows更新策略调整,避免兼容性问题。

萑澈的博客 3768

技术面 - 手撕算法题整理

这篇博客整理了华为OD面试中涉及的算法题,包括LeetCode热门题目、OD原题和需要手写的算法,如二分查找、冒泡排序等。建议优先刷"hot100"的LeetCode题目,全刷OD原题,以及掌握手撕算法的基本原理。

m0_73659489的博客 1025

Apollo Portal管理后台的使用

打开 Portal 地址,首先看到的是登录页面,默认的账号是 apollo,密码是 admin。输入之后点击登录即可跳转到首页

unber的博客 1337

AI快速变现之路,AI视频创作

初期投入$3000(工具订阅+样片制作),重点攻克跨境电商和本地生活服务市场。关注2025年AIGC视频工具的API降价趋势(如Pika企业版折扣可达50%),建议与云服务商签订长期合作协议。

qq_29896351的博客 1981

1、Apollo源码解析 - Portal 创建 App

文章摘要:系统通过PortalService和AdminService协同完成App创建流程。

weixin_38687619的博客 163

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

3、Apollo源码解析 - Portal创建 AppNamespace

摘要:文章描述了Portal和AdminService两端创建AppNamespace的流程。Portal端首先检查权限,创建AppNamespace表记录并授权,然后发送创建事件通知;AdminService端接收事件后,进行唯一性检查并插入相关数据库表(AppNamespace和Namespace),同时为应用所有集群创建对应的Namespace记录。

weixin_38687619的博客 112

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【知其然,知其所以然】配置中心 Apollo源码剖析

第2章 Apollo源码剖析 能力目标 能够基于Git导入Apollo源码 能够基于IDEA实现DEBUG分析APP创建 掌握Namespace创建过程 掌握Item创建过程 掌握灰度发布创建过程 1:namespace创建、灰度发布配置、Item创建作为自学 2:客户端剖析 ​ 通信->Http、轮询机制 ​ 配置文件优先级、缓存、关联关系 ​ 刷新机制【注解解析】 1 Apollo源码搭建 在上一章我们已经学习了Apollo项目实战,为了更进一步学习Apollo、

博学谷狂野架构师的博客 1545

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Apollo 源码解析

Apollo 源码解析 —— 调试环境搭建 Apollo 源码解析 —— Portal 创建 App Apollo 源码解析 —— Portal 创建 Cluster Apollo 源码解析 —— Portal 创建 Namespace Apollo 源码解析 —— Portal 关联 Namespace Apollo 源码解析 —— Portal 创建 Item Apollo 源...

HIP的博客 5126

Apollo 源码解析 —— Portal 创建 Namespace

点击上方“芋道源码”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创 | Java 2020 超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络...

芋艿V 414

Apollo 源码解析 —— Portal 创建 Cluster

1. 概述 老艿艿:本系列假定胖友已经阅读过《Apollo 官方 wiki 文档》。 本文分享Portal 创建 Cluster的流程,整个过程涉及 Portal、Admin Service ,如下图所示: 下面,我们先来看看 Cluster 的实体结构 老艿艿:因为 Portal 是管理后台,所以从代码实现上,和业务系统非常相像。也因此,本文会略显啰嗦。 2. Cluster com.ctrip.framework.apollo.biz.entity.Cluster,继承...

weixin_42073629的博客 288

Apollo 源码解析 —— Portal 创建 Item

1. 概述 老艿艿:本系列假定胖友已经阅读过《Apollo 官方 wiki 文档》。 Item ,配置项,是 Namespace 下最小颗粒度的单位。在 Namespace 分成五种类型:propertiesymlyamljsonxml。其中: properties:每一行配置对应一条Item 记录。 后四者:无法进行拆分,所以一个Namespace仅仅对应一条Item 记录。 本文先分享Portal 创建类型为 properties 的 Namespace 的 Ite...

weixin_42073629的博客 561

微服务配置中心 Apollo解析——Portal 关联 Namespace

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!式,性能优化,微服务 高级架构开发等等。

2401_84046912的博客 1233

Apollo 源码解析 —— Portal 配置灰度规则

1. 概述 老艿艿:本系列假定胖友已经阅读过《Apollo 官方 wiki 文档》,特别是《Apollo 官方 wiki 文档 —— 灰度发布使用指南》。 本文分享Portal 配置灰度规则的流程,整个过程涉及 Portal、Admin Service ,如下图所示: 对于一个子Namespace 仅对应一条有效灰度规则 GrayReleaseRule 记录。每次变更灰度规则时,标记删除老的灰度规则,新增保存新的灰度规则。 变更灰度配置完成后,会发布一条 ReleaseMessage...

weixin_42073629的博客 627
上一篇: 老大又批评我不要为了“分库分表”而“分库分表”
下一篇: 芋道 Spring Boot API 接口文档 Swagger 入门
公众号-芋道源码
博客等级 码龄10年 1631粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值