分布式锁的集中实现方式

原文链接:分布式锁的几种实现方式


背景介绍

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的 CAP 理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java 中其实提供了很多并发处理相关的 API,但是这些 API 在分布式场景中就无能为力了。也就是说单纯的 Java Api 并不能提供分布式锁的能力。所以针对分布式锁的实现目前有多种方案。

常见解决方法

  • 基于数据库实现分布式锁
  • 基于缓存(redis,memcached,tair)
  • 实现分布式锁 基于 Zookeeper 实现分布式锁

需要的分布式锁应该是怎么样的?

这里以方法锁为例,资源锁同理

  • 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
  • 这把锁要是一把可重入锁(避免死锁)
  • 这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)
  • 有高可用的获取锁和释放锁功能
  • 获取锁和释放锁的性能要好

字符串的展开题目

题目描述

给定一个字符串,字符串包含数字、大小写字母以及括号(包括大括号,中括号,小括号),括号可以嵌套,即括号里面可以出现数字和括号。

按照如下规则对字符串进行展开,不需要考虑括号不成对的问题,不考虑数字后面没有括号的情况,即 2a2(b)不考虑。

  • 数字表示括号里的字符串重复的次数,展开后的字符串不包含括号
  • 将字符串进行逆序展开
1
2
3
输入abc2{de3[fg]}

输出gfgfgfedgfgfgfedcba

解法

利用栈进行计算,每次判断此时是否是右括号,如果是的话,拿到对应的左括号之前的所有字符,在拿到对应左括号的数字,对字符进行重复以后,全部入栈。

如果不是右括号,那么直接入栈。

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
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String line = scanner.nextLine();
int len = line.length();
LinkedList<String> stack1 = new LinkedList<>();

//输入abc2{de3[fg]}
//输出gfgfgfedgfgfgfedcba
for (int i = 0; i < len; i++) {
String temp = line.charAt(i) + "";
if (")]}".contains(temp)) {

//得到上一个(括号之前的字母
StringBuilder dup = new StringBuilder();
while (!"({[".contains(stack1.getFirst())) {
dup.insert(0,stack1.removeFirst());
}
stack1.removeFirst();

//注意此时的坑,因为数字可能是多位数,因此需要得到所有的数字
StringBuilder numStr = new StringBuilder();
while (!stack1.isEmpty() && stack1.getFirst().matches("[0-9]")) {
numStr.insert(0, stack1.removeFirst());
}
int num = Integer.parseInt(numStr.toString());

//按照(前的数字进行重复,并且再次压入栈
StringBuilder str = new StringBuilder();
while (num > 0) {
str.append(dup);
--num;
}
for (int k = 0; k < str.length(); k++) {
stack1.addFirst(str.charAt(k) + "");
}
} else {
stack1.addFirst(temp);
}
}
StringBuilder result = new StringBuilder();
while (!stack1.isEmpty()) {
result.append(stack1.removeFirst());
}
System.out.println(result.toString());
}
}

待办事项日历的设计、实验文档

界面设计

如图所示。在日历的右侧设置了事项面板,并且根据按照全天和自定义的方式分别设置了两个框图,在下方是显示事件的栏目。

日历UI
左侧显示的是日历,当成功添加一个事件,对应的日期变为黄色。当删除一个事件的时候,变回原来的颜色。

点击左侧日期时,按钮变为灰色,并且全天事项变为当天。如果当天有事项,就在下方显示出对应的事项。

代办事项功能设计

待办事项是一个独立的功能,因此可以重写一个 CalendarItem 类,DateChooser 类,ItemUtil 类来控制这个功能。

CalendarItem 类

该类继承自 CalendarDate 类,表示日历中会用到的事项,并且提供了几个属性 :Date start,Date end 分别表示开始的日期和结束的日期;属性 text 保存相关的待办事项;

方法名 方法描述
CalendarItem(Date start, Date end, String text) 参数为代办事项的开始时间以及结束时间,输入的Date对象为null,或者text为null,那么就抛出异常(CalendarException)

