开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Springfox swagger30项目配置和实战

发表于 2021-11-19

由于项目重构,所以需要从springboot 1.5.7版本切换到2.5.6
但切换后出现了swagger 依赖冲突的问题,所以swagger也要换到3.0的版本

首先可以直接引入springfox-boot-starter,这样就简洁点引入依赖了

1
2
3
4
5
xml复制代码<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

然后修改swagger 配置类,我这里没有用到很多的属性,所以就直接点
其实改动点就是

1
复制代码DocumentationType SWAGGER_2换成了DocumentationType.OAS_30

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
less复制代码@Configuration
@ConditionalOnProperty(prefix="swagger",value={"enable"},havingValue="true")
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.meicloud.meidevops.controller"))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("devops api")
.description("devops")
.termsOfServiceUrl("https://www.meicloud.com")
.version("1.0")
.build();
}
}

然后启动访问http://localhost:8082/swagger-ui/index.html#/

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

一文带你了解数据库连接池 1 数据库连接池

发表于 2021-11-19

这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战

  1. 数据库连接池

1.1 连接池介绍

1.1.1 什么是连接池

  • 实际开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常情况我们采用连接池技术,来共享连接Connection。
  • 这样我们就不需要每次都创建连接、释放连接了,这些操作都交给了连接池.

1.1.2 连接池的好处

  • 用池来管理Connection,这样可以重复使用Connection。 当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。

1.2 JDBC方式与连接池方式

普通 JDBC方式

image.png

连接池方式

image.png

1.3 如何使用数据库连接池

  • Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。 这样应用程序可以方便的切换不同厂商的连接池!
  • 常见的连接池有 DBCP连接池, C3P0连接池, Druid连接池。

1.4 数据准备

image.png

1.5 DBCP连接池

  • DBCP也是一个开源的连接池,是Apache成员之一,在企业开发中也比较常见,tomcat内置的连接池。

1.5.1 创建项目导入jar包

  • 1)将这两个 jar包添加到 myJar文件夹中 (jar包在资料里的软件文件夹中)

image.png

  • 2)添加myJar库 到项目的依赖中

image.png

1.5.2 编写工具类

  • 连接数据库表的工具类, 采用DBCP连接池的方式来完成
    • Java中提供了一个连接池的规则接口 :DataSource, 它是java中提供的连接池
    • 在DBCP包中提供了DataSource接口的实现类,我们要用的具体的连接池类
  • 代码示例

image.png
image.png

1.5.3 常见配置项

属性 描述
driverClassName 数据库驱动名称
url 数据库地址
username 用户名
password 密码
maxActive 最大连接数量
maxIdle 最大空闲连接
minIdle 最小空闲连接
initialSize 初始化连接

1.6 C3P0连接池

  • C3P0是一个开源的JDBC连接池,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。

1.6.1 导入jar包及配置文件

    1. 将jar包 复制到myJar文件夹即可,IDEA会自动导入

image.png

    1. 导入配置文件 c3p0-config.xml
      • c3p0-config.xml 文件名不可更改
      • 直接放到src下,也可以放到到资源文件夹中

image.png

    1. 在项目下创建一个resource文件夹(专门存放资源文件)

image.png

    1. 选择文件夹,右键 将resource文件夹指定为资源文件夹

image.png

    1. 将文件放在resource目录下即可,创建连接池对象的时候会去加载这个配置文件

image.png

1.6.2 编写C3P0工具类

  • C3P0提供的核心工具类,ComboPooledDataSource, 如果想使用连接池,就必须创建该类的对象
+ new ComboPooledDataSource(); 使用 默认配置
+ new ComboPooledDataSource("mysql"); 使用命名配置

image.png
image.png

1.6.3 常见配置

image.png

1.7 Druid连接池

  • Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。

1.7.1 导入jar包及配置文件

    1. 导入 jar包

image.png

    1. 导入配置文件
      • 是properties形式的
      • 可以叫任意名称,可以放在任意目录下,我们统一放到 resources资源目录

image.png

image.png

1.7.2 编写Druid工具类

  • 获取数据库连接池对象
    • 通过工厂来来获取 DruidDataSourceFactory类的createDataSource方法
    • createDataSource(Properties p) 方法参数可以是一个属性集对象

image.png

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

从操作系统的发展了解线程的前世今生 专题简介 内容导航 了解

发表于 2021-11-19

「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」

专题简介

作为一个合格的Java程序员,必须要对并发编程有一个深层次的了解,在很多互联网企业都会重点考察这一块。可能很多工作3年以上的Java程序员对于这一领域几乎没有太多研究。所以在接下来内容中,我会将并发编程整个领域由浅到深做非常全面的分析。

内容导航

  1. 从操作系统的发展了解进程、线程模型
  2. 线程的优势
  3. 线程的生命周期
  4. 线程的应用场景

了解进程、线程模型

