高可用网站架构演讲

什么是高可用

[第二页 什么是高可用]

是指以减少服务中断时间为目的技术统称。它通过保护用户的业务程序对外不间断地提供服务,把因为软件,硬件,人为造成的故障对业务的影响降低到最小程度。总而言之就是保证公司业务7*24小时不宕机。

高可用的衡量标准

通常用平均无故障时间(MTTF:mean time to failure)来衡量系统的可靠性,用平均故障维修时间(MTTR:Mean Time Between Failures)来度量系统的可维护性。于是可用性被定义为: HA=MTTF/(MTTF+MTTR)*100%。

简单理解:如果系统每运行100个时间单位,会有1个时间单位无法提供服务,我们说系统的可用性是99%。

衡量也受监测设备的局限,比如监测设备本身的故障、监测点的盲区、监测链路的网络故障等影响,不存在完美的衡量标准!

如何保障系统的高可用

[第三页 如何保障高可用]

我们都知道,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务肯定会受影响;如果有冗余备份,挂了还有其他backup能够顶上。

保证系统高可用,架构设计的核心准则是:冗余。

有了冗余之后,还不够,如果每次出现故障需要人工介入恢复势必会增加系统的不可服务时间。所以,又往往是通过“自动故障转移”来实现系统的高可用。

下面我们看看一般网站是如何做冗余的

[第四页 架构图]

图上是一个比较大型的网站架构图,可以看到里面大量使用到了分布式集群化部署,这种分布式集群化就是一种冗余架构。但是大部分中小网站目前还是采用单点形式,事实上分布式集群并不适合所有场景。

下面来想想看为什么? (为什么分布式集群并不适合所有场景?)

[第五页 为什么分布式集群不适合所有场景? ]

在网站初期考虑到人员、成本、项目复杂度等因素,一般会采用单点形式。

先来通俗解释一下什么是分布式:原本需要一个人干的事,现在分给n个人干!

但问题来了,既然分给了n个人,那就涉及到这些人的沟通交流协作问题。

如何来解决这些问题,就需要先聊聊分布式系统中的CAP理论。

CAP定理

[第六页 CAP理论 ]

我们先来了解一下这个理论产生的历史,CAP定理,也叫布鲁尔定理。

这个定理起源于加州大学柏克莱分校的计算机科学家埃里克·布鲁尔在2000年的分布式计算原理研讨会上提出的一个猜想。 在2002年,MIT麻省理工学院的赛斯·吉尔伯特 和 南希·林奇 发表了 布鲁尔猜想 的证明,使之成为一个定理。

事实上 吉尔伯特 和 林奇 证明的CAP定理比 布鲁尔 设想的某种程度上更加狭义。定理讨论了在两个互相矛盾的请求到达彼此连接不通的两个不同的分布式节点的时候的处理方案。

埃里克·布鲁尔是加州大学伯克利分校的终身教授。 1996年,布鲁尔创立了Inktomi公司(该公司开发了Traffic Server,这是一种代理服务器 Web缓存,用于万维网流量和按需流媒体,它们可以将图像转码为较小的尺寸,以供拨号Internet访问的用户使用。Traffic Server由包括AOL(American Online美国在线)在内的数家大型ISP部署。 在2003年互联网泡沫破灭之后,该公司以被2.41亿美元Yahoo!收购。)在2000年他与比尔·克林顿合作,他帮助创建USA.gov。在大约1990年代末的分布式网络应用中,他由于制定CAP定理而闻名。目前,他在谷歌任职。

下面我们来看看CAP定理

[第七页 CAP定理 ]

CAP定理指的是一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

一致性

[第八页 一致性]

一致性,意思是写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。

接下来,用户的读操作就会得到 v1。这就叫一致性。

问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。

为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。

这样的话,用户向 G2 发起读操作,也能得到 v1。

可用性

可用性,意思是只要收到用户的请求,服务器就必须给出回应。

用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。

一致性和可用性的矛盾

[第九页 一致性和可用性的矛盾]

一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。

如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。

如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。

综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。

一致性和可用性权衡

【讨论1】在什么场合,可用性高于一致性?

CDN,新闻资讯,静态资源,搜索引擎。。。

【讨论2】在什么场合,一致性高于可用性?

电商订单,银行系统,证券金融系统,购票系统。。。

讨论完毕,就要引出BASE理论了

BASE理论

[第十页 BASE理论]

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

  • 基本可用(basically available):在分布式系统出现,允许损失部分可用性(服务降级、页面降级)
  • 软状态(soft state):允许分布式系统出现中间状态,而且中间状态不影响系统的可用性
  • 最终一致性(Eventually consistent):数据复制经过一段时间达到一致性

分布式系统是一个非常广泛的概念,它最终要落实到解决实际问题上,不同的问题有不同的方法和架构。

但如果以算法划分,到能分出几类:

比如经过严格数学证明的,分布式一致性算法paxos。

1.以Leader选举为主的分布式一致性算法,比如paxos、viewstamp,就是现在zookeeper、Chuby等工具的主体 2.以分布式事务为主的一类主要是二段提交,这些分布式数据库管理器及数据库都支持 3.以若一致性为主的,主要代表是Cassandra的W、R、N可调节的一致性 4.以租赁机制为主的,主要是一些分布式锁的概念,目前还没有看到纯粹“分布式”锁的实现 5.以失败探测为主的,主要是Gossip和phi失败探测算法,当然也包括简单的心跳 6.以弱一致性、因果一致性、顺序一致性为主的,开源尚不多,但大都应用在Linkedin、Twitter、Facebook等公司内部 7.以异步解耦为主的,有各类Queue

系统工程

[第十一页 系统工程]

前面扯了些理论的东西,要落实到实际工程领域,将有着很多系统性的挑战。