ItemUtil 类

该类是工具类,提供事项和日期相关的一些计算和判断方法。该构造函数已经被声明为 private,因此不可以实例化。属性 list属于 static,得到保存的事项列表。

方法名 方法描述
checkInTime(Date start, Date end, CalendarItem item) 参数为查询的日期,以及事项,返回值为boolean值。当事项的时间与输入的时间段有交叉部分时2,返回true。注意:当输入的对象全部为null,或者开始时间大于结束时间的时候,返回false。
hasItem(Date start) 参数为一天的年月日,返返回值为boolean值。判断这一天是否有项目,这个是用于更新面板时的动作。如果这一天有事项存在,那么返回true。
注意:当输入的对象为null时,返回false。
getItems(DateChooser chooser) 参数为DateChooser面板类型的对象,返回值为List。输入需要查询的面板类型,从中可以得到面板上的时间,从而得到在该时间段所有的事列表。
注意:当输入的chooser对象为null,返回null。
formmat(Date date) 参数为日期对象,返回值为Date对象。主要作用是将这个日期设置为当天的23:59分。
注意:当传入的参数为null时,返回null

DateChooser 类

继承自 JPanl,这是一个定制化的面板,定制化 JSpinner 对象,用于选择日期,还有查询按钮和添加事件的按钮。因为全天设置查询和自定义的设置查询不同,因此日期选择的行为也不同,也就是选择器上显示的日期不同。

方法名 方法描述
DateChooser(int select) 输入为int值,1或者2,分别代表是全天查询还是任意查询。
setText(Date startDate, Date endDate) 参数为开始时间和结束时间,用于设置时间选择器上的日期显示,没有返回值。如果输入的时间为null,或者不符合逻辑,那么将日期重新设置为今天。

实验报告

设计思路

待办事项是一个独立的功能,因此可以重写一个 CalendarItem 类,DateChooser 类,ItemUtil 类来控制这个功能。

CalendarItem 类是一个保存有关事项的类,里面主要保存事件的开始时间和结束时间,以及内容。每次成功创立一个事件之后,都 new 一个 CalendarItem 对象,并且将它保存在一个 Static 的 list 列表中。

ItemUtil 类是一个工具类,构造函数已经被 private 修饰,因此不能实例化。里面封装了和事项有关的各种工具。这个类主要被 DateChooser 类调用。

DateChooser 类继承自 JPanel,这是一个定制化的面板,设置了两个 JSpinner 的对象,用于选择日期,还有查询按钮和添加事件的按钮。因为全天设置查询和自定义的设置查询不同,因此日期选择的行为也不同。

事项的面板和日历的属于不同的个体,在上面进行操作,两者的联系便在于开始日期和结束日期。因为在日历面板的上方可以得到当前年月,因此二者可以顺利的匹配。

设计亮点

此次设计主要有以下亮点:

  • 面板设置人性化,较为合理。在日历的右侧设置了事项面板,并且根据按照全天和自定义的方式分别设置了两个框图,在下方是显示事件的栏目,一目了然。
  • 用户交互十分友好。按照逻辑,显然开始时间必须小于等于结束时间,因此动态监听了时间选择器的内容改变事件。当结束时间小于开始的时候,就自动将结束时间调整为开始时间,极大地避免了用户因为失误而造成的逻辑混乱。
  • 时间选择器的设置。此次学习到采用定制的JSpinner 组件十分的方便快捷。如下所示,windows 的内部也是采用这个模型,显然十分的简捷和方便,易于编辑并且选择。对于用户的错误输入,他可以自行处理为正确的,比如超过合理的输入,它可以自动进行时分的运算,转换为对应的第二天;或者选择没有反应。
//获得时间日期模型
SpinnerDateModel model = new SpinnerDateModel();
//获得JSPinner对象
JSpinner year = new JSpinner(model);
year.setValue(new Date());