每次学习一个新技术,建议先去了解这个技术的背景,这个过程看似浪费时间,其实在后续的学习过程中,能够促进理解很多问题。所以对于线程这个概念,我会先从操作系统讲起。因为操作系统的发展带来了软件层面的变革。
从多线程的发展来看,可以操作系统的发展分为三个历史阶段:

  1. 真空管和穿孔卡片
  2. 晶体管和批处理系统
  3. 集成电路和多道程序设计

最早的计算机只能解决简单的数学运算问题,比如正弦、余弦等。运行方式:程序员首先把程序写到纸上,然后穿孔成卡票,再把卡片盒带入到专门的输入室。输入室会有专门的操作员将卡片的程序输入到计算机上。计算机运行完当前的任务以后,把计算结果从打印机上进行输出,操作员再把打印出来的结果送入到输出室,程序员就可以从输出室取到结果。然后,操作员再继续从已经送入到输入室的卡片盒中读入另一个任务重复上述的步骤。

操作员在机房里面来回调度资源,造成计算机存在大量的空闲状态 。而当时的计算机是非常昂贵的,人们为了减少这种资源的浪费。就采用了 批处理系统来解决

批处理操作系统的运行方式:在输入室收集全部的作业,然后用一台比较便宜的计算机把它们读取到磁带上。然后把磁带输入到计算机,计算机通过读取磁带的指令来进行运算,最后把结果输出磁带上。批处理操作系统的好处在于,计算机会一直处于运算状态,合理的利用了计算机资源。(运行流程如下图所示)

image.png

批处理操作系统虽然能够解决计算机的空闲问题,但是当某一个作业因为等待磁盘或者其他I/O操作而暂停,那CPU就只能阻塞直到该I/O完成,对于CPU操作密集型的程序,I/O操作相对较少,因此浪费的时间也很少。但是对于I/O操作较多的场景来说,CPU的资源是属于严重浪费的。

多道程序设计的出现解决了这个问题,就是把内存分为几个部分,每一个部分放不同的程序。当一个程序需要等待I/O操作完成时。那么CPU可以切换执行内存中的另外一个程序。如果内存中可以同时存放足够多的程序,那CPU的利用率可以接近100%。
在这个时候,引入了第一个概念- 进程, 进程的本质是一个正在执行的程序,程序运行时系统会创建一个进程,并且给每个进程分配独立的内存地址空间保证每个进程地址不会相互干扰。同时,在CPU对进程做时间片的切换时,保证进程切换过程中仍然要从进程切换之前运行的位置出开始执行。所以进程通常还会包括程序计数器、堆栈指针。

有了进程以后,可以让操作系统从宏观层面实现多应用并发。而并发的实现是通过CPU时间片不端切换执行的。对于单核CPU来说,在任意一个时刻只会有一个进程在被CPU调度

有了进程以后,为什么还会出现线程呢?

在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。举个具体的例子来说,我们平常用word文档编辑内容的时候,都会有一个自动保存的功能,这个功能的作用是,当计算机出现故障的情况下如果用户未保存文档,则能够恢复到上一次自动保存的点。假设word的自动保存因为磁盘问题导致写入较慢,势必会影响到用户的文档编辑功能,直到磁盘写入完成用户才可编辑,这种体验是很差的。如果我们把一个进程中的多个任务通过线程的方式进行隔离,那么按照前面提到的进程演进的理论来说,在单核心CPU架构中可以通过CPU的时间片切换实现线程的调度充分利用CPU资源以达到最大的性能。

我们用了比较长的篇幅介绍了进程、线程发展的历史。总的来说是人们对于计算机的要求越来越高;对于计算机本身的资源的利用率也在不断提高。

线程的优势

前面分析了线程的发展历史,这里简单总结一下线程有的优势如下

  1. 线程可以认为是轻量级的进程,所以线程的创建、销毁要比进程更快
  2. 从性能上考虑,如果进程中存在大量的I/O处理,通过多线程能够加快应用程序的执行速度(通过CPU时间片的快速切换)。
  3. 由于线程是CPU的最小调度单元,所以在多CPU架构中能够实现真正的并行执行。每一个CPU可以调度一个线程
    这里有两个概念很多人没有搞明白,就是并行和并发

**并行:**同时执行多个任务,在多核心CPU架构中,一个CPU核心运行一个线程,那么4核心CPU,可以同时执行4个线程
**并发:**同处理多个任务的能力,通常我们会通过TPS或者QPS来表示某某系统支持的并发数是多少。

总的来说,并行是并发的子集。也就是说我们可以写一个拥有多线程并行的程序,如果在没有多核心CPU来执行这些线程,那就不能以并行的方式来运行程序中的多个线程。所以并发程序可以是并行的,也可以不是。

Erlang之父Joe Armstrong通过一张图型的方式来解释并发和并行的区别

线程的生命周期