比如图上展示的:要保障高可用,我们需要从硬件、监测、运维、开发等多个领域去努力。

当然这将是一个非常大的课题,今天我们只粗浅的聊聊,在工程领域是如何保障高可用的。

硬件

[第十二页 硬件]

首先是硬件,在绝大部分系统架构中,都离不开3样硬件:防火墙、交换机、服务器。

防火墙

[第十三页 防火墙]

我们先看看防火墙,防火墙是保障系统架构安全可用性的重要屏障,阻挡大部分的黑客攻击是通过防火墙来实现的,防火墙分硬件防火墙和软件防火墙。

硬件防火墙常见的品牌,如图所示。如果做企业采购,我们一般从稳定可靠、 负载均衡、 双机热备、 优异性能、 售后支持这几个方面来考虑。

软件防火墙事实上有很多,大致分服务器级和用户终端级,早期还有专门的软件防火墙售卖,不过最近几年大多数操作系统都已集成性能不错的软件防火墙。

下面给大家演示一下Windows10里的防火墙配置。

交换机、路由器

[第十四页 交换机、路由器]

交换机和路由器,我们似乎经常接触,平常给我们感觉可能是同一样东西。因为家用级的路由器往往还有多个交换功能口。但在企业级网络中,这两者会用独立的硬件去处理。

常见的交换机、路由器品牌,如图所示。

这里我们要提一下,衡量交换机好坏,除了稳定性以外,还有个重要指标就是吞吐量。常见的百兆、千兆,企业级会用万兆、十万兆甚至更高。注意这里的兆是指兆比特。与我们通常理解的文件字节大小,要除以8来换算。

服务器

[第十五页 服务器]

服务器主要分大型机、小型机、X86架构服务器等,如图所示。

我们常用的当然是X86服务器。

在服务器领域,我们往往会遇到配置选择问题,如何去衡量一个服务器配置是否合理?

接下来就要引出一个概念:木桶效应

木桶效应

[第十六页 木桶效应]

木桶效应也称短板理论,原是管理学上的一个概念,想必大家都理解,当然它在硬件领域同样适用。

不过我们今天聊的是:反木桶效应!

如图所示,反木桶效应,就是在硬件性能出现瓶颈的地方,尽可能的通过软件去规避这种瓶颈。

比如我们常见的cache缓存,就是利用内存资源,去规避DiskIO上的性能瓶颈。

比如多线程,就是利用多核心来规避CPU主频的性能瓶颈。

这种体现在开发领域就是我们常说的性能优化。

性能优化也是我们今天重点讨论的问题,在开始这个问题前,我先提个问题。

一次访问经历了什么?

[第十七页 一次访问经历了什么]

我们来思考一个问题,用户访问一个网站到底经历了什么?如图。

请人回答。。。时间控制在3分钟!

HOSTS

[第十八页 HOSTS]

Hosts是我们经常忽视的一点,事实上输入URL后首先去找本地的Hosts文件,当Hosts里不存在,才会去请求DNS。

DNS

[第十九页 DNS]

DNS域名解析是互联网的根基,比如图上列表展示的是一些公共DNS。我们可以通过命令nslookup去手动查询DNS解析情况。

DNS劫持

[第二十页 DNS劫持]

全球的DNS网络是一项非常复杂的技术,这里就不展开了。

我们来聊一下DNS劫持,DNS劫持也叫域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现劫持用户访问的目的。

我们可以从新闻媒体经常了解到,某某互联网公司网站被DNS劫持了,导致大量用户访问不了,或者被钓鱼攻击。比如:。。。

DNS高可用

[第21页 DNS轮询]

在高可用方面,DNS同样也做到了,比如我们可以为域名设置轮询,把用户访问导向最近的服务器IP上。如图所示。

DNS缓存

[第22页 DNS缓存]

DNS缓存也是一种高可用方案,我们访问网站的时候,系统会根据域名的配置,去自动缓存解析结果,通常会设定一个TTL缓存时间。

我们也可以通过命令去手动清除它。

[第23页 DNS缓存2]

如图所示,浏览器为了优化性能,也帮我们缓存了DNS解析数据。

网络质量

[第24页 网络质量]

解析完DNS,下一步要传输数据,传输数据的过程我们要关注:延迟、带宽、可用率等。

比如通过图上命令,去查看延迟、带宽。

[第25页 网络吞吐量]

我们可以通过如图所示的命令或工具来观察网络吞吐量等性能指标。

经过一系列的网络转发就到达我们服务器了。

运维与架构

[第26页 运维与架构]

运维

运维高可用主要体现在 故障发现机制 和 系统恢复机制。

故障发现机制比如:

报警的移动化

报警的实时化

监控的可视化

系统恢复机制比如:

回滚、重启、扩容、主备切换、集群迁移、异地容灾等等机制。

当然这些以后可以由我们运维工程师***为大家讲解。

架构

我们来看看架构这块,如图所示。如果大家理解了之前讲到的CAP定理和BASE理论,就能明白这张架构图的意义了。

比如,我们经常提到的动静分离,静态资源往往不会经常变动,而且是无状态的,所以适用于CAP定理中的AP,也就是高可用性。

像MySql集群,如果需要线程安全的场景,比如减库存,那么一定要保证一致性,也就是CAP定理中的CP。

像全文检索引擎 ElasticSearch,它的集群机制比如副本复制,故障迁移,就是基于BASE理论。

静态资源实现高可用最常见的就是部署CDN,但企业自己建设CDN往往成本巨大,一般都会选用一些专业的服务商,比如著名的CDN服务商cloudflare

[第27页 cloudflare]

事实上有很多CDN服务商,国内比较有名的有 阿里云、腾讯云、七牛云、新浪云、网宿科技、金山云等

[第26页 运维与架构]

图上所示的集群架构,需要大量的物理设备和人力维护成本,所以最近几年特别是中小企业,随着业务成本的考量,大家都从自建机房迁移到了云端。