设计遗憾

  • 由于对 swing 组件的错误认知,导致界面上有许多可以继续改进的地方。比如颜色形状等。
  • 查询事件的时候,在下方的框内显示事项不怎么友好。因为时间原因,并且对于 swing 的 Box 布局不熟,多个事项往往会溢出整个 frame;并且当添加的事项内容过多的时候,只能查看到一部分。这些都是需要改进的地方。

所遇问题及解决方法

  1. 如何处理全天和自定义日期之间的关系?
`因为按照全天和自定义的日期进行操作,在本质上相同,都是有着开始和结束时间。因此可以将两者归为一类进行事件的处理。将全天的开始时间设为00:00,结束设为23:59,就可以和自定义的用同一种事件处理机制。`
  1. 如何设置时间选择器?
`想了很多种方法,最简单的就是直接设置两个TexxtField框,让用户进行文本的输入,再次进行判断。可是这样对于用户很不友好,因为需要输入的文本过长,因此可能导致错误的输入。因此采用**JSpinner的组件**,用户既可以输入,也可以点击两旁的按钮进行选择;并且可以处理用户的错误输入,比如超过合理的输入,它可以自动进行时分的运算,转换为对应的第二天;或者不反应。`
  1. 点击删除按钮的时候,如何正确删除事件?
`因为需要将删除按钮和事件进行绑定,因此需要得到delete按钮的属性。e.getSource得到的是一个event,我一开始无论如何都无法得到事件源的属性。最终终于得到一个方法,就是将event强制转化为JButton类型,才解决问题。`

Test 类的修改说明

  • 新的测试改变了工具类 ItemUtil 类的测试用例,因为重新定义了用户输入日期的组件,并且组件可以自动判断自身的合法性,因此无需再重新鉴定输入的合法。

广告系统

前期准备

技术栈:

  • java 1.8
  • mysql 8.0
  • springcloud:Eureka(用于服务注册和服务发现,微服务中服务治理的功能);Zuul(服务网关,用于统一应用入口,实现请求分发);Feign(声明式的服务调用)
  • Kafka 2.1.0:分布式消息队列,高性能,横向扩展,部署简单
    • 安装和使用,术语解释,原生 API

不着急编码,搞清楚广告思想
主动思考
直接将之应用于自己的项目中

广告系统概览和准备工作

实现了什么样的功能

  • 广告主的广告投放

    • 推广计划,推广单元
  • 媒体方的广告曝光:能展示广告的媒介

    • CPM:千次收费
    • CPT:按时间段收费
    • CPC:点击类的竞价广告

一个完整的广告系统包含哪些子系统

  • 广告投放系统:最前端(对应广告主)

  • 广告检索系统:最核心

  • 曝光检测系统

    • 本身的曝光
    • 广告主不信任,请第三方来做检测
  • 扣费系统:实时监 测广告的曝光,来对费用进行管理

  • 报表系统

如何扩展

  • 更多的纬度(获取广告的 feature 条件):地域,兴趣
  • 用户画像:对用户本身的兴趣点,根据兴趣点进行投放
  • AI:智能检索引擎

广告系统的架构

  • 全量索引:首先读取的静态类型的索引数据
  • 增量索引:运行过程中,对广告数据的改变

广告系统骨架开发

Maven

多模块聚合

  • 父模块使用 models 进行聚合
  • 父模块统一管理依赖包:使用 dependencymanagement 标签,进行管理版本号
  • 子模块需要自 pom 中声明父模块,使用 parent

秒杀系统特点

  • 读多写少:缓存
  • 高并发:缓存,限流(令牌桶,漏斗 ),负载均衡(集群),异步,队列
  • 资源冲突:
    原子操作(不会被其他线程中断)(分布式集群的 jvm 锁不可以解决集群环境)
    异步

原子操作:共用

  • 数据库锁:乐观锁,悲观锁
  • 分布式锁:redis,zk
  • 其他原子操作:redis desc

集群:数据一致性,tomcat 集群后连的同一套数据库

基本架构

带宽

应用层(常见产品:浏览器/APP)