线程是存在生命周期的,从线程的创建到销毁,可能会经历6种不同的状态,但是在一个时刻线程只能处于其中一种状态

  1. NEW:初始状态,线程被创建时候的状态,还没有调用start方法
  2. RUNNABLE:运行状态,运行状态包含就绪和运行两种状态,因为线程启动以后,并不是立即执行,而是需要通过调度去分配CPU时间片
  3. BLOCKED:阻塞状态,当线程去访问一个加锁的方法时,如果已经有其他线程获得锁,那么当前线程会处于阻塞状态
  4. WAITING:等待状态,设置线程进入等待状态等待其他线程做一些特定的动作进行触发
  5. TIME_WAITING:超时等待状态,和WAITING状态的区别在于超时以后自动返回
  6. TERMINATED:终止状态,线程执行完毕

下图整理了线程的状态变更过程及变更的操作,每一个具体的操作原理,我会在后续的文章中进行详细分析。

image.png

这里有一个问题大家可能搞不明白,BLOCKED和WAITING这两个阻塞有什么区别?

BLOCKED状态是指当前线程在等待一个获取锁的操作时的状态。
WAITING是通过Object.wait或者Thread.join、LockSupport.park等操作实现的
BLOCKED是被动的标记,而WAITING是主动操作
如果说得再深入一点,处于WAITING状态的线程,被唤醒以后,需要进入同步队列去竞争锁操作,而在同步队列中,如果已经有其他线程持有锁,则线程会处于BLOCKED状态。所以可以说BLOCKED状态是处于WAITING状态的线程重新唤醒的必经的状态

线程的应用场景

线程的出现,在多核心CPU架构下实现了真正意义上的并行执行。也就是说,一个进程内多个任务可以通过多线程并行执行来提高程序运行的性能。那线程的使用场景有哪些呢?

  1. 执行后台任务,在很多场景中,可能会有一些定时的批量任务,比如定时发送短信、定时生成批量文件。在这些场景中可以通过多线程的来执行
  2. 异步处理,比如在用户注册成功以后给用户发送优惠券或者短信,可以通过异步的方式来执行,一方面提升主程序的执行性能;另一方面可以解耦核心功能,防止非核心功能对核心功能造成影响
  3. 分布式处理,比如fork/join,将一个任务拆分成多个子任务分别执行
  4. BIO模型中的线程任务分发,也是一种比较常见的使用场景,一个请求对应一个线程

合理的利用多线程,可以提升程序的吞吐量。同时,还可以通过增加CPU的核心数来提升程序的性能,这就体现了伸缩性的特点

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

ClickHouse之ReplacingMergeTree详

发表于 2021-11-19

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。

ReplacingMergeTree 是 MergeTree 的一个变种,它存储特性完全继承 MergeTree,只是多了一个去重的功能。

尽管 MergeTree 可以设置主键,但是 primary key 其实没有唯一约束的功能。

如果你想处理掉重复的数据,可以借助这个 ReplacingMergeTree。

去重时机

数据的去重只会在合并的过程中出现。合并会在未知的时间在后台进行,所以你无法预先作出计划。有一些数据可能仍未被处理。

去重范围

如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重。

所以 ReplacingMergeTree 能力有限,ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现。

案例演示

创建表

1
2
3
4
5
6
7
8
9
sql复制代码create table order_table4
(
id UInt32,
item_id String,
total_amount Decimal(16, 2),
create_time Datetime
) engine = ReplacingMergeTree(create_time)
partition by toYYYYMMDD(create_time) primary key (id)
order by (id, item_id);

ReplacingMergeTree() 填入的参数为表字段,重复数据保留版本字段值最大的。

如果不填表字段,默认按照插入顺序保留最后一条。

插入数据

1
2
3
4
5
6
7
sql复制代码insert into order_table4
values (101, 's_001', 1000.00, '2020-06-01 12:00:00'),
(102, 's_002', 2000.00, '2020-06-01 11:00:00'),
(102, 's_004', 2500.00, '2020-06-01 12:00:00'),
(102, 's_002', 2000.00, '2020-06-01 13:00:00'),
(102, 's_002', 12000.00, '2020-06-01 13:00:00'),
(102, 's_002', 600.00, '2020-06-02 12:00:00');

执行第一次查询

截屏2021-11-18 下午3.43.17.png

手动合并

1
sql复制代码OPTIMIZE TABLE order_table4 FINAL;

再执行一次查询

截屏2021-11-18 下午3.45.11.png

结论

◼ 实际上是使用 order by 字段作为唯一键

◼ 去重不能跨分区

◼ 只有合并才会进行去重

◼ 认定重复的数据保留,表字段值最大的

◼ 如果表字段相同则按插入顺序保留最后一条

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

☆打卡算法☆LeetCode 51、N皇后 算法解析

发表于 2021-11-19

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

推荐阅读

  • CSDN主页
  • GitHub开源地址
  • Unity3D插件分享
  • 简书地址
  • 我的个人博客
  • QQ群:1040082875

大家好,我是小魔龙,Unity3D软件工程师,VR、AR,虚拟仿真方向,不定时更新软件开发技巧,生活感悟,觉得有用记得一键三连哦。

一、题目

1、算法题目

“给定一个整数n,返回所有不同的N皇后问题的解决方案。”

题目链接:

来源:力扣(LeetCode)