[第28页 云计算]

高可用的低成本解决方案:

云计算的出现,让企业对高可用的需求渐渐摆脱了运维与人力的束缚,我们甚至可以通过几次点击很方便的用一个PAAS平台,来部署一个高可用网站。

现如今,比如我们公司,就使用了阿里云。

前面讲了些理论的东西,下面讲点实际工作中需要注意的优化项。

[第29页 如何做好高可用]

大家觉得自己工作中该如何做好高可用?

请大家回答,3分钟

下面从前端和后端讲讲,保障高可用的一些技术点:

前端:

后端:

数据库技术选型的时候面临:MySql、Mongodb如何抉择?

你期望一个更高的写负载

默认情况下,对比事务安全,MongoDB更关注高的插入速度。如果你需要加载大量低价值的业务数据,那么MongoDB将很适合你的用例。但是必须避免在要求高事务安全的情景下使用MongoDB,比如一个1000万的交易。

不可靠环境保证高可用性

设置副本集(主-从服务器设置)不仅方便而且很快,此外,使用MongoDB还可以快速、安全及自动化的实现节点(或数据中心)故障转移。

未来会有一个很大的规模

数据库扩展是非常有挑战性的,当单表格大小达到5-10GB时,MySQL表格性能会毫无疑问的降低。如果你需要分片并且分割你的数据库,MongoDB将很容易实现这一点。

使用基于位置的数据查询

MongoDB支持二维空间索引,因此可以快速及精确的从指定位置获取数据。

非结构化数据的爆发增长

给RDBMS(关系数据库管理系统)增加列在有些情况下可能锁定整个数据库,或者增加负载从而导致性能下降,这个问题通常发生在表格大于1GB的情况下。鉴于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响,整个过程会非常快速;因此,在应用程序发生改变时,你不需要专门的1个DBA去修改数据库模式。

缺少专业的数据库管理员

如果你没有专业的DBA,同时你也不需要结构化你的数据及做join查询,MongoDB将会是你的首选。MongoDB非常适合类的持久化,类可以被序列化成JSON并储存在MongoDB。需要注意的是,如果期望获得一个更大的规模,你必须要了解一些最佳实践来避免走入误区。

[第42页 电商秒杀]

过年的时候正好遇到电商秒杀的业务需求,这里顺便聊聊。

秒杀活动将在较短时间内产生比平时大数十倍,上百倍的页面访问流量和下单请求流量。

秒杀活动可以分为3个阶段:

  • 秒杀前:用户不断刷新商品详情页,页面请求达到瞬时峰值。
  • 秒杀开始:用户点击秒杀按钮,下单请求达到瞬时峰值。
  • 秒杀后:一部分成功下单的用户不断刷新订单或者产生退单操作,大部分用户继续刷新商品详情页等待退单机会。

消费者提交订单,一般做法是利用数据库的行级锁,只有抢到锁的请求可以进行库存查询和下单操作。但是在高并发的情况下,数据库无法承担如此大的请求,往往会使整个服务blocked阻塞,在消费者看来就是服务器宕机。

秒杀系统的流量虽然很高,但是实际有效流量是十分有限的。利用系统的层次结构,在每个阶段提前校验,拦截无效流量,可以减少大量无效的流量涌入数据库。

利用浏览器缓存和CDN抗压静态页面流量

秒杀前,用户不断刷新商品详情页,造成大量的页面请求。所以,我们需要把秒杀商品详情页与普通的商品详情页分开。对于秒杀商品详情页尽量将能静态化的元素静态化处理,除了秒杀按钮需要服务端进行动态判断,其他的静态数据可以缓存在浏览器和CDN上。这样,秒杀前刷新页面导致的流量进入服务端的流量只有很小的一部分。

利用读写分离Redis缓存拦截流量

CDN是第一级流量拦截,第二级流量拦截我们使用支持读写分离的Redis。在这一阶段我们主要读取数据,读写分离Redis能支持高达60万以上qps,完全可以支持需求。

[第43页 电商秒杀技术要点]

静态分离

前端处理

独立服务

限流

缓存

队列

分布式锁

作弊对抗

CC/DDOS攻击

并发测试

[第44页 总结如何做好高可用]

前端优化效果最好用户体验好

开发过程利用反木桶理论

数据库性能瓶颈重点关注

并发测试,覆盖测试

运维与监测

技术团队开发规范

本规范作用,促进团队合作,降低维护成本,提高团队开发效率。

适用范围,Web技术团队:后端程序员,前端程序员。

代码审查不以代码量为目的,请写出简洁,优雅,结构清晰的代码。

1.统一开发环境

操作系统:

​ 开发机:Windows 7 或 Windows 10

​ 服务器:Ubuntu 16.04

Java版本: JDK 1.8.0_26x

Java框架:Spring Boot 2.2.x、MyBatis

PHP版本:5.6.x

PHP框架:ThinkPHP 5.1.x

依赖管理:Maven 3.6.x

前端框架:Vue 2.x、Node 12.18.x

数据库:MySQL 5.7 存储引擎:InnoDB

中间件:Redis 6.x

代码库管理:Git

2.技术规范细节

2.1 前后分离

为保证开发进度和可复用性,整体项目采用前后分离并行开发。

2.2 上传资源分离

客户上传文件与项目分离,比如用户上传的文件,使用单独服务器接收,数据库只保存文件相对路径。

2.3 前端

浏览器兼容,须支持主流浏览器,IE9+。

静态文件均归档至约定的目录中。

字符编码统一指定为’UTF-8’。

尽可能组件化开发,降低代码量,提高复用率。

2.4 并发性能

项目需承受一定并发能力,需对关键接口做并发测试。

热点数据做缓存处理,且要考虑数据一致性。