浏览器缓存,本地缓存,按钮控制,图形验证码

(为了限制不必要的流量)

网络层(网络路由)

CDN(超大并发秒杀很有必要)

负载层(nginx)

nginx,负载均衡,动静分离,反响代理,限流

服务层(Java 应用)

动态页面静态化(用作缓存),应用缓存,分布式缓存,异步,队列,限流(代码中),原子操作保障(分布式锁,redis 集群)

数据库层(mysql)

原子操作保障(乐观锁,悲观锁)

解决方式

计算秒杀的人群和规模,确定一个最终的技术选型以及服务器容量。

用户终端

  • 缓存(静态流量:CSS)
  • 图形验证码(垃圾流量:防止机器人刷单)
  • 按钮(多余流量)

CDN

部署静态资源

nginx

  • 动静分离,静态资源可以做缓存
  • 限流(依赖漏斗)
    new Thread(new Request()).start;
    countDownLatch.countDown();
限流算法

令牌桶:按照固定的速度放令牌,填满则丢弃令牌,拿到令牌的人可以进入系统

漏斗:按照固定的速度漏水

conf:limit_req_zone,可以针对远程 ip 地址进行限流

容量设置为 8,可以成功 9 个,因为第一个不阻塞,直接透过去了。

  • 负载均衡:进入下一个

分布式限流

令牌桶:大小一般根据商品数量来限制的,可以略为大于秒杀数量):进入我们的 tomcat 服务器

tomcat 集群

tomcat

如果没有分布式限流,那么有

  • 限流:java 的令牌桶限流算法
  • 队列:只有 10 个,其余抛出异常
  • 队列 MQ:可以在 tomcat 之前加一层 MQ,然后 MQ 通知数据库一个一个扣。

数据库

乐观锁,悲观锁直接扣库存

或者 tomcat redis 直接扣

乐观锁

线程 A

select version,counts from goods where id=#id#:0,100
update goods set count=count-1,version=version+1 where id=#id# and version=#version#
commit

线程 B

select version,counts from goods where id=#id#:0,100
update goods set count=count-1,version=version+1 where id=#id# and version=#version#
commit

悲观锁

select * from goods id=#id# for update

高并发下悲观锁效率更高,因为乐观锁一直失败,一直 for 自旋重试,知道更新成功为止

读多写少:乐观锁
高并发:悲观锁,效率高

LeetCode回溯法经典题目

求 1+2+3+…+n

求 1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。

采用了&&来进行流程控制

1
2
3
4
5
public int Sum_Solution(int n) {
int ans = n;
boolean flag=(ans!=0) && ((ans += Sum_Solution(n - 1))!=0);//注意java中不能强转boolean
return ans;
}

不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

原理是两数相加,可以转化为位运算的异或,进位是与运算。

1
2
3
4
5
6
7
8
public int Add(int num1,int num2) {
while(num2!=0){
int temp=num1^num2;
num2=(num1&num2)<<1;
num1=temp;
}
return num1;
}

二进制中 1 的个数

输入一个整数,输出该数二进制表示中 1 的个数。其中负数用补码表示

注意:关键点是消除原来 n 中的 1,然后判断是否还有 1

1
2
3
4
5
6
7
8
public int NumberOf1(int n) {
int count=0;
while(n!=0){
n=n&(n-1);
count++;
}
return count;
}

数组中重复的数字

在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为 7 的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字 2。

空间复杂度 O(n)

可以用一个 boolean 数组来进行标记,如果已经被用过了,那么就重复了。

1
2
3
4
5
6
7
8
9
10
11
12
public boolean duplicate(int numbers[],int length,int [] duplication) {
boolean[] flag=new boolean[length];
for(int i=0;i<length;i++){
if(flag[numbers[i]]){
duplication[0]=numbers[i];
return true;
}
flag[numbers[i]]=true;
}
return false;

}

空间复杂度 O(1)

在原来的数组上进行标记,注意临界条件