链接:51. N 皇后 - 力扣(LeetCode) (leetcode-cn.com)

2、题目描述

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

image.png

1
2
3
4
css复制代码示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
1
2
3
lua复制代码示例 2:
输入: n = 1
输出: [["Q"]]

二、解题

1、思路分析

N皇后问题是一道经典的回溯问题。首先,分析规则,N皇后放置在N*N 棋盘上,然后皇后彼此之间不能相互攻击。

直接的做法是暴力枚举所以可能的情况,然后判断互相不攻击的情况,但是暴力枚举的时间复杂度非常高,必须利用限制条件进行优化。

可以通过回溯的方法找到可能的解,首先,使用一个数组记录每个每行皇后的列下标,依次在每一行放置一个皇后后,下一个放置皇后的位置都不能和已经放置皇后的位置之间有攻击,当所有皇后放置完毕,就可以找到一个解。

为了降低总时间复杂度,需要在放置皇后时快速判断每个位置是否可以放置皇后,那么就可以在O(1)的时间内判断该位置的列和两条斜线上是否已经有皇后。

2、代码实现

代码参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
csharp复制代码public class Solution {
List<IList<string>> res;
public IList<IList<string>> SolveNQueens(int n) {
res = new List<IList<string>>();
char[][] board = new char[n][];
for (int i = 0; i < n; ++i) board[i] = new char[n];
foreach (char[] chars in board){
Array.Fill(chars, '.');
}
backTrack(board, 0);
return res;
}
private void backTrack(char[][] board, int row){
if (row == board.Length){
res.Add(charToString(board));
return;
}
int n = board[row].Length;
for (int col = 0; col < n; ++col){
if (!isValid(board, row, col)) continue;
board[row][col] = 'Q';
backTrack(board, row + 1);
board[row][col] = '.';
}
}
private bool isValid(char[][] board, int row, int col){
int rows = board.Length;
foreach (char[] chars in board) if (chars[col] == 'Q') return false;
for (int i = row - 1, j = col + 1; i >= 0 && j < rows; i--, j++){
if (board[i][j] == 'Q') return false;
}
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
if (board[i][j] == 'Q') return false;
}
return true;
}
private List<string> charToString(char[][] array){
List<string> result = new List<string>();
foreach (char[] chars in array) result.Add(new String(chars));
return result;
}
}

image.png

3、时间复杂度

时间复杂度 : O(N!)

其中N是皇后数量。

空间复杂度: O(N)

其中N是皇后数量。

三、总结

这道题可以好好理解一下。

爱奇艺和字节的笔试都出现过这道题。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis 主从架构之深扒配置文件(一)

发表于 2021-11-19

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战」。

上文中我们简单介绍了 Redis 主从复制的基本概念,为了让大家对概念有更清晰的认识,我们先来看一下配置文件中主从复制的参数介绍。

REPLICATION

replicaof <masterip> <masterport>

通过设置 master 的 ip 和 port ,可以使当前的 Redis 实例成为另一台 Redis 实例的副本。在Redis启动时,它会自动从 master 进行数据同步。

  • Redis 复制是异步的,可以通过修改 master 的配置,在 master 没有与给定数量的 replica 连接时,主机停止接收写入;
  • 如果复制链路丢失的时间相对较短,Redis replica 可以与 master 执行部分重新同步,可以使用合理的 backlog 值来进行配置(见下文);
  • 复制是自动的,不需要用户干预。在网络分区后,replica 会自动尝试重新连接到 master 并与 master 重新同步;

masterauth <master-password>

当 master 设置了密码保护时,replica 服务连接 master 的密码

replica-read-only

可以将 replica 配置为是否只读,yes 代表为只读状态,将会拒绝所有写入命令;no 表示可以写入。 从 Redis 2.6 之后, replica 支持只读模式且默认开启。可以在运行时使用 CONFIG SET 来随时开启或者关闭。

对 replica 进行写入可能有助于存储一些临时数据(因为写入 replica 的数据在与 master 重新同步后很容易被删除),计算慢速集或排序集操作并将其存储到本地密钥是多次观察到的可写副本的一个用例。但如果客户端由于配置错误而向其写入数据,则也可能会导致问题。

在级联结构中即使 replica B 节点是可写的,Sub-replica C 也不会看到 B 的写入,而是将拥有和 master A 相同的数据集。

设置为 yes 并不表示客户端用集群方式以 replica 为入口连入集群时,不可以进行 set 操作,且 set 操作的数据不会被放在 replica 的槽上,会被放到某 master 的槽上。

注意:只读 replica 设计的目的不是为了暴露于互联网上不受信任的客户端,它只是一个防止实例误用的保护层。默认情况下,只读副本仍会导出所有管理命令,如CONFIG、DEBUG 等。在一定程度上,可以使用rename-command来隐藏所有管理/危险命令,从而提高只读副本的安全性。

Redis 的配置文件配置比较多,我们将分几个章节依次击破,大家记得查看连载呦!如果你有不同的意见或者更好的idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

部署Apollo