内存、连接池、锁等系统资源及时释放。

接口做幂等处理。

2.5 微服务

如果技术允许,可采用微服务架构。

2.6 安全

接口处做入参检查,防止SQL注入。

一些关键业务接口如短信发送,须做机器防刷机制。

源码保证可编译的情况下,每天定时提交。

数据库连接信息不可外泄,由专人管理。

数据库用户密码等关键数据需做加密处理,不可明文保存。

2.7 命名

命名具有语义化,PHP项目遵循PSR-2代码规范。

类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。

变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。

方法名规范:同变量名。

文件命名:同类名。

数据库命名:数据库命名的时候尽量保证数据库的名称和项目工程的名称一致,全部使用小写字母,数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头。

2.8 注释

每个功能函数、方法、接口、类都要有对应注释,各个关键业务代码段,也需注释。

2.9 数据库

数据库设计满足数据库三范式

第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;

第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;

第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余;

没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。

正确认识数据冗余

主键与外键在多表中的重复出现, 不属于数据冗余。非键字段的重复出现, 才是数据冗余!而且是一种低级冗余,即重复性的冗余。高级冗余不是字段的重复出现,而是字段的派生出现。

例:商品中的“单价、数量、金额”三个字段,“金额”就是由“单价”乘以“数量”派生出来的,它就是冗余,而且是一种高级冗余。冗余的目的是为了提高处理速度。只有低级冗余才会增加数据的不一致性,因为同一数据,可能从不同时间、地点、角色上多次录入。因此,我们提倡高级冗余(派生性冗余),反对低级冗余(重复性冗余)。

使用“三少原则”防止数据库打补丁式设计

一个数据库中表的个数越少越好。只有表的个数少了,才能说明系统的E-R图少而精,去掉了重复的多余的实体,形成了对客观世界的高度抽象,进行了系统的数据集成,防止了打补丁式的设计;

一个表中组合主键的字段个数越少越好。因为主键的作用,一是建主键索引,二是做为子表的外键,所以组合主键的字段个数少了,不仅节省了运行时间,而且节省了索引存储空间;

一个表中的字段个数越少越好。只有字段的个数少了,才能说明在系统中不存在数据重复,且很少有数据冗余,更重要的是督促设计者学会将主表中的一部分内容拉出去,另外单独建一个子表;

适当使用索引,合理使用联合索引

省市区、经纬地图坐标统一

省市区参考全国统计用区划代码和城乡划分代码

2.10 日志

JAVA服务端日志使用lombok->Slf4j注解,存放至:/var/log/spring/项目名/模块名.log。

PHP服务端日志统一存放在 项目路径/runtime/log/ 下。

2.11 JAVA

善用lombok简化代码量。

统一入参处理,使用 jsr 303 规范验证入参。

统一异常处理。

统一分页处理。

2.12 接口

统一接口返回结构(Java可使用mtool工具类):

{
  "code": 200,
  "message": "success",
  "data": Object
}

code: 返回状态码

参考 RFC 2616 规范

  code区间 类型 含义
1** 100-199 信息 服务器接收到请求,需要请求者继续执行操作
2** 200-299 成功 请求被成功接收并处理
3** 300-399 重定向 需要进一步的操作以完成请求
4** 400-499 客户端错误 请求包含语法错误或无法完成请求
5** 500-599 服务器错误 服务器在处理的时候发生错误

message:状态描述

data: 返回数据,数据类型根据实际项目约定。

Redisson

Spring Boot快速集成Redisson

使用redisson-spring-boot-starter快速上手,Redisson支持Redis 2.8以上版本,支持Java1.6+以上版本。

pom.xml

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.3</version>
</dependency>

application.yml

spring:
  profiles: dev
  redis:
    redisson:
      config: classpath:redisson-dev.yaml

redisson-dev.yaml

singleServerConfig:   # 单实例模式
  idleConnectionTimeout: 10000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  password: null
  subscriptionsPerConnection: 1
  clientName: null
  address: "redis://127.0.0.1:6379"
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 1
  connectionMinimumIdleSize: 1
  connectionPoolSize: 5
  database: 0
  dnsMonitoringInterval: 5000
threads: 1
nettyThreads: 1
codec: null
transportMode: "NIO"
lockWatchdogTimeout: 30000  # 监控锁的看门狗超时,单位:毫秒

Redisson支持以下模式

集群模式

复制模式

单实例模式

哨兵模式

主从模式

代理模式

通用参数

threads

默认值:16

RTopic对象的所有侦听器RRemoteServiceRTopic对象和RExecutorService任务的调用处理程序之间共享的线程数量。

nettyThreads

默认值:32

Redisson使用的所有Redis客户端之间共享的线程数量。Redis响应解码和命令发送中使用的Netty线程。0=cores_amount * 2

codec

Redis数据编解码器。在读写Redis数据期间使用。几种实现方式提供

transportMode

默认值: TransportMode.NIO

可用值: TransportMode.NIO TransportMode.EPOLL - 需要netty-transport-native-epoll在类路径 TransportMode.KQUEUE - 需要netty-transport-native-kqueue在类路径

lockWatchdogTimeout(监控锁的看门狗超时,单位:毫秒)

默认值:30000

监控锁的看门狗超时时间单位为毫秒。该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况。如果该看门狗未使用lockWatchdogTimeout去重新调整一个分布式锁的lockWatchdogTimeout超时,那么这个锁将变为失效状态。这个参数可以用来避免由Redisson客户端节点宕机或其他原因造成死锁的情况。

分布式锁

可重入锁

lock

/**
 * RLock分布式可重入锁,接口场景并发测试
 * @return
 */
@RequestMapping("lock2")
public R lock2() {
    RLock lock = redissonClient.getLock("redisson_lock2");
    lock.lock();
    lock.unlock();
    return R();
}