1
2
3
4
5
6
7
8
9
10
11
12
public boolean duplicate(int numbers[],int length,int [] duplication) {
for(int i=0;i<length;i++){
int index=numbers[i];
if(index<0) index+=length;
if(numbers[index]<0){
duplication[0]=index;
return true;
}
numbers[index]=numbers[index]-length;
}
return false;
}

Restful学习

RESTful 学习

Restful 是什么

本质:一种软件架构风格

核心:面向资源

解决问题:降低复杂性,提高系统的可伸缩性

设计概念和准则:

  • 网络上的所有事物都可以被抽象为资源
  • 每一个资源都有唯一的资源标识,对资源的操作不会改变这些标识。删除资源,标识还在,资源不在了
  • 所有的操作都是无状态的

什么是资源?
网络上的任何东西

为什么要使用 Restful

Restful 中 HTTP 协议介绍

restful 与 http 无关,但是常用基于 http 协议的一种实现。

HTTP 协议-URL

应用层协议,简捷快速

schema://host[:port]/path [?query-string][#anchor]
  • schema:底层使用的协议,如 http,https,ftp
  • host:端口或者域名
  • port:http 默认 80,https 默认 443
  • path:资源路径
  • query-string:发送给 http 服务器的数据
  • anchor:锚。??

HTTP-请求

请求行、消息报头、正文

  • GET:请求资源
  • POST:在标识的资源后附加新的数据
  • HEAD:请求资源的响应消息报头。比如资源的创建时间和最后修改时间
  • PUT:更新资源
  • DELETE:请求删除标识资源
  • OPTIONS:查询服务器的性能,或者与资源相关的选项和需求。比如说可以获得的哪些服务

HTTP-响应

状态行、消息报头、响应正文

响应状态码

  • 200,OK,成功返回用户请求数据
  • 201 CREATED,新建或修改数据成功
  • 204 NO CONTENT,删除数据成功
  • 400,Bad Request,语法错误
  • 401,Unauthorized,表示用户没有认证,无法进行当前操作
  • 403 Forbidden,表示用户访问是被禁止的
  • 404,资源不存在
  • 422,创建一个对象时,发生一个验证错误。比如用户需要提供用户名和密码,但前端只提供了用户名,这个时候就返回 422
  • 500,服务器内部错误,用户无法判断发出的请求是否成功
  • 503,服务器当前不能处理请求,比如性能到瓶颈了

RESRful 和其他的区别

  • SOAP WebService
    也是一种基于 http,采用 XML 格式进行封装,增加了特定的 http 消息头。更安全

restful 最大程度的利用了 http

如何使用 RESTful API

RESTful API 设计要素

  • 资源路径(URI)
  • HTTP 动词
  • 过滤信息
  • 状态码
  • 错误处理
  • 返回结果

资源路径

  • 用名词而不是动词,用复数

例子:
有一个 API 体哦给你 zoo 的信息,还包括各种动物和员工信息

    https://api.example.com/v1/zoos
    https://api.example.com/v1/animals

如上,v1 代表 uri 的版本号。两种方式,在 url 中加入版本,或者在 http 请求头。

HTTP 动词

  • POST /zoos: 新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息
  • DELETE /zoos/ID:删除某个动物园

过滤信息

用户自己设置的,需要什么信息,多少信息

错误处理

如果状态码是 4XX 或者 5XX,就应该返回出错信息。

用键值对返回。

1
2
3
{
"error":"参数错误"
}

返回结果

  • GET /collections:返回资源对象的列表(数组)
  • GET /collections/identity:返回单个资源对象(没有就 404)
  • POST /collections:返回新生成的资源对象
  • PUT /collections/identity:返回完整的资源对象
  • PATCH /collections/identity:返回被修改的属性
  • DELETE /collections/identity:返回一个空文档(204)

确认设计要素

  • 用户登录、注册

  • 文章发表、编辑、管理、列表

  • 用户注册:
    email 为空
    用户名为空
    密码为空

    检测 email 重复
    然后写入数据库,password 加密
    注册失败
    返回用户信息:user_id,user_name,email,figure。删除密码字段

  • 用户登录:
    email 为空
    password 为空
    返回用户信息:user_id,user_name,email,figure。删除密码字段

