Skip to content

Commit

Permalink
Merge pull request #3 from foxdd/master
Browse files Browse the repository at this point in the history
update code
  • Loading branch information
foxdd authored Aug 31, 2021
2 parents 6676159 + c1b028b commit 639eea4
Show file tree
Hide file tree
Showing 129 changed files with 2,899 additions and 497 deletions.
88 changes: 42 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,60 @@
# devops-framework
![GitHub](https://img.shields.io/github/license/bkdevops-projects/devops-framework)
![Maven Central](https://img.shields.io/maven-central/v/com.tencent.devops/devops-boot)
![GitHub Workflow Status (event)](https://img.shields.io/github/workflow/status/bkdevops-projects/devops-framework/build)
<h1 align="center" style="font-weight: bold;">DevOps Boot</h1>
<h4 align="center">基于Spring Boot的微服务快速开发框架</h4>
<div align="center">

[![GitHub](https://img.shields.io/github/license/bkdevops-projects/devops-framework)](https://img.shields.io/github/license/bkdevops-projects/devops-framework)
[![Maven Central](https://img.shields.io/maven-central/v/com.tencent.devops/devops-boot)](https://img.shields.io/maven-central/v/com.tencent.devops/devops-boot)
[![GitHub Workflow Status (event)](https://img.shields.io/github/workflow/status/bkdevops-projects/devops-framework/build)](https://img.shields.io/github/workflow/status/bkdevops-projects/devops-framework/build)

`devops-framework`是一款基于`Spring Boot`的微服务快速开发框架,提炼自腾讯DevOps团队内部多个项目,使用约定优于配置的设计理念,帮助我们专注于`DevOps`业务快速开发。
</div>

## 项目特点
<div align="center">

[中文文档地址](https://bkdevops-projects.github.io/devops-framework/)

</div>

----------

## DevOps Boot 是什么?

`devops-boot`提炼自腾讯DevOps团队内部多个项目,使用约定优于配置的设计理念,帮助我们专注于DevOps业务快速开发,它具有以下优势:

- **简单** :几乎零配置快速开发微服务,低成本上手
- **易用** :采用`Spring Boot`组件化思想,易于学习理解
- **统一** :目前已集成了微服务开发常用组件和统一配置
- **扩展** :组件之间低耦合,高内聚,扩展十分方便

查看[快速开始](quick-start.md)了解详情。

## DevOps Boot 能解决什么问题?

- **统一项目配置** : 免去繁琐的项目配置,gradle插件帮您解决烦恼
- **统一依赖版本管理** : 多个项目统一jdk和三方依赖版本,避免版本冲突
- **统一微服务治理解决方案**: 解决多个项目技术方案参差不齐,架构不统一问题
- **统一常用工具类** : 避免代码重复

## 功能特性
- 提供gradle快速开发插件[devops-boot-gradle-plugin](./devops-boot-project/devops-boot-tools/devops-boot-gradle-plugin/README.md)
- 提供gradle快速发布插件[devops-publish-gradle-plugin](./devops-boot-project/devops-boot-tools/devops-publish-gradle-plugin/README.md)
- 提供统一版本依赖管理[devops-boot-dependencies](./devops-boot-project/devops-boot-dependencies/README.md)
- 提供多个开箱即用的starter组件
- [starter-logging](./devops-boot-project/devops-boot-starters/devops-boot-starter-logging/README.md)
- [starter-api](./devops-boot-project/devops-boot-starters/devops-boot-starter-api/README.md)
- [starter-logging](./devops-boot-project/devops-boot-starters/devops-boot-starter-logging/README.md)
- [starter-web](./devops-boot-project/devops-boot-starters/devops-boot-starter-web/README.md)
- [starter-service](./devops-boot-project/devops-boot-starters/devops-boot-starter-service/README.md)
- TODO

## 快速开始
- **gradle.build.kts**
```groovy
// 添加devops-boot gradle插件
plugins {
id("com.tencent.devops.boot") version ${version}
}
dependencies {
// 添加需要的starter组件
implementation("com.tencent.devops:devops-boot-starter-web")
}
```
只需要添加一个`devops-boot`插件,会自动为我们配置`jdk`版本、编译选项、依赖管理、`kotlin`依赖及`kotlin-spring`插件等等繁琐的配置项。

接下来即可直接开始业务逻辑代码的编写了。


## 工程结构
```shell script
devops-framework/
├── buildSrc # gradle项目构建目录
├── devops-boot-project # devops-boot源码目录
│   ├── devops-boot-core # 核心模块
│   ├── devops-boot-dependencies # maven bom模块
│   ├── devops-boot-starters # starter组件目录
│   └── devops-boot-tools # gradle脚本等工具目录
├── devops-boot-sample # sample项目
└── docs # 开发文档
```
- ...

## 核心依赖

| 依赖 | 版本 |
| ------------ | ------------- |
| JDK | 1.8+ |
| Kotlin | 1.3.72 |
| Gradle | 6.6.1 |
| Spring Boot | 2.3.7.RELEASE |
| Spring Cloud | Hoxton.SR9 |
| Kotlin | 1.4.32 |
| Gradle | 6.8.3 |
| Spring Boot | 2.4.5 |
| Spring Cloud | 2020.0.2 |

## 示例

## 发行版本
- 0.0.1 2020年10月9日
- 0.0.2 2020年12月22日
可以查看[sample](https://github.com/bkdevops-projects/devops-framework/tree/master/devops-boot-sample)来了解如何优雅集成`devops-boot`框架。
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
object Libs {
const val KotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.Kotlin}"
const val KotlinReflectLib = "org.jetbrains.kotlin:kotlin-reflect:${Versions.Kotlin}"
const val KotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin}"
const val SpringBootGradlePlugin = "org.springframework.boot:spring-boot-gradle-plugin:${Versions.SpringBoot}"
const val DependencyManagement = "io.spring.gradle:dependency-management-plugin:${Versions.DependencyManagement}"
Expand Down
12 changes: 6 additions & 6 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
object Release {
const val Group = "com.tencent.devops"
const val Version = "0.0.4-SNAPSHOT"
const val Version = "0.0.4"
}

object Versions {
const val Java = "1.8"
const val Kotlin = "1.3.72"
const val SpringBoot = "2.3.7.RELEASE"
const val SpringCloud = "Hoxton.SR9"
const val DependencyManagement = "1.0.10.RELEASE"
const val Kotlin = "1.4.32"
const val SpringBoot = "2.4.5"
const val SpringCloud = "2020.0.2"
const val DependencyManagement = "1.0.11.RELEASE"
const val NexusPublish = "0.4.0"
const val NexusStaging = "0.22.0"
const val KtLint = "0.37.2"
const val KtLint = "0.41.0"
}
6 changes: 5 additions & 1 deletion buildSrc/src/main/kotlin/ktlint.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
val ktlint by configurations.creating

dependencies {
ktlint(Libs.KtLint)
ktlint(Libs.KtLint) {
attributes {
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling::class.java, Bundling.EXTERNAL))
}
}
}

val outputDir = "${project.buildDir}/reports/ktlint/"
Expand Down
6 changes: 3 additions & 3 deletions devops-boot-project/devops-boot-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ subprojects {

dependencies {
implementation(platform(project(MavenBom.DevOpsBoot)))
implementation(Libs.KotlinReflectLib)
implementation(Libs.KotlinStdLib)
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
kapt("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description = "DevOps Boot Circuit Breaker"

dependencies {
api("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.tencent.devops.circuitbreaker

import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.PropertySource

/**
* 客户端熔断器自动配置
*/
@Configuration(proxyBeanMethods = false)
@PropertySource("classpath:common-circuitbreaker.properties")
class CircuitBreakerAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.devops.circuitbreaker.CircuitBreakerAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feign.circuitbreaker.enabled=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description = "DevOps Boot Load Balancer"

dependencies {
api("org.springframework.cloud:spring-cloud-starter-loadbalancer")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tencent.devops.loadbalancer

import com.tencent.devops.loadbalancer.config.DevOpsLoadBalancerProperties
import com.tencent.devops.loadbalancer.gray.GrayLoadBalancerConfiguration
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients
import org.springframework.context.annotation.Configuration

/**
* 客户端负载均衡自动配置类
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@EnableConfigurationProperties(DevOpsLoadBalancerProperties::class)
@LoadBalancerClients(defaultConfiguration = [GrayLoadBalancerConfiguration::class])
class LoadBalancerAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.tencent.devops.loadbalancer.config

import com.tencent.devops.loadbalancer.config.DevOpsLoadBalancerProperties.Companion.PREFIX
import org.springframework.boot.context.properties.ConfigurationProperties

/**
* 客户端负载均衡配置
*/
@ConfigurationProperties(PREFIX)
data class DevOpsLoadBalancerProperties(
/**
* 优先访问本地服务
*/
var localPrior: LocalPrior = LocalPrior(),

/**
* 灰度调用配置
*/
var gray: Gray = Gray()
) {
data class Gray(
/**
* 是否启用灰度调用
*/
var enabled: Boolean = false,
/**
* 匹配metadata key值
*/
var metaKey: String = "env"
)

data class LocalPrior(
/**
* 是否优先访问本地服务
*/
var enabled: Boolean = false
)

companion object {
const val PREFIX = "devops.loadbalancer"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.tencent.devops.loadbalancer.gray

import com.tencent.devops.loadbalancer.config.DevOpsLoadBalancerProperties
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.cloud.client.ServiceInstance
import org.springframework.cloud.client.serviceregistry.Registration
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory
import org.springframework.context.annotation.Bean
import org.springframework.core.env.Environment

/**
* 支持灰度调用的loadbalancer配置
*/
class GrayLoadBalancerConfiguration : LoadBalancerClientConfiguration() {

@Bean
@ConditionalOnMissingBean
fun reactorServiceInstanceLoadBalancer(
loadBalancerProperties: DevOpsLoadBalancerProperties,
registration: Registration,
environment: Environment,
loadBalancerClientFactory: LoadBalancerClientFactory
): ReactorLoadBalancer<ServiceInstance> {
val name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME)
val serviceInstanceListSupplier =
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier::class.java)
return GraySupportedLoadBalancer(
loadBalancerProperties = loadBalancerProperties,
registration = registration,
serviceInstanceListSupplierProvider = serviceInstanceListSupplier,
serviceId = name.orEmpty()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.tencent.devops.loadbalancer.gray

import com.tencent.devops.loadbalancer.config.DevOpsLoadBalancerProperties
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.ObjectProvider
import org.springframework.cloud.client.ServiceInstance
import org.springframework.cloud.client.loadbalancer.DefaultResponse
import org.springframework.cloud.client.loadbalancer.EmptyResponse
import org.springframework.cloud.client.loadbalancer.Request
import org.springframework.cloud.client.loadbalancer.Response
import org.springframework.cloud.client.serviceregistry.Registration
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier
import reactor.core.publisher.Mono
import java.util.Random
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.abs

/**
* 支持灰度调用的loadbalancer
* 代码复用RoundRobinLoadBalancer,在此基础上做了增强
*/
class GraySupportedLoadBalancer(
private val loadBalancerProperties: DevOpsLoadBalancerProperties,
private val registration: Registration,
private val serviceInstanceListSupplierProvider: ObjectProvider<ServiceInstanceListSupplier>,
private val serviceId: String,
private val position: AtomicInteger = AtomicInteger(Random().nextInt(1000))
) : ReactorServiceInstanceLoadBalancer {
override fun choose(request: Request<*>?): Mono<Response<ServiceInstance>> {
val supplier = serviceInstanceListSupplierProvider.getIfAvailable { NoopServiceInstanceListSupplier() }
return supplier.get(request).next().map { processInstanceResponse(supplier, it) }
}

private fun processInstanceResponse(
supplier: ServiceInstanceListSupplier,
serviceInstances: List<ServiceInstance>
): Response<ServiceInstance> {
val serviceInstanceResponse = getInstanceResponse(serviceInstances)
if (supplier is SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
supplier.selectedServiceInstance(serviceInstanceResponse.server)
}
return serviceInstanceResponse
}

private fun getInstanceResponse(instances: List<ServiceInstance>): Response<ServiceInstance> {
val filteredInstances = if (loadBalancerProperties.gray.enabled) {
if (loadBalancerProperties.gray.metaKey.isEmpty()) {
logger.warn("Load balancer gray meta-key is empty.")
}
val localMetaValue = registration.metadata[loadBalancerProperties.gray.metaKey].orEmpty()
instances.filter { it.metadata[loadBalancerProperties.gray.metaKey].orEmpty() == localMetaValue }
} else instances

if (loadBalancerProperties.localPrior.enabled) {
for (instance in filteredInstances) {
if (instance.host == registration.host) {
return DefaultResponse(instance)
}
}
}

return roundRobinChoose(filteredInstances)
}

private fun roundRobinChoose(instances: List<ServiceInstance>): Response<ServiceInstance> {
if (instances.isEmpty()) {
if (logger.isWarnEnabled) {
logger.warn("No servers available for service: $serviceId")
}
return EmptyResponse()
}

// TODO: enforce order?
val pos = abs(this.position.incrementAndGet())
val instance = instances[pos % instances.size]
return DefaultResponse(instance)
}

companion object {
private val logger = LoggerFactory.getLogger(GraySupportedLoadBalancer::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.devops.service.ServiceAutoConfiguration
com.tencent.devops.loadbalancer.LoadBalancerAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ description = "DevOps Boot Logging"

dependencies {
api("org.springframework.boot:spring-boot-starter-logging")
api("org.springframework.boot:spring-boot-starter")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.springframework.context.ConfigurableApplicationContext
class ApplicationLoggerInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
val environment = applicationContext.environment
val applicationName = environment.getProperty("spring.application.name", "app")
val applicationName = environment.getProperty("spring.application.name", "application")
val loggingFilePath = environment.getProperty("logging.file.path", "logs")
val loggingFileName = "%s/%s/%s.log".format(loggingFilePath, applicationName, applicationName)
// 为spring boot admin 提供日志路径用于展示
Expand Down
Loading

0 comments on commit 639eea4

Please sign in to comment.