Redisson客户端节点宕机或出现异常会造成死锁,为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒,可修改配置参数lockWatchdogTimeout来重新指定。

指定自动解锁时间

注意:如果设置自动解锁时间,那么看门狗就失效了

// Redisson可通过加锁的方法提供的leaseTime的参数来指定加锁的时间,超过这个时间后锁便自动解开。
lock.lock(10, TimeUnit.SECONDS);

tryLock

/**
 * RLock分布式可重入锁,tryLock
 *
 * @return
 */
@RequestMapping("lock5")
public R lock5() throws InterruptedException {
    RLock lock = redissonClient.getLock("redisson_lock5");
    boolean getLockOK = lock.tryLock();
    if (getLockOK) {
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        } finally {
            lock.unlock();
        }
        return R("获取到锁");
    } else {
        return R("没有获取到锁");
    }
}

lock -> 调用后一直阻塞到获得锁

tryLock -> 尝试是否能获得锁 如果不能获得立即返回

lockInterruptibly -> 调用后一直阻塞到获得锁 但是接受中断信号

synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock 在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock 时需要在 finally块中释放锁。

Zookeeper

zookeeper可视化管理工具

ZooInspector

下载地址:https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip

执行文件路径 ZooInspector\build

启动:

java -jar zookeeper-dev-ZooInspector.jar

IDEA Zookeeper管理工具

插件里搜索Zookeeper即可

zookeeper集群

为了方便演示,我们在windows下建立单机集群

1.创建配置文件

在目录 apache-zookeeper-3.6.1-bin\conf 下创建

zoo1.cfg

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/zookeeper/data1
clientPort=2181

server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883

zoo2.cfg

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/zookeeper/data2
clientPort=2182

server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883

zoo3.cfg

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/zookeeper/data3
clientPort=2183

server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883

配置说明

tickTime:基本事件单元,这个时间是作为Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,每隔tickTime时间就会发送一个心跳;最小 的session过期时间为2倍tickTime

initLimit:允许follower连接并同步到Leader的初始化连接时间,以tickTime为单位。当初始化连接时间超过该值,则表示连接失败。

syncLimit:表示Leader与Follower之间发送消息时,请求和应答时间长度。如果follower在设置时间内不能与leader通信,那么此follower将会被丢弃。

dataDir:存储内存中数据库快照的位置,除非另有说明,否则指向数据库更新的事务日志。注意:应该谨慎的选择日志存放的位置,使用专用的日志存储设备能够大大提高系统的性能,如果将日志存储在比较繁忙的存储设备上,那么将会很大程度上影像系统性能。

clientPort:监听客户端连接的端口。

server.A=B:C:D

    A:其中 A 是一个数字,表示这个是服务器的编号;

    B:是这个服务器的 ip 地址;

    C:Leader选举的端口;

    D:Zookeeper服务器之间的通信端口。

2.在目录 C:\zookeeper\ 下创建目录 和 文件,分别对应server.id

C:\zookeeper\data1\myid

1

C:\zookeeper\data2\myid

2

C:\zookeeper\data3\myid

3

3.修改启动脚本

由于 zkServer.cmd 里默认使用conf/zoo.cfg配置文件,集群启动需要读取不同的配置文件

分别拷贝 zkServer.cmd 到 zkServer1.cmd zkServer2.cmd zkServer3.cmd

把里面 %ZOOCFG% 变量指定对应的配置文件 ../conf/zoo1.cfg ../conf/zoo2.cfg ../conf/zoo3.cfg

启动zookeeper集群.cmd

start zkServer1.cmd
start zkServer2.cmd
start zkServer3.cmd

4.客户端连接验证

.\zkCli.cmd -server localhost:2181

.\zkCli.cmd -server localhost:2182

.\zkCli.cmd -server localhost:2183

zookeeper的ACL权限控制

acl权限控制,使用 scheme:id:permission 来标识,主要涵盖3个方面:

  • 权限模式(scheme):授权的策略
  • 授权对象(id):授权的对象
  • 权限(permission):授予的权限

权限模式scheme

  • 采用何种方式授权
方案 描述
world 只有一个用户:anyone,代表登录zookeeper所有人(默认)
ip 对客户端使用IP地址认证
auth 使用已添加认证的用户认证
digest 使用”用户名:密码”方式认证

授权对象id

  • 给谁授予权限
  • 授权对象ID是指,权限赋予的实体,例如:IP地址或用户

权限permission

  • 授予什么权限
权限 ACL简写 描述
create c 可以创建子结点
delete d 可以删除子结点(仅下一级结点)
read r 可以读取结点数据以及显示子结点列表
write w 可以设置结点数据
admin a 可以设置结点访问控制权限列表

授权的相关命令

命令 使用方式 描述
getAcl getAcl 读取ACL权限
setAcl setAcl 设置ACL权限
addauth addauth 添加认证用户

eg.

create /zk "zk"                   # 初始化测试用的结点
addauth digest malu:123456        # 添加认证用户
setAcl /zk auth:malu:cdrwa        # 设置认证用户
quit                              # 退出后再./zkCli.sh 进入
get /zk                           # 这个时候就没有权限了,需要再次认证
addauth digest malu:123456        # 认证,密码错了的话 zookeeper 不会报错,但是不能认证
get /zk

多种授权模式

仅需逗号隔开

setAcl /zk ip:192.168.1.27:cdrwa,auth:malu:cdrwa,digest:malu:pl570Xd7m2mTrsmMA9zKyO3hCAU=:cdrwa