发表于 2021-11-19

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

因为楼主更改了apollo源码,所以需要重新打包,然后在制作镜像。

1.docker部署Apollo

1. 打包

可以参考上文。

2.制作镜像

1.获取zip包

分别获取打包之后zip包,在/apollo-portal/target/,apollo-adminservice/target/,apollo-configservice/target/中。
在这里插入图片描述

2.获取dockerflie

分别在apollo-portal,apollo-adminservice,apollo-configservice中获取dockerflie。
在这里插入图片描述

3.将以上两个文件分别放于任意文件夹中并分别执行

1
2
3
js复制代码docker build -t apollo-portal .
docker build -t apollo-adminservice .
docker build -t apollo-configservice .

在这里插入图片描述

3.运行容器

在虚拟机中执行以下命令即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
js复制代码adminconfig
docker run -d -p 8090:8090 --net=host -e SPRING_DATASOURCE_URL="jdbc:postgresql://ip:5432/apolloconfig?characterEncoding=utf8" -e SPRING_DATASOURCE_USERNAME=apolloconfig -e SPRING_DATASOURCE_PASSWORD=123 -d -v /tmp/logs:/opt/logs --name apollo-adminservice apollo-adminservice

SPRING_DATASOURCE_URL: 对应环境ApolloConfigDB的地址
SPRING_DATASOURCE_USERNAME: 对应环境ApolloConfigDB的用户名
SPRING_DATASOURCE_PASSWORD: 对应环境ApolloConfigDB的密码
--name apollo-adminservice apollo-adminservice:为镜像同名 下面同理

apollo-configservice
docker run -p 8080:8080 --net=host -e SPRING_DATASOURCE_URL="jdbc:postgresql://ip:5432/apolloconfig?characterEncoding=utf8" -e SPRING_DATASOURCE_USERNAME=apolloconfig -e SPRING_DATASOURCE_PASSWORD=123 -d -v /tmp/logs:/opt/logs --name apollo-configservice apollo-configservice

SPRING_DATASOURCE_URL: 对应环境ApolloConfigDB的地址
SPRING_DATASOURCE_USERNAME: 对应环境ApolloConfigDB的用户名
SPRING_DATASOURCE_PASSWORD: 对应环境ApolloConfigDB的密码

apollo-portal
docker run -p 8070:8070 --net=host -e SPRING_DATASOURCE_URL="jdbc:postgresql://ip:5432/apolloportal" -e SPRING_DATASOURCE_USERNAME=apolloportal -e SPRING_DATASOURCE_PASSWORD=123 -e APOLLO_PORTAL_ENVS=dev -e DEV_META=http://localhost:8080 -d -v /tmp/logs:/opt/logs --name apollo-portal apollo-portal

SPRING_DATASOURCE_URL: 对应环境ApolloPortalDB的地址
SPRING_DATASOURCE_USERNAME: 对应环境ApolloPortalDB的用户名
SPRING_DATASOURCE_PASSWORD: 对应环境ApolloPortalDB的密码
APOLLO_PORTAL_ENVS(可选): 对应ApolloPortalDB中的apollo.portal.envs配置项,如果没有在数据库中配置的话,可以通过此环境参数配置
DEV_META/PRO_META(可选): 配置对应环境的Meta Service地址,以${ENV}_META命名,需要注意的是如果配置了ApolloPortalDB中的apollo.portal.meta.servers配置,则以apollo.portal.meta.servers中的配置为准

如没有定制数据库需求,可以参考官方文档
github.com/ctripcorp/a…

2.k8s部署Apollo

​
书接上文 楼主定制了oracle版本的apollo,那么怎么使用k8s部署呢,本文只部署dev环境,使用yaml文件在Kubernetes-dashboard部署。

如果有对Kubernetes-dashboard不熟悉的,可以参考k8s专栏 。

1.部署apollo-admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
js复制代码---
apiVersion: v1
kind: ConfigMap
metadata:
namespace: apollo #更改自己的namespace
name: configmap-apollo-admin-server
data:
application-github.properties: |
spring.datasource.url = jdbc:postgresql://ip:5432/apolloconfig?characterEncoding=utf8
spring.datasource.username = apolloconfig
spring.datasource.password = 123
eureka.service.url = ip:5001/eureka
---
apiVersion: v1
kind: Service
metadata:
namespace: apollo
name: service-apollo-admin-server
labels:
app: service-apollo-admin-server
spec:
ports:
- protocol: TCP
port: 8090
nodePort: 8090
selector:
app: pod-apollo-admin-server
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: apollo
name: deployment-apollo-admin-server
labels:
app: deployment-apollo-admin-server
spec:
replicas: 1
selector:
matchLabels:
app: pod-apollo-admin-server
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: pod-apollo-admin-server
spec:
volumes:
- name: volume-configmap-apollo-admin-server
configMap:
name: configmap-apollo-admin-server
items:
- key: application-github.properties
path: application-github.properties
containers:
- image: ip:9000/apollo/apollo-adminservice:latest #镜像地址 楼主这里使用了私人仓库
securityContext:
privileged: true
imagePullPolicy: IfNotPresent
name: container-apollo-admin-server
ports:
- protocol: TCP
containerPort: 8090
volumeMounts:
- name: volume-configmap-apollo-admin-server
mountPath: /apollo-admin-server/config/application-github.properties
subPath: application-github.properties
env:
- name: APOLLO_ADMIN_SERVICE_NAME
value: "service-apollo-admin-server.sre"
readinessProbe:
tcpSocket:
port: 8090
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 8090
initialDelaySeconds: 120
periodSeconds: 10