错误码定义在一个文件里面

Maven学习

Maven 学习

介绍

清理,编译,测试,运行,打包,集成测试,验证,部署整个周期

目录结构

  • groupId:(公司反写网址.项目名)【主项目的标识,实际的项目】
  • artiFactId:(项目名-模块名)【maven 的项目和我们的实际的项目不一样,maven 是模块化的,一个实际的项目被划分为多个模块。一个模块的标识】
  • version:【当前版本号,一般三个数字组成。第一个 0 表示大版本号,第二个 0 表示分支版本号,第三个 0 表示小版本号】
    snapshot 快照
    alpha 内部测试
    beta 公测
    Release 稳定
    GA 正式发布
  • packaging:【maven 项目的打包方式,默认是 jar,还可以其他的 war,zip,pom 等等】

maven 常用的构建命令

mvn -v
mvn compile:编译
mvn test:测试
mvn package:打包

mvn clean :删除 target
mvn install:安装 jar 包到本地仓库,一般是将自己 compile 的 jar 包加入到本地仓库中,也就是如果一个项目用到另一个项目中的文件,我们可以这样使用

自动创建目录骨架

mvn archetype:generate -DgroupId=top.wuxiya.maven04 -DartifactId=maven04-demo -Dversion=1.0.0SNAPSHOT -Dpackage=top.wuxiya.maven04-demo

坐标和仓库

坐标是地址,用于找到对应的 jar 包,也就是 dependence

包括:groupId,artiFactId,version

仓库:本地和远程

maven 声明周期

每个证明周期是相互独立的,每个周期的内部有一定的依赖和顺序,运行之后的命令,自动执行之前的依赖

clean 清理项目 分为三个阶段:

pre-clean 执行清理前的工作
clean 清理上一次构建生成的所有文件
post-clean 执行清理后的文件

default 构建项目

compile test package install

site 生成项目站点,根据 pom

pre-site 之前的工作
site 生成项目的站点文档
post-site 生成之后的工作
site-deploy 发布生成的站点到服务器上

对于 maven 而言,所有的命令都是调用插件来使用的

pom.xml 常用元素介绍

  • :根元素,包含了一些约束信息

  • :指定了 pom 的版本,固定的 4.0.0

  • 坐标信息:

  • 项目坐标信息:

  • :项目的描述名,一般在产生项目文档的时候使用

  • :项目的地址

  • :项目的描述

  • :开发人员的列表

  • :开源框架的许可证信息

  • :组织信息

  • :依赖列表 >
    依赖包的坐标信息:

    :依赖的范围,如 test,表示只在测试的范围有用;:true/false,用来设置依赖是否可选,默认是 false;
    :排除依赖传递列表 > :A->B->C,那么 C 对于 A 来说就是传递依赖

  • :依赖的管理 > > (并不会运行,而是用于子模块的继承)

  • > 插件信息 > > 坐标信息

  • :用于在子模块中对于父模块的继承

  • :用于聚合多个 maven 项目,如果我们有很多个 maven 模块,那么可以一次性编译,而不是一个一个编译

依赖的范围

重点来看 >
如果要使用某一个框架,需要将框架加入到 classPath 中,这样就可以使用该框架。

maven 中有三种 classpath,分别是 编译,测试,运行
junit 只存在于 test 的范围。

由此可见范围是用于管理依赖包的使用范围。

  • compile:默认,三种都有效
  • provided:测试和编译,如发布的时候容器里面已经有 API 了
  • runtime:测试和运行,JDBC 的应用
  • test:junit 的应用
  • system:
  • import:只在 management 中使用

依赖的传递

A->B->C

如果 A 引入 B 的 jar 包,那么会默认导入 C 包,这个时候如果不需要 C 包,我们在 > B 中,加入:排除依赖传递列表 > :C 的坐标

依赖冲突