ACL超级管理员

  • zookeeper的权限管理模式有一种叫做super,该模式提供一个超管,可以方便的访问任何权限的节点

    假设这个超管是supper:admin,需要为超管生产密码的密文

    echo -n super:admin | openssl dgst -binary -sha1 | openssl base64
    
  • 那么打开zookeeper目录下/bin/zkServer.sh服务器脚本文件,找到如下一行:

     /nohup # 快速查找,可以看到如下
     nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"
    
  • 这个就算脚本中启动zookeeper的命令,默认只有以上两个配置项,我们需要添加一个超管的配置项

    "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="
    
  • 修改后命令变成如下

    nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="
    
    # 重起后,现在随便对任意节点添加权限限制
    setAcl /zk ip:192.168.1.21:cdrwa # 这个ip并非本机
    # 现在当前用户没有权限了
    getAcl /zk
    # 登录超管
    addauth digest super:admin
    # 强行操作节点
    get /hadoop
    

高并发

基准测试

环境

ThinkPad E14 
CPU: AMD 4500U
内存: 16G
JDK: 1.8
测试工具:Jmeter
负载:1000线程  ramp-up 5秒 1000循环次数

执行语句

jmeter -n -t 测试计划.jmx -l results.log -e -o out

eg.1

@RequestMapping("buy/get")
public String buy_get() {
    Jedis jedis = jedisPool.getResource();
    String res = jedis.get("jedis:test-99");
    jedis.close();
    return res;
}

TPS: 13926.99 13899.51 18116.93

eg.2

@RequestMapping("buy/get2")
public String buy_get2() {
    return RedisUtil.get("jedis:test-99");
}

TPS: 14203.74 18032.31 14112.73

eg.3

@RequestMapping("buy/get3")
public String buy_get3() {
    ValueOperations operations = redisTemplate.opsForValue();
    return (String) operations.get("RedisTemplate:test-99");
}

TPS: 11842.17 11517.69 11489.50

eg.4

@RequestMapping("buy/get4")
public String buy_get4() {
    return RedisUtil2.strings().get("jedis:test-99");
}

TPS: 17779.36 12976.56 17614.63

eg.5

@RequestMapping("buy/get5")
public R buy_get5() {
    return R("malu99");
}

TPS: 18562.52 21023.86 20304.16

eg.6

@RequestMapping("buy/get6")
public String buy_get6() {
    return "malu99";
}

TPS: 21628.64 23030.86 22103.36

加入负载因子(限流计数器)

eg.1

加入负载因子对性能几乎没损耗,不过该方案线程不安全

private static int l = 0;
private static long my = 0;

@RequestMapping("buy/factor1")
public String buy_factor1() {
    l++;
    if ((l % 10) == 0) {
        l = 0;
        my++;
        return "malu"+my;
    } else {
        return "error";
    }
}

TPS: 22849.83 22617.27 21693.86

返回结果:

第一次 第二次 第三次
malu99654 malu99671 malu99517

eg.2

接下来写入service来做更多的测试,首先测没有负载因子的情况:

buyController

@Autowired
buyService buyService;

@RequestMapping("buy/factor2")
public R buy_factor2() {
    return buyService.factor2();
}

buyService

public interface buyService {
    R factor2();
}

buyServiceImpl

@Service
public class buyServiceImpl implements buyService {
    @Override
    public R factor2() {
        return R();
    }
}

TPS: 19955.70 19022.98 20781.38

eg.3

使用私有静态属性,在多线程情况下不安全:

private static int l = 0;
private static long my = 0;

@Override
public R factor3(){
    l++;
    if ((l % 10) == 0) {
        l = 0;
        my++;
        return R("malu"+my);
    } else {
        return R("error");
    }
}

TPS: 22611.13 20362.87 19967.25

返回结果:

第一次 第二次 第三次
malu99815 malu99885 malu99852

eg.4

为了线程安全,首先想到的是同步方法

/**
 * 同步方法
 *
 * @return
 */
@Override
synchronized public R factor4() {
    l++;
    if ((l % 10) == 0) {
        l = 0;
        my++;
        return R("malu" + my);
    } else {
        return R("error");
    }
}

TPS: 18390.13 19664.91 18653.93

返回结果(线程安全):

第一次 第二次 第三次
malu100001 malu100001 malu100001

eg.5

同步代码段一样可以

/**
 * 同步代码段
 *
 * @return
 */
@Override
public R factor5() {
    synchronized (buyServiceImpl.class) {
        l++;
        if ((l % 10) == 0) {
            l = 0;
            my++;
            return R("malu" + my);
        } else {
            return R("error");
        }
    }
}

TPS: 19155.99 20210.59 20727.11

返回结果(线程安全):

第一次 第二次 第三次
malu100001 malu100001 malu100001

eg.6

让我们试试原子类AtomicLong

//记录实际累加的数量
private static AtomicLong successNum = new AtomicLong(0);

/**
 * 原子类 AtomicLong
 *
 * @return
 */
@Override
public R factor6() {
    if ((successNum.incrementAndGet() % 10) == 0) {
        my++;
        return R("malu" + my);
    } else {
        return R("error");
    }
}

TPS: 20164.14 19359.96 19009.24

返回结果(线程安全):

第一次 第二次 第三次
malu100001 malu100001 malu100001

eg.7

来看JDK8里的LongAdder

private static LongAdder longAdder = new LongAdder();

/**
 * LongAdder
 *
 * @return
 */
@Override
public R factor7() {
    longAdder.increment();
    if ((longAdder.sum() % 10) == 0) {
        my++;
        return R("malu" + my);
    } else {
        return R("error");
    }
}

TPS: 18867.57 18704.17 20490.54

返回结果(没有锁,这样使用线程不安全):

第一次 第二次 第三次
malu99766 malu99868 malu99664

zookeeper分布式锁

互斥锁(排他锁)

ZkConfig.java

/**
 * 类说明: zookeeper配置
 *
 * @author : Malu
 * @Since : 2020-07-30 15:15
 **/