dnsPolicy: ClusterFirst
restartPolicy: Always
nodeName: apollo #指定的nodes节点

2.部署apollo-config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
js复制代码---
kind: ConfigMap
apiVersion: v1
metadata:
namespace: apollo #更改自己的namespace
name: configmap-apollo-config-server
data:
application-github.properties: |
spring.datasource.url = jdbc:postgresql://ip:5432/apolloconfig?characterEncoding=utf8
spring.datasource.username = apolloconfig
spring.datasource.password = 123
eureka.service.url = ip:5001/eureka
---
kind: Service
apiVersion: v1
metadata:
namespace: apollo
name: service-apollo-meta-server
labels:
app: service-apollo-meta-server
spec:
ports:
- protocol: TCP
port: 8080
targetPort: 8080
selector:
app: pod-apollo-config-server
type: ClusterIP
clusterIP: None
sessionAffinity: ClientIP
---
kind: Service
apiVersion: v1
metadata:
namespace: apollo
name: service-apollo-config-server
labels:
app: service-apollo-config-server
spec:
ports:
- protocol: TCP
port: 8080
targetPort: 8080
nodePort: 8080
selector:
app: pod-apollo-config-server
type: NodePort
sessionAffinity: ClientIP
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
namespace: apollo
name: statefulset-apollo-config-server
labels:
app: statefulset-apollo-config-server
spec:
serviceName: service-apollo-meta-server
replicas: 1
selector:
matchLabels:
app: pod-apollo-config-server
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
app: pod-apollo-config-server
spec:
volumes:
- name: volume-configmap-apollo-config-server
configMap:
name: configmap-apollo-config-server
items:
- key: application-github.properties
path: application-github.properties
containers:
- image: ip:9000/apollo/apollo-configservice:latest #私人仓库
securityContext:
privileged: true
imagePullPolicy: IfNotPresent
name: container-apollo-config-server
ports:
- protocol: TCP
containerPort: 8080
volumeMounts:
- name: volume-configmap-apollo-config-server
mountPath: /apollo-config-server/config/application-github.properties
subPath: application-github.properties
env:
- name: APOLLO_CONFIG_SERVICE_NAME
value: "service-apollo-config-server.sre"
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 120
periodSeconds: 10
dnsPolicy: ClusterFirst
restartPolicy: Always
nodeName: apollo #nodes节点名称

3.apollo-portal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
js复制代码---
kind: ConfigMap
apiVersion: v1
metadata:
namespace: apollo
name: configmap-apollo-portal-server
data:
application-github.properties: |
spring.datasource.url = jdbc:postgresql://ip:5432/apolloportal
spring.datasource.username = apolloportal
spring.datasource.password = 123
apollo-env.properties: |
dev.meta=http://ip:8080 #上文的apollo-config 切记不用写localhost
---
kind: Service
apiVersion: v1
metadata:
namespace: apollo
name: service-apollo-portal-server
labels:
app: service-apollo-portal-server
spec:
ports:
- protocol: TCP
port: 8070
targetPort: 8070
nodePort: 8070
selector:
app: pod-apollo-portal-server
type: NodePort
sessionAffinity: ClientIP
---
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: apollo
name: deployment-apollo-portal-server
labels:
app: deployment-apollo-portal-server
spec:
replicas: 1
selector:
matchLabels:
app: pod-apollo-portal-server
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: pod-apollo-portal-server
spec:
volumes:
- name: volume-configmap-apollo-portal-server
configMap:
name: configmap-apollo-portal-server
items:
- key: application-github.properties
path: application-github.properties
- key: apollo-env.properties
path: apollo-env.properties
containers:
- image: ip:9000/apollo/apollo-portal:latest #私人仓库
securityContext:
privileged: true
imagePullPolicy: IfNotPresent
name: container-apollo-portal-server
ports:
- protocol: TCP
containerPort: 8070
volumeMounts:
- name: volume-configmap-apollo-portal-server
mountPath: /apollo-portal-server/config/application-github.properties
subPath: application-github.properties
- name: volume-configmap-apollo-portal-server
mountPath: /apollo-portal-server/config/apollo-env.properties
subPath: apollo-env.properties
env:
- name: APOLLO_PORTAL_SERVICE_NAME
value: "service-apollo-portal-server.sre"
readinessProbe:
tcpSocket:
port: 8070
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 8070
initialDelaySeconds: 120
periodSeconds: 15
dnsPolicy: ClusterFirst
restartPolicy: Always
nodeName: apollo #nodes地址