如果 A 依赖了同一个构件的不同版本,那么会产生冲突

  • 短路优先:优先解析路径段的版本
    A->B->C->X(jar)
    A->D->X(jar)
    那么优先解析短的路径
  • 路径相同,优先解析先声明的版本

聚合和继承

聚合

对于不同的 maven 项目,我们需要相互调用的时候必须一个一个 install 到本地的仓库中才可以调用。

我们可以将不同的 maven 项目聚合一次性运行。

新建一个 maven 项目,packaging 用 pom,用:用于聚合多个 maven 项目,如果我们有很多个 maven 模块,那么可以一次性编译,而不是一个一个编译。

继承
新建一个 maven 项目,packaging 用 pom,删除 main 和 test,依赖放在 manegement 中。

在子项目中,中引入父亲

Spring Boot学习

基础知识

Spring Boot 特点

  • 是 Spring MVC 的升级版,没有必然的联系
  • 简化配置,编码,部署,监控
  • 是下一代框架
  • 入门级微框架,学习 SpringCloud 必须学会 SpringBoot,SpringCloud 建立在 boot 的基础之上
  • 学习 maven,Spring 的注解,restful API

属性配置

两种配置方式,properties 和 yml。
yml 树状结构,对中文支持更好。

Server

  • port:默认 8080
  • context-path:给路径加上前缀

自定义单项配置

在配置文件中写:

    name: sherlock

在 java 文件中用:声明 String 或者 Integer 就可以自动转

    @Value("${name}")

在配置文件中使用配置文件:

    content:“name:${name},age${age}”

多项配置

1
2
3
person:
name: sherlock
age: 18

新建一个类,用@ConfigurationProperties(prefix=”person”)获取前缀是 person 的配置。注意@Component 成为 Bean。

创建私有属性 name ,age ,get 和 set 等

此时在使用的时候就不用@Value(“${name}”)了,直接@Autowired,将新建的类自动导入即可。

生产和上线不同的环境

新建两个 yml 文件:application-dev.yml 和 application-prod.yml。

在 application.yml 中配置:dev 或者 prod

    spring.profiles.active:dev