@Configuration
public class ZkConfig {
    @Bean(initMethod = "start")
    public CuratorFramework curatorFramework() {
        return CuratorFrameworkFactory.newClient(
                "192.168.3.27:2181",
                5000,
                5000,
                new RetryNTimes(5, 5000));
    }
}

ZkController.java

@Autowired
private CuratorFramework curatorFramework;

/**
 * zookeeper互斥锁(排他锁),高并发测试
 *
 * @return R
 */
@GetMapping("/mutex2")
public R getMutex2() throws Exception {
    // 获取一个分布式互斥锁
    InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/mutex_lock2");
    try {
        // 开启两个进程测试,会发现:如果一个分布式互斥锁获取了锁,那么直到锁释放为止数据都不会被侵扰
        lock.acquire();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 在finally中释放,防止死锁
        lock.release();
    }
    return R();
}

环境:

CPU: i5 4590 @ 3.30 Ghz
内存: 32G
JDK: 1.8
测试工具:Jmeter
负载:200线程  ramp-up 5秒 10000循环次数

Windows10 本机zookeeper

TPS: 755.19

redisson分布式锁

pom.xml

<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.3</version>
</dependency>

application.yml

spring:
  redis:
    host: 192.168.1.47
    port: 6381
    database: 15
    timeout: 10000

注意:Redis密码为空时不要加password参数,spring-boot自动装配会报错。

redissonController.java

@Autowired
private RedissonClient redissonClient;

@RequestMapping("lock2")
public R lock2() {
    RLock lock = redissonClient.getLock("redisson_lock2");
    lock.lock();
    lock.unlock();
    return R();
}

环境:

CPU: i5 4590 @ 3.30 Ghz
内存: 32G
JDK: 1.8
测试工具:Jmeter
负载:200线程  ramp-up 5秒 10000循环次数

独立Linux Redis

TPS: 1594.7 1594.08

可重入锁(tryLock)

/**
 * RLock分布式可重入锁,tryLock
 *
 * @return
 */
@RequestMapping("lock5")
public R lock5() throws InterruptedException {
    RLock lock = redissonClient.getLock("redisson_lock5");
    boolean getLockOK = lock.tryLock();
    if (getLockOK) {
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        } finally {
            lock.unlock();
        }
        return R("获取到锁");
    } else {
        return R("没有获取到锁");
    }
}

环境:

CPU: i5 4590 @ 3.30 Ghz
内存: 32G
JDK: 1.8
测试工具:Jmeter
负载:200线程  ramp-up 5秒 10000循环次数

本机Windows Redis

TPS: 1913.48

环境:

CPU: i5 4590 @ 3.30 Ghz
内存: 32G
JDK: 1.8
测试工具:Jmeter
负载:200线程  ramp-up 5秒 10000循环次数

独立Linux Redis

TPS: 5751.29 5553.32 5686.2

参考

使用Redis搭建电商秒杀系统 https://tech.antfin.com/docs/2/63920

Redis高并发秒杀测试 https://github.com/14251104246/redis-demo

Dataway

快速创建接口神器Dataway!

接口文档生成器

YApi

源码地址:https://github.com/YMFE/yapi

安装文档:https://hellosean1025.github.io/yapi/

安装过程参考:https://zhuanlan.zhihu.com/p/94297858

默认端口如果被占用,可修改配置:

~/.nvm/versions/node/v16.14.0/lib/node_modules/yapi-cli/src/commands/server.js 中的:
app.listen(3001)

Apifox

支持 API 文档定义、API Mock、API 自动化测试

首页:https://www.apifox.cn/

使用文档:https://www.apifox.cn/help/

Swagger3

结合Spring Boot快速开发

pom.xml

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

SwaggerConfig.java

/**
 * 类说明: Swagger3配置文件
 *
 * @author : Malu
 * @Since : 2020-08-31 17:49
 **/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket customDocket() {
        return new Docket(DocumentationType.OAS_30)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.bric.datacloud.dataway.controller")) //Selection by RequestHandler
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("应用接口")
                .description("欢迎访问")
                .contact(new Contact("malu", "http://malu.me", "malu@malu.me"))
                .version("1.0")
                .build();
    }
}

Swagger2

<!-- swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${swagger.version}</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${swagger.version}</version>
</dependency>

knife4j

是为Java MVC框架集成Swagger生成Api文档的增强解决方案

https://gitee.com/xiaoym/knife4j

demo代码

https://gitee.com/xiaoym/swagger-bootstrap-ui-demo

japidocs

无需额外注解的 SpringBoot API文档生成工具

https://japidocs.agilestudio.cn/

docgen

可以将Postman导出的json集合转化为HTML/Markdown文档

https://github.com/thedevsaddam/docgen

要查看邮递员收藏的实时HTML文档,请使用docgen server -f input-postman-collection.json -p 8000它将打开邮递员收藏的html版本到定义的端口
要查看邮递员收藏的实时Markown文档,请使用docgen server -f input-postman-collection.json -p 8000 -m它将邮递员收藏的markdown版本打开到定义的端口
要使用HTML文档 docgen build -i input-postman-collection.json -o ~/Downloads/index.html
制作Markdown文档 docgen build -i input-postman-collection.json -o ~/Downloads/index.md -m

oracle接口文档编辑器

https://app.apiary.io/apitest261/editor

XAMPP本地环境搭建笔记

XAMPP下载地址

https://www.apachefriends.org/zh_cn/download.html

更多版本:https://sourceforge.net/projects/xampp/files/

开启虚拟主机

1.编辑文件 C:\xampp\apache\conf\httpd.conf

去掉httpd-vhosts.conf的注释

# Virtual hosts
Include conf/extra/httpd-vhosts.conf

把Directory修改成如下

<Directory />
	Order Deny,Allow
	AllowOverride all
	Allow from all
</Directory>

2.编辑文件 C:\xampp\apache\conf\extra\httpd-vhosts.conf