注意:以上yaml 一定注意格式,比如空行,空格,注释,能去掉一定去掉,否则会报各种错误。

注意 ,以下apollo-portal.zip中的配置也需要修改(否则一直会访问localhost)
在这里插入图片描述
在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言搬砖 viper配置文件库

发表于 2021-11-19

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」。

简介

继续介绍一个spf13大佬的项目viper用于读取配置文件的库

viper官网: github.com/spf13/viper

官网声称可以读取所有类型的配置和格式,还具有热加载配置功能,非常强大

特点

支持的配置类型和格式:

  • 支持读取JSON,TOML,YAML,HCL,envfile以及java properties配置文件
  • 支持读取环境变量
  • 支持读取远程组件的配置(etcd或者consul)
  • 支持Go命令行参数(flags)
  • 支持设置默认值和确定值
  • 不区分配置大小写

配置格式优先顺序:

  • explicit call to Set
  • flag
  • env
  • config
  • key/value store
  • default

使用

本文不会将所有配置类型和所有功能做演示,只演示自已常用的部分

安装

基于go model的方式安装

1
js复制代码go get -u github.com/spf13/viper

在代码中引入

1
js复制代码import "github.com/spf13/viper"

例子1(简单读取配置然后输出)

先写一份yaml的配置文件,取名为dev.yaml

1
2
3
4
5
6
7
8
yaml复制代码serverPort: 8808
logLevel: "INFO"
mysql:
host: "127.0.0.1"
port: 3306
database: "viper"
user: "viper"
password: "viper33"

编写demo,读取配置文件,然后打印出来

viper默认没有配置路径,类型和名称,需要自行定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
go复制代码package main

import (
"fmt"
"github.com/spf13/viper"
)

func main() {
//定义配置文件名称
viper.SetConfigName("dev")
//定义配置文件类型
viper.SetConfigType("yaml")
//定义配置路径
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("找不到配置文件,错误日志:: %w \n", err))
}

//读取值,并打印出来
fmt.Printf("serverPort: %v\n",viper.Get("serverPort"))
fmt.Printf("logLevel: %v\n",viper.Get("logLevel"))
fmt.Println("mysql info:",viper.Get("mysql.host"),
viper.Get("mysql.port"),viper.Get("mysql.database"),
viper.Get("mysql.user"),viper.Get("mysql.password"))
}

效果如下
image.png

例子2(热加载和默认值)

配置文件还是以例子1为例

设置默认值调用SetDefault,填入k/v既可,不过默认值是优先级最低的。如果配置文件或环境变量有 相同key,默认值就会失效

这里的热加载需要注意点是,,在修改了配置后,一定要Ctrl+c保存,否则配置不会变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
js复制代码package main

import (
"fmt"
"github.com/spf13/viper"
"time"
)

func main() {
//定义配置文件名称
viper.SetConfigName("dev")
//定义配置文件类型
viper.SetConfigType("yaml")
//定义配置路径
viper.AddConfigPath(".")
//定义默认值
viper.SetDefault("env","production")
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("找不到配置文件,错误日志:: %w \n", err))
}

//监听配置文件变化,自动热加载
viper.WatchConfig()
//读取自定义的默认值
fmt.Printf("env: %v\n",viper.Get("env"))
//测试配置变化
fmt.Printf("logLevel: %v\n",viper.Get("logLevel"))
time.Sleep(8 * time.Second)
fmt.Printf("logLevel: %v\n",viper.Get("logLevel"))
}

运行效果如下

image.png

远程配置

根据官网编写了读取远程etcd的demo,一直报错: panic: Remote Configurations Error: No Files Found,一直没有弄好

image.png

不过有位小哥的博客弄了另一套解决方案,传送门: blog.huoding.com/2020/08/10/…

nacos支持

另外还有大佬写了关于viper支持以nacos配置的库,非常不错哦
github.com/yoyofxteam/…

参考

github.com/spf13/viper

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Flask 入门系列之 数据库迁移!

发表于 2021-11-19

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战。

使用 Flask-Migrate 实现数据库迁移

在上篇文章 Flask 入门系列之 Flask-SQLAlchemy!提到的db.create_all()不会重新创建表或是更新表,需要先使用db.drop_all()删除数据库中所有的表之后再调用db.create_all()才能重新创建表,但是这样的话,原来表中的数据就都被删除了,这肯定是不行的,这时就出现了数据库迁移的概念。

在开发过程中,随着需求的变化,有可能需要添加或修改表的一些字段,但是原表中的数据不能删除,此时就需要创建新表,并将旧表中的数据迁移至新表中,Flask-Migrate这个扩展就可以在不破坏数据的情况下更新数据库表的结构,并完成数据从旧表到新表的迁移。

Flask-Migrate的使用

可以使用pip install flask-migrate进行安装。
在程序中,我们实例化 Flask_Migrate 提供的 Migrate 类,进行初始化操作。

1
2
3
4
5
6
7
8
9
python复制代码from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