注意:在用命令行启动 jar 文件的时候,我们可以指定生产环境如下:

    java -jar target/*.jar --spring.profiles.active=prod

注意在 application.yml 中进行的配置,两个环境都可以使用

Controller 的使用

映射 URL

用来接收用户端的请求

  • @Controller:处理 http 请求。返回页面,配合模板来使用
  • @RestController:是@Controller+@ResponseBody。返回 json。【推荐使用】
  • @RequestMapping:配置 url 映射

使用方式:

  • 可以给整个类指定 url,用@RequestMapping 再给方法指定 url
  • url 可以是 json 模式{“/“,”/“}
  • get 可以从 url 进行测试,测试 post 的时候,可以用 postman 来进行尝试

获取参数

  • @PathVariable
  • @RequestParam

@PathVariable
整个 url 非常的简洁

1
2
3
4
5
value="/{id}/path"

func(@PathVariable("id") Integer id){

}

@RequestParam

1
2
3
4
5
6
7
8
9
value="/path"

func(@RequestParam("id") Integer myId){

}
//设置默认值
func(@RequestParam(value="id",required=false,defaultValue="0") Integer myId){

}

数据库操作

Spring-Data-Jpa
JPA(Java Persistence API):定义了一系列对象持久化的标准。

  • jpa.hibernate.ddl-auto: create(运行的时候创建一张新的表)
    update (第一次运行的时候创建一张表,如果有数据会保留),常用
    :create-drop(应用停下来了,会删掉表)
    :validate(验证数据库中和你的表是否一致)
  • jpa.show-sql: true

1
2
3
4
5
6
@Entity(这个是将这个类映射为数据库中的一张表)

@Id
@GeneratedValue
private Integer id;

  1. 创建表的实体,如上的 @Entity
  2. 创建实体的 reposity
  3. 创建 service,内嵌 reposity,对数据库的接口,进行再次包装
  4. 创建 controller,构建 url,解析参数

Restful 接口

  • get
  • post:新加入
  • put:更新,传入 id
  • delete:删除

事务管理

希望两条数据同时插入,要不然都不成功

  • @Transactional:在 Service 中的方法上面加入@Transactional,事务注解,表示这个方法内的多条数据,或者一条数据如果失败,那么就回退。只有查询的时候不需要加入事务操作。

  • @Test
    @Transactional
    连在一起使用,可以达到回滚的目的??也就是只用于测试,不真正的向数据库中插入

web 进阶

还需要学习

  • @Valid 表单验证
  • AOP 处理请求
  • AOP 统一异常处理
  • 单元测试

待了解

  • spring-data-jpa:关系型数据库
  • JdbcTemplate
  • Mybatis
  • redis+MongoDB:nosql 数据库
  • 用缓存应对高并发

@Valid 表单验证

@Valid 和@BindingResult 一大利器

AOP 统一处理请求日志

AOP 是一种程序的设计思想

POP:面向过程
OOP:面向对象
AOP:面向切面

将通用逻辑从业务逻辑中分出来

  • 增加 aop 的依赖
  • 增加处理文件 aspect

面向切面编程,增加 url 的切点,在之前和之后进行处理验证

@Slf4j:类注解,处理日志

1
2
3
4
5
6
7
8
9
10
11
12
@Pointcut("execution(public * edu.fudan.advancedweb.web3d.controller.UserController.findById(..))")
public void log(){

}
@Before("log()")
public void doBefore(){
System.out.println("1111");
}
@After("log()")
public void doAfter(){
System.out.println("2222");
}
  • @AfterReturning(returning = “object”, pointcut = “log()”):返回之后的对象

统一异常处理

将返回的信息进行包装,成为:code,msg,data

Spring 只对 RuntimeException,

当我们需要修改返回的 error 的 httpstatus 时,注意,有时候会出现自定义的错误,但是 status 为 200,只需要在一行的捕获下面,加上@ResponseStatus(HttpStatus.***)可。

项目优化

ab 压测模拟并发

用 Apache 进行压测

  • ab -n 100 -c 100 url(-n 表示 100 个请求,-c 表示 100 个并发。相当于 100 个人同时进行请求)

  • -t 60。表示 60s 内不停地模拟

  • 使用 synchronized:解决了问题,降低了性能。数据安全是第一位。

  • 无法做得到细粒度的控制

  • 只适合单点的情况

redis 分布式锁

  • setnx:set if not exist,不成立的时候来建立锁

  • getset:先 get,再次 set。返回原来的值,设置新的值。

  • 支持高可用,分布式集群

  • 更细粒度的锁

  • 多台机器上多个进程对一个数据进行操作的互斥

  • 他可以做分布式锁一个重要原因是它是单线程的

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
public boolean lock(String key, String value) {

//已经加锁成功了
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}

String currentValue = redisTemplate.opsForValue().get(key);

//如果过期。这段代码就是CAS,乐观锁的实现。解决的是两个线程同时进入
//的情况。
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);

//获取上一个锁的时间
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}

}

return false;
}

public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("【redis 分布式锁】解锁异常");
}

}

Redis 缓存

  • 命中
  • 失效:时间到了
  • 更新

进行缓存需要实现序列化

  • @Cacheable(cacheName=”userList”,key=”123”);
  • @CachePut(cacheName=”userList”,key=”123”);
  • @CacheEvict(cacheName=”userList”,key=”123”);
  • 类上面的@CacheConfig(cacheName=”userList”);
    取消下面的 cacheName

@Cacheable 中

  • key 可以动态,key=”#sellerId”

  • condition=”#sellerId > 30”

  • unless=”#result.getCode()!=0”(当返回 code 为 0 的时候,进行缓存)

  • 建表用 sql,不用 JPA 建表

  • 慎用@OneToMany 等,表的关系根据逻辑控制

mybatis:

  • 注解
  • xml