<VirtualHost *:80>
	ServerName localhost
	DocumentRoot "C:\xampp\htdocs"
</VirtualHost>
<VirtualHost *:80>
	ServerName cloud.localhost
	DocumentRoot "C:/01_cloud/"
	<Directory "C:/01_cloud/">
		Options Indexes FollowSymLinks
		AllowOverride all
		Order Allow,Deny
		Allow from 127.0.0.1
		Allow from ::1
	</Directory>
</VirtualHost>

3.修改 C:\Windows\System32\drivers\etc\hosts

添加

127.0.0.1 cloud.localhost

安装插件

Redis https://pecl.php.net/package/redis

MongoDB https://pecl.php.net/package/mongodb

(旧)Mongo https://pecl.php.net/package/mongo

PHP 5.6 需要TS版本 https://pecl.php.net/package/redis/2.2.7/windows

PHP 7.4 需要TS版本 https://pecl.php.net/package/redis/5.3.2/windows

把php_redis.dll文件放入ext目录,然后去php.ini开启插件

注意:Win10下,需要TS线程安全版本,才能生效!!!

将 Apache http 设置为服务

C:\xampp\apache\bin\httpd.exe -k install

如果需要安装多个apache,可以在后面加上 -n 指定名称:

C:\xampp7.4.12\apache\bin\httpd.exe -k install -n Apache2.4_PHP7.4.12

将 Apache http 服务删除

C:\xampp\apache\bin\httpd.exe -k uninstall

如果有多个apache,可以在后面加上 -n 指定名称:

C:\xampp7.4.12\apache\bin\httpd.exe -k uninstall -n Apache2.4_PHP7.4.12

将 Apache tomcat 设置为服务

C:\xampp\tomcat\bin\service.bat install

将 Apache tomcat 服务删除

C:\xampp\tomcat\bin\service.bat remove

将 MySQL 设置为服务

C:\xampp\mysql\bin\mysqld.exe install

将 MySQL 服务删除

C:\xampp\mysql\bin\mysqld.exe remove

错误

apache启动失败

首先检查端口是否被占用

如果是绿色版,需要执行目录下 setup_xampp.bat 文件,它会为配置文件赋予相应目录地址,以便寻找到apache目录。

比如 httpd.conf 里 ServerRoot 默认路径可能是错的,导致启动失败。

提示找不到MSVCR110.dll

需要下载VC++ 运行库: https://www.microsoft.com/zh-cn/download/details.aspx?id=30679

JAVA

JAVA

HotSpot https://adoptium.net/releases.html

OpenJ9 https://developer.ibm.com/languages/java/semeru-runtimes/downloads

Java SE Downloads https://www.oracle.com/java/technologies/javase-downloads.html

Oracle Java历史版本下载 https://www.oracle.com/java/technologies/oracle-java-archive-downloads.html

Java升级安装1.8版本

wget https://download.oracle.com/otn/java/jdk/8u271-b09/61ae65e088624f5aaa0b1d2d801acb16/jdk-8u271-linux-x64.tar.gz
tar -zxvf jdk-8u271-linux-x64.tar.gz
mv jdk1.8.0_271 /usr/lib/jvm/

安装:

update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.8.0_271/bin/java 1
update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk1.8.0_271/bin/javac 1

最后的1表示Priority优先级

切换版本:

# update-alternatives --config java
There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                            Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java   1071      auto mode
  1            /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java   1071      manual mode
  2            /usr/lib/jvm/jdk1.8.0_271/bin/java               1         manual mode

# update-alternatives --config javac

出现了这个,由于之前安装的是1.7版本,所以要进行选择,选择1.8版本,故选择2

MAVEN

上传本地jar包到私服

项目中有一个依赖包,包名是util-1.0.jar,想把它传到私服中

mvn deploy:deploy-file -DgroupId=com.bric -DartifactId=util -Dversion=1.0 -Dpackaging=jar -Dfile=util-1.0.jar -Durl=http://192.168.11.200:8081/nexus/content/repositories/thirdparty/ -DrepositoryId=thirdparty

对应settings.xml

<mirrors>    
	<mirror>
        <id>thirdparty</id>
        <name>thirdparty</name>
        <mirrorOf>*</mirrorOf>
        <url>http://192.168.11.200:8081/nexus/content/repositories/thirdparty/</url>
    </mirror>
</mirrors>
<servers>
    <server>
        <id>thirdparty</id>
        <username>admin</username>
        <password>123456</password>
    </server>
</servers>

注意:不要往public仓库传,该仓库没上传权限的。

maven deploy 400错误

这是由于,maven 不允许重复向release仓库中deploy相同的文件, 解决办法,修改release版本

或者修改仓库权限: Repositories -> 3rd party -> configuration -> Deployment Policy -> 改成 Allow Redeploy

手动上传到私服

Nexus Repository Manager OSS 2 版本中: Repositories -> 3rd party -> artifact upload -> 选择pom和jar文件 -> 点upload artifact

SQL

Mybatis-plus常用API全套教程 https://www.toutiao.com/i6869621037831717387 BAK

后台管理系统

Timo (SpringBoot、JPA 、Thymeleaf) http://www.linln.cn/

SmartAdmin 一套互联网企业级的通用型中后台解决方案 https://gitee.com/lab1024/smart-admin

BaseAdmin 一套简单通用的后台管理系统 https://github.com/huanzi-qch/base-admin

litemall 小商场系统 https://gitee.com/linlinjava/litemall

mall4j 开源的电商系统 https://gitee.com/gz-yami/mall4j

web-flash (SpringBoot、JPA、Vue.js) https://gitee.com/enilu/web-flash

SPTools (SpringBoot、JPA、Shiro) https://gitee.com/52itstyle/SPTools

community 开源论坛、问答系统 https://github.com/codedrinker/community