···

db = SQLAlchemy(app)
migrate = Migrate(app, db)

实例化 Migrate 类,需要传入 Flask 实例 app 和 SQLAlchemy创建的实例 db。

数据库的迁移过程

先定义 User 模型类。

1
2
3
4
5
python复制代码class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
user_name = db.Column(db.String)
password = db.Column(db.String)

创建迁移环境

在开始迁移数据之前,需要先使用下面的命令创建一个迁移环境:

1
python复制代码flask db init

迁移环境只需创建一次,创建后会在项目根目录下生成一个 migrations 目录,其中包含了自动生成的配置文件和迁移版本目录。

image.png

生成迁移脚本

使用如下命令自动生成迁移脚本:

1
python复制代码flask db migrate -m "create_table"

-m 选项添加备注信息,执行后迁移版本目录生成了迁移脚本。

image.png

迁移脚本内有两个函数:

  • upgrade():把迁移中的改动应用到数据库中
  • downgrade():将改动撤销

自动生成的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容,不一定完全正确,有必要再进行检查一下。

更新数据库

生成了迁移脚本后,使用flask db upgrade命令可完成对数据库的更新。执行后即可生成数据库及表。

如果之后我们需要改动 user 表中的字段,比如添加一个mobile字段,我们只需在 User 模型类中添加该属性,之后执行flask db migrate -m '注释'和flask db upgrade命令即可。

1
2
3
4
5
6
python复制代码class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
user_name = db.Column(db.String)
password = db.Column(db.String)
mobile = db.Column(db.String)

image.png

image.png

如果想要回滚迁移的话,可以执行flask db downgrade命令。

总结

这里只是介绍如何在 Flask 中进行数据库迁移,关于在生产环境下,是否需要使用迁移工具或者使用何种工具进行迁移,这里不做讨论,至于我的话,在生产环境中,我没有使用过Flask-Migrate,而是选择编写 SQL 脚本来处理数据库及表的更新或改动,我觉得这样更不容易出错,其实各有各的好处,看自己选择。

原创不易,如果小伙伴们觉得有帮助,麻烦点个赞再走呗~

最后,感谢女朋友在工作和生活中的包容、理解与支持 !

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

leetcode 1043 Partition Array

发表于 2021-11-19

「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」

描述

Given an integer array arr, partition the array into (contiguous) subarrays of length at most k. After partitioning, each subarray has their values changed to become the maximum value of that subarray.

Return the largest sum of the given array after partitioning. Test cases are generated so that the answer fits in a 32-bit integer.

Example 1:

1
2
3
ini复制代码Input: arr = [1,15,7,9,2,5,10], k = 3
Output: 84
Explanation: arr becomes [15,15,15,9,10,10,10]

Example 2:

1
2
ini复制代码Input: arr = [1,4,1,5,7,3,6,1,9,9,3], k = 4
Output: 83

Example 3:

1
2
ini复制代码Input: arr = [1], k = 1
Output: 1

Note:

1
2
3
matlab复制代码1 <= arr.length <= 500
0 <= arr[i] <= 10^9
1 <= k <= arr.length

解析

根据题意,给定一个整数数组 arr,将该数组划分为几个长度至多为 k 的连续子数组。 分区后,每个子数组的值都变为该子数组的最大值。题目要求我们分区后返回新数组可能的最大和。 题意很清楚,结合例子一基本也就能完全理解题意了。

这道题是看了大神的解答才写出来的,使用的是一维动态规划数组解答,文尾有大神推到链接

  • 初始化长度为 len(arr) 的一维数组, dp[i] 表示的就是 arr[:i] 经过操作后得到的最大和值,
  • 遍历 range(N) ,也就是遍历 arr 中的所有元素位置索引 i ,再遍历 range(i,max(-1, i-k),-1) 也就是可能的子数组位置索引 j ,不断更新当前子数组的最大值 MAX,又不断更新 dp[i] = max(dp[i], dp[j-1] + MAX*(i-j+1)) ,注意边界条件的限制。
  • 遍历结束返回 dp[-1] 即为答案

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
python复制代码class Solution(object):
def maxSumAfterPartitioning(self, arr, k):
"""
:type arr: List[int]
:type k: int
:rtype: int
"""
N = len(arr)
dp = [0 for _ in range(N)]
for i in range(N):
MAX = 0
for j in range(i,max(-1, i-k),-1):
MAX = max(arr[j], MAX)
if j>=1:
dp[i] = max(dp[i], dp[j-1] + MAX*(i-j+1))
else:
dp[i] = max(dp[i], MAX*(i-j+1))
return dp[-1]

运行结果

1
2
erlang复制代码Runtime: 408 ms, faster than 61.04% of Python online submissions for Partition Array for Maximum Sum.
Memory Usage: 13.4 MB, less than 94.81% of Python online submissions for Partition Array for Maximum Sum.

原题链接:leetcode.com/problems/pa…

大神解释:www.bilibili.com/video/BV143…

您的支持是我最大的动力

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…283284285…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%